Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ebc492b
Fix @mapbox/togeojson typing.
na9da May 5, 2025
7990380
Extract CrsTraits and parse CRS bounding boxes for WMS layers.
na9da May 12, 2025
2103be4
Add customizable TilingSchemeGenerator for use with plugins.
na9da May 12, 2025
7c6220b
Add method to return imagery provider for preview maps with custom CRS.
na9da May 12, 2025
462a848
Create PreviewViewer class to use with preview maps.
na9da May 12, 2025
2e1a137
Track initial camera view for viewers and return it if the camera has…
na9da May 12, 2025
91265c8
Create camera view from polygon extent.
na9da May 12, 2025
69a112a
Make Leaflet accept initial options parameter.
na9da May 12, 2025
692473f
Make _makeImageryLayerFromParts protected.
na9da May 12, 2025
138708b
Add preferredViewerMode for basemaps and track loading basemap.
na9da May 12, 2025
a013ab9
Load persisted basemap only when the initsource basemap data fails to…
na9da May 12, 2025
75d0cab
Make Leaflet, Cesium loaders overridable.
na9da May 12, 2025
6bc80f9
Add hook to inject custom UI into map settings panel.
na9da May 12, 2025
321f88d
Upgrade @types/leaflet.
na9da May 12, 2025
b73b360
Temporarily use terriamap#proj4leaflet for CI.
na9da May 12, 2025
e8698b7
Fix lint errors.
na9da May 12, 2025
60f7fa2
Merge branch 'main' into proj4leaflet
na9da May 28, 2025
fc7d196
Fix specs.
na9da May 28, 2025
533a8ac
Merge branch 'main' into proj4leaflet
na9da Aug 12, 2025
a535eec
UI feedback changes for custom projection.
na9da Aug 15, 2025
8504d1a
Simplify toast notification.
na9da Aug 18, 2025
7d5d18e
Set leaflet container background color from base map settings.
na9da Aug 18, 2025
5c0b76d
Fix lint.
na9da Aug 18, 2025
fae22ff
Fix type.
na9da Aug 18, 2025
6fded2c
Fix import.
na9da Aug 18, 2025
ff5561b
Use SlideUpFadeIn.
na9da Aug 19, 2025
4afef3c
Save base map preference on viewer switch.
na9da Aug 19, 2025
5cae1da
Add <settingpanel> link component.
na9da Aug 19, 2025
dae68df
Fix imports.
na9da Aug 19, 2025
3c0c48f
Customizable workbench controls.
na9da Aug 21, 2025
6113853
Fix CatalogMemberMixin import.
na9da Aug 21, 2025
e47eef0
Merge branch 'main' into proj4leaflet
na9da Oct 20, 2025
a2974bc
Enable/disable workbench controls via traits.
na9da Oct 23, 2025
dc49215
Lint fix.
na9da Oct 23, 2025
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
2 changes: 1 addition & 1 deletion buildprocess/ci-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ npm install -g yarn@^1.22.22

# Clone and build TerriaMap, using this version of TerriaJS
TERRIAJS_COMMIT_HASH=$(git rev-parse HEAD)
git clone -b svg-sprite-plugin-ts-592 https://github.com/TerriaJS/TerriaMap.git
git clone -b proj4leaflet https://github.com/TerriaJS/TerriaMap.git
cd TerriaMap
TERRIAMAP_COMMIT_HASH=$(git rev-parse HEAD)
sed -i -e 's@"terriajs": ".*"@"terriajs": "'$GITHUB_REPOSITORY'#'${GITHUB_BRANCH}'"@g' package.json
Expand Down
72 changes: 72 additions & 0 deletions lib/Map/ImageryProvider/TilingSchemeGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { observable } from "mobx";
import GeographicTilingScheme from "terriajs-cesium/Source/Core/GeographicTilingScheme";
import TilingScheme from "terriajs-cesium/Source/Core/TilingScheme";
import WebMercatorTilingScheme from "terriajs-cesium/Source/Core/WebMercatorTilingScheme";
import {
SUPPORTED_CRS_3857,
SUPPORTED_CRS_4326
} from "../../Traits/TraitsClasses/CrsTraits";

export interface TerriaTilingScheme extends TilingScheme {
// Custom tiling implementations can specify its CRS
customCrs?: string;
}

export type TilingSchemeGeneratorFunction = (
crs: string | undefined
) => TerriaTilingScheme | undefined;

/**
* The default tiling scheme generator
*/
const defaultTilingSchemeGenerator = (crs: string | undefined) => {
if (crs) {
if (SUPPORTED_CRS_3857.includes(crs)) return new WebMercatorTilingScheme();
if (SUPPORTED_CRS_4326.includes(crs)) return new GeographicTilingScheme();
}

return new WebMercatorTilingScheme();
};

export default class TilingSchemeGenerator {
private static readonly _generators = observable.map<
string,
TilingSchemeGeneratorFunction
>();

static readonly default = defaultTilingSchemeGenerator;

/**
* Register a custom tiling scheme generator
*/
static register(
generatorName: string,
generator: TilingSchemeGeneratorFunction
) {
this._generators.set(generatorName, generator);
}

/**
* Get a custom tiling scheme generator by its registered name
*/
static get(generatorName: string): TilingSchemeGeneratorFunction | undefined {
return this._generators.get(generatorName);
}

/**
* Return the CRS of the tiling scheme if it is known
*/
static getCustomCrs(tilingScheme: TerriaTilingScheme): string | undefined {
return tilingScheme?.customCrs;
}

/**
* Returns true if the tiling scheme is custom
*/
static isCustomTilingScheme(tilingScheme: TilingScheme): boolean {
return !(
tilingScheme instanceof WebMercatorTilingScheme ||
tilingScheme instanceof GeographicTilingScheme
);
}
}
63 changes: 36 additions & 27 deletions lib/Map/Leaflet/ImageryProviderLeafletTileLayer.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import i18next from "i18next";
import L, { TileEvent } from "leaflet";
import {
IReactionDisposer,
autorun,
computed,
IReactionDisposer,
observable,
makeObservable
makeObservable,
observable
} from "mobx";
import Cartesian2 from "terriajs-cesium/Source/Core/Cartesian2";
import Cartographic from "terriajs-cesium/Source/Core/Cartographic";
import CesiumCredit from "terriajs-cesium/Source/Core/Credit";
import defined from "terriajs-cesium/Source/Core/defined";
import CesiumEvent from "terriajs-cesium/Source/Core/Event";
import CesiumMath from "terriajs-cesium/Source/Core/Math";
import TileProviderError from "terriajs-cesium/Source/Core/TileProviderError";
import WebMercatorTilingScheme from "terriajs-cesium/Source/Core/WebMercatorTilingScheme";
import defined from "terriajs-cesium/Source/Core/defined";
import ImageryLayerFeatureInfo from "terriajs-cesium/Source/Scene/ImageryLayerFeatureInfo";
import ImageryProvider from "terriajs-cesium/Source/Scene/ImageryProvider";
import SplitDirection from "terriajs-cesium/Source/Scene/SplitDirection";
import isDefined from "../../Core/isDefined";
import TerriaError from "../../Core/TerriaError";
import isDefined from "../../Core/isDefined";
import Leaflet from "../../Models/Leaflet";
import TilingSchemeGenerator from "../ImageryProvider/TilingSchemeGenerator";
import getUrlForImageryTile from "../ImageryProvider/getUrlForImageryTile";
import { ProviderCoords } from "../PickedFeatures/PickedFeatures";

Expand Down Expand Up @@ -243,28 +244,37 @@ export default class ImageryProviderLeafletTileLayer extends L.TileLayer {
}

const tilingScheme = this.imageryProvider.tilingScheme;
if (!(tilingScheme instanceof WebMercatorTilingScheme)) {
this.errorEvent.raiseEvent(
this,
i18next.t("map.cesium.notWebMercatorTilingScheme")
);
return;
}
const isCustomTilingScheme =
TilingSchemeGenerator.isCustomTilingScheme(tilingScheme);

if (
tilingScheme.getNumberOfXTilesAtLevel(0) === 2 &&
tilingScheme.getNumberOfYTilesAtLevel(0) === 2
) {
this._zSubtract = 1;
} else if (
tilingScheme.getNumberOfXTilesAtLevel(0) !== 1 ||
tilingScheme.getNumberOfYTilesAtLevel(0) !== 1
) {
this.errorEvent.raiseEvent(
this,
i18next.t("map.cesium.unusalTilingScheme")
);
return;
if (!isCustomTilingScheme) {
// These checks apply only for Geographic or WebMercator tiling schemes

if (!(tilingScheme instanceof WebMercatorTilingScheme)) {
this.errorEvent.raiseEvent(
this,
i18next.t("map.cesium.notWebMercatorTilingScheme")
);
return;
}

if (
tilingScheme.getNumberOfXTilesAtLevel(0) === 2 &&
tilingScheme.getNumberOfYTilesAtLevel(0) === 2
) {
// This min zoom level adjustment is for Bing maps which has two
// tiles at the root level instead of a single tile.
this._zSubtract = 1;
} else if (
tilingScheme.getNumberOfXTilesAtLevel(0) !== 1 ||
tilingScheme.getNumberOfYTilesAtLevel(0) !== 1
) {
this.errorEvent.raiseEvent(
this,
i18next.t("map.cesium.unusalTilingScheme")
);
return;
}
}

if (isDefined(this.imageryProvider.maximumLevel)) {
Expand All @@ -282,7 +292,6 @@ export default class ImageryProviderLeafletTileLayer extends L.TileLayer {
}

this._usable = true;

this._update();
}, this._leafletUpdateInterval) as any;
}
Expand Down
26 changes: 18 additions & 8 deletions lib/ModelMixins/MappableMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export class ImageryParts {
clippingRectangle: Rectangle | undefined = undefined;
show: boolean = true;

/*
* An optional method that returns an ImageryProvider instance for the
* preview map which may use a CRS and tiling scheme different to the
* default imageryprovider used for the main map.
*
* This is currently only used by plugins to customize preview map behaviour.
* (eg terriajs-plugin-proj4leaflet)
*/
previewImageryProvider?: (crs: string) => ImageryProvider | undefined;

static fromAsync(options: {
imageryProviderPromise: Promise<ImageryProvider | undefined>;
alpha?: number;
Expand Down Expand Up @@ -171,13 +181,6 @@ function MappableMixin<T extends AbstractConstructor<BaseType>>(Base: T) {
*/
async loadMapItems(force?: boolean): Promise<Result<void>> {
try {
runInAction(() => {
if (this.shouldShowInitialMessage) {
// Don't await the initialMessage because this causes cyclic dependency between loading
// and user interaction (see https://github.com/TerriaJS/terriajs/issues/5528)
this.showInitialMessage();
}
});
if (CatalogMemberMixin.isMixedInto(this))
(await this.loadMetadata()).throwIfError();

Expand Down Expand Up @@ -225,8 +228,15 @@ function MappableMixin<T extends AbstractConstructor<BaseType>>(Base: T) {
: undefined,
message: this.initialMessage.content ?? "",
key: "initialMessage:" + this.initialMessage.key,
confirmAction: () => resolve()
confirmAction: () => resolve(),
showAsToast: this.initialMessage.showAsToast,
toastVisibleDuration: this.initialMessage.toastVisibleDuration
});

// No need to wait for confirmation if the message is a toast
if (this.initialMessage.showAsToast) {
resolve();
}
});
}

Expand Down
2 changes: 2 additions & 0 deletions lib/Models/BaseMaps/BaseMapsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type BaseMapsJson = Partial<
export interface BaseMapItem {
image?: string;
contrastColor?: string;
backgroundColor?: string;
item: MappableMixin.Instance;
}

Expand Down Expand Up @@ -65,6 +66,7 @@ export class BaseMapsModel extends CreateModel(BaseMapsTraits) {
enabledBaseMaps.push({
image: baseMapItem.image,
contrastColor: baseMapItem.contrastColor,
backgroundColor: baseMapItem.backgroundColor,
item: itemModel
});
}
Expand Down
68 changes: 60 additions & 8 deletions lib/Models/CameraView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import JsonValue, {
JsonObject
} from "../Core/Json";
import TerriaError from "../Core/TerriaError";
import filterOutUndefined from "../Core/filterOutUndefined";

/**
* Holds a camera's view parameters, expressed as a rectangular extent and/or as a camera position, direction,
Expand All @@ -30,6 +31,13 @@ export default class CameraView {
*/
readonly rectangle: Readonly<Rectangle>;

/**
* Points describing a bounding box. This is for example useful to describe
* extent that crosses the poles. We need at least 4 lat lon points to
* accurately describe them.
*/
readonly extent: Readonly<Cartographic[]> | undefined;

/**
* Gets the position of the camera in the Earth-centered Fixed frame.
*/
Expand All @@ -49,7 +57,8 @@ export default class CameraView {
rectangle: Rectangle,
position?: Cartesian3,
direction?: Cartesian3,
up?: Cartesian3
up?: Cartesian3,
extent?: Cartographic[]
) {
this.rectangle = Rectangle.clone(rectangle);

Expand All @@ -68,6 +77,7 @@ export default class CameraView {
this.direction = Cartesian3.clone(direction);
this.up = Cartesian3.clone(up);
}
this.extent = extent;
}

toJson(): JsonObject {
Expand All @@ -92,6 +102,14 @@ export default class CameraView {
result.up = vectorToJson(this.up);
}

if (this.extent) {
// TODO: if extent is given, derive south,west,east,north from extent?
result.extent = this.extent.map((c) => ({
latitude: CesiumMath.toDegrees(c.latitude),
longitude: CesiumMath.toDegrees(c.longitude)
}));
}

return result;
}

Expand Down Expand Up @@ -192,20 +210,39 @@ export default class CameraView {
json.north
);

let extent: Cartographic[] | undefined;
if (Array.isArray(json.extent)) {
extent = filterOutUndefined(
json.extent.map((ll) =>
isJsonObject(ll) &&
typeof ll.longitude === "number" &&
typeof ll.latitude === "number"
? Cartographic.fromDegrees(ll.longitude, ll.latitude)
: undefined
)
);
}

let position, direction, up;
if (
isVector(json.position) &&
isVector(json.direction) &&
isVector(json.up)
) {
return new CameraView(
rectangle,
new Cartesian3(json.position.x, json.position.y, json.position.z),
new Cartesian3(json.direction.x, json.direction.y, json.direction.z),
new Cartesian3(json.up.x, json.up.y, json.up.z)
position = new Cartesian3(
json.position.x,
json.position.y,
json.position.z
);
} else {
return new CameraView(rectangle);
direction = new Cartesian3(
json.direction.x,
json.direction.y,
json.direction.z
);
up = new Cartesian3(json.up.x, json.up.y, json.up.z);
}

return new CameraView(rectangle, position, direction, up, extent);
}
}

Expand Down Expand Up @@ -348,6 +385,21 @@ export default class CameraView {

return new CameraView(extent, positionECF, directionECF, upECF);
}

/**
* Create a camera view from polygon extent
*/
static fromExtent(extent: Cartographic[]) {
const positions = extent.map((c) => Cartographic.toCartesian(c));
const cameraView = new CameraView(
Rectangle.fromCartesianArray(positions),
undefined,
undefined,
undefined,
extent
);
return cameraView;
}
}

function isVector(
Expand Down
Loading
Loading