GEOS 3.4.2

- Main Page
- Related Pages
- Namespaces
- Classes
- Files

- File List

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