From 8e7e5a5a53cc0c54357f965573f895b2eb2458c1 Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@suma-ev.de> Date: Thu, 8 Feb 2024 16:58:58 +0100 Subject: [PATCH] fix routefinder --- app/package-lock.json | 11 +- app/package.json | 3 +- app/resources/js/NavigationModule.js | 2 + app/resources/js/Result.js | 36 +-- app/resources/js/Route.js | 62 ++--- app/resources/js/RouteFinder.js | 274 ++++++++++---------- app/resources/js/SearchModule.js | 4 +- app/resources/js/app.js | 15 +- app/resources/js/maps/Maplibre.js | 31 ++- app/resources/js/maps/MetaGerMap.js | 18 ++ app/resources/js/maps/Openlayers.js | 33 ++- app/resources/js/utils.js | 8 +- app/resources/less/map.less | 1 + app/resources/less/openlayers/controls.less | 4 + app/yarn.lock | 7 +- 15 files changed, 281 insertions(+), 228 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index bf124a6..c00c68c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -7,7 +7,8 @@ "dependencies": { "@turf/turf": "^6.5.0", "maplibre-gl": "^4.0.0", - "ol": "^8.2.0" + "ol": "^8.2.0", + "ol-popup": "^5.1.0" }, "devDependencies": { "axios": "^0.21", @@ -8258,6 +8259,14 @@ "url": "https://opencollective.com/openlayers" } }, + "node_modules/ol-popup": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ol-popup/-/ol-popup-5.1.0.tgz", + "integrity": "sha512-vCxtOQQUI1LZoXjwN+Ic/A7BXii/WtDCmdvRhyaEMJSNpbk2k77yNkIvucTEG3+0gS1zKgYCBMaXOKbKBXpY4A==", + "peerDependencies": { + "ol": ">=5.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", diff --git a/app/package.json b/app/package.json index d928b9a..19130e9 100644 --- a/app/package.json +++ b/app/package.json @@ -23,6 +23,7 @@ "dependencies": { "@turf/turf": "^6.5.0", "maplibre-gl": "^4.0.0", - "ol": "^8.2.0" + "ol": "^8.2.0", + "ol-popup": "^5.1.0" } } diff --git a/app/resources/js/NavigationModule.js b/app/resources/js/NavigationModule.js index 2f23a97..e863cd0 100644 --- a/app/resources/js/NavigationModule.js +++ b/app/resources/js/NavigationModule.js @@ -743,3 +743,5 @@ class NavigationModule { map.routeModule.updateInterface(); } } + +export const navigationModule = new NavigationModule(); \ No newline at end of file diff --git a/app/resources/js/Result.js b/app/resources/js/Result.js index c09d569..a83214a 100644 --- a/app/resources/js/Result.js +++ b/app/resources/js/Result.js @@ -2,6 +2,7 @@ import { featureCollection, point } from "@turf/turf"; import { routeModule } from "./RouteFinder"; import { RenderOptions } from "./maps/RenderOptions"; import { map } from "./maps/MetaGerMapModule"; +import { gpsManager } from "./GpsManager"; export class Result { data = null; @@ -68,29 +69,6 @@ export class Result { if (this.data && callback) callback(); } - showMarker() { - if (this.marker && !this.error_message) { - if (this.route && this.route.data) { - let snapped_coordinates = this.route.data.paths[this.route.selected].snapped_waypoints.coordinates[0]; - this.marker.setLngLat(snapped_coordinates).addTo(map.map); - } else { - this.marker = this.marker - .setLngLat([this.data.lon, this.data.lat]) - .addTo(map.map); - } - } - if (this.source == "gps" && this.data && this.data.location) { - map.showUserPosition(turf.point([this.data.location.coords.longitude, this.data.location.coords.latitude]), this.data.location.coords.heading, this.data.location.coords.accuracy); - } else if (this.source == "gps") { - map.hideUserPosition(); - } - } - - removeMarker() { - if (this.marker) this.marker = this.marker.remove(); - if (this.source == "gps" && this.data && this.data.location) map.hideUserPosition(); - } - toString() { switch (this.source) { case "gps": @@ -102,11 +80,6 @@ export class Result { } } - updateRank(rank) { - this.rank = rank; - this.#createDomElement(); - } - async #getFromGPS(callback) { let maximumAge = 500; let lastUpdate = Date.now() - maximumAge; @@ -169,7 +142,7 @@ export class Result { let error_callback = (error) => { console.log(error); - map.gpsManager.clearWatch(this.geolocation_watch_id); + gpsManager.clearWatch(this.geolocation_watch_id); this.geolocation_watch_id = null; if (this.data == null) { switch (error.code) { @@ -197,12 +170,12 @@ export class Result { maximumAge: 60000, }; - map.gpsManager.getCurrentPosition(position => { + gpsManager.getCurrentPosition(position => { position_callback(position); // Try to upgrade to a high accuracy position options.enableHighAccuracy = true; options.maximumAge = 500; - this.geolocation_watch_id = map.gpsManager.watchPosition(position_callback.bind(this), error_callback, options); + this.geolocation_watch_id = gpsManager.watchPosition(position_callback.bind(this), error_callback, options); }, error_callback, options); } @@ -321,6 +294,7 @@ export class Result { el.style.filter = `hue-rotate(${huerotate}deg)`; } + this.dom_element.querySelector(".result-marker").innerHTML = ""; this.dom_element.querySelector(".result-marker").appendChild(el); return el; diff --git a/app/resources/js/Route.js b/app/resources/js/Route.js index 667febc..d9f6881 100644 --- a/app/resources/js/Route.js +++ b/app/resources/js/Route.js @@ -1,4 +1,8 @@ -class Route { +import { LngLat } from "maplibre-gl"; +import { map } from "./maps/MetaGerMapModule"; +import { formatDistance, formatDuration } from "./utils"; + +export class Route { data = null; profile = "guess"; selected = null; @@ -169,11 +173,11 @@ class Route { !["car", "bike", "foot"].includes(this.profile) ) { // Guess a default profile based on the distance between both points - let from_point = new maplibregl.LngLat( + let from_point = new LngLat( waypoint_from.data.lon, waypoint_from.data.lat ); - let to_point = new maplibregl.LngLat( + let to_point = new LngLat( waypoint_to.data.lon, waypoint_to.data.lat ); @@ -353,11 +357,8 @@ class Route { if (window.screen.width < 800) { paddingInline = 50; } - map.map.fitBounds( - [ - [bbox[0], bbox[1]], - [bbox[2], bbox[3]], - ], + map.fitBounds( + bbox, { padding: { top: paddingInline, @@ -390,36 +391,37 @@ class Route { } else { time_difference += " kürzer"; } - let new_popup = new maplibregl.Popup({ - closeButton: false, - closeOnClick: false, - }) - .setLngLat(path.intersection_point) - .setHTML( - `<div class="alt-route-popup"><label>Alternative Route</label>${description}<div>${time_difference}</div></div>` - ); - - new_popup.path_index = index; + let popup_dom_element = document.createElement("div"); + popup_dom_element.classList.add("alt-route-popup"); + let popup_label = document.createElement("label"); + popup_label.textContent = "Alternative Route"; + popup_dom_element.appendChild(popup_label); + let popup_description = document.createElement("label"); + popup_description.textContent = description; + popup_dom_element.appendChild(popup_description); + let popup_time = document.createElement("div"); + popup_time.textContent = time_difference; + popup_dom_element.appendChild(popup_time); + let popup = map.createPopup(popup_dom_element, path.intersection_point); + + popup_dom_element.addEventListener("click", e => { + this.selected = index; + this.#createDomNode(); + this.#createPopups(); + this.dom_node.dispatchEvent(new Event("change")); + }); - this.popups.push(new_popup); + this.popups.push(popup); } } showPopups() { - for (const [index, popup] of this.popups.entries()) { - this.popups[index] = popup.addTo(map.map); - this.popups[index].getElement().onclick = () => { - this.selected = popup.path_index; - this.#createDomNode(); - this.#createPopups(); - this.dom_node.dispatchEvent(new Event("change")); - }; - } + this.#createPopups(); } hidePopups() { for (const [index, popup] of this.popups.entries()) { - this.popups[index] = popup.remove(); + map.removePopup(popup); } } @@ -465,4 +467,4 @@ class Route { path.intersection_point = target_point.geometry.coordinates; } } -} +} \ No newline at end of file diff --git a/app/resources/js/RouteFinder.js b/app/resources/js/RouteFinder.js index 926e031..180faa8 100644 --- a/app/resources/js/RouteFinder.js +++ b/app/resources/js/RouteFinder.js @@ -1,3 +1,14 @@ +import { feature, featureCollection, lineString, point } from "@turf/turf"; +import { gpsManager } from "./GpsManager"; +import { navigationModule } from "./NavigationModule"; +import { Result } from "./Result"; +import { switchModule } from "./app"; +import { map } from "./maps/MetaGerMapModule"; +import { Route } from "./Route"; +import { LngLatBounds } from "maplibre-gl"; +import { formatDistance, formatDuration } from "./utils"; +import { RenderOptions } from "./maps/RenderOptions"; + class RouteFinder { htmlmodule = document.getElementById("route-finder-addon"); searchinput = this.htmlmodule.querySelector("input#search-waypoint-input"); @@ -12,25 +23,23 @@ class RouteFinder { waypoints = []; routes = []; - mapsourcename = "routefinder"; // Source where all Features of the Routefinder are displayed in - linesourcename = `${this.mapsourcename}-line`; - fillsourcename = `${this.mapsourcename}-fill`; - inactivesourcename = `${this.mapsourcename}-inactive-line`; + map_layer_id = "routefinder"; // Source where all Features of the Routefinder are displayed in + map_layer_id_route_active = `${this.map_layer_id}_active`; + map_layer_id_route_inactive = `${this.map_layer_id}_inactive`; + markers = []; constructor() { - this.addMapLayers(); - this.searchinput.onkeyup = (e) => { let query = this.htmlmodule.querySelector( "input#search-waypoint-input" ).value; - map.switchModule("search", { + switchModule("search", { query: query, search: true, stateupdates: false, exit_callback: () => { - map.switchModule("route-finding"); + switchModule("route-finding"); }, }); }; @@ -60,7 +69,7 @@ class RouteFinder { while (this.waypoints.length > 0) { this.removeWaypoint(0); } - map.switchModule("search"); + switchModule("search"); }; } @@ -74,11 +83,11 @@ class RouteFinder { this.htmlmodule.classList.add("active"); this.searchinput.value = ""; - map.map.off("moveend", this.moveend_callback); - map.map.on("moveend", this.moveend_callback); + map.off("moveend", this.moveend_callback); + map.on("moveend", this.moveend_callback); - map.map.off("click", this.click_callback); - map.map.on("click", this.click_callback); + map.off("click", this.click_callback); + map.on("click", this.click_callback); if (vehicle) this.vehicle = vehicle; this.navigation_active = navigation_active; @@ -90,7 +99,7 @@ class RouteFinder { } } // Add GPS options - await map.gpsManager.isGPSAvailable().then((gps_available) => { + await gpsManager.isGPSAvailable().then((gps_available) => { this.htmlmodule.classList.remove("navigation-enabled"); if ( gps_available && @@ -140,12 +149,12 @@ class RouteFinder { } if (mapposition) { - mapposition = new maplibregl.LngLatBounds( + mapposition = new LngLatBounds( mapposition._sw, mapposition._ne ); - let pos = map.map.cameraForBounds(mapposition); - map.map.easeTo( + let pos = map.cameraForBounds(mapposition); + map.easeTo( { center: pos.center, zoom: pos.zoom, @@ -159,10 +168,9 @@ class RouteFinder { removeWaypoint(index) { if (this.waypoints.length < index - 1) return; - this.waypoints[index].removeMarker(); if (this.waypoints[index].geolocation_watch_id) { - map.gpsManager.clearWatch(this.waypoints[index].geolocation_watch_id); + gpsManager.clearWatch(this.waypoints[index].geolocation_watch_id); } if (this.waypoints[index].route) this.waypoints[index].route.hidePopups(); if (index > 0) { @@ -170,7 +178,6 @@ class RouteFinder { this.waypoints[index - 1].route = null; } this.waypoints.splice(index, 1); - this.updateWaypointRanks(); } addWaypoint(waypoint, first = false) { @@ -180,10 +187,9 @@ class RouteFinder { } else { this.waypoints.push(waypoint); } - this.updateWaypointRanks(); this.updateInterface(); // Register Event handler for gps updates - if (waypoint.rank == 0 && waypoint.source == "gps") { + if (first && waypoint.source == "gps") { waypoint.dom_element.addEventListener("update", this.gpsUpdate.bind(this)); waypoint.dom_element.addEventListener("navigation_available", e => { this.htmlmodule.classList.add("navigation-enabled"); @@ -200,15 +206,6 @@ class RouteFinder { // Check if the route needs to be recalculated } - // Go through all waypoints and validate that the stored rank is correct - updateWaypointRanks() { - for (const [index, waypoint] of this.waypoints.entries()) { - if (waypoint.rank != index) { - waypoint.updateRank(index); - } - } - } - async calculateRoutes() { let routes_calculated = 0; for (const [index, waypoint] of this.waypoints.entries()) { @@ -324,7 +321,6 @@ class RouteFinder { for (const [index, waypoint] of this.waypoints.entries()) { if (waypoint.error_code == Result.ERROR_GEOLOCATION_PERMISSION_DENIED || waypoint.error_code == Result.ERROR_GEOLOCATION_TIMEOUT) { // User denied Geolocation Access. Remove GPS waypoint - waypoint.removeMarker(); this.waypoints.splice(index, 1); this.updateInterface(); this.updateState(); @@ -337,7 +333,6 @@ class RouteFinder { e ) => { this.removeWaypoint(index); - this.updateWaypointRanks(); this.calculateRoutes() .then(() => this.updateInterface()) .then(() => this.fitRoutesOnMap()) @@ -362,7 +357,6 @@ class RouteFinder { this.waypoints[index], ]; } - this.updateWaypointRanks(); this.calculateRoutes() .then(() => this.updateInterface()) .then(() => this.fitRoutesOnMap()) @@ -390,70 +384,132 @@ class RouteFinder { */ updateMap() { // Add the markers to the map - for (const [index, waypoint] of this.waypoints.entries()) { - if (!this.navigation_active || index > 0) waypoint.showMarker(); - waypoint.showGeoJson(this.linesourcename, this.fillsourcename); + let geojson_features = []; + let geojson_route_active_features = []; + let geojson_route_inactive_features = []; - if (waypoint.route) { - waypoint.route.showPopups(); - waypoint.route.showGeoJson( - this.linesourcename, - this.inactivesourcename + for (const [index, waypoint] of this.waypoints.entries()) { + if (!waypoint.data) continue; + let show_marker = !this.navigation_active || index > 0; + let chr = String.fromCharCode(65 + index); + let new_feature = waypoint.data.geojson ? feature(waypoint.data.geojson, { text: chr }) : null; + + if (new_feature == null || !["Point", "MultiPoint"].includes(new_feature.geometry.type)) { + // Add A marker + let new_point = point( + [parseFloat(waypoint.data.lon), parseFloat(waypoint.data.lat)], + { text: chr } ); + if (new_feature != null) + geojson_features.push(new_feature); + if (show_marker) + geojson_features.push(new_point); + } else if (show_marker && new_feature != null) { + geojson_features.push(new_feature); + } + waypoint.addMarkerToDom(index); + if (waypoint.route && waypoint.route.data) { + for (const [index, path] of waypoint.route.data.paths.entries()) { + let feature = lineString(path.points.coordinates); + if (waypoint.route.selected == index) { + geojson_route_active_features.push(feature); + } else { + geojson_route_inactive_features.push(feature); + } + } } } + // Draw the waypoints + map.drawFeatures(this.map_layer_id, featureCollection(geojson_features), new RenderOptions( + { + point: { + "text-translate": [0, -19], + "text-color": "#ffffff", + }, + line: { + "line-color": "rgb(255,127,0)", + "line-width": 5, + }, + fill: { + "fill-color": "#593008", + "fill-outline-color": "rgb(255,127,0)", + "fill-opacity": 0.3, + }, + }, + { + point: { + "icon-image": "result-marker", + "icon-size": 1, + "icon-anchor": "bottom", + "icon-ignore-placement": true, + "text-field": ["get", "text"], + "text-line-height": 1, + "text-padding": 0, + "text-anchor": "bottom", + "text-justify": "center", + "text-ignore-placement": true, + }, + }) + ); + // Draw Inactive Routes + map.drawFeatures(this.map_layer_id_route_inactive, featureCollection(geojson_route_inactive_features), new RenderOptions({ + line: { + "line-color": "rgb(177, 180, 185)", + "line-width": 5, + "line-opacity": 0.6, + }, + }, {})); + map.drawFeatures(this.map_layer_id_route_active, featureCollection(geojson_route_active_features), new RenderOptions({ + line: { + "line-color": "#2e8bc0", + "line-width": 5, + "line-opacity": 1, + }, + }, {})); } - clearMap() { - for (const waypoint of this.waypoints) { - waypoint.removeMarker(); - if (waypoint.route) waypoint.route.hidePopups(); - } - let line_data = map.map.getSource(this.linesourcename)._data; - line_data.features = []; - map.map.getSource(this.linesourcename).setData(line_data); - let inactive_line_data = map.map.getSource(this.inactivesourcename)._data; - inactive_line_data.features = []; - map.map.getSource(this.inactivesourcename).setData(inactive_line_data); - - let fill_data = map.map.getSource(this.fillsourcename)._data; - fill_data.features = []; - map.map.getSource(this.fillsourcename).setData(fill_data); + clearMap() { + map.removeFeatures(this.map_layer_id); + map.removeFeatures(this.map_layer_id_route_active); + map.removeFeatures(this.map_layer_id_route_inactive); } startNavigation() { if (this.waypoints[0].source != "gps" || !this.waypoints[0].route) return; - map.gpsManager.clearWatch(this.waypoints[0].geolocation_watch_id); + gpsManager.clearWatch(this.waypoints[0].geolocation_watch_id); this.waypoints[0].geolocation_watch_id = null; this.navigation_active = true; this.htmlmodule.classList.remove("active"); // Route Overview is not shown while navigating - map.map.off("click", this.click_callback); // No reverse searches whilst navigating - map.map.off("moveend", this.moveend_callback); // No state updates whilst navigating + map.off("click", this.click_callback); // No reverse searches whilst navigating + map.off("moveend", this.moveend_callback); // No state updates whilst navigating this.updateState(); // But a final state update - map.navigationModule.enable(); + navigationModule.enable(); this.updateInterface(); } stopNavigation() { this.navigation_active = false; this.htmlmodule.classList.add("active"); // Route Overview is not shown while navigating - map.map.on("click", this.click_callback); // No reverse searches whilst navigating - map.map.on("moveend", this.moveend_callback); // No state updates whilst navigating + map.on("click", this.click_callback); // No reverse searches whilst navigating + map.on("moveend", this.moveend_callback); // No state updates whilst navigating this.updateState(); // But a final state update } handleMapClick(event) { + if (event.coordinate) { + event.lngLat = new LngLat(event.coordinate[0], event.coordinate[1]); + } let search_params = { reverse: { - lngLat: event.lngLat, - zoom: Math.round(map.map.getZoom()), + center: event.lngLat, + zoom: Math.round(map.getZoom()), }, stateupdates: false, search: true, exit_callback: () => { - map.switchModule("route-finding"); + switchModule("route-finding"); }, }; - map.switchModule("search", search_params); + switchModule("search", search_params); } fitRoutesOnMap() { @@ -468,77 +524,7 @@ class RouteFinder { else bbox_collection = turf.featureCollection([bbox]); } let bbox = turf.bbox(bbox_collection); - map.fitBbox([ - [bbox[0], bbox[1]], - [bbox[2], bbox[3]], - ]); - } - - addMapLayers() { - // Variables for map features - let lineSource = { - type: "geojson", - data: { - type: "FeatureCollection", - features: [], - }, - }; - let lineLayer = { - id: `${this.linesourcename}`, - type: "line", - source: `${this.linesourcename}`, - paint: { - "line-color": "#2e8bc0", - "line-width": 5, - "line-opacity": 1, - }, - }; - - let inactiveLineSource = { - type: "geojson", - data: { - type: "FeatureCollection", - features: [], - }, - }; - let inactiveLineLayer = { - id: `${this.inactivesourcename}`, - type: "line", - source: `${this.inactivesourcename}`, - paint: { - "line-color": "rgb(177, 180, 185)", - "line-width": 5, - "line-opacity": 0.6, - }, - }; - - let fillSource = { - type: "geojson", - data: { - type: "FeatureCollection", - features: [], - }, - }; - let fillLayer = { - id: `${this.fillsourcename}`, - type: "fill", - source: `${this.fillsourcename}`, - paint: { - "fill-color": "rgb(255,127,0)", - "fill-outline-color": "rgb(255,127,0)", - "fill-opacity": 0.3, - }, - }; - - let addLayers = () => { - map.map.addSource(this.inactivesourcename, inactiveLineSource); - map.map.addLayer(inactiveLineLayer); - map.map.addSource(this.linesourcename, lineSource); - map.map.addLayer(lineLayer); - - map.map.addSource(this.fillsourcename, fillSource); - map.map.addLayer(fillLayer); - }; + map.fitBounds(new LngLatBounds(bbox)); } clearInterface() { @@ -558,8 +544,8 @@ class RouteFinder { this.clearInterface(); - map.map.off("moveend", this.moveend_callback); - map.map.off("click", this.click_callback); + map.off("moveend", this.moveend_callback); + map.off("click", this.click_callback); } updateState(event) { @@ -580,7 +566,7 @@ class RouteFinder { } url += waypoint_strings.join(";"); - let current_pos = map.map.getBounds(); + let current_pos = map.getBounds(); // Check if current_pos changed enough if ( @@ -589,12 +575,12 @@ class RouteFinder { window.history.state.module == "route-finder" && window.history.state.navigation_active == this.navigation_active ) { - let old_pos = new maplibregl.LngLatBounds( + let old_pos = new LngLatBounds( window.history.state.mapposition._sw, window.history.state.mapposition._ne ); - let old_pos_camera = map.map.cameraForBounds(old_pos); - let new_pos_camera = map.map.cameraForBounds(current_pos); + let old_pos_camera = map.cameraForBounds(old_pos); + let new_pos_camera = map.cameraForBounds(current_pos); if ( old_pos_camera.center.distanceTo(new_pos_camera.center) <= 250 && Math.abs(old_pos_camera.zoom - new_pos_camera.zoom) < 0.5 diff --git a/app/resources/js/SearchModule.js b/app/resources/js/SearchModule.js index af18ef6..67d8d04 100644 --- a/app/resources/js/SearchModule.js +++ b/app/resources/js/SearchModule.js @@ -3,6 +3,7 @@ import { map } from "./maps/MetaGerMapModule"; import { Result } from "./Result"; import { RenderOptions } from "./maps/RenderOptions"; import { feature, featureCollection, point } from "@turf/turf"; +import { switchModule } from "./app"; class SearchModule { htmlmodule = document.getElementById("search-addon"); @@ -385,7 +386,6 @@ class SearchModule { this.searchresultscontainer.appendChild(label); } for (const [index, result] of this.results.entries()) { - result.updateRank(index); this.searchresultscontainer.appendChild(result.dom_element); // Add Click listener result.dom_element.onclick = (e) => { @@ -418,7 +418,7 @@ class SearchModule { this.updateMap(); let result_data = result.data; result_data.rank = 0; - map.switchModule("route-finding", { + switchModule("route-finding", { waypoints: [new Result(result.data, false, result.source)], }); }; diff --git a/app/resources/js/app.js b/app/resources/js/app.js index 64004d4..60e48ce 100644 --- a/app/resources/js/app.js +++ b/app/resources/js/app.js @@ -4,6 +4,8 @@ import { gpsManager } from "./GpsManager"; import { searchModule } from "./SearchModule"; import { LngLatBounds } from "maplibre-gl"; import { fakeGpsModule } from "./FakeGPSModule"; +import { routeModule } from "./RouteFinder"; +import { navigationModule } from "./NavigationModule"; let module = null; // CUrrent module @@ -218,9 +220,9 @@ export function switchModule(name, args) { } break; case "route-finding": - this.module = this.routeModule; + module = routeModule; if (typeof args == "object") { - this.module.enable( + module.enable( args.vehicle, args.waypoints, args.mapposition, @@ -228,15 +230,12 @@ export function switchModule(name, args) { args.navigation_active ); } else { - this.module.enable(); + module.enable(); } break; - case "offline-karten": - this.module = new OfflineModule(this); - break; case "navigation": - this.module = this.navigationModule; - this.module.enable(); + module = navigationModule; + module.enable(); break; case "fakegps": module = fakeGpsModule; diff --git a/app/resources/js/maps/Maplibre.js b/app/resources/js/maps/Maplibre.js index 9ad74da..1893680 100644 --- a/app/resources/js/maps/Maplibre.js +++ b/app/resources/js/maps/Maplibre.js @@ -1,7 +1,7 @@ import { config } from "../Config"; import { GeolocationControl } from "../maplibre/GeolocationControl"; import { MetaGerMap } from "./MetaGerMap"; -import { AttributionControl, LngLat, Map, NavigationControl, ScaleControl, setRTLTextPlugin } from "maplibre-gl"; +import { AttributionControl, LngLat, Map, NavigationControl, Popup, ScaleControl, setRTLTextPlugin } from "maplibre-gl"; import { RenderOptions } from "./RenderOptions"; import { circle, featureCollection, point } from "@turf/turf"; import { NavbarControl } from "../maplibre/NavbarControl"; @@ -196,6 +196,10 @@ export class Maplibre extends MetaGerMap { } } + if (layer_id == "routefinder_active") { + console.log(lineStringLayer, source); + } + // Points will be drawn as Markers // The feature can have a property `text` to change the label on the marker let pointLayer = this._map.getLayer(`${layer_id}_point`); @@ -319,4 +323,29 @@ export class Maplibre extends MetaGerMap { let layer_id = "userposition"; this.removeFeatures(layer_id); } + + /** + * + * @param {HTMLDivElement} dom_element + * @param {LngLat} position + */ + createPopup(dom_element, position) { + let new_popup = new Popup({ + closeButton: false, + closeOnClick: false, + }); + new_popup.setDOMContent(dom_element); + new_popup.setLngLat(position); + new_popup.addTo(this._map); + return new_popup; + } + + /** + * Removes a popup previously created by createPopup + * the popup argument is the result of the createPopup Method + * @param {Popup} popup + */ + removePopup(popup) { + popup.remove(); + } } diff --git a/app/resources/js/maps/MetaGerMap.js b/app/resources/js/maps/MetaGerMap.js index f31420f..9aa5706 100644 --- a/app/resources/js/maps/MetaGerMap.js +++ b/app/resources/js/maps/MetaGerMap.js @@ -170,4 +170,22 @@ export class MetaGerMap { hideUserPosition() { throw new Error("Abstract function showUserPosition must be implemented"); } + + /** + * + * @param {HTMLDivElement} dom_element + * @param {LngLat} position + */ + createPopup(dom_element, position) { + throw new Error("Abstract function createPopup must be implemented") + } + + /** + * Removes a popup previously created by createPopup + * the popup argument is the result of the createPopup Method + * @param {*} popup + */ + removePopup(popup) { + throw new Error("Abstract function removePopup must be implemented") + } } diff --git a/app/resources/js/maps/Openlayers.js b/app/resources/js/maps/Openlayers.js index 788342b..db2a102 100644 --- a/app/resources/js/maps/Openlayers.js +++ b/app/resources/js/maps/Openlayers.js @@ -10,9 +10,8 @@ import { featureCollection, point, } from "@turf/turf"; -import * as Polygon from "ol/geom/Polygon"; import XYZ from "ol/source/XYZ"; -import { Projection, useGeographic } from "ol/proj"; +import { fromLonLat, useGeographic } from "ol/proj"; import VectorLayer from "ol/layer/Vector"; import { LngLat, LngLatBounds } from "maplibre-gl"; import * as Color from "ol/color"; @@ -25,7 +24,6 @@ import Stroke from "ol/style/Stroke"; import Icon from "ol/style/Icon"; import Text from "ol/style/Text"; import Zoom from "ol/control/Zoom"; -import Rotate from "ol/control/Rotate"; import Attribution from "ol/control/Attribution"; import { NavigationControl } from "../openlayers/NavigationControl"; import ScaleLine from "ol/control/ScaleLine"; @@ -34,9 +32,8 @@ import DragPan from "ol/interaction/DragPan"; import MouseWheelZoom from "ol/interaction/MouseWheelZoom"; import KeyboardPan from "ol/interaction/KeyboardPan"; import KeyboardZoom from "ol/interaction/KeyboardZoom"; -import dragRotate from "ol/interaction/DragRotate"; import DragRotate from "ol/interaction/DragRotate"; -import Source from "ol/source/Source"; +import Popup from "ol-popup"; export class Openlayers extends MetaGerMap { /** @type {Map} */ @@ -514,4 +511,30 @@ export class Openlayers extends MetaGerMap { hideUserPosition() { this.removeFeatures("userposition"); } + + + /** + * + * @param {HTMLDivElement} dom_element + * @param {LngLat} position + */ + createPopup(dom_element, position) { + dom_element.classList.add("ol-popup"); + + let overlay = new Popup(); + this._map.addOverlay(overlay); + overlay.show(fromLonLat([position.lng, position.lat], "EPSG:3857"), dom_element); + + + return overlay; + } + + /** + * Removes a popup previously created by createPopup + * the popup argument is the result of the createPopup Method + * @param {Popup} popup + */ + removePopup(popup) { + this._map.removeOverlay(popup); + } } diff --git a/app/resources/js/utils.js b/app/resources/js/utils.js index d98c3b7..d0c1f7c 100644 --- a/app/resources/js/utils.js +++ b/app/resources/js/utils.js @@ -6,7 +6,7 @@ * @param {int} duration_ms * @returns */ -function formatDuration(duration_ms) { +export function formatDuration(duration_ms) { let negative = duration_ms < 0 ? true : false; duration_ms = Math.abs(duration_ms); let duration_minutes = Math.ceil(duration_ms / 1000 / 60); @@ -27,7 +27,7 @@ function formatDuration(duration_ms) { * @param {int} distance_meters * @returns */ -function formatDistance(distance_meters) { +export function formatDistance(distance_meters) { let distance_kilometers = Math.floor(distance_meters / 1000); distance_meters -= distance_kilometers * 1000; @@ -44,7 +44,7 @@ function formatDistance(distance_meters) { } } -function getBboxFromPoints(points) { +export function getBboxFromPoints(points) { let bbox = [ [null, null], [null, null], @@ -65,7 +65,7 @@ function getBboxFromPoints(points) { * @param {*} bearing_a * @param {*} bearing_b */ -function compareBearings(bearing_a, bearing_b) { +export function compareBearings(bearing_a, bearing_b) { // Convert all Bearings to radians bearing_a = turf.degreesToRadians(turf.bearingToAzimuth(bearing_a)); bearing_b = turf.degreesToRadians(turf.bearingToAzimuth(bearing_b)); diff --git a/app/resources/less/map.less b/app/resources/less/map.less index 89c27f8..47a9f35 100644 --- a/app/resources/less/map.less +++ b/app/resources/less/map.less @@ -1,3 +1,4 @@ @import "../../node_modules/maplibre-gl/dist/maplibre-gl.css"; @import "../../node_modules/ol/ol.css"; +@import "../../node_modules/ol-popup/src/ol-popup.css"; @import "./openlayers/controls.less"; \ No newline at end of file diff --git a/app/resources/less/openlayers/controls.less b/app/resources/less/openlayers/controls.less index d0d6649..088e554 100644 --- a/app/resources/less/openlayers/controls.less +++ b/app/resources/less/openlayers/controls.less @@ -149,4 +149,8 @@ &.control-navbar { font-size: 3rem; } +} + +.ol-popup { + position: absolute; } \ No newline at end of file diff --git a/app/yarn.lock b/app/yarn.lock index c1fac38..ec0949f 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -5393,7 +5393,12 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -ol@^8.2.0: +ol-popup@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/ol-popup/-/ol-popup-5.1.0.tgz" + integrity sha512-vCxtOQQUI1LZoXjwN+Ic/A7BXii/WtDCmdvRhyaEMJSNpbk2k77yNkIvucTEG3+0gS1zKgYCBMaXOKbKBXpY4A== + +ol@^8.2.0, ol@>=5.0.0: version "8.2.0" resolved "https://registry.npmjs.org/ol/-/ol-8.2.0.tgz" integrity sha512-/m1ddd7Jsp4Kbg+l7+ozR5aKHAZNQOBAoNZ5pM9Jvh4Etkf0WGkXr9qXd7PnhmwiC1Hnc2Toz9XjCzBBvexfXw== -- GitLab