Skip to content

Conversation

@mfedderly
Copy link
Collaborator

@mfedderly mfedderly commented Jul 15, 2025

The npm package marchingsquares has a complicated licensing story. I'm going to attempt to rewrite it from scratch and by using other compatible licensed code for reference.

@turf/isolines

Performance seems quite a bit faster (3x?) without much effort in optimization, timings taken on an M4 Pro Macbook Pro.
Rich diffs also seem to not be enabled on these geojson fixtures, so I added some screenshots using geojson.io below. To my eye they are the same, except that some of the array items have been rotated and/or the order of the linestrings may have changed.

Before After
bigMatrix x 963 ops/sec ±0.37% (97 runs sampled) bigMatrix x 3,139 ops/sec ±0.39% (98 runs sampled)
bigmatrix-before bigmatrix-after
matrix1 x 64,135 ops/sec ±0.50% (95 runs sampled) matrix1 x 178,198 ops/sec ±0.40% (98 runs sampled)
matrix1-before matrix1-after
matrix2 x 45,449 ops/sec ±0.63% (96 runs sampled) matrix2 x 126,148 ops/sec ±0.28% (96 runs sampled)
matrix2-before matrix2-after
pointGrid x 56,868 ops/sec ±0.31% (98 runs sampled) pointGrid x 154,980 ops/sec ±0.33% (100 runs sampled)
pointgrid-before pointgrid-after

@mfedderly mfedderly force-pushed the mf/rewrite-marchingsquares branch 2 times, most recently from cdcfaec to b3b9799 Compare July 15, 2025 18:48
@smallsaucepan
Copy link
Member

Brilliant work @mfedderly! When this first came up I made a start on a rewrite package. What do you think about externalising this implementation through there? The author of the marching-squares package on npm agreed to hand over that name to allow for a modern update.

@mfedderly
Copy link
Collaborator Author

I think I prefer just putting things in Turf directly if we're going to be maintaining it. Over the years we've had issues consuming other people's packages due to various repo issues, like types not being correct, or nuance about package.json export fields, etc. We spent a pretty good amount of time on the monorepo infrastructure in this repo so it makes sense to try to use it where possible. The only place where I've seen this reasoning break down is when we've got code that we want to share between two Turf packages where we don't want the underlying code to itself be a public module, that gets a little tougher.

@smallsaucepan
Copy link
Member

If it's general purpose, self contained functionality though why not release it separately so other people can benefit? There's clearly a need for a non-GPL marching squares library. All those reasons about types and package.json no longer apply as we have control over packaging and publishing. Seems like win-win to me.

@mfedderly mfedderly force-pushed the mf/rewrite-marchingsquares branch from b3b9799 to 6d5bf3f Compare July 19, 2025 15:39
@mfedderly
Copy link
Collaborator Author

I tried to make this faster for a few days, including some really neat ideas from d3-contour but was unable actually make the line segment reassembly phase faster. I'm going to leave it for now. It did give me some guidance on how to do the isobands work at least.

@mfedderly
Copy link
Collaborator Author

@smallsaucepan my primary issue with making another repo is that then we have to maintain all of this stuff in another repo, where we already have all the infrastructure in Turf to add another package. I'd be more inclined to add a new @turf/marchingsquares than an entirely separate repo somewhere.

I'm also not entirely convinced that marchingsquares code would be truly reusable. You're going to wind up with data format issues (some data will use number[] with a specific index ordering, some will use number[][] instead, etc). You're probably better off by just consuming a higher level package from Turf, or copy-pasting the marchingsquares implementation you want (isolines is different than what I'm planning for isobands, because bands needs closed polygons), and modifying it to suit your needs.

@mfedderly mfedderly force-pushed the mf/rewrite-marchingsquares branch 2 times, most recently from 90741d9 to 55a8a66 Compare July 23, 2025 17:41
@mfedderly
Copy link
Collaborator Author

Ok I think I have a working isobands, but it is remarkably slow so I need to iterate some more. I'm just happy this thing is working at this point 😩 . I really struggled with the polygon reassembly logic. This is also 'cheating' in that it doesn't use the isobands logic from Wikipedia, but it instead just calculates the rings at each threshold and then does polygon difference.

From looking at the geojson test output before and after... I may have actually fixed some bugs along the way too. 🤔

pr

bigMatrix x 81.85 ops/sec ±0.68% (72 runs sampled)
matrix1 x 2,131 ops/sec ±0.39% (98 runs sampled)
matrix2 x 1,091 ops/sec ±0.49% (97 runs sampled)
pointGrid x 597 ops/sec ±0.47% (96 runs sampled)

master

bigMatrix x 487 ops/sec ±1.27% (83 runs sampled)
matrix1 x 44,815 ops/sec ±0.26% (98 runs sampled)
matrix2 x 22,873 ops/sec ±0.24% (99 runs sampled)
pointGrid x 28,737 ops/sec ±0.27% (97 runs sampled)

@mfedderly mfedderly force-pushed the mf/rewrite-marchingsquares branch from 060359d to 1e75a17 Compare July 25, 2025 16:30
return results;
}

function isoContours(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smallsaucepan After getting this all fixed up, this is probably the most shareable nugget of code, but I'm still not convinced that it will be broadly useful outside of Turf.

Specifically: because we're scaling latitudes, our y axis is essentially reversed from the index into matrix, that means that the tr/br/bl/tl lookups are logically reversed on the Y axis, and then the counterclockwise winding of the polygon is also logically reversed for the same reason.

I'm thinking that perhaps a well commented implementation to accompany the Wikipedia explanation would be more useful to someone who wants to do isolines/isobands on data that isn't already geojson. The Isobands polygon-ification step was really hard to reason about so I tried to add a lot of comments there.

As I'm looking at this PR, I kind of think that I like Turf having its own implementations that can serve as references to others if you don't want exactly what we're providing, but want to write your own code for something similar. We can also help control our own destiny a bit more (and avoid a polyclip-ts situation).

@mfedderly mfedderly force-pushed the mf/rewrite-marchingsquares branch from 1e75a17 to f796cd9 Compare July 25, 2025 16:39
@mfedderly
Copy link
Collaborator Author

I rebased this to squash it into two commits. I think we should merge @turf/isolines and @turf/isobands changes in separate PRs, but because there's some shared code I've left them together at the moment just to help me clean up both changes at the same time.

@mfedderly
Copy link
Collaborator Author

The strange output of @turf/isobands in matrix2 is fixed over here: #2925

@mfedderly mfedderly force-pushed the mf/rewrite-marchingsquares branch from f796cd9 to fa844cf Compare July 29, 2025 18:20
@mfedderly mfedderly changed the title Rewrite marchingsquares Rewrite @turf/isolines Jul 29, 2025
@mfedderly mfedderly marked this pull request as ready for review July 29, 2025 18:26
Copy link
Member

@smallsaucepan smallsaucepan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive work @mfedderly 👏

"contributors": [
"Stefano Borghi <@stebogit>"
"Stefano Borghi <@stebogit>",
"Matt Fedderly <@mfedderly>"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This you've added yourself to isobands twice. I'll add you to isolines package.json too 😉

@smallsaucepan smallsaucepan merged commit f4dea9c into master Nov 8, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants