@reearth/core 0.0.7-alpha.64 → 0.0.7-alpha.66

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.64",
3
+ "version": "0.0.7-alpha.66",
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.",
@@ -23,7 +23,15 @@ import {
23
23
  createGooglePhotorealistic3DTileset,
24
24
  } from "cesium";
25
25
  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";
27
35
  import { CesiumComponentRef, useCesium } from "resium";
28
36
 
29
37
  import type {
@@ -490,12 +498,53 @@ export const useHooks = ({
490
498
 
491
499
  const [style, setStyle] = useState<Cesium3DTileStyle>();
492
500
  const { url, type, idProperty, googleMapApiKey, provider } = useData(layer);
501
+ const cesiumIonAccessToken =
502
+ typeof meta?.cesiumIonAccessToken === "string" ? meta.cesiumIonAccessToken : undefined;
493
503
  const shouldUseFeatureIndex = !disableIndexingFeature && !!idProperty;
494
504
 
495
505
  const [isTilesetReady, setIsTilesetReady] = useState(false);
496
506
  const [isTilesetCompReady, setIsTilesetCompReady] = useState(false);
497
507
  const [isTilesetRefReady, setIsTilesetRefReady] = useState(false);
498
508
 
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
+
499
548
  const prevPlanes = useRef(_planes);
500
549
  const planes = useMemo(() => {
501
550
  if (
@@ -563,20 +612,25 @@ export const useHooks = ({
563
612
 
564
613
  const ref = useCallback(
565
614
  (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, {
568
621
  layerId: layer?.id || id,
569
622
  featureIndex: shouldUseFeatureIndex ? featureIndex : undefined,
570
623
  appearanceType: "3dtiles",
571
624
  });
572
625
  }
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;
575
628
  }
576
- tilesetRef.current = tileset?.cesiumElement;
577
- setIsTilesetRefReady(!!tileset?.cesiumElement);
629
+ tilesetRef.current = cesiumElement;
630
+ tilesetRefKeyRef.current = cesiumElement ? tilesetKey : undefined;
631
+ setIsTilesetRefReady(!!cesiumElement);
578
632
  },
579
- [id, layer?.id, featureIndex, shouldUseFeatureIndex],
633
+ [id, layer?.id, featureIndex, shouldUseFeatureIndex, tilesetKey],
580
634
  );
581
635
 
582
636
  const selectedFeatureIdsRef = useRef<string[]>([]);
@@ -584,6 +638,20 @@ export const useHooks = ({
584
638
  selectedFeatureColorRef.current = selectedFeatureColor;
585
639
  const [selectedFeatureColorMap] = useState(() => new Map<string, Color>());
586
640
 
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
+
587
655
  useEffect(() => {
588
656
  if (!tilesetRef.current || !shouldUseFeatureIndex || !isTilesetReady) return;
589
657
  Object.assign(tilesetRef.current, {
@@ -759,12 +827,7 @@ export const useHooks = ({
759
827
 
760
828
  // For Re:Earth provider, use the custom URL from customProvider or layer data
761
829
  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;
768
831
  }
769
832
 
770
833
  // Otherwise load via Google API key or Cesium Ion.
@@ -772,11 +835,13 @@ export const useHooks = ({
772
835
  try {
773
836
  if (provider === "cesium-ion" || !googleMapApiKey) {
774
837
  const resource = await IonResource.fromAssetId(2275207, {
775
- accessToken: meta?.cesiumIonAccessToken as string | undefined,
838
+ accessToken: cesiumIonAccessToken,
776
839
  });
777
840
  return resource;
778
841
  }
779
- const tileset = await createGooglePhotorealistic3DTileset({ key: googleMapApiKey });
842
+ const tileset = await createGooglePhotorealistic3DTileset({
843
+ key: googleMapApiKey,
844
+ });
780
845
  return tileset.resource;
781
846
  } catch (error) {
782
847
  console.error(`Error loading Photorealistic 3D Tiles tileset: ${error}`);
@@ -785,7 +850,15 @@ export const useHooks = ({
785
850
  };
786
851
 
787
852
  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
+ ]);
789
862
 
790
863
  const tilesetUrl = useMemo((): string | Resource | Promise<Resource> | null => {
791
864
  if (!isVisible) return null;
@@ -803,7 +876,7 @@ export const useHooks = ({
803
876
  // OSM Buildings — only available via Cesium Ion.
804
877
  if (type === "osm-buildings") {
805
878
  return IonResource.fromAssetId(96188, {
806
- accessToken: meta?.cesiumIonAccessToken as string | undefined,
879
+ accessToken: cesiumIonAccessToken,
807
880
  }); // https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Scene/createOsmBuildings.js#L53
808
881
  }
809
882
 
@@ -813,7 +886,7 @@ export const useHooks = ({
813
886
  }
814
887
 
815
888
  return null;
816
- }, [type, isVisible, googleMapPhotorealisticResource, url, tileset, meta?.cesiumIonAccessToken]);
889
+ }, [type, isVisible, googleMapPhotorealisticResource, url, tileset, cesiumIonAccessToken]);
817
890
 
818
891
  const imageBasedLighting = useMemo(() => {
819
892
  if (
@@ -854,11 +927,29 @@ export const useHooks = ({
854
927
 
855
928
  const handleReady = useCallback(
856
929
  (tileset: Cesium3DTileset) => {
930
+ if (tilesetKey !== currentTilesetKeyRef.current) {
931
+ return;
932
+ }
857
933
  setIsTilesetCompReady(true);
858
934
  onLayerFetch?.({ properties: tileset.properties });
859
935
  onLayerLoad?.({ layerId: layerIdRef.current });
860
936
  },
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],
862
953
  );
863
954
 
864
955
  useEffect(() => {
@@ -874,6 +965,7 @@ export const useHooks = ({
874
965
  }, [type, updateCredits]);
875
966
 
876
967
  return {
968
+ tilesetKey,
877
969
  tilesetUrl,
878
970
  ref,
879
971
  style,
@@ -883,5 +975,6 @@ export const useHooks = ({
883
975
  builtinBoxProps,
884
976
  imageBasedLighting,
885
977
  handleReady,
978
+ handleError,
886
979
  };
887
980
  };
@@ -1,4 +1,4 @@
1
- import { memo, useMemo, type JSX } from "react";
1
+ import { useMemo, type JSX } from "react";
2
2
  import { Cesium3DTileset } from "resium";
3
3
 
4
4
  import type { Cesium3DTilesAppearance, ComputedLayer } from "../../..";
@@ -37,6 +37,7 @@ function Tileset({
37
37
  property ?? {};
38
38
  const boxId = `${layer?.id}_box`;
39
39
  const {
40
+ tilesetKey,
40
41
  tilesetUrl,
41
42
  ref,
42
43
  style,
@@ -46,6 +47,7 @@ function Tileset({
46
47
  builtinBoxProps,
47
48
  imageBasedLighting,
48
49
  handleReady,
50
+ handleError,
49
51
  } = useHooks({
50
52
  id,
51
53
  boxId,
@@ -72,6 +74,7 @@ function Tileset({
72
74
  return !isVisible || !tilesetUrl ? null : (
73
75
  <>
74
76
  <Cesium3DTileset
77
+ key={tilesetKey}
75
78
  ref={ref}
76
79
  url={tilesetUrl}
77
80
  customShader={
@@ -88,8 +91,10 @@ function Tileset({
88
91
  colorBlendMode={colorBlendModeFor3DTile(colorBlendMode)}
89
92
  imageBasedLighting={imageBasedLighting}
90
93
  onReady={handleReady}
94
+ onError={handleError}
91
95
  debugWireframe={showWireframe}
92
96
  debugShowBoundingVolume={showBoundingVolume}
97
+ showCreditsOnScreen
93
98
  />
94
99
  {builtinBoxProps && (
95
100
  <Box
@@ -110,19 +115,7 @@ function Tileset({
110
115
  );
111
116
  }
112
117
 
113
- export default memo(
114
- Tileset,
115
- (prev, next) =>
116
- prev.id === next.id &&
117
- prev.isVisible === next.isVisible &&
118
- prev.property === next.property &&
119
- prev.layer?.layer === next.layer?.layer &&
120
- prev.viewerProperty === next.viewerProperty &&
121
- prev.meta === next.meta &&
122
- prev.evalFeature === next.evalFeature &&
123
- prev.onComputedFeatureFetch === next.onComputedFeatureFetch &&
124
- prev.onFeatureDelete === next.onFeatureDelete,
125
- );
118
+ export default Tileset;
126
119
 
127
120
  export const config: FeatureComponentConfig = {
128
121
  noFeature: true,
@@ -112,7 +112,19 @@ export default function Feature({
112
112
  displayType.every(k => components[k][1].noFeature && !components[k][1].noLayer);
113
113
  const useTransition = !!layer?.transition?.useTransition;
114
114
  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
+ }),
125
+ ),
126
+ [data?.type, data?.url, data?.layers, data?.provider, data?.serviceTokens?.googleMapApiKey],
127
+ );
116
128
 
117
129
  const { requestRender } = useContext();
118
130
  // TODO: Find a way to wait updating the entity
@@ -139,7 +151,7 @@ export default function Feature({
139
151
  !(sketchEditingFeature?.layerId === layer.id && sketchEditingFeature?.feature?.id === f?.id);
140
152
 
141
153
  const componentId =
142
- urlMD5 +
154
+ dataSourceKey +
143
155
  generateIDWithMD5(
144
156
  `${layer.id}_${
145
157
  f?.id ?? ""
@@ -221,9 +233,9 @@ export default function Feature({
221
233
  layer?.layer?.type === "simple" && !!layer?.layer?.["3dtiles"]?.specularEnvironmentMaps;
222
234
 
223
235
  // "noFeature" component should be recreated when the following value is changed.
224
- // data.url, isVisible
236
+ // data source, Cesium Ion token, isVisible
225
237
  const key =
226
- urlMD5 +
238
+ dataSourceKey +
227
239
  generateIDWithMD5(
228
240
  `${
229
241
  layer?.id || ""
@@ -243,7 +255,7 @@ export default function Feature({
243
255
  })}
244
256
  </>
245
257
  );
246
- }, [areAllDisplayTypeNoFeature, displayType, layer, isHidden, urlMD5, props]);
258
+ }, [areAllDisplayTypeNoFeature, displayType, layer, isHidden, dataSourceKey, props]);
247
259
 
248
260
  return (
249
261
  <>