Skip to content
Merged
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 @@ -5,6 +5,7 @@
- Update docs for Client-side config: change `searchBar` parameter to `searchBarConfig`
- Fix to show preview map when used outside the explorer panel.
- Update `csv-geo-au` support to include the latest Australian Government regions.
- Keep camera steady when switching between viewer modes.
- [The next improvement]

#### 8.11.0 - 2025-10-09
Expand Down
22 changes: 22 additions & 0 deletions lib/Models/Cesium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ export default class Cesium extends GlobeOrMap {
});

private _terrainMessageViewed: boolean = false;

/**
* Initial view set when the viewer is created
*/
private _initialView: CameraView | undefined;

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

Expand Down Expand Up @@ -951,6 +957,15 @@ export default class Cesium extends GlobeOrMap {
return _zoom().finally(() => this.notifyRepaintRequired());
}

setInitialView(view: CameraView) {
this.doZoomTo(view, 0);
this._initialView = view;
const removeListener = this.scene.camera.changed.addEventListener(() => {
this._initialView = undefined;
removeListener();
});
}

notifyRepaintRequired(): void {
this.pauser.notifyRepaintRequired();
}
Expand Down Expand Up @@ -1009,6 +1024,13 @@ export default class Cesium extends GlobeOrMap {
}

getCurrentCameraView(): CameraView {
// Return the initial view if the camera hasn't changed since setting it.
// This ensures that the view remains constant when switching between
// viewer modes.
if (this._initialView) {
return this._initialView;
}

const scene = this.scene;
const camera = scene.camera;

Expand Down
5 changes: 5 additions & 0 deletions lib/Models/GlobeOrMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ export default abstract class GlobeOrMap {
);
}

/**
* Set initial camera view
*/
abstract setInitialView(cameraView: CameraView): void;

abstract getCurrentCameraView(): CameraView;

/* Gets the current container element.
Expand Down
30 changes: 29 additions & 1 deletion lib/Models/Leaflet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ export default class Leaflet extends GlobeOrMap {
@observable nw: L.Point | undefined;
@observable se: L.Point | undefined;

/**
* Initial view set when the viewer is created
*/
private _initialView: CameraView | undefined;

@action
private updateMapObservables() {
this.size = this.map.getSize();
Expand Down Expand Up @@ -339,6 +344,7 @@ export default class Leaflet extends GlobeOrMap {
this.dataSourceDisplay.destroy();
this.map.off("move");
this.map.off("zoom");
this.map.off("zoomlevelschange");
this.map.remove();
}

Expand Down Expand Up @@ -501,7 +507,6 @@ export default class Leaflet extends GlobeOrMap {
return Promise.resolve();
}
let bounds;

if (isDefined(target.entities)) {
if (isDefined(this.dataSourceDisplay)) {
bounds = this.dataSourceDisplay.getLatLngBounds(target);
Expand Down Expand Up @@ -549,7 +554,30 @@ export default class Leaflet extends GlobeOrMap {
return Promise.resolve();
}

setInitialView(view: CameraView) {
this.doZoomTo(view, 0);
this._initialView = view;
this.map.addOneTimeEventListener("move", () => {
this._initialView = undefined;
});
}

/**
* Return the initial view if it hasn't changed. Otherwise return undefined.
*/
getInitialView(): CameraView | undefined {
return this._initialView;
}

getCurrentCameraView(): CameraView {
// Return the initial view if the camera hasn't changed since setting it.
// This ensures that the view remains constant when switching between
// viewer modes.
const initialView = this.getInitialView();
if (initialView) {
return initialView;
}

const bounds = this.map.getBounds();
return new CameraView(
Rectangle.fromDegrees(
Expand Down
4 changes: 4 additions & 0 deletions lib/Models/NoViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class NoViewer extends GlobeOrMap {
return Promise.resolve();
}

setInitialView(view: CameraView) {
this._currentView = view;
}

notifyRepaintRequired(): void {}

pickFromLocation(
Expand Down
2 changes: 1 addition & 1 deletion lib/ViewModels/TerriaViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export default class TerriaViewer {
}

this._lastViewer = newViewer;
newViewer.zoomTo(currentView || untracked(() => this.homeCamera), 0.0);
newViewer.setInitialView(currentView || untracked(() => this.homeCamera));

return newViewer;
}
Expand Down
56 changes: 55 additions & 1 deletion test/Models/CesiumSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { action, computed, observable, runInAction, when } from "mobx";
import Cartesian3 from "terriajs-cesium/Source/Core/Cartesian3";
import CesiumTerrainProvider from "terriajs-cesium/Source/Core/CesiumTerrainProvider";
import EllipsoidTerrainProvider from "terriajs-cesium/Source/Core/EllipsoidTerrainProvider";
import CesiumMath from "terriajs-cesium/Source/Core/Math";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";
import GeoJsonDataSource from "terriajs-cesium/Source/DataSources/GeoJsonDataSource";
import Cesium3DTileset from "terriajs-cesium/Source/Scene/Cesium3DTileset";
import Scene from "terriajs-cesium/Source/Scene/Scene";
import WebMapServiceImageryProvider from "terriajs-cesium/Source/Scene/WebMapServiceImageryProvider";
import filterOutUndefined from "../../lib/Core/filterOutUndefined";
import runLater from "../../lib/Core/runLater";
import supportsWebGL from "../../lib/Core/supportsWebGL";
import MappableMixin, { MapItem } from "../../lib/ModelMixins/MappableMixin";
import CameraView from "../../lib/Models/CameraView";
import CesiumTerrainCatalogItem from "../../lib/Models/Catalog/CatalogItems/CesiumTerrainCatalogItem";
import CatalogMemberFactory from "../../lib/Models/Catalog/CatalogMemberFactory";
import WebMapServiceCatalogItem from "../../lib/Models/Catalog/Ows/WebMapServiceCatalogItem";
Expand All @@ -24,7 +27,6 @@ import MappableTraits, {
RectangleTraits
} from "../../lib/Traits/TraitsClasses/MappableTraits";
import TerriaViewer from "../../lib/ViewModels/TerriaViewer";
import supportsWebGL from "../../lib/Core/supportsWebGL";

const describeIfSupported = supportsWebGL() ? describe : xdescribe;

Expand Down Expand Up @@ -431,6 +433,58 @@ describeIfSupported("Cesium Model", function () {
);
});
});

describe("getCurrentCameraView", function () {
const rectangleDegrees = ({ west, south, east, north }: Rectangle) => ({
west: CesiumMath.toDegrees(west),
south: CesiumMath.toDegrees(south),
east: CesiumMath.toDegrees(east),
north: CesiumMath.toDegrees(north)
});

it("returns the current camera view", function () {
const cameraView = cesium.getCurrentCameraView();
const { west, south, east, north } = rectangleDegrees(
cameraView.rectangle
);
expect(west).toBeCloseTo(-180);
expect(south).toBeCloseTo(-90);
expect(east).toBeCloseTo(180);
expect(north).toBeCloseTo(90);
});

describe("when initial camera view is set", function () {
const viewRectangle = {
west: 119.04785,
south: -33.6512,
east: 156.31347,
north: -22.83694
};

beforeEach(function () {
const initialView = CameraView.fromJson(viewRectangle);
cesium.setInitialView(initialView);
});

it("returns the initial view", function () {
const r = rectangleDegrees(cesium.getCurrentCameraView().rectangle);
expect(r.west).toBe(viewRectangle.west);
expect(r.south).toBe(viewRectangle.south);
expect(r.east).toBe(viewRectangle.east);
expect(r.north).toBe(viewRectangle.north);
});

it("returns a new view if the camera view changes", async function () {
cesium.scene.camera.changed.raiseEvent(1.0);
const view = cesium.getCurrentCameraView();
const rectangle = rectangleDegrees(view.rectangle);
expect(rectangle.west).not.toBe(119.04785);
expect(rectangle.south).not.toBe(-33.6512);
expect(rectangle.east).not.toBe(156.31347);
expect(rectangle.north).not.toBe(-22.83694);
});
});
});
});

/**
Expand Down
46 changes: 46 additions & 0 deletions test/Models/LeafletSpec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import L from "leaflet";
import { action, computed, when } from "mobx";
import CesiumMath from "terriajs-cesium/Source/Core/Math";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";
import CameraView from "../../lib/Models/CameraView";
import WebMapServiceCatalogItem from "../../lib/Models/Catalog/Ows/WebMapServiceCatalogItem";
import CommonStrata from "../../lib/Models/Definition/CommonStrata";
import createStratumInstance from "../../lib/Models/Definition/createStratumInstance";
Expand Down Expand Up @@ -181,4 +184,47 @@ describe("Leaflet Model", function () {
expect(height).toBe(0);
});
});

describe("getCurrentCameraView", function () {
const rectangleDegrees = ({ west, south, east, north }: Rectangle) => ({
west: CesiumMath.toDegrees(west),
south: CesiumMath.toDegrees(south),
east: CesiumMath.toDegrees(east),
north: CesiumMath.toDegrees(north)
});

describe("when initial camera view is set", function () {
const viewRectangle = {
west: 119.04785,
south: -33.6512,
east: 156.31347,
north: -22.83694
};

beforeEach(function () {
const initialView = CameraView.fromJson(viewRectangle);
leaflet.setInitialView(initialView);
});

it("returns the initial view", function () {
const r = rectangleDegrees(leaflet.getCurrentCameraView().rectangle);
expect(r.west).toBe(viewRectangle.west);
expect(r.south).toBe(viewRectangle.south);
expect(r.east).toBe(viewRectangle.east);
expect(r.north).toBe(viewRectangle.north);
});

it("returns a new view if the camera view changes", async function () {
await leaflet.doZoomTo(
Rectangle.fromDegrees(107.53236, -17.32317, 151.45236, 6.61319)
);
const view = leaflet.getCurrentCameraView();
const rectangle = rectangleDegrees(view.rectangle);
expect(rectangle.west).not.toBe(119.04785);
expect(rectangle.south).not.toBe(-33.6512);
expect(rectangle.east).not.toBe(156.31347);
expect(rectangle.north).not.toBe(-22.83694);
});
});
});
});
25 changes: 23 additions & 2 deletions test/ViewModels/TerriaViewerSpec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import CesiumMath from "terriajs-cesium/Source/Core/Math";
import Terria from "../../lib/Models/Terria";
import ViewerMode, { setViewerMode } from "../../lib/Models/ViewerMode";
import TerriaViewer from "../../lib/ViewModels/TerriaViewer";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";

const mockBeforeViewerChanges = jasmine.createSpy("", () => {});
const mockAfterViewerChanges = jasmine.createSpy("", () => {});
Expand All @@ -16,14 +18,14 @@ describe("TerriaViewer", function () {
});
container = document.createElement("div");
document.body.appendChild(container);
terria.mainViewer.attach(container);
terriaViewer = terria.mainViewer;
terria.loadHomeCamera({
west: 45,
south: -20,
east: 55,
north: -10
});
terria.mainViewer.attach(container);
terriaViewer = terria.mainViewer;

setViewerMode("3d", terriaViewer);

Expand Down Expand Up @@ -70,4 +72,23 @@ describe("TerriaViewer", function () {
expect(terriaViewer.viewerMode).toBe(ViewerMode.Cesium);
});
});

describe("currentViewer", function () {
const rectangleDegrees = ({ west, south, east, north }: Rectangle) => ({
west: CesiumMath.toDegrees(west),
south: CesiumMath.toDegrees(south),
east: CesiumMath.toDegrees(east),
north: CesiumMath.toDegrees(north)
});

it("should return the home camera view", function () {
const r = rectangleDegrees(
terriaViewer.currentViewer.getCurrentCameraView().rectangle
);
expect(r.west).toBe(45);
expect(r.south).toBe(-20);
expect(r.east).toBe(55);
expect(r.north).toBe(-10);
});
});
});
Loading