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..6f1510ea 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,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 { @@ -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 = ({ 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: layerId as string, + name: (getLayerName(layer) ?? layerId) as string, + rect: layer.rectangle, + isDisabled: isBaseMapLayer(meta as Record) + }; + }).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) => @@ -124,12 +215,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 +266,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 +278,7 @@ export const ActiveLayersPanel: React.FC = ({ locale }) {/* - { event.stopPropagation(); }}> + { event.stopPropagation(); }}> diff --git a/packages/react-components/src/components/cesium-map/context-menu.stories.tsx b/packages/react-components/src/components/cesium-map/context-menu.stories.tsx index a549f9d5..71cdb92a 100644 --- a/packages/react-components/src/components/cesium-map/context-menu.stories.tsx +++ b/packages/react-components/src/components/cesium-map/context-menu.stories.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { Menu, MenuItem, MenuSurfaceAnchor } from '@map-colonies/react-core'; +import { get } from 'lodash'; +import { Button, Menu, MenuItem, MenuSurfaceAnchor } from '@map-colonies/react-core'; import { Story, Meta } from '@storybook/react'; import { Box } from '../box'; import { BASE_MAPS } from './helpers/constants'; -import { ICesiumImageryLayer, IRasterLayer } from './layers-manager'; +import { getLayerId, ICesiumImageryLayer, IRasterLayer } from './layers-manager'; import { CesiumMap, IBaseMaps, IContextMenuData, useCesiumMap } from './map'; import { CesiumCartesian2, CesiumSceneMode } from './proxied.types'; @@ -19,25 +20,38 @@ interface ILayersMozaikProps { layers: IRasterLayer[]; } +const getDebugLayerText = (layer: unknown): string => { + const imageryLayer = layer as ICesiumImageryLayer; + const layerId = getLayerId(imageryLayer) ?? 'UNKNOWN_LAYER_ID'; + const zIndex = get(layer, 'meta.zIndex') ?? 'NA'; + return `${layerId} <--> ${String(zIndex)}`; +}; + const mapDivStyle = { height: '90%', width: '100%', position: 'absolute' as const, }; -const layers = [ +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + footprint: 'layerRecord.footprint', + }, +}; + +const layers: IRasterLayer[] = [ { id: 'near_amphy', type: 'XYZ_LAYER', + zIndex: 0, opacity: 1, show: true, - meta: { - zIndex: 0, - }, options: { url: 'https://tiles.openaerialmap.org/5a9f90c42553e6000ce5ad6c/0/eee1a570-128e-4947-9ffa-1e69c1efab7c/{z}/{x}/{y}.png', }, - details: { + layerRecord: { footprint: { type: 'Polygon', coordinates: [ @@ -55,15 +69,13 @@ const layers = [ { id: 'coin_zoom_17', type: 'XYZ_LAYER', + zIndex: 1, opacity: 1, show: true, - meta: { - zIndex: 1, - }, options: { url: 'https://tiles.openaerialmap.org/5a8316e22553e6000ce5ac7f/0/c3fcbe99-d339-41b6-8ec0-33d90ccca020/{z}/{x}/{y}.png', }, - details: { + layerRecord: { footprint: { type: 'Polygon', coordinates: [ @@ -81,15 +93,13 @@ const layers = [ { id: 'biggest', type: 'XYZ_LAYER', + zIndex: 2, opacity: 1, show: true, - meta: { - zIndex: 2, - }, options: { url: 'https://tiles.openaerialmap.org/5a831b4a2553e6000ce5ac80/0/d02ddc76-9c2e-4994-97d4-a623eb371456/{z}/{x}/{y}.png', }, - details: { + layerRecord: { footprint: { type: 'Polygon', coordinates: [ @@ -106,7 +116,7 @@ const layers = [ }, ]; -const ContextMenu: React.FC = ({ data, position, style, size, handleClose }) => { +const ContextMenu: React.FC = ({ data, position, style, handleClose }) => { const mapViewer = useCesiumMap(); const [pickedLayers, setPickedLayers] = useState(); @@ -142,9 +152,7 @@ const ContextMenu: React.FC = ({ data, position, style, size,
{data?.map((layer) => { return ( -

{`${(layer as unknown as Record).meta?.id} <--> ${ - (layer as unknown as Record).meta?.meta?.zIndex - }`}

+

{getDebugLayerText(layer)}

); })}
@@ -156,14 +164,12 @@ const ContextMenu: React.FC = ({ data, position, style, size,
{pickedLayers?.map((layer) => { return ( -

{`${(layer as unknown as Record).meta?.id} <--> ${ - (layer as unknown as Record).meta?.meta?.zIndex - }`}

+

{getDebugLayerText(layer)}

); })}
- handleClose()} style={{ visibility: 'hidden', width: '100%' }}> + handleClose()} style={{ visibility: 'hidden', width: '100%' }}> @@ -183,7 +189,7 @@ const ContextMenu: React.FC = ({ data, position, style, size, >

No data found

- handleClose()} style={{ visibility: 'hidden', width: '100%' }}> + handleClose()} style={{ visibility: 'hidden', width: '100%' }}> @@ -234,10 +240,57 @@ const LayersMozaik: React.FC = (props) => { setAllShow(!allShow); }; + const controlsContainerStyle = { + display: 'flex', + flexWrap: 'wrap' as const, + alignItems: 'center', + gap: '8px', + padding: '10px 12px', + borderRadius: '10px', + background: 'rgba(0, 0, 0, 0.75)', + border: '1px solid rgba(255, 255, 255, 0.2)', + color: 'white', + position: 'absolute' as const, + top: '10px', + left: '50%', + transform: 'translateX(-50%)', + zIndex: 2, + maxWidth: 'calc(100% - 20px)', + }; + + const messageStyle = { + margin: 0, + color: '#8dff9f', + fontSize: '18px', + fontWeight: 700, + flexBasis: '100%', + }; + + const fieldStyle = { + height: '30px', + borderRadius: '6px', + border: '1px solid rgba(255, 255, 255, 0.3)', + background: 'rgba(255, 255, 255, 0.08)', + color: 'white', + padding: '0 8px', + }; + + const buttonStyle = { + height: '30px', + borderRadius: '6px', + border: '1px solid rgba(255, 255, 255, 0.25)', + background: 'rgba(96, 165, 250, 0.25)', + color: 'white', + padding: '0 10px', + cursor: 'pointer', + fontWeight: 600, + }; + return ( -
-

Change BASE MAP to see effective layers

+
+

Change BASE MAP to see effective layers

{ setTimes(parseInt(evt.target.value)); }} > - - - - - +
); }; @@ -309,10 +373,13 @@ export const MapWithLayersManagerAndContextMenu: Story = () => { // @ts-ignore imageryContextMenu={} imageryContextMenuSize={{ height: 340, width: 200 }} - layerManagerFootprintMetaFieldPath={'details.footprint'} + showDebuggerTool={true} + layerManagerMetaMapping={layerManagerMetaMapping} >
); }; + +MapWithLayersManagerAndContextMenu.storyName = 'Layers Manager and Context Menu'; diff --git a/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.stories.tsx b/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.stories.tsx index 57cf482c..508753eb 100644 --- a/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.stories.tsx +++ b/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.stories.tsx @@ -19,6 +19,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + interface IDrawingObject { type: DrawType; handler: (drawing: IDrawingEvent) => void; @@ -136,7 +143,12 @@ export const Drawings: Story = (args) => { Draw rectangle by coordinates
- + ; -} - -export type IActiveFeatureTypes = IFeatureTypeMetadata & { - zoomLevel: number; -}; - export interface IDebuggerWidgetProps extends IWidgetProps { locale?: { [key: string]: string }; } -interface LayerMetaItem { - layerId?: string; - meta?: Record; +interface LayerDebugMeta { + id?: string; + isRelevantToExtent?: boolean; + [key: string]: unknown; +} + +interface LayerDebugItem { + layerId: string; + layerName?: string; + meta: LayerDebugMeta; } type DebuggerSectionId = 'data' | 'layers' | 'tools'; const DebuggerComponent: React.FC = ({ locale, isOpen, setIsOpen }) => { - const [featureTypes, setFeatureTypes] = useState([]); - const [layersMeta, setLayersMeta] = useState([]); + const [featureTypes, setFeatureTypes] = useState([]); + const [layersMeta, setLayersMeta] = useState([]); const [collapsedSections, setCollapsedSections] = useState>({ data: false, layers: false, @@ -64,19 +58,23 @@ const DebuggerComponent: React.FC = ({ locale, isOpen, set })); }; - const updateLayerMeta = (): void => { - if (!mapViewer.layersManager?.layerList) return; - setLayersMeta( - mapViewer.layersManager.layerList - .filter((layer): boolean => layer.meta?.id !== TRANSPARENT_LAYER_ID) - .map( - (layer): LayerMetaItem => ({ - layerId: layer.meta?.id as string | undefined, - meta: layer.meta as Record | undefined, - }) - ) - ); - }; + const updateLayersMeta = useCallback((): void => { + if (!mapViewer.layersManager?.layerList) { return; } + const nextLayersMeta = mapViewer.layersManager.layerList + .map((layer): LayerDebugItem | undefined => { + const layerId = getLayerId(layer); + if (!isManagedImageryLayer(layerId)) { + return undefined; + } + return { + layerId: layerId as string, + layerName: getLayerName(layer), + meta: (layer.meta ?? {}) as LayerDebugMeta, + }; + }) + .filter((item): item is LayerDebugItem => item !== undefined); + setLayersMeta(nextLayersMeta); + }, [mapViewer.layersManager]); useEffect(() => { let moveEndRefreshTimeoutId: ReturnType | undefined; @@ -85,76 +83,77 @@ const DebuggerComponent: React.FC = ({ locale, isOpen, set clearTimeout(moveEndRefreshTimeoutId); } moveEndRefreshTimeoutId = setTimeout(() => { - updateLayerMeta(); + updateLayersMeta(); }, 0); }; const removeTileLoad = mapViewer.scene.globe.tileLoadProgressEvent.addEventListener((tilesLoadingCount) => { if (tilesLoadingCount === 0) { - updateLayerMeta(); + updateLayersMeta(); removeTileLoad(); } }); const removeMoveEnd = mapViewer.camera.moveEnd.addEventListener(() => { scheduleLayerMetaRefresh(); }); + const removeLayerMoved = mapViewer.imageryLayers.layerMoved.addEventListener(() => { + scheduleLayerMetaRefresh(); + }); + const removeLayerAdded = mapViewer.imageryLayers.layerAdded.addEventListener(() => { + scheduleLayerMetaRefresh(); + }); const removeLayerRemoved = mapViewer.imageryLayers.layerRemoved.addEventListener(() => { scheduleLayerMetaRefresh(); }); - mapViewer.layersManager?.addLayerUpdatedListener(updateLayerMeta); + mapViewer.layersManager?.addLayerUpdatedListener(updateLayersMeta); return (): void => { if (moveEndRefreshTimeoutId !== undefined) { clearTimeout(moveEndRefreshTimeoutId); } removeTileLoad(); removeMoveEnd(); + removeLayerMoved(); + removeLayerAdded(); removeLayerRemoved(); - mapViewer.layersManager?.removeLayerUpdatedListener(updateLayerMeta); + mapViewer.layersManager?.removeLayerUpdatedListener(updateLayersMeta); }; - }, []); + }, [mapViewer, updateLayersMeta]); useEffect(() => { - updateLayerMeta(); - }, [viewState?.shouldOptimizedTileRequests]); + updateLayersMeta(); + }, [updateLayersMeta, viewState?.shouldOptimizedTileRequests]); useEffect(() => { - if (!mapViewer.layersManager) return; - - const handleDataLayerUpdated = (dataLayers: ICesiumWFSLayer[], LayerId?: string | undefined): void => { + if (!mapViewer.layersManager) { return; } + const handleDataLayerUpdated = (dataLayers: ICesiumWFSLayer[], layerId?: string | undefined): void => { dataLayers.forEach((layer: ICesiumWFSLayer): void => { - if (LayerId !== undefined && LayerId !== layer.meta.id) { + if (layerId !== undefined && layerId !== getLayerId(layer)) { return; } - const { options, meta } = layer; const { zoomLevel } = options; - const { id, items, total, cache, currentZoomLevel, featureStructure } = meta as unknown as IFeatureTypeMetadata; - + const { id, items, total, cache, currentZoomLevel, layerRecord } = meta; setFeatureTypes((prevFeatureTypes) => { - const existingIndex = prevFeatureTypes.findIndex((type) => type.id === id); + const existingIndex = prevFeatureTypes.findIndex((featureType) => getLayerIdFromMeta(featureType) === id); if (existingIndex >= 0) { if ( JSON.stringify(prevFeatureTypes[existingIndex]) !== - JSON.stringify({ id, items, total, cache, currentZoomLevel, featureStructure, zoomLevel }) + JSON.stringify({ id, items, total, cache, currentZoomLevel, layerRecord, zoomLevel }) ) { const updatedFeatureTypes = [...prevFeatureTypes]; - updatedFeatureTypes[existingIndex] = { id, items, total, cache, currentZoomLevel, featureStructure, zoomLevel }; + updatedFeatureTypes[existingIndex] = { id, items, total, cache, currentZoomLevel, layerRecord, zoomLevel }; return updatedFeatureTypes; } } else { - return [...prevFeatureTypes, { id, items, total, cache, currentZoomLevel, featureStructure, zoomLevel }]; + return [...prevFeatureTypes, { id, items, total, cache, currentZoomLevel, layerRecord, zoomLevel }]; } return prevFeatureTypes; }); }); - - const activeDataLayerIds = new Set(mapViewer.layersManager?.dataLayerList.map((layer) => layer.meta.id)); - - setFeatureTypes((prevFeatureTypes) => prevFeatureTypes.filter((type) => activeDataLayerIds.has(type.id))); + const activeDataLayerIds = new Set(mapViewer.layersManager?.dataLayerList.map((layer) => getLayerId(layer))); + setFeatureTypes((prevFeatureTypes) => prevFeatureTypes.filter((featureType) => activeDataLayerIds.has(getLayerIdFromMeta(featureType)))); }; - mapViewer.layersManager.addDataLayerUpdatedListener(handleDataLayerUpdated); - return () => { mapViewer.layersManager?.removeDataLayerUpdatedListener(handleDataLayerUpdated); }; @@ -210,47 +209,52 @@ const DebuggerComponent: React.FC = ({ locale, isOpen, set })); }} /> - {viewState?.shouldOptimizedTileRequests === true && ( - - {[...layersMeta].reverse().map((layer, index) => { - 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' : ''; - const transparencyText = - layer.meta?.hasTransparency === true ? withTransparencyTiles : layer.meta?.hasTransparency === false ? withoutTransparencyTiles : ''; - const tileCoordinatesFromMeta = get(layer.meta, EXAMINED_TILES_META_PROP) as - | Array<{ x?: number; y?: number; level?: number }> - | { x?: number; y?: number; level?: number } - | undefined; - const tileCoordinatesList = Array.isArray(tileCoordinatesFromMeta) - ? tileCoordinatesFromMeta - : tileCoordinatesFromMeta !== undefined - ? [tileCoordinatesFromMeta] - : []; - const formattedTileCoordinates = tileCoordinatesList - .filter((tile) => tile.x !== undefined && tile.y !== undefined && tile.level !== undefined) - .map((tile) => `( L: ${String(tile.level)}, X: ${String(tile.x)}, Y: ${String(tile.y)} )`); - const tooltipContent = - transparencyText === '' - ? undefined - : {transparencyText}: {formattedTileCoordinates.join(', ')}; - const isRelevant = layer.meta?.relevantToExtent !== false; - if (tooltipContent === undefined) { - return ( - - {nameText + statusText} + {viewState?.shouldOptimizedTileRequests === true && layersMeta?.length > 0 && ( + + + + + + {[...layersMeta].reverse().map((layer) => { + const idText = layer.layerId; + const nameText = layer.layerName ?? idText; + const statusText = + layer.meta?.isRelevantToExtent === true ? ' → show' : layer.meta?.isRelevantToExtent === false ? ' → hide' : ''; + const hasTransparency = layer.meta[HAS_TRANSPARENCY_META_PROP] as boolean | undefined; + const transparencyText = + hasTransparency === true ? withTransparencyTiles : hasTransparency === false ? withoutTransparencyTiles : ''; + const tileCoordinatesFromMeta = layer.meta[EXAMINED_TILES_META_PROP] as + | Array<{ x?: number; y?: number; level?: number }> + | { x?: number; y?: number; level?: number } + | undefined; + const tileCoordinatesList = Array.isArray(tileCoordinatesFromMeta) + ? tileCoordinatesFromMeta + : tileCoordinatesFromMeta !== undefined + ? [tileCoordinatesFromMeta] + : []; + const formattedTileCoordinates = tileCoordinatesList + .filter((tile) => tile.x !== undefined && tile.y !== undefined && tile.level !== undefined) + .map((tile) => `( L: ${String(tile.level)}, X: ${String(tile.x)}, Y: ${String(tile.y)} )`); + const tooltipContent = + transparencyText === '' + ? undefined + : {transparencyText}: {formattedTileCoordinates.join(', ')}; + const isRelevant = layer.meta?.isRelevantToExtent !== false; + const itemContent = ( + + {nameText + statusText} ); - } - return ( - - - {nameText + statusText} - - - ); - })} + if (tooltipContent === undefined) { + return {itemContent}; + } + return ( + + {itemContent} + + ); + })} + )} diff --git a/packages/react-components/src/components/cesium-map/debug/wfs.tsx b/packages/react-components/src/components/cesium-map/debug/wfs.tsx index d9fd4946..76b633a1 100644 --- a/packages/react-components/src/components/cesium-map/debug/wfs.tsx +++ b/packages/react-components/src/components/cesium-map/debug/wfs.tsx @@ -2,12 +2,13 @@ import { get } from 'lodash'; import React, { useMemo } from 'react'; import { Tooltip } from '@map-colonies/react-core'; import { Box } from '../../box'; -import { IActiveFeatureTypes } from './debugger-widget'; +import { ICesiumWFSLayerMeta } from '../layers/wfs.layer'; +import { getDataLayerName, getLayerIdFromMeta } from '../layers-manager'; import './wfs.css'; interface IWFSProps { - featureTypes: IActiveFeatureTypes[]; + featureTypes: ICesiumWFSLayerMeta[]; locale?: { [key: string]: string }; } @@ -25,20 +26,28 @@ export const WFS: React.FC = ({ featureTypes, locale }) => { return ( <> {featureTypes.length > 0 ? ( - featureTypes.map((type, index) => ( + featureTypes.map((featureType, index) => ( - - - {type.featureStructure.aliasLayerName as string} ({String(type.zoomLevel)}): + {(() => { + const dataLayerName = getDataLayerName(featureType) ?? ''; + const zoomLevel = featureType.zoomLevel ?? 0; + return ( + + + {dataLayerName} ({zoomLevel}): + ); + })()} - {cacheLabel}: {type.cache ?? 0} + {cacheLabel}: {featureType.cache ?? 0} - {type.total > 0 && ( + {(featureType.total ?? 0) > 0 && ( - {extentLabel}: {type.items} / {type.total} + {extentLabel}: {featureType.items} / {featureType.total} )} diff --git a/packages/react-components/src/components/cesium-map/entities/entity.graphics.stories.tsx b/packages/react-components/src/components/cesium-map/entities/entity.graphics.stories.tsx index d2bb0dfa..28a913c4 100644 --- a/packages/react-components/src/components/cesium-map/entities/entity.graphics.stories.tsx +++ b/packages/react-components/src/components/cesium-map/entities/entity.graphics.stories.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Cartesian3, Color } from 'cesium'; import { Story, Meta } from '@storybook/react/types-6-0'; import { CesiumMap } from '../map'; @@ -20,9 +19,18 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + export const Polygon: Story = (args) => (
- +

Hello!

diff --git a/packages/react-components/src/components/cesium-map/entities/entity.stories.tsx b/packages/react-components/src/components/cesium-map/entities/entity.stories.tsx index d0f3c370..5ead0b0a 100644 --- a/packages/react-components/src/components/cesium-map/entities/entity.stories.tsx +++ b/packages/react-components/src/components/cesium-map/entities/entity.stories.tsx @@ -19,6 +19,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const initCanvas = (): HTMLCanvasElement => { const can = document.createElement('canvas'); can.width = 100; @@ -64,7 +71,9 @@ const CanvasEntity: React.FC = (props) => { export const Basic: Story = (args) => (
- + = (args) => { const [count, setCount] = useState(0); return ( - + = (args) => { }; export const AnimatedCanvas: Story = () => ( - + ); diff --git a/packages/react-components/src/components/cesium-map/helpers/customImageryProviders.ts b/packages/react-components/src/components/cesium-map/helpers/customImageryProviders.ts index 8642fc70..374c0491 100644 --- a/packages/react-components/src/components/cesium-map/helpers/customImageryProviders.ts +++ b/packages/react-components/src/components/cesium-map/helpers/customImageryProviders.ts @@ -8,8 +8,8 @@ import { ImageryTypes, } from 'cesium'; import { get } from 'lodash'; -import { ICesiumImageryLayer } from '../layers-manager'; -import { CesiumViewer } from '../map'; +import type { ICesiumImageryLayer } from '../layers-manager'; +import type { CesiumViewer } from '../map'; import { imageHasTransparency } from './utils'; export interface CustomImageryProvider extends ImageryProvider { @@ -30,6 +30,10 @@ const NUMBER_OF_TILES_TO_CHECK = 3; export const HAS_TRANSPARENCY_META_PROP = 'hasTransparency'; export const EXAMINED_TILES_META_PROP = 'examinedTiles'; +const getImageryProviderUrl = (layer: ImageryLayer): string | undefined => { + return get(layer, '_imageryProvider._resource._url') as string | undefined; +}; + function customCommonRequestImage( this: CustomImageryProvider, requestImageFn: ImageryProvider['requestImage'], @@ -43,7 +47,7 @@ function customCommonRequestImage( const requestedLayerMeta = this.layerListInstance.find( /* eslint-disable */ (layer: ImageryLayer): boolean => { - return (layer as any)._imageryProvider._resource?._url === (this as any)._resource?._url; + return getImageryProviderUrl(layer) === (this as any)._resource?._url; } /* eslint-enable */ )?.meta; @@ -58,7 +62,7 @@ function customCommonRequestImage( { [EXAMINED_TILES_META_PROP]: this.examinedTilesForTransparencyCheck }, /* eslint-disable */ (layer: ImageryLayer): boolean => { - return (layer as any)._imageryProvider._resource._url === (this as any)._resource._url; + return getImageryProviderUrl(layer) === (this as any)._resource._url; } /* eslint-enable */ ); @@ -67,7 +71,7 @@ function customCommonRequestImage( { [HAS_TRANSPARENCY_META_PROP]: hasTransparency }, /* eslint-disable */ (layer: ImageryLayer): boolean => { - return (layer as any)._imageryProvider._resource._url === (this as any)._resource._url; + return getImageryProviderUrl(layer) === (this as any)._resource._url; } /* eslint-enable */ ); 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..62cc047e 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, @@ -8,28 +9,70 @@ import { Rectangle, SingleTileImageryProvider, } from 'cesium'; -import { get, isEmpty } from 'lodash'; +import { get, isEmpty, set } 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 { ICesiumWFSLayer } from './layers/wfs.layer'; -import { CesiumCartesian2 } from './proxied.types'; +import { + RCesiumOSMLayerOptions, + RCesiumWMSLayerOptions, + RCesiumWMTSLayerOptions, + RCesiumXYZLayerOptions +} from './layers'; +import type { ICesiumWFSLayer, ICesiumWFSLayerMeta } from './layers/wfs.layer'; +import { IMapLegend } from './legend'; +import type { CesiumViewer, IBaseMap } from './map'; +import { CesiumCartesian2, CesiumImageryProvider } from './proxied.types'; const INC = 1; const DEC = -1; +export interface ILayerManagerMetaMapping { + layer?: { + id?: string; + name?: string; + footprint?: string; + }; + dataLayer?: { + name?: string; + fields?: string; + }; +} + +let mapping: ILayerManagerMetaMapping = {}; + +const configureLayerManagerMetaMapping = (metaMapping: ILayerManagerMetaMapping): void => { + mapping = { ...metaMapping }; +}; + +export const getLayerManagerMetaMapping = (): ILayerManagerMetaMapping => { + return { ...mapping }; +}; + +export interface ICesiumImageryLayerMeta { + id?: string; + parentBaseMapId?: string; + zIndex?: number; + type?: LayerType; + opacity?: number; + show?: boolean; + options?: RCesiumOSMLayerOptions | RCesiumWMSLayerOptions | RCesiumWMTSLayerOptions | RCesiumXYZLayerOptions; + skipRelevancyCheck?: boolean; + isRelevantToExtent?: boolean; + hasTransparency?: boolean; + examinedTiles?: Array<{ x?: number; y?: number; level?: number }>; + [key: string]: unknown; +} + export interface ICesiumImageryLayer extends InstanceType { - meta?: Record; + meta?: ICesiumImageryLayerMeta; } export type LayerType = 'OSM_LAYER' | 'WMTS_LAYER' | 'WMS_LAYER' | 'XYZ_LAYER'; @@ -39,32 +82,95 @@ export interface IRasterLayer { type: LayerType; opacity: number; zIndex: number; - show?: boolean; options: RCesiumOSMLayerOptions | RCesiumWMSLayerOptions | RCesiumWMTSLayerOptions | RCesiumXYZLayerOptions; - details?: Record; + show?: boolean; + [key: string]: unknown; } -export interface IVectorLayer { - id: string; - opacity: number; - zIndex: number; - url: string; +export interface ICesium3DModelMeta { + id?: string; + [key: string]: unknown; +} + +export interface ICesium3DModel { + tileset: CesiumTileset; + meta: ICesium3DModelMeta; +} + +export interface ICesiumDataLayerField { + fieldName: string; + aliasFieldName: string; + [key: string]: unknown; } export type LegendExtractor = (layers: (any & { meta: any })[]) => IMapLegend[]; export const TRANSPARENT_LAYER_ID = 'TRANSPARENT_BASE_LAYER'; +export const getLayerId = (layer: ICesiumImageryLayer | ICesiumWFSLayer | ICesium3DModel): string | undefined => { + return get(layer.meta, mapping.layer?.id ?? '') as string | undefined; +}; + +export const getLayerIdFromMeta = (meta: ICesiumImageryLayerMeta | ICesiumWFSLayerMeta | ICesium3DModelMeta | undefined): string | undefined => { + return get(meta, mapping.layer?.id ?? '') as string | undefined; +}; + +export const getLayerName = (layer: ICesiumImageryLayer | ICesiumWFSLayer | ICesium3DModel): string | undefined => { + return get(layer.meta, mapping.layer?.name ?? '') as string | undefined; +}; + +export const getLayerFootprint = (meta: ICesiumWFSLayerMeta | undefined): unknown => { + return get(meta, mapping.layer?.footprint ?? ''); +}; + +export const getDataLayerName = (meta: ICesiumWFSLayerMeta): string | undefined => { + return get(meta, mapping.dataLayer?.name ?? '') as string | undefined; +}; + +export const getDataLayerFields = (meta: ICesiumWFSLayerMeta | undefined): ICesiumDataLayerField[] => { + return (get(meta, mapping.dataLayer?.fields ?? '') as ICesiumDataLayerField[] | 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._resource._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 readonly layerManagerFootprintMetaFieldPath?: string; private shouldOptimizedTileRequests?: boolean; private relevancyListenersCleanup: Array<() => void>; private relevancyLayerUpdatedHandler?: (meta: Record) => void; @@ -73,21 +179,25 @@ class LayerManager { mapViewer: CesiumViewer, legendsExtractor?: LegendExtractor, onLayersUpdate?: () => void, - layerManagerFootprintMetaFieldPath?: string, + layerManagerMetaMapping?: ILayerManagerMetaMapping, shouldOptimizedTileRequests?: boolean ) { this.mapViewer = mapViewer; // 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.layerManagerFootprintMetaFieldPath = layerManagerFootprintMetaFieldPath; + this.modelUpdated = new Event(); + this.layerManagerFootprintMetaFieldPath = layerManagerMetaMapping?.layer?.footprint; this.shouldOptimizedTileRequests = shouldOptimizedTileRequests ?? false; this.relevancyListenersCleanup = []; + configureLayerManagerMetaMapping(layerManagerMetaMapping ?? {}); + if (onLayersUpdate) { this.addLayerUpdatedListener(onLayersUpdate); } @@ -106,8 +216,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 { @@ -115,7 +225,7 @@ class LayerManager { this.dataLayerUpdated.raiseEvent(this.dataLayers); } - // A general place to extend layer's data. Should be done when all providers(different types) are initialized + // A general place to extend layer's data. Should be done when all providers (different types) are initialized public addMetaToLayer(meta: any, layerPredicate: (layer: ImageryLayer, idx: number) => boolean): void { const layersReadyPromises = this.layers.map((item) => item.imageryProvider.readyPromise); @@ -130,10 +240,14 @@ class LayerManager { } public addMetaToDataLayer(meta: any): void { - const dataLayer = this.findDataLayerById(meta.id); + const dataLayerId = getLayerIdFromMeta(meta); + if (dataLayerId === undefined) { + return; + } + const dataLayer = this.findDataLayerById(dataLayerId); if (dataLayer) { dataLayer.meta = { ...(dataLayer.meta ?? {}), ...meta }; - this.dataLayerUpdated.raiseEvent(this.dataLayers, meta.id); + this.dataLayerUpdated.raiseEvent(this.dataLayers, dataLayerId as any); } } @@ -203,7 +317,7 @@ class LayerManager { if (cesiumLayer) { cesiumLayer.alpha = layer.opacity; cesiumLayer.meta = { - parentBasetMapId: parentId, + parentBaseMapId: parentId, ...layer, }; if (layer.show !== undefined) { @@ -232,7 +346,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 +356,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); @@ -262,6 +375,7 @@ class LayerManager { } this.updateLayersOrder(layerId, order, order + positions); + this.reinvokeOptimizationAfterOrderChange(); } public lower(layerId: string, positions = 1): void { @@ -281,6 +395,7 @@ class LayerManager { } this.updateLayersOrder(layerId, order, order - positions); + this.reinvokeOptimizationAfterOrderChange(); } public raiseToTop(layerId: string): void { @@ -292,6 +407,7 @@ class LayerManager { } this.updateLayersOrder(layerId, order, this.mapViewer.imageryLayers.length - this.getBaseLayersCount() - 1); + this.reinvokeOptimizationAfterOrderChange(); } public lowerToBottom(layerId: string): void { @@ -321,11 +437,13 @@ 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); + const layerId = getLayerId(layer); + if (layerId !== undefined) { + this.show(layerId, isShow); + } }); } @@ -343,8 +461,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 +473,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) => { @@ -402,11 +518,12 @@ class LayerManager { 0 ); - (transparentLayer as ICesiumImageryLayer).meta = { - id: TRANSPARENT_LAYER_ID, + const transparentLayerMeta: Record = { skipRelevancyCheck: true, - parentBasetMapId: 'TRANSPARENT_LAYER', + parentBaseMapId: 'TRANSPARENT_LAYER', }; + set(transparentLayerMeta, mapping.layer?.id ?? '', TRANSPARENT_LAYER_ID); + (transparentLayer as ICesiumImageryLayer).meta = transparentLayerMeta; } public addLayerUpdatedListener(callback: (meta: any) => void): void { @@ -425,6 +542,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; @@ -449,10 +574,30 @@ class LayerManager { public findDataLayerById(dataLayerId: string): ICesiumWFSLayer | undefined { return this.dataLayers.find((dataLayer) => { - return dataLayer.meta.id === dataLayerId; + return getLayerId(dataLayer) === dataLayerId; }); } + 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) => getLayerId(model) === modelId); + } + private setLegends(): void { if (typeof this.legendsExtractor !== 'undefined') { this.legendsList = this.legendsExtractor(this.layers); @@ -461,16 +606,14 @@ 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; } private findLayerById(layerId: string): ICesiumImageryLayer | undefined { return this.layers.find((layer) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return layer.meta !== undefined ? layer.meta.id === layerId : false; + return getLayerId(layer) === layerId; }); } @@ -478,10 +621,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; @@ -491,24 +632,22 @@ class LayerManager { private hideNonRelevantLayers(): void { for (const layer of this.layers) { - if (layer.meta?.id === TRANSPARENT_LAYER_ID) { + if (getLayerId(layer) === 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; } } } private restoreAllLayersVisibility(): void { for (const layer of this.layers) { - if (layer.meta?.id === TRANSPARENT_LAYER_ID) { + if (getLayerId(layer) === TRANSPARENT_LAYER_ID) { continue; } if (layer.imageryProvider.ready) { @@ -519,12 +658,12 @@ class LayerManager { private clearLayersRelevancy(): void { for (const layer of this.layers) { - if (layer.meta?.id === TRANSPARENT_LAYER_ID) { + if (getLayerId(layer) === 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; } } @@ -585,6 +724,14 @@ class LayerManager { this.relevancyLayerUpdatedHandler = undefined; } + private reinvokeOptimizationAfterOrderChange(): void { + if (!this.shouldOptimizedTileRequests) { + return; + } + this.markRelevantLayersForExtent(); + this.hideNonRelevantLayers(); + } + private markRelevantLayersForExtent(): void { try { const extent = this.mapViewer.camera.computeViewRectangle() as Rectangle; @@ -596,11 +743,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 +771,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..f900558e 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 @@ -20,16 +20,27 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const ArcGisProvider = new ArcGISTiledElevationTerrainProvider({ url: 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer', }); 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..b2c9b34c 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,23 +1,38 @@ -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 { getLayerIdFromMeta, ICesium3DModelMeta } from '../layers-manager'; import { CesiumViewer, useCesiumMap } from '../map'; const GROUND_LEVEL = 0.0; -export interface RCesium3DTilesetProps extends ComponentProps { +export interface ICesium3DTileset extends ComponentProps { isZoomTo?: boolean; heightFromGround?: number; + meta?: ICesium3DModelMeta; } -export const Cesium3DTileset: React.FC = (props) => { +export const Cesium3DTileset: React.FC = ({ meta, ...props }) => { const mapViewer: CesiumViewer = useCesiumMap(); + const tilesetRef = useRef(null); + + useEffect(() => { + return () => { + const modelId = getLayerIdFromMeta(meta); + if (tilesetRef.current !== null && modelId !== undefined) { + mapViewer.layersManager?.removeModel(modelId); + } + }; + }, []); + 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 +46,7 @@ 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..adc9333c 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 @@ -7,22 +7,28 @@ */ import React, { useEffect, useState } from 'react'; -import { Cesium3DTileset, Cesium3DTile, Cartographic, Cartesian3, defined, sampleTerrainMostDetailed, Cesium3DTileContent } from 'cesium'; +import { + Cesium3DTileset, + Cesium3DTile, + Cartographic, + Cartesian3, + defined, + sampleTerrainMostDetailed, + Cesium3DTileContent +} from 'cesium'; +import { getLayerIdFromMeta, ICesium3DModelMeta } from '../layers-manager'; import { CesiumViewer, useCesiumMap } from '../map'; -export interface Cesium3DTilesetWithUpdateProps { +export interface ICesium3DTilesetWithUpdate { url: string; withUpdate?: boolean; + meta?: ICesium3DModelMeta; } -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( - new Cesium3DTileset({ - url: url, - }) - ); + const [cesium3DTileset] = useState(new Cesium3DTileset({ url })); const [tileset] = useState(scene.primitives.add(cesium3DTileset)); useEffect(() => { @@ -34,6 +40,17 @@ export const Cesium3DTilesetWithUpdate: React.FC // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + if (meta === undefined) { return; } + mapViewer.layersManager?.addModel({ tileset, meta }); + return () => { + const modelId = getLayerIdFromMeta(meta); + if (modelId !== undefined) { + mapViewer.layersManager?.removeModel(modelId); + } + }; + }, [mapViewer.layersManager]); + const updateContent = (model: Cesium3DTileContent, boundingVolume: any): void => { const height = boundingVolume.minimumHeight ? boundingVolume.minimumHeight : boundingVolume.center.z - boundingVolume.radius; // @ts-ignore @@ -41,7 +58,7 @@ export const Cesium3DTilesetWithUpdate: React.FC const normal = scene.globe.ellipsoid.geodeticSurfaceNormal(center, new Cartesian3()); const offset = Cartesian3.multiplyByScalar(normal, height, new Cartesian3()); const carto = Cartographic.fromCartesian(center); - void new Promise((resolve, reject) => { + void new Promise((resolve) => { // @ts-ignore if (scene.terrainProvider._ready !== true) { const result = { ...carto }; diff --git a/packages/react-components/src/components/cesium-map/layers/geojson.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/geojson.layer.stories.tsx index 96e8869e..ba747bac 100644 --- a/packages/react-components/src/components/cesium-map/layers/geojson.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/geojson.layer.stories.tsx @@ -19,6 +19,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const data = { type: 'FeatureCollection', features: [ @@ -108,7 +115,10 @@ const onLoadAction = action('onLoad'); export const MapWithGeojsonLayer: Story = (args: Record) => (
- + (
- + (
- +
@@ -72,8 +82,11 @@ export const MapWithSettings: Story = () => { return (
- - + +
); 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..f21987d7 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'; @@ -21,6 +21,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const mapViewProps: CesiumMapProps = { center: [-117.30644008676421, 33.117098433617564], zoom: 14, @@ -82,8 +89,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 +108,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} @@ -122,7 +127,11 @@ const LayersContainer: React.FC = () => { export const OptimizedTileRequestingMap: Story = () => { return (
- +
diff --git a/packages/react-components/src/components/cesium-map/layers/osm.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/osm.layer.stories.tsx index 3342bf9f..c3e67e8a 100644 --- a/packages/react-components/src/components/cesium-map/layers/osm.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/osm.layer.stories.tsx @@ -19,6 +19,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const optionsOSM = { url: 'https://a.tile.openstreetmap.org/', }; @@ -30,7 +37,12 @@ export const MapWithOSMLayers: Story = (args) => { const [center] = useState<[number, number]>([34.82, 32.04]); return (
- + 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..baa757ce 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 @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useState } from 'react'; import { BBox } from 'geojson'; import area from '@turf/area'; import intersect from '@turf/intersect'; @@ -9,6 +9,7 @@ import bboxPolygon from '@turf/bbox-polygon'; import { Story, Meta } from '@storybook/react/types-6-0'; import { getValue } from '../../utils/config'; import { BASE_MAPS, DEFAULT_TERRAIN_PROVIDER_URL } from '../helpers/constants'; +import { getLayerIdFromMeta } from '../layers-manager'; import { CesiumMap, CesiumViewer } from '../map'; import { CesiumMath, @@ -56,14 +57,29 @@ const BRIGHT_GREEN = '#01FF1F'; const LIGHT_BLUE = '#24AEE9'; const BRIGHT_PURPLE = '#B734EB'; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + footprint: 'layerRecord.footprint', + }, + dataLayer: { + name: 'layerRecord.featureStructure.aliasLayerName', + fields: 'layerRecord.featureStructure.fields', + }, +}; + // #region STORY PP component export const MapWithPPWFSLayer: Story = (args: Record) => { return (
-
Go to ME
- + ) => { return (
- - + + {/* */} @@ -161,7 +185,7 @@ export const MapWithWFSLayerAPPScenario: Story = (args: Record) { show && ) return (
- - + +
@@ -209,10 +242,18 @@ MapWithWFSLayerAPPScenario.storyName = 'WFS Vector layer(APP Scenario)'; export const MapWithWFSLayerWithVisualizer: Story = (args: Record) => { return (
- - + + { const screenPosition = CesiumSceneTransforms.wgs84ToWindowCoordinates(scene, position); - if (!screenPosition) return null; + if (!screenPosition) { return null; } const xRight = screenPosition.x + pixelWidth; const yBottom = screenPosition.y + pixelHeight; @@ -861,57 +905,60 @@ const optionsBuildings = { const metaBuildings = { id: '1111111', - keywords: 'buildings, osm', - links: 'buildings,,WFS,http://geoserver-vector', - type: 'RECORD_VECTOR', - classification: '5', - productName: 'מבנים', - description: 'Buildings layer', - srsId: '4326', - srsName: '4326', - producerName: 'Moria', - footprint: {"type":"Polygon","coordinates":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}, - productType: 'VECTOR_BEST', - featureStructure: { - layerName: 'buildings', - aliasLayerName: 'מבנים', - fields: [ - { - fieldName: 'osm_id', - aliasFieldName: 'מזהה OSM', - type: 'String', - }, - { - fieldName: 'id', - aliasFieldName: 'מזהה', - type: 'String', - }, - { - fieldName: 'building_type', - aliasFieldName: 'סוג', - type: 'String', - }, - { - fieldName: 'sensitivity', - aliasFieldName: 'רגישות', - type: 'String', - }, - { - fieldName: 'entity_id', - aliasFieldName: 'מזהה יישות', - type: 'String', - }, - { - fieldName: 'is_sensitive', - aliasFieldName: 'רגיש', - type: 'Boolean', - }, - { - fieldName: 'date', - aliasFieldName: 'תאריך', - type: 'Date', - }, - ], + layerRecord: { + id: '1111111', + keywords: 'buildings, osm', + links: 'buildings,,WFS,http://geoserver-vector', + type: 'RECORD_VECTOR', + classification: '5', + productName: 'מבנים', + description: 'Buildings layer', + srsId: '4326', + srsName: '4326', + producerName: 'Moria', + footprint: {"type":"Polygon","coordinates":[[[-180,-90],[180,-90],[180,90],[-180,90],[-180,-90]]]}, + productType: 'VECTOR_BEST', + featureStructure: { + layerName: 'buildings', + aliasLayerName: 'מבנים', + fields: [ + { + fieldName: 'osm_id', + aliasFieldName: 'מזהה OSM', + type: 'String', + }, + { + fieldName: 'id', + aliasFieldName: 'מזהה', + type: 'String', + }, + { + fieldName: 'building_type', + aliasFieldName: 'סוג', + type: 'String', + }, + { + fieldName: 'sensitivity', + aliasFieldName: 'רגישות', + type: 'String', + }, + { + fieldName: 'entity_id', + aliasFieldName: 'מזהה יישות', + type: 'String', + }, + { + fieldName: 'is_sensitive', + aliasFieldName: 'רגיש', + type: 'Boolean', + }, + { + fieldName: 'date', + aliasFieldName: 'תאריך', + type: 'Date', + }, + ], + }, }, }; diff --git a/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx b/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx index 01210caf..60b2ad78 100644 --- a/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx @@ -19,6 +19,7 @@ import pMap from 'p-map'; import { v4 as uuidv4 } from 'uuid'; import booleanValid from '@turf/boolean-valid'; import { distance, center, rectangle2bbox, computeLimitedViewRectangle, defaultVisualizationHandler, rectangle2Feature } from '../helpers/utils'; +import { getDataLayerFields, getLayerIdFromMeta } from '../layers-manager'; import { CesiumViewer, useCesiumMap, useCesiumMapViewstate } from '../map'; export interface ICesiumWFSLayerLabelTextField { @@ -85,9 +86,19 @@ export interface ICesiumWFSLayerOptions { labeling?: ICesiumWFSLayerLabelingOptions; } +export interface ICesiumWFSLayerMeta { + id: string; + items?: number; + total?: number; + cache?: number; + currentZoomLevel?: number; + zoomLevel?: number; + [key: string]: unknown; +} + export interface ICesiumWFSLayer extends React.Attributes { options: ICesiumWFSLayerOptions; - meta: Record; + meta: ICesiumWFSLayerMeta; visualizationHandler?: (mapViewer: CesiumViewer, wfsDataSource: GeoJsonDataSource, processEntityIds: string[], extent?: BBox) => void; withGeometryValidation?: boolean; } @@ -111,6 +122,7 @@ export const CesiumWFSLayer: React.FC = (props) => { const wfsCache = useRef(new Set()); const page = useRef(0); const [metadata, setMetadata] = useState(meta); + const dataLayerId = getLayerIdFromMeta(meta); const geojsonHoveredColor = useMemo(() => CesiumColor.fromCssColorString((hover as string) ?? '#24AEE9').withAlpha(0.5), [hover]); const dataSourceName = useMemo(() => `wfs_${featureType}_${uuidv4()}`, [featureType]); const hasRunFetchRef = useRef(false); @@ -130,27 +142,25 @@ export const CesiumWFSLayer: React.FC = (props) => { const describe = (properties: Record): string => { const rows: string[] = []; - const featureStructure = meta.featureStructure as { fields: { fieldName: string; aliasFieldName: string; type: string }[] }; - if (featureStructure && featureStructure.fields) { - for (const field of featureStructure.fields) { - const { fieldName, aliasFieldName } = field; - const key = aliasFieldName; - const value = properties[fieldName] ?? 'N/A'; - const keyMaxWidth = Math.max(100, Math.min(180, key.length * 10)); - const valueMaxWidth = '260px'; - rows.push(` - - - ${key}: - - - ${value} - - - `); - } + const dataLayerFields = getDataLayerFields(meta); + for (const field of dataLayerFields) { + const { fieldName, aliasFieldName } = field; + const key = aliasFieldName; + const value = properties[fieldName] ?? 'N/A'; + const keyMaxWidth = Math.max(100, Math.min(180, key.length * 10)); + const valueMaxWidth = '260px'; + rows.push(` + + + ${key}: + + + ${value} + + + `); } - const isRightToLeft = featureStructure.fields.some((field) => field.aliasFieldName !== field.fieldName); + const isRightToLeft = dataLayerFields.some((field) => field.aliasFieldName !== field.fieldName); return ` @@ -300,11 +310,20 @@ export const CesiumWFSLayer: React.FC = (props) => { }; const fetchWfsData = async (wfsDataUrl: string, options: RequestInit = { method: 'GET' }): Promise => { - const response = await fetch(wfsDataUrl, options); - if (response.status === 200) { - return await response.json(); + if (!wfsDataUrl) { + throw new Error('WFS request URL is missing'); + } + let response: Response; + try { + response = await fetch(wfsDataUrl, options); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`WFS network request failed for ${wfsDataUrl}: ${message}`); } - return undefined; + if (!response.ok) { + throw new Error(`WFS request failed (${response.status} ${response.statusText}) for ${wfsDataUrl}`); + } + return await response.json(); }; // Create a temporary canvas to measure max width @@ -602,6 +621,11 @@ export const CesiumWFSLayer: React.FC = (props) => { const waitForTilesLoaded = () => { return new Promise((resolve) => { const interval = setInterval(() => { + if (get(mapViewer, '_cesiumWidget') === undefined) { + clearInterval(interval); + resolve(); + return; + } if (mapViewer.scene.globe.tilesLoaded) { clearInterval(interval); resolve(); @@ -617,18 +641,22 @@ export const CesiumWFSLayer: React.FC = (props) => { }; useEffect((): void => { + if (!mapViewer?.scene || !mapViewer?.dataSources) { + return; + } const dataSource = mapViewer.dataSources.getByName(dataSourceName)[0] as GeoJsonDataSource; if (dataSource) { applyVisualization(mapViewer, dataSource, [], undefined); } - }, [mapViewer.scene.mode]); + }, [mapViewer?.scene?.mode]); useEffect(() => { // Happens each time the metadata from STATE changes if ( mapViewer.layersManager && mapViewer.layersManager.dataLayerList.length > 0 && - mapViewer.layersManager.findDataLayerById(meta.id as string) !== undefined + dataLayerId !== undefined && + mapViewer.layersManager.findDataLayerById(dataLayerId) !== undefined ) { mapViewer.layersManager.addMetaToDataLayer(metadata); } @@ -640,6 +668,9 @@ export const CesiumWFSLayer: React.FC = (props) => { }, [mapViewer.layersManager]); useEffect(() => { + if (!mapViewer?.scene || !mapViewer?.dataSources) { + return; + } // DataSource mapViewer.dataSources.add(wfsDataSource); @@ -666,7 +697,9 @@ export const CesiumWFSLayer: React.FC = (props) => { fetchMetadata.current.clear(); mapViewer.dataSources.remove(mapViewer.dataSources.getByName(`${labeling?.dataSourcePrefix}${wfsDataSource.name}`)[0]); mapViewer.dataSources.remove(wfsDataSource, true); - mapViewer.layersManager?.removeDataLayer(meta.id as string); + if (dataLayerId !== undefined) { + mapViewer.layersManager?.removeDataLayer(dataLayerId); + } mapViewer.scene.camera.moveEnd.removeEventListener(fetchHandler); handler.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE); } diff --git a/packages/react-components/src/components/cesium-map/layers/wms.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/wms.layer.stories.tsx index 7e50d5b3..63da6d8f 100644 --- a/packages/react-components/src/components/cesium-map/layers/wms.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wms.layer.stories.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react/types-6-0'; import { CesiumMap } from '../map'; import { CesiumWMSLayer } from './wms.layer'; @@ -17,6 +16,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const optionsWMS = { url: 'https://ahocevar.com/geoserver/wms', layers: 'ne:NE1_HR_LC_SR_W_DR', @@ -29,7 +35,9 @@ const optionsWMS2 = { export const MapWithWMSLayers: Story = () => (
- + diff --git a/packages/react-components/src/components/cesium-map/layers/wmts.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/wmts.layer.stories.tsx index be69d27b..4d86b152 100644 --- a/packages/react-components/src/components/cesium-map/layers/wmts.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wmts.layer.stories.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react/types-6-0'; import { Credit } from 'cesium'; import { CesiumMap } from '../map'; @@ -18,6 +17,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const optionsWMTS = { url: 'http://basemap.nationalmap.gov/arcgis/rest/services/USGSShadedReliefOnly/MapServer/WMTS', layer: 'USGSShadedReliefOnly', @@ -42,7 +48,9 @@ const optionsWMTS2 = { export const MapWithWMTSLayers: Story = () => (
- + diff --git a/packages/react-components/src/components/cesium-map/layers/xyz.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/xyz.layer.stories.tsx index 564c758a..d9b11414 100644 --- a/packages/react-components/src/components/cesium-map/layers/xyz.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/xyz.layer.stories.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react/types-6-0'; import { CesiumMap } from '../map'; import { CesiumXYZLayer } from './xyz.layer'; @@ -17,6 +16,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const optionsXYZ = { url: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', }; @@ -27,7 +33,9 @@ const optionsXYZ2 = { export const MapWithXYZLayers: Story = () => (
- + diff --git a/packages/react-components/src/components/cesium-map/legend/legends-sidebar.stories.tsx b/packages/react-components/src/components/cesium-map/legend/legends-sidebar.stories.tsx index f256b88c..217c4817 100644 --- a/packages/react-components/src/components/cesium-map/legend/legends-sidebar.stories.tsx +++ b/packages/react-components/src/components/cesium-map/legend/legends-sidebar.stories.tsx @@ -3,7 +3,6 @@ import { Story, Meta } from '@storybook/react/types-6-0'; import { BASE_MAPS } from '../helpers/constants'; import { CesiumXYZLayer } from '../layers/xyz.layer'; import { CesiumMap } from '../map'; -import { CesiumSceneMode } from '../proxied.types'; export default { title: 'Cesium Map', @@ -19,6 +18,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const optionsXYZSanDiego = { url: 'https://tiles.openaerialmap.org/5d73614588556200055f10d6/0/5d73614588556200055f10d7/{z}/{x}/{y}', }; @@ -49,6 +55,7 @@ export const MapWithLegends: Story = () => { title: 'Map Legends', emptyText: 'No legends for this basemap', }} + layerManagerMetaMapping={layerManagerMetaMapping} > diff --git a/packages/react-components/src/components/cesium-map/map.stories.tsx b/packages/react-components/src/components/cesium-map/map.stories.tsx index ec8b69ab..b51345a7 100644 --- a/packages/react-components/src/components/cesium-map/map.stories.tsx +++ b/packages/react-components/src/components/cesium-map/map.stories.tsx @@ -22,6 +22,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const triggerCallbackFunc = (data: Feature, options: GeocoderOptions, i: number) => { const baseUrl = getValue('GEOCODER', 'CALLBACK_URL'); @@ -222,7 +229,11 @@ const LOCALIZED_GEOCODER_OPTIONS = [ export const BaseMap: Story = (args: CesiumMapProps) => (
- + +
); @@ -258,7 +269,11 @@ BaseMap.argTypes = { export const ZoomedMap: Story = (args: CesiumMapProps) => (
- + +
); @@ -289,7 +304,11 @@ const cesiumTheme = { export const GeocoderPanel: Story = (args: CesiumMapProps) => (
- + +
); @@ -324,7 +343,11 @@ GeocoderPanel.storyName = 'Geocoder'; export const MapWithProjection: Story = (args: CesiumMapProps) => (
- + +
); @@ -354,7 +377,11 @@ MapWithProjection.argTypes = { export const Map2DWithProjection: Story = (args: CesiumMapProps) => (
- + +
); @@ -388,7 +415,11 @@ Map2DWithProjection.storyName = '2D Map With Projection'; export const LocalizedMap: Story = (args: CesiumMapProps) => (
- + +
); @@ -422,7 +453,9 @@ LocalizedMap.argTypes = { NO_DATA_LAYERS: 'לא נמצאו שכבות מידע בתצוגה', ACTIVE_LAYERS_TITLE: 'שכבות פעילות', IMAGERY: 'ראסטר', + SERVICE: 'שירות', DATA: 'מידע', + '3D': 'תלת-מימד', FLY_TO: 'הצג מיקום', REMOVE: 'הסר', BASE_MAP_TITLE: 'מפות בסיס', diff --git a/packages/react-components/src/components/cesium-map/map.tsx b/packages/react-components/src/components/cesium-map/map.tsx index 06aa97ae..05185f4c 100644 --- a/packages/react-components/src/components/cesium-map/map.tsx +++ b/packages/react-components/src/components/cesium-map/map.tsx @@ -36,7 +36,7 @@ import { GeocoderOptions } from './geocoder/geocoder-panel'; import { GeocoderWidget } from './geocoder/geocoder-widget'; import { DEFAULT_TERRAIN_PROVIDER_URL } from './helpers/constants'; import { pointToLonLat } from './helpers/geojson/point.geojson'; -import LayerManager, { IRasterLayer, LegendExtractor } from './layers-manager'; +import LayerManager, { IRasterLayer, LegendExtractor, type ILayerManagerMetaMapping } from './layers-manager'; import { LegendWidget, IMapLegend, LegendSidebar } from './legend'; import { CesiumCompassTool } from './tools/cesium-compass.tool'; import { CoordinatesTrackerTool } from './tools/coordinates-tracker.tool'; @@ -166,7 +166,7 @@ export interface CesiumMapProps extends ViewerProps { dynamicHeightIncrement?: number; }; legends?: ILegends; - layerManagerFootprintMetaFieldPath?: string; + layerManagerMetaMapping?: ILayerManagerMetaMapping; geocoderPanel?: GeocoderOptions[]; } @@ -304,7 +304,7 @@ export const CesiumMap: React.FC = (props) => { () => { setLegendsList(mapViewRef.layersManager?.legendsList as IMapLegend[]); }, - props.layerManagerFootprintMetaFieldPath, + props.layerManagerMetaMapping, viewState?.shouldOptimizedTileRequests ), }); @@ -314,7 +314,12 @@ export const CesiumMap: React.FC = (props) => { setViewState, }; } - }, [props.legends, props.layerManagerFootprintMetaFieldPath, mapViewRef, viewState]); + }, [ + props.legends, + props.layerManagerMetaMapping, + mapViewRef, + viewState + ]); useEffect(() => { setBaseMaps(props.baseMaps); diff --git a/packages/react-components/src/components/cesium-map/proxied.types.ts b/packages/react-components/src/components/cesium-map/proxied.types.ts index 1a2857c5..601a3bea 100644 --- a/packages/react-components/src/components/cesium-map/proxied.types.ts +++ b/packages/react-components/src/components/cesium-map/proxied.types.ts @@ -5,6 +5,7 @@ import { Cartesian3, Cartographic, CesiumTerrainProvider, + Color, ConstantPositionProperty, ConstantProperty, Ellipsoid, @@ -12,18 +13,18 @@ import { GeographicTilingScheme, HeightReference, HorizontalOrigin, + ImageryProvider, JulianDate, LabelStyle, + PolygonHierarchy, PolylineDashMaterialProperty, PolylineGraphics, PositionProperty, Rectangle, Resource, - VerticalOrigin, - SceneMode, Scene, - Color, - PolygonHierarchy, + SceneMode, + VerticalOrigin, } from 'cesium'; // PROXIED CLASSES @@ -67,6 +68,8 @@ export class CesiumPolygonHierarchy extends PolygonHierarchy {} export class CesiumScene extends Scene {} +export class CesiumImageryProvider extends ImageryProvider {} + // PROXIED ENUMS // eslint-disable-next-line @typescript-eslint/naming-convention export const CesiumVerticalOrigin = VerticalOrigin; diff --git a/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx b/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx index 6454cd91..16dca14d 100644 --- a/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx +++ b/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx @@ -11,7 +11,6 @@ import { getValue } from '../../utils/config'; import { BASE_MAPS } from '../helpers/constants'; import { Cesium3DTileset } from '../layers'; import { CesiumMap, CesiumViewer, useCesiumMap } from '../map'; -import { InspectorTool } from '../tools/inspector.tool'; import { TerrainianHeightTool } from '../tools/terranian-height.tool'; export default { @@ -28,6 +27,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const EllipsoidProvider = new EllipsoidTerrainProvider({}); //#region TILER MATERIALS @@ -119,11 +125,16 @@ export const QuantizedMeshHeightsTool: Story = () => { zoom={5} imageryProvider={false} baseMaps={BASE_MAPS} + showDebuggerTool={true} + layerManagerMetaMapping={layerManagerMetaMapping} > - + -
); 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..e64c0445 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 @@ -14,7 +14,6 @@ import { getValue } from '../../utils/config'; import { BASE_MAPS } from '../helpers/constants'; import { Cesium3DTileset } from '../layers'; import { CesiumMap, useCesiumMap } from '../map'; -import { InspectorTool } from '../tools/inspector.tool'; import QuantizedMeshTerrainProvider from './custom/quantized-mesh-terrain-provider'; export default { @@ -31,6 +30,13 @@ const mapDivStyle = { position: 'absolute' as const, }; +const layerManagerMetaMapping = { + layer: { + id: 'id', + name: 'layerRecord.productName', + }, +}; + const EllipsoidProvider = new EllipsoidTerrainProvider({}); const CesiumProvider = new CesiumTerrainProvider({ @@ -105,7 +111,11 @@ const TerrainProviderSelector: React.FC = ({ terr }; return ( - <> +
+
+ + +
-
- - - +
); }; @@ -135,10 +142,15 @@ export const QuantizedMeshProviders: Story = () => { imageryProvider={false} baseMaps={BASE_MAPS} mapProjection={new WebMercatorProjection()} + showDebuggerTool={true} + layerManagerMetaMapping={layerManagerMetaMapping} > - + -
); 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'; } diff --git a/packages/react-components/src/components/ol-map/source/stories/mvt.stories.tsx b/packages/react-components/src/components/ol-map/source/stories/mvt.stories.tsx index fbf630ec..afa5d511 100644 --- a/packages/react-components/src/components/ol-map/source/stories/mvt.stories.tsx +++ b/packages/react-components/src/components/ol-map/source/stories/mvt.stories.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Style, Fill, Circle, Stroke } from 'ol/style'; import { Proj } from '../../../utils/projections'; import { VectorTileLayer } from '../../layers/vector-tile-layer'; @@ -8,7 +7,7 @@ import { MVTSource, getMVTOptions } from '../mvt'; import { TileOsm } from '..'; export default { - title: 'Map/Map Tiles/MVT', + title: 'OL Map/Tiles/MVT', component: VectorTileLayer, subcomponents: MVTSource, parameters: { @@ -22,7 +21,7 @@ const mapDivStyle = { position: 'absolute' as const, }; -export const Basic = (): JSX.Element => ( +export const VectorTiles = (): JSX.Element => (
diff --git a/packages/react-components/src/components/ol-map/source/stories/vector-source.stories.tsx b/packages/react-components/src/components/ol-map/source/stories/vector-source.stories.tsx deleted file mode 100644 index 1412db9d..00000000 --- a/packages/react-components/src/components/ol-map/source/stories/vector-source.stories.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { Geometries } from '@turf/helpers'; -import { Fill, Stroke, Style } from 'ol/style'; -import { Proj } from '../../../utils/projections'; -import { Map } from '../../map'; -import { TileLayer, VectorLayer } from '../../layers'; -import { GeoJSONFeature } from '../../feature'; -import { TileOsm } from '..'; -import { VectorSource } from '../vector-source'; - -export default { - title: 'Map/Map Tiles/Geojson', - component: VectorLayer, - subcomponents: GeoJSONFeature, - parameters: { - layout: 'fullscreen', - }, -}; - -const geometries: Geometries[] = [ - { - type: 'Polygon', - coordinates: [ - [ - [3864197.52, 3750764.97], - [3884682.65, 3750764.98], - [3884682.65, 3766052.38], - [3864197.53, 3766052.38], - [3864197.52, 3750764.97], - ], - ], - }, - { - type: 'Polygon', - coordinates: [ - [ - [3904403.4, 3765899.51], - [3896912.58, 3758255.81], - [3905779.27, 3743579.9], - [3918162.07, 3755962.7], - [3904403.4, 3765899.51], - ], - ], - }, - { - type: 'LineString', - coordinates: [ - [3931767.86, 3763147.78], - [3931003.49, 3724776.39], - ], - }, - { - type: 'Point', - coordinates: [3890186.12, 3734254.58], - }, -]; - -const mapDivStyle = { - height: '100%', - width: '100%', - position: 'absolute' as const, -}; - -export const Basic = (): JSX.Element => ( -
- - - - - - - {geometries.map((geometry, index) => { - const selected_polygon_style = new Style({ - stroke: new Stroke({ - width: 5, - color: '#ff0000', - }), - fill: new Fill({ - color: '#aa2727', - }), - }); - let featStyle = index === 0 ? selected_polygon_style : undefined; - - return ; - })} - - - -
-); diff --git a/packages/react-components/src/components/ol-map/source/stories/wms.stories.tsx b/packages/react-components/src/components/ol-map/source/stories/wms.stories.tsx index f67fc935..00344963 100644 --- a/packages/react-components/src/components/ol-map/source/stories/wms.stories.tsx +++ b/packages/react-components/src/components/ol-map/source/stories/wms.stories.tsx @@ -19,12 +19,12 @@ const mapDivStyle = { }; const story = { - title: 'Map/Map Tiles/WMS', + title: 'OL Map/Tiles/WMS', component: TileWMS, }; export default story; -export const Basic: CSFStory = () => ( +export const WmsTiles: CSFStory = () => (
@@ -34,7 +34,7 @@ export const Basic: CSFStory = () => (
); -Basic.argTypes = { +WmsTiles.argTypes = { options: { description: `{ Options } from 'ol/source/TileWMS'`, table: { diff --git a/packages/react-components/src/components/ol-map/source/stories/wmts.stories.tsx b/packages/react-components/src/components/ol-map/source/stories/wmts.stories.tsx index 332b98eb..06248d20 100644 --- a/packages/react-components/src/components/ol-map/source/stories/wmts.stories.tsx +++ b/packages/react-components/src/components/ol-map/source/stories/wmts.stories.tsx @@ -32,13 +32,13 @@ const mapDivStyle = { }; const story = { - title: 'Map/Map Tiles/WMTS', + title: 'OL Map/Tiles/WMTS', component: TileWMTS, }; export default story; -export const Basic: CSFStory = () => ( +export const WmtsTiles: CSFStory = () => (
@@ -54,7 +54,7 @@ export const Basic: CSFStory = () => (
); -Basic.argTypes = { +WmtsTiles.argTypes = { options: { description: `{ Options } from 'ol/source/WMTS'`, table: { diff --git a/packages/react-components/src/components/ol-map/source/stories/xyz.stories.tsx b/packages/react-components/src/components/ol-map/source/stories/xyz.stories.tsx index 3e70c727..8c167fb1 100644 --- a/packages/react-components/src/components/ol-map/source/stories/xyz.stories.tsx +++ b/packages/react-components/src/components/ol-map/source/stories/xyz.stories.tsx @@ -15,13 +15,13 @@ const mapDivStyle = { }; const story = { - title: 'Map/Map Tiles/XYZ', + title: 'OL Map/Tiles/XYZ', component: TileXYZ, }; export default story; -export const Basic: CSFStory = () => ( +export const XyzTiles: CSFStory = () => (
@@ -31,7 +31,7 @@ export const Basic: CSFStory = () => (
); -Basic.argTypes = { +XyzTiles.argTypes = { options: { description: `{ Options } from 'ol/source/XYZ'`, table: { diff --git a/packages/react-components/src/components/ol-map/source/stories/legend.stories.tsx b/packages/react-components/src/components/ol-map/stories/legend.stories.tsx similarity index 83% rename from packages/react-components/src/components/ol-map/source/stories/legend.stories.tsx rename to packages/react-components/src/components/ol-map/stories/legend.stories.tsx index d04d6adf..efabe137 100644 --- a/packages/react-components/src/components/ol-map/source/stories/legend.stories.tsx +++ b/packages/react-components/src/components/ol-map/stories/legend.stories.tsx @@ -1,17 +1,16 @@ -import React from 'react'; import { Geometries } from '@turf/helpers'; import { Fill, Stroke, Style } from 'ol/style'; import { Vector } from 'ol/layer'; -import { Proj } from '../../../utils/projections'; -import { Map } from '../../map'; -import { TileLayer, VectorLayer } from '../../layers'; -import { Legend, LegendItem } from '../../legend'; -import { GeoJSONFeature } from '../../feature'; -import { TileOsm } from '..'; -import { VectorSource } from '../vector-source'; +import { Proj } from '../../utils/projections'; +import { Map } from '../map'; +import { TileLayer, VectorLayer } from '../layers'; +import { Legend, LegendItem } from '../legend'; +import { GeoJSONFeature } from '../feature'; +import { TileOsm } from '../source'; +import { VectorSource } from '../source/vector-source'; export default { - title: 'Map/Map Tiles/Legend', + title: 'OL Map/Map', component: Legend, subcomponents: GeoJSONFeature, parameters: { @@ -110,7 +109,7 @@ const LegendsArray: LegendItem[] = [ }, ]; -export const Basic = (): JSX.Element => ( +export const GeojsonFeaturesWithLegend = (): JSX.Element => (
@@ -128,3 +127,5 @@ export const Basic = (): JSX.Element => (
); + +GeojsonFeaturesWithLegend.storyName = 'GeoJSON Features with Legend'; diff --git a/packages/react-components/src/components/ol-map/stories/map.stories.tsx b/packages/react-components/src/components/ol-map/stories/map.stories.tsx index ec7e6f49..ed238137 100644 --- a/packages/react-components/src/components/ol-map/stories/map.stories.tsx +++ b/packages/react-components/src/components/ol-map/stories/map.stories.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Story, Meta } from '@storybook/react/types-6-0'; import { Map } from '../map'; import { TileOsm } from '../source'; @@ -6,7 +5,7 @@ import { TileLayer } from '../layers'; import { Proj } from '../../utils/projections'; export default { - title: 'Map', + title: 'OL Map/Map', component: Map, parameters: { layout: 'fullscreen', @@ -49,12 +48,11 @@ BaseMap.argTypes = { export const ConfiguredMap: Story = () => (
- +
); - -ConfiguredMap.storyName = 'with zoom and center'; +ConfiguredMap.storyName = 'Map with Zoom and Center';