/* * 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.algorithm.distance; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.geometry.model.segment.Segment; import com.revolsys.geometry.model.vertex.Vertex; /** * An algorithm for computing a distance metric * which is an approximation to the Hausdorff Distance * based on a discretization of the input {@link Geometry}. * The algorithm computes the Hausdorff distance restricted to discrete points * for one of the geometries. * The points can be either the vertices of the geometries (the default), * or the geometries with line segments densified by a given fraction. * Also determines two points of the Geometries which are separated by the computed distance. * <p> * This algorithm is an approximation to the standard Hausdorff distance. * Specifically, * <pre> * for all geometries a, b: DHD(a, b) <= HD(a, b) * </pre> * The approximation can be made as close as needed by densifying the input geometries. * In the limit, this value will approach the true Hausdorff distance: * <pre> * DHD(A, B, densifyFactor) -> HD(A, B) as densifyFactor -> 0.0 * </pre> * The default approximation is exact or close enough for a large subset of useful cases. * Examples of these are: * <ul> * <li>computing distance between Linestrings that are roughly parallel to each other, * and roughly equal in length. This occurs in matching linear networks. * <li>Testing similarity of geometries. * </ul> * An example where the default approximation is not close is: * <pre> * A = LINESTRING (0 0, 100 0, 10 100, 10 100) * B = LINESTRING (0 100, 0 10, 80 10) * * DHD(A, B) = 22.360679774997898 * HD(A, B) ~= 47.8 * </pre> */ public class DiscreteHausdorffDistance { public static double distance(final Geometry g0, final Geometry g1) { final DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); return dist.distance(); } public static double distance(final Geometry g0, final Geometry g1, final double densifyFrac) { final DiscreteHausdorffDistance dist = new DiscreteHausdorffDistance(g0, g1); dist.setDensifyFraction(densifyFrac); return dist.distance(); } /** * Value of 0.0 indicates that no densification should take place */ private double densifyFrac = 0.0; private final Geometry g0; private final Geometry g1; private final PointPairDistance ptDist = new PointPairDistance(); public DiscreteHausdorffDistance(final Geometry g0, final Geometry g1) { this.g0 = g0; this.g1 = g1; } private void compute(final Geometry g0, final Geometry g1) { computeOrientedDistance(g0, g1, this.ptDist); computeOrientedDistance(g1, g0, this.ptDist); } private void computeOrientedDistance(final Geometry discreteGeom, final Geometry geom, final PointPairDistance ptDist) { final PointPairDistance maxPtDist = new PointPairDistance(); final PointPairDistance minPtDist = new PointPairDistance(); for (final Vertex vertex : discreteGeom.vertices()) { minPtDist.initialize(); DistanceToPoint.computeDistance(geom, vertex, minPtDist); maxPtDist.setMaximum(minPtDist); } ptDist.setMaximum(maxPtDist); if (this.densifyFrac > 0) { maxPtDist.initialize(); final int numSubSegs = 0; for (final Segment segment : discreteGeom.segments()) { final double x1 = segment.getX(0); final double y1 = segment.getY(0); final double x2 = segment.getX(1); final double y2 = segment.getY(1); final double delx = (x2 - x1) / numSubSegs; final double dely = (y2 - y1) / numSubSegs; for (int i = 0; i < numSubSegs; i++) { final double x = x1 + i * delx; final double y = y1 + i * dely; final Point pt = new PointDoubleXY(x, y); minPtDist.initialize(); DistanceToPoint.computeDistance(geom, pt, minPtDist); maxPtDist.setMaximum(minPtDist); } } ptDist.setMaximum(maxPtDist); } } public double distance() { compute(this.g0, this.g1); return this.ptDist.getDistance(); } public Point[] getCoordinates() { return this.ptDist.getPoints(); } public double orientedDistance() { computeOrientedDistance(this.g0, this.g1, this.ptDist); return this.ptDist.getDistance(); } /** * Sets the fraction by which to densify each segment. * Each segment will be (virtually) split into a number of equal-length * subsegments, whose fraction of the total length is closest * to the given fraction. * * @param densifyPercent */ public void setDensifyFraction(final double densifyFrac) { if (densifyFrac > 1.0 || densifyFrac <= 0.0) { throw new IllegalArgumentException("Fraction is not in range (0.0 - 1.0]"); } this.densifyFrac = densifyFrac; } }