Skip to content

Commit 02b29de

Browse files
committed
various
1 parent 4e52b74 commit 02b29de

File tree

8 files changed

+67
-90
lines changed

8 files changed

+67
-90
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
### Changes
1818
* Packed circles from `PGS_CirclePacking.stochasticPack()` will now always lie within a shape bounds.
1919
* `PGS_Processing.pointsOnExterior()` methods now respect GROUP shapes and holes (inner rings) and will populate them with points.
20+
* `PGS_Morphology.simplifyDCE()` now supports GROUP shapes and polygon holes.
2021

2122
### Fixed
2223
* `urquhartFaces()`, `relativeNeighborFaces()`, `gabrielFaces()` and `spannerFaces()` from `PGS_Meshing` now preserve holes from the input.
2324

24-
## Removed
25+
### Removed
2526
* `simplifyDCE(shape, targetNumVertices)` and `simplifyDCE(shape, vertexRemovalFraction)` in favour a single method that accepts a user-defined termination callback that is supplied with the current vertex candidate's coordinate, relevance score, and the number of vertices remaining.
2627

2728

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@
243243
<dependency>
244244
<groupId>org.tinspin</groupId>
245245
<artifactId>tinspin-indexes</artifactId>
246-
<version>2.1.2</version>
246+
<version>2.1.3</version>
247247
</dependency>
248248
<dependency> <!-- for test running -->
249249
<groupId>org.junit.jupiter</groupId>

src/main/java/micycle/pgs/PGS.java

+40-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import org.locationtech.jts.geom.CoordinateList;
1919
import org.locationtech.jts.geom.Geometry;
2020
import org.locationtech.jts.geom.GeometryFactory;
21+
import org.locationtech.jts.geom.GeometryFilter;
2122
import org.locationtech.jts.geom.LineString;
2223
import org.locationtech.jts.geom.LinearRing;
24+
import org.locationtech.jts.geom.MultiPolygon;
2325
import org.locationtech.jts.geom.Point;
2426
import org.locationtech.jts.geom.Polygon;
2527
import org.locationtech.jts.geom.PrecisionModel;
@@ -29,7 +31,6 @@
2931
import org.locationtech.jts.noding.snap.SnappingNoder;
3032
import org.locationtech.jts.operation.linemerge.LineMerger;
3133
import org.locationtech.jts.operation.polygonize.Polygonizer;
32-
3334
import micycle.pgs.color.Colors;
3435
import micycle.pgs.commons.Nullable;
3536
import micycle.pgs.commons.PEdge;
@@ -266,7 +267,7 @@ static final PShape polygonizeSegments(Collection<SegmentString> segments, boole
266267
* formed by the given edges
267268
*/
268269
@SuppressWarnings("unchecked")
269-
static final PShape polygonizeEdgesRobust(Collection<PEdge> edges) {
270+
private static final PShape polygonizeEdgesRobust(Collection<PEdge> edges) {
270271
final Set<PEdge> edgeSet = new HashSet<>(edges);
271272
final Polygonizer polygonizer = new Polygonizer();
272273
polygonizer.setCheckRingsValid(false);
@@ -417,6 +418,43 @@ static SimpleWeightedGraph<PVector, PEdge> makeCompleteGraph(List<PVector> point
417418
return graph;
418419
}
419420

421+
/**
422+
* Extracts all the polygons from a given geometry. If the geometry instance is
423+
* a MultiPolygon, each individual polygon is extracted and added to the result
424+
* list. Other geometry types contained within the input geometry are ignored.
425+
*/
426+
static List<Polygon> extractPolygons(Geometry g) {
427+
List<Polygon> polygons = new ArrayList<>(g.getNumGeometries());
428+
final GeometryFilter filter = new GeometryFilter() {
429+
public void filter(Geometry geom) {
430+
if (geom instanceof Polygon) {
431+
polygons.add((Polygon) geom);
432+
} else if (geom instanceof MultiPolygon) {
433+
for (int i = 0; i < geom.getNumGeometries(); i++) {
434+
polygons.add((Polygon) geom.getGeometryN(i));
435+
}
436+
}
437+
}
438+
};
439+
440+
g.apply(filter);
441+
return polygons;
442+
}
443+
444+
/**
445+
* Extracts all the LinearRings from a given polygon. This includes both the
446+
* exterior ring and all interior rings (if any).
447+
*/
448+
static List<LinearRing> extractLinearRings(Polygon polygon) {
449+
List<LinearRing> rings = new ArrayList<>(1 + polygon.getNumInteriorRing());
450+
rings.add((LinearRing) polygon.getExteriorRing());
451+
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
452+
rings.add((LinearRing) polygon.getInteriorRingN(i));
453+
}
454+
455+
return rings;
456+
}
457+
420458
/**
421459
* Provides convenient iteration of the child geometries of a JTS MultiGeometry.
422460
* This iterator does not recurse all geometries (as does

src/main/java/micycle/pgs/PGS_Contour.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ private static PShape offsetCurves(PShape shape, OffsetStyle style, double spaci
829829
}
830830

831831
if (g.getCoordinates().length > 2000) {
832-
g = DouglasPeuckerSimplifier.simplify(g, 1);
832+
g = DouglasPeuckerSimplifier.simplify(g, 0.25);
833833
}
834834

835835
final BufferParameters bufParams = new BufferParameters(8, BufferParameters.CAP_FLAT, style.style,

src/main/java/micycle/pgs/PGS_Morphology.java

+12-9
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,21 @@ public static PShape simplifyTopology(PShape shape, double distanceTolerance) {
254254
* This algorithm simplifies a shape by iteratively removing kinks from the
255255
* shape, starting with those having the least shape-relevance.
256256
* <p>
257-
* The simplification process terminates according to a user-specified callback
258-
* that decides whether the DCE algorithm should terminate based on the current
259-
* kink (having a candidate vertex), using its coordinates, relevance score, and
260-
* the number of vertices remaining in the simplified geometry. Implementations
261-
* can use this method to provide custom termination logic which may depend on
262-
* various factors, such as a threshold relevance score, a specific number of
263-
* vertices to preserve, or other criteria.
257+
* The simplification process terminates according to a user-specified
258+
* {@link DCETerminationCallback#shouldTerminate(Coordinate, double, int)
259+
* callback} that decides whether the DCE algorithm should terminate based on
260+
* the current kink (having a candidate vertex), using its coordinates,
261+
* relevance score, and the number of vertices remaining in the simplified
262+
* geometry. Implementations can use this method to provide custom termination
263+
* logic which may depend on various factors, such as a threshold relevance
264+
* score, a specific number of vertices to preserve, or other criteria.
265+
* <p>
266+
* Note: the termination callback is applied per ring (boundary, hole, line,
267+
* etc) in the input.
264268
*
265269
* @param shape The input shape to be simplified, which can be a
266270
* polygonal (inclusive of holes) or a lineal shape.
267-
* Note that GROUP shapes are not supported by this
268-
* method.
271+
* GROUP shapes are supported.
269272
* @param terminationCallback The callback used to determine when the
270273
* simplification process should terminate.
271274
* {@code true} if the DCE process should terminate;

src/main/java/micycle/pgs/PGS_PointSet.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
import org.jgrapht.graph.SimpleGraph;
2727
import org.tinfour.common.IIncrementalTin;
2828
import org.tinfour.common.Vertex;
29-
import org.tinspin.index.kdtree.KDTree;
30-
29+
import org.tinspin.index.IndexConfig;
30+
import org.tinspin.index.PointMap;
3131
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
3232
import it.unimi.dsi.util.XoRoShiRo128PlusRandom;
3333
import it.unimi.dsi.util.XoRoShiRo128PlusRandomGenerator;
@@ -67,7 +67,7 @@ private PGS_PointSet() {
6767
* @return
6868
*/
6969
public static List<PVector> prunePointsWithinDistance(List<PVector> points, double distanceTolerance) {
70-
final KDTree<PVector> tree = KDTree.create(2);
70+
final PointMap<Object> tree = PointMap.Factory.createKdTree(IndexConfig.create(2).setDefensiveKeyCopy(false));
7171
final List<PVector> newPoints = new ArrayList<>();
7272
for (PVector p : points) {
7373
final double[] coords = new double[] { p.x, p.y };

src/main/java/micycle/pgs/PGS_SegmentSet.java

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.locationtech.jts.geom.LineSegment;
2323
import org.locationtech.jts.geom.LineString;
2424
import org.locationtech.jts.geom.Location;
25-
import org.locationtech.jts.geom.util.LineStringExtracter;
2625
import org.locationtech.jts.geom.util.LinearComponentExtracter;
2726
import org.locationtech.jts.noding.MCIndexSegmentSetMutualIntersector;
2827
import org.locationtech.jts.noding.NodedSegmentString;

src/main/java/micycle/pgs/commons/AreaMerge.java

+8-72
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
package micycle.pgs.commons;
22

3-
import java.util.Collection;
43
import java.util.HashMap;
5-
import java.util.HashSet;
4+
import java.util.List;
65
import java.util.Map;
76
import java.util.Map.Entry;
87
import java.util.Objects;
9-
import java.util.Set;
108
import java.util.TreeSet;
119
import java.util.stream.Collectors;
1210
import org.jgrapht.Graph;
1311
import org.jgrapht.Graphs;
14-
import org.jgrapht.alg.util.NeighborCache;
1512
import org.jgrapht.alg.util.Pair;
1613
import org.jgrapht.graph.DefaultEdge;
1714
import org.jgrapht.graph.SimpleGraph;
@@ -44,14 +41,13 @@ private AreaMerge() {
4441
*/
4542
public static PShape areaMerge(PShape mesh, double areaThreshold) {
4643
SimpleGraph<PShape, DefaultEdge> graph = PGS_Conversion.toDualGraph(mesh);
47-
NeighborCache<PShape, DefaultEdge> neighborCache = new NeighborCache<>(graph);
4844

4945
Map<PShape, FaceGroup> initialFaceMap = new HashMap<>(graph.vertexSet().size());
5046
SimpleGraph<FaceGroup, DefaultEdge> groupsGraph = new SimpleGraph<>(DefaultEdge.class);
5147
TreeSet<FaceGroup> smallGroups = new TreeSet<>(); // groups having area < areaThreshold
5248
for (PShape face : graph.vertexSet()) {
5349
double area = PGS_ShapePredicates.area(face);
54-
FaceGroup f = new FaceGroup(face, area, neighborCache);
50+
FaceGroup f = new FaceGroup(face, area);
5551
initialFaceMap.put(face, f);
5652
groupsGraph.addVertex(f);
5753

@@ -72,11 +68,13 @@ public static PShape areaMerge(PShape mesh, double areaThreshold) {
7268

7369
while (!smallGroups.isEmpty()) {
7470
final FaceGroup toMerge = smallGroups.pollFirst();
75-
Collection<FaceGroup> neighbors = Graphs.neighborSetOf(groupsGraph, toMerge);
7671

7772
// find smallest neighbor of the toMerge face
78-
// FaceGroup smallestNeighbor = neighbors.stream().min((a, b) -> Double.compare(a.area, b.area)).orElse(null);
79-
FaceGroup smallestNeighbor = Graphs.neighborListOf(groupsGraph, toMerge).get(0);
73+
List<FaceGroup> neighboringGroups = Graphs.neighborListOf(groupsGraph, toMerge);
74+
// sort neighbors by area, pick the smallest. ensures algorithm is stable on the
75+
// same input
76+
FaceGroup smallestNeighbor = neighboringGroups.stream().min((a, b) -> Double.compare(a.area, b.area)).orElse(null);
77+
// FaceGroup smallestNeighbor = neighbors.get(0);
8078
if (smallestNeighbor == null) {
8179
break; // exit merging
8280
}
@@ -86,10 +84,6 @@ public static PShape areaMerge(PShape mesh, double areaThreshold) {
8684

8785
if (smallestNeighbor.area > areaThreshold) {
8886
smallGroups.remove(smallestNeighbor);
89-
} else {
90-
// re-insert and order the new group
91-
// smallGroups.remove(smallestNeighbor);
92-
// smallGroups.add(smallestNeighbor);
9387
}
9488
}
9589

@@ -141,13 +135,9 @@ static class FaceGroup implements Comparable<FaceGroup> {
141135
private double area;
142136
/** A map of faces comprising this group and their respective areas. */
143137
private Map<PShape, Double> faces;
144-
private Set<PShape> neighborFaces; // union of neighbors of each face - faces themselves
145-
private final NeighborCache<PShape, DefaultEdge> neighborCache;
146138

147-
FaceGroup(PShape initial, double area, NeighborCache<PShape, DefaultEdge> meshNeighborCache) {
139+
FaceGroup(PShape initial, double area) {
148140
this.faces = new HashMap<>();
149-
this.neighborFaces = new HashSet<>();
150-
this.neighborCache = meshNeighborCache;
151141
addFace(initial, area);
152142
}
153143

@@ -156,45 +146,20 @@ static class FaceGroup implements Comparable<FaceGroup> {
156146
*/
157147
public boolean addFace(PShape face, double area) {
158148
if (face == null) {
159-
// System.err.println("Tried to add null face.");
160149
return false;
161150
}
162-
// if (!faces.isEmpty() && !neighborFaces.contains(face)) {
163-
// System.err.println("Tried to add a face that is not a neighboring face of the group.");
164-
// return false;
165-
// }
166151
if (!faces.containsKey(face)) {
167152
faces.put(face, area);
168153
this.area += area;
169-
// recomputeNeighbors(face);
170154
return true;
171155
}
172156
return false;
173157
}
174158

175-
private void recomputeNeighbors(PShape face) {
176-
neighborFaces.addAll(neighborCache.neighborsOf(face));
177-
/*
178-
* Now remove any of the group's own faces from the neighbours.
179-
*/
180-
neighborFaces.removeAll(faces.keySet());
181-
}
182-
183-
private void recomputeNeighbors() {
184-
neighborFaces.clear();
185-
for (PShape face : faces.keySet()) {
186-
neighborFaces.addAll(neighborCache.neighborsOf(face));
187-
}
188-
neighborFaces.removeAll(faces.keySet());
189-
}
190-
191159
/**
192160
* Merges another facegroup into this one.
193161
*/
194162
public boolean mergeWith(FaceGroup other) {
195-
// if (!neighbors(other)) {
196-
// return false;
197-
// }
198163
boolean changed = false;
199164
for (Entry<PShape, Double> faceEntry : other.faces.entrySet()) {
200165
changed = changed | addFace(faceEntry.getKey(), faceEntry.getValue());
@@ -203,35 +168,6 @@ public boolean mergeWith(FaceGroup other) {
203168
return changed;
204169
}
205170

206-
/**
207-
* @return true if this and the other facegroup have no faces in common
208-
*/
209-
public boolean disjoint(FaceGroup other) {
210-
Set<PShape> myFaces = new HashSet<>(faces.keySet());
211-
myFaces.retainAll(other.faces.keySet());
212-
return myFaces.isEmpty();
213-
}
214-
215-
public boolean neighbors(PShape face) {
216-
return neighborFaces.contains(face);
217-
}
218-
219-
/**
220-
* @param facegroup
221-
* @return true if the groups are disjoint and neighborFaces each other
222-
*/
223-
public boolean neighbors(FaceGroup facegroup) {
224-
if (!disjoint(facegroup)) {
225-
return false;
226-
}
227-
for (PShape face : facegroup.faces.keySet()) {
228-
if (neighborFaces.contains(face)) {
229-
return true;
230-
}
231-
}
232-
return false;
233-
}
234-
235171
public boolean hasFace(PShape face) {
236172
return faces.keySet().contains(face);
237173
}

0 commit comments

Comments
 (0)