diff --git a/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.css b/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.css index 4dffcfe4..7f83b255 100644 --- a/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.css +++ b/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.css @@ -11,6 +11,12 @@ body[dir='rtl'] .cesium-viewer .activeLayersPanel .cesium-cesiumInspector-sectio } .cesium-viewer .activeLayersPanel .name { + min-width: 0; + flex: 1 1 auto; +} + +.cesium-viewer .activeLayersPanel .name bdi { + display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -34,7 +40,7 @@ body[dir='rtl'] .cesium-viewer .activeLayersPanel .cesium-cesiumInspector-sectio .cesium-viewer .activeLayersPanel .icon { width: 17px; height: 17px; - margin-left: 8px; + margin: 0 8px; cursor: pointer; } diff --git a/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx b/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx index a23632be..1557ed61 100644 --- a/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx +++ b/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx @@ -1,22 +1,36 @@ -import { Rectangle } from 'cesium'; +import { Cesium3DTileset, Rectangle } from 'cesium'; import { get } from 'lodash'; import React, { useEffect, useState } from 'react'; import { Tooltip, Typography } from '@map-colonies/react-core'; import bbox from '@turf/bbox'; import { Box } from '../../box'; -import { TRANSPARENT_LAYER_ID } from '../layers-manager'; +import { + getImageryProvider, + getImageryProviderName, + getLayerId, + ICesiumImageryLayer, + isBaseMapLayer, + isManagedImageryLayer, + isServiceLayer, + TRANSPARENT_LAYER_ID, +} from '../layers-manager'; import { useCesiumMap } from '../map'; import './active-layers-panel.css'; const IMAGERY = 'Imagery'; +const SERVICE = 'Service'; const DATA = 'Data'; +const THREE_D = '3D'; +const TRANSPARENT_LAYER = 'TRANSPARENT_LAYER_FOR_OPTIMIZATION'; +const SERVICE_LAYER = 'LAYER_WITH_NO_ID #'; interface IActiveLayer { id: string; name: string; - rect: Rectangle; - isBaseMap: boolean; + isDisabled: boolean; + rect?: Rectangle; + zoomToTarget?: Cesium3DTileset; } interface ISection { @@ -28,27 +42,79 @@ interface IActiveLayersPanelProps { locale?: { [key: string]: string }; } +const GENERIC_PATH_SEGMENTS = new Set(['data', 'act', 'assets', 'cesium', 'tiles', 'tileset', '3d', 'model', 'models']); + +const extractModelName = (rawUrl: string): string => { + try { + const { hostname, pathname } = new URL(rawUrl); + const segments = pathname.split('/').filter((s) => s.length > 0 && !s.includes('.')); + const named = [...segments] + .reverse() + .find((s) => !GENERIC_PATH_SEGMENTS.has(s.toLowerCase()) && /[a-zA-Z]/.test(s)); + return named ?? hostname; + } catch { + return rawUrl; + } +}; + export const ActiveLayersPanel: React.FC = ({ locale }) => { const mapViewer = useCesiumMap(); - const [sections, setSections] = useState([ { id: IMAGERY, values: [] }, { id: DATA, values: [] } ]); + const [sections, setSections] = useState([ + { id: IMAGERY, values: [] }, + { id: SERVICE, values: [] }, + { id: DATA, values: [] }, + { id: THREE_D, values: [] } + ]); const [collapsedSections, setCollapsedSections] = useState>({}); const getLabel = (key: string) => { return get(locale, key.toUpperCase()) ?? key; }; + const getLayerList = (): ICesiumImageryLayer[] => { + return mapViewer.layersManager?.layerList ?? []; + }; + const getImageryLayers = (): IActiveLayer[] => { - return mapViewer.imageryLayers - ? Array.from({ length: mapViewer.imageryLayers.length }, (_, i) => { - const layer = mapViewer.imageryLayers.get(i); - const meta = (layer as any).meta; + const layerList = getLayerList(); + return layerList.length > 0 + ? layerList.map((layer): IActiveLayer | undefined => { + const meta = get(layer, 'meta'); + const layerId = getLayerId(layer); + if (!isManagedImageryLayer(layerId)) { + return undefined; + } return { - id: meta?.id as string, - name: (get(meta, 'layerRecord.productName') ?? meta?.id) as string, + id: layerId as string, + name: (get(meta, 'layerRecord.productName') ?? layerId) as string, rect: layer.rectangle, - isBaseMap: mapViewer.layersManager?.isBaseMapLayer(meta) as boolean + isDisabled: isBaseMapLayer(meta as Record) }; - }).filter((layer) => layer.id !== TRANSPARENT_LAYER_ID) + }).filter((item): item is IActiveLayer => item !== undefined) + : []; + }; + + const getServiceLayers = (): IActiveLayer[] => { + const layerList = getLayerList(); + return layerList.length > 0 + ? layerList.map((layer, i): IActiveLayer | undefined => { + const layerId = getLayerId(layer); + if (!isServiceLayer(layerId)) { + return undefined; + } + const isTransparentLayer = layerId === TRANSPARENT_LAYER_ID; + const providerName = getImageryProviderName(getImageryProvider(layer)); + const name = isTransparentLayer + ? TRANSPARENT_LAYER + : `${SERVICE_LAYER} ${String(i + 1)}`; + + return { + id: `SERVICE_LAYER_${String(i)}`, + name: isTransparentLayer ? name : providerName ?? name, + rect: layer.rectangle, + isDisabled: true + }; + }).filter((item): item is IActiveLayer => item !== undefined) : []; }; @@ -58,10 +124,23 @@ export const ActiveLayersPanel: React.FC = ({ locale }) id: dataLayer.meta?.id as string, name: (get(dataLayer.meta, 'featureStructure.aliasLayerName') ?? dataLayer.meta.productName) as string, rect: Rectangle.fromDegrees(...bbox(dataLayer.meta?.footprint)), - isBaseMap: false + isDisabled: false }; }) || []; }; + const get3DModels = (): IActiveLayer[] => { + return (mapViewer.layersManager?.modelList ?? []).map((model, index): IActiveLayer => { + const modelUrl = get(model.tileset, 'resource.url') as string | undefined; + const modelName = (get(model.meta, 'layerRecord.productName') ?? extractModelName(modelUrl ?? `Model #${String(index + 1)}`)) as string; + return { + id: (model.meta.id as string) ?? `3D_MODEL_${String(index)}`, + name: modelName, + zoomToTarget: model.tileset, + isDisabled: false, + }; + }); + }; + useEffect(() => { const updateSections = () => { const newSections = [ @@ -69,10 +148,18 @@ export const ActiveLayersPanel: React.FC = ({ locale }) id: IMAGERY, values: getImageryLayers() }, + { + id: SERVICE, + values: getServiceLayers() + }, { id: DATA, values: getDataLayers() }, + { + id: THREE_D, + values: get3DModels() + }, ]; setSections(newSections); setCollapsedSections(newSections.reduce((acc, section) => ({ ...acc, [section.id]: true }), {})); @@ -81,7 +168,7 @@ export const ActiveLayersPanel: React.FC = ({ locale }) }, []); useEffect(() => { - if (!mapViewer.layersManager) return; + if (!mapViewer.layersManager) { return; } const handleLayerEvent = (): void => { setSections((prev) => prev.map((item) => @@ -90,22 +177,29 @@ export const ActiveLayersPanel: React.FC = ({ locale }) ...item, values: getImageryLayers() } - : item + : item.id === SERVICE + ? { + ...item, + values: getServiceLayers() + } + : item ) ); }; mapViewer.layersManager.addLayerUpdatedListener(handleLayerEvent); + mapViewer.imageryLayers.layerAdded.addEventListener(handleLayerEvent); mapViewer.imageryLayers.layerRemoved.addEventListener(handleLayerEvent); return () => { if (get(mapViewer, '_cesiumWidget') !== undefined) { mapViewer.layersManager?.removeLayerUpdatedListener(handleLayerEvent); + mapViewer.imageryLayers.layerAdded.removeEventListener(handleLayerEvent); mapViewer.imageryLayers.layerRemoved.removeEventListener(handleLayerEvent); } }; }, [mapViewer.layersManager?.layerList]); useEffect(() => { - if (!mapViewer.layersManager) return; + if (!mapViewer.layersManager) { return; } const handleDataLayerEvent = (): void => { setSections((prev) => prev.map((item) => @@ -124,12 +218,38 @@ export const ActiveLayersPanel: React.FC = ({ locale }) }; }, [mapViewer.layersManager?.dataLayerList]); + useEffect(() => { + if (!mapViewer.layersManager) { return; } + const handle3DModelEvent = (): void => { + setSections((prev) => + prev.map((item) => + item.id === THREE_D + ? { + ...item, + values: get3DModels() + } + : item + ) + ); + }; + mapViewer.layersManager.addModelUpdatedListener(handle3DModelEvent); + return () => { + mapViewer.layersManager?.removeModelUpdatedListener(handle3DModelEvent); + }; + }, [mapViewer.layersManager?.modelList]); + const toggleSection = (id: string) => { setCollapsedSections((prev) => ({ ...prev, [id]: !prev[id] })); }; - const handleFlyTo = (rect: Rectangle) => { - mapViewer.camera.flyTo({ destination: rect }); + const handleFlyTo = (activeLayer: IActiveLayer) => { + if (activeLayer.zoomToTarget !== undefined) { + void mapViewer.zoomTo(activeLayer.zoomToTarget); + return; + } + if (activeLayer.rect !== undefined) { + mapViewer.camera.flyTo({ destination: activeLayer.rect }); + } }; return ( @@ -149,11 +269,11 @@ export const ActiveLayersPanel: React.FC = ({ locale }) section.values.map((activeLayer: IActiveLayer) => ( - {activeLayer.name} + {activeLayer.name} - { event.stopPropagation(); handleFlyTo(activeLayer.rect); }}> + { event.stopPropagation(); handleFlyTo(activeLayer); }}> @@ -161,7 +281,7 @@ export const ActiveLayersPanel: React.FC = ({ locale }) {/* - { event.stopPropagation(); }}> + { event.stopPropagation(); }}> diff --git a/packages/react-components/src/components/cesium-map/debug/debugger-widget.css b/packages/react-components/src/components/cesium-map/debug/debugger-widget.css index 92a9e8d7..2a5f2d49 100644 --- a/packages/react-components/src/components/cesium-map/debug/debugger-widget.css +++ b/packages/react-components/src/components/cesium-map/debug/debugger-widget.css @@ -58,11 +58,19 @@ body[dir='rtl'] .cesium-viewer .debuggerWidgetSectionHeaderToggle { .cesium-viewer .debuggerWidgetSectionContent .optimizationCheckbox, .cesium-viewer .debuggerWidgetSectionContent .cesiumInspectorCheckbox { + display: flex; + align-items: center; + width: 100%; margin-bottom: 0; } .cesium-viewer .debuggerWidgetSectionContent .optimizationCheckbox label, .cesium-viewer .debuggerWidgetSectionContent .cesiumInspectorCheckbox label { + flex: 1 1 auto; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; cursor: pointer; } diff --git a/packages/react-components/src/components/cesium-map/debug/debugger-widget.tsx b/packages/react-components/src/components/cesium-map/debug/debugger-widget.tsx index cff95382..ace5c8c9 100644 --- a/packages/react-components/src/components/cesium-map/debug/debugger-widget.tsx +++ b/packages/react-components/src/components/cesium-map/debug/debugger-widget.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { get } from 'lodash'; +import { get, isEmpty } from 'lodash'; import { Checkbox, Tooltip } from '@map-colonies/react-core'; import { Box } from '../../box'; import { EXAMINED_TILES_META_PROP } from '../helpers/customImageryProviders'; @@ -68,7 +68,7 @@ const DebuggerComponent: React.FC = ({ locale, isOpen, set if (!mapViewer.layersManager?.layerList) return; setLayersMeta( mapViewer.layersManager.layerList - .filter((layer): boolean => layer.meta?.id !== TRANSPARENT_LAYER_ID) + .filter((layer): boolean => !isEmpty(layer.meta?.id) && layer.meta?.id !== TRANSPARENT_LAYER_ID) .map( (layer): LayerMetaItem => ({ layerId: layer.meta?.id as string | undefined, @@ -216,7 +216,7 @@ const DebuggerComponent: React.FC = ({ locale, isOpen, set const idText = layer.layerId ?? `LAYER-${layersMeta.length - index}`; const nameText = (get(layer.meta, 'layerRecord.productName') as string | undefined) ?? idText; const statusText = - layer.meta?.relevantToExtent === true ? ' → show' : layer.meta?.relevantToExtent === false ? ' → hide' : ''; + layer.meta?.isRelevantToExtent === true ? ' → show' : layer.meta?.isRelevantToExtent === false ? ' → hide' : ''; const transparencyText = layer.meta?.hasTransparency === true ? withTransparencyTiles : layer.meta?.hasTransparency === false ? withoutTransparencyTiles : ''; const tileCoordinatesFromMeta = get(layer.meta, EXAMINED_TILES_META_PROP) as @@ -235,7 +235,7 @@ const DebuggerComponent: React.FC = ({ locale, isOpen, set transparencyText === '' ? undefined : {transparencyText}: {formattedTileCoordinates.join(', ')}; - const isRelevant = layer.meta?.relevantToExtent !== false; + const isRelevant = layer.meta?.isRelevantToExtent !== false; if (tooltipContent === undefined) { return ( diff --git a/packages/react-components/src/components/cesium-map/layers-manager.ts b/packages/react-components/src/components/cesium-map/layers-manager.ts index c52ee5f7..812a3b9b 100644 --- a/packages/react-components/src/components/cesium-map/layers-manager.ts +++ b/packages/react-components/src/components/cesium-map/layers-manager.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { + Cesium3DTileset as CesiumTileset, ImageryLayer, UrlTemplateImageryProvider, WebMapServiceImageryProvider, @@ -11,19 +12,24 @@ import { import { get, isEmpty } from 'lodash'; import { Feature, Point, Polygon } from 'geojson'; import booleanPointInPolygon from '@turf/boolean-point-in-polygon'; -import { RCesiumOSMLayerOptions, RCesiumWMSLayerOptions, RCesiumWMTSLayerOptions, RCesiumXYZLayerOptions } from './layers'; -import { CesiumViewer, IBaseMap } from './map'; -import { pointToGeoJSON } from './helpers/geojson/point.geojson'; -import { IMapLegend } from './legend'; import { CustomUrlTemplateImageryProvider, CustomWebMapServiceImageryProvider, CustomWebMapTileServiceImageryProvider, HAS_TRANSPARENCY_META_PROP, } from './helpers/customImageryProviders'; +import { pointToGeoJSON } from './helpers/geojson/point.geojson'; import { cesiumRectangleContained } from './helpers/utils'; +import { + RCesiumOSMLayerOptions, + RCesiumWMSLayerOptions, + RCesiumWMTSLayerOptions, + RCesiumXYZLayerOptions +} from './layers'; import { ICesiumWFSLayer } from './layers/wfs.layer'; -import { CesiumCartesian2 } from './proxied.types'; +import { IMapLegend } from './legend'; +import { CesiumViewer, IBaseMap } from './map'; +import { CesiumCartesian2, CesiumImageryProvider } from './proxied.types'; const INC = 1; const DEC = -1; @@ -44,6 +50,11 @@ export interface IRasterLayer { details?: Record; } +export interface ICesium3DModel { + tileset: CesiumTileset; + meta: Record; +} + export interface IVectorLayer { id: string; opacity: number; @@ -55,14 +66,48 @@ export type LegendExtractor = (layers: (any & { meta: any })[]) => IMapLegend[]; export const TRANSPARENT_LAYER_ID = 'TRANSPARENT_BASE_LAYER'; +export const getLayerId = (layer: ICesiumImageryLayer): string | undefined => { + return get(layer, 'meta.id') as string | undefined; +}; + +export const isServiceLayer = (layerId: string | undefined): boolean => { + return isEmpty(layerId) || layerId === TRANSPARENT_LAYER_ID; +}; + +export const isManagedImageryLayer = (layerId: string | undefined): boolean => { + return !isServiceLayer(layerId); +}; + +export const getParentBaseMapId = (meta: Record | undefined): string | undefined => { + return get(meta, 'parentBaseMapId') as string | undefined; +}; + +export const isBaseMapLayer = (meta: Record | undefined): boolean => { + return !!getParentBaseMapId(meta); +}; + +export const getImageryProvider = (layer: ICesiumImageryLayer): CesiumImageryProvider => { + return get(layer, 'imageryProvider'); +}; + +export const getImageryProviderUrl = (layer: ICesiumImageryLayer): string | undefined => { + return get(layer, 'imageryProvider.url'); +}; + +export const getImageryProviderName = (provider: CesiumImageryProvider): string => { + return provider.constructor.name; +}; + class LayerManager { public mapViewer: CesiumViewer; public legendsList: IMapLegend[]; public layerUpdated: Event; public dataLayerUpdated: Event; + public modelUpdated: Event; private readonly layers: ICesiumImageryLayer[]; private readonly dataLayers: ICesiumWFSLayer[]; + private readonly models: ICesium3DModel[]; private readonly legendsExtractor?: LegendExtractor; private readonly layerManagerFootprintMetaFieldPath: string | undefined; private shouldOptimizedTileRequests?: boolean; @@ -80,10 +125,12 @@ class LayerManager { // eslint-disable-next-line this.layers = (this.mapViewer.imageryLayers as any)._layers; this.dataLayers = []; + this.models = []; this.legendsList = []; this.legendsExtractor = legendsExtractor; this.layerUpdated = new Event(); this.dataLayerUpdated = new Event(); + this.modelUpdated = new Event(); this.layerManagerFootprintMetaFieldPath = layerManagerFootprintMetaFieldPath; this.shouldOptimizedTileRequests = shouldOptimizedTileRequests ?? false; this.relevancyListenersCleanup = []; @@ -106,8 +153,8 @@ class LayerManager { return this.dataLayers; } - public isBaseMapLayer(meta: any): boolean { - return !!get(meta, 'parentBasetMapId'); + public get modelList(): ICesium3DModel[] { + return this.models; } public addDataLayer(dataLayer: ICesiumWFSLayer): void { @@ -203,7 +250,7 @@ class LayerManager { if (cesiumLayer) { cesiumLayer.alpha = layer.opacity; cesiumLayer.meta = { - parentBasetMapId: parentId, + parentBaseMapId: parentId, ...layer, }; if (layer.show !== undefined) { @@ -232,7 +279,7 @@ class LayerManager { public removeBaseMapLayers(): void { const layerToDelete = this.layers.filter((layer) => { - return this.isBaseMapLayer(layer.meta); + return isBaseMapLayer(layer.meta); }); layerToDelete.forEach((layer) => { this.mapViewer.imageryLayers.remove(layer, true); @@ -242,8 +289,7 @@ class LayerManager { public removeNotBaseMapLayers(): void { const layerToDelete = this.layers.filter((layer) => { - const parentId = get(layer.meta, 'parentBasetMapId') as string; - return parentId ? false : true; + return !isBaseMapLayer(layer.meta); }); layerToDelete.forEach((layer) => { this.mapViewer.imageryLayers.remove(layer, true); @@ -321,8 +367,7 @@ class LayerManager { public showAllNotBase(isShow: boolean): void { const nonBaseLayers = this.layers.filter((layer) => { - const parentId = get(layer.meta, 'parentBasetMapId') as string; - return parentId ? false : true; + return !isBaseMapLayer(layer.meta); }); nonBaseLayers.forEach((layer: ICesiumImageryLayer) => { this.show(layer.meta?.id as string, isShow); @@ -343,8 +388,7 @@ class LayerManager { if (pickRay) { nonBaseLayers = this.mapViewer.imageryLayers.pickImageryLayers(pickRay, this.mapViewer.scene)?.filter((layer: ICesiumImageryLayer) => { - const parentId = get(layer.meta, 'parentBasetMapId') as string; - return parentId ? false : true; + return !isBaseMapLayer(layer.meta); }); } @@ -356,8 +400,7 @@ class LayerManager { const position = pointToGeoJSON(this.mapViewer, x, y) as Feature; const nonBaseLayers = this.layers.filter((layer) => { - const parentId = get(layer.meta, 'parentBasetMapId') as string; - return parentId ? false : true; + return !isBaseMapLayer(layer.meta); }); const selectedVisibleLayers = nonBaseLayers.filter((layer) => { @@ -405,7 +448,7 @@ class LayerManager { (transparentLayer as ICesiumImageryLayer).meta = { id: TRANSPARENT_LAYER_ID, skipRelevancyCheck: true, - parentBasetMapId: 'TRANSPARENT_LAYER', + parentBaseMapId: 'TRANSPARENT_LAYER', }; } @@ -425,6 +468,14 @@ class LayerManager { this.dataLayerUpdated.removeEventListener(callback, this); } + public addModelUpdatedListener(callback: (models: ICesium3DModel[]) => void): void { + this.modelUpdated.addEventListener(callback, this); + } + + public removeModelUpdatedListener(callback: (models: ICesium3DModel[]) => void): void { + this.modelUpdated.removeEventListener(callback, this); + } + public setShouldOptimizedTileRequests(shouldOptimize: boolean): void { if (this.shouldOptimizedTileRequests === shouldOptimize) { return; @@ -453,6 +504,26 @@ class LayerManager { }); } + public addModel(model: ICesium3DModel): void { + this.models.push({ ...model }); + this.modelUpdated.raiseEvent(this.models); + } + + public removeModel(modelId: string): void { + const model = this.findModelById(modelId); + if (model) { + const index = this.models.indexOf(model); + if (index > -1) { + this.models.splice(index, 1); + } + this.modelUpdated.raiseEvent(this.models); + } + } + + public findModelById(modelId: string): ICesium3DModel | undefined { + return this.models.find((model) => model.meta.id === modelId); + } + private setLegends(): void { if (typeof this.legendsExtractor !== 'undefined') { this.legendsList = this.legendsExtractor(this.layers); @@ -461,8 +532,7 @@ class LayerManager { private getBaseLayersCount(): number { const baseLayers = this.layers.filter((layer) => { - const parentId = get(layer.meta, 'parentBasetMapId') as string; - return parentId ? true : false; + return isBaseMapLayer(layer.meta); }); return baseLayers.length; } @@ -478,10 +548,8 @@ class LayerManager { const move = from > to ? INC : DEC; const min = from < to ? from : to; const max = from < to ? to : from; - this.layers.forEach((layer) => { - const parentId = get(layer.meta, 'parentBasetMapId') as string; - if (!parentId) { + if (!isBaseMapLayer(layer.meta)) { const layerOrder = layer.meta?.zIndex as number; (layer.meta as Record).zIndex = layerOrder >= min && layerOrder <= max && layerOrder !== from ? layerOrder + move : layerOrder === from ? to : layerOrder; @@ -494,14 +562,12 @@ class LayerManager { if (layer.meta?.id === TRANSPARENT_LAYER_ID) { continue; } - - const relevantToExtent = layer.meta?.relevantToExtent; - if (typeof relevantToExtent !== 'boolean') { + const isRelevantToExtent = layer.meta?.isRelevantToExtent; + if (typeof isRelevantToExtent !== 'boolean') { continue; } - - if (relevantToExtent !== layer.show && layer.imageryProvider.ready) { - layer.show = relevantToExtent; + if (isRelevantToExtent !== layer.show && layer.imageryProvider.ready) { + layer.show = isRelevantToExtent; } } } @@ -522,9 +588,9 @@ class LayerManager { if (layer.meta?.id === TRANSPARENT_LAYER_ID) { continue; } - if (layer.meta && 'relevantToExtent' in layer.meta) { - const { relevantToExtent, ...restMeta } = layer.meta; - void relevantToExtent; + if (layer.meta && 'isRelevantToExtent' in layer.meta) { + const { isRelevantToExtent, ...restMeta } = layer.meta; + void isRelevantToExtent; layer.meta = restMeta; } } @@ -596,11 +662,11 @@ class LayerManager { const layer = this.layers[i]; const intersectsExtent = !isEmpty(layer.rectangle) && Rectangle.intersection(extent, layer.rectangle) instanceof Rectangle; if (layer.meta?.skipRelevancyCheck === true) { - layer.meta = { ...layer.meta, relevantToExtent: true }; + layer.meta = { ...layer.meta, isRelevantToExtent: true }; continue; } if (!intersectsExtent) { - layer.meta = { ...(layer.meta ?? {}), relevantToExtent: false }; + layer.meta = { ...(layer.meta ?? {}), isRelevantToExtent: false }; continue; } let isOccludedByOpaqueLayerAbove = false; @@ -624,7 +690,7 @@ class LayerManager { // Layer is relevant if it intersects extent and has no opaque layer above it layer.meta = { ...(layer.meta ?? {}), - relevantToExtent: !isOccludedByOpaqueLayerAbove, + isRelevantToExtent: !isOccludedByOpaqueLayerAbove, }; } } catch (e) { diff --git a/packages/react-components/src/components/cesium-map/layers/3d.tileset.stories.tsx b/packages/react-components/src/components/cesium-map/layers/3d.tileset.stories.tsx index 9e27d627..86c904d7 100644 --- a/packages/react-components/src/components/cesium-map/layers/3d.tileset.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/3d.tileset.stories.tsx @@ -28,8 +28,9 @@ export const Cesium3DTilesetLayer: Story = (args: Record) => (
+
); diff --git a/packages/react-components/src/components/cesium-map/layers/3d.tileset.tsx b/packages/react-components/src/components/cesium-map/layers/3d.tileset.tsx index f876dc5a..d76a88d7 100644 --- a/packages/react-components/src/components/cesium-map/layers/3d.tileset.tsx +++ b/packages/react-components/src/components/cesium-map/layers/3d.tileset.tsx @@ -1,5 +1,5 @@ -import React, { ComponentProps } from 'react'; -import { Cartesian3, Cartographic, Matrix4 } from 'cesium'; +import React, { ComponentProps, useEffect, useRef } from 'react'; +import { Cartesian3, Cartographic, Matrix4, Cesium3DTileset as CesiumTileset } from 'cesium'; import { Cesium3DTileset as Resium3DTileset } from 'resium'; import { CesiumViewer, useCesiumMap } from '../map'; @@ -8,16 +8,29 @@ const GROUND_LEVEL = 0.0; export interface RCesium3DTilesetProps extends ComponentProps { isZoomTo?: boolean; heightFromGround?: number; + meta?: Record; } -export const Cesium3DTileset: React.FC = (props) => { +export const Cesium3DTileset: React.FC = ({ meta, ...props }) => { const mapViewer: CesiumViewer = useCesiumMap(); + const tilesetRef = useRef(null); + + useEffect(() => { + return () => { + if (tilesetRef.current !== null && meta?.id !== undefined) { + mapViewer.layersManager?.removeModel(meta.id as string); + } + }; + }, []); + return ( { - // props.onReady?.(tileset); - + tilesetRef.current = tileset; + if (meta !== undefined) { + mapViewer.layersManager?.addModel({ tileset, meta }); + } if (props.isZoomTo === true) { void mapViewer.zoomTo(tileset); } @@ -31,6 +44,8 @@ export const Cesium3DTileset: React.FC = (props) => { const translation = Cartesian3.subtract(offset, surface, new Cartesian3()); tileset.modelMatrix = Matrix4.fromTranslation(translation); } + + props.onReady?.(tileset); }} /> ); diff --git a/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx b/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx index a240b3b4..6bf61143 100644 --- a/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx +++ b/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx @@ -13,9 +13,10 @@ import { CesiumViewer, useCesiumMap } from '../map'; export interface Cesium3DTilesetWithUpdateProps { url: string; withUpdate?: boolean; + meta?: Record; } -export const Cesium3DTilesetWithUpdate: React.FC = ({ url, withUpdate }) => { +export const Cesium3DTilesetWithUpdate: React.FC = ({ url, withUpdate, meta }) => { const mapViewer: CesiumViewer = useCesiumMap(); const scene = mapViewer.scene; const [cesium3DTileset] = useState( @@ -34,6 +35,16 @@ export const Cesium3DTilesetWithUpdate: React.FC // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (meta === undefined) { return; } + mapViewer.layersManager?.addModel({ tileset, meta }); + return () => { + if (meta.id !== undefined) { + mapViewer.layersManager?.removeModel(meta.id as string); + } + }; + }, [mapViewer.layersManager]); + const updateContent = (model: Cesium3DTileContent, boundingVolume: any): void => { const height = boundingVolume.minimumHeight ? boundingVolume.minimumHeight : boundingVolume.center.z - boundingVolume.radius; // @ts-ignore diff --git a/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx b/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx index a8a094fe..4b45d773 100644 --- a/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx @@ -1,9 +1,9 @@ import React, { ReactNode, useState } from 'react'; import { ImageryLayer, Rectangle } from 'cesium'; -import { get } from 'lodash'; import { Story, Meta } from '@storybook/react'; import bbox from '@turf/bbox'; import { BASE_MAPS } from '../helpers/constants'; +import { getImageryProviderUrl } from '../layers-manager'; import { CesiumMap, CesiumMapProps, IBaseMaps } from '../map'; import { CesiumXYZLayer } from './xyz.layer'; @@ -82,8 +82,7 @@ const LayersContainer: React.FC = () => { id: 'Transparent Layer', options: { ...optionsXYZTransparency }, searchLayerPredicate: (layer: ImageryLayer): boolean => - get(layer, 'imageryProvider.url') === optionsXYZTransparency.url || - get(layer, 'imageryProvider._url') === optionsXYZTransparency.url, + getImageryProviderUrl(layer) === optionsXYZTransparency.url }} rectangle={Rectangle.fromDegrees(...bbox(optionsXYZTransparency.footprint))} options={optionsXYZTransparency} @@ -102,8 +101,7 @@ const LayersContainer: React.FC = () => { id: 'Opaque Layer', options: { ...optionsXYZOpaque }, searchLayerPredicate: (layer: ImageryLayer): boolean => - get(layer, 'imageryProvider.url') === optionsXYZOpaque.url || - get(layer, 'imageryProvider._url') === optionsXYZOpaque.url, + getImageryProviderUrl(layer) === optionsXYZOpaque.url }} rectangle={Rectangle.fromDegrees(...bbox(optionsXYZOpaque.footprint))} options={optionsXYZOpaque} diff --git a/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx index b64726d7..135ef74a 100644 --- a/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx @@ -101,7 +101,11 @@ export const MapWithWFSLayer: Story = (args: Record) => { return (
- + ) return (
- +
@@ -210,7 +218,11 @@ export const MapWithWFSLayerWithVisualizer: Story = (args: Record - + { imageryProvider={false} baseMaps={BASE_MAPS} > - + diff --git a/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider.stories.tsx b/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider.stories.tsx index 021bef3e..2213eac9 100644 --- a/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider.stories.tsx +++ b/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider.stories.tsx @@ -136,7 +136,11 @@ export const QuantizedMeshProviders: Story = () => { baseMaps={BASE_MAPS} mapProjection={new WebMercatorProjection()} > - + diff --git a/packages/react-components/src/components/cesium-map/tools/inspector.tool.tsx b/packages/react-components/src/components/cesium-map/tools/inspector.tool.tsx index 2df9c79c..c1986f00 100644 --- a/packages/react-components/src/components/cesium-map/tools/inspector.tool.tsx +++ b/packages/react-components/src/components/cesium-map/tools/inspector.tool.tsx @@ -1,7 +1,8 @@ import React, { useEffect } from 'react'; -import { viewerCesiumInspectorMixin } from 'cesium'; +import { viewerCesiumInspectorMixin, TileCoordinatesImageryProvider } from 'cesium'; import { Box } from '../../box'; import { CesiumViewer, useCesiumMap } from '../map'; +import { getImageryProvider, getImageryProviderName } from '../layers-manager'; interface ICesiumInspectorInstance { container?: HTMLElement; @@ -19,6 +20,21 @@ const applyInspectorContainerStyles = (container: HTMLElement): void => { container.style.position = 'relative'; }; +const keepTileCoordinatesLayerOnTop = (viewer: CesiumViewer): void => { + const layerList = viewer.layersManager?.layerList; + const tileCoordinatesLayer = layerList?.find((layer) => { + const provider = getImageryProvider(layer); + return provider instanceof TileCoordinatesImageryProvider || getImageryProviderName(provider) === 'TileCoordinatesImageryProvider'; + }); + if (tileCoordinatesLayer === undefined) { + return; + } + const topLayer = layerList?.[layerList.length - 1]; + if (topLayer !== tileCoordinatesLayer) { + viewer.imageryLayers.raiseToTop(tileCoordinatesLayer); + } +}; + export const InspectorTool: React.FC = () => { const mapViewer: CesiumViewer = useCesiumMap(); @@ -40,7 +56,20 @@ export const InspectorTool: React.FC = () => { applyInspectorContainerStyles(inspectorContainer); } + const refreshTileCoordinatesOrder = (): void => { + keepTileCoordinatesLayerOnTop(mapViewer); + }; + + const removeLayerAdded = mapViewer.imageryLayers.layerAdded.addEventListener(refreshTileCoordinatesOrder); + const removeLayerMoved = mapViewer.imageryLayers.layerMoved.addEventListener(refreshTileCoordinatesOrder); + const removeLayerRemoved = mapViewer.imageryLayers.layerRemoved.addEventListener(refreshTileCoordinatesOrder); + + setTimeout(refreshTileCoordinatesOrder, 0); + return () => { + removeLayerAdded(); + removeLayerMoved(); + removeLayerRemoved(); if (inspectorContainer) { inspectorContainer.style.display = 'none'; }