package com.revolsys.geometry.model.coordinates.list;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.revolsys.geometry.algorithm.RobustDeterminant;
import com.revolsys.geometry.graph.Edge;
import com.revolsys.geometry.graph.Graph;
import com.revolsys.geometry.graph.Node;
import com.revolsys.geometry.graph.linestring.LineStringGraph;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.coordinates.LineSegmentUtil;
import com.revolsys.geometry.model.impl.LineStringDouble;
import com.revolsys.geometry.model.segment.LineSegment;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.math.Angle;
import com.revolsys.util.number.Doubles;
public class CoordinatesListUtil {
public static final String COORDINATE_DISTANCE = "coordinateDistance";
public static final String COORDINATE_INDEX = "coordinateIndex";
public static final String SEGMENT_DISTANCE = "segmentDistance";
public static final String SEGMENT_INDEX = "segmentIndex";
public static double angleToNext(final LineString points, final int i) {
final double x1 = points.getX(i);
final double y1 = points.getY(i);
double x2;
double y2;
int j = i + 1;
do {
x2 = points.getX(j);
y2 = points.getY(j);
j++;
} while (x1 == x2 && y1 == y2 && j < points.getVertexCount());
final double angle = Angle.angle2d(x1, y1, x2, y2);
return angle;
}
public static double angleToPrevious(final LineString points, final int i) {
if (i > 0) {
final double x1 = points.getX(i);
final double y1 = points.getY(i);
double x2;
double y2;
int j = i - 1;
do {
x2 = points.getX(j);
y2 = points.getY(j);
j--;
} while (x1 == x2 && y1 == y2 && j > -1);
final double angle = Angle.angle2d(x1, y1, x2, y2);
return angle;
} else {
throw new IllegalArgumentException("Index must be > 0 to calculate previous angle");
}
}
public static int append(final int axisCount, final LineString source, final int sourceIndex,
final double[] targetCoordinates, final int targetIndex, final int vertexCount) {
int coordIndex = targetIndex;
double previousX;
double previousY;
if (targetIndex == 0) {
previousX = Double.NaN;
previousY = Double.NaN;
} else {
previousX = targetCoordinates[(targetIndex - 1) * axisCount];
previousY = targetCoordinates[(targetIndex - 1) * axisCount + 1];
}
int coordinateIndex = coordIndex * axisCount;
for (int i = 0; i < vertexCount; i++) {
final int sourceVertexIndex = sourceIndex + i;
final double x = source.getX(sourceVertexIndex);
final double y = source.getY(sourceVertexIndex);
if (x != previousX || y != previousY) {
targetCoordinates[coordinateIndex++] = x;
targetCoordinates[coordinateIndex++] = y;
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
final double coordinate = source.getCoordinate(sourceVertexIndex, axisIndex);
targetCoordinates[coordinateIndex++] = coordinate;
}
coordIndex++;
}
previousX = x;
previousY = y;
}
return coordIndex;
}
public static int appendReverse(final int axisCount, final LineString source,
final int sourceStartIndex, final double[] targetCoordinates, final int targetStartIndex,
final int vertexCount) {
int coordIndex = targetStartIndex;
final int sourceVertexCount = source.getVertexCount();
double previousX;
double previousY;
if (targetStartIndex == 0) {
previousX = Double.NaN;
previousY = Double.NaN;
} else {
previousX = targetCoordinates[(targetStartIndex - 1) * axisCount];
previousY = targetCoordinates[(targetStartIndex - 1) * axisCount + 1];
}
int coordinateIndex = coordIndex * axisCount;
int sourceIndex = sourceVertexCount - 1 - sourceStartIndex;
for (int i = 0; i < vertexCount; i++) {
final double x = source.getX(sourceIndex);
final double y = source.getY(sourceIndex);
if (x != previousX || y != previousY) {
targetCoordinates[coordinateIndex++] = x;
targetCoordinates[coordinateIndex++] = y;
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
final double coordinate = source.getCoordinate(sourceIndex, axisIndex);
targetCoordinates[coordinateIndex++] = coordinate;
}
coordIndex++;
}
sourceIndex--;
previousX = x;
previousY = y;
}
return coordIndex;
}
/**
* <p>
* Check within a given tolerance that the LINESTRING defined by points2 is
* contained within the points1.
* </p>
* <p>
* The algorithm is as follows:
* <ol>
* <li>Find all coordinates from points2 that are within the tolerance from
* the line segments of points1.</li>
* <li>Find all coordinates from points1 that are within the tolerance from
* the line segments of points2.</li>
* <li>Split all the line sgements of points1 that were matched in step 1.</li>
* <li>Split all the line sgements of points2 that were matched in step 2.</li>
* <li>Line is contained if all line segments from point2 have matching lines
* in points1.</li>
* </ol>
*
* @param points1
* @param points2
* @param tolerance
* @return
*/
public static boolean containsWithinTolerance(final LineString points1, final LineString points2,
final double tolerance) {
final LineStringGraph graph1 = new LineStringGraph(points1);
final LineStringGraph graph2 = new LineStringGraph(points2);
graph1.forEachNode((node) -> movePointsWithinTolerance(null, graph2, tolerance, node));
graph1.forEachNode((node) -> movePointsWithinTolerance(null, graph1, tolerance, node));
final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge1 = graph1
.getPointsOnEdges(graph2, tolerance);
final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge2 = graph2
.getPointsOnEdges(graph1, tolerance);
graph1.splitEdges(pointsOnEdge1);
graph2.splitEdges(pointsOnEdge2);
for (final Edge<LineSegment> edge : graph2.getEdges()) {
final Node<LineSegment> fromNode = edge.getFromNode();
final Node<LineSegment> toNode = edge.getToNode();
if (!graph1.hasEdgeBetween(fromNode, toNode)) {
return false;
}
}
return true;
}
public static boolean containsXy(final double[] coordinates, final int vertexCount,
final int axisCount, final double x, final double y) {
for (int vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++) {
final double x1 = coordinates[vertexIndex * axisCount];
if (Doubles.equal(x, x1)) {
final double y1 = coordinates[vertexIndex * axisCount + 1];
if (Doubles.equal(y, y1)) {
return true;
}
}
}
return false;
}
public static Point[] getPointArray(final Geometry geometry) {
return getPointArray(geometry, geometry.getVertexCount());
}
public static Point[] getPointArray(final Geometry geometry, final int vertexCount) {
final Point[] points = new Point[vertexCount];
int i = 0;
for (final Vertex vertex : geometry.vertices()) {
if (i > vertexCount) {
break;
}
points[i++] = vertex.newPoint();
}
return points;
}
public static List<LineString> intersection(final GeometryFactory geometryFactory,
final LineString points1, final LineString points2, final double maxDistance) {
final LineStringGraph graph1 = new LineStringGraph(points1);
graph1.setPrecisionModel(geometryFactory);
final LineStringGraph graph2 = new LineStringGraph(points2);
graph2.setPrecisionModel(geometryFactory);
final Map<Point, Point> movedNodes = new HashMap<>();
graph1.forEachNode((node) -> movePointsWithinTolerance(movedNodes, graph2, maxDistance, node));
graph2.forEachNode((node) -> movePointsWithinTolerance(movedNodes, graph1, maxDistance, node));
final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge1 = graph1
.getPointsOnEdges(graph2, maxDistance);
final Map<Edge<LineSegment>, List<Node<LineSegment>>> pointsOnEdge2 = graph2
.getPointsOnEdges(graph1, maxDistance);
graph1.splitEdges(pointsOnEdge1);
graph2.splitEdges(pointsOnEdge2);
Point startPoint = points1.getPoint(0);
if (movedNodes.containsKey(startPoint)) {
startPoint = movedNodes.get(startPoint);
}
Point endPoint = points1.getPoint(points1.getVertexCount() - 1);
if (movedNodes.containsKey(endPoint)) {
endPoint = movedNodes.get(endPoint);
}
final List<LineString> intersections = new ArrayList<>();
final List<Point> currentCoordinates = new ArrayList<>();
Node<LineSegment> previousNode = graph1.getNode(startPoint);
do {
final List<Edge<LineSegment>> outEdges = previousNode.getOutEdges();
if (outEdges.isEmpty()) {
previousNode = null;
} else if (outEdges.size() > 1) {
throw new IllegalArgumentException("Cannot handle overlaps\n" + points1 + "\n " + points2);
} else {
final Edge<LineSegment> edge = outEdges.get(0);
final LineSegment line = edge.getObject();
final Node<LineSegment> nextNode = edge.getToNode();
if (graph2.hasEdgeBetween(previousNode, nextNode)) {
if (currentCoordinates.size() == 0) {
currentCoordinates.add(line.getPoint(0));
}
currentCoordinates.add(line.getPoint(1));
} else {
if (currentCoordinates.size() > 0) {
final LineString points = new LineStringDouble(points1.getAxisCount(),
currentCoordinates);
intersections.add(points);
currentCoordinates.clear();
}
}
previousNode = nextNode;
}
} while (previousNode != null && !endPoint.equals(2, startPoint));
if (currentCoordinates.size() > 0) {
final LineString points = new LineStringDouble(points1.getAxisCount(), currentCoordinates);
intersections.add(points);
}
return intersections;
}
/**
* Only move the node if there is one of them
*
* @param graph2
* @param maxDistance
* @param node1
* @return
*/
public static <T> boolean movePointsWithinTolerance(final Map<Point, Point> movedNodes,
final Graph<T> graph2, final double maxDistance, final Node<T> node1) {
final Graph<T> graph1 = node1.getGraph();
final List<Node<T>> nodes2 = graph2.getNodes(node1, maxDistance);
if (nodes2.size() == 1) {
final Node<T> node2 = nodes2.get(0);
if (graph1.findNode(node2) == null) {
final GeometryFactory precisionModel = graph1.getPrecisionModel();
final Point midPoint = LineSegmentUtil.midPoint(precisionModel, node1, node2);
if (!node1.equals(2, midPoint)) {
if (movedNodes != null) {
movedNodes.put(node1.newPoint2D(), midPoint);
}
node1.moveNode(midPoint);
}
if (!node2.equals(2, midPoint)) {
if (movedNodes != null) {
movedNodes.put(node2.newPoint2D(), midPoint);
}
node2.moveNode(midPoint);
}
}
}
return true;
}
/**
* Returns the index of the direction of the point <code>x,y</code> relative to
* a vector specified by <code>(x1,y1)->(x2,y2)</code>.
*
* @param p1 the origin point of the vector
* @param p2 the final point of the vector
* @param q the point to compute the direction to
* @return 1 if q is counter-clockwise (left) from p1-p2
* @return -1 if q is clockwise (right) from p1-p2
* @return 0 if q is collinear with p1-p2
*/
public static int orientationIndex(final double x1, final double y1, final double x2,
final double y2, final double x, final double y3) {
// travelling along p1->p2, turn counter clockwise to get to q return 1,
// travelling along p1->p2, turn clockwise to get to q return -1,
// p1, p2 and q are colinear return 0.
final double dx1 = x2 - x1;
final double dy1 = y2 - y1;
final double dx2 = x - x2;
final double dy2 = y3 - y2;
return RobustDeterminant.signOfDet2x2(dx1, dy1, dx2, dy2);
}
public static int orientationIndex(final LineString ring, final int index1, final int index2,
final int index) {
return orientationIndex(ring.getX(index1), ring.getY(index1), ring.getX(index2),
ring.getY(index2), ring.getX(index), ring.getY(index));
}
public static LineString removeRepeatedPoints(final LineString points) {
final int axisCount = points.getAxisCount();
final List<Double> coordinates = new ArrayList<>();
double x = points.getX(0);
double y = points.getY(0);
coordinates.add(x);
coordinates.add(y);
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
coordinates.add(points.getCoordinate(0, axisIndex));
}
for (int i = 0; i < points.getVertexCount(); i++) {
final double x1 = points.getX(i);
final double y1 = points.getY(i);
if (x != x1 || y != y1) {
coordinates.add(x1);
coordinates.add(y1);
for (int axisIndex = 2; axisIndex < axisCount; axisIndex++) {
coordinates.add(points.getCoordinate(i, axisIndex));
}
x = x1;
y = y1;
}
}
return new LineStringDouble(axisCount, coordinates);
}
public static void setCoordinates(final double[] coordinates, final int axisCount, final int i,
final double... point) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value;
if (axisIndex < point.length) {
value = point[axisIndex];
} else {
value = Double.NaN;
}
coordinates[i * axisCount + axisIndex] = value;
}
}
public static void setCoordinates(final double[] coordinates, final int axisCount, final int i,
final LineString line, final int j) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value = line.getCoordinate(j, axisIndex);
coordinates[i * axisCount + axisIndex] = value;
}
}
public static void setCoordinates(final double[] coordinates, final int axisCount,
final int targetVertexIndex, final LineString line, final int sourceVertexIndex,
final int vertexCount) {
for (int i = 0; i < vertexCount; i++) {
setCoordinates(coordinates, axisCount, targetVertexIndex + i, line, sourceVertexIndex + i);
}
}
public static void setCoordinates(final double[] coordinates, final int axisCount, final int i,
final Point point) {
if (point != null && !point.isEmpty()) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value = point.getCoordinate(axisIndex);
coordinates[i * axisCount + axisIndex] = value;
}
}
}
public static void setCoordinates(final GeometryFactory geometryFactory,
final double[] coordinates, final int axisCount, final int vertexIndex, Point point) {
if (geometryFactory != null) {
point = point.convertGeometry(geometryFactory, axisCount);
}
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
double value = point.getCoordinate(axisIndex);
value = geometryFactory.makePrecise(axisIndex, value);
coordinates[vertexIndex * axisCount + axisIndex] = value;
}
}
public static void switchCoordinates(final double[] coordinates, final int axisCount,
final int vertexIndex1, final int vertexIndex2) {
for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) {
final double value1 = coordinates[vertexIndex1 * axisCount + axisIndex];
final double value2 = coordinates[vertexIndex2 * axisCount + axisIndex];
coordinates[vertexIndex1 * axisCount + axisIndex] = value2;
coordinates[vertexIndex2 * axisCount + axisIndex] = value1;
}
}
}