giswqs commited on
Commit
32fdadc
β€’
1 Parent(s): 97235c4

Added multipage app

Browse files
Home.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import leafmap.foliumap as leafmap
3
+
4
+ st.set_page_config(layout="wide")
5
+
6
+ st.sidebar.title("About")
7
+ st.sidebar.info(
8
+ """
9
+ Web App URL: <https://geospatial.streamlitapp.com>
10
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
11
+ """
12
+ )
13
+
14
+ st.sidebar.title("Contact")
15
+ st.sidebar.info(
16
+ """
17
+ Qiusheng Wu: <https://wetlands.io>
18
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
19
+ """
20
+ )
21
+
22
+ st.title("Streamlit for Geospatial Applications")
23
+
24
+ st.markdown(
25
+ """
26
+ This multi-page web app demonstrates various interactive web apps created using [streamlit](https://streamlit.io) and open-source mapping libraries,
27
+ such as [leafmap](https://leafmap.org), [geemap](https://geemap.org), [pydeck](https://deckgl.readthedocs.io), and [kepler.gl](https://docs.kepler.gl/docs/keplergl-jupyter).
28
+ This is an open-source project and you are very welcome to contribute your comments, questions, resources, and apps as [issues](https://github.com/giswqs/streamlit-geospatial/issues) or
29
+ [pull requests](https://github.com/giswqs/streamlit-geospatial/pulls) to the [GitHub repository](https://github.com/giswqs/streamlit-geospatial).
30
+
31
+ """
32
+ )
33
+
34
+ st.info("Click on the left sidebar menu to navigate to the different apps.")
35
+
36
+ st.subheader("Timelapse of Satellite Imagery")
37
+ st.markdown(
38
+ """
39
+ The following timelapse animations were created using the Timelapse web app. Click `Timelapse` on the left sidebar menu to create your own timelapse for any location around the globe.
40
+ """
41
+ )
42
+
43
+ row1_col1, row1_col2 = st.columns(2)
44
+ with row1_col1:
45
+ st.image("https://github.com/giswqs/data/raw/main/timelapse/spain.gif")
46
+ st.image("https://github.com/giswqs/data/raw/main/timelapse/las_vegas.gif")
47
+
48
+ with row1_col2:
49
+ st.image("https://github.com/giswqs/data/raw/main/timelapse/goes.gif")
50
+ st.image("https://github.com/giswqs/data/raw/main/timelapse/fire.gif")
Procfile CHANGED
@@ -1 +1 @@
1
- web: sh setup.sh && streamlit run app.py
 
1
+ web: sh setup.sh && streamlit run Home.py
apps/hurricane.py CHANGED
@@ -3,8 +3,9 @@ import tropycal.tracks as tracks
3
 
4
 
5
  @st.cache
6
- def read_data(basin='north_atlantic',source='hurdat',include_btk=False):
7
- return tracks.TrackDataset(basin=basin,source=source,include_btk=include_btk)
 
8
 
9
  def app():
10
 
@@ -23,14 +24,16 @@ def app():
23
  if st.session_state.get('hurricane') is None:
24
  st.session_state['hurricane'] = read_data()
25
 
26
- years = st.slider('Select a year', min_value=1950, max_value=2022, value=(2000, 2010))
 
 
27
  storms = st.session_state['hurricane'].filter_storms(year_range=years)
28
  selected = st.selectbox('Select a storm', storms)
29
  storm = st.session_state['hurricane'].get_storm(selected)
30
  ax = storm.plot()
31
  fig = ax.get_figure()
32
- empty.pyplot(fig)
33
- else:
34
 
35
  name = st.text_input("Or enter a storm Name", "michael")
36
  if name:
@@ -40,12 +43,10 @@ def app():
40
  years = basin.search_name(name)
41
  if len(years) > 0:
42
  year = st.selectbox("Select a year", years)
43
- storm = basin.get_storm((name,year))
44
  ax = storm.plot()
45
  fig = ax.get_figure()
46
  empty.pyplot(fig)
47
  else:
48
  empty.text("No storms found")
49
  st.write("No storms found")
50
-
51
-
 
3
 
4
 
5
  @st.cache
6
+ def read_data(basin='north_atlantic', source='hurdat', include_btk=False):
7
+ return tracks.TrackDataset(basin=basin, source=source, include_btk=include_btk)
8
+
9
 
10
  def app():
11
 
 
24
  if st.session_state.get('hurricane') is None:
25
  st.session_state['hurricane'] = read_data()
26
 
27
+ years = st.slider(
28
+ 'Select a year', min_value=1950, max_value=2022, value=(2000, 2010)
29
+ )
30
  storms = st.session_state['hurricane'].filter_storms(year_range=years)
31
  selected = st.selectbox('Select a storm', storms)
32
  storm = st.session_state['hurricane'].get_storm(selected)
33
  ax = storm.plot()
34
  fig = ax.get_figure()
35
+ empty.pyplot(fig)
36
+ else:
37
 
38
  name = st.text_input("Or enter a storm Name", "michael")
39
  if name:
 
43
  years = basin.search_name(name)
44
  if len(years) > 0:
45
  year = st.selectbox("Select a year", years)
46
+ storm = basin.get_storm((name, year))
47
  ax = storm.plot()
48
  fig = ax.get_figure()
49
  empty.pyplot(fig)
50
  else:
51
  empty.text("No storms found")
52
  st.write("No storms found")
 
 
apps/timelapse.py CHANGED
@@ -368,14 +368,10 @@ def app():
368
  # st.info(
369
  # "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click Submit button"
370
  # )
371
- if (
372
- collection
373
- in [
374
- "Geostationary Operational Environmental Satellites (GOES)",
375
- "USDA National Agriculture Imagery Program (NAIP)",
376
- ]
377
- and (not keyword)
378
- ):
379
  m.set_center(-100, 40, 3)
380
  # else:
381
  # m.set_center(4.20, 18.63, zoom=2)
 
368
  # st.info(
369
  # "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click Submit button"
370
  # )
371
+ if collection in [
372
+ "Geostationary Operational Environmental Satellites (GOES)",
373
+ "USDA National Agriculture Imagery Program (NAIP)",
374
+ ] and (not keyword):
 
 
 
 
375
  m.set_center(-100, 40, 3)
376
  # else:
377
  # m.set_center(4.20, 18.63, zoom=2)
environment-bk.yml CHANGED
@@ -2,7 +2,7 @@ name: geo
2
  channels:
3
  - conda-forge
4
  dependencies:
5
- - gdal=3.2.2
6
  - pip
7
  - pip:
8
  - geopandas
 
2
  channels:
3
  - conda-forge
4
  dependencies:
5
+ - gdal=3.4.3
6
  - pip
7
  - pip:
8
  - geopandas
environment-bk2.yml DELETED
@@ -1,20 +0,0 @@
1
- name: geo-env
2
- channels:
3
- - conda-forge
4
- dependencies:
5
- # - gdal=3.4.3
6
- - cartopy
7
- - pip
8
- - pip:
9
- - geopandas
10
- - keplergl
11
- - palettable
12
- - localtileserver
13
- - owslib
14
- - plotly
15
- - streamlit
16
- - streamlit-folium
17
- - streamlit-bokeh-events
18
- - tropycal
19
- - geemap
20
- - leafmap
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/1_πŸ“·_Timelapse.py ADDED
@@ -0,0 +1,1505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sys import modules
2
+ import ee
3
+ import os
4
+ import datetime
5
+ import geopandas as gpd
6
+ import folium
7
+ import streamlit as st
8
+ import geemap.colormaps as cm
9
+ import geemap.foliumap as geemap
10
+ from datetime import date
11
+ from shapely.geometry import Polygon
12
+
13
+ st.set_page_config(layout="wide")
14
+
15
+ st.sidebar.title("About")
16
+ st.sidebar.info(
17
+ """
18
+ Web App URL: <https://geospatial.streamlitapp.com>
19
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
20
+ """
21
+ )
22
+
23
+ st.sidebar.title("Contact")
24
+ st.sidebar.info(
25
+ """
26
+ Qiusheng Wu: <https://wetlands.io>
27
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
28
+ """
29
+ )
30
+
31
+ goes_rois = {
32
+ "Creek Fire, CA (2020-09-05)": {
33
+ "region": Polygon(
34
+ [
35
+ [-121.003418, 36.848857],
36
+ [-121.003418, 39.049052],
37
+ [-117.905273, 39.049052],
38
+ [-117.905273, 36.848857],
39
+ [-121.003418, 36.848857],
40
+ ]
41
+ ),
42
+ "start_time": "2020-09-05T15:00:00",
43
+ "end_time": "2020-09-06T02:00:00",
44
+ },
45
+ "Bomb Cyclone (2021-10-24)": {
46
+ "region": Polygon(
47
+ [
48
+ [-159.5954, 60.4088],
49
+ [-159.5954, 24.5178],
50
+ [-114.2438, 24.5178],
51
+ [-114.2438, 60.4088],
52
+ ]
53
+ ),
54
+ "start_time": "2021-10-24T14:00:00",
55
+ "end_time": "2021-10-25T01:00:00",
56
+ },
57
+ "Hunga Tonga Volcanic Eruption (2022-01-15)": {
58
+ "region": Polygon(
59
+ [
60
+ [-192.480469, -32.546813],
61
+ [-192.480469, -8.754795],
62
+ [-157.587891, -8.754795],
63
+ [-157.587891, -32.546813],
64
+ [-192.480469, -32.546813],
65
+ ]
66
+ ),
67
+ "start_time": "2022-01-15T03:00:00",
68
+ "end_time": "2022-01-15T07:00:00",
69
+ },
70
+ "Hunga Tonga Volcanic Eruption Closer Look (2022-01-15)": {
71
+ "region": Polygon(
72
+ [
73
+ [-178.901367, -22.958393],
74
+ [-178.901367, -17.85329],
75
+ [-171.452637, -17.85329],
76
+ [-171.452637, -22.958393],
77
+ [-178.901367, -22.958393],
78
+ ]
79
+ ),
80
+ "start_time": "2022-01-15T03:00:00",
81
+ "end_time": "2022-01-15T07:00:00",
82
+ },
83
+ }
84
+
85
+
86
+ landsat_rois = {
87
+ "Aral Sea": Polygon(
88
+ [
89
+ [57.667236, 43.834527],
90
+ [57.667236, 45.996962],
91
+ [61.12793, 45.996962],
92
+ [61.12793, 43.834527],
93
+ [57.667236, 43.834527],
94
+ ]
95
+ ),
96
+ "Dubai": Polygon(
97
+ [
98
+ [54.541626, 24.763044],
99
+ [54.541626, 25.427152],
100
+ [55.632019, 25.427152],
101
+ [55.632019, 24.763044],
102
+ [54.541626, 24.763044],
103
+ ]
104
+ ),
105
+ "Hong Kong International Airport": Polygon(
106
+ [
107
+ [113.825226, 22.198849],
108
+ [113.825226, 22.349758],
109
+ [114.085121, 22.349758],
110
+ [114.085121, 22.198849],
111
+ [113.825226, 22.198849],
112
+ ]
113
+ ),
114
+ "Las Vegas, NV": Polygon(
115
+ [
116
+ [-115.554199, 35.804449],
117
+ [-115.554199, 36.558188],
118
+ [-113.903503, 36.558188],
119
+ [-113.903503, 35.804449],
120
+ [-115.554199, 35.804449],
121
+ ]
122
+ ),
123
+ "Pucallpa, Peru": Polygon(
124
+ [
125
+ [-74.672699, -8.600032],
126
+ [-74.672699, -8.254983],
127
+ [-74.279938, -8.254983],
128
+ [-74.279938, -8.600032],
129
+ ]
130
+ ),
131
+ "Sierra Gorda, Chile": Polygon(
132
+ [
133
+ [-69.315491, -22.837104],
134
+ [-69.315491, -22.751488],
135
+ [-69.190006, -22.751488],
136
+ [-69.190006, -22.837104],
137
+ [-69.315491, -22.837104],
138
+ ]
139
+ ),
140
+ }
141
+
142
+ modis_rois = {
143
+ "World": Polygon(
144
+ [
145
+ [-171.210938, -57.136239],
146
+ [-171.210938, 79.997168],
147
+ [177.539063, 79.997168],
148
+ [177.539063, -57.136239],
149
+ [-171.210938, -57.136239],
150
+ ]
151
+ ),
152
+ "Africa": Polygon(
153
+ [
154
+ [-18.6983, 38.1446],
155
+ [-18.6983, -36.1630],
156
+ [52.2293, -36.1630],
157
+ [52.2293, 38.1446],
158
+ ]
159
+ ),
160
+ "USA": Polygon(
161
+ [
162
+ [-127.177734, 23.725012],
163
+ [-127.177734, 50.792047],
164
+ [-66.269531, 50.792047],
165
+ [-66.269531, 23.725012],
166
+ [-127.177734, 23.725012],
167
+ ]
168
+ ),
169
+ }
170
+
171
+ ocean_rois = {
172
+ "Gulf of Mexico": Polygon(
173
+ [
174
+ [-101.206055, 15.496032],
175
+ [-101.206055, 32.361403],
176
+ [-75.673828, 32.361403],
177
+ [-75.673828, 15.496032],
178
+ [-101.206055, 15.496032],
179
+ ]
180
+ ),
181
+ "North Atlantic Ocean": Polygon(
182
+ [
183
+ [-85.341797, 24.046464],
184
+ [-85.341797, 45.02695],
185
+ [-55.810547, 45.02695],
186
+ [-55.810547, 24.046464],
187
+ [-85.341797, 24.046464],
188
+ ]
189
+ ),
190
+ "World": Polygon(
191
+ [
192
+ [-171.210938, -57.136239],
193
+ [-171.210938, 79.997168],
194
+ [177.539063, 79.997168],
195
+ [177.539063, -57.136239],
196
+ [-171.210938, -57.136239],
197
+ ]
198
+ ),
199
+ }
200
+
201
+
202
+ @st.cache
203
+ def uploaded_file_to_gdf(data):
204
+ import tempfile
205
+ import os
206
+ import uuid
207
+
208
+ _, file_extension = os.path.splitext(data.name)
209
+ file_id = str(uuid.uuid4())
210
+ file_path = os.path.join(tempfile.gettempdir(), f"{file_id}{file_extension}")
211
+
212
+ with open(file_path, "wb") as file:
213
+ file.write(data.getbuffer())
214
+
215
+ if file_path.lower().endswith(".kml"):
216
+ gpd.io.file.fiona.drvsupport.supported_drivers["KML"] = "rw"
217
+ gdf = gpd.read_file(file_path, driver="KML")
218
+ else:
219
+ gdf = gpd.read_file(file_path)
220
+
221
+ return gdf
222
+
223
+
224
+ def app():
225
+
226
+ today = date.today()
227
+
228
+ st.title("Create Satellite Timelapse")
229
+
230
+ st.markdown(
231
+ """
232
+ An interactive web app for creating [Landsat](https://developers.google.com/earth-engine/datasets/catalog/landsat)/[GOES](https://jstnbraaten.medium.com/goes-in-earth-engine-53fbc8783c16) timelapse for any location around the globe.
233
+ The app was built using [streamlit](https://streamlit.io), [geemap](https://geemap.org), and [Google Earth Engine](https://earthengine.google.com). For more info, check out my streamlit [blog post](https://blog.streamlit.io/creating-satellite-timelapse-with-streamlit-and-earth-engine).
234
+ """
235
+ )
236
+
237
+ row1_col1, row1_col2 = st.columns([2, 1])
238
+
239
+ if st.session_state.get("zoom_level") is None:
240
+ st.session_state["zoom_level"] = 4
241
+
242
+ st.session_state["ee_asset_id"] = None
243
+ st.session_state["bands"] = None
244
+ st.session_state["palette"] = None
245
+ st.session_state["vis_params"] = None
246
+
247
+ with row1_col1:
248
+ m = geemap.Map(
249
+ basemap="HYBRID",
250
+ plugin_Draw=True,
251
+ Draw_export=True,
252
+ locate_control=True,
253
+ plugin_LatLngPopup=False,
254
+ )
255
+ m.add_basemap("ROADMAP")
256
+
257
+ with row1_col2:
258
+
259
+ keyword = st.text_input("Search for a location:", "")
260
+ if keyword:
261
+ locations = geemap.geocode(keyword)
262
+ if locations is not None and len(locations) > 0:
263
+ str_locations = [str(g)[1:-1] for g in locations]
264
+ location = st.selectbox("Select a location:", str_locations)
265
+ loc_index = str_locations.index(location)
266
+ selected_loc = locations[loc_index]
267
+ lat, lng = selected_loc.lat, selected_loc.lng
268
+ folium.Marker(location=[lat, lng], popup=location).add_to(m)
269
+ m.set_center(lng, lat, 12)
270
+ st.session_state["zoom_level"] = 12
271
+
272
+ collection = st.selectbox(
273
+ "Select a satellite image collection: ",
274
+ [
275
+ "Any Earth Engine ImageCollection",
276
+ "Landsat TM-ETM-OLI Surface Reflectance",
277
+ "Sentinel-2 MSI Surface Reflectance",
278
+ "Geostationary Operational Environmental Satellites (GOES)",
279
+ "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km",
280
+ "MODIS Gap filled Land Surface Temperature Daily",
281
+ "MODIS Ocean Color SMI",
282
+ "USDA National Agriculture Imagery Program (NAIP)",
283
+ ],
284
+ index=1,
285
+ )
286
+
287
+ if collection in [
288
+ "Landsat TM-ETM-OLI Surface Reflectance",
289
+ "Sentinel-2 MSI Surface Reflectance",
290
+ ]:
291
+ roi_options = ["Uploaded GeoJSON"] + list(landsat_rois.keys())
292
+
293
+ elif collection == "Geostationary Operational Environmental Satellites (GOES)":
294
+ roi_options = ["Uploaded GeoJSON"] + list(goes_rois.keys())
295
+
296
+ elif collection in [
297
+ "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km",
298
+ "MODIS Gap filled Land Surface Temperature Daily",
299
+ ]:
300
+ roi_options = ["Uploaded GeoJSON"] + list(modis_rois.keys())
301
+ elif collection == "MODIS Ocean Color SMI":
302
+ roi_options = ["Uploaded GeoJSON"] + list(ocean_rois.keys())
303
+ else:
304
+ roi_options = ["Uploaded GeoJSON"]
305
+
306
+ if collection == "Any Earth Engine ImageCollection":
307
+ keyword = st.text_input("Enter a keyword to search (e.g., MODIS):", "")
308
+ if keyword:
309
+
310
+ assets = geemap.search_ee_data(keyword)
311
+ ee_assets = []
312
+ for asset in assets:
313
+ if asset["ee_id_snippet"].startswith("ee.ImageCollection"):
314
+ ee_assets.append(asset)
315
+
316
+ asset_titles = [x["title"] for x in ee_assets]
317
+ dataset = st.selectbox("Select a dataset:", asset_titles)
318
+ if len(ee_assets) > 0:
319
+ st.session_state["ee_assets"] = ee_assets
320
+ st.session_state["asset_titles"] = asset_titles
321
+ index = asset_titles.index(dataset)
322
+ ee_id = ee_assets[index]["id"]
323
+ else:
324
+ ee_id = ""
325
+
326
+ if dataset is not None:
327
+ with st.expander("Show dataset details", False):
328
+ index = asset_titles.index(dataset)
329
+ html = geemap.ee_data_html(st.session_state["ee_assets"][index])
330
+ st.markdown(html, True)
331
+ # elif collection == "MODIS Gap filled Land Surface Temperature Daily":
332
+ # ee_id = ""
333
+ else:
334
+ ee_id = ""
335
+
336
+ asset_id = st.text_input("Enter an ee.ImageCollection asset ID:", ee_id)
337
+
338
+ if asset_id:
339
+ with st.expander("Customize band combination and color palette", True):
340
+ try:
341
+ col = ee.ImageCollection.load(asset_id)
342
+ st.session_state["ee_asset_id"] = asset_id
343
+ except:
344
+ st.error("Invalid Earth Engine asset ID.")
345
+ st.session_state["ee_asset_id"] = None
346
+ return
347
+
348
+ img_bands = col.first().bandNames().getInfo()
349
+ if len(img_bands) >= 3:
350
+ default_bands = img_bands[:3][::-1]
351
+ else:
352
+ default_bands = img_bands[:]
353
+ bands = st.multiselect(
354
+ "Select one or three bands (RGB):", img_bands, default_bands
355
+ )
356
+ st.session_state["bands"] = bands
357
+
358
+ if len(bands) == 1:
359
+ palette_options = st.selectbox(
360
+ "Color palette",
361
+ cm.list_colormaps(),
362
+ index=2,
363
+ )
364
+ palette_values = cm.get_palette(palette_options, 15)
365
+ palette = st.text_area(
366
+ "Enter a custom palette:",
367
+ palette_values,
368
+ )
369
+ st.write(
370
+ cm.plot_colormap(cmap=palette_options, return_fig=True)
371
+ )
372
+ st.session_state["palette"] = eval(palette)
373
+
374
+ if bands:
375
+ vis_params = st.text_area(
376
+ "Enter visualization parameters",
377
+ "{'bands': ["
378
+ + ", ".join([f"'{band}'" for band in bands])
379
+ + "]}",
380
+ )
381
+ else:
382
+ vis_params = st.text_area(
383
+ "Enter visualization parameters",
384
+ "{}",
385
+ )
386
+ try:
387
+ st.session_state["vis_params"] = eval(vis_params)
388
+ st.session_state["vis_params"]["palette"] = st.session_state[
389
+ "palette"
390
+ ]
391
+ except Exception as e:
392
+ st.session_state["vis_params"] = None
393
+ st.error(
394
+ f"Invalid visualization parameters. It must be a dictionary."
395
+ )
396
+
397
+ elif collection == "MODIS Gap filled Land Surface Temperature Daily":
398
+ with st.expander("Show dataset details", False):
399
+ st.markdown(
400
+ """
401
+ See the [Awesome GEE Community Datasets](https://samapriya.github.io/awesome-gee-community-datasets/projects/daily_lst/).
402
+ """
403
+ )
404
+
405
+ MODIS_options = ["Daytime (1:30 pm)", "Nighttime (1:30 am)"]
406
+ MODIS_option = st.selectbox("Select a MODIS dataset:", MODIS_options)
407
+ if MODIS_option == "Daytime (1:30 pm)":
408
+ st.session_state[
409
+ "ee_asset_id"
410
+ ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_day_1km"
411
+ else:
412
+ st.session_state[
413
+ "ee_asset_id"
414
+ ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_night_1km"
415
+
416
+ palette_options = st.selectbox(
417
+ "Color palette",
418
+ cm.list_colormaps(),
419
+ index=90,
420
+ )
421
+ palette_values = cm.get_palette(palette_options, 15)
422
+ palette = st.text_area(
423
+ "Enter a custom palette:",
424
+ palette_values,
425
+ )
426
+ st.write(cm.plot_colormap(cmap=palette_options, return_fig=True))
427
+ st.session_state["palette"] = eval(palette)
428
+ elif collection == "MODIS Ocean Color SMI":
429
+ with st.expander("Show dataset details", False):
430
+ st.markdown(
431
+ """
432
+ See the [Earth Engine Data Catalog](https://developers.google.com/earth-engine/datasets/catalog/NASA_OCEANDATA_MODIS-Aqua_L3SMI).
433
+ """
434
+ )
435
+
436
+ MODIS_options = ["Aqua", "Terra"]
437
+ MODIS_option = st.selectbox("Select a satellite:", MODIS_options)
438
+ st.session_state["ee_asset_id"] = MODIS_option
439
+ # if MODIS_option == "Daytime (1:30 pm)":
440
+ # st.session_state[
441
+ # "ee_asset_id"
442
+ # ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_day_1km"
443
+ # else:
444
+ # st.session_state[
445
+ # "ee_asset_id"
446
+ # ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_night_1km"
447
+
448
+ band_dict = {
449
+ "Chlorophyll a concentration": "chlor_a",
450
+ "Normalized fluorescence line height": "nflh",
451
+ "Particulate organic carbon": "poc",
452
+ "Sea surface temperature": "sst",
453
+ "Remote sensing reflectance at band 412nm": "Rrs_412",
454
+ "Remote sensing reflectance at band 443nm": "Rrs_443",
455
+ "Remote sensing reflectance at band 469nm": "Rrs_469",
456
+ "Remote sensing reflectance at band 488nm": "Rrs_488",
457
+ "Remote sensing reflectance at band 531nm": "Rrs_531",
458
+ "Remote sensing reflectance at band 547nm": "Rrs_547",
459
+ "Remote sensing reflectance at band 555nm": "Rrs_555",
460
+ "Remote sensing reflectance at band 645nm": "Rrs_645",
461
+ "Remote sensing reflectance at band 667nm": "Rrs_667",
462
+ "Remote sensing reflectance at band 678nm": "Rrs_678",
463
+ }
464
+
465
+ band_options = list(band_dict.keys())
466
+ band = st.selectbox(
467
+ "Select a band",
468
+ band_options,
469
+ band_options.index("Sea surface temperature"),
470
+ )
471
+ st.session_state["band"] = band_dict[band]
472
+
473
+ colors = cm.list_colormaps()
474
+ palette_options = st.selectbox(
475
+ "Color palette",
476
+ colors,
477
+ index=colors.index("coolwarm"),
478
+ )
479
+ palette_values = cm.get_palette(palette_options, 15)
480
+ palette = st.text_area(
481
+ "Enter a custom palette:",
482
+ palette_values,
483
+ )
484
+ st.write(cm.plot_colormap(cmap=palette_options, return_fig=True))
485
+ st.session_state["palette"] = eval(palette)
486
+
487
+ sample_roi = st.selectbox(
488
+ "Select a sample ROI or upload a GeoJSON file:",
489
+ roi_options,
490
+ index=0,
491
+ )
492
+
493
+ add_outline = st.checkbox(
494
+ "Overlay an administrative boundary on timelapse", False
495
+ )
496
+
497
+ if add_outline:
498
+
499
+ with st.expander("Customize administrative boundary", True):
500
+
501
+ overlay_options = {
502
+ "User-defined": None,
503
+ "Continents": "continents",
504
+ "Countries": "countries",
505
+ "US States": "us_states",
506
+ "China": "china",
507
+ }
508
+
509
+ overlay = st.selectbox(
510
+ "Select an administrative boundary:",
511
+ list(overlay_options.keys()),
512
+ index=2,
513
+ )
514
+
515
+ overlay_data = overlay_options[overlay]
516
+
517
+ if overlay_data is None:
518
+ overlay_data = st.text_input(
519
+ "Enter an HTTP URL to a GeoJSON file or an ee.FeatureCollection asset id:",
520
+ "https://raw.githubusercontent.com/giswqs/geemap/master/examples/data/countries.geojson",
521
+ )
522
+
523
+ overlay_color = st.color_picker(
524
+ "Select a color for the administrative boundary:", "#000000"
525
+ )
526
+ overlay_width = st.slider(
527
+ "Select a line width for the administrative boundary:", 1, 20, 1
528
+ )
529
+ overlay_opacity = st.slider(
530
+ "Select an opacity for the administrative boundary:",
531
+ 0.0,
532
+ 1.0,
533
+ 1.0,
534
+ 0.05,
535
+ )
536
+ else:
537
+ overlay_data = None
538
+ overlay_color = "black"
539
+ overlay_width = 1
540
+ overlay_opacity = 1
541
+
542
+ with row1_col1:
543
+
544
+ with st.expander(
545
+ "Steps: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Expand this tab to see a demo πŸ‘‰"
546
+ ):
547
+ video_empty = st.empty()
548
+
549
+ data = st.file_uploader(
550
+ "Upload a GeoJSON file to use as an ROI. Customize timelapse parameters and then click the Submit button πŸ˜‡πŸ‘‡",
551
+ type=["geojson", "kml", "zip"],
552
+ )
553
+
554
+ crs = "epsg:4326"
555
+ if sample_roi == "Uploaded GeoJSON":
556
+ if data is None:
557
+ # st.info(
558
+ # "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click Submit button"
559
+ # )
560
+ if collection in [
561
+ "Geostationary Operational Environmental Satellites (GOES)",
562
+ "USDA National Agriculture Imagery Program (NAIP)",
563
+ ] and (not keyword):
564
+ m.set_center(-100, 40, 3)
565
+ # else:
566
+ # m.set_center(4.20, 18.63, zoom=2)
567
+ else:
568
+ if collection in [
569
+ "Landsat TM-ETM-OLI Surface Reflectance",
570
+ "Sentinel-2 MSI Surface Reflectance",
571
+ ]:
572
+ gdf = gpd.GeoDataFrame(
573
+ index=[0], crs=crs, geometry=[landsat_rois[sample_roi]]
574
+ )
575
+ elif (
576
+ collection
577
+ == "Geostationary Operational Environmental Satellites (GOES)"
578
+ ):
579
+ gdf = gpd.GeoDataFrame(
580
+ index=[0], crs=crs, geometry=[goes_rois[sample_roi]["region"]]
581
+ )
582
+ elif collection == "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km":
583
+ gdf = gpd.GeoDataFrame(
584
+ index=[0], crs=crs, geometry=[modis_rois[sample_roi]]
585
+ )
586
+
587
+ if sample_roi != "Uploaded GeoJSON":
588
+
589
+ if collection in [
590
+ "Landsat TM-ETM-OLI Surface Reflectance",
591
+ "Sentinel-2 MSI Surface Reflectance",
592
+ ]:
593
+ gdf = gpd.GeoDataFrame(
594
+ index=[0], crs=crs, geometry=[landsat_rois[sample_roi]]
595
+ )
596
+ elif (
597
+ collection
598
+ == "Geostationary Operational Environmental Satellites (GOES)"
599
+ ):
600
+ gdf = gpd.GeoDataFrame(
601
+ index=[0], crs=crs, geometry=[goes_rois[sample_roi]["region"]]
602
+ )
603
+ elif collection in [
604
+ "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km",
605
+ "MODIS Gap filled Land Surface Temperature Daily",
606
+ ]:
607
+ gdf = gpd.GeoDataFrame(
608
+ index=[0], crs=crs, geometry=[modis_rois[sample_roi]]
609
+ )
610
+ elif collection == "MODIS Ocean Color SMI":
611
+ gdf = gpd.GeoDataFrame(
612
+ index=[0], crs=crs, geometry=[ocean_rois[sample_roi]]
613
+ )
614
+ st.session_state["roi"] = geemap.geopandas_to_ee(gdf, geodesic=False)
615
+ m.add_gdf(gdf, "ROI")
616
+
617
+ elif data:
618
+ gdf = uploaded_file_to_gdf(data)
619
+ st.session_state["roi"] = geemap.geopandas_to_ee(gdf, geodesic=False)
620
+ m.add_gdf(gdf, "ROI")
621
+
622
+ m.to_streamlit(height=600)
623
+
624
+ with row1_col2:
625
+
626
+ if collection in [
627
+ "Landsat TM-ETM-OLI Surface Reflectance",
628
+ "Sentinel-2 MSI Surface Reflectance",
629
+ ]:
630
+
631
+ if collection == "Landsat TM-ETM-OLI Surface Reflectance":
632
+ sensor_start_year = 1984
633
+ timelapse_title = "Landsat Timelapse"
634
+ timelapse_speed = 5
635
+ elif collection == "Sentinel-2 MSI Surface Reflectance":
636
+ sensor_start_year = 2015
637
+ timelapse_title = "Sentinel-2 Timelapse"
638
+ timelapse_speed = 5
639
+ video_empty.video("https://youtu.be/VVRK_-dEjR4")
640
+
641
+ with st.form("submit_landsat_form"):
642
+
643
+ roi = None
644
+ if st.session_state.get("roi") is not None:
645
+ roi = st.session_state.get("roi")
646
+ out_gif = geemap.temp_file_path(".gif")
647
+
648
+ title = st.text_input(
649
+ "Enter a title to show on the timelapse: ", timelapse_title
650
+ )
651
+ RGB = st.selectbox(
652
+ "Select an RGB band combination:",
653
+ [
654
+ "Red/Green/Blue",
655
+ "NIR/Red/Green",
656
+ "SWIR2/SWIR1/NIR",
657
+ "NIR/SWIR1/Red",
658
+ "SWIR2/NIR/Red",
659
+ "SWIR2/SWIR1/Red",
660
+ "SWIR1/NIR/Blue",
661
+ "NIR/SWIR1/Blue",
662
+ "SWIR2/NIR/Green",
663
+ "SWIR1/NIR/Red",
664
+ "SWIR2/NIR/SWIR1",
665
+ "SWIR1/NIR/SWIR2",
666
+ ],
667
+ index=9,
668
+ )
669
+
670
+ frequency = st.selectbox(
671
+ "Select a temporal frequency:",
672
+ ["year", "quarter", "month"],
673
+ index=0,
674
+ )
675
+
676
+ with st.expander("Customize timelapse"):
677
+
678
+ speed = st.slider("Frames per second:", 1, 30, timelapse_speed)
679
+ dimensions = st.slider(
680
+ "Maximum dimensions (Width*Height) in pixels", 768, 2000, 768
681
+ )
682
+ progress_bar_color = st.color_picker(
683
+ "Progress bar color:", "#0000ff"
684
+ )
685
+ years = st.slider(
686
+ "Start and end year:",
687
+ sensor_start_year,
688
+ today.year,
689
+ (sensor_start_year, today.year),
690
+ )
691
+ months = st.slider("Start and end month:", 1, 12, (1, 12))
692
+ font_size = st.slider("Font size:", 10, 50, 30)
693
+ font_color = st.color_picker("Font color:", "#ffffff")
694
+ apply_fmask = st.checkbox(
695
+ "Apply fmask (remove clouds, shadows, snow)", True
696
+ )
697
+ font_type = st.selectbox(
698
+ "Select the font type for the title:",
699
+ ["arial.ttf", "alibaba.otf"],
700
+ index=0,
701
+ )
702
+ fading = st.slider(
703
+ "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
704
+ )
705
+ mp4 = st.checkbox("Save timelapse as MP4", True)
706
+
707
+ empty_text = st.empty()
708
+ empty_image = st.empty()
709
+ empty_fire_image = st.empty()
710
+ empty_video = st.container()
711
+ submitted = st.form_submit_button("Submit")
712
+ if submitted:
713
+
714
+ if sample_roi == "Uploaded GeoJSON" and data is None:
715
+ empty_text.warning(
716
+ "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Alternatively, you can select a sample ROI from the dropdown list."
717
+ )
718
+ else:
719
+
720
+ empty_text.text("Computing... Please wait...")
721
+
722
+ start_year = years[0]
723
+ end_year = years[1]
724
+ start_date = str(months[0]).zfill(2) + "-01"
725
+ end_date = str(months[1]).zfill(2) + "-30"
726
+ bands = RGB.split("/")
727
+
728
+ try:
729
+ if collection == "Landsat TM-ETM-OLI Surface Reflectance":
730
+ out_gif = geemap.landsat_timelapse(
731
+ roi=roi,
732
+ out_gif=out_gif,
733
+ start_year=start_year,
734
+ end_year=end_year,
735
+ start_date=start_date,
736
+ end_date=end_date,
737
+ bands=bands,
738
+ apply_fmask=apply_fmask,
739
+ frames_per_second=speed,
740
+ dimensions=dimensions,
741
+ overlay_data=overlay_data,
742
+ overlay_color=overlay_color,
743
+ overlay_width=overlay_width,
744
+ overlay_opacity=overlay_opacity,
745
+ frequency=frequency,
746
+ date_format=None,
747
+ title=title,
748
+ title_xy=("2%", "90%"),
749
+ add_text=True,
750
+ text_xy=("2%", "2%"),
751
+ text_sequence=None,
752
+ font_type=font_type,
753
+ font_size=font_size,
754
+ font_color=font_color,
755
+ add_progress_bar=True,
756
+ progress_bar_color=progress_bar_color,
757
+ progress_bar_height=5,
758
+ loop=0,
759
+ mp4=mp4,
760
+ fading=fading,
761
+ )
762
+ elif collection == "Sentinel-2 MSI Surface Reflectance":
763
+ out_gif = geemap.sentinel2_timelapse(
764
+ roi=roi,
765
+ out_gif=out_gif,
766
+ start_year=start_year,
767
+ end_year=end_year,
768
+ start_date=start_date,
769
+ end_date=end_date,
770
+ bands=bands,
771
+ apply_fmask=apply_fmask,
772
+ frames_per_second=speed,
773
+ dimensions=dimensions,
774
+ overlay_data=overlay_data,
775
+ overlay_color=overlay_color,
776
+ overlay_width=overlay_width,
777
+ overlay_opacity=overlay_opacity,
778
+ frequency=frequency,
779
+ date_format=None,
780
+ title=title,
781
+ title_xy=("2%", "90%"),
782
+ add_text=True,
783
+ text_xy=("2%", "2%"),
784
+ text_sequence=None,
785
+ font_type=font_type,
786
+ font_size=font_size,
787
+ font_color=font_color,
788
+ add_progress_bar=True,
789
+ progress_bar_color=progress_bar_color,
790
+ progress_bar_height=5,
791
+ loop=0,
792
+ mp4=mp4,
793
+ fading=fading,
794
+ )
795
+ except:
796
+ empty_text.error(
797
+ "An error occurred while computing the timelapse. Your probably requested too much data. Try reducing the ROI or timespan."
798
+ )
799
+ st.stop()
800
+
801
+ if out_gif is not None and os.path.exists(out_gif):
802
+
803
+ empty_text.text(
804
+ "Right click the GIF to save it to your computerπŸ‘‡"
805
+ )
806
+ empty_image.image(out_gif)
807
+
808
+ out_mp4 = out_gif.replace(".gif", ".mp4")
809
+ if mp4 and os.path.exists(out_mp4):
810
+ with empty_video:
811
+ st.text(
812
+ "Right click the MP4 to save it to your computerπŸ‘‡"
813
+ )
814
+ st.video(out_gif.replace(".gif", ".mp4"))
815
+
816
+ else:
817
+ empty_text.error(
818
+ "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
819
+ )
820
+
821
+ elif collection == "Geostationary Operational Environmental Satellites (GOES)":
822
+
823
+ video_empty.video("https://youtu.be/16fA2QORG4A")
824
+
825
+ with st.form("submit_goes_form"):
826
+
827
+ roi = None
828
+ if st.session_state.get("roi") is not None:
829
+ roi = st.session_state.get("roi")
830
+ out_gif = geemap.temp_file_path(".gif")
831
+
832
+ satellite = st.selectbox("Select a satellite:", ["GOES-17", "GOES-16"])
833
+ earliest_date = datetime.date(2017, 7, 10)
834
+ latest_date = datetime.date.today()
835
+
836
+ if sample_roi == "Uploaded GeoJSON":
837
+ roi_start_date = today - datetime.timedelta(days=2)
838
+ roi_end_date = today - datetime.timedelta(days=1)
839
+ roi_start_time = datetime.time(14, 00)
840
+ roi_end_time = datetime.time(1, 00)
841
+ else:
842
+ roi_start = goes_rois[sample_roi]["start_time"]
843
+ roi_end = goes_rois[sample_roi]["end_time"]
844
+ roi_start_date = datetime.datetime.strptime(
845
+ roi_start[:10], "%Y-%m-%d"
846
+ )
847
+ roi_end_date = datetime.datetime.strptime(roi_end[:10], "%Y-%m-%d")
848
+ roi_start_time = datetime.time(
849
+ int(roi_start[11:13]), int(roi_start[14:16])
850
+ )
851
+ roi_end_time = datetime.time(
852
+ int(roi_end[11:13]), int(roi_end[14:16])
853
+ )
854
+
855
+ start_date = st.date_input("Select the start date:", roi_start_date)
856
+ end_date = st.date_input("Select the end date:", roi_end_date)
857
+
858
+ with st.expander("Customize timelapse"):
859
+
860
+ add_fire = st.checkbox("Add Fire/Hotspot Characterization", False)
861
+
862
+ scan_type = st.selectbox(
863
+ "Select a scan type:", ["Full Disk", "CONUS", "Mesoscale"]
864
+ )
865
+
866
+ start_time = st.time_input(
867
+ "Select the start time of the start date:", roi_start_time
868
+ )
869
+
870
+ end_time = st.time_input(
871
+ "Select the end time of the end date:", roi_end_time
872
+ )
873
+
874
+ start = (
875
+ start_date.strftime("%Y-%m-%d")
876
+ + "T"
877
+ + start_time.strftime("%H:%M:%S")
878
+ )
879
+ end = (
880
+ end_date.strftime("%Y-%m-%d")
881
+ + "T"
882
+ + end_time.strftime("%H:%M:%S")
883
+ )
884
+
885
+ speed = st.slider("Frames per second:", 1, 30, 5)
886
+ add_progress_bar = st.checkbox("Add a progress bar", True)
887
+ progress_bar_color = st.color_picker(
888
+ "Progress bar color:", "#0000ff"
889
+ )
890
+ font_size = st.slider("Font size:", 10, 50, 20)
891
+ font_color = st.color_picker("Font color:", "#ffffff")
892
+ fading = st.slider(
893
+ "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
894
+ )
895
+ mp4 = st.checkbox("Save timelapse as MP4", True)
896
+
897
+ empty_text = st.empty()
898
+ empty_image = st.empty()
899
+ empty_video = st.container()
900
+ empty_fire_text = st.empty()
901
+ empty_fire_image = st.empty()
902
+
903
+ submitted = st.form_submit_button("Submit")
904
+ if submitted:
905
+ if sample_roi == "Uploaded GeoJSON" and data is None:
906
+ empty_text.warning(
907
+ "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Alternatively, you can select a sample ROI from the dropdown list."
908
+ )
909
+ else:
910
+ empty_text.text("Computing... Please wait...")
911
+
912
+ geemap.goes_timelapse(
913
+ out_gif,
914
+ start_date=start,
915
+ end_date=end,
916
+ data=satellite,
917
+ scan=scan_type.replace(" ", "_").lower(),
918
+ region=roi,
919
+ dimensions=768,
920
+ framesPerSecond=speed,
921
+ date_format="YYYY-MM-dd HH:mm",
922
+ xy=("3%", "3%"),
923
+ text_sequence=None,
924
+ font_type="arial.ttf",
925
+ font_size=font_size,
926
+ font_color=font_color,
927
+ add_progress_bar=add_progress_bar,
928
+ progress_bar_color=progress_bar_color,
929
+ progress_bar_height=5,
930
+ loop=0,
931
+ overlay_data=overlay_data,
932
+ overlay_color=overlay_color,
933
+ overlay_width=overlay_width,
934
+ overlay_opacity=overlay_opacity,
935
+ mp4=mp4,
936
+ fading=fading,
937
+ )
938
+
939
+ if out_gif is not None and os.path.exists(out_gif):
940
+ empty_text.text(
941
+ "Right click the GIF to save it to your computerπŸ‘‡"
942
+ )
943
+ empty_image.image(out_gif)
944
+
945
+ out_mp4 = out_gif.replace(".gif", ".mp4")
946
+ if mp4 and os.path.exists(out_mp4):
947
+ with empty_video:
948
+ st.text(
949
+ "Right click the MP4 to save it to your computerπŸ‘‡"
950
+ )
951
+ st.video(out_gif.replace(".gif", ".mp4"))
952
+
953
+ if add_fire:
954
+ out_fire_gif = geemap.temp_file_path(".gif")
955
+ empty_fire_text.text(
956
+ "Delineating Fire Hotspot... Please wait..."
957
+ )
958
+ geemap.goes_fire_timelapse(
959
+ out_fire_gif,
960
+ start_date=start,
961
+ end_date=end,
962
+ data=satellite,
963
+ scan=scan_type.replace(" ", "_").lower(),
964
+ region=roi,
965
+ dimensions=768,
966
+ framesPerSecond=speed,
967
+ date_format="YYYY-MM-dd HH:mm",
968
+ xy=("3%", "3%"),
969
+ text_sequence=None,
970
+ font_type="arial.ttf",
971
+ font_size=font_size,
972
+ font_color=font_color,
973
+ add_progress_bar=add_progress_bar,
974
+ progress_bar_color=progress_bar_color,
975
+ progress_bar_height=5,
976
+ loop=0,
977
+ )
978
+ if os.path.exists(out_fire_gif):
979
+ empty_fire_image.image(out_fire_gif)
980
+ else:
981
+ empty_text.text(
982
+ "Something went wrong, either the ROI is too big or there are no data available for the specified date range. Please try a smaller ROI or different date range."
983
+ )
984
+
985
+ elif collection == "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km":
986
+
987
+ video_empty.video("https://youtu.be/16fA2QORG4A")
988
+
989
+ satellite = st.selectbox("Select a satellite:", ["Terra", "Aqua"])
990
+ band = st.selectbox("Select a band:", ["NDVI", "EVI"])
991
+
992
+ with st.form("submit_modis_form"):
993
+
994
+ roi = None
995
+ if st.session_state.get("roi") is not None:
996
+ roi = st.session_state.get("roi")
997
+ out_gif = geemap.temp_file_path(".gif")
998
+
999
+ with st.expander("Customize timelapse"):
1000
+
1001
+ start = st.date_input(
1002
+ "Select a start date:", datetime.date(2000, 2, 8)
1003
+ )
1004
+ end = st.date_input("Select an end date:", datetime.date.today())
1005
+
1006
+ start_date = start.strftime("%Y-%m-%d")
1007
+ end_date = end.strftime("%Y-%m-%d")
1008
+
1009
+ speed = st.slider("Frames per second:", 1, 30, 5)
1010
+ add_progress_bar = st.checkbox("Add a progress bar", True)
1011
+ progress_bar_color = st.color_picker(
1012
+ "Progress bar color:", "#0000ff"
1013
+ )
1014
+ font_size = st.slider("Font size:", 10, 50, 20)
1015
+ font_color = st.color_picker("Font color:", "#ffffff")
1016
+
1017
+ font_type = st.selectbox(
1018
+ "Select the font type for the title:",
1019
+ ["arial.ttf", "alibaba.otf"],
1020
+ index=0,
1021
+ )
1022
+ fading = st.slider(
1023
+ "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1024
+ )
1025
+ mp4 = st.checkbox("Save timelapse as MP4", True)
1026
+
1027
+ empty_text = st.empty()
1028
+ empty_image = st.empty()
1029
+ empty_video = st.container()
1030
+
1031
+ submitted = st.form_submit_button("Submit")
1032
+ if submitted:
1033
+ if sample_roi == "Uploaded GeoJSON" and data is None:
1034
+ empty_text.warning(
1035
+ "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Alternatively, you can select a sample ROI from the dropdown list."
1036
+ )
1037
+ else:
1038
+
1039
+ empty_text.text("Computing... Please wait...")
1040
+
1041
+ geemap.modis_ndvi_timelapse(
1042
+ out_gif,
1043
+ satellite,
1044
+ band,
1045
+ start_date,
1046
+ end_date,
1047
+ roi,
1048
+ 768,
1049
+ speed,
1050
+ overlay_data=overlay_data,
1051
+ overlay_color=overlay_color,
1052
+ overlay_width=overlay_width,
1053
+ overlay_opacity=overlay_opacity,
1054
+ mp4=mp4,
1055
+ fading=fading,
1056
+ )
1057
+
1058
+ geemap.reduce_gif_size(out_gif)
1059
+
1060
+ empty_text.text(
1061
+ "Right click the GIF to save it to your computerπŸ‘‡"
1062
+ )
1063
+ empty_image.image(out_gif)
1064
+
1065
+ out_mp4 = out_gif.replace(".gif", ".mp4")
1066
+ if mp4 and os.path.exists(out_mp4):
1067
+ with empty_video:
1068
+ st.text(
1069
+ "Right click the MP4 to save it to your computerπŸ‘‡"
1070
+ )
1071
+ st.video(out_gif.replace(".gif", ".mp4"))
1072
+
1073
+ elif collection == "Any Earth Engine ImageCollection":
1074
+
1075
+ with st.form("submit_ts_form"):
1076
+ with st.expander("Customize timelapse"):
1077
+
1078
+ title = st.text_input(
1079
+ "Enter a title to show on the timelapse: ", "Timelapse"
1080
+ )
1081
+ start_date = st.date_input(
1082
+ "Select the start date:", datetime.date(2020, 1, 1)
1083
+ )
1084
+ end_date = st.date_input(
1085
+ "Select the end date:", datetime.date.today()
1086
+ )
1087
+ frequency = st.selectbox(
1088
+ "Select a temporal frequency:",
1089
+ ["year", "quarter", "month", "day", "hour", "minute", "second"],
1090
+ index=0,
1091
+ )
1092
+ reducer = st.selectbox(
1093
+ "Select a reducer for aggregating data:",
1094
+ ["median", "mean", "min", "max", "sum", "variance", "stdDev"],
1095
+ index=0,
1096
+ )
1097
+ data_format = st.selectbox(
1098
+ "Select a date format to show on the timelapse:",
1099
+ [
1100
+ "YYYY-MM-dd",
1101
+ "YYYY",
1102
+ "YYMM-MM",
1103
+ "YYYY-MM-dd HH:mm",
1104
+ "YYYY-MM-dd HH:mm:ss",
1105
+ "HH:mm",
1106
+ "HH:mm:ss",
1107
+ "w",
1108
+ "M",
1109
+ "d",
1110
+ "D",
1111
+ ],
1112
+ index=0,
1113
+ )
1114
+
1115
+ speed = st.slider("Frames per second:", 1, 30, 5)
1116
+ add_progress_bar = st.checkbox("Add a progress bar", True)
1117
+ progress_bar_color = st.color_picker(
1118
+ "Progress bar color:", "#0000ff"
1119
+ )
1120
+ font_size = st.slider("Font size:", 10, 50, 30)
1121
+ font_color = st.color_picker("Font color:", "#ffffff")
1122
+ font_type = st.selectbox(
1123
+ "Select the font type for the title:",
1124
+ ["arial.ttf", "alibaba.otf"],
1125
+ index=0,
1126
+ )
1127
+ fading = st.slider(
1128
+ "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1129
+ )
1130
+ mp4 = st.checkbox("Save timelapse as MP4", True)
1131
+
1132
+ empty_text = st.empty()
1133
+ empty_image = st.empty()
1134
+ empty_video = st.container()
1135
+ empty_fire_image = st.empty()
1136
+
1137
+ roi = None
1138
+ if st.session_state.get("roi") is not None:
1139
+ roi = st.session_state.get("roi")
1140
+ out_gif = geemap.temp_file_path(".gif")
1141
+
1142
+ submitted = st.form_submit_button("Submit")
1143
+ if submitted:
1144
+
1145
+ if sample_roi == "Uploaded GeoJSON" and data is None:
1146
+ empty_text.warning(
1147
+ "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Alternatively, you can select a sample ROI from the dropdown list."
1148
+ )
1149
+ else:
1150
+
1151
+ empty_text.text("Computing... Please wait...")
1152
+ try:
1153
+ geemap.create_timelapse(
1154
+ st.session_state.get("ee_asset_id"),
1155
+ start_date=start_date.strftime("%Y-%m-%d"),
1156
+ end_date=end_date.strftime("%Y-%m-%d"),
1157
+ region=roi,
1158
+ frequency=frequency,
1159
+ reducer=reducer,
1160
+ date_format=data_format,
1161
+ out_gif=out_gif,
1162
+ bands=st.session_state.get("bands"),
1163
+ palette=st.session_state.get("palette"),
1164
+ vis_params=st.session_state.get("vis_params"),
1165
+ dimensions=768,
1166
+ frames_per_second=speed,
1167
+ crs="EPSG:3857",
1168
+ overlay_data=overlay_data,
1169
+ overlay_color=overlay_color,
1170
+ overlay_width=overlay_width,
1171
+ overlay_opacity=overlay_opacity,
1172
+ title=title,
1173
+ title_xy=("2%", "90%"),
1174
+ add_text=True,
1175
+ text_xy=("2%", "2%"),
1176
+ text_sequence=None,
1177
+ font_type=font_type,
1178
+ font_size=font_size,
1179
+ font_color=font_color,
1180
+ add_progress_bar=add_progress_bar,
1181
+ progress_bar_color=progress_bar_color,
1182
+ progress_bar_height=5,
1183
+ loop=0,
1184
+ mp4=mp4,
1185
+ fading=fading,
1186
+ )
1187
+ except:
1188
+ empty_text.error(
1189
+ "An error occurred while computing the timelapse. You probably requested too much data. Try reducing the ROI or timespan."
1190
+ )
1191
+
1192
+ empty_text.text(
1193
+ "Right click the GIF to save it to your computerπŸ‘‡"
1194
+ )
1195
+ empty_image.image(out_gif)
1196
+
1197
+ out_mp4 = out_gif.replace(".gif", ".mp4")
1198
+ if mp4 and os.path.exists(out_mp4):
1199
+ with empty_video:
1200
+ st.text(
1201
+ "Right click the MP4 to save it to your computerπŸ‘‡"
1202
+ )
1203
+ st.video(out_gif.replace(".gif", ".mp4"))
1204
+
1205
+ elif collection in [
1206
+ "MODIS Gap filled Land Surface Temperature Daily",
1207
+ "MODIS Ocean Color SMI",
1208
+ ]:
1209
+
1210
+ with st.form("submit_ts_form"):
1211
+ with st.expander("Customize timelapse"):
1212
+
1213
+ title = st.text_input(
1214
+ "Enter a title to show on the timelapse: ",
1215
+ "Surface Temperature",
1216
+ )
1217
+ start_date = st.date_input(
1218
+ "Select the start date:", datetime.date(2018, 1, 1)
1219
+ )
1220
+ end_date = st.date_input(
1221
+ "Select the end date:", datetime.date(2020, 12, 31)
1222
+ )
1223
+ frequency = st.selectbox(
1224
+ "Select a temporal frequency:",
1225
+ ["year", "quarter", "month", "week", "day"],
1226
+ index=2,
1227
+ )
1228
+ reducer = st.selectbox(
1229
+ "Select a reducer for aggregating data:",
1230
+ ["median", "mean", "min", "max", "sum", "variance", "stdDev"],
1231
+ index=0,
1232
+ )
1233
+
1234
+ vis_params = st.text_area(
1235
+ "Enter visualization parameters",
1236
+ "",
1237
+ help="Enter a string in the format of a dictionary, such as '{'min': 23, 'max': 32}'",
1238
+ )
1239
+
1240
+ speed = st.slider("Frames per second:", 1, 30, 5)
1241
+ add_progress_bar = st.checkbox("Add a progress bar", True)
1242
+ progress_bar_color = st.color_picker(
1243
+ "Progress bar color:", "#0000ff"
1244
+ )
1245
+ font_size = st.slider("Font size:", 10, 50, 30)
1246
+ font_color = st.color_picker("Font color:", "#ffffff")
1247
+ font_type = st.selectbox(
1248
+ "Select the font type for the title:",
1249
+ ["arial.ttf", "alibaba.otf"],
1250
+ index=0,
1251
+ )
1252
+ add_colorbar = st.checkbox("Add a colorbar", True)
1253
+ colorbar_label = st.text_input(
1254
+ "Enter the colorbar label:", "Surface Temperature (Β°C)"
1255
+ )
1256
+ fading = st.slider(
1257
+ "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1258
+ )
1259
+ mp4 = st.checkbox("Save timelapse as MP4", True)
1260
+
1261
+ empty_text = st.empty()
1262
+ empty_image = st.empty()
1263
+ empty_video = st.container()
1264
+
1265
+ roi = None
1266
+ if st.session_state.get("roi") is not None:
1267
+ roi = st.session_state.get("roi")
1268
+ out_gif = geemap.temp_file_path(".gif")
1269
+
1270
+ submitted = st.form_submit_button("Submit")
1271
+ if submitted:
1272
+
1273
+ if sample_roi == "Uploaded GeoJSON" and data is None:
1274
+ empty_text.warning(
1275
+ "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Alternatively, you can select a sample ROI from the dropdown list."
1276
+ )
1277
+ else:
1278
+
1279
+ empty_text.text("Computing... Please wait...")
1280
+ try:
1281
+ if (
1282
+ collection
1283
+ == "MODIS Gap filled Land Surface Temperature Daily"
1284
+ ):
1285
+ out_gif = geemap.create_timelapse(
1286
+ st.session_state.get("ee_asset_id"),
1287
+ start_date=start_date.strftime("%Y-%m-%d"),
1288
+ end_date=end_date.strftime("%Y-%m-%d"),
1289
+ region=roi,
1290
+ bands=None,
1291
+ frequency=frequency,
1292
+ reducer=reducer,
1293
+ date_format=None,
1294
+ out_gif=out_gif,
1295
+ palette=st.session_state.get("palette"),
1296
+ vis_params=None,
1297
+ dimensions=768,
1298
+ frames_per_second=speed,
1299
+ crs="EPSG:3857",
1300
+ overlay_data=overlay_data,
1301
+ overlay_color=overlay_color,
1302
+ overlay_width=overlay_width,
1303
+ overlay_opacity=overlay_opacity,
1304
+ title=title,
1305
+ title_xy=("2%", "90%"),
1306
+ add_text=True,
1307
+ text_xy=("2%", "2%"),
1308
+ text_sequence=None,
1309
+ font_type=font_type,
1310
+ font_size=font_size,
1311
+ font_color=font_color,
1312
+ add_progress_bar=add_progress_bar,
1313
+ progress_bar_color=progress_bar_color,
1314
+ progress_bar_height=5,
1315
+ add_colorbar=add_colorbar,
1316
+ colorbar_label=colorbar_label,
1317
+ loop=0,
1318
+ mp4=mp4,
1319
+ fading=fading,
1320
+ )
1321
+ elif collection == "MODIS Ocean Color SMI":
1322
+ if vis_params.startswith("{") and vis_params.endswith(
1323
+ "}"
1324
+ ):
1325
+ vis_params = eval(vis_params)
1326
+ else:
1327
+ vis_params = None
1328
+ out_gif = geemap.modis_ocean_color_timelapse(
1329
+ st.session_state.get("ee_asset_id"),
1330
+ start_date=start_date.strftime("%Y-%m-%d"),
1331
+ end_date=end_date.strftime("%Y-%m-%d"),
1332
+ region=roi,
1333
+ bands=st.session_state["band"],
1334
+ frequency=frequency,
1335
+ reducer=reducer,
1336
+ date_format=None,
1337
+ out_gif=out_gif,
1338
+ palette=st.session_state.get("palette"),
1339
+ vis_params=vis_params,
1340
+ dimensions=768,
1341
+ frames_per_second=speed,
1342
+ crs="EPSG:3857",
1343
+ overlay_data=overlay_data,
1344
+ overlay_color=overlay_color,
1345
+ overlay_width=overlay_width,
1346
+ overlay_opacity=overlay_opacity,
1347
+ title=title,
1348
+ title_xy=("2%", "90%"),
1349
+ add_text=True,
1350
+ text_xy=("2%", "2%"),
1351
+ text_sequence=None,
1352
+ font_type=font_type,
1353
+ font_size=font_size,
1354
+ font_color=font_color,
1355
+ add_progress_bar=add_progress_bar,
1356
+ progress_bar_color=progress_bar_color,
1357
+ progress_bar_height=5,
1358
+ add_colorbar=add_colorbar,
1359
+ colorbar_label=colorbar_label,
1360
+ loop=0,
1361
+ mp4=mp4,
1362
+ fading=fading,
1363
+ )
1364
+ except:
1365
+ empty_text.error(
1366
+ "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
1367
+ )
1368
+
1369
+ if out_gif is not None and os.path.exists(out_gif):
1370
+
1371
+ geemap.reduce_gif_size(out_gif)
1372
+
1373
+ empty_text.text(
1374
+ "Right click the GIF to save it to your computerπŸ‘‡"
1375
+ )
1376
+ empty_image.image(out_gif)
1377
+
1378
+ out_mp4 = out_gif.replace(".gif", ".mp4")
1379
+ if mp4 and os.path.exists(out_mp4):
1380
+ with empty_video:
1381
+ st.text(
1382
+ "Right click the MP4 to save it to your computerπŸ‘‡"
1383
+ )
1384
+ st.video(out_gif.replace(".gif", ".mp4"))
1385
+
1386
+ else:
1387
+ st.error(
1388
+ "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
1389
+ )
1390
+
1391
+ elif collection == "USDA National Agriculture Imagery Program (NAIP)":
1392
+
1393
+ with st.form("submit_naip_form"):
1394
+ with st.expander("Customize timelapse"):
1395
+
1396
+ title = st.text_input(
1397
+ "Enter a title to show on the timelapse: ", "NAIP Timelapse"
1398
+ )
1399
+
1400
+ years = st.slider(
1401
+ "Start and end year:",
1402
+ 2003,
1403
+ today.year,
1404
+ (2003, today.year),
1405
+ )
1406
+
1407
+ bands = st.selectbox(
1408
+ "Select a band combination:", ["N/R/G", "R/G/B"], index=0
1409
+ )
1410
+
1411
+ speed = st.slider("Frames per second:", 1, 30, 3)
1412
+ add_progress_bar = st.checkbox("Add a progress bar", True)
1413
+ progress_bar_color = st.color_picker(
1414
+ "Progress bar color:", "#0000ff"
1415
+ )
1416
+ font_size = st.slider("Font size:", 10, 50, 30)
1417
+ font_color = st.color_picker("Font color:", "#ffffff")
1418
+ font_type = st.selectbox(
1419
+ "Select the font type for the title:",
1420
+ ["arial.ttf", "alibaba.otf"],
1421
+ index=0,
1422
+ )
1423
+ fading = st.slider(
1424
+ "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1425
+ )
1426
+ mp4 = st.checkbox("Save timelapse as MP4", True)
1427
+
1428
+ empty_text = st.empty()
1429
+ empty_image = st.empty()
1430
+ empty_video = st.container()
1431
+ empty_fire_image = st.empty()
1432
+
1433
+ roi = None
1434
+ if st.session_state.get("roi") is not None:
1435
+ roi = st.session_state.get("roi")
1436
+ out_gif = geemap.temp_file_path(".gif")
1437
+
1438
+ submitted = st.form_submit_button("Submit")
1439
+ if submitted:
1440
+
1441
+ if sample_roi == "Uploaded GeoJSON" and data is None:
1442
+ empty_text.warning(
1443
+ "Steps to create a timelapse: Draw a rectangle on the map -> Export it as a GeoJSON -> Upload it back to the app -> Click the Submit button. Alternatively, you can select a sample ROI from the dropdown list."
1444
+ )
1445
+ else:
1446
+
1447
+ empty_text.text("Computing... Please wait...")
1448
+ try:
1449
+ geemap.naip_timelapse(
1450
+ roi,
1451
+ years[0],
1452
+ years[1],
1453
+ out_gif,
1454
+ bands=bands.split("/"),
1455
+ palette=st.session_state.get("palette"),
1456
+ vis_params=None,
1457
+ dimensions=768,
1458
+ frames_per_second=speed,
1459
+ crs="EPSG:3857",
1460
+ overlay_data=overlay_data,
1461
+ overlay_color=overlay_color,
1462
+ overlay_width=overlay_width,
1463
+ overlay_opacity=overlay_opacity,
1464
+ title=title,
1465
+ title_xy=("2%", "90%"),
1466
+ add_text=True,
1467
+ text_xy=("2%", "2%"),
1468
+ text_sequence=None,
1469
+ font_type=font_type,
1470
+ font_size=font_size,
1471
+ font_color=font_color,
1472
+ add_progress_bar=add_progress_bar,
1473
+ progress_bar_color=progress_bar_color,
1474
+ progress_bar_height=5,
1475
+ loop=0,
1476
+ mp4=mp4,
1477
+ fading=fading,
1478
+ )
1479
+ except:
1480
+ empty_text.error(
1481
+ "Something went wrong. You either requested too much data or the ROI is outside the U.S."
1482
+ )
1483
+
1484
+ if out_gif is not None and os.path.exists(out_gif):
1485
+
1486
+ empty_text.text(
1487
+ "Right click the GIF to save it to your computerπŸ‘‡"
1488
+ )
1489
+ empty_image.image(out_gif)
1490
+
1491
+ out_mp4 = out_gif.replace(".gif", ".mp4")
1492
+ if mp4 and os.path.exists(out_mp4):
1493
+ with empty_video:
1494
+ st.text(
1495
+ "Right click the MP4 to save it to your computerπŸ‘‡"
1496
+ )
1497
+ st.video(out_gif.replace(".gif", ".mp4"))
1498
+
1499
+ else:
1500
+ st.error(
1501
+ "Something went wrong. You either requested too much data or the ROI is outside the U.S."
1502
+ )
1503
+
1504
+
1505
+ app()
pages/2_🏠_U.S._Housing.py ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import os
3
+ import pathlib
4
+ import requests
5
+ import zipfile
6
+ import pandas as pd
7
+ import pydeck as pdk
8
+ import geopandas as gpd
9
+ import streamlit as st
10
+ import leafmap.colormaps as cm
11
+ from leafmap.common import hex_to_rgb
12
+
13
+ st.set_page_config(layout="wide")
14
+
15
+ st.sidebar.title("About")
16
+ st.sidebar.info(
17
+ """
18
+ Web App URL: <https://geospatial.streamlitapp.com>
19
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
20
+ """
21
+ )
22
+
23
+ st.sidebar.title("Contact")
24
+ st.sidebar.info(
25
+ """
26
+ Qiusheng Wu: <https://wetlands.io>
27
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
28
+ """
29
+ )
30
+
31
+ STREAMLIT_STATIC_PATH = pathlib.Path(st.__path__[0]) / "static"
32
+ # We create a downloads directory within the streamlit static asset directory
33
+ # and we write output files to it
34
+ DOWNLOADS_PATH = STREAMLIT_STATIC_PATH / "downloads"
35
+ if not DOWNLOADS_PATH.is_dir():
36
+ DOWNLOADS_PATH.mkdir()
37
+
38
+ # Data source: https://www.realtor.com/research/data/
39
+ # link_prefix = "https://econdata.s3-us-west-2.amazonaws.com/Reports/"
40
+ link_prefix = "https://raw.githubusercontent.com/giswqs/data/main/housing/"
41
+
42
+ data_links = {
43
+ "weekly": {
44
+ "national": link_prefix + "Core/listing_weekly_core_aggregate_by_country.csv",
45
+ "metro": link_prefix + "Core/listing_weekly_core_aggregate_by_metro.csv",
46
+ },
47
+ "monthly_current": {
48
+ "national": link_prefix + "Core/RDC_Inventory_Core_Metrics_Country.csv",
49
+ "state": link_prefix + "Core/RDC_Inventory_Core_Metrics_State.csv",
50
+ "metro": link_prefix + "Core/RDC_Inventory_Core_Metrics_Metro.csv",
51
+ "county": link_prefix + "Core/RDC_Inventory_Core_Metrics_County.csv",
52
+ "zip": link_prefix + "Core/RDC_Inventory_Core_Metrics_Zip.csv",
53
+ },
54
+ "monthly_historical": {
55
+ "national": link_prefix + "Core/RDC_Inventory_Core_Metrics_Country_History.csv",
56
+ "state": link_prefix + "Core/RDC_Inventory_Core_Metrics_State_History.csv",
57
+ "metro": link_prefix + "Core/RDC_Inventory_Core_Metrics_Metro_History.csv",
58
+ "county": link_prefix + "Core/RDC_Inventory_Core_Metrics_County_History.csv",
59
+ "zip": link_prefix + "Core/RDC_Inventory_Core_Metrics_Zip_History.csv",
60
+ },
61
+ "hotness": {
62
+ "metro": link_prefix
63
+ + "Hotness/RDC_Inventory_Hotness_Metrics_Metro_History.csv",
64
+ "county": link_prefix
65
+ + "Hotness/RDC_Inventory_Hotness_Metrics_County_History.csv",
66
+ "zip": link_prefix + "Hotness/RDC_Inventory_Hotness_Metrics_Zip_History.csv",
67
+ },
68
+ }
69
+
70
+
71
+ def get_data_columns(df, category, frequency="monthly"):
72
+ if frequency == "monthly":
73
+ if category.lower() == "county":
74
+ del_cols = ["month_date_yyyymm", "county_fips", "county_name"]
75
+ elif category.lower() == "state":
76
+ del_cols = ["month_date_yyyymm", "state", "state_id"]
77
+ elif category.lower() == "national":
78
+ del_cols = ["month_date_yyyymm", "country"]
79
+ elif category.lower() == "metro":
80
+ del_cols = ["month_date_yyyymm", "cbsa_code", "cbsa_title", "HouseholdRank"]
81
+ elif category.lower() == "zip":
82
+ del_cols = ["month_date_yyyymm", "postal_code", "zip_name", "flag"]
83
+ elif frequency == "weekly":
84
+ if category.lower() == "national":
85
+ del_cols = ["week_end_date", "geo_country"]
86
+ elif category.lower() == "metro":
87
+ del_cols = ["week_end_date", "cbsa_code", "cbsa_title", "hh_rank"]
88
+
89
+ cols = df.columns.values.tolist()
90
+
91
+ for col in cols:
92
+ if col.strip() in del_cols:
93
+ cols.remove(col)
94
+ if category.lower() == "metro":
95
+ return cols[2:]
96
+ else:
97
+ return cols[1:]
98
+
99
+
100
+ @st.cache
101
+ def get_inventory_data(url):
102
+ df = pd.read_csv(url)
103
+ url = url.lower()
104
+ if "county" in url:
105
+ df["county_fips"] = df["county_fips"].map(str)
106
+ df["county_fips"] = df["county_fips"].str.zfill(5)
107
+ elif "state" in url:
108
+ df["STUSPS"] = df["state_id"].str.upper()
109
+ elif "metro" in url:
110
+ df["cbsa_code"] = df["cbsa_code"].map(str)
111
+ elif "zip" in url:
112
+ df["postal_code"] = df["postal_code"].map(str)
113
+ df["postal_code"] = df["postal_code"].str.zfill(5)
114
+
115
+ if "listing_weekly_core_aggregate_by_country" in url:
116
+ columns = get_data_columns(df, "national", "weekly")
117
+ for column in columns:
118
+ if column != "median_days_on_market_by_day_yy":
119
+ df[column] = df[column].str.rstrip("%").astype(float) / 100
120
+ if "listing_weekly_core_aggregate_by_metro" in url:
121
+ columns = get_data_columns(df, "metro", "weekly")
122
+ for column in columns:
123
+ if column != "median_days_on_market_by_day_yy":
124
+ df[column] = df[column].str.rstrip("%").astype(float) / 100
125
+ df["cbsa_code"] = df["cbsa_code"].str[:5]
126
+ return df
127
+
128
+
129
+ def filter_weekly_inventory(df, week):
130
+ df = df[df["week_end_date"] == week]
131
+ return df
132
+
133
+
134
+ def get_start_end_year(df):
135
+ start_year = int(str(df["month_date_yyyymm"].min())[:4])
136
+ end_year = int(str(df["month_date_yyyymm"].max())[:4])
137
+ return start_year, end_year
138
+
139
+
140
+ def get_periods(df):
141
+ return [str(d) for d in list(set(df["month_date_yyyymm"].tolist()))]
142
+
143
+
144
+ @st.cache
145
+ def get_geom_data(category):
146
+
147
+ prefix = (
148
+ "https://raw.githubusercontent.com/giswqs/streamlit-geospatial/master/data/"
149
+ )
150
+ links = {
151
+ "national": prefix + "us_nation.geojson",
152
+ "state": prefix + "us_states.geojson",
153
+ "county": prefix + "us_counties.geojson",
154
+ "metro": prefix + "us_metro_areas.geojson",
155
+ "zip": "https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_zcta510_500k.zip",
156
+ }
157
+
158
+ if category.lower() == "zip":
159
+ r = requests.get(links[category])
160
+ out_zip = os.path.join(DOWNLOADS_PATH, "cb_2018_us_zcta510_500k.zip")
161
+ with open(out_zip, "wb") as code:
162
+ code.write(r.content)
163
+ zip_ref = zipfile.ZipFile(out_zip, "r")
164
+ zip_ref.extractall(DOWNLOADS_PATH)
165
+ gdf = gpd.read_file(out_zip.replace("zip", "shp"))
166
+ else:
167
+ gdf = gpd.read_file(links[category])
168
+ return gdf
169
+
170
+
171
+ def join_attributes(gdf, df, category):
172
+
173
+ new_gdf = None
174
+ if category == "county":
175
+ new_gdf = gdf.merge(df, left_on="GEOID", right_on="county_fips", how="outer")
176
+ elif category == "state":
177
+ new_gdf = gdf.merge(df, left_on="STUSPS", right_on="STUSPS", how="outer")
178
+ elif category == "national":
179
+ if "geo_country" in df.columns.values.tolist():
180
+ df["country"] = None
181
+ df.loc[0, "country"] = "United States"
182
+ new_gdf = gdf.merge(df, left_on="NAME", right_on="country", how="outer")
183
+ elif category == "metro":
184
+ new_gdf = gdf.merge(df, left_on="CBSAFP", right_on="cbsa_code", how="outer")
185
+ elif category == "zip":
186
+ new_gdf = gdf.merge(df, left_on="GEOID10", right_on="postal_code", how="outer")
187
+ return new_gdf
188
+
189
+
190
+ def select_non_null(gdf, col_name):
191
+ new_gdf = gdf[~gdf[col_name].isna()]
192
+ return new_gdf
193
+
194
+
195
+ def select_null(gdf, col_name):
196
+ new_gdf = gdf[gdf[col_name].isna()]
197
+ return new_gdf
198
+
199
+
200
+ def get_data_dict(name):
201
+ in_csv = os.path.join(os.getcwd(), "data/realtor_data_dict.csv")
202
+ df = pd.read_csv(in_csv)
203
+ label = list(df[df["Name"] == name]["Label"])[0]
204
+ desc = list(df[df["Name"] == name]["Description"])[0]
205
+ return label, desc
206
+
207
+
208
+ def get_weeks(df):
209
+ seq = list(set(df[~df["week_end_date"].isnull()]["week_end_date"].tolist()))
210
+ weeks = [
211
+ datetime.date(int(d.split("/")[2]), int(d.split("/")[0]), int(d.split("/")[1]))
212
+ for d in seq
213
+ ]
214
+ weeks.sort()
215
+ return weeks
216
+
217
+
218
+ def get_saturday(in_date):
219
+ idx = (in_date.weekday() + 1) % 7
220
+ sat = in_date + datetime.timedelta(6 - idx)
221
+ return sat
222
+
223
+
224
+ def app():
225
+
226
+ st.title("U.S. Real Estate Data and Market Trends")
227
+ st.markdown(
228
+ """**Introduction:** This interactive dashboard is designed for visualizing U.S. real estate data and market trends at multiple levels (i.e., national,
229
+ state, county, and metro). The data sources include [Real Estate Data](https://www.realtor.com/research/data) from realtor.com and
230
+ [Cartographic Boundary Files](https://www.census.gov/geographies/mapping-files/time-series/geo/carto-boundary-file.html) from U.S. Census Bureau.
231
+ Several open-source packages are used to process the data and generate the visualizations, e.g., [streamlit](https://streamlit.io),
232
+ [geopandas](https://geopandas.org), [leafmap](https://leafmap.org), and [pydeck](https://deckgl.readthedocs.io).
233
+ """
234
+ )
235
+
236
+ with st.expander("See a demo"):
237
+ st.image("https://i.imgur.com/Z3dk6Tr.gif")
238
+
239
+ row1_col1, row1_col2, row1_col3, row1_col4, row1_col5 = st.columns(
240
+ [0.6, 0.8, 0.6, 1.4, 2]
241
+ )
242
+ with row1_col1:
243
+ frequency = st.selectbox("Monthly/weekly data", ["Monthly", "Weekly"])
244
+ with row1_col2:
245
+ types = ["Current month data", "Historical data"]
246
+ if frequency == "Weekly":
247
+ types.remove("Current month data")
248
+ cur_hist = st.selectbox(
249
+ "Current/historical data",
250
+ types,
251
+ )
252
+ with row1_col3:
253
+ if frequency == "Monthly":
254
+ scale = st.selectbox(
255
+ "Scale", ["National", "State", "Metro", "County"], index=3
256
+ )
257
+ else:
258
+ scale = st.selectbox("Scale", ["National", "Metro"], index=1)
259
+
260
+ gdf = get_geom_data(scale.lower())
261
+
262
+ if frequency == "Weekly":
263
+ inventory_df = get_inventory_data(data_links["weekly"][scale.lower()])
264
+ weeks = get_weeks(inventory_df)
265
+ with row1_col1:
266
+ selected_date = st.date_input("Select a date", value=weeks[-1])
267
+ saturday = get_saturday(selected_date)
268
+ selected_period = saturday.strftime("%-m/%-d/%Y")
269
+ if saturday not in weeks:
270
+ st.error(
271
+ "The selected date is not available in the data. Please select a date between {} and {}".format(
272
+ weeks[0], weeks[-1]
273
+ )
274
+ )
275
+ selected_period = weeks[-1].strftime("%-m/%-d/%Y")
276
+ inventory_df = get_inventory_data(data_links["weekly"][scale.lower()])
277
+ inventory_df = filter_weekly_inventory(inventory_df, selected_period)
278
+
279
+ if frequency == "Monthly":
280
+ if cur_hist == "Current month data":
281
+ inventory_df = get_inventory_data(
282
+ data_links["monthly_current"][scale.lower()]
283
+ )
284
+ selected_period = get_periods(inventory_df)[0]
285
+ else:
286
+ with row1_col2:
287
+ inventory_df = get_inventory_data(
288
+ data_links["monthly_historical"][scale.lower()]
289
+ )
290
+ start_year, end_year = get_start_end_year(inventory_df)
291
+ periods = get_periods(inventory_df)
292
+ with st.expander("Select year and month", True):
293
+ selected_year = st.slider(
294
+ "Year",
295
+ start_year,
296
+ end_year,
297
+ value=start_year,
298
+ step=1,
299
+ )
300
+ selected_month = st.slider(
301
+ "Month",
302
+ min_value=1,
303
+ max_value=12,
304
+ value=int(periods[0][-2:]),
305
+ step=1,
306
+ )
307
+ selected_period = str(selected_year) + str(selected_month).zfill(2)
308
+ if selected_period not in periods:
309
+ st.error("Data not available for selected year and month")
310
+ selected_period = periods[0]
311
+ inventory_df = inventory_df[
312
+ inventory_df["month_date_yyyymm"] == int(selected_period)
313
+ ]
314
+
315
+ data_cols = get_data_columns(inventory_df, scale.lower(), frequency.lower())
316
+
317
+ with row1_col4:
318
+ selected_col = st.selectbox("Attribute", data_cols)
319
+ with row1_col5:
320
+ show_desc = st.checkbox("Show attribute description")
321
+ if show_desc:
322
+ try:
323
+ label, desc = get_data_dict(selected_col.strip())
324
+ markdown = f"""
325
+ **{label}**: {desc}
326
+ """
327
+ st.markdown(markdown)
328
+ except:
329
+ st.warning("No description available for selected attribute")
330
+
331
+ row2_col1, row2_col2, row2_col3, row2_col4, row2_col5, row2_col6 = st.columns(
332
+ [0.6, 0.68, 0.7, 0.7, 1.5, 0.8]
333
+ )
334
+
335
+ palettes = cm.list_colormaps()
336
+ with row2_col1:
337
+ palette = st.selectbox("Color palette", palettes, index=palettes.index("Blues"))
338
+ with row2_col2:
339
+ n_colors = st.slider("Number of colors", min_value=2, max_value=20, value=8)
340
+ with row2_col3:
341
+ show_nodata = st.checkbox("Show nodata areas", value=True)
342
+ with row2_col4:
343
+ show_3d = st.checkbox("Show 3D view", value=False)
344
+ with row2_col5:
345
+ if show_3d:
346
+ elev_scale = st.slider(
347
+ "Elevation scale", min_value=1, max_value=1000000, value=1, step=10
348
+ )
349
+ with row2_col6:
350
+ st.info("Press Ctrl and move the left mouse button.")
351
+ else:
352
+ elev_scale = 1
353
+
354
+ gdf = join_attributes(gdf, inventory_df, scale.lower())
355
+ gdf_null = select_null(gdf, selected_col)
356
+ gdf = select_non_null(gdf, selected_col)
357
+ gdf = gdf.sort_values(by=selected_col, ascending=True)
358
+
359
+ colors = cm.get_palette(palette, n_colors)
360
+ colors = [hex_to_rgb(c) for c in colors]
361
+
362
+ for i, ind in enumerate(gdf.index):
363
+ index = int(i / (len(gdf) / len(colors)))
364
+ if index >= len(colors):
365
+ index = len(colors) - 1
366
+ gdf.loc[ind, "R"] = colors[index][0]
367
+ gdf.loc[ind, "G"] = colors[index][1]
368
+ gdf.loc[ind, "B"] = colors[index][2]
369
+
370
+ initial_view_state = pdk.ViewState(
371
+ latitude=40,
372
+ longitude=-100,
373
+ zoom=3,
374
+ max_zoom=16,
375
+ pitch=0,
376
+ bearing=0,
377
+ height=700,
378
+ width=None,
379
+ )
380
+
381
+ min_value = gdf[selected_col].min()
382
+ max_value = gdf[selected_col].max()
383
+ color = "color"
384
+ # color_exp = f"[({selected_col}-{min_value})/({max_value}-{min_value})*255, 0, 0]"
385
+ color_exp = f"[R, G, B]"
386
+
387
+ geojson = pdk.Layer(
388
+ "GeoJsonLayer",
389
+ gdf,
390
+ pickable=True,
391
+ opacity=0.5,
392
+ stroked=True,
393
+ filled=True,
394
+ extruded=show_3d,
395
+ wireframe=True,
396
+ get_elevation=f"{selected_col}",
397
+ elevation_scale=elev_scale,
398
+ # get_fill_color="color",
399
+ get_fill_color=color_exp,
400
+ get_line_color=[0, 0, 0],
401
+ get_line_width=2,
402
+ line_width_min_pixels=1,
403
+ )
404
+
405
+ geojson_null = pdk.Layer(
406
+ "GeoJsonLayer",
407
+ gdf_null,
408
+ pickable=True,
409
+ opacity=0.2,
410
+ stroked=True,
411
+ filled=True,
412
+ extruded=False,
413
+ wireframe=True,
414
+ # get_elevation="properties.ALAND/100000",
415
+ # get_fill_color="color",
416
+ get_fill_color=[200, 200, 200],
417
+ get_line_color=[0, 0, 0],
418
+ get_line_width=2,
419
+ line_width_min_pixels=1,
420
+ )
421
+
422
+ # tooltip = {"text": "Name: {NAME}"}
423
+
424
+ # tooltip_value = f"<b>Value:</b> {median_listing_price}""
425
+ tooltip = {
426
+ "html": "<b>Name:</b> {NAME}<br><b>Value:</b> {"
427
+ + selected_col
428
+ + "}<br><b>Date:</b> "
429
+ + selected_period
430
+ + "",
431
+ "style": {"backgroundColor": "steelblue", "color": "white"},
432
+ }
433
+
434
+ layers = [geojson]
435
+ if show_nodata:
436
+ layers.append(geojson_null)
437
+
438
+ r = pdk.Deck(
439
+ layers=layers,
440
+ initial_view_state=initial_view_state,
441
+ map_style="light",
442
+ tooltip=tooltip,
443
+ )
444
+
445
+ row3_col1, row3_col2 = st.columns([6, 1])
446
+
447
+ with row3_col1:
448
+ st.pydeck_chart(r)
449
+ with row3_col2:
450
+ st.write(
451
+ cm.create_colormap(
452
+ palette,
453
+ label=selected_col.replace("_", " ").title(),
454
+ width=0.2,
455
+ height=3,
456
+ orientation="vertical",
457
+ vmin=min_value,
458
+ vmax=max_value,
459
+ font_size=10,
460
+ )
461
+ )
462
+ row4_col1, row4_col2, row4_col3 = st.columns([1, 2, 3])
463
+ with row4_col1:
464
+ show_data = st.checkbox("Show raw data")
465
+ with row4_col2:
466
+ show_cols = st.multiselect("Select columns", data_cols)
467
+ with row4_col3:
468
+ show_colormaps = st.checkbox("Preview all color palettes")
469
+ if show_colormaps:
470
+ st.write(cm.plot_colormaps(return_fig=True))
471
+ if show_data:
472
+ if scale == "National":
473
+ st.dataframe(gdf[["NAME", "GEOID"] + show_cols])
474
+ elif scale == "State":
475
+ st.dataframe(gdf[["NAME", "STUSPS"] + show_cols])
476
+ elif scale == "County":
477
+ st.dataframe(gdf[["NAME", "STATEFP", "COUNTYFP"] + show_cols])
478
+ elif scale == "Metro":
479
+ st.dataframe(gdf[["NAME", "CBSAFP"] + show_cols])
480
+ elif scale == "Zip":
481
+ st.dataframe(gdf[["GEOID10"] + show_cols])
482
+
483
+
484
+ app()
pages/3_πŸͺŸ_Split_Map.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import leafmap.foliumap as leafmap
3
+
4
+ st.set_page_config(layout="wide")
5
+
6
+ st.sidebar.title("About")
7
+ st.sidebar.info(
8
+ """
9
+ Web App URL: <https://geospatial.streamlitapp.com>
10
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
11
+ """
12
+ )
13
+
14
+ st.sidebar.title("Contact")
15
+ st.sidebar.info(
16
+ """
17
+ Qiusheng Wu: <https://wetlands.io>
18
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
19
+ """
20
+ )
21
+
22
+ st.title("Split-panel Map")
23
+
24
+ with st.expander("See source code"):
25
+ with st.echo():
26
+ m = leafmap.Map()
27
+ m.split_map(
28
+ left_layer='ESA WorldCover 2020 S2 FCC', right_layer='ESA WorldCover 2020'
29
+ )
30
+ m.add_legend(title='ESA Land Cover', builtin_legend='ESA_WorldCover')
31
+
32
+ m.to_streamlit(height=700)
pages/4_πŸ”₯_Heatmap.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import leafmap.foliumap as leafmap
3
+
4
+ st.set_page_config(layout="wide")
5
+
6
+ markdown = """
7
+ Web App URL: <https://template.streamlitapp.com>
8
+ GitHub Repository: <https://github.com/giswqs/streamlit-multipage-template>
9
+ """
10
+
11
+ st.sidebar.title("About")
12
+ st.sidebar.info(markdown)
13
+ logo = "https://i.imgur.com/UbOXYAU.png"
14
+ st.sidebar.image(logo)
15
+
16
+ st.title("Heatmap")
17
+
18
+ with st.expander("See source code"):
19
+ with st.echo():
20
+ filepath = "https://raw.githubusercontent.com/giswqs/leafmap/master/examples/data/us_cities.csv"
21
+ m = leafmap.Map(center=[40, -100], zoom=4, tiles="stamentoner")
22
+ m.add_heatmap(
23
+ filepath,
24
+ latitude="latitude",
25
+ longitude="longitude",
26
+ value="pop_max",
27
+ name="Heat map",
28
+ radius=20,
29
+ )
30
+ m.to_streamlit(height=700)
pages/5_πŸ“_Marker_Cluster.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import leafmap.foliumap as leafmap
3
+
4
+ st.set_page_config(layout="wide")
5
+
6
+ st.sidebar.title("About")
7
+ st.sidebar.info(
8
+ """
9
+ Web App URL: <https://geospatial.streamlitapp.com>
10
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
11
+ """
12
+ )
13
+
14
+ st.sidebar.title("Contact")
15
+ st.sidebar.info(
16
+ """
17
+ Qiusheng Wu: <https://wetlands.io>
18
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
19
+ """
20
+ )
21
+
22
+ st.title("Marker Cluster")
23
+
24
+ with st.expander("See source code"):
25
+ with st.echo():
26
+
27
+ m = leafmap.Map(center=[40, -100], zoom=4)
28
+ cities = 'https://raw.githubusercontent.com/giswqs/leafmap/master/examples/data/us_cities.csv'
29
+ regions = 'https://raw.githubusercontent.com/giswqs/leafmap/master/examples/data/us_regions.geojson'
30
+
31
+ m.add_geojson(regions, layer_name='US Regions')
32
+ m.add_points_from_xy(
33
+ cities,
34
+ x="longitude",
35
+ y="latitude",
36
+ color_column='region',
37
+ icon_names=['gear', 'map', 'leaf', 'globe'],
38
+ spin=True,
39
+ add_legend=True,
40
+ )
41
+
42
+ m.to_streamlit(height=700)
pages/6_πŸ—ΊοΈ_Basemaps.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import leafmap.foliumap as leafmap
3
+
4
+ st.set_page_config(layout="wide")
5
+
6
+ st.sidebar.title("About")
7
+ st.sidebar.info(
8
+ """
9
+ Web App URL: <https://geospatial.streamlitapp.com>
10
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
11
+ """
12
+ )
13
+
14
+ st.sidebar.title("Contact")
15
+ st.sidebar.info(
16
+ """
17
+ Qiusheng Wu: <https://wetlands.io>
18
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
19
+ """
20
+ )
21
+
22
+
23
+ def app():
24
+ st.title("Search Basemaps")
25
+ st.markdown(
26
+ """
27
+ This app is a demonstration of searching and loading basemaps from [xyzservices](https://github.com/geopandas/xyzservices) and [Quick Map Services (QMS)](https://github.com/nextgis/quickmapservices). Selecting from 1000+ basemaps with a few clicks.
28
+ """
29
+ )
30
+
31
+ with st.expander("See demo"):
32
+ st.image("https://i.imgur.com/0SkUhZh.gif")
33
+
34
+ row1_col1, row1_col2 = st.columns([3, 1])
35
+ width = 800
36
+ height = 600
37
+ tiles = None
38
+
39
+ with row1_col2:
40
+
41
+ checkbox = st.checkbox("Search Quick Map Services (QMS)")
42
+ keyword = st.text_input("Enter a keyword to search and press Enter:")
43
+ empty = st.empty()
44
+
45
+ if keyword:
46
+ options = leafmap.search_xyz_services(keyword=keyword)
47
+ if checkbox:
48
+ options = options + leafmap.search_qms(keyword=keyword)
49
+
50
+ tiles = empty.multiselect("Select XYZ tiles to add to the map:", options)
51
+
52
+ with row1_col1:
53
+ m = leafmap.Map()
54
+
55
+ if tiles is not None:
56
+ for tile in tiles:
57
+ m.add_xyz_service(tile)
58
+
59
+ m.to_streamlit(width, height)
60
+
61
+
62
+ app()
pages/7_πŸ“¦_Web_Map_Service.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import streamlit as st
3
+ import leafmap.foliumap as leafmap
4
+
5
+ st.set_page_config(layout="wide")
6
+
7
+ st.sidebar.title("About")
8
+ st.sidebar.info(
9
+ """
10
+ Web App URL: <https://geospatial.streamlitapp.com>
11
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
12
+ """
13
+ )
14
+
15
+ st.sidebar.title("Contact")
16
+ st.sidebar.info(
17
+ """
18
+ Qiusheng Wu: <https://wetlands.io>
19
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
20
+ """
21
+ )
22
+
23
+
24
+ @st.cache
25
+ def get_layers(url):
26
+ options = leafmap.get_wms_layers(url)
27
+ return options
28
+
29
+
30
+ def app():
31
+ st.title("Web Map Service (WMS)")
32
+ st.markdown(
33
+ """
34
+ This app is a demonstration of loading Web Map Service (WMS) layers. Simply enter the URL of the WMS service
35
+ in the text box below and press Enter to retrieve the layers. Go to https://apps.nationalmap.gov/services to find
36
+ some WMS URLs if needed.
37
+ """
38
+ )
39
+
40
+ row1_col1, row1_col2 = st.columns([3, 1.3])
41
+ width = 800
42
+ height = 600
43
+ layers = None
44
+
45
+ with row1_col2:
46
+
47
+ esa_landcover = "https://services.terrascope.be/wms/v2"
48
+ url = st.text_input(
49
+ "Enter a WMS URL:", value="https://services.terrascope.be/wms/v2"
50
+ )
51
+ empty = st.empty()
52
+
53
+ if url:
54
+ options = get_layers(url)
55
+
56
+ default = None
57
+ if url == esa_landcover:
58
+ default = "WORLDCOVER_2020_MAP"
59
+ layers = empty.multiselect(
60
+ "Select WMS layers to add to the map:", options, default=default
61
+ )
62
+ add_legend = st.checkbox("Add a legend to the map", value=True)
63
+ if default == "WORLDCOVER_2020_MAP":
64
+ legend = str(leafmap.builtin_legends["ESA_WorldCover"])
65
+ else:
66
+ legend = ""
67
+ if add_legend:
68
+ legend_text = st.text_area(
69
+ "Enter a legend as a dictionary {label: color}",
70
+ value=legend,
71
+ height=200,
72
+ )
73
+
74
+ with row1_col1:
75
+ m = leafmap.Map(center=(36.3, 0), zoom=2)
76
+
77
+ if layers is not None:
78
+ for layer in layers:
79
+ m.add_wms_layer(
80
+ url, layers=layer, name=layer, attribution=" ", transparent=True
81
+ )
82
+ if add_legend and legend_text:
83
+ legend_dict = ast.literal_eval(legend_text)
84
+ m.add_legend(legend_dict=legend_dict)
85
+
86
+ m.to_streamlit(width, height)
87
+
88
+
89
+ app()
pages/8_🏜️_Raster_Data_Visualization.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import leafmap.foliumap as leafmap
3
+ import streamlit as st
4
+ import palettable
5
+
6
+ st.set_page_config(layout="wide")
7
+
8
+ st.sidebar.title("About")
9
+ st.sidebar.info(
10
+ """
11
+ Web App URL: <https://geospatial.streamlitapp.com>
12
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
13
+ """
14
+ )
15
+
16
+ st.sidebar.title("Contact")
17
+ st.sidebar.info(
18
+ """
19
+ Qiusheng Wu: <https://wetlands.io>
20
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
21
+ """
22
+ )
23
+
24
+
25
+ @st.cache
26
+ def load_cog_list():
27
+ print(os.getcwd())
28
+ in_txt = os.path.join(os.getcwd(), "data/cog_files.txt")
29
+ with open(in_txt) as f:
30
+ return [line.strip() for line in f.readlines()[1:]]
31
+
32
+
33
+ @st.cache
34
+ def get_palettes():
35
+ palettes = dir(palettable.matplotlib)[:-16]
36
+ return ["matplotlib." + p for p in palettes]
37
+
38
+
39
+ def app():
40
+
41
+ st.title("Visualize Raster Datasets")
42
+ st.markdown(
43
+ """
44
+ An interactive web app for visualizing local raster datasets and Cloud Optimized GeoTIFF ([COG](https://www.cogeo.org)). The app was built using [streamlit](https://streamlit.io), [leafmap](https://leafmap.org), and [localtileserver](https://github.com/banesullivan/localtileserver).
45
+
46
+
47
+ """
48
+ )
49
+
50
+ row1_col1, row1_col2 = st.columns([2, 1])
51
+
52
+ with row1_col1:
53
+ cog_list = load_cog_list()
54
+ cog = st.selectbox("Select a sample Cloud Opitmized GeoTIFF (COG)", cog_list)
55
+
56
+ with row1_col2:
57
+ empty = st.empty()
58
+
59
+ url = empty.text_input(
60
+ "Enter a HTTP URL to a Cloud Optimized GeoTIFF (COG)",
61
+ cog,
62
+ )
63
+
64
+ data = st.file_uploader("Upload a raster dataset", type=["tif", "img"])
65
+
66
+ if data:
67
+ url = empty.text_input(
68
+ "Enter a URL to a Cloud Optimized GeoTIFF (COG)",
69
+ "",
70
+ )
71
+
72
+ add_palette = st.checkbox("Add a color palette")
73
+ if add_palette:
74
+ palette = st.selectbox("Select a color palette", get_palettes())
75
+ else:
76
+ palette = None
77
+
78
+ submit = st.button("Submit")
79
+
80
+ m = leafmap.Map(latlon_control=False)
81
+
82
+ if submit:
83
+ if data or url:
84
+ try:
85
+ if data:
86
+ file_path = leafmap.save_data(data)
87
+ m.add_local_tile(file_path, palette=palette, debug=True)
88
+ elif url:
89
+ m.add_remote_tile(url, palette=palette, debug=True)
90
+ except Exception as e:
91
+ with row1_col2:
92
+ st.error("Work in progress. Try it again later.")
93
+
94
+ with row1_col1:
95
+ m.to_streamlit()
96
+
97
+
98
+ app()
pages/9_πŸ”²_Vector_Data_Visualization.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import geopandas as gpd
3
+ import streamlit as st
4
+
5
+ st.set_page_config(layout="wide")
6
+
7
+ st.sidebar.title("About")
8
+ st.sidebar.info(
9
+ """
10
+ Web App URL: <https://geospatial.streamlitapp.com>
11
+ GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
12
+ """
13
+ )
14
+
15
+ st.sidebar.title("Contact")
16
+ st.sidebar.info(
17
+ """
18
+ Qiusheng Wu: <https://wetlands.io>
19
+ [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
20
+ """
21
+ )
22
+
23
+
24
+ def save_uploaded_file(file_content, file_name):
25
+ """
26
+ Save the uploaded file to a temporary directory
27
+ """
28
+ import tempfile
29
+ import os
30
+ import uuid
31
+
32
+ _, file_extension = os.path.splitext(file_name)
33
+ file_id = str(uuid.uuid4())
34
+ file_path = os.path.join(tempfile.gettempdir(), f"{file_id}{file_extension}")
35
+
36
+ with open(file_path, "wb") as file:
37
+ file.write(file_content.getbuffer())
38
+
39
+ return file_path
40
+
41
+
42
+ def app():
43
+
44
+ st.title("Upload Vector Data")
45
+
46
+ row1_col1, row1_col2 = st.columns([2, 1])
47
+ width = 950
48
+ height = 600
49
+
50
+ with row1_col2:
51
+
52
+ backend = st.selectbox(
53
+ "Select a plotting backend", ["folium", "kepler.gl", "pydeck"], index=2
54
+ )
55
+
56
+ if backend == "folium":
57
+ import leafmap.foliumap as leafmap
58
+ elif backend == "kepler.gl":
59
+ import leafmap.kepler as leafmap
60
+ elif backend == "pydeck":
61
+ import leafmap.deck as leafmap
62
+
63
+ url = st.text_input(
64
+ "Enter a URL to a vector dataset",
65
+ "https://github.com/giswqs/streamlit-geospatial/raw/master/data/us_states.geojson",
66
+ )
67
+
68
+ data = st.file_uploader(
69
+ "Upload a vector dataset", type=["geojson", "kml", "zip", "tab"]
70
+ )
71
+
72
+ container = st.container()
73
+
74
+ if data or url:
75
+ if data:
76
+ file_path = save_uploaded_file(data, data.name)
77
+ layer_name = os.path.splitext(data.name)[0]
78
+ elif url:
79
+ file_path = url
80
+ layer_name = url.split("/")[-1].split(".")[0]
81
+
82
+ with row1_col1:
83
+ if file_path.lower().endswith(".kml"):
84
+ gpd.io.file.fiona.drvsupport.supported_drivers["KML"] = "rw"
85
+ gdf = gpd.read_file(file_path, driver="KML")
86
+ else:
87
+ gdf = gpd.read_file(file_path)
88
+ lon, lat = leafmap.gdf_centroid(gdf)
89
+ if backend == "pydeck":
90
+
91
+ column_names = gdf.columns.values.tolist()
92
+ random_column = None
93
+ with container:
94
+ random_color = st.checkbox("Apply random colors", True)
95
+ if random_color:
96
+ random_column = st.selectbox(
97
+ "Select a column to apply random colors", column_names
98
+ )
99
+
100
+ m = leafmap.Map(center=(lat, lon))
101
+ m.add_gdf(gdf, random_color_column=random_column)
102
+ st.pydeck_chart(m)
103
+
104
+ else:
105
+ m = leafmap.Map(center=(lat, lon), draw_export=True)
106
+ m.add_gdf(gdf, layer_name=layer_name)
107
+ # m.add_vector(file_path, layer_name=layer_name)
108
+ if backend == "folium":
109
+ m.zoom_to_gdf(gdf)
110
+ m.to_streamlit(width=width, height=height)
111
+
112
+ else:
113
+ with row1_col1:
114
+ m = leafmap.Map()
115
+ st.pydeck_chart(m)
116
+
117
+
118
+ app()
postBuild ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # enable nbserverproxy
2
+ jupyter serverextension enable --sys-prefix nbserverproxy
3
+ # streamlit launches at startup
4
+ mv streamlit_call.py ${NB_PYTHON_PREFIX}/lib/python*/site-packages/
5
+ # enable streamlit extension
6
+ jupyter serverextension enable --sys-prefix streamlit_call
requirements.txt CHANGED
@@ -1,18 +1,20 @@
1
  --find-links=https://girder.github.io/large_image_wheels GDAL
2
  # cartopy
 
3
  geopandas
 
4
  keplergl
5
- palettable
6
  localtileserver
 
7
  owslib
 
8
  plotly
9
  streamlit
 
10
  streamlit-folium
11
  streamlit-keplergl
12
- streamlit-bokeh-events
13
  tropycal
14
- # leafmap
15
- # geemap
16
- git+https://github.com/giswqs/leafmap
17
- git+https://github.com/giswqs/geemap
18
 
 
1
  --find-links=https://girder.github.io/large_image_wheels GDAL
2
  # cartopy
3
+ geemap
4
  geopandas
5
+ jupyter-server-proxy
6
  keplergl
7
+ leafmap
8
  localtileserver
9
+ nbserverproxy
10
  owslib
11
+ palettable
12
  plotly
13
  streamlit
14
+ streamlit-bokeh-events
15
  streamlit-folium
16
  streamlit-keplergl
 
17
  tropycal
18
+ # git+https://github.com/giswqs/leafmap
19
+ # git+https://github.com/giswqs/geemap
 
 
20
 
streamlit_app.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import leafmap.foliumap as leafmap
3
+
4
+ st.set_page_config(layout="wide")
5
+
6
+ # Customize the sidebar
7
+ markdown = """
8
+ Web App URL: <https://template.streamlitapp.com>
9
+ GitHub Repository: <https://github.com/giswqs/streamlit-multipage-template>
10
+ """
11
+
12
+ st.sidebar.title("About")
13
+ st.sidebar.info(markdown)
14
+ logo = "https://i.imgur.com/UbOXYAU.png"
15
+ st.sidebar.image(logo)
16
+
17
+ # Customize page title
18
+ st.title("Streamlit for Geospatial Applications")
19
+
20
+ st.markdown(
21
+ """
22
+ This multipage app template demonstrates various interactive web apps created using [streamlit](https://streamlit.io) and [leafmap](https://leafmap.org). It is an open-source project and you are very welcome to contribute to the [GitHub repository](https://github.com/giswqs/streamlit-multipage-template).
23
+ """
24
+ )
25
+
26
+ st.header("Instructions")
27
+
28
+ markdown = """
29
+ 1. For the [GitHub repository](https://github.com/giswqs/streamlit-multipage-template) or [use it as a template](https://github.com/giswqs/streamlit-multipage-template/generate) for your own project.
30
+ 2. Customize the sidebar by changing the sidebar text and logo in each Python files.
31
+ 3. Find your favorite emoji from https://emojipedia.org.
32
+ 4. Add a new app to the `pages/` directory with an emoji in the file name, e.g., `1_πŸš€_Chart.py`.
33
+
34
+ """
35
+
36
+ st.markdown(markdown)
37
+
38
+ m = leafmap.Map(minimap_control=True)
39
+ m.add_basemap("OpenTopoMap")
40
+ m.to_streamlit(height=500)
streamlit_call.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from subprocess import Popen
2
+
3
+
4
+ def load_jupyter_server_extension(nbapp):
5
+ """serve the streamlit app"""
6
+ Popen(
7
+ [
8
+ "streamlit",
9
+ "run",
10
+ "Home.py",
11
+ "--browser.serverAddress=0.0.0.0",
12
+ "--server.enableCORS=False",
13
+ ]
14
+ )