/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.precision; import com.revolsys.geometry.index.strtree.ItemBoundable; import com.revolsys.geometry.index.strtree.ItemDistance; import com.revolsys.geometry.index.strtree.STRtree; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Lineal; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Punctual; import com.revolsys.geometry.model.coordinates.LineSegmentUtil; import com.revolsys.geometry.operation.distance.FacetSequence; import com.revolsys.geometry.operation.distance.FacetSequenceTreeBuilder; import com.revolsys.util.Pair; /** * 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 pertubation 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 Punctual} 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 Punctual} 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 multi part {@link Punctual}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 { /** * 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<FacetSequence> { private double minDist = Double.MAX_VALUE; private double minX1; private double minX2; private double minY1; private double minY2; private final boolean calculateLine; public MinClearanceDistance(final boolean calculateLine) { this.calculateLine = calculateLine; } public double distance(final FacetSequence fs1, final FacetSequence fs2) { if (!vertexDistance(fs1, fs2)) { if (!(fs1.getVertexCount() == 1 && fs2.getVertexCount() == 1)) { if (!segmentDistance(fs1, fs2)) { segmentDistance(fs2, fs1); } } } return this.minDist; } @Override public double distance(final ItemBoundable<BoundingBox, FacetSequence> b1, final ItemBoundable<BoundingBox, FacetSequence> b2) { final FacetSequence fs1 = b1.getItem(); final FacetSequence fs2 = b2.getItem(); this.minDist = Double.MAX_VALUE; return distance(fs1, fs2); } public double getMinX1() { return this.minX1; } public double getMinX2() { return this.minX2; } public double getMinY1() { return this.minY1; } public double getMinY2() { return this.minY2; } private boolean segmentDistance(final FacetSequence fs1, final FacetSequence fs2) { final int vertexCount1 = fs1.getVertexCount(); final int vertexCount2 = fs2.getVertexCount(); for (int i1 = 0; i1 < vertexCount1; i1++) { final double x = fs1.getCoordinate(i1, 0); final double y = fs1.getCoordinate(i1, 1); double x1 = fs2.getCoordinate(0, 0); double y1 = fs2.getCoordinate(0, 1); for (int i2 = 1; i2 < vertexCount2; i2++) { final double x2 = fs2.getCoordinate(i2, 0); final double y2 = fs2.getCoordinate(i2, 1); if (!(x1 == x && y1 == y) && // !(x2 == x && y2 == y)) { final double distance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y); if (distance < this.minDist) { this.minDist = distance; if (this.calculateLine) { this.minX1 = x; this.minY1 = y; final Point closestPoint = LineSegmentUtil.closestPoint(x1, y1, x2, y2, x, y); this.minX2 = closestPoint.getX(); this.minY2 = closestPoint.getY(); } if (distance == 0.0) { return true; } } } x1 = x2; y1 = y2; } } return this.minDist <= 0.0; } private boolean vertexDistance(final FacetSequence fs1, final FacetSequence fs2) { final int vertexCount1 = fs1.getVertexCount(); final int vertexCount2 = fs2.getVertexCount(); for (int i1 = 0; i1 < vertexCount1; i1++) { final double x1 = fs1.getCoordinate(i1, 0); final double y1 = fs1.getCoordinate(i1, 1); for (int i2 = 0; i2 < vertexCount2; i2++) { final double x2 = fs2.getCoordinate(i2, 0); final double y2 = fs2.getCoordinate(i2, 1); if (!(x1 == x2 && y1 == y2)) { final double dx = x2 - x1; final double dy = y2 - y1; final double distance = Math.sqrt(dx * dx + dy * dy); if (distance < this.minDist) { this.minDist = distance; if (this.calculateLine) { this.minX1 = x1; this.minY1 = y1; this.minX2 = x2; this.minY2 = y2; } if (distance == 0.0) { return true; } } } } } return this.minDist <= 0.0; } } /** * Computes the Minimum Clearance distance for * the given Geometry. * * @param geometry the input geometry * @return the Minimum Clearance distance */ public static double getDistance(final Geometry geometry) { final MinimumClearance rp = new MinimumClearance(geometry, false); return rp.getDistance(); } /** * Gets a LineString containing two points * which are at the Minimum Clearance distance * for the given Geometry. * * @param geometry 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(final Geometry geometry) { final MinimumClearance rp = new MinimumClearance(geometry, true); return rp.getLine(); } private final Geometry geometry; private double minClearance; private Point[] minClearancePts; private boolean calculateLine; private boolean calculated = false; /** * Creates an object to compute the Minimum Clearance * for the given Geometry * * @param geom the input geometry */ public MinimumClearance(final Geometry geom) { this.geometry = geom; } public MinimumClearance(final Geometry geometry, final boolean calculateLine) { this.geometry = geometry; this.calculateLine = calculateLine; } private void compute() { if (!this.calculated) { this.calculated = true; this.minClearance = Double.MAX_VALUE; if (!this.geometry.isEmpty()) { final STRtree<FacetSequence> geomTree = FacetSequenceTreeBuilder.build(this.geometry); final Pair<FacetSequence, FacetSequence> nearest = geomTree .nearestNeighbour(new MinClearanceDistance(this.calculateLine)); final MinClearanceDistance mcd = new MinClearanceDistance(this.calculateLine); this.minClearance = mcd.distance(nearest.getValue1(), nearest.getValue2()); final GeometryFactory geometryFactory = this.geometry.getGeometryFactory(); if (this.calculateLine) { this.minClearancePts = new Point[] { // geometryFactory.point(mcd.getMinX1(), mcd.getMinY1()), geometryFactory.point(mcd.getMinX2(), mcd.getMinY2()) }; } } } } /** * 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 this.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 final GeometryFactory geometryFactory = this.geometry.getGeometryFactory(); if (this.minClearance == Double.MAX_VALUE) { return geometryFactory.lineString(); } else { return geometryFactory.lineString(this.minClearancePts); } } }