@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.
- package/dist/core.js +8312 -8318
- package/dist/core.umd.cjs +75 -75
- package/dist/index.d.ts +183 -184
- package/package.json +1 -1
- package/src/Map/ClusteredLayers/index.tsx +2 -2
- package/src/Map/Layer/index.tsx +3 -3
- package/src/Map/Layers/index.tsx +2 -2
- package/src/Map/hooks.ts +0 -4
- package/src/Map/index.tsx +8 -5
- package/src/Map/types/index.ts +5 -182
- package/src/Map/types/viewerProperty.ts +216 -0
- package/src/Map/useTimelineManager.ts +2 -2
- package/src/Map/utils.ts +1 -43
- package/src/Visualizer/coreContext.tsx +1 -8
- package/src/Visualizer/hooks.ts +6 -75
- package/src/Visualizer/index.stories.tsx +4 -48
- package/src/Visualizer/index.tsx +8 -6
- package/src/Visualizer/useCoreAPI.ts +30 -0
- package/src/engines/Cesium/Feature/Ellipse/index.stories.tsx +1 -1
- package/src/engines/Cesium/Feature/Frustum/index.stories.tsx +1 -1
- package/src/engines/Cesium/Feature/Model/index.stories.tsx +1 -1
- package/src/engines/Cesium/Feature/Model/index.tsx +10 -9
- package/src/engines/Cesium/Feature/Polygon/index.stories.tsx +8 -6
- package/src/engines/Cesium/Feature/Raster/index.stories.tsx +2 -2
- package/src/engines/Cesium/Feature/Resource/index.stories.tsx +1 -1
- package/src/engines/Cesium/Feature/Tileset/hooks.ts +12 -11
- package/src/engines/Cesium/Feature/Tileset/index.stories.tsx +1 -1
- package/src/engines/Cesium/Feature/Tileset/index.tsx +4 -4
- package/src/engines/Cesium/Feature/index.tsx +5 -4
- package/src/engines/Cesium/Feature/utils.tsx +2 -2
- package/src/engines/Cesium/core/Globe.tsx +36 -68
- package/src/engines/Cesium/core/Imagery.test.ts +9 -9
- package/src/engines/Cesium/core/Imagery.tsx +17 -19
- package/src/engines/Cesium/core/Indicator/Indicator.tsx +8 -8
- package/src/engines/Cesium/hooks/useCamera.ts +144 -0
- package/src/engines/Cesium/{cameraLimiter.ts → hooks/useCameraLimiter.ts} +22 -45
- package/src/engines/Cesium/{useEngineRef.test.tsx → hooks/useEngineRef.test.tsx} +14 -10
- package/src/engines/Cesium/{useEngineRef.ts → hooks/useEngineRef.ts} +17 -17
- package/src/engines/Cesium/hooks/useExplicitRender.ts +65 -0
- package/src/engines/Cesium/hooks/useLayerDragDrop.ts +77 -0
- package/src/engines/Cesium/{VertexTerrainElevationMaterial.ts → hooks/useOverrideGlobeShader/VertexTerrainElevationMaterial.ts} +3 -2
- package/src/engines/Cesium/{useOverrideGlobeShader.ts → hooks/useOverrideGlobeShader/useOverrideGlobeShader.ts} +19 -16
- package/src/engines/Cesium/hooks/useViewerProperty.ts +90 -0
- package/src/engines/Cesium/hooks.ts +117 -353
- package/src/engines/Cesium/index.stories.tsx +1 -1
- package/src/engines/Cesium/index.tsx +48 -50
- package/src/engines/index.ts +1 -1
- /package/src/engines/Cesium/{JapanSeaLevelEllipsoid.ts → hooks/useOverrideGlobeShader/JapanSeaLevelEllipsoid.ts} +0 -0
- /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 {
|
|
13
|
-
import {
|
|
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?:
|
|
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
|
|
25
|
+
[property?.terrain],
|
|
28
26
|
);
|
|
29
27
|
|
|
30
28
|
const terrainProvider = useMemo((): Promise<TerrainProvider> | TerrainProvider | undefined => {
|
|
31
29
|
const opts = {
|
|
32
|
-
terrain: terrainProperty?.
|
|
33
|
-
terrainType: terrainProperty?.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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?.
|
|
44
|
-
terrainProperty?.
|
|
45
|
-
terrainProperty?.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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?.
|
|
54
|
-
[property?.
|
|
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
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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={!!
|
|
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["
|
|
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: ({
|
|
78
|
+
cesium: ({ ionAccessToken, normal }) =>
|
|
106
79
|
CesiumTerrainProvider.fromUrl(
|
|
107
80
|
IonResource.fromAssetId(1, {
|
|
108
|
-
accessToken:
|
|
81
|
+
accessToken: ionAccessToken,
|
|
109
82
|
}),
|
|
110
83
|
{
|
|
111
|
-
requestVertexNormals:
|
|
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
|
-
|
|
121
|
-
terrainCesiumIonAsset,
|
|
122
|
-
terrainCesiumIonUrl,
|
|
123
|
-
terrainNormal,
|
|
124
|
-
}) =>
|
|
125
|
-
terrainCesiumIonAsset
|
|
92
|
+
cesiumion: ({ ionAccessToken, ionAsset, ionUrl, normal }) =>
|
|
93
|
+
ionAsset
|
|
126
94
|
? CesiumTerrainProvider.fromUrl(
|
|
127
|
-
|
|
128
|
-
IonResource.fromAssetId(parseInt(
|
|
129
|
-
accessToken:
|
|
95
|
+
ionUrl ||
|
|
96
|
+
IonResource.fromAssetId(parseInt(ionAsset, 10), {
|
|
97
|
+
accessToken: ionAccessToken,
|
|
130
98
|
}),
|
|
131
99
|
{
|
|
132
|
-
requestVertexNormals:
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
46
|
-
{ id: "1",
|
|
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",
|
|
62
|
-
{ id: "2",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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,
|
|
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={
|
|
64
|
-
maximumTerrainLevel={
|
|
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
|
-
|
|
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.
|
|
99
|
-
url: t.
|
|
98
|
+
presets[t.type || "default"]({
|
|
99
|
+
url: t.url,
|
|
100
100
|
cesiumIonAccessToken: ciat,
|
|
101
101
|
heatmap: t.heatmap,
|
|
102
|
-
|
|
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.
|
|
156
|
-
prevUrl !== tile.
|
|
157
|
-
(isCesiumAccessTokenUpdated && (!tile.
|
|
158
|
-
? [tile.
|
|
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 {
|
|
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?:
|
|
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 {
|
|
33
|
-
const [img, w, h] = useIcon({ image:
|
|
32
|
+
const { type, image, imageScale } = property?.indicator ?? {};
|
|
33
|
+
const [img, w, h] = useIcon({ image: image, imageSize: imageScale });
|
|
34
34
|
|
|
35
35
|
useEffect(() => {
|
|
36
|
-
!(!
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
) :
|
|
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
|
|
16
|
-
import { Camera } from "
|
|
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:
|
|
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?.
|
|
34
|
-
!property.
|
|
35
|
-
!property.
|
|
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
|
-
|
|
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?.
|
|
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?.
|
|
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?.
|
|
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.
|
|
90
|
-
property.
|
|
91
|
-
property.
|
|
74
|
+
property.targetArea.lng,
|
|
75
|
+
property.targetArea.lat,
|
|
76
|
+
property.targetArea.height,
|
|
92
77
|
),
|
|
93
78
|
orientation: {
|
|
94
|
-
heading: property?.
|
|
95
|
-
pitch: property?.
|
|
96
|
-
roll: property?.
|
|
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
|
-
|
|
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),
|