Skip to content

Commit 82b6e54

Browse files
ZTongciclaude
andcommitted
fix(cesium): suppress Ion logo when no Ion assets are actively rendering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 44c060f commit 82b6e54

6 files changed

Lines changed: 140 additions & 48 deletions

File tree

src/Map/cesiumIonDetection.test.ts

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,31 @@ describe("computeHasCesiumIonAsset", () => {
2020
expect(computeHasCesiumIonAsset({ tiles: [] })).toBe(false);
2121
});
2222

23-
test("returns true for cesium_ion tile type", () => {
23+
test("returns true for cesium_ion tile type with valid assetId", () => {
2424
expect(
25-
computeHasCesiumIonAsset({ tiles: [{ id: "", type: "cesium_ion" }] }),
25+
computeHasCesiumIonAsset(
26+
{ tiles: [{ id: "", type: "cesium_ion", cesiumIonAssetId: 12345 }] },
27+
undefined,
28+
"my-token",
29+
),
2630
).toBe(true);
2731
});
2832

29-
test("returns true for cesium_ion_default tile type", () => {
33+
test("returns true for cesium_ion_default tile type when token present", () => {
3034
expect(
31-
computeHasCesiumIonAsset({
32-
tiles: [{ id: "", type: "cesium_ion_default" }],
33-
}),
35+
computeHasCesiumIonAsset(
36+
{ tiles: [{ id: "", type: "cesium_ion_default" }] },
37+
undefined,
38+
"my-token",
39+
),
3440
).toBe(true);
3541
});
3642

37-
test("returns true for legacy tile types", () => {
38-
for (const type of [
39-
"default",
40-
"default_road",
41-
"default_label",
42-
"black_marble",
43-
]) {
44-
expect(computeHasCesiumIonAsset({ tiles: [{ id: "", type }] })).toBe(
45-
true,
46-
);
43+
test("returns true for legacy tile types when token present", () => {
44+
for (const type of ["default", "default_road", "default_label", "black_marble"]) {
45+
expect(
46+
computeHasCesiumIonAsset({ tiles: [{ id: "", type }] }, undefined, "my-token"),
47+
).toBe(true);
4748
}
4849
});
4950

@@ -65,14 +66,23 @@ describe("computeHasCesiumIonAsset", () => {
6566
).toBe(true);
6667
});
6768

68-
test("returns true for cesiumion terrain type when enabled", () => {
69+
test("returns true for cesiumion terrain type with ionAsset", () => {
6970
expect(
7071
computeHasCesiumIonAsset({
7172
terrain: { enabled: true, type: "cesiumion" },
72-
}),
73+
assets: { cesium: { terrain: { ionAsset: "1" } } },
74+
} as any),
7375
).toBe(true);
7476
});
7577

78+
test("returns false for cesiumion terrain type without ionAsset or ionUrl", () => {
79+
expect(
80+
computeHasCesiumIonAsset({
81+
terrain: { enabled: true, type: "cesiumion" },
82+
}),
83+
).toBe(false);
84+
});
85+
7686
test("returns false for cesium terrain when disabled", () => {
7787
expect(
7888
computeHasCesiumIonAsset({
@@ -221,9 +231,11 @@ describe("computeHasCesiumIonAsset", () => {
221231

222232
test("returns true when only tile uses ion", () => {
223233
expect(
224-
computeHasCesiumIonAsset({ tiles: [{ id: "", type: "default" }] }, [
225-
makeSimple({ type: "geojson" }),
226-
] as any),
234+
computeHasCesiumIonAsset(
235+
{ tiles: [{ id: "", type: "default" }] },
236+
[makeSimple({ type: "geojson" })] as any,
237+
"my-token",
238+
),
227239
).toBe(true);
228240
});
229241

@@ -242,4 +254,44 @@ describe("computeHasCesiumIonAsset", () => {
242254
expect(computeHasCesiumIonAsset(undefined, [])).toBe(false);
243255
});
244256
});
257+
258+
describe("token and assetId gating", () => {
259+
test("returns false for cesium_ion tile with no cesiumIonAssetId", () => {
260+
expect(
261+
computeHasCesiumIonAsset(
262+
{ tiles: [{ id: "", type: "cesium_ion" }] },
263+
undefined,
264+
"my-token",
265+
),
266+
).toBe(false);
267+
});
268+
269+
test("returns false for cesium_ion tile with assetId of 0", () => {
270+
expect(
271+
computeHasCesiumIonAsset(
272+
{ tiles: [{ id: "", type: "cesium_ion", cesiumIonAssetId: 0 }] },
273+
undefined,
274+
"my-token",
275+
),
276+
).toBe(false);
277+
});
278+
279+
test("returns false for cesium_ion_default when no token", () => {
280+
expect(
281+
computeHasCesiumIonAsset({ tiles: [{ id: "", type: "cesium_ion_default" }] }),
282+
).toBe(false);
283+
});
284+
285+
test("returns false for legacy tile types when no token", () => {
286+
for (const type of ["default", "default_road", "default_label", "black_marble"]) {
287+
expect(computeHasCesiumIonAsset({ tiles: [{ id: "", type }] })).toBe(false);
288+
}
289+
});
290+
291+
test("returns true for cesium_ion with valid assetId (token gate is at engine level)", () => {
292+
expect(
293+
computeHasCesiumIonAsset({ tiles: [{ id: "", type: "cesium_ion", cesiumIonAssetId: 2275207 }] }),
294+
).toBe(true);
295+
});
296+
});
245297
});

src/Map/cesiumIonDetection.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,28 @@ function isIonUrl(url?: string | null): boolean {
1414
return !!url && url.includes(CESIUM_ION_URL_PATTERN);
1515
}
1616

17-
function tileUsesIon(tile: TileProperty): boolean {
17+
function tileUsesIon(tile: TileProperty, accessToken?: string): boolean {
1818
if (!tile.type) return false;
19-
if (tile.type.startsWith("cesium_ion")) return true;
20-
if (CESIUM_ION_LEGACY_TILE_TYPES.has(tile.type)) return true;
19+
if (tile.type === "cesium_ion") {
20+
const id = tile.cesiumIonAssetId;
21+
return !!id && !isNaN(parseInt(String(id), 10));
22+
}
23+
if (tile.type.startsWith("cesium_ion")) return !!accessToken;
24+
if (CESIUM_ION_LEGACY_TILE_TYPES.has(tile.type)) return !!accessToken;
2125
return false;
2226
}
2327

2428
function terrainUsesIon(property?: ViewerProperty): boolean {
2529
const terrain = property?.terrain;
2630
if (!terrain?.enabled) return false;
27-
if (terrain.type === "cesium" || terrain.type === "cesiumion") return true;
31+
if (terrain.type === "cesium") return true;
32+
if (terrain.type === "cesiumion") {
33+
// Mirrors useTerrainProviderPromise: returns EllipsoidTerrainProvider when neither is set.
34+
return !!(
35+
property?.assets?.cesium?.terrain?.ionAsset ||
36+
isIonUrl(property?.assets?.cesium?.terrain?.ionUrl)
37+
);
38+
}
2839
if (isIonUrl(property?.assets?.cesium?.terrain?.ionUrl)) return true;
2940
return false;
3041
}
@@ -47,8 +58,12 @@ function anyLayerUsesIon(layer: Layer): boolean {
4758
return layerUsesIon(layer);
4859
}
4960

50-
export function computeHasCesiumIonAsset(property?: ViewerProperty, layers?: Layer[]): boolean {
51-
if (property?.tiles?.some(tileUsesIon)) return true;
61+
export function computeHasCesiumIonAsset(
62+
property?: ViewerProperty,
63+
layers?: Layer[],
64+
accessToken?: string,
65+
): boolean {
66+
if (property?.tiles?.some(tile => tileUsesIon(tile, accessToken))) return true;
5267
if (terrainUsesIon(property)) return true;
5368
if (layers?.some(anyLayerUsesIon)) return true;
5469
return false;

src/Map/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,13 @@ function MapFn(
106106
onAPIReady,
107107
});
108108

109-
const hasCesiumIonAsset = useMemo(
110-
() => computeHasCesiumIonAsset(props.property, layers),
111-
[props.property, layers],
112-
);
109+
const hasCesiumIonAsset = useMemo(() => {
110+
const token =
111+
typeof props.meta?.cesiumIonAccessToken === "string" && props.meta.cesiumIonAccessToken
112+
? (props.meta.cesiumIonAccessToken as string)
113+
: undefined;
114+
return computeHasCesiumIonAsset(props.property, layers, token);
115+
}, [props.property, layers, props.meta]);
113116

114117
const selectedLayerIds = useMemo(
115118
() => ({

src/engines/Cesium/common.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -914,24 +914,33 @@ export function getCredits(viewer: Viewer, hasCesiumIonAsset?: boolean) {
914914
screenCredits: { _array: { credit?: CesiumCredit }[] };
915915
};
916916
_currentCesiumCredit: CesiumCredit;
917+
// _cesiumCredit is the per-instance clone that beginFrame() resets _currentCesiumCredit to
918+
// each frame. addCreditToNextFrame(ionCredit) sets _currentCesiumCredit to _defaultCredit
919+
// (a different object) when Ion tiles are actually rendering — see GlobeSurfaceTileProvider
920+
// updateCredits(). If they are equal, no Ion tile rendered in the most-recent frame.
921+
_cesiumCredit: CesiumCredit;
917922
})
918923
| undefined;
919924

920925
if (!creditDisplay) return emptyCredites;
921926

922927
const { lightboxCredits, screenCredits } = creditDisplay?._currentFrameCredits || {};
923-
const cesiumCredits = creditDisplay._currentCesiumCredit;
928+
const currentCesiumCredit = creditDisplay._currentCesiumCredit;
929+
const staticCesiumCredit = creditDisplay._cesiumCredit;
930+
931+
// Ion tiles are truly rendering when _currentCesiumCredit !== _cesiumCredit.
932+
// beginFrame() resets _currentCesiumCredit = _cesiumCredit each frame; only
933+
// addCreditToNextFrame() (called for every ready+visible Ion layer) overrides it.
934+
// hasCesiumIonAsset === false is a fast-path: skip the check when Ion is definitely absent.
935+
const ionIsRendering =
936+
hasCesiumIonAsset !== false &&
937+
currentCesiumCredit !== undefined &&
938+
staticCesiumCredit !== undefined &&
939+
currentCesiumCredit !== staticCesiumCredit;
924940

925941
const credits: Credits = {
926942
engine: {
927-
// Only include Cesium-ion credit when Ion assets are actually in use.
928-
// hasCesiumIonAsset === false means explicitly no Ion assets; undefined preserves existing behavior.
929-
cesium:
930-
hasCesiumIonAsset === false
931-
? undefined
932-
: cesiumCredits?.html
933-
? { html: cesiumCredits.html }
934-
: undefined,
943+
cesium: ionIsRendering ? { html: currentCesiumCredit.html } : undefined,
935944
},
936945
lightbox: Array.from(lightboxCredits?._array ?? []).map(c => ({
937946
html: c?.credit?.html,

src/engines/Cesium/core/Globe/index.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Globe as CesiumGlobeType } from "cesium";
1+
import { EllipsoidTerrainProvider, type Globe as CesiumGlobeType } from "cesium";
22
import { useEffect, useMemo, useRef, type JSX } from "react";
33
import { Globe as CesiumGlobe, type CesiumComponentRef } from "resium";
44

@@ -48,7 +48,13 @@ export default function Globe({
4848
}
4949
})
5050
.catch(() => {
51-
// provider errors are handled by the existing useEffect below
51+
// Fall back to flat terrain so stale Ion terrain doesn't persist and mislead
52+
// the runtime Ion-credit check.
53+
if (cancelled) return;
54+
const cesiumGlobe = cesiumGlobeRef.current?.cesiumElement;
55+
if (cesiumGlobe) {
56+
cesiumGlobe.terrainProvider = new EllipsoidTerrainProvider();
57+
}
5258
});
5359
return () => {
5460
cancelled = true;
@@ -68,7 +74,10 @@ export default function Globe({
6874
}
6975
})
7076
.catch(error => {
71-
if (!isCancelled) console.warn("Terrain provider failed to load:", error);
77+
if (isCancelled) return;
78+
console.warn("Terrain provider failed to load:", error);
79+
// Notify so the engine re-evaluates credits even when terrain fails.
80+
onTerrainProviderChange?.();
7281
});
7382

7483
return () => {

src/engines/Cesium/hooks.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ interface CustomGlobeSurface {
6363
type CesiumMouseEvent = (movement: CesiumMovementEvent, target: RootEventTarget) => void;
6464
type CesiumMouseWheelEvent = (delta: number) => void;
6565

66+
6667
export default ({
6768
ref,
6869
property,
@@ -143,8 +144,10 @@ export default ({
143144
? meta.cesiumIonAccessToken
144145
: undefined;
145146

146-
const hasCesiumIonAssetRef = useRef(hasCesiumIonAsset);
147-
hasCesiumIonAssetRef.current = hasCesiumIonAsset;
147+
const effectiveHasCesiumIonAsset = hasCesiumIonAsset && !!cesiumIonAccessToken;
148+
const hasCesiumIonAssetRef = useRef(effectiveHasCesiumIonAsset);
149+
hasCesiumIonAssetRef.current = effectiveHasCesiumIonAsset;
150+
148151

149152
// expose ref
150153
const engineAPI = useEngineRef(ref, cesium, hasCesiumIonAssetRef);
@@ -665,9 +668,10 @@ export default ({
665668
onCreditsUpdateRef.current = onCreditsUpdate;
666669
const updateCredits = useCallback(() => {
667670
if (!onCreditsUpdateRef.current) return;
668-
// currently we don't have a proper way to get the credits update event
669-
// wait for 3 seconds to get latest credits
670-
// some internal property is been used here.
671+
// Wait for tiles to load/render before checking credits. Cesium's GlobeSurfaceTileProvider
672+
// calls addCreditToNextFrame() each frame for ready+visible Ion layers, which sets
673+
// _currentCesiumCredit !== _cesiumCredit — that comparison is the actual Ion-rendering check
674+
// inside getCredits(). 3 s gives tiles enough time to reach that state.
671675
setTimeout(() => {
672676
if (!onCreditsUpdateRef.current) return;
673677
const viewer = cesium.current?.cesiumElement;

0 commit comments

Comments
 (0)