]> git.openstreetmap.org Git - nominatim-ui.git/blob - src/assets/js/nominatim-ui.js
first commit
[nominatim-ui.git] / src / assets / js / nominatim-ui.js
1 var map;
2 var last_click_latlng;
3
4
5 /*********************************************************
6 * HELPERS
7 *********************************************************/
8
9 function get_config_value(str, default_val) {
10     return (typeof Nominatim_Config[str] !== 'undefined' ? Nominatim_Config[str] :  default_val);
11 }
12
13 function parse_and_normalize_geojson_string(raw_string){
14     // normalize places the geometry into a featurecollection, similar to
15     // https://github.com/mapbox/geojson-normalize
16     var parsed_geojson = {
17         type: "FeatureCollection",
18         features: [
19             {
20                 type: "Feature",
21                 geometry: JSON.parse(raw_string),
22                 properties: {}
23             }
24         ]
25     };
26     return parsed_geojson;
27 }
28
29 function map_link_to_osm(){
30     return "https://openstreetmap.org/#map=" + map.getZoom() + "/" + map.getCenter().lat + "/" + map.getCenter().lng;
31 }
32
33 function map_viewbox_as_string() {
34     // since .toBBoxString() doesn't round numbers
35     return [
36         map.getBounds().getSouthWest().lng.toFixed(5), // left
37         map.getBounds().getNorthEast().lat.toFixed(5), // top
38         map.getBounds().getNorthEast().lng.toFixed(5), // right
39         map.getBounds().getSouthWest().lat.toFixed(5)  // bottom
40     ].join(',');
41 }
42
43
44 /*********************************************************
45 * PAGE HELPERS
46 *********************************************************/
47
48 function fetch_from_api(endpoint_name, params, callback) {
49     var api_url = get_config_value('Nominatim_API_Endpoint') + endpoint_name + '.php?' + $.param(params);
50     $('#api-request-link').attr('href', api_url);
51
52     $.get(api_url, function(data){
53         callback(data);
54     });
55 }
56
57 function update_data_date() {
58     fetch_from_api('status', {format: 'json'}, function(data){
59         $('#data-date').text(data.data_last_updated.formatted);
60     });
61 }
62
63 function render_template(el, template_name, page_context) {
64     var template_source = $('#' + template_name).text();
65     var template = Handlebars.compile(template_source);
66     var html    = template(page_context);
67     el.html(html);
68 }
69
70
71 /*********************************************************
72 * FORWARD/REVERSE SEARCH PAGE
73 *********************************************************/
74
75
76 function display_map_position(mouse_lat_lng){
77
78     html_mouse = "mouse position " + (mouse_lat_lng ? [mouse_lat_lng.lat.toFixed(5), mouse_lat_lng.lng.toFixed(5)].join(',') : '-');
79     html_click = "last click: " + (last_click_latlng ? [last_click_latlng.lat.toFixed(5),last_click_latlng.lng.toFixed(5)].join(',') : '-');
80
81     html_center = 
82         "map center: " + 
83         map.getCenter().lat.toFixed(5) + ',' + map.getCenter().lng.toFixed(5) +
84         " <a target='_blank' href='" + map_link_to_osm() + "'>view on osm.org</a>";
85
86     html_zoom = "map zoom: " + map.getZoom();
87
88     html_viewbox = "viewbox: " + map_viewbox_as_string();
89
90     $('#map-position-inner').html([html_center,html_zoom,html_viewbox,html_click,html_mouse].join('<br/>'));
91
92     var reverse_params = {
93         // lat: map.getCenter().lat.toFixed(5),
94         // lon: map.getCenter().lng.toFixed(5),
95         // zoom: 2,
96         // format: 'html'
97     }
98     $('#switch-to-reverse').attr('href', 'reverse.html?' + $.param(reverse_params));
99
100     $('input#use_viewbox').trigger('change');
101 }
102
103
104
105
106 function init_map_on_search_page(is_reverse_search, nominatim_results) {
107
108     map = new L.map('map', {
109         // center: [nominatim_map_init.lat, nominatim_map_init.lon],
110         // zoom:   nominatim_map_init.zoom,
111         attributionControl: (get_config_value('Map_Tile_Attribution') && get_config_value('Map_Tile_Attribution').length),
112         scrollWheelZoom:    true, // !L.Browser.touch,
113         touchZoom:          false,
114     });
115
116
117     L.tileLayer(get_config_value('Map_Tile_URL'), {
118         noWrap: true, // otherwise we end up with click coordinates like latitude -728
119         // moved to footer
120         attribution: (get_config_value('Map_Tile_Attribution') || null ) //'&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
121     }).addTo(map);
122
123     // console.log(Nominatim_Config);
124
125     map.setView([get_config_value('Map_Default_Lat'), get_config_value('Map_Default_Lon')], get_config_value('Map_Default_Zoom'));
126
127     var osm2 = new L.TileLayer(get_config_value('Map_Tile_URL'), {minZoom: 0, maxZoom: 13, attribution: (get_config_value('Map_Tile_Attribution') || null )});
128     var miniMap = new L.Control.MiniMap(osm2, {toggleDisplay: true}).addTo(map);
129
130     if (is_reverse_search) {
131         // We don't need a marker, but an L.circle instance changes radius once you zoom in/out
132         var cm = L.circleMarker([get_config_value('Map_Default_Lat'), get_config_value('Map_Default_Lon')], { radius: 5, weight: 2, fillColor: '#ff7800', color: 'red', opacity: 0.75, clickable: false});
133         cm.addTo(map);
134     }
135
136     var MapPositionControl = L.Control.extend({
137         options: {
138             position: 'topright'
139         },
140         onAdd: function (map) {
141             var container = L.DomUtil.create('div', 'my-custom-control');
142
143             $(container).text('show map bounds').addClass('leaflet-bar btn btn-sm btn-default').on('click', function(e){
144                 e.preventDefault();
145                 e.stopPropagation();
146                 $('#map-position').show();
147                 $(container).hide();
148             });
149             $('#map-position-close a').on('click', function(e){
150                 e.preventDefault();
151                 e.stopPropagation();
152                 $('#map-position').hide();
153                 $(container).show();
154             });
155
156             return container;
157         }
158     });
159
160     map.addControl(new MapPositionControl());
161
162
163
164
165
166     function update_viewbox_field(){
167         // hidden HTML field
168         $('input[name=viewbox]').val( $('input#use_viewbox').prop('checked') ? map_viewbox_as_string() : '');
169     }
170
171     map.on('move', function(e) {
172         display_map_position();
173         update_viewbox_field();
174     });
175
176     map.on('mousemove', function(e) {
177         display_map_position(e.latlng);
178     });
179
180     map.on('click', function(e) {
181         last_click_latlng = e.latlng;
182         display_map_position();
183     });
184
185     map.on('load', function(e){
186         display_map_position();
187     });
188
189
190     $('input#use_viewbox').on('change', function(){
191         update_viewbox_field();
192     });
193
194
195
196
197     function get_result_element(position){
198         return $('.result').eq(position);
199     }
200     function marker_for_result(result){
201         return L.marker([result.lat,result.lon], {riseOnHover:true,title:result.name });
202     }
203     function circle_for_result(result){
204         return L.circleMarker([result.lat,result.lon], { radius: 10, weight: 2, fillColor: '#ff7800', color: 'blue', opacity: 0.75, clickable: !is_reverse_search});
205     }
206
207     var layerGroup = new L.layerGroup().addTo(map);
208     function highlight_result(position, bool_focus){
209         var result = nominatim_results[position];
210         if (!result){ return }
211         var result_el = get_result_element(position);
212
213         $('.result').removeClass('highlight');
214         result_el.addClass('highlight');
215
216         layerGroup.clearLayers();
217
218         if (result.lat){
219             var circle = circle_for_result(result);
220             circle.on('click', function(){
221                 highlight_result(position);
222             });
223             layerGroup.addLayer(circle);            
224         }
225         if (result.aBoundingBox){
226
227             var bounds = [[result.aBoundingBox[0]*1,result.aBoundingBox[2]*1], [result.aBoundingBox[1]*1,result.aBoundingBox[3]*1]];
228             map.fitBounds(bounds);
229
230             if (result.asgeojson && result.asgeojson.match(/(Polygon)|(Line)/) ){
231
232                 var geojson_layer = L.geoJson(
233                     parse_and_normalize_geojson_string(result.asgeojson),
234                     {
235                         // http://leafletjs.com/reference-1.0.3.html#path-option
236                         style: function(feature) {
237                             return { interactive: false, color: 'blue' }; 
238                         }
239                     }
240                 );
241                 layerGroup.addLayer(geojson_layer);
242             }
243             // else {
244             //     var layer = L.rectangle(bounds, {color: "#ff7800", weight: 1} );
245             //     layerGroup.addLayer(layer);
246             // }
247         }
248         else {
249             var result_coord = L.latLng(result.lat, result.lon);
250             if ( result_coord ){
251                 if ( is_reverse_search ){
252                     // make sure the search coordinates are in the map view as well
253                     map.fitBounds([result_coord, [get_config_value('Map_Default_Lat'), get_config_value('Map_Default_Lon')]], {padding: [50,50], maxZoom: map.getZoom()});
254
255                     // better, but causes a leaflet warning
256                     // map.panInsideBounds([[result.lat,result.lon], [nominatim_map_init.lat,nominatim_map_init.lon]], {animate: false});
257                 }
258                 else {
259                     map.panTo(result_coord, result.zoom || get_config_value('Map_Default_Zoom'));
260                 }
261             }
262         }
263         if (bool_focus){
264             $('#map').focus();
265         }
266     }
267
268
269     $('.result').on('click', function(e){
270         highlight_result($(this).data('position'), true);
271     });
272
273     if ( is_reverse_search ){
274         map.on('click', function(e){
275             $('form input[name=lat]').val( e.latlng.lat);
276             $('form input[name=lon]').val( e.latlng.lng);
277             $('form').submit();
278         });
279
280         $('#switch-coords').on('click', function(e){
281             e.preventDefault();
282             e.stopPropagation();
283             var lat = $('form input[name=lat]').val();
284             var lon = $('form input[name=lon]').val();
285             $('form input[name=lat]').val(lon);
286             $('form input[name=lon]').val(lat);
287             $('form').submit();
288         });
289     }
290
291     highlight_result(0, false);
292
293     // common mistake is to copy&paste latitude and longitude into the 'lat' search box
294     $('form input[name=lat]').on('change', function(){
295         var coords = $(this).val().split(',');
296         if (coords.length == 2) {
297             $(this).val(L.Util.trim(coords[0]));
298             $(this).siblings('input[name=lon]').val(L.Util.trim(coords[1]));
299         }
300     });
301 };
302
303
304
305
306
307
308
309
310
311
312 jQuery(document).ready(function(){
313
314     if ( !$('#search-page,#reverse-page').length ){ return; }
315     
316     var is_reverse_search = !!( $('#reverse-page').length );
317     var endpoint = is_reverse_search ? 'reverse' : 'search';
318
319
320     var search_params = new URLSearchParams(location.search);
321
322
323     // return view('search', [
324     //     'sQuery' => $sQuery,
325     //     'bAsText' => '',
326     //     'sViewBox' => '',
327     //     'aSearchResults' => $aSearchResults,
328     //     'sMoreURL' => 'example.com',
329     //     'sDataDate' => $this->fetch_status_date(),
330     //     'sApiURL' => $url
331     // ]);
332
333
334     if (is_reverse_search) {
335         var api_request_params = {
336             lat: search_params.get('lat') || 0,
337             lon: search_params.get('lon') || 0,
338             zoom: search_params.get('zoom'),
339             format: 'jsonv2'
340         }
341
342         fetch_from_api('reverse', api_request_params, function(aPlace){
343
344             if (aPlace.error) {
345                 aPlace = null;
346             }
347
348             var context = {
349                 aPlace: aPlace,
350                 fLat: api_request_params.lat,
351                 fLon: api_request_params.lon,
352                 iZoom: api_request_params.zoom
353             };
354
355             render_template($('main'), 'reversepage-template', context);
356
357             init_map_on_search_page(is_reverse_search, [aPlace]);
358
359             update_data_date();
360         });
361     } else {
362         var api_request_params = {
363             q: search_params.get('q'),
364             polygon_geojson: search_params.get('polygon_geojson') ? 1 : 0,
365             polygon: search_params.get('polygon'),
366             format: 'jsonv2'
367         };
368
369         fetch_from_api('search', api_request_params, function(aResults){
370
371             var context = {
372                 aSearchResults: aResults,
373                 sQuery: api_request_params.q,
374                 sViewBox: '',
375                 env: Nominatim_Config,
376                 sMoreURL: ''
377             };
378
379             render_template($('main'), 'searchpage-template', context);
380
381             init_map_on_search_page(is_reverse_search, aResults);
382
383             $('#q').focus();
384
385             update_data_date();
386         });
387     }
388 });
389
390
391 /*********************************************************
392 * DETAILS PAGE
393 *********************************************************/
394
395
396
397 function init_map_on_detail_page(lat, lon, geojson) {
398     map = new L.map('map', {
399         // center: [nominatim_map_init.lat, nominatim_map_init.lon],
400         // zoom:   nominatim_map_init.zoom,
401         attributionControl: (get_config_value('Map_Tile_Attribution') && get_config_value('Map_Tile_Attribution').length),
402         scrollWheelZoom:    true, // !L.Browser.touch,
403         touchZoom:          false,
404     });
405
406     L.tileLayer(get_config_value('Map_Tile_URL'), {
407         // moved to footer
408         attribution: (get_config_value('Map_Tile_Attribution') || null ) //'&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
409     }).addTo(map);
410
411     var layerGroup = new L.layerGroup().addTo(map);
412
413     var circle = L.circleMarker([lat,lon], { radius: 10, weight: 2, fillColor: '#ff7800', color: 'blue', opacity: 0.75});
414     map.addLayer(circle);
415
416     if (geojson) {
417         var geojson_layer = L.geoJson(
418             // http://leafletjs.com/reference-1.0.3.html#path-option
419             parse_and_normalize_geojson_string(geojson),
420             {
421                 style: function(feature) {
422                     return { interactive: false, color: 'blue' }; 
423                 }
424             }
425         );
426         map.addLayer(geojson_layer);
427         map.fitBounds(geojson_layer.getBounds());
428     } else {
429         map.setView([lat,lon],10);
430     }
431
432     var osm2 = new L.TileLayer(get_config_value('Map_Tile_URL'), {minZoom: 0, maxZoom: 13, attribution: (get_config_value('Map_Tile_Attribution') || null )});
433     var miniMap = new L.Control.MiniMap(osm2, {toggleDisplay: true}).addTo(map);
434 }
435
436 jQuery(document).ready(function(){
437
438     if ( !$('#details-page').length ){ return; }
439
440     var search_params = new URLSearchParams(location.search);
441     // var place_id = search_params.get('place_id');
442
443     var api_request_params = {
444         place_id: search_params.get('place_id'),
445         group_parents: 1,
446         format: 'json'
447     };
448
449     fetch_from_api('details', api_request_params, function(aFeature){
450
451         var context = { aPlace: aFeature };
452
453         render_template($('main'), 'detailspage-template', context);
454
455         update_data_date();
456
457         init_map_on_detail_page(aFeature.lat, aFeature.lon, aFeature.asgeojson);
458     });
459 });
460