/*
* 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.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import javax.measure.Measure;
import javax.measure.quantity.Length;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.geometry.algorithm.LineIntersector;
import com.revolsys.geometry.algorithm.LineStringLocation;
import com.revolsys.geometry.algorithm.RayCrossingCounter;
import com.revolsys.geometry.algorithm.RobustLineIntersector;
import com.revolsys.geometry.cs.CoordinateSystem;
import com.revolsys.geometry.cs.GeographicCoordinateSystem;
import com.revolsys.geometry.cs.ProjectedCoordinateSystem;
import com.revolsys.geometry.cs.projection.CoordinatesOperation;
import com.revolsys.geometry.graph.linemerge.LineMerger;
import com.revolsys.geometry.model.coordinates.CoordinatesUtil;
import com.revolsys.geometry.model.coordinates.LineSegmentUtil;
import com.revolsys.geometry.model.coordinates.list.CoordinatesListUtil;
import com.revolsys.geometry.model.editor.LineStringEditor;
import com.revolsys.geometry.model.impl.LineStringDoubleBuilder;
import com.revolsys.geometry.model.metrics.PointLineStringMetrics;
import com.revolsys.geometry.model.prep.PreparedLineString;
import com.revolsys.geometry.model.segment.LineSegmentDouble;
import com.revolsys.geometry.model.segment.LineStringSegment;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.AbstractVertex;
import com.revolsys.geometry.model.vertex.LineStringVertex;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.operation.BoundaryOp;
import com.revolsys.geometry.util.BoundingBoxUtil;
import com.revolsys.util.MathUtil;
import com.revolsys.util.Pair;
import com.revolsys.util.Property;
import com.revolsys.util.number.Doubles;
/**
* Models an OGC-style <code>LineString</code>. A LineString consists of a
* sequence of two or more vertices, along with all points along the
* linearly-interpolated curves (line segments) between each pair of consecutive
* vertices. Consecutive vertices may be equal. The line segments in the line
* may intersect each other (in other words, the linestring may "curl back" in
* itself and self-intersect. Linestrings with exactly two identical points are
* invalid.
* <p>
* A linestring must have either 0 or 2 or more points. If these conditions are
* not met, the constructors throw an {@link IllegalArgumentException}
*
* @version 1.7
*/
public interface LineString extends Lineal {
@SuppressWarnings("unchecked")
static <G extends Geometry> G newLineString(final Object value) {
if (value == null) {
return null;
} else if (value instanceof LineString) {
return (G)value;
} else if (value instanceof Geometry) {
throw new IllegalArgumentException(
((Geometry)value).getGeometryType() + " cannot be converted to a LineString");
} else {
final String string = DataTypes.toString(value);
return (G)GeometryFactory.DEFAULT_3D.geometry(string, false);
}
}
@Override
@SuppressWarnings("unchecked")
default <V extends Geometry> V appendVertex(Point newPoint, final int... geometryId) {
if (geometryId.length == 0) {
final GeometryFactory geometryFactory = getGeometryFactory();
if (newPoint == null || newPoint.isEmpty()) {
return (V)this;
} else if (isEmpty()) {
return newPoint.convertGeometry(geometryFactory);
} else {
newPoint = newPoint.convertGeometry(geometryFactory);
final int vertexCount = getVertexCount();
final double[] coordinates = getCoordinates();
final int axisCount = getAxisCount();
final double[] newCoordinates = new double[axisCount * (vertexCount + 1)];
final int length = vertexCount * axisCount;
System.arraycopy(coordinates, 0, newCoordinates, 0, length);
CoordinatesListUtil.setCoordinates(newCoordinates, axisCount, vertexCount, newPoint);
return (V)newLineString(newCoordinates);
}
} else {
throw new IllegalArgumentException("Geometry id's for " + getGeometryType()
+ " must have length 0. " + Arrays.toString(geometryId));
}
}
@Override
default Lineal applyLineal(final Function<LineString, LineString> function) {
final LineString newLine = function.apply(this);
return newLine;
}
default LineString cleanCloseVertices(final double maxDistance) {
final GeometryFactory geometryFactory = getGeometryFactory();
final int axisCount = geometryFactory.getAxisCount();
final int vertexCount = getVertexCount();
final LineStringDoubleBuilder newLine = new LineStringDoubleBuilder(geometryFactory,
vertexCount);
double x1 = getX(0);
double y1 = getY(0);
newLine.appendVertex(x1, y1);
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
final double coordinate = getCoordinate(0, axisIndex);
newLine.setCoordinate(0, axisIndex, coordinate);
}
final int lastVertexIndex = getLastVertexIndex();
for (int vertexIndex = 1; vertexIndex < lastVertexIndex; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
if (MathUtil.distance(x1, y1, x2, y2) < maxDistance) {
// Skip vertex
} else {
newLine.appendVertex(x2, y2);
final int newVertexIndex = newLine.getLastVertexIndex();
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
final double coordinate = getCoordinate(newVertexIndex, axisIndex);
newLine.setCoordinate(newVertexIndex, axisIndex, coordinate);
}
x1 = x2;
y1 = y2;
}
}
final double xn = getX(lastVertexIndex);
final double yn = getY(lastVertexIndex);
if (lastVertexIndex > 2 && MathUtil.distance(x1, y1, xn, yn) < maxDistance) {
final int newVertexIndex = newLine.getLastVertexIndex();
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double coordinate = getCoordinate(lastVertexIndex, axisIndex);
newLine.setCoordinate(newVertexIndex, axisIndex, coordinate);
}
} else {
newLine.appendVertex(xn, yn);
final int newVertexIndex = newLine.getLastVertexIndex();
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
final double coordinate = getCoordinate(lastVertexIndex, axisIndex);
newLine.setCoordinate(newVertexIndex, axisIndex, coordinate);
}
}
if (newLine.getVertexCount() == 1) {
return this;
} else {
final LineString newLineString = newLine.newLineString();
return newLineString;
}
}
@Override
LineString clone();
@Override
default int compareToSameClass(final Geometry geometry) {
final LineString line2 = (LineString)geometry;
final Iterator<Vertex> iterator1 = vertices().iterator();
final Iterator<Vertex> iterator2 = line2.vertices().iterator();
while (iterator1.hasNext() && iterator2.hasNext()) {
final Point vertex1 = iterator1.next();
final Point vertex2 = iterator2.next();
final int comparison = vertex1.compareTo(vertex2);
if (comparison != 0) {
return comparison;
}
}
if (iterator1.hasNext()) {
return 1;
} else if (iterator2.hasNext()) {
return -1;
} else {
return 0;
}
}
default int compareVertex(final int vertexIndex1, final int vertexIndex2) {
final double x1 = getX(vertexIndex1);
final double y1 = getY(vertexIndex1);
final double x2 = getX(vertexIndex2);
final double y2 = getY(vertexIndex2);
return CoordinatesUtil.compare(x1, y1, x2, y2);
}
default int compareVertex(final int vertexIndex1, final LineString line2,
final int vertexIndex2) {
final double x1 = getX(vertexIndex1);
final double y1 = getY(vertexIndex1);
final double x2 = line2.getX(vertexIndex2);
final double y2 = line2.getY(vertexIndex2);
return CoordinatesUtil.compare(x1, y1, x2, y2);
}
default void convertVertexCoordinates2d(final int vertexIndex,
final GeometryFactory geometryFactory, final double[] targetCoordinates) {
final double x = getX(vertexIndex);
final double y = getY(vertexIndex);
targetCoordinates[X] = x;
targetCoordinates[Y] = y;
final CoordinatesOperation coordinatesOperation = getGeometryFactory()
.getCoordinatesOperation(geometryFactory);
if (coordinatesOperation != null) {
coordinatesOperation.perform(2, targetCoordinates, 2, targetCoordinates);
}
}
default int copyCoordinates(final int axisCount, final double nanValue,
final double[] destCoordinates, int destOffset) {
if (isEmpty()) {
return destOffset;
} else {
for (int vertexIndex = 0; vertexIndex < getVertexCount(); vertexIndex++) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
double coordinate = getCoordinate(vertexIndex, axisIndex);
if (Double.isNaN(coordinate)) {
coordinate = nanValue;
}
destCoordinates[destOffset++] = coordinate;
}
}
return destOffset;
}
}
default int copyCoordinatesReverse(final int axisCount, final double nanValue,
final double[] destCoordinates, int destOffset) {
if (isEmpty()) {
return destOffset;
} else {
for (int vertexIndex = getVertexCount() - 1; vertexIndex >= 0; vertexIndex--) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
double coordinate = getCoordinate(vertexIndex, axisIndex);
if (Double.isNaN(coordinate)) {
coordinate = nanValue;
}
destCoordinates[destOffset++] = coordinate;
}
}
return destOffset;
}
}
@Override
@SuppressWarnings("unchecked")
default <V extends Geometry> V deleteVertex(final int... vertexId) {
if (vertexId.length == 1) {
final int vertexIndex = vertexId[0];
return (V)deleteVertex(vertexIndex);
} else {
throw new IllegalArgumentException("Vertex id's for " + getGeometryType()
+ " must have length 1. " + Arrays.toString(vertexId));
}
}
default LineString deleteVertex(final int vertexIndex) {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot delete vertex for empty LineString");
} else {
final int vertexCount = getVertexCount();
if (vertexCount <= 2) {
throw new IllegalArgumentException("LineString must have a minimum of 2 vertices");
} else if (vertexIndex >= 0 && vertexIndex < vertexCount) {
final double[] coordinates = getCoordinates();
final int axisCount = getAxisCount();
final double[] newCoordinates = new double[axisCount * (vertexCount - 1)];
final int beforeLength = vertexIndex * axisCount;
if (vertexIndex > 0) {
System.arraycopy(coordinates, 0, newCoordinates, 0, beforeLength);
}
final int sourceIndex = (vertexIndex + 1) * axisCount;
final int length = (vertexCount - vertexIndex - 1) * axisCount;
System.arraycopy(coordinates, sourceIndex, newCoordinates, beforeLength, length);
return newLineString(newCoordinates);
} else {
throw new IllegalArgumentException("Vertex index must be between 0 and " + vertexCount);
}
}
}
@Override
default double distance(final double x, final double y) {
return distance(x, y, 0);
}
@Override
default double distance(final double x, final double y, final double terminateDistance) {
if (isEmpty()) {
return Double.POSITIVE_INFINITY;
} else {
double minDistance = Double.POSITIVE_INFINITY;
double x1 = getX(0);
double y1 = getY(0);
final int vertexCount = getVertexCount();
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
final double distance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y);
if (distance < minDistance) {
minDistance = distance;
if (minDistance == terminateDistance) {
return minDistance;
}
}
x1 = x2;
y1 = y2;
}
return minDistance;
}
}
@Override
default double distance(final Geometry geometry, final double terminateDistance) {
if (geometry instanceof Point) {
final Point point = (Point)geometry;
return distance(point, terminateDistance);
} else if (geometry instanceof LineString) {
final LineString line = (LineString)geometry;
return distance(line, terminateDistance);
} else {
return geometry.distance(this, terminateDistance);
}
}
default double distance(final LineString line, final double terminateDistance) {
if (isEmpty()) {
return Double.POSITIVE_INFINITY;
} else if (Property.isEmpty(line)) {
return Double.POSITIVE_INFINITY;
} else {
final GeometryFactory geometryFactory = getGeometryFactory();
double minDistance = Double.POSITIVE_INFINITY;
final int vertexCount1 = getVertexCount();
final int vertexCount2 = line.getVertexCount();
final CoordinatesOperation coordinatesOperation = getGeometryFactory()
.getCoordinatesOperation(geometryFactory);
double line2x1 = line.getX(0);
double line2y1 = line.getY(0);
double[] coordinates = null;
if (coordinatesOperation != null) {
coordinates = new double[] {
line2x1, line2y1
};
coordinatesOperation.perform(2, coordinates, 2, coordinates);
line2x1 = coordinates[X];
line2y1 = coordinates[Y];
}
for (int vertexIndex2 = 1; vertexIndex2 < vertexCount2; vertexIndex2++) {
double line2x2 = line.getX(vertexIndex2);
double line2y2 = line.getY(vertexIndex2);
if (coordinatesOperation != null) {
coordinates[X] = line2x2;
coordinates[Y] = line2y2;
coordinatesOperation.perform(2, coordinates, 2, coordinates);
line2x2 = coordinates[X];
line2y2 = coordinates[Y];
}
double line1x1 = getX(0);
double line1y1 = getY(0);
for (int vertexIndex1 = 1; vertexIndex1 < vertexCount1; vertexIndex1++) {
final double line1x2 = getX(vertexIndex1);
final double line1y2 = getY(vertexIndex1);
final double distance = LineSegmentUtil.distanceLineLine(line1x1, line1y1, line1x2,
line1y2, line2x1, line2y1, line2x2, line2y2);
if (distance < minDistance) {
minDistance = distance;
if (minDistance <= terminateDistance) {
return minDistance;
}
}
line1x1 = line1x2;
line1y1 = line1y2;
}
line2x1 = line2x2;
line2y1 = line2y2;
}
return minDistance;
}
}
default double distanceAlong(final Point point) {
if (isEmpty() && point.isEmpty()) {
return Double.MAX_VALUE;
} else {
double distanceAlongSegments = 0;
double closestDistance = Double.MAX_VALUE;
double distanceAlong = 0;
final GeometryFactory geometryFactory = getGeometryFactory();
final double x = point.getX();
final double y = point.getY();
final double resolutionXy = geometryFactory.getResolutionX();
final int vertexCount = getVertexCount();
if (vertexCount > 0) {
double x1 = getX(0);
double y1 = getY(0);
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
if (x1 == x && y1 == y) {
return distanceAlongSegments;
} else {
final double segmentLength = MathUtil.distance(x1, y1, x2, y2);
final double distance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y);
final double projectionFactor = LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x, y);
if (distance < resolutionXy) {
return distanceAlongSegments + MathUtil.distance(x1, y1, x, y);
} else if (distance < closestDistance) {
closestDistance = distance;
if (projectionFactor == 0) {
distanceAlong = distanceAlongSegments;
} else if (projectionFactor < 0) {
if (vertexIndex == 1) {
distanceAlong = segmentLength * projectionFactor;
} else {
distanceAlong = distanceAlongSegments;
}
} else if (projectionFactor >= 1) {
if (vertexIndex == vertexCount - 1) {
distanceAlong = distanceAlongSegments + segmentLength * projectionFactor;
} else {
distanceAlong = distanceAlongSegments + segmentLength;
}
} else {
distanceAlong = distanceAlongSegments + segmentLength * projectionFactor;
}
}
distanceAlongSegments += segmentLength;
}
x1 = x2;
y1 = y2;
}
}
return distanceAlong;
}
}
default double distanceVertex(final int index, final double x, final double y) {
final double x1 = getX(index);
final double y1 = getY(index);
return MathUtil.distance(x1, y1, x, y);
}
default double distanceVertex(final int index, final Point point) {
final GeometryFactory geometryFactory = getGeometryFactory();
final Point convertedPoint = point.convertPoint2d(geometryFactory);
final double x = convertedPoint.getX();
final double y = convertedPoint.getY();
return distanceVertex(index, x, y);
}
@Override
default boolean equals(final int axisCount, final Geometry geometry) {
if (geometry == this) {
return true;
} else if (geometry == null) {
return false;
} else if (axisCount < 2) {
throw new IllegalArgumentException("Axis Count must be >=2");
} else if (isEquivalentClass(geometry)) {
if (isEmpty()) {
return geometry.isEmpty();
} else if (geometry.isEmpty()) {
return false;
} else {
final LineString line = (LineString)geometry;
final int vertexCount = getVertexCount();
final int vertexCount2 = line.getVertexCount();
if (vertexCount == vertexCount2) {
for (int i = 0; i < vertexCount2; i++) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value1 = getCoordinate(i, axisIndex);
final double value2 = line.getCoordinate(i, axisIndex);
if (!Doubles.equal(value1, value2)) {
return false;
}
}
}
return true;
}
}
}
return false;
}
default boolean equals(final int axisCount, final int vertexIndex, final Point point) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value = getCoordinate(vertexIndex, axisIndex);
final double value2 = point.getCoordinate(axisIndex);
if (!Doubles.equal(value, value2)) {
return false;
}
}
return true;
}
@Override
default boolean equalsExact(final Geometry other, final double tolerance) {
if (!isEquivalentClass(other)) {
return false;
}
final LineString otherLineString = (LineString)other;
if (getVertexCount() != otherLineString.getVertexCount()) {
return false;
}
for (int i = 0; i < getVertexCount(); i++) {
final Point point = getPoint(i);
final Point otherPoint = otherLineString.getPoint(i);
if (!equal(point, otherPoint, tolerance)) {
return false;
}
}
return true;
}
default boolean equalsVertex(final int vertexIndex, final double... coordinates) {
if (isEmpty() || coordinates == null || coordinates.length < 2) {
return false;
} else {
for (int axisIndex = 0; axisIndex < coordinates.length; axisIndex++) {
final double coordinate = coordinates[axisIndex];
final double matchCoordinate = getCoordinate(vertexIndex, axisIndex);
if (!Doubles.equal(coordinate, matchCoordinate)) {
return false;
}
}
return true;
}
}
default boolean equalsVertex(final int vertexIndex, final double x, final double y) {
final double x1 = getX(vertexIndex);
if (Double.compare(x, x1) == 0) {
final double y1 = getY(vertexIndex);
if (Double.compare(y, y1) == 0) {
return true;
}
}
return false;
}
default boolean equalsVertex(final int axisCount, final int vertexIndex1,
final int vertexIndex2) {
if (isEmpty()) {
return false;
} else {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double coordinate1 = getCoordinate(vertexIndex1, axisIndex);
final double coordinate2 = getCoordinate(vertexIndex2, axisIndex);
if (!Doubles.equal(coordinate1, coordinate2)) {
return false;
}
}
return true;
}
}
default boolean equalsVertex(final int axisCount, final int vertexIndex, final LineString line2,
final int vertexIndex2) {
if (Property.isEmpty(line2)) {
return false;
} else {
final Vertex vertex2 = line2.getVertex(vertexIndex2);
return equalsVertex(axisCount, vertexIndex, vertex2);
}
}
default boolean equalsVertex(final int axisCount, final int vertexIndex, Point point) {
if (isEmpty() || Property.isEmpty(point)) {
return false;
} else {
final GeometryFactory geometryFactory = getGeometryFactory();
point = point.convertGeometry(geometryFactory, axisCount);
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double coordinate = point.getCoordinate(axisIndex);
final double matchCoordinate = getCoordinate(vertexIndex, axisIndex);
if (!Doubles.equal(coordinate, matchCoordinate)) {
return false;
}
}
return true;
}
}
default boolean equalsVertex(final int vertexIndex, final Point point) {
if (Property.isEmpty(point)) {
return false;
} else {
final int axisCount = point.getAxisCount();
return equalsVertex(axisCount, vertexIndex, point);
}
}
default boolean equalsVertex2d(final int vertexIndex, final double x, final double y) {
final double x1 = getX(vertexIndex);
if (x1 == x) {
final double y1 = getY(vertexIndex);
if (y1 == y) {
return true;
}
}
return false;
}
default boolean equalsVertex2d(final int vertexIndex1, final int vertexIndex2) {
final double x1 = getX(vertexIndex1);
final double x2 = getX(vertexIndex2);
if (x1 == x2) {
final double y1 = getY(vertexIndex1);
final double y2 = getY(vertexIndex2);
if (y1 == y2) {
return true;
}
}
return false;
}
@Override
default Pair<GeometryComponent, Double> findClosestGeometryComponent(final double x,
final double y) {
if (isEmpty()) {
return new Pair<>();
} else {
final GeometryFactory geometryFactory = getGeometryFactory();
boolean closestIsVertex = false;
int closestIndex = 0;
double x1 = getX(0);
double y1 = getY(0);
if (x == x1 && y == y1) {
final AbstractVertex closestVertex = getVertex(0);
return new Pair<>(closestVertex, 0.0);
} else {
double closestDistance = geometryFactory.makePrecise(0, MathUtil.distance(x, y, x1, y1));
final int vertexCount = getVertexCount();
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
if (x == x2 && y == y2) {
final AbstractVertex closestVertex = getVertex(vertexIndex);
return new Pair<>(closestVertex, 0.0);
} else {
final double toDistance = geometryFactory.makePrecise(0,
MathUtil.distance(x, y, x2, y2));
if (toDistance <= closestDistance) {
if (!closestIsVertex || toDistance < closestDistance) {
closestIndex = vertexIndex;
closestIsVertex = true;
closestDistance = toDistance;
}
}
final double segmentDistance = geometryFactory.makePrecise(0,
LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y));
if (segmentDistance == 0) {
final Segment closestSegment = getSegment(vertexIndex - 1);
return new Pair<>(closestSegment, 0.0);
} else if (segmentDistance < closestDistance) {
closestIsVertex = false;
closestIndex = vertexIndex - 1;
closestDistance = segmentDistance;
}
}
x1 = x2;
y1 = y2;
}
if (closestIsVertex) {
final Vertex closestVertex = getVertex(closestIndex);
return new Pair<>(closestVertex, closestDistance);
} else {
final Segment closestSegment = getSegment(closestIndex);
return new Pair<>(closestSegment, closestDistance);
}
}
}
}
@Override
default Pair<GeometryComponent, Double> findClosestGeometryComponent(Point point) {
final GeometryFactory geometryFactory = getGeometryFactory();
point = point.convertPoint2d(geometryFactory);
return findClosestGeometryComponent(point.getX(), point.getY());
}
/**
* Gets the boundary of this geometry. The boundary of a lineal geometry is
* always a zero-dimensional geometry (which may be empty).
*
* @return the boundary geometry
* @see Geometry#getBoundary
*/
@Override
default Geometry getBoundary() {
return new BoundaryOp(this).getBoundary();
}
@Override
default int getBoundaryDimension() {
if (isClosed()) {
return Dimension.FALSE;
}
return 0;
}
default ClockDirection getClockDirection() {
final int pointCount = getVertexCount() - 1;
// find highest point
double hiPtX = getX(0);
double hiPtY = getY(0);
int hiIndex = 0;
for (int i = 1; i <= pointCount; i++) {
final double x = getX(i);
final double y = getY(i);
if (y > hiPtY) {
hiPtX = x;
hiPtY = y;
hiIndex = i;
}
}
// find distinct point before highest point
int iPrev = hiIndex;
do {
iPrev = iPrev - 1;
if (iPrev < 0) {
iPrev = pointCount;
}
} while (equalsVertex(iPrev, hiPtX, hiPtY) && iPrev != hiIndex);
// find distinct point after highest point
int iNext = hiIndex;
do {
iNext = (iNext + 1) % pointCount;
} while (equalsVertex(iNext, hiPtX, hiPtY) && iNext != hiIndex);
/**
* This check catches cases where the ring contains an A-B-A configuration
* of points. This can happen if the ring does not contain 3 distinct points
* (including the case where the input array has fewer than 4 elements), or
* it contains coincident line segments.
*/
if (equalsVertex(iPrev, hiPtX, hiPtY) || equalsVertex(iNext, hiPtX, hiPtY)
|| equalsVertex2d(iPrev, iNext)) {
return ClockDirection.CLOCKWISE;
}
final int disc = orientationIndex(iPrev, hiIndex, iNext);
/**
* If disc is exactly 0, lines are collinear. There are two possible cases:
* (1) the lines lie along the x axis in opposite directions (2) the lines
* lie on top of one another (1) is handled by checking if next is left of
* prev ==> CCW (2) will never happen if the ring is valid, so don't check
* for it (Might want to assert this)
*/
boolean counterClockwise = false;
if (disc == 0) {
// poly is CCW if prev x is right of next x
final double prevX = getX(iPrev);
final double nextX = getX(iNext);
counterClockwise = prevX > nextX;
} else {
// if area is positive, points are ordered CCW
counterClockwise = disc > 0;
}
if (counterClockwise) {
return ClockDirection.COUNTER_CLOCKWISE;
} else {
return ClockDirection.CLOCKWISE;
}
}
double getCoordinate(int vertexIndex, final int axisIndex);
@Override
default double getCoordinate(final int partIndex, final int vertexIndex, final int axisIndex) {
if (partIndex == 0) {
return getCoordinate(vertexIndex, axisIndex);
} else {
return Double.NaN;
}
}
default double getCoordinateFast(final int vertexIndex, final int axisIndex) {
return getCoordinate(vertexIndex, axisIndex);
}
double[] getCoordinates();
default double[] getCoordinates(final int axisCount) {
if (axisCount == getAxisCount()) {
return getCoordinates();
} else if (isEmpty()) {
return new double[0];
} else {
final int vertexCount = getVertexCount();
final double[] coordinates = new double[axisCount * vertexCount];
int i = 0;
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double coordinate = getCoordinate(vertexIndex, axisIndex);
coordinates[i++] = coordinate;
}
}
return coordinates;
}
}
@Override
default DataType getDataType() {
return DataTypes.LINE_STRING;
}
@Override
default int getDimension() {
return 1;
}
default Point getFromPoint() {
if (isEmpty()) {
return null;
} else {
return getPoint(0);
}
}
default int getLastVertexIndex() {
return getVertexCount() - 1;
}
/**
* Returns the length of this <code>LineString</code>
*
* @return the length of the linestring
*/
@Override
default double getLength() {
final int vertexCount = getVertexCount();
if (vertexCount <= 1) {
return 0.0;
} else {
double len = 0.0;
double x0 = getX(0);
double y0 = getY(0);
for (int i = 1; i < vertexCount; i++) {
final double x1 = getX(i);
final double y1 = getY(i);
final double dx = x1 - x0;
final double dy = y1 - y0;
len += Math.sqrt(dx * dx + dy * dy);
x0 = x1;
y0 = y1;
}
return len;
}
}
@Override
default double getLength(final Unit<Length> unit) {
double length = 0;
final CoordinateSystem coordinateSystem = getCoordinateSystem();
if (coordinateSystem instanceof GeographicCoordinateSystem) {
final int vertexCount = getVertexCount();
if (vertexCount > 1) {
double lon0 = getX(0);
double lat0 = getY(0);
for (int i = 1; i < vertexCount; i++) {
final double lon1 = getX(i);
final double lat1 = getY(i);
length += GeographicCoordinateSystem.distanceMetres(lon0, lat0, lon1, lat1);
lon0 = lon1;
lat0 = lat1;
}
}
final Measure<Length> lengthMeasure = Measure.valueOf(length, SI.METRE);
length = lengthMeasure.doubleValue(unit);
} else if (coordinateSystem instanceof ProjectedCoordinateSystem) {
final ProjectedCoordinateSystem projectedCoordinateSystem = (ProjectedCoordinateSystem)coordinateSystem;
final Unit<Length> lengthUnit = projectedCoordinateSystem.getLengthUnit();
length = getLength();
final Measure<Length> lengthMeasure = Measure.valueOf(length, lengthUnit);
length = lengthMeasure.doubleValue(unit);
} else {
length = getLength();
}
return length;
}
@Override
default LineString getLineString(final int partIndex) {
if (partIndex == 0) {
return this;
} else {
return null;
}
}
/**
* Get the
* @author Paul Austin <paul.austin@revolsys.com>
* @param x
* @param y
* @param maxDistance
* @return
*/
default LineStringLocation getLineStringLocation(final double x, final double y) {
double minDistance = Double.POSITIVE_INFINITY;
int minSegmentIndex = 0;
double minFraction = -1.0;
double x1 = getX(0);
double y1 = getY(0);
if (x1 == x && y1 == y) {
minDistance = 0;
minFraction = 0;
} else {
final int vertexCount = getVertexCount();
for (int vertexIndex = 1; vertexIndex < vertexCount && minDistance > 0; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
if (x2 == x && y2 == y) {
minSegmentIndex = vertexIndex - 1;
minDistance = 0;
minFraction = 1.0;
} else {
final double segmentDistance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y);
if (segmentDistance < minDistance) {
minSegmentIndex = vertexIndex - 1;
minFraction = LineSegmentUtil.segmentFractionOnLine(x1, y1, x2, y2, x, y);
minDistance = segmentDistance;
}
x1 = x2;
y1 = y2;
}
}
}
if (minFraction >= 0) {
return new LineStringLocation(this, minSegmentIndex, minFraction, minDistance);
} else {
return null;
}
}
default double getM(final int vertexIndex) {
return getCoordinate(vertexIndex, 3);
}
/**
* Computes the Maximal Nearest Subline of a given linestring relative to
* another linestring. The Maximal Nearest Subline of A relative to B is the
* shortest subline of A which contains all the points of A which are the
* nearest points to the points in B. This effectively "trims" the ends of A
* which are not near to B.
* <p>
* An exact computation of the MNS would require computing a line Voronoi. For
* this reason, the algorithm used in this class is heuristic-based. It may
* compute a geometry which is shorter than the actual MNS.
*/
default LineString getMaximalNearestSubline(LineString line) {
final GeometryFactory geometryFactory = getGeometryFactory();
line = line.convertGeometry(geometryFactory);
LineStringLocation fromLocation = null;
LineStringLocation toLocation = null;
/**
* The basic strategy is to pick test points on B and find their nearest
* point on A. The interval containing these nearest points is approximately
* the MaximalNeareastSubline of A.
*/
{
final int vertexCount2 = line.getVertexCount();
for (int vertexIndex2 = 0; vertexIndex2 < vertexCount2; vertexIndex2++) {
final double x = line.getX(vertexIndex2);
final double y = line.getY(vertexIndex2);
final LineStringLocation location = getLineStringLocation(x, y);
if (location != null) {
if (fromLocation == null || location.compareTo(fromLocation) < 0) {
fromLocation = location;
}
if (toLocation == null || location.compareTo(toLocation) > 0) {
toLocation = location;
}
}
}
}
/**
* Heuristic #2: find the nearest point on B to all vertices of A and use
* those points of B as test points. For efficiency use only vertices of A
* outside current max interval.
*/
if (fromLocation != null) {
final int vertexCount1 = getVertexCount();
for (int vertexIndex1 = 0; vertexIndex1 < vertexCount1; vertexIndex1++) {
final double x = getX(vertexIndex1);
final double y = getY(vertexIndex1);
if (vertexIndex1 <= fromLocation.getSegmentIndex()
|| vertexIndex1 > toLocation.getSegmentIndex()) {
final LineStringLocation location2 = line.getLineStringLocation(x, y);
if (location2 != null) {
final Point point2 = location2.getPoint();
final double x2 = point2.getX();
final double y2 = point2.getY();
final LineStringLocation location = getLineStringLocation(x2, y2);
if (fromLocation == null || location.compareTo(fromLocation) < 0) {
fromLocation = location;
}
if (toLocation == null || location.compareTo(toLocation) > 0) {
toLocation = location;
}
}
}
}
}
return subLine(fromLocation, toLocation);
}
default PointLineStringMetrics getMetrics(Point point) {
final GeometryFactory geometryFactory = getGeometryFactory();
point = point.convertGeometry(geometryFactory, 2);
if (isEmpty() && point.isEmpty()) {
return PointLineStringMetrics.EMPTY;
} else {
final double x = point.getX();
final double y = point.getY();
double lineLength = 0;
double closestDistance = Double.MAX_VALUE;
double distanceAlong = 0;
Side side = null;
final double resolutionXy;
if (geometryFactory.isGeographics()) {
resolutionXy = 0.0000001;
} else {
resolutionXy = 0.001;
}
final int vertexCount = getVertexCount();
if (vertexCount > 0) {
double x1 = getX(0);
double y1 = getY(0);
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
final double distance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y);
final double segmentLength = MathUtil.distance(x1, y1, x2, y2);
final double projectionFactor = LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x, y);
final boolean isEnd = vertexIndex == vertexCount - 1;
if (vertexIndex == 1) {
if (isEnd || projectionFactor <= 1) {
if (distance < resolutionXy) {
side = null;
} else {
side = Side.getSide(x1, y1, x2, y2, x, y);
}
closestDistance = distance;
if (projectionFactor <= 1 || isEnd) {
distanceAlong = segmentLength * projectionFactor;
} else {
distanceAlong = segmentLength;
}
}
} else if (distance < closestDistance) {
if (isEnd || projectionFactor <= 1) {
closestDistance = distance;
if (distance == 0 || distance < resolutionXy) {
side = null;
} else {
side = Side.getSide(x1, y1, x2, y2, x, y);
}
// TODO handle intermediate cases right right hand bends in lines
if (projectionFactor == 0) {
distanceAlong = lineLength;
} else if (projectionFactor < 0) {
distanceAlong = lineLength;
} else if (projectionFactor >= 1) {
if (isEnd) {
distanceAlong = lineLength + segmentLength * projectionFactor;
} else {
distanceAlong = lineLength + segmentLength;
}
} else {
distanceAlong = lineLength + segmentLength * projectionFactor;
}
}
}
lineLength += segmentLength;
x1 = x2;
y1 = y2;
}
}
return new PointLineStringMetrics(lineLength, distanceAlong, closestDistance, side);
}
}
default int getMinVertexCount() {
return 2;
}
@Override
default Point getPoint() {
if (isEmpty()) {
return null;
} else {
return getPoint(0);
}
}
default Point getPoint(final End lineEnd) {
if (End.isFrom(lineEnd)) {
return getFromPoint();
} else {
return getToPoint();
}
}
default Point getPoint(int vertexIndex) {
if (isEmpty()) {
return null;
} else {
final int vertexCount = getVertexCount();
while (vertexIndex < 0) {
vertexIndex += vertexCount;
}
if (vertexIndex > vertexCount) {
return null;
} else {
final int axisCount = getAxisCount();
final double[] coordinates = new double[axisCount];
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
coordinates[axisIndex] = getCoordinate(vertexIndex, axisIndex);
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.point(coordinates);
}
}
}
@Override
default Point getPointWithin() {
final GeometryFactory geometryFactory = this.getGeometryFactory();
if (this.isEmpty()) {
return geometryFactory.point();
} else {
final int numPoints = getVertexCount();
if (numPoints > 1) {
final double totalLength = getLength();
final double midPointLength = totalLength / 2;
double currentLength = 0;
for (int i = 1; i < numPoints && currentLength < midPointLength; i++) {
final Point p1 = getPoint(i - 1);
final Point p2 = getPoint(i);
final double segmentLength = p1.distancePoint(p2);
if (segmentLength + currentLength >= midPointLength) {
final Point midPoint = LineSegmentUtil.project(geometryFactory, p1, p2,
(midPointLength - currentLength) / segmentLength);
return geometryFactory.point(midPoint);
}
currentLength += segmentLength;
}
return geometryFactory.point();
} else {
return this.getPoint(0);
}
}
}
@Override
default LineStringSegment getSegment(final int... segmentId) {
if (segmentId.length == 1) {
int segmentIndex = segmentId[0];
final int vertexCount = getSegmentCount();
if (segmentIndex < vertexCount) {
while (segmentIndex < 0) {
segmentIndex += vertexCount;
}
return new LineStringSegment(this, segmentIndex);
}
}
return null;
}
@Override
default int getSegmentCount() {
if (isEmpty()) {
return 0;
} else {
return getVertexCount() - 1;
}
}
default Side getSide(final double x, final double y) {
Side side = null;
if (!isEmpty()) {
final GeometryFactory geometryFactory = getGeometryFactory();
double closestDistance = Double.MAX_VALUE;
final double resolutionXy;
if (geometryFactory.isGeographics()) {
resolutionXy = 0.0000001;
} else {
resolutionXy = 0.001;
}
final int vertexCount = getVertexCount();
final int lastVertexIndex = vertexCount - 1;
if (vertexCount > 1) {
final double x1 = getX(0);
final double y1 = getY(0);
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
final double distance = LineSegmentUtil.distanceLinePoint(x1, y1, x2, y2, x, y);
final double projectionFactor = LineSegmentUtil.projectionFactor(x1, y1, x2, y2, x, y);
final boolean isEnd = vertexCount == lastVertexIndex;
if (vertexIndex == 1) {
if (isEnd || projectionFactor <= 1) {
if (distance < resolutionXy) {
side = null;
} else {
side = Side.getSide(x1, y1, x2, y2, x, y);
}
closestDistance = distance;
}
} else if (distance < closestDistance) {
if (isEnd || projectionFactor <= 1) {
closestDistance = distance;
if (distance == 0 || distance < resolutionXy) {
side = null;
} else {
side = Side.getSide(x1, y1, x2, y2, x, y);
}
}
}
}
}
}
return side;
}
default Side getSide(Point point) {
final GeometryFactory geometryFactory = getGeometryFactory();
point = point.convertGeometry(geometryFactory, 2);
if (point.isEmpty()) {
return null;
} else {
final double x = point.getX();
final double y = point.getY();
return getSide(x, y);
}
}
default Point getToPoint() {
if (isEmpty()) {
return null;
} else {
final int vertexCount = getVertexCount();
return getPoint(vertexCount - 1);
}
}
@Override
default AbstractVertex getToVertex(final int... vertexId) {
if (vertexId.length == 1) {
int vertexIndex = vertexId[0];
final int vertexCount = getVertexCount();
vertexIndex = vertexCount - vertexIndex - 1;
if (vertexIndex < vertexCount) {
while (vertexIndex < 0) {
vertexIndex += vertexCount;
}
return new LineStringVertex(this, vertexIndex);
}
}
return null;
}
@Override
default AbstractVertex getVertex(final int... vertexId) {
if (vertexId.length == 1) {
final int vertexIndex = vertexId[0];
return getVertex(vertexIndex);
} else {
return null;
}
}
default AbstractVertex getVertex(int vertexIndex) {
final int vertexCount = getVertexCount();
if (vertexIndex < vertexCount) {
while (vertexIndex < 0) {
vertexIndex += vertexCount;
}
return new LineStringVertex(this, vertexIndex);
} else {
return null;
}
}
default double getX(final int vertexIndex) {
return getCoordinate(vertexIndex, X);
}
default double getY(final int vertexIndex) {
return getCoordinate(vertexIndex, Y);
}
default double getZ(final int vertexIndex) {
return getCoordinate(vertexIndex, Z);
}
@Override
default boolean hasInvalidXyCoordinates() {
final int vertexCount = getVertexCount();
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
final double x = getX(vertexIndex);
if (!Double.isFinite(x)) {
return true;
}
final double y = getY(vertexIndex);
if (!Double.isFinite(y)) {
return true;
}
}
return false;
}
default boolean hasVertex(final double x, final double y) {
final int vertexCount = getVertexCount();
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
if (equalsVertex2d(vertexIndex, x, y)) {
return true;
}
}
return false;
}
default boolean hasVertex(final Point point) {
final int vertexCount = getVertexCount();
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
if (equalsVertex(2, vertexIndex, point)) {
return true;
}
}
return false;
}
@Override
default <G extends Geometry> G insertVertex(final Point newPoint, final int... vertexId) {
if (vertexId.length == 1) {
final int vertexIndex = vertexId[0];
return insertVertex(newPoint, vertexIndex);
} else {
throw new IllegalArgumentException("Geometry id's for " + getGeometryType()
+ " must have length 1. " + Arrays.toString(vertexId));
}
}
@SuppressWarnings("unchecked")
default <G extends Geometry> G insertVertex(Point newPoint, final int vertexIndex) {
final GeometryFactory geometryFactory = getGeometryFactory();
if (newPoint == null || newPoint.isEmpty()) {
return (G)this;
} else if (isEmpty()) {
return newPoint.convertGeometry(geometryFactory);
} else {
newPoint = newPoint.convertGeometry(geometryFactory);
final int vertexCount = getVertexCount();
final double[] coordinates = getCoordinates();
final int axisCount = getAxisCount();
final double[] newCoordinates = new double[axisCount * (vertexCount + 1)];
final int beforeLength = vertexIndex * axisCount;
System.arraycopy(coordinates, 0, newCoordinates, 0, beforeLength);
CoordinatesListUtil.setCoordinates(newCoordinates, axisCount, vertexIndex, newPoint);
final int afterSourceIndex = vertexIndex * axisCount;
final int afterNewIndex = (vertexIndex + 1) * axisCount;
final int afterLength = (vertexCount - vertexIndex) * axisCount;
System.arraycopy(coordinates, afterSourceIndex, newCoordinates, afterNewIndex, afterLength);
return (G)newLineString(newCoordinates);
}
}
@Override
default boolean intersects(final BoundingBox boundingBox) {
if (isEmpty() || boundingBox.isEmpty()) {
return false;
} else {
final double minX = boundingBox.getMinX();
final double maxX = boundingBox.getMaxX();
final double minY = boundingBox.getMinY();
final double maxY = boundingBox.getMaxY();
final int vertexCount = getVertexCount();
final GeometryFactory geometryFactory = boundingBox.getGeometryFactory();
final CoordinatesOperation coordinatesOperation = getCoordinatesOperation(geometryFactory);
if (coordinatesOperation == null) {
double previousX = getX(0);
double previousY = getY(0);
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x = getX(vertexIndex);
final double y = getY(vertexIndex);
if (BoundingBoxUtil.intersectsLine(minX, minY, maxX, maxY, //
previousX, previousY, x, y)) {
return true;
}
previousX = x;
previousY = y;
}
} else {
final double[] coordinates = new double[] {
getX(0), getY(0)
};
coordinatesOperation.perform(2, coordinates, 2, coordinates);
double previousX = coordinates[X];
double previousY = coordinates[Y];
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
coordinates[X] = getX(vertexIndex);
coordinates[Y] = getY(vertexIndex);
coordinatesOperation.perform(2, coordinates, 2, coordinates);
final double x = coordinates[X];
final double y = coordinates[Y];
if (BoundingBoxUtil.intersectsLine(minX, minY, maxX, maxY, //
previousX, previousY, x, y)) {
return true;
}
previousX = x;
previousY = y;
}
}
return false;
}
}
default boolean isClockwise() {
final ClockDirection clockDirection = getClockDirection();
return clockDirection.isClockwise();
}
@Override
default boolean isClosed() {
if (isEmpty()) {
return false;
} else {
final int lastIndex = getVertexCount() - 1;
final double x1 = getX(0);
final double xn = getX(lastIndex);
if (x1 == xn) {
final double y1 = getY(0);
final double yn = getY(lastIndex);
if (y1 == yn) {
return true;
}
}
}
return false;
}
/**
* Tests if a linestring is completely contained in the boundary of the target rectangle.
* @param boundingBox TODO
* @return true if the linestring is contained in the boundary
*/
@Override
default boolean isContainedInBoundary(final BoundingBox boundingBox) {
final int vertexCount = getVertexCount();
if (vertexCount > 0) {
final double minX = boundingBox.getMinX();
final double minY = boundingBox.getMinY();
final double maxX = boundingBox.getMaxX();
final double maxY = boundingBox.getMaxY();
double previousX = getX(0);
double previousY = getY(0);
boolean hadSegment = false;
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x = getX(vertexIndex);
final double y = getY(vertexIndex);
if (!(x == previousX && y == previousY)) {
hadSegment = true;
// we already know that the segment is contained in the rectangle envelope
if (previousX == x) {
if (previousX == minX || previousX == maxX) {
return true;
}
} else if (previousY == y) {
if (previousY == minY || previousY == maxY) {
return true;
}
}
previousX = x;
previousY = y;
}
}
if (!hadSegment) {
if (previousX == minX || previousX == maxX || previousY == minY || previousY == maxY) {
return true;
}
}
}
return false;
}
default boolean isCounterClockwise() {
final ClockDirection clockDirection = getClockDirection();
return clockDirection.isCounterClockwise();
}
@Override
default boolean isEmpty() {
return getVertexCount() == 0;
}
@Override
default boolean isEquivalentClass(final Geometry other) {
return other instanceof LineString;
}
default boolean isLeft(final Point point) {
final double x = point.getX();
final double y = point.getY();
final int vertexCount = getVertexCount();
if (vertexCount > 1) {
double x1 = getX(0);
double y1 = getY(0);
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final double x2 = getX(vertexIndex);
final double y2 = getY(vertexIndex);
if (!crosses(new LineSegmentDouble(2, x1, y1, x, y))
&& !crosses(new LineSegmentDouble(2, x2, y2, x, y))) {
final int orientation = LineSegmentUtil.orientationIndex(x1, y1, x2, y2, x, y);
if (orientation == 1) {
return true;
} else {
return false;
}
}
x1 = x2;
y1 = y2;
}
return true;
} else {
return false;
}
}
/**
* Tests whether a point lies on the line segments defined by a list of
* coordinates.
*
* @return true if the point is a vertex of the line or lies in the interior
* of a line segment in the linestring
*/
default boolean isOnLine(final Point point) {
final double x = point.getX();
final double y = point.getY();
final LineIntersector lineIntersector = new RobustLineIntersector();
double x1 = getX(0);
double y1 = getY(0);
final int vertexCount = getVertexCount();
for (int i = 1; i < vertexCount; i++) {
final double x2 = getX(i);
final double y2 = getY(i);
if (lineIntersector.computeIntersection(x, y, x1, y1, x2, y2)) {
return true;
} else {
x1 = x2;
y1 = y2;
}
}
return false;
}
default boolean isPointInRing(final Point point) {
return RayCrossingCounter.locatePointInRing(point, this) != Location.EXTERIOR;
}
default boolean isRing() {
return isClosed() && isSimple();
}
@Override
default Iterable<LineString> lineStrings() {
return Collections.singletonList(this);
}
@Override
default Location locate(final Point point) {
// bounding-box check
if (point.intersects(getBoundingBox())) {
if (!isClosed()) {
if (equals(2, 0, point) || equals(2, getVertexCount() - 1, point)) {
return Location.BOUNDARY;
}
}
if (isOnLine(point)) {
return Location.INTERIOR;
}
}
return Location.EXTERIOR;
}
/**
* Merge two lines that share common coordinates at either the start or end.
* If the lines touch only at their start coordinates, the line2 will be
* reversed and joined before the start of line1. If the two lines touch only
* at their end coordinates, the line2 will be reversed and joined after the
* end of line1.
*
* @param line1 The first line.
* @param line2 The second line.
* @return The new line string
*/
default LineString merge(final LineString line2) {
final int axisCount = Math.max(getAxisCount(), line2.getAxisCount());
final int vertexCount1 = getVertexCount();
final int vertexCount2 = line2.getVertexCount();
final int vertexCount = vertexCount1 + vertexCount2 - 1;
final double[] coordinates = new double[vertexCount * axisCount];
int newVertexCount = 0;
final Point line1From = getVertex(0);
final Point line1To = getVertex(getVertexCount() - 1);
final Point line2From = line2.getVertex(0);
final Point line2To = line2.getVertex(line2.getVertexCount() - 1);
if (line1From.equals(2, line2To)) {
newVertexCount = CoordinatesListUtil.append(axisCount, line2, 0, coordinates, 0,
vertexCount2);
newVertexCount = CoordinatesListUtil.append(axisCount, this, 1, coordinates, newVertexCount,
vertexCount1 - 1);
} else if (line2From.equals(2, line1To)) {
newVertexCount = CoordinatesListUtil.append(axisCount, this, 0, coordinates, 0, vertexCount1);
newVertexCount = CoordinatesListUtil.append(axisCount, line2, 1, coordinates, newVertexCount,
vertexCount2 - 1);
} else if (line1From.equals(2, line2From)) {
newVertexCount = CoordinatesListUtil.appendReverse(axisCount, line2, 0, coordinates, 0,
vertexCount2);
newVertexCount = CoordinatesListUtil.append(axisCount, this, 1, coordinates, newVertexCount,
vertexCount1 - 1);
} else if (line1To.equals(2, line2To)) {
newVertexCount = CoordinatesListUtil.append(axisCount, this, 0, coordinates, newVertexCount,
vertexCount1);
newVertexCount = CoordinatesListUtil.appendReverse(axisCount, line2, 1, coordinates,
newVertexCount, vertexCount2 - 1);
} else {
throw new IllegalArgumentException("lines don't touch\n" + this + "\n" + line2);
}
final LineString newLine = newLineString(newVertexCount, coordinates);
return newLine;
}
default LineString merge(final Point point, final LineString line2) {
if (isEmpty() || Property.isEmpty(line2) || Property.isEmpty(point)) {
return newLineStringEmpty();
} else {
final int axisCount = Math.max(getAxisCount(), line2.getAxisCount());
final int vertexCount1 = getVertexCount();
final int vertexCount2 = line2.getVertexCount();
final int vertexCount = vertexCount1 + vertexCount2 - 1;
final double[] coordinates = new double[vertexCount * axisCount];
int newVertexCount = 0;
final Point line1From = getVertex(0);
final Point line1To = getVertex(getVertexCount() - 1);
final Point line2From = line2.getVertex(0);
final Point line2To = line2.getVertex(line2.getVertexCount() - 1);
if (line1To.equals(2, line2From) && line1To.equals(2, point)) {
// -->*--> = ---->
newVertexCount = CoordinatesListUtil.append(axisCount, this, 0, coordinates, 0,
vertexCount1);
newVertexCount = CoordinatesListUtil.append(axisCount, line2, 1, coordinates,
newVertexCount, vertexCount2 - 1);
} else if (line1From.equals(2, line2To) && line1From.equals(2, point)) {
// <--*<-- = <----
newVertexCount = CoordinatesListUtil.append(axisCount, line2, 0, coordinates, 0,
vertexCount2);
newVertexCount = CoordinatesListUtil.append(axisCount, this, 1, coordinates, newVertexCount,
vertexCount1 - 1);
} else if (line1From.equals(2, line2From) && line1From.equals(2, point)) {
// <--*--> = <----
newVertexCount = CoordinatesListUtil.appendReverse(axisCount, line2, 0, coordinates, 0,
vertexCount2);
newVertexCount = CoordinatesListUtil.append(axisCount, this, 1, coordinates, newVertexCount,
vertexCount1 - 1);
} else if (line1To.equals(2, line2To) && line1To.equals(2, point)) {
// -->*<-- = ---->
newVertexCount = CoordinatesListUtil.append(axisCount, this, 0, coordinates, newVertexCount,
vertexCount1);
newVertexCount = CoordinatesListUtil.appendReverse(axisCount, line2, 1, coordinates,
newVertexCount, vertexCount2 - 1);
} else {
throw new IllegalArgumentException("lines don't touch\n" + this + "\n" + line2);
}
final LineString newLine = newLineString(newVertexCount, coordinates);
return newLine;
}
}
@Override
default Lineal mergeLines() {
return this;
}
@Override
default LineString move(final double... deltas) {
if (deltas == null || isEmpty()) {
return this;
} else {
final double[] coordinates = moveCoordinates(deltas);
return newLineString(coordinates);
}
}
default double[] moveCoordinates(final double... deltas) {
final double[] coordinates = getCoordinates();
final int vertexCount = getVertexCount();
final int axisCount = getAxisCount();
final int deltaCount = Math.min(deltas.length, getAxisCount());
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
for (int axisIndex = 0; axisIndex < deltaCount; axisIndex++) {
coordinates[vertexIndex * axisCount + axisIndex] += deltas[axisIndex];
}
}
return coordinates;
}
@Override
@SuppressWarnings("unchecked")
default <V extends Geometry> V moveVertex(final Point newPoint, final int... vertexId) {
if (vertexId.length == 1) {
final int vertexIndex = vertexId[0];
return (V)moveVertex(newPoint, vertexIndex);
} else {
throw new IllegalArgumentException("Vertex id's for " + getGeometryType()
+ " must have length 1. " + Arrays.toString(vertexId));
}
}
default LineString moveVertex(Point newPoint, final int vertexIndex) {
if (newPoint == null || newPoint.isEmpty()) {
return this;
} else if (isEmpty()) {
throw new IllegalArgumentException("Cannot move vertex for empty LineString");
} else {
final int vertexCount = getVertexCount();
if (vertexIndex >= 0 && vertexIndex < vertexCount) {
final GeometryFactory geometryFactory = getGeometryFactory();
newPoint = newPoint.convertGeometry(geometryFactory);
final double[] coordinates = getCoordinates();
final int axisCount = getAxisCount();
CoordinatesListUtil.setCoordinates(coordinates, axisCount, vertexIndex, newPoint);
return newLineString(coordinates);
} else {
throw new IllegalArgumentException("Vertex index must be between 0 and " + vertexCount);
}
}
}
@Override
default LineString newGeometry(final GeometryFactory geometryFactory) {
return geometryFactory.lineString(this);
}
@Override
default LineStringEditor newGeometryEditor() {
return new LineStringEditor(this);
}
@Override
default LineStringEditor newGeometryEditor(final int axisCount) {
final LineStringEditor geometryEditor = newGeometryEditor();
geometryEditor.setAxisCount(axisCount);
return geometryEditor;
}
/**
* Create a new {@link LinearRing} of this {@link LineString} using this geometry's geometry factory.
*
* @return The new linear ring.
*/
default LinearRing newLinearRing() {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.linearRing(this);
}
default LineString newLineString() {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.lineString(this);
}
default LineString newLineString(final double... coordinates) {
final GeometryFactory geometryFactory = getGeometryFactory();
final int axisCount = getAxisCount();
final int vertexCount = coordinates.length / axisCount;
return newLineString(geometryFactory, axisCount, vertexCount, coordinates);
}
default LineString newLineString(final GeometryFactory geometryFactory) {
return geometryFactory.lineString(this);
}
default LineString newLineString(final GeometryFactory geometryFactory, final int axisCount,
final int vertexCount, final double... coordinates) {
final GeometryFactory geometryFactoryAxisCount = geometryFactory.convertAxisCount(axisCount);
return geometryFactoryAxisCount.lineString(axisCount, vertexCount, coordinates);
}
default LineString newLineString(final int vertexCount, final double... coordinates) {
final GeometryFactory geometryFactory = getGeometryFactory();
final int axisCount = getAxisCount();
return newLineString(geometryFactory, axisCount, vertexCount, coordinates);
}
/**
* Create a new {@link LineString} of this {@link LineString} using this geometry's geometry factory.
*
* @return The new line string.
*/
default LineString newLineStringEmpty() {
final GeometryFactory geometryFactory = getGeometryFactory();
return newLineStringEmpty(geometryFactory);
}
default LineString newLineStringEmpty(final GeometryFactory geometryFactory) {
return geometryFactory.lineString();
}
@SuppressWarnings("unchecked")
@Override
default <G> G newUsingGeometryFactory(final GeometryFactory factory) {
if (factory == getGeometryFactory()) {
return (G)this;
} else if (isEmpty()) {
return (G)newLineStringEmpty();
} else {
final double[] coordinates = getCoordinates();
return (G)newLineString(coordinates);
}
}
@Override
@SuppressWarnings("unchecked")
default <G extends Geometry> G newValidGeometry() {
if (isEmpty()) {
return (G)this;
} else if (isValid()) {
return (G)normalize();
} else {
final LineMerger lines = new LineMerger(this);
return (G)lines.getLineal();
}
}
/**
* Normalizes a LineString. A normalized linestring has the first point which
* is not equal to it's reflected point less than the reflected point.
*/
@Override
default LineString normalize() {
final int vertexCount = getVertexCount();
for (int i = 0; i < vertexCount / 2; i++) {
final int j = vertexCount - 1 - i;
final Vertex point1 = getVertex(i);
final Vertex point2 = getVertex(j);
// skip equal points on both ends
if (!point1.equals(2, point2)) {
if (point1.compareTo(point2) > 0) {
return reverse();
}
return this;
}
}
return this;
}
default int orientationIndex(final int index1, final int index2, final int index) {
final double x1 = getX(index1);
final double y1 = getY(index1);
final double x2 = getX(index2);
final double y2 = getY(index2);
final double x = getX(index);
final double y = getY(index);
return CoordinatesListUtil.orientationIndex(x1, y1, x2, y2, x, y);
}
default Iterable<Point> points() {
final List<Point> points = new ArrayList<>();
for (int i = 0; i < getVertexCount(); i++) {
final Point point = getPoint(i);
points.add(point);
}
return points;
}
@Override
@Deprecated
default LineString prepare() {
return new PreparedLineString(this);
}
@Override
default LineString removeDuplicatePoints() {
if (isEmpty()) {
return this;
} else {
final int vertexCount = getVertexCount();
if (vertexCount < 2) {
return this;
} else {
final int axisCount = getAxisCount();
final double[] coordinates = new double[vertexCount * axisCount];
double previousX = getX(0);
double previousY = getY(0);
CoordinatesListUtil.setCoordinates(coordinates, axisCount, 0, this, 0);
int j = 1;
for (int i = 1; i < vertexCount; i++) {
final double x = getX(i);
final double y = getY(i);
if (x != previousX || y != previousY) {
CoordinatesListUtil.setCoordinates(coordinates, axisCount, j++, this, i);
}
previousX = x;
previousY = y;
}
if (j < 2) {
return newLineStringEmpty();
} else {
return newLineString(j, coordinates);
}
}
}
}
/**
* Creates a {@link LineString} whose coordinates are in the reverse order of
* this objects
*
* @return a {@link LineString} with coordinates in the reverse order
*/
@Override
default LineString reverse() {
final int vertexCount = getVertexCount();
final int axisCount = getAxisCount();
final double[] coordinates = new double[vertexCount * axisCount];
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final int coordinateIndex = (vertexCount - 1 - vertexIndex) * axisCount + axisIndex;
coordinates[coordinateIndex] = getCoordinate(vertexIndex, axisIndex);
}
}
final LineString reverseLine = newLineString(coordinates);
return reverseLine;
}
@Override
default Iterable<Segment> segments() {
return new LineStringSegment(this, -1);
}
default List<LineString> split(final Iterable<LineStringLocation> splitLocations) {
final Set<LineStringLocation> locations = new TreeSet<>();
for (final LineStringLocation location : splitLocations) {
if (location.getLine() == this) {
if (location.isFromVertex()) {
// Don't split at the start
} else if (location.isToVertex()) {
// Don't split at the end
} else {
locations.add(location);
}
}
}
if (locations.isEmpty()) {
return Collections.singletonList(this);
} else {
final List<LineString> newLines = new ArrayList<>();
LineStringLocation previousLocation = null;
for (final LineStringLocation location : locations) {
final LineString newLine = subLine(previousLocation, location);
if (!newLine.isEmpty()) {
newLines.add(newLine);
}
previousLocation = location;
}
final LineString newLine = subLine(previousLocation, null);
if (!newLine.isEmpty()) {
newLines.add(newLine);
}
return newLines;
}
}
default List<LineString> split(final LineStringLocation location) {
if (location == null || location.isFromVertex() || location.isToVertex()) {
return Collections.singletonList(this);
} else {
final List<LineString> newLines = new ArrayList<>(2);
final LineString newLine1 = subLine(null, location);
if (!newLine1.isEmpty()) {
newLines.add(newLine1);
}
final LineString newLine2 = subLine(location, null);
if (!newLine2.isEmpty()) {
newLines.add(newLine2);
}
return newLines;
}
}
default List<LineString> split(Point point) {
final GeometryFactory geometryFactory = getGeometryFactory();
point = point.convertGeometry(geometryFactory);
final double x = point.getX();
final double y = point.getY();
final Pair<GeometryComponent, Double> result = findClosestGeometryComponent(x, y);
if (result.isEmpty()) {
return Collections.<LineString> singletonList(this);
} else {
final int vertexCount = getVertexCount();
final GeometryComponent geometryComponent = result.getValue1();
final double distance = result.getValue2();
if (geometryComponent instanceof Vertex) {
final Vertex vertex = (Vertex)geometryComponent;
final int vertexIndex = vertex.getVertexIndex();
if (distance == 0) {
if (vertexIndex <= 0 || vertexIndex >= vertexCount - 1) {
return Collections.<LineString> singletonList(this);
} else {
final LineString line1 = subLine(vertexIndex + 1);
final LineString line2 = subLine(vertexIndex, vertexCount - vertexIndex);
return Arrays.asList(line1, line2);
}
} else {
final LineString line1 = subLine(vertexIndex + 1, point);
final LineString line2 = subLine(point, vertexIndex, vertexCount - vertexIndex, null);
return Arrays.asList(line1, line2);
}
} else if (geometryComponent instanceof Segment) {
final Segment segment = (Segment)geometryComponent;
final int segmentIndex = segment.getSegmentIndex();
final LineString line1 = subLine(segmentIndex + 1, point);
final LineString line2 = subLine(point, segmentIndex + 1, vertexCount - segmentIndex - 1,
null);
return Arrays.asList(line1, line2);
} else {
return Collections.<LineString> singletonList(this);
}
}
}
default LineString subLine(final int vertexCount) {
return subLine(null, 0, vertexCount, null);
}
default LineString subLine(final int fromVertexIndex, final int vertexCount) {
return subLine(null, fromVertexIndex, vertexCount, null);
}
default LineString subLine(final int vertexCount, final Point toPoint) {
return subLine(null, 0, vertexCount, toPoint);
}
default LineString subLine(final LineStringLocation fromLocation,
final LineStringLocation toLocation) {
final GeometryFactory geometryFactory = getGeometryFactory();
final LineStringDoubleBuilder lineBuilder = new LineStringDoubleBuilder(geometryFactory);
int vertexIndexFrom;
if (fromLocation == null || fromLocation.getLine() != this) {
vertexIndexFrom = 0;
} else {
vertexIndexFrom = fromLocation.getSegmentIndex();
if (fromLocation.getSegmentFraction() > 0.0) {
vertexIndexFrom += 1;
}
if (!fromLocation.isVertex()) {
final Point point = fromLocation.getPoint();
lineBuilder.appendVertex(point, false);
}
}
Point toPoint;
int vertexIndexTo;
if (toLocation == null) {
vertexIndexTo = getVertexCount() - 1;
toPoint = null;
} else {
vertexIndexTo = toLocation.getSegmentIndex();
if (toLocation.getSegmentFraction() >= 1.0) {
vertexIndexTo += 1;
}
if (toLocation.isVertex()) {
toPoint = null;
} else {
toPoint = toLocation.getPoint();
}
}
for (int vertexIndex = vertexIndexFrom; vertexIndex <= vertexIndexTo; vertexIndex++) {
final Point point = getPoint(vertexIndex);
lineBuilder.appendVertex(point, false);
}
if (toPoint != null) {
lineBuilder.appendVertex(toPoint, false);
}
if (lineBuilder.getVertexCount() < 2) {
return lineBuilder.newLineStringEmpty();
} else {
return lineBuilder.newLineString();
}
}
default LineString subLine(final Point fromPoint, final int fromVertexIndex, int vertexCount,
final Point toPoint) {
if (fromVertexIndex + vertexCount > getVertexCount()) {
vertexCount = getVertexCount() - fromVertexIndex;
}
int newVertexCount = vertexCount;
final boolean hasFromPoint = fromPoint != null && !fromPoint.isEmpty();
if (hasFromPoint) {
newVertexCount++;
}
final boolean hasToPoint = toPoint != null && !toPoint.isEmpty();
if (hasToPoint) {
newVertexCount++;
}
if (newVertexCount < 2) {
return newLineStringEmpty();
} else {
final int axisCount = getAxisCount();
final double[] coordinates = new double[newVertexCount * axisCount];
int vertexIndex = 0;
if (hasFromPoint) {
CoordinatesListUtil.setCoordinates(coordinates, axisCount, vertexIndex++, fromPoint);
}
CoordinatesListUtil.setCoordinates(coordinates, axisCount, vertexIndex, this, fromVertexIndex,
vertexCount);
vertexIndex += vertexCount;
if (hasToPoint) {
CoordinatesListUtil.setCoordinates(coordinates, axisCount, vertexIndex++, toPoint);
}
final LineString newLine = newLineString(coordinates);
return newLine;
}
}
@Override
@SuppressWarnings("unchecked")
default <G extends Geometry> G toClockwise() {
if (isClockwise()) {
return (G)this;
} else {
return (G)this.reverse();
}
}
@Override
@SuppressWarnings("unchecked")
default <G extends Geometry> G toCounterClockwise() {
if (isClockwise()) {
return (G)this.reverse();
} else {
return (G)this;
}
}
/**
* Get the end of this line that touches an end of the other line. If the
* lines don't touch at the ends then null will be returned.
*
* @return The end that touches.
*/
default End touchingEnd(final LineString line) {
if (isEmpty() || Property.isEmpty(line)) {
return null;
} else {
for (final End end : End.VALUES) {
final Point point = line.getPoint(end);
final End touchingEnd = touchingEnd(point);
if (touchingEnd != null) {
return touchingEnd;
}
}
return null;
}
}
/**
* Get the end of this line that touches the other point. If the point and
* line don't touch at the end then null will be returned.
*
* @return The end that touches.
*/
default End touchingEnd(final Point point) {
if (isEmpty() || Property.isEmpty(point)) {
return null;
} else if (equalsVertex(0, point)) {
return End.FROM;
} else if (equalsVertex(getVertexCount() - 1, point)) {
return End.TO;
} else {
return null;
}
}
/**
* Get the end of this line that touches an end of the other line. If the
* lines don't touch at the ends then null will be returned.
*
* @return An array with the end of this line and then end of the other that
* touches, or null if they don't touch.
*/
default End[] touchingEnds(final LineString line) {
if (isEmpty() || Property.isEmpty(line)) {
return null;
} else {
for (final End end : End.VALUES) {
final Point point = line.getPoint(end);
final End touchingEnd = touchingEnd(point);
if (touchingEnd != null) {
return new End[] {
touchingEnd, end
};
}
}
return null;
}
}
@Override
default LineStringVertex vertices() {
return new LineStringVertex(this, -1);
}
}