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