Yearly variant
Browse files- app.py +48 -17
- sandbox.ipynb +74 -4
app.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1 |
import os
|
|
|
2 |
import ee
|
3 |
import json
|
4 |
import geojson
|
5 |
import geemap
|
|
|
6 |
import geemap.foliumap as gee_folium
|
7 |
import leafmap.foliumap as leaf_folium
|
8 |
import streamlit as st
|
@@ -36,11 +38,15 @@ def calculate_ndvi(image, nir_band, red_band):
|
|
36 |
ndvi = (nir.subtract(red)).divide(nir.add(red)).rename("NDVI")
|
37 |
return image.addBands(ndvi)
|
38 |
|
39 |
-
def process_date(
|
40 |
try:
|
41 |
attrs = satellites[satellite]
|
42 |
collection = attrs["collection"]
|
43 |
collection = collection.filterBounds(ee_geometry)
|
|
|
|
|
|
|
|
|
44 |
collection = collection.filterDate(start_date, end_date)
|
45 |
mosaic = collection.qualityMosaic("NDVI")
|
46 |
fc = geemap.zonal_stats(
|
@@ -169,12 +175,11 @@ st.markdown(
|
|
169 |
|
170 |
# Input: Date and Cloud Cover
|
171 |
col = st.columns(4)
|
172 |
-
|
173 |
-
|
174 |
-
end_year = col[2].selectbox("End Year", list(range(2014, 2027)), index=
|
175 |
-
|
176 |
-
|
177 |
-
end_date = f"{end_year}-{end_month:02d}"
|
178 |
|
179 |
# Input: GeoJSON/KML file
|
180 |
uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])
|
@@ -185,8 +190,8 @@ gdf = preprocess_gdf(gpd.read_file(uploaded_file))
|
|
185 |
|
186 |
# Input: Geometry
|
187 |
selected_geometry = st.selectbox("Select the geometry", gdf.Name.values)
|
188 |
-
|
189 |
-
selected_geometry =
|
190 |
if selected_geometry.type != "Polygon":
|
191 |
st.error(
|
192 |
f"Selected geometry is of type {selected_geometry.type}. Please provide a polygon geometry."
|
@@ -196,8 +201,25 @@ if selected_geometry.type != "Polygon":
|
|
196 |
# Derived Inputs
|
197 |
selected_geometry = selected_geometry.__geo_interface__
|
198 |
ee_geometry = ee.Geometry(selected_geometry)
|
|
|
|
|
199 |
ee_feature_collection = ee.FeatureCollection(ee_geometry)
|
200 |
feature_collection = geojson.FeatureCollection([{"type": "Feature", "geometry": selected_geometry, "properties": {"name": "Selected Geometry"}}])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
|
202 |
# visualize the geometry
|
203 |
m = leaf_folium.Map()
|
@@ -208,6 +230,11 @@ m.add_geojson(feature_collection)
|
|
208 |
write_info(f"Visual Esri Wayback Basemap - {latest_date}")
|
209 |
m.to_streamlit()
|
210 |
|
|
|
|
|
|
|
|
|
|
|
211 |
|
212 |
# Input: Satellite Sources
|
213 |
st.write("Select the satellite sources:")
|
@@ -223,7 +250,10 @@ if submit:
|
|
223 |
st.stop()
|
224 |
|
225 |
# Create month range
|
226 |
-
|
|
|
|
|
|
|
227 |
write_info(
|
228 |
f"Start Date (inclusive): {start_date}, End Date (exclusive): {end_date}"
|
229 |
)
|
@@ -234,14 +264,14 @@ if submit:
|
|
234 |
|
235 |
with st.spinner(f"Processing {satellite} ..."):
|
236 |
progress_bar = st.progress(0)
|
237 |
-
for i,
|
238 |
-
mosaic, mean_ndvi, cloud_proba = process_date(
|
239 |
-
result[satellite][
|
240 |
"mosaic": mosaic,
|
241 |
"mean_ndvi": mean_ndvi,
|
242 |
"cloud_mask_probability": cloud_proba,
|
243 |
}
|
244 |
-
progress_bar.progress((i + 1) /
|
245 |
|
246 |
st.session_state.result = result
|
247 |
|
@@ -279,14 +309,15 @@ if "result" in st.session_state:
|
|
279 |
|
280 |
df_numeric = df.select_dtypes(include=["float64"])
|
281 |
st.write(df_numeric)
|
|
|
|
|
|
|
282 |
|
283 |
-
fig = px.line(df, y=df_numeric.columns, title="Mean NDVI", markers=True)
|
284 |
fig.update_yaxes(range=[-0.2, 1])
|
285 |
st.plotly_chart(fig)
|
286 |
|
287 |
st.subheader("Visual Inspection")
|
288 |
-
_, lonlat = ee_geometry.centroid().getInfo().values()
|
289 |
-
lon, lat = lonlat
|
290 |
write_info(f"Centroid of the selected geometry (lat, lon): ({lat}, {lon})")
|
291 |
cols = st.columns(2)
|
292 |
df_dates = df.index.strftime("%Y-%m").tolist()
|
|
|
1 |
import os
|
2 |
+
import utm
|
3 |
import ee
|
4 |
import json
|
5 |
import geojson
|
6 |
import geemap
|
7 |
+
import numpy as np
|
8 |
import geemap.foliumap as gee_folium
|
9 |
import leafmap.foliumap as leaf_folium
|
10 |
import streamlit as st
|
|
|
38 |
ndvi = (nir.subtract(red)).divide(nir.add(red)).rename("NDVI")
|
39 |
return image.addBands(ndvi)
|
40 |
|
41 |
+
def process_date(date, satellite):
|
42 |
try:
|
43 |
attrs = satellites[satellite]
|
44 |
collection = attrs["collection"]
|
45 |
collection = collection.filterBounds(ee_geometry)
|
46 |
+
str_start_date = date+"-01"
|
47 |
+
start_date = pd.to_datetime(str_start_date)
|
48 |
+
end_date = start_date + pd.DateOffset(months=1)
|
49 |
+
write_info(f"Processing {satellite} - {start_date} to {end_date}")
|
50 |
collection = collection.filterDate(start_date, end_date)
|
51 |
mosaic = collection.qualityMosaic("NDVI")
|
52 |
fc = geemap.zonal_stats(
|
|
|
175 |
|
176 |
# Input: Date and Cloud Cover
|
177 |
col = st.columns(4)
|
178 |
+
month_of_interest = col[0].selectbox("Month of Interest", list(range(1, 13)), index=11)
|
179 |
+
start_year = col[1].selectbox("Start Year", list(range(2014, 2027)), index=6)
|
180 |
+
end_year = col[2].selectbox("End Year", list(range(2014, 2027)), index=9) + 1
|
181 |
+
start_date = f"{start_year}-{month_of_interest:02d}"
|
182 |
+
end_date = f"{end_year}-{month_of_interest:02d}"
|
|
|
183 |
|
184 |
# Input: GeoJSON/KML file
|
185 |
uploaded_file = st.file_uploader("Upload KML/GeoJSON file", type=["geojson", "kml"])
|
|
|
190 |
|
191 |
# Input: Geometry
|
192 |
selected_geometry = st.selectbox("Select the geometry", gdf.Name.values)
|
193 |
+
selected_geometry_gdf = gdf[gdf.Name == selected_geometry]
|
194 |
+
selected_geometry = selected_geometry_gdf.iloc[0].geometry
|
195 |
if selected_geometry.type != "Polygon":
|
196 |
st.error(
|
197 |
f"Selected geometry is of type {selected_geometry.type}. Please provide a polygon geometry."
|
|
|
201 |
# Derived Inputs
|
202 |
selected_geometry = selected_geometry.__geo_interface__
|
203 |
ee_geometry = ee.Geometry(selected_geometry)
|
204 |
+
_, lonlat = ee_geometry.centroid().getInfo().values()
|
205 |
+
lon, lat = lonlat
|
206 |
ee_feature_collection = ee.FeatureCollection(ee_geometry)
|
207 |
feature_collection = geojson.FeatureCollection([{"type": "Feature", "geometry": selected_geometry, "properties": {"name": "Selected Geometry"}}])
|
208 |
+
x, y, zone, _ = utm.from_latlon(lat, lon)
|
209 |
+
epsg = f"EPSG:326{zone}"
|
210 |
+
selected_geometry_gdf = selected_geometry_gdf.to_crs(epsg)
|
211 |
+
area = selected_geometry_gdf.area.values[0]
|
212 |
+
perimeter = selected_geometry_gdf.length.values[0]
|
213 |
+
|
214 |
+
stats_df = pd.DataFrame(
|
215 |
+
{
|
216 |
+
"Area (m^2)": [area],
|
217 |
+
"Perimeter (m)": [perimeter],
|
218 |
+
"Centroid (lat, lon)": [f"{lat}, {lon}"],
|
219 |
+
"Points": np.array(selected_geometry['coordinates']).tolist(),
|
220 |
+
}
|
221 |
+
)
|
222 |
+
|
223 |
|
224 |
# visualize the geometry
|
225 |
m = leaf_folium.Map()
|
|
|
230 |
write_info(f"Visual Esri Wayback Basemap - {latest_date}")
|
231 |
m.to_streamlit()
|
232 |
|
233 |
+
st.write(stats_df)
|
234 |
+
# download option
|
235 |
+
stats_csv = stats_df.to_csv()
|
236 |
+
st.download_button("Download Geometry Stats", stats_csv, "geometry_stats.csv", "text/csv")
|
237 |
+
|
238 |
|
239 |
# Input: Satellite Sources
|
240 |
st.write("Select the satellite sources:")
|
|
|
250 |
st.stop()
|
251 |
|
252 |
# Create month range
|
253 |
+
# print(start_date, end_date)
|
254 |
+
dates = pd.date_range(start_date, end_date, freq="Y").strftime("%Y-%m").tolist()
|
255 |
+
# print(dates)
|
256 |
+
# asjasndjasndj
|
257 |
write_info(
|
258 |
f"Start Date (inclusive): {start_date}, End Date (exclusive): {end_date}"
|
259 |
)
|
|
|
264 |
|
265 |
with st.spinner(f"Processing {satellite} ..."):
|
266 |
progress_bar = st.progress(0)
|
267 |
+
for i, date in enumerate(dates):
|
268 |
+
mosaic, mean_ndvi, cloud_proba = process_date(date, satellite)
|
269 |
+
result[satellite][date] = {
|
270 |
"mosaic": mosaic,
|
271 |
"mean_ndvi": mean_ndvi,
|
272 |
"cloud_mask_probability": cloud_proba,
|
273 |
}
|
274 |
+
progress_bar.progress((i + 1) / len(dates))
|
275 |
|
276 |
st.session_state.result = result
|
277 |
|
|
|
309 |
|
310 |
df_numeric = df.select_dtypes(include=["float64"])
|
311 |
st.write(df_numeric)
|
312 |
+
# give streamlit option to download the data
|
313 |
+
csv = df_numeric.to_csv()
|
314 |
+
st.download_button("Download Time Series", csv, "data.csv", "text/csv")
|
315 |
|
316 |
+
fig = px.line(df, y=df_numeric.columns[0:1], title="Mean NDVI", markers=True)
|
317 |
fig.update_yaxes(range=[-0.2, 1])
|
318 |
st.plotly_chart(fig)
|
319 |
|
320 |
st.subheader("Visual Inspection")
|
|
|
|
|
321 |
write_info(f"Centroid of the selected geometry (lat, lon): ({lat}, {lon})")
|
322 |
cols = st.columns(2)
|
323 |
df_dates = df.index.strftime("%Y-%m").tolist()
|
sandbox.ipynb
CHANGED
@@ -140,7 +140,7 @@
|
|
140 |
},
|
141 |
{
|
142 |
"cell_type": "code",
|
143 |
-
"execution_count":
|
144 |
"metadata": {},
|
145 |
"outputs": [
|
146 |
{
|
@@ -180,16 +180,16 @@
|
|
180 |
{
|
181 |
"data": {
|
182 |
"text/plain": [
|
183 |
-
"
|
184 |
]
|
185 |
},
|
186 |
-
"execution_count":
|
187 |
"metadata": {},
|
188 |
"output_type": "execute_result"
|
189 |
}
|
190 |
],
|
191 |
"source": [
|
192 |
-
"2.75e-05"
|
193 |
]
|
194 |
},
|
195 |
{
|
@@ -2631,6 +2631,76 @@
|
|
2631 |
"layers = [\"Esri.WorldTopoMap\", \"OpenTopoMap\"]\n",
|
2632 |
"leafmap.linked_maps(rows=1, cols=2, height=\"400px\", layers=layers)"
|
2633 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2634 |
}
|
2635 |
],
|
2636 |
"metadata": {
|
|
|
140 |
},
|
141 |
{
|
142 |
"cell_type": "code",
|
143 |
+
"execution_count": 26,
|
144 |
"metadata": {},
|
145 |
"outputs": [
|
146 |
{
|
|
|
180 |
{
|
181 |
"data": {
|
182 |
"text/plain": [
|
183 |
+
"7272.727272727272"
|
184 |
]
|
185 |
},
|
186 |
+
"execution_count": 26,
|
187 |
"metadata": {},
|
188 |
"output_type": "execute_result"
|
189 |
}
|
190 |
],
|
191 |
"source": [
|
192 |
+
"1/2.75e-05 * 0.2"
|
193 |
]
|
194 |
},
|
195 |
{
|
|
|
2631 |
"layers = [\"Esri.WorldTopoMap\", \"OpenTopoMap\"]\n",
|
2632 |
"leafmap.linked_maps(rows=1, cols=2, height=\"400px\", layers=layers)"
|
2633 |
]
|
2634 |
+
},
|
2635 |
+
{
|
2636 |
+
"cell_type": "code",
|
2637 |
+
"execution_count": 1,
|
2638 |
+
"metadata": {},
|
2639 |
+
"outputs": [
|
2640 |
+
{
|
2641 |
+
"data": {
|
2642 |
+
"application/vnd.jupyter.widget-view+json": {
|
2643 |
+
"model_id": "1ce50feee5594597a06e1de0b80600b1",
|
2644 |
+
"version_major": 2,
|
2645 |
+
"version_minor": 0
|
2646 |
+
},
|
2647 |
+
"text/plain": [
|
2648 |
+
"Map(center=[45.8107, 8.6288], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoo…"
|
2649 |
+
]
|
2650 |
+
},
|
2651 |
+
"metadata": {},
|
2652 |
+
"output_type": "display_data"
|
2653 |
+
}
|
2654 |
+
],
|
2655 |
+
"source": [
|
2656 |
+
"from ipyleaflet import Map, TileLayer, WidgetControl\n",
|
2657 |
+
"from ipywidgets import SelectionSlider\n",
|
2658 |
+
"\n",
|
2659 |
+
"# Prepare a slider widget to navigate the version history of the basemap\n",
|
2660 |
+
"# There doesn't seem to be any pattern between timeId and basemap date, so just a few couples manually extracted from\n",
|
2661 |
+
"# https://wayback.maptiles.arcgis.com/arcgis/rest/services/world_imagery/mapserver/wmts/1.0.0/wmtscapabilities.xml\n",
|
2662 |
+
"date_timeId_mapping = [('2016-01-13', 3515),\n",
|
2663 |
+
" ('2017-01-11', 577),\n",
|
2664 |
+
" ('2018-01-08', 13161),\n",
|
2665 |
+
" ('2019-01-09', 6036),\n",
|
2666 |
+
" ('2020-01-08', 23001),\n",
|
2667 |
+
" ('2021-01-13', 1049),\n",
|
2668 |
+
" ('2022-01-12', 42663),\n",
|
2669 |
+
" ('2023-01-11', 11475)]\n",
|
2670 |
+
"\n",
|
2671 |
+
"# Date slider widget\n",
|
2672 |
+
"wayback_slider = SelectionSlider(\n",
|
2673 |
+
" options=date_timeId_mapping,\n",
|
2674 |
+
" value=3515,\n",
|
2675 |
+
" description='Date',\n",
|
2676 |
+
" disabled=False,\n",
|
2677 |
+
" continuous_update=True,\n",
|
2678 |
+
" orientation='horizontal',\n",
|
2679 |
+
" readout=True\n",
|
2680 |
+
")\n",
|
2681 |
+
"time_control = WidgetControl(widget=wayback_slider, position='topright')\n",
|
2682 |
+
"\n",
|
2683 |
+
"# Connect tile layer (wayback, not yet defined) with slider value\n",
|
2684 |
+
"def on_date_change(*args):\n",
|
2685 |
+
" wayback.url = 'https://wayback.maptiles.arcgis.com/arcgis/rest/services/world_imagery/wmts/1.0.0/default028mm/mapserver/tile/%d/{z}/{y}/{x}' % wayback_slider.value\n",
|
2686 |
+
" wayback.redraw()\n",
|
2687 |
+
" \n",
|
2688 |
+
"wayback_slider.observe(on_date_change, 'value')\n",
|
2689 |
+
"\n",
|
2690 |
+
"# Prepare map, tilelayer and draw it\n",
|
2691 |
+
"m = Map(center=(45.8107, 8.6288), zoom=16, scroll_wheel_zoom=True)\n",
|
2692 |
+
"wayback = TileLayer(url='https://wayback.maptiles.arcgis.com/arcgis/rest/services/world_imagery/wmts/1.0.0/default028mm/mapserver/tile/3515/{z}/{y}/{x}')\n",
|
2693 |
+
"m.add_layer(wayback)\n",
|
2694 |
+
"m.add_control(time_control)\n",
|
2695 |
+
"m"
|
2696 |
+
]
|
2697 |
+
},
|
2698 |
+
{
|
2699 |
+
"cell_type": "code",
|
2700 |
+
"execution_count": null,
|
2701 |
+
"metadata": {},
|
2702 |
+
"outputs": [],
|
2703 |
+
"source": []
|
2704 |
}
|
2705 |
],
|
2706 |
"metadata": {
|