7 // *********************************************************
9 // *********************************************************
11 function get_config_value(str, default_val) {
12 return (typeof Nominatim_Config[str] !== 'undefined' ? Nominatim_Config[str] : default_val);
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',
28 return parsed_geojson;
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;
38 function map_viewbox_as_string() {
39 var bounds = map.getBounds();
40 var west = bounds.getWest();
41 var east = bounds.getEast();
43 if ((east - west) >= 360) { // covers more than whole planet
44 west = map.getCenter().lng - 179.999;
45 east = map.getCenter().lng + 179.999;
47 east = L.latLng(77, east).wrap().lng;
48 west = L.latLng(77, west).wrap().lng;
51 west.toFixed(5), // left
52 bounds.getNorth().toFixed(5), // top
53 east.toFixed(5), // right
54 bounds.getSouth().toFixed(5) // bottom
59 // *********************************************************
61 // *********************************************************
63 function fetch_from_api(endpoint_name, params, callback) {
64 // `&a=&b=&c=1` => '&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]];
76 var api_url = get_config_value('Nominatim_API_Endpoint') + endpoint_name + '.php?'
78 if (endpoint_name !== 'status') {
79 $('#api-request-link').attr('href', api_url);
81 $.get(api_url, function (data) {
86 function update_data_date() {
87 fetch_from_api('status', { format: 'json' }, function (data) {
88 $('#data-date').text(data.data_updated);
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);
99 function update_html_title(title) {
101 if (title && title.length > 1) {
102 prefix = title + ' | ';
104 $('head title').text(prefix + 'OpenStreetMap Nominatim');
107 function show_error(html) {
108 $('#error-overlay').html(html).show();
111 function hide_error() {
112 $('#error-overlay').empty().hide();
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>');
124 jQuery(document).ready(function () {
127 // *********************************************************
129 // *********************************************************
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,
142 L.tileLayer(get_config_value('Map_Tile_URL'), {
144 // '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
145 attribution: attribution
148 // var layerGroup = new L.layerGroup().addTo(map);
150 var circle = L.circleMarker([lat, lon], {
151 radius: 10, weight: 2, fillColor: '#ff7800', color: 'blue', opacity: 0.75
153 map.addLayer(circle);
156 var geojson_layer = L.geoJson(
157 // https://leafletjs.com/reference-1.0.3.html#path-option
158 parse_and_normalize_geojson_string(geojson),
161 return { interactive: false, color: 'blue' };
165 map.addLayer(geojson_layer);
166 map.fitBounds(geojson_layer.getBounds());
168 map.setView([lat, lon], 10);
171 var osm2 = new L.TileLayer(
172 get_config_value('Map_Tile_URL'),
176 attribution: (get_config_value('Map_Tile_Attribution') || null)
179 (new L.Control.MiniMap(osm2, { toggleDisplay: true })).addTo(map);
183 jQuery(document).ready(function () {
184 if (!$('#details-page').length) { return; }
186 var search_params = new URLSearchParams(window.location.search);
187 // var place_id = search_params.get('place_id');
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'),
195 hierarchy: (search_params.get('hierarchy') === '1' ? 1 : 0),
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 };
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);
209 update_html_title('Details for ' + api_request_params.osmtype + api_request_params.osmid);
214 var lat = aFeature.centroid.coordinates[1];
215 var lon = aFeature.centroid.coordinates[0];
216 init_map_on_detail_page(lat, lon, aFeature.geometry);
219 render_template($('main'), 'detailspage-index-template');
222 $('#form-by-type-and-id,#form-by-osm-url').on('submit', function (e) {
225 var val = $(this).find('input[type=edit]').val();
226 var matches = val.match(/^\s*([NWR])(\d+)\s*$/i);
229 matches = val.match(/\/(relation|way|node)\/(\d+)\s*$/);
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();
237 alert('invalid input');
242 // *********************************************************
243 // FORWARD/REVERSE SEARCH PAGE
244 // *********************************************************
247 function display_map_position(mouse_lat_lng) {
250 mouse_lat_lng = map.wrapLatLng(mouse_lat_lng);
253 var html_mouse = 'mouse position: -';
255 html_mouse = 'mouse position: '
256 + [mouse_lat_lng.lat.toFixed(5), mouse_lat_lng.lng.toFixed(5)].join(',');
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(',');
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>';
268 var html_zoom = 'map zoom: ' + map.getZoom();
269 var html_viewbox = 'viewbox: ' + map_viewbox_as_string();
271 $('#map-position-inner').html([
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)
286 $('#switch-to-reverse').attr('href', 'reverse.html?' + $.param(reverse_params));
288 $('input#use_viewbox').trigger('change');
291 function init_map_on_search_page(is_reverse_search, nominatim_results, request_lat,
292 request_lon, init_zoom) {
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,
304 L.tileLayer(get_config_value('Map_Tile_URL'), {
306 // '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
307 attribution: attribution
310 // console.log(Nominatim_Config);
312 map.setView([request_lat, request_lon], init_zoom);
314 var osm2 = new L.TileLayer(get_config_value('Map_Tile_URL'), {
317 attribution: attribution
319 new L.Control.MiniMap(osm2, { toggleDisplay: true }).addTo(map);
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],
328 fillColor: '#ff7800',
337 var search_params = new URLSearchParams(window.location.search);
338 var viewbox = search_params.get('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, {
352 var MapPositionControl = L.Control.extend({
356 onAdd: function (/* map */) {
357 var container = L.DomUtil.create('div', 'my-custom-control');
359 $(container).text('show map bounds')
360 .addClass('leaflet-bar btn btn-sm btn-default')
361 .on('click', function (e) {
364 $('#map-position').show();
367 $('#map-position-close a').on('click', function (e) {
370 $('#map-position').hide();
378 map.addControl(new MapPositionControl());
384 function update_viewbox_field() {
386 $('input[name=viewbox]')
387 .val($('input#use_viewbox')
388 .prop('checked') ? map_viewbox_as_string() : '');
391 map.on('move', function () {
392 display_map_position();
393 update_viewbox_field();
396 map.on('mousemove', function (e) {
397 display_map_position(e.latlng);
400 map.on('click', function (e) {
401 last_click_latlng = e.latlng;
402 display_map_position();
405 map.on('load', function () {
406 display_map_position();
409 $('input#use_viewbox').on('change', function () {
410 update_viewbox_field();
416 function get_result_element(position) {
417 return $('.result').eq(position);
419 // function marker_for_result(result) {
420 // return L.marker([result.lat, result.lon], { riseOnHover: true, title: result.name });
422 function circle_for_result(result) {
426 fillColor: '#ff7800',
429 clickable: !is_reverse_search
431 return L.circleMarker([result.lat, result.lon], cm_style);
434 var layerGroup = (new L.layerGroup()).addTo(map);
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);
441 $('.result').removeClass('highlight');
442 result_el.addClass('highlight');
444 layerGroup.clearLayers();
447 var circle = circle_for_result(result);
448 circle.on('click', function () {
449 highlight_result(position);
451 layerGroup.addLayer(circle);
454 if (result.boundingbox) {
456 [result.boundingbox[0] * 1, result.boundingbox[2] * 1],
457 [result.boundingbox[1] * 1, result.boundingbox[3] * 1]
461 if (result.geojson && result.geojson.type.match(/(Polygon)|(Line)/)) {
463 var geojson_layer = L.geoJson(
464 parse_and_normalize_geojson_string(result.geojson),
466 // https://leafletjs.com/reference-1.0.3.html#path-option
467 style: function (/* feature */) {
468 return { interactive: false, color: 'blue' };
472 layerGroup.addLayer(geojson_layer);
475 // var layer = L.rectangle(bounds, {color: "#ff7800", weight: 1} );
476 // layerGroup.addLayer(layer);
479 var result_coord = L.latLng(result.lat, result.lon);
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
485 [result_coord, [request_lat, request_lon]],
488 maxZoom: map.getZoom()
492 map.panTo(result_coord, result.zoom || get_config_value('Map_Default_Zoom'));
502 $('.result').on('click', function () {
503 highlight_result($(this).data('position'), true);
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);
513 $('#switch-coords').on('click', function (e) {
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);
524 highlight_result(0, false);
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]));
542 jQuery(document).ready(function () {
544 if (!$('#search-page,#reverse-page').length) { return; }
546 var is_reverse_search = !!($('#reverse-page').length);
548 var search_params = new URLSearchParams(window.location.search);
550 // return view('search', [
551 // 'sQuery' => $sQuery,
554 // 'aSearchResults' => $aSearchResults,
555 // 'sMoreURL' => 'example.com',
556 // 'sDataDate' => $this->fetch_status_date(),
560 var api_request_params;
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')),
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'))
583 if (api_request_params.lat && api_request_params.lon) {
585 fetch_from_api('reverse', api_request_params, function (aPlace) {
591 context.aPlace = aPlace;
593 render_template($('main'), 'reversepage-template', context);
594 update_html_title('Reverse result for '
595 + api_request_params.lat
597 + api_request_params.lon);
599 init_map_on_search_page(
602 api_request_params.lat,
603 api_request_params.lon,
604 api_request_params.zoom
610 render_template($('main'), 'reversepage-template', context);
612 init_map_on_search_page(
615 get_config_value('Map_Default_Lat'),
616 get_config_value('Map_Default_Lon'),
617 get_config_value('Map_Default_Zoom')
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'),
630 // aSearchResults: aResults,
631 sQuery: api_request_params.q,
632 sViewBox: search_params.get('viewbox'),
633 env: Nominatim_Config,
637 if (api_request_params.q) {
639 fetch_from_api('search', api_request_params, function (aResults) {
641 context.aSearchResults = aResults;
643 render_template($('main'), 'searchpage-template', context);
644 update_html_title('Result for ' + api_request_params.q);
646 init_map_on_search_page(
649 get_config_value('Map_Default_Lat'),
650 get_config_value('Map_Default_Lon'),
651 get_config_value('Map_Default_Zoom')
659 render_template($('main'), 'searchpage-template', context);
661 init_map_on_search_page(
664 get_config_value('Map_Default_Lat'),
665 get_config_value('Map_Default_Lon'),
666 get_config_value('Map_Default_Zoom')