]> git.openstreetmap.org Git - nominatim-ui.git/blob - src/components/Map.svelte
Search page: Set HTML page title for structured searches, too
[nominatim-ui.git] / src / components / Map.svelte
1
2 <script>
3   import * as L from 'leaflet';
4   import 'leaflet-minimap';
5   import 'leaflet/dist/leaflet.css';
6   import 'leaflet-minimap/dist/Control.MiniMap.min.css';
7
8   import { get } from 'svelte/store';
9   import { map_store } from '../lib/stores.js';
10   import MapPosition from '../components/MapPosition.svelte';
11
12   export let display_minimap = false;
13   export let current_result = null;
14   export let position_marker = null;
15
16   let dataLayers = [];
17
18   function createMap(container) {
19     const attribution = Nominatim_Config.Map_Tile_Attribution;
20
21     let map = new L.map(container, {
22       attributionControl: false,
23       scrollWheelZoom: true, // !L.Browser.touch,
24       touchZoom: false,
25       center: [
26         Nominatim_Config.Map_Default_Lat,
27         Nominatim_Config.Map_Default_Lon
28       ],
29       zoom: Nominatim_Config.Map_Default_Zoom
30     });
31     if (typeof Nominatim_Config.Map_Default_Bounds !== 'undefined'
32       && Nominatim_Config.Map_Default_Bounds) {
33       map.fitBounds(Nominatim_Config.Map_Default_Bounds);
34     }
35
36     if (attribution && attribution.length) {
37       L.control.attribution({ prefix: '<a href="https://leafletjs.com/">Leaflet</a>' }).addTo(map);
38     }
39
40     L.tileLayer(Nominatim_Config.Map_Tile_URL, {
41       attribution: attribution
42     }).addTo(map);
43
44     if (display_minimap) {
45       let osm2 = new L.TileLayer(Nominatim_Config.Map_Tile_URL, {
46         minZoom: 0,
47         maxZoom: 13,
48         attribution: attribution
49       });
50       new L.Control.MiniMap(osm2, { toggleDisplay: true }).addTo(map);
51     }
52
53     const MapPositionControl = L.Control.extend({
54       options: { position: 'topright' },
55       onAdd: () => { return document.getElementById('show-map-position'); }
56     });
57     map.addControl(new MapPositionControl());
58
59     return map;
60   }
61
62   function mapAction(container) {
63     let map = createMap(container);
64     map_store.set(map);
65     setMapData(current_result);
66
67     return {
68       destroy: () => {
69         map_store.set(null);
70         map.remove();
71       }
72     };
73   }
74
75   function parse_and_normalize_geojson_string(part) {
76     // normalize places the geometry into a featurecollection, similar to
77     // https://github.com/mapbox/geojson-normalize
78     var parsed_geojson = {
79       type: 'FeatureCollection',
80       features: [
81         {
82           type: 'Feature',
83           geometry: part,
84           properties: {}
85         }
86       ]
87     };
88     return parsed_geojson;
89   }
90
91   function resetMapData() {
92     let map = get(map_store);
93     if (!map) { return; }
94
95     dataLayers.forEach(function (layer) {
96       map.removeLayer(layer);
97     });
98   }
99
100   function setMapData(aFeature) {
101     let map = get(map_store);
102     if (!map) { return; }
103
104     resetMapData();
105
106     if (position_marker) {
107       // We don't need a marker, but L.circle would change radius when you zoom in/out
108       let cm = L.circleMarker(
109         position_marker,
110         {
111           radius: 5,
112           weight: 2,
113           fillColor: '#ff7800',
114           color: 'red',
115           opacity: 0.75,
116           zIndexOffset: 100,
117           clickable: false
118         }
119       );
120       cm.bindTooltip(`Search (${position_marker[0]},${position_marker[1]})`).openTooltip();
121       cm.addTo(map);
122       dataLayers.push(cm);
123     }
124
125     var search_params = new URLSearchParams(window.location.search);
126     var viewbox = search_params.get('viewbox');
127     if (viewbox) {
128       let coords = viewbox.split(','); // <x1>,<y1>,<x2>,<y2>
129       let bounds = L.latLngBounds([coords[1], coords[0]], [coords[3], coords[2]]);
130       let viewbox_on_map = L.rectangle(bounds, {
131         color: '#69d53e',
132         weight: 3,
133         dashArray: '5 5',
134         opacity: 0.8,
135         fill: false,
136         interactive: false
137       });
138       map.addLayer(viewbox_on_map);
139       dataLayers.push(viewbox_on_map);
140     }
141
142     if (!aFeature) return;
143
144     let lat = aFeature.centroid ? aFeature.centroid.coordinates[1] : aFeature.lat;
145     let lon = aFeature.centroid ? aFeature.centroid.coordinates[0] : aFeature.lon;
146     let geojson = aFeature.geometry || aFeature.geojson;
147
148     if (lat && lon) {
149       let circle = L.circleMarker([lat, lon], {
150         radius: 10, weight: 2, fillColor: '#ff7800', color: 'blue', opacity: 0.75
151       });
152       if (position_marker) { // reverse result
153         circle.bindTooltip('Result').openTooltip();
154       }
155       map.addLayer(circle);
156       dataLayers.push(circle);
157     }
158
159
160     if (geojson) {
161       var geojson_layer = L.geoJson(
162         // https://leafletjs.com/reference-1.7.1.html#path-option
163         parse_and_normalize_geojson_string(geojson),
164         {
165           style: function () {
166             return { interactive: false, color: 'blue' };
167           }
168         }
169       );
170       map.addLayer(geojson_layer);
171       dataLayers.push(geojson_layer);
172       map.fitBounds(geojson_layer.getBounds());
173     } else if (lat && lon && position_marker) {
174       map.fitBounds([[lat, lon], position_marker], { padding: [50, 50] });
175     } else if (lat && lon) {
176       map.setView([lat, lon], 10);
177     }
178   }
179
180   $: setMapData(current_result);
181
182   function show_map_position_click(e) {
183     e.target.style.display = 'none';
184     document.getElementById('map-position').style.display = 'block';
185   }
186 </script>
187
188 <MapPosition />
189 <div id="map" use:mapAction />
190 <button id="show-map-position" class="leaflet-bar btn btn-sm btn-outline-secondary"
191       on:click|stopPropagation={show_map_position_click}
192 >show map bounds</button>
193
194 <style>
195   #map {
196     height: 100%;
197     background:#eee;
198   }
199
200   .btn-outline-secondary {
201     background-color: white;
202   }
203
204   .btn-outline-secondary:hover {
205     color: #111;
206   }
207
208   @media (max-width: 768px) {
209     #map {
210       height: 300px;
211     }
212   }
213
214 </style>