From 7ceece0ae150368224c844d41991e33088fd55ad Mon Sep 17 00:00:00 2001 From: Luke McFarlane Date: Wed, 11 Dec 2024 14:25:14 +1100 Subject: [PATCH] created add center control, current location, location marker functions, used in both map components Signed-off-by: Luke McFarlane --- app/src/gui/components/map/center-control.tsx | 43 ------------ .../gui/components/notebook/overview_map.tsx | 66 +++---------------- app/src/gui/fields/maps/MapWrapper.tsx | 21 ++++-- app/src/lib/map/center-control.tsx | 35 ++++++++++ app/src/lib/map/current-location.ts | 60 +++++++++++++++++ 5 files changed, 120 insertions(+), 105 deletions(-) delete mode 100644 app/src/gui/components/map/center-control.tsx create mode 100644 app/src/lib/map/center-control.tsx create mode 100644 app/src/lib/map/current-location.ts diff --git a/app/src/gui/components/map/center-control.tsx b/app/src/gui/components/map/center-control.tsx deleted file mode 100644 index 1fefb5ae1..000000000 --- a/app/src/gui/components/map/center-control.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import {Control} from 'ol/control'; -import {Coordinate} from 'ol/coordinate'; -import {View} from 'ol'; -import {CreateDomIcon} from './dom-icon'; -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. - * @returns {Control} - The custom control instance. - */ -export const createCenterControl = ( - view: View, - center: Coordinate -): Control => { - const button = document.createElement('button'); - button.className = 'ol-center-button'; - - button.appendChild( - CreateDomIcon({ - src, - width: 24, - height: 24, - alt: 'Center map', - }) - ); - - const handleClick = () => { - view.setCenter(center); - }; - - button.addEventListener('click', handleClick); - - const element = document.createElement('div'); - element.className = 'ol-center-box'; - element.appendChild(button); - - return new Control({ - element: element, - }); -}; diff --git a/app/src/gui/components/notebook/overview_map.tsx b/app/src/gui/components/notebook/overview_map.tsx index 58d9bfee2..d0343b535 100644 --- a/app/src/gui/components/notebook/overview_map.tsx +++ b/app/src/gui/components/notebook/overview_map.tsx @@ -31,16 +31,18 @@ import GeoJSON from 'ol/format/GeoJSON'; import TileLayer from 'ol/layer/Tile'; import VectorLayer from 'ol/layer/Vector'; import Map from 'ol/Map'; -import {transform} from 'ol/proj'; import {OSM} from 'ol/source'; import VectorSource from 'ol/source/Vector'; -import {Fill, RegularShape, Stroke, Style} from 'ol/style'; +import {Fill, Stroke, Style} from 'ol/style'; import CircleStyle from 'ol/style/Circle'; import {useCallback, useMemo, useRef, useState} from 'react'; import {Link} from 'react-router-dom'; import * as ROUTES from '../../../constants/routes'; -import {createCenterControl} from '../map/center-control'; -import {Geolocation} from '@capacitor/geolocation'; +import {addCenterControl} from '../../../lib/map/center-control'; +import { + addCurrentLocationMarker, + getCurrentLocation, +} from '../../../lib/map/current-location'; interface OverviewMapProps { uiSpec: ProjectUIModel; @@ -140,13 +142,7 @@ export const OverviewMap = (props: OverviewMapProps) => { const mapRef = useRef(); mapRef.current = map; - const {data: map_center, isLoading: loadingLocation} = useQuery({ - queryKey: ['current_location'], - queryFn: async (): Promise<[number, number]> => { - const position = await Geolocation.getCurrentPosition(); - return [position.coords.longitude, position.coords.latitude]; - }, - }); + const {data: currentLocation} = getCurrentLocation(); /** * Create the OpenLayers map element @@ -182,43 +178,6 @@ export const OverviewMap = (props: OverviewMapProps) => { * * @param map the map element */ - const addCurrentLocationMarker = (map: Map) => { - const source = new VectorSource(); - const geoJson = new GeoJSON(); - - const stroke = new Stroke({color: 'black', width: 2}); - const layer = new VectorLayer({ - source: source, - style: new Style({ - image: new RegularShape({ - stroke: stroke, - points: 4, - radius: 10, - radius2: 0, - angle: 0, - }), - }), - }); - - // only do this if we have a real map_center - if (map_center) { - const centerFeature = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: map_center, - }, - }; - - source.addFeature( - geoJson.readFeature(centerFeature, { - dataProjection: 'EPSG:4326', - featureProjection: map.getView().getProjection(), - }) - ); - map.addLayer(layer); - } - }; /** * Add the features to the map and set the map view to @@ -263,14 +222,9 @@ export const OverviewMap = (props: OverviewMapProps) => { map.addLayer(layer); }; - // when we have a location and a map, add the 'here' marker to the map - if (!loadingLocation && map) { - addCurrentLocationMarker(map); - if (map_center) { - const center = transform(map_center, 'EPSG:4326', defaultMapProjection); - // add the 'here' button to go to the current location - map.addControl(createCenterControl(map.getView(), center)); - } + if (map && currentLocation) { + addCurrentLocationMarker(map, currentLocation); + addCenterControl(map, currentLocation); } // when we have features, add them to the map diff --git a/app/src/gui/fields/maps/MapWrapper.tsx b/app/src/gui/fields/maps/MapWrapper.tsx index ed6d80c30..0b61186c4 100644 --- a/app/src/gui/fields/maps/MapWrapper.tsx +++ b/app/src/gui/fields/maps/MapWrapper.tsx @@ -17,7 +17,7 @@ * Description: * Internals of map generation for MapFormField */ -import React, {useState, useRef, useCallback} from 'react'; +import {useState, useRef, useCallback, useEffect} from 'react'; // openlayers import Map from 'ol/Map'; @@ -67,8 +67,12 @@ interface MapProps extends ButtonProps { import {AppBar, Dialog, IconButton, Toolbar, Typography} from '@mui/material'; import Feature from 'ol/Feature'; import {Geometry} from 'ol/geom'; -import {createCenterControl} from '../../components/map/center-control'; +import {addCenterControl} from '../../../lib/map/center-control'; import {useNotification} from '../../../context/popup'; +import { + addCurrentLocationMarker, + getCurrentLocation, +} from '../../../lib/map/current-location'; const styles = { mapContainer: { @@ -88,6 +92,15 @@ function MapWrapper(props: MapProps) { const defaultMapProjection = 'EPSG:3857'; const geoJson = new GeoJSON(); + const {data: currentLocation} = getCurrentLocation(); + + useEffect(() => { + if (map && currentLocation) { + addCenterControl(map, currentLocation); + addCurrentLocationMarker(map, currentLocation); + } + }, [map, currentLocation]); + // notifications const notify = useNotification(); @@ -142,10 +155,6 @@ function MapWrapper(props: MapProps) { controls: [new Zoom()], }); - theMap.addControl(createCenterControl(theMap.getView(), center)); - - theMap.getView().setCenter(center); - return theMap; }, [] diff --git a/app/src/lib/map/center-control.tsx b/app/src/lib/map/center-control.tsx new file mode 100644 index 000000000..a86233ca7 --- /dev/null +++ b/app/src/lib/map/center-control.tsx @@ -0,0 +1,35 @@ +import {Control} from 'ol/control'; +import {Coordinate} from 'ol/coordinate'; +import Map from 'ol/Map'; +import {CreateDomIcon} from '../../gui/components/map/dom-icon'; +import src from '../../target.svg'; +import {transform} from 'ol/proj'; + +export const addCenterControl = (map: Map, coords: Coordinate) => { + const element = document.createElement('div'); + element.className = 'ol-center-box'; + + const button = document.createElement('button'); + button.className = 'ol-center-button'; + + button.appendChild( + CreateDomIcon({ + src, + width: 24, + height: 24, + alt: 'Center map', + }) + ); + + button.addEventListener('click', () => { + map.getView().setCenter(transform(coords, 'EPSG:4326', 'EPSG:3857')); + }); + + element.appendChild(button); + + map.addControl( + new Control({ + element: element, + }) + ); +}; diff --git a/app/src/lib/map/current-location.ts b/app/src/lib/map/current-location.ts new file mode 100644 index 000000000..feb6aa7cc --- /dev/null +++ b/app/src/lib/map/current-location.ts @@ -0,0 +1,60 @@ +import GeoJSON from 'ol/format/GeoJSON'; +import VectorSource from 'ol/source/Vector'; +import Map from 'ol/Map'; +import VectorLayer from 'ol/layer/Vector'; +import {Fill, Style} from 'ol/style'; +import CircleStyle from 'ol/style/Circle'; +import {useQuery} from '@tanstack/react-query'; +import {Geolocation} from '@capacitor/geolocation'; + +export const getCurrentLocation = () => + useQuery({ + queryKey: ['current_location'], + queryFn: async (): Promise<[number, number]> => { + const { + coords: {longitude, latitude}, + } = await Geolocation.getCurrentPosition(); + return [longitude, latitude]; + }, + }); + +export const addCurrentLocationMarker = ( + map: Map, + coordinates: [number, number] +) => { + const source = new VectorSource(); + + source.addFeature( + new GeoJSON().readFeature( + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates, + }, + }, + { + dataProjection: 'EPSG:4326', + featureProjection: map.getView().getProjection(), + } + ) + ); + + for (const {radius, color} of [ + {radius: 14, color: 'rgba(59, 130, 246, 0.1)'}, + {radius: 8, color: 'rgb(255, 255, 255)'}, + {radius: 6, color: 'rgb(59, 130, 246)'}, + ]) { + map.addLayer( + new VectorLayer({ + source: source, + style: new Style({ + image: new CircleStyle({ + radius, + fill: new Fill({color}), + }), + }), + }) + ); + } +};