Skip to content

Commit

Permalink
Merged exploration
Browse files Browse the repository at this point in the history
  • Loading branch information
nerik committed Oct 20, 2023
2 parents f2816c2 + ff15c00 commit 01a143f
Show file tree
Hide file tree
Showing 25 changed files with 1,167 additions and 208 deletions.
28 changes: 14 additions & 14 deletions app/scripts/components/common/browse-controls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ function BrowseControls(props: BrowseControlsProps) {

return (
<BrowseControlsWrapper {...rest}>
<TaxonomyWrapper>
{taxonomiesOptions.map(({ name, values }) => (
<DropdownOptions
key={name}
prefix={name}
items={[optionAll].concat(values)}
currentId={taxonomies?.[name] ?? 'all'}
onChange={(v) => {
onAction(Actions.TAXONOMY, { key: name, value: v });
}}
size={isLargeUp ? 'large' : 'medium'}
/>
))}
</TaxonomyWrapper>
<SearchWrapper>
<SearchField
size={isLargeUp ? 'large' : 'medium'}
Expand Down Expand Up @@ -142,20 +156,6 @@ function BrowseControls(props: BrowseControlsProps) {
</DropMenu>
</DropdownScrollable>
</SearchWrapper>
<TaxonomyWrapper>
{taxonomiesOptions.map(({ name, values }) => (
<DropdownOptions
key={name}
prefix={name}
items={[optionAll].concat(values)}
currentId={taxonomies?.[name] ?? 'all'}
onChange={(v) => {
onAction(Actions.TAXONOMY, { key: name, value: v });
}}
size={isLargeUp ? 'large' : 'medium'}
/>
))}
</TaxonomyWrapper>
</BrowseControlsWrapper>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import useQsStateCreator from 'qs-state-hook';
import { set, omit } from 'lodash';

export enum Actions {
CLEAR = 'clear',
SEARCH = 'search',
SORT_FIELD = 'sfield',
SORT_DIR = 'sdir',
TAXONOMY = 'taxonomy'
}

export type BrowserControlsAction = (what: Actions, value: any) => void;
export type BrowserControlsAction = (what: Actions, value?: any) => void;

export interface FilterOption {
id: string;
Expand Down Expand Up @@ -84,6 +85,10 @@ export function useBrowserControls({ sortOptions }: BrowseControlsHookParams) {
const onAction = useCallback<BrowserControlsAction>(
(what, value) => {
switch (what) {
case Actions.CLEAR:
setSearch('');
setTaxonomies({});
break;
case Actions.SEARCH:
setSearch(value);
break;
Expand Down
7 changes: 5 additions & 2 deletions app/scripts/components/common/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ interface CardComponentProps {
parentTo?: string;
footerContent?: ReactNode;
onCardClickCapture?: MouseEventHandler;
onLinkClick?: MouseEventHandler;
}

function CardComponent(props: CardComponentProps) {
Expand All @@ -316,7 +317,8 @@ function CardComponent(props: CardComponentProps) {
parentName,
parentTo,
footerContent,
onCardClickCapture
onCardClickCapture,
onLinkClick
} = props;

return (
Expand All @@ -327,7 +329,8 @@ function CardComponent(props: CardComponentProps) {
linkLabel={linkLabel || 'View more'}
linkProps={{
as: Link,
to: linkTo
to: linkTo,
onClick: onLinkClick
}}
onClickCapture={onCardClickCapture}
>
Expand Down
26 changes: 14 additions & 12 deletions app/scripts/components/common/empty-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import { themeVal } from '@devseed-ui/theme-provider';

import { variableGlsp } from '$styles/variable-utils';

const EmptyHubWrapper = styled.div`
function EmptyHub(props: { children: ReactNode }) {
const theme = useTheme();

const { children, ...rest } = props;

return (
<div {...rest}>
<CollecticonPage size='xxlarge' color={theme.color!['base-400']} />
{children}
</div>
);
}

export default styled(EmptyHub)`
max-width: 100%;
grid-column: 1/-1;
display: flex;
Expand All @@ -16,14 +29,3 @@ const EmptyHubWrapper = styled.div`
border: 1px dashed ${themeVal('color.base-300')};
gap: ${variableGlsp(1)};
`;

export default function EmptyHub(props: { children: ReactNode }) {
const theme = useTheme();

return (
<EmptyHubWrapper>
<CollecticonPage size='xxlarge' color={theme.color!['base-400']} />
{props.children}
</EmptyHubWrapper>
);
}
12 changes: 6 additions & 6 deletions app/scripts/components/common/map/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,12 @@ export function resolveConfigFunctions(
return datum(bag);
} catch (error) {
/* eslint-disable-next-line no-console */
console.error(
'Failed to resolve function %s(%o) with error %s',
datum.name,
bag,
error.message
);
// console.error(
// 'Failed to resolve function %s(%o) with error %s',
// datum.name,
// bag,
// error.message
// );
return null;
}
}
Expand Down
9 changes: 4 additions & 5 deletions app/scripts/components/data-catalog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo, useRef } from 'react';
import styled from 'styled-components';
import { DatasetData, datasets, datasetTaxonomies, getString } from 'veda';
import { DatasetData, datasetTaxonomies, getString } from 'veda';
import { Link } from 'react-router-dom';
import { glsp } from '@devseed-ui/theme-provider';
import { Subtitle } from '@devseed-ui/typography';
Expand Down Expand Up @@ -47,8 +47,7 @@ import {
TAXONOMY_TOPICS
} from '$utils/veda-data';
import { DatasetClassification } from '$components/common/dataset-classification';

const allDatasets = Object.values(datasets).map((d) => d!.data);
import { allDatasets } from '$components/exploration/data-utils';

const DatasetCount = styled(Subtitle)`
grid-column: 1 / -1;
Expand All @@ -66,9 +65,9 @@ const BrowseFoldHeader = styled(FoldHeader)`
align-items: flex-start;
`;

const sortOptions = [{ id: 'name', name: 'Name' }];
export const sortOptions = [{ id: 'name', name: 'Name' }];

const prepareDatasets = (
export const prepareDatasets = (
data: DatasetData[],
options: {
search: string;
Expand Down
201 changes: 201 additions & 0 deletions app/scripts/components/exploration/analysis-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import axios, { AxiosRequestConfig } from 'axios';
import { QueryClient } from '@tanstack/react-query';
import { FeatureCollection, Polygon } from 'geojson';
import { ConcurrencyManagerInstance } from './concurrency';
import {
TimelineDataset,
TimelineDatasetAnalysis,
TimelineDatasetStatus
} from './types.d.ts';
import {
combineFeatureCollection,
getFilterPayload
} from '$components/analysis/utils';

interface DatasetAssetsRequestParams {
stacCol: string;
assets: string;
dateStart: Date;
dateEnd: Date;
aoi: FeatureCollection<Polygon>;
}

/**
* Gets the asset urls for all datasets in the results of a STAC search given by
* the input parameters.
*
* @param params Dataset search request parameters
* @param opts Options for the request (see Axios)
* @returns Promise with the asset urls
*/
async function getDatasetAssets(
{ dateStart, dateEnd, stacCol, assets, aoi }: DatasetAssetsRequestParams,
opts: AxiosRequestConfig
): Promise<{ assets: { date: Date; url: string }[] }> {
const searchReqRes = await axios.post(
`${process.env.API_STAC_ENDPOINT}/search`,
{
'filter-lang': 'cql2-json',
limit: 10000,
fields: {
include: [
`assets.${assets}.href`,
'properties.start_datetime',
'properties.datetime'
],
exclude: ['collection', 'links']
},
filter: getFilterPayload(dateStart, dateEnd, aoi, [stacCol])
},
opts
);

return {
assets: searchReqRes.data.features.map((o) => ({
date: new Date(o.properties.start_datetime || o.properties.datetime),
url: o.assets[assets].href
}))
};
}

interface TimeseriesRequesterParams {
start: Date;
end: Date;
aoi: FeatureCollection<Polygon>;
dataset: TimelineDataset;
queryClient: QueryClient;
concurrencyManager: ConcurrencyManagerInstance;
onProgress: (data: TimelineDatasetAnalysis) => void;
}

/**
* Gets the statistics for the given dataset within the given time range and
* area of interest.
*/
export async function requestDatasetTimeseriesData({
start,
end,
aoi,
dataset,
queryClient,
concurrencyManager,
onProgress
}: TimeseriesRequesterParams) {
const datasetData = dataset.data;
const datasetAnalysis = dataset.analysis;

const id = datasetData.id;

onProgress({
status: TimelineDatasetStatus.LOADING,
error: null,
data: null,
meta: {}
});

try {
const layerInfoFromSTAC = await concurrencyManager.queue(
`${id}-analysis`,
() => {
return queryClient.fetchQuery(
['analysis', 'dataset', id, aoi, start, end],
({ signal }) =>
getDatasetAssets(
{
stacCol: datasetData.stacCol,
assets: datasetData.sourceParams?.assets || 'cog_default',
aoi,
dateStart: start,
dateEnd: end
},
{ signal }
),
{
staleTime: Infinity
}
);
}
);

const { assets } = layerInfoFromSTAC;

onProgress({
status: TimelineDatasetStatus.LOADING,
error: null,
data: null,
meta: {
total: assets.length,
loaded: 0
}
});

let loaded = 0;

const layerStatistics = await Promise.all(
assets.map(async ({ date, url }) => {
const statistics = await concurrencyManager.queue(
`${id}-analysis-asset`,
() => {
return queryClient.fetchQuery(
['analysis', id, 'asset', url, aoi],
async ({ signal }) => {
const { data } = await axios.post(
`${process.env.API_RASTER_ENDPOINT}/cog/statistics?url=${url}`,
// Making a request with a FC causes a 500 (as of 2023/01/20)
combineFeatureCollection(aoi),
{ signal }
);
return {
date,
...data.properties.statistics.b1
};
},
{
staleTime: Infinity
}
);
}
);

onProgress({
status: TimelineDatasetStatus.LOADING,
error: null,
data: null,
meta: {
total: assets.length,
loaded: ++loaded
}
});

return statistics;
})
);

onProgress({
status: TimelineDatasetStatus.SUCCESS,
meta: {
total: assets.length,
loaded: assets.length
},
error: null,
data: {
timeseries: layerStatistics
}
});
} catch (error) {
// Discard abort related errors.
if (error.revert) return;

// Cancel any inflight queries.
queryClient.cancelQueries({ queryKey: ['analysis', id] });
// Remove other requests from the queue.
concurrencyManager.dequeue(`${id}-analysis-asset`);

onProgress({
...datasetAnalysis,
status: TimelineDatasetStatus.ERROR,
error,
data: null
});
}
}
Loading

0 comments on commit 01a143f

Please sign in to comment.