From 6606b3e1a136b140472ac176e82d7fa10088691e Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Wed, 11 Feb 2026 16:25:14 +0200 Subject: [PATCH 1/2] fix: flyTo feature on row selected --- .../ModelSelection/CesiumGeojsonLayer.tsx | 38 ++++++ .../Wizard/ModelSelection/ModelSelection.tsx | 43 +++---- src/utils/GetLayerFootprint.ts | 116 ++++++++++++++++++ src/utils/fly-to.tsx | 49 ++++++++ 4 files changed, 223 insertions(+), 23 deletions(-) create mode 100644 src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx create mode 100644 src/utils/GetLayerFootprint.ts create mode 100644 src/utils/fly-to.tsx diff --git a/src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx b/src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx new file mode 100644 index 0000000..2fbbcfb --- /dev/null +++ b/src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { CesiumGeojsonLayer, CesiumRectangle, RCesiumGeojsonLayerProps } from "@map-colonies/react-components"; +import { FeatureCollection } from "geojson"; +import { getLayerFootprint } from "../../../utils/GetLayerFootprint"; +import { FlyTo } from "../../../utils/fly-to"; + +export const CesiumGeojsonFootprint: React.FC = (props) => { + const [layersFootprints, setlayersFootprints] = useState(); + const [rect, setRect] = useState(undefined); + + useEffect(() => { + let footprintsCollection: FeatureCollection = { + type: 'FeatureCollection', + features: [] + }; + const layer = props.data; + if (layer) { + const footprint = getLayerFootprint(layer, false, true); + if (footprint.type !== 'FeatureCollection') { + footprintsCollection.features.push(footprint); + } else { + footprintsCollection = footprint; + } + } + setlayersFootprints(footprintsCollection); + setRect(new CesiumRectangle()); + }, [props.data]); + + return ( + <> + + {rect && } + + ); +} \ No newline at end of file diff --git a/src/components/Wizard/ModelSelection/ModelSelection.tsx b/src/components/Wizard/ModelSelection/ModelSelection.tsx index f2ec25e..98945fb 100644 --- a/src/components/Wizard/ModelSelection/ModelSelection.tsx +++ b/src/components/Wizard/ModelSelection/ModelSelection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { Geometry } from 'geojson'; import { @@ -8,7 +8,8 @@ import { CesiumConstantProperty, CesiumGeojsonLayer, CesiumMap, - CesiumSceneMode + CesiumSceneMode, + useCesiumMap } from '@map-colonies/react-components'; import { fetchCatalog } from '../../../common/services/CatalogService'; import { getTokenResource } from '../../../utils/cesium'; @@ -18,15 +19,9 @@ import { Terrain } from '../../common/Terrain/Terrain'; import { CatalogTreeNode, WizardSelectionProps } from '../Wizard.types'; import './ModelSelection.css'; +import { CesiumGeojsonFootprint } from './CesiumGeojsonLayer'; export const ModelSelection: React.FC = (props) => { - useEffect(() => { - if (!props.selectedItem) { - props.setIsNextBtnDisabled(true); - } else { - props.setIsNextBtnDisabled(false); - } - }, []); useEffect(() => { if (props.selectedItem?.isSelected) { @@ -67,6 +62,17 @@ export const ModelSelection: React.FC = (props) => { })(); }, []); + const centerCesiumView = useMemo(() => { + return JSON.parse(appConfig.mapCenter); + }, [appConfig.mapCenter]); + + const selectedItemFootprint = useMemo(() => { + if (!props.selectedItem?.['mc:footprint']) { + return; + } + return JSON.parse(props.selectedItem?.['mc:footprint']) as Geometry; + }, [props.selectedItem]); + return ( @@ -90,11 +96,12 @@ export const ModelSelection: React.FC = (props) => { { props.selectedItem?.['mc:links'] && (props.selectedItem?.isShown as boolean) && @@ -104,20 +111,10 @@ export const ModelSelection: React.FC = (props) => { /> } { - props.selectedItem?.['mc:footprint'] && - { - geojsonDataSource.entities.values.forEach((item) => { - if (item.polyline) { - const color = CesiumColor.CYAN; - (item.polyline.width as CesiumConstantProperty).setValue(5); - // @ts-ignore - item.polyline.material = color; - } - }); - }} + data={selectedItemFootprint} /> } diff --git a/src/utils/GetLayerFootprint.ts b/src/utils/GetLayerFootprint.ts new file mode 100644 index 0000000..badeabb --- /dev/null +++ b/src/utils/GetLayerFootprint.ts @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { Feature, FeatureCollection, Geometry, Polygon, Position } from 'geojson'; +import polygonToLine from '@turf/polygon-to-line'; +import bbox from '@turf/bbox'; +import bboxPolygon from '@turf/bbox-polygon'; +import convex from '@turf/convex'; + + +const shrinkExtremeCoordinatesInOuterRing = (geometry: Geometry, factor = 0.99) => { + const LAT_THRESHOLD = 84.9; + const LON_THRESHOLD = 179; + + function maybeShrink([lon, lat]: Position) { + const needsShrink = + Math.abs(lat) > LAT_THRESHOLD || Math.abs(lon) > LON_THRESHOLD; + if (needsShrink) { + return [lon * factor, lat * factor]; + } + return [lon, lat]; + } + + function processRing(ring: Position[]) { + const processed = ring.map(maybeShrink); + + // Ensure ring is closed + const first = processed[0]; + const last = processed[processed.length - 1]; + if (first[0] !== last[0] || first[1] !== last[1]) { + processed.push([...first]); + } + + return processed; + } + + if (geometry.type === "Polygon") { + const [outer, ...holes] = geometry.coordinates; + return { + type: "Polygon", + coordinates: [processRing(outer), ...holes] + } as Geometry; + } + + if (geometry.type === "MultiPolygon") { + return { + type: "MultiPolygon", + coordinates: geometry.coordinates.map(polygon => { + const [outer, ...holes] = polygon; + return [processRing(outer), ...holes]; + }) + } as Geometry; + } + + throw new Error("[shrinkExtremeCoordinatesInOuterRing] Unsupported geometry type: " + geometry.type); +} + +export const getLayerFootprint = (layerFootprint: Geometry, isBbox: boolean, isPolylined = true, isConvexHull = false): Feature | FeatureCollection => { + if (layerFootprint === undefined) { + return { + type: 'Feature', + // @ts-ignore + geometry: null + }; + } + + if (isBbox) { + let geometry = layerFootprint; + switch (layerFootprint.type) { + case 'MultiPolygon': + geometry = (bboxPolygon(bbox(geometry)) as Feature).geometry; + break; + default: + break; + } + + if (isPolylined) { + geometry = (polygonToLine(geometry as Polygon) as Feature).geometry; + } + + return { + type: 'Feature', + geometry: { + ...geometry, + }, + properties: {} + }; + } else { + let geometry: Geometry = shrinkExtremeCoordinatesInOuterRing(layerFootprint, 0.999); + if (isConvexHull) { + // @ts-ignore + geometry = isPolylined ? (polygonToLine(convex(geometry)) as Feature).geometry : (convex(geometry) as Feature).geometry; + return { + type: 'Feature', + geometry: { + ...geometry, + }, + properties: {}, + }; + } else { + if (isPolylined) { + return { + // @ts-ignore + ...polygonToLine(geometry), + properties: {}, + } as Feature + } else { + return { + type: 'Feature', + geometry: { + ...geometry, + }, + properties: {}, + }; + } + } + } +} diff --git a/src/utils/fly-to.tsx b/src/utils/fly-to.tsx new file mode 100644 index 0000000..7dad67b --- /dev/null +++ b/src/utils/fly-to.tsx @@ -0,0 +1,49 @@ +import React, { useEffect } from 'react'; +import { CesiumColor, CesiumRectangle, useCesiumMap } from '@map-colonies/react-components'; +import bbox from '@turf/bbox'; +import { Geometry } from 'geojson'; + +const TRANSPARENT = 0.0; + +interface FlyToProps { + setRect: (rect: CesiumRectangle | undefined) => void; + geometry: Geometry; + tilt?: boolean; +} + +export const generateLayerRectangle = (geometry: Geometry): CesiumRectangle => { + // eslint-disable-next-line + return CesiumRectangle.fromDegrees(...bbox(geometry)) as CesiumRectangle; +}; + +export const FlyTo: React.FC = ({ setRect, geometry, tilt = false }): JSX.Element => { + const mapViewer = useCesiumMap(); + let rect; + + useEffect(() => { + rect = generateLayerRectangle(geometry); + + const rectangle = mapViewer.entities.add({ + rectangle: { + coordinates: rect, + material: CesiumColor.PURPLE.withAlpha(TRANSPARENT) + }, + }); + + void mapViewer.flyTo( + rectangle, !tilt ? { + offset: { + heading: 0, + pitch: -Math.PI / 2, + range: 0 + } + } : {} + ).then(() => { + mapViewer.entities.remove(rectangle); + }); + + setRect(undefined); + }, []); + + return <>; +}; From f3bb371c7c140c4b7e87985d5c421f40c83c5a91 Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Thu, 12 Feb 2026 11:26:54 +0200 Subject: [PATCH 2/2] fix: layer image shown only after flyTo is completed --- ...onLayer.tsx => CesiumGeojsonFootprint.tsx} | 23 ++++++++++---- .../Wizard/ModelSelection/ModelSelection.tsx | 30 +++++++++---------- src/components/common/Terrain/Terrain.tsx | 2 +- .../{cesium.ts => Cesium/CesiumResource.ts} | 2 +- src/utils/{fly-to.tsx => Cesium/FlyTo.tsx} | 6 ++-- src/utils/{ => Cesium}/GetLayerFootprint.ts | 1 - 6 files changed, 39 insertions(+), 25 deletions(-) rename src/components/Wizard/ModelSelection/{CesiumGeojsonLayer.tsx => CesiumGeojsonFootprint.tsx} (66%) rename src/utils/{cesium.ts => Cesium/CesiumResource.ts} (94%) rename src/utils/{fly-to.tsx => Cesium/FlyTo.tsx} (87%) rename src/utils/{ => Cesium}/GetLayerFootprint.ts (99%) diff --git a/src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx b/src/components/Wizard/ModelSelection/CesiumGeojsonFootprint.tsx similarity index 66% rename from src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx rename to src/components/Wizard/ModelSelection/CesiumGeojsonFootprint.tsx index 2fbbcfb..dae5bdb 100644 --- a/src/components/Wizard/ModelSelection/CesiumGeojsonLayer.tsx +++ b/src/components/Wizard/ModelSelection/CesiumGeojsonFootprint.tsx @@ -1,10 +1,15 @@ import { useEffect, useState } from "react"; import { CesiumGeojsonLayer, CesiumRectangle, RCesiumGeojsonLayerProps } from "@map-colonies/react-components"; import { FeatureCollection } from "geojson"; -import { getLayerFootprint } from "../../../utils/GetLayerFootprint"; -import { FlyTo } from "../../../utils/fly-to"; +import { getLayerFootprint } from "../../../utils/Cesium/GetLayerFootprint"; +import { FlyTo } from "../../../utils/Cesium/FlyTo"; -export const CesiumGeojsonFootprint: React.FC = (props) => { +interface CesiumGeojsonFootprintProps extends RCesiumGeojsonLayerProps { + id: string; + setFinishedFlying?: (finished: boolean) => void; +} + +export const CesiumGeojsonFootprint: React.FC = (props) => { const [layersFootprints, setlayersFootprints] = useState(); const [rect, setRect] = useState(undefined); @@ -24,7 +29,7 @@ export const CesiumGeojsonFootprint: React.FC = (props } setlayersFootprints(footprintsCollection); setRect(new CesiumRectangle()); - }, [props.data]); + }, [props.id]); return ( <> @@ -32,7 +37,15 @@ export const CesiumGeojsonFootprint: React.FC = (props {...props} data={layersFootprints} /> - {rect && } + { + rect && + + } ); } \ No newline at end of file diff --git a/src/components/Wizard/ModelSelection/ModelSelection.tsx b/src/components/Wizard/ModelSelection/ModelSelection.tsx index 01e5ebe..aafba23 100644 --- a/src/components/Wizard/ModelSelection/ModelSelection.tsx +++ b/src/components/Wizard/ModelSelection/ModelSelection.tsx @@ -4,26 +4,24 @@ import { Geometry } from 'geojson'; import { Box, Cesium3DTileset, - CesiumColor, - CesiumConstantProperty, - CesiumGeojsonLayer, CesiumMap, - CesiumSceneMode, - useCesiumMap + CesiumSceneMode } from '@map-colonies/react-components'; import { fetchCatalog } from '../../../common/services/CatalogService'; import { Curtain } from '../../../common/Curtain/curtain'; -import { getTokenResource } from '../../../utils/cesium'; +import { getTokenResource } from '../../../utils/Cesium/CesiumResource'; import appConfig from '../../../utils/Config'; import { CatalogTree } from '../../common/Tree/CatalogTree/CatalogTree'; import { Terrain } from '../../common/Terrain/Terrain'; -import { CatalogTreeNode, WizardSelectionProps } from '../Wizard.types'; +import { CatalogTreeNode, IDENTIFIER_FIELD, WizardSelectionProps } from '../Wizard.types'; import './ModelSelection.css'; -import { CesiumGeojsonFootprint } from './CesiumGeojsonLayer'; +import { CesiumGeojsonFootprint } from './CesiumGeojsonFootprint'; export const ModelSelection: React.FC = (props) => { const [isLoading, setIsLoading] = useState(true); + const [finishedFlying, setFinishedFlying] = useState(false); + const treeTheme = { "--rst-selected-background-color": '#f8fafc33', "--rst-hover-background-color": '#1e293b80', @@ -114,18 +112,20 @@ export const ModelSelection: React.FC = (props) => { showActiveLayersTool={false} infoBox={false} > - { - props.selectedItem?.['mc:links'] && (props.selectedItem?.isShown as boolean) && - - } { props.selectedItem?.isSelected as boolean && props.selectedItem?.['mc:footprint'] && + } + { + props.selectedItem?.['mc:links'] && (props.selectedItem?.isShown as boolean) && finishedFlying && + } diff --git a/src/components/common/Terrain/Terrain.tsx b/src/components/common/Terrain/Terrain.tsx index 246c592..c40f944 100644 --- a/src/components/common/Terrain/Terrain.tsx +++ b/src/components/common/Terrain/Terrain.tsx @@ -5,7 +5,7 @@ import { CesiumEllipsoidTerrainProvider, useCesiumMap, } from '@map-colonies/react-components'; -import { getTokenResource } from '../../../utils/cesium'; +import { getTokenResource } from '../../../utils/Cesium/CesiumResource'; import appConfig from '../../../utils/Config'; const NONE = 0; diff --git a/src/utils/cesium.ts b/src/utils/Cesium/CesiumResource.ts similarity index 94% rename from src/utils/cesium.ts rename to src/utils/Cesium/CesiumResource.ts index e22f731..ce6d3c0 100644 --- a/src/utils/cesium.ts +++ b/src/utils/Cesium/CesiumResource.ts @@ -1,5 +1,5 @@ import { CesiumResource } from '@map-colonies/react-components'; -import appConfig from './Config'; +import appConfig from '../Config'; export const getTokenResource = (url: string): CesiumResource => { const tokenProps: any = { url }; diff --git a/src/utils/fly-to.tsx b/src/utils/Cesium/FlyTo.tsx similarity index 87% rename from src/utils/fly-to.tsx rename to src/utils/Cesium/FlyTo.tsx index 7dad67b..cf01666 100644 --- a/src/utils/fly-to.tsx +++ b/src/utils/Cesium/FlyTo.tsx @@ -9,14 +9,14 @@ interface FlyToProps { setRect: (rect: CesiumRectangle | undefined) => void; geometry: Geometry; tilt?: boolean; + setFinishedFlying?: (finished: boolean) => void; } export const generateLayerRectangle = (geometry: Geometry): CesiumRectangle => { - // eslint-disable-next-line return CesiumRectangle.fromDegrees(...bbox(geometry)) as CesiumRectangle; }; -export const FlyTo: React.FC = ({ setRect, geometry, tilt = false }): JSX.Element => { +export const FlyTo: React.FC = ({ setRect, geometry, setFinishedFlying, tilt = false }): JSX.Element => { const mapViewer = useCesiumMap(); let rect; @@ -30,6 +30,7 @@ export const FlyTo: React.FC = ({ setRect, geometry, tilt = false }) }, }); + setFinishedFlying?.(false); void mapViewer.flyTo( rectangle, !tilt ? { offset: { @@ -39,6 +40,7 @@ export const FlyTo: React.FC = ({ setRect, geometry, tilt = false }) } } : {} ).then(() => { + setFinishedFlying?.(true); mapViewer.entities.remove(rectangle); }); diff --git a/src/utils/GetLayerFootprint.ts b/src/utils/Cesium/GetLayerFootprint.ts similarity index 99% rename from src/utils/GetLayerFootprint.ts rename to src/utils/Cesium/GetLayerFootprint.ts index badeabb..dcc2f2c 100644 --- a/src/utils/GetLayerFootprint.ts +++ b/src/utils/Cesium/GetLayerFootprint.ts @@ -5,7 +5,6 @@ import bbox from '@turf/bbox'; import bboxPolygon from '@turf/bbox-polygon'; import convex from '@turf/convex'; - const shrinkExtremeCoordinatesInOuterRing = (geometry: Geometry, factor = 0.99) => { const LAT_THRESHOLD = 84.9; const LON_THRESHOLD = 179;