Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1b7ac75
fix: new checkboxes layout and active layers transparent service layers
EllaMartirosyan Jun 16, 2026
9af2424
fix: active layers fly to icon
EllaMartirosyan Jun 16, 2026
6c491ad
fix: cesium inspector tile coordinates if checked should be always on…
EllaMartirosyan Jun 17, 2026
16c9c63
fix: bdi with ellipsis
EllaMartirosyan Jun 17, 2026
aecbbdd
feat: add 3d models to active layers panel
EllaMartirosyan Jun 17, 2026
89e3322
fix: for 3d models when scene doesn't support event listeners
EllaMartirosyan Jun 17, 2026
836241e
fix: 3d model name
EllaMartirosyan Jun 17, 2026
cb8d2dd
fix: 3d model url
EllaMartirosyan Jun 17, 2026
e523203
fix: 3d model url
EllaMartirosyan Jun 17, 2026
fd889c4
fix: 3d model url
EllaMartirosyan Jun 17, 2026
2f107ca
fix: 3d model use product name
EllaMartirosyan Jun 17, 2026
b672133
fix: 3d model properties name
EllaMartirosyan Jun 18, 2026
1fc1207
fix: use meta
EllaMartirosyan Jun 21, 2026
67e53c5
fix: use layersManager instead of cesium mapViewer.imageryLayers dire…
EllaMartirosyan Jun 21, 2026
3678561
chore: fix
EllaMartirosyan Jun 21, 2026
0d759c8
feat: add 3D models to layersManager
EllaMartirosyan Jun 21, 2026
24ae456
fix: stories with 3D model meta
EllaMartirosyan Jun 22, 2026
637e602
chore: fix
EllaMartirosyan Jun 22, 2026
465bdea
fix: naming convention
EllaMartirosyan Jun 22, 2026
9d60f09
fix: base map internal
EllaMartirosyan Jun 22, 2026
067d3de
fix: encapsulate cesium internals
EllaMartirosyan Jun 22, 2026
d23d34a
fix: when predicate fails meta.id is never attached so managed raster…
EllaMartirosyan Jun 23, 2026
f3d6493
fix: debugger
EllaMartirosyan Jun 23, 2026
78abd0f
fix: reuse
EllaMartirosyan Jun 23, 2026
cddbe4d
fix: reuse
EllaMartirosyan Jun 23, 2026
c5e3419
fix: data layer layerRecord
EllaMartirosyan Jun 23, 2026
5276838
fix: featureStructure
EllaMartirosyan Jun 23, 2026
2c6c4d3
fix: wfs
EllaMartirosyan Jun 23, 2026
f816496
fix: zoomLevel
EllaMartirosyan Jun 24, 2026
eccf6e9
fix: guard on http error
EllaMartirosyan Jun 24, 2026
63698ed
chore: fix
EllaMartirosyan Jun 24, 2026
0dc567c
chore: fix
EllaMartirosyan Jun 24, 2026
08476f9
fix: pp story
EllaMartirosyan Jun 24, 2026
0846cc4
fix: encapsulate catalog data
EllaMartirosyan Jun 24, 2026
98f2cfd
fix: details renamed to layerRecord
EllaMartirosyan Jun 25, 2026
eed776d
fix: new cesium map props
EllaMartirosyan Jun 25, 2026
7231baa
fix: get layerRecord props from app
EllaMartirosyan Jun 25, 2026
663a3a5
fix: sync stories with new props
EllaMartirosyan Jun 25, 2026
6de6786
fix: active layers update when reordering layers on the map
EllaMartirosyan Jun 26, 2026
07a359c
fix: acdebugger optimization section layers order
EllaMartirosyan Jun 26, 2026
fbafacf
fix: inspector tool moved to debugger panel
EllaMartirosyan Jun 26, 2026
21d1a5d
fix: rerun optimization when layer order changes
EllaMartirosyan Jun 28, 2026
e397e89
fix: code review
EllaMartirosyan Jun 28, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
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,
getDataLayerName,
getLayerFootprint,
getLayerId,
getLayerName,
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 {
Expand All @@ -28,84 +45,158 @@ interface IActiveLayersPanelProps {
locale?: { [key: string]: string };
}

const extractModelName = (rawUrl: string): string => {
try {
const { hostname, pathname } = new URL(rawUrl);
const segments = pathname.split('/').filter((s) => s.length > 0);
if (segments.length >= 2) {
return `${segments[segments.length - 2]}/${segments[segments.length - 1]}`;
}
if (segments.length === 1) {
return segments[0];
}
return hostname;
} catch {
return rawUrl;
}
};

export const ActiveLayersPanel: React.FC<IActiveLayersPanelProps> = ({ locale }) => {
const mapViewer = useCesiumMap();
const [sections, setSections] = useState<ISection[]>([ { id: IMAGERY, values: [] }, { id: DATA, values: [] } ]);
const [sections, setSections] = useState<ISection[]>([
{ id: IMAGERY, values: [] },
{ id: SERVICE, values: [] },
{ id: DATA, values: [] },
{ id: THREE_D, values: [] }
]);
const [collapsedSections, setCollapsedSections] = useState<Record<string, boolean>>({});

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: layerId as string,
name: (getLayerName(layer) ?? layerId) as string,
rect: layer.rectangle,
isDisabled: isBaseMapLayer(meta as Record<string, unknown>)
};
}).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: meta?.id as string,
name: (get(meta, 'layerRecord.productName') ?? meta?.id) as string,
id: `SERVICE_LAYER_${String(i)}`,
name: isTransparentLayer ? name : providerName ?? name,
rect: layer.rectangle,
isBaseMap: mapViewer.layersManager?.isBaseMapLayer(meta) as boolean
isDisabled: true
};
}).filter((layer) => layer.id !== TRANSPARENT_LAYER_ID)
}).filter((item): item is IActiveLayer => item !== undefined)
: [];
};

const getDataLayers = (): IActiveLayer[] => {
return mapViewer.layersManager?.dataLayerList.map((dataLayer) => {
return {
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
id: getLayerId(dataLayer) as string,
name: (getDataLayerName(dataLayer.meta) ?? getLayerName(dataLayer)) as string,
rect: Rectangle.fromDegrees(...bbox(getLayerFootprint(dataLayer.meta))),
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 = getLayerName(model) ?? extractModelName(modelUrl ?? `Model #${String(index + 1)}`);
return {
id: (getLayerId(model) as string) ?? `3D_MODEL_${String(index)}`,
name: modelName,
zoomToTarget: model.tileset,
isDisabled: false,
};
});
};

const refreshSections = (): void => {
setSections([
{
id: IMAGERY,
values: getImageryLayers(),
},
{
id: SERVICE,
values: getServiceLayers(),
},
{
id: DATA,
values: getDataLayers(),
},
{
id: THREE_D,
values: get3DModels(),
},
]);
};

useEffect(() => {
const updateSections = () => {
const newSections = [
{
id: IMAGERY,
values: getImageryLayers()
},
{
id: DATA,
values: getDataLayers()
},
];
setSections(newSections);
setCollapsedSections(newSections.reduce((acc, section) => ({ ...acc, [section.id]: true }), {}));
};
updateSections();
refreshSections();
setCollapsedSections({
[IMAGERY]: true,
[SERVICE]: true,
[DATA]: true,
[THREE_D]: true,
});
}, []);

useEffect(() => {
if (!mapViewer.layersManager) return;
if (!mapViewer.layersManager) { return; }
const handleLayerEvent = (): void => {
setSections((prev) =>
prev.map((item) =>
item.id === IMAGERY
? {
...item,
values: getImageryLayers()
}
: item
)
);
refreshSections();
};
mapViewer.layersManager.addLayerUpdatedListener(handleLayerEvent);
mapViewer.imageryLayers.layerAdded.addEventListener(handleLayerEvent);
mapViewer.imageryLayers.layerRemoved.addEventListener(handleLayerEvent);
mapViewer.imageryLayers.layerMoved.addEventListener(handleLayerEvent);
return () => {
if (get(mapViewer, '_cesiumWidget') !== undefined) {
mapViewer.layersManager?.removeLayerUpdatedListener(handleLayerEvent);
mapViewer.imageryLayers.layerAdded.removeEventListener(handleLayerEvent);
mapViewer.imageryLayers.layerRemoved.removeEventListener(handleLayerEvent);
mapViewer.imageryLayers.layerMoved.removeEventListener(handleLayerEvent);
}
};
}, [mapViewer.layersManager?.layerList]);
}, [mapViewer.layersManager]);

useEffect(() => {
if (!mapViewer.layersManager) return;
if (!mapViewer.layersManager) { return; }
const handleDataLayerEvent = (): void => {
setSections((prev) =>
prev.map((item) =>
Expand All @@ -124,12 +215,38 @@ export const ActiveLayersPanel: React.FC<IActiveLayersPanelProps> = ({ 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 (
Expand All @@ -149,19 +266,19 @@ export const ActiveLayersPanel: React.FC<IActiveLayersPanelProps> = ({ locale })
section.values.map((activeLayer: IActiveLayer) => (
<Box key={activeLayer.id} className="layer">
<Tooltip content={activeLayer.name}>
<Box className={`name ${activeLayer.isBaseMap ? 'disabled' : ''}`}>{activeLayer.name}</Box>
<Box className={`name ${activeLayer.isDisabled ? 'disabled' : ''}`}><bdi>{activeLayer.name}</bdi></Box>
</Tooltip>
<Box className="icons">
<Tooltip content={get(locale, 'FLY_TO') ?? 'Fly To'}>
<Box className="icon" onClick={(event) => { event.stopPropagation(); handleFlyTo(activeLayer.rect); }}>
<Box className="icon" onClick={(event) => { event.stopPropagation(); handleFlyTo(activeLayer); }}>
<svg fill="var(--mdc-theme-cesium-color)" width="100%" height="100%" viewBox="0 0 256 256">
<path d="M236,120H223.66406A96.15352,96.15352,0,0,0,136,32.33618V20a8,8,0,0,0-16,0V32.33618A96.15352,96.15352,0,0,0,32.33594,120H20a8,8,0,0,0,0,16H32.33594A96.15352,96.15352,0,0,0,120,223.66382V236a8,8,0,0,0,16,0V223.66382A96.15352,96.15352,0,0,0,223.66406,136H236a8,8,0,0,0,0-16Zm-40,16h11.59912A80.14164,80.14164,0,0,1,136,207.59912V196a8,8,0,0,0-16,0v11.59912A80.14164,80.14164,0,0,1,48.40088,136H60a8,8,0,0,0,0-16H48.40088A80.14164,80.14164,0,0,1,120,48.40088V60a8,8,0,0,0,16,0V48.40088A80.14164,80.14164,0,0,1,207.59912,120H196a8,8,0,0,0,0,16Z"/>
<polygon points="128,80 80,170 128,150 176,170" fill="var(--mdc-theme-cesium-color)"/>
</svg>
</Box>
</Tooltip>
{/* <Tooltip content={get(locale, 'REMOVE') ?? 'Remove'}>
<Box className={`icon ${activeLayer.isBaseMap ? 'disabled' : ''}`} onClick={(event) => { event.stopPropagation(); }}>
<Box className={`icon ${activeLayer.isDisabled ? 'disabled' : ''}`} onClick={(event) => { event.stopPropagation(); }}>
<svg width="100%" height="100%" viewBox="0 0 16 16" fill="var(--mdc-theme-cesium-color)">
<path fillRule="evenodd" clipRule="evenodd" d="M10 3h3v1h-1v9l-1 1H4l-1-1V4H2V3h3V2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zM9 2H6v1h3V2zM4 13h7V4H4v9zm2-8H5v7h1V5zm1 0h1v7H7V5zm2 0h1v7H9V5z"/>
</svg>
Expand Down
Loading