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

Support (Multi)Point,FeatureCollection and GeometryCollection in bboxClip #2814

Open
wants to merge 8 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ node_modules

.vscode

.nx/
.nx/
21 changes: 11 additions & 10 deletions packages/turf-bbox-clip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

## bboxClip

Takes a [Feature][1] and a bbox and clips the feature to the bbox using
[lineclip][2].
May result in degenerate edges when clipping Polygons.
Takes a [Feature][1], [Geometry][2] or [FeatureCollection][3] and a bbox and returns the part of the object within the bbox.
Lines and polygons are clipped using [lineclip][4].
If a Point or LineString geometry is entirely outside the bbox, a [MultiPoint][5] or [MultiLineString][6] with empty `coordinates` array is returned.
LineString geometries may also become MultiLineString if the clipping process cuts them into several pieces.

### Parameters

* `feature` **[Feature][1]<([LineString][3] | [MultiLineString][4] | [Polygon][5] | [MultiPolygon][6])>** feature to clip to the bbox
* `feature` **([Geometry][2] | [Feature][1] | [FeatureCollection][3])** GeoJSON object to clip to the bbox
* `bbox` **[BBox][7]** extent in \[minX, minY, maxX, maxY] order

### Examples
Expand All @@ -25,19 +26,19 @@ var clipped = turf.bboxClip(poly, bbox);
var addToMap = [bbox, poly, clipped]
```

Returns **[Feature][1]<([LineString][3] | [MultiLineString][4] | [Polygon][5] | [MultiPolygon][6])>** clipped Feature
Returns **([Feature][1] | [FeatureCollection][3])** clipped GeoJSON object.

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

[2]: https://github.com/mapbox/lineclip
[2]: https://tools.ietf.org/html/rfc7946#section-3.1

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

[4]: https://tools.ietf.org/html/rfc7946#section-3.1.5
[4]: https://github.com/mapbox/lineclip

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

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

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

Expand Down
116 changes: 102 additions & 14 deletions packages/turf-bbox-clip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,37 @@ import {
MultiPolygon,
GeoJsonProperties,
Polygon,
Point,
MultiPoint,
Position,
FeatureCollection,
Geometry,
GeometryCollection,
} from "geojson";

import {
featureCollection,
geometryCollection,
lineString,
multiLineString,
multiPoint,
multiPolygon,
point,
polygon,
} from "@turf/helpers";
import { getGeom } from "@turf/invariant";
import { lineclip, polygonclip } from "./lib/lineclip.js";

/**
* Takes a {@link Feature} and a bbox and clips the feature to the bbox using
* [lineclip](https://github.com/mapbox/lineclip).
* May result in degenerate edges when clipping Polygons.
* Takes a {@link Feature}, {@link Geometry} or {@link FeatureCollection} and a bbox and returns the part of the object within the bbox.
* Lines and polygons are clipped using [lineclip](https://github.com/mapbox/lineclip).
* If a Point or LineString geometry is entirely outside the bbox, a {@link MultiPoint} or {@link MultiLineString} with empty `coordinates` array is returned.
* LineString geometries may also become MultiLineString if the clipping process cuts them into several pieces.
*
* @function
* @param {Feature<LineString|MultiLineString|Polygon|MultiPolygon>} feature feature to clip to the bbox
* @param {Geometry|Feature|FeatureCollection} feature GeoJSON object to clip to the bbox
* @param {BBox} bbox extent in [minX, minY, maxX, maxY] order
* @returns {Feature<LineString|MultiLineString|Polygon|MultiPolygon>} clipped Feature
* @returns {Feature|FeatureCollection} clipped GeoJSON object.
* @example
* var bbox = [0, 0, 10, 10];
* var poly = turf.polygon([[[2, 2], [8, 4], [12, 8], [3, 7], [2, 2]]]);
Expand All @@ -35,15 +46,72 @@ import { lineclip, polygonclip } from "./lib/lineclip.js";
* //addToMap
* var addToMap = [bbox, poly, clipped]
*/
// pass a specific geometry type or FeatureCollection, get the same type back
function bboxClip<
G extends Polygon | MultiPolygon | LineString | MultiLineString,
G extends Point | MultiPoint,
P extends GeoJsonProperties = GeoJsonProperties,
>(feature: Feature<G, P> | G, bbox: BBox) {
>(feature: Feature<G, P> | G, bbox: BBox): Feature<Point | MultiPoint, P>;
function bboxClip<
G extends LineString | MultiLineString,
P extends GeoJsonProperties = GeoJsonProperties,
>(
feature: Feature<G, P> | G,
bbox: BBox
): Feature<LineString | MultiLineString, P>;
function bboxClip<
G extends Polygon | MultiPolygon,
P extends GeoJsonProperties = GeoJsonProperties,
>(feature: Feature<G, P> | G, bbox: BBox): Feature<Polygon | MultiPolygon, P>;
function bboxClip<
G extends GeometryCollection,
P extends GeoJsonProperties = GeoJsonProperties,
>(feature: Feature<G, P> | G, bbox: BBox): Feature<G, P>;
function bboxClip<
G extends FeatureCollection,
P extends GeoJsonProperties = GeoJsonProperties,
>(feature: G, bbox: BBox): FeatureCollection<Geometry, P>;

// pass a non-specific geometry type, get a generic feature back
function bboxClip<P extends GeoJsonProperties = GeoJsonProperties>(
feature: Feature<Geometry, P>,
bbox: BBox
): Feature<Geometry, P>;
function bboxClip(feature: Geometry, bbox: BBox): Feature<Geometry>;

// "implementation signature", can't be called directly
function bboxClip<
G extends Geometry,
P extends GeoJsonProperties = GeoJsonProperties,
>(
feature: Feature<G, P> | G | FeatureCollection,
bbox: BBox
): Feature<Geometry> | FeatureCollection {
if (feature.type === "FeatureCollection") {
return featureCollection(
feature.features.map((f: Feature) => bboxClip(f, bbox) as Feature)
);
}

// const test = { type: "Point", coordinates: [0, 0] } as Geometry;
// const out = bboxClip(test, bbox);

const geom = getGeom(feature);
const type = geom.type;
const properties = feature.type === "Feature" ? feature.properties : {};
let coords: any[] = geom.coordinates;
const properties: GeoJsonProperties =
feature.type === "Feature" ? feature.properties : {};

if (type === "GeometryCollection") {
const gs = geom.geometries;
const outGs = gs.map(
(g: Geometry) => (bboxClip(g, bbox) as Feature<Geometry>).geometry
) as Exclude<Geometry, GeometryCollection>[];
return geometryCollection(outGs, properties) as Feature<
GeometryCollection,
P
>;
}

let coords: any[] = geom.coordinates;
switch (type) {
case "LineString":
case "MultiLineString": {
Expand All @@ -54,27 +122,47 @@ function bboxClip<
coords.forEach((line) => {
lineclip(line, bbox, lines);
});
if (lines.length === 1) {
if (lines.length === 1 && type === "LineString") {
return lineString(lines[0], properties);
}
return multiLineString(lines, properties);
}
case "Polygon":
return polygon(clipPolygon(coords, bbox), properties);
case "Polygon": {
const poly = clipPolygon(coords, bbox);
return polygon(poly, properties);
}
case "MultiPolygon":
return multiPolygon(
coords.map((poly) => {
return clipPolygon(poly, bbox);
}),
properties
);
case "Point": {
const coord = geom.coordinates;
if (checkCoord(coord, bbox)) return point(coord, properties);
return multiPoint([], properties);
}
case "MultiPoint": {
return multiPoint(coords.filter((coord) => checkCoord(coord, bbox)));
}

default:
throw new Error("geometry " + type + " not supported");
}
}

function clipPolygon(rings: number[][][], bbox: BBox) {
const outRings = [];
function checkCoord(coord: Position, bbox: BBox) {
return (
coord[0] >= bbox[0] &&
coord[1] >= bbox[1] &&
coord[0] <= bbox[2] &&
coord[1] <= bbox[3]
);
}

function clipPolygon(rings: Position[][], bbox: BBox) {
const outRings: Position[][] = [];
for (const ring of rings) {
const clipped = polygonclip(ring, bbox);
if (clipped.length > 0) {
Expand Down
3 changes: 2 additions & 1 deletion packages/turf-bbox-clip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"contributors": [
"Tim Channell <@tcql>",
"Vladimir Agafonkin <@mourner>",
"Denis Carriere <@DenisCarriere>"
"Denis Carriere <@DenisCarriere>",
"Steve Bennett <@stevage>"
],
"license": "MIT",
"bugs": {
Expand Down
39 changes: 23 additions & 16 deletions packages/turf-bbox-clip/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,28 @@ test("turf-bbox-clip", (t) => {
const filename = fixture.filename;
const name = fixture.name;
const geojson = fixture.geojson;
const feature = geojson.features[0];
const bbox = turfBBox(geojson.features[1]);
const isFeatureCollection = geojson.features.length > 2;
const feature = isFeatureCollection
? featureCollection(geojson.features.slice(0, -1))
: geojson.features[0];
const clipRegion = geojson.features.slice(-1)[0];
const bbox = turfBBox(clipRegion);
const clipped = bboxClip(feature, bbox);
const results = featureCollection([
colorize(feature, "#080"),
colorize(clipped, "#F00"),
colorize(geojson.features[1], "#00F", 3),
]);

let results;
if (isFeatureCollection) {
results = featureCollection([
...feature.features.map((f) => colorize(f, "#080", "input")),
...clipped.features.map((c) => colorize(c, "#F00", "output")),
colorize(clipRegion, "#00F", "clipping bbox", 3),
]);
} else {
results = featureCollection([
colorize(feature, "#080", "input"),
colorize(clipped, "#F00", "output"),
colorize(clipRegion, "#00F", "clipping bbox", 3),
]);
}

if (process.env.REGEN)
writeJsonFileSync(directories.out + filename, results);
Expand All @@ -44,22 +58,15 @@ test("turf-bbox-clip", (t) => {
t.end();
});

test("turf-bbox-clip -- throws", (t) => {
t.throws(
() => bboxClip(point([5, 10]), [-180, -90, 180, 90]),
/geometry Point not supported/
);
t.end();
});

function colorize(feature, color, width) {
function colorize(feature, color, name, width) {
color = color || "#F00";
width = width || 6;
feature.properties = {
stroke: color,
fill: color,
"stroke-width": width,
"fill-opacity": 0.1,
name,
};
return feature;
}
Expand Down
94 changes: 94 additions & 0 deletions packages/turf-bbox-clip/test/in/feature-collection.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [92.78854443123498, -4.0631420171898185],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [88.80200582619335, -8.780228029258652],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[84.45578448719408, -0.42584674848238535],
[104.00625645806042, -17.30113187521333],
[108.37396514395846, -11.998337187512362]
],
"type": "LineString"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[
[95.6755173613135, -5.412355325867296],
[95.6755173613135, -9.59572648513172],
[101.64270834459677, -9.59572648513172],
[101.64270834459677, -5.412355325867296],
[95.6755173613135, -5.412355325867296]
]
],
"type": "Polygon"
}
},

{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[77.58616268346043, -7.556977393000622],
[92.76209967554053, -19.675822211591438],
[84.86920130192482, -25.38820065310992]
],
"type": "LineString"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[
[84.5161586806538, 10.150704513809302],
[84.5161586806538, 3.6873648155754495],
[93.5370745092431, 3.6873648155754495],
[93.5370745092431, 10.150704513809302],
[84.5161586806538, 10.150704513809302]
]
],
"type": "Polygon"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
[
[91.0644737234989, -0.5254035861065773],
[91.0644737234989, -15.195974906738272],
[98.81078524424902, -15.195974906738272],
[98.81078524424902, -0.5254035861065773],
[91.0644737234989, -0.5254035861065773]
]
],
"type": "Polygon"
}
}
]
}
Loading