1- import {
2- Alert ,
3- Box ,
4- Container ,
5- FileUpload ,
6- Flex ,
7- GridItem ,
8- SimpleGrid ,
9- useBreakpointValue ,
10- useFileUpload ,
11- } from "@chakra-ui/react" ;
12- import { QueryClient , QueryClientProvider } from "@tanstack/react-query" ;
1+ import { Box , Container , FileUpload , useFileUpload } from "@chakra-ui/react" ;
132import { useEffect , useState } from "react" ;
14- import { ErrorBoundary } from "react-error-boundary" ;
15- import { MapProvider } from "react-map-gl/dist/esm/exports-maplibre" ;
16- import Header from "./components/header" ;
173import Map from "./components/map" ;
18- import Panel from "./components/panel" ;
194import { Toaster } from "./components/ui/toaster" ;
20- import { StacMapProvider } from "./provider" ;
5+ import Overlay from "./components/overlay" ;
6+ import useStacValue from "./hooks/stac-value" ;
7+ import type { BBox2D , Color } from "./types/map" ;
8+ import type { StacCollection } from "stac-ts" ;
9+
10+ const lineColor : Color = [ 207 , 63 , 2 , 100 ] ;
11+ const fillColor : Color = [ 207 , 63 , 2 , 50 ] ;
2112
2213export default function App ( ) {
23- const queryClient = new QueryClient ( { } ) ;
14+ // The href of a STAC value. Everything is derived from the href.
2415 const [ href , setHref ] = useState < string | undefined > ( getInitialHref ( ) ) ;
25- const fileUpload = useFileUpload ( { maxFiles : 1 } ) ;
26- const isHeaderAbovePanel = useBreakpointValue ( { base : true , md : false } ) ;
27-
2816 useEffect ( ( ) => {
2917 function handlePopState ( ) {
3018 setHref ( new URLSearchParams ( location . search ) . get ( "href" ) ?? "" ) ;
@@ -35,7 +23,6 @@ export default function App() {
3523 window . removeEventListener ( "popstate" , handlePopState ) ;
3624 } ;
3725 } , [ ] ) ;
38-
3926 useEffect ( ( ) => {
4027 if ( href && new URLSearchParams ( location . search ) . get ( "href" ) != href ) {
4128 history . pushState ( null , "" , "?href=" + href ) ;
@@ -44,73 +31,92 @@ export default function App() {
4431 }
4532 } , [ href ] ) ;
4633
34+ // Uploading a file sets the href to its local name.
35+ const fileUpload = useFileUpload ( { maxFiles : 1 } ) ;
4736 useEffect ( ( ) => {
4837 // It should never be more than 1.
4938 if ( fileUpload . acceptedFiles . length == 1 ) {
5039 setHref ( fileUpload . acceptedFiles [ 0 ] . name ) ;
5140 }
5241 } , [ fileUpload . acceptedFiles ] ) ;
5342
54- const header = (
55- < Header href = { href } setHref = { setHref } fileUpload = { fileUpload } > </ Header >
56- ) ;
43+ // State derived from the href.
44+ const { value, error, collections } = useStacValue ( { href, fileUpload } ) ;
45+ useEffect ( ( ) => {
46+ if ( value && ( value . title || value . id ) ) {
47+ document . title = "stac-map | " + ( value . title || value . id ) ;
48+ } else {
49+ document . title = "stac-map" ;
50+ }
51+ } , [ value ] ) ;
5752
58- return (
59- < QueryClientProvider client = { queryClient } >
60- < MapProvider >
61- < StacMapProvider href = { href } fileUpload = { fileUpload } >
62- < Box zIndex = { 0 } position = { "absolute" } top = { 0 } left = { 0 } >
63- < FileUpload . RootProvider value = { fileUpload } unstyled = { true } >
64- < FileUpload . Dropzone
65- disableClick = { true }
66- style = { {
67- height : "100dvh" ,
68- width : "100dvw" ,
69- } }
70- >
71- < ErrorBoundary FallbackComponent = { MapFallback } >
72- < Map > </ Map >
73- </ ErrorBoundary >
74- </ FileUpload . Dropzone >
75- </ FileUpload . RootProvider >
76- </ Box >
77- < Container zIndex = { 1 } fluid h = { "dvh" } py = { 4 } pointerEvents = { "none" } >
78- < SimpleGrid columns = { { base : 1 , md : 3 } } gap = { 4 } >
79- { isHeaderAbovePanel && < GridItem colSpan = { 1 } > { header } </ GridItem > }
80- < GridItem colSpan = { 1 } >
81- < Panel
82- href = { href }
83- setHref = { setHref }
84- fileUpload = { fileUpload }
85- > </ Panel >
86- </ GridItem >
87- { ! isHeaderAbovePanel && (
88- < GridItem colSpan = { 2 } hideBelow = { "md" } >
89- { header }
90- </ GridItem >
91- ) }
92- </ SimpleGrid >
93- </ Container >
94- < Toaster > </ Toaster >
95- </ StacMapProvider >
96- </ MapProvider >
97- </ QueryClientProvider >
98- ) ;
99- }
53+ // User-controlled state.
54+ const [ bbox , setBbox ] = useState < BBox2D > ( ) ;
55+ const [ datetimeBounds , setDatetimeBounds ] = useState ( ) ;
56+ const [ filter , setFilter ] = useState ( true ) ;
57+ const [ filteredCollections , setFilteredCollections ] =
58+ useState < StacCollection [ ] > ( ) ;
59+
60+ useEffect ( ( ) => {
61+ if ( filter && collections && bbox ) {
62+ setFilteredCollections (
63+ collections . filter ( ( collection ) =>
64+ isCollectionInBbox ( collection , bbox ) ,
65+ ) ,
66+ ) ;
67+ } else {
68+ setFilteredCollections ( undefined ) ;
69+ }
70+ } , [ collections , filter , bbox ] ) ;
10071
101- function MapFallback ( { error } : { error : Error } ) {
10272 return (
103- < Flex h = { "100dvh" } w = { "100dvw" } alignItems = "center" justifyContent = "center" >
104- < Box >
105- < Alert . Root status = "error" >
106- < Alert . Indicator />
107- < Alert . Content >
108- < Alert . Title > Error while rendering the map</ Alert . Title >
109- < Alert . Description > { error . message } </ Alert . Description >
110- </ Alert . Content >
111- </ Alert . Root >
73+ < >
74+ < Box zIndex = { - 1 } position = { "absolute" } top = { 0 } left = { 0 } h = "100dvh" >
75+ < FileUpload . RootProvider value = { fileUpload } unstyled = { true } >
76+ < FileUpload . Dropzone
77+ disableClick = { true }
78+ style = { {
79+ height : "100dvh" ,
80+ width : "100dvw" ,
81+ } }
82+ > </ FileUpload . Dropzone >
83+ </ FileUpload . RootProvider >
84+ </ Box >
85+ < Box h = { "100dvh" } >
86+ < Map
87+ value = { value }
88+ collections = { collections }
89+ filteredCollections = { filteredCollections }
90+ fillColor = { fillColor }
91+ lineColor = { lineColor }
92+ setBbox = { setBbox }
93+ > </ Map >
11294 </ Box >
113- </ Flex >
95+ < Container
96+ zIndex = { 1 }
97+ fluid
98+ h = "100dvh"
99+ pointerEvents = { "none" }
100+ position = { "absolute" }
101+ top = { 0 }
102+ left = { 0 }
103+ pt = { 2 }
104+ >
105+ < Overlay
106+ href = { href }
107+ setHref = { setHref }
108+ fileUpload = { fileUpload }
109+ value = { value }
110+ error = { error }
111+ collections = { collections }
112+ filteredCollections = { filteredCollections }
113+ filter = { filter }
114+ setFilter = { setFilter }
115+ bbox = { bbox }
116+ > </ Overlay >
117+ </ Container >
118+ < Toaster > </ Toaster >
119+ </ >
114120 ) ;
115121}
116122
@@ -123,3 +129,29 @@ function getInitialHref() {
123129 }
124130 return href ;
125131}
132+
133+ function isCollectionInBbox ( collection : StacCollection , bbox : BBox2D ) {
134+ if ( bbox [ 2 ] - bbox [ 0 ] >= 360 ) {
135+ // A global bbox always contains every collection
136+ return true ;
137+ }
138+ const collectionBbox = collection ?. extent ?. spatial ?. bbox ?. [ 0 ] ;
139+ if ( collectionBbox ) {
140+ return (
141+ ! (
142+ collectionBbox [ 0 ] < bbox [ 0 ] &&
143+ collectionBbox [ 1 ] < bbox [ 1 ] &&
144+ collectionBbox [ 2 ] > bbox [ 2 ] &&
145+ collectionBbox [ 3 ] > bbox [ 3 ]
146+ ) &&
147+ ! (
148+ collectionBbox [ 0 ] > bbox [ 2 ] ||
149+ collectionBbox [ 1 ] > bbox [ 3 ] ||
150+ collectionBbox [ 2 ] < bbox [ 0 ] ||
151+ collectionBbox [ 3 ] < bbox [ 1 ]
152+ )
153+ ) ;
154+ } else {
155+ return false ;
156+ }
157+ }
0 commit comments