Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for holes to @turf/mask #2346

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion packages/turf-mask/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ import { Feature, Polygon, MultiPolygon, FeatureCollection } from "geojson";
export default function <T extends Polygon | MultiPolygon>(
poly: Feature<T> | FeatureCollection<T> | T,
mask?: Feature<Polygon> | Polygon
): Feature<Polygon>;
): Feature<Polygon | MultiPolygon>;
36 changes: 29 additions & 7 deletions packages/turf-mask/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ 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.
* Takes any type of {@link Polygon|polygon} and an optional mask and returns a {@link Polygon|polygon} exterior ring with holes. If the input polygon has holes
* and you'd like to retain them then set `ignoreHoles=false` and a {@link MultiPolygon|multipolygon} will be returned where the holes will be returned as outer rings.
*
* @name mask
* @param {FeatureCollection|Feature<Polygon|MultiPolygon>} polygon GeoJSON Polygon used as interior rings or holes.
* @param {Feature<Polygon>} [mask] GeoJSON Polygon used as the exterior ring (if undefined, the world extent is used)
* @returns {Feature<Polygon>} Masked Polygon (exterior ring with holes).
* @param {Object} [options={}] Optional parameters
* @param {boolean} [options.ignoreHoles=true] Ignore holes in the input polygon. If false then they are converted to outer rings and a MultiPolygon is returned.
* @returns {Feature<Polygon|MultiPolygon>} Masked Polygon (exterior ring with holes).
* @example
* 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]]]);
Expand All @@ -17,9 +20,18 @@ import polygonClipping from "polygon-clipping";
* //addToMap
* var addToMap = [masked]
*/
function mask(polygon, mask) {
function mask(polygon, mask, options) {
// Handling in case someone doesn't pass in a mask but they do provide options
if (typeof mask === "object" && !("type" in mask) && options === undefined) {
options = mask;
mask = undefined;
}

options = options || {};

var ignoreHoles = "ignoreHoles" in options ? options.ignoreHoles : true;
// Define mask
var maskPolygon = createMask(mask);
var maskPolygon = createMask(mask, ignoreHoles);

var polygonOuters = null;
if (polygon.type === "FeatureCollection") polygonOuters = unionFc(polygon);
Expand All @@ -29,7 +41,15 @@ function mask(polygon, mask) {
);

polygonOuters.geometry.coordinates.forEach(function (contour) {
maskPolygon.geometry.coordinates.push(contour[0]);
if (ignoreHoles) {
maskPolygon.geometry.coordinates.push(contour[0]);
} else {
for (let index = 0; index < contour.length; index++) {
const ring = contour[index];
if (index === 0) maskPolygon.geometry.coordinates[0].push(ring);
else maskPolygon.geometry.coordinates.push([ring]);
}
}
});

return maskPolygon;
Expand Down Expand Up @@ -60,9 +80,10 @@ function createGeomFromPolygonClippingOutput(unioned) {
*
* @private
* @param {Feature<Polygon>} [mask] default to world if undefined
* @param {Boolean} [ignoreHoles] whether to ignore holes or not. If false we return a MultiPolygon
* @returns {Feature<Polygon>} mask coordinate
*/
function createMask(mask) {
function createMask(mask, ignoreHoles) {
var world = [
[
[180, 90],
Expand All @@ -73,7 +94,8 @@ function createMask(mask) {
],
];
var coordinates = (mask && mask.geometry.coordinates) || world;
return createPolygon(coordinates);

return ignoreHoles ? createPolygon(coordinates) : multiPolygon([coordinates]);
}

export default mask;
102 changes: 101 additions & 1 deletion packages/turf-mask/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import load from "load-json-file";
import write from "write-json-file";
import mask from "./index";

const SKIP = ["multi-polygon.geojson", "overlapping.geojson"];
const SKIP = [];

const directories = {
in: path.join(__dirname, "test", "in") + path.sep,
Expand Down Expand Up @@ -34,3 +34,103 @@ test("turf-mask", (t) => {
}
t.end();
});

test("turf-mask-ignoreHoles", (t) => {
const { name, geojson } = fixtures.find(
(f) => f.name === "polygon-with-hole"
);
const [polygon, masking] = geojson.features;
const results = mask(polygon, masking, {
ignoreHoles: false,
});

t.deepEquals(
results,
{
type: "Feature",
properties: {},
geometry: {
type: "MultiPolygon",
coordinates: [
[
[
[180, 90],
[-180, 90],
[-180, -90],
[180, -90],
[180, 90],
],
[
[0, 0],
[16, 0],
[16, 16],
[0, 16],
[0, 0],
],
],
[
[
[6, 6],
[6, 10],
[10, 10],
[10, 6],
[6, 6],
],
],
],
},
},
name
);
t.end();
});

test("turf-mask with options but no mask", (t) => {
const { name, geojson } = fixtures.find(
(f) => f.name === "polygon-with-hole"
);
const [polygon] = geojson.features;
const results = mask(polygon, {
ignoreHoles: false,
});

t.deepEquals(
results,
{
type: "Feature",
properties: {},
geometry: {
type: "MultiPolygon",
coordinates: [
[
[
[180, 90],
[-180, 90],
[-180, -90],
[180, -90],
[180, 90],
],
[
[0, 0],
[16, 0],
[16, 16],
[0, 16],
[0, 0],
],
],
[
[
[6, 6],
[6, 10],
[10, 10],
[10, 6],
[6, 6],
],
],
],
},
},
name
);
t.end();
});
28 changes: 28 additions & 0 deletions packages/turf-mask/test/in/polygon-with-hole.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[0, 0],
[16, 0],
[16, 16],
[0, 16],
[0, 0]
],
[
[6, 6],
[6, 10],
[10, 10],
[10, 6],
[6, 6]
]
]
},
"properties": {}
}
]
}
Loading