From 09e7f53252a00ace9a952551a75080602b623b44 Mon Sep 17 00:00:00 2001 From: "amaury.zarzelli" Date: Tue, 6 Aug 2024 15:30:44 +0200 Subject: [PATCH 1/3] feat(location): better location btn UX + add pitch on navigation --- src/js/map-buttons-listeners.js | 15 +++--- src/js/map-listeners.js | 2 +- src/js/services/location.js | 90 ++++++++++++++++++++++++++++----- 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/src/js/map-buttons-listeners.js b/src/js/map-buttons-listeners.js index d6f6a24b..fe74767b 100644 --- a/src/js/map-buttons-listeners.js +++ b/src/js/map-buttons-listeners.js @@ -33,14 +33,15 @@ const addListeners = () => { // Rotation de la boussole DOM.$compassBtn.addEventListener("click", () => { const map = Globals.map; - if (Location.isTrackingActive()){ - // De tracking a simple suivi de position - Location.disableTracking(); - } - if (map.getBearing() === 0) { - DOM.$compassBtn.classList.add("d-none"); + if (Location.isNavigationActive()){ + // De nivigation a simple suivi de position + Location.disableNavigation(0); + } else { + if (map.getBearing() === 0) { + DOM.$compassBtn.classList.add("d-none"); + } + map.rotateTo(0); } - map.rotateTo(0); }); // Bouton Comparaison de carte diff --git a/src/js/map-listeners.js b/src/js/map-listeners.js index be69f90b..d1902981 100644 --- a/src/js/map-listeners.js +++ b/src/js/map-listeners.js @@ -35,7 +35,7 @@ const addListeners = () => { }); map.on("dragend", () => { if (Location.isTrackingActive() && lastMapCenter){ - if (lastMapCenter.distanceTo(map.getCenter()) > 100) { + if (lastMapCenter.distanceTo(map.getCenter()) > 50) { // De tracking a simple suivi de position Location.disableTracking(); } diff --git a/src/js/services/location.js b/src/js/services/location.js index 0c4f2af4..c560f578 100644 --- a/src/js/services/location.js +++ b/src/js/services/location.js @@ -75,6 +75,10 @@ let location_active = false; // Suivi de la carte let tracking_active = false; + +// Suivi de la carte avec boussole +let navigation_active = false; + let watch_id; let currentPosition = null; @@ -186,6 +190,7 @@ const moveTo = (coords, zoom = Globals.map.getZoom(), panTo = true, gps = true) Globals.myPositionMarker = new maplibregl.Marker({ element: (gps) ? positionIcon : Globals.searchResultIcon, anchor: (gps) ? "center" : "bottom", + pitchAlignment: "map", }) .setLngLat([coords.lon, coords.lat]) .addTo(Globals.map); @@ -216,11 +221,26 @@ const moveTo = (coords, zoom = Globals.map.getZoom(), panTo = true, gps = true) if (panTo) { if (tracking_active) { + let bearing = Globals.map.getBearing(); + let pitch = 0; + if (navigation_active) { + bearing = -mapBearing; + pitch = 45; + } isMapPanning = true; - Globals.map.flyTo({center: [coords.lon, coords.lat], zoom: zoom, bearing: -mapBearing, duration: 500}); + Globals.map.flyTo({ + center: [coords.lon, coords.lat], + zoom: zoom, + bearing: bearing, + pitch: pitch, + duration: 500}); Globals.map.once("moveend", () => {isMapPanning = false;}); } else { - Globals.map.flyTo({center: [coords.lon, coords.lat], zoom: zoom}); + Globals.map.flyTo({ + center: [coords.lon, coords.lat], + zoom: zoom, + pitch: 0 + }); } } }; @@ -249,8 +269,8 @@ const watchPositionCallback = (position) => { currentPosition = position; localStorage.setItem("lastKnownPosition", JSON.stringify({lat: currentPosition.coords.latitude, lng: currentPosition.coords.longitude})); var zoom = Globals.map.getZoom(); - if (firstLocation || tracking_active) { - zoom = Math.max(Globals.map.getZoom(), 16); + if (firstLocation) { + zoom = Math.max(Globals.map.getZoom(), 16.5); } moveTo({ lat: position.coords.latitude, @@ -360,6 +380,7 @@ const enablePosition = async() => { return; } location_active = true; + tracking_active = true; if (!currentPosition && localStorage.getItem("lastKnownPosition")) { const lastPosition = JSON.parse(localStorage.getItem("lastKnownPosition")); moveTo({ @@ -382,11 +403,26 @@ const locationOnOff = async () => { if (currentPosition === null) { return; } - DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFollowImg + "\")"; + DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFixeImg + "\")"; tracking_active = true; Globals.map.setCenter([currentPosition.coords.longitude, currentPosition.coords.latitude]); - Globals.map.setZoom(16); - Globals.map.setBearing(-mapBearing); + Toast.show({ + text: "Suivi de position activé", + duration: "short", + position: "bottom" + }); + } else if (!navigation_active) { + if (currentPosition === null) { + return; + } + DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFollowImg + "\")"; + navigation_active = true; + Globals.map.setMaxPitch(45); + Globals.map.flyTo({ + center: [currentPosition.coords.longitude, currentPosition.coords.latitude], + bearing: -mapBearing, + pitch: 45, + }); DOM.$compassBtn.classList.remove("d-none"); DOM.$compassBtn.style.transform = "rotate(" + mapBearing + "deg)"; Toast.show({ @@ -395,14 +431,16 @@ const locationOnOff = async () => { position: "bottom" }); } else { + Globals.map.flyTo({ + pitch: 0, + duration: 500, + }); + setTimeout( () => {Globals.map.setMaxPitch(0);}, 500); DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; - Geolocation.clearWatch({id: watch_id}); - clean(); - currentPosition = null; - location_active = false; tracking_active = false; + navigation_active = false; Toast.show({ - text: "Navigation et suivi de position désactivés", + text: "Navigation désactivée", duration: "short", position: "bottom" }); @@ -434,7 +472,7 @@ const getOrientation = async (event) => { tempMapBearing -= 90; } mapBearing = tempMapBearing; - if (tracking_active) { + if (navigation_active) { if (!isMapPanning) { Globals.map.easeTo({bearing: -mapBearing, duration: 100}); } @@ -491,8 +529,26 @@ const getLocation = async () => { }; const disableTracking = () => { - DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFixeImg + "\")"; + DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; + tracking_active = false; + navigation_active = false; + Toast.show({ + text: "Suivi de position activé", + duration: "short", + position: "bottom" + }); +}; + +const disableNavigation = (bearing = Globals.map.getBearing()) => { + DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; + navigation_active = false; tracking_active = false; + Globals.map.flyTo({ + pitch: 0, + bearing: bearing, + duration: 500, + }); + setTimeout( () => {Globals.map.setMaxPitch(0);}, 500); Toast.show({ text: "Suivi de position activé", duration: "short", @@ -577,6 +633,10 @@ const isTrackingActive = () => { return tracking_active; }; +const isNavigationActive = () => { + return navigation_active; +}; + const getCurrentPosition = () => { return currentPosition; }; @@ -586,10 +646,12 @@ export default { getCurrentPosition, isLocationActive, isTrackingActive, + isNavigationActive, moveTo, enablePosition, locationOnOff, getOrientation, getLocation, disableTracking, + disableNavigation, }; From 996513b331f758d611d7f739788f44459b20aedd Mon Sep 17 00:00:00 2001 From: azarz Date: Tue, 27 Aug 2024 12:07:44 +0200 Subject: [PATCH 2/3] feat(location): zoom and rotate around center when tracking active --- src/js/map-listeners.js | 16 +++------ src/js/services/location.js | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/js/map-listeners.js b/src/js/map-listeners.js index d1902981..424a0a6d 100644 --- a/src/js/map-listeners.js +++ b/src/js/map-listeners.js @@ -26,22 +26,14 @@ const addListeners = () => { } }); - let lastMapCenter = null; // Désactivation du tracking au déplacement non programmatique de la carte map.on("dragstart", () => { - if (Location.isTrackingActive()){ - lastMapCenter = map.getCenter(); + if (Location.isNavigationActive()){ + Location.disableNavigation(0); + } else if (Location.isTrackingActive()){ + Location.disableTracking(); } }); - map.on("dragend", () => { - if (Location.isTrackingActive() && lastMapCenter){ - if (lastMapCenter.distanceTo(map.getCenter()) > 50) { - // De tracking a simple suivi de position - Location.disableTracking(); - } - } - lastMapCenter = null; - }); // l'event contextmenu n'est pas enclenché par clic long sur la carte... https://github.com/maplibre/maplibre-gl-js/issues/373 // map.on("contextmenu", ...) serait mieux diff --git a/src/js/services/location.js b/src/js/services/location.js index c560f578..b6708625 100644 --- a/src/js/services/location.js +++ b/src/js/services/location.js @@ -101,6 +101,13 @@ let firstLocation; // Est-ce que le marqueur de "Ma Position" a un écouteur d'évènement ? let hasEventListener = false; +// Varaibles pour la gestion d'évènements touch et zoom spécifiques à la localisation activée +let startDist = 0; +let startZoom = 0; +let startBearing =0; +let startAngle = 0; +let rotationEnabled = false; + /** * Interface pour les evenements * @example @@ -406,6 +413,9 @@ const locationOnOff = async () => { DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFixeImg + "\")"; tracking_active = true; Globals.map.setCenter([currentPosition.coords.longitude, currentPosition.coords.latitude]); + Globals.map.touchZoomRotate.disable(); + Globals.map.getCanvasContainer().addEventListener("touchstart", locationOnTouchStartHandler); + Globals.map.getCanvasContainer().addEventListener("touchmove", locationOnTouchMoveHandler); Toast.show({ text: "Suivi de position activé", duration: "short", @@ -439,6 +449,9 @@ const locationOnOff = async () => { DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; tracking_active = false; navigation_active = false; + Globals.map.touchZoomRotate.enable(); + Globals.map.getCanvasContainer().removeEventListener("touchstart", locationOnTouchStartHandler); + Globals.map.getCanvasContainer().removeEventListener("touchmove", locationOnTouchMoveHandler); Toast.show({ text: "Navigation désactivée", duration: "short", @@ -532,6 +545,9 @@ const disableTracking = () => { DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; tracking_active = false; navigation_active = false; + Globals.map.touchZoomRotate.enable(); + Globals.map.getCanvasContainer().removeEventListener("touchstart", locationOnTouchStartHandler); + Globals.map.getCanvasContainer().removeEventListener("touchmove", locationOnTouchMoveHandler); Toast.show({ text: "Suivi de position activé", duration: "short", @@ -548,6 +564,9 @@ const disableNavigation = (bearing = Globals.map.getBearing()) => { bearing: bearing, duration: 500, }); + Globals.map.touchZoomRotate.enable(); + Globals.map.getCanvasContainer().removeEventListener("touchstart", locationOnTouchStartHandler); + Globals.map.getCanvasContainer().removeEventListener("touchmove", locationOnTouchMoveHandler); setTimeout( () => {Globals.map.setMaxPitch(0);}, 500); Toast.show({ text: "Suivi de position activé", @@ -641,6 +660,55 @@ const getCurrentPosition = () => { return currentPosition; }; +// Event handlers for rotation and zoom when tracking active +const locationOnTouchStartHandler = (e) => { + if (e.touches.length === 2) { + startDist = getTouchDistance(e.touches); + startZoom = Globals.map.getZoom(); + startBearing = Globals.map.getBearing(); + startAngle = getTouchAngle(e.touches); + rotationEnabled = false; + } +}; + +const locationOnTouchMoveHandler = (e) => { + if (e.touches.length === 2) { + e.preventDefault(); + + const currentDist = getTouchDistance(e.touches); + const scale = currentDist / startDist; + const newZoom = startZoom + Math.log2(scale); + + const currentAngle = getTouchAngle(e.touches); + let angleDelta = currentAngle - startAngle; + if (!rotationEnabled && Math.abs(angleDelta) > 8) { + rotationEnabled = true; + startAngle += angleDelta; + angleDelta = 0; + } + const newBearing = startBearing + angleDelta; + + Globals.map.setZoom(newZoom, { around: Globals.map.getCenter() }); + if (rotationEnabled) { + Globals.map.setBearing(newBearing); + } + } +}; + +function getTouchDistance(touches) { + const [touch1, touch2] = touches; + const dx = touch1.clientX - touch2.clientX; + const dy = touch1.clientY - touch2.clientY; + return Math.sqrt(dx * dx + dy * dy); +} + +function getTouchAngle(touches) { + const [touch1, touch2] = touches; + const dx = touch1.clientX - touch2.clientX; + const dy = touch1.clientY - touch2.clientY; + return Math.atan2(dy, dx) * (180 / Math.PI); +} + export default { target, getCurrentPosition, From a9c6ad67d7d0911d967297290a854be3beb3fd7d Mon Sep 17 00:00:00 2001 From: "amaury.zarzelli" Date: Wed, 28 Aug 2024 16:08:07 +0200 Subject: [PATCH 3/3] feat(locationBtn): UX improvements --- src/js/map-listeners.js | 6 +----- src/js/services/location.js | 43 +++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/js/map-listeners.js b/src/js/map-listeners.js index 424a0a6d..f01c44b0 100644 --- a/src/js/map-listeners.js +++ b/src/js/map-listeners.js @@ -28,11 +28,7 @@ const addListeners = () => { // Désactivation du tracking au déplacement non programmatique de la carte map.on("dragstart", () => { - if (Location.isNavigationActive()){ - Location.disableNavigation(0); - } else if (Location.isTrackingActive()){ - Location.disableTracking(); - } + Location.disableTracking(); }); // l'event contextmenu n'est pas enclenché par clic long sur la carte... https://github.com/maplibre/maplibre-gl-js/issues/373 diff --git a/src/js/services/location.js b/src/js/services/location.js index b6708625..525fefca 100644 --- a/src/js/services/location.js +++ b/src/js/services/location.js @@ -230,17 +230,21 @@ const moveTo = (coords, zoom = Globals.map.getZoom(), panTo = true, gps = true) if (tracking_active) { let bearing = Globals.map.getBearing(); let pitch = 0; + let padding = 0; if (navigation_active) { bearing = -mapBearing; pitch = 45; + padding = {top: DOM.$map.clientHeight * 0.5}; } isMapPanning = true; - Globals.map.flyTo({ + Globals.map.easeTo({ center: [coords.lon, coords.lat], zoom: zoom, bearing: bearing, pitch: pitch, - duration: 500}); + duration: 500, + padding: padding, + }); Globals.map.once("moveend", () => {isMapPanning = false;}); } else { Globals.map.flyTo({ @@ -289,11 +293,9 @@ const watchPositionCallback = (position) => { var padding; // gestion du mode paysage / écran large if (window.matchMedia("screen and (min-aspect-ratio: 1/1) and (min-width:400px)").matches) { - var paddingLeft = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--safe-area-inset-left").slice(0, -2)) + - Math.min(window.innerHeight, window.innerWidth/2) + 42; - padding = {top: 20, right: 20, bottom: 20, left: paddingLeft}; + padding = {top: 20, right: 20, bottom: 20, left: 20}; } else { - padding = {top: 80, right: 20, bottom: 120, left: 20}; + padding = {top: 80, right: 20, bottom: 40, left: 20}; } Globals.map.fitBounds(bbox, { padding: padding @@ -412,6 +414,7 @@ const locationOnOff = async () => { } DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFixeImg + "\")"; tracking_active = true; + Globals.map.setPadding({top: 0, right: 0, bottom: 0, left: 0}); Globals.map.setCenter([currentPosition.coords.longitude, currentPosition.coords.latitude]); Globals.map.touchZoomRotate.disable(); Globals.map.getCanvasContainer().addEventListener("touchstart", locationOnTouchStartHandler); @@ -428,10 +431,12 @@ const locationOnOff = async () => { DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFollowImg + "\")"; navigation_active = true; Globals.map.setMaxPitch(45); - Globals.map.flyTo({ + const padding = {top: DOM.$map.clientHeight * 0.5}; + Globals.map.easeTo({ center: [currentPosition.coords.longitude, currentPosition.coords.latitude], bearing: -mapBearing, pitch: 45, + padding: padding, }); DOM.$compassBtn.classList.remove("d-none"); DOM.$compassBtn.style.transform = "rotate(" + mapBearing + "deg)"; @@ -544,35 +549,27 @@ const getLocation = async () => { const disableTracking = () => { DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; tracking_active = false; - navigation_active = false; + if (navigation_active) { + Globals.map.setMaxPitch(0); + navigation_active = false; + } Globals.map.touchZoomRotate.enable(); Globals.map.getCanvasContainer().removeEventListener("touchstart", locationOnTouchStartHandler); Globals.map.getCanvasContainer().removeEventListener("touchmove", locationOnTouchMoveHandler); - Toast.show({ - text: "Suivi de position activé", - duration: "short", - position: "bottom" - }); }; const disableNavigation = (bearing = Globals.map.getBearing()) => { - DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationImg + "\")"; + DOM.$geolocateBtn.style.backgroundImage = "url(\"" + LocationFixeImg + "\")"; navigation_active = false; - tracking_active = false; Globals.map.flyTo({ pitch: 0, bearing: bearing, duration: 500, }); - Globals.map.touchZoomRotate.enable(); - Globals.map.getCanvasContainer().removeEventListener("touchstart", locationOnTouchStartHandler); - Globals.map.getCanvasContainer().removeEventListener("touchmove", locationOnTouchMoveHandler); setTimeout( () => {Globals.map.setMaxPitch(0);}, 500); - Toast.show({ - text: "Suivi de position activé", - duration: "short", - position: "bottom" - }); + if (bearing === 0) { + DOM.$compassBtn.classList.add("d-none"); + } }; let listenResumeAfterLocation = false;