CoverageCleaner not preserving outskirts/borders
The new CoverageCleaner fixes most gaps in between polygons, but on the outskirts of the coverage, some surfaces are removed and some new surfaces are introduced. I would have expected the algorithm to preserve the borders of the coverage.
For instance when running Coverage.cleanSnap and snapDistance of 5 on the attached text file.
The missing surfaces, i.e. the surfaces from the original data that is missing from the cleaned data, are:
GEOMETRYCOLLECTION (POLYGON ((905357.5 7877870, 905360 7877852.5, 905352.5 7877870, 905357.5 7877870)), POLYGON ((905367.5 7877800, 905370 7877785, 905362.5 7877800, 905367.5 7877800)), POLYGON ((905390 7877708.085099999, 905355.0866 7877701.397399999, 905390 7877710, 905390 7877708.085099999)), POLYGON ((905282.5 7877760, 905280 7877765, 905287.5 7877760, 905282.5 7877760)), POLYGON ((905057.5 7879210, 905060 7879202.5, 905052.5 7879210, 905057.5 7879210)), POLYGON ((905447.5 7878640, 905450 7878597.5, 905442.5 7878640, 905447.5 7878640)))
While these are the new surfaces that are introduced by the cleaning:
GEOMETRYCOLLECTION (POLYGON ((905310 7877967.5, 905312.5 7877980, 905317.5 7877980, 905310 7877967.5)), POLYGON ((905049.3317999998 7877676.943599999, 905127.3246999998 7877678.223200001, 905131.6261999998 7877678.2782999985, 905049.3317999998 7877676.943599999)), POLYGON ((905050 7877780, 905047.5 7877760, 905044.4718000004 7877760.000100002, 905050 7877780)), POLYGON ((905062.5 7877860, 905057.5 7877840, 905054.4342 7877840.000100002, 905062.5 7877860)), POLYGON ((905169.4358000001 7878150.807300001, 905165 7878137.5, 905160.7324999999 7878135.3664, 905169.4358000001 7878150.807300001)), POLYGON ((905170 7878159.678800002, 905170 7878152.5, 905169.4446 7878150.833500002, 905169.4437999995 7878150.8215, 905169.4358000001 7878150.807300001, 905170 7878159.678800002)), POLYGON ((905420 7878372.5, 905422.5 7878390, 905427.5 7878390, 905420 7878372.5)))
If this is the expected behaviour , are there other algorithms better suited to clean up coverages while preserving the borders?
The reason the border linework is affected is that the snapping tolerance is fairly large compared to the size of the input (and more specifically, the input segment lengths). It's best to use a small snapping tolerance to avoid snapping away line segments which should not be changed. For this input, the smallest segment has length of 0.005, so a snapping tolerance of less that is safest. The default snapping tolerance is very small, so the simplest thing is to just use that.
If you want to eliminate the gaps in the coverage, then specify a gap width tolerance as well. A gap width of 20 works to remove all the gaps in this coverage.
CoverageCleaner.cleanGapWidth(coverage, 20.0);
Does this solve your issue?
@dr-jts Thank you, your solution works well for preserving the outer linework, but it doesn't remove/eliminate thin outskirt spikes/slivers. I have other data (attached) where there are very long spikes running along multiple polygons along the coast. You touch on this in the "Further Enhancements" section here: https://lin-ear-th-inking.blogspot.com/2025/04/coverage-cleaning-in-jts.html?m=1, so I guess this might not be intended to be fixable yet?
but it doesn't remove/eliminate thin outskirt spikes/slivers.
Yes, the current sliver merging doesn't split slivers. So it's possible to have a long sliver which lies adjacent to several polygons, and so when merged to a single polygon will a "spike" in that polygon. This is a bit of a research project about how to split the slivers into pieces which can be merged to multiple polygons to avoid spikes. (I think the only current solution that (might?) do this is pprepair, since it splits the input in triangles.). I'm hopeful to have a solution before very long.
I have other data (attached) where there are very long spikes running along multiple polygons along the coast.
This is a complex situation, since there are thin and/or small-area polygons which are present in the input (if I'm understanding it correctly). The coverage cleaner is designed to preserve input polygons, so it doesn't check for or merge thin/small polygons which are in the input. (Some may get merged as a result of the snapping, however).
This could be added as an option or a separate operation, I think. And perhaps there should be a way to be able to prevent some small polygons from being merged, in case there are some which are actually important to be kept? And of course the polygon-splitting described above would be good to have as well.
@dr-jts I see, thank you. Both the polygon splitter and the eliminator of thin polygons would be very useful.
Another very important operation for my use case, would be to snap one set of geometries to another set of geometries. This is an option in the QGIS snap function, where you can snap geometries to a "reference layer".
https://github.com/qgis/QGIS/blob/78aa6f1fc4506fa369c024e797fad4e8ba86bd6f/src/analysis/vector/qgsgeometrysnapper.h
The thin polygons I sent are mostly a result of overlaying geometries from multiple input datasets with almost, but not exactly, matching lineworks. Ideally, these datasets should be aligned/snapped together before performing the overlays.
Another very important operation for my use case, would be to snap one set of geometries to another set of geometries. This is an option in the QGIS snap function, where you can snap geometries to a "reference layer".
Yes, that's a useful cleaning function. Does your use case have polygonal coverages as input? Or is your use case more general?
@dr-jts Yes, we have a couple of polygon datasets (land use) that cover the entire mainland of Norway, and some more detailed datasets of specific land use types (roads, lakes, rivers, buildings etc.) to be put on top. The final product also needs to be snapped to the coast line, which is a separate dataset.
@dr-jts Sorry for putting several topics into one issue, but yet another operation we need to do is to reduce the precision of the coordinates.
When taking the snapRoundingNoder, as suggested in the last paragraph of https://lin-ear-th-inking.blogspot.com/2025/04/coverage-cleaning-in-jts.html?m=1, and applying it on the latest dataset I attached with a precision scale of 10, I get a MultiLineString where the number of decimal places are all reduced to 1, but the number of coordinate pairs go from 2818 in the original polygon data to 5419 in the resulting MultiLineString. This makes the data larger in bytes and more complex to work with. Is this the expected behavior?
Also, why is the result a MultiLineString? I don't see an obvious way to convert this back to polygons as there is no info on what were polygon exteriors and what were holes/interiors.
@mortewle I'm not sure why you're seeing that behaviour with the SnapRoundingNoder. Can you open a separate issue and post sample code?
but it doesn't remove/eliminate thin outskirt spikes/slivers. I have other data (attached) where there are very long spikes running along multiple polygons along the coast.
A critical difference between spikes formed by gaps (which are surrounded by coverage polygons) and spikes along the outside is that with gap spikes there is always a "longest edge" polygon to merge to, This is not the case for outer spikes, since the long edge may lie along the outside. This means that just relying on splitting at existing vertices is still going to create spikes. Avoiding this would require introducing vertices on the outer edge of the spike, opposite interior nodes. I'm not sure how much more complicated the logic will be to do this.