worldmap-viewer

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

master

raw · 2986 bytes

// urlstate.js — view state <-> location hash
import { YEAR_DAYS, DAY_MOMENTS } from './sky.js';
import { MIN_ALT, MAX_ALT, MAX_ZOOM, MAX_PITCH, MAX_EXAG } from './limits.js';

export const VIEWS = ['globe', 'equal_earth', 'plate_carree'];
export const DEFAULT = Object.freeze({ layer: 'relief', view: 'globe', lat: 20, lon: 0, alt: 2, zoom: 1,
  overlays: ['coast', 'lakes', 'rivers', 'graticule', 'regions', 'pins'], over: '', op: 0.5, sky: false, day: Math.round(YEAR_DAYS / 4), mo: Math.round(DAY_MOMENTS / 2),
  hd: 0, pt: 0, terrain: 1, era: '', water: true });
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));

export function parse(hash) {
  const s = { ...DEFAULT, overlays: [...DEFAULT.overlays] };
  const q = new URLSearchParams((hash || '').replace(/^#/, ''));
  if (q.get('layer')) s.layer = q.get('layer');
  if (VIEWS.includes(q.get('view'))) s.view = q.get('view');
  for (const k of ['lat', 'lon', 'alt', 'zoom', 'op', 'day', 'mo', 'hd', 'pt']) {
    const v = parseFloat(q.get(k));
    if (Number.isFinite(v)) s[k] = v;
  }
  s.lat = clamp(s.lat, -90, 90);
  s.lon = ((((s.lon + 180) % 360) + 360) % 360) - 180;
  s.alt = clamp(s.alt, MIN_ALT, MAX_ALT);
  s.zoom = clamp(s.zoom, 1, MAX_ZOOM);
  s.op = clamp(s.op, 0, 1);
  s.day = clamp(Math.round(s.day), 0, YEAR_DAYS - 1);
  s.mo = clamp(Math.round(s.mo), 0, DAY_MOMENTS - 1);
  s.hd = ((s.hd % 360) + 360) % 360;
  s.pt = clamp(s.pt, 0, MAX_PITCH);
  const t = parseFloat(q.get('3d'));
  if (Number.isFinite(t)) s.terrain = t <= 0 ? 0 : clamp(t, 1, MAX_EXAG);   // 0 = flat tiles
  s.over = q.get('over') || '';
  s.sky = q.get('sky') === '1';
  s.era = q.get('era') || '';
  s.water = q.get('water') !== '0';
  if (q.has('overlays')) s.overlays = q.get('overlays').split(',').filter(Boolean);
  return s;
}

export const drawnLayer = (s, kind) =>   // Water off on the 3D globe: the sea floor in place of relief
  (kind === 'globe' && s.terrain > 0 && !s.water && s.layer === 'relief' ? 'seafloor' : s.layer);

export function serialize(s) {
  const q = { layer: s.layer, view: s.view, lat: String(+s.lat.toFixed(4)), lon: String(+s.lon.toFixed(4)),
    alt: String(+s.alt.toPrecision(4)), zoom: String(+s.zoom.toPrecision(5)), overlays: s.overlays.join(',') };
  if (s.over) Object.assign(q, { over: s.over, op: String(+s.op.toFixed(2)) });
  if (s.sky) Object.assign(q, { sky: '1', day: String(s.day), mo: String(s.mo) });
  if (s.view === 'globe' && (s.hd || s.pt)) Object.assign(q, { hd: String(+s.hd.toFixed(1)), pt: String(+s.pt.toFixed(1)) });
  if (s.terrain !== 1) q['3d'] = String(s.terrain);
  if (s.era) q.era = s.era;
  if (!s.water) q.water = '0';
  return '#' + new URLSearchParams(q).toString();
}

export function carryHash(hash) {   // to another world: the view carries over; era and time of year/day do not
  const q = new URLSearchParams((hash || '').replace(/^#/, ''));
  for (const k of ['era', 'day', 'mo']) q.delete(k);
  const s = q.toString();
  return s ? '#' + s : '';
}