Skip to content

Commit 0c388e7

Browse files
committed
added boolean-rectangle and precise/validate.js
1 parent b411627 commit 0c388e7

File tree

7 files changed

+192
-2
lines changed

7 files changed

+192
-2
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Bounding boxes, or rectangular extents, are represented as an array of 4 numbers
1414
- [booleanContains](#booleanContains)
1515
- [booleanContainsPoint](#booleanContainsPoint)
1616
- [booleanIntersects](#booleanIntersects)
17+
- [booleanRectangle](#booleanRectangle)
1718
- [calc](#calc)
1819
- [densePolygon](#densePolygon)
1920
- [intersect](#intersect)
@@ -113,6 +114,38 @@ booleanIntersects(western_hemisphere, eastern_hemisphere);
113114
true
114115
```
115116

117+
### booleanRectangle
118+
Checks if coordinates represent a rectangular bounding box
119+
```js
120+
import booleanRectangle from "bbox-fns/boolean-rectangle.js";
121+
122+
// rectangle for the globe bbox [-180, -90, 180, 90]
123+
const coords = [
124+
[ -180, 90 ],
125+
[ -180, -90 ],
126+
[ 0, -90 ],
127+
[ 0, 90 ],
128+
[ -180, 90 ]
129+
];
130+
booleanRectangle(coords);
131+
true
132+
133+
// extra points that don't affect shape
134+
const denseCoords = [
135+
[ -180, 90 ],
136+
[ -180, 0 ],
137+
[ -180, -90 ],
138+
[ -90, -90 ],
139+
[ 0, -90 ],
140+
[ 0, 0 ],
141+
[ 0, 90 ],
142+
[ -90, 90 ],
143+
[ -180, 90 ]
144+
];
145+
booleanRectangle(denseCoords);
146+
true
147+
```
148+
116149
### intersect
117150
```js
118151
import intersect from "bbox-fns/intersect.js";

boolean-rectangle.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const VALID_ORDERING = [
2+
// clockwise
3+
"right -> down -> left -> up",
4+
"down -> left -> up -> right",
5+
"left -> up -> right -> down",
6+
"up -> right -> down -> left",
7+
8+
// counter-clockwise
9+
"down -> right -> up -> left",
10+
"right -> up -> left -> down",
11+
"up -> left -> down -> right",
12+
"left -> down -> right -> up"
13+
];
14+
15+
function booleanRectangle(coords, { debug = 0 } = { debug: 0 }) {
16+
if (!Array.isArray(coords)) {
17+
if (debug) {
18+
console.log("[bbox-fns/booleanRectangle] coords is not an array");
19+
}
20+
return false;
21+
}
22+
23+
// unwrap ring from polygon
24+
if (coords.length === 1) coords = coords[0];
25+
26+
// if multi-polygon, may need to unwrap twice
27+
if (coords.length === 1) coords = coords[0];
28+
29+
if (
30+
!coords.every(
31+
pt => Array.isArray(pt) && pt.every(n => typeof n === "number")
32+
)
33+
) {
34+
if (debug) console.log("[bbox-fns/booleanRectangle] invalid points");
35+
return false;
36+
}
37+
38+
// first and last coordinate should be the same
39+
if (JSON.stringify(coords[0]) !== JSON.stringify(coords[coords.length - 1])) {
40+
if (debug)
41+
console.log(
42+
"[bbox-fns/booleanRectangle] first and last coordinates not equal"
43+
);
44+
return false;
45+
}
46+
47+
let order = [];
48+
for (let i = 1; i < coords.length; i++) {
49+
const [x0, y0] = coords[i - 1];
50+
const [x1, y1] = coords[i];
51+
const vertical = x0 === x1;
52+
const horizontal = y0 === y1;
53+
54+
// if both true or both false
55+
if (vertical === horizontal) {
56+
if (debug) console.log("[bbox-fns/booleanRectangle] invalid angle");
57+
return false;
58+
}
59+
60+
let direction;
61+
if (vertical) {
62+
direction = y1 > y0 ? "up" : "down";
63+
} else if (horizontal) {
64+
direction = x1 > x0 ? "right" : "left";
65+
}
66+
67+
if (direction !== order[order.length - 1]) {
68+
if (order.length === 4) {
69+
if (debug) console.log("[bbox-fns/booleanRectangle] more than 4 turns");
70+
return false;
71+
}
72+
order.push(direction);
73+
}
74+
}
75+
76+
order = order.join(" -> ");
77+
if (debug) console.log("[bbox-fns/booleanRectangle] order: " + order);
78+
79+
if (VALID_ORDERING.indexOf(order) === -1) {
80+
if (debug) console.log("[bbox-fns/booleanRectangle] invalid order");
81+
return false;
82+
}
83+
84+
return true;
85+
}
86+
87+
module.exports = booleanRectangle;
88+
module.exports.default = booleanRectangle;

index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const bboxSize = require("./bbox-size.js");
77
const booleanContains = require("./boolean-contains.js");
88
const booleanContainsPoint = require("./boolean-contains-point.js");
99
const booleanIntersects = require("./boolean-intersects.js");
10+
const booleanRectangle = require("./boolean-rectangle.js");
1011
const calc = require("./calc.js");
1112
const densePolygon = require("./dense-polygon.js");
1213
const intersect = require("./intersect.js");
@@ -18,6 +19,7 @@ const preciseReproject = require("./precise/reproject.js");
1819
const reproject = require("./reproject.js");
1920
const scale = require("./scale.js");
2021
const validate = require("./validate.js");
22+
const preciseValidate = require("./precise/validate.js");
2123

2224
const bboxfns = {
2325
bboxArea,
@@ -27,6 +29,7 @@ const bboxfns = {
2729
booleanContains,
2830
booleanContainsPoint,
2931
booleanIntersects,
32+
booleanRectangle,
3033
calc,
3134
densePolygon,
3235
intersect,
@@ -37,7 +40,8 @@ const bboxfns = {
3740
preciseReproject,
3841
reproject,
3942
scale,
40-
validate
43+
validate,
44+
preciseValidate
4145
};
4246

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

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"boolean-contains.js",
1313
"boolean-contains-point.js",
1414
"boolean-intersects.js",
15+
"boolean-rectangle.js",
1516
"calc.js",
1617
"dense-polygon.js",
1718
"intersect.js",
@@ -23,6 +24,7 @@
2324
"precise/dense-polygon.js",
2425
"precise/divide.js",
2526
"precise/reproject.js",
27+
"precise/validate.js",
2628
"validate.js"
2729
],
2830
"scripts": {

precise/validate.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const compare = require("preciso/compare.js");
2+
3+
function preciseValidate(bbox) {
4+
if (!Array.isArray(bbox)) return false;
5+
6+
if (bbox.length !== 4) return false;
7+
8+
if (bbox.some(n => typeof n !== "string")) return false;
9+
10+
const [xmin, ymin, xmax, ymax] = bbox;
11+
if (compare(xmin, xmax) === ">") return false;
12+
if (compare(ymin, ymax) === ">") return false;
13+
14+
return true;
15+
}
16+
17+
module.exports = preciseValidate;
18+
module.exports.default = preciseValidate;

test.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
booleanContains,
1212
booleanContainsPoint,
1313
booleanIntersects,
14+
booleanRectangle,
1415
calc,
1516
densePolygon,
1617
intersect,
@@ -21,7 +22,8 @@ const {
2122
preciseReproject,
2223
scale,
2324
preciseDivide,
24-
validate
25+
validate,
26+
preciseValidate
2527
} = require("./index.js");
2628

2729
const globe = [-180, -90, 180, 90];
@@ -94,6 +96,41 @@ test("booleanIntersects", ({ eq }) => {
9496
eq(booleanIntersects(western_hemisphere, eastern_hemisphere), true); // overlap on prime meridian
9597
});
9698

99+
test("booleanRectangle", ({ eq }) => {
100+
const bbox = [-180, -90, 0, 90];
101+
102+
// example
103+
eq(booleanRectangle(densePolygon(bbox, { density: 1 })), true);
104+
105+
eq(booleanRectangle(bbox), false); // invalid input
106+
107+
const poly = polygon(bbox);
108+
const densePoly = densePolygon(bbox, { density: 10 });
109+
eq(booleanRectangle(poly), true);
110+
eq(booleanRectangle(poly[0]), true);
111+
eq(booleanRectangle([poly]), true);
112+
eq(booleanRectangle(densePoly), true);
113+
eq(booleanRectangle(densePoly[0]), true);
114+
eq(booleanRectangle([densePoly]), true);
115+
116+
const triangle = [
117+
[8.9257, 25.4035],
118+
[25.625, 12.6403],
119+
[32.1289, 23.8054],
120+
[8.9257, 25.4035]
121+
];
122+
eq(booleanRectangle(triangle), false);
123+
124+
const quadrilateral = [
125+
[-9, 22],
126+
[-9, -22],
127+
[58, -30],
128+
[58, 22],
129+
[-9, 22]
130+
];
131+
eq(booleanRectangle(quadrilateral, { debug: false }), false);
132+
});
133+
97134
test("calc: GeoJSON Point", ({ eq }) => {
98135
eq(
99136
calc({
@@ -554,3 +591,9 @@ test("validate", ({ eq }) => {
554591
eq(validate([-180, 0, 0, 180, 45, 0]), false);
555592
eq(validate([-45, 10, -90, 20]), false);
556593
});
594+
595+
test("preciseValidate", ({ eq }) => {
596+
eq(preciseValidate(["-180", "0", "180", "45"]), true);
597+
eq(preciseValidate(["-180", "0", "0", "180", "45", "0"]), false);
598+
eq(preciseValidate(["-45", "10", "-90", "20"]), false);
599+
});

validate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* @return {Boolean} valid - true or false
77
*/
88
function validate(bbox) {
9+
if (!Array.isArray(bbox)) return false;
10+
911
if (bbox.length !== 4) return false;
1012

1113
if (bbox.some(n => typeof n !== "number")) return false;

0 commit comments

Comments
 (0)