summaryrefslogtreecommitdiff
path: root/frontend/src/components/CityMap.tsx
blob: b61e69a9f799e1b5a646f18398b1a2a80d5641f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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;