Skip to content

Commit

Permalink
[PR] User defined dataset bounds (#596)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfdsilva authored Aug 3, 2023
2 parents f8067b7 + f654526 commit 1ced268
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 30 deletions.
3 changes: 3 additions & 0 deletions app/scripts/components/common/mapbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,11 @@ function MapboxMapComponent(
id={`base-${baseLayerResolvedData.id}`}
stacCol={baseLayerResolvedData.stacCol}
mapInstance={mapRef.current}
isPositionSet={!!initialPosition}
date={date}
sourceParams={baseLayerResolvedData.sourceParams}
zoomExtent={baseLayerResolvedData.zoomExtent}
bounds={baseLayerResolvedData.bounds}
onStatusChange={onBaseLayerStatusChange}
/>
)}
Expand Down Expand Up @@ -471,6 +473,7 @@ function MapboxMapComponent(
date={compareToDate ?? undefined}
sourceParams={compareLayerResolvedData.sourceParams}
zoomExtent={compareLayerResolvedData.zoomExtent}
bounds={compareLayerResolvedData.bounds}
onStatusChange={onCompareLayerStatusChange}
/>
)}
Expand Down
24 changes: 12 additions & 12 deletions app/scripts/components/common/mapbox/layers/raster-timeseries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import { featureCollection, point } from '@turf/helpers';

import { useMapStyle } from './styles';
import {
checkFitBoundsFromLayer,
FIT_BOUNDS_PADDING,
getFilterPayload,
getMergedBBox,
requestQuickCache,
useFitBbox,
useLayerInteraction
} from './utils';
import { useCustomMarker } from './custom-marker';
Expand All @@ -34,18 +35,18 @@ import {
// Whether or not to print the request logs.
const LOG = true;

const FIT_BOUNDS_PADDING = 32;

export interface MapLayerRasterTimeseriesProps {
id: string;
stacCol: string;
date?: Date;
mapInstance: MapboxMap;
sourceParams?: Record<string, any>;
zoomExtent?: number[];
bounds?: number[];
onStatusChange?: (result: { status: ActionStatus; id: string }) => void;
isHidden?: boolean;
idSuffix?: string;
isPositionSet?: boolean;
}

export interface StacFeature {
Expand All @@ -72,9 +73,11 @@ export function MapLayerRasterTimeseries(props: MapLayerRasterTimeseriesProps) {
mapInstance,
sourceParams,
zoomExtent,
bounds,
onStatusChange,
isHidden,
idSuffix = ''
idSuffix = '',
isPositionSet
} = props;

const theme = useTheme();
Expand Down Expand Up @@ -466,14 +469,11 @@ export function MapLayerRasterTimeseries(props: MapLayerRasterTimeseriesProps) {
//
// FitBounds when needed
//
useEffect(() => {
if (!stacCollection.length) return;
const layerBounds = getMergedBBox(stacCollection);

if (checkFitBoundsFromLayer(layerBounds, mapInstance)) {
mapInstance.fitBounds(layerBounds, { padding: FIT_BOUNDS_PADDING });
}
}, [mapInstance, stacCol, stacCollection]);
const layerBounds = useMemo(
() => (stacCollection.length ? getMergedBBox(stacCollection) : undefined),
[stacCollection]
);
useFitBbox(mapInstance, !!isPositionSet, bounds, layerBounds);

return null;
}
36 changes: 35 additions & 1 deletion app/scripts/components/common/mapbox/layers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const getCompareLayerData = (
type: otherLayer.type,
name: otherLayer.name,
description: otherLayer.description,
legend: otherLayer.legend,
legend: otherLayer.legend,
stacCol: otherLayer.stacCol,
zoomExtent: zoomExtent ?? otherLayer.zoomExtent,
sourceParams: defaultsDeep({}, sourceParams, otherLayer.sourceParams),
Expand Down Expand Up @@ -370,6 +370,8 @@ export function getMergedBBox(features: StacFeature[]) {
) as [number, number, number, number];
}

export const FIT_BOUNDS_PADDING = 32;

export function checkFitBoundsFromLayer(
layerBounds?: [number, number, number, number],
mapInstance?: MapboxMap
Expand Down Expand Up @@ -429,3 +431,35 @@ export function useLayerInteraction({
};
}, [layerId, mapInstance, onClick]);
}

type OptionalBbox = number[] | undefined | null;

/**
* Centers on the given bounds if the current position is not within the bounds,
* and there's no user defined position (via user initiated map movement). Gives
* preference to the layer defined bounds over the STAC collection bounds.
*
* @param mapInstance Mapbox instance
* @param isUserPositionSet Whether the user has set a position
* @param initialBbox Bounding box from the layer
* @param stacBbox Bounds from the STAC collection
*/
export function useFitBbox(
mapInstance: MapboxMap,
isUserPositionSet: boolean,
initialBbox: OptionalBbox,
stacBbox: OptionalBbox
) {
useEffect(() => {
if (isUserPositionSet) return;

// Prefer layer defined bounds to STAC collection bounds.
const bounds = (initialBbox ?? stacBbox) as
| [number, number, number, number]
| undefined;

if (bounds?.length && checkFitBoundsFromLayer(bounds, mapInstance)) {
mapInstance.fitBounds(bounds, { padding: FIT_BOUNDS_PADDING });
}
}, [mapInstance, isUserPositionSet, initialBbox, stacBbox]);
}
32 changes: 27 additions & 5 deletions app/scripts/components/common/mapbox/layers/vector-timeseries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Feature } from 'geojson';
import { endOfDay, startOfDay } from 'date-fns';
import centroid from '@turf/centroid';

import { requestQuickCache, useLayerInteraction } from './utils';
import { requestQuickCache, useFitBbox, useLayerInteraction } from './utils';
import { useMapStyle } from './styles';
import { useCustomMarker } from './custom-marker';

Expand All @@ -26,9 +26,11 @@ export interface MapLayerVectorTimeseriesProps {
mapInstance: MapboxMap;
sourceParams?: Record<string, any>;
zoomExtent?: number[];
bounds?: number[];
onStatusChange?: (result: { status: ActionStatus; id: string }) => void;
isHidden?: boolean;
idSuffix?: string;
isPositionSet?: boolean;
}

export function MapLayerVectorTimeseries(props: MapLayerVectorTimeseriesProps) {
Expand All @@ -39,14 +41,18 @@ export function MapLayerVectorTimeseries(props: MapLayerVectorTimeseriesProps) {
mapInstance,
sourceParams,
zoomExtent,
bounds,
onStatusChange,
isHidden,
idSuffix = ''
idSuffix = '',
isPositionSet
} = props;

const theme = useTheme();
const { updateStyle } = useMapStyle();
const [featuresApiEndpoint, setFeaturesApiEndpoint] = useState('');
const [featuresBbox, setFeaturesBbox] =
useState<[number, number, number, number]>();

const [minZoom, maxZoom] = zoomExtent ?? [0, 20];

Expand All @@ -67,7 +73,19 @@ export function MapLayerVectorTimeseries(props: MapLayerVectorTimeseriesProps) {
controller
});

setFeaturesApiEndpoint(data.links.find((l) => l.rel === 'external').href);
const endpoint = data.links.find((l) => l.rel === 'external').href;
setFeaturesApiEndpoint(endpoint);

const featuresData = await requestQuickCache({
url: endpoint,
method: 'GET',
controller
});

if (featuresData.extent.spatial.bbox) {
setFeaturesBbox(featuresData.extent.spatial.bbox[0]);
}

onStatusChange?.({ status: S_SUCCEEDED, id });
} catch (error) {
if (!controller.signal.aborted) {
Expand All @@ -85,7 +103,6 @@ export function MapLayerVectorTimeseries(props: MapLayerVectorTimeseriesProps) {
};
}, [mapInstance, id, stacCol, date, onStatusChange]);


const markerLayout = useCustomMarker(mapInstance);

//
Expand Down Expand Up @@ -192,7 +209,7 @@ export function MapLayerVectorTimeseries(props: MapLayerVectorTimeseriesProps) {
'source-layer': 'default',
layout: {
...(markerLayout as any),
visibility: isHidden ? 'none' : 'visible',
visibility: isHidden ? 'none' : 'visible'
},
paint: {
'icon-color': theme.color?.infographicB,
Expand Down Expand Up @@ -266,5 +283,10 @@ export function MapLayerVectorTimeseries(props: MapLayerVectorTimeseriesProps) {
onClick: onPointsClick
});

//
// FitBounds when needed
//
useFitBbox(mapInstance, !!isPositionSet, bounds, featuresBbox);

return null;
}
9 changes: 8 additions & 1 deletion app/scripts/components/datasets/s-explore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ function DatasetsExplore() {
useEffect(() => {
setPanelRevealed(!isMediumDown);
}, [isMediumDown]);

// When the panel changes resize the map after a the animation finishes.
useEffect(() => {
const id = setTimeout(
Expand Down Expand Up @@ -568,7 +569,13 @@ function DatasetsExplore() {
compareDate={selectedCompareDatetime ?? undefined}
isComparing={isComparing}
initialPosition={mapPosition ?? undefined}
onPositionChange={setMapPosition}
onPositionChange={(v) => {
// Only store the map position if the change was initiated by
// the user.
if (v.userInitiated) {
setMapPosition(v);
}
}}
projection={mapProjection ?? projectionDefault}
onProjectionChange={setMapProjection}
/>
Expand Down
15 changes: 15 additions & 0 deletions docs/content/frontmatter/layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ initialDatetime: 'oldest' | 'newest' | Date(YYYY-MM-DD) = 'newest'
description: string
projection: Projection
zoomExtent: [int, int] | null | fn(bag)
bounds: [int, int, int, int] | null | fn(bag)
sourceParams:
[key]: value | fn(bag)
compare: Compare
Expand Down Expand Up @@ -67,6 +68,20 @@ These values may vary greatly depending on the layer being added but some may be
`string`
The colormap to use for the layer. One of https://cogeotiff.github.io/rio-tiler/colormap/#default-rio-tilers-colormaps

**bounds**
`[int, int, int, int] | fn(bag)`
Initial bounds for the map. This is useful for adjusting the initial view on datasets for which the STAC bounds are not appropriate.

This property should be an array with 4 numbers, representing the minimum and maximum longitude and latitude values, in the following order: [minLongitude, minLatitude, maxLongitude, maxLatitude].
Example (world bounds)
```yml
bounds: [-180, -90, 180, 90]
```

Note on bounds and dataset layer switching:
The exploration map will always prioritize the position set in the url. This is so that the user can share a link to a specific location. However, upon load the map will check if the position set in the url is within or overlapping the bounds of the dataset layer. If it is not, the map will switch to the dataset layer bounds avoiding showing an empty map when the user shares a link to a location that is not within the dataset layer bounds.
If there are no bounds set in the dataset configuration, the bbox from the STAC catalog will be used if available, otherwise it will default to the world bounds.

### Projection

**projection**
Expand Down
10 changes: 1 addition & 9 deletions mock/datasets/fire.data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,9 @@ taxonomy:
values:
- COx
layers:
- id: eis_fire_fireline
stacCol: eis_fire_fireline
name: Fire
type: vector
description: eis_fire_fireline
zoomExtent:
- 5
- 20
- id: eis_fire_perimeter
stacCol: eis_fire_perimeter
name: Fire Perimeter
name: Fire
type: vector
description: eis_fire_perimeter
zoomExtent:
Expand Down
6 changes: 4 additions & 2 deletions mock/datasets/no2.data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ taxonomy:
layers:
- id: no2-monthly
stacCol: no2-monthly
name: No2
name: No2 PT
type: raster
bounds: [-10, 36, -5, 42]
description: Levels in 10¹⁵ molecules cm⁻². Darker colors indicate higher nitrogen dioxide (NO₂) levels associated and more activity. Lighter colors indicate lower levels of NO₂ and less activity.
zoomExtent:
- 0
Expand Down Expand Up @@ -73,7 +74,8 @@ layers:
- "#050308"
- id: no2-monthly-2
stacCol: no2-monthly
name: No2
name: No2 US
bounds: [-124, 29, -65, 49]
type: raster
description: Levels in 10¹⁵ molecules cm⁻². Darker colors indicate higher nitrogen dioxide (NO₂) levels associated and more activity. Lighter colors indicate lower levels of NO₂ and less activity.
zoomExtent:
Expand Down
1 change: 1 addition & 0 deletions parcel-resolver-veda/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ declare module 'veda' {

interface DatasetLayerCommonProps {
zoomExtent?: number[];
bounds?: number[];
sourceParams?: Record<string, any>;
}

Expand Down

0 comments on commit 1ced268

Please sign in to comment.