This is to be able to link from the booking form, with a link under the area preferences, and show the zones layer, that is what customers most certainly want to see at that point.
214 lines
6.7 KiB
JavaScript
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);
|
|
}
|
|
})();
|