-
Notifications
You must be signed in to change notification settings - Fork 198
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
Back BooleanOps with i_overlay to avoid crashes from floating point collapse #1234
Conversation
65b936c
to
02487c1
Compare
@rmanoka - I'd love to get your input on this. |
@@ -215,6 +215,11 @@ pub(crate) enum Operation { | |||
op: BoolOp, | |||
expected: Geometry<f64>, | |||
}, | |||
ClipOp { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JTS doesn't have a special "clip" operation. Instead it supports boolean operations on LineString x Polygons, so I've mapped those to our BooleanOp::clip method to get some more test coverage.
fcb660b
to
8f3105d
Compare
Looks great! Maybe we can squeeze out a bit more performance in the future but for now I'd say getting a non panicking version of the algorithms would be far more important and a big relief for a lot of people. I'll review it as soon as I'll be back home! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellect! LGTM!
Been testing this on data where I basically perform thousands of union and differences in loops, working with geo map data, where I get every road segment as a polygon, then union them all into one large Multipolygon struct. Then with this I want the difference of these multipolygon structs. To create a puzzle like map. Which for now I have not been able to crash this after the hotfix to iOverlay :) |
Just wanted to say a huge thank you to @michaelkirk, @NailxSharipov, @RobWalt, and everyone else who's been pushing this forward. Stable boolean ops are incredibly useful to a bunch of downstream projects! |
type StringOverlayType = F64StringOverlay; | ||
} | ||
|
||
impl super::BoolOpsCoord<f64> for F64Point { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe inlining all of these method impls would be a good idea to get a bit more performance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically I don't like to back seat drive the compiler, but there are some nice boosts here.
re: https://std-dev-guide.rust-lang.org/policy/inline.html
Maybe this falls under "public, small, non-generic functions."
cargo bench from inline
Circular polygon boolean-ops/bops::intersection/256
time: [456.98 µs 461.66 µs 467.85 µs]
change: [-1.5757% -0.8205% +0.0412%] (p = 0.11 > 0.05)
No change in performance detected.
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe
Circular polygon boolean-ops/bops::union/256
time: [457.72 µs 458.64 µs 459.76 µs]
change: [-2.0602% -1.7872% -1.5125%] (p = 0.00 < 0.05)
Performance has improved.
Circular polygon boolean-ops/bops::intersection/512
time: [1.3860 ms 1.3888 ms 1.3916 ms]
change: [-10.975% -10.623% -10.346%] (p = 0.00 < 0.05)
Performance has improved.
Circular polygon boolean-ops/bops::union/512
time: [1.3902 ms 1.3922 ms 1.3943 ms]
change: [-11.574% -10.967% -10.469%] (p = 0.00 < 0.05)
Performance has improved.
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
Circular polygon boolean-ops/bops::intersection/1024
time: [4.2887 ms 4.2972 ms 4.3112 ms]
change: [-8.7721% -7.8505% -7.1175%] (p = 0.00 < 0.05)
Performance has improved.
Found 2 outliers among 10 measurements (20.00%)
1 (10.00%) high mild
1 (10.00%) high severe
Circular polygon boolean-ops/bops::union/1024
time: [4.2956 ms 4.3075 ms 4.3290 ms]
change: [-7.9012% -7.3024% -6.5849%] (p = 0.00 < 0.05)
Performance has improved.
Found 2 outliers among 10 measurements (20.00%)
1 (10.00%) high mild
1 (10.00%) high severe
Circular polygon boolean-ops/bops::intersection/2048
time: [14.081 ms 14.098 ms 14.122 ms]
change: [+0.1742% +0.4568% +0.8040%] (p = 0.01 < 0.05)
Change within noise threshold.
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe
Circular polygon boolean-ops/bops::union/2048
time: [14.078 ms 14.116 ms 14.160 ms]
change: [-0.2573% +0.3188% +0.8433%] (p = 0.30 > 0.05)
No change in performance detected.
Circular polygon boolean-ops/bops::intersection/4096
time: [50.853 ms 50.954 ms 51.038 ms]
change: [-0.8439% -0.2164% +0.4735%] (p = 0.54 > 0.05)
No change in performance detected.
Found 2 outliers among 10 measurements (20.00%)
1 (10.00%) high mild
1 (10.00%) high severe
Circular polygon boolean-ops/bops::union/4096
time: [50.889 ms 50.969 ms 51.048 ms]
change: [-0.7440% -0.2436% +0.3009%] (p = 0.40 > 0.05)
No change in performance detected.
Found 2 outliers among 10 measurements (20.00%)
2 (20.00%) high severe
Circular polygon boolean-ops/bops::intersection/8192
time: [188.02 ms 188.28 ms 188.58 ms]
change: [-1.2461% -1.0530% -0.8445%] (p = 0.00 < 0.05)
Change within noise threshold.
Circular polygon boolean-ops/bops::union/8192
time: [187.91 ms 188.13 ms 188.39 ms]
change: [-1.1870% -1.0354% -0.8655%] (p = 0.00 < 0.05)
Change within noise threshold.
// debug!("\t{geom:?}", geom = aseg.0.geom()); | ||
// false | ||
// }); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of commenting it out, what if we put this logic behind a debug assertions flag? https://stackoverflow.com/a/39205417
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commented out code was altogether deleted, so I don't think there's anything further for us to do here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow I completely misread the diff
overlay::F64Overlay, | ||
string::{F64StringGraph, F64StringOverlay}, | ||
}; | ||
use i_overlay::i_float::f64_point::F64Point; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if they'd be open to incorporating num-traits
in the future. Might simplify some of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very likely they just didnt know about it, or there were some issues using it, the maintainer seems very nice so you could probably just ask them over at their github
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉 🎉
Any blockers to merging @michaelkirk ? |
We've had several reports of crashes in our BooleanOps implementation over the years. Some of them have been addressed, but there remains a long tail of crashes. FIXES #913, #948, #976, #1053, #1064, #1103, #1104, #1127, #1174, #1189, 1193 The root of the issue (as I understand it) is that floating point rounding errors break the invariants of our sweep line algorithm. After a couple years, it seems like a "simple" resolution is not in sight. In the meanwhile, the i_overlay crate appeared. It uses a strategy that maps floating point geometries to a scaled fixed point grid, which nicely avoids the kind of problems we were having. Related changes included: Included are some tests that cover all reports in the issue tracker of the existing BoolOps panic'ing JTS supports Bops between all geometry types. We support a more limited set: 1. Two 2-Dimensional geometries `boolean_op`. 2. A 1-Dimenstinoal geometry with a 2-D geometry, which we call `clip`. So this maps JTS's Line x Poly intersection tests to our Clip method. - rm unused sweep code now that old boolops is gone I marked a couple fields as `allow(unused)` because they are used for printing debug repr in tests. Speed up benches by only benching local boolop impl by default
2b0f994
to
4915dfc
Compare
rebased and squashed, since my commits were kind of a mess.
I'll queue for merge now. 👍 |
CHANGES.md
if knowledge of this change could be valuable to users.This PR replaces our existing BooleanOps implementation with one backed by i_overlay.
We've had several reports of crashes in our BooleanOps implementation over the years. Some of them have been addressed, but there remains a long tail of crashes.
The root of the issue (as I understand it) is that floating point rounding errors break the invariants of our sweep line algorithm. After a couple years, it seems like a "simple" resolution is not in sight.
In the meanwhile, this i_overlay crate has appeared. It is well documented (seriously, check out the algorithm description and interactive demo). Additionally, the maintainer @NailxSharipov has been responsive at answering questions and even making some changes to match our needs.
Performance
Performance is interesting. For our benches the new implementation is faster for smaller inputs and slower for larger inputs. The break even size is about 2k points. My guess is that this will be a win for most people, but it's unfortunate that it's a slowdown for large inputs.
(obligatory disclaimer that our benches might not accurately reflect real world use cases)
Apparently there are some performance oriented changes in the works upstream: iShape-Rust/iOverlay#6 (comment)
`cargo bench` output with this PR
Alternatives
Another solution is proposed by @RobWalt, implementing boolean operations by "triangulating" polygons and then "stitching" that triangulation back together. This too avoids crashing in all known test cases. Unfortunately it's quite a bit slower. I haven't seen it run on the additional JTS test cases we're now using, but getting that assurance of correctness would be good if we were to pursue that route.
`cargo bench` output with SpadeBoolops from #1089
FIXES #913, #948, #976, #1053, #1064, #1103, #1104, #1127, #1174, #1189, #1193
Also a little progress towards #1203, in that you can now mix and match
Polygon x MultiPolygon
, but you still can't generically "bop"Point x LineString
orLineString x LineString
.