fix(polygonize): deduplicate segments in planar graph#3045
Conversation
|
Hi @NickCis and @DenisCarriere, I have a few fixes to make this more robust, I can do them 1 at a time. Let me know if it is of interest and if there is anything else you need. Thanks. |
|
Hey @DenisCarriere , it has been a while since I wrote the code. So basically you can do what you want with it! It's fine by me. |
|
Unfortunately I haven't monitored the code base in a very long time, I'm not the person anymore to review PRs |
|
Hey @DenisCarriere, do you know who I should contact on this now? Who is the active maintainer? |
mfedderly
left a comment
There was a problem hiding this comment.
👋 Got a chance to check this out today. Thanks for doing the digging on this!
The thing that popped out to to me in the review of this is that all of these objects with string keys on them is probably really slow. I know we're already doing this for the node ids, so this is kind of the natural extension of that code. As implemented the bench seems to have regressed 20-30% for this PR against the baseline.
I think its possible (with a larger change) to fix this bug and improve performance overall by leveraging the newer Set and Map primitives. I took a crack at reworking the Node storage in Graph and saw 15-70% improvements depending on how complicated the input was. #3074
What do you think about the change in #3074? Would you be willing to do something similar in your PR for the edge storage in Graph? I think you can have the edgeIndex be something like Map<Node, Set<Node>> and have edges be Set<Edge>. I haven't looked into that part too closely.
| removeEdge(edge: Edge) { | ||
| this.edges = this.edges.filter((e) => !e.isEqual(edge)); | ||
| edge.deleteEdge(); | ||
| delete this.edgeIds[`${edge.from.id}->${edge.to.id}`]; |
There was a problem hiding this comment.
Note to myself: from a quick look I don't think we need to delete the symmetric edge here, because calls to removeEdge are doing the deletions in a second removeEdge call.
* Migrate @turf/polygonize to Map/Set When looking at #3045 it occurred to me that this code probably hasn't been updated since we moved from ES5 to ES8 support in Turf. Specifically: Node was getting a very log string assigned as its id, and then we were using an Object keyed by those ids to do lookups. This changes Node's id to a number (incremented as Graph creates them). Graph keeps both a Set<Node> and an index of Map<longitude, Map<latitude, Node>> used to quickly figure out if a Node has already been created for a given set of coordinates for reuse. * Rename byLng to byLat for clarity of the nodeIdx structure
contributorsfield ofpackage.json- you've earned it!Summary
When input geometry contains duplicate line segments (the same segment appearing more than once),
@turf/polygonizesilently returns 0 polygons instead of the expected result.Bug
Root cause
Graph.addEdge()creates a new pair of directed edges for every input segment, including duplicates. The duplicate edges corrupt the planar graph topology, causing edge-ring traversal to fail.Fix
Track seen edge IDs in
Graphand skip segments that have already been added. Two lines inaddEdge()plus cleanup inremoveEdge().Test
Added a regression test with the above scenario — confirms polygonize correctly returns 1 polygon when a duplicate segment is present.
This is not a breaking change. Inputs that previously worked continue to produce the same output. Only the failure case (duplicate segments -> 0 polygons) is fixed.
Relates to #1714.
Note: This is a focused subset of #1714, which was opened in 2019 but covered multiple fixes in a single large PR (1000+ lines). This PR intentionally targets just the duplicate segment issue to keep the change small and reviewable per the contributing guidelines. Happy to follow up with separate PRs for the other polygonize issues if this lands.