Skip to content

Commit

Permalink
Rework Mask module (#2130)
Browse files Browse the repository at this point in the history
* rework dissolve

* make es5 friendly

* remove spread operator

* remove arrow function

* another prettier fix

Co-authored-by: mfedderly <mdfedderly@mdfedderly.com>
  • Loading branch information
rowanwins and mfedderly committed Jul 6, 2021
1 parent 779ac05 commit 4a0ff38
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 160 deletions.
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]&lt;([Polygon][4] \| [MultiPolygon][5])>)** GeoJSON Polygon used as interior rings or holes.
- `mask` **[Feature][3]&lt;[Polygon][4]>?** GeoJSON Polygon used as the exterior ring (if undefined, the world extent is used)
- `polygon` **([FeatureCollection][2] \| [Feature][3]&lt;([Polygon][4] \| [MultiPolygon][5])>)** GeoJSON Polygon used as interior rings or holes.
- `mask` **[Feature][3]&lt;[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]&lt;[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)
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

0 comments on commit 4a0ff38

Please sign in to comment.