Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Titiler-cmr layer #1001

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,38 +1,22 @@
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<string, any>;
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,
stacApiEndpoint,
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 <ZarrPaintLayer {...props} assetUrl={assetUrl} />;
const tileParams = useCMRSTAC({ id, stacCol, stacApiEndpointToUse, date, assetUrlReplacements, stacApiEndpoint, onStatusChange, sourceParams });
return <RasterPaintLayer {...props} tileParams={tileParams} />;
}
95 changes: 82 additions & 13 deletions app/scripts/components/common/map/style-generators/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface ZarrResponseData {
zarr: {
href: string
}
}
},
}
interface CMRResponseData {
features: {
Expand All @@ -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<string, any>;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder 🤔 if the backend team has json schemas we could generate into typescript interfaces to import, interested.. I'll follow up with them on this


export function useZarr({ id, stacCol, stacApiEndpointToUse, date, onStatusChange, sourceParams }){
const [tileParams, setTileParams] = useState({});

useEffect(() => {
const controller = new AbortController();
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 : This matches the naming of local state variable, can we change the name so it isn't confusing.

};

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;
}
Original file line number Diff line number Diff line change
@@ -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<string, any>;
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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <RasterPaintLayer {...props} tileParams={tileParams} />;
}
Loading