camper/web/static/map.js

214 lines
6.7 KiB
JavaScript

(function () {
'use strict';
const container = document.getElementById('campground_map');
if (!container) {
console.warn('could not find map container');
}
const svg = container.childNodes[0];
svg.remove();
const latLngBounds = L.latLngBounds([[0, 0], [1192, 1500]]);
const map = L.map(container, {
center: latLngBounds.getCenter(),
minZoom: -2,
maxZoom: 2,
zoom: -1,
scrollWheelZoom: false,
maxBounds: latLngBounds,
maxBoundsViscosity: 1.0,
zoomSnap: 0,
zoomDelta: 0.5,
crs: L.CRS.Simple,
});
map.fitBounds(latLngBounds);
L.svgOverlay(svg, latLngBounds).addTo(map);
// from https://github.com/jkroso/parse-svg-path/
const parse = (function () {
'use strict';
const length = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0}
const segment = /([astvzqmhlc])([^astvzqmhlc]*)/ig;
const number = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/ig;
function parseValues(args) {
const numbers = args.match(number)
return numbers ? numbers.map(Number) : []
}
return function (path) {
const data = []
path.replace(segment, function (_, command, args) {
let type = command.toLowerCase()
args = parseValues(args)
// overloaded moveTo
if (type === 'm' && args.length > 2) {
data.push([command].concat(args.splice(0, 2)))
type = 'l'
command = command === 'm' ? 'l' : 'L'
}
while (true) {
if (args.length === length[type]) {
args.unshift(command)
return data.push(args)
}
if (args.length < length[type]) throw new Error('malformed path data')
data.push([command].concat(args.splice(0, length[type])))
}
})
return data
}
})();
function highlightAccommodation(e) {
const layer = e.target;
layer.setStyle({
color: '#ffe673',
});
}
function resetHighlight(e) {
const layer = e.target;
layer.setStyle({
color: 'transparent',
});
}
function getPath(el) {
if (!el) {
return el;
}
switch (el.localName) {
case 'path':
case 'rect':
return el;
case 'g':
return getPath(el.firstElementChild);
default:
console.warn('unhandled element type', el.localName);
return null
}
}
function getCTM(ctm, el) {
if (el.localName === 'svg') {
return ctm;
}
const transform = el.getAttribute('transform');
if (!transform) {
return getCTM(ctm, el.parentElement);
}
const matrix = new DOMMatrix(transform);
return getCTM(matrix.multiply(ctm), el.parentElement);
}
function convertToPoints(path) {
const ctm = getCTM(new DOMMatrix([1, 0, 0, 1, 0, 0]), path);
const points = [];
const p0 = new DOMPoint();
function setP0(x, y) {
p0.x = x;
p0.y = y;
const p1 = p0.matrixTransform(ctm);
p0.x = p1.x;
p0.y = latLngBounds.getNorth() - p1.y;
}
switch (path.localName) {
case 'rect':
const x = parseFloat(path.getAttribute('x'));
const y = parseFloat(path.getAttribute('y'));
const w = parseFloat(path.getAttribute('width'));
const h = parseFloat(path.getAttribute('height'));
setP0(x, y);
points.push([p0.y, p0.x]);
setP0(x + w, y);
points.push([p0.y, p0.x]);
setP0(x + w, y + h);
points.push([p0.y, p0.x]);
setP0(x, y + h);
points.push([p0.y, p0.x]);
break;
case 'path':
const commands = parse(path.getAttribute('d'));
for (const cmd of commands) {
switch (cmd[0]) {
case 'M':
case 'L':
setP0(cmd[1], cmd[2]);
break;
case 'm':
case 'l':
p0.x += cmd[1];
p0.y -= cmd[2];
break;
case 'C':
setP0(cmd[5], cmd[6]);
break;
case 'Z':
case 'z':
continue;
default:
console.error(cmd);
}
points.push([p0.y, p0.x]);
}
break;
}
return points;
}
function setupFeatures(prefix, baseURI) {
for (const campsite of Array.from(container.querySelectorAll(`[id^="${prefix}"]`))) {
const label = campsite.id.substring(prefix.length);
if (!label) {
continue;
}
const path = getPath(campsite);
if (!path) {
console.warn(campsite, 'has no path');
continue;
}
const points = convertToPoints(path);
const feature = L.polygon(points, {color: 'transparent'}).addTo(map);
feature.on({
mouseover: highlightAccommodation,
mouseout: resetHighlight,
click: function () {
window.location = `${baseURI}/${label}`;
},
})
}
}
const language = document.documentElement.getAttribute('lang');
setupFeatures('cp_', `/${language}/campsites`);
setupFeatures('cr_', `/${language}/amenities`);
const zones = container.querySelector('#zones');
const zonesOverlay = L.layerGroup();
for (const path of Array.from(zones.querySelectorAll('path'))) {
const points = convertToPoints(path);
const zone = L
.polygon(points, {color: 'var(--accent)'})
.bindTooltip(path.getAttribute('aria-label'), {permanent: true})
;
zonesOverlay.addLayer(zone);
}
L.control.layers(null, null, {collapsed: false})
.addOverlay(zonesOverlay, zones.getAttribute('aria-label'))
.addTo(map)
;
const params = new URLSearchParams(window.location.search);
if (params.has('zones')) {
map.addLayer(zonesOverlay);
}
})();