aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/viewer/js/urlstate.js
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/js/urlstate.js')
-rw-r--r--viewer/js/urlstate.js59
1 files changed, 59 insertions, 0 deletions
diff --git a/viewer/js/urlstate.js b/viewer/js/urlstate.js
new file mode 100644
index 0000000..113ae7f
--- /dev/null
+++ b/viewer/js/urlstate.js
@@ -0,0 +1,59 @@
+// 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 : '';
+}