ol-cesium
Version:
OpenLayers Cesium integration library
401 lines (340 loc) • 9.83 kB
JavaScript
/**
* @module olcs.contrib.Manager
*/
import olcsContribLazyLoader from '../contrib/LazyLoader.js';
import OLCesium from '../OLCesium.js';
import olcsCore from '../core.js';
import {toRadians} from '../math.js';
import olObservable from 'ol/Observable.js';
/**
* @typedef {Object} ManagerOptions
* @property {import('ol/Map.js').default} map
* @property {import('ol/extent.js').Extent} [cameraExtentInRadians]
*/
const Manager = class extends olObservable {
/**
* @param {string} cesiumUrl
* @param {olcsx.contrib.ManagerOptions} options
* @api
*/
constructor(cesiumUrl, {map, cameraExtentInRadians} = {}) {
super();
/**
* @type {string}
* @private
*/
this.cesiumUrl_ = cesiumUrl;
/**
* @type {ol.Map}
* @protected
*/
this.map = map;
/**
* @type {ol.Extent}
* @protected
*/
this.cameraExtentInRadians = cameraExtentInRadians || null;
/**
* @private
* @type {Cesium.BoundingSphere}
*/
this.boundingSphere_;
/**
* @type {boolean}
* @private
*/
this.blockLimiter_ = false;
/**
* @type {Promise.<olcs.OLCesium>}
* @private
*/
this.promise_;
/**
* @type {olcs.OLCesium}
* @protected
*/
this.ol3d;
/**
* @const {number} Tilt angle in radians
* @private
*/
this.cesiumInitialTilt_ = toRadians(50);
/**
* @protected
* @type {number}
*/
this.fogDensity = 0.0001;
/**
* @protected
* @type {number}
*/
this.fogSSEFactor = 25;
/**
* Limit the minimum distance to the terrain to 2m.
* @protected
* @type {number}
*/
this.minimumZoomDistance = 2;
/**
* Limit the maximum distance to the earth to 10'000km.
* @protected
* @type {number}
*/
this.maximumZoomDistance = 10000000;
// when closer to 3000m, restrict the available positions harder
/**
* @protected
* @param {number} height
*/
this.limitCameraToBoundingSphereRatio = height => (height > 3000 ? 9 : 3);
}
/**
* @return {Promise.<olcs.OLCesium>}
*/
load() {
if (!this.promise_) {
const cesiumLazyLoader = new olcsContribLazyLoader(this.cesiumUrl_);
this.promise_ = cesiumLazyLoader.load().then(() => this.onCesiumLoaded());
}
return this.promise_;
}
/**
* @protected
* @return {olcs.OLCesium}
*/
onCesiumLoaded() {
if (this.cameraExtentInRadians) {
const rect = new Cesium.Rectangle(...this.cameraExtentInRadians);
// Set the fly home rectangle
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = rect;
this.boundingSphere_ = Cesium.BoundingSphere.fromRectangle3D(rect, Cesium.Ellipsoid.WGS84, 300); // lux mean height is 300m
}
this.ol3d = this.instantiateOLCesium();
const scene = this.ol3d.getCesiumScene();
this.configureForUsability(scene);
this.configureForPerformance(scene);
this.dispatchEvent('load');
return this.ol3d;
}
/**
* Application code should override this method.
* @return {olcs.OLCesium}
*/
instantiateOLCesium() {
console.assert(this.map);
const ol3d = new OLCesium({map: this.map});
const scene = ol3d.getCesiumScene();
const terrainProvider = Cesium.createWorldTerrain();
scene.terrainProvider = terrainProvider;
return ol3d;
}
/**
* @param {!Cesium.Scene} scene The scene, passed as parameter for convenience.
* @protected
*/
configureForPerformance(scene) {
const fog = scene.fog;
fog.enabled = true;
fog.density = this.fogDensity;
fog.screenSpaceErrorFactor = this.fogSSEFactor;
}
/**
* @param {!Cesium.Scene} scene The scene, passed as parameter for convenience.
* @protected
*/
configureForUsability(scene) {
const sscController = scene.screenSpaceCameraController;
sscController.minimumZoomDistance = this.minimumZoomDistance;
sscController.maximumZoomDistance = this.maximumZoomDistance;
// Do not see through the terrain. Seeing through the terrain does not make
// sense anyway, except for debugging
scene.globe.depthTestAgainstTerrain = true;
// Use white instead of the black default colour for the globe when tiles are missing
scene.globe.baseColor = Cesium.Color.WHITE;
scene.backgroundColor = Cesium.Color.WHITE;
if (this.boundingSphere_) {
scene.postRender.addEventListener(this.limitCameraToBoundingSphere.bind(this), scene);
}
// Stop rendering Cesium when there is nothing to do. This drastically reduces CPU/GPU consumption.
this.ol3d.enableAutoRenderLoop();
}
/**
* Constrain the camera so that it stays close to the bounding sphere of the map extent.
* Near the ground the allowed distance is shorter.
* @protected
*/
limitCameraToBoundingSphere() {
if (this.boundingSphere_ && !this.blockLimiter_) {
const scene = this.ol3d.getCesiumScene();
const camera = scene.camera;
const position = camera.position;
const carto = Cesium.Cartographic.fromCartesian(position);
const ratio = this.limitCameraToBoundingSphereRatio(carto.height);
if (Cesium.Cartesian3.distance(this.boundingSphere_.center, position) > this.boundingSphere_.radius * ratio) {
const currentlyFlying = camera.flying;
if (currentlyFlying === true) {
// There is a flying property and its value is true
return;
} else {
this.blockLimiter_ = true;
const unblockLimiter = () => this.blockLimiter_ = false;
camera.flyToBoundingSphere(this.boundingSphere_, {
complete: unblockLimiter,
cancel: unblockLimiter
});
}
}
}
}
/**
* Enable or disable ol3d with a default animation.
* @export
* @return {Promise<undefined>}
*/
toggle3d() {
return this.load().then((/** @const {!olcs.OLCesium} */ ol3d) => {
const is3DCurrentlyEnabled = ol3d.getEnabled();
const scene = ol3d.getCesiumScene();
if (is3DCurrentlyEnabled) {
// Disable 3D
console.assert(this.map);
return olcsCore.resetToNorthZenith(this.map, scene).then(() => {
ol3d.setEnabled(false);
this.dispatchEvent('toggle');
});
} else {
// Enable 3D
ol3d.setEnabled(true);
this.dispatchEvent('toggle');
return olcsCore.rotateAroundBottomCenter(scene, this.cesiumInitialTilt_);
}
});
}
/**
* Enable ol3d with a view built from parameters.
*
* @export
* @param {number} lon
* @param {number} lat
* @param {number} elevation
* @param {number} headingDeg Heading value in degrees.
* @param {number} pitchDeg Pitch value in degrees.
* @returns {Promise<undefined>}
*/
set3dWithView(lon, lat, elevation, headingDeg, pitchDeg) {
return this.load().then((/** @const {!olcs.OLCesium} */ ol3d) => {
const is3DCurrentlyEnabled = ol3d.getEnabled();
const scene = ol3d.getCesiumScene();
const camera = scene.camera;
const destination = Cesium.Cartesian3.fromDegrees(lon, lat, elevation);
const heading = Cesium.Math.toRadians(headingDeg);
const pitch = Cesium.Math.toRadians(pitchDeg);
const roll = 0;
const orientation = {heading, pitch, roll};
if (!is3DCurrentlyEnabled) {
ol3d.setEnabled(true);
this.dispatchEvent('toggle');
}
camera.setView({
destination,
orientation
});
});
}
/**
* @export
* @return {boolean}
*/
is3dEnabled() {
return !!this.ol3d && this.ol3d.getEnabled();
}
/**
* @return {number}
*/
getHeading() {
return this.map ? this.map.getView().getRotation() || 0 : 0;
}
/**
* @return {number|undefined}
*/
getTiltOnGlobe() {
const scene = this.ol3d.getCesiumScene();
const tiltOnGlobe = olcsCore.computeSignedTiltAngleOnGlobe(scene);
return -tiltOnGlobe;
}
/**
* @param {number} angle
*/
setHeading(angle) {
const scene = this.ol3d.getCesiumScene();
const bottom = olcsCore.pickBottomPoint(scene);
if (bottom) {
olcsCore.setHeadingUsingBottomCenter(scene, angle, bottom);
}
}
/**
* @export
* @return {olcs.OLCesium}
*/
getOl3d() {
return this.ol3d;
}
/**
* @export
* @return {!ol.View}
*/
getOlView() {
const view = this.map.getView();
console.assert(view);
return view;
}
/**
* @export
* @return {Cesium.Matrix4}
*/
getCesiumViewMatrix() {
return this.ol3d.getCesiumScene().camera.viewMatrix;
}
/**
* @export
* @return {!Cesium.Scene}
*/
getCesiumScene() {
return this.ol3d.getCesiumScene();
}
/**
* @export
* @param {!Cesium.Rectangle} rectangle
* @param {number=} offset in meters
* @return {Promise<undefined>}
*/
flyToRectangle(rectangle, offset = 0) {
const camera = this.getCesiumScene().camera;
const destination = camera.getRectangleCameraCoordinates(rectangle);
const mag = Cesium.Cartesian3.magnitude(destination) + offset;
Cesium.Cartesian3.normalize(destination, destination);
Cesium.Cartesian3.multiplyByScalar(destination, mag, destination);
return new Promise((resolve, reject) => {
if (!this.cameraExtentInRadians) {
reject();
return;
}
camera.flyTo({
destination,
complete: () => resolve(),
cancel: () => reject(),
endTransform: Cesium.Matrix4.IDENTITY
});
});
}
/**
* @protected
* @return {Cesium.Rectangle|undefined}
*/
getCameraExtentRectangle() {
if (this.cameraExtentInRadians) {
return new Cesium.Rectangle(...this.cameraExtentInRadians);
}
}
};
export default Manager;