diff options
Diffstat (limited to 'frontend/src/components/CityMap.tsx')
| -rw-r--r-- | frontend/src/components/CityMap.tsx | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/frontend/src/components/CityMap.tsx b/frontend/src/components/CityMap.tsx new file mode 100644 index 0000000..b61e69a --- /dev/null +++ b/frontend/src/components/CityMap.tsx @@ -0,0 +1,98 @@ +import { Feature, Map, View } from "ol"; +import { defaults } from "ol/control"; +import { Point } from "ol/geom"; +import TileLayer from "ol/layer/Tile"; +import VectorLayer from "ol/layer/Vector"; +import { fromLonLat, transformExtent } from "ol/proj"; +import { OSM } from "ol/source"; +import VectorSource from "ol/source/Vector"; +import Fill from "ol/style/Fill"; +import Icon from "ol/style/Icon"; +import Stroke from "ol/style/Stroke"; +import Style from "ol/style/Style"; +import Text from "ol/style/Text"; +import { useEffect, useRef } from "react"; +import robotMarker from "../assets/robot-head.svg"; +import "../styles/dashboard.css"; +import type { Robot } from "../types/robot"; + +type Props = { + robots: Robot[]; +}; + +function CityMap({ robots }: Props) { + const mapRef = useRef<HTMLDivElement | null>(null); + const mapInstance = useRef<Map | null>(null); + const markerLayer = useRef<VectorLayer<VectorSource> | null>(null); + + useEffect(() => { + if (mapInstance.current) return; // prevent re-init + + markerLayer.current = new VectorLayer({ + source: new VectorSource(), + }); + + const leipzigCenter = fromLonLat([12.375919, 51.340863]); + const leipzigArea = transformExtent( + // west, south, east, north; transform from WGS84 lat-long system to Web Mercator system + [12.22, 51.26, 12.54, 51.44], + "EPSG:4326", + "EPSG:3857" + ); + + mapInstance.current = new Map({ + target: mapRef.current as HTMLDivElement, + controls: defaults({ attribution: false }), + layers: [ + new TileLayer({ + source: new OSM(), + }), + markerLayer.current, + ], + view: new View({ + center: leipzigCenter, + zoom: 15, + extent: leipzigArea, + }), + }); + }, []); + + useEffect(() => { + if (!markerLayer.current) return; + + const source = markerLayer.current.getSource(); + if (!source) return; + + source.clear(); + + robots.forEach((robot) => { + const feature = new Feature({ + geometry: new Point(fromLonLat([robot?.lon, robot?.lat])), + robotId: robot?.id, + }); + + feature.setStyle( + new Style({ + image: new Icon({ + src: robotMarker, + scale: 1.2, + anchor: [0.5, 0.4], // anchor label bottom center of icon + }), + text: new Text({ + text: robot?.name, + font: "bold 14px sans-serif", + fill: new Fill({ color: "#000" }), + stroke: new Stroke({ color: "#fff", width: 3 }), // outline for better visibility + offsetY: -25, // move label above the marker + }), + }) + ); + + source.addFeature(feature); + }); + }, [robots]); + + return <div id="map" ref={mapRef}></div>; +} + +export default CityMap; |
