diff --git a/proxy/css/ui.css b/proxy/css/ui.css index 6726793f..ab21ca76 100644 --- a/proxy/css/ui.css +++ b/proxy/css/ui.css @@ -27,9 +27,6 @@ body { #search-results, #search-milestones-form { display: none; } -#search-results .no-results { - text-align: center; -} #legend { position: absolute; diff --git a/proxy/index.html b/proxy/index.html index c9e7c958..177dcb2e 100644 --- a/proxy/index.html +++ b/proxy/index.html @@ -92,7 +92,7 @@ -
+
diff --git a/proxy/js/styles.mjs b/proxy/js/styles.mjs index bdb3be99..46c49663 100644 --- a/proxy/js/styles.mjs +++ b/proxy/js/styles.mjs @@ -889,6 +889,13 @@ const sources = { tileSize: 256, attribution: '© OpenStreetMap contributors' }, + search: { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [], + }, + }, openrailwaymap_low: { type: 'vector', url: `${origin}/railway_line_low`, @@ -954,6 +961,18 @@ const backgroundMap = { } }; +const searchResults = { + id: 'search', + type: 'circle', + source: 'search', + paint: { + 'circle-radius': 8, + 'circle-color': 'rgba(183, 255, 0, 0.7)', + 'circle-stroke-width': 2, + 'circle-stroke-color': 'black', + } +}; + // TODO remove all [switch, [zoom]] to ensure legend displays only visible features const layers = { standard: [ @@ -1838,6 +1857,7 @@ const layers = { 'text-padding': 10, }, }, + searchResults, ], speed: [ @@ -2011,6 +2031,7 @@ const layers = { 'symbol-spacing': 100, }, }, + searchResults, ], signals: [ @@ -2303,6 +2324,7 @@ const layers = { ], } }, + searchResults, ], electrification: [ @@ -2562,6 +2584,7 @@ const layers = { 'symbol-spacing': 100, }, }, + searchResults, ], gauge: [ @@ -2839,6 +2862,7 @@ const layers = { 'symbol-spacing': 100, }, }, + searchResults, ], }; diff --git a/proxy/js/ui.js b/proxy/js/ui.js index 15b945cd..0ddb074e 100644 --- a/proxy/js/ui.js +++ b/proxy/js/ui.js @@ -13,6 +13,20 @@ const backgroundRasterUrlControl = document.getElementById('backgroundRasterUrl' const legend = document.getElementById('legend') const legendMapContainer = document.getElementById('legend-map') +function registerLastSearchResults(results) { + const data = { + type: 'FeatureCollection', + features: results.map(result => ({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [result.latitude, result.longitude], + }, + })), + }; + map.getSource('search').setData(data); +} + function facilitySearchQuery(type, term) { const encoded = encodeURIComponent(term) @@ -67,20 +81,48 @@ function searchForMilestones(ref, position) { } function showSearchResults(results, renderItem) { - let content = ''; - if (results.length === 0) { - content += `
No results
` - } else { - results.forEach(result => { - content += `${renderItem(result)}` - }) - } - searchResults.innerHTML = content; + registerLastSearchResults(results); + + const bounds = results.length > 0 + ? JSON.stringify(results.reduce( + (bounds, result) => + bounds.extend({lat: result.longitude, lon: result.latitude}), + new maplibregl.LngLatBounds({lat: results[0].longitude, lon: results[0].latitude}) + ).toArray()) + : null; + + searchResults.innerHTML = results.length === 0 + ? ` +
+ + 0 results + +
+ ` + : ` +
+ + ${results.length} results + + +
+
+ ${results.map(result => + ` + ${renderItem(result)} + ` + ).join('')} +
+ `; searchResults.style.display = 'block'; } function hideSearchResults() { searchResults.style.display = 'none'; + registerLastSearchResults([]); } function showSearch() { @@ -118,6 +160,13 @@ function searchMilestones() { hideSearchResults(); } +function viewSearchResultsOnMap(bounds) { + hideSearch(); + map.fitBounds(bounds, { + padding: 40, + }); +} + function showConfiguration() { backgroundSaturationControl.value = configuration.backgroundSaturation ?? defaultConfiguration.backgroundSaturation; backgroundOpacityControl.value = configuration.backgroundOpacity ?? defaultConfiguration.backgroundOpacity;