From 8d49857daed1c7a5aa36cd0e869167549ab68034 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Thu, 25 Jan 2024 04:28:51 +0100 Subject: [PATCH] Add a polygon around each accommodation on the public map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I can not use in that map because Leaflet handles the mouse over before the anchors sees it, thus it is impossible to click on them; i have to use a Leaflet layer. Fortunately, i can just use the ’s coordinates as the polygon points, because with CRS.Simple the coordinates map to pixel, except for the reversed Y/latitude coordinate. Unfortunately, coordinates are not straightforward to get: I have to follow the drawing coordinates, taking into account the current transformation (CTM), and keeping the last point around for relative coordinates. Bézier curves are simplified to a straight line from start to end. There is one single accommodation that started with a relative move command (m), which apparently have to be treated as an absolute move (M), but subsequent pairs are treated as relative coordinates[0]. It was easier for me to convert that relative move to absolute and add a relative lineto command (l) to the next pair. For now, all i do is highlight the accommodation and zoom it on click, because i do not know how i should the accommodation’s information. [0]: https://www.w3.org/TR/SVG11/paths.html#PathDataMovetoCommands --- web/static/map.js | 132 +++++++++++++++++++++++++ web/templates/campground_map.svg | 2 +- web/templates/public/campground.gohtml | 25 +---- 3 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 web/static/map.js diff --git a/web/static/map.js b/web/static/map.js new file mode 100644 index 0000000..7b414df --- /dev/null +++ b/web/static/map.js @@ -0,0 +1,132 @@ +(function () { + 'use strict'; + + const container = document.getElementById('campground_map'); + container.style.height = '80vh'; + + 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, + 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', + }); + layer.bringToFront(); + } + + function resetHighlight(e) { + const layer = e.target; + layer.setStyle({ + color: 'transparent', + }); + } + + function zoomToAccommodation(e) { + map.fitBounds(e.target.getBounds()); + } + + // XXX: this is from the “parceles” layer. + const ctm = [1, 0, 0, 1, 83.2784, 66.1766]; + const transform = function (x, y) { + return [ctm[0] * x + ctm[2] * y + ctm[4], ctm[1] * x + ctm[3] * y + ctm[5]]; + } + + const prefix = 'cp_'; + for (const campsite of Array.from(container.querySelectorAll(`[id^="${prefix}"]`))) { + const label = campsite.id.substring(prefix.length); + if (!label) { + continue; + } + const path = campsite.firstElementChild; + const commands = parse(path.getAttribute('d')); + const points = []; + const p0 = [0, 0]; + for (const cmd of commands) { + switch (cmd[0]) { + case 'M': + case 'L': + const cmdM = transform(cmd[1], cmd[2]); + p0[0] = cmdM[0]; + p0[1] = latLngBounds.getNorth() - cmdM[1]; + break; + case 'm': + case 'l': + p0[0] += cmd[1]; + p0[1] -= cmd[2]; + break; + case 'C': + const cmdC = transform(cmd[5], cmd[6]); + p0[0] = cmdC[0]; + p0[1] = latLngBounds.getNorth() - cmdC[1]; + break; + case 'Z': + case 'z': + continue; + default: + console.error(cmd); + } + points.push([p0[1], p0[0]]); + } + const accommodation = L.polygon(points, {color: 'transparent'}).addTo(map); + accommodation.on({ + mouseover: highlightAccommodation, + mouseout: resetHighlight, + click: zoomToAccommodation, + }) + } +})(); diff --git a/web/templates/campground_map.svg b/web/templates/campground_map.svg index 124dca6..9fccee1 100644 --- a/web/templates/campground_map.svg +++ b/web/templates/campground_map.svg @@ -139,7 +139,7 @@ - + diff --git a/web/templates/public/campground.gohtml b/web/templates/public/campground.gohtml index 3e1ff61..4bef288 100644 --- a/web/templates/public/campground.gohtml +++ b/web/templates/public/campground.gohtml @@ -17,28 +17,5 @@
{{ template "campground_map.svg" }}
- + {{- end }}