201 lines
5.7 KiB
TypeScript
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='© <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,
|
|
})}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|