]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.app.geo.js/src/org.argeo.app.geo.js/OpenLayersMapPart.js
Experiment with GML
[gpl/argeo-suite.git] / org.argeo.app.geo.js / src / org.argeo.app.geo.js / 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 OSM from 'ol/source/OSM.js';
8 import TileLayer from 'ol/layer/Tile.js';
9 import { fromLonLat, getPointResolution } from 'ol/proj.js';
10 import VectorSource from 'ol/source/Vector.js';
11 import Feature from 'ol/Feature.js';
12 import { Point } from 'ol/geom.js';
13 import VectorLayer from 'ol/layer/Vector.js';
14 import GeoJSON from 'ol/format/GeoJSON.js';
15 import GPX from 'ol/format/GPX.js';
16 import Select from 'ol/interaction/Select.js';
17 import Overlay from 'ol/Overlay.js';
18 import { Style, Icon } from 'ol/style.js';
19
20 import * as SLDReader from '@nieuwlandgeo/sldreader';
21
22 import MapPart from './MapPart.js';
23 import { SentinelCloudless } from './OpenLayerTileSources.js';
24
25 /** OpenLayers implementation of MapPart. */
26 export default class OpenLayersMapPart extends MapPart {
27 /** The OpenLayers Map. */
28 #map;
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 // new TileLayer({
39 // source: new SentinelCloudless(),
40 // }),
41 new TileLayer({
42 source: new OSM(),
43 opacity: 0.4,
44 transition: 0,
45 }),
46 ],
47 target: this.getMapName(),
48 });
49 }
50
51 /* GEOGRAPHICAL METHODS */
52
53 setZoom(zoom) {
54 this.#map.getView().setZoom(zoom);
55 }
56
57 setCenter(lng, lat) {
58 this.#map.getView().setCenter(fromLonLat([lng, lat]));
59 }
60
61 addPoint(lng, lat, style) {
62 let vectorSource = new VectorSource({
63 features: [new Feature({
64 geometry: new Point(fromLonLat([lng, lat]))
65 })]
66 });
67 this.#map.addLayer(new VectorLayer({
68 source: vectorSource,
69 style: style,
70 }));
71 }
72
73 addUrlLayer(url, format, style, sld) {
74 let featureFormat;
75 if (format === 'GEOJSON') {
76 featureFormat = new GeoJSON();
77 }
78 else if (format === 'GPX') {
79 featureFormat = new GPX();
80 } else {
81 throw new Error("Unsupported format " + format);
82 }
83 const vectorSource = new VectorSource({
84 url: url,
85 format: featureFormat,
86 });
87 const vectorLayer = new VectorLayer({
88 source: vectorSource,
89 });
90 if (sld) {
91 this.#applySLD(vectorLayer, style);
92 } else {
93 vectorLayer.setStyle(style);
94 }
95 this.#map.addLayer(vectorLayer);
96 }
97
98
99 /* CALLBACKS */
100 enableFeatureSingleClick() {
101 // we cannot use 'this' in the function provided to OpenLayers
102 let mapPart = this;
103 this.#map.on('singleclick', function(e) {
104 let feature = null;
105 // we chose the first one
106 e.map.forEachFeatureAtPixel(e.pixel, function(f) {
107 feature = f;
108 return true;
109 });
110 if (feature !== null)
111 mapPart.callbacks['onFeatureSingleClick'](feature.get('path'));
112 });
113 }
114
115 enableFeatureSelected() {
116 // we cannot use 'this' in the function provided to OpenLayers
117 let mapPart = this;
118 var select = new Select();
119 this.#map.addInteraction(select);
120 select.on('select', function(e) {
121 if (e.selected.length > 0) {
122 let feature = e.selected[0];
123 mapPart.callbacks['onFeatureSelected'](feature.get('path'));
124 }
125 });
126 }
127
128 enableFeaturePopup() {
129 // we cannot use 'this' in the function provided to OpenLayers
130 let mapPart = this;
131 /**
132 * Elements that make up the popup.
133 */
134 const container = document.getElementById('popup');
135 const content = document.getElementById('popup-content');
136 const closer = document.getElementById('popup-closer');
137
138 /**
139 * Create an overlay to anchor the popup to the map.
140 */
141 const overlay = new Overlay({
142 element: container,
143 autoPan: false,
144 autoPanAnimation: {
145 duration: 250,
146 },
147 });
148 this.#map.addOverlay(overlay);
149
150 let selected = null;
151 this.#map.on('pointermove', function(e) {
152 if (selected !== null) {
153 selected.setStyle(undefined);
154 selected = null;
155 }
156
157 e.map.forEachFeatureAtPixel(e.pixel, function(f) {
158 selected = f;
159 return true;
160 });
161
162 if (selected == null) {
163 overlay.setPosition(undefined);
164 return;
165 }
166 const coordinate = e.coordinate;
167 const path = selected.get('path');
168 if (path === null)
169 return true;
170 const res = mapPart.callbacks['onFeaturePopup'](path);
171 if (res != null) {
172 content.innerHTML = res;
173 overlay.setPosition(coordinate);
174 } else {
175 overlay.setPosition(undefined);
176 }
177 });
178 }
179
180 //
181 // HTML
182 //
183 getMapDivCssClass() {
184 return 'map';
185 }
186
187 //
188 // STATIC FOR EXTENSION
189 //
190 static newStyle(args) {
191 return new Style(args);
192 }
193
194 static newIcon(args) {
195 return new Icon(args);
196 }
197
198 //
199 // SLD STYLING
200 //
201 #applySLD(vectorLayer, text) {
202 const sldObject = SLDReader.Reader(text);
203 const sldLayer = SLDReader.getLayer(sldObject);
204 const style = SLDReader.getStyle(sldLayer);
205 const featureTypeStyle = style.featuretypestyles[0];
206
207 const viewProjection = this.#map.getView().getProjection();
208 const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
209 // Use the convertResolution option to calculate a more accurate resolution.
210 convertResolution: viewResolution => {
211 const viewCenter = this.#map.getView().getCenter();
212 return getPointResolution(viewProjection, viewResolution, viewCenter);
213 },
214 // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
215 // to update the vector layer when an image finishes loading.
216 // If you do not do this, the image will only be visible after next layer pan/zoom.
217 imageLoadedCallback: () => {
218 vectorLayer.changed();
219 },
220 });
221 vectorLayer.setStyle(olStyleFunction);
222 }
223
224
225 }