aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/viewer/js/overlay_geom.js
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/js/overlay_geom.js')
-rw-r--r--viewer/js/overlay_geom.js106
1 files changed, 106 insertions, 0 deletions
diff --git a/viewer/js/overlay_geom.js b/viewer/js/overlay_geom.js
new file mode 100644
index 0000000..b43609e
--- /dev/null
+++ b/viewer/js/overlay_geom.js
@@ -0,0 +1,106 @@
+// overlay_geom.js — vector overlays → line-segment vertex arrays for the globe and flat views
+import { R_KM, toVec, interpolate, project, EE_XMAX, PC_XMAX } from './geo.js';
+
+const onSeam = (a, b) => Math.abs(a[0]) === 180 && Math.abs(b[0]) === 180;
+
+export function linesFromGeoJSON(fc, keep = () => true) {
+ const out = [];
+ const add = coords => { // split at segments that run along the antimeridian cut
+ let cur = [];
+ coords.forEach((c, i) => {
+ if (i > 0 && onSeam(coords[i - 1], c)) {
+ if (cur.length > 1) out.push(cur);
+ cur = [];
+ }
+ cur.push({ lat: c[1], lon: c[0] });
+ });
+ if (cur.length > 1) out.push(cur);
+ };
+ for (const f of fc.features || []) {
+ const g = f.geometry;
+ if (!g || !keep(f.properties || {})) continue;
+ if (g.type === 'LineString') add(g.coordinates);
+ else if (g.type === 'MultiLineString' || g.type === 'Polygon') g.coordinates.forEach(add);
+ else if (g.type === 'MultiPolygon') g.coordinates.forEach(poly => poly.forEach(add));
+ }
+ return out;
+}
+
+export function densify(lines, stepDeg = 1) {
+ return lines.map(line => {
+ if (line.length < 2) return [...line];
+ const out = [];
+ for (let i = 0; i + 1 < line.length; i++) out.push(...interpolate(line[i], line[i + 1], stepDeg).slice(i === 0 ? 0 : 1));
+ return out;
+ });
+}
+
+export function globeSegments(lines, radius = 1.002, stepDeg = 1) {
+ const pos = [];
+ for (const line of lines) {
+ for (let i = 0; i + 1 < line.length; i++) {
+ const pts = interpolate(line[i], line[i + 1], stepDeg);
+ for (let k = 0; k + 1 < pts.length; k++) {
+ const a = toVec(pts[k].lat, pts[k].lon), b = toVec(pts[k + 1].lat, pts[k + 1].lon);
+ pos.push(a[0] * radius, a[1] * radius, a[2] * radius, b[0] * radius, b[1] * radius, b[2] * radius);
+ }
+ }
+ }
+ return new Float32Array(pos);
+}
+
+// Lines laid on the 3D ground near the camera. Segments with an end inside the cap (cosCap around n, the eye's
+// direction) are cut into pieces ≈ 1/100 of their distance from the eye; each vertex sits on heightM (the drawn
+// ground, m), then is pulled 1% of the way toward the eye: the same pixel, a nearer depth, so the line wins over
+// the ground it lies on but hills in front still hide it. Positions are relative to origin.
+const PULL = 0.01;
+export function drapedSegments(lines, { eye, n, cosCap, origin, heightM }) {
+ const pos = [], kmPerDeg = (R_KM * Math.PI) / 180;
+ const inCap = p => { const v = toVec(p.lat, p.lon); return v[0] * n[0] + v[1] * n[1] + v[2] * n[2] >= cosCap; };
+ const distKm = p => { const v = toVec(p.lat, p.lon); return Math.hypot(v[0] - eye[0], v[1] - eye[1], v[2] - eye[2]) * R_KM; };
+ const put = p => {
+ const v = toVec(p.lat, p.lon), r = 1 + (Math.max(0, heightM(p.lat, p.lon)) + 1) / (R_KM * 1000);
+ for (let i = 0; i < 3; i++) pos.push(v[i] * r + (eye[i] - v[i] * r) * PULL - origin[i]);
+ };
+ for (const line of lines) {
+ for (let i = 0; i + 1 < line.length; i++) {
+ const a = line[i], b = line[i + 1];
+ if (!inCap(a) && !inCap(b)) continue;
+ const pts = interpolate(a, b, Math.max(0.01, Math.min(distKm(a), distKm(b)) / 100) / kmPerDeg);
+ for (let k = 0; k + 1 < pts.length; k++) {
+ put(pts[k]);
+ put(pts[k + 1]);
+ }
+ }
+ }
+ return new Float32Array(pos);
+}
+
+export function flatSegments(lines, view, z = 0.001) {
+ const xmax = view === 'equal_earth' ? EE_XMAX : PC_XMAX;
+ const pos = [];
+ for (const line of lines) {
+ let prev = null;
+ for (const p of line) {
+ const q = project(view, p.lat, p.lon);
+ if (prev && Math.abs(q.x - prev.x) < xmax) pos.push(prev.x, prev.y, z, q.x, q.y, z);
+ prev = q;
+ }
+ }
+ return new Float32Array(pos);
+}
+
+export function graticuleLines(step = 30) {
+ const lines = [];
+ for (let lon = -180; lon <= 180; lon += step) {
+ const l = [];
+ for (let lat = -90; lat <= 90; lat += 2) l.push({ lat, lon });
+ lines.push(l);
+ }
+ for (let lat = -90 + step; lat < 90; lat += step) {
+ const l = [];
+ for (let lon = -180; lon <= 180; lon += 2) l.push({ lat, lon });
+ lines.push(l);
+ }
+ return lines;
+}