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