diff --git a/app/scripts/components/common/map/style-generators/hooks.ts b/app/scripts/components/common/map/style-generators/hooks.ts index d204dc7c0..1985c394b 100644 --- a/app/scripts/components/common/map/style-generators/hooks.ts +++ b/app/scripts/components/common/map/style-generators/hooks.ts @@ -157,5 +157,41 @@ export function useArc({ id, stacCol, stacApiEndpointToUse, onStatusChange }){ }; }, [id, stacCol, stacApiEndpointToUse, onStatusChange]); + return wmsUrl; +} + +export function useArcImageServer({ id, stacCol, stacApiEndpointToUse, date, onStatusChange }){ + const [wmsUrl, setWmsUrl] = useState(''); + + useEffect(() => { + const controller = new AbortController(); + + async function load() { + try { + onStatusChange?.({ status: S_LOADING, id }); + const data:ArcResponseData = await requestQuickCache({ + url: `${stacApiEndpointToUse}/collections/${stacCol}`, + method: 'GET', + controller + }); + + setWmsUrl(data.links[0].href); + onStatusChange?.({ status: S_SUCCEEDED, id }); + } catch (error) { + if (!controller.signal.aborted) { + setWmsUrl(''); + onStatusChange?.({ status: S_FAILED, id }); + } + return; + } + } + + load(); + + return () => { + controller.abort(); + }; + }, [id, stacCol, stacApiEndpointToUse, date, onStatusChange]); + return wmsUrl; } \ No newline at end of file diff --git a/app/scripts/components/common/mapbox/layers/arc-imageserver.tsx b/app/scripts/components/common/mapbox/layers/arc-imageserver.tsx new file mode 100644 index 000000000..c1d8f74ff --- /dev/null +++ b/app/scripts/components/common/mapbox/layers/arc-imageserver.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useMemo } from 'react'; +import qs from 'qs'; +import { RasterSource, RasterLayer } from 'mapbox-gl'; + +import { useMapStyle } from './styles'; +import { useArcImageServer } from '$components/common/map/style-generators/hooks'; + +import { userTzDate2utcString } from '$utils/date'; +import { ActionStatus } from '$utils/status'; + +export interface MapLayerArcImageServerProps { + 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 ArcPaintLayerProps { + id: string; + date?: Date; + sourceParams?: Record; + tileApiEndpoint?: string; + zoomExtent?: number[]; + isHidden?: boolean; + idSuffix?: string; + wmsUrl: string; +} + +export function ArcPaintLayer(props: ArcPaintLayerProps) { + const { + id, + tileApiEndpoint, + date, + sourceParams, + zoomExtent, + isHidden, + wmsUrl, + idSuffix = '' + } = props; + + const { updateStyle } = useMapStyle(); + + const [minZoom] = zoomExtent ?? [0, 20]; + + const generatorId = 'arc-timeseries' + idSuffix; + + // Generate Mapbox GL layers and sources for raster timeseries + // + const haveSourceParamsChanged = useMemo( + () => JSON.stringify(sourceParams), + [sourceParams] + ); + + useEffect( + () => { + if (!wmsUrl) return; + //https://arcgis.asdc.larc.nasa.gov/server/services/POWER/power_901_annual_meterology_utc/ImageServer/WMSServer + // ?bbox={bbox-epsg-3857}&format=image/png&service=WMS&version=1.1.1&request=GetMap&srs=EPSG:3857&transparent=true&width=256&height=256&LAYERS=PS&DIM_StdTime=1981-12-31T00:00:00Z" + + // TODO: investigate For some reason the request being made is 6 hours ahead? Something to do with UTC <-> local conversion? + const tileParams = qs.stringify({ + format: 'image/png', + service: "WMS", + version: "1.1.1", + request: "GetMap", + srs: "EPSG:3857", + transparent: "true", // TODO: get from sourceparams maybe + width: "256", + height: "256", + DIM_StdTime: userTzDate2utcString(date), // TODO: better date conversion + ...sourceParams + }); + + const arcSource: RasterSource = { + type: 'raster', + tiles: [`${wmsUrl}?${tileParams}&bbox={bbox-epsg-3857}`] + }; + + const arcLayer: 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]: arcSource + }; + const layers = [arcLayer]; + + updateStyle({ + generatorId, + sources, + layers + }); + }, + // sourceParams not included, but using a stringified version of it to detect changes (haveSourceParamsChanged) + [ + updateStyle, + id, + date, + wmsUrl, + minZoom, + haveSourceParamsChanged, + isHidden, + generatorId, + tileApiEndpoint + ] + ); + + // + // Cleanup layers on unmount. + // + useEffect(() => { + return () => { + updateStyle({ + generatorId, + sources: {}, + layers: [] + }); + }; + }, [updateStyle, generatorId]); + + return null; +} + +export function MapLayerArcImageServer(props:MapLayerArcImageServerProps) { + const { + id, + stacCol, + stacApiEndpoint, + date, + onStatusChange, + } = props; + + const stacApiEndpointToUse = stacApiEndpoint?? process.env.API_STAC_ENDPOINT; + const wmsUrl = useArcImageServer({id, stacCol, stacApiEndpointToUse, date, 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 e9000795d..bda303bbd 100644 --- a/app/scripts/components/common/mapbox/layers/utils.ts +++ b/app/scripts/components/common/mapbox/layers/utils.ts @@ -42,6 +42,11 @@ import { MapLayerArcProps } from './arc'; +import { + MapLayerArcImageServer, + MapLayerArcImageServerProps +} from './arc-imageserver'; + import { userTzDate2utcString, utcString2userTzDate } from '$utils/date'; import { AsyncDatasetLayer } from '$context/layer-data'; import { S_FAILED, S_IDLE, S_LOADING, S_SUCCEEDED } from '$utils/status'; @@ -56,6 +61,7 @@ export const getLayerComponent = ( | MapLayerZarrTimeseriesProps | MapLayerCMRTimeseriesProps | MapLayerArcProps + | MapLayerArcImageServerProps > | null => { if (isTimeseries) { if (layerType === 'raster') return MapLayerRasterTimeseries; @@ -63,6 +69,7 @@ export const getLayerComponent = ( if (layerType === 'zarr') return MapLayerZarrTimeseries; if (layerType === 'cmr') return MapLayerCMRTimeseries; if (layerType === 'arc') return MapLayerArc; + if (layerType === 'arc-imageserver') return MapLayerArcImageServer; } return null; diff --git a/mock/datasets/power_meteorology.data.mdx b/mock/datasets/power_meteorology.data.mdx new file mode 100644 index 000000000..bf4d02ced --- /dev/null +++ b/mock/datasets/power_meteorology.data.mdx @@ -0,0 +1,126 @@ +--- +id: power_901_annual_meterology_utc +name: 'POWER meteorology' +featured: true +description: "Meteorology" +media: + src: ::file ./no2--dataset-cover.jpg + alt: Power plant shooting steam at the sky. + author: + name: Mick Truyts + url: https://unsplash.com/photos/x6WQeNYJC1w +taxonomy: + - name: Topics + values: + - Air Quality +layers: + - id: cdd10 + stacApiEndpoint: https://hh76k5rqdh.execute-api.us-west-2.amazonaws.com/dev + tileApiEndpoint: https://hh76k5rqdh.execute-api.us-west-2.amazonaws.com/dev + stacCol: POWER/power_901_annual_meterology_utc + name: Cooling Degree Days Above 10 C + type: arc-imageserver + description: + "Test" + zoomExtent: + - 0 + - 20 + sourceParams: + layers: CDD10 + compare: + datasetId: power_901_annual_meterology_utc + layerId: power_901_annual_meterology_utc_DISPH + mapLabel: | + ::js ({ dateFns, datetime, compareDatetime }) => { + return `${dateFns.format(datetime, 'LLL yyyy')} VS ${dateFns.format(compareDatetime, 'LLL yyyy')}`; + } + legend: + unit: + label: degree-day + type: gradient + min: 0 + max: 8972.0625 + stops: + - "#4575b4" + - "#91bfdb" + - "#e0f3f8" + - "#ffffbf" + - "#fee090" + - "#fc8d59" + - "#d73027" + # analysis: + # exclude: true + - id: cdd18_3 + stacApiEndpoint: https://hh76k5rqdh.execute-api.us-west-2.amazonaws.com/dev + tileApiEndpoint: https://hh76k5rqdh.execute-api.us-west-2.amazonaws.com/dev + stacCol: POWER/power_901_annual_meterology_utc + name: Cooling Degree Days Above 18.3 C + type: arc + description: + "Lorem Ipsum" + zoomExtent: + - 0 + - 20 + sourceParams: + layers: CDD18_3 + # TODO: get this from `/legend` endpoint? ex - https://arcgis.asdc.larc.nasa.gov/server/rest/services/POWER/power_901_annual_meterology_utc/ImageServer/legend + compare: + datasetId: power_901_annual_meterology_utc + layerId: cdd10 + mapLabel: | + ::js ({ dateFns, datetime, compareDatetime }) => { + return `${dateFns.format(datetime, 'LLL yyyy')} VS ${dateFns.format(compareDatetime, 'LLL yyyy')}`; + } + legend: + unit: + label: degree-day + type: gradient + min: 0 + max: 8972.0625 + stops: + - "#4575b4" + - "#91bfdb" + - "#e0f3f8" + - "#ffffbf" + - "#fee090" + - "#fc8d59" + - "#d73027" + # analysis: + # exclude: true + - id: power_901_annual_meterology_utc_DISPH + stacApiEndpoint: https://hh76k5rqdh.execute-api.us-west-2.amazonaws.com/dev + tileApiEndpoint: https://hh76k5rqdh.execute-api.us-west-2.amazonaws.com/dev + stacCol: POWER/power_901_annual_meterology_utc + name: Zero Plane Displacement Height + type: arc + description: + "Lorem Ipsum" + zoomExtent: + - 0 + - 20 + sourceParams: + layers: DISPH + compare: + datasetId: power_901_annual_meterology_utc + layerId: power_901_annual_meterology_utc_DISPH + mapLabel: | + ::js ({ dateFns, datetime, compareDatetime }) => { + return `${dateFns.format(datetime, 'LLL yyyy')} VS ${dateFns.format(compareDatetime, 'LLL yyyy')}`; + } + legend: + unit: + label: m + type: gradient + min: 0 + max: 8972.0625 + stops: + - "#4575b4" + - "#91bfdb" + - "#e0f3f8" + - "#ffffbf" + - "#fee090" + - "#fc8d59" + - "#d73027" + # analysis: + # exclude: true +---