PageSourceSearch

https://friendsoftheheron.github.io/pwa/js/map.js

js friendsoftheheron.github.io collected 2026-10-03 09:12:59 UTC 14,133 bytes, 383 lines download raw bytes

1import "https://unpkg.com/[email protected]/dist/leaflet.js"
2import config from "./config.js"
3import Labs from "./labs.js"
4import Location from "./location.js"
5import du from "./domutils.js"
6import st from "./settings.js"
7
8
9export default class Map {
10    static map = null;
11    static tile_name = "";
12    static marker = null;
13    static active = null;
14    static position = null;
15    static zoom = null;
16    static block_size = null;
17    static center_img = null;
18    static down_timer = false;
19    static down_timeout = 932;
20    static drag_timer = false;
21    static drag_timeout = 1053;
22
23    //static labs = {}
24
25    static weight = {
26        orange: 11,
27        red: 3,
28        yellow: 0,
29
30    }
31
32    // https://leaflet-extras.github.io/leaflet-providers/preview/
33    static tiles = Object.entries({
34        "OpenStreetMap": [
35            "https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
36                attribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
37                maxZoom: 19,
38            }
39        ],
40        "Esri WorldImagery": [
41            "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
42                attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community",
43            }
44        ],
45        "OpenTopoMap": [
46            "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", {
47                attribution: "Map data: &copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors, <a href=\"http://viewfinderpanoramas.org\">SRTM</a> | Map style: &copy; <a href=\"https://opentopomap.org\">OpenTopoMap</a> (<a href=\"https://creativecommons.org/licenses/by-sa/3.0/\">CC-BY-SA</a>)",
48                maxZoom: 17,
49            }
50        ],
51        "Alidade Smooth": [
52            "https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.{ext}",
53            {
54                attribution: "&copy; <a href=\"https://www.stadiamaps.com/\">Stadia Maps</a> &copy; <a href=\"https://openmaptiles.org/\">OpenMapTiles</a> &copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
55                ext: "png",
56                maxZoom: 20,
57                minZoom: 0,
58            }
59        ],
60        "Alidade SmoothDark": [
61            "https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.{ext}",
62            {
63                attribution: "&copy; <a href=\"https://www.stadiamaps.com/\">Stadia Maps</a> &copy; <a href=\"https://openmaptiles.org/\">OpenMapTiles</a> &copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors",
64                ext: "png",
65                maxZoom: 20,
66                minZoom: 0,
67            }
68        ],
69        "CartoDB Positron": [
70            "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", {
71                attribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors &copy; <a href=\"https://carto.com/attributions\">CARTO</a>",
72                maxZoom: 20,
73                subdomains: "abcd",
74            }
75        ],
76        "CartoDB DarkMatter": [
77            "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", {
78                attribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors &copy; <a href=\"https://carto.com/attributions\">CARTO</a>",
79                maxZoom: 20,
80                subdomains: "abcd",
81            }
82        ],
83        "TopPlusOpen Color": [
84            "http://sgx.geodatenzentrum.de/wmts_topplus_open/tile/1.0.0/web/default/WEBMERCATOR/{z}/{y}/{x}.png", {
85                attribution: "Map data: &copy; <a href=\"http://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a>",
86                maxZoom: 18,
87            }
88        ],
89    }).map(x => [x[0], L.tileLayer(x[1][0], x[1][1])])
90        .reduce(function(acc, x) {
91            acc[x[0]] = x[1];
92            return acc;
93        }, {})
94    ;
95
96
97
98    static init = () => {
99        this.map = L.map("leaflet").fitWorld();
100        this.marker = L.marker(
101            [0, 0],
102            {
103                icon: L.icon({
104                    iconUrl: "images/svg/marker-blue.svg",
105                    shadowUrl: "images/svg/marker-shadow.png",
106                    iconSize: [25, 41],
107                    shadowSize: [41, 41],
108                    iconAnchor: [12, 41],
109                    shadowAnchor: [13, 41],
110                }),
111            }
112        ).addTo(this.map);
113        this.active = L.marker(
114            [0.0],
115            {
116                icon: L.icon({
117                    iconUrl: "images/svg/marker-orange.svg",
118                    shadowUrl: "images/svg/marker-shadow.png",
119                    iconSize: [25, 41],
120                    shadowSize: [41, 41],
121                    iconAnchor: [12, 41],
122                    shadowAnchor: [13, 41],
123                }),
124            }
125        );
126
127        Map.setTile();
128
129        L.Control.Center = L.Control.extend({
130            onAdd: function(map) {
131                this.center_div = L.DomUtil.create("div");
132                this.center_div.setAttribute("id", "center_div");
133                this.center_div.src = "./images/center.png";
134
135                L.DomEvent.on(this.center_div, "dblclick", (e) => {
136                    L.DomEvent.stopPropagation(e);
137                });
138
139                L.DomEvent.on(this.center_div, "click", (e) => {
140                    Map.centerMap();
141                    this.center_div.classList.add("clicked");
142                    if (Map.down_timer) {
143                        window.clearTimeout(Map.down_timer)
144                        this.center_div.classList.remove("clicked");
145                        Map.down_timer = false;
146                        du.setElementValue(
147                            "center_position",
148                            !du.getElementValue("center_position")
149                        );
150                        console.log("center_position", du.getElementValue("center_position"));
151
152                    } else {
153                        Map.down_timer = window.setTimeout(
154                            () => {
155                                this.center_div.classList.remove("clicked");
156                                Map.down_timer = false;
157                            },
158                            Map.down_timeout
159                        );
160                    }
161                    return false;
162                });
163
164                return this.center_div;
165            },
166
167            onRemove: function(map) {
168                // Nothing to do here
169            }
170        });
171
172        L.control.center = (opts) => new L.Control.Center(opts);
173        L.control.center({ position: "topright" }).addTo(this.map);
174
175        L.control.scale().addTo(this.map);
176        L.control.layers(Map.tiles, {}, { position: "bottomright"}).addTo(this.map);
177
178
179        this.map.on("zoomend", e => this.handleEndDrag());
180        this.map.on("moveend", e => this.handleEndDrag());
181        this.map.on("baselayerchange", e => {
182            st.getSetting("tile-name")
183                .then(text => {
184                    let json = JSON.parse(text);
185                    if (!json) { json = {}; }
186                    json[du.getColorScheme()] = e.name;
187                    st.setItem("tile-name", JSON.stringify(json));
188                })
189        });
190    }
191
192    static setTile = () => {
193        if (this.map) {
194            st.getSetting("tile-name")
195                .then(text => {
196                    const old_name = Map.tile_name;
197                    const json = JSON.parse(text);
198                    document.dispatchEvent(
199                        new MessageEvent(
200                            "message",
201                            {data: "tile: " + text + ", old_name: " + old_name}
202                        )
203                    );
204
205                    if (json) {
206                        Map.tile_name = json[du.getColorScheme()] || Map.tile_name || "OpenStreetMap";
207                        if (!Map.tiles[Map.tile_name]) {
208                            Map.tile_name = old_name;
209                        }
210                    }
211                    if (old_name !== Map.tile_name) {
212                        if (("" !== old_name)) {
213                            Map.tiles[old_name].remove();
214                        }
215                        Map.tiles[Map.tile_name].addTo(this.map);
216                    }
217                })
218            ;
219        }
220    }
221    static clear = () => {
222        this.removeLabs()
223        this.zoom = 1969;
224    }
225    static handleEndDrag = () => new Promise((resolve, reject) => {
226        if (this.drag_timer) {
227            window.clearTimeout(this.drag_timer);
228        }
229        this.drag_timer = window.setTimeout(
230            () => Promise.all([
231                st.getSetting("block-size"),
232                st.getSetting("hide-logged"),
233            ]).then(([block_size, hide_logged,]) => {
234                const center = new Location(this.map.getCenter())
235                const bounds = this.map.getBounds();
236                if (
237                    (!this.position) ||
238                    (Math.abs(+block_size) * 250 < this.position.distance(center)) ||
239                    (this.map.getZoom() < this.zoom) ||
240                    (
241                        this.map.getZoom() > this.zoom &&
242                        document.getElementById("icon-map").classList.contains("alert")
243                    )
244                ) {
245                    this.position = center;
246                    this.zoom = this.map.getZoom();
247                    this.block_size = Math.max(this.map.distance(bounds._southWest, bounds._northEast), 1) / 1000;
248                    Labs
249                        .getData({
250                            block_size: this.block_size * (hide_logged ? -1 : +1),
251                            filters: localStorage.getItem(config.filters_key),
252                            latitude: center.latitude,
253                            longitude: center.longitude,
254                        })
255                        .then(labs => Labs.updateLabs(labs, "map"))
256                        .then(res => resolve(res))
257                        .catch(err => reject(err))
258                }
259            }),
260            this.drag_timeout
261        );
262    });
263
264    static id2layer(id) {
265        let ret
266        this.map.eachLayer(layer => {
267            if (
268                layer.options.hasOwnProperty("lab_id") &&
269                layer.options.lab_id === id
270            ) {
271                ret = layer;
272            }
273        })
274        return ret
275    }
276
277    static setCircleColor = (id, color) => {
278        //console.log(`setCircleColor(${id}, ${color})`);
279        const circle = Map.id2layer(id);
280        if (circle) {
281            circle.setStyle({
282                color: config.color["sky" === color ? "orange" : "orange" === color ? "sky" : color],
283                weight: undefined !== Map.weight[color] ? Map.weight[color] : 1,
284            });
285        }
286    }
287
288    static removeLabs = () => {
289       const ids = Labs.labs === null ? [] : Labs.labs.map(lab => lab.id);
290       this.map.eachLayer(layer => {
291            if (
292                layer.options.hasOwnProperty("lab_id") &&
293                !ids.includes(layer.options.lab_id)
294            ) {
295              this.map.removeLayer(layer);
296            }
297        })
298    }
299
300    static addLabs = () => {
301        const template = document.getElementById("map_template");
302        if (!template) {
303            console.error("Map.addLabs: map_template not found");
304            return
305        }
306        const ids = []
307        this.map.eachLayer(layer => {
308            if (layer.options.hasOwnProperty("lab_id")) {
309                ids.push(layer.options.lab_id);
310            }
311        })
312
313        Labs.labs.forEach(lab => {
314            if (!ids.includes(lab.id)) {
315                new L.circle(
316                    [
317                        lab.location_latitude,
318                        lab.location_longitude,
319                    ], {
320                        radius : lab.geofencing_radius,
321                        opacity: 0.8,
322                        fillOpacity: 0.15,
323                        lab_id: lab.id,
324                    }
325                )
326                    .bindPopup(du.renderTemplate(template.innerHTML, lab))
327                    .addTo(this.map);
328                this.setCircleColor(lab.id, lab.color);
329            }
330        })
331    }
332
333    static updateLabs = () => {
334        this.removeLabs();
335        this.addLabs();
336    }
337
338    static centerMap = () => {
339        Labs.blockSize().then(block_size => {
340            const delta = Math.abs(block_size) * 90 / 10010 / 4;
341            this.position = new Location(
342                localStorage.getItem(config.current_latitude),
343                localStorage.getItem(config.current_longitude)
344            );
345            const bounds = [[
346                this.position.latitude - delta,
347                this.position.longitude - delta / Math.cos(this.position.phi)
348            ], [
349                this.position.latitude + delta,
350                this.position.longitude + delta / Math.cos(this.position.phi)
351            ]];
352            this.map.fitBounds(bounds);
353            this.zoom = this.map.getZoom()
354            this.block_size = block_size
355            return bounds;
356        });
357    }
358
359    static center = () => {
360        this.map.invalidateSize();
361        if (
362            du.getElementValue("symbol-map") &&
363            (
364                !this.position ||
365                du.getElementValue("center_position")
366            )
367        ) {
368            this.centerMap();
369        }
370    }
371}
372
373const colorSchemeObserver = new MutationObserver((mutations, observer) => {
374    mutations.forEach(mutation => {
375        if (("attributes" === mutation.type) && ("class" === mutation.attributeName)) {
376            Map.setTile();
377        }
378    });
379});
380colorSchemeObserver.observe(document.body, {
381    attributes: true,
382    attributeFilter: ["class"],
383});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.