Skip to content
245 changes: 245 additions & 0 deletions src/Map/cesiumIonDetection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import { describe, expect, test } from "vitest";

import { computeHasCesiumIonAsset } from "./cesiumIonDetection";

const makeSimple = (data?: object) => ({
id: "layer1",
type: "simple" as const,
...(data ? { data } : {}),
});

const makeGroup = (children: object[]) => ({
id: "group1",
type: "group" as const,
children,
});

describe("computeHasCesiumIonAsset", () => {
describe("tiles", () => {
test("returns false when no tiles", () => {
expect(computeHasCesiumIonAsset({ tiles: [] })).toBe(false);
});

test("returns true for cesium_ion tile type", () => {
expect(

Check warning on line 24 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎········computeHasCesiumIonAsset({·tiles:·[{·id:·"",·type:·"cesium_ion"·}]·}),⏎······` with `computeHasCesiumIonAsset({·tiles:·[{·id:·"",·type:·"cesium_ion"·}]·})`
computeHasCesiumIonAsset({ tiles: [{ id: "", type: "cesium_ion" }] }),
).toBe(true);
});

test("returns true for cesium_ion_default tile type", () => {
expect(
computeHasCesiumIonAsset({
tiles: [{ id: "", type: "cesium_ion_default" }],
}),
).toBe(true);
});

test("returns true for legacy tile types", () => {
for (const type of [

Check warning on line 38 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎········"default",⏎········"default_road",⏎········"default_label",⏎········"black_marble",⏎······` with `"default",·"default_road",·"default_label",·"black_marble"`
"default",
"default_road",
"default_label",
"black_marble",
]) {
expect(computeHasCesiumIonAsset({ tiles: [{ id: "", type }] })).toBe(

Check warning on line 44 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎··········true,⏎········` with `true`
true,
);
}
});

test("returns false for non-ion tile type", () => {
expect(
computeHasCesiumIonAsset({
tiles: [{ id: "", type: "open_street_map" }],
}),
).toBe(false);
});
});

describe("terrain", () => {
test("returns true for cesium terrain type when enabled", () => {
expect(
computeHasCesiumIonAsset({
terrain: { enabled: true, type: "cesium" },
}),
).toBe(true);
});

test("returns true for cesiumion terrain type when enabled", () => {
expect(
computeHasCesiumIonAsset({
terrain: { enabled: true, type: "cesiumion" },
}),
).toBe(true);
});

test("returns false for cesium terrain when disabled", () => {
expect(
computeHasCesiumIonAsset({
terrain: { enabled: false, type: "cesium" },
}),
).toBe(false);
});

test("returns true for ion terrain URL", () => {
expect(
computeHasCesiumIonAsset({
terrain: { enabled: true, type: "url" },
assets: {
cesium: {
terrain: {
ionUrl: "https://assets.ion.cesium.com/1/tileset.json",
},
},
},
} as any),
).toBe(true);
});

test("returns false for non-ion terrain URL", () => {
expect(
computeHasCesiumIonAsset({
terrain: { enabled: true, type: "url" },
assets: {
cesium: { terrain: { ionUrl: "https://example.com/terrain" } },
},
} as any),
).toBe(false);
});
});

describe("layers", () => {
test("returns true for osm-buildings layer", () => {
expect(
computeHasCesiumIonAsset(undefined, [

Check warning on line 114 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎··········makeSimple({·type:·"osm-buildings"·}),⏎········` with `makeSimple({·type:·"osm-buildings"·})`
makeSimple({ type: "osm-buildings" }),
] as any),
).toBe(true);
});

test("returns true for google-photorealistic with cesium-ion provider", () => {
expect(
computeHasCesiumIonAsset(undefined, [
makeSimple({ type: "google-photorealistic", provider: "cesium-ion" }),
] as any),
).toBe(true);
});

test("returns false for google-photorealistic with reearth provider", () => {
expect(
computeHasCesiumIonAsset(undefined, [
makeSimple({ type: "google-photorealistic", provider: "reearth" }),
] as any),
).toBe(false);
});

test("returns false for google-photorealistic with no provider (google API path)", () => {
expect(
computeHasCesiumIonAsset(undefined, [

Check warning on line 138 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎··········makeSimple({·type:·"google-photorealistic"·}),⏎········` with `makeSimple({·type:·"google-photorealistic"·})`
makeSimple({ type: "google-photorealistic" }),
] as any),
).toBe(false);
});

test("returns true for layer with ion URL", () => {
expect(
computeHasCesiumIonAsset(undefined, [
makeSimple({
type: "3dtiles",
url: "https://assets.ion.cesium.com/123/tileset.json",
}),
] as any),
).toBe(true);
});

test("returns true for any layer type with ion URL", () => {
expect(
computeHasCesiumIonAsset(undefined, [
makeSimple({
type: "geojson",
url: "https://assets.ion.cesium.com/456/data.json",
}),
] as any),
).toBe(true);
});

test("returns false for layer with non-ion URL", () => {
expect(
computeHasCesiumIonAsset(undefined, [
makeSimple({
type: "3dtiles",
url: "https://example.com/tileset.json",
}),
] as any),
).toBe(false);
});

test("returns false for layer with no data", () => {
expect(computeHasCesiumIonAsset(undefined, [makeSimple()] as any)).toBe(

Check warning on line 178 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎········false,⏎······` with `false`
false,
);
});
});

describe("layer groups (recursion)", () => {
test("returns true when nested layer uses ion", () => {
const group = makeGroup([makeSimple({ type: "osm-buildings" })]);
expect(computeHasCesiumIonAsset(undefined, [group] as any)).toBe(true);
});

test("returns false when nested layer does not use ion", () => {
const group = makeGroup([
makeSimple({ type: "geojson", url: "https://example.com/data.json" }),
]);
expect(computeHasCesiumIonAsset(undefined, [group] as any)).toBe(false);
});

test("returns true when deeply nested layer uses ion", () => {
const inner = makeGroup([makeSimple({ type: "osm-buildings" })]);
const outer = makeGroup([inner]);
expect(computeHasCesiumIonAsset(undefined, [outer] as any)).toBe(true);
});
});

describe("combined", () => {
test("returns false when nothing uses ion", () => {
expect(
computeHasCesiumIonAsset(
{
tiles: [{ id: "", type: "open_street_map" }],
terrain: { enabled: false, type: "cesium" },
},
[
makeSimple({
type: "geojson",
url: "https://example.com/data.json",
}),
] as any,
),
).toBe(false);
});

test("returns true when only tile uses ion", () => {
expect(
computeHasCesiumIonAsset({ tiles: [{ id: "", type: "default" }] }, [
makeSimple({ type: "geojson" }),
] as any),
).toBe(true);
});

test("returns true when only terrain uses ion", () => {
expect(
computeHasCesiumIonAsset(

Check warning on line 232 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `⏎··········{·terrain:·{·enabled:·true,·type:·"cesium"·}·},` with `{·terrain:·{·enabled:·true,·type:·"cesium"·}·},·[`
{ terrain: { enabled: true, type: "cesium" } },
[makeSimple({ type: "geojson" })] as any,

Check warning on line 234 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Replace `[makeSimple({·type:·"geojson"·})]·as·any` with `makeSimple({·type:·"geojson"·})`
),

Check warning on line 235 in src/Map/cesiumIonDetection.test.ts

View workflow job for this annotation

GitHub Actions / Lint and Test

Insert `]·as·any`
).toBe(true);
});

test("returns undefined-safe (no property, no layers)", () => {
expect(computeHasCesiumIonAsset()).toBe(false);
expect(computeHasCesiumIonAsset(undefined, undefined)).toBe(false);
expect(computeHasCesiumIonAsset(undefined, [])).toBe(false);
});
});
});
55 changes: 55 additions & 0 deletions src/Map/cesiumIonDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Layer, LayerSimple } from "../mantle";

import type { TileProperty, ViewerProperty } from "./types/viewerProperty";

const CESIUM_ION_URL_PATTERN = "ion.cesium.com";
const CESIUM_ION_LEGACY_TILE_TYPES = new Set([
"default",
"default_road",
"default_label",
"black_marble",
]);

function isIonUrl(url?: string | null): boolean {
return !!url && url.includes(CESIUM_ION_URL_PATTERN);
}

function tileUsesIon(tile: TileProperty): boolean {
if (!tile.type) return false;
if (tile.type.startsWith("cesium_ion")) return true;
if (CESIUM_ION_LEGACY_TILE_TYPES.has(tile.type)) return true;
return false;
}

function terrainUsesIon(property?: ViewerProperty): boolean {
const terrain = property?.terrain;
if (!terrain?.enabled) return false;
if (terrain.type === "cesium" || terrain.type === "cesiumion") return true;
if (isIonUrl(property?.assets?.cesium?.terrain?.ionUrl)) return true;
return false;
}

function layerUsesIon(layer: LayerSimple): boolean {
const data = layer.data;
if (!data) return false;
if (data.type === "osm-buildings") return true;
if (data.type === "google-photorealistic") {
return data.provider === "cesium-ion";
}
Comment thread
ZTongci marked this conversation as resolved.
if (isIonUrl(data.url)) return true;
return false;
}

function anyLayerUsesIon(layer: Layer): boolean {
if (layer.type === "group") {
return layer.children.some(anyLayerUsesIon);
}
return layerUsesIon(layer);
}

export function computeHasCesiumIonAsset(property?: ViewerProperty, layers?: Layer[]): boolean {
if (property?.tiles?.some(tileUsesIon)) return true;
if (terrainUsesIon(property)) return true;
if (layers?.some(anyLayerUsesIon)) return true;
return false;
Comment thread
ZTongci marked this conversation as resolved.
}
14 changes: 12 additions & 2 deletions src/Map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { forwardRef, useMemo, type Ref, type JSX } from "react";

import { INTERACTION_MODES } from "../shared/interactionMode";

import { computeHasCesiumIonAsset } from "./cesiumIonDetection";
import Geoid from "./Geoid";
import useHooks, { MapRef } from "./hooks";
import Layers, { type Props as LayersProps } from "./Layers";
Expand Down Expand Up @@ -44,7 +45,10 @@ export type Props = {
| "selectedLayerId"
| "viewerProperty"
> &
Omit<EngineProps, "onLayerSelect" | "layerSelectionReason" | "selectedLayerId"> &
Omit<
EngineProps,
"onLayerSelect" | "layerSelectionReason" | "selectedLayerId" | "hasCesiumIonAsset"
> &
Omit<SketchProps, "layersRef" | "engineRef" | "SketchComponent">;

function MapFn(
Expand Down Expand Up @@ -102,6 +106,11 @@ function MapFn(
onAPIReady,
});

const hasCesiumIonAsset = useMemo(
() => computeHasCesiumIonAsset(props.property, layers),
[props.property, layers],
);

const selectedLayerIds = useMemo(
() => ({
layerId: selectedLayer.layerId,
Expand All @@ -125,7 +134,8 @@ function MapFn(
onLayerSelect={handleEngineLayerSelect}
featureFlags={featureFlags}
onMount={handleEngineMount}
{...props}>
{...props}
hasCesiumIonAsset={hasCesiumIonAsset}>
<Layers
ref={layersRef}
engineRef={engineRef}
Expand Down
1 change: 1 addition & 0 deletions src/Map/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export type EngineProps = {
meta?: Record<string, unknown>;
customProvider?: CustomProviderConfig;
displayCredits?: boolean;
hasCesiumIonAsset?: boolean;
layersRef?: RefObject<LayersRef | null>;
requestingRenderMode?: MutableRefObject<RequestingRenderMode>;
timelineManagerRef?: TimelineManagerRef;
Expand Down
11 changes: 9 additions & 2 deletions src/engines/Cesium/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ export function getExtrudedHeight(
return;
}

export function getCredits(viewer: Viewer) {
export function getCredits(viewer: Viewer, hasCesiumIonAsset?: boolean) {
if (!viewer) return emptyCredites;
const creditDisplay = viewer.creditDisplay as
| (CreditDisplay & {
Expand All @@ -924,7 +924,14 @@ export function getCredits(viewer: Viewer) {

const credits: Credits = {
engine: {
cesium: cesiumCredits?.html ? { html: cesiumCredits.html } : undefined,
// Only include Cesium-ion credit when Ion assets are actually in use.
// hasCesiumIonAsset === false means explicitly no Ion assets; undefined preserves existing behavior.
cesium:
hasCesiumIonAsset === false
? undefined
: cesiumCredits?.html
? { html: cesiumCredits.html }
: undefined,
},
lightbox: Array.from(lightboxCredits?._array ?? []).map(c => ({
html: c?.credit?.html,
Expand Down
Loading
Loading