Class BufferResultValidator

  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 org.locationtech.jts.geom.Coordinate;
 15 import org.locationtech.jts.geom.Envelope;
 16 import org.locationtech.jts.geom.Geometry;
 17 import org.locationtech.jts.geom.MultiPolygon;
 18 import org.locationtech.jts.geom.Polygon;
 19  
 20 /**
 21  * Validates that the result of a buffer operation
 22  * is geometrically correct, within a computed tolerance.
 23  * <p>
 24  * This is a heuristic test, and may return false positive results
 25  * (I.e. it may fail to detect an invalid result.)
 26  * It should never return a false negative result, however
 27  * (I.e. it should never report a valid result as invalid.)
 28  * <p>
 29  * This test may be (much) more expensive than the original
 30  * buffer computation.
 31  *
 32  * @author Martin Davis
 33  */
 34 public class BufferResultValidator 
 35 {
 36   private static boolean VERBOSE = false;
 37   
 38     /**
 39      * Maximum allowable fraction of buffer distance the 
 40      * actual distance can differ by.
 41      * 1% sometimes causes an error - 1.2% should be safe.
 42      */
 43     private static final double MAX_ENV_DIFF_FRAC = .012;
 44  
 45   public static boolean isValid(Geometry g, double distance, Geometry result)
 46   {
 47       BufferResultValidator validator = new BufferResultValidator(g, distance, result);
 48     if (validator.isValid())
 49         return true;
 50     return false;
 51   }
 52  
 53   /**
 54    * Checks whether the geometry buffer is valid, 
 55    * and returns an error message if not.
 56    * 
 57    * @param g
 58    * @param distance
 59    * @param result
 60    * @return an appropriate error message
 61    * or null if the buffer is valid
 62    */
 63   public static String isValidMsg(Geometry g, double distance, Geometry result)
 64   {
 65       BufferResultValidator validator = new BufferResultValidator(g, distance, result);
 66     if (! validator.isValid())
 67         return validator.getErrorMessage();
 68     return null;
 69   }
 70  
 71   private Geometry input;
 72   private double distance;
 73   private Geometry result;
 74   private boolean isValid = true;
 75   private String errorMsg = null;
 76   private Coordinate errorLocation = null;
 77   private Geometry errorIndicator = null;
 78   
 79   public BufferResultValidator(Geometry input, double distance, Geometry result)
 80   {
 81       this.input = input;
 82       this.distance = distance;
 83       this.result = result;
 84   }
 85   
 86   public boolean isValid()
 87   {
 88       checkPolygonal();
 89       if (! isValid) return isValid;
 90       checkExpectedEmpty();
 91       if (! isValid) return isValid;
 92       checkEnvelope();
 93       if (! isValid) return isValid;
 94       checkArea();
 95       if (! isValid) return isValid;
 96       checkDistance();
 97       return isValid;
 98   }
 99   
100   public String getErrorMessage()
101   {
102       return errorMsg;
103   }
104   
105   public Coordinate getErrorLocation()
106   {
107       return errorLocation;
108   }
109   
110   /**
111    * Gets a geometry which indicates the location and nature of a validation failure.
112    * <p>
113    * If the failure is due to the buffer curve being too far or too close 
114    * to the input, the indicator is a line segment showing the location and size
115    * of the discrepancy.
116    * 
117    * @return a geometric error indicator
118    * or null if no error was found
119    */
120   public Geometry getErrorIndicator()
121   {
122     return errorIndicator;
123   }
124   
125   private void report(String checkName)
126   {
127     if (! VERBOSE) return;
128     System.out.println("Check " + checkName + ": " 
129         + (isValid ? "passed" : "FAILED"));
130   }
131   
132   private void checkPolygonal()
133   {
134       if (! (result instanceof Polygon 
135               || result instanceof MultiPolygon))
136       isValid = false;
137       errorMsg = "Result is not polygonal";
138     errorIndicator = result;
139     report("Polygonal");
140   }
141   
142   private void checkExpectedEmpty()
143   {
144       // can't check areal features
145       if (input.getDimension() >= 2return;
146       // can't check positive distances
147       if (distance > 0.0return;
148           
149       // at this point can expect an empty result
150       if (! result.isEmpty()) {
151           isValid = false;
152           errorMsg = "Result is non-empty";
153       errorIndicator = result;
154       }
155     report("ExpectedEmpty");
156   }
157   
158   private void checkEnvelope()
159   {
160       if (distance < 0.0return;
161       
162       double padding = distance * MAX_ENV_DIFF_FRAC;
163       if (padding == 0.0padding = 0.001;
164  
165       Envelope expectedEnv = new Envelope(input.getEnvelopeInternal());
166       expectedEnv.expandBy(distance);
167       
168       Envelope bufEnv = new Envelope(result.getEnvelopeInternal());
169       bufEnv.expandBy(padding);
170  
171       if (! bufEnv.contains(expectedEnv)) {
172           isValid = false;
173           errorMsg = "Buffer envelope is incorrect";
174           errorIndicator = input.getFactory().toGeometry(bufEnv);
175       }
176     report("Envelope");
177   }
178   
179   private void checkArea()
180   {
181       double inputArea = input.getArea();
182       double resultArea = result.getArea();
183       
184       if (distance > 0.0
185               && inputArea > resultArea) {
186           isValid = false;
187           errorMsg = "Area of positive buffer is smaller than input";
188       errorIndicator = result;
189       }
190       if (distance < 0.0
191               && inputArea < resultArea) {
192           isValid = false;
193           errorMsg = "Area of negative buffer is larger than input";
194           errorIndicator = result;
195       }
196     report("Area");
197   }
198   
199   private void checkDistance()
200   {
201       BufferDistanceValidator distValid = new BufferDistanceValidator(input, distance, result);
202       if (! distValid.isValid()) {
203           isValid = false;
204           errorMsg = distValid.getErrorMessage();
205           errorLocation = distValid.getErrorLocation();
206           errorIndicator = distValid.getErrorIndicator();
207       }
208     report("Distance");
209   }
210 }
211