UNPKG

ol-cesium

Version:

OpenLayers Cesium integration library

1,400 lines (1,304 loc) 209 kB
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