Files
gods-eye-view/server/providers/overpass/geometry.js
T

102 lines
3.2 KiB
JavaScript

import {
OVERPASS_SIMPLIFY_MIN_BYTES,
OVERPASS_SIMPLIFY_MIN_POINTS,
OVERPASS_SIMPLIFY_TOLERANCE_DEG,
} from './constants.js';
/** Iterative Douglas-Peucker on [{lat,lon},...] (planar-degree approx — fine at
* the ~44 m tolerance used here). Endpoints always kept. */
function douglasPeucker(points, toleranceDeg) {
const n = points.length;
if (n <= 2) return points;
const keep = new Uint8Array(n);
keep[0] = 1;
keep[n - 1] = 1;
const stack = [[0, n - 1]];
while (stack.length) {
const [a, b] = stack.pop();
if (b - a < 2) continue;
const ax = points[a].lon;
const ay = points[a].lat;
const vx = points[b].lon - ax;
const vy = points[b].lat - ay;
const c2 = vx * vx + vy * vy;
let worst = -1;
let worstDist = toleranceDeg;
for (let i = a + 1; i < b; i++) {
const wx = points[i].lon - ax;
const wy = points[i].lat - ay;
let d;
if (c2 === 0) {
d = Math.hypot(wx, wy);
} else {
const t = Math.max(0, Math.min(1, (vx * wx + vy * wy) / c2));
d = Math.hypot(wx - t * vx, wy - t * vy);
}
if (d > worstDist) {
worstDist = d;
worst = i;
}
}
if (worst >= 0) {
keep[worst] = 1;
stack.push([a, worst], [worst, b]);
}
}
const out = [];
for (let i = 0; i < n; i++) if (keep[i]) out.push(points[i]);
return out;
}
/** Simplify one element's geometry array in place if it is big enough. */
function simplifyElementGeometry(el, minPoints, toleranceDeg) {
if (Array.isArray(el?.geometry) && el.geometry.length >= minPoints) {
el.geometry = douglasPeucker(el.geometry, toleranceDeg);
}
if (Array.isArray(el?.members)) {
for (const member of el.members) {
if (
Array.isArray(member?.geometry) &&
member.geometry.length >= minPoints
) {
member.geometry = douglasPeucker(member.geometry, toleranceDeg);
}
}
}
}
/**
* Server-side geometry simplification for large Overpass `out geom` payloads.
* Region/state boundary pivots return multi-MB coastline rings whose fidelity
* nothing downstream needs (the client re-simplifies for draw); decimating them
* HERE shrinks the disk cache, the wire, and client parse time — and is what
* makes the raised read cap safe. Anything unparseable or below the thresholds
* passes through byte-identical. Exported for tests (opts override thresholds).
*
* @param {string} bodyText - Raw upstream JSON body.
* @returns {string} Possibly-simplified JSON body.
*/
function simplifyOverpassPayloadBody(bodyText, opts = {}) {
const minBytes = opts.minBytes ?? OVERPASS_SIMPLIFY_MIN_BYTES;
const minPoints = opts.minPoints ?? OVERPASS_SIMPLIFY_MIN_POINTS;
const toleranceDeg = opts.toleranceDeg ?? OVERPASS_SIMPLIFY_TOLERANCE_DEG;
if (typeof bodyText !== 'string' || bodyText.length < minBytes)
return bodyText;
let data;
try {
data = JSON.parse(bodyText);
} catch {
return bodyText;
}
if (!Array.isArray(data?.elements)) return bodyText;
for (const el of data.elements)
simplifyElementGeometry(el, minPoints, toleranceDeg);
try {
return JSON.stringify(data);
} catch {
return bodyText;
}
}
export { simplifyOverpassPayloadBody };