From e0410aa0a7c536b4ec5385dd3f7646f395621cb2 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Mon, 10 Jul 2023 18:40:33 +0100 Subject: [PATCH] Simplify zarr layer handling --- .../common/blocks/scrollytelling/index.tsx | 2 +- .../components/common/mapbox/index.tsx | 61 ++++---- .../mapbox/layers/raster-timeseries.tsx | 6 +- .../components/common/mapbox/layers/utils.ts | 1 - .../mapbox/layers/vector-timeseries.tsx | 6 +- .../common/mapbox/layers/zarr-timeseries.tsx | 147 ++++++++++++------ app/scripts/context/layer-data.tsx | 18 +-- parcel-resolver-veda/index.d.ts | 2 - 8 files changed, 144 insertions(+), 99 deletions(-) diff --git a/app/scripts/components/common/blocks/scrollytelling/index.tsx b/app/scripts/components/common/blocks/scrollytelling/index.tsx index 695a229ae..e56873285 100644 --- a/app/scripts/components/common/blocks/scrollytelling/index.tsx +++ b/app/scripts/components/common/blocks/scrollytelling/index.tsx @@ -434,7 +434,7 @@ function Scrollytelling(props) { {isMapLoaded && resolvedLayers.map((resolvedLayer, lIdx) => { - if (!resolvedLayer) return null; + if (!resolvedLayer || !mapRef.current) return null; const { runtimeData, Component: LayerCmp, layer } = resolvedLayer; const isHidden = diff --git a/app/scripts/components/common/mapbox/index.tsx b/app/scripts/components/common/mapbox/index.tsx index 22a2ebf24..eaf22a64d 100644 --- a/app/scripts/components/common/mapbox/index.tsx +++ b/app/scripts/components/common/mapbox/index.tsx @@ -1,4 +1,5 @@ import React, { + MutableRefObject, ReactNode, forwardRef, useCallback, @@ -105,7 +106,10 @@ const getMapPositionOptions = (position) => { return opts; }; -function MapboxMapComponent(props: MapboxMapProps, ref) { +function MapboxMapComponent( + props: MapboxMapProps, + ref: MutableRefObject +) { /* eslint-disable react/prop-types */ const { className, @@ -371,21 +375,19 @@ function MapboxMapComponent(props: MapboxMapProps, ref) { title={baseLayerResolvedData.name} description={baseLayerResolvedData.description} {...baseLayerResolvedData.legend} - /> + /> {compareLayerResolvedData?.legend && - isComparing && - (baseLayerResolvedData.id !== compareLayerResolvedData.id) && - } + isComparing && + baseLayerResolvedData.id !== compareLayerResolvedData.id && ( + + )} - )} - - - + )} {/* Maps container @@ -409,18 +411,20 @@ function MapboxMapComponent(props: MapboxMapProps, ref) { labelsOption={labelsOption} boundariesOption={boundariesOption} /> - {isMapLoaded && baseLayerResolvedData && BaseLayerComponent && ( - - )} + {mapRef.current && + isMapLoaded && + baseLayerResolvedData && + BaseLayerComponent && ( + + )} - {isMapCompareLoaded && + {mapCompareRef.current && + isMapCompareLoaded && compareLayerResolvedData && CompareLayerComponent && ( ; + zoomExtent?: number[]; onStatusChange?: (result: { status: ActionStatus; id: string }) => void; - isHidden: boolean; + isHidden?: boolean; idSuffix?: string; } diff --git a/app/scripts/components/common/mapbox/layers/utils.ts b/app/scripts/components/common/mapbox/layers/utils.ts index ba269df44..a8d298978 100644 --- a/app/scripts/components/common/mapbox/layers/utils.ts +++ b/app/scripts/components/common/mapbox/layers/utils.ts @@ -85,7 +85,6 @@ export const getCompareLayerData = ( id: stacCol, stacCol, type: type || layerData.type, - assetUrl: layerData.assetUrl, zoomExtent: zoomExtent ?? layerData.zoomExtent, sourceParams: defaultsDeep({}, sourceParams, layerData.sourceParams), ...passThroughProps diff --git a/app/scripts/components/common/mapbox/layers/vector-timeseries.tsx b/app/scripts/components/common/mapbox/layers/vector-timeseries.tsx index 719980353..e790322d8 100644 --- a/app/scripts/components/common/mapbox/layers/vector-timeseries.tsx +++ b/app/scripts/components/common/mapbox/layers/vector-timeseries.tsx @@ -24,10 +24,10 @@ export interface MapLayerVectorTimeseriesProps { stacCol: string; date?: Date; mapInstance: MapboxMap; - sourceParams: object; - zoomExtent?: [number, number]; + sourceParams?: Record; + zoomExtent?: number[]; onStatusChange?: (result: { status: ActionStatus; id: string }) => void; - isHidden: boolean; + isHidden?: boolean; idSuffix?: string; } diff --git a/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx b/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx index 57fe023b0..74c60799c 100644 --- a/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx +++ b/app/scripts/components/common/mapbox/layers/zarr-timeseries.tsx @@ -1,26 +1,78 @@ -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import qs from 'qs'; -import { AnyLayer, AnySourceImpl, RasterLayer, RasterSource } from 'mapbox-gl'; +import { Map as MapboxMap, RasterSource, RasterLayer } from 'mapbox-gl'; +import { requestQuickCache } from './utils'; import { useMapStyle } from './styles'; +import { ActionStatus, S_FAILED, S_LOADING, S_SUCCEEDED } from '$utils/status'; + +const tilerUrl = process.env.API_XARRAY_ENDPOINT; + export interface MapLayerZarrTimeseriesProps { id: string; + stacCol: string; date?: Date; - sourceParams?: object; + mapInstance: MapboxMap; + sourceParams?: Record; zoomExtent?: number[]; - assetUrl: string; + onStatusChange?: (result: { status: ActionStatus; id: string }) => void; isHidden?: boolean; + idSuffix?: string; } export function MapLayerZarrTimeseries(props: MapLayerZarrTimeseriesProps) { - const { id, date, isHidden } = props; - const { sourceParams = {}, zoomExtent, assetUrl } = props; + const { + id, + stacCol, + date, + mapInstance, + sourceParams, + zoomExtent, + onStatusChange, + isHidden, + idSuffix = '' + } = props; const { updateStyle } = useMapStyle(); + const [assetUrl, setAssetUrl] = useState(''); + + const [minZoom] = zoomExtent ?? [0, 20]; + + const generatorId = 'zarr-timeseries' + idSuffix; + + // + // Get the asset url + // + useEffect(() => { + const controller = new AbortController(); + + async function load() { + try { + onStatusChange?.({ status: S_LOADING, id }); + const data = await requestQuickCache({ + url: `${process.env.API_STAC_ENDPOINT}/collections/${stacCol}`, + method: 'GET', + controller + }); - const minZoom = zoomExtent?.[0] ?? 0; - const tilerUrl = process.env.API_XARRAY_ENDPOINT; + setAssetUrl(data.assets.zarr.href); + onStatusChange?.({ status: S_SUCCEEDED, id }); + } catch (error) { + if (!controller.signal.aborted) { + setAssetUrl(''); + onStatusChange?.({ status: S_FAILED, id }); + } + return; + } + } + + load(); + + return () => { + controller.abort(); + }; + }, [mapInstance, id, stacCol, date, onStatusChange]); // // Generate Mapbox GL layers and sources for raster timeseries @@ -32,48 +84,45 @@ export function MapLayerZarrTimeseries(props: MapLayerZarrTimeseriesProps) { useEffect( () => { - let layers: AnyLayer[] = []; - let sources: Record = {}; - - if (tilerUrl) { - const tileParams = qs.stringify({ - url: assetUrl, - time_slice: date, - ...sourceParams - }); + if (!tilerUrl) return; + + const tileParams = qs.stringify({ + url: assetUrl, + time_slice: date, + ...sourceParams + }); - const zarrSource: RasterSource = { - type: 'raster', - url: `${tilerUrl}?${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 zarrSource: RasterSource = { + type: 'raster', + url: `${tilerUrl}?${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' + } + }; - sources = { - [id]: zarrSource - }; - layers = [zarrLayer]; - } + const sources = { + [id]: zarrSource + }; + const layers = [zarrLayer]; updateStyle({ - generatorId: 'raster-timeseries', + generatorId, sources, layers }); @@ -84,10 +133,10 @@ export function MapLayerZarrTimeseries(props: MapLayerZarrTimeseriesProps) { id, date, assetUrl, - tilerUrl, minZoom, haveSourceParamsChanged, - isHidden + isHidden, + generatorId ] ); @@ -97,12 +146,12 @@ export function MapLayerZarrTimeseries(props: MapLayerZarrTimeseriesProps) { useEffect(() => { return () => { updateStyle({ - generatorId: 'raster-timeseries', + generatorId, sources: {}, layers: [] }); }; - }, [updateStyle]); + }, [updateStyle, generatorId]); return null; } diff --git a/app/scripts/context/layer-data.tsx b/app/scripts/context/layer-data.tsx index 46e50c2ee..20fa952aa 100644 --- a/app/scripts/context/layer-data.tsx +++ b/app/scripts/context/layer-data.tsx @@ -17,7 +17,6 @@ import { S_SUCCEEDED } from '$utils/status'; export type TimeDensity = 'day' | 'month' | 'year' | null; interface STACLayerData { - assetUrl?: string; timeseries: { isPeriodic: boolean; timeDensity: TimeDensity; @@ -52,21 +51,16 @@ const fetchLayerById = async ( } }; } else { - const defaultData = { + const domain = data.summaries + ? data.summaries.datetime + : data.extent.temporal.interval[0]; + + return { timeseries: { ...commonTimeseriesParams, - domain: data.summaries ? data.summaries.datetime : data.extent.temporal.interval[0] + domain } }; - - if (type === 'zarr') { - return { - ...defaultData, - assetUrl: data.assets.zarr.href - }; - } else { - return defaultData; - } } }; diff --git a/parcel-resolver-veda/index.d.ts b/parcel-resolver-veda/index.d.ts index 989de20ed..82be3ac03 100644 --- a/parcel-resolver-veda/index.d.ts +++ b/parcel-resolver-veda/index.d.ts @@ -57,7 +57,6 @@ declare module 'veda' { description: string; initialDatetime?: 'newest' | 'oldest' | string; projection?: ProjectionOptions; - assetUrl?: string; type: DatasetLayerType; compare: DatasetLayerCompareSTAC | DatasetLayerCompareInternal | null; legend?: LayerLegendCategorical | LayerLegendGradient; @@ -79,7 +78,6 @@ declare module 'veda' { name: string; description: string; stacCol: string; - assetUrl?: string; type: DatasetLayerType; legend?: LayerLegendCategorical | LayerLegendGradient; }