package com.revolsys.geometry.graph.geometry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import com.revolsys.geometry.graph.Edge;
import com.revolsys.geometry.graph.Graph;
import com.revolsys.geometry.graph.Node;
import com.revolsys.geometry.graph.comparator.EdgeAttributeValueComparator;
import com.revolsys.geometry.graph.linemerge.LineMerger;
import com.revolsys.geometry.graph.linestring.EdgeLessThanDistance;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Lineal;
import com.revolsys.geometry.model.LinearRing;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.Punctual;
import com.revolsys.geometry.model.segment.LineSegment;
import com.revolsys.geometry.model.segment.LineSegmentDoubleGF;
public class GeometryGraph extends Graph<LineSegment> {
private BoundingBox boundingBox;
private final List<Geometry> geometries = new ArrayList<>();
private double maxDistance;
private final List<Point> points = new ArrayList<>();
private final List<Point> startPoints = new ArrayList<>();
public GeometryGraph(final Geometry geometry) {
this(geometry.getGeometryFactory());
addGeometry(geometry);
}
public GeometryGraph(final GeometryFactory geometryFactory) {
super(false);
setGeometryFactory(geometryFactory);
this.boundingBox = geometryFactory.newBoundingBoxEmpty();
final double scaleXY = getGeometryFactory().getScaleXY();
if (scaleXY > 0) {
this.maxDistance = 1 / scaleXY;
} else {
this.maxDistance = 0;
}
}
public void addEdge(final Node<LineSegment> fromNode, final Node<LineSegment> toNode) {
final LineSegment lineSegment = new LineSegmentDoubleGF(fromNode, toNode);
addEdge(lineSegment, fromNode, toNode);
}
private void addEdges(final LineString points, final Map<String, Object> attributes) {
this.startPoints.add(points.getPoint(0).newPoint2D());
int index = 0;
for (LineSegment lineSegment : points.segments()) {
lineSegment = (LineSegment)lineSegment.clone();
final double fromX = lineSegment.getX(0);
final double fromY = lineSegment.getY(0);
final double toX = lineSegment.getX(1);
final double toY = lineSegment.getY(1);
final Edge<LineSegment> edge = addEdge(lineSegment, fromX, fromY, toX, toY);
attributes.put("segmentIndex", index++);
edge.setProperties(attributes);
}
}
public void addGeometry(Geometry geometry) {
geometry = getGeometryFactory().geometry(geometry);
final Map<String, Object> properties = new LinkedHashMap<>();
final int geometryIndex = this.geometries.size();
properties.put("geometryIndex", geometryIndex);
this.geometries.add(geometry);
for (int partIndex = 0; partIndex < geometry.getGeometryCount(); partIndex++) {
properties.put("partIndex", partIndex);
final Geometry part = geometry.getGeometry(partIndex);
if (part instanceof Point) {
final Point point = (Point)part;
this.points.add(point);
} else if (part instanceof LineString) {
final LineString line = (LineString)part;
final LineString points = line;
properties.put("type", "LineString");
addEdges(points, properties);
} else if (part instanceof Polygon) {
final Polygon polygon = (Polygon)part;
int ringIndex = 0;
for (final LinearRing ring : polygon.rings()) {
properties.put("ringIndex", ringIndex++);
if (ringIndex == 0) {
properties.put("type", "PolygonShell");
} else {
properties.put("type", "PolygonHole");
}
addEdges(ring, properties);
}
properties.remove("ringIndex");
}
}
this.boundingBox = this.boundingBox.expandToInclude(geometry);
}
@Override
protected LineSegment clone(final LineSegment segment, final LineString line) {
return new LineSegmentDoubleGF(line);
}
/**
* Get the intersection between the line and the boundary of this geometry.
*
* @param line
* @return
*/
public Geometry getBoundaryIntersection(final LineString line) {
final List<Point> pointIntersections = new ArrayList<>();
final List<LineString> lineIntersections = new ArrayList<>();
final GeometryFactory geometryFactory = getGeometryFactory();
final BoundingBox boundingBox = getBoundingBox(line);
if (boundingBox.intersects(this.boundingBox)) {
final LineString points = line;
final int vertexCount = points.getVertexCount();
final Point fromPoint = points.getPoint(0);
final Point toPoint = points.getPoint(vertexCount - 1);
Point previousPoint = fromPoint;
for (int vertexIndex = 1; vertexIndex < vertexCount; vertexIndex++) {
final Point nextPoint = points.getPoint(vertexIndex);
final LineSegment line1 = new LineSegmentDoubleGF(getGeometryFactory(), previousPoint,
nextPoint);
final List<Edge<LineSegment>> edges = EdgeLessThanDistance.getEdges(this, line1,
this.maxDistance);
for (final Edge<LineSegment> edge2 : edges) {
final LineSegment line2 = edge2.getObject();
final Geometry segmentIntersection = line1.getIntersection(line2);
if (segmentIntersection instanceof Point) {
final Point intersection = (Point)segmentIntersection;
if (intersection.equals(fromPoint) || intersection.equals(toPoint)) {
// Point intersection, make sure it's not at the start
final Node<LineSegment> node = findNode(intersection);
if (node == null) {
pointIntersections.add(geometryFactory.point(intersection));
} else {
final int degree = node.getDegree();
if (isStartPoint(node)) {
if (degree > 2) {
// Intersection not at the start/end of the other line,
// taking
// into account loops
pointIntersections.add(geometryFactory.point(intersection));
}
} else if (degree > 1) {
// Intersection not at the start/end of the other line
pointIntersections.add(geometryFactory.point(intersection));
}
}
} else {
// Intersection not at the start/end of the line
pointIntersections.add(geometryFactory.point(intersection));
}
} else if (segmentIntersection instanceof LineSegment) {
lineIntersections.add((LineSegment)segmentIntersection);
}
for (final Point point : line1.vertices()) {
if (line2.distancePoint(point) < this.maxDistance) {
if (point.equals(fromPoint) || point.equals(toPoint)) {
// Point intersection, make sure it's not at the start
final double maxDistance1 = this.maxDistance;
for (final Node<LineSegment> node : this.getNodes(point, maxDistance1)) {
final int degree = node.getDegree();
if (isStartPoint(node)) {
if (degree > 2) {
// Intersection not at the start/end of the other line,
// taking
// into account loops
pointIntersections.add(geometryFactory.point(point));
}
} else if (degree > 1) {
// Intersection not at the start/end of the other line
pointIntersections.add(geometryFactory.point(point));
}
}
} else {
// Intersection not at the start/end of the line
pointIntersections.add(geometryFactory.point(point));
}
}
}
}
previousPoint = nextPoint;
}
}
if (lineIntersections.isEmpty()) {
return geometryFactory.punctual(pointIntersections);
} else {
final List<LineString> mergedLines = LineMerger.merge(lineIntersections);
final Lineal multiLine = geometryFactory.lineal(mergedLines);
if (pointIntersections.isEmpty()) {
return multiLine;
} else {
final Punctual multiPoint = geometryFactory.punctual(pointIntersections);
return multiPoint.union(multiLine);
}
}
}
public BoundingBox getBoundingBox(final Geometry geometry) {
if (geometry == null) {
return BoundingBox.empty();
} else {
BoundingBox boundingBox = geometry.getBoundingBox();
boundingBox = boundingBox.expand(this.maxDistance);
return boundingBox;
}
}
@Override
public LineString getEdgeLine(final int edgeId) {
final LineSegment object = getEdgeObject(edgeId);
if (object == null) {
return null;
} else {
final LineString line = object;
return line;
}
}
/**
* Only currently works for lines and points.
*
* @return
*/
public Geometry getGeometry() {
removeDuplicateLineEdges();
final EdgeAttributeValueComparator<LineSegment> comparator = new EdgeAttributeValueComparator<>(
"geometryIndex", "partIndex", "segmentIndex");
final List<Geometry> geometries = new ArrayList<>(this.points);
final GeometryFactory geometryFactory = getGeometryFactory();
final List<Point> points = new ArrayList<>();
final Consumer<Edge<LineSegment>> action = new Consumer<Edge<LineSegment>>() {
private Node<LineSegment> previousNode = null;
@Override
public void accept(final Edge<LineSegment> edge) {
final LineSegment lineSegment = edge.getObject();
if (lineSegment.getLength() > 0) {
final Node<LineSegment> fromNode = edge.getFromNode();
final Node<LineSegment> toNode = edge.getToNode();
if (this.previousNode == null) {
points.add(lineSegment.getPoint(0));
points.add(lineSegment.getPoint(1));
} else if (fromNode == this.previousNode) {
if (edge.getLength() > 0) {
points.add(toNode);
}
} else {
if (points.size() > 1) {
final LineString line = geometryFactory.lineString(points);
geometries.add(line);
}
points.clear();
;
points.add(lineSegment.getPoint(0));
points.add(lineSegment.getPoint(1));
}
if (points.size() > 1) {
final int toDegree = toNode.getDegree();
if (toDegree != 2) {
final LineString line = geometryFactory.lineString(points);
geometries.add(line);
points.clear();
;
points.add(toNode);
}
}
this.previousNode = toNode;
}
}
};
forEachEdge(comparator, action);
if (points.size() > 1) {
final LineString line = geometryFactory.lineString(points);
geometries.add(line);
}
return geometryFactory.geometry(geometries);
}
public boolean intersects(final LineString line) {
BoundingBox boundingBox = line.getBoundingBox();
final double scaleXY = getGeometryFactory().getScaleXY();
double maxDistance = 0;
if (scaleXY > 0) {
maxDistance = 1 / scaleXY;
}
boundingBox = boundingBox.expand(maxDistance);
if (boundingBox.intersects(this.boundingBox)) {
final LineString points = line;
final int numPoints = points.getVertexCount();
final Point fromPoint = points.getPoint(0);
final Point toPoint = points.getPoint(numPoints - 1);
Point previousPoint = fromPoint;
for (int i = 1; i < numPoints; i++) {
final Point nextPoint = points.getPoint(i);
final LineSegment line1 = new LineSegmentDoubleGF(previousPoint, nextPoint);
final List<Edge<LineSegment>> edges = EdgeLessThanDistance.getEdges(this, line1,
maxDistance);
for (final Edge<LineSegment> edge2 : edges) {
final LineSegment line2 = edge2.getObject();
final Geometry intersections = line1.getIntersection(line2);
for (final Point intersection : intersections.vertices()) {
if (intersection.equals(fromPoint) || intersection.equals(toPoint)) {
// Point intersection, make sure it's not at the start
final Node<LineSegment> node = findNode(intersection);
final int degree = node.getDegree();
if (isStartPoint(node)) {
if (degree > 2) {
// Intersection not at the start/end of the other line, taking
// into account loops
return true;
}
} else if (degree > 1) {
// Intersection not at the start/end of the other line
return true;
}
} else {
// Intersection not at the start/end of the line
return true;
}
}
for (final Point point : line1.vertices()) {
if (line2.distancePoint(point) < maxDistance) {
if (point.equals(fromPoint) || point.equals(toPoint)) {
// Point intersection, make sure it's not at the start
final double maxDistance1 = maxDistance;
for (final Node<LineSegment> node : this.getNodes(point, maxDistance1)) {
final int degree = node.getDegree();
if (isStartPoint(node)) {
if (degree > 2) {
// Intersection not at the start/end of the other line,
// taking
// into account loops
return true;
}
} else if (degree > 1) {
// Intersection not at the start/end of the other line
return true;
}
}
} else {
// Intersection not at the start/end of the line
return true;
}
}
}
}
previousPoint = nextPoint;
}
}
return false;
}
private boolean isLineString(final Edge<LineSegment> edge) {
if ("LineString".equals(edge.getProperty("type"))) {
return true;
} else {
return false;
}
}
public boolean isStartPoint(final Point coordinates) {
return this.startPoints.contains(coordinates);
}
public void removeDuplicateLineEdges() {
final Comparator<Edge<LineSegment>> comparator = new EdgeAttributeValueComparator<>(
"geometryIndex", "partIndex", "segmentIndex");
forEachEdge(comparator, (edge) -> {
if (isLineString(edge)) {
final Node<LineSegment> fromNode = edge.getFromNode();
final Node<LineSegment> toNode = edge.getToNode();
final Collection<Edge<LineSegment>> edges = fromNode.getEdgesTo(toNode);
final int duplicateCount = edges.size();
if (duplicateCount > 1) {
edges.remove(edge);
for (final Edge<LineSegment> removeEdge : edges) {
if (isLineString(removeEdge)) {
removeEdge.remove();
}
}
}
}
});
}
}