terriajs
Version:
Geospatial data visualization platform.
503 lines • 22.1 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 i18next from "i18next";
import { action, computed, isObservableArray, makeObservable, observable, override, runInAction, toJS } from "mobx";
import Cartesian3 from "terriajs-cesium/Source/Core/Cartesian3";
import HeadingPitchRoll from "terriajs-cesium/Source/Core/HeadingPitchRoll";
import IonResource from "terriajs-cesium/Source/Core/IonResource";
import Matrix3 from "terriajs-cesium/Source/Core/Matrix3";
import Matrix4 from "terriajs-cesium/Source/Core/Matrix4";
import Quaternion from "terriajs-cesium/Source/Core/Quaternion";
import Resource from "terriajs-cesium/Source/Core/Resource";
import Transforms from "terriajs-cesium/Source/Core/Transforms";
import Cesium3DTileColorBlendMode from "terriajs-cesium/Source/Scene/Cesium3DTileColorBlendMode";
import Cesium3DTileFeature from "terriajs-cesium/Source/Scene/Cesium3DTileFeature";
import Cesium3DTilePointFeature from "terriajs-cesium/Source/Scene/Cesium3DTilePointFeature";
import Cesium3DTileset from "terriajs-cesium/Source/Scene/Cesium3DTileset";
import { isJsonObject } from "../Core/Json";
import TerriaError from "../Core/TerriaError";
import isDefined from "../Core/isDefined";
import runLater from "../Core/runLater";
import proxyCatalogItemUrl from "../Models/Catalog/proxyCatalogItemUrl";
import CommonStrata from "../Models/Definition/CommonStrata";
import LoadableStratum from "../Models/Definition/LoadableStratum";
import StratumOrder from "../Models/Definition/StratumOrder";
import createStratumInstance from "../Models/Definition/createStratumInstance";
import TerriaFeature from "../Models/Feature/Feature";
import Cesium3DTilesCatalogItemTraits from "../Traits/TraitsClasses/Cesium3DTilesCatalogItemTraits";
import { default as Cesium3DTilesTraits, OptionsTraits } from "../Traits/TraitsClasses/Cesium3dTilesTraits";
import CatalogMemberMixin, { getName } from "./CatalogMemberMixin";
import Cesium3dTilesStyleMixin from "./Cesium3dTilesStyleMixin";
import ClippingMixin from "./ClippingMixin";
import MappableMixin from "./MappableMixin";
import ShadowMixin from "./ShadowMixin";
export class ObservableCesium3DTileset extends Cesium3DTileset {
_catalogItem;
destroyed = false;
constructor(...args) {
super(...args);
makeObservable(this);
}
destroy() {
super.destroy();
// TODO: we are running later to prevent this
// modification from happening in some computed up the call chain.
// Figure out why that is happening and fix it.
runLater(() => {
runInAction(() => {
this.destroyed = true;
});
});
}
}
__decorate([
observable
], ObservableCesium3DTileset.prototype, "destroyed", void 0);
class Cesium3dTilesLoadableStratum extends LoadableStratum(Cesium3DTilesTraits) {
static stratumName = "cesium3dTilesLoadableStratum";
duplicateLoadableStratum(newModel) {
return new Cesium3dTilesLoadableStratum(newModel);
}
get supportsReordering() {
// Enable reordering in workbench if draping is enabled
return this.drapeImagery;
}
}
StratumOrder.addLoadStratum(Cesium3dTilesLoadableStratum.stratumName);
function Cesium3dTilesMixin(Base) {
class Cesium3dTilesMixin extends Cesium3dTilesStyleMixin(ClippingMixin(ShadowMixin(MappableMixin(CatalogMemberMixin(Base))))) {
tileset;
constructor(...args) {
super(...args);
makeObservable(this);
this.strata.set(Cesium3dTilesLoadableStratum.stratumName, new Cesium3dTilesLoadableStratum(this));
}
get hasCesium3dTilesMixin() {
return true;
}
// Just a variable to save the original tileset.root.transform if it exists
originalRootTransform = Matrix4.IDENTITY.clone();
clippingPlanesOriginMatrix() {
if (this.tileset) {
// clippingPlanesOriginMatrix is private.
// We need it to find the position where cesium centers the clipping plane for the tileset.
// See if we can find another way to get it.
if (this.tileset.clippingPlanesOriginMatrix) {
return this.tileset.clippingPlanesOriginMatrix.clone();
}
}
return Matrix4.IDENTITY.clone();
}
async forceLoadMapItems() {
try {
await this.loadTileset();
if (this.tileset) {
const tileset = this.tileset;
if (tileset.extras !== undefined &&
tileset.extras.style !== undefined) {
runInAction(() => {
this.strata.set(CommonStrata.defaults, createStratumInstance(Cesium3DTilesCatalogItemTraits, {
style: tileset.extras.style
}));
});
}
}
}
catch (e) {
throw TerriaError.from(e, "Failed to load 3d-tiles tileset");
}
}
loadTileset() {
if (!isDefined(this.url) && !isDefined(this.ionAssetId)) {
throw `\`url\` and \`ionAssetId\` are not defined for ${getName(this)}`;
}
let resource = undefined;
if (isDefined(this.ionAssetId)) {
resource = this.createResourceFromIonId(this.ionAssetId, this.ionAccessToken, this.ionServer);
}
else if (isDefined(this.url)) {
resource = this.createResourceFromUrl(proxyCatalogItemUrl(this, this.url));
}
if (!isDefined(resource)) {
return;
}
// Save the original root tile transform and set its value to an identity
// matrix This lets us control the whole model transformation using just
// tileset.modelMatrix We later derive a tilset.modelMatrix by combining
// the root transform and transformation traits in mapItems.
return Promise.resolve(resource).then((resource) => {
if (resource === undefined)
return;
const tilesetPromise = Cesium3DTileset.fromUrl(resource, {
...this.optionsObj
});
return tilesetPromise.then((tileset) => {
// Hackily turn the Cesium3DTileset into an ObservableCesium3DTileset
const anyTileset = tileset;
anyTileset._catalogItem = this;
anyTileset.destroyed = tileset.isDestroyed();
const superDestroy = anyTileset.destroy;
anyTileset.destroy = function () {
superDestroy.call(this);
// TODO: we are running later to prevent this
// modification from happening in some computed up the call chain.
// Figure out why that is happening and fix it.
runLater(() => {
runInAction(() => {
this.destroyed = true;
});
});
};
makeObservable(anyTileset, {
destroyed: observable
});
const observableTileset = anyTileset;
runInAction(() => {
observableTileset._catalogItem = this;
if (!observableTileset.destroyed) {
this.tileset = observableTileset;
if (observableTileset.root !== undefined) {
this.originalRootTransform =
observableTileset.root.transform.clone();
observableTileset.root.transform = Matrix4.IDENTITY.clone();
}
}
});
});
});
}
/**
* Computes a new model matrix by combining the given matrix with the
* origin, rotation & scale trait values
*/
computeModelMatrixFromTransformationTraits(modelMatrix) {
let scale = Matrix4.getScale(modelMatrix, new Cartesian3());
const position = Matrix4.getTranslation(modelMatrix, new Cartesian3());
let orientation = Quaternion.fromRotationMatrix(Matrix4.getRotation(modelMatrix, new Matrix3()));
const { latitude, longitude, height } = this.origin;
if (latitude !== undefined && longitude !== undefined) {
const positionFromLatLng = Cartesian3.fromDegrees(longitude, latitude, height);
position.x = positionFromLatLng.x;
position.y = positionFromLatLng.y;
if (height !== undefined) {
position.z = positionFromLatLng.z;
}
}
const { heading, pitch, roll } = this.rotation;
if (heading !== undefined && pitch !== undefined && roll !== undefined) {
const hpr = HeadingPitchRoll.fromDegrees(heading, pitch, roll);
orientation = Transforms.headingPitchRollQuaternion(position, hpr);
}
if (this.scale !== undefined) {
scale = new Cartesian3(this.scale, this.scale, this.scale);
}
return Matrix4.fromTranslationQuaternionRotationScale(position, orientation, scale);
}
/**
* A computed that returns the result of transforming the original tileset
* root transform with the origin, rotation & scale traits for this catalog
* item
*/
get modelMatrix() {
const modelMatrixFromTraits = this.computeModelMatrixFromTransformationTraits(this.originalRootTransform);
return modelMatrixFromTraits;
}
get mapItems() {
if (this.isLoadingMapItems || !isDefined(this.tileset)) {
return [];
}
if (this.tileset.destroyed) {
this.loadMapItems(true);
}
this.tileset.style = toJS(this.cesiumTileStyle);
this.tileset.shadows = this.cesiumShadows;
this.tileset.show = this.show;
const key = this
.colorBlendMode;
const colorBlendMode = Cesium3DTileColorBlendMode[key];
if (colorBlendMode !== undefined)
this.tileset.colorBlendMode = colorBlendMode;
this.tileset.colorBlendAmount = this.colorBlendAmount;
if (this.lightColor)
this.tileset.lightColor = Cartesian3.fromArray(this.lightColor.slice());
// default is 16 (baseMaximumScreenSpaceError @ 2)
// we want to reduce to 8 for higher levels of quality
// the slider goes from [quality] 1 to 3 [performance]
// in 0.1 steps
const tilesetBaseSse = this.options.maximumScreenSpaceError !== undefined
? this.options.maximumScreenSpaceError / 2.0
: 8;
this.tileset.maximumScreenSpaceError =
tilesetBaseSse * this.terria.baseMaximumScreenSpaceError;
this.tileset.modelMatrix = this.modelMatrix;
this.tileset.clippingPlanes = this.clippingPlaneCollection;
this.clippingMapItems.forEach((mapItem) => {
mapItem.show = this.show;
});
return [this.tileset, ...this.clippingMapItems];
}
get shortReport() {
if (this.terria.currentViewer.type === "Leaflet") {
return i18next.t("models.commonModelErrors.3dTypeIn2dMode", this);
}
return super.shortReport;
}
get optionsObj() {
const options = {};
if (isDefined(this.options)) {
Object.keys(OptionsTraits.traits).forEach((name) => {
options[name] = this.options[name];
});
}
return options;
}
createResourceFromUrl(url) {
if (!isDefined(url)) {
return;
}
let resource;
if (url instanceof Resource) {
resource = url;
}
else {
resource = new Resource({ url });
}
return resource;
}
async createResourceFromIonId(ionAssetId, ionAccessToken, ionServer) {
if (!isDefined(ionAssetId)) {
return;
}
const resource = await IonResource.fromAssetId(ionAssetId, {
accessToken: ionAccessToken || this.terria.configParameters.cesiumIonAccessToken,
server: ionServer
});
return resource;
}
/**
* This function should return null if allowFeaturePicking = false
* @param _screenPosition
* @param pickResult
*/
buildFeatureFromPickResult(_screenPosition, pickResult) {
if (this.allowFeaturePicking &&
(pickResult instanceof Cesium3DTileFeature ||
pickResult instanceof Cesium3DTilePointFeature)) {
const properties = {};
pickResult.getPropertyIds().forEach((name) => {
properties[name] = pickResult.getProperty(name);
});
const result = new TerriaFeature({
properties
});
result._cesium3DTileFeature = pickResult;
return result;
}
}
/**
* Returns the name of properties to be used as an ID for this catalog item.
*
* The return value is an array of strings as the Id value could be formed
* by combining multiple properties. eg: ["latitudeprop", "longitudeprop"]
*/
getIdPropertiesForFeature(feature) {
// If `featureIdProperties` is set return it, otherwise if the feature has
// a property named `id` return it.
if (this.featureIdProperties)
return this.featureIdProperties.slice();
const propretyNamedId = feature
.getPropertyIds()
.find((name) => name.toLowerCase() === "id");
return propretyNamedId ? [propretyNamedId] : [];
}
/**
* Returns a selector that can be used for filtering or styling the given
* feature. For this to work, the feature should have a property called
* `id` or the catalog item should have the trait `featureIdProperties` defined.
*
* @returns Selector string or `undefined` when no unique selector can be constructed for the feature
*/
getSelectorForFeature(feature) {
const idProperties = this.getIdPropertiesForFeature(feature).sort();
if (idProperties.length === 0) {
return;
}
const terms = idProperties.map((p) => `\${${p}} === ${JSON.stringify(feature.getProperty(p))}`);
const selector = terms.join(" && ");
return selector ? selector : undefined;
}
setVisibilityForMatchingFeature(expression, visibility) {
if (expression) {
const style = this.style || {};
const show = normalizeShowExpression(style?.show);
show.conditions.unshift([expression, visibility]);
this.setTrait(CommonStrata.user, "style", { ...style, show });
}
}
/**
* Modifies the style traits to show/hide a 3d tile feature
*
*/
setFeatureVisibility(feature, visibility) {
const showExpr = this.getSelectorForFeature(feature);
if (showExpr) {
this.setVisibilityForMatchingFeature(showExpr, visibility);
}
}
/**
* Adds a new show expression to the styles trait.
*
* To ensure that we can add multiple show expressions, we first normalize
* the show expressions to a `show.conditions` array and then add the new
* expression. The new expression is added to the beginning of
* `show.conditions` so it will have the highest priority.
*
* @param newShowExpr The new show expression to add to the styles trait
*/
applyShowExpression(newShowExpr) {
const style = this.style || {};
const show = normalizeShowExpression(style?.show);
show.conditions.unshift([newShowExpr.condition, newShowExpr.show]);
this.setTrait(CommonStrata.user, "style", { ...style, show });
}
/**
* Remove all show expressions that match the given condition.
*
* @param condition The condition string used to match the show expression.
*/
removeShowExpression(condition) {
const show = this.style?.show;
if (!isJsonObject(show))
return;
if (!isObservableArray(show.conditions))
return;
const conditions = show.conditions
.slice()
.filter((e) => e[0] !== condition);
this.setTrait(CommonStrata.user, "style", {
...this.style,
show: {
...show,
conditions
}
});
}
/**
* Adds a new color expression to the style traits.
*
* To ensure that we can add multiple color expressions, we first normalize the
* color expression to a `color.conditions` array. Then add the new expression to the
* beginning of the array. This gives the highest priority for the new color expression.
*
* @param newColorExpr The new color expression to add
*/
applyColorExpression(newColorExpr) {
const style = this.style || {};
const color = normalizeColorExpression(style?.color);
color.conditions.unshift([newColorExpr.condition, newColorExpr.value]);
if (!color.conditions.find((c) => c[0] === "true")) {
color.conditions.push(["true", "color('#ffffff')"]); // ensure there is a default color
}
this.setTrait(CommonStrata.user, "style", {
...style,
color
});
}
/**
* Removes all color expressions with the given condition from the style traits.
*/
removeColorExpression(condition) {
const color = this.style?.color;
if (!isJsonObject(color))
return;
if (!isObservableArray(color.conditions))
return;
const conditions = color.conditions
.slice()
.filter((e) => e[0] !== condition);
this.setTrait(CommonStrata.user, "style", {
...this.style,
color: {
...color,
conditions
}
});
}
get selectableDimensions() {
return [
...super.selectableDimensions,
...super.shadowDimensions,
...super.clippingDimensions
];
}
}
__decorate([
observable
], Cesium3dTilesMixin.prototype, "originalRootTransform", void 0);
__decorate([
computed
], Cesium3dTilesMixin.prototype, "modelMatrix", null);
__decorate([
computed
], Cesium3dTilesMixin.prototype, "mapItems", null);
__decorate([
override
], Cesium3dTilesMixin.prototype, "shortReport", null);
__decorate([
computed
], Cesium3dTilesMixin.prototype, "optionsObj", null);
__decorate([
action
], Cesium3dTilesMixin.prototype, "setFeatureVisibility", null);
__decorate([
action
], Cesium3dTilesMixin.prototype, "applyShowExpression", null);
__decorate([
action
], Cesium3dTilesMixin.prototype, "removeShowExpression", null);
__decorate([
action
], Cesium3dTilesMixin.prototype, "applyColorExpression", null);
__decorate([
action
], Cesium3dTilesMixin.prototype, "removeColorExpression", null);
__decorate([
override
], Cesium3dTilesMixin.prototype, "selectableDimensions", null);
return Cesium3dTilesMixin;
}
(function (Cesium3dTilesMixin) {
function isMixedInto(model) {
return model && model.hasCesium3dTilesMixin;
}
Cesium3dTilesMixin.isMixedInto = isMixedInto;
})(Cesium3dTilesMixin || (Cesium3dTilesMixin = {}));
function normalizeShowExpression(show) {
let conditions;
if (Array.isArray(show?.conditions?.slice())) {
conditions = [...show.conditions];
}
else if (typeof show === "string") {
conditions = [[show, true]];
}
else {
conditions = [["true", true]];
}
return { ...show, conditions };
}
function normalizeColorExpression(expr) {
const normalized = {
conditions: []
};
if (typeof expr === "string")
normalized.expression = expr;
if (isJsonObject(expr))
Object.assign(normalized, expr);
return normalized;
}
export default Cesium3dTilesMixin;
//# sourceMappingURL=Cesium3dTilesMixin.js.map