diff --git a/app/public/assets/icon/circle-medium.svg b/app/public/assets/icon/circle-medium.svg
new file mode 100644
index 0000000000..28796ab32e
--- /dev/null
+++ b/app/public/assets/icon/circle-medium.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/components/map/components/DrawControls.tsx b/app/src/components/map/components/DrawControls.tsx
index 2d5aefdaa1..ab0d0f4839 100644
--- a/app/src/components/map/components/DrawControls.tsx
+++ b/app/src/components/map/components/DrawControls.tsx
@@ -4,6 +4,7 @@ import L from 'leaflet';
import 'leaflet-draw';
import 'leaflet-draw/dist/leaflet.draw.css';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
+import { coloredPoint } from 'utils/mapUtils';
/**
* Custom subset of `L.Control.DrawConstructorOptions` that omits `edit.featureGroup` as this will be added automatically
@@ -89,17 +90,32 @@ const DrawControls = forwardRef {
const featureGroup = getFeatureGroup();
+ const CustomMarker = L.Icon.extend({
+ // The preview icon rendered when you are in the process of adding a marker to the map
+ options: {
+ iconUrl: 'assets/icon/circle-medium.svg',
+ iconRetinaUrl: 'assets/icon/circle-medium.svg',
+ iconSize: new L.Point(36, 36),
+ iconAnchor: new L.Point(18, 18),
+ popupAnchor: [18, 18],
+ shadowUrl: null
+ }
+ });
+
const drawOptions: L.Control.DrawConstructorOptions = {
+ draw: {
+ ...options?.draw,
+ marker: {
+ icon: new CustomMarker()
+ }
+ },
edit: {
...options?.edit,
featureGroup: featureGroup
- }
+ },
+ position: options?.position || 'topright'
};
- drawOptions.draw = { ...options?.draw };
-
- drawOptions.position = drawOptions?.position || 'topright';
-
return new L.Control.Draw(drawOptions);
};
@@ -170,7 +186,7 @@ const DrawControls = forwardRef> = (pro
const layerControls: ReactElement[] = [];
- props.layers.forEach((layer, index) => {
+ props.layers.forEach((layer) => {
if (!layer.features?.length) {
return;
}
@@ -57,12 +58,12 @@ const StaticLayers: React.FC> = (pro
return new L.Circle([latlng.lat, latlng.lng], feature.properties.radius);
}
- return new L.Marker([latlng.lat, latlng.lng]);
+ return coloredPoint({ latlng });
}}
data={item.geoJSON}
{...item.GeoJSONProps}>
{item.tooltip && (
-
+
{item.tooltip}
)}
diff --git a/app/src/features/surveys/observations/ObservationsMap.tsx b/app/src/features/surveys/observations/ObservationsMap.tsx
index 79b0598024..fb530ee3b9 100644
--- a/app/src/features/surveys/observations/ObservationsMap.tsx
+++ b/app/src/features/surveys/observations/ObservationsMap.tsx
@@ -2,23 +2,25 @@ import { mdiRefresh } from '@mdi/js';
import Icon from '@mdi/react';
import { IconButton } from '@mui/material';
import Box from '@mui/material/Box';
-import { square } from '@turf/turf';
import BaseLayerControls from 'components/map/components/BaseLayerControls';
import { SetMapBounds } from 'components/map/components/Bounds';
import FullScreenScrollingEventHandler from 'components/map/components/FullScreenScrollingEventHandler';
import { MapBaseCss } from 'components/map/components/MapBaseCss';
-import { MAP_DEFAULT_CENTER } from 'constants/spatial';
+import StaticLayers from 'components/map/components/StaticLayers';
+import { ALL_OF_BC_BOUNDARY, MAP_DEFAULT_CENTER } from 'constants/spatial';
import { ObservationsContext } from 'contexts/observationsContext';
-import { Position } from 'geojson';
+import { SurveyContext } from 'contexts/surveyContext';
+import { Feature, Position } from 'geojson';
import { LatLngBoundsExpression } from 'leaflet';
-import { useCallback, useContext, useMemo, useState } from 'react';
+import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { GeoJSON, LayersControl, MapContainer as LeafletMapContainer } from 'react-leaflet';
-import { calculateFeatureBoundingBox, latLngBoundsFromBoundingBox } from 'utils/mapBoundaryUploadHelpers';
+import { calculateUpdatedMapBounds } from 'utils/mapBoundaryUploadHelpers';
import { coloredPoint, INonEditableGeometries } from 'utils/mapUtils';
import { v4 as uuidv4 } from 'uuid';
const ObservationsMap = () => {
const observationsContext = useContext(ObservationsContext);
+ const surveyContext = useContext(SurveyContext);
const surveyObservations: INonEditableGeometries[] = useMemo(() => {
const observations = observationsContext.observationsDataLoader.data?.surveyObservations;
@@ -58,29 +60,59 @@ const ObservationsMap = () => {
});
}, [observationsContext.observationsDataLoader.data]);
- const getDefaultMapBounds = useCallback((): LatLngBoundsExpression | undefined => {
- const features = surveyObservations.map((observation) => observation.feature);
- const boundingBox = calculateFeatureBoundingBox(features);
+ const studyAreaFeatures: Feature[] = useMemo(() => {
+ const locations = surveyContext.surveyDataLoader.data?.surveyData.locations;
+ if (!locations) {
+ return [];
+ }
- if (!boundingBox) {
- return;
+ return locations.flatMap((item) => item.geojson);
+ }, [surveyContext.surveyDataLoader.data]);
+
+ const sampleSiteFeatures: Feature[] = useMemo(() => {
+ const sites = surveyContext.sampleSiteDataLoader.data?.sampleSites;
+ if (!sites) {
+ return [];
}
- return latLngBoundsFromBoundingBox(square(boundingBox));
- }, [surveyObservations]);
+ return sites.map((item) => item.geojson);
+ }, [surveyContext.sampleSiteDataLoader.data]);
- const [bounds, setBounds] = useState(getDefaultMapBounds());
+ const getDefaultMapBounds = useCallback((): LatLngBoundsExpression | undefined => {
+ const features = surveyObservations.map((observation) => observation.feature);
+ return calculateUpdatedMapBounds([...features, ...studyAreaFeatures, ...sampleSiteFeatures]);
+ }, [surveyObservations, studyAreaFeatures, sampleSiteFeatures]);
+
+ // set default bounds to encompass all of BC
+ const [bounds, setBounds] = useState(
+ calculateUpdatedMapBounds([ALL_OF_BC_BOUNDARY])
+ );
const zoomToBoundaryExtent = useCallback(() => {
setBounds(getDefaultMapBounds());
}, [getDefaultMapBounds]);
+ useEffect(() => {
+ // Once all data loaders have finished loading it will zoom the map to include all features
+ if (
+ !surveyContext.surveyDataLoader.isLoading &&
+ !surveyContext.sampleSiteDataLoader.isLoading &&
+ !observationsContext.observationsDataLoader.isLoading
+ ) {
+ zoomToBoundaryExtent();
+ }
+ }, [
+ observationsContext.observationsDataLoader.isLoading,
+ surveyContext.sampleSiteDataLoader.isLoading,
+ surveyContext.surveyDataLoader.isLoading,
+ zoomToBoundaryExtent
+ ]);
+
return (
{
coloredPoint({ feature, latlng })}>
+ pointToLayer={(_, latlng) => coloredPoint({ latlng, fillColor: '#F28C28' })}>
{nonEditableGeo.popupComponent}
))}
+ ({ geoJSON: feature, tooltip: <>Study Area> }))
+ },
+ {
+ layerName: 'Sample Sites',
+ features: sampleSiteFeatures.map((feature) => ({ geoJSON: feature, tooltip: <>Sample Site> }))
+ }
+ ]}
+ />
- {surveyObservations.length > 0 && (
+ {(surveyObservations.length > 0 || studyAreaFeatures.length > 0 || sampleSiteFeatures.length > 0) && (
=> {
return new L.CircleMarker(point.latlng, {
radius: 6,
fillOpacity: 1,
- fillColor: point.fillColor ?? '#006edc',
+ fillColor: point.fillColor ?? MapDefaultBlue,
color: point.borderColor ?? '#ffffff',
weight: 1
});