Skip to content

Commit 8317a1a

Browse files
authored
1 parent 8baeef7 commit 8317a1a

File tree

5 files changed

+129
-10
lines changed

5 files changed

+129
-10
lines changed

src/app.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import useStacValue from "./hooks/stac-value";
99
import type { BBox2D, Color } from "./types/map";
1010
import type { DatetimeBounds, StacValue } from "./types/stac";
1111
import {
12+
isCog,
1213
isCollectionInBbox,
1314
isCollectionInDatetimeBounds,
1415
isItemInBbox,
1516
isItemInDatetimeBounds,
17+
isVisual,
1618
} from "./utils/stac";
1719

1820
// TODO make this configurable by the user.
@@ -30,6 +32,7 @@ export default function App() {
3032
const [datetimeBounds, setDatetimeBounds] = useState<DatetimeBounds>();
3133
const [filter, setFilter] = useState(true);
3234
const [stacGeoparquetItemId, setStacGeoparquetItemId] = useState<string>();
35+
const [cogTileHref, setCogTileHref] = useState<string>();
3336

3437
// Derived state
3538
const {
@@ -116,6 +119,17 @@ export default function App() {
116119
setItems(undefined);
117120
setDatetimeBounds(undefined);
118121

122+
let cogTileHref = undefined;
123+
if (value && value.assets) {
124+
for (const asset of Object.values(value.assets)) {
125+
if (isCog(asset) && isVisual(asset)) {
126+
cogTileHref = asset.href as string;
127+
break;
128+
}
129+
}
130+
}
131+
setCogTileHref(cogTileHref);
132+
119133
if (value && (value.title || value.id)) {
120134
document.title = "stac-map | " + (value.title || value.id);
121135
} else {
@@ -151,6 +165,7 @@ export default function App() {
151165
picked={picked}
152166
setPicked={setPicked}
153167
setStacGeoparquetItemId={setStacGeoparquetItemId}
168+
cogTileHref={cogTileHref}
154169
></Map>
155170
</FileUpload.Dropzone>
156171
</FileUpload.RootProvider>
@@ -184,6 +199,8 @@ export default function App() {
184199
filteredItems={filteredItems}
185200
setItems={setItems}
186201
setDatetimeBounds={setDatetimeBounds}
202+
cogTileHref={cogTileHref}
203+
setCogTileHref={setCogTileHref}
187204
></Overlay>
188205
</Container>
189206
<Toaster></Toaster>

src/components/assets.tsx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,66 @@
1-
import { useState } from "react";
1+
import { useEffect, useState } from "react";
22
import { LuDownload } from "react-icons/lu";
33
import {
44
Button,
55
ButtonGroup,
66
Card,
7+
Checkbox,
78
Collapsible,
89
DataList,
910
HStack,
1011
Image,
12+
Span,
1113
} from "@chakra-ui/react";
1214
import type { StacAsset } from "stac-ts";
1315
import Properties from "./properties";
1416
import type { StacAssets } from "../types/stac";
17+
import { isCog, isVisual } from "../utils/stac";
1518

16-
export default function Assets({ assets }: { assets: StacAssets }) {
19+
export default function Assets({
20+
assets,
21+
cogTileHref,
22+
setCogTileHref,
23+
}: {
24+
assets: StacAssets;
25+
cogTileHref: string | undefined;
26+
setCogTileHref: (href: string | undefined) => void;
27+
}) {
1728
return (
1829
<DataList.Root>
1930
{Object.keys(assets).map((key) => (
2031
<DataList.Item key={"asset-" + key}>
2132
<DataList.ItemLabel>{key}</DataList.ItemLabel>
2233
<DataList.ItemValue>
23-
<Asset asset={assets[key]} />
34+
<Asset
35+
asset={assets[key]}
36+
cogTileHref={cogTileHref}
37+
setCogTileHref={setCogTileHref}
38+
/>
2439
</DataList.ItemValue>
2540
</DataList.Item>
2641
))}
2742
</DataList.Root>
2843
);
2944
}
3045

31-
function Asset({ asset }: { asset: StacAsset }) {
46+
function Asset({
47+
asset,
48+
cogTileHref,
49+
setCogTileHref,
50+
}: {
51+
asset: StacAsset;
52+
cogTileHref: string | undefined;
53+
setCogTileHref: (href: string | undefined) => void;
54+
}) {
3255
const [imageError, setImageError] = useState(false);
56+
const [checked, setChecked] = useState(false);
3357
// eslint-disable-next-line
3458
const { href, roles, type, title, ...properties } = asset;
3559

60+
useEffect(() => {
61+
setChecked(cogTileHref === asset.href);
62+
}, [cogTileHref, asset.href]);
63+
3664
return (
3765
<Card.Root size={"sm"} w="full">
3866
<Card.Header>
@@ -64,7 +92,24 @@ function Asset({ asset }: { asset: StacAsset }) {
6492
</Collapsible.Content>
6593
</Collapsible.Root>
6694
)}
67-
<HStack justify={"right"}>
95+
<HStack>
96+
{isCog(asset) && isVisual(asset) && (
97+
<Checkbox.Root
98+
checked={checked}
99+
onCheckedChange={(e) => {
100+
setChecked(!!e.checked);
101+
if (e.checked) setCogTileHref(asset.href);
102+
else setCogTileHref(undefined);
103+
}}
104+
>
105+
<Checkbox.HiddenInput />
106+
<Checkbox.Control />
107+
<Checkbox.Label>Visualize</Checkbox.Label>
108+
</Checkbox.Root>
109+
)}
110+
111+
<Span flex={"1"} />
112+
68113
<ButtonGroup size="sm" variant="outline">
69114
<Button asChild>
70115
<a href={asset.href} target="_blank">

src/components/map.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
useControl,
66
} from "react-map-gl/maplibre";
77
import { type DeckProps, Layer } from "@deck.gl/core";
8-
import { GeoJsonLayer } from "@deck.gl/layers";
8+
import { TileLayer } from "@deck.gl/geo-layers";
9+
import { BitmapLayer, GeoJsonLayer } from "@deck.gl/layers";
910
import { MapboxOverlay } from "@deck.gl/mapbox";
1011
import { GeoArrowPolygonLayer } from "@geoarrow/deck.gl-layers";
1112
import bbox from "@turf/bbox";
@@ -31,6 +32,7 @@ export default function Map({
3132
setPicked,
3233
table,
3334
setStacGeoparquetItemId,
35+
cogTileHref,
3436
}: {
3537
value: StacValue | undefined;
3638
collections: StacCollection[] | undefined;
@@ -44,6 +46,7 @@ export default function Map({
4446
setPicked: (picked: StacValue | undefined) => void;
4547
table: Table | undefined;
4648
setStacGeoparquetItemId: (id: string | undefined) => void;
49+
cogTileHref: string | undefined;
4750
}) {
4851
const mapRef = useRef<MapRef>(null);
4952
const mapStyle = useColorModeValue(
@@ -87,7 +90,36 @@ export default function Map({
8790
fillColor[3],
8891
];
8992

90-
const layers: Layer[] = [
93+
let layers: Layer[] = [];
94+
95+
if (cogTileHref)
96+
layers.push(
97+
new TileLayer({
98+
id: "cog-tiles",
99+
extent: value && getBbox(value, collections),
100+
maxRequests: 10,
101+
data:
102+
cogTileHref &&
103+
`https://titiler.xyz/cog/tiles/WebMercatorQuad/{z}/{x}/{y}.png?url=${cogTileHref}`,
104+
renderSubLayers: (props) => {
105+
const { boundingBox } = props.tile;
106+
107+
return new BitmapLayer(props, {
108+
data: undefined,
109+
image: props.data,
110+
bounds: [
111+
boundingBox[0][0],
112+
boundingBox[0][1],
113+
boundingBox[1][0],
114+
boundingBox[1][1],
115+
],
116+
});
117+
},
118+
})
119+
);
120+
121+
layers = [
122+
...layers,
91123
new GeoJsonLayer({
92124
id: "picked",
93125
data: pickedGeoJson,
@@ -121,7 +153,7 @@ export default function Map({
121153
new GeoJsonLayer({
122154
id: "value",
123155
data: valueGeoJson,
124-
filled: !items,
156+
filled: !items && !cogTileHref,
125157
getFillColor: collections ? inverseFillColor : fillColor,
126158
getLineColor: collections ? inverseLineColor : lineColor,
127159
getLineWidth: 2,

src/components/value.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export interface SharedValueProps {
6262
bbox: BBox2D | undefined;
6363
setItems: (items: StacItem[] | undefined) => void;
6464
setDatetimeBounds: (bounds: DatetimeBounds | undefined) => void;
65+
cogTileHref: string | undefined;
66+
setCogTileHref: (href: string | undefined) => void;
6567
}
6668

6769
interface ValueProps extends SharedValueProps {
@@ -84,6 +86,8 @@ export function Value({
8486
setFilter,
8587
bbox,
8688
setDatetimeBounds,
89+
cogTileHref,
90+
setCogTileHref,
8791
}: ValueProps) {
8892
const [search, setSearch] = useState<StacSearch>();
8993
const [numberOfCollections, setNumberOfCollections] = useState<number>();
@@ -369,7 +373,11 @@ export function Value({
369373
AccordionIcon={LuFiles}
370374
accordionValue="assets"
371375
>
372-
<Assets assets={assets} />
376+
<Assets
377+
assets={assets}
378+
cogTileHref={cogTileHref}
379+
setCogTileHref={setCogTileHref}
380+
/>
373381
</Section>
374382
)}
375383

src/utils/stac.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { UseFileUploadReturn } from "@chakra-ui/react";
2-
import type { StacCollection, StacItem, StacLink } from "stac-ts";
2+
import type { StacAsset, StacCollection, StacItem, StacLink } from "stac-ts";
33
import type { BBox2D } from "../types/map";
44
import type { DatetimeBounds, StacAssets, StacValue } from "../types/stac";
55

@@ -250,3 +250,20 @@ export function getImportantLinks(links: StacLink[]) {
250250
}
251251
return { rootLink, collectionsLink, nextLink, prevLink, filteredLinks };
252252
}
253+
254+
export function isCog(asset: StacAsset) {
255+
return (
256+
asset.type === "image/tiff; application=geotiff; profile=cloud-optimized"
257+
);
258+
}
259+
260+
export function isVisual(asset: StacAsset) {
261+
if (asset.roles) {
262+
for (const role of asset.roles) {
263+
if (role === "visual" || role === "thumbnail") {
264+
return true;
265+
}
266+
}
267+
}
268+
return false;
269+
}

0 commit comments

Comments
 (0)