Skip to content
50 changes: 50 additions & 0 deletions mock/datasets/hls-external.data.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
id: hls-l30-external
name: 'HLS Landsat 30m (External STAC)'
featured: true
sourceExclusive: Mock
description: "Harmonized Landsat Sentinel-2 (HLS) Landsat 30m Surface Reflectance from NASA CMR STAC."
media:
src: ::file ./img-placeholder-3.jpg
alt: HLS Landsat imagery placeholder
taxonomy:
- name: Source
values:
- Mock
- name: Topics
values:
- Agriculture
layers:
- id: hls-l30-external
stacCol: HLSL30_2.0
stacApiEndpoint: https://cmr.earthdata.nasa.gov/cloudstac/LPCLOUD
tileApiEndpoint: https://openveda.cloud/api/raster
name: HLS Landsat 30m
type: raster
tilingMode: cog
time_density: day
description: Harmonized Landsat Sentinel-2 (HLS) Landsat Operational Land Imager Surface Reflectance and TOA Brightness Daily Global 30m v2.0.
zoomExtent:
- 7
- 20
sourceParams:
assets: s3_B04
colormap_name: viridis
rescale:
- 0
- 3000
legend:
type: gradient
min: "0"
max: "3000"
stops:
- "#440154"
- "#3b528b"
- "#21918c"
- "#5ec962"
- "#fde725"
---

Test dataset for the `raster` type with `tilingMode: cog` using NASA CMR STAC (LPCLOUD).

HLS provides consistent surface reflectance data from a virtual constellation of Landsat and Sentinel-2 sensors.
51 changes: 51 additions & 0 deletions mock/datasets/landsat-element84.data.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
id: landsat-element84
name: 'Landsat C2 L2 (Element84 Earth Search)'
featured: true
sourceExclusive: Mock
description: "Atmospherically corrected global Landsat Collection 2 Level-2 data from Element84 Earth Search."
media:
src: ::file ./img-placeholder-3.jpg
alt: Landsat satellite imagery placeholder
taxonomy:
- name: Source
values:
- Mock
- name: Topics
values:
- Agriculture
layers:
- id: landsat-c2-l2-element84
stacCol: landsat-c2-l2
stacApiEndpoint: https://earth-search.aws.element84.com/v1
tileApiEndpoint: https://openveda.cloud/api/raster
name: Landsat Collection 2 Level-2
type: raster
tilingMode: cog
time_density: day
description: Atmospherically corrected global Landsat Collection 2 Level-2 data from Element84 Earth Search.
zoomExtent:
- 7
- 20
searchLimit: 100
sourceParams:
assets: red
colormap_name: viridis
rescale:
- 0
- 20000
legend:
type: gradient
min: "0"
max: "20000"
stops:
- "#440154"
- "#3b528b"
- "#21918c"
- "#5ec962"
- "#fde725"
---

Test dataset for the `raster` type with `tilingMode: cog` using Element84 Earth Search STAC API.

Landsat Collection 2 Level-2 includes atmospherically corrected surface reflectance data from Landsat 4-9 satellites.
51 changes: 51 additions & 0 deletions mock/datasets/sentinel2-element84.data.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
id: sentinel2-element84
name: 'Sentinel-2 L2A (Element84 Earth Search)'
featured: true
sourceExclusive: Mock
description: "Global Sentinel-2 Level-2A data from the Multispectral Instrument (MSI) via Element84 Earth Search."
media:
src: ::file ./img-placeholder-3.jpg
alt: Sentinel-2 satellite imagery placeholder
taxonomy:
- name: Source
values:
- Mock
- name: Topics
values:
- Agriculture
layers:
- id: sentinel-2-l2a-element84
stacCol: sentinel-2-l2a
stacApiEndpoint: https://earth-search.aws.element84.com/v1
tileApiEndpoint: https://openveda.cloud/api/raster
name: Sentinel-2 Level-2A
type: raster
tilingMode: cog
time_density: day
description: Global Sentinel-2 Level-2A surface reflectance data from Element84 Earth Search.
zoomExtent:
- 7
- 20
searchLimit: 100
sourceParams:
assets: red
colormap_name: viridis
rescale:
- 0
- 10000
legend:
type: gradient
min: "0"
max: "10000"
stops:
- "#440154"
- "#3b528b"
- "#21918c"
- "#5ec962"
- "#fde725"
---

Test dataset for the `raster` type with `tilingMode: cog` using Element84 Earth Search STAC API.

Sentinel-2 Level-2A provides atmospherically corrected surface reflectance data from the Sentinel-2 constellation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { useEffect } from 'react';
import {
LayerSpecification,
SourceSpecification,
GeoJSONSourceSpecification,
FillLayerSpecification,
LineLayerSpecification
} from 'mapbox-gl';
import { useTheme } from 'styled-components';
import { featureCollection, polygon, Feature, Polygon } from '@turf/helpers';
import bboxPolygon from '@turf/bbox-polygon';
import useMapStyle from '../hooks/use-map-style';

import useLayerInteraction from '../hooks/use-layer-interaction';
import useGeneratorParams from '../hooks/use-generator-params';

interface Footprint {
bounds: [[number, number], [number, number]];
geometry?: Polygon;
}

interface FootprintsLayerProps {
id: string;
footprints: Footprint[] | null;
zoomExtent?: number[];
hidden?: boolean;
opacity?: number;
generatorOrder?: number;
onFootprintsClick?: (features: Feature[]) => void;
}

export default function FootprintsLayer(props: FootprintsLayerProps) {
const {
id,
footprints,
zoomExtent,
hidden,
opacity,
generatorOrder,
onFootprintsClick
} = props;

const generatorParams = useGeneratorParams({
generatorOrder: generatorOrder ?? 1000000, // on top of any layers
hidden: !!hidden,
opacity: opacity ?? 1
});

const { updateStyle } = useMapStyle();
const minZoom = zoomExtent?.[0] ?? 0;
const generatorId = `footprints-${id}`;

const theme = useTheme();

useEffect(() => {
let layers: LayerSpecification[] = [];
let sources: Record<string, SourceSpecification> = {};

const footprintsSourceId = `${id}-footprints`;
if (footprints && minZoom > 0) {
const features: Feature<Polygon>[] = footprints.map((f) => {
const props = { bounds: f.bounds };
if (f.geometry) {
return polygon(f.geometry.coordinates, props);
}
const [[w, s], [e, n]] = f.bounds;
return bboxPolygon([w, s, e, n], {
properties: props
}) as Feature<Polygon>;
});

const footprintsSource: GeoJSONSourceSpecification = {
type: 'geojson',
data: featureCollection(features)
};

const fillLayer: FillLayerSpecification = {
type: 'fill',
id: footprintsSourceId,
source: footprintsSourceId,
paint: {
'fill-color': theme.color?.primary,
'fill-opacity': 0.4,
'fill-outline-color': theme.color?.primary
},
maxzoom: minZoom,
metadata: {
layerOrderPosition: 'markers'
}
};

const lineLayer: LineLayerSpecification = {
type: 'line',
id: `${footprintsSourceId}-outline`,
source: footprintsSourceId,
paint: {
'line-color': theme.color?.primary,
'line-width': 3,
'line-opacity': 1
},
layout: {
'line-join': 'round',
'line-cap': 'round'
},
maxzoom: minZoom,
metadata: {
layerOrderPosition: 'markers'
}
};

sources = {
[footprintsSourceId]: footprintsSource as SourceSpecification
};
layers = [fillLayer, lineLayer];
}

updateStyle({
generatorId,
sources,
layers,
params: generatorParams
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, footprints, minZoom, generatorParams]);

//
// Cleanup layers on unmount.
//
useEffect(() => {
return () => {
updateStyle({
generatorId,
sources: {},
layers: []
});
};
}, [updateStyle, generatorId]);

useLayerInteraction({
layerId: `${id}-footprints`,
onClick: onFootprintsClick
});

return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import styled from 'styled-components';
import { Button } from '@devseed-ui/button';
import { glsp, themeVal } from '@devseed-ui/theme-provider';

const Overlay = styled.div`
position: absolute;
bottom: ${glsp(1)};
left: 50%;
transform: translateX(-50%);
z-index: 10;
background: ${themeVal('color.surface')};
padding: ${glsp(0.5, 1)};
border-radius: ${themeVal('shape.rounded')};
box-shadow: ${themeVal('boxShadow.elevationA')};
display: flex;
align-items: center;
gap: ${glsp(0.5)};
font-size: 0.875rem;
`;

interface PaginationOverlayProps {
loadedCount: number;
totalMatched: number;
isLoadingMore: boolean;
onLoadMore: () => void;
}

export default function PaginationOverlay(props: PaginationOverlayProps) {
const { loadedCount, totalMatched, isLoadingMore, onLoadMore } = props;

return (
<Overlay>
<span>
Showing {loadedCount} of {totalMatched} items
</span>
<Button size='small' onClick={onLoadMore} disabled={isLoadingMore}>
{isLoadingMore ? 'Loading...' : 'Load More'}
</Button>
</Overlay>
);
}
Loading
Loading