Files
fuel-wizard/frontend/app/components/map/CustomMapContent.tsx

201 lines
5.7 KiB
TypeScript

import {
Badge,
Container,
Stack,
Title,
useMantineColorScheme,
useMantineTheme,
} from "@mantine/core";
import { Feature } from "geojson";
import { useEffect, useState } from "react";
import {
Circle,
GeoJSON,
Marker,
Popup,
TileLayer,
useMap,
useMapEvent,
} from "react-leaflet";
import { SuburbBoundary } from "../../../generated";
import { Coordinate } from "../../../models/Coordinate";
import {
defaultIcon,
limeIcon,
redIcon,
useTileFilter,
whiteIcon,
} from "../../../utils/customMapUtils";
import { isSameGeographicalPoint } from "../../../utils/pointUtils";
import { ControlledMarker } from "./ControlledMarker";
import { CustomMapPoint } from "./CustomMap";
export type CustomMapContentProps = {
points?: CustomMapPoint[];
clickToPlaceMarker?: boolean;
onMarkerPlaced?: (coordinates: Coordinate) => void;
markerPosition?: Coordinate;
suburbBoundary?: SuburbBoundary;
circle?: { centre: Coordinate; radius: number };
routeGeoJson?: Feature<GeoJSON.LineString, object>;
selectedPoint?: CustomMapPoint;
};
export default function CustomMapContent(props: CustomMapContentProps) {
const [firstPoint, ...remainingPoints] = props.points || [];
const [markerPosition, setMarkerPosition] = useState<Coordinate | null>(null);
const theme = useMantineTheme();
const mantineColourScheme = useMantineColorScheme();
const map = useMap();
useEffect(() => {
if (props.selectedPoint) {
map.flyTo({
lat: props.selectedPoint.lat,
lng: props.selectedPoint.long,
});
}
}, [props.selectedPoint]);
//update the marker position from the parent if `props.markerPosition` changes
useEffect(() => {
if (props.markerPosition) {
setMarkerPosition(props.markerPosition);
}
}, [props.markerPosition]);
const handleMarkerPlaced = (coordinates: Coordinate) => {
setMarkerPosition(coordinates); //update the state with new marker coordinates
if (props.onMarkerPlaced) {
props.onMarkerPlaced(coordinates); //notify parent of new coordinates
}
};
useTileFilter(mantineColourScheme.colorScheme);
useEffect(() => {
if (props.suburbBoundary) {
map.setView(
[
props.suburbBoundary.centroidCoordinates?.latitude as number,
props.suburbBoundary.centroidCoordinates?.longitude as number,
],
12,
{ animate: true, duration: 1 }
);
}
}, [props.suburbBoundary, map]);
useMapEvent("click", (e) => {
if (props.clickToPlaceMarker) {
const { lat, lng } = e.latlng;
if (props.onMarkerPlaced) {
handleMarkerPlaced({ lat, long: lng }); //pass the coordinates back to the parent
}
}
});
return (
<>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://tile.fuelwizard.au/tile/{z}/{x}/{y}.png"
/>
{props.routeGeoJson && (
<GeoJSON
key={JSON.stringify(props.routeGeoJson)}
data={props.routeGeoJson}
style={() => ({
color:
mantineColourScheme.colorScheme === "dark"
? theme.primaryColor
: "#3388ff",
weight: 4,
opacity: 1,
fillOpacity: 0,
})}
/>
)}
{props.circle?.centre && props.circle.radius && (
<Circle
center={[props.circle.centre.lat, props.circle.centre.long]}
radius={props.circle.radius}
pathOptions={{
color:
mantineColourScheme.colorScheme === "dark"
? theme.primaryColor
: "#3388ff",
fillColor:
mantineColourScheme.colorScheme === "dark"
? theme.primaryColor
: "#3388ff",
fillOpacity: 0.2,
}}
/>
)}
{markerPosition && (
<Marker
position={[markerPosition.lat, markerPosition.long]}
icon={redIcon}
>
<Popup>
<Container p={"xs"} w={200}>
<Stack align="center">
<Title order={3}>Click to move</Title>
<Badge>Latitude: {markerPosition.lat.toFixed(4)}</Badge>
<Badge>Longitude: {markerPosition.long.toFixed(4)}</Badge>
</Stack>
</Container>
</Popup>
</Marker>
)}
{firstPoint && remainingPoints && (
<ControlledMarker
key={0}
zIndexOffset={remainingPoints.length}
position={[firstPoint.lat, firstPoint.long]}
icon={limeIcon}
forceOpen={isSameGeographicalPoint(props.selectedPoint, firstPoint)}
>
{firstPoint.popup && <Popup>{firstPoint.popup}</Popup>}
</ControlledMarker>
)}
{remainingPoints &&
remainingPoints.map((point, index) => (
<ControlledMarker
key={index + 1}
position={[point.lat, point.long]}
icon={
mantineColourScheme.colorScheme === "dark"
? whiteIcon
: defaultIcon
}
forceOpen={isSameGeographicalPoint(props.selectedPoint, point)}
>
{point.popup && <Popup>{point.popup}</Popup>}
</ControlledMarker>
))}
{props.suburbBoundary && (
<GeoJSON
key={props.suburbBoundary.id}
data={
props.suburbBoundary.geometry &&
JSON.parse(props.suburbBoundary.geometry)
}
style={() => ({
color:
mantineColourScheme.colorScheme === "dark"
? theme.primaryColor
: "#3388ff",
weight: 2,
opacity: 1,
fillOpacity: 0.2,
})}
/>
)}
</>
);
}