worldmap-viewer

git clone https://git.godosa.eu/worldmap-viewer

master

raw ยท 4163 bytes

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