GEOS  3.3.8
BinaryOp.h
1 /**********************************************************************
2  * $Id: BinaryOp.h 3750 2013-01-17 12:04:00Z strk $
3  *
4  * GEOS - Geometry Engine Open Source
5  * http://geos.refractions.net
6  *
7  * Copyright (C) 2013 Sandro Santilli <strk@keybit.net>
8  * Copyright (C) 2006 Refractions Research Inc.
9  *
10  * This is free software; you can redistribute and/or modify it under
11  * the terms of the GNU Lesser General Public Licence as published
12  * by the Free Software Foundation.
13  * See the COPYING file for more information.
14  *
15  **********************************************************************
16  *
17  * Last port: ORIGINAL WORK
18  *
19  **********************************************************************
20  *
21  * This file provides a single templated function, taking two
22  * const Geometry pointers, applying a binary operator to them
23  * and returning a result Geometry in an auto_ptr<>.
24  *
25  * The binary operator is expected to take two const Geometry pointers
26  * and return a newly allocated Geometry pointer, possibly throwing
27  * a TopologyException to signal it couldn't succeed due to robustness
28  * issues.
29  *
30  * This function will catch TopologyExceptions and try again with
31  * slightly modified versions of the input. The following heuristic
32  * is used:
33  *
34  * - Try with original input.
35  * - Try removing common bits from input coordinate values
36  * - Try snaping input geometries to each other
37  * - Try snaping input coordinates to a increasing grid (size from 1/25 to 1)
38  * - Try simplifiying input with increasing tolerance (from 0.01 to 0.04)
39  *
40  * If none of the step succeeds the original exception is thrown.
41  *
42  * Note that you can skip Grid snapping, Geometry snapping and Simplify policies
43  * by a compile-time define when building geos.
44  * See USE_TP_SIMPLIFY_POLICY, USE_PRECISION_REDUCTION_POLICY and
45  * USE_SNAPPING_POLICY macros below.
46  *
47  *
48  **********************************************************************/
49 
50 #ifndef GEOS_GEOM_BINARYOP_H
51 #define GEOS_GEOM_BINARYOP_H
52 
53 #include <geos/geom/Geometry.h>
54 #include <geos/geom/GeometryCollection.h>
55 #include <geos/geom/Polygon.h>
56 #include <geos/geom/PrecisionModel.h>
57 #include <geos/precision/CommonBitsRemover.h>
58 #include <geos/precision/SimpleGeometryPrecisionReducer.h>
59 
60 #include <geos/operation/overlay/snap/GeometrySnapper.h>
61 
62 #include <geos/simplify/TopologyPreservingSimplifier.h>
63 #include <geos/operation/valid/IsValidOp.h>
64 #include <geos/operation/valid/TopologyValidationError.h>
65 #include <geos/util/TopologyException.h>
66 #include <geos/util.h>
67 
68 #include <memory> // for auto_ptr
69 
70 //#define GEOS_DEBUG_BINARYOP 1
71 
72 #ifdef GEOS_DEBUG_BINARYOP
73 # include <iostream>
74 # include <iomanip>
75 # include <sstream>
76 #endif
77 
78 
79 /*
80  * Always try original input first
81  */
82 #ifndef USE_ORIGINAL_INPUT
83 # define USE_ORIGINAL_INPUT 1
84 #endif
85 
86 /*
87  * Define this to use PrecisionReduction policy
88  * in an attempt at by-passing binary operation
89  * robustness problems (handles TopologyExceptions)
90  */
91 #ifndef USE_PRECISION_REDUCTION_POLICY
92 //# define USE_PRECISION_REDUCTION_POLICY 1
93 #endif
94 
95 /*
96  * Define this to use TopologyPreserving simplification policy
97  * in an attempt at by-passing binary operation
98  * robustness problems (handles TopologyExceptions)
99  */
100 #ifndef USE_TP_SIMPLIFY_POLICY
101 //# define USE_TP_SIMPLIFY_POLICY 1
102 #endif
103 
104 /*
105  * Use common bits removal policy.
106  * If enabled, this would be tried /before/
107  * Geometry snapping.
108  */
109 #ifndef USE_COMMONBITS_POLICY
110 # define USE_COMMONBITS_POLICY 1
111 #endif
112 
113 /*
114  * Check validity of operation performed
115  * by common bits removal policy.
116  *
117  * This matches what EnhancedPrecisionOp does in JTS
118  * and fixes 5 tests of invalid outputs in our testsuite
119  * (stmlf-cases-20061020-invalid-output.xml)
120  * and breaks 1 test (robustness-invalid-output.xml) so much
121  * to prevent a result.
122  *
123  */
124 #define GEOS_CHECK_COMMONBITS_VALIDITY 1
125 
126 /*
127  * Use snapping policy
128  */
129 #ifndef USE_SNAPPING_POLICY
130 # define USE_SNAPPING_POLICY 1
131 #endif
132 
133 namespace geos {
134 namespace geom { // geos::geom
135 
136 inline bool
137 check_valid(const Geometry& g, const std::string& label)
138 {
139  operation::valid::IsValidOp ivo(&g);
140  if ( ! ivo.isValid() )
141  {
142 #ifdef GEOS_DEBUG_BINARYOP
143  using operation::valid::TopologyValidationError;
144  TopologyValidationError* err = ivo.getValidationError();
145  std::cerr << label << " is INVALID: "
146  << err->toString()
147  << " (" << std::setprecision(20)
148  << err->getCoordinate() << ")"
149  << std::endl;
150 #else
151  (void)label;
152 #endif
153  return false;
154  }
155  return true;
156 }
157 
158 /*
159  * Attempt to fix noding of multilines and
160  * self-intersection of multipolygons
161  *
162  * May return the input untouched.
163  */
164 inline std::auto_ptr<Geometry>
165 fix_self_intersections(std::auto_ptr<Geometry> g, const std::string& label)
166 {
167 #ifdef GEOS_DEBUG_BINARYOP
168  std::cerr << label << " fix_self_intersection (UnaryUnion)" << std::endl;
169 #endif
170 
171  // Only multi-components can be fixed by UnaryUnion
172  if ( ! dynamic_cast<const GeometryCollection*>(g.get()) ) return g;
173 
174  using operation::valid::IsValidOp;
175 
176  IsValidOp ivo(g.get());
177 
178  // Polygon is valid, nothing to do
179  if ( ivo.isValid() ) return g;
180 
181  // Not all invalidities can be fixed by this code
182 
183  using operation::valid::TopologyValidationError;
184  TopologyValidationError* err = ivo.getValidationError();
185  switch ( err->getErrorType() ) {
186  case TopologyValidationError::eRingSelfIntersection:
187  case TopologyValidationError::eTooFewPoints: // collapsed lines
188 #ifdef GEOS_DEBUG_BINARYOP
189  std::cerr << label << " ATTEMPT_TO_FIX: " << err->getErrorType() << ": " << *g << std::endl;
190 #endif
191  g = g->Union();
192 #ifdef GEOS_DEBUG_BINARYOP
193  std::cerr << label << " ATTEMPT_TO_FIX succeeded.. " << std::endl;
194 #endif
195  return g;
196  case TopologyValidationError::eSelfIntersection:
197  // this one is within a single component, won't be fixed
198  default:
199 #ifdef GEOS_DEBUG_BINARYOP
200  std::cerr << label << " invalidity is: " << err->getErrorType() << std::endl;
201 #endif
202  return g;
203  }
204 }
205 
206 
212 template <class BinOp>
213 std::auto_ptr<Geometry>
214 SnapOp(const Geometry* g0, const Geometry *g1, BinOp _Op)
215 {
216  typedef std::auto_ptr<Geometry> GeomPtr;
217 
218 #define CBR_BEFORE_SNAPPING 1
219 
220  //using geos::precision::GeometrySnapper;
222 
223  // Snap tolerance must be computed on the original
224  // (not commonbits-removed) geoms
225  double snapTolerance = GeometrySnapper::computeOverlaySnapTolerance(*g0, *g1);
226 #if GEOS_DEBUG_BINARYOP
227  std::cerr<< std::setprecision(20) << "Computed snap tolerance: "<<snapTolerance<<std::endl;
228 #endif
229 
230 
231 #if CBR_BEFORE_SNAPPING
232  // Compute common bits
234  cbr.add(g0); cbr.add(g1);
235 #if GEOS_DEBUG_BINARYOP
236  std::cerr<<"Computed common bits: "<<cbr.getCommonCoordinate()<<std::endl;
237 #endif
238 
239  // Now remove common bits
240  GeomPtr rG0( cbr.removeCommonBits(g0->clone()) );
241  GeomPtr rG1( cbr.removeCommonBits(g1->clone()) );
242 
243 #if GEOS_DEBUG_BINARYOP
244  check_valid(*rG0, "CBR: removed-bits geom 0");
245  check_valid(*rG1, "CBR: removed-bits geom 1");
246 #endif
247 
248  const Geometry& operand0 = *rG0;
249  const Geometry& operand1 = *rG1;
250 #else // don't CBR before snapping
251  const Geometry& operand0 = *g0;
252  const Geometry& operand1 = *g1;
253 #endif
254 
255 
256  GeometrySnapper snapper0( operand0 );
257  GeomPtr snapG0( snapper0.snapTo(operand1, snapTolerance) );
258  snapG0 = fix_self_intersections(snapG0, "SNAP: snapped geom 0");
259 
260  // NOTE: second geom is snapped on the snapped first one
261  GeometrySnapper snapper1( operand1 );
262  GeomPtr snapG1( snapper1.snapTo(*snapG0, snapTolerance) );
263  snapG1 = fix_self_intersections(snapG1, "SNAP: snapped geom 1");
264 
265  // Run the binary op
266  GeomPtr result( _Op(snapG0.get(), snapG1.get()) );
267 
268 #if GEOS_DEBUG_BINARYOP
269  check_valid(*result, "SNAP: result (before common-bits addition");
270 #endif
271 
272 #if CBR_BEFORE_SNAPPING
273  // Add common bits back in
274  cbr.addCommonBits( result.get() );
275  result = fix_self_intersections(result, "SNAP: result (after common-bits addition)");
276 #endif
277 
278 #if GEOS_DEBUG_BINARYOP
279  check_valid(*result, "SNAP: result (after common-bits addition");
280 #endif
281 
282  return result;
283 }
284 
285 template <class BinOp>
286 std::auto_ptr<Geometry>
287 BinaryOp(const Geometry* g0, const Geometry *g1, BinOp _Op)
288 {
289  typedef std::auto_ptr<Geometry> GeomPtr;
290 
291  GeomPtr ret;
292  geos::util::TopologyException origException;
293 
294 #ifdef USE_ORIGINAL_INPUT
295  // Try with original input
296  try
297  {
298 #if GEOS_DEBUG_BINARYOP
299  std::cerr << "Trying with original input." << std::endl;
300 #endif
301  ret.reset(_Op(g0, g1));
302  return ret;
303  }
304  catch (const geos::util::TopologyException& ex)
305  {
306  origException=ex;
307 #if GEOS_DEBUG_BINARYOP
308  std::cerr << "Original exception: " << ex.what() << std::endl;
309 #endif
310  }
311 
312 #if GEOS_DEBUG_BINARYOP
313  // Should we just give up here ?
314  check_valid(*g0, "Input geom 0");
315  check_valid(*g1, "Input geom 1");
316 #endif
317 
318 #endif // USE_ORIGINAL_INPUT
319 
320 
321 #ifdef USE_COMMONBITS_POLICY
322  // Try removing common bits (possibly obsoleted by snapping below)
323  //
324  // NOTE: this policy was _later_ implemented
325  // in JTS as EnhancedPrecisionOp
326  // TODO: consider using the now-ported EnhancedPrecisionOp
327  // here too
328  //
329  try
330  {
331  GeomPtr rG0;
332  GeomPtr rG1;
333  precision::CommonBitsRemover cbr;
334 
335 #if GEOS_DEBUG_BINARYOP
336  std::cerr << "Trying with Common Bits Remover (CBR)" << std::endl;
337 #endif
338 
339  cbr.add(g0);
340  cbr.add(g1);
341 
342  rG0.reset( cbr.removeCommonBits(g0->clone()) );
343  rG1.reset( cbr.removeCommonBits(g1->clone()) );
344 
345 #if GEOS_DEBUG_BINARYOP
346  check_valid(*rG0, "CBR: geom 0 (after common-bits removal)");
347  check_valid(*rG1, "CBR: geom 1 (after common-bits removal)");
348 #endif
349 
350  ret.reset( _Op(rG0.get(), rG1.get()) );
351 
352 #if GEOS_DEBUG_BINARYOP
353  check_valid(*ret, "CBR: result (before common-bits addition)");
354 #endif
355 
356  cbr.addCommonBits( ret.get() );
357 
358 #if GEOS_DEBUG_BINARYOP
359  check_valid(*ret, "CBR: result (after common-bits addition)");
360 #endif
361 
362  // Common-bits removal policy could introduce self-intersections
363  ret = fix_self_intersections(ret, "CBR: result (after common-bits addition)");
364 
365 #if GEOS_DEBUG_BINARYOP
366  check_valid(*ret, "CBR: result (after common-bits addition and fix_self_intersections)");
367 #endif
368 
369 #if GEOS_CHECK_COMMONBITS_VALIDITY
370  // check that result is a valid geometry after the
371  // reshift to orginal precision (see EnhancedPrecisionOp)
372  using operation::valid::IsValidOp;
373  using operation::valid::TopologyValidationError;
374  IsValidOp ivo(ret.get());
375  if ( ! ivo.isValid() )
376  {
377  TopologyValidationError* e = ivo.getValidationError();
379  "Result of overlay became invalid "
380  "after re-addin common bits of operand "
381  "coordinates: " + e->toString(),
382  e->getCoordinate());
383  }
384 #endif // GEOS_CHECK_COMMONBITS_VALIDITY
385 
386  return ret;
387  }
388  catch (const geos::util::TopologyException& ex)
389  {
390  ::geos::ignore_unused_variable_warning(ex);
391 #if GEOS_DEBUG_BINARYOP
392  std::cerr << "CBR: " << ex.what() << std::endl;
393 #endif
394  }
395 #endif
396 
397  // Try with snapping
398  //
399  // TODO: possible optimization would be reusing the
400  // already common-bit-removed inputs and just
401  // apply geometry snapping, whereas the current
402  // SnapOp function does both.
403 // {
404 #if USE_SNAPPING_POLICY
405 
406 #if GEOS_DEBUG_BINARYOP
407  std::cerr << "Trying with snapping " << std::endl;
408 #endif
409 
410  try {
411  ret = SnapOp(g0, g1, _Op);
412 #if GEOS_DEBUG_BINARYOP
413  std::cerr << "SnapOp succeeded" << std::endl;
414 #endif
415  return ret;
416 
417  }
418  catch (const geos::util::TopologyException& ex)
419  {
420  ::geos::ignore_unused_variable_warning(ex);
421 #if GEOS_DEBUG_BINARYOP
422  std::cerr << "SNAP: " << ex.what() << std::endl;
423 #endif
424  }
425 
426 #endif // USE_SNAPPING_POLICY }
427 
428 
429 
430 // {
431 #if USE_PRECISION_REDUCTION_POLICY
432 
433 
434  // Try reducing precision
435  try
436  {
437  int maxPrecision=25;
438 
439  for (int precision=maxPrecision; precision; --precision)
440  {
441  std::auto_ptr<PrecisionModel> pm(new PrecisionModel(precision));
442 #if GEOS_DEBUG_BINARYOP
443  std::cerr << "Trying with precision " << precision << std::endl;
444 #endif
445 
446  precision::SimpleGeometryPrecisionReducer reducer( pm.get() );
447  GeomPtr rG0( reducer.reduce(g0) );
448  GeomPtr rG1( reducer.reduce(g1) );
449 
450  try
451  {
452  ret.reset( _Op(rG0.get(), rG1.get()) );
453  return ret;
454  }
455  catch (const geos::util::TopologyException& ex)
456  {
457  if ( precision == 1 ) throw ex;
458 #if GEOS_DEBUG_BINARYOP
459  std::cerr << "Reduced with precision (" << precision << "): "
460  << ex.what() << std::endl;
461 #endif
462  }
463 
464  }
465 
466  }
467  catch (const geos::util::TopologyException& ex)
468  {
469 #if GEOS_DEBUG_BINARYOP
470  std::cerr << "Reduced: " << ex.what() << std::endl;
471 #endif
472  }
473 
474 #endif
475 // USE_PRECISION_REDUCTION_POLICY }
476 
477 // {
478 #if USE_TP_SIMPLIFY_POLICY
479 
480  // Try simplifying
481  try
482  {
483 
484  double maxTolerance = 0.04;
485  double minTolerance = 0.01;
486  double tolStep = 0.01;
487 
488  for (double tol = minTolerance; tol <= maxTolerance; tol += tolStep)
489  {
490 #if GEOS_DEBUG_BINARYOP
491  std::cerr << "Trying simplifying with tolerance " << tol << std::endl;
492 #endif
493 
494  GeomPtr rG0( simplify::TopologyPreservingSimplifier::simplify(g0, tol) );
495  GeomPtr rG1( simplify::TopologyPreservingSimplifier::simplify(g1, tol) );
496 
497  try
498  {
499  ret.reset( _Op(rG0.get(), rG1.get()) );
500  return ret;
501  }
502  catch (const geos::util::TopologyException& ex)
503  {
504  if ( tol >= maxTolerance ) throw ex;
505 #if GEOS_DEBUG_BINARYOP
506  std::cerr << "Simplified with tolerance (" << tol << "): "
507  << ex.what() << std::endl;
508 #endif
509  }
510 
511  }
512 
513  return ret;
514 
515  }
516  catch (const geos::util::TopologyException& ex)
517  {
518 #if GEOS_DEBUG_BINARYOP
519  std::cerr << "Simplified: " << ex.what() << std::endl;
520 #endif
521  }
522 
523 #endif
524 // USE_TP_SIMPLIFY_POLICY }
525 
526  throw origException;
527 }
528 
529 
530 } // namespace geos::geom
531 } // namespace geos
532 
533 #endif // GEOS_GEOM_BINARYOP_H