@reearth/core 0.0.6 → 0.0.7-alpha.1

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.
Files changed (49) hide show
  1. package/dist/core.js +8312 -8318
  2. package/dist/core.umd.cjs +75 -75
  3. package/dist/index.d.ts +183 -184
  4. package/package.json +1 -1
  5. package/src/Map/ClusteredLayers/index.tsx +2 -2
  6. package/src/Map/Layer/index.tsx +3 -3
  7. package/src/Map/Layers/index.tsx +2 -2
  8. package/src/Map/hooks.ts +0 -4
  9. package/src/Map/index.tsx +8 -5
  10. package/src/Map/types/index.ts +5 -182
  11. package/src/Map/types/viewerProperty.ts +216 -0
  12. package/src/Map/useTimelineManager.ts +2 -2
  13. package/src/Map/utils.ts +1 -43
  14. package/src/Visualizer/coreContext.tsx +1 -8
  15. package/src/Visualizer/hooks.ts +6 -75
  16. package/src/Visualizer/index.stories.tsx +4 -48
  17. package/src/Visualizer/index.tsx +8 -6
  18. package/src/Visualizer/useCoreAPI.ts +30 -0
  19. package/src/engines/Cesium/Feature/Ellipse/index.stories.tsx +1 -1
  20. package/src/engines/Cesium/Feature/Frustum/index.stories.tsx +1 -1
  21. package/src/engines/Cesium/Feature/Model/index.stories.tsx +1 -1
  22. package/src/engines/Cesium/Feature/Model/index.tsx +10 -9
  23. package/src/engines/Cesium/Feature/Polygon/index.stories.tsx +8 -6
  24. package/src/engines/Cesium/Feature/Raster/index.stories.tsx +2 -2
  25. package/src/engines/Cesium/Feature/Resource/index.stories.tsx +1 -1
  26. package/src/engines/Cesium/Feature/Tileset/hooks.ts +12 -11
  27. package/src/engines/Cesium/Feature/Tileset/index.stories.tsx +1 -1
  28. package/src/engines/Cesium/Feature/Tileset/index.tsx +4 -4
  29. package/src/engines/Cesium/Feature/index.tsx +5 -4
  30. package/src/engines/Cesium/Feature/utils.tsx +2 -2
  31. package/src/engines/Cesium/core/Globe.tsx +36 -68
  32. package/src/engines/Cesium/core/Imagery.test.ts +9 -9
  33. package/src/engines/Cesium/core/Imagery.tsx +17 -19
  34. package/src/engines/Cesium/core/Indicator/Indicator.tsx +8 -8
  35. package/src/engines/Cesium/hooks/useCamera.ts +144 -0
  36. package/src/engines/Cesium/{cameraLimiter.ts → hooks/useCameraLimiter.ts} +22 -45
  37. package/src/engines/Cesium/{useEngineRef.test.tsx → hooks/useEngineRef.test.tsx} +14 -10
  38. package/src/engines/Cesium/{useEngineRef.ts → hooks/useEngineRef.ts} +17 -17
  39. package/src/engines/Cesium/hooks/useExplicitRender.ts +65 -0
  40. package/src/engines/Cesium/hooks/useLayerDragDrop.ts +77 -0
  41. package/src/engines/Cesium/{VertexTerrainElevationMaterial.ts → hooks/useOverrideGlobeShader/VertexTerrainElevationMaterial.ts} +3 -2
  42. package/src/engines/Cesium/{useOverrideGlobeShader.ts → hooks/useOverrideGlobeShader/useOverrideGlobeShader.ts} +19 -16
  43. package/src/engines/Cesium/hooks/useViewerProperty.ts +90 -0
  44. package/src/engines/Cesium/hooks.ts +117 -353
  45. package/src/engines/Cesium/index.stories.tsx +1 -1
  46. package/src/engines/Cesium/index.tsx +48 -50
  47. package/src/engines/index.ts +1 -1
  48. /package/src/engines/Cesium/{JapanSeaLevelEllipsoid.ts → hooks/useOverrideGlobeShader/JapanSeaLevelEllipsoid.ts} +0 -0
  49. /package/src/engines/Cesium/{vertexTerrainElevationMaterial.glsl → hooks/useOverrideGlobeShader/vertexTerrainElevationMaterial.glsl} +0 -0
@@ -5,16 +5,15 @@ import {
5
5
  IonResource,
6
6
  TerrainProvider,
7
7
  } from "cesium";
8
- import { pick } from "lodash-es";
9
8
  import { useMemo } from "react";
10
9
  import { Globe as CesiumGlobe } from "resium";
11
10
 
12
- import type { SceneProperty, TerrainProperty } from "../..";
13
- import { objKeys } from "../../../utils";
11
+ import type { ViewerProperty, TerrainProperty } from "../..";
12
+ import { AssetsCesiumProperty } from "../../../Map";
14
13
  import { toColor } from "../common";
15
14
 
16
15
  export type Props = {
17
- property?: SceneProperty;
16
+ property?: ViewerProperty;
18
17
  cesiumIonAccessToken?: string;
19
18
  };
20
19
 
@@ -22,93 +21,67 @@ export default function Globe({ property, cesiumIonAccessToken }: Props): JSX.El
22
21
  const terrainProperty = useMemo(
23
22
  (): TerrainProperty => ({
24
23
  ...property?.terrain,
25
- ...pick(property?.default, terrainPropertyKeys),
26
24
  }),
27
- [property?.terrain, property?.default],
25
+ [property?.terrain],
28
26
  );
29
27
 
30
28
  const terrainProvider = useMemo((): Promise<TerrainProvider> | TerrainProvider | undefined => {
31
29
  const opts = {
32
- terrain: terrainProperty?.terrain,
33
- terrainType: terrainProperty?.terrainType,
34
- terrainNormal: terrainProperty?.terrainNormal,
35
- terrainCesiumIonAccessToken:
36
- terrainProperty?.terrainCesiumIonAccessToken || cesiumIonAccessToken,
37
- terrainCesiumIonAsset: terrainProperty?.terrainCesiumIonAsset,
38
- terrainCesiumIonUrl: terrainProperty?.terrainCesiumIonUrl,
30
+ terrain: terrainProperty?.enabled,
31
+ terrainType: terrainProperty?.type,
32
+ normal: terrainProperty?.normal,
33
+ ionAccessToken: property?.assets?.cesium?.terrian?.ionAccessToken || cesiumIonAccessToken,
34
+ ionAsset: property?.assets?.cesium?.terrian?.ionAsset,
35
+ ionUrl: property?.assets?.cesium?.terrian?.ionUrl,
39
36
  };
40
37
  const provider = opts.terrain ? terrainProviders[opts.terrainType || "cesium"] : undefined;
41
38
  return (typeof provider === "function" ? provider(opts) : provider) ?? defaultTerrainProvider;
42
39
  }, [
43
- terrainProperty?.terrain,
44
- terrainProperty?.terrainType,
45
- terrainProperty?.terrainCesiumIonAccessToken,
46
- terrainProperty?.terrainCesiumIonAsset,
47
- terrainProperty?.terrainCesiumIonUrl,
48
- terrainProperty?.terrainNormal,
40
+ terrainProperty?.enabled,
41
+ terrainProperty?.type,
42
+ terrainProperty?.normal,
43
+ property?.assets?.cesium?.terrian?.ionAccessToken,
44
+ property?.assets?.cesium?.terrian?.ionAsset,
45
+ property?.assets?.cesium?.terrian?.ionUrl,
49
46
  cesiumIonAccessToken,
50
47
  ]);
51
48
 
52
49
  const baseColor = useMemo(
53
- () => toColor(property?.atmosphere?.globeBaseColor),
54
- [property?.atmosphere?.globeBaseColor],
50
+ () => toColor(property?.globe?.baseColor),
51
+ [property?.globe?.baseColor],
55
52
  );
56
53
 
57
54
  return (
58
55
  <CesiumGlobe
59
56
  baseColor={baseColor}
60
- enableLighting={
61
- !!(property?.atmosphere?.enable_lighting ?? property?.globeLighting?.globeLighting)
62
- }
63
- showGroundAtmosphere={
64
- property?.atmosphere?.ground_atmosphere ??
65
- property?.globeAtmosphere?.globeAtmosphere ??
66
- true
67
- }
68
- atmosphereLightIntensity={property?.globeAtmosphere?.globeAtmosphereIntensity}
69
- atmosphereSaturationShift={property?.atmosphere?.surturation_shift}
70
- atmosphereHueShift={property?.atmosphere?.hue_shift}
71
- atmosphereBrightnessShift={property?.atmosphere?.brightness_shift}
57
+ enableLighting={!!property?.globe?.enableLighting}
58
+ showGroundAtmosphere={property?.globe?.atmosphere?.enabled ?? true}
59
+ atmosphereLightIntensity={property?.globe?.atmosphere?.lightIntensity}
60
+ atmosphereSaturationShift={property?.globe?.atmosphere?.saturationShift}
61
+ atmosphereHueShift={property?.globe?.atmosphere?.hueShift}
62
+ atmosphereBrightnessShift={property?.globe?.atmosphere?.brightnessShift}
72
63
  terrainProvider={terrainProvider}
73
- depthTestAgainstTerrain={!!terrainProperty.depthTestAgainstTerrain}
64
+ depthTestAgainstTerrain={!!property?.globe?.depthTestAgainstTerrain}
74
65
  />
75
66
  );
76
67
  }
77
68
 
78
- const terrainPropertyKeys = objKeys<TerrainProperty>({
79
- terrain: 0,
80
- terrainType: 0,
81
- terrainExaggeration: 0,
82
- terrainExaggerationRelativeHeight: 0,
83
- depthTestAgainstTerrain: 0,
84
- terrainCesiumIonAsset: 0,
85
- terrainCesiumIonAccessToken: 0,
86
- terrainCesiumIonUrl: 0,
87
- terrainUrl: 0,
88
- });
89
-
90
69
  const defaultTerrainProvider = new EllipsoidTerrainProvider();
91
70
 
92
71
  const terrainProviders: {
93
- [k in NonNullable<TerrainProperty["terrainType"]>]:
72
+ [k in NonNullable<TerrainProperty["type"]>]:
94
73
  | TerrainProvider
95
74
  | ((
96
- opts: Pick<
97
- TerrainProperty,
98
- | "terrainCesiumIonAccessToken"
99
- | "terrainCesiumIonAsset"
100
- | "terrainCesiumIonUrl"
101
- | "terrainNormal"
102
- >,
75
+ opts: Pick<TerrainProperty, "normal"> & AssetsCesiumProperty["terrian"],
103
76
  ) => Promise<TerrainProvider> | TerrainProvider | null);
104
77
  } = {
105
- cesium: ({ terrainCesiumIonAccessToken, terrainNormal }) =>
78
+ cesium: ({ ionAccessToken, normal }) =>
106
79
  CesiumTerrainProvider.fromUrl(
107
80
  IonResource.fromAssetId(1, {
108
- accessToken: terrainCesiumIonAccessToken,
81
+ accessToken: ionAccessToken,
109
82
  }),
110
83
  {
111
- requestVertexNormals: terrainNormal,
84
+ requestVertexNormals: normal,
112
85
  requestWaterMask: false,
113
86
  },
114
87
  ),
@@ -116,20 +89,15 @@ const terrainProviders: {
116
89
  ArcGISTiledElevationTerrainProvider.fromUrl(
117
90
  "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer",
118
91
  ),
119
- cesiumion: ({
120
- terrainCesiumIonAccessToken,
121
- terrainCesiumIonAsset,
122
- terrainCesiumIonUrl,
123
- terrainNormal,
124
- }) =>
125
- terrainCesiumIonAsset
92
+ cesiumion: ({ ionAccessToken, ionAsset, ionUrl, normal }) =>
93
+ ionAsset
126
94
  ? CesiumTerrainProvider.fromUrl(
127
- terrainCesiumIonUrl ||
128
- IonResource.fromAssetId(parseInt(terrainCesiumIonAsset, 10), {
129
- accessToken: terrainCesiumIonAccessToken,
95
+ ionUrl ||
96
+ IonResource.fromAssetId(parseInt(ionAsset, 10), {
97
+ accessToken: ionAccessToken,
130
98
  }),
131
99
  {
132
- requestVertexNormals: terrainNormal,
100
+ requestVertexNormals: normal,
133
101
  },
134
102
  )
135
103
  : null,
@@ -14,7 +14,7 @@ test("useImageryProviders", () => {
14
14
  presets,
15
15
  cesiumIonAccessToken,
16
16
  }),
17
- { initialProps: { tiles: [{ id: "1", tile_type: "default" }] } },
17
+ { initialProps: { tiles: [{ id: "1", type: "default" }] } },
18
18
  );
19
19
 
20
20
  expect(result.current.providers).toEqual({ "1": ["default", undefined, { hoge: undefined }] });
@@ -23,14 +23,14 @@ test("useImageryProviders", () => {
23
23
  const prevImageryProvider = result.current.providers["1"][2];
24
24
 
25
25
  // re-render with same tiles
26
- rerender({ tiles: [{ id: "1", tile_type: "default" }] });
26
+ rerender({ tiles: [{ id: "1", type: "default" }] });
27
27
 
28
28
  expect(result.current.providers).toEqual({ "1": ["default", undefined, { hoge: undefined }] });
29
29
  expect(result.current.providers["1"][2]).toBe(prevImageryProvider); // 1's provider should be reused
30
30
  expect(provider).toBeCalledTimes(1);
31
31
 
32
32
  // update a tile URL
33
- rerender({ tiles: [{ id: "1", tile_type: "default", tile_url: "a" }] });
33
+ rerender({ tiles: [{ id: "1", type: "default", url: "a" }] });
34
34
 
35
35
  expect(result.current.providers).toEqual({ "1": ["default", "a", { hoge: "a" }] });
36
36
  expect(result.current.providers["1"][2]).not.toBe(prevImageryProvider);
@@ -42,8 +42,8 @@ test("useImageryProviders", () => {
42
42
  // add a tile with URL
43
43
  rerender({
44
44
  tiles: [
45
- { id: "2", tile_type: "default" },
46
- { id: "1", tile_type: "default", tile_url: "a" },
45
+ { id: "2", type: "default" },
46
+ { id: "1", type: "default", url: "a" },
47
47
  ],
48
48
  });
49
49
 
@@ -58,8 +58,8 @@ test("useImageryProviders", () => {
58
58
  // sort tiles
59
59
  rerender({
60
60
  tiles: [
61
- { id: "1", tile_type: "default", tile_url: "a" },
62
- { id: "2", tile_type: "default" },
61
+ { id: "1", type: "default", url: "a" },
62
+ { id: "2", type: "default" },
63
63
  ],
64
64
  });
65
65
 
@@ -73,7 +73,7 @@ test("useImageryProviders", () => {
73
73
 
74
74
  // delete a tile
75
75
  rerender({
76
- tiles: [{ id: "1", tile_type: "default", tile_url: "a" }],
76
+ tiles: [{ id: "1", type: "default", url: "a" }],
77
77
  cesiumIonAccessToken: "a",
78
78
  });
79
79
 
@@ -86,7 +86,7 @@ test("useImageryProviders", () => {
86
86
 
87
87
  // update a tile type
88
88
  rerender({
89
- tiles: [{ id: "1", tile_type: "foobar", tile_url: "u" }],
89
+ tiles: [{ id: "1", type: "foobar", url: "u" }],
90
90
  cesiumIonAccessToken: "a",
91
91
  });
92
92
 
@@ -20,11 +20,11 @@ export type ImageryLayerData = {
20
20
 
21
21
  export type Tile = {
22
22
  id: string;
23
- tile_url?: string;
24
- tile_type?: string;
25
- tile_opacity?: number;
26
- tile_zoomLevel?: number[];
27
- tile_zoomLevelForURL?: number[];
23
+ url?: string;
24
+ type?: string;
25
+ opacity?: number;
26
+ zoomLevel?: number[];
27
+ zoomLevelForURL?: number[];
28
28
  heatmap?: boolean;
29
29
  };
30
30
 
@@ -55,13 +55,13 @@ export default function ImageryLayers({ tiles, cesiumIonAccessToken }: Props) {
55
55
  id,
56
56
  provider: providers[id]?.[2],
57
57
  }))
58
- .map(({ id, tile_opacity: opacity, tile_zoomLevel, provider, heatmap }, i) =>
58
+ .map(({ id, opacity, zoomLevel, provider, heatmap }, i) =>
59
59
  provider ? (
60
60
  <ImageryLayer
61
61
  key={`${id}_${i}_${counter}`}
62
62
  imageryProvider={provider}
63
- minimumTerrainLevel={tile_zoomLevel?.[0]}
64
- maximumTerrainLevel={tile_zoomLevel?.[1]}
63
+ minimumTerrainLevel={zoomLevel?.[0]}
64
+ maximumTerrainLevel={zoomLevel?.[1]}
65
65
  alpha={opacity}
66
66
  index={i}
67
67
  colorToAlpha={heatmap ? Color.WHITE : undefined}
@@ -89,17 +89,17 @@ export function useImageryProviders({
89
89
  url?: string;
90
90
  cesiumIonAccessToken?: string;
91
91
  heatmap?: boolean;
92
- tile_zoomLevel?: number[];
92
+ zoomLevel?: number[];
93
93
  }) => Promise<ImageryProvider> | ImageryProvider | null;
94
94
  };
95
95
  }): { providers: Providers; updated: boolean } {
96
96
  const newTile = useCallback(
97
97
  (t: Tile, ciat?: string) =>
98
- presets[t.tile_type || "default"]({
99
- url: t.tile_url,
98
+ presets[t.type || "default"]({
99
+ url: t.url,
100
100
  cesiumIonAccessToken: ciat,
101
101
  heatmap: t.heatmap,
102
- tile_zoomLevel: t.tile_zoomLevelForURL,
102
+ zoomLevel: t.zoomLevelForURL,
103
103
  }),
104
104
  [presets],
105
105
  );
@@ -152,10 +152,10 @@ export function useImageryProviders({
152
152
  : [
153
153
  key,
154
154
  added ||
155
- prevType !== tile.tile_type ||
156
- prevUrl !== tile.tile_url ||
157
- (isCesiumAccessTokenUpdated && (!tile.tile_type || tile.tile_type === "default"))
158
- ? [tile.tile_type, tile.tile_url, newTile(tile, cesiumIonAccessToken)]
155
+ prevType !== tile.type ||
156
+ prevUrl !== tile.url ||
157
+ (isCesiumAccessTokenUpdated && (!tile.type || tile.type === "default"))
158
+ ? [tile.type, tile.url, newTile(tile, cesiumIonAccessToken)]
159
159
  : [prevType, prevUrl, prevProvider],
160
160
  ],
161
161
  )
@@ -169,9 +169,7 @@ export function useImageryProviders({
169
169
  !!added.length ||
170
170
  !!isCesiumAccessTokenUpdated ||
171
171
  !isEqual(prevTileKeys.current, tileKeys) ||
172
- rawProviders.some(
173
- p => p.tile && (p.prevType !== p.tile.tile_type || p.prevUrl !== p.tile.tile_url),
174
- );
172
+ rawProviders.some(p => p.tile && (p.prevType !== p.tile.type || p.prevUrl !== p.tile.url));
175
173
 
176
174
  prevTileKeys.current = tileKeys;
177
175
  prevCesiumIonAccessToken.current = cesiumIonAccessToken;
@@ -4,7 +4,7 @@ import { BoundingSphere, Cartesian3, SceneTransforms, Cartesian2, JulianDate } f
4
4
  import { useEffect, useState } from "react";
5
5
  import { useCesium } from "resium";
6
6
 
7
- import type { SceneProperty } from "../../..";
7
+ import type { ViewerProperty } from "../../..";
8
8
  import { TimelineManagerRef } from "../../../../Map/useTimelineManager";
9
9
  import { useIcon } from "../../common";
10
10
 
@@ -12,7 +12,7 @@ import Crosshair from "./crosshair.svg?react";
12
12
 
13
13
  export type Props = {
14
14
  className?: string;
15
- property?: SceneProperty;
15
+ property?: ViewerProperty;
16
16
  timelineManagerRef?: TimelineManagerRef;
17
17
  };
18
18
 
@@ -29,17 +29,17 @@ export default function Indicator({
29
29
  mountOnEnter: true,
30
30
  unmountOnExit: true,
31
31
  });
32
- const { indicator_type, indicator_image, indicator_image_scale } = property?.indicator ?? {};
33
- const [img, w, h] = useIcon({ image: indicator_image, imageSize: indicator_image_scale });
32
+ const { type, image, imageScale } = property?.indicator ?? {};
33
+ const [img, w, h] = useIcon({ image: image, imageSize: imageScale });
34
34
 
35
35
  useEffect(() => {
36
- !(!indicator_type || indicator_type === "default")
36
+ !(!type || type === "default")
37
37
  ? viewer?.selectionIndicator.viewModel.selectionIndicatorElement.setAttribute(
38
38
  "hidden",
39
39
  "true",
40
40
  )
41
41
  : viewer?.selectionIndicator.viewModel.selectionIndicatorElement.removeAttribute("hidden");
42
- }, [indicator_type, viewer, viewer?.selectionIndicator]);
42
+ }, [type, viewer, viewer?.selectionIndicator]);
43
43
 
44
44
  useEffect(() => {
45
45
  if (!viewer) return;
@@ -90,7 +90,7 @@ export default function Indicator({
90
90
  }, [viewer, timelineManagerRef]);
91
91
 
92
92
  return transition !== "unmounted" && pos ? (
93
- indicator_type === "crosshair" ? (
93
+ type === "crosshair" ? (
94
94
  <div
95
95
  className={className}
96
96
  style={{
@@ -109,7 +109,7 @@ export default function Indicator({
109
109
  }}>
110
110
  <Crosshair />
111
111
  </div>
112
- ) : indicator_type === "custom" ? (
112
+ ) : type === "custom" ? (
113
113
  <img
114
114
  src={img}
115
115
  width={w}
@@ -0,0 +1,144 @@
1
+ import { Math as CesiumMath, Scene, Viewer } from "cesium";
2
+ import { isEqual } from "lodash-es";
3
+ import { useCallback, useEffect, useRef } from "react";
4
+ import { CesiumComponentRef } from "resium";
5
+ import { RefObject } from "use-callback-ref/dist/es5/types";
6
+ import { useCustomCompareCallback } from "use-custom-compare";
7
+
8
+ import { EngineRef, ViewerProperty } from "../..";
9
+ import { Camera } from "../../../mantle";
10
+ import { FEATURE_FLAGS } from "../../../Visualizer";
11
+ import { getCamera } from "../common";
12
+
13
+ import { useCameraLimiter } from "./useCameraLimiter";
14
+
15
+ export default ({
16
+ cesium,
17
+ property,
18
+ camera,
19
+ featureFlags,
20
+ engineAPI,
21
+ cameraForceHorizontalRoll = false,
22
+ onCameraChange,
23
+ }: {
24
+ cesium: RefObject<CesiumComponentRef<Viewer>>;
25
+ property?: ViewerProperty;
26
+ engineAPI: EngineRef;
27
+ featureFlags: number;
28
+ camera?: Camera;
29
+ cameraForceHorizontalRoll?: boolean;
30
+ onCameraChange?: (camera: Camera) => void;
31
+ }) => {
32
+ // cache the camera data emitted from viewer camera change
33
+ const emittedCamera = useRef<Camera[]>([]);
34
+ const updateCamera = useCallback(() => {
35
+ const viewer = cesium?.current?.cesiumElement;
36
+ if (!viewer || viewer.isDestroyed() || !onCameraChange) return;
37
+
38
+ const c = getCamera(viewer);
39
+ if (c && !isEqual(c, camera)) {
40
+ emittedCamera.current.push(c);
41
+ // The state change is not sync now. This number is how many state updates can actually happen to be merged within one re-render.
42
+ if (emittedCamera.current.length > 10) {
43
+ emittedCamera.current.shift();
44
+ }
45
+ onCameraChange?.(c);
46
+ }
47
+ }, [cesium, camera, onCameraChange]);
48
+
49
+ const handleCameraChange = useCallback(() => {
50
+ updateCamera();
51
+ }, [updateCamera]);
52
+
53
+ const handleCameraMoveEnd = useCallback(() => {
54
+ updateCamera();
55
+ }, [updateCamera]);
56
+
57
+ useEffect(() => {
58
+ if (camera && !emittedCamera.current.includes(camera)) {
59
+ engineAPI.flyTo(camera, { duration: 0 });
60
+ emittedCamera.current = [];
61
+ }
62
+ }, [camera, engineAPI]);
63
+
64
+ useEffect(() => {
65
+ if (!cesium.current?.cesiumElement) return;
66
+ const allowCameraMove = !!(featureFlags & FEATURE_FLAGS.CAMERA_MOVE);
67
+ const allowCameraZoom = !!(featureFlags & FEATURE_FLAGS.CAMERA_ZOOM);
68
+ const allowCameraTilt = !!(featureFlags & FEATURE_FLAGS.CAMERA_TILT);
69
+ const allowCameraLook = !!(featureFlags & FEATURE_FLAGS.CAMERA_LOOK);
70
+ cesium.current.cesiumElement.scene.screenSpaceCameraController.enableTranslate =
71
+ allowCameraMove;
72
+ cesium.current.cesiumElement.scene.screenSpaceCameraController.enableRotate = allowCameraMove;
73
+ cesium.current.cesiumElement.scene.screenSpaceCameraController.enableLook = allowCameraLook;
74
+ cesium.current.cesiumElement.scene.screenSpaceCameraController.enableTilt = allowCameraTilt;
75
+ cesium.current.cesiumElement.scene.screenSpaceCameraController.enableZoom = allowCameraZoom;
76
+ }, [cesium, featureFlags]);
77
+
78
+ // move to initial position at startup
79
+ const initialCameraFlight = useRef(false);
80
+
81
+ const mountCamera = useCustomCompareCallback(
82
+ () => {
83
+ if (initialCameraFlight.current) return;
84
+ initialCameraFlight.current = true;
85
+ if (property?.camera?.limiter?.enabled && property?.camera?.limiter?.targetArea) {
86
+ engineAPI.flyTo(property?.camera?.limiter?.targetArea, { duration: 0 });
87
+ } else if (property?.camera?.camera) {
88
+ const camera = property?.camera?.camera;
89
+ engineAPI.flyTo(camera as Camera, { duration: 0 });
90
+ }
91
+ const camera = getCamera(cesium?.current?.cesiumElement);
92
+ if (camera) {
93
+ onCameraChange?.(camera);
94
+ }
95
+ },
96
+ [engineAPI, property?.camera?.camera, property?.camera?.limiter?.enabled, onCameraChange],
97
+ (prevDeps, nextDeps) =>
98
+ prevDeps[0] === nextDeps[0] &&
99
+ isEqual(prevDeps[1], nextDeps[1]) &&
100
+ prevDeps[2] === nextDeps[2] &&
101
+ prevDeps[3] === nextDeps[3],
102
+ );
103
+
104
+ const unmountCamera = useCallback(() => {
105
+ initialCameraFlight.current = false;
106
+ }, []);
107
+
108
+ // camera limiter
109
+ const { cameraViewBoundaries, cameraViewOuterBoundaries, cameraViewBoundariesMaterial } =
110
+ useCameraLimiter(cesium, camera, property?.camera?.limiter);
111
+
112
+ // horizontal roll
113
+ const fixCameraHorizontal = useCallback(
114
+ (scene: Scene) => {
115
+ if (!scene.camera || !cameraForceHorizontalRoll) return;
116
+ if (Math.abs(CesiumMath.negativePiToPi(scene.camera.roll)) > Math.PI / 86400) {
117
+ scene.camera.setView({
118
+ orientation: {
119
+ heading: scene.camera.heading,
120
+ pitch: scene.camera.pitch,
121
+ roll: 0,
122
+ },
123
+ });
124
+ }
125
+ },
126
+ [cameraForceHorizontalRoll],
127
+ );
128
+
129
+ useEffect(() => {
130
+ const viewer = cesium.current?.cesiumElement;
131
+ if (!viewer) return;
132
+ return viewer.scene.preRender.addEventListener(fixCameraHorizontal);
133
+ }, [cesium, fixCameraHorizontal]);
134
+
135
+ return {
136
+ cameraViewBoundaries,
137
+ cameraViewOuterBoundaries,
138
+ cameraViewBoundariesMaterial,
139
+ mountCamera,
140
+ unmountCamera,
141
+ handleCameraChange,
142
+ handleCameraMoveEnd,
143
+ };
144
+ };
@@ -12,10 +12,9 @@ import type { Viewer as CesiumViewer } from "cesium";
12
12
  import { useEffect, useMemo, useState, RefObject } from "react";
13
13
  import { CesiumComponentRef } from "resium";
14
14
 
15
- import type { SceneProperty } from "..";
16
- import { Camera } from "../../utils";
17
-
18
- import { getCamera } from "./common";
15
+ import { CameraLimiterProperty } from "../../../Map";
16
+ import { Camera } from "../../../utils";
17
+ import { getCamera } from "../common";
19
18
 
20
19
  const targetWidth = 1000000;
21
20
  const targetLength = 1000000;
@@ -23,44 +22,30 @@ const targetLength = 1000000;
23
22
  export function useCameraLimiter(
24
23
  cesium: RefObject<CesiumComponentRef<CesiumViewer>>,
25
24
  camera: Camera | undefined,
26
- property: SceneProperty["cameraLimiter"] | undefined,
25
+ property: CameraLimiterProperty | undefined,
27
26
  ) {
28
27
  const geodesic = useMemo(() => {
29
28
  const viewer = cesium.current?.cesiumElement;
30
29
  if (
31
30
  !viewer ||
32
31
  viewer.isDestroyed() ||
33
- !property?.cameraLimitterEnabled ||
34
- !property.cameraLimitterTargetArea?.lng ||
35
- !property.cameraLimitterTargetArea.lat
32
+ !property?.enabled ||
33
+ !property.targetArea?.lng ||
34
+ !property.targetArea.lat
36
35
  ) {
37
36
  return undefined;
38
37
  }
39
38
 
40
- return getGeodesic(
41
- viewer,
42
- property.cameraLimitterTargetArea.lng,
43
- property.cameraLimitterTargetArea.lat,
44
- );
45
- }, [
46
- cesium,
47
- property?.cameraLimitterEnabled,
48
- property?.cameraLimitterTargetArea?.lng,
49
- property?.cameraLimitterTargetArea?.lat,
50
- ]);
39
+ return getGeodesic(viewer, property.targetArea.lng, property.targetArea.lat);
40
+ }, [cesium, property?.enabled, property?.targetArea?.lng, property?.targetArea?.lat]);
51
41
 
52
42
  // calculate inner limiter dimensions
53
43
  const limiterDimensions = useMemo((): InnerLimiterDimensions | undefined => {
54
44
  if (!geodesic) return undefined;
55
45
 
56
- const width =
57
- typeof property?.cameraLimitterTargetWidth === "number"
58
- ? property.cameraLimitterTargetWidth
59
- : targetWidth;
46
+ const width = typeof property?.targetWidth === "number" ? property.targetWidth : targetWidth;
60
47
  const length =
61
- typeof property?.cameraLimitterTargetLength === "number"
62
- ? property.cameraLimitterTargetLength
63
- : targetLength;
48
+ typeof property?.targetLength === "number" ? property.targetLength : targetLength;
64
49
 
65
50
  const { cartesianArray, cartographicDimensions } = calcBoundaryBox(
66
51
  geodesic,
@@ -72,7 +57,7 @@ export function useCameraLimiter(
72
57
  cartographicDimensions,
73
58
  cartesianArray,
74
59
  };
75
- }, [property?.cameraLimitterTargetWidth, property?.cameraLimitterTargetLength, geodesic]);
60
+ }, [property?.targetWidth, property?.targetLength, geodesic]);
76
61
 
77
62
  // calculate maximum camera view (outer boundaries)
78
63
  const [cameraViewOuterBoundaries, setCameraViewOuterBoundaries] = useState<
@@ -81,19 +66,19 @@ export function useCameraLimiter(
81
66
 
82
67
  useEffect(() => {
83
68
  const viewer = cesium.current?.cesiumElement;
84
- if (!viewer || viewer.isDestroyed() || !property?.cameraLimitterTargetArea || !geodesic) return;
69
+ if (!viewer || viewer.isDestroyed() || !property?.targetArea || !geodesic) return;
85
70
 
86
71
  const camera = new CesiumCamera(viewer.scene);
87
72
  camera.setView({
88
73
  destination: Cartesian3.fromDegrees(
89
- property.cameraLimitterTargetArea.lng,
90
- property.cameraLimitterTargetArea.lat,
91
- property.cameraLimitterTargetArea.height,
74
+ property.targetArea.lng,
75
+ property.targetArea.lat,
76
+ property.targetArea.height,
92
77
  ),
93
78
  orientation: {
94
- heading: property?.cameraLimitterTargetArea.heading,
95
- pitch: property?.cameraLimitterTargetArea.pitch,
96
- roll: property?.cameraLimitterTargetArea.roll,
79
+ heading: property?.targetArea.heading,
80
+ pitch: property?.targetArea.pitch,
81
+ roll: property?.targetArea.roll,
97
82
  up: camera.up,
98
83
  },
99
84
  });
@@ -103,10 +88,8 @@ export function useCameraLimiter(
103
88
  const rectangleHalfWidth = Rectangle.computeWidth(computedViewRectangle) * Math.PI * 1000000;
104
89
  const rectangleHalfHeight = Rectangle.computeHeight(computedViewRectangle) * Math.PI * 1000000;
105
90
 
106
- const {
107
- cameraLimitterTargetWidth: width = targetWidth,
108
- cameraLimitterTargetLength: length = targetLength,
109
- } = property ?? {};
91
+ const { targetWidth: width = targetWidth, targetLength: length = targetLength } =
92
+ property ?? {};
110
93
 
111
94
  const { cartesianArray } = calcBoundaryBox(
112
95
  geodesic,
@@ -121,13 +104,7 @@ export function useCameraLimiter(
121
104
  useEffect(() => {
122
105
  const viewer = cesium?.current?.cesiumElement;
123
106
  const camera = getCamera(cesium?.current?.cesiumElement);
124
- if (
125
- !viewer ||
126
- viewer.isDestroyed() ||
127
- !camera ||
128
- !property?.cameraLimitterEnabled ||
129
- !limiterDimensions
130
- )
107
+ if (!viewer || viewer.isDestroyed() || !camera || !property?.enabled || !limiterDimensions)
131
108
  return;
132
109
  viewer.camera.setView({
133
110
  destination: getAllowedCameraDestination(camera, limiterDimensions),