]> git.openstreetmap.org Git - nominatim-ui.git/blob - dist/assets/js/nominatim-ui.js
eslint configuration and fixed lots of warnings
[nominatim-ui.git] / dist / assets / js / nominatim-ui.js
1 'use strict';
2
3 var map;
4 var last_click_latlng;
5
6
7 // *********************************************************
8 // HELPERS
9 // *********************************************************
10
11 function get_config_value(str, default_val) {
12   return (typeof Nominatim_Config[str] !== 'undefined' ? Nominatim_Config[str] : default_val);
13 }
14
15 function parse_and_normalize_geojson_string(part) {
16   // normalize places the geometry into a featurecollection, similar to
17   // https://github.com/mapbox/geojson-normalize
18   var parsed_geojson = {
19     type: 'FeatureCollection',
20     features: [
21       {
22         type: 'Feature',
23         geometry: part,
24         properties: {}
25       }
26     ]
27   };
28   return parsed_geojson;
29 }
30
31 function map_link_to_osm() {
32   return 'https://openstreetmap.org/#map=' + map.getZoom() + '/' + map.getCenter().lat + '/' + map.getCenter().lng;
33 }
34
35 function map_viewbox_as_string() {
36   var bounds = map.getBounds();
37   var west = bounds.getWest();
38   var east = bounds.getEast();
39
40   if ((east - west) >= 360) { // covers more than whole planet
41     west = map.getCenter().lng - 179.999;
42     east = map.getCenter().lng + 179.999;
43   }
44   east = L.latLng(77, east).wrap().lng;
45   west = L.latLng(77, west).wrap().lng;
46
47   return [
48     west.toFixed(5), // left
49     bounds.getNorth().toFixed(5), // top
50     east.toFixed(5), // right
51     bounds.getSouth().toFixed(5) // bottom
52   ].join(',');
53 }
54
55
56 // *********************************************************
57 // PAGE HELPERS
58 // *********************************************************
59
60 function fetch_from_api(endpoint_name, params, callback) {
61   // `&a=&b=&c=1` => '&c='
62   for (var k in params) {
63     if (typeof (params[k]) === 'undefined' || params[k] === '' || params[k] === null) delete params[k];
64   }
65
66   var api_url = get_config_value('Nominatim_API_Endpoint') + endpoint_name + '.php?' + $.param(params);
67   if (endpoint_name !== 'status') {
68     $('#api-request-link').attr('href', api_url);
69   }
70   $.get(api_url, function (data) {
71     callback(data);
72   });
73 }
74
75 function update_data_date() {
76   fetch_from_api('status', { format: 'json' }, function (data) {
77     $('#data-date').text(data.data_updated);
78   });
79 }
80
81 function render_template(el, template_name, page_context) {
82   var template_source = $('#' + template_name).text();
83   var template = Handlebars.compile(template_source);
84   var html = template(page_context);
85   el.html(html);
86 }
87
88 function update_html_title(title) {
89   var prefix = '';
90   if (title && title.length > 1) {
91     prefix = title + ' | ';
92   }
93   $('head title').text(prefix + 'OpenStreetMap Nominatim');
94 }
95
96 function show_error(html) {
97   $('#error-overlay').html(html).show();
98 }
99
100 function hide_error() {
101   $('#error-overlay').empty().hide();
102 }
103
104
105 $(document).ajaxError(function (event, jqXHR, ajaxSettings/* , thrownError */) {
106   // console.log(thrownError);
107   // console.log(ajaxSettings);
108   show_error('Error fetching results from <a href="' + ajaxSettings.url + '">' + ajaxSettings.url + '</a>');
109 });
110
111
112 jQuery(document).ready(function () {
113   hide_error();
114 });
115 // *********************************************************
116 // DETAILS PAGE
117 // *********************************************************
118
119
120 function init_map_on_detail_page(lat, lon, geojson) {
121   map = new L.map('map', {
122     // center: [nominatim_map_init.lat, nominatim_map_init.lon],
123     // zoom:   nominatim_map_init.zoom,
124     attributionControl: (get_config_value('Map_Tile_Attribution') && get_config_value('Map_Tile_Attribution').length),
125     scrollWheelZoom: true, // !L.Browser.touch,
126     touchZoom: false
127   });
128
129   L.tileLayer(get_config_value('Map_Tile_URL'), {
130     // moved to footer
131     attribution: (get_config_value('Map_Tile_Attribution') || null) // '&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
132   }).addTo(map);
133
134   // var layerGroup = new L.layerGroup().addTo(map);
135
136   var circle = L.circleMarker([lat, lon], {
137     radius: 10, weight: 2, fillColor: '#ff7800', color: 'blue', opacity: 0.75
138   });
139   map.addLayer(circle);
140
141   if (geojson) {
142     var geojson_layer = L.geoJson(
143       // https://leafletjs.com/reference-1.0.3.html#path-option
144       parse_and_normalize_geojson_string(geojson),
145       {
146         style: function () {
147           return { interactive: false, color: 'blue' };
148         }
149       }
150     );
151     map.addLayer(geojson_layer);
152     map.fitBounds(geojson_layer.getBounds());
153   } else {
154     map.setView([lat, lon], 10);
155   }
156
157   var osm2 = new L.TileLayer(
158     get_config_value('Map_Tile_URL'),
159     {
160       minZoom: 0,
161       maxZoom: 13,
162       attribution: (get_config_value('Map_Tile_Attribution') || null)
163     }
164   );
165   (new L.Control.MiniMap(osm2, { toggleDisplay: true })).addTo(map);
166 }
167
168
169 jQuery(document).ready(function () {
170   if (!$('#details-page').length) { return; }
171
172   var search_params = new URLSearchParams(window.location.search);
173   // var place_id = search_params.get('place_id');
174
175   var api_request_params = {
176     place_id: search_params.get('place_id'),
177     osmtype: search_params.get('osmtype'),
178     osmid: search_params.get('osmid'),
179     keywords: search_params.get('keywords'),
180     addressdetails: 1,
181     hierarchy: (search_params.get('hierarchy') === '1' ? 1 : 0),
182     group_hierarchy: 1,
183     polygon_geojson: 1,
184     format: 'json'
185   };
186
187   if (api_request_params.place_id || (api_request_params.osmtype && api_request_params.osmid)) {
188     fetch_from_api('details', api_request_params, function (aFeature) {
189       var context = { aPlace: aFeature, base_url: window.location.search };
190
191       render_template($('main'), 'detailspage-template', context);
192       if (api_request_params.place_id) {
193         update_html_title('Details for ' + api_request_params.place_id);
194       } else {
195         update_html_title('Details for ' + api_request_params.osmtype + api_request_params.osmid);
196       }
197
198       update_data_date();
199
200       var lat = aFeature.centroid.coordinates[1];
201       var lon = aFeature.centroid.coordinates[0];
202       init_map_on_detail_page(lat, lon, aFeature.geometry);
203     });
204   } else {
205     render_template($('main'), 'detailspage-index-template');
206   }
207
208   $('#form-by-type-and-id,#form-by-osm-url').on('submit', function (e) {
209     e.preventDefault();
210
211     var val = $(this).find('input[type=edit]').val();
212     var matches = val.match(/^\s*([NWR])(\d+)\s*$/i);
213
214     if (!matches) {
215       matches = val.match(/\/(relation|way|node)\/(\d+)\s*$/);
216     }
217
218     if (matches) {
219       $(this).find('input[name=osmtype]').val(matches[1].charAt(0).toUpperCase());
220       $(this).find('input[name=osmid]').val(matches[2]);
221       $(this).get(0).submit();
222     } else {
223       alert('invalid input');
224     }
225   });
226 });
227
228 // *********************************************************
229 // FORWARD/REVERSE SEARCH PAGE
230 // *********************************************************
231
232
233 function display_map_position(mouse_lat_lng) {
234   //
235   if (mouse_lat_lng) {
236     mouse_lat_lng = map.wrapLatLng(mouse_lat_lng);
237   }
238
239   var html_mouse = 'mouse position: -';
240   if (mouse_lat_lng) {
241     html_mouse = 'mouse position: ' + [mouse_lat_lng.lat.toFixed(5), mouse_lat_lng.lng.toFixed(5)].join(',');
242   }
243   var html_click = 'last click: -';
244   if (last_click_latlng) {
245     html_click = 'last click: ' + [last_click_latlng.lat.toFixed(5), last_click_latlng.lng.toFixed(5)].join(',');
246   }
247
248   var html_center = 'map center: '
249     + map.getCenter().lat.toFixed(5) + ',' + map.getCenter().lng.toFixed(5)
250     + ' <a target="_blank" href="' + map_link_to_osm() + '">view on osm.org</a>';
251
252   var html_zoom = 'map zoom: ' + map.getZoom();
253   var html_viewbox = 'viewbox: ' + map_viewbox_as_string();
254
255   $('#map-position-inner').html([html_center, html_zoom, html_viewbox, html_click, html_mouse].join('<br/>'));
256
257   var center_lat_lng = map.wrapLatLng(map.getCenter());
258   var reverse_params = {
259     lat: center_lat_lng.lat.toFixed(5),
260     lon: center_lat_lng.lng.toFixed(5)
261     // zoom: 2,
262     // format: 'html'
263   };
264   $('#switch-to-reverse').attr('href', 'reverse.html?' + $.param(reverse_params));
265
266   $('input#use_viewbox').trigger('change');
267 }
268
269 function init_map_on_search_page(is_reverse_search, nominatim_results, request_lat, request_lon, init_zoom) {
270   //
271   map = new L.map('map', {
272     // center: [nominatim_map_init.lat, nominatim_map_init.lon],
273     // zoom:   nominatim_map_init.zoom,
274     attributionControl: (get_config_value('Map_Tile_Attribution') && get_config_value('Map_Tile_Attribution').length),
275     scrollWheelZoom: true, // !L.Browser.touch,
276     touchZoom: false
277   });
278
279
280   L.tileLayer(get_config_value('Map_Tile_URL'), {
281     // moved to footer
282     attribution: (get_config_value('Map_Tile_Attribution') || null) // '&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
283   }).addTo(map);
284
285   // console.log(Nominatim_Config);
286
287   map.setView([request_lat, request_lon], init_zoom);
288
289   var osm2 = new L.TileLayer(get_config_value('Map_Tile_URL'), {
290     minZoom: 0,
291     maxZoom: 13,
292     attribution: (get_config_value('Map_Tile_Attribution') || null)
293   });
294   new L.Control.MiniMap(osm2, { toggleDisplay: true }).addTo(map);
295
296   if (is_reverse_search) {
297     // We don't need a marker, but an L.circle instance changes radius once you zoom in/out
298     var cm = L.circleMarker(
299       [request_lat, request_lon],
300       {
301         radius: 5,
302         weight: 2,
303         fillColor: '#ff7800',
304         color: 'red',
305         opacity: 0.75,
306         zIndexOffset: 100,
307         clickable: false
308       }
309     );
310     cm.addTo(map);
311   } else {
312     var search_params = new URLSearchParams(window.location.search);
313     var viewbox = search_params.get('viewbox');
314     if (viewbox) {
315       var coords = viewbox.split(','); // <x1>,<y1>,<x2>,<y2>
316       var bounds = L.latLngBounds([coords[1], coords[0]], [coords[3], coords[2]]);
317       L.rectangle(bounds, {
318         color: '#69d53e',
319         weight: 3,
320         dashArray: '5 5',
321         opacity: 0.8,
322         fill: false
323       }).addTo(map);
324     }
325   }
326
327   var MapPositionControl = L.Control.extend({
328     options: {
329       position: 'topright'
330     },
331     onAdd: function (/* map */) {
332       var container = L.DomUtil.create('div', 'my-custom-control');
333
334       $(container).text('show map bounds').addClass('leaflet-bar btn btn-sm btn-default').on('click', function (e) {
335         e.preventDefault();
336         e.stopPropagation();
337         $('#map-position').show();
338         $(container).hide();
339       });
340       $('#map-position-close a').on('click', function (e) {
341         e.preventDefault();
342         e.stopPropagation();
343         $('#map-position').hide();
344         $(container).show();
345       });
346
347       return container;
348     }
349   });
350
351   map.addControl(new MapPositionControl());
352
353
354
355
356
357   function update_viewbox_field() {
358     // hidden HTML field
359     $('input[name=viewbox]').val($('input#use_viewbox').prop('checked') ? map_viewbox_as_string() : '');
360   }
361
362   map.on('move', function () {
363     display_map_position();
364     update_viewbox_field();
365   });
366
367   map.on('mousemove', function (e) {
368     display_map_position(e.latlng);
369   });
370
371   map.on('click', function (e) {
372     last_click_latlng = e.latlng;
373     display_map_position();
374   });
375
376   map.on('load', function () {
377     display_map_position();
378   });
379
380   $('input#use_viewbox').on('change', function () {
381     update_viewbox_field();
382   });
383
384
385
386
387   function get_result_element(position) {
388     return $('.result').eq(position);
389   }
390   // function marker_for_result(result) {
391   //   return L.marker([result.lat, result.lon], { riseOnHover: true, title: result.name });
392   // }
393   function circle_for_result(result) {
394     var cm_style = {
395       radius: 10,
396       weight: 2,
397       fillColor: '#ff7800',
398       color: 'blue',
399       opacity: 0.75,
400       clickable: !is_reverse_search
401     };
402     return L.circleMarker([result.lat, result.lon], cm_style);
403   }
404
405   var layerGroup = (new L.layerGroup()).addTo(map);
406
407   function highlight_result(position, bool_focus) {
408     var result = nominatim_results[position];
409     if (!result) { return; }
410     var result_el = get_result_element(position);
411
412     $('.result').removeClass('highlight');
413     result_el.addClass('highlight');
414
415     layerGroup.clearLayers();
416
417     if (result.lat) {
418       var circle = circle_for_result(result);
419       circle.on('click', function () {
420         highlight_result(position);
421       });
422       layerGroup.addLayer(circle);
423     }
424
425     if (result.boundingbox) {
426       var bbox = [
427         [result.boundingbox[0] * 1, result.boundingbox[2] * 1],
428         [result.boundingbox[1] * 1, result.boundingbox[3] * 1]
429       ];
430       map.fitBounds(bbox);
431
432       if (result.geojson && result.geojson.type.match(/(Polygon)|(Line)/)) {
433         //
434         var geojson_layer = L.geoJson(
435           parse_and_normalize_geojson_string(result.geojson),
436           {
437             // https://leafletjs.com/reference-1.0.3.html#path-option
438             style: function (/* feature */) {
439               return { interactive: false, color: 'blue' };
440             }
441           }
442         );
443         layerGroup.addLayer(geojson_layer);
444       }
445       // else {
446       //     var layer = L.rectangle(bounds, {color: "#ff7800", weight: 1} );
447       //     layerGroup.addLayer(layer);
448       // }
449     } else {
450       var result_coord = L.latLng(result.lat, result.lon);
451       if (result_coord) {
452         if (is_reverse_search) {
453           // console.dir([result_coord, [request_lat, request_lon]]);
454           // make sure the search coordinates are in the map view as well
455           map.fitBounds(
456             [result_coord, [request_lat, request_lon]],
457             {
458               padding: [50, 50],
459               maxZoom: map.getZoom()
460             }
461           );
462         } else {
463           map.panTo(result_coord, result.zoom || get_config_value('Map_Default_Zoom'));
464         }
465       }
466     }
467     if (bool_focus) {
468       $('#map').focus();
469     }
470   }
471
472
473   $('.result').on('click', function () {
474     highlight_result($(this).data('position'), true);
475   });
476
477   if (is_reverse_search) {
478     map.on('click', function (e) {
479       $('form input[name=lat]').val(e.latlng.lat);
480       $('form input[name=lon]').val(e.latlng.wrap().lng);
481       $('form').submit();
482     });
483
484     $('#switch-coords').on('click', function (e) {
485       e.preventDefault();
486       e.stopPropagation();
487       var lat = $('form input[name=lat]').val();
488       var lon = $('form input[name=lon]').val();
489       $('form input[name=lat]').val(lon);
490       $('form input[name=lon]').val(lat);
491       $('form').submit();
492     });
493   }
494
495   highlight_result(0, false);
496
497   // common mistake is to copy&paste latitude and longitude into the 'lat' search box
498   $('form input[name=lat]').on('change', function () {
499     var coords_split = $(this).val().split(',');
500     if (coords_split.length === 2) {
501       $(this).val(L.Util.trim(coords_split[0]));
502       $(this).siblings('input[name=lon]').val(L.Util.trim(coords_split[1]));
503     }
504   });
505 }
506
507
508
509
510
511
512
513 jQuery(document).ready(function () {
514   //
515   if (!$('#search-page,#reverse-page').length) { return; }
516
517   var is_reverse_search = !!($('#reverse-page').length);
518
519   var search_params = new URLSearchParams(window.location.search);
520
521   // return view('search', [
522   //     'sQuery' => $sQuery,
523   //     'bAsText' => '',
524   //     'sViewBox' => '',
525   //     'aSearchResults' => $aSearchResults,
526   //     'sMoreURL' => 'example.com',
527   //     'sDataDate' => $this->fetch_status_date(),
528   //     'sApiURL' => $url
529   // ]);
530
531   var api_request_params;
532   var context;
533
534   if (is_reverse_search) {
535     api_request_params = {
536       lat: search_params.get('lat'),
537       lon: search_params.get('lon'),
538       zoom: (search_params.get('zoom') > 1 ? search_params.get('zoom') : get_config_value('Reverse_Default_Search_Zoom')),
539       format: 'jsonv2'
540     };
541
542     context = {
543       // aPlace: aPlace,
544       fLat: api_request_params.lat,
545       fLon: api_request_params.lon,
546       iZoom: (search_params.get('zoom') > 1 ? api_request_params.zoom : get_config_value('Reverse_Default_Search_Zoom'))
547     };
548
549     update_html_title();
550     if (api_request_params.lat && api_request_params.lon) {
551
552       fetch_from_api('reverse', api_request_params, function (aPlace) {
553
554         if (aPlace.error) {
555           aPlace = null;
556         }
557
558         context.aPlace = aPlace;
559
560         render_template($('main'), 'reversepage-template', context);
561         update_html_title('Reverse result for ' + api_request_params.lat + ',' + api_request_params.lon);
562
563         init_map_on_search_page(
564           is_reverse_search,
565           [aPlace],
566           api_request_params.lat,
567           api_request_params.lon,
568           api_request_params.zoom
569         );
570
571         update_data_date();
572       });
573     } else {
574       render_template($('main'), 'reversepage-template', context);
575
576       init_map_on_search_page(
577         is_reverse_search,
578         [],
579         get_config_value('Map_Default_Lat'),
580         get_config_value('Map_Default_Lon'),
581         get_config_value('Map_Default_Zoom')
582       );
583     }
584
585   } else {
586     api_request_params = {
587       q: search_params.get('q'),
588       polygon_geojson: search_params.get('polygon_geojson') ? 1 : 0,
589       viewbox: search_params.get('viewbox'),
590       format: 'jsonv2'
591     };
592
593     context = {
594       // aSearchResults: aResults,
595       sQuery: api_request_params.q,
596       sViewBox: search_params.get('viewbox'),
597       env: Nominatim_Config,
598       sMoreURL: ''
599     };
600
601     if (api_request_params.q) {
602
603       fetch_from_api('search', api_request_params, function (aResults) {
604
605         context.aSearchResults = aResults;
606
607         render_template($('main'), 'searchpage-template', context);
608         update_html_title('Result for ' + api_request_params.q);
609
610         init_map_on_search_page(
611           is_reverse_search,
612           aResults,
613           get_config_value('Map_Default_Lat'),
614           get_config_value('Map_Default_Lon'),
615           get_config_value('Map_Default_Zoom')
616         );
617
618         $('#q').focus();
619
620         update_data_date();
621       });
622     } else {
623       render_template($('main'), 'searchpage-template', context);
624
625       init_map_on_search_page(
626         is_reverse_search,
627         [],
628         get_config_value('Map_Default_Lat'),
629         get_config_value('Map_Default_Lon'),
630         get_config_value('Map_Default_Zoom')
631       );
632     }
633   }
634 });