]> git.argeo.org Git - gpl/argeo-suite.git/blob - js/src/geo/OpenLayersMapPart.js
Revert loading of XSD
[gpl/argeo-suite.git] / js / src / geo / OpenLayersMapPart.js
1 /** OpenLayers-based implementation.
2 * @module OpenLayersMapPart
3 */
4
5 import { fromLonLat, getPointResolution } from 'ol/proj.js';
6
7 import TileLayer from 'ol/layer/Tile.js';
8
9 import OSM from 'ol/source/OSM.js';
10 import { isEmpty } from 'ol/extent';
11
12 import Select from 'ol/interaction/Select.js';
13 import Overlay from 'ol/Overlay.js';
14
15 import Map from 'ol/Map.js';
16
17 import { OverviewMap, ScaleLine, defaults as defaultControls } from 'ol/control.js';
18 import { easeOut } from 'ol/easing';
19
20 import * as SLDReader from '@nieuwlandgeo/sldreader';
21
22 import MapPart from './MapPart.js';
23 import { transformToOlLonLatExtent } from './OpenLayersUtils.js';
24
25 /** OpenLayers implementation of MapPart. */
26 export default class OpenLayersMapPart extends MapPart {
27 /** The OpenLayers Map. */
28 #map;
29
30 /** The overview map */
31 #overviewMap;
32
33 /** Styled layer descriptor */
34 #sld;
35
36 /** The select interaction */
37 select;
38
39 /** Externally added callback functions. */
40 callbacks = {};
41
42 /** Constructor taking the mapName as an argument. */
43 constructor(mapName) {
44 super(mapName);
45 this.#overviewMap = new OverviewMap({
46 layers: [
47 new TileLayer({
48 source: new OSM(),
49 }),
50 ],
51 });
52 this.select = new Select();
53 this.#map = new Map({
54 controls: defaultControls({
55 attribution: false,
56 rotate: false,
57 }).extend([this.#overviewMap, new ScaleLine({
58 bar: false,
59 steps: 2,
60 text: false,
61 minWidth: 150,
62 maxWidth: 200,
63 })]),
64 layers: [
65 ],
66 // view: new View({
67 // projection: 'EPSG:4326',
68 // center: [0, 0],
69 // zoom: 2,
70 // }),
71 target: this.getMapName(),
72 });
73 this.#map.addInteraction(this.select);
74 //this.#map.getView().set('projection', 'EPSG:4326', true);
75 }
76
77 /* GEOGRAPHICAL METHODS */
78
79 setCenter(lat, lon) {
80 this.#map.getView().setCenter(fromLonLat([lon, lat]));
81 }
82
83 fit(extent, options) {
84 var transformed = transformToOlLonLatExtent(extent, this.#map.getView().getProjection());
85 this.#map.getView().fit(transformed, options);
86 }
87
88 /** Accessors */
89 getMap() {
90 return this.#map;
91 }
92
93 getLayerByName(name) {
94 let layers = this.#map.getLayers();
95 for (let i = 0; i < layers.getLength(); i++) {
96 let layer = layers.item(i);
97 let n = layer.get('name');
98 if (n !== undefined) {
99 if (name === n)
100 return layer;
101 }
102 }
103 return undefined;
104 }
105
106 /* CALLBACKS */
107 enableFeatureSingleClick() {
108 // we cannot use 'this' in the function provided to OpenLayers
109 let mapPart = this;
110 this.#map.on('singleclick', function(e) {
111 let feature = null;
112 // we chose the first one
113 e.map.forEachFeatureAtPixel(e.pixel, function(f) {
114 feature = f;
115 return true;
116 });
117 if (feature !== null)
118 mapPart.callbacks['onFeatureSingleClick'](feature.get('cr:path'));
119 });
120 }
121
122 enableFeatureSelected() {
123 // we cannot use 'this' in the function provided to OpenLayers
124 let mapPart = this;
125 var select = new Select();
126 this.#map.addInteraction(select);
127 select.on('select', function(e) {
128 if (e.selected.length > 0) {
129 let feature = e.selected[0];
130 mapPart.callbacks['onFeatureSelected'](feature.get('cr:path'));
131 }
132 });
133 }
134
135 enableFeaturePopup() {
136 // we cannot use 'this' in the function provided to OpenLayers
137 let mapPart = this;
138 /**
139 * Elements that make up the popup.
140 */
141 const container = document.getElementById('popup');
142 const content = document.getElementById('popup-content');
143 const closer = document.getElementById('popup-closer');
144
145 /**
146 * Create an overlay to anchor the popup to the map.
147 */
148 const overlay = new Overlay({
149 element: container,
150 autoPan: false,
151 autoPanAnimation: {
152 duration: 250,
153 },
154 });
155 this.#map.addOverlay(overlay);
156
157 let selected = null;
158 this.#map.on('pointermove', function(e) {
159 if (selected !== null) {
160 selected.setStyle(undefined);
161 selected = null;
162 }
163
164 e.map.forEachFeatureAtPixel(e.pixel, function(f) {
165 selected = f;
166 return true;
167 });
168
169 if (selected == null) {
170 overlay.setPosition(undefined);
171 return;
172 }
173 const coordinate = e.coordinate;
174 const path = selected.get('cr:path');
175 if (path === null)
176 return true;
177 const res = mapPart.callbacks['onFeaturePopup'](path);
178 if (res != null) {
179 content.innerHTML = res;
180 overlay.setPosition(coordinate);
181 } else {
182 overlay.setPosition(undefined);
183 }
184 });
185 }
186
187 selectFeatures(layerName, featureIds) {
188 // we cannot use 'this' in the function provided to OpenLayers
189 let mapPart = this;
190 this.select.getFeatures().clear();
191 const layer = this.getLayerByName(layerName);
192 const source = layer.getSource();
193 for (const featureId of featureIds) {
194 let feature = source.getFeatureById(featureId);
195 if (feature === null) {
196 source.on('featuresloadend', function(e) {
197 feature = source.getFeatureById(featureId);
198 if (feature !== null)
199 mapPart.select.getFeatures().push(feature);
200 });
201 } else {
202 this.select.getFeatures().push(feature);
203 }
204 }
205 }
206
207 fitToLayer(layerName) {
208 // we cannot use 'this' in the function provided to OpenLayers
209 let mapPart = this;
210 const layer = this.getLayerByName(layerName);
211 const source = layer.getSource();
212 const extent = source.getExtent();
213 const options = {
214 duration: 1000,
215 padding: [20, 20, 20, 20],
216 easing: easeOut,
217 };
218 if (!isEmpty(extent))
219 this.#map.getView().fit(source.getExtent(), options);
220 source.on('featuresloadend', function(e) {
221 mapPart.getMap().getView().fit(source.getExtent(), options);
222 });
223 }
224
225 //
226 // HTML
227 //
228 getMapDivCssClass() {
229 return 'map';
230 }
231
232
233 //
234 // STATIC FOR EXTENSION
235 //
236 static newStyle(args) {
237 return new Style(args);
238 }
239
240 static newIcon(args) {
241 return new Icon(args);
242 }
243
244 //
245 // SLD STYLING
246 //
247
248 setSld(xml) {
249 this.#sld = SLDReader.Reader(xml);
250 }
251
252 /** Get a FeatureTypeStyle (https://nieuwlandgeo.github.io/SLDReader/api.html#FeatureTypeStyle). */
253 getFeatureTypeStyle(styledLayerName, styleName) {
254 const sldLayer = SLDReader.getLayer(this.#sld, styledLayerName);
255 const style = styleName === undefined ? SLDReader.getStyle(sldLayer) : SLDReader.getStyle(sldLayer, styleName);
256 // OpenLayers can only use one definition
257 const featureTypeStyle = style.featuretypestyles[0];
258 return featureTypeStyle;
259 }
260
261 applyStyle(layerName, styledLayerName, styleName) {
262 const layer = this.getLayerByName(layerName);
263 const featureTypeStyle = this.getFeatureTypeStyle(styledLayerName, styleName);
264 const viewProjection = this.#map.getView().getProjection();
265 const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
266 // Use the convertResolution option to calculate a more accurate resolution.
267 convertResolution: viewResolution => {
268 const viewCenter = this.#map.getView().getCenter();
269 return getPointResolution(viewProjection, viewResolution, viewCenter);
270 },
271 // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
272 // to update the vector layer when an image finishes loading.
273 // If you do not do this, the image will only be visible after next layer pan/zoom.
274 imageLoadedCallback: () => {
275 layer.changed();
276 },
277 });
278 layer.setStyle(olStyleFunction);
279 }
280
281 #applySLD(vectorLayer, text) {
282 const sldObject = SLDReader.Reader(text);
283 const sldLayer = SLDReader.getLayer(sldObject);
284 const style = SLDReader.getStyle(sldLayer);
285 const featureTypeStyle = style.featuretypestyles[0];
286
287 const viewProjection = this.#map.getView().getProjection();
288 const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
289 // Use the convertResolution option to calculate a more accurate resolution.
290 convertResolution: viewResolution => {
291 const viewCenter = this.#map.getView().getCenter();
292 return getPointResolution(viewProjection, viewResolution, viewCenter);
293 },
294 // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
295 // to update the vector layer when an image finishes loading.
296 // If you do not do this, the image will only be visible after next layer pan/zoom.
297 imageLoadedCallback: () => {
298 vectorLayer.changed();
299 },
300 });
301 vectorLayer.setStyle(olStyleFunction);
302 }
303 }