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