20 package org.matsim.core.network.algorithms.intersectionSimplifier.containers;
25 import org.apache.commons.csv.CSVFormat;
26 import org.apache.commons.csv.CSVPrinter;
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
29 import org.locationtech.jts.geom.*;
30 import org.locationtech.jts.operation.linemerge.LineMerger;
31 import org.locationtech.jts.triangulate.DelaunayTriangulationBuilder;
32 import org.locationtech.jts.triangulate.quadedge.QuadEdge;
33 import org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision;
34 import org.locationtech.jts.triangulate.quadedge.QuadEdgeTriangle;
35 import org.locationtech.jts.triangulate.quadedge.Vertex;
36 import org.locationtech.jts.util.UniqueCoordinateArrayFilter;
63 private final HashMap<LineSegment, Integer>
segments =
new HashMap<>();
64 private final HashMap<Integer, HullEdge>
edges =
new HashMap<>();
65 private final HashMap<Integer, HullTriangle>
triangles =
new HashMap<>();
68 private final HashMap<Integer, HullEdge>
ignoredEdges =
new HashMap<>();
70 private final Map<Coordinate, Integer>
coordinates =
new HashMap<>();
71 private final Map<Integer, HullNode>
vertices =
new HashMap<>();
73 private final String[]
hullHeader =
new String[]{
"iteration",
"firstX",
"firstY",
"secondX",
"secondY"};
94 public ConcaveHull(GeometryCollection points,
double threshold) {
113 public ConcaveHull(GeometryCollection points,
double threshold, String folderOutput) {
114 this.geomFactory = points.getFactory();
117 UniqueCoordinateArrayFilter filter =
new UniqueCoordinateArrayFilter();
118 points.apply(filter);
119 Coordinate[] ca = filter.getCoordinates();
122 Geometry[] ga =
new Geometry[ca.length];
123 for (
int i = 0; i < ca.length; i++) {
124 ga[i] = geomFactory.createPoint(ca[i]);
126 this.filteredPoints =
new GeometryCollection(ga, geomFactory);
131 if (folderOutput != null) {
132 folderOutput += folderOutput.endsWith(
"/") ?
"" :
"/";
133 File folder =
new File(folderOutput);
134 if (!folder.exists()) {
135 boolean created = folder.mkdirs();
137 LOG.error(
"Could not create the output folder '" + folderOutput +
"'; this may crash down the line.");
140 this.fileTriangles = String.format(
"%sThreshold_%.0f_triangles.csv", folderOutput, this.threshold);
141 this.fileBorder = String.format(
"%sThreshold_%.0f_border.csv", folderOutput, this.threshold);
142 this.printOutput =
true;
144 this.fileTriangles = null;
145 this.fileBorder = null;
146 this.printOutput =
false;
150 if (this.printOutput) {
158 CSVPrinter printer =
new CSVPrinter(writer, CSVFormat.Builder.create().setHeader(hullHeader).build())) {
160 }
catch (IOException e) {
206 int numberOfGeometries = this.filteredPoints.getNumGeometries();
207 return switch (numberOfGeometries) {
208 case 0 -> this.geomFactory.createGeometryCollection(null);
209 case 1 -> this.filteredPoints.getGeometryN(0);
210 case 2 -> this.geomFactory.createLineString(this.filteredPoints.getCoordinates());
218 DelaunayTriangulationBuilder dtb =
new DelaunayTriangulationBuilder();
219 dtb.setSites(this.filteredPoints);
221 QuadEdgeSubdivision qes = dtb.getSubdivision();
224 Collection<QuadEdge> quadEdges = qes.getEdges();
226 List<QuadEdgeTriangle> qeTriangles = QuadEdgeTriangle.createOn(qes);
227 Collection<Vertex> qeVertices = qes.getVertices(
false);
232 if (qeTriangles.isEmpty() || qeVertices.isEmpty()) {
233 LOG.warn(
"No triangulation for " + this.filteredPoints.getNumPoints() +
" points!!");
234 LOG.warn(
" --> Unique id for the group of points: " + facilityIdentifier);
235 Coordinate[] ca = this.filteredPoints.getCoordinates();
236 if (ca.length == 3) {
237 LOG.warn(
" --> Instead, a polygon (triangle) of the three points will be returned.");
238 Coordinate[] caClosed =
new Coordinate[4];
243 return this.geomFactory.createPolygon(this.geomFactory.createLinearRing(caClosed), null);
245 LOG.warn(
" --> Returning a single point geometry as the weighted coordinates of the filtered points.");
248 for (Coordinate c : this.filteredPoints.getCoordinates()) {
252 double newX = xSum / ((double) this.filteredPoints.getCoordinates().length);
253 double newY = ySum / ((double) this.filteredPoints.getCoordinates().length);
255 return this.geomFactory.createPoint(
new Coordinate(newX, newY));
261 for (Vertex v : qeVertices) {
262 this.coordinates.put(v.getCoordinate(), nodeId);
263 this.vertices.put(nodeId,
new HullNode(nodeId, v.getCoordinate()));
287 List<QuadEdge> qeFrameBorder =
new ArrayList<>();
288 List<QuadEdge> qeFrame =
new ArrayList<>();
289 List<QuadEdge> qeBorder =
new ArrayList<>();
291 for (QuadEdge qe : quadEdges) {
292 if (qes.isFrameBorderEdge(qe)) {
293 qeFrameBorder.add(qe);
295 if (qes.isFrameEdge(qe)) {
301 for (QuadEdge q : qeFrameBorder) {
302 if (!qeFrame.contains(q)) {
310 for (QuadEdge q : qeFrame) {
315 Comparator<QuadEdge> comparator = Comparator.comparingDouble(QuadEdge::getLength);
316 List<QuadEdge> sortedEdges =
new ArrayList<>(quadEdges);
317 sortedEdges.sort(comparator);
327 for (QuadEdge qe : sortedEdges) {
328 LineSegment ls = qe.toLineSegment();
332 Integer idOrigin = this.coordinates.get(ls.p0);
333 Integer idDestination = this.coordinates.get(ls.p1);
334 HullNode nodeOrigin = this.vertices.get(idOrigin);
335 HullNode nodeDestination = this.vertices.get(idDestination);
338 if (qeBorder.contains(qe)) {
341 edge =
new HullEdge(edgeId, ls, nodeOrigin, nodeDestination,
true);
343 this.ignoredEdges.put(edgeId, edge);
345 this.consideredEdges.put(edgeId, edge);
348 edge =
new HullEdge(edgeId, ls, nodeOrigin, nodeDestination,
false);
350 this.edges.put(edgeId, edge);
351 this.segments.put(ls, edgeId);
357 for (QuadEdgeTriangle qet : qeTriangles) {
358 LineSegment lsA = qet.getEdge(0).toLineSegment();
359 LineSegment lsB = qet.getEdge(1).toLineSegment();
360 LineSegment lsC = qet.getEdge(2).toLineSegment();
365 HullEdge edgeA = this.edges.get(this.segments.get(lsA));
366 HullEdge edgeB = this.edges.get(this.segments.get(lsB));
367 HullEdge edgeC = this.edges.get(this.segments.get(lsC));
378 this.triangles.put(triangleId, triangle);
384 for (
HullEdge edge : this.edges.values()) {
385 int numberOfTriangles = edge.getTriangles().size();
386 if (numberOfTriangles > 1) {
394 if (numberOfTriangles == 0) {
395 LOG.error(
"An edge not associated with a triangle!");
396 LOG.warn(
" --> Unique id for the group of points: " + facilityIdentifier);
402 if (this.printOutput) {
409 while (!this.consideredEdges.isEmpty()) {
412 HullEdge e = this.consideredEdges.firstEntry().getValue();
417 LOG.warn(
"Considered edge without a triangle association!!");
418 LOG.warn(
" --> For now (20130703) we deal with this by simply making the link 'ignored'.");
419 LOG.warn(
" --> Unique id for the group of points: " + facilityIdentifier);
420 this.consideredEdges.remove(e.
getId());
421 this.ignoredEdges.put(e.
getId(), e);
434 if (neighbours.size() == 1) {
437 this.consideredEdges.remove(e.
getId());
438 this.ignoredEdges.put(e.
getId(), e);
453 this.consideredEdges.remove(e.
getId());
454 this.ignoredEdges.put(e.
getId(), e);
462 this.triangles.remove(triangle.
getId());
468 List<HullEdge> triangleEdges = triangle.
getEdges();
469 triangleEdges.remove(e);
472 this.edges.remove(e.
getId());
473 this.consideredEdges.remove(e.
getId());
477 HullEdge eA = triangleEdges.getFirst();
480 this.consideredEdges.put(eA.
getId(), eA);
482 this.ignoredEdges.put(eA.
getId(), eA);
489 this.consideredEdges.put(eB.
getId(), eB);
491 this.ignoredEdges.put(eB.
getId(), eB);
496 if (this.printOutput) {
505 List<LineString> borderEdges =
new ArrayList<>();
506 for (
HullEdge e : this.ignoredEdges.values()) {
507 borderEdges.add(e.getGeometry().toGeometry(this.geomFactory));
511 LineMerger lineMerger =
new LineMerger();
512 lineMerger.add(borderEdges);
513 LineString merge = (LineString) lineMerger.getMergedLineStrings().iterator().next();
515 LOG.info(
"Done calculating the concave hull.");
516 LOG.info(
" Triangles: " + this.triangles.size());
517 LOG.info(
" Segments: " + borderEdges.size());
519 if (merge.isRing()) {
520 LinearRing lr =
new LinearRing(merge.getCoordinateSequence(), this.
geomFactory);
521 return new Polygon(lr, null, this.geomFactory);
523 LOG.warn(
"Could not create hull as the line segments do not form a closed ring.");
524 LOG.warn(
" --> Unique id for the group of points: " + facilityIdentifier);
525 LOG.warn(
" --> Unique points (" + this.filteredPoints.getNumGeometries() +
"):");
526 Coordinate[] ca = this.filteredPoints.getCoordinates();
527 for (Coordinate c : ca) {
528 LOG.warn(
" (" + c.x +
";" + c.y +
")");
532 LOG.warn(
" --> Returning the convex hull.");
533 return geomFactory.createMultiPointFromCoords(this.filteredPoints.getCoordinates()).convexHull();
543 return this.filteredPoints.getNumGeometries();
555 LOG.info(
"Reading coordinate list from " + fileName);
556 List<Coordinate> coordinateList =
new ArrayList<>();
563 while ((lines = br.readLine()) != null) {
564 String[] inputString = lines.split(
",");
565 double x = Double.parseDouble(inputString[0]);
566 double y = Double.parseDouble(inputString[1]);
567 Coordinate coord =
new Coordinate(x, y);
568 coordinateList.add(coord);
570 }
catch (IOException e) {
573 return coordinateList;
596 private void writeHullEdges(Map<Integer,HullEdge> hullEdges, String filename,
int iteration){
598 CSVPrinter printer =
new CSVPrinter(writer, CSVFormat.Builder.create().setSkipHeaderRecord(
true).build())){
599 for(
HullEdge edge : hullEdges.values()){
602 edge.getOriginNode().getCoordinate().getX(),
603 edge.getOriginNode().getCoordinate().getY(),
604 edge.getDestinationNode().getCoordinate().getX(),
605 edge.getDestinationNode().getCoordinate().getY()
608 }
catch(IOException e){
630 public static void main(String[] args) {
631 if(args.length != 3){
632 throw new IllegalArgumentException(
"Insufficient arguments. Look at the test for an example.");
639 GeometryFactory gf =
new GeometryFactory();
640 Geometry[] points =
new Geometry[coordinates.size()];
641 for (
int i = 0; i < coordinates.size(); i++) {
642 points[i] = gf.createPoint(coordinates.get(i));
644 GeometryCollection gc =
new GeometryCollection(points, gf);
649 LOG.info(
"The hull contains " + (g.getCoordinates().length-1) +
" coordinates");
final HashMap< Integer, HullEdge > edges
final boolean printOutput
static void main(String[] args)
final String [] hullHeader
boolean addNeighbour(HullTriangle triangle)
static List< Coordinate > getClusterCoords(String fileName)
Geometry concaveHull(String facilityIdentifier)
ConcaveHull(GeometryCollection points, double threshold, String folderOutput)
static BufferedReader getBufferedReader(URL url, Charset charset)
final GeometryCollection filteredPoints
final HashMap< Integer, HullTriangle > triangles
final HashMap< Integer, HullEdge > ignoredEdges
HullNode getDestinationNode()
boolean removeNeighbour(HullTriangle triangle)
static BufferedWriter getBufferedWriter(URL url, Charset charset, boolean append)
boolean addTriangle(HullTriangle triangle)
final TreeMap< Integer, HullEdge > consideredEdges
Geometry getConcaveHull()
List< HullTriangle > getTriangles()
List< HullTriangle > getNeighbours()
void writeOutput(int iteration)
final GeometryFactory geomFactory
final Map< Integer, HullNode > vertices
final Map< Coordinate, Integer > coordinates
boolean addEdge(HullEdge edge)
Geometry getConcaveHull(String facilityIdentifier)
boolean removeTriangle(HullTriangle triangle)
LineSegment getGeometry()
final String fileTriangles
static BufferedWriter getAppendingBufferedWriter(String filename)
void setBorder(boolean border)
final HashMap< LineSegment, Integer > segments
List< HullEdge > getEdges()
void writeHullEdges(Map< Integer, HullEdge > hullEdges, String filename, int iteration)
void setBorder(boolean border)
void printHeader(String filename)
ConcaveHull(GeometryCollection points, double threshold)