GEOS  3.3.4
BinaryOp.h
1 /**********************************************************************
2  * $Id: BinaryOp.h 3606 2012-04-10 15:02:23Z strk $
3  *
4  * GEOS - Geometry Engine Open Source
5  * http://geos.refractions.net
6  *
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/geom/Geometry.h>
53 #include <geos/geom/PrecisionModel.h>
54 #include <geos/precision/CommonBitsRemover.h>
55 #include <geos/precision/SimpleGeometryPrecisionReducer.h>
56 
57 #include <geos/operation/overlay/snap/GeometrySnapper.h>
58 
59 #include <geos/simplify/TopologyPreservingSimplifier.h>
60 #include <geos/operation/valid/IsValidOp.h>
61 #include <geos/operation/valid/TopologyValidationError.h>
62 #include <geos/util/TopologyException.h>
63 #include <geos/util.h>
64 
65 #include <memory> // for auto_ptr
66 
67 //#define GEOS_DEBUG_BINARYOP 1
68 
69 #ifdef GEOS_DEBUG_BINARYOP
70 # include <iostream>
71 # include <iomanip>
72 # include <sstream>
73 #endif
74 
75 
76 /*
77  * Always try original input first
78  */
79 #ifndef USE_ORIGINAL_INPUT
80 # define USE_ORIGINAL_INPUT 1
81 #endif
82 
83 /*
84  * Define this to use PrecisionReduction policy
85  * in an attempt at by-passing binary operation
86  * robustness problems (handles TopologyExceptions)
87  */
88 #ifndef USE_PRECISION_REDUCTION_POLICY
89 //# define USE_PRECISION_REDUCTION_POLICY 1
90 #endif
91 
92 /*
93  * Define this to use TopologyPreserving simplification policy
94  * in an attempt at by-passing binary operation
95  * robustness problems (handles TopologyExceptions)
96  */
97 #ifndef USE_TP_SIMPLIFY_POLICY
98 //# define USE_TP_SIMPLIFY_POLICY 1
99 #endif
100 
101 /*
102  * Use common bits removal policy.
103  * If enabled, this would be tried /before/
104  * Geometry snapping.
105  */
106 #ifndef USE_COMMONBITS_POLICY
107 # define USE_COMMONBITS_POLICY 1
108 #endif
109 
110 /*
111  * Check validity of operation performed
112  * by common bits removal policy.
113  *
114  * This matches what EnhancedPrecisionOp does in JTS
115  * and fixes 5 tests of invalid outputs in our testsuite
116  * (stmlf-cases-20061020-invalid-output.xml)
117  * and breaks 1 test (robustness-invalid-output.xml) so much
118  * to prevent a result.
119  *
120  */
121 #define GEOS_CHECK_COMMONBITS_VALIDITY 1
122 
123 /*
124  * Use snapping policy
125  */
126 #ifndef USE_SNAPPING_POLICY
127 # define USE_SNAPPING_POLICY 1
128 #endif
129 
130 namespace geos {
131 namespace geom { // geos::geom
132 
133 inline bool
134 check_valid(const Geometry& g, const std::string& label)
135 {
136  operation::valid::IsValidOp ivo(&g);
137  if ( ! ivo.isValid() )
138  {
139 #ifdef GEOS_DEBUG_BINARYOP
140  using operation::valid::TopologyValidationError;
141  TopologyValidationError* err = ivo.getValidationError();
142  std::cerr << label << " is INVALID: "
143  << err->toString()
144  << " (" << std::setprecision(20)
145  << err->getCoordinate() << ")"
146  << std::endl;
147 #else
148  (void)label;
149 #endif
150  return false;
151  }
152  return true;
153 }
154 
155 /* A single component may become a multi component */
156 inline std::auto_ptr<Geometry>
157 fix_self_intersections(std::auto_ptr<Geometry> g, const std::string& label)
158 {
159  // TODO: check for presence of self-intersections first ?
160  return g->Union();
161 }
162 
163 
169 template <class BinOp>
170 std::auto_ptr<Geometry>
171 SnapOp(const Geometry* g0, const Geometry *g1, BinOp _Op)
172 {
173  typedef std::auto_ptr<Geometry> GeomPtr;
174 
175 #define CBR_BEFORE_SNAPPING 1
176 
177  //using geos::precision::GeometrySnapper;
179 
180  // Snap tolerance must be computed on the original
181  // (not commonbits-removed) geoms
182  double snapTolerance = GeometrySnapper::computeOverlaySnapTolerance(*g0, *g1);
183 #if GEOS_DEBUG_BINARYOP
184  std::cerr<< std::setprecision(20) << "Computed snap tolerance: "<<snapTolerance<<std::endl;
185 #endif
186 
187 
188 #if CBR_BEFORE_SNAPPING
189  // Compute common bits
191  cbr.add(g0); cbr.add(g1);
192 #if GEOS_DEBUG_BINARYOP
193  std::cerr<<"Computed common bits: "<<cbr.getCommonCoordinate()<<std::endl;
194 #endif
195 
196  // Now remove common bits
197  GeomPtr rG0( cbr.removeCommonBits(g0->clone()) );
198  GeomPtr rG1( cbr.removeCommonBits(g1->clone()) );
199 
200 #if GEOS_DEBUG_BINARYOP
201  check_valid(*rG0, "CBR: removed-bits geom 0");
202  check_valid(*rG1, "CBR: removed-bits geom 1");
203 #endif
204 
205  const Geometry& operand0 = *rG0;
206  const Geometry& operand1 = *rG1;
207 #else // don't CBR before snapping
208  const Geometry& operand0 = *g0;
209  const Geometry& operand1 = *g1;
210 #endif
211 
212 
213  GeometrySnapper snapper0( operand0 );
214  GeomPtr snapG0( snapper0.snapTo(operand1, snapTolerance) );
215  snapG0 = fix_self_intersections(snapG0, "SNAP: snapped geom 0");
216 
217  // NOTE: second geom is snapped on the snapped first one
218  GeometrySnapper snapper1( operand1 );
219  GeomPtr snapG1( snapper1.snapTo(*snapG0, snapTolerance) );
220  snapG1 = fix_self_intersections(snapG1, "SNAP: snapped geom 1");
221 
222  // Run the binary op
223  GeomPtr result( _Op(snapG0.get(), snapG1.get()) );
224 
225 #if GEOS_DEBUG_BINARYOP
226  check_valid(*result, "SNAP: result (before common-bits addition");
227 #endif
228 
229 #if CBR_BEFORE_SNAPPING
230  // Add common bits back in
231  cbr.addCommonBits( result.get() );
232  result = fix_self_intersections(result, "SNAP: result (after common-bits addition)");
233 #endif
234 
235 #if GEOS_DEBUG_BINARYOP
236  check_valid(*result, "SNAP: result (after common-bits addition");
237 #endif
238 
239  return result;
240 }
241 
242 template <class BinOp>
243 std::auto_ptr<Geometry>
244 BinaryOp(const Geometry* g0, const Geometry *g1, BinOp _Op)
245 {
246  typedef std::auto_ptr<Geometry> GeomPtr;
247 
248  GeomPtr ret;
249  geos::util::TopologyException origException;
250 
251 #ifdef USE_ORIGINAL_INPUT
252  // Try with original input
253  try
254  {
255 #if GEOS_DEBUG_BINARYOP
256  std::cerr << "Trying with original input." << std::endl;
257 #endif
258  ret.reset(_Op(g0, g1));
259  return ret;
260  }
261  catch (const geos::util::TopologyException& ex)
262  {
263  origException=ex;
264 #if GEOS_DEBUG_BINARYOP
265  std::cerr << "Original exception: " << ex.what() << std::endl;
266 #endif
267  }
268 
269 //#if GEOS_DEBUG_BINARYOP
270  // Should we just give up here ?
271  check_valid(*g0, "Input geom 0");
272  check_valid(*g1, "Input geom 1");
273 //#endif
274 
275 #endif // USE_ORIGINAL_INPUT
276 
277 
278 #ifdef USE_COMMONBITS_POLICY
279  // Try removing common bits (possibly obsoleted by snapping below)
280  //
281  // NOTE: this policy was _later_ implemented
282  // in JTS as EnhancedPrecisionOp
283  // TODO: consider using the now-ported EnhancedPrecisionOp
284  // here too
285  //
286  try
287  {
288  GeomPtr rG0;
289  GeomPtr rG1;
290  precision::CommonBitsRemover cbr;
291 
292 #if GEOS_DEBUG_BINARYOP
293  std::cerr << "Trying with Common Bits Remover (CBR)" << std::endl;
294 #endif
295 
296  cbr.add(g0);
297  cbr.add(g1);
298 
299  rG0.reset( cbr.removeCommonBits(g0->clone()) );
300  rG1.reset( cbr.removeCommonBits(g1->clone()) );
301 
302 #if GEOS_DEBUG_BINARYOP
303  check_valid(*rG0, "CBR: geom 0 (after common-bits removal)");
304  check_valid(*rG1, "CBR: geom 1 (after common-bits removal)");
305 #endif
306 
307  ret.reset( _Op(rG0.get(), rG1.get()) );
308 
309 #if GEOS_DEBUG_BINARYOP
310  check_valid(*ret, "CBR: result (before common-bits addition)");
311 #endif
312 
313  cbr.addCommonBits( ret.get() );
314 
315 #if GEOS_DEBUG_BINARYOP
316  check_valid(*ret, "CBR: result (after common-bits addition)");
317 #endif
318 
319  // Common-bits removal policy could introduce self-intersections
320  ret = fix_self_intersections(ret, "CBR: result (after common-bits addition)");
321 
322 #if GEOS_DEBUG_BINARYOP
323  check_valid(*ret, "CBR: result (after common-bits addition and fix_self_intersections)");
324 #endif
325 
326 #if GEOS_CHECK_COMMONBITS_VALIDITY
327  // check that result is a valid geometry after the
328  // reshift to orginal precision (see EnhancedPrecisionOp)
329  using operation::valid::IsValidOp;
330  using operation::valid::TopologyValidationError;
331  IsValidOp ivo(ret.get());
332  if ( ! ivo.isValid() )
333  {
334  TopologyValidationError* e = ivo.getValidationError();
336  "Result of overlay became invalid "
337  "after re-addin common bits of operand "
338  "coordinates: " + e->toString(),
339  e->getCoordinate());
340  }
341 #endif // GEOS_CHECK_COMMONBITS_VALIDITY
342 
343  return ret;
344  }
345  catch (const geos::util::TopologyException& ex)
346  {
347  ::geos::ignore_unused_variable_warning(ex);
348 #if GEOS_DEBUG_BINARYOP
349  std::cerr << "CBR: " << ex.what() << std::endl;
350 #endif
351  }
352 #endif
353 
354  // Try with snapping
355  //
356  // TODO: possible optimization would be reusing the
357  // already common-bit-removed inputs and just
358  // apply geometry snapping, whereas the current
359  // SnapOp function does both.
360 // {
361 #if USE_SNAPPING_POLICY
362 
363 #if GEOS_DEBUG_BINARYOP
364  std::cerr << "Trying with snapping " << std::endl;
365 #endif
366 
367  try {
368  ret = SnapOp(g0, g1, _Op);
369 #if GEOS_DEBUG_BINARYOP
370  std::cerr << "SnapOp succeeded" << std::endl;
371 #endif
372  return ret;
373 
374  }
375  catch (const geos::util::TopologyException& ex)
376  {
377  ::geos::ignore_unused_variable_warning(ex);
378 #if GEOS_DEBUG_BINARYOP
379  std::cerr << "SNAP: " << ex.what() << std::endl;
380 #endif
381  }
382 
383 #endif // USE_SNAPPING_POLICY }
384 
385 
386 
387 // {
388 #if USE_PRECISION_REDUCTION_POLICY
389 
390 
391  // Try reducing precision
392  try
393  {
394  int maxPrecision=25;
395 
396  for (int precision=maxPrecision; precision; --precision)
397  {
398  std::auto_ptr<PrecisionModel> pm(new PrecisionModel(precision));
399 #if GEOS_DEBUG_BINARYOP
400  std::cerr << "Trying with precision " << precision << std::endl;
401 #endif
402 
403  precision::SimpleGeometryPrecisionReducer reducer( pm.get() );
404  GeomPtr rG0( reducer.reduce(g0) );
405  GeomPtr rG1( reducer.reduce(g1) );
406 
407  try
408  {
409  ret.reset( _Op(rG0.get(), rG1.get()) );
410  return ret;
411  }
412  catch (const geos::util::TopologyException& ex)
413  {
414  if ( precision == 1 ) throw ex;
415 #if GEOS_DEBUG_BINARYOP
416  std::cerr << "Reduced with precision (" << precision << "): "
417  << ex.what() << std::endl;
418 #endif
419  }
420 
421  }
422 
423  }
424  catch (const geos::util::TopologyException& ex)
425  {
426 #if GEOS_DEBUG_BINARYOP
427  std::cerr << "Reduced: " << ex.what() << std::endl;
428 #endif
429  }
430 
431 #endif
432 // USE_PRECISION_REDUCTION_POLICY }
433 
434 // {
435 #if USE_TP_SIMPLIFY_POLICY
436 
437  // Try simplifying
438  try
439  {
440 
441  double maxTolerance = 0.04;
442  double minTolerance = 0.01;
443  double tolStep = 0.01;
444 
445  for (double tol = minTolerance; tol <= maxTolerance; tol += tolStep)
446  {
447 #if GEOS_DEBUG_BINARYOP
448  std::cerr << "Trying simplifying with tolerance " << tol << std::endl;
449 #endif
450 
451  GeomPtr rG0( simplify::TopologyPreservingSimplifier::simplify(g0, tol) );
452  GeomPtr rG1( simplify::TopologyPreservingSimplifier::simplify(g1, tol) );
453 
454  try
455  {
456  ret.reset( _Op(rG0.get(), rG1.get()) );
457  return ret;
458  }
459  catch (const geos::util::TopologyException& ex)
460  {
461  if ( tol >= maxTolerance ) throw ex;
462 #if GEOS_DEBUG_BINARYOP
463  std::cerr << "Simplified with tolerance (" << tol << "): "
464  << ex.what() << std::endl;
465 #endif
466  }
467 
468  }
469 
470  return ret;
471 
472  }
473  catch (const geos::util::TopologyException& ex)
474  {
475 #if GEOS_DEBUG_BINARYOP
476  std::cerr << "Simplified: " << ex.what() << std::endl;
477 #endif
478  }
479 
480 #endif
481 // USE_TP_SIMPLIFY_POLICY }
482 
483  throw origException;
484 }
485 
486 
487 } // namespace geos::geom
488 } // namespace geos
489 
490 #endif // GEOS_GEOM_BINARYOP_H