All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vividsolutions.jts.algorithm.MinimumDiameter Maven / Gradle / Ivy

There is a newer version: 0.1.4
Show newest version

/*
 * 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.vividsolutions.jts.algorithm;

import com.vividsolutions.jts.geom.*;

/**
 * Computes the minimum diameter of a {@link Geometry}.
 * The minimum diameter is defined to be the
 * width of the smallest band that
 * contains the geometry,
 * where a band is a strip of the plane defined
 * by two parallel lines.
 * This can be thought of as the smallest hole that the geometry can be
 * moved through, with a single rotation.
 * 

* The first step in the algorithm is computing the convex hull of the Geometry. * If the input Geometry is known to be convex, a hint can be supplied to * avoid this computation. *

* This class can also be used to compute a line segment representing * the minimum diameter, the supporting line segment of the minimum diameter, * and a minimum rectangle enclosing the input geometry. * This rectangle will * have width equal to the minimum diameter, and have one side * parallel to the supporting segment. * * @see ConvexHull * * @version 1.7 */ public class MinimumDiameter { /** * Gets the minimum rectangle enclosing a geometry. * * @param geom the geometry * @return the minimum rectangle enclosing the geometry */ public static Geometry getMinimumRectangle(Geometry geom) { return (new MinimumDiameter(geom)).getMinimumRectangle(); } /** * Gets the length of the minimum diameter enclosing a geometry * @param geom the geometry * @return the length of the minimum diameter of the geometry */ public static Geometry getMinimumDiameter(Geometry geom) { return (new MinimumDiameter(geom)).getDiameter(); } private final Geometry inputGeom; private final boolean isConvex; private Coordinate[] convexHullPts = null; private LineSegment minBaseSeg = new LineSegment(); private Coordinate minWidthPt = null; private int minPtIndex; private double minWidth = 0.0; /** * Compute a minimum diameter for a given {@link Geometry}. * * @param geom a Geometry */ public MinimumDiameter(Geometry inputGeom) { this(inputGeom, false); } /** * Compute a minimum diameter for a giver {@link Geometry}, * with a hint if * the Geometry is convex * (e.g. a convex Polygon or LinearRing, * or a two-point LineString, or a Point). * * @param geom a Geometry which is convex * @param isConvex true if the input geometry is convex */ public MinimumDiameter(Geometry inputGeom, boolean isConvex) { this.inputGeom = inputGeom; this.isConvex = isConvex; } /** * Gets the length of the minimum diameter of the input Geometry * * @return the length of the minimum diameter */ public double getLength() { computeMinimumDiameter(); return minWidth; } /** * Gets the {@link Coordinate} forming one end of the minimum diameter * * @return a coordinate forming one end of the minimum diameter */ public Coordinate getWidthCoordinate() { computeMinimumDiameter(); return minWidthPt; } /** * Gets the segment forming the base of the minimum diameter * * @return the segment forming the base of the minimum diameter */ public LineString getSupportingSegment() { computeMinimumDiameter(); return inputGeom.getFactory().createLineString(new Coordinate[] { minBaseSeg.p0, minBaseSeg.p1 } ); } /** * Gets a {@link LineString} which is a minimum diameter * * @return a {@link LineString} which is a minimum diameter */ public LineString getDiameter() { computeMinimumDiameter(); // return empty linestring if no minimum width calculated if (minWidthPt == null) return inputGeom.getFactory().createLineString((Coordinate[])null); Coordinate basePt = minBaseSeg.project(minWidthPt); return inputGeom.getFactory().createLineString(new Coordinate[] { basePt, minWidthPt } ); } private void computeMinimumDiameter() { // check if computation is cached if (minWidthPt != null) return; if (isConvex) computeWidthConvex(inputGeom); else { Geometry convexGeom = (new ConvexHull(inputGeom)).getConvexHull(); computeWidthConvex(convexGeom); } } private void computeWidthConvex(Geometry convexGeom) { //System.out.println("Input = " + geom); if (convexGeom instanceof Polygon) convexHullPts = ((Polygon) convexGeom).getExteriorRing().getCoordinates(); else convexHullPts = convexGeom.getCoordinates(); // special cases for lines or points or degenerate rings if (convexHullPts.length == 0) { minWidth = 0.0; minWidthPt = null; minBaseSeg = null; } else if (convexHullPts.length == 1) { minWidth = 0.0; minWidthPt = convexHullPts[0]; minBaseSeg.p0 = convexHullPts[0]; minBaseSeg.p1 = convexHullPts[0]; } else if (convexHullPts.length == 2 || convexHullPts.length == 3) { minWidth = 0.0; minWidthPt = convexHullPts[0]; minBaseSeg.p0 = convexHullPts[0]; minBaseSeg.p1 = convexHullPts[1]; } else computeConvexRingMinDiameter(convexHullPts); } /** * Compute the width information for a ring of {@link Coordinate}s. * Leaves the width information in the instance variables. * * @param pts */ private void computeConvexRingMinDiameter(Coordinate[] pts) { // for each segment in the ring minWidth = Double.MAX_VALUE; int currMaxIndex = 1; LineSegment seg = new LineSegment(); // compute the max distance for all segments in the ring, and pick the minimum for (int i = 0; i < pts.length - 1; i++) { seg.p0 = pts[i]; seg.p1 = pts[i + 1]; currMaxIndex = findMaxPerpDistance(pts, seg, currMaxIndex); } } private int findMaxPerpDistance(Coordinate[] pts, LineSegment seg, int startIndex) { double maxPerpDistance = seg.distancePerpendicular(pts[startIndex]); double nextPerpDistance = maxPerpDistance; int maxIndex = startIndex; int nextIndex = maxIndex; while (nextPerpDistance >= maxPerpDistance) { maxPerpDistance = nextPerpDistance; maxIndex = nextIndex; nextIndex = nextIndex(pts, maxIndex); nextPerpDistance = seg.distancePerpendicular(pts[nextIndex]); } // found maximum width for this segment - update global min dist if appropriate if (maxPerpDistance < minWidth) { minPtIndex = maxIndex; minWidth = maxPerpDistance; minWidthPt = pts[minPtIndex]; minBaseSeg = new LineSegment(seg); // System.out.println(minBaseSeg); // System.out.println(minWidth); } return maxIndex; } private static int nextIndex(Coordinate[] pts, int index) { index++; if (index >= pts.length) index = 0; return index; } /** * Gets the minimum rectangular {@link Polygon} which encloses the input geometry. * The rectangle has width equal to the minimum diameter, * and a longer length. * If the convex hull of the input is degenerate (a line or point) * a {@link LineString} or {@link Point} is returned. *

* The minimum rectangle can be used as an extremely generalized representation * for the given geometry. * * @return the minimum rectangle enclosing the input (or a line or point if degenerate) */ public Geometry getMinimumRectangle() { computeMinimumDiameter(); // check if minimum rectangle is degenerate (a point or line segment) if (minWidth == 0.0) { if (minBaseSeg.p0.equals2D(minBaseSeg.p1)) { return inputGeom.getFactory().createPoint(minBaseSeg.p0); } return minBaseSeg.toGeometry(inputGeom.getFactory()); } // deltas for the base segment of the minimum diameter double dx = minBaseSeg.p1.x - minBaseSeg.p0.x; double dy = minBaseSeg.p1.y - minBaseSeg.p0.y; /* double c0 = computeC(dx, dy, minBaseSeg.p0); double c1 = computeC(dx, dy, minBaseSeg.p1); */ double minPara = Double.MAX_VALUE; double maxPara = -Double.MAX_VALUE; double minPerp = Double.MAX_VALUE; double maxPerp = -Double.MAX_VALUE; // compute maxima and minima of lines parallel and perpendicular to base segment for (int i = 0; i < convexHullPts.length; i++) { double paraC = computeC(dx, dy, convexHullPts[i]); if (paraC > maxPara) maxPara = paraC; if (paraC < minPara) minPara = paraC; double perpC = computeC(-dy, dx, convexHullPts[i]); if (perpC > maxPerp) maxPerp = perpC; if (perpC < minPerp) minPerp = perpC; } // compute lines along edges of minimum rectangle LineSegment maxPerpLine = computeSegmentForLine(-dx, -dy, maxPerp); LineSegment minPerpLine = computeSegmentForLine(-dx, -dy, minPerp); LineSegment maxParaLine = computeSegmentForLine(-dy, dx, maxPara); LineSegment minParaLine = computeSegmentForLine(-dy, dx, minPara); // compute vertices of rectangle (where the para/perp max & min lines intersect) Coordinate p0 = maxParaLine.lineIntersection(maxPerpLine); Coordinate p1 = minParaLine.lineIntersection(maxPerpLine); Coordinate p2 = minParaLine.lineIntersection(minPerpLine); Coordinate p3 = maxParaLine.lineIntersection(minPerpLine); LinearRing shell = inputGeom.getFactory().createLinearRing( new Coordinate[] { p0, p1, p2, p3, p0 }); return inputGeom.getFactory().createPolygon(shell, null); } private static double computeC(double a, double b, Coordinate p) { return a * p.y - b * p.x; } private static LineSegment computeSegmentForLine(double a, double b, double c) { Coordinate p0; Coordinate p1; /* * Line eqn is ax + by = c * Slope is a/b. * If slope is steep, use y values as the inputs */ if (Math.abs(b) > Math.abs(a)) { p0 = new Coordinate(0.0, c/b); p1 = new Coordinate(1.0, c/b - a/b); } else { p0 = new Coordinate(c/a, 0.0); p1 = new Coordinate(c/a - b/a, 1.0); } return new LineSegment(p0, p1); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy