terriajs
Version:
Geospatial data visualization platform.
272 lines • 10.6 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { isEqual } from "lodash-es";
import { action, computed, observable, reaction, runInAction, untracked, makeObservable } from "mobx";
import { fromPromise, FULFILLED } from "mobx-utils";
import CesiumEvent from "terriajs-cesium/Source/Core/Event";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";
import CatalogMemberMixin from "../ModelMixins/CatalogMemberMixin";
import CameraView from "../Models/CameraView";
import NoViewer from "../Models/NoViewer";
import ViewerMode, { getViewerType } from "../Models/ViewerMode";
// Async loading of Leaflet and Cesium
const leafletFromPromise = computed(() => fromPromise(import("../Models/Leaflet").then((Leaflet) => Leaflet.default)), { keepAlive: true });
const cesiumFromPromise = computed(() => fromPromise(import("../Models/Cesium").then((Cesium) => Cesium.default)), { keepAlive: true });
const viewerOptionsDefaults = {
useTerrain: true
};
/**
* A class that deals with initialising, destroying and switching between viewers
* Each map-view should have it's own TerriaViewer (main viewer, preview map, etc.)
*/
export default class TerriaViewer {
terria;
_baseMap;
/**
* Tracks the basemap that is currently being loaded
*/
_loadingBaseMap;
get baseMap() {
return this._baseMap;
}
/**
* Returns the basemap that is currently loading
*/
get loadingBaseMap() {
return this._loadingBaseMap;
}
async setBaseMap(baseMap) {
if (!baseMap)
return;
runInAction(() => {
this._loadingBaseMap = baseMap;
});
try {
const result = await baseMap.loadMapItems();
if (result.error) {
result.raiseError(this.terria, {
title: {
key: "models.terria.loadingBaseMapErrorTitle",
parameters: {
name: (CatalogMemberMixin.isMixedInto(baseMap)
? baseMap.name
: baseMap.uniqueId) ?? "Unknown item"
}
}
});
}
else {
runInAction(() => {
// Concurrent attempts to load basemap might not complete in the same
// order they were called. Set as current basemap only if this was
// the last call to setBaseMap.
if (this._loadingBaseMap === baseMap) {
// If the basemap specifies a preferred viewer mode, switch to it.
if (baseMap.preferredViewerMode) {
this.viewerMode =
getViewerType(baseMap.preferredViewerMode) ?? this.viewerMode;
}
this._baseMap = baseMap;
}
});
}
}
finally {
// Unset loadingBaseMap
if (this._loadingBaseMap === baseMap) {
runInAction(() => {
this._loadingBaseMap = undefined;
});
}
}
}
/**
* Switch to a base map that is compatible with the current viewer
*
* @returns A promise that yields `true` if the switch was made.
*/
async useViewerCompatibleBaseMap() {
const currentViewerType = this.viewerMode;
const baseMapViewerType = this.baseMap?.preferredViewerMode
? getViewerType(this.baseMap.preferredViewerMode)
: undefined;
if (!baseMapViewerType || baseMapViewerType === currentViewerType) {
return false;
}
// Select a base map that either does not require a specific viewer mode or
// specifies a compatible mode.
const compatibleBaseMap = this.terria.baseMapsModel.baseMapItems.find((it) => !it.item.preferredViewerMode ||
getViewerType(it.item.preferredViewerMode) === currentViewerType)?.item;
return compatibleBaseMap
? this.setBaseMap(compatibleBaseMap).then(() => true)
: false;
}
// This is a "view" of a workbench/other
items;
viewerMode = ViewerMode.Cesium;
// Set by UI
viewerOptions = viewerOptionsDefaults;
// Disable all mouse (& keyboard) interaction
disableInteraction = false;
_homeCamera = new CameraView(Rectangle.MAX_VALUE);
get homeCamera() {
return this._homeCamera;
}
set homeCamera(cameraView) {
if (isEqual(this._homeCamera.rectangle, Rectangle.MAX_VALUE)) {
this.currentViewer.zoomTo(cameraView, 0.0);
}
this._homeCamera = cameraView;
}
mapContainer;
/**
* The distance between two pixels at the bottom center of the screen.
* Set in lib/ReactViews/Map/Legend/DistanceLegend.jsx
*/
scale = 1;
beforeViewerChanged = new CesiumEvent();
afterViewerChanged = new CesiumEvent();
constructor(terria, items) {
makeObservable(this);
this.terria = terria;
this.items = items;
if (!this.viewerChangeTracker) {
this.viewerChangeTracker = reaction(() => this.currentViewer, () => {
this.afterViewerChanged.raiseEvent();
});
}
}
get attached() {
return this.mapContainer !== undefined;
}
_lastViewer;
viewerChangeTracker = undefined;
/**
* Promise for async loading of current `viewerMode`
* Starts when TerriaViewer is attached to a div and `viewerMode` is set
*/
get viewerLoadPromise() {
return Promise.resolve(this._currentViewerConstructorPromise).then(() => { });
}
/**
* Get a mobx-utils promise to a constructor for currentViewer. Start loading
* Leaflet or Cesium depending on `viewerMode` if attached to a div
*/
get _currentViewerConstructorPromise() {
let viewerFromPromise = fromPromise.resolve(NoViewer);
if (this.attached && this.viewerMode === ViewerMode.Leaflet) {
viewerFromPromise = leafletFromPromise.get();
}
else if (this.attached && this.viewerMode === ViewerMode.Cesium) {
viewerFromPromise = cesiumFromPromise.get();
}
return viewerFromPromise;
}
get currentViewer() {
// Use untracked on everything to ensure the viewer isn't recreated
// except when the viewer is required to change, the currently required
// viewer class finishes loading from an async chunk or the map container
// is changed
const currentView = untracked(() => this.destroyCurrentViewer());
let newViewer;
try {
// If a div is attached and a viewer is ready, use it
if (this.attached &&
this._currentViewerConstructorPromise.state === FULFILLED) {
const SomeViewer = this._currentViewerConstructorPromise.value;
newViewer = untracked(() => new SomeViewer(this, this.mapContainer));
}
else {
newViewer = untracked(() => new NoViewer(this));
}
}
catch (error) {
// Switch viewerMode inside computed. Could change viewers to
// guarantee no throw in constructor and instead have a `start()`
// method that can throw. Then call that `start()` method inside
// a reaction (reaction would also deal with viewer fallback).
// Using this approach might remove the need for `untracked`
setTimeout(action(() => {
this.terria.raiseErrorToUser(error);
this.viewerMode =
this.viewerMode === ViewerMode.Cesium
? ViewerMode.Leaflet
: undefined;
}), 0);
newViewer = untracked(() => new NoViewer(this));
}
this._lastViewer = newViewer;
newViewer.setInitialView(currentView || untracked(() => this.homeCamera));
return newViewer;
}
// Pull out attaching logic into it's own step. This allows constructing a TerriaViewer
// before its UI element is mounted in React to set basemap, items, viewermode
attach(mapContainer) {
this.mapContainer = mapContainer;
}
detach() {
// Detach from a container
this.mapContainer = undefined;
this.destroyCurrentViewer();
}
destroy() {
this.detach();
this.viewerChangeTracker?.();
}
destroyCurrentViewer() {
let currentView;
if (this._lastViewer !== undefined) {
this.beforeViewerChanged.raiseEvent();
currentView = this._lastViewer.getCurrentCameraView();
this._lastViewer.destroy();
this._lastViewer = undefined;
}
return currentView;
}
}
__decorate([
observable
], TerriaViewer.prototype, "_baseMap", void 0);
__decorate([
observable
], TerriaViewer.prototype, "_loadingBaseMap", void 0);
__decorate([
observable
], TerriaViewer.prototype, "viewerMode", void 0);
__decorate([
observable
], TerriaViewer.prototype, "viewerOptions", void 0);
__decorate([
observable
], TerriaViewer.prototype, "disableInteraction", void 0);
__decorate([
computed
], TerriaViewer.prototype, "homeCamera", null);
__decorate([
observable
], TerriaViewer.prototype, "mapContainer", void 0);
__decorate([
observable
], TerriaViewer.prototype, "scale", void 0);
__decorate([
computed
], TerriaViewer.prototype, "viewerLoadPromise", null);
__decorate([
computed
], TerriaViewer.prototype, "_currentViewerConstructorPromise", null);
__decorate([
computed({
keepAlive: true
})
], TerriaViewer.prototype, "currentViewer", null);
__decorate([
action
], TerriaViewer.prototype, "attach", null);
__decorate([
action
], TerriaViewer.prototype, "detach", null);
//# sourceMappingURL=TerriaViewer.js.map