Conversation
|
FWIW this would be amazing if it works out for both the buffer and the clipping operations! On the off chance if there is any support you would need for this, let me know. |
|
Spent some time taking a look at just the intersect operations today. Couple of insights if you haven't got there already:
For reference the aforementioned benchmarks: Current 7.3 polyclip-ts: My clipper replacement (but as noted above, anything final will be slower than this: |
|
re: winding order, I think it still needs the reversal step because latitude is inverse of the cartesian Y axis. re: integer vs float64, I intended to use the float64 variants before but got it wrong in the first commit. I added the D suffix today to all of the calls. My output fixtures are now visually the same (except I think clipper2 adds more points along the rounded sides with clipper's default settings). I also had to add handling for invalid winding orders as seen in polygon-with-holes's inner ring. turf.min.js's linting does complain about the bigint syntax, which I think I'm not transitively using, but gets retained by the build instead of being treeshaken. We may have to do a major rev to be able to change that to some newer syntax (probably es2022). Perhaps when we do this, we just go ahead and document that we may roll forward and use any syntax that caniuse declares as "baseline" or another metric that we can use with babel's preset-env. |
|
Winding order HOWEVER - given that you are also projecting in your code the projection (which, apropos of the quote, is indeed from a graphics library :-)) it is the projection that does as you say and inverts the y and therefore requires the winding reversing that you are doing. I was playing with clipping, so was not projecting, hence experiencing the opposite. So in summary - clipper and geojson wind the same, but clipper and a d3 geoAzimuthalEquidistant projection wind opposite. Therefore - no reversal required when using geojson, reversal is required when running through a projection. Integer vs Float I think this means that the bigint syntax will be a requirement for clipper, so definitely major release, but as you point out it has been baseline for ages. Won't comment on the broader policy question. |
…s on closed linestrings
For the es5 compatible cdn version we have (I think) been able to transform and polyfill the bigint code in #2997. And if someone is importing the individual package esm / cjs directly into a web page, any browsers not supporting bigint will be under the > 0.25% / not dead criteria. For the node side of things, I think we're on safe ground to say we can start depending on a library that utilises ecmascript bigint. Node18 supports that at runtime, and that's the oldest node version we support. |
patches/clipper2-ts.patch
Outdated
| - hi64: Number(res >> 64n) | ||
| - }; | ||
| + lo64: JSBI.toNumber(JSBI.bitwiseAnd(res, JSBI.BigInt("0xffffffffffffffffn"))), | ||
| + hi64: JSBI.toNumber(JSBI.signedRightShift(res, JSBI.BigInt(64))), |
There was a problem hiding this comment.
From looking at this, I'm kind of skeptical that we can safely do lo64, hi64 without loss of precision, because Number can only contain 2^53 – 1. 🤔
| "@turf/unkink-polygon": "workspace:*", | ||
| "@turf/voronoi": "workspace:*", | ||
| "@types/geojson": "^7946.0.10", | ||
| "jsbi": "^4.3.2", |
There was a problem hiding this comment.
Perhaps this should be a devDependency? And its a touch sketchy to only do this here because we're actually using the patched version of clipper2-ts in the @turf/buffer tests. That means that consumers would be the first folks to test this with the real BigInt syntax.
|
@smallsaucepan curious for your take on how you like the pnpm patch for clipper2-ts (most recent commit on this PR). I think this is probably simpler than trying to use the babel transform since it didn't work out of the box. Edit: Its probably worth noting that I generated the |
- Be more intentional about the projection units (meters -> centimeters) - Use the clipper2-ts integer methods instead of the double ones - Update clipper2-ts to pick up some upstream fixes - TODO: wrap my head around the range that the projection outputs
|
(I know this doesn't build yet, but I wanted to check the generated test outputs and its easier to push a failing build. The necessary porting is done I think 🙏 ) |
…dian and north pole
…previous behavior
mfedderly
left a comment
There was a problem hiding this comment.
@smallsaucepan @bratter I think this is ready for input from ya'll. Happy for feedback over at https://github.com/mfedderly/geoclipper2 as well.
| @@ -1,26 +1,28 @@ | ||
| +import jsbi from "jsbi"; | ||
| + | ||
| export function crossProductSign64(pt1, pt2, pt3) { |
There was a problem hiding this comment.
crossProductSign64 is just a standard BigInt => JSBI conversion
| diff --git a/dist/isCollinear.js b/dist/isCollinear.js | ||
| index d86ab7b49f089586cd3c757554c4daa6237b4371..2c55a58cb9a5fe68aa14fde3009b888d570bf792 100644 | ||
| --- a/dist/isCollinear.js | ||
| +++ b/dist/isCollinear.js |
There was a problem hiding this comment.
isCollinear is a much more involved patch. When I profiled the bench script with native BigInt, the BigInt path barely showed up. When using jsbi it dominates the profile and speeds are about half of the speed of the jsts implementation. By just making sure we don't overflow on the multiply, we can usually get by without needing to use the jsbi path at all, and speeds return to faster-than-jsts.
I'll probably try to pull the BigInt avoiding path into the upstream eventually.
| - "packages/*" | ||
| - packages/* | ||
| patchedDependencies: | ||
| geoclipper2: patches/geoclipper2.patch |
There was a problem hiding this comment.
This patch only needs to exist until we can add native BigInt support, perhaps this summer with the new v8 milestone date.
| throw new Error("options must be an object"); | ||
| } | ||
|
|
||
| // NOTE steps is unused, clipper2 uses arcTolerance for the same effect |
There was a problem hiding this comment.
I think I'm proposing just deprecating steps and exposing arcTolerance as long as this migration PR sticks for a while without creating issues. They are two different ways of accomplishing the same thing (limit the number of points along the circle), but arcTolernace is not something we can cross-calculate from steps.
| if (radius === undefined) throw new Error("radius is required"); | ||
| if (steps <= 0) throw new Error("steps must be greater than 0"); | ||
|
|
||
| switch (geojson.type) { |
There was a problem hiding this comment.
All of this switching was required to match the output behavior of the original spec, specifically the nesting of Feature vs FeatureCollection. I suspect this could be made simpler by using some of the fooEach methods, but I think this is clear even though its verbose.
| const pt = proj.unproject(ring[i])!; | ||
|
|
||
| // normalize longitude to [-180, 180] | ||
| pt[0] = (((pt[0] % 360) + 540) % 360) - 180; |
There was a problem hiding this comment.
I've seen this done with while loops which can be fast if you are only one copy of the world off, but I tried some jsbench and the divide operations implied by the % operator here seemed to be around as fast. For that reason I like this because its less susceptible to performance if you wind up many copies of the world off.
| const inflated = inflatePaths( | ||
| options.project([[geojson.coordinates]]), | ||
| options.distance, | ||
| JoinType.Round, |
There was a problem hiding this comment.
I believe that this is the setting that people want control over (#639)
| unproject: (poly: Paths64) => [number, number][][]; | ||
| } | ||
| ): Polygon | MultiPolygon { | ||
| switch (geojson.type) { |
There was a problem hiding this comment.
This switch is once again verbose, but each of the little details is slightly different around how the project and unproject logic is done, as well as the EndType enum.
| DEFAULT_ARC_TOLERANCE | ||
| ); | ||
|
|
||
| // inflated can contain many rings, but they should all be outer rings |
There was a problem hiding this comment.
This implies that we can skip the groupPolygonPaths, but that it is required for MultiLineStrings and MultiPolygons
| return { | ||
| type: "MultiPolygon", | ||
| coordinates: polygons.map((poly) => options.unproject(poly)), | ||
| }; |
There was a problem hiding this comment.
Do we need to simplify MultiPolygons with only one Polygon to Polygon? If so we should remove any of that logic from bufferGeometry and put it in bufferGeometryWrapper instead.
Instead of messing around with @turf/jsts needing types, there's actually a new entrant into the underlying library space that solves a lot of problems at once.
clipper2-ts seems pretty exciting for us. I happened to find it because of our desire to drop our jsts dependency, but it also has intersect, union, difference, xor. So perhaps we can rework the other packages to depend on this library (polyclip-ts is slow with its bignumber implementation, and we'd have to fork it if we wanted to drop that). Because it's a port of a mature C library, it already comes with a ready-made test suite.
In the current state of this PR, the test suite fails to run, and 3 of the output fixtures look weird/broken (issue#783, negative-buffer, polygon-with-holes), but I'm pretty sure this is just me not understanding how to use the library.
Here's some numbers I grabbed with the work in progress version. Dropping @turf/jsts saves us ~26 of the total turf.min.js size, and it benchmarks faster (sometimes by a lot!).
// Note: the above benchmarks were done with native BigInt, and not the patched JSBI BigInt implementation
// The two BigInt methods barely show up in profiles, so I doubt jsbi is going to move the needle too much
Fixes #2929
Fixes #2920
Fixes #2895
Fixes #88
Fixes #2522
Fixes #2701
Fixes #2469 (Although I'm not really convinced that we create a usable polygon at the pole)