feat(foundry): close the operational map data loop
This commit is contained in:
@@ -10,11 +10,11 @@ import {
|
||||
Cesium3DTileStyle,
|
||||
CesiumTerrainProvider,
|
||||
CallbackProperty,
|
||||
CallbackPositionProperty,
|
||||
Cartographic,
|
||||
ConstantPositionProperty,
|
||||
CustomDataSource,
|
||||
DefaultProxy,
|
||||
DistanceDisplayCondition,
|
||||
EllipsoidTerrainProvider,
|
||||
Entity,
|
||||
HeightReference,
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
LabelGraphics,
|
||||
Matrix4,
|
||||
Math as CesiumMath,
|
||||
PolygonHierarchy,
|
||||
PointGraphics,
|
||||
PolylineGraphics,
|
||||
Resource,
|
||||
sampleTerrainMostDetailed,
|
||||
ScreenSpaceEventHandler,
|
||||
@@ -36,36 +36,22 @@ import {
|
||||
Viewer,
|
||||
} from "cesium";
|
||||
import "cesium/Build/Cesium/Widgets/widgets.css";
|
||||
import sceneFixture from "../../../registry/fixtures/map/map-operational-v0.1.json";
|
||||
import { MAX_SPIRAL_RADIUS_METERS, spiralSurfaceFrame, type GeodeticRadians } from "./mapSpiralMath.js";
|
||||
import { cameraSurveyPitchForViewport, cameraSurveySampleDistances, cameraSurveySpiralDistance } from "./mapCameraPresets.js";
|
||||
import { mapRuntimeEntityId, type MapRuntimeBinding, type MapRuntimeFact } from "./useMapDataProductRuntime.js";
|
||||
import {
|
||||
mapPresentationProfileForFact,
|
||||
mapRuntimeDisplayLabel,
|
||||
mapRuntimeFactIsVisible,
|
||||
resolveMapPresentationClass,
|
||||
resolveMapPresentationStyle,
|
||||
type MapPresentationFilters,
|
||||
type MapPresentationProfile,
|
||||
} from "./mapPresentationProfile.js";
|
||||
|
||||
type Position = [number, number, number?];
|
||||
const MAX_SPIRAL_SUBSTEPS_PER_FRAME = 300;
|
||||
const TERRAIN_SAMPLE_TIMEOUT_MS = 12_000;
|
||||
const SPIRAL_TILE_WAIT_TIMEOUT_MS = 45_000;
|
||||
type PinPresentation = {
|
||||
variant: "elevated-spike";
|
||||
stemHeightMeters: number;
|
||||
headSizePx: number;
|
||||
stemWidthPx: number;
|
||||
outlineColor: string;
|
||||
outlineOpacity: number;
|
||||
outlineWidthPx: number;
|
||||
labelOffsetX: number;
|
||||
labelOffsetY: number;
|
||||
pinHideCameraHeightMeters?: number;
|
||||
labelHideCameraHeightMeters?: number;
|
||||
};
|
||||
type MapStyleProfile = {
|
||||
id: string;
|
||||
kind: string;
|
||||
color?: string;
|
||||
opacity?: number;
|
||||
size?: number;
|
||||
pinPresentation?: PinPresentation;
|
||||
};
|
||||
type RuntimeConfig = {
|
||||
cesiumVersion: string;
|
||||
provider: string;
|
||||
@@ -202,6 +188,8 @@ export type CesiumMapRendererHandle = {
|
||||
startSpiralAnimation: (config: CameraSpiralConfig) => boolean;
|
||||
stopSpiralAnimation: (reason?: CameraSpiralState["reason"]) => void;
|
||||
getCameraView: () => MapCameraView | null;
|
||||
fitRuntimeEntities: (entityIds?: string[]) => boolean;
|
||||
focusRuntimeEntity: (entityId: string) => boolean;
|
||||
};
|
||||
|
||||
type TerrainRouteSample = {
|
||||
@@ -237,28 +225,50 @@ type SpiralSession = {
|
||||
previousBuildingsFoveatedTimeDelay: number | null;
|
||||
};
|
||||
|
||||
const toCartesian = ([longitude, latitude, height = 0]: Position) => Cartesian3.fromDegrees(longitude, latitude, height);
|
||||
const toCartesianArray = (positions: Position[]) => positions.map(toCartesian);
|
||||
const accent = Color.fromCssColorString("#ff2f92");
|
||||
const violet = Color.fromCssColorString("#8f72dc");
|
||||
const clamp = (value: number, minimum: number, maximum: number) => Math.max(minimum, Math.min(maximum, value));
|
||||
const defaultElevatedPin: PinPresentation = {
|
||||
variant: "elevated-spike",
|
||||
stemHeightMeters: 120,
|
||||
headSizePx: 8,
|
||||
stemWidthPx: 2,
|
||||
outlineColor: "#0c0d12",
|
||||
outlineOpacity: 0.6,
|
||||
outlineWidthPx: 1,
|
||||
labelOffsetX: 10,
|
||||
labelOffsetY: 0,
|
||||
};
|
||||
|
||||
function showBelowCameraHeight(viewer: Viewer, limit?: number) {
|
||||
if (!limit) return true;
|
||||
return new CallbackProperty(() => Number(viewer.camera.positionCartographic?.height || 0) <= limit, false);
|
||||
}
|
||||
|
||||
function elevatedPinGroundHeight(viewer: Viewer, longitude: number, latitude: number, fallbackHeightMeters = 0) {
|
||||
const sampled = viewer.scene.globe.getHeight(Cartographic.fromDegrees(longitude, latitude));
|
||||
return Number.isFinite(sampled) ? Number(sampled) : fallbackHeightMeters;
|
||||
}
|
||||
|
||||
function elevatedPinTopPosition(
|
||||
viewer: Viewer,
|
||||
longitude: number,
|
||||
latitude: number,
|
||||
stemHeightMeters: number,
|
||||
fallbackHeightMeters = 0,
|
||||
) {
|
||||
return new CallbackPositionProperty(() => Cartesian3.fromDegrees(
|
||||
longitude,
|
||||
latitude,
|
||||
elevatedPinGroundHeight(viewer, longitude, latitude, fallbackHeightMeters) + stemHeightMeters,
|
||||
), false);
|
||||
}
|
||||
|
||||
function elevatedPinStemPositions(
|
||||
viewer: Viewer,
|
||||
longitude: number,
|
||||
latitude: number,
|
||||
stemHeightMeters: number,
|
||||
fallbackHeightMeters = 0,
|
||||
) {
|
||||
return new CallbackProperty(() => {
|
||||
const groundHeight = elevatedPinGroundHeight(viewer, longitude, latitude, fallbackHeightMeters);
|
||||
return [
|
||||
Cartesian3.fromDegrees(longitude, latitude, groundHeight),
|
||||
Cartesian3.fromDegrees(longitude, latitude, groundHeight + stemHeightMeters),
|
||||
];
|
||||
}, false);
|
||||
}
|
||||
|
||||
function getCameraView(viewer: Viewer): MapCameraView {
|
||||
const position = viewer.camera.positionCartographic;
|
||||
return {
|
||||
@@ -302,148 +312,6 @@ function interpolateTerrainRouteHeight(samples: TerrainRouteSample[], distanceMe
|
||||
return null;
|
||||
}
|
||||
|
||||
function addFixtureEntities(viewer: Viewer) {
|
||||
const styleProfiles = new Map<string, MapStyleProfile>(
|
||||
(sceneFixture.styleProfiles as unknown as MapStyleProfile[]).map((profile) => [profile.id, profile]),
|
||||
);
|
||||
for (const place of sceneFixture.scene.places) {
|
||||
viewer.entities.add({
|
||||
id: place.id,
|
||||
position: toCartesian(place.position as Position),
|
||||
label: {
|
||||
text: place.label.text,
|
||||
font: "700 28px Arial",
|
||||
fillColor: Color.WHITE,
|
||||
outlineColor: Color.BLACK.withAlpha(0.8),
|
||||
outlineWidth: 4,
|
||||
style: 2,
|
||||
distanceDisplayCondition: new DistanceDisplayCondition(100_000, 50_000_000),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const route of sceneFixture.scene.routes) {
|
||||
viewer.entities.add({
|
||||
id: route.id,
|
||||
polyline: {
|
||||
positions: toCartesianArray(route.coordinates as Position[]),
|
||||
width: 4,
|
||||
material: accent.withAlpha(0.92),
|
||||
clampToGround: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const track of sceneFixture.scene.tracks) {
|
||||
viewer.entities.add({
|
||||
id: track.id,
|
||||
polyline: {
|
||||
positions: toCartesianArray(track.coordinates as Position[]),
|
||||
width: 2,
|
||||
material: violet.withAlpha(0.88),
|
||||
clampToGround: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const zone of sceneFixture.scene.zones) {
|
||||
const ring = zone.geometry.type === "Polygon" ? zone.geometry.coordinates[0] : zone.geometry.coordinates[0][0];
|
||||
viewer.entities.add({
|
||||
id: zone.id,
|
||||
polygon: {
|
||||
hierarchy: new PolygonHierarchy(toCartesianArray(ring as Position[])),
|
||||
material: accent.withAlpha(0.2),
|
||||
},
|
||||
});
|
||||
viewer.entities.add({
|
||||
id: `${zone.id}:boundary`,
|
||||
polyline: {
|
||||
positions: toCartesianArray(ring as Position[]),
|
||||
width: 2,
|
||||
material: accent.withAlpha(0.78),
|
||||
clampToGround: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const object of sceneFixture.scene.movingObjects) {
|
||||
if (object.trace) {
|
||||
viewer.entities.add({
|
||||
id: `${object.id}:trace`,
|
||||
polyline: {
|
||||
positions: toCartesianArray(object.trace as Position[]),
|
||||
width: 2,
|
||||
material: accent.withAlpha(0.52),
|
||||
clampToGround: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
const pinStyle = styleProfiles.get(object.pinStyleProfileId);
|
||||
const pin = pinStyle?.pinPresentation ?? defaultElevatedPin;
|
||||
const pinColor = Color.fromCssColorString(pinStyle?.color || "#ff2f92").withAlpha(pinStyle?.opacity ?? 1);
|
||||
const outlineColor = Color.fromCssColorString(pin.outlineColor).withAlpha(pin.outlineOpacity);
|
||||
const [longitude, latitude] = object.position as Position;
|
||||
const base = Cartesian3.fromDegrees(longitude, latitude, 0);
|
||||
const top = Cartesian3.fromDegrees(longitude, latitude, pin.stemHeightMeters);
|
||||
viewer.entities.add({
|
||||
id: object.id,
|
||||
position: top,
|
||||
polyline: {
|
||||
positions: [base, top],
|
||||
width: pin.stemWidthPx,
|
||||
material: pinColor,
|
||||
show: showBelowCameraHeight(viewer, pin.pinHideCameraHeightMeters),
|
||||
},
|
||||
point: {
|
||||
pixelSize: pin.headSizePx,
|
||||
color: pinColor,
|
||||
outlineColor,
|
||||
outlineWidth: pin.outlineWidthPx,
|
||||
show: showBelowCameraHeight(viewer, pin.pinHideCameraHeightMeters),
|
||||
},
|
||||
label: {
|
||||
text: object.label.text,
|
||||
font: "700 13px Arial",
|
||||
fillColor: Color.WHITE,
|
||||
showBackground: true,
|
||||
backgroundColor: Color.BLACK.withAlpha(0.72),
|
||||
backgroundPadding: new Cartesian2(10, 7),
|
||||
pixelOffset: new Cartesian2(pin.labelOffsetX, pin.labelOffsetY),
|
||||
horizontalOrigin: HorizontalOrigin.LEFT,
|
||||
verticalOrigin: VerticalOrigin.BOTTOM,
|
||||
heightReference: HeightReference.NONE,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
show: showBelowCameraHeight(viewer, pin.labelHideCameraHeightMeters),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const station of sceneFixture.scene.stations) {
|
||||
viewer.entities.add({
|
||||
id: station.id,
|
||||
position: toCartesian(station.position as Position),
|
||||
point: { pixelSize: station.stationType === "metro" ? 21 : 18, color: violet, outlineColor: Color.WHITE, outlineWidth: 3 },
|
||||
label: {
|
||||
text: station.label.text,
|
||||
font: "700 14px Arial",
|
||||
fillColor: Color.WHITE,
|
||||
showBackground: true,
|
||||
backgroundColor: Color.BLACK.withAlpha(0.72),
|
||||
backgroundPadding: new Cartesian2(10, 7),
|
||||
pixelOffset: new Cartesian2(0, -33),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeDisplayLabel(fact: MapRuntimeFact) {
|
||||
for (const key of ["label", "name", "title", "subject_id"]) {
|
||||
const value = fact.attributes[key];
|
||||
if (typeof value === "string" && value.trim()) return value.trim();
|
||||
}
|
||||
return fact.sourceId;
|
||||
}
|
||||
|
||||
function runtimePointColor(fact: MapRuntimeFact) {
|
||||
// This is a semantic default for the generic Map entity-stream adapter,
|
||||
// not a provider style. A renderer-neutral style profile can refine it
|
||||
@@ -459,6 +327,8 @@ function syncRuntimeDataSources(
|
||||
viewer: Viewer,
|
||||
dataSources: Map<string, CustomDataSource>,
|
||||
bindings: MapRuntimeBinding[],
|
||||
presentationProfiles: MapPresentationProfile[],
|
||||
presentationFilters: MapPresentationFilters,
|
||||
) {
|
||||
const activeBindings = new Map(bindings
|
||||
.filter((binding) => binding.slotId === "points")
|
||||
@@ -479,35 +349,98 @@ function syncRuntimeDataSources(
|
||||
}
|
||||
const wanted = new Set<string>();
|
||||
for (const fact of binding.facts) {
|
||||
if (!fact.geometry) continue;
|
||||
const profile = mapPresentationProfileForFact(
|
||||
presentationProfiles,
|
||||
binding.presentationProfileId,
|
||||
fact.semanticType,
|
||||
);
|
||||
const presentationClass = profile ? resolveMapPresentationClass(fact, profile) : undefined;
|
||||
if (
|
||||
!fact.geometry
|
||||
|| (profile && !mapRuntimeFactIsVisible(fact, profile, presentationFilters, binding.bindingId))
|
||||
) continue;
|
||||
const entityId = mapRuntimeEntityId(binding.bindingId, fact);
|
||||
wanted.add(entityId);
|
||||
const [longitude, latitude] = fact.geometry.coordinates;
|
||||
const color = runtimePointColor(fact);
|
||||
const label = runtimeDisplayLabel(fact);
|
||||
const resolvedStyle = profile ? resolveMapPresentationStyle(profile, presentationClass) : undefined;
|
||||
const color = resolvedStyle
|
||||
? Color.fromCssColorString(resolvedStyle.color).withAlpha(resolvedStyle.opacity)
|
||||
: runtimePointColor(fact);
|
||||
const label = mapRuntimeDisplayLabel(fact, profile);
|
||||
const entity = dataSource.entities.getById(entityId) ?? dataSource.entities.add({ id: entityId });
|
||||
entity.name = label;
|
||||
entity.position = new ConstantPositionProperty(Cartesian3.fromDegrees(longitude, latitude, 0));
|
||||
entity.point = new PointGraphics({
|
||||
pixelSize: 10,
|
||||
color,
|
||||
outlineColor: Color.fromCssColorString("#0c0d12").withAlpha(0.72),
|
||||
outlineWidth: 2,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
entity.label = new LabelGraphics({
|
||||
text: label,
|
||||
font: "700 13px Arial",
|
||||
fillColor: Color.WHITE,
|
||||
showBackground: true,
|
||||
backgroundColor: Color.BLACK.withAlpha(0.72),
|
||||
backgroundPadding: new Cartesian2(10, 7),
|
||||
pixelOffset: new Cartesian2(10, 0),
|
||||
horizontalOrigin: HorizontalOrigin.LEFT,
|
||||
verticalOrigin: VerticalOrigin.BOTTOM,
|
||||
heightReference: HeightReference.NONE,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
if (profile) {
|
||||
const fallbackHeightMeters = typeof fact.attributes.elevation_meters === "number" && Number.isFinite(fact.attributes.elevation_meters)
|
||||
? fact.attributes.elevation_meters
|
||||
: 0;
|
||||
entity.position = elevatedPinTopPosition(
|
||||
viewer,
|
||||
longitude,
|
||||
latitude,
|
||||
profile.target.stemHeightMeters,
|
||||
fallbackHeightMeters,
|
||||
);
|
||||
entity.polyline = new PolylineGraphics({
|
||||
positions: elevatedPinStemPositions(
|
||||
viewer,
|
||||
longitude,
|
||||
latitude,
|
||||
profile.target.stemHeightMeters,
|
||||
fallbackHeightMeters,
|
||||
),
|
||||
width: profile.target.stemWidthPx,
|
||||
material: color,
|
||||
show: showBelowCameraHeight(viewer, profile.target.hideCameraHeightMeters),
|
||||
});
|
||||
entity.point = new PointGraphics({
|
||||
pixelSize: profile.target.headSizePx,
|
||||
color,
|
||||
outlineColor: Color.fromCssColorString(profile.target.outlineColor).withAlpha(profile.target.outlineOpacity),
|
||||
outlineWidth: profile.target.outlineWidthPx,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
show: showBelowCameraHeight(viewer, profile.target.hideCameraHeightMeters),
|
||||
});
|
||||
entity.label = new LabelGraphics({
|
||||
text: label,
|
||||
font: `${profile.label.fontWeight} ${profile.label.sizePx}px Arial`,
|
||||
fillColor: Color.fromCssColorString(profile.label.color),
|
||||
outlineColor: Color.fromCssColorString(profile.label.outlineColor),
|
||||
outlineWidth: profile.label.outlineWidthPx,
|
||||
style: 2,
|
||||
showBackground: profile.label.backgroundOpacity > 0,
|
||||
backgroundColor: Color.fromCssColorString(profile.label.backgroundColor).withAlpha(profile.label.backgroundOpacity),
|
||||
backgroundPadding: new Cartesian2(profile.label.paddingX, profile.label.paddingY),
|
||||
pixelOffset: new Cartesian2(profile.label.offsetX, profile.label.offsetY),
|
||||
horizontalOrigin: HorizontalOrigin.LEFT,
|
||||
verticalOrigin: VerticalOrigin.BOTTOM,
|
||||
heightReference: HeightReference.NONE,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
show: profile.label.mode !== "none" && showBelowCameraHeight(viewer, profile.label.hideCameraHeightMeters),
|
||||
});
|
||||
} else {
|
||||
entity.position = new ConstantPositionProperty(Cartesian3.fromDegrees(longitude, latitude, 0));
|
||||
entity.polyline = undefined;
|
||||
entity.point = new PointGraphics({
|
||||
pixelSize: 10,
|
||||
color,
|
||||
outlineColor: Color.fromCssColorString("#0c0d12").withAlpha(0.72),
|
||||
outlineWidth: 2,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
entity.label = new LabelGraphics({
|
||||
text: label,
|
||||
font: "700 13px Arial",
|
||||
fillColor: Color.WHITE,
|
||||
showBackground: true,
|
||||
backgroundColor: Color.BLACK.withAlpha(0.72),
|
||||
backgroundPadding: new Cartesian2(10, 7),
|
||||
pixelOffset: new Cartesian2(10, 0),
|
||||
horizontalOrigin: HorizontalOrigin.LEFT,
|
||||
verticalOrigin: VerticalOrigin.BOTTOM,
|
||||
heightReference: HeightReference.NONE,
|
||||
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const entity of [...dataSource.entities.values]) {
|
||||
if (typeof entity.id === "string" && !wanted.has(entity.id)) dataSource.entities.remove(entity);
|
||||
@@ -530,9 +463,9 @@ function rebuildElevatedGrid(viewer: Viewer, dataSource: CustomDataSource, prese
|
||||
const safeStepKm = clamp(stepKm, 0.25, 100);
|
||||
const safeRadiusKm = clamp(presentation.gridRadiusKm, safeStepKm, 150);
|
||||
const stepsPerSide = Math.min(32, Math.max(1, Math.floor(safeRadiusKm / safeStepKm)));
|
||||
const center = sceneFixture.viewport.center as Position;
|
||||
const latitude = center[1];
|
||||
const longitude = center[0];
|
||||
const cameraPosition = viewer.camera.positionCartographic;
|
||||
const latitude = cameraPosition ? CesiumMath.toDegrees(cameraPosition.latitude) : 55.751244;
|
||||
const longitude = cameraPosition ? CesiumMath.toDegrees(cameraPosition.longitude) : 37.618423;
|
||||
const metersPerLatitudeDegree = 110_574;
|
||||
const metersPerLongitudeDegree = Math.max(1, 111_320 * Math.cos(CesiumMath.toRadians(latitude)));
|
||||
const stepMeters = safeStepKm * 1000;
|
||||
@@ -646,6 +579,8 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
initialCamera?: MapCameraView;
|
||||
presentation: MapPresentation;
|
||||
runtimeBindings?: MapRuntimeBinding[];
|
||||
presentationProfiles?: MapPresentationProfile[];
|
||||
presentationFilters?: MapPresentationFilters;
|
||||
}>(function CesiumMapRenderer({
|
||||
onSelect,
|
||||
onGatewayHealth,
|
||||
@@ -657,6 +592,8 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
initialCamera,
|
||||
presentation,
|
||||
runtimeBindings = [],
|
||||
presentationProfiles = [],
|
||||
presentationFilters = {},
|
||||
}, ref) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const creditContainerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -668,6 +605,8 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
const runtimeDataSourcesRef = useRef(new Map<string, CustomDataSource>());
|
||||
const presentationRef = useRef(presentation);
|
||||
const runtimeBindingsRef = useRef(runtimeBindings);
|
||||
const presentationProfilesRef = useRef(presentationProfiles);
|
||||
const presentationFiltersRef = useRef(presentationFilters);
|
||||
const onSelectRef = useRef(onSelect);
|
||||
const onCameraChangeRef = useRef(onCameraChange);
|
||||
const onCacheRefreshConsumedRef = useRef(onCacheRefreshConsumed);
|
||||
@@ -1020,6 +959,34 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
return true;
|
||||
}, [stopSpiralAnimation]);
|
||||
|
||||
const runtimeEntities = useCallback((entityIds?: string[]) => {
|
||||
const allowed = entityIds ? new Set(entityIds) : null;
|
||||
return [...runtimeDataSourcesRef.current.values()].flatMap((dataSource) => (
|
||||
[...dataSource.entities.values].filter((entity) => !allowed || allowed.has(String(entity.id)))
|
||||
));
|
||||
}, []);
|
||||
|
||||
const fitRuntimeEntities = useCallback((entityIds?: string[]) => {
|
||||
const viewer = viewerRef.current;
|
||||
if (!viewer || viewer.isDestroyed()) return false;
|
||||
const entities = runtimeEntities(entityIds);
|
||||
if (!entities.length) return false;
|
||||
void viewer.flyTo(entities, { duration: 0.55 });
|
||||
return true;
|
||||
}, [runtimeEntities]);
|
||||
|
||||
const focusRuntimeEntity = useCallback((entityId: string) => {
|
||||
const viewer = viewerRef.current;
|
||||
if (!viewer || viewer.isDestroyed()) return false;
|
||||
const entity = runtimeEntities([entityId])[0];
|
||||
if (!entity) return false;
|
||||
void viewer.flyTo(entity, {
|
||||
duration: 0.45,
|
||||
offset: new HeadingPitchRange(0, -0.9, 8_000),
|
||||
});
|
||||
return true;
|
||||
}, [runtimeEntities]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
startSpiralAnimation,
|
||||
stopSpiralAnimation,
|
||||
@@ -1027,7 +994,9 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
const viewer = viewerRef.current;
|
||||
return viewer && !viewer.isDestroyed() ? getCameraView(viewer) : null;
|
||||
},
|
||||
}), [startSpiralAnimation, stopSpiralAnimation]);
|
||||
fitRuntimeEntities,
|
||||
focusRuntimeEntity,
|
||||
}), [fitRuntimeEntities, focusRuntimeEntity, startSpiralAnimation, stopSpiralAnimation]);
|
||||
|
||||
useEffect(() => {
|
||||
const stopForPageLeave = () => stopSpiralAnimation("stopped");
|
||||
@@ -1066,10 +1035,18 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
|
||||
useEffect(() => {
|
||||
runtimeBindingsRef.current = runtimeBindings;
|
||||
presentationProfilesRef.current = presentationProfiles;
|
||||
presentationFiltersRef.current = presentationFilters;
|
||||
if (viewerRef.current && !viewerRef.current.isDestroyed()) {
|
||||
syncRuntimeDataSources(viewerRef.current, runtimeDataSourcesRef.current, runtimeBindings);
|
||||
syncRuntimeDataSources(
|
||||
viewerRef.current,
|
||||
runtimeDataSourcesRef.current,
|
||||
runtimeBindings,
|
||||
presentationProfiles,
|
||||
presentationFilters,
|
||||
);
|
||||
}
|
||||
}, [runtimeBindings]);
|
||||
}, [presentationFilters, presentationProfiles, runtimeBindings]);
|
||||
|
||||
useEffect(() => {
|
||||
let viewer: Viewer | undefined;
|
||||
@@ -1146,8 +1123,13 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
const terrain = { world: null as CesiumTerrainProvider | null, ellipsoid: new EllipsoidTerrainProvider() };
|
||||
viewer.terrainProvider = terrain.ellipsoid;
|
||||
viewer.scene.globe.depthTestAgainstTerrain = true;
|
||||
addFixtureEntities(viewer);
|
||||
syncRuntimeDataSources(viewer, runtimeDataSourcesRef.current, runtimeBindingsRef.current);
|
||||
syncRuntimeDataSources(
|
||||
viewer,
|
||||
runtimeDataSourcesRef.current,
|
||||
runtimeBindingsRef.current,
|
||||
presentationProfilesRef.current,
|
||||
presentationFiltersRef.current,
|
||||
);
|
||||
viewerRef.current = viewer;
|
||||
terrainRef.current = terrain;
|
||||
const rebuildGrid = () => rebuildElevatedGrid(viewer!, gridDataSource, presentationRef.current);
|
||||
@@ -1274,11 +1256,11 @@ export const CesiumMapRenderer = forwardRef<CesiumMapRendererHandle, {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const center = toCartesian(sceneFixture.viewport.center as Position);
|
||||
const center = Cartesian3.fromDegrees(37.618423, 55.751244, 0);
|
||||
viewer.camera.lookAt(center, new HeadingPitchRange(
|
||||
CesiumMath.toRadians(sceneFixture.viewport.heading),
|
||||
CesiumMath.toRadians(sceneFixture.viewport.pitch),
|
||||
sceneFixture.viewport.range,
|
||||
0,
|
||||
-0.9,
|
||||
40_000,
|
||||
));
|
||||
viewer.camera.lookAtTransform(Matrix4.IDENTITY);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user