/** * @module olcs.RasterSynchronizer */ import olLayerGroup from 'ol/layer/Group.js'; import {getUid, stableSort} from './util.js'; import olcsAbstractSynchronizer from './AbstractSynchronizer.js'; import olcsCore from './core.js'; class RasterSynchronizer extends olcsAbstractSynchronizer { /** * This object takes care of one-directional synchronization of * Openlayers raster layers to the given Cesium globe. * @param {!ol.Map} map * @param {!Cesium.Scene} scene * @constructor * @extends {olcsAbstractSynchronizer.} * @api */ constructor(map, scene) { super(map, scene); /** * @type {!Cesium.ImageryLayerCollection} * @private */ this.cesiumLayers_ = scene.imageryLayers; /** * @type {!Cesium.ImageryLayerCollection} * @private */ this.ourLayers_ = new Cesium.ImageryLayerCollection(); } /** * @inheritDoc */ addCesiumObject(object) { this.cesiumLayers_.add(object); this.ourLayers_.add(object); } /** * @inheritDoc */ destroyCesiumObject(object) { object.destroy(); } /** * @inheritDoc */ removeSingleCesiumObject(object, destroy) { this.cesiumLayers_.remove(object, destroy); this.ourLayers_.remove(object, false); } /** * @inheritDoc */ removeAllCesiumObjects(destroy) { for (let i = 0; i < this.ourLayers_.length; ++i) { this.cesiumLayers_.remove(this.ourLayers_.get(i), destroy); } this.ourLayers_.removeAll(false); } /** * Creates an array of Cesium.ImageryLayer. * May be overriden by child classes to implement custom behavior. * The default implementation handles tiled imageries in EPSG:4326 or * EPSG:3859. * @param {!ol.layer.Base} olLayer * @param {!ol.proj.Projection} viewProj Projection of the view. * @return {?Array.} array or null if not possible * (or supported) * @protected */ convertLayerToCesiumImageries(olLayer, viewProj) { const result = olcsCore.tileLayerToImageryLayer(this.map, olLayer, viewProj); return result ? [result] : null; } /** * @inheritDoc */ createSingleLayerCounterparts(olLayerWithParents) { const olLayer = olLayerWithParents.layer; const uid = getUid(olLayer).toString(); const viewProj = this.view.getProjection(); console.assert(viewProj); const cesiumObjects = this.convertLayerToCesiumImageries(olLayer, viewProj); if (cesiumObjects) { const listenKeyArray = []; [olLayerWithParents.layer].concat(olLayerWithParents.parents).forEach((olLayerItem) => { listenKeyArray.push(olLayerItem.on(['change:opacity', 'change:visible'], () => { // the compiler does not seem to be able to infer this console.assert(cesiumObjects); for (let i = 0; i < cesiumObjects.length; ++i) { olcsCore.updateCesiumLayerProperties(olLayerWithParents, cesiumObjects[i]); } })); }); if (olLayer.getStyleFunction) { let previousStyleFunction = olLayer.getStyleFunction(); // there is no convenient way to detect a style function change in OL listenKeyArray.push(olLayer.on('change', () => { const currentStyleFunction = olLayer.getStyleFunction(); if (previousStyleFunction === currentStyleFunction) { return; } previousStyleFunction = currentStyleFunction; for (let i = 0; i < cesiumObjects.length; ++i) { const csObj = cesiumObjects[i]; // clear cache and set new style if (csObj._imageryCache && csObj.imageryProvider.cache_) { csObj._imageryCache = {}; csObj.imageryProvider.cache_ = {}; csObj.imageryProvider.styleFunction_ = currentStyleFunction; } } this.scene.requestRender(); })); } for (let i = 0; i < cesiumObjects.length; ++i) { olcsCore.updateCesiumLayerProperties(olLayerWithParents, cesiumObjects[i]); } // there is no way to modify Cesium layer extent, // we have to recreate when OpenLayers layer extent changes: listenKeyArray.push(olLayer.on('change:extent', (e) => { for (let i = 0; i < cesiumObjects.length; ++i) { this.cesiumLayers_.remove(cesiumObjects[i], true); // destroy this.ourLayers_.remove(cesiumObjects[i], false); } delete this.layerMap[getUid(olLayer)]; // invalidate the map entry this.synchronize(); })); listenKeyArray.push(olLayer.on('change', (e) => { // when the source changes, re-add the layer to force update for (let i = 0; i < cesiumObjects.length; ++i) { const position = this.cesiumLayers_.indexOf(cesiumObjects[i]); if (position >= 0) { this.cesiumLayers_.remove(cesiumObjects[i], false); this.cesiumLayers_.add(cesiumObjects[i], position); } } })); this.olLayerListenKeys[uid].push(...listenKeyArray); } return Array.isArray(cesiumObjects) ? cesiumObjects : null; } /** * Order counterparts using the same algorithm as the Openlayers renderer: * z-index then original sequence order. * @override * @protected */ orderLayers() { const layers = []; const zIndices = {}; const queue = [this.mapLayerGroup]; while (queue.length > 0) { const olLayer = queue.splice(0, 1)[0]; layers.push(olLayer); zIndices[getUid(olLayer)] = olLayer.getZIndex() || 0; if (olLayer instanceof olLayerGroup) { const sublayers = olLayer.getLayers(); if (sublayers) { // Prepend queue with sublayers in order queue.unshift(...sublayers.getArray()); } } } stableSort(layers, (layer1, layer2) => zIndices[getUid(layer1)] - zIndices[getUid(layer2)] ); layers.forEach((olLayer) => { const olLayerId = getUid(olLayer).toString(); const cesiumObjects = this.layerMap[olLayerId]; if (cesiumObjects) { cesiumObjects.forEach((cesiumObject) => { this.raiseToTop(cesiumObject); }); } }); } /** * @param {Cesium.ImageryLayer} counterpart */ raiseToTop(counterpart) { this.cesiumLayers_.raiseToTop(counterpart); } } export default RasterSynchronizer;