Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions packages/turf-mask/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,44 @@ Takes any type of [polygon][1] and an optional mask and returns a [polygon][1] e

**Parameters**

- `polygon` **([FeatureCollection][2] \| [Feature][3]<([Polygon][4] \| [MultiPolygon][5])>)** GeoJSON Polygon used as interior rings or holes.
- `mask` **[Feature][3]<[Polygon][4]>?** GeoJSON Polygon used as the exterior ring (if undefined, the world extent is used)
- `polygon` **([FeatureCollection][2] \| [Feature][3]<([Polygon][4] \| [MultiPolygon][5])>)** GeoJSON Polygon used as interior rings or holes.
- `mask` **[Feature][3]<[Polygon][4]>?** GeoJSON Polygon used as the exterior ring (if undefined, the world extent is used)

**Examples**

```javascript
var polygon = turf.polygon([[[112, -21], [116, -36], [146, -39], [153, -24], [133, -10], [112, -21]]]);
var mask = turf.polygon([[[90, -55], [170, -55], [170, 10], [90, 10], [90, -55]]]);
var polygon = turf.polygon([
[
[112, -21],
[116, -36],
[146, -39],
[153, -24],
[133, -10],
[112, -21],
],
]);
var mask = turf.polygon([
[
[90, -55],
[170, -55],
[170, 10],
[90, 10],
[90, -55],
],
]);

var masked = turf.mask(polygon, mask);

//addToMap
var addToMap = [masked]
var addToMap = [masked];
```

Returns **[Feature][3]<[Polygon][4]>** Masked Polygon (exterior ring with holes).

[1]: https://tools.ietf.org/html/rfc7946#section-3.1.6

[2]: https://tools.ietf.org/html/rfc7946#section-3.3

[3]: https://tools.ietf.org/html/rfc7946#section-3.2

[4]: https://tools.ietf.org/html/rfc7946#section-3.1.6

[5]: https://tools.ietf.org/html/rfc7946#section-3.1.7

<!-- This file is automatically generated. Please don't edit it directly:
Expand Down
4 changes: 4 additions & 0 deletions packages/turf-mask/bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ for (const { name, geojson } of fixtures) {
suite.add(name, () => turfMask(polygon, masking));
}

// basic x 4,627 ops/sec ±25.23% (21 runs sampled)
// mask-outside x 3,892 ops/sec ±34.80% (15 runs sampled)
// multi-polygon x 5,837 ops/sec ±3.03% (91 runs sampled)
// overlapping x 22,326 ops/sec ±1.34% (90 runs sampled)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🥇 I AM SPEED

suite
.on("cycle", (event) => {
console.log(String(event.target));
Expand Down
174 changes: 29 additions & 145 deletions packages/turf-mask/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import rbush from "rbush";
import union from "@turf/union";
import { polygon, featureCollection } from "@turf/helpers";
import turfBBox from "@turf/bbox";
import { flattenEach } from "@turf/meta";
import { polygon as createPolygon, multiPolygon } from "@turf/helpers";
import polygonClipping from "polygon-clipping";

/**
* Takes any type of {@link Polygon|polygon} and an optional mask and returns a {@link Polygon|polygon} exterior ring with holes.
Expand All @@ -24,63 +21,38 @@ function mask(polygon, mask) {
// Define mask
var maskPolygon = createMask(mask);

// Define polygon
var separated = separatePolygons(polygon);
var polygonOuters = separated[0];
var polygonInners = separated[1];
var polygonOuters = null;
if (polygon.type === "FeatureCollection") polygonOuters = unionFc(polygon);
else
polygonOuters = createGeomFromPolygonClippingOutput(
polygonClipping.union(polygon.geometry.coordinates)
);

// Union Outers & Inners
polygonOuters = unionPolygons(polygonOuters);
polygonInners = unionPolygons(polygonInners);
polygonOuters.geometry.coordinates.forEach(function (contour) {
maskPolygon.geometry.coordinates.push(contour[0]);
});

// Create masked area
var masked = buildMask(maskPolygon, polygonOuters, polygonInners);
return masked;
return maskPolygon;
}

/**
* Build Mask
*
* @private
* @param {Feature<Polygon>} maskPolygon Mask Outer
* @param {FeatureCollection<Polygon>} polygonOuters Polygon Outers
* @param {FeatureCollection<Polygon>} polygonInners Polygon Inners
* @returns {Feature<Polygon>} Feature Polygon
*/
function buildMask(maskPolygon, polygonOuters, polygonInners) {
var coordinates = [];
coordinates.push(maskPolygon.geometry.coordinates[0]);

flattenEach(polygonOuters, function (feature) {
coordinates.push(feature.geometry.coordinates[0]);
});

flattenEach(polygonInners, function (feature) {
coordinates.push(feature.geometry.coordinates[0]);
});
return polygon(coordinates);
function unionFc(fc) {
var unioned =
fc.features.length === 2
? polygonClipping.union(
fc.features[0].geometry.coordinates,
fc.features[1].geometry.coordinates
)
: polygonClipping.union.apply(
polygonClipping,
fc.features.map(function (f) {
return f.geometry.coordinates;
})
);
return createGeomFromPolygonClippingOutput(unioned);
}

/**
* Separate Polygons to inners & outers
*
* @private
* @param {FeatureCollection|Feature<Polygon|MultiPolygon>} poly GeoJSON Feature
* @returns {Array<FeatureCollection<Polygon>, FeatureCollection<Polygon>>} Outer & Inner lines
*/
function separatePolygons(poly) {
var outers = [];
var inners = [];
flattenEach(poly, function (feature) {
var coordinates = feature.geometry.coordinates;
var featureOuter = coordinates[0];
var featureInner = coordinates.slice(1);
outers.push(polygon([featureOuter]));
featureInner.forEach(function (inner) {
inners.push(polygon([inner]));
});
});
return [featureCollection(outers), featureCollection(inners)];
function createGeomFromPolygonClippingOutput(unioned) {
return multiPolygon(unioned);
}

/**
Expand All @@ -101,95 +73,7 @@ function createMask(mask) {
],
];
var coordinates = (mask && mask.geometry.coordinates) || world;
return polygon(coordinates);
}

/**
* Union Polygons
*
* @private
* @param {FeatureCollection<Polygon>} polygons collection of polygons
* @returns {FeatureCollection<Polygon>} polygons only apply union if they collide
*/
function unionPolygons(polygons) {
if (polygons.features.length <= 1) return polygons;

var tree = createIndex(polygons);
var results = [];
var removed = {};

flattenEach(polygons, function (currentFeature, currentIndex) {
// Exclude any removed features
if (removed[currentIndex]) return true;

// Don't search for itself
tree.remove({ index: currentIndex }, filterByIndex);
removed[currentIndex] = true;

// Keep applying the union operation until no more overlapping features
while (true) {
var bbox = turfBBox(currentFeature);
var search = tree.search({
minX: bbox[0],
minY: bbox[1],
maxX: bbox[2],
maxY: bbox[3],
});
if (search.length > 0) {
var polys = search.map(function (item) {
removed[item.index] = true;
tree.remove({ index: item.index }, filterByIndex);
return item.geojson;
});

for (var i = 0, l = polys.length; i < l; i++) {
currentFeature = union(currentFeature, polys[i]);
}
}
// Done
if (search.length === 0) break;
}
results.push(currentFeature);
});

return featureCollection(results);
}

/**
* Filter by Index - RBush helper function
*
* @private
* @param {Object} a remove item
* @param {Object} b search item
* @returns {boolean} true if matches
*/
function filterByIndex(a, b) {
return a.index === b.index;
}

/**
* Create RBush Tree Index
*
* @private
* @param {FeatureCollection<any>} features GeoJSON FeatureCollection
* @returns {RBush} RBush Tree
*/
function createIndex(features) {
var tree = rbush();
var load = [];
flattenEach(features, function (feature, index) {
var bbox = turfBBox(feature);
load.push({
minX: bbox[0],
minY: bbox[1],
maxX: bbox[2],
maxY: bbox[3],
geojson: feature,
index: index,
});
});
tree.load(load);
return tree;
return createPolygon(coordinates);
}

export default mask;
5 changes: 1 addition & 4 deletions packages/turf-mask/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@
"write-json-file": "*"
},
"dependencies": {
"@turf/bbox": "^6.4.0",
"@turf/helpers": "^6.4.0",
"@turf/meta": "^6.4.0",
"@turf/union": "^6.4.0",
"rbush": "^2.0.1"
"polygon-clipping": "^0.15.3"
}
}
4 changes: 2 additions & 2 deletions packages/turf-mask/test/out/mask-outside.geojson
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
[119.53125, -39.77476948529546]
],
[
[113.115234375, -22.105998799750566],
[112.587890625, -24.766784522874428],
[113.73046875, -29.382175075145277],
[114.78515624999999, -32.916485347314385],
Expand All @@ -25,7 +24,8 @@
[127.44140625, -17.560246503294888],
[122.431640625, -17.308687886770024],
[116.806640625, -17.978733095556155],
[113.115234375, -22.105998799750566]
[113.115234375, -22.105998799750566],
[112.587890625, -24.766784522874428]
]
]
}
Expand Down