@sk-global/hazard-risk
Version:
A TypeScript library for analyzing hazard risks and calculating risk assessments
1,663 lines (1,615 loc) • 65 kB
JavaScript
import axios from 'axios';
import { PNG } from 'pngjs';
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
// index.ts
var earthRadius = 63710088e-1;
var factors = {
centimeters: earthRadius * 100,
centimetres: earthRadius * 100,
degrees: 360 / (2 * Math.PI),
feet: earthRadius * 3.28084,
inches: earthRadius * 39.37,
kilometers: earthRadius / 1e3,
kilometres: earthRadius / 1e3,
meters: earthRadius,
metres: earthRadius,
miles: earthRadius / 1609.344,
millimeters: earthRadius * 1e3,
millimetres: earthRadius * 1e3,
nauticalmiles: earthRadius / 1852,
radians: 1,
yards: earthRadius * 1.0936
};
function feature(geom, properties, options = {}) {
const feat = { type: "Feature" };
if (options.id === 0 || options.id) {
feat.id = options.id;
}
if (options.bbox) {
feat.bbox = options.bbox;
}
feat.properties = properties || {};
feat.geometry = geom;
return feat;
}
function point(coordinates, properties, options = {}) {
if (!coordinates) {
throw new Error("coordinates is required");
}
if (!Array.isArray(coordinates)) {
throw new Error("coordinates must be an Array");
}
if (coordinates.length < 2) {
throw new Error("coordinates must be at least 2 numbers long");
}
if (!isNumber(coordinates[0]) || !isNumber(coordinates[1])) {
throw new Error("coordinates must contain numbers");
}
const geom = {
type: "Point",
coordinates
};
return feature(geom, properties, options);
}
function featureCollection(features, options = {}) {
const fc = { type: "FeatureCollection" };
if (options.id) {
fc.id = options.id;
}
if (options.bbox) {
fc.bbox = options.bbox;
}
fc.features = features;
return fc;
}
function radiansToLength(radians, units = "kilometers") {
const factor = factors[units];
if (!factor) {
throw new Error(units + " units is invalid");
}
return radians * factor;
}
function degreesToRadians(degrees) {
const normalisedDegrees = degrees % 360;
return normalisedDegrees * Math.PI / 180;
}
function isNumber(num) {
return !isNaN(num) && num !== null && !Array.isArray(num);
}
// index.js
function coordEach(geojson, callback, excludeWrapCoord) {
if (geojson === null) return;
var j, k, l, geometry, stopG, coords, geometryMaybeCollection, wrapShrink = 0, coordIndex = 0, isGeometryCollection, type = geojson.type, isFeatureCollection = type === "FeatureCollection", isFeature = type === "Feature", stop = isFeatureCollection ? geojson.features.length : 1;
for (var featureIndex = 0; featureIndex < stop; featureIndex++) {
geometryMaybeCollection = isFeatureCollection ? geojson.features[featureIndex].geometry : isFeature ? geojson.geometry : geojson;
isGeometryCollection = geometryMaybeCollection ? geometryMaybeCollection.type === "GeometryCollection" : false;
stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1;
for (var geomIndex = 0; geomIndex < stopG; geomIndex++) {
var multiFeatureIndex = 0;
var geometryIndex = 0;
geometry = isGeometryCollection ? geometryMaybeCollection.geometries[geomIndex] : geometryMaybeCollection;
if (geometry === null) continue;
coords = geometry.coordinates;
var geomType = geometry.type;
wrapShrink = excludeWrapCoord && (geomType === "Polygon" || geomType === "MultiPolygon") ? 1 : 0;
switch (geomType) {
case null:
break;
case "Point":
if (callback(
coords,
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false)
return false;
coordIndex++;
multiFeatureIndex++;
break;
case "LineString":
case "MultiPoint":
for (j = 0; j < coords.length; j++) {
if (callback(
coords[j],
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false)
return false;
coordIndex++;
if (geomType === "MultiPoint") multiFeatureIndex++;
}
if (geomType === "LineString") multiFeatureIndex++;
break;
case "Polygon":
case "MultiLineString":
for (j = 0; j < coords.length; j++) {
for (k = 0; k < coords[j].length - wrapShrink; k++) {
if (callback(
coords[j][k],
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false)
return false;
coordIndex++;
}
if (geomType === "MultiLineString") multiFeatureIndex++;
if (geomType === "Polygon") geometryIndex++;
}
if (geomType === "Polygon") multiFeatureIndex++;
break;
case "MultiPolygon":
for (j = 0; j < coords.length; j++) {
geometryIndex = 0;
for (k = 0; k < coords[j].length; k++) {
for (l = 0; l < coords[j][k].length - wrapShrink; l++) {
if (callback(
coords[j][k][l],
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false)
return false;
coordIndex++;
}
geometryIndex++;
}
multiFeatureIndex++;
}
break;
case "GeometryCollection":
for (j = 0; j < geometry.geometries.length; j++)
if (coordEach(geometry.geometries[j], callback, excludeWrapCoord) === false)
return false;
break;
default:
throw new Error("Unknown Geometry Type");
}
}
}
}
function featureEach(geojson, callback) {
if (geojson.type === "Feature") {
callback(geojson, 0);
} else if (geojson.type === "FeatureCollection") {
for (var i = 0; i < geojson.features.length; i++) {
if (callback(geojson.features[i], i) === false) break;
}
}
}
// index.ts
function bbox(geojson, options = {}) {
if (geojson.bbox != null && true !== options.recompute) {
return geojson.bbox;
}
const result = [Infinity, Infinity, -Infinity, -Infinity];
coordEach(geojson, (coord) => {
if (result[0] > coord[0]) {
result[0] = coord[0];
}
if (result[1] > coord[1]) {
result[1] = coord[1];
}
if (result[2] < coord[0]) {
result[2] = coord[0];
}
if (result[3] < coord[1]) {
result[3] = coord[1];
}
});
return result;
}
var turf_bbox_default = bbox;
const epsilon = 1.1102230246251565e-16;
const splitter = 134217729;
const resulterrbound = (3 + 8 * epsilon) * epsilon;
// fast_expansion_sum_zeroelim routine from oritinal code
function sum(elen, e, flen, f, h) {
let Q, Qnew, hh, bvirt;
let enow = e[0];
let fnow = f[0];
let eindex = 0;
let findex = 0;
if ((fnow > enow) === (fnow > -enow)) {
Q = enow;
enow = e[++eindex];
} else {
Q = fnow;
fnow = f[++findex];
}
let hindex = 0;
if (eindex < elen && findex < flen) {
if ((fnow > enow) === (fnow > -enow)) {
Qnew = enow + Q;
hh = Q - (Qnew - enow);
enow = e[++eindex];
} else {
Qnew = fnow + Q;
hh = Q - (Qnew - fnow);
fnow = f[++findex];
}
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
while (eindex < elen && findex < flen) {
if ((fnow > enow) === (fnow > -enow)) {
Qnew = Q + enow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (enow - bvirt);
enow = e[++eindex];
} else {
Qnew = Q + fnow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (fnow - bvirt);
fnow = f[++findex];
}
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
}
}
while (eindex < elen) {
Qnew = Q + enow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (enow - bvirt);
enow = e[++eindex];
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
}
while (findex < flen) {
Qnew = Q + fnow;
bvirt = Qnew - Q;
hh = Q - (Qnew - bvirt) + (fnow - bvirt);
fnow = f[++findex];
Q = Qnew;
if (hh !== 0) {
h[hindex++] = hh;
}
}
if (Q !== 0 || hindex === 0) {
h[hindex++] = Q;
}
return hindex;
}
function estimate(elen, e) {
let Q = e[0];
for (let i = 1; i < elen; i++) Q += e[i];
return Q;
}
function vec(n) {
return new Float64Array(n);
}
const ccwerrboundA = (3 + 16 * epsilon) * epsilon;
const ccwerrboundB = (2 + 12 * epsilon) * epsilon;
const ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon;
const B = vec(4);
const C1 = vec(8);
const C2 = vec(12);
const D = vec(16);
const u = vec(4);
function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {
let acxtail, acytail, bcxtail, bcytail;
let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;
const acx = ax - cx;
const bcx = bx - cx;
const acy = ay - cy;
const bcy = by - cy;
s1 = acx * bcy;
c = splitter * acx;
ahi = c - (c - acx);
alo = acx - ahi;
c = splitter * bcy;
bhi = c - (c - bcy);
blo = bcy - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acy * bcx;
c = splitter * acy;
ahi = c - (c - acy);
alo = acy - ahi;
c = splitter * bcx;
bhi = c - (c - bcx);
blo = bcx - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
B[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
B[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
B[2] = _j - (u3 - bvirt) + (_i - bvirt);
B[3] = u3;
let det = estimate(4, B);
let errbound = ccwerrboundB * detsum;
if (det >= errbound || -det >= errbound) {
return det;
}
bvirt = ax - acx;
acxtail = ax - (acx + bvirt) + (bvirt - cx);
bvirt = bx - bcx;
bcxtail = bx - (bcx + bvirt) + (bvirt - cx);
bvirt = ay - acy;
acytail = ay - (acy + bvirt) + (bvirt - cy);
bvirt = by - bcy;
bcytail = by - (bcy + bvirt) + (bvirt - cy);
if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) {
return det;
}
errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det);
det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
if (det >= errbound || -det >= errbound) return det;
s1 = acxtail * bcy;
c = splitter * acxtail;
ahi = c - (c - acxtail);
alo = acxtail - ahi;
c = splitter * bcy;
bhi = c - (c - bcy);
blo = bcy - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acytail * bcx;
c = splitter * acytail;
ahi = c - (c - acytail);
alo = acytail - ahi;
c = splitter * bcx;
bhi = c - (c - bcx);
blo = bcx - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
u[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
u[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
u[3] = u3;
const C1len = sum(4, B, 4, u, C1);
s1 = acx * bcytail;
c = splitter * acx;
ahi = c - (c - acx);
alo = acx - ahi;
c = splitter * bcytail;
bhi = c - (c - bcytail);
blo = bcytail - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acy * bcxtail;
c = splitter * acy;
ahi = c - (c - acy);
alo = acy - ahi;
c = splitter * bcxtail;
bhi = c - (c - bcxtail);
blo = bcxtail - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
u[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
u[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
u[3] = u3;
const C2len = sum(C1len, C1, 4, u, C2);
s1 = acxtail * bcytail;
c = splitter * acxtail;
ahi = c - (c - acxtail);
alo = acxtail - ahi;
c = splitter * bcytail;
bhi = c - (c - bcytail);
blo = bcytail - bhi;
s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
t1 = acytail * bcxtail;
c = splitter * acytail;
ahi = c - (c - acytail);
alo = acytail - ahi;
c = splitter * bcxtail;
bhi = c - (c - bcxtail);
blo = bcxtail - bhi;
t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
_i = s0 - t0;
bvirt = s0 - _i;
u[0] = s0 - (_i + bvirt) + (bvirt - t0);
_j = s1 + _i;
bvirt = _j - s1;
_0 = s1 - (_j - bvirt) + (_i - bvirt);
_i = _0 - t1;
bvirt = _0 - _i;
u[1] = _0 - (_i + bvirt) + (bvirt - t1);
u3 = _j + _i;
bvirt = u3 - _j;
u[2] = _j - (u3 - bvirt) + (_i - bvirt);
u[3] = u3;
const Dlen = sum(C2len, C2, 4, u, D);
return D[Dlen - 1];
}
function orient2d(ax, ay, bx, by, cx, cy) {
const detleft = (ay - cy) * (bx - cx);
const detright = (ax - cx) * (by - cy);
const det = detleft - detright;
const detsum = Math.abs(detleft + detright);
if (Math.abs(det) >= ccwerrboundA * detsum) return det;
return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);
}
function pointInPolygon(p, polygon) {
var i;
var ii;
var k = 0;
var f;
var u1;
var v1;
var u2;
var v2;
var currentP;
var nextP;
var x = p[0];
var y = p[1];
var numContours = polygon.length;
for (i = 0; i < numContours; i++) {
ii = 0;
var contour = polygon[i];
var contourLen = contour.length - 1;
currentP = contour[0];
if (currentP[0] !== contour[contourLen][0] &&
currentP[1] !== contour[contourLen][1]) {
throw new Error('First and last coordinates in a ring must be the same')
}
u1 = currentP[0] - x;
v1 = currentP[1] - y;
for (ii; ii < contourLen; ii++) {
nextP = contour[ii + 1];
u2 = nextP[0] - x;
v2 = nextP[1] - y;
if (v1 === 0 && v2 === 0) {
if ((u2 <= 0 && u1 >= 0) || (u1 <= 0 && u2 >= 0)) { return 0 }
} else if ((v2 >= 0 && v1 <= 0) || (v2 <= 0 && v1 >= 0)) {
f = orient2d(u1, u2, v1, v2, 0, 0);
if (f === 0) { return 0 }
if ((f > 0 && v2 > 0 && v1 <= 0) || (f < 0 && v2 <= 0 && v1 > 0)) { k++; }
}
currentP = nextP;
v1 = v2;
u1 = u2;
}
}
if (k % 2 === 0) { return false }
return true
}
// index.ts
function getCoord(coord) {
if (!coord) {
throw new Error("coord is required");
}
if (!Array.isArray(coord)) {
if (coord.type === "Feature" && coord.geometry !== null && coord.geometry.type === "Point") {
return [...coord.geometry.coordinates];
}
if (coord.type === "Point") {
return [...coord.coordinates];
}
}
if (Array.isArray(coord) && coord.length >= 2 && !Array.isArray(coord[0]) && !Array.isArray(coord[1])) {
return [...coord];
}
throw new Error("coord must be GeoJSON Point or an Array of numbers");
}
function getCoords(coords) {
if (Array.isArray(coords)) {
return coords;
}
if (coords.type === "Feature") {
if (coords.geometry !== null) {
return coords.geometry.coordinates;
}
} else {
if (coords.coordinates) {
return coords.coordinates;
}
}
throw new Error(
"coords must be GeoJSON Feature, Geometry Object or an Array"
);
}
function getGeom(geojson) {
if (geojson.type === "Feature") {
return geojson.geometry;
}
return geojson;
}
// index.ts
function booleanPointInPolygon(point, polygon, options = {}) {
if (!point) {
throw new Error("point is required");
}
if (!polygon) {
throw new Error("polygon is required");
}
const pt = getCoord(point);
const geom = getGeom(polygon);
const type = geom.type;
const bbox = polygon.bbox;
let polys = geom.coordinates;
if (bbox && inBBox(pt, bbox) === false) {
return false;
}
if (type === "Polygon") {
polys = [polys];
}
let result = false;
for (var i = 0; i < polys.length; ++i) {
const polyResult = pointInPolygon(pt, polys[i]);
if (polyResult === 0) return options.ignoreBoundary ? false : true;
else if (polyResult) result = true;
}
return result;
}
function inBBox(pt, bbox) {
return bbox[0] <= pt[0] && bbox[1] <= pt[1] && bbox[2] >= pt[0] && bbox[3] >= pt[1];
}
// Calculate bounding box [minX, minY, maxX, maxY]
function getBoundingBox(polygon) {
return turf_bbox_default(polygon);
}
// index.ts
function booleanPointOnLine(pt, line, options = {}) {
const ptCoords = getCoord(pt);
const lineCoords = getCoords(line);
for (let i = 0; i < lineCoords.length - 1; i++) {
let ignoreBoundary = false;
if (options.ignoreEndVertices) {
if (i === 0) {
ignoreBoundary = "start";
}
if (i === lineCoords.length - 2) {
ignoreBoundary = "end";
}
if (i === 0 && i + 1 === lineCoords.length - 1) {
ignoreBoundary = "both";
}
}
if (isPointOnLineSegment(
lineCoords[i],
lineCoords[i + 1],
ptCoords,
ignoreBoundary,
typeof options.epsilon === "undefined" ? null : options.epsilon
)) {
return true;
}
}
return false;
}
function isPointOnLineSegment(lineSegmentStart, lineSegmentEnd, pt, excludeBoundary, epsilon) {
const x = pt[0];
const y = pt[1];
const x1 = lineSegmentStart[0];
const y1 = lineSegmentStart[1];
const x2 = lineSegmentEnd[0];
const y2 = lineSegmentEnd[1];
const dxc = pt[0] - x1;
const dyc = pt[1] - y1;
const dxl = x2 - x1;
const dyl = y2 - y1;
const cross = dxc * dyl - dyc * dxl;
if (epsilon !== null) {
if (Math.abs(cross) > epsilon) {
return false;
}
} else if (cross !== 0) {
return false;
}
if (Math.abs(dxl) === Math.abs(dyl) && Math.abs(dxl) === 0) {
if (excludeBoundary) {
return false;
}
if (pt[0] === lineSegmentStart[0] && pt[1] === lineSegmentStart[1]) {
return true;
} else {
return false;
}
}
if (!excludeBoundary) {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 <= x && x <= x2 : x2 <= x && x <= x1;
}
return dyl > 0 ? y1 <= y && y <= y2 : y2 <= y && y <= y1;
} else if (excludeBoundary === "start") {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 < x && x <= x2 : x2 <= x && x < x1;
}
return dyl > 0 ? y1 < y && y <= y2 : y2 <= y && y < y1;
} else if (excludeBoundary === "end") {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 <= x && x < x2 : x2 < x && x <= x1;
}
return dyl > 0 ? y1 <= y && y < y2 : y2 < y && y <= y1;
} else if (excludeBoundary === "both") {
if (Math.abs(dxl) >= Math.abs(dyl)) {
return dxl > 0 ? x1 < x && x < x2 : x2 < x && x < x1;
}
return dyl > 0 ? y1 < y && y < y2 : y2 < y && y < y1;
}
return false;
}
// index.ts
function booleanWithin(feature1, feature2) {
var geom1 = getGeom(feature1);
var geom2 = getGeom(feature2);
var type1 = geom1.type;
var type2 = geom2.type;
switch (type1) {
case "Point":
switch (type2) {
case "MultiPoint":
return isPointInMultiPoint(geom1, geom2);
case "LineString":
return booleanPointOnLine(geom1, geom2, { ignoreEndVertices: true });
case "Polygon":
case "MultiPolygon":
return booleanPointInPolygon(geom1, geom2, { ignoreBoundary: true });
default:
throw new Error("feature2 " + type2 + " geometry not supported");
}
case "MultiPoint":
switch (type2) {
case "MultiPoint":
return isMultiPointInMultiPoint(geom1, geom2);
case "LineString":
return isMultiPointOnLine(geom1, geom2);
case "Polygon":
case "MultiPolygon":
return isMultiPointInPoly(geom1, geom2);
default:
throw new Error("feature2 " + type2 + " geometry not supported");
}
case "LineString":
switch (type2) {
case "LineString":
return isLineOnLine(geom1, geom2);
case "Polygon":
case "MultiPolygon":
return isLineInPoly(geom1, geom2);
default:
throw new Error("feature2 " + type2 + " geometry not supported");
}
case "Polygon":
switch (type2) {
case "Polygon":
case "MultiPolygon":
return isPolyInPoly(geom1, geom2);
default:
throw new Error("feature2 " + type2 + " geometry not supported");
}
default:
throw new Error("feature1 " + type1 + " geometry not supported");
}
}
function isPointInMultiPoint(point, multiPoint) {
var i;
var output = false;
for (i = 0; i < multiPoint.coordinates.length; i++) {
if (compareCoords(multiPoint.coordinates[i], point.coordinates)) {
output = true;
break;
}
}
return output;
}
function isMultiPointInMultiPoint(multiPoint1, multiPoint2) {
for (var i = 0; i < multiPoint1.coordinates.length; i++) {
var anyMatch = false;
for (var i2 = 0; i2 < multiPoint2.coordinates.length; i2++) {
if (compareCoords(multiPoint1.coordinates[i], multiPoint2.coordinates[i2])) {
anyMatch = true;
}
}
if (!anyMatch) {
return false;
}
}
return true;
}
function isMultiPointOnLine(multiPoint, lineString) {
var foundInsidePoint = false;
for (var i = 0; i < multiPoint.coordinates.length; i++) {
if (!booleanPointOnLine(multiPoint.coordinates[i], lineString)) {
return false;
}
if (!foundInsidePoint) {
foundInsidePoint = booleanPointOnLine(
multiPoint.coordinates[i],
lineString,
{ ignoreEndVertices: true }
);
}
}
return foundInsidePoint;
}
function isMultiPointInPoly(multiPoint, polygon) {
var output = true;
var isInside = false;
for (var i = 0; i < multiPoint.coordinates.length; i++) {
isInside = booleanPointInPolygon(multiPoint.coordinates[i], polygon);
if (!isInside) {
output = false;
break;
}
{
isInside = booleanPointInPolygon(multiPoint.coordinates[i], polygon, {
ignoreBoundary: true
});
}
}
return output && isInside;
}
function isLineOnLine(lineString1, lineString2) {
for (var i = 0; i < lineString1.coordinates.length; i++) {
if (!booleanPointOnLine(lineString1.coordinates[i], lineString2)) {
return false;
}
}
return true;
}
function isLineInPoly(linestring, polygon) {
var polyBbox = bbox(polygon);
var lineBbox = bbox(linestring);
if (!doBBoxOverlap(polyBbox, lineBbox)) {
return false;
}
var foundInsidePoint = false;
for (var i = 0; i < linestring.coordinates.length; i++) {
if (!booleanPointInPolygon(linestring.coordinates[i], polygon)) {
return false;
}
if (!foundInsidePoint) {
foundInsidePoint = booleanPointInPolygon(
linestring.coordinates[i],
polygon,
{ ignoreBoundary: true }
);
}
if (!foundInsidePoint && i < linestring.coordinates.length - 1) {
var midpoint = getMidpoint(
linestring.coordinates[i],
linestring.coordinates[i + 1]
);
foundInsidePoint = booleanPointInPolygon(midpoint, polygon, {
ignoreBoundary: true
});
}
}
return foundInsidePoint;
}
function isPolyInPoly(geometry1, geometry2) {
var poly1Bbox = bbox(geometry1);
var poly2Bbox = bbox(geometry2);
if (!doBBoxOverlap(poly2Bbox, poly1Bbox)) {
return false;
}
for (var i = 0; i < geometry1.coordinates[0].length; i++) {
if (!booleanPointInPolygon(geometry1.coordinates[0][i], geometry2)) {
return false;
}
}
return true;
}
function doBBoxOverlap(bbox1, bbox2) {
if (bbox1[0] > bbox2[0]) return false;
if (bbox1[2] < bbox2[2]) return false;
if (bbox1[1] > bbox2[1]) return false;
if (bbox1[3] < bbox2[3]) return false;
return true;
}
function compareCoords(pair1, pair2) {
return pair1[0] === pair2[0] && pair1[1] === pair2[1];
}
function getMidpoint(pair1, pair2) {
return [(pair1[0] + pair2[0]) / 2, (pair1[1] + pair2[1]) / 2];
}
// index.ts
function distance(from, to, options = {}) {
var coordinates1 = getCoord(from);
var coordinates2 = getCoord(to);
var dLat = degreesToRadians(coordinates2[1] - coordinates1[1]);
var dLon = degreesToRadians(coordinates2[0] - coordinates1[0]);
var lat1 = degreesToRadians(coordinates1[1]);
var lat2 = degreesToRadians(coordinates2[1]);
var a = Math.pow(Math.sin(dLat / 2), 2) + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
return radiansToLength(
2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)),
options.units
);
}
// index.ts
function pointGrid(bbox, cellSide, options = {}) {
if (options.mask && !options.units) options.units = "kilometers";
var results = [];
var west = bbox[0];
var south = bbox[1];
var east = bbox[2];
var north = bbox[3];
var xFraction = cellSide / distance([west, south], [east, south], options);
var cellWidth = xFraction * (east - west);
var yFraction = cellSide / distance([west, south], [west, north], options);
var cellHeight = yFraction * (north - south);
var bboxWidth = east - west;
var bboxHeight = north - south;
var columns = Math.floor(bboxWidth / cellWidth);
var rows = Math.floor(bboxHeight / cellHeight);
var deltaX = (bboxWidth - columns * cellWidth) / 2;
var deltaY = (bboxHeight - rows * cellHeight) / 2;
var currentX = west + deltaX;
while (currentX <= east) {
var currentY = south + deltaY;
while (currentY <= north) {
var cellPt = point([currentX, currentY], options.properties);
if (options.mask) {
if (booleanWithin(cellPt, options.mask)) results.push(cellPt);
} else {
results.push(cellPt);
}
currentY += cellHeight;
}
currentX += cellWidth;
}
return featureCollection(results);
}
var turf_point_grid_default = pointGrid;
// Create point grid covering bounding box
function createGrid(polygon, gridSize,
// meters
zoom) {
if (!polygon || polygon.type !== 'Polygon' || !Array.isArray(polygon.coordinates)) {
throw new Error('Invalid GeoJSON Polygon');
}
const bbox = getBoundingBox(polygon);
const pointGrids = turf_point_grid_default(bbox, gridSize, {
units: 'meters',
mask: feature(polygon)
});
// Convert turf points to GridPoint[]
const grid = [];
for (const feature of pointGrids.features) {
const [lon, lat] = feature.geometry.coordinates;
const tile = latLonToTile(lat, lon, zoom);
const pixel = latLonToPixel(lat, lon, zoom);
grid.push({
lat,
lon,
tile,
pixel,
isWater: false // Default initialization
});
}
return grid;
}
// Convert lat/lon to XYZ tile
function latLonToTile(lat, lon, zoom) {
const n = Math.pow(2, zoom);
const xtile = Math.floor((lon + 180) / 360 * n);
const ytile = Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * n);
return {
z: zoom,
x: xtile,
y: ytile
};
}
// Convert lat/lon to pixel in tile
function latLonToPixel(lat, lon, zoom) {
const n = Math.pow(2, zoom);
const x = (lon + 180) / 360 * n * 256;
const y = (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * n * 256;
return {
x: Math.floor(x % 256),
y: Math.floor(y % 256)
};
}
// Default hazard config for tsunami (GSI Japan)
const DEFAULT_TSUNAMI_CONFIG = {
name: 'Tsunami',
levels: {
0: {
name: 'level0',
color: '0,0,0',
description: 'No risk'
},
1: {
name: 'level1',
color: '255,255,0',
description: 'Attention'
},
2: {
name: 'level2',
color: '255,165,0',
description: 'Warning'
},
3: {
name: 'level3',
color: '255,0,0',
description: 'Very dangerous'
}
},
// Array of predefined water colors
waterColors: ['#bed2ff', '#a8c8ff', '#8bb8ff', '#6aa8ff']
};
// Convert RGB to hex format
function rgbToHex(r, g, b) {
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
// Convert hex to RGB
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) {
throw new Error(`Invalid hex color: ${hex}`);
}
return {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
};
}
// Convert RGB string to RGB object
function parseRgbString(rgbString) {
const parts = rgbString.split(',').map(s => parseInt(s.trim()));
if (parts.length !== 3) {
throw new Error(`Invalid RGB string: ${rgbString}`);
}
return {
r: parts[0],
g: parts[1],
b: parts[2]
};
}
// Normalize color to RGB object
function normalizeColor(color) {
if (color.startsWith('#')) {
return hexToRgb(color);
} else {
return parseRgbString(color);
}
}
// Create color mapping from levels config
function createColorMapping(hazardConfig) {
const colorMapping = {};
for (const [level, config] of Object.entries(hazardConfig.levels)) {
// Normalize color to RGB string for comparison
const rgb = normalizeColor(config.color);
const rgbString = `${rgb.r},${rgb.g},${rgb.b}`;
colorMapping[rgbString] = parseInt(level);
}
return colorMapping;
}
// Classify risk from RGB color with hazard config
function classifyRiskFromRGB(r, g, b, hazardConfig = DEFAULT_TSUNAMI_CONFIG) {
const colorKey = `${r},${g},${b}`;
const colorMapping = createColorMapping(hazardConfig);
// Check in color mapping
if (colorMapping[colorKey] !== undefined) {
return colorMapping[colorKey];
}
// Default to level 0 (no risk) for undefined colors
return 0;
}
// Check water color with predefined water color array
function isWaterColor(r, g, b, hazardConfig = DEFAULT_TSUNAMI_CONFIG) {
const waterColors = hazardConfig.waterColors || ['#bed2ff'];
const hexColor = rgbToHex(r, g, b);
// Check if color is in water color list
return waterColors.includes(hexColor);
}
// Create hazard config for different hazard types
function createHazardConfig(name, levels, waterColors) {
return {
name,
levels,
waterColors
};
}
// Fetch tile from URL with axios
async function fetchTile(url, cache, coords) {
// Create key for cache
const cacheKey = coords || {
z: 0,
x: 0,
y: 0
};
// Check cache first
if (cache) {
const cached = cache.get(cacheKey.z, cacheKey.x, cacheKey.y, url);
if (cached) {
return cached;
}
}
// Fetch from network with axios
try {
const response = await axios.get(url, {
responseType: 'arraybuffer',
timeout: 30000,
// Increase timeout to 30 seconds for GSI Japan
headers: {
'User-Agent': 'HazardRisk/1.0',
Accept: 'image/png,image/*,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate'
}
});
const buffer = Buffer.from(response.data);
// Check if buffer is valid
if (buffer.length === 0) {
throw new Error('Empty tile data');
}
// Check content-type
const contentType = response.headers['content-type'];
if (contentType && !contentType.includes('image/')) {
throw new Error(`Invalid content-type: ${contentType}`);
}
// Save to cache only when successful
if (cache) {
cache.set(cacheKey.z, cacheKey.x, cacheKey.y, url, buffer);
}
return buffer;
} catch (error) {
var _error$response, _error$message;
// Handle 404 case specifically (tile doesn't exist)
if (((_error$response = error.response) == null ? void 0 : _error$response.status) === 404) {
console.warn(`Tile not found (404): ${url} - Treating as no risk`);
// Return empty buffer instead of throwing error
return Buffer.alloc(0);
}
// Handle timeout
if (error.code === 'ECONNABORTED' || (_error$message = error.message) != null && _error$message.includes('timeout')) {
console.warn(`Timeout fetching tile: ${url}`);
// throw new Error(`TIMEOUT: ${url}`);
return Buffer.alloc(0);
}
// throw new Error(`Failed to fetch tile ${url}: ${error.message}`);
return Buffer.alloc(0);
}
}
// Create tile provider from URL template
function createTileProvider(urlTemplate, cache) {
return async (z, x, y) => {
const url = urlTemplate.replace('{z}', z.toString()).replace('{x}', x.toString()).replace('{y}', y.toString());
return await fetchTile(url, cache, {
z,
x,
y
});
};
}
// Implementation for Node.js (using pngjs)
class NodeRasterReader {
constructor(tileProvider) {
this.tileProvider = void 0;
this.tileProvider = tileProvider;
}
async getPixelRGB(tile, pixel) {
try {
const tileBuffer = await this.tileProvider(tile.z, tile.x, tile.y);
// Check if buffer is valid or empty (tile not found)
if (!tileBuffer || tileBuffer.length === 0) {
console.warn(`Empty tile buffer for tile ${tile.z}/${tile.x}/${tile.y} - Treating as no risk`);
return {
r: 0,
g: 0,
b: 0
}; // Return level 0 color (no risk)
}
// Use pngjs to read PNG
return new Promise((resolve, reject) => {
try {
const png = PNG.sync.read(tileBuffer);
const {
width,
height,
data
} = png;
// Check if pixel coordinates are valid
if (pixel.x < 0 || pixel.x >= width || pixel.y < 0 || pixel.y >= height) {
console.warn(`Invalid pixel coordinates: ${pixel.x}, ${pixel.y} for tile ${width}x${height}`);
resolve({
r: 0,
g: 0,
b: 0
}); // Return level 0 color
return;
}
// PNG data format: RGBA (4 bytes per pixel)
const index = (pixel.y * width + pixel.x) * 4;
resolve({
r: data[index] || 0,
g: data[index + 1] || 0,
b: data[index + 2] || 0
});
} catch (error) {
console.warn(`pngjs error for tile ${tile.z}/${tile.x}/${tile.y}:`, error);
// Fallback: return level 0 color
resolve({
r: 0,
g: 0,
b: 0
});
}
});
} catch (error) {
console.warn(`Error reading pixel from tile ${tile.z}/${tile.x}/${tile.y}:`, error);
// Fallback: return level 0 color (no risk)
return {
r: 0,
g: 0,
b: 0
};
}
}
async getPixelRiskInfo(tile, pixel, hazardConfig) {
const rgb = await this.getPixelRGB(tile, pixel);
const riskLevel = classifyRiskFromRGB(rgb.r, rgb.g, rgb.b, hazardConfig);
// isWater: always false in this NodeRasterReader demo
return {
riskLevel,
isWater: false
};
}
}
// Implementation for Browser (using canvas)
class BrowserRasterReader {
constructor(hazardTileProvider, baseTileProvider, level0Color = '0,0,0') {
this.hazardTileProvider = void 0;
this.baseTileProvider = void 0;
this.level0Color = void 0;
this.hazardTileProvider = hazardTileProvider;
this.baseTileProvider = baseTileProvider;
this.level0Color = normalizeColor(level0Color);
}
async getPixelRGB(tile, pixel) {
try {
const tileImage = await this.hazardTileProvider(tile.z, tile.x, tile.y);
// Create canvas to read pixel
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 256;
ctx.drawImage(tileImage, 0, 0);
const imageData = ctx.getImageData(pixel.x, pixel.y, 1, 1);
const data = imageData.data;
return {
r: data[0],
g: data[1],
b: data[2]
};
} catch (error) {
var _error$message2;
// Handle error similarly to Node.js
if ((_error$message2 = error.message) != null && _error$message2.includes('TILE_NOT_FOUND')) {
console.warn(`Tile not found for ${tile.z}/${tile.x}/${tile.y} - Treating as no risk`);
return this.level0Color; // Return level 0 color instead of black
}
console.warn(`Error reading pixel from tile ${tile.z}/${tile.x}/${tile.y}:`, error);
return this.level0Color; // Return level 0 color instead of black
}
}
async getPixelRiskInfo(tile, pixel, hazardConfig) {
try {
// Read hazard tile to get risk level
const hazardRgb = await this.getPixelRGB(tile, pixel);
const riskLevel = classifyRiskFromRGB(hazardRgb.r, hazardRgb.g, hazardRgb.b, hazardConfig);
// Read base tile to detect water (if baseTileProvider exists)
let isWater = false;
if (this.baseTileProvider) {
try {
const baseTileImage = await this.baseTileProvider(tile.z, tile.x, tile.y);
// Create canvas to read pixel from base tile
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 256;
ctx.drawImage(baseTileImage, 0, 0);
const imageData = ctx.getImageData(pixel.x, pixel.y, 1, 1);
const data = imageData.data;
const baseRgb = {
r: data[0],
g: data[1],
b: data[2]
};
// Check water color
isWater = isWaterColor(baseRgb.r, baseRgb.g, baseRgb.b, hazardConfig);
} catch (error) {
console.warn(`Error reading base tile for water detection:`, error);
// If unable to read base tile, assume not water
isWater = false;
}
}
return {
riskLevel,
isWater
};
} catch (error) {
console.warn(`Error in getPixelRiskInfo:`, error);
return {
riskLevel: 0,
isWater: false
};
}
}
}
// Tile provider for browser: fetch by axios, return ImageBitmap
function createBrowserTileProvider(urlTemplate) {
return async (z, x, y) => {
const url = urlTemplate.replace('{z}', z.toString()).replace('{x}', x.toString()).replace('{y}', y.toString());
try {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
console.warn(`Tile not found (404): ${url} - Treating as no risk`);
throw new Error(`TILE_NOT_FOUND: ${url}`);
}
throw new Error(`HTTP ${res.status}: ${url}`);
}
const blob = await res.blob();
return await createImageBitmap(blob);
} catch (error) {
var _error$message3;
// Handle timeout
if ((_error$message3 = error.message) != null && _error$message3.includes('timeout')) {
console.warn(`Timeout fetching tile: ${url}`);
throw new Error(`TIMEOUT: ${url}`);
}
throw new Error(`Failed to fetch tile ${url}: ${error.message}`);
}
};
}
// index.ts
function clone(geojson) {
if (!geojson) {
throw new Error("geojson is required");
}
switch (geojson.type) {
case "Feature":
return cloneFeature(geojson);
case "FeatureCollection":
return cloneFeatureCollection(geojson);
case "Point":
case "LineString":
case "Polygon":
case "MultiPoint":
case "MultiLineString":
case "MultiPolygon":
case "GeometryCollection":
return cloneGeometry(geojson);
default:
throw new Error("unknown GeoJSON type");
}
}
function cloneFeature(geojson) {
const cloned = { type: "Feature" };
Object.keys(geojson).forEach((key) => {
switch (key) {
case "type":
case "properties":
case "geometry":
return;
default:
cloned[key] = geojson[key];
}
});
cloned.properties = cloneProperties(geojson.properties);
if (geojson.geometry == null) {
cloned.geometry = null;
} else {
cloned.geometry = cloneGeometry(geojson.geometry);
}
return cloned;
}
function cloneProperties(properties) {
const cloned = {};
if (!properties) {
return cloned;
}
Object.keys(properties).forEach((key) => {
const value = properties[key];
if (typeof value === "object") {
if (value === null) {
cloned[key] = null;
} else if (Array.isArray(value)) {
cloned[key] = value.map((item) => {
return item;
});
} else {
cloned[key] = cloneProperties(value);
}
} else {
cloned[key] = value;
}
});
return cloned;
}
function cloneFeatureCollection(geojson) {
const cloned = { type: "FeatureCollection" };
Object.keys(geojson).forEach((key) => {
switch (key) {
case "type":
case "features":
return;
default:
cloned[key] = geojson[key];
}
});
cloned.features = geojson.features.map((feature) => {
return cloneFeature(feature);
});
return cloned;
}
function cloneGeometry(geometry) {
const geom = { type: geometry.type };
if (geometry.bbox) {
geom.bbox = geometry.bbox;
}
if (geometry.type === "GeometryCollection") {
geom.geometries = geometry.geometries.map((g) => {
return cloneGeometry(g);
});
return geom;
}
geom.coordinates = deepSlice(geometry.coordinates);
return geom;
}
function deepSlice(coords) {
const cloned = coords;
if (typeof cloned[0] !== "object") {
return cloned.slice();
}
return cloned.map((coord) => {
return deepSlice(coord);
});
}
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
function nearestPoint(targetPoint, points, options = {}) {
if (!targetPoint) throw new Error("targetPoint is required");
if (!points) throw new Error("points is required");
let minDist = Infinity;
let bestFeatureIndex = 0;
featureEach(points, (pt, featureIndex) => {
const distanceToPoint = distance(targetPoint, pt, options);
if (distanceToPoint < minDist) {
bestFeatureIndex = featureIndex;
minDist = distanceToPoint;
}
});
const nearestPoint2 = clone(points.features[bestFeatureIndex]);
return __spreadProps(__spreadValues({}, nearestPoint2), {
properties: __spreadProps(__spreadValues({}, nearestPoint2.properties), {
featureIndex: bestFeatureIndex,
distanceToPoint: minDist
})
});
}
var turf_nearest_point_default = nearestPoint;
// Constants for elevation calculation
const POW2_8 = Math.pow(2, 8);
const POW2_16 = Math.pow(2, 16);
const POW2_23 = Math.pow(2, 23);
const POW2_24 = Math.pow(2, 24);
// No data color (128, 0, 0)
const NO_DATA_R = 128;
const NO_DATA_G = 0;
const NO_DATA_B = 0;
// Convert lat/lng to tile coordinates
function latLngToTile(lat, lng, z) {
const lng_rad = lng * Math.PI / 180;
const R = 128 / Math.PI;
const worldCoordX = R * (lng_rad + Math.PI);
const pixelCoordX = worldCoordX * Math.pow(2, z);
const tileCoordX = Math.floor(pixelCoordX / 256);
const lat_rad = lat * Math.PI / 180;
const worldCoordY = -R / 2 * Math.log((1 + Math.sin(lat_rad)) / (1 - Math.sin(lat_rad))) + 128;
const pixelCoordY = worldCoordY * Math.pow(2, z);
const tileCoordY = Math.floor(pixelCoordY / 256);
return {
tile: {
z,
x: tileCoordX,
y: tileCoordY
},
pixel: {
x: Math.floor(pixelCoordX - tileCoordX * 256),
y: Math.floor(pixelCoordY - tileCoordY * 256)
}
};
}
// Get pixel RGB from PNG buffer (unified function)
function getPixelFromPNG(png, x, y) {
if (!png) return [0, 0, 0];
const {
width,
height,
data
} = png;
if (x < 0 || x >= width || y < 0 || y >= height) {
return [0, 0, 0];
}
const idx = (y * width + x) * 4;
return [data[idx] || 0, data[idx + 1] || 0, data[idx + 2] || 0];
}
// Get pixel RGB from ImageBitmap (browser)
function getPixelFromImageBitmap(imageBitmap, x, y) {
// Create canvas to read pixel
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 256;
ctx.drawImage(imageBitmap, 0, 0);
const imageData = ctx.getImageData(x, y, 1, 1);
const data = imageData.data;
return [data[0], data[1], data[2]];
}
// Calculate elevation from RGB values
function calculateElevationFromRGB(r, g, b) {
// Check for no data color
if (r === NO_DATA_R && g === NO_DATA_G && b === NO_DATA_B) {
return null;
}
// Calculate elevation using the same formula as JavaScript code
const d = r * POW2_16 + g * POW2_8 + b;
let h = d < POW2_23 ? d : d - POW2_24;
if (h === -POW2_23) {
h = 0;
} else {
h *= 0.01;
}
return h;
}
// Preload tiles in parallel (generic function)
async function preloadTiles(tileCoords, tileProviders) {
const promises = [];
for (const coord of tileCoords) {
for (const provider of tileProviders) {
promises.push(provider(coord.z, coord.x, coord.y));
}
}
await Promise.all(promises);
}
// Create URL list from DEM configs
function createDEMUrlList(demConfigs) {
const urlList = [];
for (const demConfig of demConfigs) {
const minzoom = Math.min(demConfig.minzoom, demConfig.maxzoom);
const maxzoom = Math.max(demConfig.minzoom, demConfig.maxzoom);
for (let z = maxzoom; z >= minzoom; z--) {
urlList.push(_extends({}, demConfig, {
zoom: z
}));
}
}
return urlList;
}
// Read PNG from buffer with error handling
function readPNGFromBuffer(buffer) {
try {
return PNG.sync.read(buffer);
} catch (error) {
console.warn('Failed to read PNG from buffer:', error);
return undefined;
}
}
// LRU Cache for tile images
class TileCache {
constructor(maxSize = 100 * 1024 * 1024, ttl = 5 * 60 * 1000) {
this.cache = new Map();
this.maxSize = void 0;
this.ttl = void 0;
this.timestamps = new Map();
this.maxSize = maxSize;
this.ttl = ttl;
}
// Create key for cache
createKey(z, x, y, url) {
return `${z}/${x}/${y}|${url}`;
}
// Get tile from cache
get(z, x, y, url) {
const key = this.createKey(z, x, y, url);
const data = this.cache.get(key);
const timestamp = this.timestamps.get(key);
if (!data || !timestamp) {
return null;
}
// Check TTL
if (Date.now() - timestamp > this.ttl) {
this.cache.delete(key);
this.timestamps.delete(key);
return null;
}
return data;
}
// Save tile to cache
set(z, x, y, url, data) {
const key = this.createKey(z, x, y, url);
// Check cache size
if (this.cache.size > 0 && this.getCacheSize() + data.length > this.maxSize) {
this.evictOldest();
}
this.cache.set(key, data);
this.timestamps.set(key, Date.now());
}
// Preload tiles into cache with deduplication
async preloadTiles(tileUrls, zoom, tileCoords) {
var _this = this;
console.log(`🔄 Preloading tiles into cache...`);
// Create unique set of all tile coordinates
const uniqueTiles = new Set();
for (const coord of tileCoords) {
for (const urlTemplate of tileUrls) {
const url = urlTemplate.replace('{z}', coord.z.toString()).replace('{x}', coord.x.toString()).replace('{y}', coord.y.toString());
uniqueTiles.add(`${coord.z}/${coord.x}/${coord.y}|${url}`);
}
}
console.log(`📊 Found ${uniqueTiles.size} unique tiles to preload`);
// Preload with concurrency limit to avoid overwhelming server
const concurrencyLimit = 5;
const tiles = Array.from(uniqueTiles);
const results = [];
for (let i = 0; i < tiles.length; i += concurrencyLimit) {
const batch = tiles.slice(i, i + concurrencyLimit);
const batchPromises = batch.map(async function (tileKey) {
const [coordPart, url] = tileKey.split('|');
const [z, x, y] = coordPart.split('/').map(Number);
try {
const buffer = await fetchTile(url, _this, {
z,
x,
y
});
console.log(`✅ Preloaded: ${z}/${x}/${y}`);
return {
success: true,
tile: tileKey
};
} catch (error) {
var _error$message;
if ((_error$message = error.message) != null && _error$message.includes('TILE_NOT_FOUND')) {
console.log(`⚠️ Not found: ${z}/${x}/${y}`);