/* * 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.overlay.validate; import java.util.ArrayList; import java.util.List; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Location; import org.locationtech.jts.operation.overlay.OverlayOp; import org.locationtech.jts.operation.overlay.snap.GeometrySnapper; /** * Validates that the result of an overlay operation is * geometrically correct, within a determined tolerance. * Uses fuzzy point location to find points which are * definitely in either the interior or exterior of the result * geometry, and compares these results with the expected ones. * <p> * This algorithm is only useful where the inputs are polygonal. * 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.) * * @author Martin Davis * @version 1.7 * @see OverlayOp */ public class OverlayResultValidator { public static boolean isValid(Geometry a, Geometry b, int overlayOp, Geometry result) { OverlayResultValidator validator = new OverlayResultValidator(a, b, result); return validator.isValid(overlayOp); } private static double computeBoundaryDistanceTolerance(Geometry g0, Geometry g1) { return Math.min(GeometrySnapper.computeSizeBasedSnapTolerance(g0), GeometrySnapper.computeSizeBasedSnapTolerance(g1)); } private static final double TOLERANCE = 0.000001; private Geometry[] geom; private FuzzyPointLocator[] locFinder; private int[] location = new int[3] ; private Coordinate invalidLocation = null; private double boundaryDistanceTolerance = TOLERANCE; private List testCoords = new ArrayList(); public OverlayResultValidator(Geometry a, Geometry b, Geometry result) { /** * The tolerance to use needs to depend on the size of the geometries. * It should not be more precise than double-precision can support. */ boundaryDistanceTolerance = computeBoundaryDistanceTolerance(a, b); geom = new Geometry[] { a, b, result }; locFinder = new FuzzyPointLocator[] { new FuzzyPointLocator(geom[0], boundaryDistanceTolerance), new FuzzyPointLocator(geom[1], boundaryDistanceTolerance), new FuzzyPointLocator(geom[2], boundaryDistanceTolerance) }; } public boolean isValid(int overlayOp) { addTestPts(geom[0]); addTestPts(geom[1]); boolean isValid = checkValid(overlayOp); /* System.out.println("OverlayResultValidator: " + isValid); System.out.println("G0"); System.out.println(geom[0]); System.out.println("G1"); System.out.println(geom[1]); System.out.println("Result"); System.out.println(geom[2]); */ return isValid; } public Coordinate getInvalidLocation() { return invalidLocation; } private void addTestPts(Geometry g) { OffsetPointGenerator ptGen = new OffsetPointGenerator(g); testCoords.addAll(ptGen.getPoints(5 * boundaryDistanceTolerance)); } private boolean checkValid(int overlayOp) { for (int i = 0; i < testCoords.size(); i++) { Coordinate pt = (Coordinate) testCoords.get(i); if (! checkValid(overlayOp, pt)) { invalidLocation = pt; return false; } } return true; } private boolean checkValid(int overlayOp, Coordinate pt) { location[0] = locFinder[0].getLocation(pt); location[1] = locFinder[1].getLocation(pt); location[2] = locFinder[2].getLocation(pt); /** * If any location is on the Boundary, can't deduce anything, so just return true */ if (hasLocation(location, Location.BOUNDARY)) return true; return isValidResult(overlayOp, location); } private static boolean hasLocation(int[] location, int loc) { for (int i = 0; i < 3; i ++) { if (location[i] == loc) return true; } return false; } private boolean isValidResult(int overlayOp, int[] location) { boolean expectedInterior = OverlayOp.isResultOfOp(location[0], location[1], overlayOp); boolean resultInInterior = (location[2] == Location.INTERIOR); // MD use simpler: boolean isValid = (expectedInterior == resultInInterior); boolean isValid = ! (expectedInterior ^ resultInInterior); if (! isValid) reportResult(overlayOp, location, expectedInterior); return isValid; } private void reportResult(int overlayOp, int[] location, boolean expectedInterior) { System.out.println( "Overlay result invalid - A:" + Location.toLocationSymbol(location[0]) + " B:" + Location.toLocationSymbol(location[1]) + " expected:" + (expectedInterior ? 'i' : 'e') + " actual:" + Location.toLocationSymbol(location[2]) ); } }