Skip to content

Commit da53e7b

Browse files
committed
added calcAll, grid, and union
1 parent 98708fd commit da53e7b

File tree

8 files changed

+279
-2
lines changed

8 files changed

+279
-2
lines changed

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ Bounding boxes, or rectangular extents, are represented as an array of 4 numbers
1616
- [booleanIntersects](#booleanIntersects)
1717
- [booleanRectangle](#booleanRectangle)
1818
- [calc](#calc)
19+
- [calcAll](#calcAll)
1920
- [densePolygon](#densePolygon)
21+
- [grid](#grid)
2022
- [intersect](#intersect)
2123
- [merge](#merge)
24+
- [union](#union)
2225
- [polygon](#polygon)
2326
- [scale](#scale)
2427
- [reproject](#reproject)
@@ -206,6 +209,19 @@ calc({
206209
[10, 10, 40, 40]
207210
```
208211

212+
### calcAll
213+
Calculate an array of bounding boxes for all the input geometries.
214+
For example, a multipolygon will return multiple bounding boxes.
215+
```js
216+
import calcAll from "bbox-fns/calc-all.js";
217+
218+
calcAll({
219+
type: "MultiPolygon",
220+
coordinates: [ ... ]
221+
});
222+
[bbox1, bbox2, ...]
223+
```
224+
209225
### densePolygon
210226
A more advanced version of polygon. Create a polygon
211227
while adding points to each side of the rectangle.
@@ -220,6 +236,23 @@ densePolygon(bbox, { density: 100 });
220236
densePolygon(bbox, { density: [100, 400] });
221237
```
222238

239+
### grid
240+
Chop bounding box up into multiple smaller bounding boxes.
241+
```js
242+
import grid from "bbox-fns/grid.js";
243+
244+
const globe = [-180, -90, 180, 90];
245+
const number_of_columns = 2; // how many grid cells left to right
246+
const number_of_rows = 2; // how many grid cells top to bottom
247+
const quadrants = grid(globe, [number_of_columns, number_of_rows]);
248+
[
249+
[-180, -90, 0, 0], // south-western
250+
[0, -90, 180, 0], // south-eastern
251+
[-180, 0, 0, 90], // north-western
252+
[0, 0, 180, 90] // north-eastern
253+
]
254+
```
255+
223256
### scale
224257
Multiply x and y values by the given scale values
225258
```js
@@ -252,6 +285,22 @@ reproject(bbox, forwardAsync, { async: true })
252285
reproject(bbox, forward, { density: 99 })
253286
```
254287

288+
### union
289+
Combine all bounding boxes that intersect.
290+
This is different from merge, which will combine bounding boxes even if they don't intersect.
291+
```js
292+
import union from "bbox-fns/union.js";
293+
294+
const wyoming = [-110.99, 40.97, -104.08, 45.03];
295+
const usa = [-125.10, 24.75, -66, 49.54];
296+
const iceland = [-24.40, 63.29, -13.16, 66.73];
297+
298+
union([wyoming, usa, iceland]);
299+
300+
// only includes usa and iceland, because wyoming merged into usa
301+
[[-125.10, 24.75, -66, 49.54], [-24.40, 63.29, -13.16, 66.73]]
302+
```
303+
255304
### validate
256305
```js
257306
import validate from "bbox-fns/validate.js";

calc-all.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use_strict";
2+
3+
const dedupe = require("./dedupe.js");
4+
5+
function calcAll(geom) {
6+
if (geom.geometry) geom = geom.geometry;
7+
if (geom.coordinates) geom = geom.coordinates;
8+
9+
if (geom.paths) geom = geom.paths; // ArcGIS Polyline
10+
if (geom.points) geom = geom.points; // ArcGIS Multipoint
11+
if (geom.rings) geom = geom.rings; // ArcGIS Polygon
12+
13+
// GeoJSON FeatureCollection
14+
if (Array.isArray(geom.features)) {
15+
return dedupe(geom.features.map(calcAll));
16+
}
17+
18+
// GeoJSON GeometryCollection
19+
if (Array.isArray(geom.geometries)) {
20+
return dedupe(geom.geometries.map(calcAll));
21+
}
22+
23+
if (
24+
Array.isArray(geom) &&
25+
Array.isArray(geom[0]) &&
26+
Array.isArray(geom[0][0])
27+
) {
28+
return dedupe(geom.map(calcAll));
29+
}
30+
31+
// array of [x, y] coordinate pairs
32+
if (
33+
Array.isArray(geom) &&
34+
Array.isArray(geom[0]) &&
35+
typeof geom[0][0] === "number"
36+
) {
37+
const [x, y] = geom[0];
38+
let xmin = x;
39+
let ymin = y;
40+
let xmax = x;
41+
let ymax = y;
42+
geom.forEach(([px, py]) => {
43+
if (px < xmin) xmin = px;
44+
if (px > xmax) xmax = px;
45+
if (py < ymin) ymin = py;
46+
if (py > ymax) ymax = py;
47+
});
48+
return [xmin, ymin, xmax, ymax];
49+
}
50+
51+
// point
52+
if (
53+
Array.isArray(geom) &&
54+
(geom.length === 2 || geom.length === 3) &&
55+
typeof geom[0] === "number"
56+
) {
57+
const [x, y] = geom;
58+
return [x, y, x, y];
59+
}
60+
61+
// ArcGIS Point
62+
if (typeof geom.x === "number" && typeof geom.y === "number") {
63+
const { x, y } = geom;
64+
return [x, y, x, y];
65+
}
66+
67+
if (
68+
["xmin", "xmax", "ymin", "ymax"].every(k => typeof geom[k] === "number")
69+
) {
70+
return [geom.xmin, geom.ymin, geom.xmax, geom.ymax];
71+
}
72+
}
73+
74+
module.exports = calcAll;
75+
module.exports.default = calcAll;

dedupe.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
function dedupe(arr) {
2+
const strs = [];
3+
for (let i = 0; i < arr.length; i++) {
4+
const it = arr[i];
5+
const s = JSON.stringify(it);
6+
if (strs.indexOf(s) === -1) {
7+
strs.push(s);
8+
}
9+
}
10+
return strs.map(s => JSON.parse(s));
11+
}
12+
13+
module.exports = dedupe;
14+
module.exports.default = dedupe;

grid.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use strict";
2+
3+
/**
4+
* @name grid
5+
* @param {Array} bbox - bounding box in form [xmin, ymin, xmax, ymax]
6+
* @return {Array<bbox>} array of bounding boxes
7+
*/
8+
function grid([xmin, ymin, xmax, ymax], div) {
9+
if (typeof div === "number") div = [div, div];
10+
else if (typeof div === "undefined") div = [2, 2];
11+
12+
const [columns, rows] = div;
13+
14+
const height = ymax - ymin;
15+
const width = xmax - xmin;
16+
17+
const cells = [];
18+
19+
const cell_width = width / columns;
20+
const cell_height = height / rows;
21+
22+
for (let r = 0; r < rows; r++) {
23+
const cell_ymin = ymin + r * cell_height;
24+
const cell_ymax = r === rows.length - 1 ? ymax : cell_ymin + cell_height;
25+
for (let c = 0; c < columns; c++) {
26+
const cell_xmin = xmin + c * cell_width;
27+
const cell_xmax =
28+
c === columns.length - 1 ? xmax : cell_xmin + cell_width;
29+
cells.push([cell_xmin, cell_ymin, cell_xmax, cell_ymax]);
30+
}
31+
}
32+
33+
return cells;
34+
}
35+
36+
module.exports = grid;
37+
module.exports.default = grid;

index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ const booleanContainsPoint = require("./boolean-contains-point.js");
99
const booleanIntersects = require("./boolean-intersects.js");
1010
const booleanRectangle = require("./boolean-rectangle.js");
1111
const calc = require("./calc.js");
12+
const calcAll = require("./calc-all.js");
1213
const densePolygon = require("./dense-polygon.js");
14+
const grid = require("./grid.js");
1315
const intersect = require("./intersect.js");
1416
const merge = require("./merge.js");
1517
const polygon = require("./polygon.js");
@@ -20,6 +22,7 @@ const reproject = require("./reproject.js");
2022
const scale = require("./scale.js");
2123
const validate = require("./validate.js");
2224
const preciseValidate = require("./precise/validate.js");
25+
const union = require("./union.js");
2326

2427
const bboxfns = {
2528
bboxArea,
@@ -31,7 +34,9 @@ const bboxfns = {
3134
booleanIntersects,
3235
booleanRectangle,
3336
calc,
37+
calcAll,
3438
densePolygon,
39+
grid,
3540
intersect,
3641
merge,
3742
polygon,
@@ -41,7 +46,8 @@ const bboxfns = {
4146
reproject,
4247
scale,
4348
validate,
44-
preciseValidate
49+
preciseValidate,
50+
union
4551
};
4652

4753
if (typeof define === "function" && define.amd) {

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
"boolean-intersects.js",
1515
"boolean-rectangle.js",
1616
"calc.js",
17+
"calc-all.js",
18+
"dedupe.js",
1719
"dense-polygon.js",
20+
"grid.js",
1821
"intersect.js",
1922
"merge.js",
2023
"polygon.js",
@@ -25,6 +28,7 @@
2528
"precise/divide.js",
2629
"precise/reproject.js",
2730
"precise/validate.js",
31+
"union.js",
2832
"validate.js"
2933
],
3034
"scripts": {

test.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,27 @@
33
const test = require("flug");
44
const subtract = require("preciso/subtract.js");
55

6+
const polygons_split_across_antimeridian = [
7+
[
8+
[
9+
[-180, -18],
10+
[-178, -18],
11+
[-178, -20],
12+
[-180, -20],
13+
[-180, -18]
14+
]
15+
],
16+
[
17+
[
18+
[180, -20],
19+
[178, -20],
20+
[178, -18],
21+
[180, -18],
22+
[180, -20]
23+
]
24+
]
25+
];
26+
627
const {
728
bboxArea,
829
bboxArray,
@@ -13,7 +34,9 @@ const {
1334
booleanIntersects,
1435
booleanRectangle,
1536
calc,
37+
calcAll,
1638
densePolygon,
39+
grid,
1740
intersect,
1841
merge,
1942
polygon,
@@ -23,7 +46,8 @@ const {
2346
scale,
2447
preciseDivide,
2548
validate,
26-
preciseValidate
49+
preciseValidate,
50+
union
2751
} = require("./index.js");
2852

2953
const globe = [-180, -90, 180, 90];
@@ -461,6 +485,22 @@ test("calc: ArcGIS Envelope in 2D", ({ eq }) => {
461485
);
462486
});
463487

488+
test("polygons split across antimeridian", ({ eq }) => {
489+
eq(calc(polygons_split_across_antimeridian), [-180, -20, 180, -18]);
490+
eq(calcAll(polygons_split_across_antimeridian), [
491+
[[-180, -20, -178, -18]],
492+
[[178, -20, 180, -18]]
493+
]);
494+
eq(
495+
calcAll(
496+
polygons_split_across_antimeridian.concat(
497+
polygons_split_across_antimeridian
498+
)
499+
),
500+
[[[-180, -20, -178, -18]], [[178, -20, 180, -18]]]
501+
);
502+
});
503+
464504
test("densePolygon", ({ eq }) => {
465505
eq(densePolygon(globe, { density: 1 }), [
466506
[
@@ -530,6 +570,16 @@ test("intersect", ({ eq }) => {
530570
eq(intersect([-180, 1, -1, 90], [1, 1, 180, 90]), null);
531571
});
532572

573+
test("grid", ({ eq }) => {
574+
eq(grid(globe, [2, 1]), [western_hemisphere, eastern_hemisphere]);
575+
eq(grid(globe, [2, 2]), [
576+
[-180, -90, 0, 0],
577+
[0, -90, 180, 0],
578+
[-180, 0, 0, 90],
579+
[0, 0, 180, 90]
580+
]);
581+
});
582+
533583
test("merge", ({ eq }) => {
534584
const bboxes = [western_hemisphere, eastern_hemisphere];
535585
eq(merge(bboxes), [-180, -90, 180, 90]);
@@ -597,3 +647,15 @@ test("preciseValidate", ({ eq }) => {
597647
eq(preciseValidate(["-180", "0", "0", "180", "45", "0"]), false);
598648
eq(preciseValidate(["-45", "10", "-90", "20"]), false);
599649
});
650+
651+
test("union", ({ eq }) => {
652+
const wyoming = [-110.99, 40.97, -104.08, 45.03];
653+
const usa = [-125.1, 24.75, -66, 49.54];
654+
const iceland = [-24.4, 63.29, -13.16, 66.73];
655+
656+
eq(union([]), []);
657+
eq(union([iceland]), [iceland]);
658+
eq(union([wyoming, usa]), [usa]);
659+
eq(union([wyoming, usa, iceland]), [iceland, usa]);
660+
eq(union([wyoming, iceland]), [iceland, wyoming]);
661+
});

0 commit comments

Comments
 (0)