map.templ
1package components
2
3import "encoding/json"
4
5var mapHandle = templ.NewOnceHandle()
6var routeHandle = templ.NewOnceHandle()
7
8func toJSON(bbox [4]float64) string {
9 v, _ := json.Marshal(bbox)
10 return string(v)
11}
12
13templ Map(style string, bbox [4]float64, class string) {
14 <script type="module">
15 import { Map } from 'https://esm.sh/maplibre-gl@5';
16
17 function loadMapCSS() {
18 if (document.querySelector('link[href*="maplibre-gl.css"]')) return;
19 const link = document.createElement('link');
20 link.rel = 'stylesheet';
21 link.href = 'https://esm.sh/maplibre-gl@5/dist/maplibre-gl.css';
22 document.head.appendChild(link);
23 }
24
25 class RTWmap extends HTMLElement {
26 map;
27
28 constructor() {
29 super();
30 loadMapCSS();
31 this.addEventListener('map:context', (event) => {
32 console.log("received map:context");
33 event.detail.cb(this.map);
34 })
35 }
36 connectedCallback() {
37 this.style.cssText = 'display: block; width: 100%; height: 100%; overflow: hidden; border-radius: inherit;';
38
39 const elem = document.createElement('div');
40 elem.style.cssText = 'width:100%;height:100%;overflow:hidden;'
41 this.appendChild(elem);
42
43 const styleURL = this.getAttribute('map-style');
44 this.map = new Map({
45 container: elem,
46 style: styleURL,
47 center: [0, 0],
48 zoom: 1,
49 attributionControl: false
50 });
51 const bbox = JSON.parse(this.getAttribute('bbox'));
52 this.map.fitBounds(bbox, {padding: {top: 50, bottom: 50, left: 75, right: 75}});
53 }
54 disconnectedCallback() {
55 this.map?.remove();
56 }
57 }
58 if (!customElements.get('rtw-map')) {
59 customElements.define('rtw-map', RTWmap);
60 }
61 </script>
62 <rtw-map map-style={ style } bbox={ toJSON(bbox) } class={ class }>
63 { children... }
64 </rtw-map>
65}
66
67templ Route(src string, color string) {
68 <script type="module">
69 class RTWRoute extends HTMLElement {
70 map;
71 constructor() {
72 super();
73 }
74 connectedCallback() {
75 console.log("route connected");
76 if (!this.map) {
77 this.dispatchEvent(new CustomEvent('map:context', {
78 detail: {
79 cb: (map) => {
80 console.log("received map", map);
81 this.map = map;
82 this.addRoute();
83 }
84 },
85 bubbles: true,
86 composed: true
87 }));
88 }
89 }
90 addRoute() {
91 if (!this.map) return;
92 const hash = this.getAttribute('src');
93 console.log("got hash", hash);
94 if (!hash) return;
95
96 if (!this.map.isStyleLoaded()) {
97 this.map.once('load', () => this.addRoute());
98 }
99
100 this.map.addSource(`source-${hash}`, {type: 'geojson', data: `/route/${hash}.geojson`});
101 this.map.addLayer({
102 id: `layer-${hash}`,
103 source: `source-${hash}`,
104 type: 'line',
105 layout: {
106 'line-join': 'round',
107 'line-cap': 'round'
108 },
109 paint: {
110 'line-color': this.getAttribute('color') || '#888',
111 'line-width': 4
112 }
113 });
114 }
115 }
116 if (!customElements.get('rtw-route')) {
117 customElements.define('rtw-route', RTWRoute);
118 }
119 </script>
120 <rtw-route src={ src } color={ color }></rtw-route>
121}