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