Comparing version 0.1.2 to 0.1.3
{ | ||
"name": "stac-js", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "JS drop-in classes with utilities for STAC", | ||
@@ -5,0 +5,0 @@ "author": "Matthias Mohr", |
@@ -9,3 +9,3 @@ # stac-js | ||
- **Package version:** 0.1.2 | ||
- **Package version:** 0.1.3 | ||
- **STAC versions:** >= 0.6.0 (through [stac-migrate](https://github.com/stac-utils/stac-migrate)). | ||
@@ -12,0 +12,0 @@ - **Documentation:** <https://m-mohr.github.io/stac-js/latest/> |
@@ -5,3 +5,3 @@ import Asset from './asset.js'; | ||
import { isoToDate } from './datetime.js'; | ||
import { bbox2D, isBoundingBox, toGeoJSON } from './geo.js'; | ||
import { ensureBoundingBox, toGeoJSON } from './geo.js'; | ||
import { hasText } from './utils.js'; | ||
@@ -84,4 +84,4 @@ | ||
let bboxes = this.getRawBoundingBoxes(); | ||
if (bboxes.length > 0 && isBoundingBox(bboxes[0])) { | ||
return bbox2D(bboxes[0]); | ||
if (bboxes.length > 0) { | ||
return ensureBoundingBox(bboxes[0]); | ||
} | ||
@@ -99,10 +99,9 @@ return null; | ||
let raw = this.getRawBoundingBoxes(); | ||
let bboxes = []; | ||
if (raw.length === 1 && isBoundingBox(raw[0])) { | ||
bboxes = raw; | ||
if (raw.length === 1) { | ||
return [ensureBoundingBox(raw[0])]; | ||
} | ||
else if (raw.length > 1) { | ||
bboxes = raw.filter((bbox, i) => i > 0 && isBoundingBox(bbox)); | ||
return raw.slice(1).map(ensureBoundingBox); | ||
} | ||
return bboxes.map(bbox => bbox2D(bbox)); | ||
return null; | ||
} | ||
@@ -109,0 +108,0 @@ |
125
src/geo.js
@@ -0,1 +1,3 @@ | ||
import { ensureNumber, isObject } from "./utils.js"; | ||
function toObject(bbox) { | ||
@@ -35,3 +37,4 @@ let hasZ = bbox.length >= 6; | ||
export function centerOfBoundingBox(bbox) { | ||
if (!isBoundingBox(bbox)) { | ||
bbox = ensureBoundingBox(bbox, true); | ||
if (!bbox) { | ||
return null; | ||
@@ -41,2 +44,3 @@ } | ||
let point = []; | ||
// todo: implement also for bboxes that cross the boundaries at the poles | ||
if (isAntimeridianBoundingBox(bbox)) { | ||
@@ -59,3 +63,43 @@ let x = (obj.west + 360 + obj.east) / 2; | ||
function fixGeoJsonGoordinates(coords) { | ||
if (Array.isArray(coords[0])) { | ||
// Handle nested coordinates (e.g., MultiPolygons, LineStrings) | ||
return coords.map(fixGeoJsonGoordinates); | ||
} | ||
// Fix individual coordinate [longitude, latitude] | ||
const [lon, lat] = coords; | ||
return [ensureNumber(lon, -180, 180), ensureNumber(lat, -90, 90)]; | ||
} | ||
/** | ||
* Fix coordinates in a GeoJSON object to be within the CRS range. | ||
* | ||
* Function works in-place. | ||
* | ||
* @param {Object} geojson - The GeoJSON object to be checked. | ||
* @returns {Object} The fixed GeoJSON object. | ||
*/ | ||
export function fixGeoJson(geojson) { | ||
if (!isObject(geojson)) { | ||
return geojson; | ||
} | ||
if (geojson.bbox) { | ||
geojson.bbox = ensureBoundingBox(geojson.bbox); | ||
} | ||
if (geojson.type === "FeatureCollection") { | ||
geojson.features.forEach((feature) => fixGeoJson(feature)); | ||
} | ||
else if (geojson.type === "Feature") { | ||
geojson.geometry = fixGeoJson(geojson.geometry); | ||
} | ||
else if (geojson.type === "GeometryCollection") { | ||
geojson.geometries.forEach((geometry) => fixGeoJson(geometry)); | ||
} | ||
else if (geojson.coordinates) { | ||
geojson.coordinates = fixGeoJsonGoordinates(geojson.coordinates); | ||
} | ||
return geojson; | ||
} | ||
/** | ||
* Converts one or more bounding boxes to a GeoJSON Feature. | ||
@@ -69,9 +113,12 @@ * | ||
export function toGeoJSON(bboxes) { | ||
if (isBoundingBox(bboxes)) { | ||
// Wrap a single bounding into an array | ||
bboxes = [bboxes]; | ||
const bbox = ensureBoundingBox(bboxes); | ||
if (bbox) { | ||
// Wrap a single bounding box into an array | ||
bboxes = [bbox]; | ||
} | ||
else if (Array.isArray(bboxes)) { | ||
// Remove invalid bounding boxes | ||
bboxes = bboxes.filter(bbox => isBoundingBox(bbox)); | ||
bboxes = bboxes | ||
.map(bbox => ensureBoundingBox(bbox)) | ||
.filter(bbox => bbox !== null); | ||
} | ||
@@ -84,2 +131,4 @@ // Return if no valid bbox is given | ||
let coordinates = bboxes.reduce((list, bbox) => { | ||
// todo: implement also for bboxes that cross the boundaries at the poles | ||
// see https://github.com/DanielJDufour/bbox-fns/blob/main/split.js | ||
if (isAntimeridianBoundingBox(bbox)) { | ||
@@ -119,38 +168,34 @@ let { west, east, south, north } = toObject(bbox); | ||
/** | ||
* Converts a bounding box to be two-dimensional. | ||
* Ensure this is a valid bounding box. | ||
* | ||
* @param {BoundingBox} bbox | ||
* @returns {BoundingBox} | ||
* This function will ensure that the given bounding box is valid and otherwise return `null`. | ||
* | ||
* If the bounding box is 3D, the function will return `null` unless `allow3D` is set to `true`. | ||
* | ||
* @param {BoundingBox|Array.<number>} bbox The bounding box to check. | ||
* @param {boolean} allow3D - Whether to allow 3D bounding boxes or not. | ||
* @returns {BoundingBox|null} | ||
*/ | ||
export function bbox2D(bbox) { | ||
if (bbox.length === 4) { | ||
return bbox; | ||
export function ensureBoundingBox(bbox, allow3D = false) { | ||
if (!Array.isArray(bbox) || ![4,6].includes(bbox.length)) { | ||
return null; | ||
} | ||
let { west, east, south, north, base, height } = toObject(bbox); | ||
// Some bounding boxes are slightly too large (due to floating point errors). | ||
// So you may get 90.00000001 instead of 90. To avoid this, we allow for a small delta. | ||
west = ensureNumber(west, -180, 180); | ||
south = ensureNumber(south, -90, 90); | ||
east = ensureNumber(east, -180, 180); | ||
north = ensureNumber(north, -90, 90); | ||
if (allow3D && bbox.length === 6) { | ||
bbox = [west, south, base, east, north, height]; | ||
} | ||
else { | ||
let { west, east, south, north } = toObject(bbox); | ||
return [west, south, east, north]; | ||
bbox = [west, south, east, north]; | ||
} | ||
} | ||
/** | ||
* Checks whether the given thing is a valid bounding box. | ||
* | ||
* A valid bounding box is an array with 4 or 6 numbers that are valid WGS84 coordinates and span a rectangle. | ||
* See the STAC specification for details. | ||
* | ||
* @param {BoundingBox|Array.<number>} bbox A potential bounding box. | ||
* @returns {boolean} `true` if valid, `false` otherwise | ||
*/ | ||
export function isBoundingBox(bbox) { | ||
if (!Array.isArray(bbox) || ![4,6].includes(bbox.length) || bbox.some(n => typeof n !== "number")) { | ||
return false; | ||
if (bbox.some(n => n === null)) { | ||
return null; | ||
} | ||
let { west, east, south, north } = toObject(bbox); | ||
return ( | ||
south <= north && | ||
west >= -180 && west <= 180 && | ||
south >= -90 && | ||
east <= 180 && east >= -180 && | ||
north <= 90 | ||
); | ||
return bbox; | ||
} | ||
@@ -165,3 +210,4 @@ | ||
export function isAntimeridianBoundingBox(bbox) { | ||
if (!isBoundingBox(bbox)) { | ||
bbox = ensureBoundingBox(bbox); | ||
if (!bbox) { | ||
return false; | ||
@@ -181,3 +227,3 @@ } | ||
* @returns {BoundingBox|null} | ||
* @see {isBoundingBox} | ||
* @see {ensureBoundingBox} | ||
*/ | ||
@@ -196,3 +242,4 @@ export function unionBoundingBox(bboxes) { | ||
bboxes.forEach(bbox => { | ||
if (!isBoundingBox(bbox)) { | ||
bbox = ensureBoundingBox(bbox); | ||
if (!bbox) { | ||
return; | ||
@@ -209,3 +256,3 @@ } | ||
let bbox = [extrema.west, extrema.south, extrema.east, extrema.north]; | ||
return isBoundingBox(bbox) ? bbox : null; | ||
return ensureBoundingBox(bbox); | ||
} |
import Asset from './asset.js'; | ||
import { centerDateTime, isoToDate } from './datetime.js'; | ||
import { bbox2D, isBoundingBox } from './geo.js'; | ||
import { ensureBoundingBox } from './geo.js'; | ||
import { hasText } from './utils.js'; | ||
@@ -65,3 +65,3 @@ import STAC from './stac.js'; | ||
getBoundingBox() { | ||
return isBoundingBox(this.bbox) ? bbox2D(this.bbox) : null; | ||
return ensureBoundingBox(this.bbox); | ||
} | ||
@@ -68,0 +68,0 @@ |
@@ -12,2 +12,24 @@ /** | ||
/** | ||
* Ensures a number is between a minimum and maximum value, but with a delta. | ||
* | ||
* @param {number} num The number to check. | ||
* @param {number} min The minimum value. | ||
* @param {number} max The maximum value. | ||
* @param {number} delta The delta that the number is allowed to be larger or smaller. | ||
* @returns {number|null} | ||
*/ | ||
export function ensureNumber(num, min, max, delta = 0.00000001) { | ||
if (typeof num !== 'number') { | ||
return null; | ||
} | ||
const min2 = min - delta; | ||
const max2 = max + delta; | ||
if (num < min2 || num > max2) { | ||
return null; | ||
} | ||
return Math.min(Math.max(num, min), max); | ||
} | ||
/** | ||
* Checks whether a variable is a real object or not. | ||
@@ -14,0 +36,0 @@ * |
86531
2536