Daniele Castellana pjgerrits pjgerrits commited on
Commit
d033501
Β·
1 Parent(s): e71bae0

Create new version (#2)

Browse files

* flood_mapper update

* include flood extent computations

* Edits Piet 09/09

* Piet updates Mapoutput

* Piet update Output

* added icon in map

* Update layout, move parameters to sidebar.

* Design updates; tabs for input output, css, font

* updates on tabs

* Download button attempt

* update output text

* Add dynamic maps

* change slider

* large update on download button

* Ras and Vec download and change in layout

* create new version

* create docs

* create docs and fix bugs

* increase font size text

* fix typos and add docstrings and comments to code

* fix typo

* fix text fontsize

* update Readme, add pre-commit and license

* update readme

* update readme

* add config file

* add usage to readme

* change project structure

* reformat text

* .DS_Store banished

* .DS_Store banished

* update readme

* update pre-commit file

* update requirements

* reformat text again

* update readme

* update readme

* update setup

* change position caption in docs

* correct broken urls

* update readme

* update readme and gitignore

* Update README.md

Co-authored-by: pjgerrits <[email protected]>
Co-authored-by: pjgerrits <[email protected]>

This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. .gitignore +6 -0
  3. .pre-commit-config.yaml +42 -0
  4. Home.py +0 -50
  5. LICENSE +0 -0
  6. Procfile +0 -1
  7. README.md +62 -1
  8. app-bk.py +0 -19
  9. app.py +0 -50
  10. app/Home.py +133 -0
  11. app/img/MA-logo.png +0 -0
  12. app/img/workflow.png +0 -0
  13. app/pages/1_🌍_Flood_extent_analysis.py +331 -0
  14. app/pages/2_πŸ“–_Documentation.py +177 -0
  15. app/src/__init__.py +1 -0
  16. app/src/config_parameters.py +72 -0
  17. app/src/utils_flood_analysis.py +369 -0
  18. app/src/utils_sidebar.py +180 -0
  19. apps/gee_datasets.py +0 -186
  20. apps/rois.py +0 -174
  21. apps/timelapse.py +0 -1313
  22. data/cog_files.txt +0 -77
  23. data/html/sfo_buildings.html +0 -34
  24. data/realtor_data_dict.csv +0 -37
  25. data/scotland_xyz.tsv +0 -51
  26. data/us_counties.geojson +0 -0
  27. data/us_metro_areas.geojson +0 -0
  28. data/us_nation.geojson +0 -0
  29. data/us_states.geojson +0 -0
  30. environment-bk.yml +0 -17
  31. index.html +0 -39
  32. multiapp.py +0 -75
  33. packages.txt +0 -9
  34. pages/.DS_Store +0 -0
  35. pages/10_🌍_Earth_Engine_Datasets.py +0 -144
  36. pages/11_🧱_Ordnance_Survey.py +0 -110
  37. pages/12_🌲_Land_Cover_Mapping.py +0 -113
  38. pages/1_πŸ“·_Timelapse.py +0 -1517
  39. pages/2_🏠_U.S._Housing.py +0 -484
  40. pages/3_πŸͺŸ_Split_Map.py +0 -32
  41. pages/4_πŸ”₯_Heatmap.py +0 -36
  42. pages/5_πŸ“_Marker_Cluster.py +0 -42
  43. pages/6_πŸ—ΊοΈ_Basemaps.py +0 -62
  44. pages/7_πŸ“¦_Web_Map_Service.py +0 -89
  45. pages/8_🏜️_Raster_Data_Visualization.py +0 -108
  46. pages/9_πŸ”²_Vector_Data_Visualization.py +0 -118
  47. postBuild +0 -6
  48. pyproject.toml +6 -0
  49. requirements.txt +6 -18
  50. setup.cfg +6 -0
.DS_Store DELETED
Binary file (6.15 kB)
 
.gitignore CHANGED
@@ -127,3 +127,9 @@ dmypy.json
127
 
128
  # Pyre type checker
129
  .pyre/
 
 
 
 
 
 
 
127
 
128
  # Pyre type checker
129
  .pyre/
130
+
131
+ # MacOS hidden folder
132
+ .DS_Store
133
+
134
+ # GEE authentication keys
135
+ .private-key.json
.pre-commit-config.yaml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_language_version:
2
+ python: python3.10
3
+
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v4.0.1
7
+ hooks:
8
+ - id: trailing-whitespace
9
+ - id: end-of-file-fixer
10
+ - id: check-ast
11
+ - id: check-toml
12
+ - id: check-yaml
13
+ - repo: https://github.com/psf/black
14
+ rev: 22.3.0
15
+ hooks:
16
+ - id: black
17
+ - repo: https://github.com/PyCQA/isort
18
+ rev: 5.9.3
19
+ hooks:
20
+ - id: isort
21
+ - repo: https://github.com/pycqa/flake8
22
+ rev: 3.9.2
23
+ hooks:
24
+ - id: flake8
25
+ additional_dependencies:
26
+ - flake8-bugbear==21.9.2
27
+ - flake8-comprehensions==3.6.1
28
+ - flake8-deprecated==1.3
29
+ - flake8-docstrings==1.6.0
30
+ - flake8-implicit-str-concat==0.2.0
31
+ - flake8-keyword-arguments==0.1.0
32
+ - flake8-print==4.0.0
33
+ - flake8-sfs==0.0.3
34
+ - repo: https://github.com/pre-commit/mirrors-mypy
35
+ rev: v0.910
36
+ hooks:
37
+ - id: mypy
38
+ additional_dependencies:
39
+ - types-PyYAML==5.4.10
40
+ - types-setuptools==57.4.0
41
+ - types-requests==2.25.9
42
+ exclude: tests
Home.py DELETED
@@ -1,50 +0,0 @@
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")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE CHANGED
File without changes
Procfile DELETED
@@ -1 +0,0 @@
1
- web: sh setup.sh && streamlit run Home.py
 
 
README.md CHANGED
@@ -1 +1,62 @@
1
- # Flood extent tool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flood mapping tool
2
+
3
+ [![license](https://img.shields.io/github/license/OCHA-DAP/pa-aa-toolbox.svg)](https://github.com/mapaction/flood-mapping-tool/blob/main/LICENSE)
4
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
5
+ [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
6
+
7
+ This repository contains a Streamlit app that allows to estimate flood extent using Sentinel-1 synthetic-aperture radar <a href='https://sentinel.esa.int/web/sentinel/user-guidessentinel-1-sar'>SAR</a> data.
8
+
9
+ The methodology is based on a <a href='https://un-spider.org/advisory-support/recommended-practices/recommended-\practice-google-earth-engine-flood-mapping'> recommended practice </a> published by the United Nations Platform for Space-based Information for Disaster Management and Emergency Response (UN-SPIDER) and it uses several satellite imagery datasets to produce the final output. The datasets are retrieved from <a href='https://earthengine.google.com/'>Google Earth Engine</a> which is a powerful web-platform for cloud-based processing of remote sensing data on large scales. More information on the methodology is given in the <i>Description</i> page in the Streamlit app.
10
+
11
+ This analysis provides a comprehensive overview of a flooding event, across different areas of interest, from settlements to countries. However, as mentioned in the UN-SPIDER website, the methodology is meant for broad information provision in a global context, and contains inherent uncertainties. Therefore, it is important that the tool is not used as the only source of information for rescue response planning.
12
+
13
+ ## Usage
14
+
15
+ #### Requirements
16
+
17
+ The Python version currently used is 3.10. Please install all packages from
18
+ ``requirements.txt``:
19
+
20
+ ```shell
21
+ pip install -r requirements.txt
22
+ ```
23
+
24
+ #### Google Earth Engine authentication
25
+
26
+ [Sign up](https://signup.earthengine.google.com/) for a Google Earth Engine account, if you don't already have one. Open a terminal window, type `python` and then paste the following code:
27
+
28
+ ```python
29
+ import ee
30
+ ee.Authenticate()
31
+ ```
32
+
33
+ Log in to your Google account to obtain the authorization code and paste it back into the terminal. Once you press "Enter", an authorization token will be saved to your computer under the following file path (depending on your operating system):
34
+
35
+ - Windows: `C:\\Users\\USERNAME\\.config\\earthengine\\credentials`
36
+ - Linux: `/home/USERNAME/.config/earthengine/credentials`
37
+ - MacOS: `/Users/USERNAME/.config/earthengine/credentials`
38
+
39
+ The credentials will be used when initialising Google Earth Engine in the app.
40
+
41
+ #### Run the app
42
+
43
+ Finally, open a terminal and run
44
+
45
+ ```shell
46
+ streamlit run app/Home.py
47
+ ```
48
+
49
+ A new browser window will open and you can start using the tool.
50
+
51
+ ## Contributing
52
+
53
+ #### Pre-commit
54
+
55
+ All code is formatted according to
56
+ [black](https://github.com/psf/black) and [flake8](https://flake8.pycqa.org/en/latest) guidelines. The repo is set-up to use [pre-commit](https://github.com/pre-commit/pre-commit). Please run ``pre-commit install`` the first time you are editing. Thereafter all commits will be checked against black and flake8 guidelines.
57
+
58
+ To check if your changes pass pre-commit without committing, run:
59
+
60
+ ```shell
61
+ pre-commit run --all-files
62
+ ```
app-bk.py DELETED
@@ -1,19 +0,0 @@
1
- import streamlit as st
2
- from multiapp import MultiApp
3
- from apps import (
4
- timelapse,
5
- gee_datasets,
6
- )
7
-
8
- st.set_page_config(layout="wide")
9
-
10
-
11
- apps = MultiApp()
12
-
13
- # Add all your application here
14
-
15
- apps.add_app("Create Timelapse", timelapse.app)
16
- apps.add_app("Awesome GEE Community Datasets", gee_datasets.app)
17
-
18
- # The main app
19
- apps.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py DELETED
@@ -1,50 +0,0 @@
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")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/Home.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Home page for Streamlit app."""
2
+ import streamlit as st
3
+ from src.config_parameters import config
4
+ from src.utils_sidebar import add_about, add_logo
5
+
6
+ # Page configuration
7
+ st.set_page_config(layout="wide")
8
+
9
+ # Create sidebar
10
+ add_logo("app/img/MA-logo.png")
11
+ add_about()
12
+
13
+ # Set fontisize text
14
+ st.markdown(
15
+ """
16
+ <style> p { font-size: %s; } </style>
17
+ """
18
+ % config["docs_fontsize"],
19
+ unsafe_allow_html=True,
20
+ )
21
+
22
+ # Page title
23
+ st.markdown("# Home")
24
+
25
+ # First section
26
+ st.markdown("## Introduction")
27
+ st.markdown(
28
+ """
29
+ This tool allows to estimate flood extent using Sentinel-1
30
+ synthetic-aperture radar
31
+ <a href='%s'>SAR</a> data.<br><br>
32
+ The methodology is based on a <a href=
33
+ '%s'>recommended practice</a>
34
+ published by the United Nations Platform for Space-based Information for
35
+ Disaster Management and Emergency Response (UN-SPIDER) and it uses several
36
+ satellite imagery datasets to produce the final output. The datasets are
37
+ retrieved from <a href='%s'>Google Earth
38
+ Engine</a> which is a powerful web-platform for cloud-based processing of
39
+ remote sensing data on large scales. More information on the methodology is
40
+ given in the Description.<br><br>
41
+ This analysis provides a comprehensive overview of a flooding event, across
42
+ different areas of interest, from settlements to countries. However, as
43
+ mentioned in the UN-SPIDER website, the methodology is meant for broad
44
+ information provision in a global context, and contains inherent
45
+ uncertainties. Therefore, it is important that the tool is not used as the
46
+ only source of information for rescue response planning.
47
+ """
48
+ % (
49
+ config["url_sentinel_esa"],
50
+ config["url_unspider_tutorial"],
51
+ config["url_gee"],
52
+ ),
53
+ unsafe_allow_html=True,
54
+ )
55
+
56
+ # Second section
57
+ st.markdown("## How to use the tool")
58
+ st.markdown(
59
+ """
60
+ <ul>
61
+ <li><p>
62
+ In the sidebar, choose <i>Flood extent analysis</i> to start the
63
+ analysis.
64
+ </p>
65
+ <li><p>
66
+ In the left panel, use the drawing tool to select an area of
67
+ interest on the map. You can delete your selection by clicking on
68
+ the bin icon. While the flood mapping is generated regardless of
69
+ the size of the selected region, you will be able to save raster
70
+ and vector flooding extent only if the side of the rectangular
71
+ selection does not exceed 100 km.
72
+ </p>
73
+ <li><p>
74
+ In the right panel click on the title <i>Choose Image Dates</i>
75
+ in order to expand the section. Here you need to select four dates.
76
+ The first two identify a range of dates based on which the
77
+ reference imagery (before the flooding event) is defined. You can
78
+ select even years worth of data (the reference imagery is
79
+ calculated as the median between the range of observations), but
80
+ make sure you take into account wet and dry seasons if only taking
81
+ a few months. The last two refer to a period of time which comes
82
+ after the flooding event. By setting periods, not single dates, you
83
+ allow the selection of enough tiles to cover the area of interest.
84
+ Sentinel-1 imagery is acquired minimum every 12 days for each point
85
+ on the globe (see Figure 2 in the documentation).
86
+ </p>
87
+ <li>
88
+ <p>
89
+ By clicking on <i>Choose parameters</i>, you will be able to
90
+ set two variables:
91
+ </p>
92
+ <ul>
93
+ <li><p>
94
+ The <i>threshold</i> is the value against which the
95
+ difference the two satellite images - before and after the
96
+ flooding event - is tested. Lower thresholds result in a
97
+ greater area considered "flooded". It is recommended to set
98
+ the value to 1.25, which was selected through trial and
99
+ error. You may want to adjust the value in case of high
100
+ rates of false positive or negative values, especially in
101
+ case other sources of information are available and it is
102
+ possible to compare flood extent estimations between
103
+ sources.
104
+ </p>
105
+ <li><p>
106
+ The <i>pass direction</i> has to do with the way the
107
+ satellite travels around the Earth. Depending on your area
108
+ of interest and time period, you may find more imagery
109
+ available for either the <i>Ascending</i> or the
110
+ <i>Descending</i> pass directions (see Figure 2 in the
111
+ Documentation). It is recommended to leave the parameter
112
+ unchanged for a first estimation and change its value in
113
+ case partial or no imagery is produced.
114
+ </p>
115
+ </ul>
116
+ <li><p>
117
+ Once the parameters are set, you can finally click on <i>Compute
118
+ flood extent</i> to run the calculations. A map will appear
119
+ underneath, with a layer containing the flooded area within the
120
+ area of interest.
121
+ </p>
122
+ <li><p>
123
+ If you wish to export the layer to file, you can click on <i>Export
124
+ to file</i> and download the raster and/or vector data.
125
+ </p>
126
+ </ul>
127
+ <p>
128
+ In case you get errors, follow the intructions. If you have doubts,
129
+ feel free to contact the Data Science team.
130
+ </p>
131
+ """,
132
+ unsafe_allow_html=True,
133
+ )
app/img/MA-logo.png ADDED
app/img/workflow.png ADDED
app/pages/1_🌍_Flood_extent_analysis.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Flood extent analysis page for Streamlit app."""
2
+ import datetime as dt
3
+
4
+ import ee
5
+ import folium
6
+ import geemap.foliumap as geemap
7
+ import requests
8
+ import streamlit as st
9
+ import streamlit_ext as ste
10
+ from folium.plugins import Draw, Geocoder, MiniMap
11
+ from src.config_parameters import config
12
+ from src.utils_flood_analysis import derive_flood_extents
13
+ from src.utils_sidebar import add_about, add_logo
14
+ from streamlit_folium import st_folium
15
+
16
+ # Page configuration
17
+ st.set_page_config(layout="wide")
18
+
19
+ # Create sidebar
20
+ add_logo("app/img/MA-logo.png")
21
+ add_about()
22
+
23
+ # Page title
24
+ st.markdown("# Flood extent analysis")
25
+
26
+ # Set styles for text fontsize and buttons
27
+ st.markdown(
28
+ """
29
+ <style>
30
+ .streamlit-expanderHeader {
31
+ font-size: %s;
32
+ color: #000053;
33
+ }
34
+ .stDateInput > label {
35
+ font-size: %s;
36
+ }
37
+ .stSlider > label {
38
+ font-size: %s;
39
+ }
40
+ .stRadio > label {
41
+ font-size: %s;
42
+ }
43
+ .stButton > button {
44
+ font-size: %s;
45
+ font-weight: %s;
46
+ background-color: %s;
47
+ }
48
+ </style>
49
+ """
50
+ % (
51
+ config["expander_header_fontsize"],
52
+ config["widget_header_fontsize"],
53
+ config["widget_header_fontsize"],
54
+ config["widget_header_fontsize"],
55
+ config["button_text_fontsize"],
56
+ config["button_text_fontweight"],
57
+ config["button_background_color"],
58
+ ),
59
+ unsafe_allow_html=True,
60
+ )
61
+
62
+
63
+ # Initialise Google Earth Engine
64
+ @st.cache
65
+ def _initialize_ee():
66
+ ee.Initialize()
67
+
68
+
69
+ _initialize_ee()
70
+
71
+
72
+ # Create app
73
+ def app():
74
+ """Create Streamlit app."""
75
+ # Output_created is useful to decide whether the bottom panel with the
76
+ # output map should be visualised or not
77
+ if "output_created" not in st.session_state:
78
+ st.session_state.output_created = False
79
+
80
+ # Function to be used to hide bottom panel (when setting parameters for a
81
+ # new analysis)
82
+ def callback():
83
+ st.session_state.output_created = False
84
+
85
+ # Create two rows: top and bottom panel
86
+ row1 = st.container()
87
+ row2 = st.container()
88
+ # Crate two columns in the top panel: input map and paramters
89
+ col1, col2 = row1.columns([2, 1])
90
+ with col1:
91
+ # Add collapsable container for input map
92
+ with st.expander("Input map", expanded=True):
93
+ # Create folium map
94
+ Map = folium.Map(
95
+ location=[52.205276, 0.119167],
96
+ zoom_start=3,
97
+ control_scale=True,
98
+ # crs='EPSG4326'
99
+ )
100
+ # Add drawing tools to map
101
+ Draw(
102
+ export=False,
103
+ draw_options={
104
+ "circle": False,
105
+ "polyline": False,
106
+ "polygon": False,
107
+ "circle": False,
108
+ "marker": False,
109
+ "circlemarker": False,
110
+ },
111
+ ).add_to(Map)
112
+ # Add search bar with geocoder to map
113
+ Geocoder(add_marker=False).add_to(Map)
114
+ # Add minimap to map
115
+ MiniMap().add_to(Map)
116
+ # Add file uploader for GeoJSON to add polygons to map
117
+ # data = st.file_uploader(
118
+ # "Upload a GeoJSON file to use as an ROI.",
119
+ # type=["geojson", "kml", "zip"],
120
+ # )
121
+ # ss = st.empty()
122
+ # with ss:
123
+ # Export map to Streamlit
124
+ output = st_folium(Map, width=800, height=600)
125
+ # if data is not None:
126
+ # with ss:
127
+ # # gj = geojson.load(data)
128
+ # # coords = gj['features'][0]['geometry']['coordinates']
129
+ # st.write('Still to be implemented')
130
+ with col2:
131
+ # Add collapsable container for image dates
132
+ with st.expander("Choose Image Dates"):
133
+ # Callback is added, so that, every time a parameters is changed,
134
+ # the bottom panel containing the output map is hidden
135
+ before_start = st.date_input(
136
+ "Start date for reference imagery",
137
+ value=dt.date(year=2022, month=7, day=1),
138
+ help="It needs to be prior to the flooding event",
139
+ on_change=callback,
140
+ )
141
+ before_end = st.date_input(
142
+ "End date for reference imagery",
143
+ value=dt.date(year=2022, month=7, day=30),
144
+ help=(
145
+ "It needs to be prior to the flooding event, at least 15 "
146
+ "days subsequent to the date selected above"
147
+ ),
148
+ on_change=callback,
149
+ )
150
+ after_start = st.date_input(
151
+ "Start date for flooding imagery",
152
+ value=dt.date(year=2022, month=9, day=1),
153
+ help="It needs to be subsequent to the flooding event",
154
+ on_change=callback,
155
+ )
156
+ after_end = st.date_input(
157
+ "End date for flooding imagery",
158
+ value=dt.date(year=2022, month=9, day=16),
159
+ help=(
160
+ "It needs to be subsequent to the flooding event and at "
161
+ "least 10 days to the date selected above"
162
+ ),
163
+ on_change=callback,
164
+ )
165
+ # Add collapsable container for parameters
166
+ with st.expander("Choose Parameters"):
167
+ # Add slider for threshold
168
+ add_slider = st.slider(
169
+ label="Select a threshold",
170
+ min_value=0.0,
171
+ max_value=5.0,
172
+ value=1.25,
173
+ step=0.25,
174
+ help="Higher values might reduce overall noise",
175
+ on_change=callback,
176
+ )
177
+ # Add radio buttons for pass direction
178
+ pass_direction = st.radio(
179
+ "Set pass direction",
180
+ ["Ascending", "Descending"],
181
+ on_change=callback,
182
+ )
183
+ # Button for computation
184
+ submitted = st.button("Compute flood extent")
185
+ # Introduce date validation
186
+ check_dates = before_start < before_end <= after_start < after_end
187
+ # Introduce drawing validation (a polygon needs to exist)
188
+ check_drawing = (
189
+ output["all_drawings"] != [] and output["all_drawings"] is not None
190
+ )
191
+ # What happens when button is clicked on?
192
+ if submitted:
193
+ with col2:
194
+ # Output error if dates are not valid
195
+ if not check_dates:
196
+ st.error("Make sure that the dates were inserted correctly")
197
+ # Output error if no polygons were drawn
198
+ elif not check_drawing:
199
+ st.error("No region selected.")
200
+ else:
201
+ # Add output for computation
202
+ with st.spinner("Computing... Please wait..."):
203
+ # Extract coordinates from drawn polygon
204
+ coords = output["all_drawings"][-1]["geometry"][
205
+ "coordinates"
206
+ ][0]
207
+ # Create geometry from coordinates
208
+ ee_geom_region = ee.Geometry.Polygon(coords)
209
+ # Crate flood raster and vector
210
+ (
211
+ detected_flood_vector,
212
+ detected_flood_raster,
213
+ _,
214
+ _,
215
+ ) = derive_flood_extents(
216
+ aoi=ee_geom_region,
217
+ before_start_date=str(before_start),
218
+ before_end_date=str(before_end),
219
+ after_start_date=str(after_start),
220
+ after_end_date=str(after_end),
221
+ difference_threshold=add_slider,
222
+ polarization="VH",
223
+ pass_direction=pass_direction,
224
+ export=False,
225
+ )
226
+ # Create output map
227
+ Map2 = geemap.Map(
228
+ # basemap="HYBRID",
229
+ plugin_Draw=False,
230
+ Draw_export=False,
231
+ locate_control=False,
232
+ plugin_LatLngPopup=False,
233
+ )
234
+ try:
235
+ # Add flood vector layer to map
236
+ Map2.add_layer(
237
+ ee_object=detected_flood_vector,
238
+ name="Flood extent vector",
239
+ )
240
+ # Center map on flood raster
241
+ Map2.centerObject(detected_flood_raster)
242
+ except ee.EEException:
243
+ # If error contains the sentence below, it means that
244
+ # an image could not be properly generated
245
+ st.error(
246
+ """
247
+ No satellite image found for the selected
248
+ dates.\n\n
249
+ Try changing the pass direction or the
250
+ polarization.\n\n
251
+ If this does not work, choose different
252
+ dates: it is likely that the satellite did not
253
+ cover the area of interest in the range of
254
+ dates specified (either before or after the
255
+ flooding event).
256
+ """
257
+ )
258
+ else:
259
+ # If computation was succesfull, save outputs for
260
+ # output map
261
+ st.success("Computation complete")
262
+ st.session_state.output_created = True
263
+ st.session_state.Map2 = Map2
264
+ st.session_state.detected_flood_raster = (
265
+ detected_flood_raster
266
+ )
267
+ st.session_state.detected_flood_vector = (
268
+ detected_flood_vector
269
+ )
270
+ st.session_state.ee_geom_region = ee_geom_region
271
+ # If computation was successful, create output map in bottom panel
272
+ if st.session_state.output_created:
273
+ with row2:
274
+ # Add collapsable container for output map
275
+ with st.expander("Output map", expanded=True):
276
+ # Export Map2 to streamlit
277
+ st.session_state.Map2.to_streamlit()
278
+ # Create button to export to file
279
+ submitted2 = st.button("Export to file")
280
+ # What happens if button is clicked on?
281
+ if submitted2:
282
+ # Add output for computation
283
+ with st.spinner("Computing... Please wait..."):
284
+ try:
285
+ # Get download url for raster data
286
+ raster = st.session_state.detected_flood_raster
287
+ url_r = raster.getDownloadUrl(
288
+ {
289
+ "region": st.session_state.ee_geom_region,
290
+ "scale": 30,
291
+ "format": "GEO_TIFF",
292
+ }
293
+ )
294
+ except Exception:
295
+ st.error(
296
+ """
297
+ The image size is too big for the image to
298
+ be exported to file. Select a smaller area
299
+ of interest (side <~ 150km) and repeat the
300
+ analysis.
301
+ """
302
+ )
303
+ else:
304
+ response_r = requests.get(url_r)
305
+ # Get download url for raster data
306
+ vector = st.session_state.detected_flood_vector
307
+ url_v = vector.getDownloadUrl("GEOJSON")
308
+ response_v = requests.get(url_v)
309
+ with row2:
310
+ # Create download buttons for raster and vector
311
+ # data
312
+ with open("flood_extent.tif", "wb"):
313
+ ste.download_button(
314
+ label="Download Raster Extent",
315
+ data=response_r.content,
316
+ file_name="flood_extent_raster.tif",
317
+ mime="image/tif",
318
+ )
319
+ with open("flood_extent.geojson", "wb"):
320
+ ste.download_button(
321
+ label="Download Vector Extent",
322
+ data=response_v.content,
323
+ file_name="flood_extent_vec.geojson",
324
+ mime="text/json",
325
+ )
326
+ # Output for computation complete
327
+ st.success("Computation complete")
328
+
329
+
330
+ # Run app
331
+ app()
app/pages/2_πŸ“–_Documentation.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Documentation page for Streamlit app."""
2
+ import streamlit as st
3
+ from PIL import Image
4
+ from src.config_parameters import config
5
+ from src.utils_sidebar import add_about, add_logo
6
+
7
+ # Page configuration
8
+ st.set_page_config(layout="wide")
9
+
10
+ # Create sidebar
11
+ add_logo("app/img/MA-logo.png")
12
+ add_about()
13
+
14
+ # Set fontisize text
15
+ st.markdown(
16
+ """
17
+ <style> p { font-size: %s; } </style>
18
+ """
19
+ % config["docs_fontsize"],
20
+ unsafe_allow_html=True,
21
+ )
22
+
23
+ # Page title
24
+ st.markdown("# Documentation")
25
+
26
+ # First section
27
+ st.markdown("## Methodology")
28
+ st.markdown(
29
+ """
30
+ The methodology is based on the workflow depicted in Figure 1. In
31
+ addition to Sentinel-1 synthetic-aperture radar <a href='%s'>SAR</a> data,
32
+ two other datasets are used through <a href='%s'>Google Earth Engine</a>:
33
+ <ul>
34
+ <li><p>
35
+ The <i>WWF HydroSHEDS Void-Filled DEM, 3 Arc-Seconds</i>
36
+ <a href='%s'>dataset</a> is based on elevation data
37
+ obtained in 2000 by NASA's Shuttle Radar Topography Mission (SRTM),
38
+ and it is used to mask out areas with more than 5 percent slope
39
+ (see following section on limitations).
40
+ </p>
41
+ <li><p>
42
+ The <i>JRC Global Surface Water Mapping Layers, v1.4</i>
43
+ <a href='%s'>dataset</a> contains maps of the
44
+ location and temporal distribution of surface water from 1984 to
45
+ 2021, and it is used to mask areas with perennial water bodies,
46
+ such as rivers or lakes.
47
+ </p>
48
+ </ul>
49
+ """
50
+ % (
51
+ config["url_sentinel_dataset"],
52
+ config["url_gee"],
53
+ config["url_elevation_dataset"],
54
+ config["url_surface_water_dataset"],
55
+ ),
56
+ unsafe_allow_html=True,
57
+ )
58
+
59
+ # Add image workflow
60
+ img = Image.open("app/img/workflow.png")
61
+ col1, mid, col2, last = st.columns([5, 3, 10, 10])
62
+ with col1:
63
+ st.image(img, width=350)
64
+ with col2:
65
+ # Trick to add caption at the bottom of the column, as Streamlit has not
66
+ # developed a functionality to allign text to bottom
67
+ space_before_caption = "<br>" * 27
68
+ st.markdown(
69
+ space_before_caption,
70
+ unsafe_allow_html=True,
71
+ )
72
+ st.markdown(
73
+ """
74
+ <p style="font-size:%s;">
75
+ Figure 1. Workflow of the flood mapping methodology (<a href=
76
+ '%s'>source</a>).
77
+ </p>
78
+ """
79
+ % (
80
+ config["docs_caption_fontsize"],
81
+ config["url_unspider_tutorial_detail"],
82
+ ),
83
+ unsafe_allow_html=True,
84
+ )
85
+
86
+
87
+ # Second section
88
+ st.markdown("## Radar imagery for flood detection")
89
+ st.markdown(
90
+ """
91
+ While there are multiple change detections techniques for radar imagery,
92
+ the one used by Sentinel-1 is one of the simplest. Active radar satellites
93
+ produce active radiation directed at the land, and images are formed as a
94
+ function of the time it takes for that radiation to reach back to the
95
+ satellite. Because of this, radar systems are side-looking (otherwise
96
+ radiation from multiple areas would reach back at the same time). To be
97
+ detected and imaged, radiation needs to be scattered back, but not all
98
+ surfaces are equally able to scatter back, and that ability is also
99
+ influenced by the radiation's wavelength (shorter wavelengths are better at
100
+ detecting smaller objects, while longer wavelengths allow penetration,
101
+ which is good for forest canopies for example, and biomass studies).
102
+ Sentinel-1 satellites are C-band (~ 6 cm).<br><br>
103
+ Water is characterised by a mirror-like reflection mechanism, meaning that
104
+ no or very little radiation is scattered back to the satellite, so pixels
105
+ on the image will appear very dark. This very simple change detection takes
106
+ a "before" image, and looks for drops in intensity, dark spots, in the
107
+ "after" image.<br><br>
108
+ Sentinel-1 data is the result of measurements from a constellation of two
109
+ satellites, assing over the same areas following the same orbit on average
110
+ every 6 days. On Google Earth Engine, the processing level is Ground Range
111
+ Detected (GRD), meaning that it has been detected, multi-looked and
112
+ projected to ground range using an Earth ellipsoid model. GRD products
113
+ report on intensity of radiation, but have lost the phase and amplitude
114
+ information which is needed for other applications (interferometry for
115
+ example). These satellites emits in different polarizations, and can
116
+ acquire both single horizonal or vertical, or dual polarizations. Flood
117
+ water is best detected by using VH (vertical transmit and horizontal
118
+ receive), although VV (vertical transmit and vertical receive) can be
119
+ effective to identify partially submerged features. This tool uses VH
120
+ polarization. Figure 2 shows an overview of the Sentinel-1 observation
121
+ plan, where pass directions and coverage frequencies are highlighted.
122
+ """,
123
+ unsafe_allow_html=True,
124
+ )
125
+
126
+ # Add image satellite overview
127
+ st.image(
128
+ "%s" % config["url_sentinel_img"],
129
+ width=1000,
130
+ )
131
+ st.markdown(
132
+ """
133
+ <p style="font-size:%s;">
134
+ Figure 2. Overview of the Sentinel-1 observation plan (<a href=
135
+ '%s'>source</a>).
136
+ </p>
137
+ """
138
+ % (config["docs_caption_fontsize"], config["url_sentinel_img_location"]),
139
+ unsafe_allow_html=True,
140
+ )
141
+
142
+ # Third section
143
+ st.markdown("## Key limtations")
144
+ st.markdown(
145
+ """
146
+ Radar imagery is great for detecting floods, as it is good at picking up
147
+ water and it is not affected by the time of the day or clouds (at this
148
+ wavelength). But it has its limits, and performs actually quite bad if
149
+ having to detect water in mountainous regions, especially if with narrow
150
+ valleys, and in urban areas (urban canyons). The reasons are mainly around
151
+ the viewing angles, which can cause image distortions. This method may also
152
+ result in false positives for other land cover changes with smooth
153
+ surfaces, such as roads and sand. Rough surface texture caused by wind or
154
+ rainfall may also make it challenging for the radar imagery to identify
155
+ water bodies.
156
+ """,
157
+ unsafe_allow_html=True,
158
+ )
159
+
160
+
161
+ # Last section
162
+ st.markdown("## Useful links")
163
+ st.markdown(
164
+ """
165
+ <a href='%s'>UN-SPIDER recommended practice</a><br>
166
+ <a href='%s'>Sentinel-1 satellite imagery user guide</a><br>
167
+ Relevant scientific publications:
168
+ <a href='%s'>1</a>, <a href='%s'>2</a><br>
169
+ """
170
+ % (
171
+ config["url_unspider_tutorial"],
172
+ config["url_sentinel_esa"],
173
+ config["url_publication_1"],
174
+ config["url_publication_2"],
175
+ ),
176
+ unsafe_allow_html=True,
177
+ )
app/src/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Sources for Streamlit app."""
app/src/config_parameters.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration file."""
2
+ config = {
3
+ # Sidebar
4
+ "MA_logo_width": "60%",
5
+ "MA_logo_background_position": "35% 10%",
6
+ "sidebar_header_fontsize": "30px",
7
+ "sidebar_header_fontweight": "30px",
8
+ "about_box_background_color": "#dae7f4",
9
+ # Introduction and Documentation
10
+ "docs_fontsize": "1.2rem",
11
+ "docs_caption_fontsize": "1rem",
12
+ # Tool
13
+ "expander_header_fontsize": "23px",
14
+ "widget_header_fontsize": "18px",
15
+ "button_text_fontsize": "24px",
16
+ "button_text_fontweight": "bold",
17
+ "button_background_color": "#dae7f4",
18
+ # Data scientists involved
19
+ "data_scientists": {
20
+ "Piet": "[email protected]",
21
+ "Daniele": "[email protected]",
22
+ "Cate": "[email protected]",
23
+ },
24
+ # Urls
25
+ "url_data_science_wiki": (
26
+ "https://mapaction.atlassian.net/wiki/spaces/GAFO/overview"
27
+ ),
28
+ "url_gee": "https://earthengine.google.com/",
29
+ "url_project_wiki": (
30
+ "https://mapaction.atlassian.net/wiki/spaces/GAFO/pages/15920922751/"
31
+ "Rapid+flood+mapping+from+satellite+imagery"
32
+ ),
33
+ "url_github_repo": "https://github.com/mapaction/flood-extent-tool",
34
+ "url_sentinel_esa": (
35
+ "https://sentinel.esa.int/web/sentinel/user-guides/sentinel-1-sar"
36
+ ),
37
+ "url_sentinel_dataset": (
38
+ "https://developers.google.com/earth-engine/datasets/catalog/"
39
+ "COPERNICUS_S1_GRD"
40
+ ),
41
+ "url_sentinel_img": (
42
+ "https://sentinel.esa.int/documents/247904/4748961/Sentinel-1-Repeat-"
43
+ "Coverage-Frequency-Geometry-2021.jpg"
44
+ ),
45
+ "url_sentinel_img_location": (
46
+ "https://sentinel.esa.int/web/sentinel/missions/sentinel-1/"
47
+ "observation-scenario"
48
+ ),
49
+ "url_unspider_tutorial": (
50
+ "https://un-spider.org/advisory-support/recommended-practices/"
51
+ "recommended-practice-google-earth-engine-flood-mapping"
52
+ ),
53
+ "url_unspider_tutorial_detail": (
54
+ "https://un-spider.org/advisory-support/recommended-practices/"
55
+ "recommended-practice-google-earth-engine-flood-mapping/in-detail"
56
+ ),
57
+ "url_elevation_dataset": (
58
+ "https://developers.google.com/earth-engine/datasets/catalog/"
59
+ "WWF_HydroSHEDS_03VFDEM"
60
+ ),
61
+ "url_surface_water_dataset": (
62
+ "https://developers.google.com/earth-engine/datasets/catalog/"
63
+ "JRC_GSW1_4_GlobalSurfaceWater"
64
+ ),
65
+ "url_publication_1": (
66
+ "https://onlinelibrary.wiley.com/doi/full/10.1111/jfr3.12303"
67
+ ),
68
+ "url_publication_2": (
69
+ "https://www.sciencedirect.com/science/article/abs/pii/"
70
+ "S0924271620301702"
71
+ ),
72
+ }
app/src/utils_flood_analysis.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions to derive flood extent using Google Earth Engine."""
2
+ import time
3
+
4
+ import ee
5
+
6
+
7
+ def _check_task_completed(task_id, verbose=False):
8
+ """
9
+ Return True if a task export completes successfully, else returns false.
10
+
11
+ Inputs:
12
+ task_id (str): Google Earth Engine task id
13
+
14
+ Returns:
15
+ boolean
16
+
17
+ """
18
+ status = ee.data.getTaskStatus(task_id)[0]
19
+ if status["state"] in (
20
+ ee.batch.Task.State.CANCELLED,
21
+ ee.batch.Task.State.FAILED,
22
+ ):
23
+ if "error_message" in status:
24
+ if verbose:
25
+ print(status["error_message"])
26
+ return True
27
+ elif status["state"] == ee.batch.Task.State.COMPLETED:
28
+ return True
29
+ return False
30
+
31
+
32
+ def wait_for_tasks(task_ids, timeout=3600, verbose=False):
33
+ """
34
+ Wait for tasks to complete, fail, or timeout.
35
+
36
+ Wait for all active tasks if task_ids is not provided.
37
+ Note: Tasks will not be canceled after timeout, and
38
+ may continue to run.
39
+ Inputs:
40
+ task_ids (list):
41
+ timeout (int):
42
+
43
+ Returns:
44
+ None
45
+ """
46
+ start = time.time()
47
+ elapsed = 0
48
+ while elapsed < timeout or timeout == 0:
49
+ elapsed = time.time() - start
50
+ finished = [_check_task_completed(task) for task in task_ids]
51
+ if all(finished):
52
+ if verbose:
53
+ print(f"Tasks {task_ids} completed after {elapsed}s")
54
+ return True
55
+ time.sleep(5)
56
+ if verbose:
57
+ print(
58
+ f"Stopped waiting for {len(task_ids)} tasks \
59
+ after {timeout} seconds"
60
+ )
61
+ return False
62
+
63
+
64
+ def export_flood_data(
65
+ flooded_area_vector,
66
+ flooded_area_raster,
67
+ image_before_flood,
68
+ image_after_flood,
69
+ region,
70
+ filename="flood_extents",
71
+ verbose=False,
72
+ ):
73
+ """
74
+ Export the results of derive_flood_extents function to Google Drive.
75
+
76
+ Inputs:
77
+ flooded_area_vector (ee.FeatureCollection): Detected flood extents as
78
+ vector geometries.
79
+ flooded_area_raster (ee.Image): Detected flood extents as a binary
80
+ raster.
81
+ image_before_flood (ee.Image): The 'before' Sentinel-1 image.
82
+ image_after_flood (ee.Image): The 'after' Sentinel-1 image containing
83
+ view of the flood waters.
84
+ region (ee.Geometry.Polygon): Geographic extent of analysis area.
85
+ filename (str): Desired filename prefix for exported files
86
+
87
+ Returns:
88
+ None
89
+ """
90
+ if verbose:
91
+ print(
92
+ "Exporting detected flood extents to your Google Drive. \
93
+ Please wait..."
94
+ )
95
+ s1_before_task = ee.batch.Export.image.toDrive(
96
+ image=image_before_flood,
97
+ description="export_before_s1_scene",
98
+ scale=30,
99
+ region=region,
100
+ fileNamePrefix=filename + "_s1_before",
101
+ crs="EPSG:4326",
102
+ fileFormat="GeoTIFF",
103
+ )
104
+
105
+ s1_after_task = ee.batch.Export.image.toDrive(
106
+ image=image_after_flood,
107
+ description="export_flooded_s1_scene",
108
+ scale=30,
109
+ region=region,
110
+ fileNamePrefix=filename + "_s1_after",
111
+ crs="EPSG:4326",
112
+ fileFormat="GeoTIFF",
113
+ )
114
+
115
+ raster_task = ee.batch.Export.image.toDrive(
116
+ image=flooded_area_raster,
117
+ description="export_flood_extents_raster",
118
+ scale=30,
119
+ region=region,
120
+ fileNamePrefix=filename + "_raster",
121
+ crs="EPSG:4326",
122
+ fileFormat="GeoTIFF",
123
+ )
124
+
125
+ vector_task = ee.batch.Export.table.toDrive(
126
+ collection=flooded_area_vector,
127
+ description="export_flood_extents_polygons",
128
+ fileFormat="shp",
129
+ fileNamePrefix=filename + "_polygons",
130
+ )
131
+
132
+ s1_before_task.start()
133
+ s1_after_task.start()
134
+ raster_task.start()
135
+ vector_task.start()
136
+
137
+ if verbose:
138
+ print("Exporting before Sentinel-1 scene: Task id ", s1_before_task.id)
139
+ print("Exporting flooded Sentinel-1 scene: Task id ", s1_after_task.id)
140
+ print("Exporting flood extent geotiff: Task id ", raster_task.id)
141
+ print("Exporting flood extent shapefile: Task id ", vector_task.id)
142
+
143
+ wait_for_tasks(
144
+ [s1_before_task.id, s1_after_task.id, raster_task.id, vector_task.id]
145
+ )
146
+
147
+
148
+ def retrieve_image_collection(
149
+ search_region,
150
+ start_date,
151
+ end_date,
152
+ polarization="VH",
153
+ pass_direction="Ascending",
154
+ ):
155
+ """
156
+ Retrieve Sentinel-1 immage collection from Google Earth Engine.
157
+
158
+ Inputs:
159
+ search_region (ee.Geometry.Polygon): Geographic extent of image search.
160
+ start_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
161
+ end_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
162
+ polarization (str): Synthetic aperture radar polarization mode, e.g.,
163
+ 'VH' or 'VV'. VH is mostly is the preferred polarization for
164
+ flood mapping.
165
+ pass_direction (str): Synthetic aperture radar pass direction, either
166
+ 'Ascending' or 'Descending'.
167
+
168
+ Returns:
169
+ collection (ee.ImageCollection): Sentinel-1 images matching the search
170
+ criteria.
171
+ """
172
+ collection = (
173
+ ee.ImageCollection("COPERNICUS/S1_GRD")
174
+ .filter(ee.Filter.eq("instrumentMode", "IW"))
175
+ .filter(
176
+ ee.Filter.listContains(
177
+ "transmitterReceiverPolarisation", polarization
178
+ )
179
+ )
180
+ .filter(ee.Filter.eq("orbitProperties_pass", pass_direction.upper()))
181
+ .filter(ee.Filter.eq("resolution_meters", 10))
182
+ .filterDate(start_date, end_date)
183
+ .filterBounds(search_region)
184
+ .select(polarization)
185
+ )
186
+
187
+ return collection
188
+
189
+
190
+ def smooth(image, smoothing_radius=50):
191
+ """
192
+ Reduce the radar speckle by smoothing.
193
+
194
+ Inputs:
195
+ image (ee.Image): Input image.
196
+ smoothing_radius (int): The radius of the kernel to use for focal mean
197
+ smoothing.
198
+
199
+ Returns:
200
+ smoothed_image (ee.Image): The resulting image after smoothing is
201
+ applied.
202
+ """
203
+ smoothed_image = image.focal_mean(
204
+ radius=smoothing_radius, kernelType="circle", units="meters"
205
+ )
206
+
207
+ return smoothed_image
208
+
209
+
210
+ def mask_permanent_water(image):
211
+ """
212
+ Query the JRC Global Surface Water Mapping Layers, v1.3.
213
+
214
+ The goal is to determine where perennial water bodies (water > 10
215
+ months/yr), and mask these areas.
216
+ Inputs:
217
+ image (ee.Image): Input image.
218
+
219
+ Returns:
220
+ masked_image (ee.Image): The resulting image after surface water
221
+ masking is applied.
222
+ """
223
+ surface_water = ee.Image("JRC/GSW1_4/GlobalSurfaceWater").select(
224
+ "seasonality"
225
+ )
226
+ surface_water_mask = surface_water.gte(10).updateMask(
227
+ surface_water.gte(10)
228
+ )
229
+
230
+ # Flooded layer where perennial water bodies(water > 10 mo / yr) is
231
+ # assigned a 0 value
232
+ where_surface_water = image.where(surface_water_mask, 0)
233
+
234
+ masked_image = image.updateMask(where_surface_water)
235
+
236
+ return masked_image
237
+
238
+
239
+ def reduce_noise(image):
240
+ """
241
+ Reduce noise in the image.
242
+
243
+ Compute connectivity of pixels to eliminate those connected to 8 or fewer
244
+ neighbours.
245
+ Inputs:
246
+ image (ee.Image): A binary image.
247
+
248
+ Returns:
249
+ reduced_noise_image (ee.Image): The resulting image after noise
250
+ reduction is applied.
251
+ """
252
+ connections = image.connectedPixelCount()
253
+ reduced_noise_image = image.updateMask(connections.gte(8))
254
+
255
+ return reduced_noise_image
256
+
257
+
258
+ def mask_slopes(image):
259
+ """
260
+ Mask out areas with more than 5 % slope with a Digital Elevation Model.
261
+
262
+ Inputs:
263
+ image (ee.Image): Input image.
264
+ Returns:
265
+ slopes_masked (ee.Image): The resulting image after slope masking is
266
+ applied.
267
+ """
268
+ dem = ee.Image("WWF/HydroSHEDS/03VFDEM")
269
+ terrain = ee.Algorithms.Terrain(dem)
270
+ slope = terrain.select("slope")
271
+ slopes_masked = image.updateMask(slope.lt(5))
272
+
273
+ return slopes_masked
274
+
275
+
276
+ def derive_flood_extents(
277
+ aoi,
278
+ before_start_date,
279
+ before_end_date,
280
+ after_start_date,
281
+ after_end_date,
282
+ difference_threshold=1.25,
283
+ polarization="VH",
284
+ pass_direction="Ascending",
285
+ export=False,
286
+ export_filename="flood_extents",
287
+ ):
288
+ """
289
+ Set start and end dates of a period BEFORE and AFTER a flood.
290
+
291
+ These periods need to be long enough for Sentinel-1 to acquire an image.
292
+
293
+ Inputs:
294
+ aoi (ee.Geometry.Polygon): Geographic extent of analysis area.
295
+ before_start_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
296
+ before_end_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
297
+ after_start_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
298
+ after_end_date (str): Date in format yyyy-mm-dd, e.g., '2020-10-01'.
299
+ difference_threshold (float): Threshold to be applied on the
300
+ differenced image (after flood - before flood). It has been chosen
301
+ by trial and error. In case your flood extent result shows many
302
+ false-positive or negative signals, consider changing it.
303
+ export (bool): Flag to export derived flood extents to Google Drive
304
+ export_filename (str): Desired filename prefix for exported files. Only
305
+ used if export=True.
306
+
307
+ Returns:
308
+ flood_vectors (ee.FeatureCollection): Detected flood extents as vector
309
+ geometries.
310
+ flood_rasters (ee.Image): Detected flood extents as a binary raster.
311
+ before_filtered (ee.Image): The 'before' Sentinel-1 image.
312
+ after_filtered (ee.Image): The 'after' Sentinel-1 image containing view
313
+ of the flood waters.
314
+ """
315
+ before_flood_img_col = retrieve_image_collection(
316
+ search_region=aoi,
317
+ start_date=before_start_date,
318
+ end_date=before_end_date,
319
+ polarization=polarization,
320
+ pass_direction=pass_direction,
321
+ )
322
+ after_flood_img_col = retrieve_image_collection(
323
+ search_region=aoi,
324
+ start_date=after_start_date,
325
+ end_date=after_end_date,
326
+ polarization=polarization,
327
+ pass_direction=pass_direction,
328
+ )
329
+
330
+ # Create a mosaic of selected tiles and clip to study area
331
+ before_mosaic = before_flood_img_col.mosaic().clip(aoi)
332
+ after_mosaic = after_flood_img_col.mosaic().clip(aoi)
333
+
334
+ before_filtered = smooth(before_mosaic)
335
+ after_filtered = smooth(after_mosaic)
336
+
337
+ # Calculate the difference between the before and after images
338
+ difference = after_filtered.divide(before_filtered)
339
+
340
+ # Apply the predefined difference - threshold and create the flood extent
341
+ # mask
342
+ difference_binary = difference.gt(difference_threshold)
343
+ difference_binary_masked = mask_permanent_water(difference_binary)
344
+ difference_binary_masked_reduced_noise = reduce_noise(
345
+ difference_binary_masked
346
+ )
347
+ flood_rasters = mask_slopes(difference_binary_masked_reduced_noise)
348
+
349
+ # Export the extent of detected flood in vector format
350
+ flood_vectors = flood_rasters.reduceToVectors(
351
+ scale=10,
352
+ geometryType="polygon",
353
+ geometry=aoi,
354
+ eightConnected=False,
355
+ bestEffort=True,
356
+ tileScale=2,
357
+ )
358
+
359
+ if export:
360
+ export_flood_data(
361
+ flooded_area_vector=flood_vectors,
362
+ flooded_area_raster=flood_rasters,
363
+ image_before_flood=before_filtered,
364
+ image_after_flood=after_filtered,
365
+ region=aoi,
366
+ filename=export_filename,
367
+ )
368
+
369
+ return flood_vectors, flood_rasters, before_filtered, after_filtered
app/src/utils_sidebar.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Functions for the sidebar of the Streamlit app."""
2
+ import base64
3
+ from datetime import date
4
+
5
+ import streamlit as st
6
+ from src.config_parameters import config
7
+
8
+ sidebar_title = "Flood Mapping Tool"
9
+
10
+
11
+ @st.cache(allow_output_mutation=True)
12
+ def get_base64_of_bin_file(png_file):
13
+ """
14
+ Get base64 from image file.
15
+
16
+ Inputs:
17
+ png_file (str): image filename
18
+
19
+ Returns:
20
+ str: encoded ASCII file
21
+ """
22
+ with open(png_file, "rb") as f:
23
+ data = f.read()
24
+ return base64.b64encode(data).decode()
25
+
26
+
27
+ def build_markup_for_logo(
28
+ png_file,
29
+ background_position=f"{config['MA_logo_background_position']}",
30
+ image_width=f"{config['MA_logo_width']}",
31
+ image_height="",
32
+ sidebar_header_fontsize=config["sidebar_header_fontsize"],
33
+ sidebar_header_fontweight=config["sidebar_header_fontweight"],
34
+ ):
35
+ """
36
+ Create full string for navigation bar, including logo and title.
37
+
38
+ Inputs:
39
+ png_file (str): image filename
40
+ background_position (str): position logo
41
+ image_width (str): width logo
42
+ image_height (str): height logo
43
+
44
+ Returns
45
+ str: full string with logo and title for sidebar
46
+ """
47
+ binary_string = get_base64_of_bin_file(png_file)
48
+ return """
49
+ <style>
50
+ [data-testid="stSidebarNav"] {
51
+ background-image: url("data:image/png;base64,%s");
52
+ background-repeat: no-repeat;
53
+ padding-top: 50px;
54
+ padding-bottom: 10px;
55
+ background-position: %s;
56
+ background-size: %s %s;
57
+ }
58
+ [data-testid="stSidebarNav"]::before {
59
+ content: %s;
60
+ margin-left: 20px;
61
+ margin-top: 20px;
62
+ margin-bottom: 20px;
63
+ padding-bottom: 50px;
64
+ font-size: %s;
65
+ font-weight: %s;
66
+ position: relative;
67
+ top: 85px;
68
+ }
69
+ </style>
70
+ """ % (
71
+ binary_string,
72
+ background_position,
73
+ image_width,
74
+ image_height,
75
+ sidebar_title,
76
+ sidebar_header_fontsize,
77
+ sidebar_header_fontweight,
78
+ )
79
+
80
+
81
+ def add_logo(png_file):
82
+ """
83
+ Add logo to sidebar.
84
+
85
+ Inputs:
86
+ png_file (str): image filename
87
+ Returns:
88
+ None
89
+ """
90
+ logo_markup = build_markup_for_logo(png_file)
91
+ st.markdown(
92
+ logo_markup,
93
+ unsafe_allow_html=True,
94
+ )
95
+
96
+
97
+ def add_about():
98
+ """
99
+ Add about and contacts to sidebar.
100
+
101
+ Inputs:
102
+ None
103
+ Returns:
104
+ None
105
+ """
106
+ today = date.today().strftime("%B %d, %Y")
107
+
108
+ # About textbox
109
+ st.sidebar.markdown("## About")
110
+ st.sidebar.markdown(
111
+ """
112
+ <div class='warning' style='
113
+ background-color: %s;
114
+ margin: 0px;
115
+ padding: 1em;'
116
+ '>
117
+ <p style='
118
+ margin-left:1em;
119
+ margin: 0px;
120
+ font-size: 1rem;
121
+ margin-bottom: 1em;
122
+ '>
123
+ Last update: %s
124
+ </p>
125
+ <p style='
126
+ margin-left:1em;
127
+ font-size: 1rem;
128
+ margin: 0px
129
+ '>
130
+ <a href='%s'>
131
+ Wiki reference page</a><br>
132
+ <a href='%s'>
133
+ GitHub repository</a><br>
134
+ <a href='%s'>
135
+ Data Science Lab</a>
136
+ </p>
137
+ </div>
138
+ """
139
+ % (
140
+ config["about_box_background_color"],
141
+ today,
142
+ config["url_project_wiki"],
143
+ config["url_github_repo"],
144
+ config["url_data_science_wiki"],
145
+ ),
146
+ unsafe_allow_html=True,
147
+ )
148
+
149
+ # Contacts textbox
150
+ st.sidebar.markdown(" ")
151
+ st.sidebar.markdown("## Contacts")
152
+
153
+ # Add data scientists and emails
154
+ contacts_text = ""
155
+ for ds, email in config["data_scientists"].items():
156
+ contacts_text += ds + (
157
+ "<span style='float:right; margin-right: 3px;'>"
158
+ "<a href='mailto:%s'>%s</a></span><br>" % (email, email)
159
+ )
160
+
161
+ # Add text box
162
+ st.sidebar.markdown(
163
+ """
164
+ <div class='warning' style='
165
+ background-color: %s;
166
+ margin: 0px;
167
+ padding: 1em;'
168
+ '>
169
+ <p style='
170
+ margin-left:1em;
171
+ font-size: 1rem;
172
+ margin: 0px
173
+ '>
174
+ %s
175
+ </p>
176
+ </div>
177
+ """
178
+ % (config["about_box_background_color"], contacts_text),
179
+ unsafe_allow_html=True,
180
+ )
apps/gee_datasets.py DELETED
@@ -1,186 +0,0 @@
1
- import ee
2
- import streamlit as st
3
- import geemap.foliumap as geemap
4
-
5
- WIDTH = 1060
6
- HEIGHT = 600
7
-
8
-
9
- def function():
10
- st.write("Not implemented yet.")
11
- Map = geemap.Map()
12
- Map.to_streamlit(WIDTH, HEIGHT)
13
-
14
-
15
- def lulc_mrb_floodplain():
16
-
17
- Map = geemap.Map()
18
-
19
- State_boundaries = ee.FeatureCollection('users/giswqs/MRB/State_Boundaries')
20
- State_style = State_boundaries.style(
21
- **{'color': '808080', 'width': 1, 'fillColor': '00000000'}
22
- )
23
-
24
- MRB_boundary = ee.FeatureCollection('users/giswqs/MRB/MRB_Boundary')
25
- MRB_style = MRB_boundary.style(
26
- **{'color': '000000', 'width': 2, 'fillColor': '00000000'}
27
- )
28
-
29
- floodplain = ee.Image('users/giswqs/MRB/USGS_Floodplain')
30
-
31
- class_values = [34, 38, 46, 50, 62]
32
- class_palette = ['c500ff', '00ffc5', '00a9e6', '73004d', '004d73']
33
-
34
- img_1950 = ee.Image('users/giswqs/MRB/Major_Transitions_1941_1950')
35
- img_1950 = img_1950.set('b1_class_values', class_values)
36
- img_1950 = img_1950.set('b1_class_palette', class_palette)
37
-
38
- img_1960 = ee.Image('users/giswqs/MRB/Major_Transitions_1941_1960')
39
- img_1960 = img_1960.set('b1_class_values', class_values)
40
- img_1960 = img_1960.set('b1_class_palette', class_palette)
41
-
42
- img_1970 = ee.Image('users/giswqs/MRB/Major_Transitions_1941_1970')
43
- img_1970 = img_1970.set('b1_class_values', class_values)
44
- img_1970 = img_1970.set('b1_class_palette', class_palette)
45
-
46
- img_1980 = ee.Image('users/giswqs/MRB/Major_Transitions_1941_1980')
47
- img_1980 = img_1980.set('b1_class_values', class_values)
48
- img_1980 = img_1980.set('b1_class_palette', class_palette)
49
-
50
- img_1990 = ee.Image('users/giswqs/MRB/Major_Transitions_1941_1990')
51
- img_1990 = img_1990.set('b1_class_values', class_values)
52
- img_1990 = img_1990.set('b1_class_palette', class_palette)
53
-
54
- img_2000 = ee.Image('users/giswqs/MRB/Major_Transitions_1941_2000')
55
- img_2000 = img_2000.set('b1_class_values', class_values)
56
- img_2000 = img_2000.set('b1_class_palette', class_palette)
57
-
58
- Map.addLayer(floodplain, {'palette': ['cccccc']}, 'Floodplain', True, 0.5)
59
- Map.addLayer(img_2000, {}, 'Major Transitions 1941-2000')
60
- Map.addLayer(img_1990, {}, 'Major Transitions 1941-1990')
61
- Map.addLayer(img_1980, {}, 'Major Transitions 1941-1980')
62
- Map.addLayer(img_1970, {}, 'Major Transitions 1941-1970')
63
- Map.addLayer(img_1960, {}, 'Major Transitions 1941-1960')
64
- Map.addLayer(img_1950, {}, 'Major Transitions 1941-1950')
65
-
66
- Map.addLayer(State_style, {}, 'State Boundaries')
67
- Map.addLayer(MRB_style, {}, 'MRB Boundary')
68
-
69
- Map.to_streamlit(WIDTH, HEIGHT)
70
-
71
-
72
- def global_mangrove_watch():
73
- """https://samapriya.github.io/awesome-gee-community-datasets/projects/mangrove/"""
74
- Map = geemap.Map()
75
- gmw2007 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_2007_v2")
76
- gmw2008 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_2008_v2")
77
- gmw2009 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_2009_v2")
78
- gmw2010 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_2010_v2")
79
- gmw2015 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_2015_v2")
80
- gmw2016 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_2016_v2")
81
- gmw1996 = ee.FeatureCollection("projects/sat-io/open-datasets/GMW/GMW_1996_v2")
82
-
83
- Map.addLayer(
84
- ee.Image().paint(gmw1996, 0, 3),
85
- {"palette": ["228B22"]},
86
- 'Global Mangrove Watch 1996',
87
- )
88
- Map.addLayer(
89
- ee.Image().paint(gmw2007, 0, 3),
90
- {"palette": ["228B22"]},
91
- 'Global Mangrove Watch 2007',
92
- )
93
- Map.addLayer(
94
- ee.Image().paint(gmw2008, 0, 3),
95
- {"palette": ["228B22"]},
96
- 'Global Mangrove Watch 2008',
97
- )
98
- Map.addLayer(
99
- ee.Image().paint(gmw2009, 0, 3),
100
- {"palette": ["228B22"]},
101
- 'Global Mangrove Watch 2009',
102
- )
103
- Map.addLayer(
104
- ee.Image().paint(gmw2010, 0, 3),
105
- {"palette": ["228B22"]},
106
- 'Global Mangrove Watch 2010',
107
- )
108
- Map.addLayer(
109
- ee.Image().paint(gmw2015, 0, 3),
110
- {"palette": ["228B22"]},
111
- 'Global Mangrove Watch 2015',
112
- )
113
- Map.addLayer(
114
- ee.Image().paint(gmw2016, 0, 3),
115
- {"palette": ["228B22"]},
116
- 'Global Mangrove Watch 2015',
117
- )
118
-
119
- Map.to_streamlit(WIDTH, HEIGHT)
120
-
121
-
122
- def app():
123
-
124
- st.title("Awesome GEE Community Datasets")
125
-
126
- st.markdown(
127
- """
128
-
129
- This app is for exploring the [Awesome GEE Community Datasets](https://samapriya.github.io/awesome-gee-community-datasets). Work in progress.
130
-
131
- """
132
- )
133
-
134
- datasets = {
135
- "Population & Socioeconomic": {
136
- "High Resolution Settlement Layer": "function()",
137
- "World Settlement Footprint (2015)": "function()",
138
- "Gridded Population of the World": "function()",
139
- "geoBoundaries Global Database": "function()",
140
- "West Africa Coastal Vulnerability Mapping": "function()",
141
- "Relative Wealth Index (RWI)": "function()",
142
- "Social Connectedness Index (SCI)": "function()",
143
- "Native Land (Indigenous Land Maps)": "function()",
144
- },
145
- "Geophysical, Biological & Biogeochemical": {
146
- "Geomorpho90m Geomorphometric Layers": "function()",
147
- },
148
- "Land Use and Land Cover": {
149
- "Global Mangrove Watch": "global_mangrove_watch()",
150
- "Mississippi River Basin Floodplain Land Use Change (1941-2000)": "lulc_mrb_floodplain()",
151
- },
152
- "Hydrology": {
153
- "Global Shoreline Dataset": "function()",
154
- },
155
- "Agriculture, Vegetation and Forestry": {
156
- "Landfire Mosaics LF v2.0.0": "function()",
157
- },
158
- "Global Utilities, Assets and Amenities Layers": {
159
- "Global Power": "function()",
160
- },
161
- "EarthEnv Biodiversity ecosystems & climate Layers": {
162
- "Global Consensus Landcover": "function()",
163
- },
164
- "Weather and Climate Layers": {
165
- "Global Reference Evapotranspiration Layers": "function()",
166
- },
167
- "Global Events Layers": {
168
- "Global Fire Atlas (2003-2016)": "function()",
169
- },
170
- }
171
-
172
- row1_col1, row1_col2, _ = st.columns([1.2, 1.8, 1])
173
-
174
- with row1_col1:
175
- category = st.selectbox("Select a category", datasets.keys(), index=2)
176
- with row1_col2:
177
- dataset = st.selectbox("Select a dataset", datasets[category].keys())
178
-
179
- Map = geemap.Map()
180
-
181
- if dataset:
182
- eval(datasets[category][dataset])
183
-
184
- else:
185
- Map = geemap.Map()
186
- Map.to_streamlit(WIDTH, HEIGHT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/rois.py DELETED
@@ -1,174 +0,0 @@
1
- """ A module for storing some sample ROIs for creating Landsat/GOES timelapse.
2
- """
3
-
4
- from shapely.geometry import Polygon
5
-
6
- goes_rois = {
7
- "Creek Fire, CA (2020-09-05)": {
8
- "region": Polygon(
9
- [
10
- [-121.003418, 36.848857],
11
- [-121.003418, 39.049052],
12
- [-117.905273, 39.049052],
13
- [-117.905273, 36.848857],
14
- [-121.003418, 36.848857],
15
- ]
16
- ),
17
- "start_time": "2020-09-05T15:00:00",
18
- "end_time": "2020-09-06T02:00:00",
19
- },
20
- "Bomb Cyclone (2021-10-24)": {
21
- "region": Polygon(
22
- [
23
- [-159.5954, 60.4088],
24
- [-159.5954, 24.5178],
25
- [-114.2438, 24.5178],
26
- [-114.2438, 60.4088],
27
- ]
28
- ),
29
- "start_time": "2021-10-24T14:00:00",
30
- "end_time": "2021-10-25T01:00:00",
31
- },
32
- "Hunga Tonga Volcanic Eruption (2022-01-15)": {
33
- "region": Polygon(
34
- [
35
- [-192.480469, -32.546813],
36
- [-192.480469, -8.754795],
37
- [-157.587891, -8.754795],
38
- [-157.587891, -32.546813],
39
- [-192.480469, -32.546813],
40
- ]
41
- ),
42
- "start_time": "2022-01-15T03:00:00",
43
- "end_time": "2022-01-15T07:00:00",
44
- },
45
- "Hunga Tonga Volcanic Eruption Closer Look (2022-01-15)": {
46
- "region": Polygon(
47
- [
48
- [-178.901367, -22.958393],
49
- [-178.901367, -17.85329],
50
- [-171.452637, -17.85329],
51
- [-171.452637, -22.958393],
52
- [-178.901367, -22.958393],
53
- ]
54
- ),
55
- "start_time": "2022-01-15T03:00:00",
56
- "end_time": "2022-01-15T07:00:00",
57
- },
58
- }
59
-
60
-
61
- landsat_rois = {
62
- "Aral Sea": Polygon(
63
- [
64
- [57.667236, 43.834527],
65
- [57.667236, 45.996962],
66
- [61.12793, 45.996962],
67
- [61.12793, 43.834527],
68
- [57.667236, 43.834527],
69
- ]
70
- ),
71
- "Dubai": Polygon(
72
- [
73
- [54.541626, 24.763044],
74
- [54.541626, 25.427152],
75
- [55.632019, 25.427152],
76
- [55.632019, 24.763044],
77
- [54.541626, 24.763044],
78
- ]
79
- ),
80
- "Hong Kong International Airport": Polygon(
81
- [
82
- [113.825226, 22.198849],
83
- [113.825226, 22.349758],
84
- [114.085121, 22.349758],
85
- [114.085121, 22.198849],
86
- [113.825226, 22.198849],
87
- ]
88
- ),
89
- "Las Vegas, NV": Polygon(
90
- [
91
- [-115.554199, 35.804449],
92
- [-115.554199, 36.558188],
93
- [-113.903503, 36.558188],
94
- [-113.903503, 35.804449],
95
- [-115.554199, 35.804449],
96
- ]
97
- ),
98
- "Pucallpa, Peru": Polygon(
99
- [
100
- [-74.672699, -8.600032],
101
- [-74.672699, -8.254983],
102
- [-74.279938, -8.254983],
103
- [-74.279938, -8.600032],
104
- ]
105
- ),
106
- "Sierra Gorda, Chile": Polygon(
107
- [
108
- [-69.315491, -22.837104],
109
- [-69.315491, -22.751488],
110
- [-69.190006, -22.751488],
111
- [-69.190006, -22.837104],
112
- [-69.315491, -22.837104],
113
- ]
114
- ),
115
- }
116
-
117
- modis_rois = {
118
- "World": Polygon(
119
- [
120
- [-171.210938, -57.136239],
121
- [-171.210938, 79.997168],
122
- [177.539063, 79.997168],
123
- [177.539063, -57.136239],
124
- [-171.210938, -57.136239],
125
- ]
126
- ),
127
- "Africa": Polygon(
128
- [
129
- [-18.6983, 38.1446],
130
- [-18.6983, -36.1630],
131
- [52.2293, -36.1630],
132
- [52.2293, 38.1446],
133
- ]
134
- ),
135
- "USA": Polygon(
136
- [
137
- [-127.177734, 23.725012],
138
- [-127.177734, 50.792047],
139
- [-66.269531, 50.792047],
140
- [-66.269531, 23.725012],
141
- [-127.177734, 23.725012],
142
- ]
143
- ),
144
- }
145
-
146
- ocean_rois = {
147
- "Gulf of Mexico": Polygon(
148
- [
149
- [-101.206055, 15.496032],
150
- [-101.206055, 32.361403],
151
- [-75.673828, 32.361403],
152
- [-75.673828, 15.496032],
153
- [-101.206055, 15.496032],
154
- ]
155
- ),
156
- "North Atlantic Ocean": Polygon(
157
- [
158
- [-85.341797, 24.046464],
159
- [-85.341797, 45.02695],
160
- [-55.810547, 45.02695],
161
- [-55.810547, 24.046464],
162
- [-85.341797, 24.046464],
163
- ]
164
- ),
165
- "World": Polygon(
166
- [
167
- [-171.210938, -57.136239],
168
- [-171.210938, 79.997168],
169
- [177.539063, 79.997168],
170
- [177.539063, -57.136239],
171
- [-171.210938, -57.136239],
172
- ]
173
- ),
174
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/timelapse.py DELETED
@@ -1,1313 +0,0 @@
1
- import ee
2
- import os
3
- import datetime
4
- import geopandas as gpd
5
- import folium
6
- import streamlit as st
7
- import geemap.colormaps as cm
8
- import geemap.foliumap as geemap
9
- from datetime import date
10
- from .rois import *
11
-
12
-
13
- @st.cache
14
- def uploaded_file_to_gdf(data):
15
- import tempfile
16
- import os
17
- import uuid
18
-
19
- _, file_extension = os.path.splitext(data.name)
20
- file_id = str(uuid.uuid4())
21
- file_path = os.path.join(tempfile.gettempdir(), f"{file_id}{file_extension}")
22
-
23
- with open(file_path, "wb") as file:
24
- file.write(data.getbuffer())
25
-
26
- if file_path.lower().endswith(".kml"):
27
- gpd.io.file.fiona.drvsupport.supported_drivers["KML"] = "rw"
28
- gdf = gpd.read_file(file_path, driver="KML")
29
- else:
30
- gdf = gpd.read_file(file_path)
31
-
32
- return gdf
33
-
34
-
35
- def app():
36
-
37
- today = date.today()
38
-
39
- st.title("Create Timelapse")
40
-
41
- st.markdown(
42
- """
43
- 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.
44
- 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).
45
- """
46
- )
47
-
48
- row1_col1, row1_col2 = st.columns([2, 1])
49
-
50
- if st.session_state.get("zoom_level") is None:
51
- st.session_state["zoom_level"] = 4
52
-
53
- st.session_state["ee_asset_id"] = None
54
- st.session_state["bands"] = None
55
- st.session_state["palette"] = None
56
- st.session_state["vis_params"] = None
57
-
58
- with row1_col1:
59
- m = geemap.Map(
60
- basemap="HYBRID",
61
- plugin_Draw=True,
62
- Draw_export=True,
63
- locate_control=True,
64
- plugin_LatLngPopup=False,
65
- )
66
- m.add_basemap("ROADMAP")
67
-
68
- with row1_col2:
69
-
70
- keyword = st.text_input("Search for a location:", "")
71
- if keyword:
72
- locations = geemap.geocode(keyword)
73
- if locations is not None and len(locations) > 0:
74
- str_locations = [str(g)[1:-1] for g in locations]
75
- location = st.selectbox("Select a location:", str_locations)
76
- loc_index = str_locations.index(location)
77
- selected_loc = locations[loc_index]
78
- lat, lng = selected_loc.lat, selected_loc.lng
79
- folium.Marker(location=[lat, lng], popup=location).add_to(m)
80
- m.set_center(lng, lat, 12)
81
- st.session_state["zoom_level"] = 12
82
-
83
- collection = st.selectbox(
84
- "Select a satellite image collection: ",
85
- [
86
- "Any Earth Engine ImageCollection",
87
- "Landsat TM-ETM-OLI Surface Reflectance",
88
- "Sentinel-2 MSI Surface Reflectance",
89
- "Geostationary Operational Environmental Satellites (GOES)",
90
- "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km",
91
- "MODIS Gap filled Land Surface Temperature Daily",
92
- "MODIS Ocean Color SMI",
93
- "USDA National Agriculture Imagery Program (NAIP)",
94
- ],
95
- index=1,
96
- )
97
-
98
- if collection in [
99
- "Landsat TM-ETM-OLI Surface Reflectance",
100
- "Sentinel-2 MSI Surface Reflectance",
101
- ]:
102
- roi_options = ["Uploaded GeoJSON"] + list(landsat_rois.keys())
103
-
104
- elif collection == "Geostationary Operational Environmental Satellites (GOES)":
105
- roi_options = ["Uploaded GeoJSON"] + list(goes_rois.keys())
106
-
107
- elif collection in [
108
- "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km",
109
- "MODIS Gap filled Land Surface Temperature Daily",
110
- ]:
111
- roi_options = ["Uploaded GeoJSON"] + list(modis_rois.keys())
112
- elif collection == "MODIS Ocean Color SMI":
113
- roi_options = ["Uploaded GeoJSON"] + list(ocean_rois.keys())
114
- else:
115
- roi_options = ["Uploaded GeoJSON"]
116
-
117
- if collection == "Any Earth Engine ImageCollection":
118
- keyword = st.text_input("Enter a keyword to search (e.g., MODIS):", "")
119
- if keyword:
120
-
121
- assets = geemap.search_ee_data(keyword)
122
- ee_assets = []
123
- for asset in assets:
124
- if asset["ee_id_snippet"].startswith("ee.ImageCollection"):
125
- ee_assets.append(asset)
126
-
127
- asset_titles = [x["title"] for x in ee_assets]
128
- dataset = st.selectbox("Select a dataset:", asset_titles)
129
- if len(ee_assets) > 0:
130
- st.session_state["ee_assets"] = ee_assets
131
- st.session_state["asset_titles"] = asset_titles
132
- index = asset_titles.index(dataset)
133
- ee_id = ee_assets[index]["id"]
134
- else:
135
- ee_id = ""
136
-
137
- if dataset is not None:
138
- with st.expander("Show dataset details", False):
139
- index = asset_titles.index(dataset)
140
- html = geemap.ee_data_html(st.session_state["ee_assets"][index])
141
- st.markdown(html, True)
142
- # elif collection == "MODIS Gap filled Land Surface Temperature Daily":
143
- # ee_id = ""
144
- else:
145
- ee_id = ""
146
-
147
- asset_id = st.text_input("Enter an ee.ImageCollection asset ID:", ee_id)
148
-
149
- if asset_id:
150
- with st.expander("Customize band combination and color palette", True):
151
- try:
152
- col = ee.ImageCollection.load(asset_id)
153
- st.session_state["ee_asset_id"] = asset_id
154
- except:
155
- st.error("Invalid Earth Engine asset ID.")
156
- st.session_state["ee_asset_id"] = None
157
- return
158
-
159
- img_bands = col.first().bandNames().getInfo()
160
- if len(img_bands) >= 3:
161
- default_bands = img_bands[:3][::-1]
162
- else:
163
- default_bands = img_bands[:]
164
- bands = st.multiselect(
165
- "Select one or three bands (RGB):", img_bands, default_bands
166
- )
167
- st.session_state["bands"] = bands
168
-
169
- if len(bands) == 1:
170
- palette_options = st.selectbox(
171
- "Color palette",
172
- cm.list_colormaps(),
173
- index=2,
174
- )
175
- palette_values = cm.get_palette(palette_options, 15)
176
- palette = st.text_area(
177
- "Enter a custom palette:",
178
- palette_values,
179
- )
180
- st.write(
181
- cm.plot_colormap(cmap=palette_options, return_fig=True)
182
- )
183
- st.session_state["palette"] = eval(palette)
184
-
185
- if bands:
186
- vis_params = st.text_area(
187
- "Enter visualization parameters",
188
- "{'bands': ["
189
- + ", ".join([f"'{band}'" for band in bands])
190
- + "]}",
191
- )
192
- else:
193
- vis_params = st.text_area(
194
- "Enter visualization parameters",
195
- "{}",
196
- )
197
- try:
198
- st.session_state["vis_params"] = eval(vis_params)
199
- st.session_state["vis_params"]["palette"] = st.session_state[
200
- "palette"
201
- ]
202
- except Exception as e:
203
- st.session_state["vis_params"] = None
204
- st.error(
205
- f"Invalid visualization parameters. It must be a dictionary."
206
- )
207
-
208
- elif collection == "MODIS Gap filled Land Surface Temperature Daily":
209
- with st.expander("Show dataset details", False):
210
- st.markdown(
211
- """
212
- See the [Awesome GEE Community Datasets](https://samapriya.github.io/awesome-gee-community-datasets/projects/daily_lst/).
213
- """
214
- )
215
-
216
- MODIS_options = ["Daytime (1:30 pm)", "Nighttime (1:30 am)"]
217
- MODIS_option = st.selectbox("Select a MODIS dataset:", MODIS_options)
218
- if MODIS_option == "Daytime (1:30 pm)":
219
- st.session_state[
220
- "ee_asset_id"
221
- ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_day_1km"
222
- else:
223
- st.session_state[
224
- "ee_asset_id"
225
- ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_night_1km"
226
-
227
- palette_options = st.selectbox(
228
- "Color palette",
229
- cm.list_colormaps(),
230
- index=90,
231
- )
232
- palette_values = cm.get_palette(palette_options, 15)
233
- palette = st.text_area(
234
- "Enter a custom palette:",
235
- palette_values,
236
- )
237
- st.write(cm.plot_colormap(cmap=palette_options, return_fig=True))
238
- st.session_state["palette"] = eval(palette)
239
- elif collection == "MODIS Ocean Color SMI":
240
- with st.expander("Show dataset details", False):
241
- st.markdown(
242
- """
243
- See the [Earth Engine Data Catalog](https://developers.google.com/earth-engine/datasets/catalog/NASA_OCEANDATA_MODIS-Aqua_L3SMI).
244
- """
245
- )
246
-
247
- MODIS_options = ["Aqua", "Terra"]
248
- MODIS_option = st.selectbox("Select a satellite:", MODIS_options)
249
- st.session_state["ee_asset_id"] = MODIS_option
250
- # if MODIS_option == "Daytime (1:30 pm)":
251
- # st.session_state[
252
- # "ee_asset_id"
253
- # ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_day_1km"
254
- # else:
255
- # st.session_state[
256
- # "ee_asset_id"
257
- # ] = "projects/sat-io/open-datasets/gap-filled-lst/gf_night_1km"
258
-
259
- band_dict = {
260
- "Chlorophyll a concentration": "chlor_a",
261
- "Normalized fluorescence line height": "nflh",
262
- "Particulate organic carbon": "poc",
263
- "Sea surface temperature": "sst",
264
- "Remote sensing reflectance at band 412nm": "Rrs_412",
265
- "Remote sensing reflectance at band 443nm": "Rrs_443",
266
- "Remote sensing reflectance at band 469nm": "Rrs_469",
267
- "Remote sensing reflectance at band 488nm": "Rrs_488",
268
- "Remote sensing reflectance at band 531nm": "Rrs_531",
269
- "Remote sensing reflectance at band 547nm": "Rrs_547",
270
- "Remote sensing reflectance at band 555nm": "Rrs_555",
271
- "Remote sensing reflectance at band 645nm": "Rrs_645",
272
- "Remote sensing reflectance at band 667nm": "Rrs_667",
273
- "Remote sensing reflectance at band 678nm": "Rrs_678",
274
- }
275
-
276
- band_options = list(band_dict.keys())
277
- band = st.selectbox(
278
- "Select a band",
279
- band_options,
280
- band_options.index("Sea surface temperature"),
281
- )
282
- st.session_state["band"] = band_dict[band]
283
-
284
- colors = cm.list_colormaps()
285
- palette_options = st.selectbox(
286
- "Color palette",
287
- colors,
288
- index=colors.index("coolwarm"),
289
- )
290
- palette_values = cm.get_palette(palette_options, 15)
291
- palette = st.text_area(
292
- "Enter a custom palette:",
293
- palette_values,
294
- )
295
- st.write(cm.plot_colormap(cmap=palette_options, return_fig=True))
296
- st.session_state["palette"] = eval(palette)
297
-
298
- sample_roi = st.selectbox(
299
- "Select a sample ROI or upload a GeoJSON file:",
300
- roi_options,
301
- index=0,
302
- )
303
-
304
- add_outline = st.checkbox(
305
- "Overlay an administrative boundary on timelapse", False
306
- )
307
-
308
- if add_outline:
309
-
310
- with st.expander("Customize administrative boundary", True):
311
-
312
- overlay_options = {
313
- "User-defined": None,
314
- "Continents": "continents",
315
- "Countries": "countries",
316
- "US States": "us_states",
317
- "China": "china",
318
- }
319
-
320
- overlay = st.selectbox(
321
- "Select an administrative boundary:",
322
- list(overlay_options.keys()),
323
- index=2,
324
- )
325
-
326
- overlay_data = overlay_options[overlay]
327
-
328
- if overlay_data is None:
329
- overlay_data = st.text_input(
330
- "Enter an HTTP URL to a GeoJSON file or an ee.FeatureCollection asset id:",
331
- "https://raw.githubusercontent.com/giswqs/geemap/master/examples/data/countries.geojson",
332
- )
333
-
334
- overlay_color = st.color_picker(
335
- "Select a color for the administrative boundary:", "#000000"
336
- )
337
- overlay_width = st.slider(
338
- "Select a line width for the administrative boundary:", 1, 20, 1
339
- )
340
- overlay_opacity = st.slider(
341
- "Select an opacity for the administrative boundary:",
342
- 0.0,
343
- 1.0,
344
- 1.0,
345
- 0.05,
346
- )
347
- else:
348
- overlay_data = None
349
- overlay_color = "black"
350
- overlay_width = 1
351
- overlay_opacity = 1
352
-
353
- with row1_col1:
354
-
355
- with st.expander(
356
- "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 πŸ‘‰"
357
- ):
358
- video_empty = st.empty()
359
-
360
- data = st.file_uploader(
361
- "Upload a GeoJSON file to use as an ROI. Customize timelapse parameters and then click the Submit button πŸ˜‡πŸ‘‡",
362
- type=["geojson", "kml", "zip"],
363
- )
364
-
365
- crs = "epsg:4326"
366
- if sample_roi == "Uploaded GeoJSON":
367
- if data is None:
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)
378
- else:
379
- if collection in [
380
- "Landsat TM-ETM-OLI Surface Reflectance",
381
- "Sentinel-2 MSI Surface Reflectance",
382
- ]:
383
- gdf = gpd.GeoDataFrame(
384
- index=[0], crs=crs, geometry=[landsat_rois[sample_roi]]
385
- )
386
- elif (
387
- collection
388
- == "Geostationary Operational Environmental Satellites (GOES)"
389
- ):
390
- gdf = gpd.GeoDataFrame(
391
- index=[0], crs=crs, geometry=[goes_rois[sample_roi]["region"]]
392
- )
393
- elif collection == "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km":
394
- gdf = gpd.GeoDataFrame(
395
- index=[0], crs=crs, geometry=[modis_rois[sample_roi]]
396
- )
397
-
398
- if sample_roi != "Uploaded GeoJSON":
399
-
400
- if collection in [
401
- "Landsat TM-ETM-OLI Surface Reflectance",
402
- "Sentinel-2 MSI Surface Reflectance",
403
- ]:
404
- gdf = gpd.GeoDataFrame(
405
- index=[0], crs=crs, geometry=[landsat_rois[sample_roi]]
406
- )
407
- elif (
408
- collection
409
- == "Geostationary Operational Environmental Satellites (GOES)"
410
- ):
411
- gdf = gpd.GeoDataFrame(
412
- index=[0], crs=crs, geometry=[goes_rois[sample_roi]["region"]]
413
- )
414
- elif collection in [
415
- "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km",
416
- "MODIS Gap filled Land Surface Temperature Daily",
417
- ]:
418
- gdf = gpd.GeoDataFrame(
419
- index=[0], crs=crs, geometry=[modis_rois[sample_roi]]
420
- )
421
- elif collection == "MODIS Ocean Color SMI":
422
- gdf = gpd.GeoDataFrame(
423
- index=[0], crs=crs, geometry=[ocean_rois[sample_roi]]
424
- )
425
- st.session_state["roi"] = geemap.gdf_to_ee(gdf, geodesic=False)
426
- m.add_gdf(gdf, "ROI")
427
-
428
- elif data:
429
- gdf = uploaded_file_to_gdf(data)
430
- st.session_state["roi"] = geemap.gdf_to_ee(gdf, geodesic=False)
431
- m.add_gdf(gdf, "ROI")
432
-
433
- m.to_streamlit(height=600)
434
-
435
- with row1_col2:
436
-
437
- if collection in [
438
- "Landsat TM-ETM-OLI Surface Reflectance",
439
- "Sentinel-2 MSI Surface Reflectance",
440
- ]:
441
-
442
- if collection == "Landsat TM-ETM-OLI Surface Reflectance":
443
- sensor_start_year = 1984
444
- timelapse_title = "Landsat Timelapse"
445
- timelapse_speed = 5
446
- elif collection == "Sentinel-2 MSI Surface Reflectance":
447
- sensor_start_year = 2015
448
- timelapse_title = "Sentinel-2 Timelapse"
449
- timelapse_speed = 5
450
- video_empty.video("https://youtu.be/VVRK_-dEjR4")
451
-
452
- with st.form("submit_landsat_form"):
453
-
454
- roi = None
455
- if st.session_state.get("roi") is not None:
456
- roi = st.session_state.get("roi")
457
- out_gif = geemap.temp_file_path(".gif")
458
-
459
- title = st.text_input(
460
- "Enter a title to show on the timelapse: ", timelapse_title
461
- )
462
- RGB = st.selectbox(
463
- "Select an RGB band combination:",
464
- [
465
- "Red/Green/Blue",
466
- "NIR/Red/Green",
467
- "SWIR2/SWIR1/NIR",
468
- "NIR/SWIR1/Red",
469
- "SWIR2/NIR/Red",
470
- "SWIR2/SWIR1/Red",
471
- "SWIR1/NIR/Blue",
472
- "NIR/SWIR1/Blue",
473
- "SWIR2/NIR/Green",
474
- "SWIR1/NIR/Red",
475
- "SWIR2/NIR/SWIR1",
476
- "SWIR1/NIR/SWIR2",
477
- ],
478
- index=9,
479
- )
480
-
481
- frequency = st.selectbox(
482
- "Select a temporal frequency:",
483
- ["year", "quarter", "month"],
484
- index=0,
485
- )
486
-
487
- with st.expander("Customize timelapse"):
488
-
489
- speed = st.slider("Frames per second:", 1, 30, timelapse_speed)
490
- dimensions = st.slider(
491
- "Maximum dimensions (Width*Height) in pixels", 768, 2000, 768
492
- )
493
- progress_bar_color = st.color_picker(
494
- "Progress bar color:", "#0000ff"
495
- )
496
- years = st.slider(
497
- "Start and end year:",
498
- sensor_start_year,
499
- today.year,
500
- (sensor_start_year, today.year),
501
- )
502
- months = st.slider("Start and end month:", 1, 12, (1, 12))
503
- font_size = st.slider("Font size:", 10, 50, 30)
504
- font_color = st.color_picker("Font color:", "#ffffff")
505
- apply_fmask = st.checkbox(
506
- "Apply fmask (remove clouds, shadows, snow)", True
507
- )
508
- font_type = st.selectbox(
509
- "Select the font type for the title:",
510
- ["arial.ttf", "alibaba.otf"],
511
- index=0,
512
- )
513
- fading = st.slider(
514
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
515
- )
516
- mp4 = st.checkbox("Save timelapse as MP4", True)
517
-
518
- empty_text = st.empty()
519
- empty_image = st.empty()
520
- empty_fire_image = st.empty()
521
- empty_video = st.container()
522
- submitted = st.form_submit_button("Submit")
523
- if submitted:
524
-
525
- if sample_roi == "Uploaded GeoJSON" and data is None:
526
- empty_text.warning(
527
- "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."
528
- )
529
- else:
530
-
531
- empty_text.text("Computing... Please wait...")
532
-
533
- start_year = years[0]
534
- end_year = years[1]
535
- start_date = str(months[0]).zfill(2) + "-01"
536
- end_date = str(months[1]).zfill(2) + "-30"
537
- bands = RGB.split("/")
538
-
539
- try:
540
- if collection == "Landsat TM-ETM-OLI Surface Reflectance":
541
- out_gif = geemap.landsat_timelapse(
542
- roi=roi,
543
- out_gif=out_gif,
544
- start_year=start_year,
545
- end_year=end_year,
546
- start_date=start_date,
547
- end_date=end_date,
548
- bands=bands,
549
- apply_fmask=apply_fmask,
550
- frames_per_second=speed,
551
- dimensions=dimensions,
552
- overlay_data=overlay_data,
553
- overlay_color=overlay_color,
554
- overlay_width=overlay_width,
555
- overlay_opacity=overlay_opacity,
556
- frequency=frequency,
557
- date_format=None,
558
- title=title,
559
- title_xy=("2%", "90%"),
560
- add_text=True,
561
- text_xy=("2%", "2%"),
562
- text_sequence=None,
563
- font_type=font_type,
564
- font_size=font_size,
565
- font_color=font_color,
566
- add_progress_bar=True,
567
- progress_bar_color=progress_bar_color,
568
- progress_bar_height=5,
569
- loop=0,
570
- mp4=mp4,
571
- fading=fading,
572
- )
573
- elif collection == "Sentinel-2 MSI Surface Reflectance":
574
- out_gif = geemap.sentinel2_timelapse(
575
- roi=roi,
576
- out_gif=out_gif,
577
- start_year=start_year,
578
- end_year=end_year,
579
- start_date=start_date,
580
- end_date=end_date,
581
- bands=bands,
582
- apply_fmask=apply_fmask,
583
- frames_per_second=speed,
584
- dimensions=dimensions,
585
- overlay_data=overlay_data,
586
- overlay_color=overlay_color,
587
- overlay_width=overlay_width,
588
- overlay_opacity=overlay_opacity,
589
- frequency=frequency,
590
- date_format=None,
591
- title=title,
592
- title_xy=("2%", "90%"),
593
- add_text=True,
594
- text_xy=("2%", "2%"),
595
- text_sequence=None,
596
- font_type=font_type,
597
- font_size=font_size,
598
- font_color=font_color,
599
- add_progress_bar=True,
600
- progress_bar_color=progress_bar_color,
601
- progress_bar_height=5,
602
- loop=0,
603
- mp4=mp4,
604
- fading=fading,
605
- )
606
- except:
607
- empty_text.error(
608
- "An error occurred while computing the timelapse. Your probably requested too much data. Try reducing the ROI or timespan."
609
- )
610
- st.stop()
611
-
612
- if out_gif is not None and os.path.exists(out_gif):
613
-
614
- empty_text.text(
615
- "Right click the GIF to save it to your computerπŸ‘‡"
616
- )
617
- empty_image.image(out_gif)
618
-
619
- out_mp4 = out_gif.replace(".gif", ".mp4")
620
- if mp4 and os.path.exists(out_mp4):
621
- with empty_video:
622
- st.text(
623
- "Right click the MP4 to save it to your computerπŸ‘‡"
624
- )
625
- st.video(out_gif.replace(".gif", ".mp4"))
626
-
627
- else:
628
- empty_text.error(
629
- "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
630
- )
631
-
632
- elif collection == "Geostationary Operational Environmental Satellites (GOES)":
633
-
634
- video_empty.video("https://youtu.be/16fA2QORG4A")
635
-
636
- with st.form("submit_goes_form"):
637
-
638
- roi = None
639
- if st.session_state.get("roi") is not None:
640
- roi = st.session_state.get("roi")
641
- out_gif = geemap.temp_file_path(".gif")
642
-
643
- satellite = st.selectbox("Select a satellite:", ["GOES-17", "GOES-16"])
644
- earliest_date = datetime.date(2017, 7, 10)
645
- latest_date = datetime.date.today()
646
-
647
- if sample_roi == "Uploaded GeoJSON":
648
- roi_start_date = today - datetime.timedelta(days=2)
649
- roi_end_date = today - datetime.timedelta(days=1)
650
- roi_start_time = datetime.time(14, 00)
651
- roi_end_time = datetime.time(1, 00)
652
- else:
653
- roi_start = goes_rois[sample_roi]["start_time"]
654
- roi_end = goes_rois[sample_roi]["end_time"]
655
- roi_start_date = datetime.datetime.strptime(
656
- roi_start[:10], "%Y-%m-%d"
657
- )
658
- roi_end_date = datetime.datetime.strptime(roi_end[:10], "%Y-%m-%d")
659
- roi_start_time = datetime.time(
660
- int(roi_start[11:13]), int(roi_start[14:16])
661
- )
662
- roi_end_time = datetime.time(
663
- int(roi_end[11:13]), int(roi_end[14:16])
664
- )
665
-
666
- start_date = st.date_input("Select the start date:", roi_start_date)
667
- end_date = st.date_input("Select the end date:", roi_end_date)
668
-
669
- with st.expander("Customize timelapse"):
670
-
671
- add_fire = st.checkbox("Add Fire/Hotspot Characterization", False)
672
-
673
- scan_type = st.selectbox(
674
- "Select a scan type:", ["Full Disk", "CONUS", "Mesoscale"]
675
- )
676
-
677
- start_time = st.time_input(
678
- "Select the start time of the start date:", roi_start_time
679
- )
680
-
681
- end_time = st.time_input(
682
- "Select the end time of the end date:", roi_end_time
683
- )
684
-
685
- start = (
686
- start_date.strftime("%Y-%m-%d")
687
- + "T"
688
- + start_time.strftime("%H:%M:%S")
689
- )
690
- end = (
691
- end_date.strftime("%Y-%m-%d")
692
- + "T"
693
- + end_time.strftime("%H:%M:%S")
694
- )
695
-
696
- speed = st.slider("Frames per second:", 1, 30, 5)
697
- add_progress_bar = st.checkbox("Add a progress bar", True)
698
- progress_bar_color = st.color_picker(
699
- "Progress bar color:", "#0000ff"
700
- )
701
- font_size = st.slider("Font size:", 10, 50, 20)
702
- font_color = st.color_picker("Font color:", "#ffffff")
703
- fading = st.slider(
704
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
705
- )
706
- mp4 = st.checkbox("Save timelapse as MP4", True)
707
-
708
- empty_text = st.empty()
709
- empty_image = st.empty()
710
- empty_video = st.container()
711
- empty_fire_text = st.empty()
712
- empty_fire_image = st.empty()
713
-
714
- submitted = st.form_submit_button("Submit")
715
- if submitted:
716
- if sample_roi == "Uploaded GeoJSON" and data is None:
717
- empty_text.warning(
718
- "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."
719
- )
720
- else:
721
- empty_text.text("Computing... Please wait...")
722
-
723
- geemap.goes_timelapse(
724
- out_gif,
725
- start_date=start,
726
- end_date=end,
727
- data=satellite,
728
- scan=scan_type.replace(" ", "_").lower(),
729
- region=roi,
730
- dimensions=768,
731
- framesPerSecond=speed,
732
- date_format="YYYY-MM-dd HH:mm",
733
- xy=("3%", "3%"),
734
- text_sequence=None,
735
- font_type="arial.ttf",
736
- font_size=font_size,
737
- font_color=font_color,
738
- add_progress_bar=add_progress_bar,
739
- progress_bar_color=progress_bar_color,
740
- progress_bar_height=5,
741
- loop=0,
742
- overlay_data=overlay_data,
743
- overlay_color=overlay_color,
744
- overlay_width=overlay_width,
745
- overlay_opacity=overlay_opacity,
746
- mp4=mp4,
747
- fading=fading,
748
- )
749
-
750
- if out_gif is not None and os.path.exists(out_gif):
751
- empty_text.text(
752
- "Right click the GIF to save it to your computerπŸ‘‡"
753
- )
754
- empty_image.image(out_gif)
755
-
756
- out_mp4 = out_gif.replace(".gif", ".mp4")
757
- if mp4 and os.path.exists(out_mp4):
758
- with empty_video:
759
- st.text(
760
- "Right click the MP4 to save it to your computerπŸ‘‡"
761
- )
762
- st.video(out_gif.replace(".gif", ".mp4"))
763
-
764
- if add_fire:
765
- out_fire_gif = geemap.temp_file_path(".gif")
766
- empty_fire_text.text(
767
- "Delineating Fire Hotspot... Please wait..."
768
- )
769
- geemap.goes_fire_timelapse(
770
- out_fire_gif,
771
- start_date=start,
772
- end_date=end,
773
- data=satellite,
774
- scan=scan_type.replace(" ", "_").lower(),
775
- region=roi,
776
- dimensions=768,
777
- framesPerSecond=speed,
778
- date_format="YYYY-MM-dd HH:mm",
779
- xy=("3%", "3%"),
780
- text_sequence=None,
781
- font_type="arial.ttf",
782
- font_size=font_size,
783
- font_color=font_color,
784
- add_progress_bar=add_progress_bar,
785
- progress_bar_color=progress_bar_color,
786
- progress_bar_height=5,
787
- loop=0,
788
- )
789
- if os.path.exists(out_fire_gif):
790
- empty_fire_image.image(out_fire_gif)
791
- else:
792
- empty_text.text(
793
- "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."
794
- )
795
-
796
- elif collection == "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km":
797
-
798
- video_empty.video("https://youtu.be/16fA2QORG4A")
799
-
800
- satellite = st.selectbox("Select a satellite:", ["Terra", "Aqua"])
801
- band = st.selectbox("Select a band:", ["NDVI", "EVI"])
802
-
803
- with st.form("submit_modis_form"):
804
-
805
- roi = None
806
- if st.session_state.get("roi") is not None:
807
- roi = st.session_state.get("roi")
808
- out_gif = geemap.temp_file_path(".gif")
809
-
810
- with st.expander("Customize timelapse"):
811
-
812
- start = st.date_input(
813
- "Select a start date:", datetime.date(2000, 2, 8)
814
- )
815
- end = st.date_input("Select an end date:", datetime.date.today())
816
-
817
- start_date = start.strftime("%Y-%m-%d")
818
- end_date = end.strftime("%Y-%m-%d")
819
-
820
- speed = st.slider("Frames per second:", 1, 30, 5)
821
- add_progress_bar = st.checkbox("Add a progress bar", True)
822
- progress_bar_color = st.color_picker(
823
- "Progress bar color:", "#0000ff"
824
- )
825
- font_size = st.slider("Font size:", 10, 50, 20)
826
- font_color = st.color_picker("Font color:", "#ffffff")
827
-
828
- font_type = st.selectbox(
829
- "Select the font type for the title:",
830
- ["arial.ttf", "alibaba.otf"],
831
- index=0,
832
- )
833
- fading = st.slider(
834
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
835
- )
836
- mp4 = st.checkbox("Save timelapse as MP4", True)
837
-
838
- empty_text = st.empty()
839
- empty_image = st.empty()
840
- empty_video = st.container()
841
-
842
- submitted = st.form_submit_button("Submit")
843
- if submitted:
844
- if sample_roi == "Uploaded GeoJSON" and data is None:
845
- empty_text.warning(
846
- "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."
847
- )
848
- else:
849
-
850
- empty_text.text("Computing... Please wait...")
851
-
852
- geemap.modis_ndvi_timelapse(
853
- out_gif,
854
- satellite,
855
- band,
856
- start_date,
857
- end_date,
858
- roi,
859
- 768,
860
- speed,
861
- overlay_data=overlay_data,
862
- overlay_color=overlay_color,
863
- overlay_width=overlay_width,
864
- overlay_opacity=overlay_opacity,
865
- mp4=mp4,
866
- fading=fading,
867
- )
868
-
869
- geemap.reduce_gif_size(out_gif)
870
-
871
- empty_text.text(
872
- "Right click the GIF to save it to your computerπŸ‘‡"
873
- )
874
- empty_image.image(out_gif)
875
-
876
- out_mp4 = out_gif.replace(".gif", ".mp4")
877
- if mp4 and os.path.exists(out_mp4):
878
- with empty_video:
879
- st.text(
880
- "Right click the MP4 to save it to your computerπŸ‘‡"
881
- )
882
- st.video(out_gif.replace(".gif", ".mp4"))
883
-
884
- elif collection == "Any Earth Engine ImageCollection":
885
-
886
- with st.form("submit_ts_form"):
887
- with st.expander("Customize timelapse"):
888
-
889
- title = st.text_input(
890
- "Enter a title to show on the timelapse: ", "Timelapse"
891
- )
892
- start_date = st.date_input(
893
- "Select the start date:", datetime.date(2020, 1, 1)
894
- )
895
- end_date = st.date_input(
896
- "Select the end date:", datetime.date.today()
897
- )
898
- frequency = st.selectbox(
899
- "Select a temporal frequency:",
900
- ["year", "quarter", "month", "day", "hour", "minute", "second"],
901
- index=0,
902
- )
903
- reducer = st.selectbox(
904
- "Select a reducer for aggregating data:",
905
- ["median", "mean", "min", "max", "sum", "variance", "stdDev"],
906
- index=0,
907
- )
908
- data_format = st.selectbox(
909
- "Select a date format to show on the timelapse:",
910
- [
911
- "YYYY-MM-dd",
912
- "YYYY",
913
- "YYMM-MM",
914
- "YYYY-MM-dd HH:mm",
915
- "YYYY-MM-dd HH:mm:ss",
916
- "HH:mm",
917
- "HH:mm:ss",
918
- "w",
919
- "M",
920
- "d",
921
- "D",
922
- ],
923
- index=0,
924
- )
925
-
926
- speed = st.slider("Frames per second:", 1, 30, 5)
927
- add_progress_bar = st.checkbox("Add a progress bar", True)
928
- progress_bar_color = st.color_picker(
929
- "Progress bar color:", "#0000ff"
930
- )
931
- font_size = st.slider("Font size:", 10, 50, 30)
932
- font_color = st.color_picker("Font color:", "#ffffff")
933
- font_type = st.selectbox(
934
- "Select the font type for the title:",
935
- ["arial.ttf", "alibaba.otf"],
936
- index=0,
937
- )
938
- fading = st.slider(
939
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
940
- )
941
- mp4 = st.checkbox("Save timelapse as MP4", True)
942
-
943
- empty_text = st.empty()
944
- empty_image = st.empty()
945
- empty_video = st.container()
946
- empty_fire_image = st.empty()
947
-
948
- roi = None
949
- if st.session_state.get("roi") is not None:
950
- roi = st.session_state.get("roi")
951
- out_gif = geemap.temp_file_path(".gif")
952
-
953
- submitted = st.form_submit_button("Submit")
954
- if submitted:
955
-
956
- if sample_roi == "Uploaded GeoJSON" and data is None:
957
- empty_text.warning(
958
- "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."
959
- )
960
- else:
961
-
962
- empty_text.text("Computing... Please wait...")
963
- try:
964
- geemap.create_timelapse(
965
- st.session_state.get("ee_asset_id"),
966
- start_date=start_date.strftime("%Y-%m-%d"),
967
- end_date=end_date.strftime("%Y-%m-%d"),
968
- region=roi,
969
- frequency=frequency,
970
- reducer=reducer,
971
- date_format=data_format,
972
- out_gif=out_gif,
973
- bands=st.session_state.get("bands"),
974
- palette=st.session_state.get("palette"),
975
- vis_params=st.session_state.get("vis_params"),
976
- dimensions=768,
977
- frames_per_second=speed,
978
- crs="EPSG:3857",
979
- overlay_data=overlay_data,
980
- overlay_color=overlay_color,
981
- overlay_width=overlay_width,
982
- overlay_opacity=overlay_opacity,
983
- title=title,
984
- title_xy=("2%", "90%"),
985
- add_text=True,
986
- text_xy=("2%", "2%"),
987
- text_sequence=None,
988
- font_type=font_type,
989
- font_size=font_size,
990
- font_color=font_color,
991
- add_progress_bar=add_progress_bar,
992
- progress_bar_color=progress_bar_color,
993
- progress_bar_height=5,
994
- loop=0,
995
- mp4=mp4,
996
- fading=fading,
997
- )
998
- except:
999
- empty_text.error(
1000
- "An error occurred while computing the timelapse. You probably requested too much data. Try reducing the ROI or timespan."
1001
- )
1002
-
1003
- empty_text.text(
1004
- "Right click the GIF to save it to your computerπŸ‘‡"
1005
- )
1006
- empty_image.image(out_gif)
1007
-
1008
- out_mp4 = out_gif.replace(".gif", ".mp4")
1009
- if mp4 and os.path.exists(out_mp4):
1010
- with empty_video:
1011
- st.text(
1012
- "Right click the MP4 to save it to your computerπŸ‘‡"
1013
- )
1014
- st.video(out_gif.replace(".gif", ".mp4"))
1015
-
1016
- elif collection in [
1017
- "MODIS Gap filled Land Surface Temperature Daily",
1018
- "MODIS Ocean Color SMI",
1019
- ]:
1020
-
1021
- with st.form("submit_ts_form"):
1022
- with st.expander("Customize timelapse"):
1023
-
1024
- title = st.text_input(
1025
- "Enter a title to show on the timelapse: ",
1026
- "Surface Temperature",
1027
- )
1028
- start_date = st.date_input(
1029
- "Select the start date:", datetime.date(2018, 1, 1)
1030
- )
1031
- end_date = st.date_input(
1032
- "Select the end date:", datetime.date(2020, 12, 31)
1033
- )
1034
- frequency = st.selectbox(
1035
- "Select a temporal frequency:",
1036
- ["year", "quarter", "month", "week", "day"],
1037
- index=2,
1038
- )
1039
- reducer = st.selectbox(
1040
- "Select a reducer for aggregating data:",
1041
- ["median", "mean", "min", "max", "sum", "variance", "stdDev"],
1042
- index=0,
1043
- )
1044
-
1045
- vis_params = st.text_area(
1046
- "Enter visualization parameters",
1047
- "",
1048
- help="Enter a string in the format of a dictionary, such as '{'min': 23, 'max': 32}'",
1049
- )
1050
-
1051
- speed = st.slider("Frames per second:", 1, 30, 5)
1052
- add_progress_bar = st.checkbox("Add a progress bar", True)
1053
- progress_bar_color = st.color_picker(
1054
- "Progress bar color:", "#0000ff"
1055
- )
1056
- font_size = st.slider("Font size:", 10, 50, 30)
1057
- font_color = st.color_picker("Font color:", "#ffffff")
1058
- font_type = st.selectbox(
1059
- "Select the font type for the title:",
1060
- ["arial.ttf", "alibaba.otf"],
1061
- index=0,
1062
- )
1063
- add_colorbar = st.checkbox("Add a colorbar", True)
1064
- colorbar_label = st.text_input(
1065
- "Enter the colorbar label:", "Surface Temperature (Β°C)"
1066
- )
1067
- fading = st.slider(
1068
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1069
- )
1070
- mp4 = st.checkbox("Save timelapse as MP4", True)
1071
-
1072
- empty_text = st.empty()
1073
- empty_image = st.empty()
1074
- empty_video = st.container()
1075
-
1076
- roi = None
1077
- if st.session_state.get("roi") is not None:
1078
- roi = st.session_state.get("roi")
1079
- out_gif = geemap.temp_file_path(".gif")
1080
-
1081
- submitted = st.form_submit_button("Submit")
1082
- if submitted:
1083
-
1084
- if sample_roi == "Uploaded GeoJSON" and data is None:
1085
- empty_text.warning(
1086
- "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."
1087
- )
1088
- else:
1089
-
1090
- empty_text.text("Computing... Please wait...")
1091
- try:
1092
- if (
1093
- collection
1094
- == "MODIS Gap filled Land Surface Temperature Daily"
1095
- ):
1096
- out_gif = geemap.create_timelapse(
1097
- st.session_state.get("ee_asset_id"),
1098
- start_date=start_date.strftime("%Y-%m-%d"),
1099
- end_date=end_date.strftime("%Y-%m-%d"),
1100
- region=roi,
1101
- bands=None,
1102
- frequency=frequency,
1103
- reducer=reducer,
1104
- date_format=None,
1105
- out_gif=out_gif,
1106
- palette=st.session_state.get("palette"),
1107
- vis_params=None,
1108
- dimensions=768,
1109
- frames_per_second=speed,
1110
- crs="EPSG:3857",
1111
- overlay_data=overlay_data,
1112
- overlay_color=overlay_color,
1113
- overlay_width=overlay_width,
1114
- overlay_opacity=overlay_opacity,
1115
- title=title,
1116
- title_xy=("2%", "90%"),
1117
- add_text=True,
1118
- text_xy=("2%", "2%"),
1119
- text_sequence=None,
1120
- font_type=font_type,
1121
- font_size=font_size,
1122
- font_color=font_color,
1123
- add_progress_bar=add_progress_bar,
1124
- progress_bar_color=progress_bar_color,
1125
- progress_bar_height=5,
1126
- add_colorbar=add_colorbar,
1127
- colorbar_label=colorbar_label,
1128
- loop=0,
1129
- mp4=mp4,
1130
- fading=fading,
1131
- )
1132
- elif collection == "MODIS Ocean Color SMI":
1133
- if vis_params.startswith("{") and vis_params.endswith(
1134
- "}"
1135
- ):
1136
- vis_params = eval(vis_params)
1137
- else:
1138
- vis_params = None
1139
- out_gif = geemap.modis_ocean_color_timelapse(
1140
- st.session_state.get("ee_asset_id"),
1141
- start_date=start_date.strftime("%Y-%m-%d"),
1142
- end_date=end_date.strftime("%Y-%m-%d"),
1143
- region=roi,
1144
- bands=st.session_state["band"],
1145
- frequency=frequency,
1146
- reducer=reducer,
1147
- date_format=None,
1148
- out_gif=out_gif,
1149
- palette=st.session_state.get("palette"),
1150
- vis_params=vis_params,
1151
- dimensions=768,
1152
- frames_per_second=speed,
1153
- crs="EPSG:3857",
1154
- overlay_data=overlay_data,
1155
- overlay_color=overlay_color,
1156
- overlay_width=overlay_width,
1157
- overlay_opacity=overlay_opacity,
1158
- title=title,
1159
- title_xy=("2%", "90%"),
1160
- add_text=True,
1161
- text_xy=("2%", "2%"),
1162
- text_sequence=None,
1163
- font_type=font_type,
1164
- font_size=font_size,
1165
- font_color=font_color,
1166
- add_progress_bar=add_progress_bar,
1167
- progress_bar_color=progress_bar_color,
1168
- progress_bar_height=5,
1169
- add_colorbar=add_colorbar,
1170
- colorbar_label=colorbar_label,
1171
- loop=0,
1172
- mp4=mp4,
1173
- fading=fading,
1174
- )
1175
- except:
1176
- empty_text.error(
1177
- "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
1178
- )
1179
-
1180
- if out_gif is not None and os.path.exists(out_gif):
1181
-
1182
- geemap.reduce_gif_size(out_gif)
1183
-
1184
- empty_text.text(
1185
- "Right click the GIF to save it to your computerπŸ‘‡"
1186
- )
1187
- empty_image.image(out_gif)
1188
-
1189
- out_mp4 = out_gif.replace(".gif", ".mp4")
1190
- if mp4 and os.path.exists(out_mp4):
1191
- with empty_video:
1192
- st.text(
1193
- "Right click the MP4 to save it to your computerπŸ‘‡"
1194
- )
1195
- st.video(out_gif.replace(".gif", ".mp4"))
1196
-
1197
- else:
1198
- st.error(
1199
- "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
1200
- )
1201
-
1202
- elif collection == "USDA National Agriculture Imagery Program (NAIP)":
1203
-
1204
- with st.form("submit_naip_form"):
1205
- with st.expander("Customize timelapse"):
1206
-
1207
- title = st.text_input(
1208
- "Enter a title to show on the timelapse: ", "NAIP Timelapse"
1209
- )
1210
-
1211
- years = st.slider(
1212
- "Start and end year:",
1213
- 2003,
1214
- today.year,
1215
- (2003, today.year),
1216
- )
1217
-
1218
- bands = st.selectbox(
1219
- "Select a band combination:", ["N/R/G", "R/G/B"], index=0
1220
- )
1221
-
1222
- speed = st.slider("Frames per second:", 1, 30, 3)
1223
- add_progress_bar = st.checkbox("Add a progress bar", True)
1224
- progress_bar_color = st.color_picker(
1225
- "Progress bar color:", "#0000ff"
1226
- )
1227
- font_size = st.slider("Font size:", 10, 50, 30)
1228
- font_color = st.color_picker("Font color:", "#ffffff")
1229
- font_type = st.selectbox(
1230
- "Select the font type for the title:",
1231
- ["arial.ttf", "alibaba.otf"],
1232
- index=0,
1233
- )
1234
- fading = st.slider(
1235
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1236
- )
1237
- mp4 = st.checkbox("Save timelapse as MP4", True)
1238
-
1239
- empty_text = st.empty()
1240
- empty_image = st.empty()
1241
- empty_video = st.container()
1242
- empty_fire_image = st.empty()
1243
-
1244
- roi = None
1245
- if st.session_state.get("roi") is not None:
1246
- roi = st.session_state.get("roi")
1247
- out_gif = geemap.temp_file_path(".gif")
1248
-
1249
- submitted = st.form_submit_button("Submit")
1250
- if submitted:
1251
-
1252
- if sample_roi == "Uploaded GeoJSON" and data is None:
1253
- empty_text.warning(
1254
- "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."
1255
- )
1256
- else:
1257
-
1258
- empty_text.text("Computing... Please wait...")
1259
- try:
1260
- geemap.naip_timelapse(
1261
- roi,
1262
- years[0],
1263
- years[1],
1264
- out_gif,
1265
- bands=bands.split("/"),
1266
- palette=st.session_state.get("palette"),
1267
- vis_params=None,
1268
- dimensions=768,
1269
- frames_per_second=speed,
1270
- crs="EPSG:3857",
1271
- overlay_data=overlay_data,
1272
- overlay_color=overlay_color,
1273
- overlay_width=overlay_width,
1274
- overlay_opacity=overlay_opacity,
1275
- title=title,
1276
- title_xy=("2%", "90%"),
1277
- add_text=True,
1278
- text_xy=("2%", "2%"),
1279
- text_sequence=None,
1280
- font_type=font_type,
1281
- font_size=font_size,
1282
- font_color=font_color,
1283
- add_progress_bar=add_progress_bar,
1284
- progress_bar_color=progress_bar_color,
1285
- progress_bar_height=5,
1286
- loop=0,
1287
- mp4=mp4,
1288
- fading=fading,
1289
- )
1290
- except:
1291
- empty_text.error(
1292
- "Something went wrong. You either requested too much data or the ROI is outside the U.S."
1293
- )
1294
-
1295
- if out_gif is not None and os.path.exists(out_gif):
1296
-
1297
- empty_text.text(
1298
- "Right click the GIF to save it to your computerπŸ‘‡"
1299
- )
1300
- empty_image.image(out_gif)
1301
-
1302
- out_mp4 = out_gif.replace(".gif", ".mp4")
1303
- if mp4 and os.path.exists(out_mp4):
1304
- with empty_video:
1305
- st.text(
1306
- "Right click the MP4 to save it to your computerπŸ‘‡"
1307
- )
1308
- st.video(out_gif.replace(".gif", ".mp4"))
1309
-
1310
- else:
1311
- st.error(
1312
- "Something went wrong. You either requested too much data or the ROI is outside the U.S."
1313
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/cog_files.txt DELETED
@@ -1,77 +0,0 @@
1
- https://www.maxar.com/open-data/california-colorado-fires
2
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-02-16/pine-gulch-fire20/1030010076004E00.tif
3
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-08-18/pine-gulch-fire20/1040010041D3B300.tif
4
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-11-13/grizzly-creek-fire20/1040010045785200.tif
5
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2018-11-13/grizzly-creek-fire20/10400100443AEC00.tif
6
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-02-06/czu-lightning-complex-fire/104001004941E100.tif
7
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-02-18/cameron-peak-fire20/103001008DA5B500.tif
8
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-02-22/czu-lightning-complex-fire/103001008DB2E200.tif
9
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-04-01/grizzly-creek-fire20/104001004881EF00.tif
10
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-04-17/czu-lightning-complex-fire/103001008F905300.tif
11
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-04-17/czu-lightning-complex-fire/1030010092B22200.tif
12
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-06-27/czu-lightning-complex-fire/1030010094A52300.tif
13
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-09-08/czu-lightning-complex-fire/103001009C9FBB00.tif
14
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-09-24/lnu-lightning-complex-fire/103001009A079B00.tif
15
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-10-05/czu-lightning-complex-fire/103001009C10F800.tif
16
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-10-05/czu-lightning-complex-fire/103001009A266800.tif
17
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-04/czu-lightning-complex-fire/1050010019917900.tif
18
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-04/czu-lightning-complex-fire/1050010019917800.tif
19
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-18/czu-lightning-complex-fire/1050010019C2F600.tif
20
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-11-28/cameron-peak-fire20/103001009D72E000.tif
21
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-12-10/czu-lightning-complex-fire/105001001A3A8700.tif
22
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-12-28/lnu-lightning-complex-fire/10300100A1972700.tif
23
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2019-12-28/lnu-lightning-complex-fire/103001009F5D6B00.tif
24
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-01-15/cameron-peak-fire20/1040010057992100.tif
25
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-04-15/lnu-lightning-complex-fire/10300100A4B23600.tif
26
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-04-23/czu-lightning-complex-fire/10300100A589D100.tif
27
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-09/lnu-lightning-complex-fire/10300100A332EE00.tif
28
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-23/river-carmel-fires/10300100A77E9400.tif
29
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-23/river-carmel-fires/10300100A500A500.tif
30
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-05-24/river-carmel-fires/105001001D64E200.tif
31
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-27/lnu-lightning-complex-fire/10300100A8663800.tif
32
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-30/river-carmel-fires/10300100A9D60C00.tif
33
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-30/czu-lightning-complex-fire/10300100A8C66400.tif
34
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-06-30/czu-lightning-complex-fire/10300100A8892900.tif
35
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-11/czu-lightning-complex-fire/10300100AB381200.tif
36
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-11/czu-lightning-complex-fire/10300100AA180600.tif
37
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-13/pine-gulch-fire20/10300100AA57D700.tif
38
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-20/lnu-lightning-complex-fire/104001005C529000.tif
39
- https://opendata.digitalglobe.com/events/california-fire-2020/pre-event/2020-07-28/pine-gulch-fire20/104001005DB06E00.tif
40
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-14/pine-gulch-fire20/10300100AAC8DD00.tif
41
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-16/pine-gulch-fire20/104001005D4A6100.tif
42
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-17/grizzly-creek-fire20/10300100ACCA3700.tif
43
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-17/cameron-peak-fire20/10300100AB4ED400.tif
44
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/swir-cog/104A0100606FFE00.tif
45
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/pine-gulch-fire20/10300100ACD06200.tif
46
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/pine-gulch-fire20/10300100AAD4A000.tif
47
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/pine-gulch-fire20/10300100AA293800.tif
48
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-20/lnu-lightning-complex-fire/10400100606FFE00.tif
49
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/10300100ACBA2B00.tif
50
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/10300100AA49F600.tif
51
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/lnu-lightning-complex-fire/104001005C1AC900.tif
52
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/104001005F9F5300.tif
53
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/104001005F453300.tif
54
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/river-carmel-fires/10300100ADC14400.tif
55
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-21/czu-lightning-complex-fire/104001005F43D400.tif
56
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-23/grizzly-creek-fire20/104001005FA09C00.tif
57
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-23/grizzly-creek-fire20/104001005DC71000.tif
58
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-26/river-carmel-fires/105001001F58F000.tif
59
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-26/lnu-lightning-complex-fire/10300100AC163A00.tif
60
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-29/river-carmel-fires/10300100AAD27500.tif
61
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-08-29/river-carmel-fires/10300100A9C75A00.tif
62
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-03/cameron-peak-fire20/1040010060188800.tif
63
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-03/cameron-peak-fire20/104001005F7E6500.tif
64
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-03/cameron-peak-fire20/10300100AE685A00.tif
65
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-09-04/cameron-peak-fire20/1040010060761C00.tif
66
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-05/cameron-peak-fire20/104001006113B700.tif
67
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-05/cameron-peak-fire20/10400100610CD400.tif
68
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/1040010062B14C00.tif
69
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/10400100626BFA00.tif
70
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/10400100622A6600.tif
71
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/10400100606B6300.tif
72
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-12/cameron-peak-fire20/104001005F908800.tif
73
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-15/cameron-peak-fire20/10500100205EDA00.tif
74
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-15/cameron-peak-fire20/10500100205ED900.tif
75
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-22/east-troublesome-fire20/10300100B0004A00.tif
76
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-22/east-troublesome-fire20/10300100AD0D1200.tif
77
- https://opendata.digitalglobe.com/events/california-fire-2020/post-event/2020-10-22/east-troublesome-fire20/10300100AD0CA600.tif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/html/sfo_buildings.html DELETED
@@ -1,34 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <!-- Include the CesiumJS JavaScript and CSS files -->
6
- <script src="https://cesium.com/downloads/cesiumjs/releases/1.88/Build/Cesium/Cesium.js"></script>
7
- <link href="https://cesium.com/downloads/cesiumjs/releases/1.88/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
8
- </head>
9
- <body>
10
- <div id="cesiumContainer"></div>
11
- <script>
12
- // Your access token can be found at: https://cesium.com/ion/tokens.
13
- // Replace `your_access_token` with your Cesium ion access token.
14
-
15
- Cesium.Ion.defaultAccessToken = 'your_access_token';
16
-
17
- // Initialize the Cesium Viewer in the HTML element with the `cesiumContainer` ID.
18
- const viewer = new Cesium.Viewer('cesiumContainer', {
19
- terrainProvider: Cesium.createWorldTerrain()
20
- });
21
- // Add Cesium OSM Buildings, a global 3D buildings layer.
22
- const buildingTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
23
- // Fly the camera to San Francisco at the given longitude, latitude, and height.
24
- viewer.camera.flyTo({
25
- destination : Cesium.Cartesian3.fromDegrees(-122.4175, 37.655, 400),
26
- orientation : {
27
- heading : Cesium.Math.toRadians(0.0),
28
- pitch : Cesium.Math.toRadians(-15.0),
29
- }
30
- });
31
- </script>
32
- </div>
33
- </body>
34
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/realtor_data_dict.csv DELETED
@@ -1,37 +0,0 @@
1
- Name,Label,Description
2
- median_listing_price,Median Listing Price,The median listing price within the specified geography during the specified month.
3
- median_listing_price_mm,Median Listing Price M/M,The percentage change in the median listing price from the previous month.
4
- median_listing_price_yy,Median Listing Price Y/Y,The percentage change in the median listing price from the same month in the previous year.
5
- active_listing_count,Active Listing Count,"The count of active listings within the specified geography during the specified month. The active listing count tracks the number of for sale properties on the market, excluding pending listings where a pending status is available. This is a snapshot measure of how many active listings can be expected on any given day of the specified month."
6
- active_listing_count_mm,Active Listing Count M/M,The percentage change in the active listing count from the previous month.
7
- active_listing_count_yy,Active Listing Count Y/Y,The percentage change in the active listing count from the same month in the previous year.
8
- median_days_on_market,Days on Market,The median number of days property listings spend on the market within the specified geography during the specified month. Time spent on the market is defined as the time between the initial listing of a property and either its closing date or the date it is taken off the market.
9
- median_days_on_market_mm,Days on Market M/M,The percentage change in the median days on market from the previous month.
10
- median_days_on_market_yy,Days on Market Y/Y,The percentage change in the median days on market from the same month in the previous year.
11
- new_listing_count,New Listing Count,The count of new listings added to the market within the specified geography. The new listing count represents a typical week’s worth of new listings in a given month. The new listing count can be multiplied by the number of weeks in a month to produce a monthly new listing count.
12
- new_listing_count_mm,New Listing Count M/M,The percentage change in the new listing count from the previous month.
13
- new_listing_count_yy,New Listing Count Y/Y,The percentage change in the new listing count from the same month in the previous year.
14
- price_increased_count,Price Increase Count,The count of listings which have had their price increased within the specified geography. The price increase count represents a typical week’s worth of listings which have had their price increased in a given month. The price increase count can be multiplied by the number of weeks in a month to produce a monthly price increase count.
15
- price_increased_count_mm,Price Increase Count M/M,The percentage change in the price increase count from the previous month.
16
- price_increased_count_yy,Price Increase Count Y/Y,The percentage change in the price increase count from the same month in the previous year.
17
- price_reduced_count,Price Decrease Count,The count of listings which have had their price reduced within the specified geography. The price decrease count represents a typical week’s worth of listings which have had their price reduced in a given month. The price decrease count can be multiplied by the number of weeks in a month to produce a monthly price decrease count.
18
- price_reduced_count_mm,Price Decrease Count M/M,The percentage change in the price decrease count from the previous month.
19
- price_reduced_count_yy,Price Decrease Count Y/Y,The percentage change in the price decrease count from the same month in the previous year.
20
- pending_listing_count,Pending Listing Count,"The count of pending listings within the specified geography during the specified month, if a pending definition is available for that geography. This is a snapshot measure of how many pending listings can be expected on any given day of the specified month."
21
- pending_listing_count_mm,Pending Listing Count M/M,The percentage change in the pending listing count from the previous month.
22
- pending_listing_count_yy,Pending Listing Count Y/Y,The percentage change in the pending listing count from the same month in the previous year.
23
- median_listing_price_per_square_foot,Median List Price Per Sqft,The median listing price per square foot within the specified geography during the specified month.
24
- median_listing_price_per_square_foot_mm,Median List Price Per Sqft M/M,The percentage change in the median listing price per square foot from the previous month.
25
- median_listing_price_per_square_foot_yy,Median List Price Per Sqft Y/Y,The percentage change in the median listing price per square foot from the same month in the previous year.
26
- median_square_feet,Median Listing Sqft,The median listing square feet within the specified geography during the specified month.
27
- median_square_feet_mm,Median Listing Sqft M/M,The percentage change in the median listing square feet from the previous month.
28
- median_square_feet_yy,Median Listing Sqft Y/Y,The percentage change in the median listing square feet from the same month in the previous year.
29
- average_listing_price,Avg Listing Price,The average listing price within the specified geography during the specified month.
30
- average_listing_price_mm,Avg Listing Price M/M,The percentage change in the average listing price from the previous month.
31
- average_listing_price_yy,Avg Listing Price Y/Y,The percentage change in the average listing price from the same month in the previous year.
32
- total_listing_count,Total Listing Count,The total of both active listings and pending listings within the specified geography during the specified month. This is a snapshot measure of how many total listings can be expected on any given day of the specified month.
33
- total_listing_count_mm,Total Listing Count M/M,The percentage change in the total listing count from the previous month.
34
- total_listing_count_yy,Total Listing Count Y/Y,The percentage change in the total listing count from the same month in the previous year.
35
- pending_ratio,Pending Ratio,The ratio of the pending listing count to the active listing count within the specified geography during the specified month.
36
- pending_ratio_mm,Pending Ratio M/M,The change in the pending ratio from the previous month.
37
- pending_ratio_yy,Pending Ratio Y/Y,The change in the pending ratio from the same month in the previous year.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/scotland_xyz.tsv DELETED
@@ -1,51 +0,0 @@
1
- Name URL
2
- Ordnance Survey - Air Photos, 1944-1950 - 1:10,560 https://geo.nls.uk/maps/air-photos/{z}/{x}/{y}.png
3
- Ordnance Survey - Six Inch Scotland, 1843-1882 - 1:10,560 https://mapseries-tilesets.s3.amazonaws.com/os/6inchfirst/{z}/{x}/{y}.png
4
- War Office, Great Britain 1:25,000. GSGS 3906, 1940-43 https://mapseries-tilesets.s3.amazonaws.com/gsgs3906/{z}/{x}/{y}.png
5
- Roy - Roy Highlands, 1747-1752 - 1:36000 https://mapseries-tilesets.s3.amazonaws.com/roy/highlands/{z}/{x}/{y}.png
6
- Roy - Roy Lowlands, 1752-1755 - 1:36000 https://mapseries-tilesets.s3.amazonaws.com/roy/lowlands/{z}/{x}/{y}.png
7
- Great Britain - OS 1:10,560, 1949-1970 https://mapseries-tilesets.s3.amazonaws.com/os/britain10knatgrid/{z}/{x}/{y}.png
8
- Great Britain - Bartholomew Half Inch, 1897-1907 https://mapseries-tilesets.s3.amazonaws.com/bartholomew_great_britain/{z}/{x}/{y}.png
9
- OS 25 inch, 1892-1914 - Scotland South https://mapseries-tilesets.s3.amazonaws.com/25_inch/scotland_1/{z}/{x}/{y}.png
10
- OS 25 inch, 1892-1914 - Scotland North https://mapseries-tilesets.s3.amazonaws.com/25_inch/scotland_2/{z}/{x}/{y}.png
11
- OS 25 inch, 1892-1914 - Bedfordshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/bedfordshire/{z}/{x}/{y}.png
12
- OS 25 inch, 1892-1914 - Berkshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/berkshire/{z}/{x}/{y}.png
13
- OS 25 inch, 1892-1914 - Buckinghamshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/buckingham/{z}/{x}/{y}.png
14
- OS 25 inch, 1892-1914 - Cambridgeshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/cambridge/{z}/{x}/{y}.png
15
- OS 25 inch, 1892-1914 - Cheshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/cheshire/{z}/{x}/{y}.png
16
- OS 25 inch, 1892-1914 - Cornwall https://mapseries-tilesets.s3.amazonaws.com/25_inch/cornwall/{z}/{x}/{y}.png
17
- OS 25 inch, 1892-1914 - Cumberland https://mapseries-tilesets.s3.amazonaws.com/25_inch/cumberland/{z}/{x}/{y}.png
18
- OS 25 inch, 1892-1914 - Devon https://mapseries-tilesets.s3.amazonaws.com/25_inch/devon/{z}/{x}/{y}.png
19
- OS 25 inch, 1892-1914 - Dorset https://mapseries-tilesets.s3.amazonaws.com/25_inch/dorset/{z}/{x}/{y}.png
20
- OS 25 inch, 1892-1914 - Durham https://mapseries-tilesets.s3.amazonaws.com/25_inch/durham/{z}/{x}/{y}.png
21
- OS 25 inch, 1892-1914 - Essex https://mapseries-tilesets.s3.amazonaws.com/25_inch/essex/{z}/{x}/{y}.png
22
- OS 25 inch, 1892-1914 - Gloucestershire https://mapseries-tilesets.s3.amazonaws.com/25_inch/gloucestershire/{z}/{x}/{y}.png
23
- OS 25 inch, 1892-1914 - Hampshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/hampshire/{z}/{x}/{y}.png
24
- OS 25 inch, 1892-1914 - Herefordshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/herefordshire/{z}/{x}/{y}.png
25
- OS 25 inch, 1892-1914 - Hertfordshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/hertfordshire/{z}/{x}/{y}.png
26
- OS 25 inch, 1892-1914 - Huntingdon https://mapseries-tilesets.s3.amazonaws.com/25_inch/huntingdon/{z}/{x}/{y}.png
27
- OS 25 inch, 1892-1914 - Kent https://mapseries-tilesets.s3.amazonaws.com/25_inch/kent/{z}/{x}/{y}.png
28
- OS 25 inch, 1892-1914 - Lancashire https://mapseries-tilesets.s3.amazonaws.com/25_inch/lancashire/{z}/{x}/{y}.png
29
- OS 25 inch, 1892-1914 - Leicestershire https://mapseries-tilesets.s3.amazonaws.com/25_inch/leicestershire/{z}/{x}/{y}.png
30
- OS 25 inch, 1892-1914 - Lincolnshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/lincolnshire/{z}/{x}/{y}.png
31
- OS 25 inch, 1892-1914 - London https://mapseries-tilesets.s3.amazonaws.com/25_inch/london/{z}/{x}/{y}.png
32
- OS 25 inch, 1892-1914 - Middlesex https://mapseries-tilesets.s3.amazonaws.com/25_inch/middlesex/{z}/{x}/{y}.png
33
- OS 25 inch, 1892-1914 - Norfolk https://mapseries-tilesets.s3.amazonaws.com/25_inch/norfolk/{z}/{x}/{y}.png
34
- OS 25 inch, 1892-1914 - Northamptonshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/northampton/{z}/{x}/{y}.png
35
- OS 25 inch, 1892-1914 - Northumberland https://mapseries-tilesets.s3.amazonaws.com/25_inch/northumberland/{z}/{x}/{y}.png
36
- OS 25 inch, 1892-1914 - Nottinghamshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/nottinghamshire/{z}/{x}/{y}.png
37
- OS 25 inch, 1892-1914 - Oxford https://mapseries-tilesets.s3.amazonaws.com/25_inch/oxford/{z}/{x}/{y}.png
38
- OS 25 inch, 1892-1914 - Rutland https://mapseries-tilesets.s3.amazonaws.com/25_inch/rutland/{z}/{x}/{y}.png
39
- OS 25 inch, 1892-1914 - Shropshire / Derbyshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/Shrop_Derby/{z}/{x}/{y}.png
40
- OS 25 inch, 1892-1914 - Somerset https://mapseries-tilesets.s3.amazonaws.com/25_inch/somerset/{z}/{x}/{y}.png
41
- OS 25 inch, 1892-1914 - Stafford https://mapseries-tilesets.s3.amazonaws.com/25_inch/stafford/{z}/{x}/{y}.png
42
- OS 25 inch, 1892-1914 - Suffolk https://mapseries-tilesets.s3.amazonaws.com/25_inch/suffolk/{z}/{x}/{y}.png
43
- OS 25 inch, 1892-1914 - Surrey https://mapseries-tilesets.s3.amazonaws.com/25_inch/surrey/{z}/{x}/{y}.png
44
- OS 25 inch, 1892-1914 - Sussex https://mapseries-tilesets.s3.amazonaws.com/25_inch/sussex/{z}/{x}/{y}.png
45
- OS 25 inch, 1892-1914 - Wales https://mapseries-tilesets.s3.amazonaws.com/25_inch/wales/{z}/{x}/{y}.png
46
- OS 25 inch, 1892-1914 - Warwick https://mapseries-tilesets.s3.amazonaws.com/25_inch/warwick/{z}/{x}/{y}.png
47
- OS 25 inch, 1892-1914 - Westmorland https://mapseries-tilesets.s3.amazonaws.com/25_inch/westmorland/{z}/{x}/{y}.png
48
- OS 25 inch, 1892-1914 - Wiltshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/wiltshire2nd/{z}/{x}/{y}.png
49
- OS 25 inch, 1892-1914 - Worcestershire https://mapseries-tilesets.s3.amazonaws.com/25_inch/Worcestershire/{z}/{x}/{y}.png
50
- OS 25 inch, 1892-1914 - Yorkshire https://mapseries-tilesets.s3.amazonaws.com/25_inch/yorkshire/{z}/{x}/{y}.png
51
- OS 25 inch, 1892-1914 'Holes' (fills gaps in series) https://geo.nls.uk/mapdata3/os/25_inch_holes_england/{z}/{x}/{y}.png
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/us_counties.geojson DELETED
The diff for this file is too large to render. See raw diff
 
data/us_metro_areas.geojson DELETED
The diff for this file is too large to render. See raw diff
 
data/us_nation.geojson DELETED
The diff for this file is too large to render. See raw diff
 
data/us_states.geojson DELETED
The diff for this file is too large to render. See raw diff
 
environment-bk.yml DELETED
@@ -1,17 +0,0 @@
1
- name: geo
2
- channels:
3
- - conda-forge
4
- dependencies:
5
- - gdal=3.4.3
6
- - pip
7
- - pip:
8
- - geopandas
9
- - keplergl
10
- - streamlit
11
- - localtileserver
12
- - palettable
13
- - streamlit-folium
14
- - streamlit-keplergl
15
- - streamlit-bokeh-events
16
- - git+https://github.com/giswqs/leafmap
17
- - git+https://github.com/giswqs/geemap
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
index.html DELETED
@@ -1,39 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Streamlit for Geospatial</title>
5
- <style type="text/css">
6
- html {
7
- overflow: auto;
8
- }
9
- html,
10
- body,
11
- div,
12
- iframe {
13
- margin: 0px;
14
- padding: 0px;
15
- height: 100%;
16
- border: none;
17
- }
18
- iframe {
19
- display: block;
20
- width: 100%;
21
- border: none;
22
- overflow-y: auto;
23
- overflow-x: hidden;
24
- }
25
- </style>
26
- </head>
27
- <body>
28
- <iframe
29
- src="https://share.streamlit.io/giswqs/streamlit-geospatial/app.py"
30
- frameborder="0"
31
- marginheight="0"
32
- marginwidth="0"
33
- width="100%"
34
- height="100%"
35
- scrolling="auto"
36
- >
37
- </iframe>
38
- </body>
39
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
multiapp.py DELETED
@@ -1,75 +0,0 @@
1
- """Frameworks for running multiple Streamlit applications as a single app.
2
- """
3
- import streamlit as st
4
-
5
- # app_state = st.experimental_get_query_params()
6
- # app_state = {k: v[0] if isinstance(v, list) else v for k, v in app_state.items()} # fetch the first item in each query string as we don't have multiple values for each query string key in this example
7
-
8
-
9
- class MultiApp:
10
- """Framework for combining multiple streamlit applications.
11
- Usage:
12
- def foo():
13
- st.title("Hello Foo")
14
- def bar():
15
- st.title("Hello Bar")
16
- app = MultiApp()
17
- app.add_app("Foo", foo)
18
- app.add_app("Bar", bar)
19
- app.run()
20
- It is also possible keep each application in a separate file.
21
- import foo
22
- import bar
23
- app = MultiApp()
24
- app.add_app("Foo", foo.app)
25
- app.add_app("Bar", bar.app)
26
- app.run()
27
- """
28
-
29
- def __init__(self):
30
- self.apps = []
31
-
32
- def add_app(self, title, func):
33
- """Adds a new application.
34
- Parameters
35
- ----------
36
- func:
37
- the python function to render this app.
38
- title:
39
- title of the app. Appears in the dropdown in the sidebar.
40
- """
41
- self.apps.append({"title": title, "function": func})
42
-
43
- def run(self):
44
- app_state = st.experimental_get_query_params()
45
- app_state = {
46
- k: v[0] if isinstance(v, list) else v for k, v in app_state.items()
47
- } # fetch the first item in each query string as we don't have multiple values for each query string key in this example
48
-
49
- # st.write('before', app_state)
50
-
51
- titles = [a["title"] for a in self.apps]
52
- functions = [a["function"] for a in self.apps]
53
- default_radio = titles.index(app_state["page"]) if "page" in app_state else 0
54
-
55
- st.sidebar.title("Navigation")
56
-
57
- title = st.sidebar.radio("Go To", titles, index=default_radio, key="radio")
58
-
59
- app_state["page"] = st.session_state.radio
60
- # st.write('after', app_state)
61
-
62
- st.experimental_set_query_params(**app_state)
63
- # st.experimental_set_query_params(**st.session_state.to_dict())
64
- functions[titles.index(title)]()
65
-
66
- st.sidebar.title("Contribute")
67
- st.sidebar.info(
68
- "Welcome, [source code](https://github.com/giswqs/streamlit-geospatial). "
69
- )
70
- st.sidebar.title("About")
71
- st.sidebar.info(
72
- """
73
- This web [app](https://share.streamlit.io/giswqs/streamlit-geospatial/app.py) is developed by Mapaction
74
- """
75
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
packages.txt DELETED
@@ -1,9 +0,0 @@
1
- ffmpeg
2
- gifsicle
3
- build-essential
4
- python3-dev
5
- gdal-bin
6
- libgdal-dev
7
- libproj-dev
8
- libgeos-dev
9
- proj-bin
 
 
 
 
 
 
 
 
 
 
pages/.DS_Store DELETED
Binary file (6.15 kB)
 
pages/10_🌍_Earth_Engine_Datasets.py DELETED
@@ -1,144 +0,0 @@
1
- import ee
2
- import streamlit as st
3
- import geemap.foliumap as geemap
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 nlcd():
25
-
26
- # st.header("National Land Cover Database (NLCD)")
27
-
28
- row1_col1, row1_col2 = st.columns([3, 1])
29
- width = 950
30
- height = 600
31
-
32
- Map = geemap.Map(center=[40, -100], zoom=4)
33
-
34
- # Select the seven NLCD epoches after 2000.
35
- years = ["2001", "2004", "2006", "2008", "2011", "2013", "2016", "2019"]
36
-
37
- # Get an NLCD image by year.
38
- def getNLCD(year):
39
- # Import the NLCD collection.
40
- dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD")
41
-
42
- # Filter the collection by year.
43
- nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first()
44
-
45
- # Select the land cover band.
46
- landcover = nlcd.select("landcover")
47
- return landcover
48
-
49
- with row1_col2:
50
- selected_year = st.multiselect("Select a year", years)
51
- add_legend = st.checkbox("Show legend")
52
-
53
- if selected_year:
54
- for year in selected_year:
55
- Map.addLayer(getNLCD(year), {}, "NLCD " + year)
56
-
57
- if add_legend:
58
- Map.add_legend(
59
- legend_title="NLCD Land Cover Classification", builtin_legend="NLCD"
60
- )
61
- with row1_col1:
62
- Map.to_streamlit(width=width, height=height)
63
-
64
- else:
65
- with row1_col1:
66
- Map.to_streamlit(width=width, height=height)
67
-
68
-
69
- def search_data():
70
-
71
- # st.header("Search Earth Engine Data Catalog")
72
-
73
- Map = geemap.Map()
74
-
75
- if "ee_assets" not in st.session_state:
76
- st.session_state["ee_assets"] = None
77
- if "asset_titles" not in st.session_state:
78
- st.session_state["asset_titles"] = None
79
-
80
- col1, col2 = st.columns([2, 1])
81
-
82
- dataset = None
83
- with col2:
84
- keyword = st.text_input("Enter a keyword to search (e.g., elevation)", "")
85
- if keyword:
86
- ee_assets = geemap.search_ee_data(keyword)
87
- asset_titles = [x["title"] for x in ee_assets]
88
- dataset = st.selectbox("Select a dataset", asset_titles)
89
- if len(ee_assets) > 0:
90
- st.session_state["ee_assets"] = ee_assets
91
- st.session_state["asset_titles"] = asset_titles
92
-
93
- if dataset is not None:
94
- with st.expander("Show dataset details", True):
95
- index = asset_titles.index(dataset)
96
- html = geemap.ee_data_html(st.session_state["ee_assets"][index])
97
- st.markdown(html, True)
98
-
99
- ee_id = ee_assets[index]["ee_id_snippet"]
100
- uid = ee_assets[index]["uid"]
101
- st.markdown(f"""**Earth Engine Snippet:** `{ee_id}`""")
102
-
103
- vis_params = st.text_input(
104
- "Enter visualization parameters as a dictionary", {}
105
- )
106
- layer_name = st.text_input("Enter a layer name", uid)
107
- button = st.button("Add dataset to map")
108
- if button:
109
- vis = {}
110
- try:
111
- if vis_params.strip() == "":
112
- # st.error("Please enter visualization parameters")
113
- vis_params = "{}"
114
- vis = eval(vis_params)
115
- if not isinstance(vis, dict):
116
- st.error("Visualization parameters must be a dictionary")
117
- try:
118
- Map.addLayer(eval(ee_id), vis, layer_name)
119
- except Exception as e:
120
- st.error(f"Error adding layer: {e}")
121
- except Exception as e:
122
- st.error(f"Invalid visualization parameters: {e}")
123
-
124
- with col1:
125
- Map.to_streamlit()
126
- else:
127
- with col1:
128
- Map.to_streamlit()
129
-
130
-
131
- def app():
132
- st.title("Earth Engine Data Catalog")
133
-
134
- apps = ["Search Earth Engine Data Catalog", "National Land Cover Database (NLCD)"]
135
-
136
- selected_app = st.selectbox("Select an app", apps)
137
-
138
- if selected_app == "National Land Cover Database (NLCD)":
139
- nlcd()
140
- elif selected_app == "Search Earth Engine Data Catalog":
141
- search_data()
142
-
143
-
144
- app()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/11_🧱_Ordnance_Survey.py DELETED
@@ -1,110 +0,0 @@
1
- import folium
2
- import pandas as pd
3
- import streamlit as st
4
- import leafmap.foliumap as leafmap
5
- import folium.plugins as plugins
6
-
7
- st.set_page_config(layout="wide")
8
-
9
- st.sidebar.title("About")
10
- st.sidebar.info(
11
- """
12
- Web App URL: <https://geospatial.streamlitapp.com>
13
- GitHub repository: <https://github.com/giswqs/streamlit-geospatial>
14
- """
15
- )
16
-
17
- st.sidebar.title("Contact")
18
- st.sidebar.info(
19
- """
20
- Qiusheng Wu: <https://wetlands.io>
21
- [GitHub](https://github.com/giswqs) | [Twitter](https://twitter.com/giswqs) | [YouTube](https://www.youtube.com/c/QiushengWu) | [LinkedIn](https://www.linkedin.com/in/qiushengwu)
22
- """
23
- )
24
-
25
- st.title("National Library of Scotland XYZ Layers")
26
- df = pd.read_csv("data/scotland_xyz.tsv", sep="\t")
27
- basemaps = leafmap.basemaps
28
- names = df["Name"].values.tolist() + list(basemaps.keys())
29
- links = df["URL"].values.tolist() + list(basemaps.values())
30
-
31
- col1, col2, col3, col4, col5, col6, col7 = st.columns([3, 3, 1, 1, 1, 1.5, 1.5])
32
- with col1:
33
- left_name = st.selectbox(
34
- "Select the left layer",
35
- names,
36
- index=names.index("Great Britain - Bartholomew Half Inch, 1897-1907"),
37
- )
38
-
39
- with col2:
40
- right_name = st.selectbox(
41
- "Select the right layer",
42
- names,
43
- index=names.index("HYBRID"),
44
- )
45
-
46
- with col3:
47
- # lat = st.slider('Latitude', -90.0, 90.0, 55.68, step=0.01)
48
- lat = st.text_input("Latitude", " 55.68")
49
-
50
- with col4:
51
- # lon = st.slider('Longitude', -180.0, 180.0, -2.98, step=0.01)
52
- lon = st.text_input("Longitude", "-2.98")
53
-
54
- with col5:
55
- # zoom = st.slider('Zoom', 1, 24, 6, step=1)
56
- zoom = st.text_input("Zoom", "6")
57
-
58
- with col6:
59
- checkbox = st.checkbox("Add OS 25 inch")
60
-
61
- # with col7:
62
- with st.expander("Acknowledgements"):
63
- markdown = """
64
- The map tile access is by kind arrangement of the National Library of Scotland on the understanding that re-use is for personal purposes. They host most of the map layers except these:
65
- - The Roy Maps are owned by the British Library.
66
- - The Great Britain – OS maps 1:25,000, 1937-61 and One Inch 7th series, 1955-61 are hosted by MapTiler.
67
-
68
- If you wish you use these layers within a website, or for a commercial or public purpose, please view the [National Library of Scotland Historic Maps Subscription API](https://maps.nls.uk/projects/subscription-api/) or contact them at [email protected].
69
- """
70
- st.markdown(markdown, unsafe_allow_html=True)
71
-
72
- m = leafmap.Map(
73
- center=[float(lat), float(lon)],
74
- zoom=int(zoom),
75
- locate_control=True,
76
- draw_control=False,
77
- measure_control=False,
78
- )
79
- measure = plugins.MeasureControl(position="bottomleft", active_color="orange")
80
- measure.add_to(m)
81
-
82
- if left_name in basemaps:
83
- left_layer = basemaps[left_name]
84
- else:
85
- left_layer = folium.TileLayer(
86
- tiles=links[names.index(left_name)],
87
- name=left_name,
88
- attr="National Library of Scotland",
89
- overlay=True,
90
- )
91
-
92
- if right_name in basemaps:
93
- right_layer = basemaps[right_name]
94
- else:
95
- right_layer = folium.TileLayer(
96
- tiles=links[names.index(right_name)],
97
- name=right_name,
98
- attr="National Library of Scotland",
99
- overlay=True,
100
- )
101
-
102
- if checkbox:
103
- for index, name in enumerate(names):
104
- if "OS 25 inch" in name:
105
- m.add_tile_layer(
106
- links[index], name, attribution="National Library of Scotland"
107
- )
108
-
109
- m.split_map(left_layer, right_layer)
110
- m.to_streamlit(height=600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/12_🌲_Land_Cover_Mapping.py DELETED
@@ -1,113 +0,0 @@
1
- import datetime
2
- import ee
3
- import streamlit as st
4
- import geemap.foliumap as geemap
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
- st.title("Comparing Global Land Cover Maps")
25
-
26
- col1, col2 = st.columns([4, 1])
27
-
28
- Map = geemap.Map()
29
- Map.add_basemap("ESA WorldCover 2020 S2 FCC")
30
- Map.add_basemap("ESA WorldCover 2020 S2 TCC")
31
- Map.add_basemap("HYBRID")
32
-
33
- esa = ee.ImageCollection("ESA/WorldCover/v100").first()
34
- esa_vis = {"bands": ["Map"]}
35
-
36
-
37
- esri = ee.ImageCollection(
38
- "projects/sat-io/open-datasets/landcover/ESRI_Global-LULC_10m"
39
- ).mosaic()
40
- esri_vis = {
41
- "min": 1,
42
- "max": 10,
43
- "palette": [
44
- "#1A5BAB",
45
- "#358221",
46
- "#A7D282",
47
- "#87D19E",
48
- "#FFDB5C",
49
- "#EECFA8",
50
- "#ED022A",
51
- "#EDE9E4",
52
- "#F2FAFF",
53
- "#C8C8C8",
54
- ],
55
- }
56
-
57
-
58
- markdown = """
59
- - [Dynamic World Land Cover](https://developers.google.com/earth-engine/datasets/catalog/GOOGLE_DYNAMICWORLD_V1?hl=en)
60
- - [ESA Global Land Cover](https://developers.google.com/earth-engine/datasets/catalog/ESA_WorldCover_v100)
61
- - [ESRI Global Land Cover](https://samapriya.github.io/awesome-gee-community-datasets/projects/esrilc2020)
62
-
63
- """
64
-
65
- with col2:
66
-
67
- longitude = st.number_input("Longitude", -180.0, 180.0, -89.3998)
68
- latitude = st.number_input("Latitude", -90.0, 90.0, 43.0886)
69
- zoom = st.number_input("Zoom", 0, 20, 11)
70
-
71
- Map.setCenter(longitude, latitude, zoom)
72
-
73
- start = st.date_input("Start Date for Dynamic World", datetime.date(2020, 1, 1))
74
- end = st.date_input("End Date for Dynamic World", datetime.date(2021, 1, 1))
75
-
76
- start_date = start.strftime("%Y-%m-%d")
77
- end_date = end.strftime("%Y-%m-%d")
78
-
79
- region = ee.Geometry.BBox(-179, -89, 179, 89)
80
- dw = geemap.dynamic_world(region, start_date, end_date, return_type="hillshade")
81
-
82
- layers = {
83
- "Dynamic World": geemap.ee_tile_layer(dw, {}, "Dynamic World Land Cover"),
84
- "ESA Land Cover": geemap.ee_tile_layer(esa, esa_vis, "ESA Land Cover"),
85
- "ESRI Land Cover": geemap.ee_tile_layer(esri, esri_vis, "ESRI Land Cover"),
86
- }
87
-
88
- options = list(layers.keys())
89
- left = st.selectbox("Select a left layer", options, index=1)
90
- right = st.selectbox("Select a right layer", options, index=0)
91
-
92
- left_layer = layers[left]
93
- right_layer = layers[right]
94
-
95
- Map.split_map(left_layer, right_layer)
96
-
97
- legend = st.selectbox("Select a legend", options, index=options.index(right))
98
- if legend == "Dynamic World":
99
- Map.add_legend(
100
- title="Dynamic World Land Cover",
101
- builtin_legend="Dynamic_World",
102
- )
103
- elif legend == "ESA Land Cover":
104
- Map.add_legend(title="ESA Land Cover", builtin_legend="ESA_WorldCover")
105
- elif legend == "ESRI Land Cover":
106
- Map.add_legend(title="ESRI Land Cover", builtin_legend="ESRI_LandCover")
107
-
108
- with st.expander("Data sources"):
109
- st.markdown(markdown)
110
-
111
-
112
- with col1:
113
- Map.to_streamlit(height=750)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/1_πŸ“·_Timelapse.py DELETED
@@ -1,1517 +0,0 @@
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
- try:
615
- st.session_state["roi"] = geemap.gdf_to_ee(gdf, geodesic=False)
616
- except Exception as e:
617
- st.error(e)
618
- st.error("Please draw another ROI and try again.")
619
- return
620
- m.add_gdf(gdf, "ROI")
621
-
622
- elif data:
623
- gdf = uploaded_file_to_gdf(data)
624
- try:
625
- st.session_state["roi"] = geemap.gdf_to_ee(gdf, geodesic=False)
626
- m.add_gdf(gdf, "ROI")
627
- except Exception as e:
628
- st.error(e)
629
- st.error("Please draw another ROI and try again.")
630
- return
631
-
632
- m.to_streamlit(height=600)
633
-
634
- with row1_col2:
635
-
636
- if collection in [
637
- "Landsat TM-ETM-OLI Surface Reflectance",
638
- "Sentinel-2 MSI Surface Reflectance",
639
- ]:
640
-
641
- if collection == "Landsat TM-ETM-OLI Surface Reflectance":
642
- sensor_start_year = 1984
643
- timelapse_title = "Landsat Timelapse"
644
- timelapse_speed = 5
645
- elif collection == "Sentinel-2 MSI Surface Reflectance":
646
- sensor_start_year = 2015
647
- timelapse_title = "Sentinel-2 Timelapse"
648
- timelapse_speed = 5
649
- video_empty.video("https://youtu.be/VVRK_-dEjR4")
650
-
651
- with st.form("submit_landsat_form"):
652
-
653
- roi = None
654
- if st.session_state.get("roi") is not None:
655
- roi = st.session_state.get("roi")
656
- out_gif = geemap.temp_file_path(".gif")
657
-
658
- title = st.text_input(
659
- "Enter a title to show on the timelapse: ", timelapse_title
660
- )
661
- RGB = st.selectbox(
662
- "Select an RGB band combination:",
663
- [
664
- "Red/Green/Blue",
665
- "NIR/Red/Green",
666
- "SWIR2/SWIR1/NIR",
667
- "NIR/SWIR1/Red",
668
- "SWIR2/NIR/Red",
669
- "SWIR2/SWIR1/Red",
670
- "SWIR1/NIR/Blue",
671
- "NIR/SWIR1/Blue",
672
- "SWIR2/NIR/Green",
673
- "SWIR1/NIR/Red",
674
- "SWIR2/NIR/SWIR1",
675
- "SWIR1/NIR/SWIR2",
676
- ],
677
- index=9,
678
- )
679
-
680
- frequency = st.selectbox(
681
- "Select a temporal frequency:",
682
- ["year", "quarter", "month"],
683
- index=0,
684
- )
685
-
686
- with st.expander("Customize timelapse"):
687
-
688
- speed = st.slider("Frames per second:", 1, 30, timelapse_speed)
689
- dimensions = st.slider(
690
- "Maximum dimensions (Width*Height) in pixels", 768, 2000, 768
691
- )
692
- progress_bar_color = st.color_picker(
693
- "Progress bar color:", "#0000ff"
694
- )
695
- years = st.slider(
696
- "Start and end year:",
697
- sensor_start_year,
698
- today.year,
699
- (sensor_start_year, today.year),
700
- )
701
- months = st.slider("Start and end month:", 1, 12, (1, 12))
702
- font_size = st.slider("Font size:", 10, 50, 30)
703
- font_color = st.color_picker("Font color:", "#ffffff")
704
- apply_fmask = st.checkbox(
705
- "Apply fmask (remove clouds, shadows, snow)", True
706
- )
707
- font_type = st.selectbox(
708
- "Select the font type for the title:",
709
- ["arial.ttf", "alibaba.otf"],
710
- index=0,
711
- )
712
- fading = st.slider(
713
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
714
- )
715
- mp4 = st.checkbox("Save timelapse as MP4", True)
716
-
717
- empty_text = st.empty()
718
- empty_image = st.empty()
719
- empty_fire_image = st.empty()
720
- empty_video = st.container()
721
- submitted = st.form_submit_button("Submit")
722
- if submitted:
723
-
724
- if sample_roi == "Uploaded GeoJSON" and data is None:
725
- empty_text.warning(
726
- "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."
727
- )
728
- else:
729
-
730
- empty_text.text("Computing... Please wait...")
731
-
732
- start_year = years[0]
733
- end_year = years[1]
734
- start_date = str(months[0]).zfill(2) + "-01"
735
- end_date = str(months[1]).zfill(2) + "-30"
736
- bands = RGB.split("/")
737
-
738
- try:
739
- if collection == "Landsat TM-ETM-OLI Surface Reflectance":
740
- out_gif = geemap.landsat_timelapse(
741
- roi=roi,
742
- out_gif=out_gif,
743
- start_year=start_year,
744
- end_year=end_year,
745
- start_date=start_date,
746
- end_date=end_date,
747
- bands=bands,
748
- apply_fmask=apply_fmask,
749
- frames_per_second=speed,
750
- # dimensions=dimensions,
751
- dimensions=768,
752
- overlay_data=overlay_data,
753
- overlay_color=overlay_color,
754
- overlay_width=overlay_width,
755
- overlay_opacity=overlay_opacity,
756
- frequency=frequency,
757
- date_format=None,
758
- title=title,
759
- title_xy=("2%", "90%"),
760
- add_text=True,
761
- text_xy=("2%", "2%"),
762
- text_sequence=None,
763
- font_type=font_type,
764
- font_size=font_size,
765
- font_color=font_color,
766
- add_progress_bar=True,
767
- progress_bar_color=progress_bar_color,
768
- progress_bar_height=5,
769
- loop=0,
770
- mp4=mp4,
771
- fading=fading,
772
- )
773
- elif collection == "Sentinel-2 MSI Surface Reflectance":
774
- out_gif = geemap.sentinel2_timelapse(
775
- roi=roi,
776
- out_gif=out_gif,
777
- start_year=start_year,
778
- end_year=end_year,
779
- start_date=start_date,
780
- end_date=end_date,
781
- bands=bands,
782
- apply_fmask=apply_fmask,
783
- frames_per_second=speed,
784
- dimensions=768,
785
- # dimensions=dimensions,
786
- overlay_data=overlay_data,
787
- overlay_color=overlay_color,
788
- overlay_width=overlay_width,
789
- overlay_opacity=overlay_opacity,
790
- frequency=frequency,
791
- date_format=None,
792
- title=title,
793
- title_xy=("2%", "90%"),
794
- add_text=True,
795
- text_xy=("2%", "2%"),
796
- text_sequence=None,
797
- font_type=font_type,
798
- font_size=font_size,
799
- font_color=font_color,
800
- add_progress_bar=True,
801
- progress_bar_color=progress_bar_color,
802
- progress_bar_height=5,
803
- loop=0,
804
- mp4=mp4,
805
- fading=fading,
806
- )
807
- except:
808
- empty_text.error(
809
- "An error occurred while computing the timelapse. Your probably requested too much data. Try reducing the ROI or timespan."
810
- )
811
- st.stop()
812
-
813
- if out_gif is not None and os.path.exists(out_gif):
814
-
815
- empty_text.text(
816
- "Right click the GIF to save it to your computerπŸ‘‡"
817
- )
818
- empty_image.image(out_gif)
819
-
820
- out_mp4 = out_gif.replace(".gif", ".mp4")
821
- if mp4 and os.path.exists(out_mp4):
822
- with empty_video:
823
- st.text(
824
- "Right click the MP4 to save it to your computerπŸ‘‡"
825
- )
826
- st.video(out_gif.replace(".gif", ".mp4"))
827
-
828
- else:
829
- empty_text.error(
830
- "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
831
- )
832
-
833
- elif collection == "Geostationary Operational Environmental Satellites (GOES)":
834
-
835
- video_empty.video("https://youtu.be/16fA2QORG4A")
836
-
837
- with st.form("submit_goes_form"):
838
-
839
- roi = None
840
- if st.session_state.get("roi") is not None:
841
- roi = st.session_state.get("roi")
842
- out_gif = geemap.temp_file_path(".gif")
843
-
844
- satellite = st.selectbox("Select a satellite:", ["GOES-17", "GOES-16"])
845
- earliest_date = datetime.date(2017, 7, 10)
846
- latest_date = datetime.date.today()
847
-
848
- if sample_roi == "Uploaded GeoJSON":
849
- roi_start_date = today - datetime.timedelta(days=2)
850
- roi_end_date = today - datetime.timedelta(days=1)
851
- roi_start_time = datetime.time(14, 00)
852
- roi_end_time = datetime.time(1, 00)
853
- else:
854
- roi_start = goes_rois[sample_roi]["start_time"]
855
- roi_end = goes_rois[sample_roi]["end_time"]
856
- roi_start_date = datetime.datetime.strptime(
857
- roi_start[:10], "%Y-%m-%d"
858
- )
859
- roi_end_date = datetime.datetime.strptime(roi_end[:10], "%Y-%m-%d")
860
- roi_start_time = datetime.time(
861
- int(roi_start[11:13]), int(roi_start[14:16])
862
- )
863
- roi_end_time = datetime.time(
864
- int(roi_end[11:13]), int(roi_end[14:16])
865
- )
866
-
867
- start_date = st.date_input("Select the start date:", roi_start_date)
868
- end_date = st.date_input("Select the end date:", roi_end_date)
869
-
870
- with st.expander("Customize timelapse"):
871
-
872
- add_fire = st.checkbox("Add Fire/Hotspot Characterization", False)
873
-
874
- scan_type = st.selectbox(
875
- "Select a scan type:", ["Full Disk", "CONUS", "Mesoscale"]
876
- )
877
-
878
- start_time = st.time_input(
879
- "Select the start time of the start date:", roi_start_time
880
- )
881
-
882
- end_time = st.time_input(
883
- "Select the end time of the end date:", roi_end_time
884
- )
885
-
886
- start = (
887
- start_date.strftime("%Y-%m-%d")
888
- + "T"
889
- + start_time.strftime("%H:%M:%S")
890
- )
891
- end = (
892
- end_date.strftime("%Y-%m-%d")
893
- + "T"
894
- + end_time.strftime("%H:%M:%S")
895
- )
896
-
897
- speed = st.slider("Frames per second:", 1, 30, 5)
898
- add_progress_bar = st.checkbox("Add a progress bar", True)
899
- progress_bar_color = st.color_picker(
900
- "Progress bar color:", "#0000ff"
901
- )
902
- font_size = st.slider("Font size:", 10, 50, 20)
903
- font_color = st.color_picker("Font color:", "#ffffff")
904
- fading = st.slider(
905
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
906
- )
907
- mp4 = st.checkbox("Save timelapse as MP4", True)
908
-
909
- empty_text = st.empty()
910
- empty_image = st.empty()
911
- empty_video = st.container()
912
- empty_fire_text = st.empty()
913
- empty_fire_image = st.empty()
914
-
915
- submitted = st.form_submit_button("Submit")
916
- if submitted:
917
- if sample_roi == "Uploaded GeoJSON" and data is None:
918
- empty_text.warning(
919
- "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."
920
- )
921
- else:
922
- empty_text.text("Computing... Please wait...")
923
-
924
- geemap.goes_timelapse(
925
- out_gif,
926
- start_date=start,
927
- end_date=end,
928
- data=satellite,
929
- scan=scan_type.replace(" ", "_").lower(),
930
- region=roi,
931
- dimensions=768,
932
- framesPerSecond=speed,
933
- date_format="YYYY-MM-dd HH:mm",
934
- xy=("3%", "3%"),
935
- text_sequence=None,
936
- font_type="arial.ttf",
937
- font_size=font_size,
938
- font_color=font_color,
939
- add_progress_bar=add_progress_bar,
940
- progress_bar_color=progress_bar_color,
941
- progress_bar_height=5,
942
- loop=0,
943
- overlay_data=overlay_data,
944
- overlay_color=overlay_color,
945
- overlay_width=overlay_width,
946
- overlay_opacity=overlay_opacity,
947
- mp4=mp4,
948
- fading=fading,
949
- )
950
-
951
- if out_gif is not None and os.path.exists(out_gif):
952
- empty_text.text(
953
- "Right click the GIF to save it to your computerπŸ‘‡"
954
- )
955
- empty_image.image(out_gif)
956
-
957
- out_mp4 = out_gif.replace(".gif", ".mp4")
958
- if mp4 and os.path.exists(out_mp4):
959
- with empty_video:
960
- st.text(
961
- "Right click the MP4 to save it to your computerπŸ‘‡"
962
- )
963
- st.video(out_gif.replace(".gif", ".mp4"))
964
-
965
- if add_fire:
966
- out_fire_gif = geemap.temp_file_path(".gif")
967
- empty_fire_text.text(
968
- "Delineating Fire Hotspot... Please wait..."
969
- )
970
- geemap.goes_fire_timelapse(
971
- out_fire_gif,
972
- start_date=start,
973
- end_date=end,
974
- data=satellite,
975
- scan=scan_type.replace(" ", "_").lower(),
976
- region=roi,
977
- dimensions=768,
978
- framesPerSecond=speed,
979
- date_format="YYYY-MM-dd HH:mm",
980
- xy=("3%", "3%"),
981
- text_sequence=None,
982
- font_type="arial.ttf",
983
- font_size=font_size,
984
- font_color=font_color,
985
- add_progress_bar=add_progress_bar,
986
- progress_bar_color=progress_bar_color,
987
- progress_bar_height=5,
988
- loop=0,
989
- )
990
- if os.path.exists(out_fire_gif):
991
- empty_fire_image.image(out_fire_gif)
992
- else:
993
- empty_text.text(
994
- "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."
995
- )
996
-
997
- elif collection == "MODIS Vegetation Indices (NDVI/EVI) 16-Day Global 1km":
998
-
999
- video_empty.video("https://youtu.be/16fA2QORG4A")
1000
-
1001
- satellite = st.selectbox("Select a satellite:", ["Terra", "Aqua"])
1002
- band = st.selectbox("Select a band:", ["NDVI", "EVI"])
1003
-
1004
- with st.form("submit_modis_form"):
1005
-
1006
- roi = None
1007
- if st.session_state.get("roi") is not None:
1008
- roi = st.session_state.get("roi")
1009
- out_gif = geemap.temp_file_path(".gif")
1010
-
1011
- with st.expander("Customize timelapse"):
1012
-
1013
- start = st.date_input(
1014
- "Select a start date:", datetime.date(2000, 2, 8)
1015
- )
1016
- end = st.date_input("Select an end date:", datetime.date.today())
1017
-
1018
- start_date = start.strftime("%Y-%m-%d")
1019
- end_date = end.strftime("%Y-%m-%d")
1020
-
1021
- speed = st.slider("Frames per second:", 1, 30, 5)
1022
- add_progress_bar = st.checkbox("Add a progress bar", True)
1023
- progress_bar_color = st.color_picker(
1024
- "Progress bar color:", "#0000ff"
1025
- )
1026
- font_size = st.slider("Font size:", 10, 50, 20)
1027
- font_color = st.color_picker("Font color:", "#ffffff")
1028
-
1029
- font_type = st.selectbox(
1030
- "Select the font type for the title:",
1031
- ["arial.ttf", "alibaba.otf"],
1032
- index=0,
1033
- )
1034
- fading = st.slider(
1035
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1036
- )
1037
- mp4 = st.checkbox("Save timelapse as MP4", True)
1038
-
1039
- empty_text = st.empty()
1040
- empty_image = st.empty()
1041
- empty_video = st.container()
1042
-
1043
- submitted = st.form_submit_button("Submit")
1044
- if submitted:
1045
- if sample_roi == "Uploaded GeoJSON" and data is None:
1046
- empty_text.warning(
1047
- "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."
1048
- )
1049
- else:
1050
-
1051
- empty_text.text("Computing... Please wait...")
1052
-
1053
- geemap.modis_ndvi_timelapse(
1054
- out_gif,
1055
- satellite,
1056
- band,
1057
- start_date,
1058
- end_date,
1059
- roi,
1060
- 768,
1061
- speed,
1062
- overlay_data=overlay_data,
1063
- overlay_color=overlay_color,
1064
- overlay_width=overlay_width,
1065
- overlay_opacity=overlay_opacity,
1066
- mp4=mp4,
1067
- fading=fading,
1068
- )
1069
-
1070
- geemap.reduce_gif_size(out_gif)
1071
-
1072
- empty_text.text(
1073
- "Right click the GIF to save it to your computerπŸ‘‡"
1074
- )
1075
- empty_image.image(out_gif)
1076
-
1077
- out_mp4 = out_gif.replace(".gif", ".mp4")
1078
- if mp4 and os.path.exists(out_mp4):
1079
- with empty_video:
1080
- st.text(
1081
- "Right click the MP4 to save it to your computerπŸ‘‡"
1082
- )
1083
- st.video(out_gif.replace(".gif", ".mp4"))
1084
-
1085
- elif collection == "Any Earth Engine ImageCollection":
1086
-
1087
- with st.form("submit_ts_form"):
1088
- with st.expander("Customize timelapse"):
1089
-
1090
- title = st.text_input(
1091
- "Enter a title to show on the timelapse: ", "Timelapse"
1092
- )
1093
- start_date = st.date_input(
1094
- "Select the start date:", datetime.date(2020, 1, 1)
1095
- )
1096
- end_date = st.date_input(
1097
- "Select the end date:", datetime.date.today()
1098
- )
1099
- frequency = st.selectbox(
1100
- "Select a temporal frequency:",
1101
- ["year", "quarter", "month", "day", "hour", "minute", "second"],
1102
- index=0,
1103
- )
1104
- reducer = st.selectbox(
1105
- "Select a reducer for aggregating data:",
1106
- ["median", "mean", "min", "max", "sum", "variance", "stdDev"],
1107
- index=0,
1108
- )
1109
- data_format = st.selectbox(
1110
- "Select a date format to show on the timelapse:",
1111
- [
1112
- "YYYY-MM-dd",
1113
- "YYYY",
1114
- "YYMM-MM",
1115
- "YYYY-MM-dd HH:mm",
1116
- "YYYY-MM-dd HH:mm:ss",
1117
- "HH:mm",
1118
- "HH:mm:ss",
1119
- "w",
1120
- "M",
1121
- "d",
1122
- "D",
1123
- ],
1124
- index=0,
1125
- )
1126
-
1127
- speed = st.slider("Frames per second:", 1, 30, 5)
1128
- add_progress_bar = st.checkbox("Add a progress bar", True)
1129
- progress_bar_color = st.color_picker(
1130
- "Progress bar color:", "#0000ff"
1131
- )
1132
- font_size = st.slider("Font size:", 10, 50, 30)
1133
- font_color = st.color_picker("Font color:", "#ffffff")
1134
- font_type = st.selectbox(
1135
- "Select the font type for the title:",
1136
- ["arial.ttf", "alibaba.otf"],
1137
- index=0,
1138
- )
1139
- fading = st.slider(
1140
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1141
- )
1142
- mp4 = st.checkbox("Save timelapse as MP4", True)
1143
-
1144
- empty_text = st.empty()
1145
- empty_image = st.empty()
1146
- empty_video = st.container()
1147
- empty_fire_image = st.empty()
1148
-
1149
- roi = None
1150
- if st.session_state.get("roi") is not None:
1151
- roi = st.session_state.get("roi")
1152
- out_gif = geemap.temp_file_path(".gif")
1153
-
1154
- submitted = st.form_submit_button("Submit")
1155
- if submitted:
1156
-
1157
- if sample_roi == "Uploaded GeoJSON" and data is None:
1158
- empty_text.warning(
1159
- "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."
1160
- )
1161
- else:
1162
-
1163
- empty_text.text("Computing... Please wait...")
1164
- try:
1165
- geemap.create_timelapse(
1166
- st.session_state.get("ee_asset_id"),
1167
- start_date=start_date.strftime("%Y-%m-%d"),
1168
- end_date=end_date.strftime("%Y-%m-%d"),
1169
- region=roi,
1170
- frequency=frequency,
1171
- reducer=reducer,
1172
- date_format=data_format,
1173
- out_gif=out_gif,
1174
- bands=st.session_state.get("bands"),
1175
- palette=st.session_state.get("palette"),
1176
- vis_params=st.session_state.get("vis_params"),
1177
- dimensions=768,
1178
- frames_per_second=speed,
1179
- crs="EPSG:3857",
1180
- overlay_data=overlay_data,
1181
- overlay_color=overlay_color,
1182
- overlay_width=overlay_width,
1183
- overlay_opacity=overlay_opacity,
1184
- title=title,
1185
- title_xy=("2%", "90%"),
1186
- add_text=True,
1187
- text_xy=("2%", "2%"),
1188
- text_sequence=None,
1189
- font_type=font_type,
1190
- font_size=font_size,
1191
- font_color=font_color,
1192
- add_progress_bar=add_progress_bar,
1193
- progress_bar_color=progress_bar_color,
1194
- progress_bar_height=5,
1195
- loop=0,
1196
- mp4=mp4,
1197
- fading=fading,
1198
- )
1199
- except:
1200
- empty_text.error(
1201
- "An error occurred while computing the timelapse. You probably requested too much data. Try reducing the ROI or timespan."
1202
- )
1203
-
1204
- empty_text.text(
1205
- "Right click the GIF to save it to your computerπŸ‘‡"
1206
- )
1207
- empty_image.image(out_gif)
1208
-
1209
- out_mp4 = out_gif.replace(".gif", ".mp4")
1210
- if mp4 and os.path.exists(out_mp4):
1211
- with empty_video:
1212
- st.text(
1213
- "Right click the MP4 to save it to your computerπŸ‘‡"
1214
- )
1215
- st.video(out_gif.replace(".gif", ".mp4"))
1216
-
1217
- elif collection in [
1218
- "MODIS Gap filled Land Surface Temperature Daily",
1219
- "MODIS Ocean Color SMI",
1220
- ]:
1221
-
1222
- with st.form("submit_ts_form"):
1223
- with st.expander("Customize timelapse"):
1224
-
1225
- title = st.text_input(
1226
- "Enter a title to show on the timelapse: ",
1227
- "Surface Temperature",
1228
- )
1229
- start_date = st.date_input(
1230
- "Select the start date:", datetime.date(2018, 1, 1)
1231
- )
1232
- end_date = st.date_input(
1233
- "Select the end date:", datetime.date(2020, 12, 31)
1234
- )
1235
- frequency = st.selectbox(
1236
- "Select a temporal frequency:",
1237
- ["year", "quarter", "month", "week", "day"],
1238
- index=2,
1239
- )
1240
- reducer = st.selectbox(
1241
- "Select a reducer for aggregating data:",
1242
- ["median", "mean", "min", "max", "sum", "variance", "stdDev"],
1243
- index=0,
1244
- )
1245
-
1246
- vis_params = st.text_area(
1247
- "Enter visualization parameters",
1248
- "",
1249
- help="Enter a string in the format of a dictionary, such as '{'min': 23, 'max': 32}'",
1250
- )
1251
-
1252
- speed = st.slider("Frames per second:", 1, 30, 5)
1253
- add_progress_bar = st.checkbox("Add a progress bar", True)
1254
- progress_bar_color = st.color_picker(
1255
- "Progress bar color:", "#0000ff"
1256
- )
1257
- font_size = st.slider("Font size:", 10, 50, 30)
1258
- font_color = st.color_picker("Font color:", "#ffffff")
1259
- font_type = st.selectbox(
1260
- "Select the font type for the title:",
1261
- ["arial.ttf", "alibaba.otf"],
1262
- index=0,
1263
- )
1264
- add_colorbar = st.checkbox("Add a colorbar", True)
1265
- colorbar_label = st.text_input(
1266
- "Enter the colorbar label:", "Surface Temperature (Β°C)"
1267
- )
1268
- fading = st.slider(
1269
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1270
- )
1271
- mp4 = st.checkbox("Save timelapse as MP4", True)
1272
-
1273
- empty_text = st.empty()
1274
- empty_image = st.empty()
1275
- empty_video = st.container()
1276
-
1277
- roi = None
1278
- if st.session_state.get("roi") is not None:
1279
- roi = st.session_state.get("roi")
1280
- out_gif = geemap.temp_file_path(".gif")
1281
-
1282
- submitted = st.form_submit_button("Submit")
1283
- if submitted:
1284
-
1285
- if sample_roi == "Uploaded GeoJSON" and data is None:
1286
- empty_text.warning(
1287
- "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."
1288
- )
1289
- else:
1290
-
1291
- empty_text.text("Computing... Please wait...")
1292
- try:
1293
- if (
1294
- collection
1295
- == "MODIS Gap filled Land Surface Temperature Daily"
1296
- ):
1297
- out_gif = geemap.create_timelapse(
1298
- st.session_state.get("ee_asset_id"),
1299
- start_date=start_date.strftime("%Y-%m-%d"),
1300
- end_date=end_date.strftime("%Y-%m-%d"),
1301
- region=roi,
1302
- bands=None,
1303
- frequency=frequency,
1304
- reducer=reducer,
1305
- date_format=None,
1306
- out_gif=out_gif,
1307
- palette=st.session_state.get("palette"),
1308
- vis_params=None,
1309
- dimensions=768,
1310
- frames_per_second=speed,
1311
- crs="EPSG:3857",
1312
- overlay_data=overlay_data,
1313
- overlay_color=overlay_color,
1314
- overlay_width=overlay_width,
1315
- overlay_opacity=overlay_opacity,
1316
- title=title,
1317
- title_xy=("2%", "90%"),
1318
- add_text=True,
1319
- text_xy=("2%", "2%"),
1320
- text_sequence=None,
1321
- font_type=font_type,
1322
- font_size=font_size,
1323
- font_color=font_color,
1324
- add_progress_bar=add_progress_bar,
1325
- progress_bar_color=progress_bar_color,
1326
- progress_bar_height=5,
1327
- add_colorbar=add_colorbar,
1328
- colorbar_label=colorbar_label,
1329
- loop=0,
1330
- mp4=mp4,
1331
- fading=fading,
1332
- )
1333
- elif collection == "MODIS Ocean Color SMI":
1334
- if vis_params.startswith("{") and vis_params.endswith(
1335
- "}"
1336
- ):
1337
- vis_params = eval(vis_params)
1338
- else:
1339
- vis_params = None
1340
- out_gif = geemap.modis_ocean_color_timelapse(
1341
- st.session_state.get("ee_asset_id"),
1342
- start_date=start_date.strftime("%Y-%m-%d"),
1343
- end_date=end_date.strftime("%Y-%m-%d"),
1344
- region=roi,
1345
- bands=st.session_state["band"],
1346
- frequency=frequency,
1347
- reducer=reducer,
1348
- date_format=None,
1349
- out_gif=out_gif,
1350
- palette=st.session_state.get("palette"),
1351
- vis_params=vis_params,
1352
- dimensions=768,
1353
- frames_per_second=speed,
1354
- crs="EPSG:3857",
1355
- overlay_data=overlay_data,
1356
- overlay_color=overlay_color,
1357
- overlay_width=overlay_width,
1358
- overlay_opacity=overlay_opacity,
1359
- title=title,
1360
- title_xy=("2%", "90%"),
1361
- add_text=True,
1362
- text_xy=("2%", "2%"),
1363
- text_sequence=None,
1364
- font_type=font_type,
1365
- font_size=font_size,
1366
- font_color=font_color,
1367
- add_progress_bar=add_progress_bar,
1368
- progress_bar_color=progress_bar_color,
1369
- progress_bar_height=5,
1370
- add_colorbar=add_colorbar,
1371
- colorbar_label=colorbar_label,
1372
- loop=0,
1373
- mp4=mp4,
1374
- fading=fading,
1375
- )
1376
- except:
1377
- empty_text.error(
1378
- "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
1379
- )
1380
-
1381
- if out_gif is not None and os.path.exists(out_gif):
1382
-
1383
- geemap.reduce_gif_size(out_gif)
1384
-
1385
- empty_text.text(
1386
- "Right click the GIF to save it to your computerπŸ‘‡"
1387
- )
1388
- empty_image.image(out_gif)
1389
-
1390
- out_mp4 = out_gif.replace(".gif", ".mp4")
1391
- if mp4 and os.path.exists(out_mp4):
1392
- with empty_video:
1393
- st.text(
1394
- "Right click the MP4 to save it to your computerπŸ‘‡"
1395
- )
1396
- st.video(out_gif.replace(".gif", ".mp4"))
1397
-
1398
- else:
1399
- st.error(
1400
- "Something went wrong. You probably requested too much data. Try reducing the ROI or timespan."
1401
- )
1402
-
1403
- elif collection == "USDA National Agriculture Imagery Program (NAIP)":
1404
-
1405
- with st.form("submit_naip_form"):
1406
- with st.expander("Customize timelapse"):
1407
-
1408
- title = st.text_input(
1409
- "Enter a title to show on the timelapse: ", "NAIP Timelapse"
1410
- )
1411
-
1412
- years = st.slider(
1413
- "Start and end year:",
1414
- 2003,
1415
- today.year,
1416
- (2003, today.year),
1417
- )
1418
-
1419
- bands = st.selectbox(
1420
- "Select a band combination:", ["N/R/G", "R/G/B"], index=0
1421
- )
1422
-
1423
- speed = st.slider("Frames per second:", 1, 30, 3)
1424
- add_progress_bar = st.checkbox("Add a progress bar", True)
1425
- progress_bar_color = st.color_picker(
1426
- "Progress bar color:", "#0000ff"
1427
- )
1428
- font_size = st.slider("Font size:", 10, 50, 30)
1429
- font_color = st.color_picker("Font color:", "#ffffff")
1430
- font_type = st.selectbox(
1431
- "Select the font type for the title:",
1432
- ["arial.ttf", "alibaba.otf"],
1433
- index=0,
1434
- )
1435
- fading = st.slider(
1436
- "Fading duration (seconds) for each frame:", 0.0, 3.0, 0.0
1437
- )
1438
- mp4 = st.checkbox("Save timelapse as MP4", True)
1439
-
1440
- empty_text = st.empty()
1441
- empty_image = st.empty()
1442
- empty_video = st.container()
1443
- empty_fire_image = st.empty()
1444
-
1445
- roi = None
1446
- if st.session_state.get("roi") is not None:
1447
- roi = st.session_state.get("roi")
1448
- out_gif = geemap.temp_file_path(".gif")
1449
-
1450
- submitted = st.form_submit_button("Submit")
1451
- if submitted:
1452
-
1453
- if sample_roi == "Uploaded GeoJSON" and data is None:
1454
- empty_text.warning(
1455
- "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."
1456
- )
1457
- else:
1458
-
1459
- empty_text.text("Computing... Please wait...")
1460
- try:
1461
- geemap.naip_timelapse(
1462
- roi,
1463
- years[0],
1464
- years[1],
1465
- out_gif,
1466
- bands=bands.split("/"),
1467
- palette=st.session_state.get("palette"),
1468
- vis_params=None,
1469
- dimensions=768,
1470
- frames_per_second=speed,
1471
- crs="EPSG:3857",
1472
- overlay_data=overlay_data,
1473
- overlay_color=overlay_color,
1474
- overlay_width=overlay_width,
1475
- overlay_opacity=overlay_opacity,
1476
- title=title,
1477
- title_xy=("2%", "90%"),
1478
- add_text=True,
1479
- text_xy=("2%", "2%"),
1480
- text_sequence=None,
1481
- font_type=font_type,
1482
- font_size=font_size,
1483
- font_color=font_color,
1484
- add_progress_bar=add_progress_bar,
1485
- progress_bar_color=progress_bar_color,
1486
- progress_bar_height=5,
1487
- loop=0,
1488
- mp4=mp4,
1489
- fading=fading,
1490
- )
1491
- except:
1492
- empty_text.error(
1493
- "Something went wrong. You either requested too much data or the ROI is outside the U.S."
1494
- )
1495
-
1496
- if out_gif is not None and os.path.exists(out_gif):
1497
-
1498
- empty_text.text(
1499
- "Right click the GIF to save it to your computerπŸ‘‡"
1500
- )
1501
- empty_image.image(out_gif)
1502
-
1503
- out_mp4 = out_gif.replace(".gif", ".mp4")
1504
- if mp4 and os.path.exists(out_mp4):
1505
- with empty_video:
1506
- st.text(
1507
- "Right click the MP4 to save it to your computerπŸ‘‡"
1508
- )
1509
- st.video(out_gif.replace(".gif", ".mp4"))
1510
-
1511
- else:
1512
- st.error(
1513
- "Something went wrong. You either requested too much data or the ROI is outside the U.S."
1514
- )
1515
-
1516
-
1517
- app()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/2_🏠_U.S._Housing.py DELETED
@@ -1,484 +0,0 @@
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=900,
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 DELETED
@@ -1,32 +0,0 @@
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 DELETED
@@ -1,36 +0,0 @@
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("Heatmap")
23
-
24
- with st.expander("See source code"):
25
- with st.echo():
26
- filepath = "https://raw.githubusercontent.com/giswqs/leafmap/master/examples/data/us_cities.csv"
27
- m = leafmap.Map(center=[40, -100], zoom=4, tiles="stamentoner")
28
- m.add_heatmap(
29
- filepath,
30
- latitude="latitude",
31
- longitude="longitude",
32
- value="pop_max",
33
- name="Heat map",
34
- radius=20,
35
- )
36
- m.to_streamlit(height=700)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/5_πŸ“_Marker_Cluster.py DELETED
@@ -1,42 +0,0 @@
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 DELETED
@@ -1,62 +0,0 @@
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 DELETED
@@ -1,89 +0,0 @@
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 DELETED
@@ -1,108 +0,0 @@
1
- import os
2
- import leafmap.foliumap as leafmap
3
- import leafmap.colormaps as cm
4
- import streamlit as st
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
- return list(cm.palettes.keys())
36
- # palettes = dir(palettable.matplotlib)[:-16]
37
- # return ["matplotlib." + p for p in palettes]
38
-
39
-
40
- st.title("Visualize Raster Datasets")
41
- st.markdown(
42
- """
43
- 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 [Titiler](https://developmentseed.org/titiler/).
44
-
45
-
46
- """
47
- )
48
-
49
- row1_col1, row1_col2 = st.columns([2, 1])
50
-
51
- with row1_col1:
52
- cog_list = load_cog_list()
53
- cog = st.selectbox("Select a sample Cloud Opitmized GeoTIFF (COG)", cog_list)
54
-
55
- with row1_col2:
56
- empty = st.empty()
57
-
58
- url = empty.text_input(
59
- "Enter a HTTP URL to a Cloud Optimized GeoTIFF (COG)",
60
- cog,
61
- )
62
-
63
- if url:
64
- try:
65
- options = leafmap.cog_bands(url)
66
- except Exception as e:
67
- st.error(e)
68
- if len(options) > 3:
69
- default = options[:3]
70
- else:
71
- default = options[0]
72
- bands = st.multiselect("Select bands to display", options, default=options)
73
-
74
- if len(bands) == 1 or len(bands) == 3:
75
- pass
76
- else:
77
- st.error("Please select one or three bands")
78
-
79
- add_params = st.checkbox("Add visualization parameters")
80
- if add_params:
81
- vis_params = st.text_area("Enter visualization parameters", "{}")
82
- else:
83
- vis_params = {}
84
-
85
- if len(vis_params) > 0:
86
- try:
87
- vis_params = eval(vis_params)
88
- except Exception as e:
89
- st.error(
90
- f"Invalid visualization parameters. It should be a dictionary. Error: {e}"
91
- )
92
- vis_params = {}
93
-
94
- submit = st.button("Submit")
95
-
96
- m = leafmap.Map(latlon_control=False)
97
-
98
- if submit:
99
- if url:
100
- try:
101
- m.add_cog_layer(url, bands=bands, **vis_params)
102
- except Exception as e:
103
- with row1_col2:
104
- st.error(e)
105
- st.error("Work in progress. Try it again later.")
106
-
107
- with row1_col1:
108
- m.to_streamlit()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/9_πŸ”²_Vector_Data_Visualization.py DELETED
@@ -1,118 +0,0 @@
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 DELETED
@@ -1,6 +0,0 @@
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
 
 
 
 
 
 
 
pyproject.toml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ [tool.black]
2
+ line-length = 79
3
+
4
+ [tool.isort]
5
+ profile = "black"
6
+ line_length = 79
requirements.txt CHANGED
@@ -1,18 +1,6 @@
1
- --find-links=https://girder.github.io/large_image_wheels GDAL
2
- geopandas~=0.9.0
3
- plotly
4
- streamlit~=1.11.0
5
- IO
6
- leafmap
7
- streamlit-bokeh-events
8
- streamlit-folium
9
- streamlit-keplergl
10
- # git+https://github.com/giswqs/leafmap
11
- # git+https://github.com/giswqs/geemap
12
- earthengine-api~=0.1.318
13
- folium~=0.12.1.post1
14
- geemap~=0.16.4
15
- pandas~=1.4.3
16
- shapely~=1.7.1
17
- requests~=2.28.1
18
- pydeck~=0.7.1
 
1
+ earthengine-api==0.1.331
2
+ folium==0.13.0
3
+ geemap==0.17.2
4
+ streamlit==1.14.1
5
+ streamlit_ext==0.1.4
6
+ streamlit-folium==0.7.0
 
 
 
 
 
 
 
 
 
 
 
 
setup.cfg ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ [flake8]
2
+ extend-ignore =
3
+ SFS301 # Allow f-strings
4
+ T001 # Allow print statements
5
+ ISC001 # Allow for implictly concatenated string literals in one line
6
+ SFS101 # Allow percent operator in string