From 45e4dfb247cfe26567b64c820e8760acc98e6dce Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Mon, 6 Oct 2025 16:39:38 -0600 Subject: [PATCH] feat: render visual cogs --- src/app.tsx | 17 ++++++++++++ src/components/assets.tsx | 55 +++++++++++++++++++++++++++++++++++---- src/components/map.tsx | 38 ++++++++++++++++++++++++--- src/components/value.tsx | 10 ++++++- src/utils/stac.ts | 19 +++++++++++++- 5 files changed, 129 insertions(+), 10 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 937545e..b2f1d06 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -9,10 +9,12 @@ import useStacValue from "./hooks/stac-value"; import type { BBox2D, Color } from "./types/map"; import type { DatetimeBounds, StacValue } from "./types/stac"; import { + isCog, isCollectionInBbox, isCollectionInDatetimeBounds, isItemInBbox, isItemInDatetimeBounds, + isVisual, } from "./utils/stac"; // TODO make this configurable by the user. @@ -30,6 +32,7 @@ export default function App() { const [datetimeBounds, setDatetimeBounds] = useState(); const [filter, setFilter] = useState(true); const [stacGeoparquetItemId, setStacGeoparquetItemId] = useState(); + const [cogTileHref, setCogTileHref] = useState(); // Derived state const { @@ -116,6 +119,17 @@ export default function App() { setItems(undefined); setDatetimeBounds(undefined); + let cogTileHref = undefined; + if (value && value.assets) { + for (const asset of Object.values(value.assets)) { + if (isCog(asset) && isVisual(asset)) { + cogTileHref = asset.href as string; + break; + } + } + } + setCogTileHref(cogTileHref); + if (value && (value.title || value.id)) { document.title = "stac-map | " + (value.title || value.id); } else { @@ -151,6 +165,7 @@ export default function App() { picked={picked} setPicked={setPicked} setStacGeoparquetItemId={setStacGeoparquetItemId} + cogTileHref={cogTileHref} > @@ -184,6 +199,8 @@ export default function App() { filteredItems={filteredItems} setItems={setItems} setDatetimeBounds={setDatetimeBounds} + cogTileHref={cogTileHref} + setCogTileHref={setCogTileHref} > diff --git a/src/components/assets.tsx b/src/components/assets.tsx index 03ef2ab..5c7372a 100644 --- a/src/components/assets.tsx +++ b/src/components/assets.tsx @@ -1,26 +1,41 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { LuDownload } from "react-icons/lu"; import { Button, ButtonGroup, Card, + Checkbox, Collapsible, DataList, HStack, Image, + Span, } from "@chakra-ui/react"; import type { StacAsset } from "stac-ts"; import Properties from "./properties"; import type { StacAssets } from "../types/stac"; +import { isCog, isVisual } from "../utils/stac"; -export default function Assets({ assets }: { assets: StacAssets }) { +export default function Assets({ + assets, + cogTileHref, + setCogTileHref, +}: { + assets: StacAssets; + cogTileHref: string | undefined; + setCogTileHref: (href: string | undefined) => void; +}) { return ( {Object.keys(assets).map((key) => ( {key} - + ))} @@ -28,11 +43,24 @@ export default function Assets({ assets }: { assets: StacAssets }) { ); } -function Asset({ asset }: { asset: StacAsset }) { +function Asset({ + asset, + cogTileHref, + setCogTileHref, +}: { + asset: StacAsset; + cogTileHref: string | undefined; + setCogTileHref: (href: string | undefined) => void; +}) { const [imageError, setImageError] = useState(false); + const [checked, setChecked] = useState(false); // eslint-disable-next-line const { href, roles, type, title, ...properties } = asset; + useEffect(() => { + setChecked(cogTileHref === asset.href); + }, [cogTileHref, asset.href]); + return ( @@ -64,7 +92,24 @@ function Asset({ asset }: { asset: StacAsset }) { )} - + + {isCog(asset) && isVisual(asset) && ( + { + setChecked(!!e.checked); + if (e.checked) setCogTileHref(asset.href); + else setCogTileHref(undefined); + }} + > + + + Visualize + + )} + + +