]> git.openstreetmap.org Git - nominatim-ui.git/blob - src/components/Map.svelte
2d1270d37b1695dd60812ec48a4896a6df469fc1
[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, current_result_store, current_request_latlon } from '../lib/stores.js';
11   import MapPosition from '../components/MapPosition.svelte';
12
13   export let display_minimap = false;
14
15   let dataLayers = [];
16
17   function createMap(container) {
18     const attribution = get_config_value('Map_Tile_Attribution') || null;
19     let map = new L.map(container, {
20       attributionControl: (attribution && attribution.length),
21       scrollWheelZoom: true, // !L.Browser.touch,
22       touchZoom: false,
23       center: [
24         get_config_value('Map_Default_Lat'),
25         get_config_value('Map_Default_Lon')
26       ],
27       zoom: get_config_value('Map_Default_Zoom')
28     });
29
30     L.tileLayer(get_config_value('Map_Tile_URL'), {
31       attribution: attribution
32     }).addTo(map);
33
34     if (display_minimap) {
35       let osm2 = new L.TileLayer(get_config_value('Map_Tile_URL'), {
36         minZoom: 0,
37         maxZoom: 13,
38         attribution: attribution
39       });
40       new L.Control.MiniMap(osm2, { toggleDisplay: true }).addTo(map);
41     }
42
43     const MapPositionControl = L.Control.extend({
44       options: { position: 'topright' },
45       onAdd: () => { return document.getElementById('show-map-position'); }
46     });
47     map.addControl(new MapPositionControl());
48
49     return map;
50   }
51
52   function mapAction(container) {
53     let map = createMap(container);
54     map_store.set(map);
55     setMapData(get(current_result_store));
56
57     return {
58       destroy: () => { map.remove(); }
59     };
60   }
61
62   function parse_and_normalize_geojson_string(part) {
63     // normalize places the geometry into a featurecollection, similar to
64     // https://github.com/mapbox/geojson-normalize
65     var parsed_geojson = {
66       type: 'FeatureCollection',
67       features: [
68         {
69           type: 'Feature',
70           geometry: part,
71           properties: {}
72         }
73       ]
74     };
75     return parsed_geojson;
76   }
77
78   function resetMapData() {
79     let map = get(map_store);
80     if (!map) { return; }
81
82     dataLayers.forEach(function (layer) {
83       map.removeLayer(layer);
84     });
85   }
86
87   function setMapData(aFeature) {
88     let map = get(map_store);
89     if (!map) { return; }
90
91     resetMapData();
92
93     let request_latlon = get(current_request_latlon);
94     if (request_latlon) {
95       // We don't need a marker, but an L.circle instance changes radius once you zoom in/out
96       let cm = L.circleMarker(
97         request_latlon,
98         {
99           radius: 5,
100           weight: 2,
101           fillColor: '#ff7800',
102           color: 'red',
103           opacity: 0.75,
104           zIndexOffset: 100,
105           clickable: false
106         }
107       );
108       cm.addTo(map);
109       dataLayers.push(cm);
110     }
111
112     var search_params = new URLSearchParams(window.location.search);
113     var viewbox = search_params.get('viewbox');
114     if (viewbox) {
115       let coords = viewbox.split(','); // <x1>,<y1>,<x2>,<y2>
116       let bounds = L.latLngBounds([coords[1], coords[0]], [coords[3], coords[2]]);
117       L.rectangle(bounds, {
118         color: '#69d53e',
119         weight: 3,
120         dashArray: '5 5',
121         opacity: 0.8,
122         fill: false
123       }).addTo(map);
124     }
125
126     // nothing to do
127     if (!aFeature) { return; }
128
129     let lat = aFeature.centroid ? aFeature.centroid.coordinates[1] : aFeature.lat;
130     let lon = aFeature.centroid ? aFeature.centroid.coordinates[0] : aFeature.lon;
131     let geojson = aFeature.geometry || aFeature.geojson;
132
133     if (lat && lon) {
134       let circle = L.circleMarker([lat, lon], {
135         radius: 10, weight: 2, fillColor: '#ff7800', color: 'blue', opacity: 0.75
136       });
137       map.addLayer(circle);
138       dataLayers.push(circle);
139     }
140
141
142     if (geojson) {
143       var geojson_layer = L.geoJson(
144         // https://leafletjs.com/reference-1.0.3.html#path-option
145         parse_and_normalize_geojson_string(geojson),
146         {
147           style: function () {
148             return { interactive: false, color: 'blue' };
149           }
150         }
151       );
152       map.addLayer(geojson_layer);
153       dataLayers.push(geojson_layer);
154       map.fitBounds(geojson_layer.getBounds());
155     } else if (lat && lon) {
156       map.setView([lat, lon], 10);
157     } else {
158       map.fitWorld();
159     }
160   }
161
162   current_result_store.subscribe(aFeature => {
163     setMapData(aFeature);
164   });
165
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>