diff --git a/app/scripts/components/common/map/style-generators/cmr-timeseries.tsx b/app/scripts/components/common/map/style-generators/cmr-timeseries.tsx index 790d38a3b..c3eb71c50 100644 --- a/app/scripts/components/common/map/style-generators/cmr-timeseries.tsx +++ b/app/scripts/components/common/map/style-generators/cmr-timeseries.tsx @@ -1,28 +1,10 @@ import React from 'react'; -import { BaseGeneratorParams } from '../types'; -import { ZarrPaintLayer } from './zarr-timeseries'; -import { useCMR } from './hooks'; -import { ActionStatus } from '$utils/status'; +import { useCMRSTAC } from './hooks'; +import { RasterTimeseriesProps } from './raster-timeseries'; +import { RasterPaintLayer } from '$components/common/map/style-generators/raster-paint-layer'; -interface AssetUrlReplacement { - from: string; - to: string; -} - -export interface CMRTimeseriesProps extends BaseGeneratorParams { - id: string; - stacCol: string; - date?: Date; - sourceParams?: Record; - stacApiEndpoint?: string; - tileApiEndpoint?: string; - assetUrlReplacements?: AssetUrlReplacement; - zoomExtent?: number[]; - onStatusChange?: (result: { status: ActionStatus; id: string }) => void; -} - -export function CMRTimeseries(props:CMRTimeseriesProps) { +export function CMRTimeseries(props: RasterTimeseriesProps) { const { id, stacCol, @@ -30,9 +12,11 @@ export function CMRTimeseries(props:CMRTimeseriesProps) { date, assetUrlReplacements, onStatusChange, + sourceParams, + hidden } = props; const stacApiEndpointToUse = stacApiEndpoint?? process.env.API_STAC_ENDPOINT; - const assetUrl = useCMR({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange }); - return ; + const tileParams = useCMRSTAC({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange, sourceParams }); + return ; } \ No newline at end of file diff --git a/app/scripts/components/common/map/style-generators/hooks.ts b/app/scripts/components/common/map/style-generators/hooks.ts index 5fbbe1830..c44a96b7d 100644 --- a/app/scripts/components/common/map/style-generators/hooks.ts +++ b/app/scripts/components/common/map/style-generators/hooks.ts @@ -11,7 +11,7 @@ interface ZarrResponseData { zarr: { href: string } - } + }, } interface CMRResponseData { features: { @@ -23,8 +23,13 @@ interface CMRResponseData { }[] } -export function useZarr({ id, stacCol, stacApiEndpointToUse, date, onStatusChange }){ - const [assetUrl, setAssetUrl] = useState(''); +interface STACforCMRResponseData { + collection_concept_id: string; + renders: Record; +} + +export function useZarr({ id, stacCol, stacApiEndpointToUse, date, onStatusChange, sourceParams }){ + const [tileParams, setTileParams] = useState({}); useEffect(() => { const controller = new AbortController(); @@ -38,11 +43,18 @@ export function useZarr({ id, stacCol, stacApiEndpointToUse, date, onStatusChang controller }); - setAssetUrl(data.assets.zarr.href); + if (data.assets.zarr.href) { + setTileParams({ + url: data.assets.zarr.href, + time_slice: date, + ...sourceParams + }); + } + onStatusChange?.({ status: S_SUCCEEDED, id }); } catch (error) { if (!controller.signal.aborted) { - setAssetUrl(''); + setTileParams({}); onStatusChange?.({ status: S_FAILED, id }); } return; @@ -54,15 +66,15 @@ export function useZarr({ id, stacCol, stacApiEndpointToUse, date, onStatusChang return () => { controller.abort(); }; - }, [id, stacCol, stacApiEndpointToUse, date, onStatusChange]); + }, [id, stacCol, stacApiEndpointToUse, date, onStatusChange, sourceParams]); - return assetUrl; + return tileParams; } -export function useCMR({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange }){ - const [assetUrl, setAssetUrl] = useState(''); +export function useCMRSTAC({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange, sourceParams }){ + const [tileParams, setTileParams] = useState({}); const replaceInAssetUrl = (url: string, replacement: AssetUrlReplacement) => { const {from, to } = replacement; @@ -90,11 +102,15 @@ export function useCMR({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplac }); const assetUrl = replaceInAssetUrl(data.features[0].assets.data.href, assetUrlReplacements); - setAssetUrl(assetUrl); + setTileParams({ + url: assetUrl, + time_slice: date, + ...sourceParams + }); onStatusChange?.({ status: S_SUCCEEDED, id }); } catch (error) { if (!controller.signal.aborted) { - setAssetUrl(''); + setTileParams({}); onStatusChange?.({ status: S_FAILED, id }); } return; @@ -106,8 +122,61 @@ export function useCMR({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplac return () => { controller.abort(); }; - }, [id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange]); + }, [id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange, sourceParams]); + + return tileParams; + +} + + +export function useTitilerCMR({ id, stacCol, stacApiEndpointToUse, date, stacApiEndpoint, onStatusChange, sourceParams }){ + const [tileParams, setTileParams] = useState({}); + + useEffect(() => { + const controller = new AbortController(); + + async function load() { + try { + onStatusChange?.({ status: S_LOADING, id }); + + const data: STACforCMRResponseData = await requestQuickCache({ + url: `${stacApiEndpointToUse}/collections/${stacCol}`, + method: 'GET', + controller + }); + + const baseParams = { + concept_id: data.collection_concept_id, + datetime: date, + ...sourceParams + }; + + const variable = sourceParams?.variable; + + if (variable) { + baseParams.variable = variable; + const renderParams = data.renders[variable] || {}; + setTileParams({ ...renderParams, ...baseParams }); + } else { + setTileParams(baseParams); + } - return assetUrl; + onStatusChange?.({ status: S_SUCCEEDED, id }); + } catch (error) { + if (!controller.signal.aborted) { + setTileParams({}); + onStatusChange?.({ status: S_FAILED, id }); + } + return; + } + } + + load(); + + return () => { + controller.abort(); + }; + }, [id, stacCol, stacApiEndpointToUse, date, stacApiEndpoint, onStatusChange, sourceParams]); + return tileParams; } \ No newline at end of file diff --git a/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx b/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx new file mode 100644 index 000000000..4457d623f --- /dev/null +++ b/app/scripts/components/common/map/style-generators/raster-paint-layer.tsx @@ -0,0 +1,119 @@ +import { useEffect, useMemo } from 'react'; +import qs from 'qs'; +import { RasterSource, RasterLayer } from 'mapbox-gl'; + +import useMapStyle from '../hooks/use-map-style'; + +interface RasterPaintLayerProps { + id: string; + tileParams?: Record; + tileApiEndpoint?: string; + zoomExtent?: number[]; + hidden?: boolean; + idSuffix?: string; + opacity?: number; +} + +export function RasterPaintLayer(props: RasterPaintLayerProps) { + const { + id, + tileApiEndpoint, + tileParams, + zoomExtent, + hidden, + opacity, + idSuffix = '' + } = props; + + const { updateStyle } = useMapStyle(); + + const [minZoom] = zoomExtent ?? [0, 20]; + + const generatorId = 'zarr-timeseries' + idSuffix; + + // Generate Mapbox GL layers and sources for raster timeseries + // + const haveSourceParamsChanged = useMemo( + () => JSON.stringify(tileParams), + [tileParams] + ); + + useEffect( + () => { + + // Customize qs to use comma separated values for arrays + // e.g. rescale: [[0, 100]] -> rescale=0,100 + // TODO: test this with multiple rescale values, for multiple bands + const options = { + arrayFormat: 'comma', + }; + + const tileParamsAsString = qs.stringify(tileParams, options); + const tileServerUrl = `${tileApiEndpoint}?${tileParamsAsString}`; + const layerSource: RasterSource = { + type: 'raster', + url: tileServerUrl + }; + const rasterOpacity = typeof opacity === 'number' ? opacity / 100 : 1; + const layer: RasterLayer = { + id: id, + type: 'raster', + source: id, + layout: { + visibility: hidden ? 'none' : 'visible' + }, + paint: { + //'raster-opacity': hidden ? 0 : rasterOpacity, + 'raster-opacity-transition': { + duration: 320 + } + }, + minzoom: minZoom, + metadata: { + id, + layerOrderPosition: 'raster', + xyzTileUrl: tileServerUrl, + } + }; + + const sources = { + [id]: layerSource + }; + const layers = [layer]; + + updateStyle({ + generatorId, + sources, + layers + }); + }, + // sourceParams not included, but using a stringified version of it to detect changes (haveSourceParamsChanged) + [ + updateStyle, + id, + tileParams, + minZoom, + haveSourceParamsChanged, + hidden, + generatorId, + tileApiEndpoint, + opacity + + ] + ); + + // + // Cleanup layers on unmount. + // + useEffect(() => { + return () => { + updateStyle({ + generatorId, + sources: {}, + layers: [] + }); + }; + }, [updateStyle, generatorId]); + + return null; +} \ No newline at end of file diff --git a/app/scripts/components/common/map/style-generators/raster-timeseries.tsx b/app/scripts/components/common/map/style-generators/raster-timeseries.tsx index 8daf4cb21..537d716cc 100644 --- a/app/scripts/components/common/map/style-generators/raster-timeseries.tsx +++ b/app/scripts/components/common/map/style-generators/raster-timeseries.tsx @@ -36,6 +36,11 @@ import { // Whether or not to print the request logs. const LOG = true; +interface AssetUrlReplacement { + from: string; + to: string; +} + export interface RasterTimeseriesProps extends BaseGeneratorParams { id: string; stacCol: string; @@ -47,6 +52,10 @@ export interface RasterTimeseriesProps extends BaseGeneratorParams { isPositionSet?: boolean; stacApiEndpoint?: string; tileApiEndpoint?: string; + // For replacing HTTPS asset URLs with S3 in the CMR STAC response. + // eslint-disable-next-line react/no-unused-prop-types + assetUrlReplacements?: AssetUrlReplacement; + hidden?: boolean; } enum STATUS_KEY { diff --git a/app/scripts/components/common/map/style-generators/titiler-cmr-timeseries.tsx b/app/scripts/components/common/map/style-generators/titiler-cmr-timeseries.tsx new file mode 100644 index 000000000..984e19faf --- /dev/null +++ b/app/scripts/components/common/map/style-generators/titiler-cmr-timeseries.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { RasterPaintLayer } from './raster-paint-layer'; +import { RasterTimeseriesProps } from './raster-timeseries'; +import { useTitilerCMR } from '$components/common/map/style-generators/hooks'; + +export function TitilerCMRTimeseries(props: RasterTimeseriesProps) { + const { + id, + stacCol, + stacApiEndpoint, + date, + onStatusChange, + sourceParams, + hidden, + } = props; + + const stacApiEndpointToUse = stacApiEndpoint?? process.env.API_STAC_ENDPOINT; + const tileParams = useTitilerCMR({ id, stacCol, stacApiEndpointToUse, date, stacApiEndpoint, onStatusChange, sourceParams }); + return ; +} \ No newline at end of file diff --git a/app/scripts/components/common/map/style-generators/zarr-timeseries.tsx b/app/scripts/components/common/map/style-generators/zarr-timeseries.tsx index 38243442d..9db1700d2 100644 --- a/app/scripts/components/common/map/style-generators/zarr-timeseries.tsx +++ b/app/scripts/components/common/map/style-generators/zarr-timeseries.tsx @@ -1,150 +1,21 @@ -import React, { useEffect, useMemo } from 'react'; -import qs from 'qs'; -import { RasterSource, RasterLayer } from 'mapbox-gl'; - -import useMapStyle from '../hooks/use-map-style'; -import useGeneratorParams from '../hooks/use-generator-params'; -import { BaseGeneratorParams } from '../types'; +import React from 'react'; import { useZarr } from './hooks'; -import { ActionStatus } from '$utils/status'; - -export interface ZarrTimeseriesProps extends BaseGeneratorParams { - id: string; - stacCol: string; - date?: Date; - sourceParams?: Record; - stacApiEndpoint?: string; - tileApiEndpoint?: string; - zoomExtent?: number[]; - onStatusChange?: (result: { status: ActionStatus; id: string }) => void; -} - -interface ZarrPaintLayerProps extends BaseGeneratorParams { - id: string; - date?: Date; - sourceParams?: Record; - tileApiEndpoint?: string; - zoomExtent?: number[]; - assetUrl: string; -} - -export function ZarrPaintLayer(props: ZarrPaintLayerProps) { - const { - id, - tileApiEndpoint, - date, - sourceParams, - zoomExtent, - assetUrl, - hidden, - opacity - } = props; - - const { updateStyle } = useMapStyle(); - const [minZoom] = zoomExtent ?? [0, 20]; - const generatorId = `zarr-timeseries-${id}`; - - // - // Generate Mapbox GL layers and sources for raster timeseries - // - const haveSourceParamsChanged = useMemo( - () => JSON.stringify(sourceParams), - [sourceParams] - ); +import { RasterTimeseriesProps } from './raster-timeseries'; +import { RasterPaintLayer } from './raster-paint-layer'; - const generatorParams = useGeneratorParams(props); - - useEffect( - () => { - if (!assetUrl) return; - - const tileParams = qs.stringify({ - url: assetUrl, - time_slice: date, - ...sourceParams - }); - - const zarrSource: RasterSource = { - type: 'raster', - url: `${tileApiEndpoint}?${tileParams}` - }; - - const rasterOpacity = typeof opacity === 'number' ? opacity / 100 : 1; - - const zarrLayer: RasterLayer = { - id: id, - type: 'raster', - source: id, - paint: { - 'raster-opacity': hidden ? 0 : rasterOpacity, - 'raster-opacity-transition': { - duration: 320 - } - }, - minzoom: minZoom, - metadata: { - layerOrderPosition: 'raster' - } - }; - - const sources = { - [id]: zarrSource - }; - const layers = [zarrLayer]; - - updateStyle({ - generatorId, - sources, - layers, - params: generatorParams - }); - }, - // sourceParams not included, but using a stringified version of it to - // detect changes (haveSourceParamsChanged) - [ - updateStyle, - id, - date, - assetUrl, - minZoom, - tileApiEndpoint, - haveSourceParamsChanged, - generatorParams - // generatorParams includes hidden and opacity - // hidden, - // opacity, - // generatorId, // - dependent on id - // sourceParams, // tracked by haveSourceParamsChanged - ] - ); - - // - // Cleanup layers on unmount. - // - useEffect(() => { - return () => { - updateStyle({ - generatorId, - sources: {}, - layers: [] - }); - }; - }, [updateStyle, generatorId]); - - return null; -} - -export function ZarrTimeseries(props:ZarrTimeseriesProps) { +export function ZarrTimeseries(props: RasterTimeseriesProps) { const { id, stacCol, stacApiEndpoint, date, onStatusChange, + sourceParams, + hidden } = props; const stacApiEndpointToUse = stacApiEndpoint?? process.env.API_STAC_ENDPOINT; - const assetUrl = useZarr({id, stacCol, stacApiEndpointToUse, date, onStatusChange}); - return ; + const tileParams = useZarr({id, stacCol, stacApiEndpointToUse, date, onStatusChange, sourceParams}); + return ; } \ No newline at end of file diff --git a/app/scripts/components/common/mapbox/index.tsx b/app/scripts/components/common/mapbox/index.tsx index 8831db786..9dd30a390 100644 --- a/app/scripts/components/common/mapbox/index.tsx +++ b/app/scripts/components/common/mapbox/index.tsx @@ -215,7 +215,7 @@ function MapboxMapComponent( // Include access to raw data. const bag = { ...resolverBag, raw: baseLayer.data }; const data = resolveConfigFunctions(baseLayer.data, bag); - + return [data, getLayerComponent(!!data.timeseries, data.type)]; }, [baseLayer, resolverBag]); diff --git a/app/scripts/components/common/mapbox/layers/cmr-timeseries.tsx b/app/scripts/components/common/mapbox/layers/cmr-timeseries.tsx deleted file mode 100644 index 9c191f8bc..000000000 --- a/app/scripts/components/common/mapbox/layers/cmr-timeseries.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { Map as MapboxMap } from 'mapbox-gl'; - -import { ZarrPaintLayer } from './zarr-timeseries'; -import { ActionStatus } from '$utils/status'; - -import { useCMR } from '$components/common/map/style-generators/hooks'; - -interface AssetUrlReplacement { - from: string; - to: string; -} - -export interface MapLayerCMRTimeseriesProps { - id: string; - stacCol: string; - date?: Date; - mapInstance: MapboxMap; - sourceParams?: Record; - stacApiEndpoint?: string; - tileApiEndpoint?: string; - assetUrlReplacements?: AssetUrlReplacement; - zoomExtent?: number[]; - onStatusChange?: (result: { status: ActionStatus; id: string }) => void; - isHidden?: boolean; - idSuffix?: string; -} - -export function MapLayerCMRTimeseries(props:MapLayerCMRTimeseriesProps) { - const { - id, - stacCol, - stacApiEndpoint, - date, - assetUrlReplacements, - onStatusChange, - } = props; - - const stacApiEndpointToUse = stacApiEndpoint?? process.env.API_STAC_ENDPOINT; - const assetUrl = useCMR({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange }); - return ; -} \ No newline at end of file diff --git a/app/scripts/components/common/mapbox/layers/utils.ts b/app/scripts/components/common/mapbox/layers/utils.ts index b8a310dc8..78068cd02 100644 --- a/app/scripts/components/common/mapbox/layers/utils.ts +++ b/app/scripts/components/common/mapbox/layers/utils.ts @@ -30,18 +30,20 @@ import { MapLayerVectorTimeseriesProps } from './vector-timeseries'; import { - MapLayerZarrTimeseries, - MapLayerZarrTimeseriesProps -} from './zarr-timeseries'; + ZarrTimeseries, +} from '$components/common/map/style-generators/zarr-timeseries'; import { - MapLayerCMRTimeseries, - MapLayerCMRTimeseriesProps -} from './cmr-timeseries'; + CMRTimeseries, +} from '$components/common/map/style-generators/cmr-timeseries'; +import { + TitilerCMRTimeseries, +} from '$components/common/map/style-generators/titiler-cmr-timeseries'; import { userTzDate2utcString, utcString2userTzDate } from '$utils/date'; import { AsyncDatasetLayer } from '$context/layer-data'; import { S_FAILED, S_IDLE, S_LOADING, S_SUCCEEDED } from '$utils/status'; import { HintedError } from '$utils/hinted-error'; +import { RasterTimeseriesProps } from '$components/common/map/style-generators/raster-timeseries'; export const getLayerComponent = ( isTimeseries: boolean, @@ -49,14 +51,16 @@ export const getLayerComponent = ( ): FunctionComponent< | MapLayerRasterTimeseriesProps | MapLayerVectorTimeseriesProps - | MapLayerZarrTimeseriesProps - | MapLayerCMRTimeseriesProps + | RasterTimeseriesProps + | RasterTimeseriesProps + | RasterTimeseriesProps > | null => { if (isTimeseries) { if (layerType === 'raster') return MapLayerRasterTimeseries; if (layerType === 'vector') return MapLayerVectorTimeseries; - if (layerType === 'zarr') return MapLayerZarrTimeseries; - if (layerType === 'cmr') return MapLayerCMRTimeseries; + if (layerType === 'zarr') return ZarrTimeseries; + if (layerType === 'cmr-stac') return CMRTimeseries; + if (layerType === 'titiler-cmr') return TitilerCMRTimeseries; } return null; diff --git a/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx b/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx deleted file mode 100644 index 59786cf8a..000000000 --- a/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useEffect, useMemo } from 'react'; -import qs from 'qs'; -import { RasterSource, RasterLayer } from 'mapbox-gl'; - -import { useMapStyle } from './styles'; - -import { ActionStatus } from '$utils/status'; -import { useZarr } from '$components/common/map/style-generators/hooks'; - -export interface MapLayerZarrTimeseriesProps { - id: string; - stacCol: string; - date?: Date; - sourceParams?: Record; - stacApiEndpoint?: string; - tileApiEndpoint?: string; - zoomExtent?: number[]; - onStatusChange?: (result: { status: ActionStatus; id: string }) => void; - isHidden?: boolean; - idSuffix?: string; -} - -interface ZarrPaintLayerProps { - id: string; - date?: Date; - sourceParams?: Record; - tileApiEndpoint?: string; - zoomExtent?: number[]; - isHidden?: boolean; - idSuffix?: string; - assetUrl: string; -} - -export function ZarrPaintLayer(props: ZarrPaintLayerProps) { - const { - id, - tileApiEndpoint, - date, - sourceParams, - zoomExtent, - isHidden, - assetUrl, - idSuffix = '' - } = props; - - const { updateStyle } = useMapStyle(); - - const [minZoom] = zoomExtent ?? [0, 20]; - - const generatorId = 'zarr-timeseries' + idSuffix; - - // Generate Mapbox GL layers and sources for raster timeseries - // - const haveSourceParamsChanged = useMemo( - () => JSON.stringify(sourceParams), - [sourceParams] - ); - - useEffect( - () => { - if (!assetUrl) return; - - const tileParams = qs.stringify({ - url: assetUrl, - time_slice: date, - ...sourceParams - }); - - const zarrSource: RasterSource = { - type: 'raster', - url: `${tileApiEndpoint}?${tileParams}` - }; - - const zarrLayer: RasterLayer = { - id: id, - type: 'raster', - source: id, - layout: { - visibility: isHidden ? 'none' : 'visible' - }, - paint: { - 'raster-opacity': Number(!isHidden), - 'raster-opacity-transition': { - duration: 320 - } - }, - minzoom: minZoom, - metadata: { - layerOrderPosition: 'raster' - } - }; - - const sources = { - [id]: zarrSource - }; - const layers = [zarrLayer]; - - updateStyle({ - generatorId, - sources, - layers - }); - }, - // sourceParams not included, but using a stringified version of it to detect changes (haveSourceParamsChanged) - [ - updateStyle, - id, - date, - assetUrl, - minZoom, - haveSourceParamsChanged, - isHidden, - generatorId, - tileApiEndpoint - ] - ); - - // - // Cleanup layers on unmount. - // - useEffect(() => { - return () => { - updateStyle({ - generatorId, - sources: {}, - layers: [] - }); - }; - }, [updateStyle, generatorId]); - - return null; -} - -export function MapLayerZarrTimeseries(props:MapLayerZarrTimeseriesProps) { - const { - id, - stacCol, - stacApiEndpoint, - date, - onStatusChange, - } = props; - - const stacApiEndpointToUse = stacApiEndpoint?? process.env.API_STAC_ENDPOINT; - const assetUrl = useZarr({id, stacCol, stacApiEndpointToUse, date, onStatusChange}); - return ; -} \ No newline at end of file diff --git a/app/scripts/components/exploration/components/map/layer.tsx b/app/scripts/components/exploration/components/map/layer.tsx index 0c3c1e47f..0d7a1fdd3 100644 --- a/app/scripts/components/exploration/components/map/layer.tsx +++ b/app/scripts/components/exploration/components/map/layer.tsx @@ -10,6 +10,7 @@ import { RasterTimeseries } from '$components/common/map/style-generators/raster import { VectorTimeseries } from '$components/common/map/style-generators/vector-timeseries'; import { ZarrTimeseries } from '$components/common/map/style-generators/zarr-timeseries'; import { CMRTimeseries } from '$components/common/map/style-generators/cmr-timeseries'; +import { TitilerCMRTimeseries } from '$components/common/map/style-generators/titiler-cmr-timeseries'; import { ActionStatus } from '$utils/status'; interface LayerProps { @@ -74,7 +75,7 @@ export function Layer(props: LayerProps) { onStatusChange={onStatusChange} /> ); - case 'cmr': + case 'cmr-stac': return ( ); + case 'titiler-cmr': + return ( +