Class OverlayResultValidator

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12 package org.locationtech.jts.operation.overlay.validate;
 13  
 14 import java.util.ArrayList;
 15 import java.util.List;
 16  
 17 import org.locationtech.jts.geom.Coordinate;
 18 import org.locationtech.jts.geom.Geometry;
 19 import org.locationtech.jts.geom.Location;
 20 import org.locationtech.jts.operation.overlay.OverlayOp;
 21 import org.locationtech.jts.operation.overlay.snap.GeometrySnapper;
 22  
 23 /**
 24  * Validates that the result of an overlay operation is
 25  * geometrically correct, within a determined tolerance.
 26  * Uses fuzzy point location to find points which are 
 27  * definitely in either the interior or exterior of the result
 28  * geometry, and compares these results with the expected ones.
 29  * <p>
 30  * This algorithm is only useful where the inputs are polygonal.
 31  * This is a heuristic test, and may return false positive results
 32  * (I.e. it may fail to detect an invalid result.)
 33  * It should never return a false negative result, however
 34  * (I.e. it should never report a valid result as invalid.)
 35  *
 36  * @author Martin Davis
 37  * @version 1.7
 38  * @see OverlayOp
 39  */
 40 public class OverlayResultValidator
 41 {
 42   public static boolean isValid(Geometry a, Geometry b, int overlayOp, Geometry result)
 43   {
 44     OverlayResultValidator validator = new OverlayResultValidator(a, b, result);
 45     return validator.isValid(overlayOp);
 46   }
 47  
 48   private static double computeBoundaryDistanceTolerance(Geometry g0, Geometry g1)
 49   {
 50       return Math.min(GeometrySnapper.computeSizeBasedSnapTolerance(g0),
 51               GeometrySnapper.computeSizeBasedSnapTolerance(g1));
 52   }
 53   
 54   private static final double TOLERANCE = 0.000001;
 55  
 56   private Geometry[] geom;
 57   private FuzzyPointLocator[] locFinder;
 58   private int[] location = new int[3] ;
 59   private Coordinate invalidLocation = null;
 60   private double boundaryDistanceTolerance = TOLERANCE;
 61  
 62   private List testCoords = new ArrayList();
 63  
 64   public OverlayResultValidator(Geometry a, Geometry b, Geometry result) 
 65   {
 66       /**
 67        * The tolerance to use needs to depend on the size of the geometries.
 68        * It should not be more precise than double-precision can support. 
 69        */
 70     boundaryDistanceTolerance = computeBoundaryDistanceTolerance(a, b);
 71     geom = new Geometry[] { a, b, result };
 72     locFinder = new FuzzyPointLocator[] {
 73       new FuzzyPointLocator(geom[0], boundaryDistanceTolerance),
 74       new FuzzyPointLocator(geom[1], boundaryDistanceTolerance),
 75       new FuzzyPointLocator(geom[2], boundaryDistanceTolerance)
 76       };
 77   }
 78  
 79   public boolean isValid(int overlayOp)
 80   {
 81     addTestPts(geom[0]);
 82     addTestPts(geom[1]);
 83     boolean isValid = checkValid(overlayOp);
 84  
 85     /*
 86     System.out.println("OverlayResultValidator: " + isValid);
 87     System.out.println("G0");
 88     System.out.println(geom[0]);
 89     System.out.println("G1");
 90     System.out.println(geom[1]);
 91     System.out.println("Result");
 92     System.out.println(geom[2]);
 93     */
 94     
 95     return isValid;
 96   }
 97  
 98   public Coordinate getInvalidLocation() { return invalidLocation; }
 99  
100   private void addTestPts(Geometry g)
101   {
102     OffsetPointGenerator ptGen = new OffsetPointGenerator(g);
103     testCoords.addAll(ptGen.getPoints(5 * boundaryDistanceTolerance));
104   }
105  
106   private boolean checkValid(int overlayOp)
107   {
108     for (int i = 0; i < testCoords.size(); i++) {
109       Coordinate pt = (Coordinate) testCoords.get(i);
110       if (! checkValid(overlayOp, pt)) {
111         invalidLocation = pt;
112         return false;
113       }
114     }
115     return true;
116   }
117  
118   private boolean checkValid(int overlayOp, Coordinate pt)
119   {
120     location[0] = locFinder[0].getLocation(pt);
121     location[1] = locFinder[1].getLocation(pt);
122     location[2] = locFinder[2].getLocation(pt);
123  
124     /**
125      * If any location is on the Boundary, can't deduce anything, so just return true
126      */
127     if (hasLocation(location, Location.BOUNDARY))
128       return true;
129  
130     return isValidResult(overlayOp, location);
131   }
132  
133   private static boolean hasLocation(int[] location, int loc)
134   {
135     for (int i = 0; i < 3; i ++) {
136       if (location[i] == loc)
137         return true;
138     }
139     return false;
140   }
141  
142   private boolean isValidResult(int overlayOp, int[] location)
143   {
144     boolean expectedInterior = OverlayOp.isResultOfOp(location[0], location[1], overlayOp);
145  
146     boolean resultInInterior = (location[2] == Location.INTERIOR);
147     // MD use simpler: boolean isValid = (expectedInterior == resultInInterior);
148     boolean isValid = ! (expectedInterior ^ resultInInterior);
149     
150     if (! isValid) reportResult(overlayOp, location, expectedInterior);
151     
152     return isValid;
153  }
154  
155   private void reportResult(int overlayOp, int[] location, boolean expectedInterior)
156   {
157       System.out.println(
158               "Overlay result invalid - A:" + Location.toLocationSymbol(location[0])
159               + " B:" + Location.toLocationSymbol(location[1])
160               + " expected:" + (expectedInterior ? 'i' : 'e')
161               + " actual:" + Location.toLocationSymbol(location[2])
162               );
163   }
164 }
165  
166