From 46045722b29b39782ae57254fa70e6362eb437ad Mon Sep 17 00:00:00 2001 From: Hidde Wieringa Date: Mon, 29 Jul 2024 20:46:14 +0200 Subject: [PATCH] Popup on clicking map features (#80) Part of #4 Needs more work on the fields that are available for each layer / source per zoom level. Also descriptions / maps like gauge sizes, train protection system and voltage description is not taken into account. In general some layers need to be merged, and reused so the standard layer gets e.g. gauge sizes on all zoom levels. --- martin/configuration.yml | 1 + proxy/css/ui.css | 2 +- proxy/js/styles.mjs | 2 +- proxy/js/ui.js | 114 ++++++++++++++++++++++++++++++--------- 4 files changed, 92 insertions(+), 27 deletions(-) diff --git a/martin/configuration.yml b/martin/configuration.yml index 3d7e2537..8ec5ec49 100644 --- a/martin/configuration.yml +++ b/martin/configuration.yml @@ -146,6 +146,7 @@ postgres: properties: railway: string ref: string + # TODO: rename local_operated railway_local_operated: boolean # --- Speed --- # diff --git a/proxy/css/ui.css b/proxy/css/ui.css index ab21ca76..fe92794b 100644 --- a/proxy/css/ui.css +++ b/proxy/css/ui.css @@ -56,7 +56,7 @@ body { border-color: #1c7430; } .maplibregl-ctrl-edit .maplibregl-ctrl-icon { - background-size: contain; + background-size: 24px; background-image: url("data:image/svg+xml,%3Csvg width='800px' height='800px' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M21.1213 2.70705C19.9497 1.53548 18.0503 1.53547 16.8787 2.70705L15.1989 4.38685L7.29289 12.2928C7.16473 12.421 7.07382 12.5816 7.02986 12.7574L6.02986 16.7574C5.94466 17.0982 6.04451 17.4587 6.29289 17.707C6.54127 17.9554 6.90176 18.0553 7.24254 17.9701L11.2425 16.9701C11.4184 16.9261 11.5789 16.8352 11.7071 16.707L19.5556 8.85857L21.2929 7.12126C22.4645 5.94969 22.4645 4.05019 21.2929 2.87862L21.1213 2.70705ZM18.2929 4.12126C18.6834 3.73074 19.3166 3.73074 19.7071 4.12126L19.8787 4.29283C20.2692 4.68336 20.2692 5.31653 19.8787 5.70705L18.8622 6.72357L17.3068 5.10738L18.2929 4.12126ZM15.8923 6.52185L17.4477 8.13804L10.4888 15.097L8.37437 15.6256L8.90296 13.5112L15.8923 6.52185ZM4 7.99994C4 7.44766 4.44772 6.99994 5 6.99994H10C10.5523 6.99994 11 6.55223 11 5.99994C11 5.44766 10.5523 4.99994 10 4.99994H5C3.34315 4.99994 2 6.34309 2 7.99994V18.9999C2 20.6568 3.34315 21.9999 5 21.9999H16C17.6569 21.9999 19 20.6568 19 18.9999V13.9999C19 13.4477 18.5523 12.9999 18 12.9999C17.4477 12.9999 17 13.4477 17 13.9999V18.9999C17 19.5522 16.5523 19.9999 16 19.9999H5C4.44772 19.9999 4 19.5522 4 18.9999V7.99994Z' fill='%23000000'/%3E%3C/svg%3E"); } .maplibregl-ctrl-configuration .maplibregl-ctrl-icon { diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index 46c49663..54f3e00d 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -3832,7 +3832,7 @@ const legendData = { feature: 'does-not-exist', type: 'line', azimuth: 135.5, - direction_both: false, + direction_both: false, }, variants: [ { diff --git a/proxy/js/ui.js b/proxy/js/ui.js index 0aff5669..6e4aecf3 100644 --- a/proxy/js/ui.js +++ b/proxy/js/ui.js @@ -140,11 +140,11 @@ function showSearchResults(results) {
${results.map(result => - ` + ` ${result.icon ? `${result.icon}` : ''} ${result.label} ` - ).join('')} + ).join('')}
`; searchResults.style.display = 'block'; @@ -579,42 +579,106 @@ const onStylesheetChange = styleSheet => { onMapZoom(map.getZoom()); } -map.on('load', () => onMapZoom(map.getZoom())); -map.on('zoomend', () => onMapZoom(map.getZoom())); - -// When a click event occurs on a feature in the places layer, open a popup at the -// location of the feature, with description HTML from its properties. -map.on('click', 'search', (e) => { - const feature = e.features[0]; - const coordinates = feature.geometry.coordinates.slice(); - const properties = feature.properties; - const content = ` +function popupContent(properties) { + // TODO move icon SVGs to proxy + // TODO reuse icons from map features for these features + // TODO lookup train protection name + // TODO format voltage + // TODO format gauge(s) + const label = properties.label ?? properties.name ?? properties.ref; + return `
${properties.icon ? `${properties.icon}` : ''} - ${properties.label} - ${icons.edit} + ${label ? `${properties.osm_id ? `` : ''}${label}${properties.osm_id ? `` : ''}` : ''} + ${properties.osm_id ? `${icons.edit}` : ''}
${properties.railway_ref ? `reference: ${properties.railway_ref}` : ''} ${properties.ref ? `reference: ${properties.ref}` : ''} ${properties.uic_ref ? `UIC reference: ${properties.uic_ref}` : ''} ${properties.position ? `position: ${properties.position}` : ''} + ${properties.pos ? `position: ${properties.pos}` : ''} ${properties.operator ? `operator: ${properties.operator}` : ''} + ${properties.track_ref ? `track: ${properties.track_ref}` : ''} + ${properties.highspeed === true ? `high speed` : ''} + ${properties.usage ? `usage: ${properties.usage}` : ''} + ${properties.service ? `service: ${properties.service}` : ''} + ${properties.tunnel === true ? `tunnel` : ''} + ${properties.bridge === true ? `bridge` : ''} + ${properties.railway_local_operated === true ? `operated locally` : ''} + ${properties.maxspeed ? `maximum speed: ${properties.maxspeed} km/h` : ''} + ${properties.direction_both ? `both directions` : ''} + ${properties.train_protection ? `train protection: ${properties.train_protection}` : ''} + ${properties.deactivated === true ? `deactivated` : ''} + ${properties.type === 'line' ? `line signal` : ''} + ${properties.electrification_state ? `line electrification: ${properties.electrification_state}` : ''} + ${properties.voltage ? `voltage: ${properties.voltage} V` : ''} + ${properties.frequency ? `frequency: ${properties.frequency} Hz` : ''} + ${properties.gauge0 ? `gauge: ${properties.gauge0}` : ''} + ${properties.gauge1 ? `gauge: ${properties.gauge1}` : ''} + ${properties.gauge2 ? `gauge: ${properties.gauge2}` : ''}
`; +} - new maplibregl.Popup() - .setLngLat(coordinates) - .setHTML(content) - .addTo(map); -}); +map.on('load', () => onMapZoom(map.getZoom())); +map.on('zoomend', () => onMapZoom(map.getZoom())); -// Change the cursor to a pointer when the mouse is over the places layer. -map.on('mouseenter', 'search', () => { - map.getCanvas().style.cursor = 'pointer'; +map.on('mousehover', event => { + const features = map.queryRenderedFeatures(event.point); + if (features.length > 0) { + map.getCanvas().style.cursor = 'pointer'; + } else { + map.getCanvas().style.cursor = ''; + } }); -// Change it back to a pointer when it leaves. -map.on('mouseleave', 'search', () => { - map.getCanvas().style.cursor = ''; +function closestPointOnLine(point, line) { + const lngLatPoint = maplibregl.LngLat.convert(point) + let {closest0, closest1} = line.map(maplibregl.LngLat.convert).reduce((acc, cur) => { + const d = lngLatPoint.distanceTo(cur) + if (acc.closest0 == null || d < lngLatPoint.distanceTo(acc.closest0)) { + return {closest0: cur, closest1: acc.closest0} + } else if (acc.closest1 == null || d < lngLatPoint.distanceTo(acc.closest1)) { + return {closest0: acc.closest0, closest1: cur} + } else { + return acc; + } + }, {closest0: null, closest1: null}); + + closest0 = closest0.toArray() + closest1 = closest1.toArray() + point = lngLatPoint.toArray() + + if (closest0 == null && closest1 == null) { + return null; + } else if (closest1 == null) { + return closest0; + } else { + // project point onto line between closest0 and closest1 + const abx = closest1[0] - closest0[0] + const aby = closest1[1] - closest0[1] + const acx = point[0] - closest0[0] + const acy = point[1] - closest0[1] + const coeff = (abx * acx + aby * acy) / (abx * abx + aby * aby) + return [closest0[0] + abx * coeff, closest0[1] + aby * coeff] + } +} + +map.on('click', event => { + const features = map.queryRenderedFeatures(event.point); + if (features.length > 0) { + const feature = features[0]; + + const coordinates = feature.geometry.type === 'Point' + ? feature.geometry.coordinates.slice() + : feature.geometry.type === 'LineString' + ? closestPointOnLine(event.lngLat, feature.geometry.coordinates) + : event.lngLat; + + new maplibregl.Popup() + .setLngLat(coordinates) + .setHTML(popupContent(feature.properties)) + .addTo(map); + } });