From a110d06a205141e951c57c9fe4297925341c4a5e Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:29:06 +1000 Subject: [PATCH 01/34] maps live location, new cursor, updated code in this branch with latest merged to test precision. Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.css | 22 ++++ app/src/gui/fields/maps/MapWrapper.tsx | 140 +++++++++++++++++++++++-- 2 files changed, 152 insertions(+), 10 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.css b/app/src/gui/fields/maps/MapWrapper.css index b4e349460..ae1ec668c 100644 --- a/app/src/gui/fields/maps/MapWrapper.css +++ b/app/src/gui/fields/maps/MapWrapper.css @@ -16,3 +16,25 @@ .mapSubmitButton { grid-row: 2; } +/* Custom map style */ +.gps-location-dot { + width: 20px; + height: 20px; + background: #1a73e8; + border: 2px solid white; + border-radius: 50%; + box-shadow: 0 0 0 rgba(26, 115, 232, 0.4); + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(26, 115, 232, 0.4); + } + 70% { + box-shadow: 0 0 0 15px rgba(26, 115, 232, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(26, 115, 232, 0); + } +} diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 1138db094..4e0965549 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -29,8 +29,8 @@ import {register} from 'ol/proj/proj4'; import VectorSource from 'ol/source/Vector'; import {Circle as CircleStyle, Fill, Stroke, Style} from 'ol/style'; import proj4 from 'proj4'; -import {useCallback, useEffect, useState} from 'react'; - +import {useCallback, useEffect, useRef, useState} from 'react'; +import {transform} from 'ol/proj'; // define some EPSG codes - these are for two sample images // TODO: we need to have a better way to include a useful set or allow // them to be defined by a project @@ -78,18 +78,31 @@ import {useNotification} from '../../../context/popup'; import {MapComponent} from '../../components/map/map-component'; import {theme} from '../../themes'; import {Extent} from 'ol/extent'; +import Feature from 'ol/Feature'; +import {Geometry, Point} from 'ol/geom'; +import {unByKey} from 'ol/Observable'; function MapWrapper(props: MapProps) { const [mapOpen, setMapOpen] = useState(false); const [map, setMap] = useState(); - const [featuresLayer, setFeaturesLayer] = useState>(); + const [featuresLayer, setFeaturesLayer] = useState(); const geoJson = new GeoJSON(); const [showConfirmSave, setShowConfirmSave] = useState(false); - const [featuresExtent, setFeaturesExtent] = useState(); + const [featuresExtent, setFeaturesExtent] = useState(); // notifications const notify = useNotification(); + // trakcing user's real-time location, make precise after test inputs- @todo ranisa + const [positionFeature, setPositionFeature] = useState | null>( + null + ); + const [positionLayer, setPositionLayer] = useState(); + const watchIdRef = useRef(null); + const [accuracyFeature, setAccuracyFeature] = useState | null>( + null + ); + const addDrawInteraction = useCallback( (theMap: Map, props: MapProps) => { const source = new VectorSource(); @@ -147,6 +160,119 @@ function MapWrapper(props: MapProps) { [setFeaturesLayer] ); + // auto-clean tracking + const stopTracking = () => { + if (watchIdRef.current) { + navigator.geolocation.clearWatch(watchIdRef.current); + watchIdRef.current = null; + } + if (map && positionLayer) { + map.removeLayer(positionLayer); + setPositionLayer(undefined); + } + }; + + // real-time blue dot + accuracy tracking + const startLocationTracking = (theMap: Map) => { + stopTracking(); + + const view = theMap.getView(); + const projection = view.getProjection(); + + // get initial position for zooming + navigator.geolocation.getCurrentPosition( + pos => { + const coords = transform( + [pos.coords.longitude, pos.coords.latitude], + 'EPSG:4326', + projection + ); + view.setCenter(coords); // auto-zoom to current location + view.setZoom(17); // adjust zoom level as needed + }, + err => console.error('Initial location error', err), + {enableHighAccuracy: true} + ); + + const positionSource = new VectorSource(); + const positionLayer = new VectorLayer({ + source: positionSource, + zIndex: 999, + }); + theMap.addLayer(positionLayer); + setPositionLayer(positionLayer); + + // blue Dot + const dotFeature = new Feature(new Point([0, 0])); + // background pluse + const accuracyFeature = new Feature(new Point([0, 0])); + + // css opacity and scale pulse chages + dotFeature.setStyle( + new Style({ + image: new CircleStyle({ + radius: 10, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#fff', width: 3}), + }), + }) + ); + + accuracyFeature.setStyle( + new Style({ + image: new CircleStyle({ + radius: 25, + fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), + stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), + }), + }) + ); + + positionSource.addFeatures([accuracyFeature, dotFeature]); + + // continuous location work in progress.. + watchIdRef.current = navigator.geolocation.watchPosition( + pos => { + const coords = transform( + [pos.coords.longitude, pos.coords.latitude], + 'EPSG:4326', + projection + ); + const accuracy = pos.coords.accuracy || 30; + + dotFeature.getGeometry()?.setCoordinates(coords); + accuracyFeature.getGeometry()?.setCoordinates(coords); + + // resize accuracy radius + accuracyFeature.setStyle( + new Style({ + image: new CircleStyle({ + radius: Math.max(25, accuracy / 3), + fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), + stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), + }), + }) + ); + }, + err => console.error('Live tracking error', err), + {enableHighAccuracy: true, maximumAge: 0, timeout: 10000} + ); + }; + + useEffect(() => { + if (mapOpen && map) { + addDrawInteraction(map, props); + startLocationTracking(map); + } + return stopTracking; + }, [mapOpen, map]); + + useEffect(() => { + if (map) { + addDrawInteraction(map, props); + } + }, [map]); + const handleClose = (action: 'save' | 'clear' | 'close') => { if (featuresLayer) { const source = featuresLayer.getSource(); @@ -188,12 +314,6 @@ function MapWrapper(props: MapProps) { setMapOpen(true); }; - useEffect(() => { - if (map) { - addDrawInteraction(map, props); - } - }, [map]); - return (
{!props.isLocationSelected ? ( From e39b5b6f1afef57307d7368db56d1ce187a7b8a6 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:45:53 +1000 Subject: [PATCH 02/34] clearing source before reuse Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 4e0965549..b70ac6442 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -172,6 +172,14 @@ function MapWrapper(props: MapProps) { } }; + // add this to stoptracking after test works. + // if (positionLayer) { + // const source = positionLayer.getSource(); + // source?.clear(); // clear any leftover features + // map.removeLayer(positionLayer); + // setPositionLayer(undefined); + // } + // real-time blue dot + accuracy tracking const startLocationTracking = (theMap: Map) => { stopTracking(); From fe2b8a512582b8a808205dbbfeb128192c45ba29 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:48:56 +1000 Subject: [PATCH 03/34] add comment for stoptracking, zoom initials Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index b70ac6442..55d419342 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -182,12 +182,12 @@ function MapWrapper(props: MapProps) { // real-time blue dot + accuracy tracking const startLocationTracking = (theMap: Map) => { - stopTracking(); + stopTracking(); // Clean up any previous tracking const view = theMap.getView(); const projection = view.getProjection(); - // get initial position for zooming + // zooming to initial location on start navigator.geolocation.getCurrentPosition( pos => { const coords = transform( From e26e970e61da19e22e19445cc6da4b226c26d461 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:49:25 +1000 Subject: [PATCH 04/34] set center and zoom Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 55d419342..da7188152 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -195,8 +195,8 @@ function MapWrapper(props: MapProps) { 'EPSG:4326', projection ); - view.setCenter(coords); // auto-zoom to current location - view.setZoom(17); // adjust zoom level as needed + view.setCenter(coords); + view.setZoom(17); }, err => console.error('Initial location error', err), {enableHighAccuracy: true} From b2be005c7f1e8a3d88e72a07223df16cf89d4938 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:50:06 +1000 Subject: [PATCH 05/34] heading and rotation related tweaks, making more precise Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index da7188152..67d33a389 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -215,13 +215,16 @@ function MapWrapper(props: MapProps) { // background pluse const accuracyFeature = new Feature(new Point([0, 0])); - // css opacity and scale pulse chages - dotFeature.setStyle( + // initial styles + directionFeature.setStyle( new Style({ - image: new CircleStyle({ + image: new RegularShape({ + points: 3, radius: 10, + rotation: 0, // rotate based on heading + angle: Math.PI / 3, fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#fff', width: 3}), + stroke: new Stroke({color: '#fff', width: 2}), }), }) ); From 616ed94eb246a63b01f57e81ea65449cb723db88 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:50:53 +1000 Subject: [PATCH 06/34] inc radius, rg to try dynamic radius Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 67d33a389..c4722e893 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -232,7 +232,7 @@ function MapWrapper(props: MapProps) { accuracyFeature.setStyle( new Style({ image: new CircleStyle({ - radius: 25, + radius: 30, // todo ranisa to try dyanmic radius fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), }), From 39497a9acda0bbcc84203107670beddde1cc0a9e Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:51:22 +1000 Subject: [PATCH 07/34] direction arrow rot code commit from stash Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index c4722e893..d6182fa34 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -264,6 +264,20 @@ function MapWrapper(props: MapProps) { }), }) ); + + // direction arrow rotation + directionFeature.setStyle( + new Style({ + image: new RegularShape({ + points: 3, + radius: 10, + rotation: heading, + angle: Math.PI / 3, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#fff', width: 2}), + }), + }) + ); }, err => console.error('Live tracking error', err), {enableHighAccuracy: true, maximumAge: 0, timeout: 10000} From 2f2eeacf624015da3c7a9452753843b3d9c01de5 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:51:48 +1000 Subject: [PATCH 08/34] enhancements to minor specs to improve pef. Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index d6182fa34..d3c55592d 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -81,6 +81,7 @@ import {Extent} from 'ol/extent'; import Feature from 'ol/Feature'; import {Geometry, Point} from 'ol/geom'; import {unByKey} from 'ol/Observable'; +import {RegularShape} from 'ol/style'; function MapWrapper(props: MapProps) { const [mapOpen, setMapOpen] = useState(false); @@ -202,17 +203,17 @@ function MapWrapper(props: MapProps) { {enableHighAccuracy: true} ); + // set up new position layer const positionSource = new VectorSource(); const positionLayer = new VectorLayer({ source: positionSource, - zIndex: 999, + zIndex: 999, // keeping it above layers }); theMap.addLayer(positionLayer); setPositionLayer(positionLayer); - // blue Dot - const dotFeature = new Feature(new Point([0, 0])); - // background pluse + // blue directional arrow + accuracy circle + const directionFeature = new Feature(new Point([0, 0])); const accuracyFeature = new Feature(new Point([0, 0])); // initial styles @@ -239,7 +240,7 @@ function MapWrapper(props: MapProps) { }) ); - positionSource.addFeatures([accuracyFeature, dotFeature]); + positionSource.addFeatures([accuracyFeature, directionFeature]); // continuous location work in progress.. watchIdRef.current = navigator.geolocation.watchPosition( @@ -249,16 +250,18 @@ function MapWrapper(props: MapProps) { 'EPSG:4326', projection ); - const accuracy = pos.coords.accuracy || 30; - dotFeature.getGeometry()?.setCoordinates(coords); + const heading = pos.coords.heading ?? 0; // Use real heading, or fallback + const accuracy = pos.coords.accuracy ?? 30; + + directionFeature.getGeometry()?.setCoordinates(coords); accuracyFeature.getGeometry()?.setCoordinates(coords); - // resize accuracy radius + // Update accuracy circle accuracyFeature.setStyle( new Style({ image: new CircleStyle({ - radius: Math.max(25, accuracy / 3), + radius: Math.max(20, accuracy / 2), fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), }), @@ -280,7 +283,11 @@ function MapWrapper(props: MapProps) { ); }, err => console.error('Live tracking error', err), - {enableHighAccuracy: true, maximumAge: 0, timeout: 10000} + { + enableHighAccuracy: true, + maximumAge: 0, + timeout: 10000, + } ); }; From 407d1d965d26599667a825be661c9043192d3e25 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 14:53:27 +1000 Subject: [PATCH 09/34] enhance stop tracking Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index d3c55592d..8898400c7 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -168,6 +168,8 @@ function MapWrapper(props: MapProps) { watchIdRef.current = null; } if (map && positionLayer) { + const source = positionLayer.getSource(); + source?.clear(); // clear any lingering features map.removeLayer(positionLayer); setPositionLayer(undefined); } From 460595e0aae2c70ff976241c41d68e8d58761c2f Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 22:51:43 +1000 Subject: [PATCH 10/34] more tweaks on header alignment agianst the blue dot, header is flaking on zoom in Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 82 +++++++++++++++----------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 8898400c7..4892712a7 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -218,33 +218,61 @@ function MapWrapper(props: MapProps) { const directionFeature = new Feature(new Point([0, 0])); const accuracyFeature = new Feature(new Point([0, 0])); - // initial styles - directionFeature.setStyle( - new Style({ - image: new RegularShape({ - points: 3, - radius: 10, - rotation: 0, // rotate based on heading - angle: Math.PI / 3, - fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#fff', width: 2}), + const getDirectionDotStyle = (headingRadians: number) => { + return [ + // πŸ”΅ Core dot + new Style({ + image: new CircleStyle({ + radius: 12, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#ffffff', width: 3}), + }), }), - }) - ); + // πŸ”Ί Pointer triangle (direction) + new Style({ + image: new RegularShape({ + points: 3, + radius: 10, + radius2: 4, // creates sharper tip + angle: 0, // base angle + rotation: headingRadians, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#ffffff', width: 2}), + }), + geometry: function (feature) { + const geometry = feature.getGeometry() as Point; + const coord = geometry?.getCoordinates(); + if (!coord) return geometry; + + // move the triangle slightly above the center + const offset = 11; + const dx = Math.sin(headingRadians) * offset; + const dy = -Math.cos(headingRadians) * offset; + + const newCoord = [coord[0] + dx, coord[1] + dy]; + return new Point(newCoord); + }, + }), + ]; + }; + // Initial style with dummy heading + directionFeature.setStyle(getDirectionDotStyle(0)); + + // Accuracy circle accuracyFeature.setStyle( new Style({ image: new CircleStyle({ - radius: 30, // todo ranisa to try dyanmic radius + radius: 30, fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), }), }) ); + // Add both to layer source positionSource.addFeatures([accuracyFeature, directionFeature]); - // continuous location work in progress.. watchIdRef.current = navigator.geolocation.watchPosition( pos => { const coords = transform( @@ -253,43 +281,27 @@ function MapWrapper(props: MapProps) { projection ); - const heading = pos.coords.heading ?? 0; // Use real heading, or fallback + const heading = pos.coords.heading ?? 0; const accuracy = pos.coords.accuracy ?? 30; directionFeature.getGeometry()?.setCoordinates(coords); accuracyFeature.getGeometry()?.setCoordinates(coords); - // Update accuracy circle accuracyFeature.setStyle( new Style({ image: new CircleStyle({ - radius: Math.max(20, accuracy / 2), + radius: Math.max(25, accuracy / 2), fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), }), }) ); - // direction arrow rotation - directionFeature.setStyle( - new Style({ - image: new RegularShape({ - points: 3, - radius: 10, - rotation: heading, - angle: Math.PI / 3, - fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#fff', width: 2}), - }), - }) - ); + // πŸ†• Apply rotated arrow + blue dot + directionFeature.setStyle(getDirectionDotStyle(heading)); }, err => console.error('Live tracking error', err), - { - enableHighAccuracy: true, - maximumAge: 0, - timeout: 10000, - } + {enableHighAccuracy: true, maximumAge: 0, timeout: 10000} ); }; From 9766751996ceee4e75261b934915cf93d08d224a Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Tue, 8 Apr 2025 22:54:01 +1000 Subject: [PATCH 11/34] more tweaks on header alignment agianst the blue dot, header is flaking on zoom in Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 4892712a7..693d873a9 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -220,7 +220,7 @@ function MapWrapper(props: MapProps) { const getDirectionDotStyle = (headingRadians: number) => { return [ - // πŸ”΅ Core dot + // core dot new Style({ image: new CircleStyle({ radius: 12, @@ -228,7 +228,7 @@ function MapWrapper(props: MapProps) { stroke: new Stroke({color: '#ffffff', width: 3}), }), }), - // πŸ”Ί Pointer triangle (direction) + // Pointer triangle (direction) new Style({ image: new RegularShape({ points: 3, From 886f1539f277d2e3552f4b25bd59a21f57efb34c Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Wed, 9 Apr 2025 15:19:40 +1000 Subject: [PATCH 12/34] css enhancmeents for the scaling on the map Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/gui/fields/maps/MapWrapper.css b/app/src/gui/fields/maps/MapWrapper.css index ae1ec668c..59c64b935 100644 --- a/app/src/gui/fields/maps/MapWrapper.css +++ b/app/src/gui/fields/maps/MapWrapper.css @@ -38,3 +38,19 @@ box-shadow: 0 0 0 0 rgba(26, 115, 232, 0); } } + +/* Optional for a true pulse if you decide to switch to DOM overlay later */ +@keyframes pulse { + 0% { + transform: scale(0.95); + opacity: 1; + } + 70% { + transform: scale(1.3); + opacity: 0; + } + 100% { + transform: scale(0.95); + opacity: 0; + } +} From 0c791bacbce6839b37485fb4819c8fb8c53fa073 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Wed, 9 Apr 2025 15:20:14 +1000 Subject: [PATCH 13/34] css enhancmeents for the scaling on the map Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/gui/fields/maps/MapWrapper.css b/app/src/gui/fields/maps/MapWrapper.css index 59c64b935..c05308b6a 100644 --- a/app/src/gui/fields/maps/MapWrapper.css +++ b/app/src/gui/fields/maps/MapWrapper.css @@ -39,7 +39,7 @@ } } -/* Optional for a true pulse if you decide to switch to DOM overlay later */ +/* Optional for a true pulse if switch to DOM overlay later */ @keyframes pulse { 0% { transform: scale(0.95); From ec34614f4b27dd0c4548ebf63cd88adef7f00971 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Wed, 9 Apr 2025 15:20:54 +1000 Subject: [PATCH 14/34] map cursor live simulation test Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 693d873a9..0ccc4fbb7 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -164,7 +164,7 @@ function MapWrapper(props: MapProps) { // auto-clean tracking const stopTracking = () => { if (watchIdRef.current) { - navigator.geolocation.clearWatch(watchIdRef.current); + clearInterval(watchIdRef.current); // Stop simulation test watchIdRef.current = null; } if (map && positionLayer) { From f8e400f332789ee143b3633ceb1506938bbd2bb7 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Wed, 9 Apr 2025 15:51:11 +1000 Subject: [PATCH 15/34] further improvements in map based on testing, precising location, geolocation tracking. prep for mobile app testing via grok Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 180 +++++++++++++------------ app/vite.config.ts | 8 +- 2 files changed, 100 insertions(+), 88 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 0ccc4fbb7..ca0c255d5 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -164,12 +164,12 @@ function MapWrapper(props: MapProps) { // auto-clean tracking const stopTracking = () => { if (watchIdRef.current) { - clearInterval(watchIdRef.current); // Stop simulation test + clearInterval(watchIdRef.current); // Stop simulation test watchIdRef.current = null; } if (map && positionLayer) { const source = positionLayer.getSource(); - source?.clear(); // clear any lingering features + source?.clear(); map.removeLayer(positionLayer); setPositionLayer(undefined); } @@ -185,94 +185,93 @@ function MapWrapper(props: MapProps) { // real-time blue dot + accuracy tracking const startLocationTracking = (theMap: Map) => { - stopTracking(); // Clean up any previous tracking + stopTracking(); // clean previous layers or intervals const view = theMap.getView(); const projection = view.getProjection(); - // zooming to initial location on start - navigator.geolocation.getCurrentPosition( - pos => { - const coords = transform( - [pos.coords.longitude, pos.coords.latitude], - 'EPSG:4326', - projection - ); - view.setCenter(coords); - view.setZoom(17); - }, - err => console.error('Initial location error', err), - {enableHighAccuracy: true} - ); - - // set up new position layer const positionSource = new VectorSource(); const positionLayer = new VectorLayer({ source: positionSource, - zIndex: 999, // keeping it above layers + zIndex: 999, }); theMap.addLayer(positionLayer); setPositionLayer(positionLayer); - // blue directional arrow + accuracy circle const directionFeature = new Feature(new Point([0, 0])); const accuracyFeature = new Feature(new Point([0, 0])); + positionSource.addFeatures([accuracyFeature, directionFeature]); - const getDirectionDotStyle = (headingRadians: number) => { - return [ - // core dot - new Style({ - image: new CircleStyle({ - radius: 12, - fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#ffffff', width: 3}), - }), + const updateStyles = ( + coords: number[], + headingRadians: number, + accuracy: number + ) => { + directionFeature.setGeometry(new Point(coords)); + accuracyFeature.setGeometry(new Point(coords)); + + // Pulsing effect using scaled circle layers + const pulseOuter = new Style({ + image: new CircleStyle({ + radius: 18, + fill: new Fill({color: 'rgba(26, 115, 232, 0.2)'}), + }), + }); + + const pulseInner = new Style({ + image: new CircleStyle({ + radius: 12, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#ffffff', width: 3}), + }), + }); + + const directionArrow = new Style({ + image: new RegularShape({ + points: 3, + radius: 10, + radius2: 4, + rotation: headingRadians, + angle: Math.PI / 3, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#ffffff', width: 2}), }), - // Pointer triangle (direction) + geometry: feature => feature.getGeometry(), + }); + + directionFeature.setStyle([pulseOuter, pulseInner, directionArrow]); + + // Accuracy circle (pulsing-style scale not supported, but visual size helps) + accuracyFeature.setStyle( new Style({ - image: new RegularShape({ - points: 3, - radius: 10, - radius2: 4, // creates sharper tip - angle: 0, // base angle - rotation: headingRadians, - fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#ffffff', width: 2}), + image: new CircleStyle({ + radius: Math.max(25, accuracy / 2), + fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), + stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), }), - geometry: function (feature) { - const geometry = feature.getGeometry() as Point; - const coord = geometry?.getCoordinates(); - if (!coord) return geometry; - - // move the triangle slightly above the center - const offset = 11; - const dx = Math.sin(headingRadians) * offset; - const dy = -Math.cos(headingRadians) * offset; - - const newCoord = [coord[0] + dx, coord[1] + dy]; - return new Point(newCoord); - }, - }), - ]; + }) + ); }; - // Initial style with dummy heading - directionFeature.setStyle(getDirectionDotStyle(0)); - - // Accuracy circle - accuracyFeature.setStyle( - new Style({ - image: new CircleStyle({ - radius: 30, - fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), - stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), - }), - }) + // doing current location for initial zoom + navigator.geolocation.getCurrentPosition( + pos => { + const coords = transform( + [pos.coords.longitude, pos.coords.latitude], + 'EPSG:4326', + projection + ); + view.setCenter(coords); + view.setZoom(17); + }, + err => { + console.error('Initial GPS fetch failed', err); + props.setNoPermission(true); + }, + {enableHighAccuracy: true} ); - // Add both to layer source - positionSource.addFeatures([accuracyFeature, directionFeature]); - + // Real-time tracking watchIdRef.current = navigator.geolocation.watchPosition( pos => { const coords = transform( @@ -280,31 +279,38 @@ function MapWrapper(props: MapProps) { 'EPSG:4326', projection ); - const heading = pos.coords.heading ?? 0; const accuracy = pos.coords.accuracy ?? 30; - directionFeature.getGeometry()?.setCoordinates(coords); - accuracyFeature.getGeometry()?.setCoordinates(coords); - - accuracyFeature.setStyle( - new Style({ - image: new CircleStyle({ - radius: Math.max(25, accuracy / 2), - fill: new Fill({color: 'rgba(100, 149, 237, 0.1)'}), - stroke: new Stroke({color: 'rgba(100, 149, 237, 0.3)', width: 1}), - }), - }) - ); - - // πŸ†• Apply rotated arrow + blue dot - directionFeature.setStyle(getDirectionDotStyle(heading)); + updateStyles(coords, heading, accuracy); }, - err => console.error('Live tracking error', err), - {enableHighAccuracy: true, maximumAge: 0, timeout: 10000} + err => { + console.error('Live tracking error:', err); + props.setNoPermission(true); + }, + { + enableHighAccuracy: true, + maximumAge: 0, + timeout: 10000, + } ); }; + // temporarilty adding for debugging + useEffect(() => { + navigator.geolocation.getCurrentPosition( + pos => { + alert( + `πŸ“ Location:\nLat: ${pos.coords.latitude}\nLon: ${pos.coords.longitude}` + ); + }, + err => { + alert('❌ GPS ERROR:\n' + err.message); + }, + {enableHighAccuracy: true} + ); + }, []); + useEffect(() => { if (mapOpen && map) { addDrawInteraction(map, props); diff --git a/app/vite.config.ts b/app/vite.config.ts index c0cb3dab8..4e604f04e 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -32,9 +32,15 @@ export default defineConfig({ build: { outDir: 'build', }, + // server: { + // port: 3000, + // host: true, + // }, server: { + host: '0.0.0.0', port: 3000, - host: true, + strictPort: true, + allowedHosts: true, // βœ… This allows ngrok or any public domain }, preview: { port: 3000, From 93af7c17a71ca737fe50523aa31afe2ef2816dbe Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Wed, 9 Apr 2025 15:52:21 +1000 Subject: [PATCH 16/34] more.. Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index ca0c255d5..4ad17c7a2 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -301,11 +301,11 @@ function MapWrapper(props: MapProps) { navigator.geolocation.getCurrentPosition( pos => { alert( - `πŸ“ Location:\nLat: ${pos.coords.latitude}\nLon: ${pos.coords.longitude}` + `Location:\nLat: ${pos.coords.latitude}\nLon: ${pos.coords.longitude}` ); }, err => { - alert('❌ GPS ERROR:\n' + err.message); + alert(' GPS ERROR:\n' + err.message); }, {enableHighAccuracy: true} ); From db99ab05080f580e10d25a721fb11fd5b7ea37aa Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Thu, 10 Apr 2025 00:53:33 +1000 Subject: [PATCH 17/34] map wrapper advanced css styling to avoid here Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.css | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.css b/app/src/gui/fields/maps/MapWrapper.css index c05308b6a..ddf2e4423 100644 --- a/app/src/gui/fields/maps/MapWrapper.css +++ b/app/src/gui/fields/maps/MapWrapper.css @@ -27,19 +27,6 @@ animation: pulse 1.5s infinite; } -@keyframes pulse { - 0% { - box-shadow: 0 0 0 0 rgba(26, 115, 232, 0.4); - } - 70% { - box-shadow: 0 0 0 15px rgba(26, 115, 232, 0); - } - 100% { - box-shadow: 0 0 0 0 rgba(26, 115, 232, 0); - } -} - -/* Optional for a true pulse if switch to DOM overlay later */ @keyframes pulse { 0% { transform: scale(0.95); @@ -54,3 +41,7 @@ opacity: 0; } } +.ol-overlay-container, +.ol-overlay-container-stopevent { + display: none !important; +} From fc837a305fe6decdbc727f554178fc6a09fce41f Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Thu, 10 Apr 2025 00:56:53 +1000 Subject: [PATCH 18/34] map wrapper jsx update cleaner code Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 82 ++++++++++++++------------ 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 4ad17c7a2..e3df6120f 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -554,45 +554,49 @@ function MapWrapper(props: MapProps) { - setShowConfirmSave(false)}> - - No location selected - Are you sure you want to save an empty location selection? - - - - - - -
+ setShowConfirmSave(false)} + > + + No location selected + Are you sure you want to save an empty location selection? + + + + + + + + ); } // added forward rendering.. From d2d27bb0f2b23a68e0d053008488727782f69b11 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Thu, 10 Apr 2025 00:57:21 +1000 Subject: [PATCH 19/34] organised code more orineted Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 106 ++++++++++++------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index e3df6120f..94587f485 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -496,63 +496,63 @@ function MapWrapper(props: MapProps) { - - + - - - - - - {/*
*/} - - - - + + + + + + {/*
*/} + + + + Date: Thu, 10 Apr 2025 00:58:03 +1000 Subject: [PATCH 20/34] handleclose updated and enhanced as per requirements Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 220 +++++++++++++------------ 1 file changed, 113 insertions(+), 107 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 94587f485..6ddf830a4 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -325,34 +325,40 @@ function MapWrapper(props: MapProps) { } }, [map]); - const handleClose = (action: 'save' | 'clear' | 'close') => { + // save cleanr and close + const handleClose = (action: MapAction | 'clear') => { + if (!map) return; + if (featuresLayer) { - const source = featuresLayer.getSource(); - - if (source) { - const features = source.getFeatures(); - - if (map) { - const geoJsonFeatures = geoJson.writeFeaturesObject(features, { - featureProjection: map.getView().getProjection(), - dataProjection: 'EPSG:4326', - rightHanded: true, - }); - if (action === 'clear') { - // if clearing - just remove locally don't callback so we don't save this change - source.clear(); - } else if (action === 'save') { - if (!features.length) { - setShowConfirmSave(true); // show confirmation dialog if no location is selected while saving. - return; - } - props.setFeatures(geoJsonFeatures, 'save'); - setMapOpen(false); - } else if (action === 'close') { - setMapOpen(false); - } - } + map.removeLayer(featuresLayer); // Remove previous layer + setFeaturesLayer(undefined); + } + + const source = featuresLayer?.getSource(); + const features = source?.getFeatures() ?? []; + + if (action === 'clear') { + console.log('Inside clear'); + props.setFeatures({}, 'save'); // Clear pin from state + addDrawInteraction(map, props); // re-ad draw layer + return; + } + // action save + if (action === 'save') { + if (!features.length) { + setShowConfirmSave(true); + return; } + + const geoJsonFeatures = geoJson.writeFeaturesObject(features, { + featureProjection: map.getView().getProjection(), + dataProjection: 'EPSG:4326', + }); + + props.setFeatures(geoJsonFeatures, 'save'); + setMapOpen(false); + } else if (action === 'close') { + setMapOpen(false); } }; @@ -403,98 +409,98 @@ function MapWrapper(props: MapProps) { }} /> - - {props.label} - - - - ) : ( - - - - + {props.label} + + + + ) : ( + + + - - - - )} + onClick={handleClickOpen} + > + + + + + )} - setMapOpen(false)}> - - setMapOpen(false)}> + - - setMapOpen(false)} - aria-label="close" - sx={{ - backgroundColor: theme.palette.primary.dark, - color: theme.palette.background.default, - fontSize: '16px', - gap: '4px', - fontWeight: 'bold', - borderRadius: '6px', - padding: '6px 12px', - transition: - 'background-color 0.3s ease-in-out, transform 0.2s ease-in-out', - '&:hover': { - backgroundColor: theme.palette.text.primary, - transform: 'scale(1.05)', - }, - }} + - setMapOpen(false)} + aria-label="close" sx={{ - stroke: theme.palette.background.default, - strokeWidth: '1.5', + backgroundColor: theme.palette.primary.dark, + color: theme.palette.background.default, + fontSize: '16px', + gap: '4px', + fontWeight: 'bold', + borderRadius: '6px', + padding: '6px 12px', + transition: + 'background-color 0.3s ease-in-out, transform 0.2s ease-in-out', + '&:hover': { + backgroundColor: theme.palette.text.primary, + transform: 'scale(1.05)', + }, }} - /> - Close - - + > + + Close + + Date: Thu, 10 Apr 2025 00:58:44 +1000 Subject: [PATCH 21/34] add stash for live gps that was tested before in browser Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 6ddf830a4..22f55fce0 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -271,7 +271,6 @@ function MapWrapper(props: MapProps) { {enableHighAccuracy: true} ); - // Real-time tracking watchIdRef.current = navigator.geolocation.watchPosition( pos => { const coords = transform( @@ -281,11 +280,10 @@ function MapWrapper(props: MapProps) { ); const heading = pos.coords.heading ?? 0; const accuracy = pos.coords.accuracy ?? 30; - updateStyles(coords, heading, accuracy); }, err => { - console.error('Live tracking error:', err); + console.error('Live GPS error', err); props.setNoPermission(true); }, { @@ -296,21 +294,7 @@ function MapWrapper(props: MapProps) { ); }; - // temporarilty adding for debugging - useEffect(() => { - navigator.geolocation.getCurrentPosition( - pos => { - alert( - `Location:\nLat: ${pos.coords.latitude}\nLon: ${pos.coords.longitude}` - ); - }, - err => { - alert(' GPS ERROR:\n' + err.message); - }, - {enableHighAccuracy: true} - ); - }, []); - + // ini. load & cleanup useEffect(() => { if (mapOpen && map) { addDrawInteraction(map, props); From e793ebf477bc5ff0961c7f2fdab54741881ef52c Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Thu, 10 Apr 2025 00:59:05 +1000 Subject: [PATCH 22/34] traingle design enhanced Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 38 ++++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 22f55fce0..313cb9c48 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -226,22 +226,30 @@ function MapWrapper(props: MapProps) { }), }); - const directionArrow = new Style({ - image: new RegularShape({ - points: 3, - radius: 10, - radius2: 4, - rotation: headingRadians, - angle: Math.PI / 3, - fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#ffffff', width: 2}), - }), - geometry: feature => feature.getGeometry(), - }); - - directionFeature.setStyle([pulseOuter, pulseInner, directionArrow]); + // directional triangle + triangleFeature.setStyle( + new Style({ + image: new RegularShape({ + points: 3, + radius: 12, // distance from center to each point + rotation: heading + Math.PI, // flip to base the (dot) + angle: Math.PI, // vertex up + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: 'white', width: 2}), + }), + geometry: () => { + const px = theMap.getPixelFromCoordinate(coords); + const offset = 22; + const dx = offset * Math.sin(heading); + const dy = -offset * Math.cos(heading); + const offsetPx = [px[0] + dx, px[1] + dy]; + return new Point(theMap.getCoordinateFromPixel(offsetPx)); + }, + zIndex: 1001, + }) + ); - // Accuracy circle (pulsing-style scale not supported, but visual size helps) + // aaccuracy circle accuracyFeature.setStyle( new Style({ image: new CircleStyle({ From 9804626b67c4a538a4f8ee1d71c41908e7cdb6a9 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Thu, 10 Apr 2025 00:59:35 +1000 Subject: [PATCH 23/34] design, accuracy improvements plus functionality that was broken is working now Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index 313cb9c48..d577e11a6 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -200,14 +200,17 @@ function MapWrapper(props: MapProps) { const directionFeature = new Feature(new Point([0, 0])); const accuracyFeature = new Feature(new Point([0, 0])); - positionSource.addFeatures([accuracyFeature, directionFeature]); - + positionSource.addFeatures([dotFeature, triangleFeature, accuracyFeature]); const updateStyles = ( coords: number[], - headingRadians: number, + heading: number, accuracy: number ) => { - directionFeature.setGeometry(new Point(coords)); + const view = theMap.getView(); + + // geometry + dotFeature.setGeometry(new Point(coords)); + triangleFeature.setGeometry(new Point(coords)); accuracyFeature.setGeometry(new Point(coords)); // Pulsing effect using scaled circle layers From 5189d96326a56bd2a3ec46fb931c1772a187e205 Mon Sep 17 00:00:00 2001 From: Ranisa Gupta Date: Thu, 10 Apr 2025 01:00:02 +1000 Subject: [PATCH 24/34] overall map tested features, updates and cleanuo Signed-off-by: Ranisa Gupta --- app/src/gui/fields/maps/MapWrapper.tsx | 195 +++++++++++-------------- 1 file changed, 84 insertions(+), 111 deletions(-) diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index d577e11a6..04962cb8a 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -27,7 +27,7 @@ import {Draw, Modify} from 'ol/interaction'; import VectorLayer from 'ol/layer/Vector'; import {register} from 'ol/proj/proj4'; import VectorSource from 'ol/source/Vector'; -import {Circle as CircleStyle, Fill, Stroke, Style} from 'ol/style'; +import {Circle as CircleStyle, Fill, Icon, Stroke, Style} from 'ol/style'; import proj4 from 'proj4'; import {useCallback, useEffect, useRef, useState} from 'react'; import {transform} from 'ol/proj'; @@ -104,6 +104,7 @@ function MapWrapper(props: MapProps) { null ); + // ddd draw interaction with pin mark - can be imporved if needed const addDrawInteraction = useCallback( (theMap: Map, props: MapProps) => { const source = new VectorSource(); @@ -111,25 +112,17 @@ function MapWrapper(props: MapProps) { const layer = new VectorLayer({ source: source, style: new Style({ - stroke: new Stroke({ - color: '#33ff33', - width: 4, - }), - image: new CircleStyle({ - radius: 7, - fill: new Fill({color: '#33ff33'}), + image: new Icon({ + src: 'https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi2_hdpi.png', + anchor: [0.5, 1], + scale: 1, }), }), }); - const draw = new Draw({ - source: source, - type: props.featureType || 'Point', - }); - const modify = new Modify({ - source: source, - }); - // add features to map if we're passed any in + const draw = new Draw({source, type: props.featureType || 'Point'}); + const modify = new Modify({source}); + if (props.features && props.features.type) { const parsedFeatures = geoJson.readFeatures(props.features, { dataProjection: 'EPSG:4326', @@ -137,68 +130,52 @@ function MapWrapper(props: MapProps) { }); source.addFeatures(parsedFeatures); - // set the view so that we can see the features - // but don't zoom too much const extent = source.getExtent(); - // don't fit if the extent is infinite because it crashes - if (!extent.includes(Infinity)) { - setFeaturesExtent(extent); - } + if (!extent.includes(Infinity)) setFeaturesExtent(extent); } + draw.on('drawstart', () => { + source.clear(); // 1 pin + }); + theMap.addLayer(layer); theMap.addInteraction(draw); theMap.addInteraction(modify); - setFeaturesLayer(layer); - - draw.on('drawstart', () => { - // clear any existing features if we start drawing again - // could allow up to a fixed number of features - // here by counting - source.clear(); - }); + setFeaturesLayer(layer); // layr clenaup }, - [setFeaturesLayer] + [geoJson, setFeaturesExtent] ); - // auto-clean tracking + // Stop tracking live GPS const stopTracking = () => { if (watchIdRef.current) { - clearInterval(watchIdRef.current); // Stop simulation test + clearInterval(watchIdRef.current); watchIdRef.current = null; } if (map && positionLayer) { - const source = positionLayer.getSource(); - source?.clear(); + positionLayer.getSource()?.clear(); map.removeLayer(positionLayer); setPositionLayer(undefined); } }; - // add this to stoptracking after test works. - // if (positionLayer) { - // const source = positionLayer.getSource(); - // source?.clear(); // clear any leftover features - // map.removeLayer(positionLayer); - // setPositionLayer(undefined); - // } - - // real-time blue dot + accuracy tracking + /// Live location + direction + accuracy style const startLocationTracking = (theMap: Map) => { - stopTracking(); // clean previous layers or intervals + stopTracking(); // Clear previous tracking const view = theMap.getView(); const projection = view.getProjection(); const positionSource = new VectorSource(); - const positionLayer = new VectorLayer({ + const layer = new VectorLayer({ source: positionSource, zIndex: 999, }); - theMap.addLayer(positionLayer); - setPositionLayer(positionLayer); + theMap.addLayer(layer); + setPositionLayer(layer); - const directionFeature = new Feature(new Point([0, 0])); + const dotFeature = new Feature(new Point([0, 0])); + const triangleFeature = new Feature(new Point([0, 0])); const accuracyFeature = new Feature(new Point([0, 0])); positionSource.addFeatures([dotFeature, triangleFeature, accuracyFeature]); const updateStyles = ( @@ -213,21 +190,17 @@ function MapWrapper(props: MapProps) { triangleFeature.setGeometry(new Point(coords)); accuracyFeature.setGeometry(new Point(coords)); - // Pulsing effect using scaled circle layers - const pulseOuter = new Style({ - image: new CircleStyle({ - radius: 18, - fill: new Fill({color: 'rgba(26, 115, 232, 0.2)'}), - }), - }); - - const pulseInner = new Style({ - image: new CircleStyle({ - radius: 12, - fill: new Fill({color: '#1a73e8'}), - stroke: new Stroke({color: '#ffffff', width: 3}), - }), - }); + // blue ocation circle) + dotFeature.setStyle( + new Style({ + image: new CircleStyle({ + radius: 14, + fill: new Fill({color: '#1a73e8'}), + stroke: new Stroke({color: '#A19F9FFF', width: 3}), + }), + zIndex: 1000, + }) + ); // directional triangle triangleFeature.setStyle( @@ -264,7 +237,6 @@ function MapWrapper(props: MapProps) { ); }; - // doing current location for initial zoom navigator.geolocation.getCurrentPosition( pos => { const coords = transform( @@ -276,7 +248,7 @@ function MapWrapper(props: MapProps) { view.setZoom(17); }, err => { - console.error('Initial GPS fetch failed', err); + console.error('Initial GPS error', err); props.setNoPermission(true); }, {enableHighAccuracy: true} @@ -297,11 +269,7 @@ function MapWrapper(props: MapProps) { console.error('Live GPS error', err); props.setNoPermission(true); }, - { - enableHighAccuracy: true, - maximumAge: 0, - timeout: 10000, - } + {enableHighAccuracy: true, maximumAge: 0, timeout: 10000} ); }; @@ -315,9 +283,7 @@ function MapWrapper(props: MapProps) { }, [mapOpen, map]); useEffect(() => { - if (map) { - addDrawInteraction(map, props); - } + if (map) addDrawInteraction(map, props); }, [map]); // save cleanr and close @@ -357,52 +323,59 @@ function MapWrapper(props: MapProps) { } }; + // ppen map const handleClickOpen = () => { if (props.fallbackCenter) { notify.showWarning( - 'Using default map location - unable to determine current location and no center location configured.' + 'Using default map location - no current GPS or center.' ); } - // We always provide a center, so it's always safe to open the map setMapOpen(true); + setTimeout(() => { + if (map) { + startLocationTracking(map); + } + }, 300); // adding delaye intentionally so map tracking instantiastes }; return ( -
- {!props.isLocationSelected ? ( -
+ ); } From 949fa06b75f5dd0c8c38bad8690c90bc6aba3be9 Mon Sep 17 00:00:00 2001 From: Steve Cassidy Date: Thu, 17 Apr 2025 13:42:59 +1000 Subject: [PATCH 33/34] Convert heading to radians for display Signed-off-by: Steve Cassidy --- app/src/gui/components/map/map-component.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/gui/components/map/map-component.tsx b/app/src/gui/components/map/map-component.tsx index bc2e741a8..790a72dba 100644 --- a/app/src/gui/components/map/map-component.tsx +++ b/app/src/gui/components/map/map-component.tsx @@ -228,14 +228,16 @@ export const MapComponent = (props: MapComponentProps) => { }) ); - // directional navigation traingle + // heading is in degrees + const headingRadians = (heading * Math.PI) / 180; + // directional navigation triangle triangleFeature.setGeometry(new Point(coords)); triangleFeature.setStyle( new Style({ image: new RegularShape({ points: 3, radius: 12, - rotation: heading + Math.PI, + rotation: headingRadians + Math.PI, angle: Math.PI, fill: new Fill({color: '#1a73e8'}), stroke: new Stroke({color: 'white', width: 2}), @@ -243,8 +245,8 @@ export const MapComponent = (props: MapComponentProps) => { geometry: () => { const px = theMap.getPixelFromCoordinate(coords); const offset = 23; - const dx = offset * Math.sin(heading); - const dy = -offset * Math.cos(heading); + const dx = offset * Math.sin(headingRadians); + const dy = -offset * Math.cos(headingRadians); const newPx = [px[0] + dx, px[1] + dy]; return new Point(theMap.getCoordinateFromPixel(newPx)); }, From d6ab86bf7b8341fff5dd582361d7d7d6688f7452 Mon Sep 17 00:00:00 2001 From: Steve Cassidy Date: Mon, 21 Apr 2025 18:39:08 +1000 Subject: [PATCH 34/34] centre button uses current location Signed-off-by: Steve Cassidy --- app/src/gui/components/map/center-control.tsx | 6 +- app/src/gui/components/map/map-component.tsx | 102 ++++++------------ 2 files changed, 37 insertions(+), 71 deletions(-) diff --git a/app/src/gui/components/map/center-control.tsx b/app/src/gui/components/map/center-control.tsx index 1fefb5ae1..2a51fcd18 100644 --- a/app/src/gui/components/map/center-control.tsx +++ b/app/src/gui/components/map/center-control.tsx @@ -8,12 +8,12 @@ import src from '../../../target.svg'; * Creates a custom control button that centers the map view to a specified coordinate. * * @param {View} view - The map view instance to be controlled. - * @param {Coordinate} center - The coordinate to which the map view should be centered. + * @param {() => void} center - Callback to center the map view. * @returns {Control} - The custom control instance. */ export const createCenterControl = ( view: View, - center: Coordinate + centerMap: () => void ): Control => { const button = document.createElement('button'); button.className = 'ol-center-button'; @@ -28,7 +28,7 @@ export const createCenterControl = ( ); const handleClick = () => { - view.setCenter(center); + centerMap(); }; button.addEventListener('click', handleClick); diff --git a/app/src/gui/components/map/map-component.tsx b/app/src/gui/components/map/map-component.tsx index 790a72dba..f7b87c48a 100644 --- a/app/src/gui/components/map/map-component.tsx +++ b/app/src/gui/components/map/map-component.tsx @@ -42,6 +42,7 @@ import {VectorTileStore} from './tile-source'; import Feature from 'ol/Feature'; import {Point} from 'ol/geom'; import CircleStyle from 'ol/style/Circle'; +import {Geolocation, Position} from '@capacitor/geolocation'; const defaultMapProjection = 'EPSG:3857'; const MAX_ZOOM = 20; @@ -124,8 +125,9 @@ export const MapComponent = (props: MapComponentProps) => { }, [props.center, currentPosition]); const positionLayerRef = useRef(); - const watchIdRef = useRef(null); + const watchIdRef = useRef(null); const fakeIntervalRef = useRef(null); + const liveLocationRef = useRef(null); /** * Initializes the map instance with base tile layers and zoom controls. @@ -167,7 +169,8 @@ export const MapComponent = (props: MapComponentProps) => { * - Blue dot at user location with directional traingular and accuracy circle. which would wokk on rela-time gps location tracking * * Also supports fake GPS simulation for browser testing. - */ const startLiveCursor = (theMap: Map) => { + */ + const startLiveCursor = (theMap: Map) => { // Clean up before re-adding if (positionLayerRef.current) { theMap.removeLayer(positionLayerRef.current); @@ -175,8 +178,9 @@ export const MapComponent = (props: MapComponentProps) => { positionLayerRef.current = undefined; } if (watchIdRef.current !== null) { - navigator.geolocation.clearWatch(watchIdRef.current); - watchIdRef.current = null; + Geolocation.clearWatch({id: watchIdRef.current}).then(() => { + watchIdRef.current = null; + }); } // to be removed later after testing @TODO: RG if (fakeIntervalRef.current !== null) { @@ -255,37 +259,29 @@ export const MapComponent = (props: MapComponentProps) => { }; if (!window.__USE_FAKE_GPS__) { - // GPS tracking for real-time (mobile/tablet etc.) - navigator.geolocation.getCurrentPosition( - pos => { - const coords = transform( - [pos.coords.longitude, pos.coords.latitude], - 'EPSG:4326', - projection - ); - view.setCenter(coords); - view.setZoom(17); - }, - () => {}, - {enableHighAccuracy: true} - ); + // Add a watch on position, update our live cursor when it changes - watchIdRef.current = navigator.geolocation.watchPosition( - pos => { + Geolocation.watchPosition({enableHighAccuracy: true}, (position, err) => { + if (err) { + console.error(err); + return; + } + if (position) { + liveLocationRef.current = position; const coords = transform( - [pos.coords.longitude, pos.coords.latitude], + [position.coords.longitude, position.coords.latitude], 'EPSG:4326', projection ); updateCursor( coords, - pos.coords.heading ?? 0, - pos.coords.accuracy ?? 30 + position.coords.heading ?? 0, + position.coords.accuracy ?? 30 ); - }, - err => console.error('Live GPS error:', err), - {enableHighAccuracy: true} - ); + } + }).then(id => { + watchIdRef.current = id; + }); } else { /** * Fake GPS Simulation (for browser testing only) @@ -309,46 +305,16 @@ export const MapComponent = (props: MapComponentProps) => { } }; - // /** - // * Add a marker to the map at the current location - // * TODO ranisa to update this based on integration of live cursor. - // * @param theMap the map element - // */ - // const addCurrentLocationMarker = (theMap: Map) => { - // const source = new VectorSource(); - // const geoJson = new GeoJSON(); - - // const stroke = new Stroke({color: '#e2ebef', width: 2}); - // const layer = new VectorLayer({ - // source: source, - // style: new Style({ - // image: new Circle({ - // radius: 10, - // fill: new Fill({color: '#465ddf90'}), - // stroke: stroke, - // }), - // }), - // }); - - // // only do this if we have a real map_center - // if (mapCenter) { - // const centerFeature = { - // type: 'Feature', - // geometry: { - // type: 'Point', - // coordinates: mapCenter, - // }, - // }; - - // // there is only one feature but readFeature return type is odd and readFeatures works for singletons - // const theFeatures = geoJson.readFeatures(centerFeature, { - // dataProjection: 'EPSG:4326', - // featureProjection: theMap.getView().getProjection(), - // }); - // source.addFeature(theFeatures[0]); - // theMap.addLayer(layer); - // } - // }; + // center the map on the current location + const centerMap = () => { + if (map && liveLocationRef.current) { + const coords = getCoordinates(liveLocationRef.current); + if (coords) { + const center = transform(coords, 'EPSG:4326', defaultMapProjection); + map.getView().setCenter(center); + } + } + }; // when we have a location and a map, add the 'here' marker to the map useEffect(() => { @@ -356,7 +322,7 @@ export const MapComponent = (props: MapComponentProps) => { startLiveCursor(map); if (mapCenter) { const center = transform(mapCenter, 'EPSG:4326', defaultMapProjection); - map.addControl(createCenterControl(map.getView(), center)); + map.addControl(createCenterControl(map.getView(), centerMap)); // we set the map extent if we were given one or if not, // set the map center which will either have been passed