/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package me.osm.gazetter.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateList;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.util.LineStringExtracter;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
/**
* Utilities for geometry processing.
* <p>
* Some of them was copyied from vividsolutions JCS and OpenJUMP
* under GNU GPLv2
* */
public class GeometryUtils {
/* Most imports from vividsolutions JCS (GNU GPLv2)
*/
/**
* Split polygon among the line. Line should cover polygon.
*
* @param polygon - polygont to be splitted
* @param line - blade
* */
public static List<Polygon> splitPolygon(Polygon polygon, LineString line) {
GeometryFactory f = new GeometryFactory();
Geometry nodedLinework = polygon.getBoundary().union(line);
Polygonizer polygonizer = new Polygonizer();
polygonizer.add(LineStringExtracter.getLines(nodedLinework));
@SuppressWarnings("unchecked")
Collection<Polygon> polygons = polygonizer.getPolygons();
// only keep polygons which are inside the input
List<Polygon> output = new ArrayList<>();
for (Polygon p : polygons) {
if (polygon.contains(p.getInteriorPoint()))
output.add(p);
}
List<Polygon> result = new ArrayList<>();
for (Polygon outer : output) {
Polygon r = outer;
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
LineString interiorRing = polygon.getInteriorRingN(i);
LinearRing interior = f.createLinearRing(interiorRing
.getCoordinates());
Polygon inner = f.createPolygon(interior);
if (inner.intersects(outer)) {
Polygon difference = (Polygon) outer.difference(inner);
if (difference.isValid()) {
r = difference;
}
}
}
result.add(r);
}
return result;
}
/**
* Splits the lineString at the point closest to c.
*
* @param lineString line to split
* @param target point where line should be split
* @param moveSplitToTarget
* true to move the split point to the target; false to leave the
* split point at the closest point on the line to the target
*/
public static LineString[] split(LineString lineString, Coordinate target,
boolean moveSplitToTarget) {
LengthToPoint lengthToPoint = new LengthToPoint(lineString, target);
LengthSubstring lengthSubstring = new LengthSubstring(lineString);
LineString[] newLineStrings = new LineString[] {
lengthSubstring.getSubstring(0, lengthToPoint.getLength()),
lengthSubstring.getSubstring(lengthToPoint.getLength(),
lineString.getLength()) };
if (moveSplitToTarget) {
newLineStrings[1].getStartPoint().getCoordinate()
.setCoordinate((Coordinate) target.clone());
}
// Make sure the coordinates are absolutely equal [Jon Aquino
// 11/21/2003]
newLineStrings[0]
.getEndPoint()
.getCoordinate()
.setCoordinate(
newLineStrings[1].getStartPoint().getCoordinate());
return newLineStrings;
}
/**
* Computes the length along a LineString to the point on the line nearest a
* given point.
*/
private static class LengthToPoint {
private double minDistanceToPoint;
private double locationLength;
public LengthToPoint(LineString line, Coordinate inputPt) {
computeLength(line, inputPt);
}
public double getLength() {
return locationLength;
}
private void computeLength(LineString line, Coordinate inputPt) {
minDistanceToPoint = Double.MAX_VALUE;
double baseLocationDistance = 0.0;
Coordinate[] pts = line.getCoordinates();
LineSegment seg = new LineSegment();
for (int i = 0; i < pts.length - 1; i++) {
seg.p0 = pts[i];
seg.p1 = pts[i + 1];
updateLength(seg, inputPt, baseLocationDistance);
baseLocationDistance += seg.getLength();
}
}
private void updateLength(LineSegment seg, Coordinate inputPt,
double segStartLocationDistance) {
double dist = seg.distance(inputPt);
if (dist > minDistanceToPoint)
return;
minDistanceToPoint = dist;
// found new minimum, so compute location distance of point
double projFactor = seg.projectionFactor(inputPt);
if (projFactor <= 0.0)
locationLength = segStartLocationDistance;
else if (projFactor <= 1.0)
locationLength = segStartLocationDistance + projFactor
* seg.getLength();
else
locationLength = segStartLocationDistance + seg.getLength();
}
}
/**
* Computes a substring of a {@link LineString} between given distances
* along the line.
* <ul>
* <li>The distances are clipped to the actual line length
* <li>If the start distance is equal to the end distance, a zero-length
* line with two identical points is returned
* <li>FUTURE: If the start distance is greater than the end distance, an
* inverted section of the line is returned
* </ul>
* <p>
* FUTURE: should handle startLength > endLength, and flip the returned
* linestring. Also should handle negative lengths (they are measured from
* end of line backwards).
*/
private static class LengthSubstring {
private LineString line;
public LengthSubstring(LineString line) {
this.line = line;
}
public LineString getSubstring(double startDistance, double endDistance) {
// future: if start > end, flip values and return an inverted line
assert startDistance <= endDistance : "inverted distances not currently supported";
Coordinate[] coordinates = line.getCoordinates();
// check for a zero-length segment and handle appropriately
if (endDistance <= 0.0) {
return line.getFactory().createLineString(
new Coordinate[] { coordinates[0], coordinates[0] });
}
if (startDistance >= line.getLength()) {
return line.getFactory().createLineString(
new Coordinate[] { coordinates[coordinates.length - 1],
coordinates[coordinates.length - 1] });
}
if (startDistance < 0.0) {
startDistance = 0.0;
}
return computeSubstring(startDistance, endDistance);
}
/**
* Assumes input is strictly valid (e.g. startDist < endDistance)
*
* @param startDistance
* @param endDistance
* @return
*/
private LineString computeSubstring(double startDistance,
double endDistance) {
Coordinate[] coordinates = line.getCoordinates();
CoordinateList newCoordinates = new CoordinateList();
double segmentStartDistance = 0.0;
double segmentEndDistance = 0.0;
int i = 0;
LineSegment segment = new LineSegment();
while (i < coordinates.length - 1
&& endDistance > segmentEndDistance) {
segment.p0 = coordinates[i];
segment.p1 = coordinates[i + 1];
i++;
segmentStartDistance = segmentEndDistance;
segmentEndDistance = segmentStartDistance + segment.getLength();
if (startDistance > segmentEndDistance)
continue;
if (startDistance >= segmentStartDistance
&& startDistance < segmentEndDistance) {
newCoordinates.add(LocatePoint.pointAlongSegment(
segment.p0, segment.p1, startDistance
- segmentStartDistance), false);
}
/*
* if (startDistance >= segmentStartDistance && startDistance ==
* segmentEndDistance) { newCoordinates.add(new
* Coordinate(segment.p1), false); }
*/
if (endDistance >= segmentEndDistance) {
newCoordinates.add(new Coordinate(segment.p1), false);
}
if (endDistance >= segmentStartDistance
&& endDistance < segmentEndDistance) {
newCoordinates.add(LocatePoint.pointAlongSegment(
segment.p0, segment.p1, endDistance
- segmentStartDistance), false);
}
}
Coordinate[] newCoordinateArray = newCoordinates
.toCoordinateArray();
/**
* Ensure there is enough coordinates to build a valid line. Make a
* 2-point line with duplicate coordinates, if necessary There will
* always be at least one coordinate in the coordList.
*/
if (newCoordinateArray.length <= 1) {
newCoordinateArray = new Coordinate[] { newCoordinateArray[0],
newCoordinateArray[0] };
}
return line.getFactory().createLineString(newCoordinateArray);
}
}
}