/* * Copyright (c) 2016 Martin Davis. * * 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.precision; import org.locationtech.jts.algorithm.CGAlgorithms; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineSegment; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Lineal; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Puntal; import org.locationtech.jts.index.strtree.ItemBoundable; import org.locationtech.jts.index.strtree.ItemDistance; import org.locationtech.jts.index.strtree.STRtree; import org.locationtech.jts.operation.distance.FacetSequence; import org.locationtech.jts.operation.distance.FacetSequenceTreeBuilder; /** * Computes the Minimum Clearance of a {@link Geometry}. * <p> * The <b>Minimum Clearance</b> is a measure of * what magnitude of perturbation of * the vertices of a geometry can be tolerated * before the geometry becomes topologically invalid. * The smaller the Minimum Clearance distance, * the less vertex perturbation the geometry can tolerate * before becoming invalid. * <p> * The concept was introduced by Thompson and Van Oosterom * [TV06], based on earlier work by Milenkovic [Mi88]. * <p> * The Minimum Clearance of a geometry G * is defined to be the value <i>r</i> * such that "the movement of all points by a distance * of <i>r</i> in any direction will * guarantee to leave the geometry valid" [TV06]. * An equivalent constructive definition [Mi88] is that * <i>r</i> is the largest value such: * <ol> * <li>No two distinct vertices of G are closer than <i>r</i> * <li>No vertex of G is closer than <i>r</i> to an edge of G * of which the vertex is not an endpoint * </ol> * The following image shows an example of the Minimum Clearance * of a simple polygon. * <p> * <center><img src='doc-files/minClearance.png'></center> * <p> * If G has only a single vertex (i.e. is a * {@link Point}), the value of the minimum clearance * is {@link Double#MAX_VALUE}. * <p> * If G is a {@link Puntal} or {@link Lineal} geometry, * then in fact no amount of perturbation * will render the geometry invalid. * In this case a Minimum Clearance is still computed * based on the vertex and segment distances * according to the constructive definition. * <p> * It is possible for no Minimum Clearance to exist. * For instance, a {@link MultiPoint} with all members identical * has no Minimum Clearance * (i.e. no amount of perturbation will cause * the member points to become non-identical). * Empty geometries also have no such distance. * The lack of a meaningful MinimumClearance distance is detected * and suitable values are returned by * {@link #getDistance()} and {@link #getLine()}. * <p> * The computation of Minimum Clearance utilizes * the {@link STRtree#nearestNeighbour(ItemDistance)} * method to provide good performance even for * large inputs. * <p> * An interesting note is that for the case of {@link MultiPoint}s, * the computed Minimum Clearance line * effectively determines the Nearest Neighbours in the collection. * * <h3>References</h3> * <ul> * <li>[Mi88] Milenkovic, V. J., * <i>Verifiable implementations of geometric algorithms * using finite precision arithmetic</i>. * in Artificial Intelligence, 377-401. 1988 * <li>[TV06] Thompson, Rod and van Oosterom, Peter, * <i>Interchange of Spatial Data-Inhibiting Factors</i>, * Agile 2006, Visegrad, Hungary. 2006 * </ul> * * @author Martin Davis * */ public class MinimumClearance { /** * Computes the Minimum Clearance distance for * the given Geometry. * * @param g the input geometry * @return the Minimum Clearance distance */ public static double getDistance(Geometry g) { MinimumClearance rp = new MinimumClearance(g); return rp.getDistance(); } /** * Gets a LineString containing two points * which are at the Minimum Clearance distance * for the given Geometry. * * @param g the input geometry * @return the value of the minimum clearance distance * or <tt>LINESTRING EMPTY</tt> if no Minimum Clearance distance exists */ public static Geometry getLine(Geometry g) { MinimumClearance rp = new MinimumClearance(g); return rp.getLine(); } private Geometry inputGeom; private double minClearance; private Coordinate[] minClearancePts; /** * Creates an object to compute the Minimum Clearance * for the given Geometry * * @param geom the input geometry */ public MinimumClearance(Geometry geom) { inputGeom = geom; } /** * Gets the Minimum Clearance distance. * <p> * If no distance exists * (e.g. in the case of two identical points) * <tt>Double.MAX_VALUE</tt> is returned. * * @return the value of the minimum clearance distance * or <tt>Double.MAX_VALUE</tt> if no Minimum Clearance distance exists */ public double getDistance() { compute(); return minClearance; } /** * Gets a LineString containing two points * which are at the Minimum Clearance distance. * <p> * If no distance could be found * (e.g. in the case of two identical points) * <tt>LINESTRING EMPTY</tt> is returned. * * @return the value of the minimum clearance distance * or <tt>LINESTRING EMPTY</tt> if no Minimum Clearance distance exists */ public LineString getLine() { compute(); // return empty line string if no min pts where found if (minClearancePts == null || minClearancePts[0] == null) return inputGeom.getFactory().createLineString((Coordinate[]) null); return inputGeom.getFactory().createLineString(minClearancePts); } private void compute() { // already computed if (minClearancePts != null) return; // initialize to "No Distance Exists" state minClearancePts = new Coordinate[2]; minClearance = Double.MAX_VALUE; // handle empty geometries if (inputGeom.isEmpty()) { return; } STRtree geomTree = FacetSequenceTreeBuilder.build(inputGeom); Object[] nearest = geomTree.nearestNeighbour(new MinClearanceDistance()); MinClearanceDistance mcd = new MinClearanceDistance(); minClearance = mcd.distance( (FacetSequence) nearest[0], (FacetSequence) nearest[1]); minClearancePts = mcd.getCoordinates(); } /** * Implements the MinimumClearance distance function: * <ul> * <li>dist(p1, p2) = * <ul> * <li>p1 != p2 : p1.distance(p2) * <li>p1 == p2 : Double.MAX * </ul> * <li>dist(p, seg) = * <ul> * <li>p != seq.p1 && p != seg.p2 : seg.distance(p) * <li>ELSE : Double.MAX * </ul> * </ul> * Also computes the values of the nearest points, if any. * * @author Martin Davis * */ private static class MinClearanceDistance implements ItemDistance { private double minDist = Double.MAX_VALUE; private Coordinate[] minPts = new Coordinate[2]; public Coordinate[] getCoordinates() { return minPts; } public double distance(ItemBoundable b1, ItemBoundable b2) { FacetSequence fs1 = (FacetSequence) b1.getItem(); FacetSequence fs2 = (FacetSequence) b2.getItem(); minDist = Double.MAX_VALUE; return distance(fs1, fs2); } public double distance(FacetSequence fs1, FacetSequence fs2) { // compute MinClearance distance metric vertexDistance(fs1, fs2); if (fs1.size() == 1 && fs2.size() == 1) return minDist; if (minDist <= 0.0) return minDist; segmentDistance(fs1, fs2); if (minDist <= 0.0) return minDist; segmentDistance(fs2, fs1); return minDist; } private double vertexDistance(FacetSequence fs1, FacetSequence fs2) { for (int i1 = 0; i1 < fs1.size(); i1++) { for (int i2 = 0; i2 < fs2.size(); i2++) { Coordinate p1 = fs1.getCoordinate(i1); Coordinate p2 = fs2.getCoordinate(i2); if (! p1.equals2D(p2)) { double d = p1.distance(p2); if (d < minDist) { minDist = d; minPts[0] = p1; minPts[1] = p2; if (d == 0.0) return d; } } } } return minDist; } private double segmentDistance(FacetSequence fs1, FacetSequence fs2) { for (int i1 = 0; i1 < fs1.size(); i1++) { for (int i2 = 1; i2 < fs2.size(); i2++) { Coordinate p = fs1.getCoordinate(i1); Coordinate seg0 = fs2.getCoordinate(i2-1); Coordinate seg1 = fs2.getCoordinate(i2); if (! (p.equals2D(seg0) || p.equals2D(seg1))) { double d = CGAlgorithms.distancePointLine(p, seg0, seg1); if (d < minDist) { minDist = d; updatePts(p, seg0, seg1); if (d == 0.0) return d; } } } } return minDist; } private void updatePts(Coordinate p, Coordinate seg0, Coordinate seg1) { minPts[0] = p; LineSegment seg = new LineSegment(seg0, seg1); minPts[1] = new Coordinate(seg.closestPoint(p)); } } }