ol-cesium
Version:
OpenLayers Cesium integration library
1,400 lines (1,304 loc) • 209 kB
JavaScript
var olcs_unused_var;
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/olcs/AbstractSynchronizer.ts":
/*!******************************************!*\
!*** ./src/olcs/AbstractSynchronizer.ts ***!
\******************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AbstractSynchronizer)
/* harmony export */ });
/* harmony import */ var ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ol/Observable.js */ "ol/Observable.js");
/* harmony import */ var ol_Observable_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var ol_layer_Group_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ol/layer/Group.js */ "ol/layer/Group.js");
/* harmony import */ var ol_layer_Group_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ol_layer_Group_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./util */ "./src/olcs/util.ts");
class AbstractSynchronizer {
constructor(map, scene) {
/**
* Map of OpenLayers layer ids (from getUid) to the Cesium ImageryLayers.
* Null value means, that we are unable to create equivalent layers.
*/
this.layerMap = {};
/**
* Map of listen keys for OpenLayers layer layers ids (from getUid).
*/
this.olLayerListenKeys = {};
/**
* Map of listen keys for OpenLayers layer groups ids (from getUid).
*/
this.olGroupListenKeys_ = {};
this.map = map;
this.view = map.getView();
this.scene = scene;
this.olLayers = map.getLayerGroup().getLayers();
this.mapLayerGroup = map.getLayerGroup();
}
/**
* Destroy all and perform complete synchronization of the layers.
*/
synchronize() {
this.destroyAll();
this.addLayers_(this.mapLayerGroup);
}
/**
* Order counterparts using the same algorithm as the Openlayers renderer:
* z-index then original sequence order.
*/
orderLayers() {
// Ordering logics is handled in subclasses.
}
/**
* Add a layer hierarchy.
*/
addLayers_(root) {
const fifo = [{
layer: root,
parents: []
}];
while (fifo.length > 0) {
const olLayerWithParents = fifo.splice(0, 1)[0];
const olLayer = olLayerWithParents.layer;
const olLayerId = (0,_util__WEBPACK_IMPORTED_MODULE_2__.getUid)(olLayer).toString();
this.olLayerListenKeys[olLayerId] = [];
console.assert(!this.layerMap[olLayerId]);
let cesiumObjects = null;
if (olLayer instanceof (ol_layer_Group_js__WEBPACK_IMPORTED_MODULE_1___default())) {
this.listenForGroupChanges_(olLayer);
if (olLayer !== this.mapLayerGroup) {
cesiumObjects = this.createSingleLayerCounterparts(olLayerWithParents);
}
if (!cesiumObjects) {
olLayer.getLayers().forEach(l => {
if (l) {
const newOlLayerWithParents = {
layer: l,
parents: olLayer === this.mapLayerGroup ? [] : [olLayerWithParents.layer].concat(olLayerWithParents.parents)
};
fifo.push(newOlLayerWithParents);
}
});
}
} else {
cesiumObjects = this.createSingleLayerCounterparts(olLayerWithParents);
if (!cesiumObjects) {
// keep an eye on the layers that once failed to be added (might work when the layer is updated)
// for example when a source is set after the layer is added to the map
const layerId = olLayerId;
const layerWithParents = olLayerWithParents;
const onLayerChange = () => {
const cesiumObjs = this.createSingleLayerCounterparts(layerWithParents);
if (cesiumObjs) {
// unsubscribe event listener
layerWithParents.layer.un('change', onLayerChange);
this.addCesiumObjects_(cesiumObjs, layerId, layerWithParents.layer);
this.orderLayers();
}
};
this.olLayerListenKeys[olLayerId].push(layerWithParents.layer.on('change', onLayerChange));
}
}
// add Cesium layers
if (cesiumObjects) {
this.addCesiumObjects_(cesiumObjects, olLayerId, olLayer);
}
}
this.orderLayers();
}
/**
* Add Cesium objects.
*/
addCesiumObjects_(cesiumObjects, layerId, layer) {
this.layerMap[layerId] = cesiumObjects;
this.olLayerListenKeys[layerId].push(layer.on('change:zIndex', () => this.orderLayers()));
cesiumObjects.forEach(cesiumObject => {
this.addCesiumObject(cesiumObject);
});
}
/**
* Remove and destroy a single layer.
* @param {ol.layer.Layer} layer
* @return {boolean} counterpart destroyed
*/
removeAndDestroySingleLayer_(layer) {
const uid = (0,_util__WEBPACK_IMPORTED_MODULE_2__.getUid)(layer).toString();
const counterparts = this.layerMap[uid];
if (!!counterparts) {
counterparts.forEach(counterpart => {
this.removeSingleCesiumObject(counterpart, false);
this.destroyCesiumObject(counterpart);
});
this.olLayerListenKeys[uid].forEach(ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey);
delete this.olLayerListenKeys[uid];
}
delete this.layerMap[uid];
return !!counterparts;
}
/**
* Unlisten a single layer group.
*/
unlistenSingleGroup_(group) {
if (group === this.mapLayerGroup) {
return;
}
const uid = (0,_util__WEBPACK_IMPORTED_MODULE_2__.getUid)(group).toString();
const keys = this.olGroupListenKeys_[uid];
keys.forEach(key => {
(0,ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey)(key);
});
delete this.olGroupListenKeys_[uid];
delete this.layerMap[uid];
}
/**
* Remove layer hierarchy.
*/
removeLayer_(root) {
if (!!root) {
const fifo = [root];
while (fifo.length > 0) {
const olLayer = fifo.splice(0, 1)[0];
const done = this.removeAndDestroySingleLayer_(olLayer);
if (olLayer instanceof (ol_layer_Group_js__WEBPACK_IMPORTED_MODULE_1___default())) {
this.unlistenSingleGroup_(olLayer);
if (!done) {
// No counterpart for the group itself so removing
// each of the child layers.
olLayer.getLayers().forEach(l => {
fifo.push(l);
});
}
}
}
}
}
/**
* Register listeners for single layer group change.
*/
listenForGroupChanges_(group) {
const uuid = (0,_util__WEBPACK_IMPORTED_MODULE_2__.getUid)(group).toString();
console.assert(this.olGroupListenKeys_[uuid] === undefined);
const listenKeyArray = [];
this.olGroupListenKeys_[uuid] = listenKeyArray;
// only the keys that need to be relistened when collection changes
let contentKeys = [];
const listenAddRemove = function () {
const collection = group.getLayers();
if (collection) {
contentKeys = [collection.on('add', event => {
this.addLayers_(event.element);
}), collection.on('remove', event => {
this.removeLayer_(event.element);
})];
listenKeyArray.push(...contentKeys);
}
}.bind(this);
listenAddRemove();
listenKeyArray.push(group.on('change:layers', e => {
contentKeys.forEach(el => {
const i = listenKeyArray.indexOf(el);
if (i >= 0) {
listenKeyArray.splice(i, 1);
}
(0,ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey)(el);
});
listenAddRemove();
}));
}
/**
* Destroys all the created Cesium objects.
*/
destroyAll() {
this.removeAllCesiumObjects(true); // destroy
let objKey;
for (objKey in this.olGroupListenKeys_) {
const keys = this.olGroupListenKeys_[objKey];
keys.forEach(ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey);
}
for (objKey in this.olLayerListenKeys) {
this.olLayerListenKeys[objKey].forEach(ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey);
}
this.olGroupListenKeys_ = {};
this.olLayerListenKeys = {};
this.layerMap = {};
}
/**
* Adds a single Cesium object to the collection.
*/
/**
* Remove single Cesium object from the collection.
*/
}
/***/ }),
/***/ "./src/olcs/AutoRenderLoop.ts":
/*!************************************!*\
!*** ./src/olcs/AutoRenderLoop.ts ***!
\************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ AutoRenderLoop)
/* harmony export */ });
/**
* By default Cesium (used to?) renders as often as possible.
* This is a waste of resources (CPU/GPU/battery).
* An alternative mechanism in Cesium is on-demand rendering.
* This class makes use of this alternative method and add some additionnal render points.
*/
class AutoRenderLoop {
/**
* @param ol3d
*/
constructor(ol3d) {
this.repaintEventNames_ = ['mousemove', 'mousedown', 'mouseup', 'touchstart', 'touchend', 'touchmove', 'pointerdown', 'pointerup', 'pointermove', 'wheel'];
this.ol3d = ol3d;
this.scene_ = ol3d.getCesiumScene();
this.canvas_ = this.scene_.canvas;
this._boundNotifyRepaintRequired = this.notifyRepaintRequired.bind(this);
this.enable();
}
/**
* Enable.
*/
enable() {
this.scene_.requestRenderMode = true;
this.scene_.maximumRenderTimeChange = 1000;
for (const repaintKey of this.repaintEventNames_) {
this.canvas_.addEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
}
window.addEventListener('resize', this._boundNotifyRepaintRequired, false);
// Listen for changes on the layer group
this.ol3d.getOlMap().getLayerGroup().on('change', this._boundNotifyRepaintRequired);
}
/**
* Disable.
*/
disable() {
for (const repaintKey of this.repaintEventNames_) {
this.canvas_.removeEventListener(repaintKey, this._boundNotifyRepaintRequired, false);
}
window.removeEventListener('resize', this._boundNotifyRepaintRequired, false);
this.ol3d.getOlMap().getLayerGroup().un('change', this._boundNotifyRepaintRequired);
this.scene_.requestRenderMode = false;
}
/**
* Restart render loop.
* Force a restart of the render loop.
*/
restartRenderLoop() {
this.notifyRepaintRequired();
}
notifyRepaintRequired() {
this.scene_.requestRender();
}
}
/***/ }),
/***/ "./src/olcs/Camera.ts":
/*!****************************!*\
!*** ./src/olcs/Camera.ts ***!
\****************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Camera),
/* harmony export */ identityProjection: () => (/* binding */ identityProjection)
/* harmony export */ });
/* harmony import */ var ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ol/Observable.js */ "ol/Observable.js");
/* harmony import */ var ol_Observable_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _math__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./math */ "./src/olcs/math.ts");
/* harmony import */ var ol_proj_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ol/proj.js */ "ol/proj.js");
/* harmony import */ var ol_proj_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ol_proj_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var _core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./core */ "./src/olcs/core.ts");
/**
* @param input Input coordinate array.
* @param opt_output Output array of coordinate values.
* @param opt_dimension Dimension.
* @return Input coordinate array (same array as input).
*/
function identityProjection(input, opt_output, opt_dimension) {
const dim = opt_dimension || input.length;
if (opt_output) {
for (let i = 0; i < dim; ++i) {
opt_output[i] = input[i];
}
}
return input;
}
class Camera {
/**
* This object takes care of additional 3d-specific properties of the view and
* ensures proper synchronization with the underlying raw Cesium.Camera object.
*/
constructor(scene, map) {
this.viewListenKey_ = null;
this.toLonLat_ = identityProjection;
this.fromLonLat_ = identityProjection;
/**
* 0 -- topdown, PI/2 -- the horizon
*/
this.tilt_ = 0;
this.distance_ = 0;
this.lastCameraViewMatrix_ = null;
/**
* This is used to discard change events on view caused by updateView method.
*/
this.viewUpdateInProgress_ = false;
this.scene_ = scene;
this.cam_ = scene.camera;
this.map_ = map;
this.map_.on('change:view', e => {
this.setView_(this.map_.getView());
});
this.setView_(this.map_.getView());
}
destroy() {
(0,ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey)(this.viewListenKey_);
this.viewListenKey_ = null;
}
/**
* @param {?ol.View} view New view to use.
* @private
*/
setView_(view) {
if (this.view_) {
(0,ol_Observable_js__WEBPACK_IMPORTED_MODULE_0__.unByKey)(this.viewListenKey_);
this.viewListenKey_ = null;
}
this.view_ = view;
if (view) {
const toLonLat = (0,ol_proj_js__WEBPACK_IMPORTED_MODULE_2__.getTransform)(view.getProjection(), 'EPSG:4326');
const fromLonLat = (0,ol_proj_js__WEBPACK_IMPORTED_MODULE_2__.getTransform)('EPSG:4326', view.getProjection());
console.assert(toLonLat && fromLonLat);
this.toLonLat_ = toLonLat;
this.fromLonLat_ = fromLonLat;
this.viewListenKey_ = view.on('propertychange', e => this.handleViewChangedEvent_());
this.readFromView();
} else {
this.toLonLat_ = identityProjection;
this.fromLonLat_ = identityProjection;
}
}
handleViewChangedEvent_() {
if (!this.viewUpdateInProgress_) {
this.readFromView();
}
}
/**
* @deprecated
* @param heading In radians.
*/
setHeading(heading) {
if (!this.view_) {
return;
}
this.view_.setRotation(heading);
}
/**
* @deprecated
* @return Heading in radians.
*/
getHeading() {
if (!this.view_) {
return undefined;
}
const rotation = this.view_.getRotation();
return rotation || 0;
}
/**
* @param tilt In radians.
*/
setTilt(tilt) {
this.tilt_ = tilt;
this.updateCamera_();
}
/**
* @return Tilt in radians.
*/
getTilt() {
return this.tilt_;
}
/**
* @param distance In meters.
*/
setDistance(distance) {
this.distance_ = distance;
this.updateCamera_();
this.updateView();
}
/**
* @return Distance in meters.
*/
getDistance() {
return this.distance_;
}
/**
* @deprecated
* Shortcut for ol.View.setCenter().
* @param center Same projection as the ol.View.
*/
setCenter(center) {
if (!this.view_) {
return;
}
this.view_.setCenter(center);
}
/**
* @deprecated
* Shortcut for ol.View.getCenter().
* @return {ol.Coordinate|undefined} Same projection as the ol.View.
* @api
*/
getCenter() {
if (!this.view_) {
return undefined;
}
return this.view_.getCenter();
}
/**
* Sets the position of the camera.
* @param position Same projection as the ol.View.
*/
setPosition(position) {
if (!this.toLonLat_) {
return;
}
const ll = this.toLonLat_(position);
console.assert(ll);
const carto = new Cesium.Cartographic((0,_math__WEBPACK_IMPORTED_MODULE_1__.toRadians)(ll[0]), (0,_math__WEBPACK_IMPORTED_MODULE_1__.toRadians)(ll[1]), this.getAltitude());
this.cam_.setView({
destination: Cesium.Ellipsoid.WGS84.cartographicToCartesian(carto)
});
this.updateView();
}
/**
* Calculates position under the camera.
* @return Coordinates in same projection as the ol.View.
* @api
*/
getPosition() {
if (!this.fromLonLat_) {
return undefined;
}
const carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(this.cam_.position);
const pos = this.fromLonLat_([(0,_math__WEBPACK_IMPORTED_MODULE_1__.toDegrees)(carto.longitude), (0,_math__WEBPACK_IMPORTED_MODULE_1__.toDegrees)(carto.latitude)]);
console.assert(pos);
return pos;
}
/**
* @param altitude In meters.
*/
setAltitude(altitude) {
const carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(this.cam_.position);
carto.height = altitude;
this.cam_.position = Cesium.Ellipsoid.WGS84.cartographicToCartesian(carto);
this.updateView();
}
/**
* @return Altitude in meters.
*/
getAltitude() {
const carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(this.cam_.position);
return carto.height;
}
/**
* Updates the state of the underlying Cesium.Camera
* according to the current values of the properties.
*/
updateCamera_() {
if (!this.view_ || !this.toLonLat_) {
return;
}
const center = this.view_.getCenter();
if (!center) {
return;
}
const ll = this.toLonLat_(center);
console.assert(ll);
const carto = new Cesium.Cartographic((0,_math__WEBPACK_IMPORTED_MODULE_1__.toRadians)(ll[0]), (0,_math__WEBPACK_IMPORTED_MODULE_1__.toRadians)(ll[1]));
if (this.scene_.globe) {
const height = this.scene_.globe.getHeight(carto);
carto.height = height || 0;
}
const destination = Cesium.Ellipsoid.WGS84.cartographicToCartesian(carto);
const orientation = {
pitch: this.tilt_ - Cesium.Math.PI_OVER_TWO,
heading: -this.view_.getRotation(),
roll: undefined
};
this.cam_.setView({
destination,
orientation
});
this.cam_.moveBackward(this.distance_);
this.checkCameraChange(true);
}
/**
* Calculates the values of the properties from the current ol.View state.
*/
readFromView() {
if (!this.view_ || !this.toLonLat_) {
return;
}
const center = this.view_.getCenter();
if (center === undefined || center === null) {
return;
}
const ll = this.toLonLat_(center);
console.assert(ll);
const resolution = this.view_.getResolution();
this.distance_ = this.calcDistanceForResolution(resolution || 0, (0,_math__WEBPACK_IMPORTED_MODULE_1__.toRadians)(ll[1]));
this.updateCamera_();
}
/**
* Calculates the values of the properties from the current Cesium.Camera state.
* Modifies the center, resolution and rotation properties of the view.
*/
updateView() {
if (!this.view_ || !this.fromLonLat_) {
return;
}
this.viewUpdateInProgress_ = true;
// target & distance
const ellipsoid = Cesium.Ellipsoid.WGS84;
const scene = this.scene_;
const target = (0,_core__WEBPACK_IMPORTED_MODULE_3__.pickCenterPoint)(scene);
let bestTarget = target;
if (!bestTarget) {
//TODO: how to handle this properly ?
const globe = scene.globe;
const carto = this.cam_.positionCartographic.clone();
const height = globe.getHeight(carto);
carto.height = height || 0;
bestTarget = Cesium.Ellipsoid.WGS84.cartographicToCartesian(carto);
}
this.distance_ = Cesium.Cartesian3.distance(bestTarget, this.cam_.position);
const bestTargetCartographic = ellipsoid.cartesianToCartographic(bestTarget);
this.view_.setCenter(this.fromLonLat_([(0,_math__WEBPACK_IMPORTED_MODULE_1__.toDegrees)(bestTargetCartographic.longitude), (0,_math__WEBPACK_IMPORTED_MODULE_1__.toDegrees)(bestTargetCartographic.latitude)]));
// resolution
this.view_.setResolution(this.calcResolutionForDistance(this.distance_, bestTargetCartographic ? bestTargetCartographic.latitude : 0));
/*
* Since we are positioning the target, the values of heading and tilt
* need to be calculated _at the target_.
*/
if (target) {
const pos = this.cam_.position;
// normal to the ellipsoid at the target
const targetNormal = new Cesium.Cartesian3();
ellipsoid.geocentricSurfaceNormal(target, targetNormal);
// vector from the target to the camera
const targetToCamera = new Cesium.Cartesian3();
Cesium.Cartesian3.subtract(pos, target, targetToCamera);
Cesium.Cartesian3.normalize(targetToCamera, targetToCamera);
// HEADING
const up = this.cam_.up;
const right = this.cam_.right;
const normal = new Cesium.Cartesian3(-target.y, target.x, 0); // what is it?
const heading = Cesium.Cartesian3.angleBetween(right, normal);
const cross = Cesium.Cartesian3.cross(target, up, new Cesium.Cartesian3());
const orientation = cross.z;
this.view_.setRotation(orientation < 0 ? heading : -heading);
// TILT
const tiltAngle = Math.acos(Cesium.Cartesian3.dot(targetNormal, targetToCamera));
this.tilt_ = isNaN(tiltAngle) ? 0 : tiltAngle;
} else {
// fallback when there is no target
this.view_.setRotation(this.cam_.heading);
this.tilt_ = -this.cam_.pitch + Math.PI / 2;
}
this.viewUpdateInProgress_ = false;
}
/**
* Check if the underlying camera state has changed and ensure synchronization.
* @param opt_dontSync Do not synchronize the view.
*/
checkCameraChange(opt_dontSync) {
const old = this.lastCameraViewMatrix_;
const current = this.cam_.viewMatrix;
if (!old || !Cesium.Matrix4.equalsEpsilon(old, current, 1e-7)) {
this.lastCameraViewMatrix_ = current.clone();
if (opt_dontSync !== true) {
this.updateView();
}
}
}
/**
* calculate the distance between camera and centerpoint based on the resolution and latitude value
* @param resolution Number of map units per pixel.
* @param latitude Latitude in radians.
* @return The calculated distance.
*/
calcDistanceForResolution(resolution, latitude) {
return (0,_core__WEBPACK_IMPORTED_MODULE_3__.calcDistanceForResolution)(resolution, latitude, this.scene_, this.view_.getProjection());
}
/**
* calculate the resolution based on a distance(camera to position) and latitude value
* @param distance
* @param latitude
* @return} The calculated resolution.
*/
calcResolutionForDistance(distance, latitude) {
return (0,_core__WEBPACK_IMPORTED_MODULE_3__.calcResolutionForDistance)(distance, latitude, this.scene_, this.view_.getProjection());
}
}
/***/ }),
/***/ "./src/olcs/FeatureConverter.ts":
/*!**************************************!*\
!*** ./src/olcs/FeatureConverter.ts ***!
\**************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ FeatureConverter)
/* harmony export */ });
/* harmony import */ var ol_style_Icon_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ol/style/Icon.js */ "ol/style/Icon.js");
/* harmony import */ var ol_style_Icon_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(ol_style_Icon_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var ol_source_Vector_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ol/source/Vector.js */ "ol/source/Vector.js");
/* harmony import */ var ol_source_Vector_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ol_source_Vector_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var ol_source_Cluster_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ol/source/Cluster.js */ "ol/source/Cluster.js");
/* harmony import */ var ol_source_Cluster_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ol_source_Cluster_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var ol_geom_Polygon_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ol/geom/Polygon.js */ "ol/geom/Polygon.js");
/* harmony import */ var ol_geom_Polygon_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(ol_geom_Polygon_js__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var ol_extent_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ol/extent.js */ "ol/extent.js");
/* harmony import */ var ol_extent_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(ol_extent_js__WEBPACK_IMPORTED_MODULE_4__);
/* harmony import */ var ol_geom_SimpleGeometry_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ol/geom/SimpleGeometry.js */ "ol/geom/SimpleGeometry.js");
/* harmony import */ var ol_geom_SimpleGeometry_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(ol_geom_SimpleGeometry_js__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var _core__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./core */ "./src/olcs/core.ts");
/* harmony import */ var _core_VectorLayerCounterpart__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./core/VectorLayerCounterpart */ "./src/olcs/core/VectorLayerCounterpart.ts");
/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./util */ "./src/olcs/util.ts");
/* harmony import */ var ol_geom_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ol/geom.js */ "ol/geom.js");
/* harmony import */ var ol_geom_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(ol_geom_js__WEBPACK_IMPORTED_MODULE_9__);
class FeatureConverter {
/**
* Concrete base class for converting from OpenLayers3 vectors to Cesium
* primitives.
* Extending this class is possible provided that the extending class and
* the library are compiled together by the closure compiler.
* @param scene Cesium scene.
* @api
*/
constructor(scene) {
/**
* Bind once to have a unique function for using as a listener
*/
this.boundOnRemoveOrClearFeatureListener_ = this.onRemoveOrClearFeature_.bind(this);
this.defaultBillboardEyeOffset_ = new Cesium.Cartesian3(0, 0, 10);
this.scene = scene;
this.scene = scene;
}
/**
* @param evt
*/
onRemoveOrClearFeature_(evt) {
const source = evt.target;
console.assert(source instanceof (ol_source_Vector_js__WEBPACK_IMPORTED_MODULE_1___default()));
const cancellers = source['olcs_cancellers'];
if (cancellers) {
const feature = evt.feature;
if (feature) {
// remove
const id = (0,_util__WEBPACK_IMPORTED_MODULE_8__.getUid)(feature);
const canceller = cancellers[id];
if (canceller) {
canceller();
delete cancellers[id];
}
} else {
// clear
for (const key in cancellers) {
if (cancellers.hasOwnProperty(key)) {
cancellers[key]();
}
}
source['olcs_cancellers'] = {};
}
}
}
/**
* @param layer
* @param feature OpenLayers feature.
* @param primitive
*/
setReferenceForPicking(layer, feature, primitive) {
primitive.olLayer = layer;
primitive.olFeature = feature;
}
/**
* Basics primitive creation using a color attribute.
* Note that Cesium has 'interior' and outline geometries.
* @param layer
* @param feature OpenLayers feature.
* @param olGeometry OpenLayers geometry.
* @param geometry
* @param color
* @param opt_lineWidth
* @return primitive
*/
createColoredPrimitive(layer, feature, olGeometry, geometry, color, opt_lineWidth) {
const createInstance = function (geometry, color) {
const instance = new Cesium.GeometryInstance({
geometry
});
if (color && !(color instanceof Cesium.ImageMaterialProperty)) {
instance.attributes = {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(color)
};
}
return instance;
};
const options = {
flat: true,
// work with all geometries
renderState: {
depthTest: {
enabled: true
}
}
};
if (opt_lineWidth !== undefined) {
options.renderState.lineWidth = opt_lineWidth;
}
const instances = createInstance(geometry, color);
const heightReference = this.getHeightReference(layer, feature, olGeometry);
let primitive;
if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) {
if (!('createShadowVolume' in instances.geometry.constructor)) {
// This is not a ground geometry
return null;
}
primitive = new Cesium.GroundPrimitive({
geometryInstances: instances
});
} else {
primitive = new Cesium.Primitive({
geometryInstances: instances
});
}
if (color instanceof Cesium.ImageMaterialProperty) {
// FIXME: we created stylings which are not time related
// What should we pass here?
// @ts-ignore
const dataUri = color.image.getValue().toDataURL();
primitive.appearance = new Cesium.MaterialAppearance({
flat: true,
renderState: {
depthTest: {
enabled: true
}
},
material: new Cesium.Material({
fabric: {
type: 'Image',
uniforms: {
image: dataUri
}
}
})
});
} else {
primitive.appearance = new Cesium.MaterialAppearance({
...options,
material: new Cesium.Material({
translucent: color.alpha !== 1,
fabric: {
type: 'Color',
uniforms: {
color
}
}
})
});
if (primitive instanceof Cesium.Primitive && (feature.get('olcs_shadows') || layer.get('olcs_shadows'))) {
primitive.shadows = 1;
}
}
this.setReferenceForPicking(layer, feature, primitive);
return primitive;
}
/**
* Return the fill or stroke color from a plain ol style.
* @param style
* @param outline
* @return {!CSColor}
*/
extractColorFromOlStyle(style, outline) {
const fillColor = style.getFill() ? style.getFill().getColor() : null;
const strokeColor = style.getStroke() ? style.getStroke().getColor() : null;
let olColor = 'black';
if (strokeColor && outline) {
olColor = strokeColor;
} else if (fillColor) {
olColor = fillColor;
}
return (0,_core__WEBPACK_IMPORTED_MODULE_6__.convertColorToCesium)(olColor);
}
/**
* Return the width of stroke from a plain ol style.
* @param style
* @return {number}
*/
extractLineWidthFromOlStyle(style) {
// Handling of line width WebGL limitations is handled by Cesium.
const width = style.getStroke() ? style.getStroke().getWidth() : undefined;
return width !== undefined ? width : 1;
}
/**
* Create a primitive collection out of two Cesium geometries.
* Only the OpenLayers style colors will be used.
*/
wrapFillAndOutlineGeometries(layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle) {
const fillColor = this.extractColorFromOlStyle(olStyle, false);
const outlineColor = this.extractColorFromOlStyle(olStyle, true);
const primitives = new Cesium.PrimitiveCollection();
if (olStyle.getFill()) {
const p1 = this.createColoredPrimitive(layer, feature, olGeometry, fillGeometry, fillColor);
console.assert(!!p1);
primitives.add(p1);
}
if (olStyle.getStroke() && outlineGeometry) {
const width = this.extractLineWidthFromOlStyle(olStyle);
const p2 = this.createColoredPrimitive(layer, feature, olGeometry, outlineGeometry, outlineColor, width);
if (p2) {
// Some outline geometries are not supported by Cesium in clamp to ground
// mode. These primitives are skipped.
primitives.add(p2);
}
}
return primitives;
}
// Geometry converters
// FIXME: would make more sense to only accept primitive collection.
/**
* Create a Cesium primitive if style has a text component.
* Eventually return a PrimitiveCollection including current primitive.
*/
addTextStyle(layer, feature, geometry, style, primitive) {
let primitives;
if (!(primitive instanceof Cesium.PrimitiveCollection)) {
primitives = new Cesium.PrimitiveCollection();
primitives.add(primitive);
} else {
primitives = primitive;
}
if (!style.getText()) {
return primitives;
}
const text = /** @type {!ol.style.Text} */style.getText();
const label = this.olGeometry4326TextPartToCesium(layer, feature, geometry, text);
if (label) {
primitives.add(label);
}
return primitives;
}
/**
* Add a billboard to a Cesium.BillboardCollection.
* Overriding this wrapper allows manipulating the billboard options.
* @param billboards
* @param bbOptions
* @param layer
* @param feature OpenLayers feature.
* @param geometry
* @param style
* @return newly created billboard
* @api
*/
csAddBillboard(billboards, bbOptions, layer, feature, geometry, style) {
if (!bbOptions.eyeOffset) {
bbOptions.eyeOffset = this.defaultBillboardEyeOffset_;
}
const bb = billboards.add(bbOptions);
this.setReferenceForPicking(layer, feature, bb);
return bb;
}
/**
* Convert an OpenLayers circle geometry to Cesium.
* @api
*/
olCircleGeometryToCesium(layer, feature, olGeometry, projection, olStyle) {
olGeometry = (0,_core__WEBPACK_IMPORTED_MODULE_6__.olGeometryCloneTo4326)(olGeometry, projection);
console.assert(olGeometry.getType() == 'Circle');
// ol.Coordinate
const olCenter = olGeometry.getCenter();
const height = olCenter.length == 3 ? olCenter[2] : 0.0;
const olPoint = olCenter.slice();
olPoint[0] += olGeometry.getRadius();
// Cesium
const center = (0,_core__WEBPACK_IMPORTED_MODULE_6__.ol4326CoordinateToCesiumCartesian)(olCenter);
const point = (0,_core__WEBPACK_IMPORTED_MODULE_6__.ol4326CoordinateToCesiumCartesian)(olPoint);
// Accurate computation of straight distance
const radius = Cesium.Cartesian3.distance(center, point);
const fillGeometry = new Cesium.CircleGeometry({
center,
radius,
height
});
let outlinePrimitive;
let outlineGeometry;
if (this.getHeightReference(layer, feature, olGeometry) === Cesium.HeightReference.CLAMP_TO_GROUND) {
const width = this.extractLineWidthFromOlStyle(olStyle);
if (width) {
const circlePolygon = (0,ol_geom_Polygon_js__WEBPACK_IMPORTED_MODULE_3__.circular)(olGeometry.getCenter(), radius);
const positions = (0,_core__WEBPACK_IMPORTED_MODULE_6__.ol4326CoordinateArrayToCsCartesians)(circlePolygon.getLinearRing(0).getCoordinates());
const op = outlinePrimitive = new Cesium.GroundPolylinePrimitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.GroundPolylineGeometry({
positions,
width
})
}),
appearance: new Cesium.PolylineMaterialAppearance({
material: this.olStyleToCesium(feature, olStyle, true)
}),
classificationType: Cesium.ClassificationType.TERRAIN
});
(0,_util__WEBPACK_IMPORTED_MODULE_8__.waitReady)(outlinePrimitive).then(() => {
this.setReferenceForPicking(layer, feature, op._primitive);
});
}
} else {
outlineGeometry = new Cesium.CircleOutlineGeometry({
center,
radius,
extrudedHeight: height,
height
});
}
const primitives = this.wrapFillAndOutlineGeometries(layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle);
if (outlinePrimitive) {
primitives.add(outlinePrimitive);
}
return this.addTextStyle(layer, feature, olGeometry, olStyle, primitives);
}
/**
* Convert an OpenLayers line string geometry to Cesium.
* @api
*/
olLineStringGeometryToCesium(layer, feature, olGeometry, projection, olStyle) {
olGeometry = (0,_core__WEBPACK_IMPORTED_MODULE_6__.olGeometryCloneTo4326)(olGeometry, projection);
console.assert(olGeometry.getType() == 'LineString');
const positions = (0,_core__WEBPACK_IMPORTED_MODULE_6__.ol4326CoordinateArrayToCsCartesians)(olGeometry.getCoordinates());
const width = this.extractLineWidthFromOlStyle(olStyle);
let outlinePrimitive;
const heightReference = this.getHeightReference(layer, feature, olGeometry);
const appearance = new Cesium.PolylineMaterialAppearance({
material: this.olStyleToCesium(feature, olStyle, true)
});
if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) {
const geometry = new Cesium.GroundPolylineGeometry({
positions,
width
});
const op = outlinePrimitive = new Cesium.GroundPolylinePrimitive({
appearance,
geometryInstances: new Cesium.GeometryInstance({
geometry
})
});
(0,_util__WEBPACK_IMPORTED_MODULE_8__.waitReady)(outlinePrimitive).then(() => {
this.setReferenceForPicking(layer, feature, op._primitive);
});
} else {
const geometry = new Cesium.PolylineGeometry({
positions,
width,
vertexFormat: appearance.vertexFormat
});
outlinePrimitive = new Cesium.Primitive({
appearance,
geometryInstances: new Cesium.GeometryInstance({
geometry
})
});
}
this.setReferenceForPicking(layer, feature, outlinePrimitive);
return this.addTextStyle(layer, feature, olGeometry, olStyle, outlinePrimitive);
}
/**
* Convert an OpenLayers polygon geometry to Cesium.
* @api
*/
olPolygonGeometryToCesium(layer, feature, olGeometry, projection, olStyle) {
olGeometry = (0,_core__WEBPACK_IMPORTED_MODULE_6__.olGeometryCloneTo4326)(olGeometry, projection);
console.assert(olGeometry.getType() == 'Polygon');
const heightReference = this.getHeightReference(layer, feature, olGeometry);
let fillGeometry, outlineGeometry;
let outlinePrimitive;
if (olGeometry.getCoordinates()[0].length == 5 && feature.get('olcs.polygon_kind') === 'rectangle') {
// Create a rectangle according to the longitude and latitude curves
const coordinates = olGeometry.getCoordinates()[0];
// Extract the West, South, East, North coordinates
const extent = (0,ol_extent_js__WEBPACK_IMPORTED_MODULE_4__.boundingExtent)(coordinates);
const rectangle = Cesium.Rectangle.fromDegrees(extent[0], extent[1], extent[2], extent[3]);
// Extract the average height of the vertices
let maxHeight = 0.0;
if (coordinates[0].length == 3) {
for (let c = 0; c < coordinates.length; c++) {
maxHeight = Math.max(maxHeight, coordinates[c][2]);
}
}
const featureExtrudedHeight = feature.get('olcs_extruded_height');
// Render the cartographic rectangle
fillGeometry = new Cesium.RectangleGeometry({
ellipsoid: Cesium.Ellipsoid.WGS84,
rectangle,
height: maxHeight,
extrudedHeight: featureExtrudedHeight
});
outlineGeometry = new Cesium.RectangleOutlineGeometry({
ellipsoid: Cesium.Ellipsoid.WGS84,
rectangle,
height: maxHeight,
extrudedHeight: featureExtrudedHeight
});
} else {
const rings = olGeometry.getLinearRings();
const hierarchy = {
positions: [],
holes: []
};
const polygonHierarchy = hierarchy;
console.assert(rings.length > 0);
for (let i = 0; i < rings.length; ++i) {
const olPos = rings[i].getCoordinates();
const positions = (0,_core__WEBPACK_IMPORTED_MODULE_6__.ol4326CoordinateArrayToCsCartesians)(olPos);
console.assert(positions && positions.length > 0);
if (i === 0) {
hierarchy.positions = positions;
} else {
hierarchy.holes.push({
positions,
holes: []
});
}
}
const featureExtrudedHeight = feature.get('olcs_extruded_height');
fillGeometry = new Cesium.PolygonGeometry({
polygonHierarchy,
perPositionHeight: true,
extrudedHeight: featureExtrudedHeight
});
// Since Cesium doesn't yet support Polygon outlines on terrain yet (coming soon...?)
// we don't create an outline geometry if clamped, but instead do the polyline method
// for each ring. Most of this code should be removeable when Cesium adds
// support for Polygon outlines on terrain.
if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) {
const width = this.extractLineWidthFromOlStyle(olStyle);
if (width > 0) {
const positions = [hierarchy.positions];
if (hierarchy.holes) {
for (let i = 0; i < hierarchy.holes.length; ++i) {
positions.push(hierarchy.holes[i].positions);
}
}
const appearance = new Cesium.PolylineMaterialAppearance({
material: this.olStyleToCesium(feature, olStyle, true)
});
const geometryInstances = [];
for (const linePositions of positions) {
const polylineGeometry = new Cesium.GroundPolylineGeometry({
positions: linePositions,
width
});
geometryInstances.push(new Cesium.GeometryInstance({
geometry: polylineGeometry
}));
}
outlinePrimitive = new Cesium.GroundPolylinePrimitive({
appearance,
geometryInstances
});
(0,_util__WEBPACK_IMPORTED_MODULE_8__.waitReady)(outlinePrimitive).then(() => {
this.setReferenceForPicking(layer, feature, outlinePrimitive._primitive);
});
}
} else {
// Actually do the normal polygon thing. This should end the removable
// section of code described above.
outlineGeometry = new Cesium.PolygonOutlineGeometry({
polygonHierarchy: hierarchy,
perPositionHeight: true,
extrudedHeight: featureExtrudedHeight
});
}
}
const primitives = this.wrapFillAndOutlineGeometries(layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle);
if (outlinePrimitive) {
primitives.add(outlinePrimitive);
}
return this.addTextStyle(layer, feature, olGeometry, olStyle, primitives);
}
/**
* @api
*/
getHeightReference(layer, feature, geometry) {
// Read from the geometry
let altitudeMode = geometry.get('altitudeMode');
// Or from the feature
if (altitudeMode === undefined) {
altitudeMode = feature.get('altitudeMode');
}
// Or from the layer
if (altitudeMode === undefined) {
altitudeMode = layer.get('altitudeMode');
}
let heightReference = Cesium.HeightReference.NONE;
if (altitudeMode === 'clampToGround') {
heightReference = Cesium.HeightReference.CLAMP_TO_GROUND;
} else if (altitudeMode === 'relativeToGround') {
heightReference = Cesium.HeightReference.RELATIVE_TO_GROUND;
}
return heightReference;
}
/**
* Convert a point geometry to a Cesium BillboardCollection.
* @param {ol.layer.Vector|ol.layer.Image} layer
* @param {!ol.Feature} feature OpenLayers feature..
* @param {!ol.geom.Point} olGeometry OpenLayers point geometry.
* @param {!ol.ProjectionLike} projection
* @param {!ol.style.Style} style
* @param {!ol.style.Image} imageStyle
* @param {!Cesium.BillboardCollection} billboards
* @param {function(!Cesium.Billboard)=} opt_newBillboardCallback Called when the new billboard is added.
* @api
*/
createBillboardFromImage(layer, feature, olGeometry, projection, style, imageStyle, billboards, opt_newBillboardCallback) {
if (imageStyle instanceof (ol_style_Icon_js__WEBPACK_IMPORTED_MODULE_0___default())) {
// make sure the image is scheduled for load
imageStyle.load();
}
const image = imageStyle.getImage(1); // get normal density
const isImageLoaded = function (image) {
return image.src != '' && image.naturalHeight != 0 && image.naturalWidth != 0 && image.complete;
};
const reallyCreateBillboard = function () {
if (!image) {
return;
}
if (!(image instanceof HTMLCanvasElement || image instanceof Image || image instanceof HTMLImageElement)) {
return;
}
const center = olGeometry.getCoordinates();
const position = (0,_core__WEBPACK_IMPORTED_MODULE_6__.ol4326CoordinateToCesiumCartesian)(center);
let color;
const opacity = imageStyle.getOpacity();
if (opacity !== undefined) {
color = new Cesium.Color(1.0, 1.0, 1.0, opacity);
}
const scale = imageStyle.getScale();
const heightReference = this.getHeightReference(layer, feature, olGeometry);
const bbOptions = {
image,
color,
scale,
heightReference,
position
};
// merge in cesium options from openlayers feature
Object.assign(bbOptions, feature.get('cesiumOptions'));
if (imageStyle instanceof (ol_style_Icon_js__WEBPACK_IMPORTED_MODULE_0___default())) {
const anchor = imageStyle.getAnchor();
if (anchor) {
const xScale = Array.isArray(scale) ? scale[0] : scale;
const yScale = Array.isArray(scale) ? scale[1] : scale;
bbOptions.pixelOffset = new Cesium.Cartesian2((image.width / 2 - anchor[0]) * xScale, (image.height / 2 - anchor[1]) * yScale);
}
}
const bb = this.csAddBillboard(billboards, bbOptions, layer, feature, olGeometry, style);
if (opt_newBillboardCallback) {
opt_newBillboardCallback(bb);
}
}.bind(this);
if (image instanceof Image && !isImageLoaded(image)) {
// Cesium requires the image to be loaded
let cancelled = false;
const source = layer.getSource();
const canceller = function () {
cancelled = true;
};
source.on(['removefeature', 'clear'], this.boundOnRemoveOrClearFeatureListener_);
let cancellers = source['olcs_cancellers'];
if (!cancellers) {
cancellers = source['olcs_cancellers'] = {};
}
const fuid = (0,_util__WEBPACK_IMPORTED_MODULE_8__.getUid)(feature);
if (cancellers[fuid]) {
// When the feature change quickly, a canceller may still be present so
// we cancel it here to prevent creation of a billboard.
cancellers[fuid]();
}
cancellers[fuid] = canceller;
const listener = function () {
image.removeEventListener('load', listener);
if (!billboards.isDestroyed() && !cancelled) {
// Create billboard if the feature is still displayed on the map.
reallyCreateBillboard();
}
};
image.addEventListener('load', listener);
} else {
reallyCreateBillboard();
}
}
/**
* Convert a point geometry to a Cesium BillboardCollection.
* @param layer
* @param feature OpenLayers feature..
* @param olGeometry OpenLayers point geometry.
* @param projection
* @param style
* @param billboards
* @param opt_newBillboardCallback Called when the new billboard is added.
* @return primitives
* @api
*/
olPointGeometryToCesium(layer, feature, olGeometry, projection, style, billboards, opt_newBillboardCallback) {
console.assert(olGeometry.getType() == 'Point');
olGeometry = (0,_core__WEBPACK_IMPORTED_MODULE_6__.olGeometryCloneTo4326)(olGeometry, projection);
let modelPrimitive = null;
const imageStyle = style.getImage();
if (imageStyle) {
const olcsModelFunction = olGeometry.get('olcs_model') || feature.get('olcs_model');
if (olcsModelFunction) {
modelPrimitive = new Cesium.PrimitiveCollection();
const olcsModel = olcsModelFunction();
const options = Object.assign({}, {
scene: this.scene
}, olcsModel.cesiumOptions);
if ('fromGltf' in Cesium.Model) {
// pre Cesium v107
// @ts-ignore
const model = Cesium.Model.fromGltf(options);
modelPrimitive.add(model);
} else {
Cesium.Model.fromGltfAsync(options).then(model => {
modelPrimitive.add(model);
});
}
if (olcsModel.debugModelMatrix) {
modelPrimitive.add(new Cesium.DebugModelMatrixPrimitive({
modelMatrix: olcsModel.debugModelMatrix
}));
}
} else {
this.createBillboardFromImage(layer, feature, olGeometry, projection, style, imageStyle, billboards, opt_newBillboardCallback);
}
}
if (style.getText()) {
return this.addTextStyle(layer, feature, olGeometry, style, modelPrimitive || new Cesium.Primitive());
} else {
return modelPrimitive;
}
}
/**
* Convert an OpenLayers multi-something geometry to Cesium.
* @param {ol.layer.Vector|ol.layer.Image} layer
* @param {!ol.Feature} feature OpenLayers feature..
* @param {!ol.geom.Geometry} geometry OpenLayers geometry.
* @param {!ol.ProjectionLike} projection
* @param {!ol.style.Style} olStyle
* @param {!Cesium.BillboardCollection} billboards
* @param {function(!Cesium.Billboard)=} opt_newBillboardCallback Called when
* the new billboard is added.
* @return {Cesium.Primitive} primitives
* @api