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