diff options
Diffstat (limited to 'viewer/js/urlstate.js')
| -rw-r--r-- | viewer/js/urlstate.js | 59 |
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 : ''; +} |
