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" ;
13
2
import { 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" ;
17
3
import Map from "./components/map" ;
18
- import Panel from "./components/panel" ;
19
4
import { 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 ] ;
21
12
22
13
export default function App ( ) {
23
- const queryClient = new QueryClient ( { } ) ;
14
+ // The href of a STAC value. Everything is derived from the href.
24
15
const [ href , setHref ] = useState < string | undefined > ( getInitialHref ( ) ) ;
25
- const fileUpload = useFileUpload ( { maxFiles : 1 } ) ;
26
- const isHeaderAbovePanel = useBreakpointValue ( { base : true , md : false } ) ;
27
-
28
16
useEffect ( ( ) => {
29
17
function handlePopState ( ) {
30
18
setHref ( new URLSearchParams ( location . search ) . get ( "href" ) ?? "" ) ;
@@ -35,7 +23,6 @@ export default function App() {
35
23
window . removeEventListener ( "popstate" , handlePopState ) ;
36
24
} ;
37
25
} , [ ] ) ;
38
-
39
26
useEffect ( ( ) => {
40
27
if ( href && new URLSearchParams ( location . search ) . get ( "href" ) != href ) {
41
28
history . pushState ( null , "" , "?href=" + href ) ;
@@ -44,73 +31,92 @@ export default function App() {
44
31
}
45
32
} , [ href ] ) ;
46
33
34
+ // Uploading a file sets the href to its local name.
35
+ const fileUpload = useFileUpload ( { maxFiles : 1 } ) ;
47
36
useEffect ( ( ) => {
48
37
// It should never be more than 1.
49
38
if ( fileUpload . acceptedFiles . length == 1 ) {
50
39
setHref ( fileUpload . acceptedFiles [ 0 ] . name ) ;
51
40
}
52
41
} , [ fileUpload . acceptedFiles ] ) ;
53
42
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 ] ) ;
57
52
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 ] ) ;
100
71
101
- function MapFallback ( { error } : { error : Error } ) {
102
72
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 >
112
94
</ 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
+ </ >
114
120
) ;
115
121
}
116
122
@@ -123,3 +129,29 @@ function getInitialHref() {
123
129
}
124
130
return href ;
125
131
}
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