goog.provide('olcs.AbstractSynchronizer');
goog.require('goog.events');
goog.require('ol.layer.Group');
goog.require('ol.layer.Layer');
/**
* @param {!ol.Map} map
* @param {!Cesium.Scene} scene
* @constructor
* @template T
* @api
*/
olcs.AbstractSynchronizer = function(map, scene) {
/**
* @type {!ol.Map}
* @protected
*/
this.map = map;
/**
* @type {ol.View}
* @protected
*/
this.view = null;
/**
* @type {!Cesium.Scene}
* @protected
*/
this.scene = scene;
/**
* @type {ol.Collection.<ol.layer.Base>}
* @protected
*/
this.olLayers = null;
/**
* @type {!Array}
* @private
*/
this.olLayersListenKeys_ = [];
/**
* Map of ol3 layer ids (from goog.getUid) to the Cesium ImageryLayers.
* null value means, that we are unable to create equivalent layer.
* @type {Object.<number, ?T>}
* @protected
*/
this.layerMap = {};
/**
* Map of listen keys for ol3 layer groups ids (from goog.getUid).
* @type {!Object.<number, !Array>}
* @private
*/
this.olGroupListenKeys_ = {};
/**
* @type {Object.<number, !Array>}
* @private
*/
this.unusedGroups_ = null;
/**
* @type {Object.<?T, number>}
* @private
*/
this.unusedCesiumObjects_ = null;
this.map.on('change:view', function(e) {
this.setView_(this.map.getView());
}, this);
this.setView_(this.map.getView());
this.map.on('change:layergroup', function(e) {
this.setLayers_(this.map.getLayers());
}, this);
this.setLayers_(this.map.getLayers());
};
/**
* @param {ol.View} view New view to use.
* @private
*/
olcs.AbstractSynchronizer.prototype.setView_ = function(view) {
this.view = view;
// destroy all, the change of view can affect which layers are synced
this.destroyAll();
this.synchronize();
};
/**
* @param {ol.Collection.<ol.layer.Base>} layers New layers to use.
* @private
*/
olcs.AbstractSynchronizer.prototype.setLayers_ = function(layers) {
if (!goog.isNull(this.olLayers)) {
goog.array.forEach(this.olLayersListenKeys_, this.olLayers.unByKey);
}
this.olLayers = layers;
if (!goog.isNull(layers)) {
var handleCollectionEvent_ = goog.bind(function(e) {
this.synchronize();
}, this);
this.olLayersListenKeys_ = [
layers.on('add', handleCollectionEvent_),
layers.on('remove', handleCollectionEvent_)
];
} else {
this.olLayersListenKeys_ = [];
}
this.destroyAll();
this.synchronize();
};
/**
* Performs complete synchronization of the layers.
* @api
*/
olcs.AbstractSynchronizer.prototype.synchronize = function() {
if (goog.isNull(this.view) || goog.isNull(this.olLayers)) {
return;
}
this.unusedGroups_ = goog.object.clone(this.olGroupListenKeys_);
this.unusedCesiumObjects_ = goog.object.transpose(this.layerMap);
this.removeAllCesiumObjects(false); // only remove, don't destroy
this.olLayers.forEach(function(el, i, arr) {
this.synchronizeSingle(el);
}, this);
// destroy unused Cesium Objects
goog.array.forEach(goog.object.getValues(this.unusedCesiumObjects_),
function(el, i, arr) {
var layerId = el;
var object = this.layerMap[layerId];
if (goog.isDef(object)) {
delete this.layerMap[layerId];
if (!goog.isNull(object)) {
this.destroyCesiumObject(object);
}
}
}, this);
this.unusedCesiumObjects_ = null;
// unlisten unused ol layer groups
goog.object.forEach(this.unusedGroups_, function(keys, groupId, obj) {
goog.array.forEach(keys, this.map.unByKey);
delete this.olGroupListenKeys_[groupId];
}, this);
this.unusedGroups_ = null;
};
/**
* Synchronizes single layer.
* @param {ol.layer.Base} olLayer
* @protected
*/
olcs.AbstractSynchronizer.prototype.synchronizeSingle = function(olLayer) {
if (goog.isNull(olLayer)) {
return;
}
var olLayerId = goog.getUid(olLayer);
// handle layer groups
if (olLayer instanceof ol.layer.Group) {
var sublayers = olLayer.getLayers();
if (goog.isDef(sublayers)) {
sublayers.forEach(function(el, i, arr) {
this.synchronizeSingle(el);
}, this);
}
if (!goog.isDef(this.olGroupListenKeys_[olLayerId])) {
var listenKeyArray = [];
this.olGroupListenKeys_[olLayerId] = listenKeyArray;
// only the keys that need to be relistened when collection changes
var collection, contentKeys = [];
var listenAddRemove = goog.bind(function() {
collection = /** @type {ol.layer.Group} */ (olLayer).getLayers();
if (goog.isDef(collection)) {
var handleContentChange_ = goog.bind(function(e) {
this.synchronize();
}, this);
contentKeys = [
collection.on('add', handleContentChange_),
collection.on('remove', handleContentChange_)
];
listenKeyArray.push.apply(listenKeyArray, contentKeys);
}
}, this);
listenAddRemove();
listenKeyArray.push(olLayer.on('change:layers', function(e) {
goog.array.forEach(contentKeys, function(el, i, arr) {
goog.array.remove(listenKeyArray, el);
collection.unByKey(el);
});
listenAddRemove();
}));
}
delete this.unusedGroups_[olLayerId];
return;
} else if (!(olLayer instanceof ol.layer.Layer)) {
return;
}
var cesiumObject = this.layerMap[olLayerId];
// no mapping -> create new layer and set up synchronization
if (!goog.isDef(cesiumObject)) {
cesiumObject = this.createSingleCounterpart(olLayer);
this.layerMap[olLayerId] = cesiumObject;
}
// add Cesium layers
if (goog.isDefAndNotNull(cesiumObject)) {
this.addCesiumObject(cesiumObject);
delete this.unusedCesiumObjects_[cesiumObject];
}
};
/**
* Destroys all the created Cesium objects.
* @protected
*/
olcs.AbstractSynchronizer.prototype.destroyAll = function() {
this.removeAllCesiumObjects(true); // destroy
this.layerMap = {};
};
/**
* Adds a single Cesium object to the collection.
* @param {!T} object
* @protected
*/
olcs.AbstractSynchronizer.prototype.addCesiumObject = goog.abstractMethod;
/**
* @param {!T} object
* @protected
*/
olcs.AbstractSynchronizer.prototype.destroyCesiumObject = goog.abstractMethod;
/**
* Remove all Cesium objects from the collection.
* @param {boolean} destroy
* @protected
*/
olcs.AbstractSynchronizer.prototype.removeAllCesiumObjects =
goog.abstractMethod;
/**
* @param {!ol.layer.Layer} olLayer
* @return {T}
* @protected
*/
olcs.AbstractSynchronizer.prototype.createSingleCounterpart =
goog.abstractMethod;