package com.revolsys.geometry.graph.linemerge;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import com.revolsys.collection.list.Lists;
import com.revolsys.geometry.graph.Edge;
import com.revolsys.geometry.graph.Node;
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.util.Property;
public class LineMerger {
public static List<LineString> merge(final Geometry... geometries) {
if (geometries == null) {
return Collections.emptyList();
} else {
final LineMerger lineMerger = new LineMerger(geometries);
return lineMerger.getLineStrings();
}
}
public static List<LineString> merge(final Iterable<? extends Geometry> geometries) {
if (Property.hasValue(geometries)) {
final LineMerger lineMerger = new LineMerger(geometries);
return lineMerger.getLineStrings();
} else {
return Collections.emptyList();
}
}
private final LineStringsGraph graph = new LineStringsGraph();
private boolean merged = true;
/**
* Creates a new line merger.
*
*/
public LineMerger() {
}
public LineMerger(final Geometry... geometries) {
addAll(geometries);
}
public LineMerger(final Iterable<? extends Geometry> lines) {
addAll(lines);
}
public LineMerger(final LineString line) {
add(line);
}
/**
* Adds a Geometry to be processed. May be called multiple times.
* Any dimension of Geometry may be added; the constituent linework will be
* extracted.
*
* @param geometry geometry to be line-merged
*/
public void add(final Geometry geometry) {
final List<LineString> lines = geometry.getGeometryComponents(LineString.class);
for (final LineString line : lines) {
add(line);
}
}
public void add(final LineString lineString) {
if (lineString != null) {
if (this.graph.getGeometryFactory() == GeometryFactory.DEFAULT_3D) {
this.graph.setGeometryFactory(lineString.getGeometryFactory());
}
this.merged = false;
this.graph.addEdge(lineString);
}
}
public void addAll(final Geometry... geometries) {
for (final Geometry geometry : geometries) {
add(geometry);
}
}
/**
* Adds a collection of Geometries to be processed. May be called multiple times.
* Any dimension of Geometry may be added; the constituent linework will be
* extracted.
*
* @param geometries the geometries to be line-merged
*/
public void addAll(final Iterable<? extends Geometry> geometries) {
for (final Geometry geometry : geometries) {
add(geometry);
}
}
public LineStringsGraph getGraph() {
return this.graph;
}
public Lineal getLineal() {
final GeometryFactory geometryFactory = this.graph.getGeometryFactory();
return getLineal(geometryFactory);
}
public Lineal getLineal(final GeometryFactory geometryFactory) {
final List<LineString> lines = getLineStrings();
return geometryFactory.lineal(lines);
}
/**
* Gets the {@link LineString}s created by the merging process.
*
* @return the collection of merged LineStrings
*/
public List<LineString> getLineStrings() {
merge();
final List<LineString> edgeLines = this.graph.getEdgeLines();
return Lists.toArray(edgeLines);
}
private void merge() {
if (!this.merged) {
mergeDegree2FromEnds();
this.merged = true;
}
}
private void mergeDegree2FromEnds() {
final Predicate<Node<LineString>> filter = (node) -> {
return !node.isRemoved() && node.getDegree() != 2;
};
for (final Node<LineString> node : this.graph.getNodes(filter)) {
if (!node.isRemoved()) {
for (final Edge<LineString> edge : Lists.toArray(node.getEdges())) {
if (!edge.isRemoved()) {
final List<LineString> lines = new ArrayList<>();
final List<Boolean> lineForwards = new ArrayList<>();
double forwardsLength = 0;
double reverseLength = 0;
Edge<LineString> previousEdge = null;
Node<LineString> currentNode = node;
Edge<LineString> currentEdge = edge;
do {
final LineString line = currentEdge.getLine();
final double length = line.getLength();
final boolean forwards = currentEdge.getEnd(currentNode).isFrom();
if (forwards) {
forwardsLength += length;
} else {
reverseLength += length;
}
lines.add(line);
lineForwards.add(forwards);
currentNode = currentEdge.getOppositeNode(currentNode);
previousEdge = currentEdge;
currentEdge = currentNode.getNextEdge(previousEdge);
} while (currentNode.getDegree() == 2);
if (lines.size() > 1) {
LineString mergedLine = null;
final boolean mergeForwards = forwardsLength >= reverseLength;
int i = 0;
for (LineString line : lines) {
final boolean forwards = lineForwards.get(i);
if (forwards != mergeForwards) {
line = line.reverse();
}
if (mergedLine == null) {
mergedLine = line;
} else {
mergedLine = mergedLine.merge(line);
}
i++;
}
this.graph.addEdge(mergedLine);
this.graph.removeEdges(lines);
}
}
}
}
}
}
public void remove(final LineString lineString) {
this.merged = false;
this.graph.removeEdge(lineString);
}
public void removeAll(final Iterable<LineString> lines) {
this.merged = false;
this.graph.removeEdges(lines);
}
}