bbox-fns
Version:
Light-weight JavaScript Bounding Box Utility Functions
425 lines (348 loc) • 10.5 kB
Markdown
# bbox-fns: work in progress
Super Light-weight JavaScript Bounding Box Utility Functions
### the bounding box
Bounding boxes, or rectangular extents, are represented as an array of 4 numbers:
```js
[xmin, ymin, xmax, ymax]
```
## functions
- [bboxArea](#bboxArea)
- [bboxArray](#bboxArray)
- [bboxPoint](#bboxPoint)
- [bboxSize](#bboxSize)
- [booleanContains](#booleanContains)
- [booleanContainsPoint](#booleanContainsPoint)
- [booleanIntersects](#booleanIntersects)
- [booleanRectangle](#booleanRectangle)
- [calc](#calc)
- [calcAll](#calcAll)
- [densePolygon](#densePolygon)
- [grid](#grid)
- [intersect](#intersect)
- [merge](#merge)
- [polygon](#polygon)
- [reproject](#reproject)
- [scale](#scale)
- [shift](#shift)
- [sort](#sort)
- [split](#split)
- [union](#union)
- [unwrap](#unwrap)
- [validate](#validate)
### bboxArea
Calculate the area of a bounding box
```js
import bboxArea from "bbox-fns/bbox-area.js";
bboxArea([2, 3, 8, 9])
36 // (8 - 2) * (9 - 3)
```
### bboxArray
Calculate the bounding box of an array of points (aka "a polygon ring")
```js
import bboxArray from "bbox-fns/bbox-array.js";
bboxArray([
[ -180, 86.06126914660831 ],
[ -180, 85.66739606126914 ],
[ -179, 84.87964989059081 ],
[ -179, 84.48577680525165 ]
]);
[-180, 84.48577680525165, -179, 86.06126914660831]
// throws because of the NaN value
bboxArray([ -180, 86.06 ], [NaN, NaN], [ -179, 84.48 ]);
// skip NaN values (don't throw)
bboxArray(points, { nan_strategy: "skip" })
[-180, 84.48, -179, 86.06]
```
### bboxPoint
Convert a single [x, y] point into a bounding box of zero width and height;
```js
import bboxPoint from "bbox-fns/bbox-point.js";
bboxPoint([-180, 86.06126914660831]);
[-180, 86.06126914660831, -180, 86.06126914660831]
```
### bboxSize
Calculate the width and height of a bounding box
```js
import bboxSize from "bbox-fns/bbox-size.js";
bboxSize([-180, 84.48577680525165, -179, 86.06126914660831]);
[1, 1.5754923413566644]
```
### booleanContains
Check if a bounding box contains another
```js
import booleanContains from "bbox-fns/boolean-contains.js";
const western_hemisphere = [-180, -90, 0, 90];
const colorado = [-109.05378, 37.0057, -102.0665, 41.0443];
booleanContains(western_hemisphere, colorado);
true
// exclude bbox that extends to the exact edge
booleanContains(western_hemisphere, [-10, -20, 0, 20], { exclusive: true });
false
```
### booleanContainsPoint
Check if a bounding box contains a point
```js
import booleanContainsPoint from "bbox-fns/boolean-contains-point.js";
const western_hemisphere = [-180, -90, 0, 90];
const eastern_hemisphere = [0, -90, 180, 90];
const hawaii = [-155.844437, 19.741755];
booleanContainsPoint(western_hemisphere, hawaii);
true
// point on boundary
booleanContainsPoint(western_hemisphere, [0, 0]);
true
// ignoring points on the exact edge
booleanContainsPoint(western_hemisphere, [0, 0], { exclusive: true });
false
```
### booleanIntersects
Checks if two bounding boxes have any intersection at all.
```js
import booleanIntersects from "bbox-fns/boolean-intersects.js";
const western_hemisphere = [-180, -90, 0, 90];
const eastern_hemisphere = [0, -90, 180, 90];
booleanIntersects(western_hemisphere, eastern_hemisphere);
true
```
### booleanRectangle
Checks if coordinates represent a rectangular bounding box
```js
import booleanRectangle from "bbox-fns/boolean-rectangle.js";
// rectangle for the globe bbox [-180, -90, 180, 90]
const coords = [
[ -180, 90 ],
[ -180, -90 ],
[ 0, -90 ],
[ 0, 90 ],
[ -180, 90 ]
];
booleanRectangle(coords);
true
// extra points that don't affect shape
const denseCoords = [
[ -180, 90 ],
[ -180, 0 ],
[ -180, -90 ],
[ -90, -90 ],
[ 0, -90 ],
[ 0, 0 ],
[ 0, 90 ],
[ -90, 90 ],
[ -180, 90 ]
];
booleanRectangle(denseCoords);
true
```
### intersect
```js
import intersect from "bbox-fns/intersect.js";
const western_hemisphere = [-180, -90, 0, 90];
const eastern_hemisphere = [0, -90, 180, 90];
intersect(western_hemisphere, eastern_hemisphere);
[0, -90, 0, 90] // prime meridian
```
### merge
```js
import merge from "bbox-fns/merge.js";
const western_hemisphere = [-180, -90, 0, 90];
const eastern_hemisphere = [0, -90, 180, 90];
const bboxes = [western_hemisphere, eastern_hemisphere];
merge(bboxes);
[-180, -90, 180, 90] // bbox for the whole globe
```
### polygon
Create GeoJSON-Like Polygon from a Bounding Box
```js
import polygon from "bbox-fns/polygon.js";
polygon([-180, -90, 180, 90]);
// polygon is in counter-clockwise order
[
[
[-180, 90], // top-left
[-180, -90], // bottom-left
[180, -90], // bottom-right
[180, 90], // top-right
[-180, 90] // top-left
]
]
```
### calc
Calculate the bounding box of a geometry in either GeoJSON or ArcGIS JSON.
```js
import calc from "bbox-fns/calc.js";
calc({
type: "Polygon",
coordinates: [
[
[30, 10],
[40, 40],
[20, 40],
[10, 20],
[30, 10]
]
]
});
[10, 10, 40, 40]
```
### calcAll
Calculate an array of bounding boxes for all the input geometries.
For example, a multipolygon will return multiple bounding boxes.
```js
import calcAll from "bbox-fns/calc-all.js";
calcAll({
type: "MultiPolygon",
coordinates: [ ... ]
});
[bbox1, bbox2, ...]
```
### densePolygon
A more advanced version of polygon. Create a polygon
while adding points to each side of the rectangle.
```js
import polygon from "bbox-fns/dense-polygon.js";
// add 100 points along each side
densePolygon(bbox, { density: 100 });
// add 100 points along the top and bottom edge (x-axis)
// and 400 points along the left and right edge (y-axis)
densePolygon(bbox, { density: [100, 400] });
```
### grid
Chop bounding box up into multiple smaller bounding boxes.
```js
import grid from "bbox-fns/grid.js";
const globe = [-180, -90, 180, 90];
const number_of_columns = 2; // how many grid cells left to right
const number_of_rows = 2; // how many grid cells top to bottom
const quadrants = grid(globe, [number_of_columns, number_of_rows]);
[
[-180, -90, 0, 0], // south-western
[0, -90, 180, 0], // south-eastern
[-180, 0, 0, 90], // north-western
[0, 0, 180, 90] // north-eastern
]
```
### scale
Multiply x and y values by the given scale values
```js
import scale from "bbox-fns/scale.js";
// shrink the grid by 50%
scale([0, 9, 50, 200], 0.5);
[0, 4.5, 25, 100];
// scale x and y values by different factors
// same as [0 * 2, 9 * 10, 50 * 2, 200 * 10]
scale([0, 9, 50, 200], [2, 10]);
[0, 90, 100, 2000]
```
### shift
Shift bounding box horizontally and/or vertically
```js
// shift bounding box overlapping left "edge"
// to the right, so it overlaps the right "edge"
shift([-200, 40, -160, 90], { x: 360 })
[160,40,200,90];
// shift horizontally and vertically
shift([-185, 90, -180, 95], [360, -90])
[175, 0, 180, 5]
// same as above
shift([-185, 90, -180, 95], { x: 360, y: -90 });
```
### split
Split bounding box that crosses given x or y boundaries. For example, split a bbox that crosses the antimeridian into two bounding boxes.
```js
import split from "bbox-fns/split.js";
// split across the antimeridian
split([-200, -90, 160, 90], { x: [-180, 180] })
[
[-200, -90, -180, 90], // overflow, left of antimeridian
[-180, -90, 160, 90] // right of antimeridian
]
// split across the antimeridian and the poles
split([-200, -90, 160, 100], { x: [-180, 180], y: [-90, 90] })
[
[-200,-90,-180,90],
[-200,90,-180,100],
[-180,-90,160,90],
[-180,90,160,100]
]
```
### reproject
Reproject a bounding box using the given reprojection function
```js
import reproject from "bbox-fns/reproject.js";
import proj4 from "proj4-fully-loaded";
// convert a bounding box from 4326 to a UTM projection
const { forward } = proj4("EPSG:4326", "EPSG:32610");
reproject(bbox, forward);
// you can also pass in an async reprojection function
reproject(bbox, forwardAsync, { async: true })
// you can also control the point density of the intermediate polygon
reproject(bbox, forward, { density: 99 })
// skip (don't throw error) when forward returns a NaN value
reproject(bbox, forward, { nan_strategy: "skip" })
```
### sort
```js
import sort from "bbox-fns/sort.js";
const bboxes = [
[175, -85, 180, 90],
[-180, -85, -175, 90]
];
sort(bboxes)
[[-180,-85,-175,90],[175,-85,180,90]]
```
### union
Combine all bounding boxes that intersect.
This is different from merge, which will combine bounding boxes even if they don't intersect.
```js
import union from "bbox-fns/union.js";
const wyoming = [-110.99, 40.97, -104.08, 45.03];
const usa = [-125.10, 24.75, -66, 49.54];
const iceland = [-24.40, 63.29, -13.16, 66.73];
union([wyoming, usa, iceland]);
// only includes usa and iceland, because wyoming merged into usa
[[-125.10, 24.75, -66, 49.54], [-24.40, 63.29, -13.16, 66.73]]
```
### unwrap
Un-wrap an extent that overflows the edge of the earth, returning an array of one or more bounding boxes.
```js
import unwrap from "bbox-fns/unwrap.js";
const earth = [-180, -90, 180, 90];
const bbox = [-200, -21, -160, 87]; // extends over "left edge" of the earth
unwrap(bbox, earth);
// bounding box unwrapped and normalized within the bounds of the earth
[
[-180, -21, -160, 87],
[160, -21, 180, 87]
]
```
### validate
```js
import validate from "bbox-fns/validate.js";
validate([-180, 0, 180, 45])
true
// invalid length
validate([-180, 0, 0, 180, 45, 0])
false
// xmin greater than xmax
validate([-45, 10, -90, 20])
false
```
### projection support
If you are looking for a library with greater projection support and a class-based approach, try [geo-extent](https://github.com/danieljdufour/geo-extent)!
### nomenclature
This library borrows the names of some similar [Turf.js](https://turfjs.org/) functions, but it does not borrow the internal code.
### advanced usage
#### precise functions
In order to avoid floating point arithmetic errors, you can use the precise version of these functions where numbers are represented as strings.
```js
import preciseBboxArray from "bbox-fns/precise/bbox-array.js";
import preciseDensePolygon from "bbox-fns/precise/dense-polygon.js";
import preciseDivide from "bbox-fns/precise/divide.js";
import preciseReproject from "bbox-fns/precise/reproject.js";
preciseDensePolygon(bbox, { density: [359, 179] }); // add 359 points to top and bottom, and 179 points to the left and right
[
[ '-180', '80' ], [ '-180', '79' ], [ '-180', '78' ], [ '-180', '77' ], /* ... */, [ '-180', '80' ]
]
preciseDivide([0, 9, 50, 200], [3, 4], { ellipsis: true })
["0", "2.25", "16.666...", "50"]
```