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
);
}