1"use strict" 2/** 3 * @author Marcus Bleil 4 */ 5 6// eslint settings 7/*global L, $, UTILS, LAYER_BUILDER */ 8 9// constructs the module ATMMAP 10let ATMMAP = {}; 11 12(function () { 13 // dependencies, see atmmaputils.js 14 let utils = UTILS; 15 // see atmmap_layerbuilder.js 16 let layerBuilder = LAYER_BUILDER; 17 18 ATMMAP.initMap = function () { 19 20 map = buildMap(); 21 22 let osmGeocoder = buildOsmGeocoderAndAddToMap(map); 23 24 L.control.locate({ strings: { title: "Gehe zum meinem Standort!" } }).addTo(map); 25 26 L.control.sidebar('sidebar', { position: 'right' }).addTo(map); 27 28 layerBuilder.buildLayers(map); 29 30 addSearchToSidebar(osmGeocoder); 31 32 loadPois(); 33 }; 34 35 /* ****************** */ 36 /* private attributes */ 37 /* ****************** */ 38 39 // contains the node id of the OSM objects 40 // JavaScript pattern: object literal 41 let nodeIds = {}; 42 let wayNodeIds = {}; 43 let map = null; 44 45 // building the api call for atms (automated teller machine) 46 // the overpass api URL 47 let ovpCall = 'https://overpass-api.de/api/interpreter?data='; 48 // setting the output format to json and timeout of 60 s 49 ovpCall += '[out:json][timeout:60];'; 50 // nodes and ways with "amenity"="bank" 51 ovpCall += '('; 52 ovpCall += 'node["amenity"="bank"]({{bbox}});'; 53 ovpCall += 'way["amenity"="bank"]({{bbox}});'; 54 ovpCall += 'node["amenity"="atm"]({{bbox}});'; 55 // other objects with "atm"="yes" 56 ovpCall += '('; 57 ovpCall += '('; 58 ovpCall += 'node["atm"="yes"]({{bbox}});'; 59 ovpCall += 'way["atm"="yes"]({{bbox}});'; 60 ovpCall += ');'; 61 // - means difference 62 ovpCall += '-'; 63 // but no banks 64 ovpCall += '('; 65 ovpCall += 'node["amenity"="bank"]({{bbox}});'; 66 ovpCall += 'way["amenity"="bank"]({{bbox}});'; 67 ovpCall += 'node["amenity"="atm"]({{bbox}});'; 68 ovpCall += ');'; 69 ovpCall += ');'; 70 // closes the atm set statement 71 ovpCall += ');'; 72 // output statement 73 ovpCall += 'out body;'; 74 // all nodes needed for ways only with lat/lng (skel) sorted by place (qt) 75 ovpCall += '>;'; 76 ovpCall += 'out skel qt;'; 77 78 /** *************** */ 79 /** private methods */ 80 /** *************** */ 81 82 let buildMap = function () { 83 let osm_layer = new L.TileLayer( 84 'https://tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', 85 { 86 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>-Mitwirkende' 87 } 88 ); 89 let map = L.map('map', { 90 center: new L.LatLng(52.516, 13.379), 91 zoom: 15, 92 layers: osm_layer 93 }); 94 map.on('moveend', moveEnd); 95 96 return map; 97 } 98 99 let buildOsmGeocoderAndAddToMap = function (map) { 100 let geocoder = new L.Control.OSMGeocoder({ 101 position: 'topright', 102 text: 'Suchen' 103 }); 104 geocoder.addTo(map); 105 return geocoder; 106 } 107 108 let addSearchToSidebar = function (osmGeocoder) { 109 // see: https://stackoverflow.com/questions/41475855/adding-leaflet-layer-control-to-sidebar 110 let htmlObject = osmGeocoder.getContainer(); 111 let searchdiv = document.getElementById("search_control"); 112 searchdiv.appendChild(htmlObject); 113 } 114 115 ATMMAP.test_query_necessary = function (next_polygon, previous_polygon = null) { 116 let query_necessary; 117 118 if (previous_polygon === null) { 119 query_necessary = true; 120 } 121 else { 122 if (previous_polygon.contains(next_polygon)) { 123 query_necessary = false; 124 } 125 else { 126 query_necessary = true; 127 } 128 129 console.debug("query_necessary: " + query_necessary); 130 } 131 return query_necessary; 132 } 133 134 let saveAllNodes = function (data) { 135 $.each(data.elements, function (index, node) { 136 // all nodes of type "node", some tagged nodes are necessary for 137 // building ways, not all nodes here are stored are necessary for storing 138 if (node.type == "node") { 139 wayNodeIds[node.id] = node; 140 } 141 }); 142 } 143 144 let addAtmNodeToMap = function (node) { 145 // bank (or anything else) with atm 146 if (node.tags.atm == "yes") { 147 addNodeWithAtmToMap(node); 148 } 149 // a single atm 150 else if (node.tags.amenity == "atm") { 151 addSingleAtmToMap(node); 152 } 153 // banks without atm or unknow state 154 else if (node.tags.amenity == "bank") { 155 if (node.tags.atm == "no") { 156 addBankWithNoAtmToMap(node); 157 } 158 else { 159 addBankWithUnknownAtmToMap(node); 160 } 161 } 162 } 163 164 let storeAtmNodesToMap = function (data) { 165 166 // overpass returns a list with elements, which contains the nodes 167 $.each(data.elements, function (index, node) { 168 169 // Guardian condition: if node has no "tags" property no further processing is necessary. 170 if (!("tags" in node)) return; 171 // Guardian condition: If the node has already been processed before, a new processing is not necessary 172 if (node.is in nodeIds) return; 173 174 nodeIds[node.id] = true; 175 176 addAtmNodeToMap(node); 177 }); 178 } 179 180 ATMMAP.query_bound = null; 181 ATMMAP.updateQueryBound = function newFunction(mapBounds) { 182 if (ATMMAP.query_bound === null) { 183 ATMMAP.query_bound = L.latLngBounds(mapBounds.getNorthWest(), mapBounds.getSouthEast()); 184 } 185 else { 186 ATMMAP.query_bound.extend(mapBounds); 187 } 188 } 189 190 let callOverpassApi = function (overpassCall) { 191 map.spin(true, { color: '#0026FF', radius: 20, width: 7, length: 20 }); 192 193 // using JQuery executing overpass api 194 $.getJSON(overpassCall, function (data) { 195 saveAllNodes(data); 196 storeAtmNodesToMap(data); 197 }).always(function () { 198 map.spin(false); 199 }).fail(function (jqXHR, textStatus, errorThrown) { 200 console.error(textStatus); 201 console.error(errorThrown); 202 console.error(jqXHR); 203 204 let message; 205 if (textStatus === "timeout") { 206 message = "Die Anfrage an die Overpass API hat zu lange gedauert. Bitte versuchen Sie es später erneut."; 207 } 208 else if (textStatus === "parsererror") { 209 message = "Die Antwort der Overpass API konnte nicht gelesen werden. Bitte versuchen Sie es später erneut."; 210 } 211 else if (textStatus === "abort") { 212 message = "Die Anfrage an die Overpass API wurde abgebrochen."; 213 } 214 else if (jqXHR.status === 400) { 215 message = "Die Overpass API hat die Anfrage abgelehnt. Möglicherweise ist die Abfrage ungültig."; 216 } 217 else if (jqXHR.status === 403) { 218 message = "Die Overpass API hat den Zugriff verweigert."; 219 } 220 else if (jqXHR.status === 404) { 221 message = "Der Overpass API-Endpunkt wurde nicht gefunden."; 222 } 223 else if (jqXHR.status === 408 || jqXHR.status === 504) { 224 message = "Die Overpass API hat die Anfrage wegen einer Zeitüberschreitung abgebrochen. Bitte versuchen Sie es später erneut."; 225 } 226 else if (jqXHR.status === 429) { 227 message = "Zu viele Anfragen an die Overpass API. Bitte warten Sie kurz und versuchen Sie es erneut."; 228 } 229 else if (jqXHR.status >= 500) { 230 message = "Die Overpass API ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut."; 231 } 232 else if (jqXHR.status === 0) { 233 message = "Die Overpass API ist nicht erreichbar. Prüfen Sie Ihre Internetverbindung und ob der Browser die Anfrage blockiert (CORS)."; 234 } 235 else { 236 message = "Beim Abrufen der Daten von der Overpass API ist ein unerwarteter Fehler aufgetreten (" + textStatus + ")."; 237 } 238 239 alert(message); 240 }); 241 } 242 243 /** 244 * The method is currently (June 2025) called twice: when the card is 245 * generated (buildMap()) and when the card is moved in any way (moveEnd()). 246 * The method empties the ATM layers, retrieves the ATMs of the current 247 * display area and fills the layers again with these current ATMs. 248 */ 249 let loadPois = function () { 250 251 if (map.getZoom() >= 14) { 252 let mapBounds = map.getBounds(); 253 let query_necessary = ATMMAP.test_query_necessary(mapBounds, ATMMAP.query_bound); 254 255 if (query_necessary) { 256 257 ATMMAP.updateQueryBound(mapBounds); 258 259 // note: g in /{{bbox}}/g means replace all occurrences of {{bbox}} not just first occurrence 260 let overpassCall = ovpCall.replace(/{{bbox}}/g, utils.latLongToString(mapBounds)); 261 262 callOverpassApi(overpassCall); 263 } 264 } 265 } 266 267 let addBankWithNoAtmToMap = function (bank) { 268 let name, marker; 269 270 name = utils.createDescriptionFromeTags(bank); 271 marker = createMarker(bank, name, utils.noAtm); 272 273 addToNamedGroups(bank, marker); 274 }; 275 276 let addBankWithUnknownAtmToMap = function (bank) { 277 let name, marker; 278 279 name = utils.createDescriptionFromeTags(bank); 280 marker = createMarker(bank, name, utils.unknownAtm); 281 282 addToNamedGroups(bank, marker); 283 }; 284 285 let addNodeWithAtmToMap = function (node) { 286 let name, marker; 287 288 name = utils.createDescriptionFromeTags(node); 289 290 if (node.tags.amenity == "bank") { 291 marker = createMarker(node, name, utils.yesAtm); 292 } 293 else { 294 marker = createMarker(node, name, utils.atm); 295 } 296 297 addToNamedGroups(node, marker); 298 }; 299 300 let createMarkerForNode = function (node, name, atmIcon) { 301 let marker = L.marker([node.lat, node.lon], { 302 icon: atmIcon 303 }); 304 305 marker.bindPopup(name); 306 307 return marker; 308 } 309 310 let createMarkerForWay = function (node, wayNodeIds, atmIcon, name) { 311 let areaNodes = new Array(); 312 let bankArea; 313 let marker = null; 314 let bounds; 315 let center; 316 317 $.each(node.nodes, function (index, nodeId) { 318 areaNodes.push(L.latLng(wayNodeIds[nodeId].lat, wayNodeIds[nodeId].lon)); 319 }); 320 321 bankArea = L.polygon(areaNodes, { clickable: false }); 322 bounds = bankArea.getBounds(); 323 center = bounds.getCenter(); 324 325 marker = L.marker([center.lat, center.lng], { icon: atmIcon }); 326 327 marker.bindPopup(name); 328 329 return L.layerGroup([bankArea, marker]); 330 } 331 332 let createMarker = function (node, name, atmIcon) { 333 334 if (node.type == "node") { 335 return createMarkerForNode(node, name, atmIcon); 336 } 337 else if (node.type == "way") { 338 return createMarkerForWay(node, wayNodeIds, atmIcon, name); 339 } 340 }; 341 342 let addSingleAtmToMap = function (atm) { 343 let name, marker; 344 345 name = utils.createDescriptionFromeTags(atm); 346 marker = L.marker([atm.lat, atm.lon], { icon: utils.atm }).bindPopup(name); 347 348 addToNamedGroups(atm, marker); 349 }; 350 351 let addToNamedGroups = function (node, marker) { 352 try { 353 layerBuilder.addToNamedGroups(node, marker); 354 } catch (e) { 355 console.debug(e); 356 } 357 }; 358 359 let moveEnd = function () { 360 loadPois(); 361 }; 362 363})();
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.