/* * 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.operation.distance; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.revolsys.geometry.algorithm.PointLocator; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Location; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.segment.Segment; /** * Find two points on two {@link Geometry}s which lie * within a given distance, or else are the nearest points * on the geometries (in which case this also * provides the distance between the geometries). * <p> * The distance computation also finds a pair of points in the input geometries * which have the minimum distance between them. * If a point lies in the interior of a line segment, * the coordinate computed is a close * approximation to the exact point. * <p> * The algorithms used are straightforward O(n^2) * comparisons. This worst-case performance could be improved on * by using Voronoi techniques or spatial indexes. * * @version 1.7 */ public class DistanceWithPoints { /** * Compute the distance between the nearest points of two geometries. * @param geometry1 a {@link Geometry} * @param geometry2 another {@link Geometry} * @return the distance between the geometries */ public static double distance(final Geometry geometry1, final Geometry geometry2) { final DistanceWithPoints distOp = new DistanceWithPoints(geometry1, geometry2); return distOp.distance(); } /** * Test whether two geometries lie within a given distance of each other. * @param geometry1 a {@link Geometry} * @param geometry2 another {@link Geometry} * @param distance the distance to test * @return true if geometry1.distance(geometry2) <= distance */ public static boolean isWithinDistance(final Geometry geometry1, final Geometry geometry2, final double distance) { final DistanceWithPoints distOp = new DistanceWithPoints(geometry1, geometry2, distance); return distOp.distance() <= distance; } /** * Compute the nearest points of two geometries. * The points are presented in the same order as the input Geometries. * * @param geometry1 a {@link Geometry} * @param geometry2 another {@link Geometry} * @return the nearest points in the geometries */ public static List<Point> nearestPoints(final Geometry geometry1, final Geometry geometry2) { final DistanceWithPoints distOp = new DistanceWithPoints(geometry1, geometry2); return distOp.nearestPoints(); } private boolean computed = false; private final Geometry geometry1; private final Geometry geometry2; private double minDistance = Double.MAX_VALUE; private Point minDistancePoint1; private Point minDistancePoint2; // working private final PointLocator pointLocator = new PointLocator(); private double terminateDistance = 0.0; /** * Constructs a DistanceWithPoints that computes the distance and nearest points between * the two specified geometries. * @param geometry1 a Geometry * @param geometry2 a Geometry */ public DistanceWithPoints(final Geometry geometry1, final Geometry geometry2) { this(geometry1, geometry2, 0.0); } /** * Constructs a DistanceWithPoints that computes the distance and nearest points between * the two specified geometries. * @param geometry1 a Geometry * @param geometry2 a Geometry * @param terminateDistance the distance on which to terminate the search */ public DistanceWithPoints(final Geometry geometry1, final Geometry geometry2, final double terminateDistance) { if (geometry1 == null || geometry2 == null) { throw new IllegalArgumentException("null geometries are not supported"); } this.geometry1 = geometry1; this.geometry2 = geometry2; this.terminateDistance = terminateDistance; } private boolean computeContainmentDistance() { if (computeContainmentDistance(this.geometry1, this.geometry2)) { return true; } else if (computeContainmentDistance(this.geometry2, this.geometry1)) { return true; } else { return false; } } private boolean computeContainmentDistance(final Geometry geometry1, final Geometry geometry2) { final List<Polygon> polygons = geometry1.getGeometries(Polygon.class); if (polygons.size() > 0) { final List<Point> insidePoints = ConnectedElementLocationFilter.getPoints(geometry2); if (computeContainmentDistance(insidePoints, polygons)) { return true; } } return false; } private boolean computeContainmentDistance(final List<Point> locations, final List<Polygon> polygons) { for (final Point insidePoint : locations) { for (final Polygon polygon : polygons) { if (computeContainmentDistance(insidePoint, polygon)) { return true; } } } return false; } private boolean computeContainmentDistance(final Point point, final Polygon poly) { // if point is not in exterior, distance to geom is 0 if (Location.EXTERIOR != poly.locate(point)) { this.minDistance = 0.0; this.minDistancePoint1 = point; this.minDistancePoint2 = point; return true; } else { return false; } } /** * Computes distance between facets (lines and points) * of input geometries. * */ private void computeFacetDistance() { /** * Geometries are not wholly inside, so compute distance from lines and points * of one to lines and points of the other */ final List<LineString> lines0 = this.geometry1.getGeometryComponents(LineString.class); final List<LineString> lines1 = this.geometry2.getGeometryComponents(LineString.class); if (!computeLinesLines(lines0, lines1)) { final List<Point> points1 = this.geometry2.getGeometries(Point.class); if (!computeLinesPoints(lines0, points1)) { final List<Point> points0 = this.geometry1.getGeometries(Point.class); if (!computePointsLines(points0, lines1)) { computePointsPoints(points0, points1); } } } } private boolean computeLineLine(final LineString line1, final LineString line2) { if (this.minDistance == Double.MAX_VALUE || line1.getBoundingBox().distance(line2.getBoundingBox()) <= this.minDistance) { for (final Segment segment1 : line1.segments()) { for (final Segment segment2 : line2.segments()) { final double dist = segment1.distance(segment2); if (dist < this.minDistance) { this.minDistance = dist; final Point[] closestPt = segment1.closestPoints(segment2); this.minDistancePoint1 = closestPt[0]; this.minDistancePoint2 = closestPt[1]; if (this.minDistance <= this.terminateDistance) { return true; } } } } } return false; } private boolean computeLinePoint(final LineString line, final Point point) { if (this.minDistance == Double.MAX_VALUE || line.getBoundingBox().distance(point) <= this.minDistance) { for (final Segment segment : line.segments()) { final double distance = segment.distancePoint(point); if (distance < this.minDistance) { this.minDistance = distance; final Point closestPoint = segment.closestPoint(point); this.minDistancePoint1 = closestPoint; this.minDistancePoint2 = point; if (this.minDistance <= this.terminateDistance) { return true; } } } } return false; } private boolean computeLinesLines(final List<LineString> lines1, final List<LineString> lines2) { for (final LineString line1 : lines1) { for (final LineString line2 : lines2) { if (computeLineLine(line1, line2)) { return true; } } } return false; } private boolean computeLinesPoints(final List<LineString> lines, final List<Point> points) { for (final LineString line : lines) { for (final Point point : points) { if (computeLinePoint(line, point)) { return true; } } } return false; } private boolean computePointLine(final Point point, final LineString line) { final BoundingBox boundingBox = line.getBoundingBox(); if (this.minDistance == Double.MAX_VALUE || boundingBox.distance(point) <= this.minDistance) { for (final Segment segment : line.segments()) { final double distance = segment.distancePoint(point); if (distance < this.minDistance) { this.minDistance = distance; final Point closestPoint = segment.closestPoint(point); this.minDistancePoint1 = point; this.minDistancePoint2 = closestPoint; if (this.minDistance <= this.terminateDistance) { return true; } } } } return false; } private boolean computePointsLines(final List<Point> points, final List<LineString> lines) { for (final Point point : points) { for (final LineString line : lines) { if (computePointLine(point, line)) { return true; } } } return false; } private boolean computePointsPoints(final List<Point> points1, final List<Point> points2) { for (final Point point1 : points1) { for (final Point point2 : points2) { final double dist = point1.distancePoint(point2); if (dist < this.minDistance) { this.minDistance = dist; this.minDistancePoint1 = point1; this.minDistancePoint2 = point2; if (this.minDistance <= this.terminateDistance) { return true; } } } } return false; } /** * Report the distance between the nearest points on the input geometries. * * @return the distance between the geometries * or 0 if either input geometry is empty * @throws IllegalArgumentException if either input geometry is null */ public double distance() { if (!this.computed) { this.computed = true; if (this.geometry1.isEmpty() || this.geometry2.isEmpty()) { this.minDistance = 0; } else { if (!computeContainmentDistance()) { computeFacetDistance(); } } } return this.minDistance; } /** * Report the coordinates of the nearest points in the input geometries. * The points are presented in the same order as the input Geometries. * * @return a pair of {@link Coordinates}s of the nearest points */ public List<Point> nearestPoints() { distance(); if (this.minDistancePoint1 == null) { return Collections.emptyList(); } else { return Arrays.asList(this.minDistancePoint1, this.minDistancePoint2); } } }