Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Upgrade webpack to version 5.105.4.
- Upgrade eslint to v10 and migrate to eslint flat config.
- Extract Terria config parameters from `Terria` class in a separate `TerriaConfig` data store. Config parameters can be update using new `updateConfig` method on `Terria` class, while previous `updateParameters` method is now removed. ([7819](https://github.com/TerriaJS/terriajs/pull/7819))
- Extract Terria config parameters from `Terria` class in a separate `TerriaConfig` data store. Config parameters can be update using new `applyConfig` method on `Terria` class, while previous `updateParameters` method is now removed. ([7819](https://github.com/TerriaJS/terriajs/pull/7819))

#### 8.12.2 - 2026-03-27

Expand Down
26 changes: 19 additions & 7 deletions lib/Models/Cesium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import TileErrorHandlerMixin from "../ModelMixins/TileErrorHandlerMixin";
import SplitterTraits from "../Traits/TraitsClasses/SplitterTraits";
import TerriaViewer from "../ViewModels/TerriaViewer";
import CameraView from "./CameraView";
import CesiumOptions from "./CesiumOptions";
import hasTraits from "./Definition/hasTraits";
import TerriaFeature from "./Feature/Feature";
import GlobeOrMap from "./GlobeOrMap";
Expand Down Expand Up @@ -184,6 +185,11 @@ export default class Cesium extends GlobeOrMap {
*/
readonly terriaPrimitives = new PrimitiveCollection();

/**
* Helper for setting and persisting cesium options
*/
readonly options: CesiumOptions;

constructor(terriaViewer: TerriaViewer, container: string | HTMLElement) {
super();

Expand All @@ -203,16 +209,15 @@ export default class Cesium extends GlobeOrMap {
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO \
9TXL0Y4OHwAAAABJRU5ErkJggg==";

const options = {
const widgetOptions = {
clock: this.terria.timelineClock,
baseLayer: ImageryLayer.fromProviderAsync(
SingleTileImageryProvider.fromUrl(img),
{}
),
scene3DOnly: true,
shadows: true,
useBrowserRecommendedResolution: !this.terria.useNativeResolution,
targetFrameRate: 30
useBrowserRecommendedResolution: !this.terria.useNativeResolution
};

// Workaround for Firefox bug with WebGL and printing:
Expand All @@ -228,9 +233,19 @@ export default class Cesium extends GlobeOrMap {
try {
this.cesiumWidget = new CesiumWidget(
container,
Object.assign({}, options, firefoxBugOptions)
Object.assign({}, widgetOptions, firefoxBugOptions)
);
this.scene = this.cesiumWidget.scene;

// Initialize CesiumOptions after all default app options have been set
this.options = new CesiumOptions(this, {
// Disable HDR lighting for better performance and to avoid changing imagery colors.
highDynamicRange: false,
...this.terria.configParameters.cesiumOptions
});
// Load locally saved preferences
this.options.loadUserPreferences();

this.scene.primitives.add(this.terriaPrimitives);
} catch (error) {
throw TerriaError.from(error, {
Expand All @@ -256,9 +271,6 @@ export default class Cesium extends GlobeOrMap {
this._updateTilesLoadingCount(currentLoadQueueLength)
);

// Disable HDR lighting for better performance and to avoid changing imagery colors.
(this.scene as any).highDynamicRange = false;

this.scene.imageryLayers.removeAll();

this.updateCredits(container);
Expand Down
206 changes: 206 additions & 0 deletions lib/Models/CesiumOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import * as z from "zod";
import Cesium from "./Cesium";

const CESIUM_OPTIONS_KEY = "cesiumOptions";

export interface CesiumOptionsSchema {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we infer this type

msaaSamples: number;
resolutionScale: number;
highDynamicRange: boolean;
fxaa: boolean;
enableCollisionDetection: boolean;
targetFrameRate?: number | "any";
bloom: boolean;
bloomBrightness: number;
bloomContrast: number;
}

export const CesiumOptionsSchema = z.object({
msaaSamples: z.number(),
resolutionScale: z.number(),
highDynamicRange: z.boolean(),
fxaa: z.boolean(),
enableCollisionDetection: z.boolean(),
targetFrameRate: z.union([z.number(), z.string("any")]).optional(),
bloom: z.boolean(),
bloomBrightness: z.number(),
bloomContrast: z.number()
});

/**
* Manage Cesium options
*/
export default class CesiumOptions implements CesiumOptionsSchema {
private readonly cesium: Cesium;
readonly defaultOptions: CesiumOptionsSchema;

constructor(cesium: Cesium, initialOptions: Partial<CesiumOptionsSchema>) {
this.cesium = cesium;
this.updateFromJson(initialOptions);

// Save snapshot of initial state so that we can diff and reset changes
this.defaultOptions = this.toJson();
}

/**
* Reset all options to terria/cesium defaults
*/
resetAll() {
this.updateFromJson(this.defaultOptions);
}

/**
* Reset specified option to terria/cesium default
*/
resetOption<K extends keyof CesiumOptionsSchema>(name: K) {
(this as CesiumOptionsSchema)[name] = this.defaultOptions[name];
}

/**
* Return snapshot of all options as JSON object
*/
toJson(): CesiumOptionsSchema {
return Object.fromEntries(
Object.keys(CesiumOptionsSchema.shape).map((name) => [
name,
(this as any)[name]
])
) as any;
}

/**
* Update options from JSON
*/
updateFromJson(json: any) {
const parsed = CesiumOptionsSchema.partial().parse(json);
(Object.keys(parsed) as Array<keyof CesiumOptionsSchema>).forEach(
(name) => {
(this as any)[name] = parsed[name];
}
);
}

/**
* Return locally stored options
*/
getUserPreferences() {
const str = this.cesium.terria.getLocalProperty(CESIUM_OPTIONS_KEY);
try {
const localChanges = typeof str === "string" ? JSON.parse(str) : {};
return CesiumOptionsSchema.partial().parse(localChanges);
} catch (error) {
console.error("Ignoring invalid cesium options from local storage");
console.error(error);
return {};
}
}

/**
* Load user preferences
*/
loadUserPreferences(): any {
this.updateFromJson(this.getUserPreferences());
}

/**
* Save user preferences
*/
saveUserPreferences() {
const currentOptions = this.toJson();
// Save only those options that differ from default options
const onlyChangedOptions = Object.fromEntries(
Object.entries(currentOptions).filter(
([name, value]) => (this.defaultOptions as any)[name] !== value
)
);
// TODO: remove item if empty instead of setting to empty object {}
this.cesium.terria.setLocalProperty(
CESIUM_OPTIONS_KEY,
JSON.stringify(onlyChangedOptions)
);
}

/**
* Returns true if user has any stored preferences
*/
get hasSavedPreferences() {
return Object.keys(this.getUserPreferences()).length > 0;
}

get msaaSamples() {
return this.cesium.scene.msaaSamples;
}

set msaaSamples(value: number) {
this.cesium.scene.msaaSamples = value;
}

get resolutionScale(): number {
return this.cesium.cesiumWidget.resolutionScale;
}

set resolutionScale(value: number) {
this.cesium.cesiumWidget.resolutionScale = value;
}

get highDynamicRange(): boolean {
return this.cesium.scene.highDynamicRange;
}

set highDynamicRange(value: boolean) {
this.cesium.scene.highDynamicRange = value;
}

get fxaa(): boolean {
return this.cesium.scene.postProcessStages.fxaa.enabled;
}

set fxaa(value: boolean) {
this.cesium.scene.postProcessStages.fxaa.enabled = value;
}

get enableCollisionDetection(): boolean {
return this.cesium.scene.screenSpaceCameraController
.enableCollisionDetection;
}

set enableCollisionDetection(value: boolean) {
this.cesium.scene.screenSpaceCameraController.enableCollisionDetection =
value;
}

get targetFrameRate(): number | "any" {
return this.cesium.cesiumWidget.targetFrameRate ?? "any";
}

set targetFrameRate(value: number | "any") {
//@ts-expect-error 2322 - Cesium typings does not allow setting
//targetFrameRate=undefined but it is still valid as per doc
this.cesium.cesiumWidget.targetFrameRate =
value === "any" ? undefined : value;
}

get bloom(): boolean {
return this.cesium.scene.postProcessStages.bloom.enabled;
}

set bloom(value: boolean) {
this.cesium.scene.postProcessStages.bloom.enabled = value;
}

get bloomBrightness(): number {
return this.cesium.scene.postProcessStages.bloom.uniforms.brightness;
}

set bloomBrightness(value: number) {
this.cesium.scene.postProcessStages.bloom.uniforms.brightness = value;
}

get bloomContrast(): number {
return this.cesium.scene.postProcessStages.bloom.uniforms.contrast;
}

set bloomContrast(value: number) {
this.cesium.scene.postProcessStages.bloom.uniforms.contrast = value;
}
}
23 changes: 23 additions & 0 deletions lib/Models/SelectableDimensions/SelectableDimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface Dimension {
readonly id?: string;
/** Human readable name */
readonly name?: string;
/** A description text */
readonly description?: string;
}

export interface EnumDimensionOption<T = string> {
Expand Down Expand Up @@ -41,6 +43,15 @@ export interface NumericalDimension extends Dimension {
readonly allowUndefined?: boolean;
}

export interface NumericalRangeDimension extends Dimension {
readonly value?: number;
readonly min: number;
readonly max: number;
readonly step: number;
readonly marks?: Record<number, string>;
readonly allowUndefined?: boolean;
}

export interface TextDimension extends Dimension {
readonly value?: string;
readonly allowUndefined?: boolean;
Expand All @@ -56,13 +67,15 @@ export interface ButtonDimension extends Dimension {
readonly icon?:
| IconGlyph // Any Icon glyph
| "spinner"; // Animated spinner icon
readonly variant?: "primary" | "secondary" | "warning" | "default";
}

export type SelectableDimensionType =
| undefined
| "select"
| "select-multi"
| "numeric"
| "numeric-range"
| "text"
| "checkbox"
| "checkbox-group"
Expand Down Expand Up @@ -141,6 +154,11 @@ export interface SelectableDimensionNumeric
type: "numeric";
}

export interface SelectableDimensionNumericRange
extends SelectableDimensionBase<number>, NumericalRangeDimension {
type: "numeric-range";
}

export interface SelectableDimensionText
extends SelectableDimensionBase<string>, TextDimension {
type: "text";
Expand Down Expand Up @@ -183,6 +201,7 @@ export type SelectableDimension =
| SelectableDimensionCheckboxGroup
| SelectableDimensionGroup
| SelectableDimensionNumeric
| SelectableDimensionNumericRange
| SelectableDimensionText
| SelectableDimensionButton
| SelectableDimensionColor;
Expand All @@ -209,6 +228,10 @@ export const isNumeric = (
dim: SelectableDimension
): dim is SelectableDimensionNumeric => dim.type === "numeric";

export const isNumericRange = (
dim: SelectableDimension
): dim is SelectableDimensionNumericRange => dim.type === "numeric-range";

export const isText = (
dim: SelectableDimension
): dim is SelectableDimensionText => dim.type === "text";
Expand Down
5 changes: 5 additions & 0 deletions lib/Models/Terria.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import { ConfigParameters, TerriaConfig } from "./TerriaConfig";
import TimelineStack from "./TimelineStack";
import { isViewerMode, setViewerMode } from "./ViewerMode";
import Workbench from "./Workbench";
import AppWorkflow from "./Workflows/AppWorkflows/AppWorkflow";
import SelectableDimensionWorkflow from "./Workflows/SelectableDimensionWorkflow";

interface StartOptions {
Expand Down Expand Up @@ -267,6 +268,10 @@ export default class Terria {
@observable
selectableDimensionWorkflow?: SelectableDimensionWorkflow;

/** Gets or sets the non-blocking app workflow, if defined then the workflow will be displayed on a panel on the right side of the app */
@observable
appWorkflow?: AppWorkflow;

/**
* Flag for zooming to workbench items after all init sources have been loaded.
*
Expand Down
6 changes: 6 additions & 0 deletions lib/Models/TerriaConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ErrorServiceOptions } from "./ErrorServiceProviders/ErrorService";
import { LanguageConfiguration } from "./Internationalization";
import { RelatedMap } from "./RelatedMaps";
import { StoryVideoSettings } from "./StoryVideoSettings";
import { CesiumOptionsSchema } from "./CesiumOptions";

type OnlyProps<T> = {
[K in keyof T as T[K] extends (...args: never) => unknown ? never : K]: T[K];
Expand Down Expand Up @@ -362,6 +363,11 @@ export class TerriaConfig {
@observable
searchProviders: ModelPropertiesFromTraits<SearchProviderTraits>[] = [];

/**
* Options for the Cesium 3D globe
*/
cesiumOptions: Partial<CesiumOptionsSchema> = {};

constructor() {
makeObservable(this);
}
Expand Down
Loading
Loading