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