PageSourceSearch

https://demo.transformap.co/map.js

js transformap.co collected 2026-10-03 19:28:27 UTC 92,310 bytes, 2,097 lines download raw bytes

1if(!window.console)  // for very old browser
2    var console = { log : function(i){}}
3
4var language = window.navigator.languages ? window.navigator.languages[0] : (window.navigator.language || window.navigator.userLanguage);
5
6if(typeof language === 'string')
7    language = [ language ];
8
9// we need to have the following languages:
10// browserlang
11// a short one (de instead of de-AT) if not present
12// en as fallback if not present
13
14if(language.indexOf("en") == -1)
15    language.push("en");
16
17for(var i = 0; i < language.length; i++) {
18    if(language[i].match(/-/)) {
19        var short_lang = language[i].match(/^([a-zA-Z]*)-/)[1];
20        if(language.indexOf(short_lang) == -1) {
21            language.push(short_lang);
22            continue;
23        }
24    }
25}
26
27console.log(language);
28
29// global texts
30var attr = {
31  osm : 'Map data &copy; <a href="https://openstreetmap.org/">OpenStreetMap</a> contributors - <a href="http://opendatacommons.org/licenses/odbl/">ODbL</a>',
32  osm_tiles : 'Tiles &copy; OSM - <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA</a>',
33  search : 'Search by OSM - <a href="http://wiki.openstreetmap.org/wiki/Nominatim">Nominatim</a>',
34  mapbox : 'Tiles &copy; <a href="http://mapbox.com/about/maps/">MapBox</a>',
35  mapquest : 'Tiles courtesy of <a href="http://www.mapquest.com/">MapQuest</a>',
36  greenmap : 'Green Map Icons used by permission &copy; <a href="http://www.greenmap.org">Green Map System 2015</a>',
37  overpass : 'POI via <a href="http://www.overpass-api.de/">Overpass API</a>'
38}
39
40if(!window.assethost)
41    var assethost = "http://demo.transformap.co/";
42
43/*
44 * takes a two args: osm datatype and osm id
45 * if marker in field of view, toggle popup.
46 * zooms in if marker is hidden in cluster to get leafletMarker
47 */
48function MytogglePopup(osm_type,osm_id) {
49    disableLoadPOI = true;
50    var leaflet_marker = getVisibleMarker(osm_type,osm_id)
51    if(leaflet_marker) {
52        leaflet_marker.togglePopup();
53        setTimeout(function () {
54            disableLoadPOI = false;
55        }, 200);
56        return;
57    } //else
58
59    var target_marker;
60    var marker_array = markers.GetMarkers();
61    for(var i = 0; i < marker_array.length; i++) {
62        var prunecluster_marker = marker_array[i];
63        if(prunecluster_marker.data.type == osm_type && prunecluster_marker.data.id == osm_id) {
64            target_marker = prunecluster_marker;
65            break;
66        }
67    }
68    if(target_marker) {
69        if(map.getZoom() == map.getMaxZoom()) {
70            var possible_clusters = [];
71            map.eachLayer(function (layer) {
72                if(layer._population > 0)
73                    possible_clusters.push(layer);
74            });
75            var distance = 123456789;
76            var nearest_cluster = null;
77            for(i = 0; i < possible_clusters.length; i++) {
78                var new_distance = calculateDistance(target_marker.position.lat, possible_clusters[i]._latlng.lat, target_marker.position.lng,  possible_clusters[i]._latlng.lng);
79                if( new_distance < distance ) {
80                    distance = new_distance;
81                    nearest_cluster = possible_clusters[i];
82                }
83            }
84            if(!nearest_cluster) {
85                console.log("Error in MytogglePopup: no nearest cluster found");
86                disableLoadPOI = false;
87                return;
88            }
89            nearest_cluster.fireEvent('click');
90        }
91        else
92          map.setZoomAround( new L.LatLng(target_marker.position.lat, target_marker.position.lng), map.getZoom() + 1);
93
94        setTimeout(function () {
95            MytogglePopup(osm_type,osm_id);
96        }, 200);
97    } else {
98        disableLoadPOI = false;
99        console.log("Error in MytogglePopup: marker " + osm_type + " " + osm_id + " not found in markers.GetMarkers()");
100    }
101}
102
103
104//source: http://jsfiddle.net/vg01q7xw/
105Number.prototype.toRad = function() {
106        return this * Math.PI / 180;
107}
108function calculateDistance(lat1, lat2, lon1, lon2) {
109    var R = 6371000; // meter
110    var Phi1 = lat1.toRad();
111    var Phi2 = lat2.toRad();
112    var DeltaPhi = (lat2 - lat1).toRad();
113    var DeltaLambda = (lon2 - lon1).toRad();
114
115    var a = Math.sin(DeltaPhi / 2) * Math.sin(DeltaPhi / 2)
116            + Math.cos(Phi1) * Math.cos(Phi2) * Math.sin(DeltaLambda / 2)
117            * Math.sin(DeltaLambda / 2);
118    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
119    var d = R * c;
120
121    return d;
122}
123
124function getVisibleMarker(osm_type,osm_id) {
125    var my_layer = null;
126    map.eachLayer(function (layer) {
127        if(! layer._popup || ! layer.options.alt)
128            return;
129        if(layer.options.alt != (osm_type + " " + osm_id))
130            return;
131
132        my_layer = layer;
133    });
134    return my_layer;
135}
136
137/* this part must be in global namespace */
138// fetch taxonomy, containing all translations, and implicit affiliations
139// taken from Wikipedia:JSON
140var taxonomy, turbolink;
141var url = assethost+"taxonomy.json";
142var http_request = new XMLHttpRequest();
143http_request.open("GET", url, true);
144http_request.onreadystatechange = function () {
145      var done = 4, ok = 200;
146      if (http_request.readyState === done && http_request.status === ok) {
147          if(!window.JSON) {
148//              document.getElementById('map').inn("Error:cannot handle JSON");
149              return;
150          }
151          taxonomy = JSON.parse(http_request.responseText);
152
153          if(taxonomy) {
154              // add map key derived from it
155              for(var osmkey_counter = 0; osmkey_counter < overpass_config.icon_tags.length; osmkey_counter++) {
156                  var osmkey = overpass_config.icon_tags[osmkey_counter];
157
158                  var taxonomy_block = taxonomy[osmkey];
159                  if (!taxonomy_block) {
160                      console.log("no entry in taxonomy for " + osmkey);
161                      break;
162                  }
163
164                  var entry_array = taxonomy_block['items'];
165                  for (var i = 0; i < entry_array.length; i++ ) {
166                      var item = entry_array[i];
167                      for ( var osmvalue_counter = 0; osmvalue_counter < item['osm:values'].length; osmvalue_counter++ ) {
168                          var li = $("<li>");
169                          var new_id = item['osm:key'] + item['osm:values'][osmvalue_counter];
170                          li.attr("onClick", "toggleInfoBox('" + new_id + "');");
171                          li.append('<img src="'+assethost+'assets/transformap/pngs/identities/24/' + item['osm:key'] + '=' + item['osm:values'][osmvalue_counter] + '.png" /> '
172                                  + item['label']['en']
173                                  + '<div class=InfoBox ' 
174                                      + 'id="' + new_id + '">'
175                                      + item['description']['en'] + '</div>');
176                          $('#mapkey').append(li);
177                      }
178                  }
179              }
180
181              if(window.filters)
182                  createFiltersOfTaxonomy();
183
184          }
185          else
186              console.log("taxonomy not here");
187
188      }
189  };
190http_request.send(null);
191
192var overpass_ql_text,
193    overpass_query,
194    overpass_query_nodes,
195    overpass_query_ways,
196    overpass_query_rels,
197    object_type_keys = [ "amenity", "shop", "tourism", "craft", "garden:type", "leisure", "office", "man_made", "landuse", "club", "farm_boxes" ] ;
198
199/*
200 * sets global the vars above
201 * MUST be called before first loadPOI
202 */
203function buildOverpassQuery() {
204    if(! window.overpass_config )
205        window.overpass_config = {};
206
207    var op_server1 = "//overpass-api.de/api/",
208        op_server2 = (window.parent.document.location.protocol == "https:") ? op_server1 : "http://api.openstreetmap.fr/oapi/", //fr does'n have https, ru too
209        op_server3 = (window.parent.document.location.protocol == "https:") ? op_server1 : "http://overpass.osm.rambler.ru/cgi/";
210
211    if(! overpass_config.timeout)             overpass_config.timeout = 180;
212    if(! overpass_config.minzoom)             overpass_config.minzoom = 12;
213    if(! overpass_config.servers)             overpass_config.servers = [ op_server1, op_server2, op_server3 ];
214    if(! overpass_config.q_array)             overpass_config.q_array = [ [ '"identity"' ] ];
215    if(! overpass_config.icon_folder)         overpass_config.icon_folder = "identities";
216    if(! overpass_config.icon_tags)           overpass_config.icon_tags = [ "identity" ];
217    if(! overpass_config.icon_size)           overpass_config.icon_size = 24;
218    if(! overpass_config.class_selector_key)  overpass_config.class_selector_key = { key: "" };
219
220    var overpass_urlstart = 'interpreter?data=';
221    var overpass_start = '[out:json][timeout:' + overpass_config.timeout + '][bbox:BBOX];';
222
223    var overpass_query_string = "";
224    var overpass_query_string_nodes = "";
225    var overpass_query_string_ways = "";
226    var overpass_query_string_rels = "";
227
228    for (var i = 0; i < overpass_config.q_array.length; i++) {
229      var anded_tags = overpass_config.q_array[i];
230      var anded_querystring = "";
231      var nr_of_and_clauses = anded_tags.length;
232      for (var j=0; j < nr_of_and_clauses; j++) {
233        anded_querystring += "[" + anded_tags[j] + "]";
234      }
235
236      overpass_query_string += "node" + anded_querystring + ";out;";
237      overpass_query_string += "(way" + anded_querystring + ";node(w));out;";
238      overpass_query_string += "rel" + anded_querystring + ";out;>;out;";
239
240      overpass_query_string_nodes += "node" + anded_querystring + ";out;";
241      overpass_query_string_ways += "(way" + anded_querystring + ";node(w));out;";
242      overpass_query_string_rels += "rel" + anded_querystring + ";out;>;out;";
243    }
244
245    overpass_ql_text = overpass_start + overpass_query_string;
246    overpass_query = overpass_config.servers[0] + overpass_urlstart + overpass_ql_text;
247    overpass_query_nodes = overpass_config.servers[0] + overpass_urlstart + overpass_start + overpass_query_string_nodes;
248    overpass_query_ways = overpass_config.servers[1] + overpass_urlstart + overpass_start + overpass_query_string_ways;
249    overpass_query_rels = overpass_config.servers[2] + overpass_urlstart + overpass_start + overpass_query_string_rels;
250    console.log(overpass_query_nodes);
251    console.log(overpass_query_ways);
252    console.log(overpass_query_rels);
253}
254
255var debugLayer,
256    map,
257    markers = new PruneClusterForLeaflet(60,20),
258    lc = {},
259    default_overlay = {
260      "POIs" : markers
261    };
262
263function initMap(defaultlayer,base_maps,overlay_maps,lat,lon,zoom) {
264  var center = new L.LatLng(lat ? lat : 0, lon ? lon : 0);
265
266  var overriddenId = new L.Control.EditInOSM.Editors.Id({ url: "http://editor.transformap.co/#background=Bing&map=" });
267
268/*  var MapQuestOpen_OSM = new L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg', {
269      subdomains: '1234',
270      attribution: [attr.osm, attr.mapquest, attr.overpass, attr.greenmap].join(', '),
271      maxZoom : 19,
272      maxNativeZoom: 18 ,
273      noWrap: true
274  }); MQ open no longer here */
275  var osm = new L.TileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
276      attribution: [attr.osm, attr.osm_tiles, attr.overpass, attr.greenmap].join(', '),
277      maxZoom : 19,
278      noWrap: true
279  });
280
281  terrain = new L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png', {
282      attribution: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, '+
283        'under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. '+
284        'Data by <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, under <a href="https://www.openstreetmap.org/copyright">ODbL</a>.'
285  });
286  terrain_bg = new L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}.png', {
287      attribution: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, '+
288        'under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. '+
289        'Data by <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, under <a href="https://www.openstreetmap.org/copyright">ODbL</a>.'
290  });
291  hot = new L.tileLayer('http://tile-{s}.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
292      attribution: 'Tiles courtesy of <a href="http://hot.openstreetmap.org/">Humanitarian OpenStreetMap Team</a>. '+
293        'Data by <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, under <a href="https://www.openstreetmap.org/copyright">ODbL</a>.'
294  });
295
296  /*if(!defaultlayer)
297    defaultlayer = (window.parent.document.location.protocol == "https:") ? osm : MapQuestOpen_OSM;*/
298  defaultlayer = terrain_bg;
299
300  if(!overlay_maps)
301    overlay_maps = default_overlay;
302
303  map = new L.Map('map', {
304    center: center,
305    zoom: zoom ? zoom : 3,
306    layers: defaultlayer,
307    editInOSMControlOptions: {
308        position: 'topright',
309        zoomThreshold: 18,
310        widget: 'multiButton',
311        editors: [overriddenId] 
312        }
313  });
314  
315  if(!base_maps) {
316
317    base_maps = {
318//      'MapQuestOpen': MapQuestOpen_OSM,
319      'Stamen - Terrain Background': terrain_bg,
320      'Stamen - Terrain': terrain,
321      'Humanitarian OpenSteetMap ': hot,
322      'OpenSteetMap - Mapnik': osm,
323
324    };
325  }
326
327  map.addLayer(markers);
328
329  var ctrl = new L.Control.Layers(base_maps,overlay_maps)
330  map.addControl(ctrl);
331
332  L.LatLngBounds.prototype.toOverpassBBoxString = function (){
333    var a = this._southWest,
334        b = this._northEast;
335    return [a.lat, a.lng, b.lat, b.lng].join(",");
336  }
337
338  var path_style = L.Path.prototype._updateStyle;
339  L.Path.prototype._updateStyle = function () {
340    path_style.apply(this);
341    for (k in this.options.svg) {
342      this._path.setAttribute(k, this.options.svg[k]);
343    }
344  }
345
346  if(window.createSideBar) {
347      createSideBar();
348      setTimeout(toggleSideBarOnLoad,200);
349      map.on('moveend', updatePOIlist);
350      if(window.filters)
351          map.on('moveend',updateFilterCount); // here because it is called on every map move
352  }
353
354  $('#map').append('<a href="https://github.com/TransforMap/transfor-map" title="Fork me on GitHub" id=forkme></a>');
355  $('#forkme').append('<img src="'+assethost+'assets/forkme-on-github.png" alt="Fork me on GitHub" />');
356
357  $('#map').append('<img src="'+assethost+'assets/ajax-loader.gif" id="loading_node" class="loading" />');
358  $('#map').append('<img src="'+assethost+'assets/ajax-loader.gif" id="loading_way" class="loading" />');
359  $('#map').append('<img src="'+assethost+'assets/ajax-loader.gif" id="loading_relation" class="loading" />');
360  $('#map').append('<div id="notificationbar">Please zoom in to update POIs!</div>');
361
362  map.on('moveend', updateLinks);
363  map.on('popupopen', setImageInPopup);
364  map.on('popupopen', setTranslationsInPopup);
365
366  var popup_param = getUrlVars()["popup"];
367  if(popup_param) {
368      open_popup_on_load.type = popup_param.match(/^(node|way|relation)/)[1];
369      open_popup_on_load.id = popup_param.replace(/^(node|way|relation)/,'' );
370  }
371
372  debugLayer = L.layerGroup();
373  debugLayer.addTo(map);
374
375  return map;
376}
377
378var open_popup_on_load = { type : "", id: "", already_shown: false };
379
380/*
381   var href = location.href;
382   var hasQuery = href.indexOf("?") + 1;
383   var hasHash = href.indexOf("#") + 1;
384   var appendix = (hasQuery ? "&" : "?") + "ts=true";
385   location.href = hasHash ? href.replace("#", appendix + "#") : href + appendix;
386   */
387
388function getUrlVars() {
389    var vars = {};
390    var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
391        vars[key] = value.replace(/#.*$/,'');
392    });
393    return vars;
394}
395
396function setImageInPopup(obj) {
397//    console.log("setImageInPopup: called");
398    if(!document.getElementById('wp-image')) {
399//        console.log("setImageInPopup: no wp-image");
400        return;
401    }
402    if ($('#wp-image img').attr('src')) {
403//        console.log("setImageInPopup: src already set");
404        return;
405    }
406    var img = $('#wp-image img');
407    var source = wikipedia_images["wpimage_" + img.attr('title')];
408
409    if(!source) {
410        $('#wp-image').css('display','none');
411//        console.log("setImageInPopup: no answer for item yet");
412        return;
413    }
414    if ( source == "UNDEF") {
415        $('#wp-image').css('display','none');
416//        console.log("setImageInPopup: no image on wikipedia");
417        return;
418    }
419//    console.log("setImageInPopup: setting src " + source);
420    $('#wp-image').css('display','table-cell');
421    img.attr('src', source);
422}
423
424function setTranslationsInPopup(obj) {
425    var elements = $('.leaflet-popup-content [translated]');
426    if(!elements.length)
427        return;
428
429    //console.log(elements.length + " untranslated elements found.");
430    elements.each(function ( index ) {
431        var jqitem = $(this);
432        var tags_string = jqitem.attr("sourcetext");
433        if(tags_string == "unknown feature")
434            return;
435        var tags = tags_string.split(",");
436        var new_strings = [];
437        var langs_used = [];
438        for(var i=0; i < tags.length; i++) {
439            var tag = tags[i].trim();
440            if(!translations.tags[tag]) {
441                console.log("setTranslationsInPopup: no entry in translations foun
441d for '" + tag + "'");
442                new_strings.push(tag);
443                continue;
444            }
445
446            var found_transl = 0;
447            for(var j=0; j < language.length; j++) {
448                var targetlang = language[j];
449                if(translations.tags[tag][targetlang]) {
450                    new_strings.push(translations.tags[tag][targetlang].value);
451                    if(langs_used.indexOf(targetlang) == -1)
452                        langs_used.push(targetlang);
453                    found_transl = 1;
454                    break;
455                }
456            } 
457            if(!found_transl) {
458                console.log("no translation in your lang or en found for: '" + tag + "'");
459                new_strings.push(tag);
460            }
461
462        }
463        var new_text = new_strings.join(", ");
464        if(jqitem.children().first().text() != new_text) {
465            jqitem.children().first().text(new_text);
466            jqitem.attr("translated",langs_used.join(","));
467        }
468    });
469}
470
471function addSearch() {
472  map.addControl( new L.Control.Search({
473    url: '//nominatim.openstreetmap.org/search?format=json&q={s}',
474    jsonpParam: 'json_callback',
475    propertyName: 'display_name',
476    propertyLoc: ['lat','lon'],
477    circleLocation: false,
478    markerLocation: false,
479    autoType: false,
480    autoCollapse: false,
481    minLength: 2,
482    zoom:16
483  }) );
484
485}
486function addLocate() {
487lc = L.control.locate({
488        position: 'topleft',
489        showPopup: false,
490        strings: {
491                title: "Jump to my location!"
492            }
493      }
494      ).addTo(map);
495}
496
497var marker_table = {};
498var old_zoom = 20;
499
500var pids = {
501    node : {
502        counter : 0,
503        active : {}
504    },
505    way : {
506        counter : 0,
507        active : {}
508    },
509    relation : {
510        counter : 0,
511        active : {}
512    }
513}
514
515var mutex_loading = { "loading_node" : 0, "loading_way" : 0, "loading_relation" : 0 };
516
517var on_start_loaded = 0;
518
519/*
520 *  type: "loading_node" / "loading_way" / "loading_relation"
521 *  change: (+)1 or -1
522 */
523function changeLoadingIndicator(type, change) {
524
525    var loading_indicator = document.getElementById(type);
526    if(mutex_loading[type] + change >= 0) //don't go into negative
527        mutex_loading[type] = mutex_loading[type] + change;
528    if(change == -1) {
529        if(mutex_loading[type] == 0) 
530          loading_indicator.style.display = "none";
531    } else  // +1
532        loading_indicator.style.display = "block";
533    loading_indicator.title = mutex_loading[type];
534}
535
536function secHTML(input) {
537    return $("<div>").text( input ).html();
538}
539
540function getIconTag(tags) {
541    for (var i = 0; i < overpass_config.icon_tags.length; i++) {
542      var key = overpass_config.icon_tags[i];
543      if(tags[key] && ! ( key == "amenity" && tags[key] == "shop" ) ) {
544        return key;
545      }
546    }
547    return "";
548}
549
550function chooseIconSrc(tags,iconsize) {
551    var icon_tag = getIconTag(tags);
552
553    var icon_url = "";
554    if(!icon_tag) {
555      icon_url = assethost+"assets/transformap/pngs/" + overpass_config.icon_folder + "/" + iconsize + "/unknown.png";
556    } else {
557
558      if (tags[icon_tag].indexOf(";") >= 0) // more than one item, take generic icon
559        icon_url = assethost+"assets/transformap/pngs/" + overpass_config.icon_folder + "/" + iconsize + "/generic.png";
560      else
561        icon_url = assethost+"assets/transformap/pngs/" + overpass_config.icon_folder + "/" + iconsize + "/" + icon_tag + "=" + tags[icon_tag] + ".png";
562    }
563    return icon_url;
564}
565
566var disableLoadPOI = false;
567
568var bboxes_requested = [
569    /* e.g. { outer_bbox: L.LatLngBounds, inner_polyboxes: [ L.LatLngBounds, L.LatLngBounds, ... ]  }, { ... } */
570    ]; //VERY SIMPLE list of areas already requested, don't load if new one is inside
571
572/*contains(2) {
573    return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&     // south2 >= south && north2 <= north &&
574           (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);       // west2 >= west && east2 <= east
575}
576intersects(2) {
577    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
578    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
579
580    return latIntersects && lngIntersects;
581}*/
582
583/*
584 * returns true if it already was in BBOX,
585 * and false if it had to be added
586 */
587function checkIfInRequestedBboxesAndIfNotaddTo(bounds) {
588    var north = bounds._northEast.lat,  // for performance reasons, do this one time
589        east  = bounds._northEast.lng,
590        south = bounds._southWest.lat,
591        west  = bounds._southWest.lng;
592    var b_height = north - south,
593        b_width  = east - west,
594        width_to_height = b_width/b_height;
595
596    var zoom_delta_to_max = map.getMaxZoom() - map.getZoom(),
597        min_height = b_height / (Math.pow(2,zoom_delta_to_max));
598//      console.log("checkIfInRequestedBboxesAndIfNotaddTo: viewbox_height = " + viewbox_height + "°, min_heigh = " + min_height);
599    var min_width = b_width / (Math.pow(2,zoom_delta_to_max));
600
601 //   console.log("checkIfInRequestedBboxesAndIfNotaddTo: called. bboxes_requested:" + JSON.stringify(bboxes_requested));
602    for(var i=0; i < bboxes_requested.length; i++) { //for-loop over outer bboxes
603        var current_bound = bboxes_requested[i];
604        if(! current_bound.outer_bbox.intersects(bounds)) //neither crossing nor inside
605            continue;
606        console.log("checkIfInRequestedBboxesAndIfNotaddTo begin: " + current_bound.inner_polyboxes.length + " inner polyboxes");
607        if(current_bound.outer_bbox.contains(bounds)) { // only if it doesn't cross outer bbox it is possible that it's already covered
608            for(var inner_bbox_i = 0; inner_bbox_i < current_bound.inner_polyboxes.length; inner_bbox_i++) {
609                current_inner_bbox = current_bound.inner_polyboxes[inner_bbox_i];
610                if(!current_inner_bbox.hasOwnProperty("_southWest")) {
611                    console.log("current_inner_bbox error:");
612                    console.log(current_inner_bbox);
613                    console.log(current_bound);
614                }
615                //current_inner_bbox.contains(bounds):
616                if(south < current_inner_bbox._southWest.lat || 
617                   north > current_inner_bbox._northEast.lat ||
618                   west  < current_inner_bbox._southWest.lng ||
619                   east  > current_inner_bbox._northEast.lng)
620                    continue;
621                return true; // all ¬ contains-checks failed, must be contained
622
623/*                if(current_inner_bbox.contains(bounds)) {
624 //                   console.log("checkIfInRequestedBboxesAndIfNotaddTo: bbox already here");
625                    return true;
626                    
627                } //false == continue*/
628            }
629        }
630        else //intersects one border
631            current_bound.outer_bbox.extend(bounds);
632        // either intersects with outer bbox or is not in any inner bbox
633        // add one horizontal and one vertical box that it intersects for EACH box!
634        var new_bboxes = [],
635            new_bounds_north_stretched = null,
636            new_bounds_south_stretched = null,
637            new_bounds_west_stretched = null,
638            new_bounds_east_stretched = null;
639        var nr_of_deletions = 0;
640        var addbounds = true;
641        for(var inner_bbox_i = 0; inner_bbox_i < current_bound.inner_polyboxes.length; inner_bbox_i++) {
642            current_inner_bbox = current_bound.inner_polyboxes[inner_bbox_i];
643
644            //if ! current_inner_bbox.intersects(bounds) continue (both boxes completely disjunct)
645            if(south >= current_inner_bbox._northEast.lat ||
646               north <= current_inner_bbox._southWest.lat ||
647               east  <= current_inner_bbox._southWest.lng ||
648               west  >= current_inner_bbox._northEast.lng)
649                continue;
650
651            /* 16 cases:
652                1.: bounds completely inside current_inner_bbox
653                    -> is already covered by 1st for-loop with inner_polyboxes
654                2.: current_inner_bbox completely inside -> delete it; continue (empty array slots cleared afterwards)
655                                                                        -> use delete array[i], it does not reindex. reindex afterwards, remove all undefined...
656                3-10: 8 where one box is crosses only one side of the other
657                   3-6:  4 where bounds is bigger
658                   7-10: 4 where current_inner_bbox is bigger
659                11-14: 4 where they cross them on two sides (corner-crossing)
660                15,15: as stretching boxes can be longer, heigher it can happen that they form a "cross" -> simply add bounds, no special handling needed
661                    =^= (bwb && beb && !bnb && !bsb) || (!bwb && !bwb && bnb && bsb)
662
663             *  WHAT IF some bounds are equal? TODO
664             *  FIXME a lot of dual boxes seem to be generated... -OK they were if a box hits one with same border
665             */
666
667            /* TODO shrinking of bounds:
668             *   stretching of inner_polyboxes is still needed, because: you don't know which side gets cut first, and if inner_polyboxes don't overlap,
669             * only a corner would get cutted out (which isn't possible on rectangles)
670             *
671             * with each run of the inner for-loop we can cut bounds smaller. if nothing left, it ... have to move first for-loop in!
672             */
673
674            var boundsnorth_bigger = (north >= current_inner_bbox._northEast.lat),
675                boundssouth_bigger = (south <= current_inner_bbox._southWest.lat),
676                boundseast_bigger = (east >= current_inner_bbox._northEast.lng),
677                boundswest_bigger = (west <= current_inner_bbox._southWest.lng);
678            console.log("bnb:"+boundsnorth_bigger+" bsb:"+boundssouth_bigger+" beb:"+boundseast_bigger+" bwb:"+boundswest_bigger);
679
680            var bounds_bigger_on_howmany_sides = (boundsnorth_bigger ? 1 : 0) + (boundssouth_bigger ? 1 : 0) + (boundseast_bigger ? 1 : 0) + (boundswest_bigger);
681            console.log("bounds_bigger_on_howmany_sides: " + bounds_bigger_on_howmany_sides);
682
683            if(bounds_bigger_on_howmany_sides == 4) { // case 2
684                delete current_bound.inner_polyboxes[inner_bbox_i];
685                nr_of_deletions++;
686                continue;
687            }
688
689            //box is an autogenerated stretching box - these are always inside downloaded boxes, ignore for generating more.
690            if(current_inner_bbox.hasOwnProperty('stretch'))
691                continue;
692
693            if(bounds_bigger_on_howmany_sides == 3) { // cases 3-6
694                if(!boundsnorth_bigger) { // current_inner_bbox crosses bounds on north, stretch southward
695                    console.log("3-6: !bnb");
696                    var inner_height = current_inner_bbox._northEast.lat - current_inner_bbox._southWest.lat;
697                    var max_to_stretch = north - inner_height;
698                    current_inner_bbox._southWest.lat = (max_to_stretch < south) ? south : max_to_stretch;
699                    continue;
700                }
701                if(!boundssouth_bigger) {
702                    console.log("3-6: !bsb");
703                    var inner_height = current_inner_bbox._northEast.lat - current_inner_bbox._southWest.lat;
704                    var max_to_stretch = south + inner_height;
705                    current_inner_bbox._northEast.lat = (max_to_stretch > north) ? north : max_to_stretch;
706                    continue;
707                }
708                if(!boundseast_bigger) {
709                    console.log("3-6: !beb");
710                    var inner_width = current_inner_bbox._northEast.lng - current_inner_bbox._southWest.lng;
711                    var max_to_stretch = east - inner_width;
712                    current_inner_bbox._southWest.lng = (max_to_stretch < west) ? west : max_to_stretch;
713                    continue;
714                }
715                if(!boundswest_bigger) {
716                    console.log("3-6: !bwb");
717                    var inner_width = current_inner_bbox._northEast.lng - current_inner_bbox._southWest.lng;
718                    var max_to_stretch = west + inner_width;
719                    current_inner_bbox._northEast.lng = (max_to_stretch > east) ? east : max_to_stretch;
720                    continue;
721                }
722                console.log("shouldn't have reached bounds_bigger_on_howmany_sides == 3 after ????????????");
723            }
724            if(bounds_bigger_on_howmany_sides == 1) { // cases 7-10
725                addbounds = false; // we add a bigger box instead
726                if(boundssouth_bigger) { // bounds crosses current_inner_bbox on south, stretch northwards
727                    console.log("7-10: bsb");
728                    var max_to_stretch = current_inner_bbox._southWest.lat + b_height;
729                    var new_north = (max_to_stretch > current_inner_bbox._northEast.lat) ? current_inner_bbox._northEast.lat : max_to_stretch;
730                    if(!new_bounds_north_stretched) {
731                        new_bounds_north_stretched = new L.latLngBounds( [ south, west ], [ new_north, east ] );
732                    } else {
733                        if(new_bounds_north_stretched._northEast.lat < new_north)
734                            new_bounds_north_stretched._northEast.lat = new_north;
735                    }
736                    //TODO shrink bounds!
737                    continue;
738                }
739                if(boundsnorth_bigger) { // bounds crosses current_inner_bbox on north, stretch southwards
740                    console.log("7-10: bnb");
741                    var max_to_stretch = current_inner_bbox._northEast.lat - b_height;
742                    var new_south = (max_to_stretch < current_inner_bbox._southWest.lat) ? current_inner_bbox._southWest.lat : max_to_stretch;
743                    if(!new_bounds_south_stretched) {
744                        new_bounds_south_stretched = new L.latLngBounds( [ new_south, west ], [ north, east ] );
745                    } else {
746                        if(new_bounds_south_stretched._southWest.lat > new_south)
747                            new_bounds_south_stretched._southWest.lat = new_south;
748                    }
749                    //TODO shrink bounds!
750                    continue;
751                }
752                if(boundseast_bigger) { // bounds crosses current_inner_bbox on east, stretch westwards
753                    console.log("7-10: beb");
754                    var max_to_stretch = current_inner_bbox._northEast.lng - b_width;
755                    var new_west = (max_to_stretch < current_inner_bbox._southWest.lng) ? current_inner_bbox._southWest.lng : max_to_stretch;
756                    if(!new_bounds_west_stretched) {
757                        new_bounds_west_stretched = new L.latLngBounds( [ south, new_west ], [ north, east ] );
758                    } else {
759                        if(new_bounds_west_stretched._southWest.lng > new_west)
760                            new_bounds_west_stretched._southWest.lng = new_west;
761                    }
762                    //TODO shrink bounds!
763                    continue;
764                }
765                if(boundswest_bigger) { // bounds crosses current_inner_bbox on west, stretch eastwards
766                    console.log("7-10: bwb");
767                    var max_to_stretch = current_inner_bbox._southWest.lng + b_width;
768                    var new_east = (max_to_stretch > current_inner_bbox._northEast.lng) ? current_inner_bbox._northEast.lng : max_to_stretch;
769                    if(!new_bounds_east_stretched) {
770                        new_bounds_east_stretched = new L.latLngBounds( [ south, west ], [ north, new_east ] );
771                    } else {
772                        if(new_bounds_east_stretched._northEast.lng < new_east)
773                            new_bounds_east_stretched._northEast.lng = new_east;
774                    }
775                    //TODO shrink bounds!
776                    continue;
777                }
778                console.log("shouldn't have reached bounds_bigger_on_howmany_sides == 1 after ????????????");
779            }
780            if(bounds_bigger_on_howmany_sides == 2) { //cases 11-16
781                if( (boundswest_bigger && boundseast_bigger && !boundsnorth_bigger && !boundssouth_bigger) ||
782                    (!boundswest_bigger && !boundseast_bigger && boundsnorth_bigger && boundssouth_bigger) )
783                    continue;  // case 15+16
784
785                    /*we only need an stretching box if:
786                     *  xbox only if overlapping_x / width_to_height < overlap_y
787                     *  ybox only if overlapping_x < overlap_y * width_to_height
788                     *  -> either an x OR an y-box!
789                     */
790                if(boundsnorth_bigger && boundseast_bigger && !boundssouth_bigger && !boundswest_bigger) {
791                    console.log("11-14: b topright, cib bottomleft ");
792                    var overlap_x = current_inner_bbox._northEast.lng - west,
793                        overlap_y = current_inner_bbox._northEast.lat - south;
794                    if(overlap_x < overlap_y * width_to_height) {
795                        //x stretching box
796                        var b_N = current_inner_bbox._northEast.lat,
797                            b_S = south,
798                            xb_height = b_N - b_S;
799                        if(xb_height <= min_height) continue; //console.log("no xb, to small");
800
801                        var x_stretch_value = xb_height * width_to_height;
802
803                        var xb_E_max = west + x_stretch_value,
804                            b_E = (xb_E_max > east) ? east : xb_E_max;
805
806                        var xb_W_max = current_inner_bbox._northEast.lng - x_stretch_value,
807                            b_W = (xb_W_max < current_inner_bbox._southWest.lng) ? current_inner_bbox._southWest.lng : xb_W_max;
808                        console.log("new xb");
809                    } else {
810                        //y stretching box
811                        var b_E = current_inner_bbox._northEast.lng,
812                            b_W = west,
813                            yb_width = b_E - b_W;
814                        if(yb_width <= min_width) continue; //console.log("no yb, to small");
815
816                        var y_stretch_value = yb_width / width_to_height;
817
818                        var yb_N_max = south + y_stretch_value,
819                            b_N = (yb_N_max > north) ? north : yb_N_max;
820
821                        var yb_S_max = current_inner_bbox._northEast.lat - y_stretch_value,
822                            b_S = (yb_S_max < current_inner_bbox._southWest.lat) ? current_inner_bbox._southWest.lat : yb_S_max;
823                        console.log("new yb");
824                    }
825                }
826                if(boundsnorth_bigger && !boundseast_bigger && !boundssouth_bigger && boundswest_bigger) {
827                    console.log("11-14: b topleft, cib bottomright ");
828                    var overlap_x = east - current_inner_bbox._southWest.lng,
829                        overlap_y = current_inner_bbox._northEast.lat - south;
830                    if(overlap_x < overlap_y * width_to_height) {//x stretching box
831                        var b_N = current_inner_bbox._northEast.lat,
832                            b_S = south,
833                            xb_height = b_N - b_S;
834                        if(xb_height <= min_height) continue;//console.log("no xb, to small");
835
836                        var x_stretch_value = xb_height * width_to_height;
837
838                        var xb_E_max = current_inner_bbox._southWest.lng + x_stretch_value,
839                            b_E = (xb_E_max > current_inner_bbox._northEast.lng) ? current_inner_bbox._northEast.lng : xb_E_max;
840
841                        var xb_W_max = east - x_stretch_value,
842                            b_W = (xb_W_max < west) ? west : xb_W_max;
843                        console.log("new xb");
844                    } else {//y stretching box
845                        var b_E = east,
846                            b_W = current_inner_bbox._southWest.lng,
847                            yb_width = b_E - b_W;
848                        if(yb_width <= min_width) continue; //console.log("no yb, to small");
849
850                        var y_stretch_value = yb_width / width_to_height;
851
852                        var yb_N_max = south + y_stretch_value,
853                            b_N = (yb_N_max > north) ? north : yb_N_max;
854
855                        var yb_S_max = current_inner_bbox._northEast.lat - y_stretch_value,
856                            b_S = (yb_S_max < current_inner_bbox._southWest.lat) ? current_inner_bbox._southWest.lat : yb_S_max;
857                        console.log("new yb");
858                    }
859                }
860                if(!boundsnorth_bigger && !boundseast_bigger && boundssouth_bigger && boundswest_bigger) {
861                    console.log("11-14: b bottomleft, cib topright ");
862                    var overlap_x = east - current_inner_bbox._southWest.lng,
863                        overlap_y = north - current_inner_bbox._southWest.lat;
864                    if(overlap_x < overlap_y * width_to_height) {//x stretching box
865                        var b_N = north,
866                            b_S = current_inner_bbox._southWest.lat,
867                            xb_height = b_N - b_S;
868                        if(xb_height <= min_height) continue;//console.log("no xb, to small");
869
870                        var x_stretch_value = xb_height * width_to_height;
871
872                        var xb_E_max = current_inner_bbox._southWest.lng + x_stretch_value,
873                            b_E = (xb_E_max > current_inner_bbox._northEast.lng) ? current_inner_bbox._northEast.lng : xb_E_max;
874
875                        var xb_W_max = east - x_stretch_value,
876                            b_W = (xb_W_max < west) ? west : xb_W_max;
877                        console.log("new xb");
878                    } else {//y stretching box
879                        var b_E = east,
880                            b_W = current_inner_bbox._southWest.lng,
881                            yb_width = b_E - b_W;
882                        if(yb_width <= min_width) continue; //console.log("no yb, to small");
883
884                        var y_stretch_value = yb_width / width_to_height;
885
886                        var yb_N_max = current_inner_bbox._southWest.lat + y_stretch_value,
887                            b_N = (yb_N_max > current_inner_bbox._northEast.lat) ? current_inner_bbox._northEast.lat : yb_N_max;
888
889                        var yb_S_max = north - y_stretch_value,
890                            b_S = (yb_S_max < south) ? south : yb_S_max;
891                        console.log("new yb");
892                    }
893                }
894                if(!boundsnorth_bigger && boundseast_bigger && boundssouth_bigger && !boundswest_bigger) {
895                    console.log("11-14: b bottomright, cib topleft ");
896                    var overlap_x = current_inner_bbox._northEast.lng - west,
897                        overlap_y = north - current_inner_bbox._southWest.lat;
898                    if(overlap_x < overlap_y * width_to_height) {//x stretching box
899                        var b_N = north,
900                            b_S = current_inner_bbox._southWest.lat,
901                            xb_height = b_N - b_S;
902                        if(xb_height <= min_height) continue;//console.log("no xb, to small");
903
904                        var x_stretch_value = xb_height * width_to_height;
905
906                        var xb_E_max = west + x_stretch_value,
907                             b_E = (xb_E_max > east) ? east : xb_E_max;
908
909                        var xb_W_max = current_inner_bbox._northEast.lng - x_stretch_value,
910                            b_W = (xb_W_max < current_inner_bbox._southWest.lng) ? current_inner_bbox._southWest.lng : xb_W_max;
911                        console.log("new xb");
912                    } else {//y stretching box
913                        var b_E = current_inner_bbox._northEast.lng,
914                            b_W = west,
915                            yb_width = b_E - b_W;
916                        if(yb_width <= min_width) continue; //console.log("no yb, to small");
917
918                        var y_stretch_value = yb_width / width_to_height;
919
920                        var yb_N_max = current_inner_bbox._southWest.lat + y_stretch_value,
921                            b_N = (yb_N_max > current_inner_bbox._northEast.lat) ? current_inner_bbox._northEast.lat : yb_N_max;
922
923                        var yb_S_max = north - y_stretch_value,
924                            b_S = (yb_S_max < south) ? south : yb_S_max;
925                        console.log("new yb");
926                    }
927                }
928                stretching_box = new L.latLngBounds( [ b_S, b_W ], [ b_N, b_E ] );
929                stretching_box.stretch = true; //add distinguishment from an orig box
930                new_bboxes.push( stretching_box );
931                continue;
932            }
933        }
934 /*       if(new_bboxes) {
935            console.log("checkIfInRequestedBboxesAndIfNotaddTo: new stretching bboxes to add:");
936            console.log(new_bboxes);
937        } else
938            console.log("checkIfInRequestedBboxesAndIfNotaddTo: no net stretching bboxes to add.");*/
939
940        //delete
941        if(nr_of_deletions)
942            console.log(nr_of_deletions + " to delete");
943        for(var inner_bbox_i = 0; nr_of_deletions ; inner_bbox_i++) {
944            if(! current_bound.inner_polyboxes[inner_bbox_i]) {
945                current_bound.inner_polyboxes.splice(inner_bbox_i--,1);
946                nr_of_deletions--;
947            }
948        }
949
950        if(addbounds)
951            current_bound.inner_polyboxes.push(bounds);
952
953        for(var new_i=0; new_i < new_bboxes.length; new_i++)
954            current_bound.inner_polyboxes.push(new_bboxes[new_i]);
955
956        var stretchbounds = { 1: new_bounds_east_stretched, 2: new_bounds_west_stretched, 3: new_bounds_south_stretched, 4: new_bounds_north_stretched };
957        for (i in stretchbounds)
958            if(stretchbounds[i])
959                current_bound.inner_polyboxes.push(stretchbounds[i]);
960
961        console.log("checkIfInRequestedBboxesAndIfNotaddTo: " + current_bound.inner_polyboxes.length + " inner polyboxes now.");
962        return false;
963    }
964    //it is completely outside from every outer bbox, create a new one
965    var new_outer_box_deep_copy = new L.latLngBounds( [ south, west ], [ north, east ] );
966    var bounds_deep_copy = new L.latLngBounds( [ south, west ], [ north, east ] );
967    bboxes_requested.push( { outer_bbox: new_outer_box_deep_copy, inner_polyboxes: [ bounds_deep_copy ] } );
968 //   console.log("checkIfInRequestedBboxesAndIfNotaddTo: outside other bboxes, add a new");
969    return false;
970}
971
972//https://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
973function rainbow(numOfSteps, step) {
974    // This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distinguishable vibrant markers in Google Maps and other apps.
975    // Adam Cole, 2011-Sept-14
976    // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-
976and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
977    var r, g, b;
978    var h = step / numOfSteps;
979    var i = ~~(h * 6);
980    var f = h * 6 - i;
981    var q = 1 - f;
982    switch(i % 6){
983        case 0: r = 1; g = f; b = 0; break;
984        case 1: r = q; g = 1; b = 0; break;
985        case 2: r = 0; g = 1; b = f; break;
986        case 3: r = 0; g = q; b = 1; break;
987        case 4: r = f; g = 0; b = 1; break;
988        case 5: r = 1; g = 0; b = q; break;
989    }
990    var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
991    return (c);
992}
993
994var debugLayerShown = false;
995function toggleDebugLayer() {
996    debugLayerShown = !debugLayerShown;
997    disableLoadPOI = true;
998    if(!debugLayerShown) {
999        debugLayer.clearLayers();
1000        disableLoadPOI = false;
1001        return;
1002    }
1003
1004    for(var i=0; i < bboxes_requested.length; i++) {
1005        var current_bound = bboxes_requested[i];
1006        for(var inner_bbox_i = 0; inner_bbox_i < current_bound.inner_polyboxes.length; inner_bbox_i++) {
1007            current_inner_bbox = current_bound.inner_polyboxes[inner_bbox_i];
1008            if(current_inner_bbox.stretch)
1009                console.log("stretch");
1010            var rect = L.rectangle(current_inner_bbox, {color: rainbow(256, Math.floor(Math.random() * 256)), weight: (current_inner_bbox.stretch) ? 2 : 8, fill: false}).on('click',clickOnLayer);
1011            debugLayer.addLayer(rect);
1012            //debugLayer.addLayer(L.rectangle(current_inner_bbox, {color: rainbow(256, 128), weight: 1, fill: false}));
1013        }
1014
1015    }
1016
1017    var dup_dump = {};
1018    var sum = 0;
1019    //find dups - there were a LOT of dups!
1020    for(var i=0; i < bboxes_requested.length; i++) {
1021        var current_bound = bboxes_requested[i];
1022        for(var inner_bbox_i = 0; inner_bbox_i < current_bound.inner_polyboxes.length; inner_bbox_i++) {
1023            current_inner_bbox = current_bound.inner_polyboxes[inner_bbox_i];
1024            var north = L.Util.formatNum(current_inner_bbox._northEast.lat,7),
1025                east  = L.Util.formatNum(current_inner_bbox._northEast.lng,7),
1026                south = L.Util.formatNum(current_inner_bbox._southWest.lat,7),
1027                west  = L.Util.formatNum(current_inner_bbox._southWest.lng,7);
1028
1029            //sort dup_dumps on north,east,south,west coordinate
1030            // dump[north][east][south][west] = true if bbox exists
1031            if(!dup_dump[north]) {
1032                var west_obj = 1;
1033                var south_obj = {};
1034                    south_obj[west] = west_obj;
1035                var east_obj = {};
1036                    east_obj[south] = south_obj;
1037                var north_obj = {};
1038                    north_obj[east] = east_obj;
1039                dup_dump[north] = north_obj;
1040            } else {
1041                if(!dup_dump[north][east]) {
1042                    var west_obj = 1;
1043                    var south_obj = {};
1044                        south_obj[west] = west_obj;
1045                    var east_obj = {};
1046                        east_obj[south] = south_obj;
1047                    dup_dump[north][east] = east_obj;
1048                } else {
1049                    if(!dup_dump[north][east][south]) {
1050                        var west_obj = 1;
1051                        var south_obj = {};
1052                            south_obj[west] = west_obj;
1053                        dup_dump[north][east][south] = south_obj;
1054                    } else {
1055                        if(!dup_dump[north][east][south][west]) {
1056                            dup_dump[north][east][south][west] = 1;
1057                        } else {
1058                            dup_dump[north][east][south][west]++;
1059                        }
1060                    }
1061                }
1062            }
1063        }
1064        var different = 0;
1065        var dups = 0;
1066        for(n in dup_dump) {
1067            for(e in dup_dump[n]) {
1068                for(s in dup_dump[n][e]) {
1069                    for(w in dup_dump[n][e][s]) {
1070                        console.log("n"+n+" e"+e+" s"+s+" w"+w+" : "+ dup_dump[n][e][s][w]);
1071                        dups = dups + dup_dump[n][e][s][w] -1;
1072                        different++;
1073                    }
1074                }
1075            }
1076        }
1077        console.log(dup_dump);
1078        console.log("unique: "+different+" of " + current_bound.inner_polyboxes.length + ", " + dups + " dups");
1079    }
1080}
1081
1082function clickOnLayer(e) {
1083    console.log(e.target._parts);
1084//    e.bringToFront();
1085}
1086
1087var wikidata_mappings = { tags : {}, ids : {} },
1088    translations = { tags: {} };
1089
1090/* how to get object descriptions:
1091ask wikidata for an object that has osm property $object_tag
1092https://wdq.wmflabs.org/api?q=string[214:%2264192849%22]
1093osm tag property is P1282
1094
1095https://wdq.wmflabs.org/api?q=string[1282:"Tag:amenity=restaurant"]
1096* takes  object_tag = "key=value"
1097*/
1098function fetchTranslationsFromWikiData(object_tag) {
1099    if(object_tag.indexOf("=") == -1) {
1100        console.log("Error in fetchTranslationsFromWikiData: tag has to be in key=value format, not :'" + object_tag + "'");
1101        return;
1102    }
1103
1104    var kv = object_tag.split("="),
1105        value = kv[1],
1106        osm_string = (value == "yes") ? "Key:" + kv[0] : "Tag:" + object_tag;
1107
1108
1109    if(!wikidata_mappings.tags[object_tag]) {
1110        var wikidata_query = 'https://wdq.wmflabs.org/api?q=string[1282:"' + osm_string + '"]'; //FIXME is it possible to retrieve page Q$id in one call?
1111        /*
1112           returns
1113                {"status":{
1114                    "error":"OK",
1115                    "items":1,
1116                    "querytime":"0ms",
1117                    "parsed_query":"STRING[1282:'Tag:amenity=restaurant']"},
1118                  "items":[11707]
1119                }
1120        */
1121
1122        $.getJSON(wikidata_query + "&callback=?", function(data) {
1123            if(data.status.error != "OK") {
1124                console.log("wikidata returned " + data.status.error + " for query '" + data.status.parsed_query + "'.");
1125                return;
1126            }
1127            var tag_array = data.status.parsed_query.match(/1282:'(Tag|Key):([a-z:_0-9-]+)[=]?(.*)']$/);
1128            var tag = (tag_array[1] == "Key" ) ? tag_array[2] + "=yes" : tag_array[2] + "=" + tag_array[3];
1129            console.log("wdm: " + tag);
1130            if(data.items.length == 0) {
1131                console.log("nothing found in wikidata for query '" + data.status.parsed_query + "'.");
1132                console.log(data);
1133                wikidata_mappings.tags[tag] = -1;
1134                return;
1135            }
1136            var item;
1137            for(var i=0; i < data.items.length; i++) { //takes last one
1138                item = data.items[i];
1139                //console.log("got answer from wikidata for query '" + data.status.parsed_query + "': '" + item + "'.");
1140            }
1141
1142            wikidata_mappings.tags[tag] = "Q" + item;
1143            wikidata_mappings.ids["Q" + item] = tag;
1144
1145            var wikidata_object_query = "https://www.wikidata.org/wiki/Special:EntityData/Q" + item + ".json"; //Note: throws CORS error if item == undefined
1146            $.getJSON(wikidata_object_query, function(data) {
1147                if(!data.entities) {
1148                    console.log("wikidata returned no entities");
1149                    return;
1150                }
1151                for(q_number in data.entities) {
1152                    var item = data.entities[q_number];
1153                    translations.tags[wikidata_mappings.ids[q_number]] = item.labels;
1154                }
1155                setTimeout(setTranslationsInPopup,100);
1156            });
1157        });
1158        wikidata_mappings.tags[object_tag] = "loading";
1159    }
1160}
1161
1162/* out of given tags, looks up "main keys" out of global var object_type_keys,
1163 * and returns a nice array with each tag split up in a single entry
1164 * tags[ "amenity=cafe; fast_food", "shop=bakery", ... ] → [ "amenity=cafe", "amenity=fast_food", "shop=bakery" ]
1165 */
1166function getMainTags(tags) {
1167    object_tags = [];
1168    for(var i=0; i < object_type_keys.length; i++) {
1169        var key = object_type_keys[i];
1170        if(tags[key]) {
1171            var value = tags[key];
1172            if(value.indexOf(";") >= 0) {
1173                var values = value.split(";");
1174                for (var j=0; j < values.length; j++) {
1175                    object_tags.push(key + "=" + values[j].trim());
1176                }
1177            }
1178            else
1179                object_tags.push(key + "=" + value);
1180        }
1181    }
1182    console.log(object_tags);
1183    return object_tags;
1184}
1185
1186function loadPoi() {
1187
1188  var notificationbar =  document.getElementById("notificationbar");
1189  if (map.getZoom() < overpass_config.minzoom ) {
1190    notificationbar.style.display = "block";
1191    if(on_start_loaded && window.createSideBar) {
1192      var json_date = new Date(pois_lz.osm3s.timestamp_osm_base);
1193      $('#tall').html("Lowzoom data: " + json_date.toLocaleString());
1194      $('#tnode').css("display", "hidden");
1195      $('#tway').css("display", "hidden");
1196      $('#trel').css("display", "hidden");
1197      return;
1198    }
1199
1200    if(pois_lz) { 
1201      console.log(pois_lz);
1202      console.log("adding lz POIs");
1203
1204      changeLoadingIndicator("loading_node",+1);
1205      handleNodes(pois_lz); 
1206
1207      changeLoadingIndicator("loading_way",+1);
1208      handleWays(pois_lz); 
1209
1210      changeLoadingIndicator("loading_relation",+1);
1211      handleRelations(pois_lz); 
1212
1213      if(window.createSideBar) {
1214          var json_date = new Date(pois_lz.osm3s.timestamp_osm_base);
1215          $('#tall').html("Lowzoom data: " + json_date.toLocaleString());
1216          $('#tnode').css("display", "hidden");
1217          $('#tway').css("display", "hidden");
1218          $('#trel').css("display", "hidden");
1219      }
1220
1221      on_start_loaded = 1;
1222    }
1223
1224    return;
1225  }
1226  notificationbar.style.display = "none";
1227
1228  var current_zoom = map.getZoom();
1229  console.log("loadPOI called, z" + current_zoom);
1230  if(current_zoom > old_zoom && current_zoom != overpass_config.minzoom) {
1231    console.log("zooming in, POI already loaded, nothing to to");
1232    old_zoom = current_zoom;
1233    return;
1234  }
1235  old_zoom = current_zoom;
1236
1237  if(disableLoadPOI)
1238      return;
1239
1240  // if Graz (start position, gets called once first) do nothing
1241      var centre = map.getCenter();
1242  if (centre.lat == 47.07 && centre.lng == 15.43) {
1243      var splitstr = window.location.href.split('#');
1244      if (splitstr[1]) {
1245          var coords_href = splitstr[1].split('/');
1246          if ( coords_href[1] == 47.07 && coords_href[2] == 15.43 )
1247              console.log("look, we are really in Graz");
1248          else {
1249              console.log("1st call, return");
1250              return;
1251          }
1252      }
1253  }
1254
1255  var bounds = map.getBounds();
1256  if(checkIfInRequestedBboxesAndIfNotaddTo(bounds))
1257      return;
1258
1259  function url_ify(link,linktext) {
1260    if (/@/.test(link)) {
1261      return '<a href="mailto:' + link + '">' + secHTML(linktext) + '</a>';
1262    } else if (/^[0-9-+\s]*$/.test(link)) {
1263      return '<a href="tel:' + link + '">' + linktext.replace(/\s/g,"&nbsp;") + '</a>'; //FIXME how to prefix fax nrs?
1264    } else {
1265      if ( ! /^http/.test(link) ) //http[s] is implicit here
1266        link = "http://" + link;
1267      return $("<div>").append( $('<a>').attr('href', link).text(linktext) ).html();
1268    }
1269  }
1270
1271  function fillPopup(tags,type,id,lat,lon) {
1272
1273    var tags_to_ignore = [ "name" , "ref", "addr:street", "addr:housenumber", "addr:postcode", "addr:city", "addr:suburb", "addr:country","website","url","contact:website","contact:url","email","contact:email","phone","contact:phone","fax","contact:fax","created_by","area","layer","room","indoor","twitter","contact:twitter","link:twitter", "contact:google_plus", "google_plus", "link:google_plus", "contact:facebook","facebook","link:facebook","facebook:page","website:facebook","url:facebook","contact:youtube","youtube","link:youtube","wheelchair","wheelchair:description","wikipedia","wikidata","image" ];
1274
1275    var r = $('<table>');
1276
1277    var href = location.href.replace(/#.*$/,'').replace(/[&?]popup=(node|way|relation)[0-9]+/,'');
1278    var hasQuery = href.indexOf("?") + 1;
1279    var appendix = (hasQuery ? "&" : "?") + "popup=" + type + id;
1280
1281    var popup_href =  href + appendix + '#' + map.getZoom() + '/' + lat + '/' + lon;
1282
1283    r.append($('<tr>')
1284            .attr('class','header')
1285            .append($('<td>').append('<a href="' + popup_href + '" title="Link to this POI on this map">Permalink</a>'))
1286            .append($('<td>').append('<a href="http://editor.transformap.co/#background=Bing&id=' + type.substring(0,1) + id + '&map=19/' + lon + '/' + lat + '" title="Edit this object with iD for TransforMap" target=_blank>Edit</a>'))
1287        );
1288    r.append($('<tr>')
1289            .attr('class','header')
1290            .append($('<td>').append('<a href="https://www.openstreetmap.org/' + type + "/" 
1290+ id + '" title="Link to ' + type + ' ' + id + ' on OpenStreetMap" target=_blank><img src="'+assethost+'assets/20px-Mf_' + type + '.svg.png" />' + type.substring(0,1) + id + '</a>'))
1291            .append($('<td>').append('<a href="http://map.project-osrm.org/?dest=' + lat + ',' + lon + '&destname=' + tags['name'] + '" target=_blank title="Route here with OSRM">Route Here</a>'))
1292        );
1293    var wikipedia_link = "";
1294
1295    function addSocialMediaLinks(tags) {
1296        var string = "";
1297
1298        for (key in tags) {
1299            var value = tags[key].replace(/^http[s]?:\/\//,""); //we add https later, regardless of link
1300            var valuestring = decodeURIComponent(value); 
1301            var img = "<img src='"+assethost+"assets/";
1302
1303            if(/twitter/.test(key)) {
1304                if(! /twitter.com\//.test(value)) valuestring = "twitter.com/" + valuestring;
1305                img += "twitter.16.png' title='on Twitter' />";
1306            } else
1307            if(/facebook/.test(key) || (/facebook.com\/|fb.com\//.test(value) && /website/.test(key)) ) {
1308                if(! /facebook.com\/|fb.com\//.test(value)) valuestring = "facebook.com/" + valuestring;
1309                img += "facebook.16.png' title='Facebook-page' />";
1310            } else
1311            if(/google_plus/.test(key)) {
1312                if(! /plus.google.com\//.test(value)) valuestring = "plus.google.com/" + valuestring;
1313                img += "g+.16.png' title='Google+ page' />";
1314            } else
1315            if(/youtube/.test(key)) {
1316                if(! /youtube.com\//.test(value)) valuestring = "youtube.com/" + valuestring;
1317                img += "YouTube.16.png' title='YouTube channel' />";
1318            } else
1319            if(/^wikipedia/.test(key)) {
1320                var lang = key.match(/^(?:wikipedia:)([a-z-]{2,7})$/) || ""; // if value starts with e.g. "de:ARTICLE", this works in WP anyway
1321                if(! /wikipedia\./.test(value)) valuestring = "wikipedia.org/wiki/" + valuestring;
1322                if(lang) valuestring = lang[1] + "." + valuestring;
1323
1324                img += "wikipedia.16.png' title='Wikipedia Article' />";
1325                wikipedia_link = "https://" + valuestring;
1326            } else
1327            if(/wikidata/.test(key)) {
1328                if(! /wikidata.org\//.test(value)) valuestring = "wikidata.org/wiki/" + valuestring;
1329                img += "wikidata.16.png' title='Wikidata Entry' />";
1330            } else
1331                continue;
1332
1333            valuestring = "https://" + valuestring;
1334
1335            string += "<a href='" + valuestring + "'>" + img + "</a> ";
1336        }
1337
1338        return string;
1339    }
1340
1341    var social_media_links = addSocialMediaLinks(tags);
1342
1343    if(tags["addr:street"] || tags["addr:housenumber"] || tags["addr:postcode"] || tags["addr:city"] || tags["addr:suburb"] || tags["addr:country"]
1344            || tags["website"] || tags["url"] || tags["contact:website"] || tags["contact:url"] || tags["email"] || tags["contact:email"]
1345            || tags["phone"] || tags["contact:phone"] || tags["fax"] || tags["contact:fax"] || tags["wheelchair"] || social_media_links) {
1346        r.append($('<tr>')
1347                .attr('class','header')
1348                .append($('<td>').append(
1349              (tags["addr:street"] ? tags["addr:street"] : "" ) +
1350              (tags["addr:housenumber"] ? ("&nbsp;" + tags["addr:housenumber"]) : "" ) + 
1351              ( (tags["addr:housenumber"] || tags["addr:street"]) ? ",<br>" : "" ) +
1352              (tags["addr:postcode"] ? (tags["addr:postcode"] + " ") : "" ) +
1353              (tags["addr:city"] ? tags["addr:city"] : "" ) + 
1354              (tags["addr:suburb"] ? "-" + tags["addr:suburb"] : "") +
1355              (tags["addr:country"] ? "<br>" + tags["addr:country"] : "") +
1356              (tags["wheelchair"] ? ("<br><img class='wheelchair " + tags["wheelchair"] + "' src='"+assethost+"assets/disability-18.png' title='wheelchair: " + 
1357                                     ( (tags["wheelchair"] == "yes") ? "100% accessible" :
1358                                       ( (tags["wheelchair"] == "limited" ) ? "limited (assist needed)" :
1359                                         ( (tags["wheelchair"] == "no") ? "no" : tags["wheelchair"] ) ) ) +
1360                                     ( tags["wheelchair:description"] ? ("\n" + secHTML(tags["wheelchair:description"])) : "" )
1361                                     + "'/>") : "")
1362              ))
1363        .append($('<td>').append(
1364            (tags["website"] ? (url_ify(tags["website"],"website") + "<br>") : "" ) +
1365            (tags["url"] ? (url_ify(tags["url"],"website") + "<br>") : "" ) +
1366            (tags["contact:website"] ? (url_ify(tags["contact:website"],"website") + "<br>") : "" ) +
1367            (tags["contact:url"] ? (url_ify(tags["contact:url"],"website") + "<br>") : "" ) +
1368
1369            (tags["email"] ? (url_ify(tags["email"],"email") + "<br>") : "" )+
1370            (tags["contact:email"] ? (url_ify(tags["contact:email"],"email") + "<br>") : "" ) +
1371
1372            (tags["phone"] ? (url_ify(tags["phone"], "Tel:&nbsp;" + tags["phone"]) + "<br>") : "" ) +
1373            (tags["contact:phone"] ? (url_ify(tags["contact:phone"], "Tel:&nbsp;" + tags["contact:phone"]) + "<br>") : "" ) + 
1374
1375            (tags["fax"] ? (url_ify(tags["fax"], "Fax:&nbsp;" + tags["fax"]) + "<br>") : "" ) +
1376            (tags["contact:fax"] ? (url_ify(tags["contact:fax"], "Fax:&nbsp;" + tags["contact:fax"]) + "<br>") : "" ) +
1377
1378            social_media_links
1379
1380          )));
1381    }
1382    var wp_articlename = "";
1383    for (key in tags) {
1384        if(/^wikipedia/.test(key)) {
1385            var value = tags[key];
1386            var lang = key.match(/^(?:wikipedia:)([a-z-]{2,7})$/) || value.match(/^([a-z-]{2,7}):/) || ""; 
1387            lang = (lang) ? lang[1] : "en";
1388
1389            if(! /wikipedia\./.test(value))
1390                wp_articlename = value;
1391            else {
1392                wp_articlename = value.replace(/^http[s]?:\/\//,'')
1393                                      .replace(/^www\./,'')
1394                                      .replace(/^([a-z-]{2,7})\.wikipedia/,'')
1395                                      .replace(/^wikipedia\./,'')
1396                                      .replace(/^[a-z]{2,3}\/wiki\//,'');
1397            }
1398            wp_articlename = wp_articlename.replace(/^[a-z-]{2,7}:/,'');
1399            var article_id = "wpimage_" + wp_articlename; // we must omit LANG here, as we don't have a way to get lang in callback function run on wikipedia's answer FIXME breaks on 2 wp links, when the first one is non-english...
1400
1401            var req_string = "https://" + lang + ".wikipedia.org/w/api.php?action=query&titles=" + wp_articlename + "&prop=pageimages&format=json&pithumbsize=276";
1402
1403            $.getJSON(req_string + "&callback=?", function(data) {
1404                    for(obj_id in data.query.pages) {
1405                        var item = data.query.pages[obj_id];
1406 //                       console.log("got answer from wikipedia for " + item.title + ".");
1407                        if(item.thumbnail) {
1408                            wikipedia_images["wpimage_" + item.title] = item.thumbnail.source;
1409                        } else {
1410                            wikipedia_images["wpimage_" + item.title] = "UNDEF";
1411                            console.log("no WP image for " + item.title);
1412                        }
1413                    }
1414                    setTimeout(setImageInPopup,100); //needed here too if site called with popup open
1415            });
1416
1417            r.append($('<tr>')
1418                    .attr('class','header')
1419                    .append("<td colspan=2 id='wp-image'><img id='" +article_id + "' title='" + wp_articlename + "'/><a href='" + wikipedia_link +"'>© Wikipedia</a></td>" )//only one popup shall be open at a time for the id to be unique
1420                    );
1421
1422        }
1423    }
1424    if(tags['image']) {
1425        if(tags['image'].match(/^File:/)) {
1426            var imagename = tags['image'].replace(/ /,"_");
1427            var req_string = "https://commons.wikimedia.org/w/api.php"
1428                + "?action=query"
1429                + "&format=json"
1430                + "&titles=" + imagename
1431                + "&prop=imageinfo"
1432                + "&iiprop=url"
1433                + "&iiurlwidth=276";
1434            $.getJSON(req_string+ "&callback=?", function(data) {
1435                for(obj_id in data.query.pages) {
1436                    var item = data.query.pages[obj_id];
1437                    console.log("got answer from wikimedia commons for " + item.title + ".");
1438                    if(item.imageinfo[0]) {
1439                        var item_name = decodeURIComponent(item.imageinfo[0].descriptionurl.replace(/.*\//,'')); //item.title is with spaces instead of underscores.
1440                        wikipedia_images["wpimage_" + item_name] = item.imageinfo[0].thumburl;
1441                    } else {
1442                        wikipedia_images["wpimage_" + item.title.replace(/ /,"_") ] = "UNDEF";
1443                        console.log("no WP image for " + item.title);
1444                    }
1445                }
1446                setTimeout(setImageInPopup,100); //needed here too if site called with popup open
1447            });
1448
1449            r.append($('<tr>')
1450                    .attr('class','header')
1451                    .append("<td colspan=2 id='wp-image'><img id='" + imagename + "' title='" + imagename + "'/><a href='https://commons.wikimedia.org/wiki/" + imagename + "'>© Wikipedia</a></td>" )
1452                        // FIXME attribution/License must be set on return of c
1452all
1453                    );
1454
1455
1456        }
1457        else if( ! (wp_articlename && tags['image'].match(/wiki(pedia|media)/)) ) {
1458            r.append($('<tr>')
1459                    .attr('class','header')
1460                    .append("<td colspan=2 id='image'><img src='" + tags['image'] + "' style='width:276px;' /></td>" )//only one popup shall be open at a time for the id to be unique
1461                );
1462        }
1463    }
1464
1465
1466
1467    for (key in tags) {
1468      var value = tags[key];
1469      if ( tags_to_ignore.indexOf(key) >= 0) 
1470        continue;
1471
1472      if ( key == 'website' || key == 'url' || key == 'contact:website' ||  key == 'contact:url') { 
1473        var teststr=/^http/; //http[s] is implicit here
1474        if ( ! teststr.test(value) )
1475          value = "http://" + value;
1476          
1477        var htlink = '<a href="' + value + '">' + value + '</a>';
1478        r.append($('<tr>').append($('<th>').text(key)).append($('<td>').append(htlink)));
1479      } else if ( /^wikipedia/.test(key)) { 
1480        var lang = key.match(/^(?:wikipedia:)([a-z-]{2,7})$/) || ""; // if value starts with e.g. "de:ARTICLE", this works in WP anyway
1481        var begin = (! /^http/.test(value)) ? "https://" + ((lang) ? (lang[1] + ".") : "") + "wikipedia.org/wiki/" : ""; //http[s] is implicit here
1482
1483        var htlink = $('<a>').attr('href', begin + value).text(decodeURIComponent(value));
1484        r.append($('<tr>').addClass('tag').append($('<th>').text(key)).append($('<td>').append(htlink)));
1485      } else if ( key == 'wikidata' ) { 
1486        var begin = (! /^http/.test(value)) ? "https://www.wikidata.org/wiki/" : ""; //http[s] is implicit here
1487
1488        var htlink = $('<a>').attr('href', begin + value).text(decodeURIComponent(value));
1489        r.append($('<tr>').addClass('tag').append($('<th>').text(key)).append($('<td>').append(htlink)));
1490      } else if (key == 'contact:email' || key == 'email') {
1491        if ( ! /^mailto:/.test(value) )
1492          value = "mailto:" + value;
1493
1494        var htlink = $('<a>').attr('href', value).text(value);
1495        r.append($('<tr>').addClass('tag').append($('<th>').text(key)).append($('<td>').append(htlink)));
1496      } else {
1497        var key_escaped = secHTML(key);
1498        var value_escaped = secHTML(value);
1499
1500        var keytext = key_escaped.replace(/:/g,":<wbr />");
1501        var valuetext = "<span>=&nbsp;</span>" + value_escaped.replace(/;/g,"; ");
1502
1503        /* display label:* instead of key */ 
1504        /*
1505        var found = 0; // use for break outer loops
1506
1507        for ( groupname in taxonomy ) { // e.g. "fulfils_needs" : {}=group 
1508          group = taxonomy[groupname];
1509
1510          var count_items = group["items"].length;
1511          for (var i = 0; i < count_items; i++) { // loop over each item in group["items"]
1512            item = group["items"][i];
1513            if(item["osm:key"] == keytext) {
1514              keytext = "<em>" + item.label["en"] + "</em>"; //<em> for displaying a translated value
1515              found = 1;
1516              break;
1517            }
1518          } 
1519          if(found == 1)
1520            break; // for ( groupname in taxonomy ) 
1521        } 
1522        */
1523
1524        r.append($('<tr>').addClass('tag')
1525            .append($('<th>').append(keytext))
1526            .append($('<td>').append(valuetext))
1527            );
1528      }
1529
1530    } // end for (key in tags)
1531
1532    var s = $('<div>');
1533    s.append(r);
1534    var retval = $('<div>').append(s);
1535
1536    var object_tags = getMainTags(tags),
1537        object_text = "unknown feature";
1538
1539    if(object_tags.length) {
1540        object_tags.forEach(function (currentValue) {
1541            fetchTranslationsFromWikiData(currentValue);
1542        });
1543
1544        object_text = object_tags.join(", ");
1545    }
1546
1547    retval.prepend($('<h3>')
1548            .attr("translated","untranslated")
1549            .attr("title", (object_text == "unknown feature") ? "This Object has no known OSM tag set" : object_text )
1550            .attr("sourcetext", object_text )
1551            .append($("<span>")
1552                .text(object_text))
1553            .append($("<span>")
1554                .text("?")
1555                .attr("title","Click here to show where this value comes from"))
1556                    );
1557    retval.prepend($('<h1>').text(tags["name"]));
1558    return retval.html();
1559  }
1560
1561  function bindPopupOnData(data) {
1562    // first: check if no item with this osm-id exists...
1563    var hashtable_key = data.type + data.id; // e.g. "node1546484546"
1564    if(marker_table[hashtable_key] == 1) //object already there
1565      return;
1566    marker_table[hashtable_key] = 1;
1567
1568    if(!data.tags) {
1569      console.log("no tags on:");
1570      console.log(data);
1571      return;
1572    }
1573    if(data.tags.disused == "yes" || data.tags.opening_hours == "off" || data.tags["disused:shop"] || data.tags["disused:amenity"]) {
1574      console.log("object disused:" + data.type + data.id);
1575      return;
1576    }
1577
1578    var icon_url = chooseIconSrc(data.tags,overpass_config.icon_size);
1579
1580    var icon_class = (overpass_config.class_selector_key && data.tags[overpass_config.class_selector_key["key"]]) ? overpass_config.class_selector_key["key"] : "color_undef";
1581
1582    var needs_icon = L.icon({
1583      iconUrl: icon_url,
1584      iconSize: new L.Point(overpass_config.icon_size, overpass_config.icon_size),
1585      iconAnchor: new L.Point(overpass_config.icon_size / 2, overpass_config.icon_size / 2),
1586      popupAnchor: new L.Point(0, - overpass_config.icon_size / 2),
1587      className: "v-" + data.tags[icon_class] + " k-" + icon_class
1588    });
1589
1590    var pdata = {
1591      lat: data.lat,
1592      lon: data.lon,
1593      id: data.id,
1594      type: data.type,
1595      icon: needs_icon,
1596      title: data.tags.name,
1597      popup: fillPopup(data.tags,data.type,data.id,data.lat,data.lon),
1598      tags: data.tags
1599    }
1600    var pmarker = new PruneCluster.Marker(data.lat, data.lon, pdata);
1601
1602    return pmarker;
1603  }
1604
1605  function nodeFunction(data) {
1606    if (! data.tags)
1607      return null;
1608
1609    var is_one_of_queried=0; // for filtering out tagged nodes which are part of ways
1610    for ( var i = 0; i < overpass_config.q_array.length; i++) {
1611        var res = overpass_config.q_array[i][0].replace(/["]?/,"").replace(/["].*$/,"");
1612        if(data.tags[res]) {
1613            is_one_of_queried=1;
1614            break;
1615        }
1616    }
1617    if (! data.tags.name && ! is_one_of_queried)
1618        return;
1619    if (! data.tags.name && data.tags.entrance)
1620      return null;
1621    return bindPopupOnData(data);
1622  }
1623  function wayFunction(data) {
1624    //calculate centre of polygon
1625
1626    var centroid = $.geo.centroid(data.geometry);
1627    //var style = {};
1628    centroid.tags = data.tags;
1629    centroid.id = data.id;
1630    centroid.type = data.type;
1631    centroid.lon = centroid.coordinates[0];
1632    centroid.lat = centroid.coordinates[1];
1633    return bindPopupOnData(centroid);
1634  }
1635  function relationFunction(data) {
1636    // calculate mean coordinates as center
1637    // for all members, calculate centroid
1638    // then calculate mean over all nodes and centroids.
1639    var centroids = [];
1640    var sum_lon = 0;
1641    var sum_lat = 0;
1642    //console.log(data);
1643    for (var i = 0; i < data.members.length; i++) {
1644      var p = data.members[i];
1645      var centroid;
1646      switch (p.type) {
1647        case 'node':
1648
1649          if(!p.obj) {
1650            console.log("!p.obj: ");
1651            console.log(p);
1652          } else {
1653            centroid = p.obj.coordinates;
1654            centroids.push(centroid);
1655          }
1656          break;
1657        case 'way':
1658          //console.log(p);
1659          if (p.role == "outer") {// FIXME add handling of rel members
1660            if(!p.obj) {
1661              console.log("!p.obj: ");
1662              console.log(p);
1663            } else {
1664              var centroid_point = $.geo.centroid(p.obj.geometry);
1665              centroid = centroid_point.coordinates;
1666              centroids.push(centroid);
1667            }
1668          } 
1669          break;
1670      }
1671      if(centroid){
1672        if(centroid.length != 0) {
1673          sum_lon += centroid[0];
1674          sum_lat += centroid[1];
1675        } else
1676          console.log("centroid empty!");
1677      } else
1678        console.log("centroid nonexisting!");
1679
1680    }
1681    //console.log(centroids);
1682    var sum_centroid = { 
1683      id : data.id,
1684      lon : sum_lon / data.members.length,
1685      lat : sum_lat / data.members.length,
1686      tags : data.tags,
1687      type : data.type
1688    }
1689
1690    //console.log(sum_centroid);
1691    return bindPopupOnData(sum_centroid);
1692    // todo: in the long term, all areas should be displayed as areas (as in overpass turbo)
1693  }
1694
1695  function handleNodes(overpassJSON) {
1696    var pid;
1697    for(i_pid in pids.node.active) {
1698        if(pids.node.active[i_pid].state == "timeouted")
1699            continue;
1700        if(JSON.stringify(pids.node.active[i_pid].bounds) === JSON.stringify(overpassJSON.bounds)) {
1701            pid = i_pid;
1702            delete pids.node.active[i_pid];
1703            break;
1704        }
1705    }
1706    console.log("handleNodes called (pid " + pid + ")");
1707
1708    var new_markers = [];
1709    for (var i = 0; i < overpassJSON.elements.length; i++) {
1710      var p = overpassJSON.elements[i];
1711      if(p.type != 'node')
1712        continue;
1713      p.coordinates = [p.lon, p.lat];
1714      p.geometry = {type: 'Point', coordinates: p.coordinates};
1715
1716      var retval = nodeFunction(p);
1717      if (retval) {
1718        if(window.filters)
1719            retval.filtered = ! getFilterStatusOnPoi(retval);
1720        new_markers.push(retval);
1721      }
1722    }
1723    var number = new_markers.length;
1724    if(number) {
1725        markers.RegisterMarkers(new_markers);
1726        markers.ProcessView();
1727        if(window.createSideBar) {
1728            var json_date = new Date(overpassJSON.osm3s.timestamp_osm_base);
1729            $('#tnode').css("display", "block");
1730            $('#tnode').html(json_date.toLocaleString());
1731            updateElementCount();
1732            updatePOIlist();
1733            if(window.filters)
1734                updateFilterCount(); // TODO it is relatively inefficient to run check all filters every time a single entry is changed - later only the filters affected on change should be counted 
1735        }
1736        new_markers = [];
1737    }
1738
1739    changeLoadingIndicator("loading_node", -1);
1740    if(! open_popup_on_load.already_shown && open_popup_on_load.type == "node") {
1741        open_popup_on_load.already_shown = true;
1742        setTimeout(function () {
1743              MytogglePopup(open_popup_on_load.type, open_popup_on_load.id);
1744        },200 );
1745    }
1746    console.log("handleNodes (pid " + pid + ") done, " + number + " added.");
1747  }
1748  function handleWays(overpassJSON) {
1749    var pid;
1750    for(i_pid in pids.way.active) {
1751        if(pids.way.active[i_pid].state == "timeouted")
1752            continue;
1753        if(JSON.stringify(pids.way.active[i_pid].bounds) === JSON.stringify(overpassJSON.bounds)) {
1754            pid = i_pid;
1755            delete pids.way.active[i_pid];
1756            break;
1757        }
1758    }
1759    if(!pid) {
1760        //console.log("handleWays: JSON bounds:");
1761        //console.log(overpassJSON.bounds);
1762        //console.log("handleWays: pid way objects:");
1763        //console.log(pids.way.active);
1764
1765        //FIXME BUG: the french overpass server (used for ways) doesn't supply the bounds...
1766        for(i_pid in pids.way.active) {
1767            if(pids.way.active[i_pid].state == "timeouted")
1768                continue;
1769            pid = i_pid;
1770            break;// get first one
1771        }
1772        delete pids.way.active[pid];
1773    }
1774    console.log("handleWays called (pid " + pid + ")");
1775
1776    var new_markers = [];
1777    var nodes = {};
1778    for (var i = 0; i < overpassJSON.elements.length; i++) {
1779      var p = overpassJSON.elements[i];
1780      switch (p.type) {
1781        case 'node':
1782          p.coordinates = [p.lon, p.lat];
1783          p.geometry = {type: 'Point', coordinates: p.coordinates};
1784          nodes[p.id] = p;
1785          break;
1786        case 'way':
1787          p.coordinates = p.nodes.map(function (id) {
1788            return nodes[id].coordinates;
1789          });
1790          p.geometry = {type: 'LineString', coordinates: p.coordinates};
1791
1792          var retval = wayFunction(p);
1793          if (retval) {
1794            if(window.filters)
1795                retval.filtered = ! getFilterStatusOnPoi(retval);
1796            new_markers.push(retval);
1797          }
1798      }
1799    }
1800
1801    var number = new_markers.length;
1802    if(number) {
1803        markers.RegisterMarkers(new_markers);
1804        markers.ProcessView();
1805        if(window.createSideBar) {
1806            var json_date = new Date(overpassJSON.osm3s.timestamp_osm_base);
1807            $('#tway').css("display", "block");
1808            $('#tway').html(json_date.toLocaleString());
1809            updateElementCount();
1810            updatePOIlist();
1811            if(window.filters)
1812                updateFilterCount(); // TODO it is relatively inefficient to run check all filters every time a single entry is changed - later only the filters affected on change should be counted 
1813        }
1814        new_markers = [];
1815    }
1816
1817    changeLoadingIndicator("loading_way", -1);
1818    if(! open_popup_on_load.already_shown && open_popup_on_load.type == "way") {
1819        open_popup_on_load.already_shown = true;
1820        setTimeout(function () {
1821              MytogglePopup(open_popup_on_load.type, open_popup_on_load.id);
1822        },200 );
1823    }
1824    console.log("handleWays (pid " + pid + ") done, " + number + " added.");
1825  }
1826  function handleRelations(overpassJSON) {
1827    var pid;
1828    for(i_pid in pids.relation.active) {
1829        if(pids.relation.active[i_pid].state == "timeouted")
1830            continue;
1831        if(JSON.stringify(pids.relation.active[i_pid].bounds) === JSON.stringify(overpassJSON.bounds)) {
1832            pid = i_pid;
1833            delete pids.relation.active[i_pid];
1834            break;
1835        }
1836    }
1837    console.log("handleRelations called (pid " + pid + ")");
1838    var nodes = {}, ways = {};
1839
1840    //overpass returns elements unsorted: rels, nodes, ways - should be nodes, ways, rels
1841    var rels = []; // to handle them last
1842
1843    var new_markers = [];
1844    //console.log(overpassJSON.elements);
1845    for (var i = 0; i < overpassJSON.elements.length; i++) {
1846      var p = overpassJSON.elements[i];
1847      switch (p.type) {
1848        case 'node':
1849          p.coordinates = [p.lon, p.lat];
1850          p.geometry = {type: 'Point', coordinates: p.coordinates};
1851          nodes[p.id] = p;
1852          break;
1853        case 'way':
1854          p.coordinates = p.nodes.map(function (id) {
1855            return nodes[id].coordinates;
1856          });
1857          p.geometry = {type: 'LineString', coordinates: p.coordinates};
1858          ways[p.id] = p;
1859          break;
1860        case 'relation':
1861          rels.push(p);
1862          break;
1863      }
1864    }
1865    //console.log("nodes count:" + Object.keys(nodes).length + "; ways count:" + Object.keys(ways).length + "; rel count: " + rels.length);
1866
1867    // handle relations last
1868    for (var i = 0; i < rels.length; i++) {
1869      var p = rels[i];
1870      p.members.map(function (mem) {
1871          if (mem.type == 'node') {
1872
1873            if(!nodes[mem.ref])
1874              console.log("mem.type=node missing: " + mem.ref);
1875
1876            mem.obj = nodes[mem.ref];
1877          } else if (mem.type == 'way') {
1878
1879            if(!ways[mem.ref])
1880              console.log("mem.type=way missing: " + mem.ref); //FIXME this seems to come from overpass query not returning childs of rels... change query!
1881
1882            mem.obj = ways[mem.ref];
1883          } else
1884            console.log("mem.type=" + mem.type);// FIXME handle rels of rels
1885      });
1886      // p has type=relaton, id, tags={k:v}, members=[{role, obj}]
1887      var retval = relationFunction(p);
1888      if (retval) {
1889          if(window.filters)
1890            retval.filtered = ! getFilterStatusOnPoi(retval);
1891          new_markers.push(retval);
1892      }
1893    }
1894    
1895    var number = new_markers.length;
1896    if(number) {
1897        markers.RegisterMarkers(new_markers);
1898        markers.ProcessView();
1899        if(window.createSideBar) {
1900            var json_date = new Date(overpassJSON.osm3s.timestamp_osm_base);
1901            $('#trel').css("display", "block");
1902            $('#trel').html(json_date.toLocaleString());
1903            updateElementCount();
1904            updatePOIlist();
1905            if(window.filters)
1906                updateFilterCount(); // TODO it is relatively inefficient to run check all filters every time a single entry is changed - later only the filters affected on change should be counted 
1907        }
1908        new_markers = [];
1909    }
1910
1911    changeLoadingIndicator("loading_relation", -1);
1912    if(! open_popup_on_load.already_shown && open_popup_on_load.type == "relation") {
1913        open_popup_on_load.already_shown = true;
1914        setTimeout(function () {
1915              MytogglePopup(open_popup_on_load.type, open_popup_on_load.id);
1916        },200 );
1917    }
1918    console.log("handleRelations (pid " + pid + ") done, " + number + " added.");
1919  }
1920
1921  var query = overpass_query;
1922
1923  var node_query = overpass_query_nodes;
1924  var way_query = overpass_query_ways;
1925  var rel_query = overpass_query_rels;
1926  var op_bounds = bounds.toOverpassBBoxString();
1927
1928  var allUrl = query.replace(/BBOX/g, op_bounds);
1929
1930  var node_url = node_query.replace(/BBOX/g, op_bounds);
1931  var way_url = way_query.replace(/BBOX/g, op_bounds);
1932  var rel_url = rel_query.replace(/BBOX/g, op_bounds);
1933
1934  var bounds_rounded = { // the 'bounds' is the only way where we can identify to which pid a returned overpassJSON belongs
1935      minlat : L.Util.formatNum(bounds._southWest.lat,4),
1936      minlon : L.Util.formatNum(bounds._southWest.lng,4),
1937      maxlat : L.Util.formatNum(bounds._northEast.lat,4),
1938      maxlon : L.Util.formatNum(bounds._northEast.lng,4)
1939  }
1940
1941  changeLoadingIndicator("loading_node", +1);
1942  var this_pid = pids.node.counter++;
1943  pids.node.active[this_pid] = { state : "running", bounds : bounds_rounded } ;
1944  console.log("loadPOI: before JSON call pid node"+this_pid+": " + node_url);
1945  $.getJSON(node_url, handleNodes);
1946  setTimeout(function () {
1947      timeOutOverpassCall("node", this_pid);
1948  },1000*overpass_config.timeout);
1949
1950  changeLoadingIndicator("loading_way", +1);
1951  this_pid = pids.way.counter++;
1952  pids.way.active[this_pid] = { state : "running", bounds : bounds_rounded } ;
1953  console.log("loadPOI: before JSON call pid way"+this_pid+": " + way_url);
1954  $.getJSON(way_url, handleWays);
1955  setTimeout(function () {
1956      timeOutOverpassCall("way", this_pid);
1957  },1000*overpass_config.timeout);
1958
1959  changeLoadingIndicator("loading_relation", +1);
1960  this_pid = pids.relation.counter++;
1961  pids.relation.active[this_pid] = { state : "running", bounds : bounds_rounded } ;
1962  console.log("loadPOI: before JSON call pid rel"+this_pid+": " + rel_url);
1963  $.getJSON(rel_url, handleRelations);
1964  setTimeout(function () {
1965      timeOutOverpassCall("relation", this_pid);
1966  },1000*overpass_config.timeout);
1967}
1968
1969function timeOutOverpassCall(osm_type,pid) {
1970    if (pids[osm_type].active[pid] && pids[osm_type].active[pid].state == "running") {
1971        changeLoadingIndicator("loading_" + osm_type, -1);
1972        pids[osm_type].active[pid].state = "timeouted";
1973        console.log("timeOutOverpassCall: pid " + osm_type + pid + " timeouted after " + overpass_config.timeout + " seconds.");
1974    }
1975}
1976
1977function updateLinks() {
1978  var centre = map.getCenter();
1979  var query = encodeURIComponent(overpass_ql_text);
1980  turbolink = "<a href=\'http://overpass-turbo.eu/?Q=" 
1981      + query.replace(/BBOX/g, map.getBounds().toOverpassBBoxString()) 
1982      + '&R&C=' + centre.lat + ';' + centre.lng + ';' + map.getZoom() 
1983      + '\' title="Export OSM data with Overpass Turbo">Export data <img src="'+assethost+'assets/turbo.png" height=12px style="margin-bottom:-2px"/></a>';
1984
1985  if(window.updateMapSwitcherLinks)
1986      updateMapSwitcherLinks();
1987}
1988
1989
1990/* taken from https://github.com/ardhi/Leaflet.MousePosition 
1991Licence: MIT, see file MIT-LICENCE-Leaflet.MousePosition.txt */
1992
1993
1994L.Control.MousePosition = L.Control.extend({
1995  options: {
1996    position: 'bottomright',
1997    separator: ' : ',
1998    emptyString: 'Unavailable',
1999    lngFirst: false,
2000    numDigits: 5,
2001    lngFormatter: undefined,
2002    latFormatter: undefined,
2003    prefix: ""
2004  },
2005
2006  onAdd: function (map) {
2007    this._container = L.DomUtil.create('div', 'leaflet-control-mouseposition');
2008    L.DomEvent.disableClickPropagation(this._container);
2009    map.on('mousemove', this._onMouseMove, this);
2010    updateLinks();
2011    this._container.innerHTML= ' | ' + turbolink;
2012    return this._container;
2013  },
2014
2015  onRemove: function (map) {
2016    map.off('mousemove', this._onMouseMove)
2017  },
2018
2019  _onMouseMove: function (e) {
2020    var lng = this.options.lngFormatter ? this.options.lngFormatter(e.latlng.lng) : L.Util.formatNum
2020(e.latlng.lng, this.options.numDigits);
2021    var lat = this.options.latFormatter ? this.options.latFormatter(e.latlng.lat) : L.Util.formatNum(e.latlng.lat, this.options.numDigits);
2022    var lng_length = lng.toString().split(".")[1].length; // it MAY be if you're on exactly one integer coordinate to throw an error, but I don't care
2023    var lat_length = lat.toString().split(".")[1].length;
2024    for(var i = 0; i < this.options.numDigits - lng_length; i++) {
2025        lng += "0";
2026    }
2027    for(var i = 0; i < this.options.numDigits - lat_length; i++) {
2028        lat += "0";
2029    }
2030
2031    var value = this.options.lngFirst ? lng + this.options.separator + lat : lat + this.options.separator + lng;
2032    var prefixAndValue = this.options.prefix + ' ' + value;
2033    this._container.innerHTML = prefixAndValue + ' | ' + turbolink;
2034  }
2035
2036});
2037
2038L.Map.mergeOptions({
2039    positionControl: false
2040});
2041
2042L.Map.addInitHook(function () {
2043    if (this.options.positionControl) {
2044        this.positionControl = new L.Control.MousePosition();
2045        this.addControl(this.positionControl);
2046    }
2047});
2048
2049L.control.mousePosition = function (options) {
2050    return new L.Control.MousePosition(options);
2051};
2052
2053var pois_lz;
2054if (window.url_pois_lz) { //TODO: must be called after map pois_lz set ... but how to have it in global namespace?
2055  var http_request_lz = new XMLHttpRequest();
2056  http_request_lz.open("GET", url_pois_lz, true);
2057  http_request_lz.onreadystatechange = function () {
2058        var done = 4, ok = 200;
2059        if (http_request_lz.readyState === done && http_request_lz.status === ok) {
2060            pois_lz = JSON.parse(http_request_lz.responseText);
2061            loadPoi();
2062        }
2063    };
2064  http_request_lz.send(null);
2065  console.log("XMLHttpRequest for pois_lz sent");
2066} else {
2067  console.log("XMLHttpRequest for pois_lz NOT sent, no url");
2068}
2069
2070var wikipedia_images = {};
2071
2072/**
2073 * jQuery.browser.mobile (http://detectmobilebrowser.com/)
2074 *
2075 * jQuery.browser.mobile will be true if the browser is a mobile device
2076 *
2077 **/
2078(function(a){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))})(navigator.userAgent||navigator.vendor||window.opera);
2079
2080window.onload = function () { 
2081    var text = 'Warning: You seem to use Internet Explorer, which is known to be buggy. This site may not work as expected. We recommend a standards-compl
2081iant free web-browser like <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> or <a href="http://chromium.woolyss.com/">Chromium</a>.',
2082        h;
2083    if(navigator.userAgent.toLowerCase().indexOf('msie') > -1) {
2084        var h = document.createElement("h1");
2085        h.setAttribute('style', 'text-align:center;color:red;background:white;position:absolute;top:5px;left:55px;right:55px;z-index:15000;padding:5px;margin:0;');
2086    } else if (navigator.userAgent.toLowerCase().indexOf('trident') > -1) {
2087        var h = document.createElement("h3");
2088        h.setAttribute('style', 'text-align:center;color:red;position:absolute;top:5px;left:55px;right:55px;z-index:15000;padding:5px;margin:0;background-color:rgba(255,255,255,0.7);');
2089    }
2090        else return;
2091    h.innerHTML = text;
2092
2093    var map = document.getElementById('map');
2094    map.appendChild(h);
2095    map.setAttribute( 'class', map.getAttribute('class') + ' ie');
2096}
2097

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.