1 /** OpenLayers-based implementation.
2 * @module OpenLayersMapPart
5 import { fromLonLat
, getPointResolution
} from 'ol/proj.js';
6 import { transformExtent
} from 'ol/proj.js';
8 import TileLayer
from 'ol/layer/Tile.js';
10 import OSM
from 'ol/source/OSM.js';
11 import { isEmpty
} from 'ol/extent';
13 import Select
from 'ol/interaction/Select.js';
14 import Overlay
from 'ol/Overlay.js';
16 import Map
from 'ol/Map.js';
18 import { OverviewMap
, ScaleLine
, defaults as defaultControls
} from 'ol/control.js';
19 import { easeOut
} from 'ol/easing';
21 import * as SLDReader
from '@nieuwlandgeo/sldreader';
23 import MapPart
from './MapPart.js';
25 /** OpenLayers implementation of MapPart. */
26 export default class OpenLayersMapPart
extends MapPart
{
27 /** The OpenLayers Map. */
30 /** The overview map */
33 /** Styled layer descriptor */
36 /** The select interaction */
39 /** Externally added callback functions. */
42 /** Constructor taking the mapName as an argument. */
43 constructor(mapName
) {
45 this.#overviewMap
= new OverviewMap({
52 this.select
= new Select();
54 controls
: defaultControls({
57 }).extend([this.#overviewMap
, new ScaleLine({
67 // projection: 'EPSG:4326',
71 target
: this.getMapName(),
73 this.#map
.addInteraction(this.select
);
74 //this.#map.getView().set('projection', 'EPSG:4326', true);
77 /* GEOGRAPHICAL METHODS */
80 this.#map
.getView().setCenter(fromLonLat([lon
, lat
]));
83 fit(extent
, options
) {
84 var transformed
= transformExtent(extent
, 'EPSG:4326', this.#map
.getView().getProjection());
85 this.#map
.getView().fit(transformed
, options
);
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) {
107 enableFeatureSingleClick() {
108 // we cannot use 'this' in the function provided to OpenLayers
110 this.#map
.on('singleclick', function(e
) {
112 // we chose the first one
113 e
.map
.forEachFeatureAtPixel(e
.pixel
, function(f
) {
117 if (feature
!== null)
118 mapPart
.callbacks
['onFeatureSingleClick'](feature
.get('cr:path'));
122 enableFeatureSelected() {
123 // we cannot use 'this' in the function provided to OpenLayers
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'));
135 enableFeaturePopup() {
136 // we cannot use 'this' in the function provided to OpenLayers
139 * Elements that make up the popup.
141 const container
= document
.getElementById('popup');
142 const content
= document
.getElementById('popup-content');
143 const closer
= document
.getElementById('popup-closer');
146 * Create an overlay to anchor the popup to the map.
148 const overlay
= new Overlay({
155 this.#map
.addOverlay(overlay
);
158 this.#map
.on('pointermove', function(e
) {
159 if (selected
!== null) {
160 selected
.setStyle(undefined);
164 e
.map
.forEachFeatureAtPixel(e
.pixel
, function(f
) {
169 if (selected
== null) {
170 overlay
.setPosition(undefined);
173 const coordinate
= e
.coordinate
;
174 const path
= selected
.get('cr:path');
177 const res
= mapPart
.callbacks
['onFeaturePopup'](path
);
179 content
.innerHTML
= res
;
180 overlay
.setPosition(coordinate
);
182 overlay
.setPosition(undefined);
187 selectFeatures(layerName
, featureIds
) {
188 // we cannot use 'this' in the function provided to OpenLayers
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
);
202 this.select
.getFeatures().push(feature
);
207 fitToLayer(layerName
) {
208 // we cannot use 'this' in the function provided to OpenLayers
210 const layer
= this.getLayerByName(layerName
);
211 const source
= layer
.getSource();
212 const extent
= source
.getExtent();
215 padding
: [20, 20, 20, 20],
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
);
228 getMapDivCssClass() {
234 // STATIC FOR EXTENSION
236 static newStyle(args
) {
237 return new Style(args
);
240 static newIcon(args
) {
241 return new Icon(args
);
249 this.#sld
= SLDReader
.Reader(xml
);
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;
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);
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: () => {
278 layer.setStyle(olStyleFunction);
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];
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);
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();
301 vectorLayer.setStyle(olStyleFunction);