-
Notifications
You must be signed in to change notification settings - Fork 362
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
GEOSGeom_setPrecision returns error for valid geometry #511
Comments
@mngr777 Here's the precision reduction issue. A question for you: the proposed fix will work if integral grid sizes are used (i.e. 153 instead of 152.87406, or 1223 instead of 1222.99245256). As long as the integral grid size is larger than the fractional one this should not cause downstream problems. Will this work for your use case? |
@Komzpa please take a look too. |
To me intuitively the current error mode looks like a simple mistake in error propagation analysis when spelling out the algorithm (something along the line of using heuristics of gridSize/100 and gridSize instead of gridSize/2 and thoroughly checking > >= compares to be complimentary). I am not convinced that making a float-point in nature grid "integerish" will fix anything rather than sweeping the issue under the carpet; the approach will still show interesting effects on coordinates after 2**54 where the distance between floats is bigger than 1. scale = 0.000817666534169 is nowhere near any floating point precision range boundary that I'd call an excuse. Generally speaking, if ST_MakeValid(ST_SnapToGrid(ST_Segmentize(geom, x/2),x)) can succeed, then GEOSGeom_setPrecision should succeed and give a similar result. If a dumb rounding can get two nodes to the same coordinates then the smart snapper should also be able to - at the very least by using dumb rounding to figure out what's supposed to become same node. The charts by @kalenikaliaksandr suggest that it often fails to do so: For grid sizes: these sized are pixel sizes of Web Mercator maps, are industry standard and can't really be changed. Not having them in proper way can create Moiré artifacts on rendering. The following grids have to be supported:
|
In this context "precision" means the number of significant digits stored. Even though the representation is FP (for convenience), the values stored can be lower precision. E.g.. if you store a value of 1 or 10 or 100 in an FP number, that is lower precision than storing 3.14159265358979323846. Values with low precision are less likely to cause numerical robustness problems.
Intuition is not code :). The Snap-Rounding code has turned out to be surprisingly delicate. But this might be a line of research to try.
That's an interesting comparison, but not quite apples-to-apples, since it adds (a lot) of vertices to the input geometry. It would be interesting to know if using Also note that the algorithm used by
Is this a proven issue, or just a theoretical concern? For vector tiles the output is eventually stored as fairly low-precision integers. Is this the case here? If so it seems like the coordinates will be rounded off anyway? |
floats store as 0.x * 10^y. there are always 53 front-aligned digits, not back-aligned so you can have insignificant leading digits. In geometry robustness is dictated by amount of (countable) float grid within the allowed error bound. A great write up by Vladimir Agafonkin https://observablehq.com/@mourner/non-robust-arithmetic-as-art It's also the reason why old overlay code sometimes can compute overlays near (0,0) and can't in their real position: https://github.com/gojuno/lostgis/blob/master/sql/functions/ST_Safe_Intersection.sql#L31
A hack we found that works is to ST_SnapToGrid(geom, eps/3) before feeding into ST_ReducePrecision(geom, eps) - it essentially classifies the nodes as "on grid lines" and "on grid centers". This confirms my sus that there is a bogus
Another one is that if you promise that integer grid sizes are robust (or that there is at least one robust grid size), then we can just wrap the thing into a scaler that will rescale the geometry so that the grid size is that legal one, snap and then scale back.
Similar things in the past triggered a vertical or horizontal gap between squares on square grid. |
Sounds like a good hack! Not sure if it really reveals the cause of the underlying issue, but it might be a direction worth pursuing.
Yes, I thought of this as well. Unfortunately I just tried it, and it looks like the GeometryPrecisionReducer still fails on the scaled geometry (using a scale factor of 1). However, it simplifies the situation which might make it easier to debug. It's possible that the problem is not with the Snap-Rounding algorithm, but with how the topology it generates is interpreted by the topology-building code. |
JTS now has the ability to specify a Update: the support for grid size is now ported to GEOS: 20a8074 Note that for
|
Closed by #513 |
@pramsey please consider re-opening the issue as second case haven't been fixed and I am working on it currently. I'm currently investigating the second case. Here's a simplified case that I believe still captures the issue: Same exception is raised as for the original case: This geometry is successfully simplified if Enlarged picture of a hole in the polygon: The only 2 intersections added by |
Reproducing in PostGIS:
|
Snap-Rounding is designed to operate in a finite fixed-precision grid. As such in theory it can/should be implemented using integral arithmetic. (This is what wagyu does). The JTS implementation is an attempt to support this concept but using FP numbers. I'm starting to wonder if it would be worth implementing a Snap-Rounding algorithm purely using integers, at least to support This is one reason why I still assert that |
If you're willing to rewrite it to be on integer grid then all the hacks that do anything about eps/100 need to be ripped away first, as they don't belong to snap rounding - the only op that's going to be needed to be used is grid-cell-equality-comparator. If you just grid it to the eps naively first it does work.
|
Yes, but this is almost certainly just due to the fact that with robustness issues like this one almost any "jiggling" of the input coordinates will no longer trigger the robustness failure. And this not a general-purpose fix. Some geometry can become invalid when snapped to a grid, and then the reducing precision algorithm fails. Here's an example: SELECT ST_AsText(ST_ReducePrecision(ST_SnapToGrid('POLYGON ((1 20, 9 20, 8.49999 1, 2.499999 18, 2.7 10, 5.66 9, 1 1, 1 20))',1), 1));
ERROR: lwgeom_reduceprecision: GEOS Error: TopologyException: side location conflict at 5 8 |
Case 2 does not depend on exact numbers and is easy to reproduce with following conditions:
E.g. here's a similar case with different numbers:
I couldn't find any related bugs to fix. It seems like an error that intersection is added at point |
Good analysis, @mngr777 . Well, as @Komzpa points out, the introduction of hacks using magic numbers (or magic division factors, in this case) is always suspect and asking for trouble. My original thinking (blind hope?) was that the snap-rounding would mask the use of a fudge-factor. Clearly it does not. More research required, I'm afraid. One thing I have recalled to mind is that technically speaking snap-rounding only applies to line arrangements whose coordinates already lie on the snapping grid. And as I showed above, it's easy to find examples where rounding the coordinates ahead of time corrupts the geometry topology. This is why the JTS snap-rounding executes in the input coordinate domain, at least initially. But this is pushing the approach perhaps further than it allows. This also says to me that just working in the integer domain may not be a solution for this particular issue, since it necessarily requires rounding the input coordinates up front. (It may be that |
@mngr777 it feels like an simple fix. The epsilon-distance circle lookup has to be replaced with box lookup originating from the snaprounding grid. If a line crosses a box of values that are going to be rounded to the same value, a node has to be dropped on that line, so that after rounding they are snapped together. In the case on the image it will cause them to just go to separate resulting grid cells and not interfere in any way. |
This is basically the description of the classic Snap-Rounding algorithm. But as I said, Snap-Rounding assumes input coordinates lie on the grid. If this is not the case then this is really outside the design envelope of SR. I found some failure cases which I tried to solve with that heuristic. They are in the JTS unit tests. Maybe it's worth trying removing the Could also make the |
Here's another possible solution:
@mngr777 @Komzpa would you be able to try this on your dataset? This will require a bit of GEOS code to be added around the call to GeometryPrecisionReducer. |
Is this still live? |
Original issue: GEOS-1127.
GEOSGeom_setPrecision
returns error on valid polygon geometry input, with certain large grid sizes.This fails in JTS as well. Note that in JTS
GeometryPrecisionReducer
is used directly. That takes a scale factor, which is the reciprocal of the grid size.Test Cases
Note: Case 1 is now resolved by #504, but in included here so it can be used for testing solutions.
Analysis
The reason this fails is because the Snap-Rounding noding used in
GeometryPrecisionReducer
is only designed to work for situations where the precision of the geometry coordinates are reduced from the full 53 bits of FP precision. Using non-integral precision scale factors (as in the failure cases) does not reduce the coordinate precision, and thus encounters robustness issues.Possible Solution
The
PrecisionModel
is designed to support integral scale factors for larger scales (= small grid sizes). Large grid sizes can produce small scale factors which contain high-precision decimal places. ThePrecisionModel
class can be extended to support integral grid sizes as well by allowing it to accept negative inputs, which will be interpreted as grid Sizes. (So for example a PrecisionModel(-1123) specifies a grid size of 1123.). If a grid size is specified then the internal precision rounding calculation will use the grid size rather than the reciprocal scale factor. This will prevent increasing the precision of the rounded numbers.This has been tested in a JTS prototype and works well.
The
GEOSGeom_setPrecision
grid size can accept negative inputs and use them as scale factors forGeometryPrecisionReducer
.The text was updated successfully, but these errors were encountered: