UNPKG

geotoolbox

Version:

geotoolbox is GIS javascript library. It is based on d3geo, topojson and geos-wasm.

172 lines (156 loc) 5.64 kB
import { geoEquirectangularRaw, geoBounds } from "d3-geo"; const d3 = Object.assign({}, { geoEquirectangularRaw, geoBounds }); import { check } from "./helpers/check.js"; /** * @function bbox * @summary Compute a geographic bounding box. * @description based on Jacob Rus code. See https://observablehq.com/@jrus/sphere-resample * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry. * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`). * @example * geotoolbox.bbox(*a geojson*) */ export function bbox(data) { const handle = check(data); let bounds = isArrayOfFourNumbers(data) ? [ [data[3], data[2]], [data[0], data[1]], ] : d3.geoBounds(handle.import(data)); let λ0 = bounds[0][0]; let φ0 = bounds[0][1]; let λ1 = bounds[1][0]; let φ1 = bounds[1][1]; const x = { type: "FeatureCollection", features: [ { type: "Feature", properties: { id: 1 }, geometry: { type: "Polygon", coordinates: φ0 === -90 ? [ [ [λ0, φ1], [λ1, φ1], ], ] // Antarctica : [ [ [λ0, φ0], [λ0, φ1], [(λ1 += (λ1 < λ0) * 360), φ1], [λ1, φ0], [λ0, φ0], ], ], }, }, ], }; let output = inverseResampleJSON(d3.geoEquirectangularRaw, 0.02)(x); output.name = "bbox"; return handle.export(output); } const inverseResampleJSON = (projection, delta) => { const maxDepth = 16, radians = Math.PI / 180, dd = Math.tan((radians * delta) / 2) ** 2; const resampleLineTo = function (w0, u0, w1, u1, ll01, depth, array) { if (depth--) { var w2 = planar_midpoint(w0, w1), λφ2 = projection.invert(...w2), u2 = cartesian(λφ2), ll02 = stereo_length2(u2, u0), ll12 = stereo_length2(u2, u1), AA = stereo_area2(u2, u0, u1), hh = (AA * (1 + 0.25 * ll01) * (1 + 0.25 * ll01)) / (dd * ll01), ww = 2 * ((ll02 - ll12) / ll01) * ((ll02 - ll12) / ll01); if (((hh + ww > 1) & (ll02 + ll12 > dd)) | (ll02 + ll12 > 0.25)) { resampleLineTo(w0, u0, w2, u2, ll02, depth, array); array.push(λφ2); resampleLineTo(w2, u2, w1, u1, ll12, depth, array); } } }; const resampleChain = (pointarray) => { let outarray = []; let w0 = pointarray[0], λφ0 = projection.invert(...w0), u0 = cartesian(λφ0); outarray.push(λφ0); for (var i = 1, n = pointarray.length; i < n; i++) { let w1 = pointarray[i], λφ1 = projection.invert(...w1), u1 = cartesian(λφ1); resampleLineTo( w0, u0, w1, u1, stereo_length2(u0, u1), maxDepth, outarray ); outarray.push(λφ1); (w0 = w1), (u0 = u1); } return outarray; }; let project = (w) => projection.invert(...w); let mapInPlace = (fn) => (array) => array.forEach((e, i) => (array[i] = fn(e))); let convert, convertType = { Point: (o) => (o.coordinates = project(o.coordinates)), MultiPoint: (o) => mapInPlace(project)(o.coordinates), LineString: (o) => (o.coordinates = resampleChain(o.coordinates)), Polygon: (o) => mapInPlace(resampleChain)(o.coordinates), MultiLineString: (o) => mapInPlace(resampleChain)(o.coordinates), MultiPolygon: (o) => o.coordinates.forEach(mapInPlace(resampleChain)), Feature: (o) => convert(o.geometry), GeometryCollection: (o) => o.geometries.forEach(convert), FeatureCollection: (o) => o.features.forEach(convert), }; // convert = (o) => (convertType?.[o?.type]?.(o), o); convert = (o) => (convertType[o.type](o), o); return function (json) { json = JSON.parse(JSON.stringify(json)); // make deep copy return convert(json); }; }; const stereo_area2 = ([x0, y0, z0], [x1, y1, z1], [x2, y2, z2]) => { var p = x0 * ((y1 - y0) * (z2 - z0) - (y2 - y0) * (z1 - z0)) + y0 * ((z1 - z0) * (x2 - x0) - (z2 - z0) * (x1 - x0)) + z0 * ((x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)), q = (x0 + x2) * (x0 + x1) + (y0 + y2) * (y0 + y1) + (z0 + z2) * (z0 + z1); return (p * p + !(q * q)) / (q * q); // adding !(q*q) means q==0 => return Infinity }; const planar_midpoint = ([x0, y0], [x1, y1]) => [ 0.5 * (x0 + x1), 0.5 * (y0 + y1), ]; const radians = Math.PI / 180; const cartesian = ([λ, φ]) => [ Math.cos(radians * φ) * Math.cos(radians * λ), Math.cos(radians * φ) * Math.sin(radians * λ), Math.sin(radians * φ), ]; const stereo_length2 = ([x0, y0, z0], [x1, y1, z1]) => { var pxy = x0 * (y1 - y0) - (x1 - x0) * y0, pyz = y0 * (z1 - z0) - (y1 - y0) * z0, pzx = z0 * (x1 - x0) - (z1 - z0) * x0, q = x0 * (x1 + x0) + y0 * (y1 + y0) + z0 * (z1 + z0); return (pxy * pxy + pyz * pyz + pzx * pzx + !(q * q)) / (q * q); // adding !(q*q) means q==0 => return Infinity }; function isArrayOfFourNumbers(value) { return ( Array.isArray(value) && // Vérifie que c'est un tableau value.length === 4 && // Vérifie qu'il y a exactement 4 éléments value.every((num) => typeof num === "number" && !isNaN(num)) // Vérifie que tous sont des nombres valides ); }