@reearth/core 0.0.7-alpha.76 → 0.0.7-alpha.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reearth/core",
3
- "version": "0.0.7-alpha.76",
3
+ "version": "0.0.7-alpha.77",
4
4
  "author": "Re:Earth contributors <community@reearth.io>",
5
5
  "license": "Apache-2.0",
6
6
  "description": "A library that abstracts a map engine as one common API.",
@@ -20,28 +20,29 @@ describe("computeHasCesiumIonAsset", () => {
20
20
  expect(computeHasCesiumIonAsset({ tiles: [] })).toBe(false);
21
21
  });
22
22
 
23
- test("returns true for cesium_ion tile type", () => {
23
+ test("returns true for cesium_ion tile type with valid assetId", () => {
24
24
  expect(
25
- computeHasCesiumIonAsset({ tiles: [{ id: "", type: "cesium_ion" }] }),
25
+ computeHasCesiumIonAsset(
26
+ { tiles: [{ id: "", type: "cesium_ion", cesiumIonAssetId: 12345 }] },
27
+ undefined,
28
+ "my-token",
29
+ ),
26
30
  ).toBe(true);
27
31
  });
28
32
 
29
- test("returns true for cesium_ion_default tile type", () => {
33
+ test("returns true for cesium_ion_default tile type when token present", () => {
30
34
  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
+ ),
34
40
  ).toBe(true);
35
41
  });
36
42
 
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(
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(computeHasCesiumIonAsset({ tiles: [{ id: "", type }] }, undefined, "my-token")).toBe(
45
46
  true,
46
47
  );
47
48
  }
@@ -65,14 +66,23 @@ describe("computeHasCesiumIonAsset", () => {
65
66
  ).toBe(true);
66
67
  });
67
68
 
68
- test("returns true for cesiumion terrain type when enabled", () => {
69
+ test("returns true for cesiumion terrain type with ionAsset", () => {
69
70
  expect(
70
71
  computeHasCesiumIonAsset({
71
72
  terrain: { enabled: true, type: "cesiumion" },
72
- }),
73
+ assets: { cesium: { terrain: { ionAsset: "1" } } },
74
+ } as any),
73
75
  ).toBe(true);
74
76
  });
75
77
 
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
+
76
86
  test("returns false for cesium terrain when disabled", () => {
77
87
  expect(
78
88
  computeHasCesiumIonAsset({
@@ -111,9 +121,7 @@ describe("computeHasCesiumIonAsset", () => {
111
121
  describe("layers", () => {
112
122
  test("returns true for osm-buildings layer", () => {
113
123
  expect(
114
- computeHasCesiumIonAsset(undefined, [
115
- makeSimple({ type: "osm-buildings" }),
116
- ] as any),
124
+ computeHasCesiumIonAsset(undefined, [makeSimple({ type: "osm-buildings" })] as any),
117
125
  ).toBe(true);
118
126
  });
119
127
 
@@ -135,9 +143,7 @@ describe("computeHasCesiumIonAsset", () => {
135
143
 
136
144
  test("returns false for google-photorealistic with no provider (google API path)", () => {
137
145
  expect(
138
- computeHasCesiumIonAsset(undefined, [
139
- makeSimple({ type: "google-photorealistic" }),
140
- ] as any),
146
+ computeHasCesiumIonAsset(undefined, [makeSimple({ type: "google-photorealistic" })] as any),
141
147
  ).toBe(false);
142
148
  });
143
149
 
@@ -175,9 +181,7 @@ describe("computeHasCesiumIonAsset", () => {
175
181
  });
176
182
 
177
183
  test("returns false for layer with no data", () => {
178
- expect(computeHasCesiumIonAsset(undefined, [makeSimple()] as any)).toBe(
179
- false,
180
- );
184
+ expect(computeHasCesiumIonAsset(undefined, [makeSimple()] as any)).toBe(false);
181
185
  });
182
186
  });
183
187
 
@@ -221,18 +225,19 @@ describe("computeHasCesiumIonAsset", () => {
221
225
 
222
226
  test("returns true when only tile uses ion", () => {
223
227
  expect(
224
- computeHasCesiumIonAsset({ tiles: [{ id: "", type: "default" }] }, [
225
- makeSimple({ type: "geojson" }),
226
- ] as any),
228
+ computeHasCesiumIonAsset(
229
+ { tiles: [{ id: "", type: "default" }] },
230
+ [makeSimple({ type: "geojson" })] as any,
231
+ "my-token",
232
+ ),
227
233
  ).toBe(true);
228
234
  });
229
235
 
230
236
  test("returns true when only terrain uses ion", () => {
231
237
  expect(
232
- computeHasCesiumIonAsset(
233
- { terrain: { enabled: true, type: "cesium" } },
234
- [makeSimple({ type: "geojson" })] as any,
235
- ),
238
+ computeHasCesiumIonAsset({ terrain: { enabled: true, type: "cesium" } }, [
239
+ makeSimple({ type: "geojson" }),
240
+ ] as any),
236
241
  ).toBe(true);
237
242
  });
238
243
 
@@ -242,4 +247,48 @@ describe("computeHasCesiumIonAsset", () => {
242
247
  expect(computeHasCesiumIonAsset(undefined, [])).toBe(false);
243
248
  });
244
249
  });
250
+
251
+ describe("token and assetId gating", () => {
252
+ test("returns false for cesium_ion tile with no cesiumIonAssetId", () => {
253
+ expect(
254
+ computeHasCesiumIonAsset(
255
+ { tiles: [{ id: "", type: "cesium_ion" }] },
256
+ undefined,
257
+ "my-token",
258
+ ),
259
+ ).toBe(false);
260
+ });
261
+
262
+ test("returns false for cesium_ion tile with assetId of 0", () => {
263
+ expect(
264
+ computeHasCesiumIonAsset(
265
+ { tiles: [{ id: "", type: "cesium_ion", cesiumIonAssetId: 0 }] },
266
+ undefined,
267
+ "my-token",
268
+ ),
269
+ ).toBe(false);
270
+ });
271
+
272
+ test("returns false for cesium_ion_default when no token", () => {
273
+ expect(
274
+ computeHasCesiumIonAsset({
275
+ tiles: [{ id: "", type: "cesium_ion_default" }],
276
+ }),
277
+ ).toBe(false);
278
+ });
279
+
280
+ test("returns false for legacy tile types when no token", () => {
281
+ for (const type of ["default", "default_road", "default_label", "black_marble"]) {
282
+ expect(computeHasCesiumIonAsset({ tiles: [{ id: "", type }] })).toBe(false);
283
+ }
284
+ });
285
+
286
+ test("returns true for cesium_ion with valid assetId (token gate is at engine level)", () => {
287
+ expect(
288
+ computeHasCesiumIonAsset({
289
+ tiles: [{ id: "", type: "cesium_ion", cesiumIonAssetId: 2275207 }],
290
+ }),
291
+ ).toBe(true);
292
+ });
293
+ });
245
294
  });
@@ -14,17 +14,28 @@ function isIonUrl(url?: string | null): boolean {
14
14
  return !!url && url.includes(CESIUM_ION_URL_PATTERN);
15
15
  }
16
16
 
17
- function tileUsesIon(tile: TileProperty): boolean {
17
+ function tileUsesIon(tile: TileProperty, accessToken?: string): boolean {
18
18
  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;
21
25
  return false;
22
26
  }
23
27
 
24
28
  function terrainUsesIon(property?: ViewerProperty): boolean {
25
29
  const terrain = property?.terrain;
26
30
  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
+ }
28
39
  if (isIonUrl(property?.assets?.cesium?.terrain?.ionUrl)) return true;
29
40
  return false;
30
41
  }
@@ -47,8 +58,12 @@ function anyLayerUsesIon(layer: Layer): boolean {
47
58
  return layerUsesIon(layer);
48
59
  }
49
60
 
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;
52
67
  if (terrainUsesIon(property)) return true;
53
68
  if (layers?.some(anyLayerUsesIon)) return true;
54
69
  return false;
package/src/Map/index.tsx CHANGED
@@ -106,10 +106,13 @@ function MapFn(
106
106
  onAPIReady,
107
107
  });
108
108
 
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]);
113
116
 
114
117
  const selectedLayerIds = useMemo(
115
118
  () => ({
@@ -914,24 +914,33 @@ export function getCredits(viewer: Viewer, hasCesiumIonAsset?: boolean) {
914
914
  screenCredits: { _array: { credit?: CesiumCredit }[] };
915
915
  };
916
916
  _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;
917
922
  })
918
923
  | undefined;
919
924
 
920
925
  if (!creditDisplay) return emptyCredites;
921
926
 
922
927
  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;
924
940
 
925
941
  const credits: Credits = {
926
942
  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,
935
944
  },
936
945
  lightbox: Array.from(lightboxCredits?._array ?? []).map(c => ({
937
946
  html: c?.credit?.html,
@@ -1,4 +1,4 @@
1
- import { type Globe as CesiumGlobeType } from "cesium";
1
+ import { EllipsoidTerrainProvider, type Globe as CesiumGlobeType } from "cesium";
2
2
  import { useEffect, useMemo, useRef, type JSX } from "react";
3
3
  import { Globe as CesiumGlobe, type CesiumComponentRef } from "resium";
4
4
 
@@ -48,7 +48,13 @@ export default function Globe({
48
48
  }
49
49
  })
50
50
  .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
+ }
52
58
  });
53
59
  return () => {
54
60
  cancelled = true;
@@ -68,7 +74,10 @@ export default function Globe({
68
74
  }
69
75
  })
70
76
  .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?.();
72
81
  });
73
82
 
74
83
  return () => {
@@ -143,8 +143,9 @@ export default ({
143
143
  ? meta.cesiumIonAccessToken
144
144
  : undefined;
145
145
 
146
- const hasCesiumIonAssetRef = useRef(hasCesiumIonAsset);
147
- hasCesiumIonAssetRef.current = hasCesiumIonAsset;
146
+ const effectiveHasCesiumIonAsset = hasCesiumIonAsset && !!cesiumIonAccessToken;
147
+ const hasCesiumIonAssetRef = useRef(effectiveHasCesiumIonAsset);
148
+ hasCesiumIonAssetRef.current = effectiveHasCesiumIonAsset;
148
149
 
149
150
  // expose ref
150
151
  const engineAPI = useEngineRef(ref, cesium, hasCesiumIonAssetRef);
@@ -665,9 +666,10 @@ export default ({
665
666
  onCreditsUpdateRef.current = onCreditsUpdate;
666
667
  const updateCredits = useCallback(() => {
667
668
  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.
669
+ // Wait for tiles to load/render before checking credits. Cesium's GlobeSurfaceTileProvider
670
+ // calls addCreditToNextFrame() each frame for ready+visible Ion layers, which sets
671
+ // _currentCesiumCredit !== _cesiumCredit — that comparison is the actual Ion-rendering check
672
+ // inside getCredits(). 3 s gives tiles enough time to reach that state.
671
673
  setTimeout(() => {
672
674
  if (!onCreditsUpdateRef.current) return;
673
675
  const viewer = cesium.current?.cesiumElement;