summaryrefslogtreecommitdiff
path: root/frontend/src/components/CityMap.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/CityMap.tsx')
-rw-r--r--frontend/src/components/CityMap.tsx98
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;