/* * Copyright (c) 2016 Vivid Solutions. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package org.locationtech.jts.operation.buffer.validate; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; /** * Validates that the result of a buffer operation * is geometrically correct, within a computed tolerance. * <p> * This is a heuristic test, and may return false positive results * (I.e. it may fail to detect an invalid result.) * It should never return a false negative result, however * (I.e. it should never report a valid result as invalid.) * <p> * This test may be (much) more expensive than the original * buffer computation. * * @author Martin Davis */ public class BufferResultValidator { private static boolean VERBOSE = false; /** * Maximum allowable fraction of buffer distance the * actual distance can differ by. * 1% sometimes causes an error - 1.2% should be safe. */ private static final double MAX_ENV_DIFF_FRAC = .012; public static boolean isValid(Geometry g, double distance, Geometry result) { BufferResultValidator validator = new BufferResultValidator(g, distance, result); if (validator.isValid()) return true; return false; } /** * Checks whether the geometry buffer is valid, * and returns an error message if not. * * @param g * @param distance * @param result * @return an appropriate error message * or null if the buffer is valid */ public static String isValidMsg(Geometry g, double distance, Geometry result) { BufferResultValidator validator = new BufferResultValidator(g, distance, result); if (! validator.isValid()) return validator.getErrorMessage(); return null; } private Geometry input; private double distance; private Geometry result; private boolean isValid = true; private String errorMsg = null; private Coordinate errorLocation = null; private Geometry errorIndicator = null; public BufferResultValidator(Geometry input, double distance, Geometry result) { this.input = input; this.distance = distance; this.result = result; } public boolean isValid() { checkPolygonal(); if (! isValid) return isValid; checkExpectedEmpty(); if (! isValid) return isValid; checkEnvelope(); if (! isValid) return isValid; checkArea(); if (! isValid) return isValid; checkDistance(); return isValid; } public String getErrorMessage() { return errorMsg; } public Coordinate getErrorLocation() { return errorLocation; } /** * Gets a geometry which indicates the location and nature of a validation failure. * <p> * If the failure is due to the buffer curve being too far or too close * to the input, the indicator is a line segment showing the location and size * of the discrepancy. * * @return a geometric error indicator * or null if no error was found */ public Geometry getErrorIndicator() { return errorIndicator; } private void report(String checkName) { if (! VERBOSE) return; System.out.println("Check " + checkName + ": " + (isValid ? "passed" : "FAILED")); } private void checkPolygonal() { if (! (result instanceof Polygon || result instanceof MultiPolygon)) isValid = false; errorMsg = "Result is not polygonal"; errorIndicator = result; report("Polygonal"); } private void checkExpectedEmpty() { // can't check areal features if (input.getDimension() >= 2) return; // can't check positive distances if (distance > 0.0) return; // at this point can expect an empty result if (! result.isEmpty()) { isValid = false; errorMsg = "Result is non-empty"; errorIndicator = result; } report("ExpectedEmpty"); } private void checkEnvelope() { if (distance < 0.0) return; double padding = distance * MAX_ENV_DIFF_FRAC; if (padding == 0.0) padding = 0.001; Envelope expectedEnv = new Envelope(input.getEnvelopeInternal()); expectedEnv.expandBy(distance); Envelope bufEnv = new Envelope(result.getEnvelopeInternal()); bufEnv.expandBy(padding); if (! bufEnv.contains(expectedEnv)) { isValid = false; errorMsg = "Buffer envelope is incorrect"; errorIndicator = input.getFactory().toGeometry(bufEnv); } report("Envelope"); } private void checkArea() { double inputArea = input.getArea(); double resultArea = result.getArea(); if (distance > 0.0 && inputArea > resultArea) { isValid = false; errorMsg = "Area of positive buffer is smaller than input"; errorIndicator = result; } if (distance < 0.0 && inputArea < resultArea) { isValid = false; errorMsg = "Area of negative buffer is larger than input"; errorIndicator = result; } report("Area"); } private void checkDistance() { BufferDistanceValidator distValid = new BufferDistanceValidator(input, distance, result); if (! distValid.isValid()) { isValid = false; errorMsg = distValid.getErrorMessage(); errorLocation = distValid.getErrorLocation(); errorIndicator = distValid.getErrorIndicator(); } report("Distance"); } }