Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
51 changes: 51 additions & 0 deletions src/components/Wizard/ModelSelection/CesiumGeojsonFootprint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useEffect, useState } from "react";
import { CesiumGeojsonLayer, CesiumRectangle, RCesiumGeojsonLayerProps } from "@map-colonies/react-components";
import { FeatureCollection } from "geojson";
import { getLayerFootprint } from "../../../utils/Cesium/GetLayerFootprint";
import { FlyTo } from "../../../utils/Cesium/FlyTo";

interface CesiumGeojsonFootprintProps extends RCesiumGeojsonLayerProps {
id: string;
setFinishedFlying?: (finished: boolean) => void;
}

export const CesiumGeojsonFootprint: React.FC<RCesiumGeojsonLayerProps & CesiumGeojsonFootprintProps> = (props) => {
const [layersFootprints, setlayersFootprints] = useState<FeatureCollection>();
const [rect, setRect] = useState<CesiumRectangle | undefined>(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.id]);

return (
<>
<CesiumGeojsonLayer
{...props}
data={layersFootprints}
/>
{
rect &&
<FlyTo
key={props.id}
setRect={setRect}
geometry={props.data}
setFinishedFlying={props.setFinishedFlying}
/>
}
</>
);
}
54 changes: 29 additions & 25 deletions src/components/Wizard/ModelSelection/ModelSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Geometry } from 'geojson';
import {
Box,
Cesium3DTileset,
CesiumColor,
CesiumConstantProperty,
CesiumGeojsonLayer,
CesiumMap,
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 './CesiumGeojsonFootprint';

export const ModelSelection: React.FC<WizardSelectionProps> = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [finishedFlying, setFinishedFlying] = useState(false);

const treeTheme = {
"--rst-selected-background-color": '#f8fafc33',
"--rst-hover-background-color": '#1e293b80',
Expand Down Expand Up @@ -68,6 +68,17 @@ export const ModelSelection: React.FC<WizardSelectionProps> = (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 (
<Box className="modelSelection">
<Box className="viewArea">
Expand All @@ -94,34 +105,27 @@ export const ModelSelection: React.FC<WizardSelectionProps> = (props) => {
</Box>
<Box className="mapPanel">
<CesiumMap
center={JSON.parse(appConfig.mapCenter)}
center={centerCesiumView}
zoom={+appConfig.mapZoom}
sceneMode={CesiumSceneMode.SCENE3D}
baseMaps={appConfig.baseMaps}
showActiveLayersTool={false}
infoBox={false}
>
{
props.selectedItem?.['mc:links'] && (props.selectedItem?.isShown as boolean) &&
<Cesium3DTileset
url={getTokenResource(props.selectedItem?.['mc:links']["#text"] as string)}
isZoomTo={true}
props.selectedItem?.isSelected as boolean && props.selectedItem?.['mc:footprint'] &&
<CesiumGeojsonFootprint
id={props.selectedItem[IDENTIFIER_FIELD] as string}
clampToGround={true}
data={selectedItemFootprint}
setFinishedFlying={setFinishedFlying}
/>
}
{
props.selectedItem?.['mc:footprint'] &&
<CesiumGeojsonLayer
clampToGround={true}
data={JSON.parse(props.selectedItem?.['mc:footprint']) as Geometry}
onLoad={(geojsonDataSource) => {
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;
}
});
}}
props.selectedItem?.['mc:links'] && (props.selectedItem?.isShown as boolean) && finishedFlying &&
<Cesium3DTileset
url={getTokenResource(props.selectedItem?.['mc:links']["#text"] as string)}
isZoomTo={true}
/>
}
<Terrain />
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Terrain/Terrain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/cesium.ts → src/utils/Cesium/CesiumResource.ts
Original file line number Diff line number Diff line change
@@ -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 };
Expand Down
51 changes: 51 additions & 0 deletions src/utils/Cesium/FlyTo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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;
setFinishedFlying?: (finished: boolean) => void;
}

export const generateLayerRectangle = (geometry: Geometry): CesiumRectangle => {
return CesiumRectangle.fromDegrees(...bbox(geometry)) as CesiumRectangle;
};

export const FlyTo: React.FC<FlyToProps> = ({ setRect, geometry, setFinishedFlying, 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)
},
});

setFinishedFlying?.(false);
void mapViewer.flyTo(
rectangle, !tilt ? {
offset: {
heading: 0,
pitch: -Math.PI / 2,
range: 0
}
} : {}
).then(() => {
setFinishedFlying?.(true);
mapViewer.entities.remove(rectangle);
});

setRect(undefined);
}, []);

return <></>;
};
115 changes: 115 additions & 0 deletions src/utils/Cesium/GetLayerFootprint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* 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: {},
};
}
}
}
}