PageSourceSearch

https://geldautomaten-suche.org/atmmap.js

js geldautomaten-suche.org collected 2026-10-04 17:11:34 UTC 10,257 bytes, 363 lines download raw bytes

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: '&copy; <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.