martinez-polygon-clipping: boolean operations on GeoJSON polygons in TypeScript
Martinez-Rueda polygon clipping algorithm, does boolean operation on polygons (multipolygons, polygons with holes etc): intersection, union, difference, xor
At a glance
- What is it?
- The martinez-polygon-clipping package implements the Martinez-Rueda-Feito polygon clipping algorithm in TypeScript, exposing intersection, union, difference and xor over GeoJSON polygon and multipolygon coordinates, including polygons with holes and self-intersecting input. It is fast on the cases the project measures, and its lineage explains both its strengths and the forks that diverged from it.
- Who is it for?
- Adopt martinez-polygon-clipping when your application needs intersection, union, difference or xor over GeoJSON polygons and multipolygons, especially if the input may contain holes or self-intersections and you care about throughput. Do not adopt it when you need arbitrary precision geometry, because the fork polyclip-ts computes with bignumber.js decimals, or when you need the coordinate snapping that polygon-clipping added, since that fork deliberately left this codebase.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A focused clipping library, not a general geometry toolkit
The package does one job. Its description is that it implements the Martinez-Rueda polygon clipping algorithm and performs boolean operations on polygons, including multipolygons and polygons with holes, with intersection, union, difference and xor.
What it is not is a geometry framework. There is no point-in-polygon helper, no distance function, no spatial index, no projection handling. The whole public surface is four functions that take two geometries and return one.
The input format is GeoJSON. The README is specific that a geometry is a GeoJSON Polygon or MultiPolygon coordinates structure, and the result is always MultiPolygon coordinates, or `[]` when the result is empty. The output rings are closed and oriented as RFC 7946 requires, with exterior rings counter-clockwise and holes clockwise. That last point is worth internalising early, because a lot of downstream work breaks on ring winding rather than on geometry.
The algorithm itself comes from academic work. The README credits a 2008 and 2013 paper on a new algorithm for computing Boolean operations on polygons by Francisco Martinez, Antonio Jesus Rueda and Francisco Ramon Feito, along with its C++ code, and lists Alexander Milevski and Vladimir Ovsyannikov as the authors of this implementation.
The one extension over the original is stated plainly: the algorithm of Martinez et al. was extended to work with multipolygons without cascading. Cascading multipolygons, where one polygon sits inside a hole of another, are the classic failure case for this family of algorithms, and handling them is the main functional difference from a straight port.
Four functions, one return shape, and an enum for dispatch
The API section of the README lists the whole interface.
- `.intersection(<Geometry>, <Geometry>) => <MultiPolygon>` - `.union(<Geometry>, <Geometry>) => <MultiPolygon>` - `.diff(<Geometry>, <Geometry>) => <MultiPolygon>` - `.xor(<Geometry>, <Geometry>) => <MultiPolygon>`
Note the asymmetry in the naming. Intersection, union and xor are spelled out, while the difference operation is called `diff`. That inconsistency will bite anyone who guesses the function name rather than reading the README, and it is the kind of detail that argues for wrapping the library behind your own thin interface if you call it from several places.
For code that chooses an operation at runtime, the README provides an `Operation` enum with the values `{ INTERSECTION: 0, UNION: 1, DIFFERENCE: 2, XOR: 3 }`, described as being there for the case where you have to decide programmatically which operation you need. The numbers are meaningful only as the enum's own values, not as anything the library interprets.
The uniform MultiPolygon output is the other thing to plan for. A union of two overlapping squares comes back as a MultiPolygon with one member, and an intersection with no overlap comes back as an empty array rather than null or an empty geometry object. Code that assumes a Polygon result will need a check, and the empty case is where a lot of downstream code quietly produces undefined behaviour.
Using it from TypeScript takes an import and a coordinates array
The README gives a short example, and the package is published on npm under the name `martinez-polygon-clipping`.
import * as martinez from 'martinez-polygon-clipping';
const gj1 = { "type": "Feature", ..., "geometry": { "type": "Polygon", "coordinates": [ [ [x, y], ... ] ]}};
const gj2 = { "type": "Feature", ..., "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [x, y], ...] ] ]}};
const intersection = {
"type": "Feature",
"properties": { ... },
"geometry": {
"type": "Polygon",
"coordinates": martinez.intersection(gj1.geometry.coordinates, gj2.geometry.coordinates)
}
};Read carefully: the functions take coordinates, not geometries. In the example the call passes `gj1.geometry.coordinates` and `gj2.geometry.coordinates`, so you strip the GeoJSON wrappers yourself and wrap the result back into whatever shape your application expects. The example output object in the README labels its geometry type as `Polygon` while the coordinates it puts in are the MultiPolygon result of the call, so treat the wrapper in the example as illustrative rather than as a contract. The README states the result is always MultiPolygon coordinates.
The package is built as an ES module with a CommonJS fallback, and the published files are the built bundle in `dist/`. The `package.json` shows `main` pointing at `dist/martinez.cjs`, `module` and `browser` both at `dist/martinez.js`, and an `exports` map with separate `types`, `import` and `require` conditions, so TypeScript consumers get `dist/index.d.ts` and bundlers get the ESM build without configuration.
The benchmark table is reproducible, and it says what it does not cover
The README publishes a benchmark table with operations per second, higher being better, and says you can reproduce it by running `npm run bench`. The script is visible in `package.json` and builds the bundle before running the suite, so the numbers are meant to be checkable rather than asserted.
The comparison set is JSTS 2.12, polygon-clipping 0.15 and polyclip-ts 0.16, and the README states that all libraries produce the same result areas on these inputs. The machine is given as an Apple M3 with Node 22.17.
Reading the table is more informative than reading the winner's margin. On the Hole_Hole union case with 20 vertices, Martinez is listed at 68,058 against 10,549 for JSTS and 28,687 for polygon-clipping, with polyclip-ts at 1,712. On the largest case, the Asia union with 28k vertices, the figures are 46.8 for Martinez, 32.2 for JSTS, 19.2 for polygon-clipping and 3.18 for polyclip-ts. The shifted cases, where the Asia polygon is clipped against a copy of itself moved slightly east so that nearly every edge intersects, show the same ordering with a much larger gap, at 16.2 against 5.29, 6.06 and 0.97.
Two caveats are worth stating. These are the project's own measurements, taken on inputs the project chose, and the README does not publish a broader corpus comparison. And the slow polygon case in particular is one where nearly every edge intersects, which is the pathological case for sweep line clipping and where a difference of a few times is expected rather than surprising. The demo page `demo/cases.html` links to a slider that visualises the shift so you can see the slivers the benchmark creates.
Because the benchmark runs the built bundle rather than the source, it measures what consumers actually import.
Two downstream forks diverged in opposite directions
The README has a Related projects section that is unusually informative, because it explains how two other libraries came to exist and what each changed.
polygon-clipping by Mike Fogel was forked from this repository in February 2018 and has been developed separately since, according to the README, with its own licence. The two changes it made are specific: it snaps coordinates and intersection points to previously seen values within floating-point precision, and it caps the sizes of its internal structures as a guard against infinite loops. The first is a robustness measure against numerical noise, the second is a termination safeguard. Neither is present in this codebase.
polyclip-ts by Luiz Barboza is a TypeScript fork of polygon-clipping, and so indirectly a fork of this repository. Its distinguishing feature is that it computes with arbitrary-precision decimals using bignumber.js, and it is the engine behind `@turf/union` and the other Turf 7 boolean operations.
Those forks define the choice you are making. If your geometry pipeline suffers from floating point noise, polygon-clipping's snapping and structure caps address problems this package leaves open. If you need exact decimal arithmetic, polyclip-ts goes further in the other direction, at a large cost in throughput. JSTS is the option for a JVM-based stack, and the README includes it in the benchmark as the reference point rather than describing it as a near equivalent.
The demo and tooling round out the comparison. `npm run dev` starts the demos, one being an interactive map where you can edit the polygons, and the other a test case viewer that lets you pick any case from `test/genericTestCases` or `test/fixtures`, choose an operation, and see the inputs, the computed result and whether it matches the expected result, with the selection kept in the URL so a failing case can be linked.
A five year gap, then v0.9.0 in 2026
The release history needs reading carefully. The most recent tag is v0.9.0 on 2026-09-30, and the last push to the repository was on 2026-10-16. Before that came v0.7.1 on 2021-05-09 and v0.6.2 on 2020-02-18. So the project spent roughly five years between v0.7.1 and v0.9.0 without a tagged release, and the version in `package.json` matches the newest tag at 0.9.0.
What that means in practice depends on how you depend on it. If you pin a version in a `package.json`, you are safe from drift until you choose to upgrade, and the 0.x line means a minor bump can still carry breaking changes under normal semantic versioning conventions. If you track the branch, the five year gap suggests the rate of change between releases is not something to plan around.
The repository is not archived, and the recent push is recent enough that work is landing. The tooling shows a modern setup: Vite for the build, Vitest for tests, oxlint for linting, oxfmt for formatting, TypeScript with a `typecheck` script, and `simple-git-hooks` wired to the `prepare` script. The `prepublishOnly` script runs lint, the test suite and the build, so a published release is gated on all three.
Licensing is MIT, with the copyright line naming Alexander Milevski and the year 2026. The MIT terms are permissive, allow use in closed source products, and carry no copyleft obligation, which is one of the reasons this library is embedded so widely in JavaScript geospatial stacks.
Editorial conclusion
Adopt martinez-polygon-clipping when your application needs intersection, union, difference or xor over GeoJSON polygons and multipolygons, especially if the input may contain holes or self-intersections and you care about throughput. Do not adopt it when you need arbitrary precision geometry, because the fork polyclip-ts computes with bignumber.js decimals, or when you need the coordinate snapping that polygon-clipping added, since that fork deliberately left this codebase. Verify first that the ring orientation of your output matches RFC 7946, that the result is always a MultiPolygon even for a simple intersection, and that your own datasets, not only the benchmark cases, produce the areas you expect before you ship it in a pipeline.
Frequently asked questions
What does martinez-polygon-clipping return for an intersection of two polygons?
The result is always MultiPolygon coordinates, or `[]` when the result is empty, with closed rings oriented as RFC 7946 requires, exterior rings counter-clockwise and holes clockwise. The functions take GeoJSON Polygon or MultiPolygon coordinates structures as input.
How do I run the martinez-polygon-clipping benchmarks?
The README says to run `npm run bench`, which the package.json defines as `vite build && vitest bench --run --reporter verbose`. It compares the built bundle against JSTS, polygon-clipping and polyclip-ts on the same inputs, which the README states produce the same result areas.
What is the difference between martinez-polygon-clipping and polyclip-ts?
polyclip-ts is a TypeScript fork of polygon-clipping, which was itself forked from this repository. It computes with arbitrary-precision decimals using bignumber.js and powers the Turf 7 boolean operations, while this package and polygon-clipping work in regular floating point.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/w8r-martinez)Was this useful?