SLD styling support, based on Geographical CSS
[gpl/argeo-suite.git] / org.argeo.app.geo.js / src / org.argeo.app.geo.js / OpenLayersMapPart.js
index b2d09115f94309e890f5d31936f0b3f857d9c65a..13597fa8cfe8c61c06c4545e704e069a89d847ec 100644 (file)
@@ -3,9 +3,10 @@
  */
 
 import Map from 'ol/Map.js';
+import View from 'ol/View.js';
 import OSM from 'ol/source/OSM.js';
 import TileLayer from 'ol/layer/Tile.js';
-import { fromLonLat } from 'ol/proj.js';
+import { fromLonLat, getPointResolution } from 'ol/proj.js';
 import VectorSource from 'ol/source/Vector.js';
 import Feature from 'ol/Feature.js';
 import { Point } from 'ol/geom.js';
@@ -14,6 +15,9 @@ import GeoJSON from 'ol/format/GeoJSON.js';
 import GPX from 'ol/format/GPX.js';
 import Select from 'ol/interaction/Select.js';
 import Overlay from 'ol/Overlay.js';
+import { Style, Icon } from 'ol/style.js';
+
+import * as SLDReader from '@nieuwlandgeo/sldreader';
 
 import MapPart from './MapPart.js';
 import { SentinelCloudless } from './OpenLayerTileSources.js';
@@ -22,23 +26,25 @@ import { SentinelCloudless } from './OpenLayerTileSources.js';
 export default class OpenLayersMapPart extends MapPart {
        /** The OpenLayers Map. */
        #map;
+
+       /** Externally added callback functions. */
        callbacks = {};
 
-       // Constructor
-       constructor() {
-               super();
+       /** Constructor taking the mapName as an argument. */
+       constructor(mapName) {
+               super(mapName);
                this.#map = new Map({
                        layers: [
-                               new TileLayer({
-                                       source: new SentinelCloudless(),
-                               }),
+                               //                              new TileLayer({
+                               //                                      source: new SentinelCloudless(),
+                               //                              }),
                                new TileLayer({
                                        source: new OSM(),
                                        opacity: 0.4,
                                        transition: 0,
                                }),
                        ],
-                       target: 'map',
+                       target: this.getMapName(),
                });
        }
 
@@ -58,22 +64,38 @@ export default class OpenLayersMapPart extends MapPart {
                                geometry: new Point(fromLonLat([lng, lat]))
                        })]
                });
-               this.#map.addLayer(new VectorLayer({ source: vectorSource }));
+               this.#map.addLayer(new VectorLayer({
+                       source: vectorSource,
+                       style: style,
+               }));
        }
 
-       addUrlLayer(url, format) {
-               let vectorSource;
+       addUrlLayer(url, format, style, sld) {
+               let featureFormat;
                if (format === 'GEOJSON') {
-                       vectorSource = new VectorSource({ url: url, format: new GeoJSON() })
+                       featureFormat = new GeoJSON();
                }
                else if (format === 'GPX') {
-                       vectorSource = new VectorSource({ url: url, format: new GPX() })
+                       featureFormat = new GPX();
+               } else {
+                       throw new Error("Unsupported format " + format);
                }
-               this.#map.addLayer(new VectorLayer({
+               const vectorSource = new VectorSource({
+                       url: url,
+                       format: featureFormat,
+               });
+               const vectorLayer = new VectorLayer({
                        source: vectorSource,
-               }));
+               });
+               if (sld) {
+                       this.#applySLD(vectorLayer, style);
+               } else {
+                       vectorLayer.setStyle(style);
+               }
+               this.#map.addLayer(vectorLayer);
        }
 
+
        /* CALLBACKS */
        enableFeatureSingleClick() {
                // we cannot use 'this' in the function provided to OpenLayers
@@ -143,6 +165,8 @@ export default class OpenLayersMapPart extends MapPart {
                        }
                        const coordinate = e.coordinate;
                        const path = selected.get('path');
+                       if (path === null)
+                               return true;
                        const res = mapPart.callbacks['onFeaturePopup'](path);
                        if (res != null) {
                                content.innerHTML = res;
@@ -152,4 +176,50 @@ export default class OpenLayersMapPart extends MapPart {
                        }
                });
        }
+
+       //
+       // HTML
+       //
+       getMapDivCssClass() {
+               return 'map';
+       }
+
+       //
+       // STATIC FOR EXTENSION
+       //
+       static newStyle(args) {
+               return new Style(args);
+       }
+
+       static newIcon(args) {
+               return new Icon(args);
+       }
+
+       //
+       // SLD STYLING
+       //
+       #applySLD(vectorLayer, text) {
+               const sldObject = SLDReader.Reader(text);
+               const sldLayer = SLDReader.getLayer(sldObject);
+               const style = SLDReader.getStyle(sldLayer);
+               const featureTypeStyle = style.featuretypestyles[0];
+
+               const viewProjection = this.#map.getView().getProjection();
+               const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
+                       // Use the convertResolution option to calculate a more accurate resolution.
+                       convertResolution: viewResolution => {
+                               const viewCenter = this.#map.getView().getCenter();
+                               return getPointResolution(viewProjection, viewResolution, viewCenter);
+                       },
+                       // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
+                       // to update the vector layer when an image finishes loading.
+                       // If you do not do this, the image will only be visible after next layer pan/zoom.
+                       imageLoadedCallback: () => {
+                               vectorLayer.changed();
+                       },
+               });
+               vectorLayer.setStyle(olStyleFunction);
+       }
+
+
 }