Skip to content

Commit 75a357d

Browse files
committed
Add cesium options config.
1 parent 5ff1691 commit 75a357d

26 files changed

Lines changed: 1449 additions & 405 deletions

File tree

lib/Models/Cesium.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import TileErrorHandlerMixin from "../ModelMixins/TileErrorHandlerMixin";
8989
import SplitterTraits from "../Traits/TraitsClasses/SplitterTraits";
9090
import TerriaViewer from "../ViewModels/TerriaViewer";
9191
import CameraView from "./CameraView";
92+
import CesiumOptions from "./CesiumOptions";
9293
import hasTraits from "./Definition/hasTraits";
9394
import TerriaFeature from "./Feature/Feature";
9495
import GlobeOrMap from "./GlobeOrMap";
@@ -184,6 +185,11 @@ export default class Cesium extends GlobeOrMap {
184185
*/
185186
readonly terriaPrimitives = new PrimitiveCollection();
186187

188+
/**
189+
* Helper for setting and persisting cesium options
190+
*/
191+
readonly options: CesiumOptions;
192+
187193
constructor(terriaViewer: TerriaViewer, container: string | HTMLElement) {
188194
super();
189195

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

206-
const options = {
212+
const widgetOptions = {
207213
clock: this.terria.timelineClock,
208214
baseLayer: ImageryLayer.fromProviderAsync(
209215
SingleTileImageryProvider.fromUrl(img),
210216
{}
211217
),
212218
scene3DOnly: true,
213219
shadows: true,
214-
useBrowserRecommendedResolution: !this.terria.useNativeResolution,
215-
targetFrameRate: 30
220+
useBrowserRecommendedResolution: !this.terria.useNativeResolution
216221
};
217222

218223
// Workaround for Firefox bug with WebGL and printing:
@@ -228,9 +233,19 @@ export default class Cesium extends GlobeOrMap {
228233
try {
229234
this.cesiumWidget = new CesiumWidget(
230235
container,
231-
Object.assign({}, options, firefoxBugOptions)
236+
Object.assign({}, widgetOptions, firefoxBugOptions)
232237
);
233238
this.scene = this.cesiumWidget.scene;
239+
240+
// Initialize CesiumOptions after all default app options have been set
241+
this.options = new CesiumOptions(this, {
242+
// Disable HDR lighting for better performance and to avoid changing imagery colors.
243+
highDynamicRange: false,
244+
...this.terria.configParameters.cesiumOptions
245+
});
246+
// Load locally saved preferences
247+
this.options.loadUserPreferences();
248+
234249
this.scene.primitives.add(this.terriaPrimitives);
235250
} catch (error) {
236251
throw TerriaError.from(error, {
@@ -256,9 +271,6 @@ export default class Cesium extends GlobeOrMap {
256271
this._updateTilesLoadingCount(currentLoadQueueLength)
257272
);
258273

259-
// Disable HDR lighting for better performance and to avoid changing imagery colors.
260-
(this.scene as any).highDynamicRange = false;
261-
262274
this.scene.imageryLayers.removeAll();
263275

264276
this.updateCredits(container);

lib/Models/CesiumOptions.ts

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import * as z from "zod";
2+
import Cesium from "./Cesium";
3+
4+
const CESIUM_OPTIONS_KEY = "cesiumOptions";
5+
6+
export interface CesiumOptionsSchema {
7+
msaaSamples: number;
8+
resolutionScale: number;
9+
highDynamicRange: boolean;
10+
fxaa: boolean;
11+
enableCollisionDetection: boolean;
12+
targetFrameRate?: number | "any";
13+
bloom: boolean;
14+
bloomBrightness: number;
15+
bloomContrast: number;
16+
}
17+
18+
export const CesiumOptionsSchema = z.object({
19+
msaaSamples: z.number(),
20+
resolutionScale: z.number(),
21+
highDynamicRange: z.boolean(),
22+
fxaa: z.boolean(),
23+
enableCollisionDetection: z.boolean(),
24+
targetFrameRate: z.union([z.number(), z.string("any")]).optional(),
25+
bloom: z.boolean(),
26+
bloomBrightness: z.number(),
27+
bloomContrast: z.number()
28+
});
29+
30+
/**
31+
* Manage Cesium options
32+
*/
33+
export default class CesiumOptions implements CesiumOptionsSchema {
34+
private readonly cesium: Cesium;
35+
readonly defaultOptions: CesiumOptionsSchema;
36+
37+
constructor(cesium: Cesium, initialOptions: Partial<CesiumOptionsSchema>) {
38+
this.cesium = cesium;
39+
this.updateFromJson(initialOptions);
40+
41+
// Save snapshot of initial state so that we can diff and reset changes
42+
this.defaultOptions = this.toJson();
43+
}
44+
45+
/**
46+
* Reset all options to terria/cesium defaults
47+
*/
48+
resetAll() {
49+
this.updateFromJson(this.defaultOptions);
50+
}
51+
52+
/**
53+
* Reset specified option to terria/cesium default
54+
*/
55+
resetOption<K extends keyof CesiumOptionsSchema>(name: K) {
56+
(this as CesiumOptionsSchema)[name] = this.defaultOptions[name];
57+
}
58+
59+
/**
60+
* Return snapshot of all options as JSON object
61+
*/
62+
toJson(): CesiumOptionsSchema {
63+
return Object.fromEntries(
64+
Object.keys(CesiumOptionsSchema.shape).map((name) => [
65+
name,
66+
(this as any)[name]
67+
])
68+
) as any;
69+
}
70+
71+
/**
72+
* Update options from JSON
73+
*/
74+
updateFromJson(json: any) {
75+
const parsed = CesiumOptionsSchema.partial().parse(json);
76+
(Object.keys(parsed) as Array<keyof CesiumOptionsSchema>).forEach(
77+
(name) => {
78+
(this as any)[name] = parsed[name];
79+
}
80+
);
81+
}
82+
83+
/**
84+
* Return locally stored options
85+
*/
86+
getUserPreferences() {
87+
const str = this.cesium.terria.getLocalProperty(CESIUM_OPTIONS_KEY);
88+
try {
89+
const localChanges = typeof str === "string" ? JSON.parse(str) : {};
90+
return CesiumOptionsSchema.partial().parse(localChanges);
91+
} catch (error) {
92+
console.error("Ignoring invalid cesium options from local storage");
93+
console.error(error);
94+
return {};
95+
}
96+
}
97+
98+
/**
99+
* Load user preferences
100+
*/
101+
loadUserPreferences(): any {
102+
this.updateFromJson(this.getUserPreferences());
103+
}
104+
105+
/**
106+
* Save user preferences
107+
*/
108+
saveUserPreferences() {
109+
const currentOptions = this.toJson();
110+
// Save only those options that differ from default options
111+
const onlyChangedOptions = Object.fromEntries(
112+
Object.entries(currentOptions).filter(
113+
([name, value]) => (this.defaultOptions as any)[name] !== value
114+
)
115+
);
116+
// TODO: remove item if empty instead of setting to empty object {}
117+
this.cesium.terria.setLocalProperty(
118+
CESIUM_OPTIONS_KEY,
119+
JSON.stringify(onlyChangedOptions)
120+
);
121+
}
122+
123+
/**
124+
* Returns true if user has any stored preferences
125+
*/
126+
get hasSavedPreferences() {
127+
return Object.keys(this.getUserPreferences()).length > 0;
128+
}
129+
130+
get msaaSamples() {
131+
return this.cesium.scene.msaaSamples;
132+
}
133+
134+
set msaaSamples(value: number) {
135+
this.cesium.scene.msaaSamples = value;
136+
}
137+
138+
get resolutionScale(): number {
139+
return this.cesium.cesiumWidget.resolutionScale;
140+
}
141+
142+
set resolutionScale(value: number) {
143+
this.cesium.cesiumWidget.resolutionScale = value;
144+
}
145+
146+
get highDynamicRange(): boolean {
147+
return this.cesium.scene.highDynamicRange;
148+
}
149+
150+
set highDynamicRange(value: boolean) {
151+
this.cesium.scene.highDynamicRange = value;
152+
}
153+
154+
get fxaa(): boolean {
155+
return this.cesium.scene.postProcessStages.fxaa.enabled;
156+
}
157+
158+
set fxaa(value: boolean) {
159+
this.cesium.scene.postProcessStages.fxaa.enabled = value;
160+
}
161+
162+
get enableCollisionDetection(): boolean {
163+
return this.cesium.scene.screenSpaceCameraController
164+
.enableCollisionDetection;
165+
}
166+
167+
set enableCollisionDetection(value: boolean) {
168+
this.cesium.scene.screenSpaceCameraController.enableCollisionDetection =
169+
value;
170+
}
171+
172+
get targetFrameRate(): number | "any" {
173+
return this.cesium.cesiumWidget.targetFrameRate ?? "any";
174+
}
175+
176+
set targetFrameRate(value: number | "any") {
177+
//@ts-expect-error 2322 - Cesium typings does not allow setting
178+
//targetFrameRate=undefined but it is still valid as per doc
179+
this.cesium.cesiumWidget.targetFrameRate =
180+
value === "any" ? undefined : value;
181+
}
182+
183+
get bloom(): boolean {
184+
return this.cesium.scene.postProcessStages.bloom.enabled;
185+
}
186+
187+
set bloom(value: boolean) {
188+
this.cesium.scene.postProcessStages.bloom.enabled = value;
189+
}
190+
191+
get bloomBrightness(): number {
192+
return this.cesium.scene.postProcessStages.bloom.uniforms.brightness;
193+
}
194+
195+
set bloomBrightness(value: number) {
196+
this.cesium.scene.postProcessStages.bloom.uniforms.brightness = value;
197+
}
198+
199+
get bloomContrast(): number {
200+
return this.cesium.scene.postProcessStages.bloom.uniforms.contrast;
201+
}
202+
203+
set bloomContrast(value: number) {
204+
this.cesium.scene.postProcessStages.bloom.uniforms.contrast = value;
205+
}
206+
}

lib/Models/SelectableDimensions/SelectableDimensions.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ interface Dimension {
1111
readonly id?: string;
1212
/** Human readable name */
1313
readonly name?: string;
14+
/** A description text */
15+
readonly description?: string;
1416
}
1517

1618
export interface EnumDimensionOption<T = string> {
@@ -41,6 +43,15 @@ export interface NumericalDimension extends Dimension {
4143
readonly allowUndefined?: boolean;
4244
}
4345

46+
export interface NumericalRangeDimension extends Dimension {
47+
readonly value?: number;
48+
readonly min: number;
49+
readonly max: number;
50+
readonly step: number;
51+
readonly marks?: Record<number, string>;
52+
readonly allowUndefined?: boolean;
53+
}
54+
4455
export interface TextDimension extends Dimension {
4556
readonly value?: string;
4657
readonly allowUndefined?: boolean;
@@ -56,13 +67,15 @@ export interface ButtonDimension extends Dimension {
5667
readonly icon?:
5768
| IconGlyph // Any Icon glyph
5869
| "spinner"; // Animated spinner icon
70+
readonly variant?: "primary" | "secondary" | "warning" | "default";
5971
}
6072

6173
export type SelectableDimensionType =
6274
| undefined
6375
| "select"
6476
| "select-multi"
6577
| "numeric"
78+
| "numeric-range"
6679
| "text"
6780
| "checkbox"
6881
| "checkbox-group"
@@ -141,6 +154,11 @@ export interface SelectableDimensionNumeric
141154
type: "numeric";
142155
}
143156

157+
export interface SelectableDimensionNumericRange
158+
extends SelectableDimensionBase<number>, NumericalRangeDimension {
159+
type: "numeric-range";
160+
}
161+
144162
export interface SelectableDimensionText
145163
extends SelectableDimensionBase<string>, TextDimension {
146164
type: "text";
@@ -183,6 +201,7 @@ export type SelectableDimension =
183201
| SelectableDimensionCheckboxGroup
184202
| SelectableDimensionGroup
185203
| SelectableDimensionNumeric
204+
| SelectableDimensionNumericRange
186205
| SelectableDimensionText
187206
| SelectableDimensionButton
188207
| SelectableDimensionColor;
@@ -209,6 +228,10 @@ export const isNumeric = (
209228
dim: SelectableDimension
210229
): dim is SelectableDimensionNumeric => dim.type === "numeric";
211230

231+
export const isNumericRange = (
232+
dim: SelectableDimension
233+
): dim is SelectableDimensionNumericRange => dim.type === "numeric-range";
234+
212235
export const isText = (
213236
dim: SelectableDimension
214237
): dim is SelectableDimensionText => dim.type === "text";

lib/Models/Terria.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import { ConfigParameters, TerriaConfig } from "./TerriaConfig";
109109
import TimelineStack from "./TimelineStack";
110110
import { isViewerMode, setViewerMode } from "./ViewerMode";
111111
import Workbench from "./Workbench";
112+
import AppWorkflow from "./Workflows/AppWorkflows/AppWorkflow";
112113
import SelectableDimensionWorkflow from "./Workflows/SelectableDimensionWorkflow";
113114

114115
interface StartOptions {
@@ -267,6 +268,10 @@ export default class Terria {
267268
@observable
268269
selectableDimensionWorkflow?: SelectableDimensionWorkflow;
269270

271+
/** 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 */
272+
@observable
273+
appWorkflow?: AppWorkflow;
274+
270275
/**
271276
* Flag for zooming to workbench items after all init sources have been loaded.
272277
*

lib/Models/TerriaConfig.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ErrorServiceOptions } from "./ErrorServiceProviders/ErrorService";
1010
import { LanguageConfiguration } from "./Internationalization";
1111
import { RelatedMap } from "./RelatedMaps";
1212
import { StoryVideoSettings } from "./StoryVideoSettings";
13+
import { CesiumOptionsSchema } from "./CesiumOptions";
1314

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

366+
/**
367+
* Options for the Cesium 3D globe
368+
*/
369+
cesiumOptions: Partial<CesiumOptionsSchema> = {};
370+
365371
constructor() {
366372
makeObservable(this);
367373
}

0 commit comments

Comments
 (0)