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