-
Notifications
You must be signed in to change notification settings - Fork 442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Fixing invalid geometry #652
Comments
Maybe this paper is worth some consideration : https://agile-online.org/conference_paper/cds/agile_2012/proceedings/papers/paper_ledoux_automatically_repairing_invalid_polygons_with_a_constrained_triangulation_2012.pdf |
That's a very interesting paper (especially because it talks about JTS and GEOS!). However, I'm not convinced that a CDT is the best way to fix invalid polygons. One obvious issue is that it requires writing a CDT algorithm, which is a big undertaking. And it's not clear that the complexity provides any better results than the node-polygonize approach above (and likely has worse performance). The examples of invalidity in the paper are useful. I wonder if that data is available? |
Didn't JTS already include a CDT algorithm ? The approach you propose seems not too far from OpenJUMP's MakeValidOp ;-). |
Nope, it has Conforming DT, which is definitely not what is needed. The whole CDT approach just seems way more complicated than needed, and I don't think it answers the hard questions about how to create valid polygons.
Great, could be handy.
No doubt!
Yes, that's a good example. Either could be considered "right". Although it does feel suspect to convert to an empty geometry - that throws away all the input information. I've added this case to the examples list. The approach is definitely subject to change. There's a similar question about a hole surrounding another hole. Should the inner hole turn into an island, or disappear? |
I have developed a polygon correct approach (remove self-intersection, correct order, correct inners), based on boost geometry: The approach for calculating and removing self-intersection is vastly simpler and more efficient than existing libraries. It was developed for tilemaker: But can be used for other projects as well. |
Thanks, this looks interesting. |
What is approach that is used now for removing self-intersection from polygons ? |
The buffer-by-zero heuristic is used to turn self-intersecting rings into valid geometry. Code is here. I this produces a similar semantic to the one advocated in the Boost blog post you referenced. |
Ah yes. It is common and does work well in many practical cases. But in cases with holes. It will not generate the second polygon. The approach i took does generate these multipolygons. |
Not sure what you mean - do you have an example? Note the buffer-by-zero call is just the lowest level of |
@kleunen I think
|
Yes that is the same. I still have to add rebuilding with difference and union. Boost geometry buffer does not allow buffer with 0. So maybe there is difference in implementation there. |
Boost buffer add many new points. It seems jts buffer does not. Or is simplification run afterwards? |
Using buffer-by-zero was just a quick way to get this functionality rolled out (since it is available in JTS). You could duplicate that semantic by using a node-and-build-topology approach, of course. But then you have to implement the noding and topology graph building code, which is non-trivial. In fact I would prefer the |
Do you mean repeated points? If so, that's a result of de-duplication in the buffer and overlay functions. |
Here you can find discussion regarding dissolve/make_valid approach for tilemaker: No, with buffer i get many additional points around the corners. To "round" them. For every corner on polygon you get 3 dots, maybe this is implementation of boost geometry. JTS implementation probably more efficient, generating less support points, because these additional points are not needed. |
Yes, that must be Boost geometry buffer behaviour. In the JTS buffer algorithm, buffer distance = 0 is treated as a special case, and the original geometry is used as the offset curve with no other modification. (In contrast, a buffer distance >0 invokes several heuristics to simplify the input linework, and then generates buffer fillets and endcaps as per the input parameters). |
@kleunen One question I have about your algorithm (which I presume also applies to the papers mentioned, although I haven't verified that): how robust is it? Numerical precision limitations can cause nodes computed in a line arrangement to "jump" across one another, which leads to inconsistent topology. JTS contains a lot of code to handle this situation (in particular, buffer checks for consistent noding and falls back to using snap-rounding if it occurs). However, perhaps this is a case where the effects aren't so severe - although I would be surprised if that was the case. Unfortunately it's hard to reproduce robustness issues, and they tend to manifest differently for each individual algorithm. Generally the only way to find them is by running many real-world examples. And to detect failure cases, you need an independent way to verify the correctness of computed outputs - which is usually not easy to create. For this fix-invalid-geometry situation, you would need a way not only to verify that the computed result is valid, but that it's valid in a sensible way, according to the desired semantics of the algorithm (since otherwise trivial or wildly malformed output that happened to be valid would be accepted). |
There are no robustness analysis in the papers. The approach basicly reuses the existing points on the ring and add additional points on the ring on the intersections. One thing i noticed you start traversing this ring and then follow the intersections to detect the non intersecting sub polygons. When doing this, you have to detect you are back at the starting point. For this i used a distance between the current point and the start point. If you do not detect this case, the algorithm keeps traversing the same ring infinitly. https://github.com/kleunen/boost_geometry_correct/blob/main/correct.hpp#L230 Also, there is no proof that by generating these pseudo vertices you can follow the subrings and always end up at the starting point. It is like you says. It works in practice but it is not proven you can not end up in a subring which does not have the starting point. And thus start looping infinitly. Maybe this will make the algorithm more robust. Instead of detecting you are returned at the starting point, you should stop following the ring if you return to a point you already visited. Moreover duplicate points should be filteren out. But this is same for other algorithms. |
Also note I calculate "where" the intersections are on the original edge of the ring: This is used to order them such, that they are ordered according to how they appear on the original ring: So possibly, numerical stability might cause two intersection to swap places, but I think this is not an issue, because they are ordered according to the comparable distance on the original edge. |
Validating the output is indeed laborious. If the algorithms drops an invalid inner or all inners or returns an empty polygon or multi polygon. The geometry is valid, but clearly is not acceptable. But how many nodes and inners should be in the output? You can not really calculate and automatically test. But i guess you are very familiar with these difficulties. |
The core problem is that the interesection point of two nearly-collinear lines might be computed to be the opposite side of another line which crosses the two lines. This leads to the computed noded line arrangement still having non-noded intersections. The only solution I've found to handle this is to introducing snapping into the noding subsystem. |
One way to validate is to use a "point probe" algorithm, which is completely different and simpler, and hence a good guarantee of correctness. This involves generating points near every section of input and output linework, and then determining whether they are correctly placed relative to the original linework. If the Non-Zero Winding number rule is used it should be possible to determine the winding number for a point and then confirm it has an appropriate value. This is compute-intensive but relatively straightforward - which computers are good at! |
Yes i think the approach in boost geometry is to simply ignore this. Going the approach that double float or even int have a limit on precision and near this limit issues might occur. The user can always pre snap the geometry before applying a make valid if he/she needs this precision. But i guess that is a matter of preference. This can occur clearly, because the intersection detection is needed. So if you need this to work correctly, you can pre-snap the nodes. But once the intersections are detected, you only need to detect if you are returned to the starting point when walking the sub-ring. For this, you can use a threshold as well. |
i have an multipolygon, wkt like this : when i use GeometryFixer, it bacame to this : while use postgis, it bacame to this: GEOMETRYCOLLECTION (POLYGON ((104.84948523000008 26.576550350000048, 104.84877030700005 26.57614817800004, 104.84817763600006 26.577031617000046, 104.84889922500008 26.57747835400005, 104.84948523000008 26.576550350000048), i saw this issue: for me , i think the one which postgis maked is correct. and an alternate mode will make it more adaptable in a lot of scenarios. |
Fixed by #704 |
您好,我是陆泽,邮寄已收到,谢谢
|
Why: - Geometry union will fail if the geometries are not valid. A typical cause of invalid geometry is a polygon with a self-intersection. JTS will then throw an exception like the following: org.locationtech.jts.geom.TopologyException: found non-noded intersection between LINESTRING ( 0 0, 1 1 ) and LINESTRING ( 1 0, 0 1 ) [ (0.5, 0.5, NaN) ] at org.locationtech.jts.noding.FastNodingValidator.checkValid (FastNodingValidator.java:139) org.locationtech.jts.geomgraph.EdgeNodingValidator.checkValid (EdgeNodingValidator.java:80) org.locationtech.jts.geomgraph.EdgeNodingValidator.checkValid (EdgeNodingValidator.java:45) org.locationtech.jts.operation.overlay.OverlayOp.computeOverlay (OverlayOp.java:229) org.locationtech.jts.operation.overlay.OverlayOp.getResultGeometry (OverlayOp.java:181) org.locationtech.jts.operation.overlay.OverlayOp.overlayOp (OverlayOp.java:84) org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp.getResultGeometry (SnapIfNeededOverlayOp.java:75) org.locationtech.jts.operation.overlay.snap.SnapIfNeededOverlayOp.overlayOp (SnapIfNeededOverlayOp.java:37) org.locationtech.jts.geom.GeometryOverlay.overlay (GeometryOverlay.java:76) org.locationtech.jts.geom.GeometryOverlay.union (GeometryOverlay.java:157) org.locationtech.jts.geom.Geometry.union (Geometry.java:1369) territory_bro.gis.geometry$union$invoke__Geometry__DOT_union__3537657.invoke (geometry.clj:49) ... territory_bro.gis.geometry$union.invokeStatic (geometry.clj:51) territory_bro.gis.geometry$union.invoke (geometry.clj:49) - JTS contains a GeometryFixer class which can be used to fix invalid geometries automatically. An older fix was to call Geometry.buffer(0), but that will lose part of the polygon. Related issues: locationtech/jts#657 locationtech/jts#652 - This commit also improves logging when there is a crash in the projections. Debugging them is easier when the error message shows which event caused the crash.
It is desirable to have a way to convert an invalid geometry into a valid one that preserves the character of the input (to some extent). This allows the fixed geometry to be used in JTS operations.
See
GeometryFixer
prototype code.Goals
Non-Goals
Motivating Cases
Descriptions use following terminology: "touch" means touches in a point, "adjacent" means touches along a line.
See XML file for example data test cases.
Issues
Semantics
NaN
,Inf
,-Inf
)Point
- keep valid coordinate, orEMPTY
LineString
- fix coordinate list. If left with 1 or 0 coordinates thenEMPTY
LinearRing
- fix coordinate list. If valid return as a ring, otherwise return as a LineString.Polygon
- transform into a valid polygon, preserving as much of the extent and vertices as possibleMultiPolygon
- fix each element, then ensure collection is non-overlapping (via union)GeometryCollection
- fix each elementPolygon Repair
There are two approaches for repairing self-intersecting polygons. Both of them start by forming the fully noded arrangement of the polygon linework.
See Subramaniam for further explanation.
The nominal shell and hole ring(s) are processed separately, and then the results are combined via a different operation to produce the final result.
A further refinement is to identify holes which are disconnected from (outside) the shell, and treat them separately as additional shells. This aligns with the above semantic of treating all linework lobes as polygons interiors.
Processing Options
The following semantic options may be provided as required.
Prior Art
ST_MakeValid
MakeValid
capability (although there are some issues with it)makeSimple
Algorithm
Polygons
buffer(0)
operations using opposite choices for winding number polarityMultiPolygons
The text was updated successfully, but these errors were encountered: