Class BufferDistanceValidator

  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.buffer.validate;
 13  
 14 import java.util.ArrayList;
 15 import java.util.Iterator;
 16 import java.util.List;
 17  
 18 import org.locationtech.jts.algorithm.distance.DiscreteHausdorffDistance;
 19 import org.locationtech.jts.geom.Coordinate;
 20 import org.locationtech.jts.geom.Geometry;
 21 import org.locationtech.jts.geom.GeometryCollection;
 22 import org.locationtech.jts.geom.MultiPolygon;
 23 import org.locationtech.jts.geom.Polygon;
 24 import org.locationtech.jts.geom.util.LinearComponentExtracter;
 25 import org.locationtech.jts.geom.util.PolygonExtracter;
 26 import org.locationtech.jts.io.WKTWriter;
 27 import org.locationtech.jts.operation.distance.DistanceOp;
 28  
 29 /**
 30  * Validates that a given buffer curve lies an appropriate distance
 31  * from the input generating it. 
 32  * Useful only for round buffers (cap and join).
 33  * Can be used for either positive or negative distances.
 34  * <p>
 35  * This is a heuristic test, and may return false positive results
 36  * (I.e. it may fail to detect an invalid result.)
 37  * It should never return a false negative result, however
 38  * (I.e. it should never report a valid result as invalid.)
 39  * 
 40  * @author mbdavis
 41  *
 42  */
 43 public class BufferDistanceValidator 
 44 {
 45   private static boolean VERBOSE = false;
 46     /**
 47      * Maximum allowable fraction of buffer distance the 
 48      * actual distance can differ by.
 49      * 1% sometimes causes an error - 1.2% should be safe.
 50      */
 51     private static final double MAX_DISTANCE_DIFF_FRAC = .012;
 52  
 53   private Geometry input;
 54   private double bufDistance;
 55   private Geometry result;
 56   
 57   private double minValidDistance;
 58   private double maxValidDistance;
 59   
 60   private double minDistanceFound;
 61   private double maxDistanceFound;
 62   
 63   private boolean isValid = true;
 64   private String errMsg = null;
 65   private Coordinate errorLocation = null;
 66   private Geometry errorIndicator = null;
 67   
 68   public BufferDistanceValidator(Geometry input, double bufDistance, Geometry result)
 69   {
 70       this.input = input;
 71       this.bufDistance = bufDistance;
 72       this.result = result;
 73   }
 74   
 75   public boolean isValid()
 76   {
 77       double posDistance = Math.abs(bufDistance);
 78       double distDelta = MAX_DISTANCE_DIFF_FRAC * posDistance;
 79       minValidDistance = posDistance - distDelta;
 80       maxValidDistance = posDistance + distDelta;
 81       
 82       // can't use this test if either is empty
 83       if (input.isEmpty() || result.isEmpty())
 84           return true;
 85       
 86       if (bufDistance > 0.0) {
 87           checkPositiveValid();
 88       }
 89       else {
 90           checkNegativeValid();
 91       }
 92     if (VERBOSE) {
 93       System.out.println("Min Dist= " + minDistanceFound + "  err= " 
 94         + (1.0 - minDistanceFound / bufDistance) 
 95         + "  Max Dist= " + maxDistanceFound + "  err= " 
 96         + (maxDistanceFound / bufDistance - 1.0)
 97         );
 98     }
 99       return isValid;
100   }
101   
102   public String getErrorMessage()
103   { 
104       return errMsg;
105   }
106   
107   public Coordinate getErrorLocation()
108   {
109     return errorLocation;
110   }
111   
112   /**
113    * Gets a geometry which indicates the location and nature of a validation failure.
114    * <p>
115    * The indicator is a line segment showing the location and size
116    * of the distance discrepancy.
117    * 
118    * @return a geometric error indicator
119    * or null if no error was found
120    */
121   public Geometry getErrorIndicator()
122   {
123     return errorIndicator;
124   }
125   
126   private void checkPositiveValid()
127   {
128       Geometry bufCurve = result.getBoundary();
129       checkMinimumDistance(input, bufCurve, minValidDistance);
130       if (! isValid) return;
131       
132       checkMaximumDistance(input, bufCurve, maxValidDistance);
133   }
134   
135   private void checkNegativeValid()
136   {
137       // Assert: only polygonal inputs can be checked for negative buffers
138       
139       // MD - could generalize this to handle GCs too
140       if (! (input instanceof Polygon 
141               || input instanceof MultiPolygon
142               || input instanceof GeometryCollection
143               )) {
144           return;
145       }
146       Geometry inputCurve = getPolygonLines(input);
147       checkMinimumDistance(inputCurve, result, minValidDistance);
148       if (! isValid) return;
149       
150       checkMaximumDistance(inputCurve, result, maxValidDistance);
151   }
152   
153   private Geometry getPolygonLines(Geometry g)
154   {
155       List lines = new ArrayList();
156       LinearComponentExtracter lineExtracter = new LinearComponentExtracter(lines);
157       List polys = PolygonExtracter.getPolygons(g);
158       for (Iterator i = polys.iterator(); i.hasNext(); ) {
159           Polygon poly = (Polygon) i.next();
160           poly.apply(lineExtracter);
161       }
162       return g.getFactory().buildGeometry(lines);
163   }
164   
165   /**
166    * Checks that two geometries are at least a minimum distance apart.
167    * 
168    * @param g1 a geometry
169    * @param g2 a geometry
170    * @param minDist the minimum distance the geometries should be separated by
171    */
172   private void checkMinimumDistance(Geometry g1, Geometry g2, double minDist)
173   {
174       DistanceOp distOp = new DistanceOp(g1, g2, minDist);
175       minDistanceFound = distOp.distance();
176     
177     
178       if (minDistanceFound < minDist) {
179           isValid = false;
180           Coordinate[] pts = distOp.nearestPoints();
181           errorLocation = distOp.nearestPoints()[1];
182           errorIndicator = g1.getFactory().createLineString(pts);
183           errMsg = "Distance between buffer curve and input is too small "
184               + "(" + minDistanceFound
185               + " at " + WKTWriter.toLineString(pts[0], pts[1]) +" )";
186       }
187   }
188   
189   /**
190    * Checks that the furthest distance from the buffer curve to the input
191    * is less than the given maximum distance.
192    * This uses the Oriented Hausdorff distance metric.
193    * It corresponds to finding
194    * the point on the buffer curve which is furthest from <i>some</i> point on the input.
195    * 
196    * @param input a geometry
197    * @param bufCurve a geometry
198    * @param maxDist the maximum distance that a buffer result can be from the input
199    */
200   private void checkMaximumDistance(Geometry input, Geometry bufCurve, double maxDist)
201   {
202 //    BufferCurveMaximumDistanceFinder maxDistFinder = new BufferCurveMaximumDistanceFinder(input);
203 //    maxDistanceFound = maxDistFinder.findDistance(bufCurve);
204     
205     DiscreteHausdorffDistance haus = new DiscreteHausdorffDistance(bufCurve, input);
206     haus.setDensifyFraction(0.25);
207     maxDistanceFound = haus.orientedDistance();
208     
209     if (maxDistanceFound > maxDist) {
210       isValid = false;
211       Coordinate[] pts = haus.getCoordinates();
212       errorLocation = pts[1];
213       errorIndicator = input.getFactory().createLineString(pts);
214       errMsg = "Distance between buffer curve and input is too large "
215         + "(" + maxDistanceFound
216         + " at " + WKTWriter.toLineString(pts[0], pts[1]) +")";
217     }
218   }
219   
220   /*
221   private void OLDcheckMaximumDistance(Geometry input, Geometry bufCurve, double maxDist)
222   {
223     BufferCurveMaximumDistanceFinder maxDistFinder = new BufferCurveMaximumDistanceFinder(input);
224     maxDistanceFound = maxDistFinder.findDistance(bufCurve);
225     
226     
227     if (maxDistanceFound > maxDist) {
228       isValid = false;
229       PointPairDistance ptPairDist = maxDistFinder.getDistancePoints();
230       errorLocation = ptPairDist.getCoordinate(1);
231       errMsg = "Distance between buffer curve and input is too large "
232         + "(" + ptPairDist.getDistance()
233         + " at " + ptPairDist.toString() +")";
234     }
235   }
236   */
237   
238   
239 }
240