Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions ui/src/components/visualizations/Cesium/CityGmlData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const CityGmlData: React.FC<Props> = ({
const { viewer } = useCesium();
const absolutePrimitiveRef = useRef<Primitive | null>(null);
const groundPrimitiveRef = useRef<GroundPrimitive | null>(null);
const meshPrimitiveRef = useRef<Primitive | null>(null);
const featureMapRef = useRef<Map<string, FeatureInstanceData>>(new Map());
const prevSelectedRef = useRef<string | null>(null);
const { buildLodGeometry, cancelPending } = useLodWorker();
Expand Down Expand Up @@ -87,6 +88,13 @@ const CityGmlData: React.FC<Props> = ({
if (attrs) attrs.show = ShowGeometryInstanceAttribute.toValue(true);
});
});
waitForPrimitive(meshPrimitiveRef.current, () => {
entry.meshInstanceIds.forEach((id) => {
const attrs =
meshPrimitiveRef.current?.getGeometryInstanceAttributes(id);
if (attrs) attrs.show = ShowGeometryInstanceAttribute.toValue(true);
});
});
},
[viewer, waitForPrimitive],
);
Expand Down Expand Up @@ -132,6 +140,13 @@ const CityGmlData: React.FC<Props> = ({
if (attrs) attrs.show = ShowGeometryInstanceAttribute.toValue(false);
});
});
waitForPrimitive(meshPrimitiveRef.current, () => {
entry.meshInstanceIds.forEach((id) => {
const attrs =
meshPrimitiveRef.current?.getGeometryInstanceAttributes(id);
if (attrs) attrs.show = ShowGeometryInstanceAttribute.toValue(false);
});
});
},
[viewer, waitForPrimitive, buildLodGeometry],
);
Expand Down Expand Up @@ -159,19 +174,30 @@ const CityGmlData: React.FC<Props> = ({
viewer.scene.primitives.remove(groundPrimitiveRef.current);
groundPrimitiveRef.current = null;
}
if (meshPrimitiveRef.current) {
viewer.scene.primitives.remove(meshPrimitiveRef.current);
meshPrimitiveRef.current = null;
}

featureMapRef.current.clear();
prevSelectedRef.current = null;

const { absolutePrimitive, groundPrimitive, featureMap, boundingSphere } =
convertFeatureCollectionToPrimitives(cityGmlData.features);
const {
absolutePrimitive,
groundPrimitive,
meshPrimitive,
featureMap,
boundingSphere,
} = convertFeatureCollectionToPrimitives(cityGmlData.features);

absolutePrimitiveRef.current = absolutePrimitive;
groundPrimitiveRef.current = groundPrimitive;
meshPrimitiveRef.current = meshPrimitive;
featureMapRef.current = featureMap;

if (absolutePrimitive) viewer.scene.primitives.add(absolutePrimitive);
if (groundPrimitive) viewer.scene.primitives.add(groundPrimitive);
if (meshPrimitive) viewer.scene.primitives.add(meshPrimitive);

if (boundingSphere) {
viewer.camera.flyToBoundingSphere(boundingSphere, { duration: 1.5 });
Expand Down Expand Up @@ -227,6 +253,7 @@ const CityGmlData: React.FC<Props> = ({
});
viewer.scene.primitives.remove(absolutePrimitiveRef.current);
viewer.scene.primitives.remove(groundPrimitiveRef.current);
viewer.scene.primitives.remove(meshPrimitiveRef.current);
}
};
}, [viewer, cancelPending]);
Expand All @@ -237,9 +264,7 @@ const CityGmlData: React.FC<Props> = ({
waitForPrimitive(absolutePrimitiveRef.current, () => {
featureMapRef.current.forEach((entry, id) => {
const isSelected = id === selectedFeatureId;

if (entry.lodPrimitiveCollection) return;

const shouldShow = !showSelectedFeatureOnly || isSelected;
entry.absoluteInstanceIds.forEach((instanceId) => {
const attrs =
Expand All @@ -252,6 +277,21 @@ const CityGmlData: React.FC<Props> = ({
});
});
});

waitForPrimitive(meshPrimitiveRef.current, () => {
featureMapRef.current.forEach((entry, id) => {
const isSelected = id === selectedFeatureId;
if (entry.lodPrimitiveCollection) return;
const shouldShow = !showSelectedFeatureOnly || isSelected;
entry.meshInstanceIds.forEach((instanceId) => {
const attrs =
meshPrimitiveRef.current?.getGeometryInstanceAttributes(instanceId);
if (attrs) {
attrs.show = ShowGeometryInstanceAttribute.toValue(shouldShow);
}
});
});
});
}, [viewer, showSelectedFeatureOnly, selectedFeatureId, waitForPrimitive]);

return null;
Expand Down
113 changes: 113 additions & 0 deletions ui/src/components/visualizations/Cesium/FlowGeometry3DData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
BoundingSphere,
Primitive,
ShowGeometryInstanceAttribute,
} from "cesium";
import { memo, useEffect, useRef } from "react";
import { useCesium } from "resium";

import {
convertFlowGeometry3DCollectionToPrimitives,
type FlowGeometry3DFeature,
type FlowGeometry3DFeatureInstanceData,
} from "./utils/flowGeometry3DToPrimitives";

type Props = {
flowGeometry3DData: {
type: "FeatureCollection";
features: FlowGeometry3DFeature[];
} | null;
selectedFeatureId?: string | null;
showSelectedFeatureOnly: boolean;
setBoundingSphere: (value: BoundingSphere | null) => void;
};

const FlowGeometry3DData: React.FC<Props> = ({
flowGeometry3DData,
selectedFeatureId,
showSelectedFeatureOnly,
setBoundingSphere,
}) => {
const { viewer } = useCesium();
const meshPrimitiveRef = useRef<Primitive | null>(null);
const linePrimitiveRef = useRef<Primitive | null>(null);
const featureMapRef = useRef<Map<string, FlowGeometry3DFeatureInstanceData>>(
new Map(),
);

// Build primitives from data
useEffect(() => {
if (!flowGeometry3DData || !viewer) return;

if (meshPrimitiveRef.current) {
viewer.scene.primitives.remove(meshPrimitiveRef.current);
meshPrimitiveRef.current = null;
}
if (linePrimitiveRef.current) {
viewer.scene.primitives.remove(linePrimitiveRef.current);
linePrimitiveRef.current = null;
}
featureMapRef.current.clear();

const { meshPrimitive, linePrimitive, featureMap, boundingSphere } =
convertFlowGeometry3DCollectionToPrimitives(flowGeometry3DData.features);

meshPrimitiveRef.current = meshPrimitive;
linePrimitiveRef.current = linePrimitive;
featureMapRef.current = featureMap;

if (meshPrimitive) viewer.scene.primitives.add(meshPrimitive);
if (linePrimitive) viewer.scene.primitives.add(linePrimitive);

if (boundingSphere) {
viewer.camera.flyToBoundingSphere(boundingSphere, { duration: 1.5 });
setBoundingSphere(boundingSphere);
}
}, [flowGeometry3DData, viewer, setBoundingSphere]);

// Handle show/hide based on selection
useEffect(() => {
if (!viewer) return;

const applyVisibility = (primitive: Primitive | null) => {
if (!primitive || !(primitive as any).ready) return;
featureMapRef.current.forEach((entry, id) => {
const isSelected = id === selectedFeatureId;
const shouldShow = !showSelectedFeatureOnly || isSelected;

entry.meshInstanceIds.forEach((instanceId) => {
const attrs =
meshPrimitiveRef.current?.getGeometryInstanceAttributes(instanceId);
if (attrs)
attrs.show = ShowGeometryInstanceAttribute.toValue(shouldShow);
});
entry.lineInstanceIds.forEach((instanceId) => {
const attrs =
linePrimitiveRef.current?.getGeometryInstanceAttributes(instanceId);
if (attrs)
attrs.show = ShowGeometryInstanceAttribute.toValue(shouldShow);
});
});
};

// Primitives are synchronous (asynchronous: false), so they're ready immediately
applyVisibility(meshPrimitiveRef.current);
applyVisibility(linePrimitiveRef.current);
Comment thread
billcookie marked this conversation as resolved.
Outdated
}, [viewer, showSelectedFeatureOnly, selectedFeatureId]);

// Cleanup on unmount
useEffect(() => {
return () => {
if (viewer) {
if (meshPrimitiveRef.current)
viewer.scene.primitives.remove(meshPrimitiveRef.current);
if (linePrimitiveRef.current)
viewer.scene.primitives.remove(linePrimitiveRef.current);
}
};
}, [viewer]);

return null;
};

export default memo(FlowGeometry3DData);
28 changes: 26 additions & 2 deletions ui/src/components/visualizations/Cesium/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { SupportedDataTypes } from "@flow/hooks/useStreamingDebugRunQuery";

import CityGmlData from "./CityGmlData";
import FlowGeometry3DData from "./FlowGeometry3DData";
import GeoJsonData from "./GeoJson";

const defaultCesiumProps: Partial<ViewerProps> = {
Expand Down Expand Up @@ -121,17 +122,23 @@ const CesiumViewer: React.FC<Props> = ({
);

// Separate features by geometry type
const { geoJsonData, cityGmlData } = useMemo(() => {
const { geoJsonData, cityGmlData, flowGeometry3DData } = useMemo(() => {
const features = fileContent?.features || [];

const geoJsonFeatures = features.filter(
(feature: any) => feature?.geometry?.type !== "CityGmlGeometry",
(feature: any) =>
feature?.geometry?.type !== "CityGmlGeometry" &&
feature?.geometry?.type !== "FlowGeometry3D",
);

const cityGmlFeatures = features.filter(
(feature: any) => feature?.geometry?.type === "CityGmlGeometry",
);

const flowGeometry3DFeatures = features.filter(
(feature: any) => feature?.geometry?.type === "FlowGeometry3D",
);

return {
geoJsonData:
geoJsonFeatures.length > 0
Expand All @@ -141,6 +148,13 @@ const CesiumViewer: React.FC<Props> = ({
cityGmlFeatures.length > 0
? { type: "FeatureCollection" as const, features: cityGmlFeatures }
: null,
flowGeometry3DData:
flowGeometry3DFeatures.length > 0
? {
type: "FeatureCollection" as const,
features: flowGeometry3DFeatures,
}
: null,
};
}, [fileContent]);

Expand Down Expand Up @@ -180,6 +194,16 @@ const CesiumViewer: React.FC<Props> = ({
showSelectedFeatureOnly={showSelectedFeatureOnly}
/>
)}

{/* FlowGeometry3D features (triangular meshes and lines) */}
{flowGeometry3DData && (
<FlowGeometry3DData
flowGeometry3DData={flowGeometry3DData}
setBoundingSphere={setCityGmlBoundingSphere}
selectedFeatureId={selectedFeatureId}
showSelectedFeatureOnly={showSelectedFeatureOnly}
/>
)}
</>
)}
</Viewer>
Expand Down
Loading
Loading