Skip to content

Commit 5d9aa9f

Browse files
committed
fix(3dtiles): refresh 3D tileset immediately when Cesium Ion token or data source changes
1 parent 9fcd65c commit 5d9aa9f

3 files changed

Lines changed: 144 additions & 39 deletions

File tree

src/engines/Cesium/Feature/Tileset/hooks.ts

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ import {
2323
createGooglePhotorealistic3DTileset,
2424
} from "cesium";
2525
import { pick } from "lodash-es";
26-
import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
26+
import {
27+
MutableRefObject,
28+
useCallback,
29+
useEffect,
30+
useLayoutEffect,
31+
useMemo,
32+
useRef,
33+
useState,
34+
} from "react";
2735
import { CesiumComponentRef, useCesium } from "resium";
2836

2937
import type {
@@ -490,12 +498,53 @@ export const useHooks = ({
490498

491499
const [style, setStyle] = useState<Cesium3DTileStyle>();
492500
const { url, type, idProperty, googleMapApiKey, provider } = useData(layer);
501+
const cesiumIonAccessToken =
502+
typeof meta?.cesiumIonAccessToken === "string" ? meta.cesiumIonAccessToken : undefined;
493503
const shouldUseFeatureIndex = !disableIndexingFeature && !!idProperty;
494504

495505
const [isTilesetReady, setIsTilesetReady] = useState(false);
496506
const [isTilesetCompReady, setIsTilesetCompReady] = useState(false);
497507
const [isTilesetRefReady, setIsTilesetRefReady] = useState(false);
498508

509+
const reearthGooglePhotorealisticUrl = useMemo(
510+
() =>
511+
customProvider?.layers?.providers?.find(
512+
p => p.id === "reearth_google_photorealistic_3d_tiles",
513+
)?.url,
514+
[customProvider],
515+
);
516+
517+
const tilesetKey = useMemo(
518+
() =>
519+
generateIDWithMD5(
520+
JSON.stringify({
521+
type,
522+
url,
523+
tileset,
524+
provider,
525+
googleMapApiKey,
526+
reearthGooglePhotorealisticUrl,
527+
cesiumIonAccessToken:
528+
type === "osm-buildings" ||
529+
(type === "google-photorealistic" && (provider === "cesium-ion" || !googleMapApiKey))
530+
? cesiumIonAccessToken
531+
: undefined,
532+
}),
533+
),
534+
[
535+
type,
536+
url,
537+
tileset,
538+
provider,
539+
googleMapApiKey,
540+
reearthGooglePhotorealisticUrl,
541+
cesiumIonAccessToken,
542+
],
543+
);
544+
const currentTilesetKeyRef = useRef(tilesetKey);
545+
currentTilesetKeyRef.current = tilesetKey;
546+
const tilesetRefKeyRef = useRef<string | undefined>(undefined);
547+
499548
const prevPlanes = useRef(_planes);
500549
const planes = useMemo(() => {
501550
if (
@@ -563,27 +612,46 @@ export const useHooks = ({
563612

564613
const ref = useCallback(
565614
(tileset: CesiumComponentRef<Cesium3DTilesetType> | null) => {
566-
if (tileset?.cesiumElement) {
567-
attachTag(tileset.cesiumElement, {
615+
const cesiumElement = tileset?.cesiumElement;
616+
if (tilesetKey !== currentTilesetKeyRef.current) {
617+
return;
618+
}
619+
if (cesiumElement) {
620+
attachTag(cesiumElement, {
568621
layerId: layer?.id || id,
569622
featureIndex: shouldUseFeatureIndex ? featureIndex : undefined,
570623
appearanceType: "3dtiles",
571624
});
572625
}
573-
if (layer?.id && tileset?.cesiumElement) {
574-
(tileset?.cesiumElement as any)[layerIdField] = layer.id;
626+
if (layer?.id && cesiumElement) {
627+
(cesiumElement as any)[layerIdField] = layer.id;
575628
}
576-
tilesetRef.current = tileset?.cesiumElement;
577-
setIsTilesetRefReady(!!tileset?.cesiumElement);
629+
tilesetRef.current = cesiumElement;
630+
tilesetRefKeyRef.current = cesiumElement ? tilesetKey : undefined;
631+
setIsTilesetRefReady(!!cesiumElement);
578632
},
579-
[id, layer?.id, featureIndex, shouldUseFeatureIndex],
633+
[id, layer?.id, featureIndex, shouldUseFeatureIndex, tilesetKey],
580634
);
581635

582636
const selectedFeatureIdsRef = useRef<string[]>([]);
583637
const selectedFeatureColorRef = useRef(selectedFeatureColor);
584638
selectedFeatureColorRef.current = selectedFeatureColor;
585639
const [selectedFeatureColorMap] = useState(() => new Map<string, Color>());
586640

641+
// The Cesium3DTileset child remounts when tilesetKey changes, while this hook stays mounted.
642+
useLayoutEffect(() => {
643+
setIsTilesetCompReady(false);
644+
setIsTilesetReady(false);
645+
if (tilesetRefKeyRef.current !== tilesetKey) {
646+
tilesetRef.current = undefined;
647+
tilesetRefKeyRef.current = undefined;
648+
setIsTilesetRefReady(false);
649+
}
650+
featureIndex.records.clear();
651+
selectedFeatureIdsRef.current = [];
652+
selectedFeatureColorMap.clear();
653+
}, [tilesetKey, featureIndex, selectedFeatureColorMap]);
654+
587655
useEffect(() => {
588656
if (!tilesetRef.current || !shouldUseFeatureIndex || !isTilesetReady) return;
589657
Object.assign(tilesetRef.current, {
@@ -759,24 +827,21 @@ export const useHooks = ({
759827

760828
// For Re:Earth provider, use the custom URL from customProvider or layer data
761829
if (provider === "reearth") {
762-
// Try to get URL from customProvider first, then fall back to layer URL
763-
const customUrl = customProvider?.layers?.providers?.find(
764-
p => p.id === "reearth_google_photorealistic_3d_tiles",
765-
)?.url;
766-
767-
return customUrl || url || null;
830+
return reearthGooglePhotorealisticUrl || url || null;
768831
}
769832

770833
// Otherwise load via Google API key or Cesium Ion.
771834
const loadTileset = async (): Promise<Resource> => {
772835
try {
773836
if (provider === "cesium-ion" || !googleMapApiKey) {
774837
const resource = await IonResource.fromAssetId(2275207, {
775-
accessToken: meta?.cesiumIonAccessToken as string | undefined,
838+
accessToken: cesiumIonAccessToken,
776839
});
777840
return resource;
778841
}
779-
const tileset = await createGooglePhotorealistic3DTileset({ key: googleMapApiKey });
842+
const tileset = await createGooglePhotorealistic3DTileset({
843+
key: googleMapApiKey,
844+
});
780845
return tileset.resource;
781846
} catch (error) {
782847
console.error(`Error loading Photorealistic 3D Tiles tileset: ${error}`);
@@ -785,7 +850,15 @@ export const useHooks = ({
785850
};
786851

787852
return loadTileset();
788-
}, [type, isVisible, googleMapApiKey, meta?.cesiumIonAccessToken, provider, customProvider, url]);
853+
}, [
854+
type,
855+
isVisible,
856+
googleMapApiKey,
857+
cesiumIonAccessToken,
858+
provider,
859+
reearthGooglePhotorealisticUrl,
860+
url,
861+
]);
789862

790863
const tilesetUrl = useMemo((): string | Resource | Promise<Resource> | null => {
791864
if (!isVisible) return null;
@@ -803,7 +876,7 @@ export const useHooks = ({
803876
// OSM Buildings — only available via Cesium Ion.
804877
if (type === "osm-buildings") {
805878
return IonResource.fromAssetId(96188, {
806-
accessToken: meta?.cesiumIonAccessToken as string | undefined,
879+
accessToken: cesiumIonAccessToken,
807880
}); // https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Scene/createOsmBuildings.js#L53
808881
}
809882

@@ -813,7 +886,7 @@ export const useHooks = ({
813886
}
814887

815888
return null;
816-
}, [type, isVisible, googleMapPhotorealisticResource, url, tileset, meta?.cesiumIonAccessToken]);
889+
}, [type, isVisible, googleMapPhotorealisticResource, url, tileset, cesiumIonAccessToken]);
817890

818891
const imageBasedLighting = useMemo(() => {
819892
if (
@@ -854,11 +927,29 @@ export const useHooks = ({
854927

855928
const handleReady = useCallback(
856929
(tileset: Cesium3DTileset) => {
930+
if (tilesetKey !== currentTilesetKeyRef.current) {
931+
return;
932+
}
857933
setIsTilesetCompReady(true);
858934
onLayerFetch?.({ properties: tileset.properties });
859935
onLayerLoad?.({ layerId: layerIdRef.current });
860936
},
861-
[onLayerFetch, onLayerLoad],
937+
[onLayerFetch, onLayerLoad, tilesetKey],
938+
);
939+
940+
const handleError = useCallback(
941+
(error: unknown) => {
942+
if (tilesetKey !== currentTilesetKeyRef.current) {
943+
return;
944+
}
945+
tilesetRef.current = undefined;
946+
tilesetRefKeyRef.current = undefined;
947+
setIsTilesetCompReady(false);
948+
setIsTilesetRefReady(false);
949+
setIsTilesetReady(false);
950+
console.error("Error loading Cesium 3D Tileset:", error);
951+
},
952+
[tilesetKey],
862953
);
863954

864955
useEffect(() => {
@@ -874,6 +965,7 @@ export const useHooks = ({
874965
}, [type, updateCredits]);
875966

876967
return {
968+
tilesetKey,
877969
tilesetUrl,
878970
ref,
879971
style,
@@ -883,5 +975,6 @@ export const useHooks = ({
883975
builtinBoxProps,
884976
imageBasedLighting,
885977
handleReady,
978+
handleError,
886979
};
887980
};

src/engines/Cesium/Feature/Tileset/index.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { memo, useMemo, type JSX } from "react";
1+
import { useMemo, type JSX } from "react";
22
import { Cesium3DTileset } from "resium";
33

44
import type { Cesium3DTilesAppearance, ComputedLayer } from "../../..";
@@ -37,6 +37,7 @@ function Tileset({
3737
property ?? {};
3838
const boxId = `${layer?.id}_box`;
3939
const {
40+
tilesetKey,
4041
tilesetUrl,
4142
ref,
4243
style,
@@ -46,6 +47,7 @@ function Tileset({
4647
builtinBoxProps,
4748
imageBasedLighting,
4849
handleReady,
50+
handleError,
4951
} = useHooks({
5052
id,
5153
boxId,
@@ -72,6 +74,7 @@ function Tileset({
7274
return !isVisible || !tilesetUrl ? null : (
7375
<>
7476
<Cesium3DTileset
77+
key={tilesetKey}
7578
ref={ref}
7679
url={tilesetUrl}
7780
customShader={
@@ -88,6 +91,7 @@ function Tileset({
8891
colorBlendMode={colorBlendModeFor3DTile(colorBlendMode)}
8992
imageBasedLighting={imageBasedLighting}
9093
onReady={handleReady}
94+
onError={handleError}
9195
debugWireframe={showWireframe}
9296
debugShowBoundingVolume={showBoundingVolume}
9397
showCreditsOnScreen
@@ -111,19 +115,7 @@ function Tileset({
111115
);
112116
}
113117

114-
export default memo(
115-
Tileset,
116-
(prev, next) =>
117-
prev.id === next.id &&
118-
prev.isVisible === next.isVisible &&
119-
prev.property === next.property &&
120-
prev.layer?.layer === next.layer?.layer &&
121-
prev.viewerProperty === next.viewerProperty &&
122-
prev.meta === next.meta &&
123-
prev.evalFeature === next.evalFeature &&
124-
prev.onComputedFeatureFetch === next.onComputedFeatureFetch &&
125-
prev.onFeatureDelete === next.onFeatureDelete,
126-
);
118+
export default Tileset;
127119

128120
export const config: FeatureComponentConfig = {
129121
noFeature: true,

src/engines/Cesium/Feature/index.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,27 @@ export default function Feature({
112112
displayType.every(k => components[k][1].noFeature && !components[k][1].noLayer);
113113
const useTransition = !!layer?.transition?.useTransition;
114114
const cacheable = !data?.updateInterval && !useTransition;
115-
const urlMD5 = useMemo(() => (data?.url ? generateIDWithMD5(data.url) : ""), [data?.url]);
115+
const dataSourceKey = useMemo(
116+
() =>
117+
generateIDWithMD5(
118+
JSON.stringify({
119+
type: data?.type,
120+
url: data?.url,
121+
layers: data?.layers,
122+
provider: data?.provider,
123+
googleMapApiKey: data?.serviceTokens?.googleMapApiKey,
124+
cesiumIonAccessToken: props.meta?.cesiumIonAccessToken,
125+
}),
126+
),
127+
[
128+
data?.type,
129+
data?.url,
130+
data?.layers,
131+
data?.provider,
132+
data?.serviceTokens?.googleMapApiKey,
133+
props.meta?.cesiumIonAccessToken,
134+
],
135+
);
116136

117137
const { requestRender } = useContext();
118138
// TODO: Find a way to wait updating the entity
@@ -139,7 +159,7 @@ export default function Feature({
139159
!(sketchEditingFeature?.layerId === layer.id && sketchEditingFeature?.feature?.id === f?.id);
140160

141161
const componentId =
142-
urlMD5 +
162+
dataSourceKey +
143163
generateIDWithMD5(
144164
`${layer.id}_${
145165
f?.id ?? ""
@@ -221,9 +241,9 @@ export default function Feature({
221241
layer?.layer?.type === "simple" && !!layer?.layer?.["3dtiles"]?.specularEnvironmentMaps;
222242

223243
// "noFeature" component should be recreated when the following value is changed.
224-
// data.url, isVisible
244+
// data source, Cesium Ion token, isVisible
225245
const key =
226-
urlMD5 +
246+
dataSourceKey +
227247
generateIDWithMD5(
228248
`${
229249
layer?.id || ""
@@ -243,7 +263,7 @@ export default function Feature({
243263
})}
244264
</>
245265
);
246-
}, [areAllDisplayTypeNoFeature, displayType, layer, isHidden, urlMD5, props]);
266+
}, [areAllDisplayTypeNoFeature, displayType, layer, isHidden, dataSourceKey, props]);
247267

248268
return (
249269
<>

0 commit comments

Comments
 (0)