Spaces:
Sleeping
Sleeping
Commit
·
9ff7f17
0
Parent(s):
Duplicate from romero61/WaterQualityMonitoring_SMB
Browse files- .gitattributes +36 -0
- .github/workflows/sync-hf.yml +20 -0
- .gitignore +161 -0
- Dockerfile +24 -0
- LICENSE +21 -0
- README.md +79 -0
- __init__.py +0 -0
- pages/02_chl-a.py +187 -0
- pages/03_SPM.py +179 -0
- pages/04_Development.py +19 -0
- pages/05_background.py +74 -0
- pages/__init__.py +0 -0
- public/__init__.py +0 -0
- public/aoi.geojson +3 -0
- public/constants.py +49 -0
- public/functions.py +133 -0
- public/study_boundary.gpkg +0 -0
- requirements.txt +7 -0
.gitattributes
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
aoi.geojson filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/sync-hf.yml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Sync to Hugging Face hub
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- uses: actions/checkout@v3
|
14 |
+
with:
|
15 |
+
fetch-depth: 0
|
16 |
+
lfs: true
|
17 |
+
- name: Push to hub
|
18 |
+
env:
|
19 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
20 |
+
run: git push --force https://romero61:[email protected]/spaces/romero61/WaterQualityMonitoring_SMB main
|
.gitignore
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
private/
|
29 |
+
|
30 |
+
# PyInstaller
|
31 |
+
# Usually these files are written by a python script from a template
|
32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
33 |
+
*.manifest
|
34 |
+
*.spec
|
35 |
+
|
36 |
+
# Installer logs
|
37 |
+
pip-log.txt
|
38 |
+
pip-delete-this-directory.txt
|
39 |
+
|
40 |
+
# Unit test / coverage reports
|
41 |
+
htmlcov/
|
42 |
+
.tox/
|
43 |
+
.nox/
|
44 |
+
.coverage
|
45 |
+
.coverage.*
|
46 |
+
.cache
|
47 |
+
nosetests.xml
|
48 |
+
coverage.xml
|
49 |
+
*.cover
|
50 |
+
*.py,cover
|
51 |
+
.hypothesis/
|
52 |
+
.pytest_cache/
|
53 |
+
cover/
|
54 |
+
|
55 |
+
# Translations
|
56 |
+
*.mo
|
57 |
+
*.pot
|
58 |
+
|
59 |
+
# Django stuff:
|
60 |
+
*.log
|
61 |
+
local_settings.py
|
62 |
+
db.sqlite3
|
63 |
+
db.sqlite3-journal
|
64 |
+
|
65 |
+
# Flask stuff:
|
66 |
+
instance/
|
67 |
+
.webassets-cache
|
68 |
+
|
69 |
+
# Scrapy stuff:
|
70 |
+
.scrapy
|
71 |
+
|
72 |
+
# Sphinx documentation
|
73 |
+
docs/_build/
|
74 |
+
|
75 |
+
# PyBuilder
|
76 |
+
.pybuilder/
|
77 |
+
target/
|
78 |
+
|
79 |
+
# Jupyter Notebook
|
80 |
+
.ipynb_checkpoints
|
81 |
+
|
82 |
+
# IPython
|
83 |
+
profile_default/
|
84 |
+
ipython_config.py
|
85 |
+
|
86 |
+
# pyenv
|
87 |
+
# For a library or package, you might want to ignore these files since the code is
|
88 |
+
# intended to run in multiple environments; otherwise, check them in:
|
89 |
+
# .python-version
|
90 |
+
|
91 |
+
# pipenv
|
92 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
93 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
94 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
95 |
+
# install all needed dependencies.
|
96 |
+
#Pipfile.lock
|
97 |
+
|
98 |
+
# poetry
|
99 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
100 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
101 |
+
# commonly ignored for libraries.
|
102 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
103 |
+
#poetry.lock
|
104 |
+
|
105 |
+
# pdm
|
106 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
107 |
+
#pdm.lock
|
108 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
109 |
+
# in version control.
|
110 |
+
# https://pdm.fming.dev/#use-with-ide
|
111 |
+
.pdm.toml
|
112 |
+
|
113 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
114 |
+
__pypackages__/
|
115 |
+
|
116 |
+
# Celery stuff
|
117 |
+
celerybeat-schedule
|
118 |
+
celerybeat.pid
|
119 |
+
|
120 |
+
# SageMath parsed files
|
121 |
+
*.sage.py
|
122 |
+
|
123 |
+
# Environments
|
124 |
+
.env
|
125 |
+
.venv
|
126 |
+
env/
|
127 |
+
venv/
|
128 |
+
ENV/
|
129 |
+
env.bak/
|
130 |
+
venv.bak/
|
131 |
+
|
132 |
+
# Spyder project settings
|
133 |
+
.spyderproject
|
134 |
+
.spyproject
|
135 |
+
|
136 |
+
# Rope project settings
|
137 |
+
.ropeproject
|
138 |
+
|
139 |
+
# mkdocs documentation
|
140 |
+
/site
|
141 |
+
|
142 |
+
# mypy
|
143 |
+
.mypy_cache/
|
144 |
+
.dmypy.json
|
145 |
+
dmypy.json
|
146 |
+
|
147 |
+
# Pyre type checker
|
148 |
+
.pyre/
|
149 |
+
|
150 |
+
# pytype static type analyzer
|
151 |
+
.pytype/
|
152 |
+
|
153 |
+
# Cython debug symbols
|
154 |
+
cython_debug/
|
155 |
+
|
156 |
+
# PyCharm
|
157 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
158 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
159 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
160 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
161 |
+
#.idea/
|
Dockerfile
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM jupyter/base-notebook:latest
|
2 |
+
|
3 |
+
RUN mamba install -c conda-forge leafmap geopandas localtileserver -y && \
|
4 |
+
fix-permissions "${CONDA_DIR}" && \
|
5 |
+
fix-permissions "/home/${NB_USER}"
|
6 |
+
|
7 |
+
COPY requirements.txt .
|
8 |
+
RUN pip install -r requirements.txt
|
9 |
+
|
10 |
+
RUN mkdir ./pages
|
11 |
+
COPY /pages ./pages
|
12 |
+
|
13 |
+
RUN mkdir ./public
|
14 |
+
COPY /public ./public
|
15 |
+
|
16 |
+
ENV PROJ_LIB='/opt/conda/share/proj'
|
17 |
+
|
18 |
+
USER root
|
19 |
+
RUN chown -R ${NB_UID} ${HOME}
|
20 |
+
USER ${NB_USER}
|
21 |
+
|
22 |
+
EXPOSE 8765
|
23 |
+
|
24 |
+
CMD ["solara", "run", "./pages", "--host=0.0.0.0"]
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 Open Geospatial Solutions
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: WaterQualityMonitoring SMB
|
3 |
+
emoji: 🌍💦💧🏃
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: green
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
license: mit
|
9 |
+
app_port: 8765
|
10 |
+
duplicated_from: romero61/WaterQualityMonitoring_SMB
|
11 |
+
---
|
12 |
+
|
13 |
+
## Earth Engine Web Apps
|
14 |
+
|
15 |
+
### Introduction
|
16 |
+
|
17 |
+
**A collection of Earth Engine web apps developed using [Solara](https://github.com/widgetti/solara) and geemap**
|
18 |
+
|
19 |
+
# WaterQualityMonitoring_SMB
|
20 |
+
|
21 |
+
Monitoring water quality in the Santa Monica Bay using Landsat 8 OLI satellite data
|
22 |
+
|
23 |
+
# Reproducing Chlorophyll-a Analysis in Santa Monica Bay Using Landsat 8
|
24 |
+
|
25 |
+
This notebook aims to reproduce and extend the analysis conducted in the ocean remote sensing project titled "Remote Sensing of Chlorophyll-a using Landsat 8". The original project, available [here](https://romero61.github.io/posts/SMB/), focused on the analysis of Chlorophyll-a concentrations in the Santa Monica Bay.
|
26 |
+
|
27 |
+
We utilize Landsat 8 satellite imagery to estimate Chlorophyll-a concentrations & Suspended Particle Matter and analyze changes over time, particularly focusing on the impact of the Hyperion Treatment Plant failure.
|
28 |
+
|
29 |
+
The notebooks include the following steps:
|
30 |
+
|
31 |
+
1. Importing necessary Python libraries for data manipulation, mathematical operations, data visualization, handling date and time data, interacting with Google Earth Engine, and handling geospatial data.
|
32 |
+
|
33 |
+
2. Initializing the Earth Engine API and creating an interactive map using the geemap library.
|
34 |
+
|
35 |
+
3. Defining the collection of satellite images to be used (Landsat 8 OLI images) and the study area.
|
36 |
+
|
37 |
+
4. Processing the satellite images, including applying scale factors and estimating Chlorophyll-a & Suspended Particle Matter concentrations.
|
38 |
+
|
39 |
+
5. Visualizing the processed images on an interactive map.
|
40 |
+
|
41 |
+
The next steps of this project will include retrieving the most recent image from the collection, calculating basic statistics for Chlorophyll-a concentrations & Suspended Particle Matter, allowing user-defined regions for analysis, and creating an interactive data exploration.
|
42 |
+
|
43 |
+
# Santa Monica Bay
|
44 |
+
|
45 |
+
Santa Monica Bay Watershed Management Area (WMA): The Santa Monica Bay WMA encompasses an area of 414 square miles and is quite diverse. Its borders reach from the crest of the Santa Monica Mountains on the north and from the Ventura-Los Angeles County line to downtown Los Angeles. The WMA includes several watersheds, the two largest being Malibu Creek to the north (west) and Ballona Creek to the south. The Malibu Creek area contains mostly undeveloped mountain areas, large acreage residential properties, and many natural stream reaches. At the same time, Ballona Creek is predominantly channelized and highly developed with both residential and commercial properties. The Santa Monica Bay was included in the National Estuary Program in 1989 and has been extensively studied by the Santa Monica Bay Restoration Project. The Santa Monica Bay Watershed Commission was established in 2002 to oversee the implementation of the Plan.
|
46 |
+
|
47 |
+
Water Quality Problems and Issues: The Santa Monica Bay WMA embraces a high diversity of geological and hydrological characteristics, habitat features, and human activities. Existing and potential beneficial use impairment problems in the watershed fall into two major categories: human health risk and natural habitat degradation. The former are issues primarily associated with recreational uses of the Santa Monica Bay. The latter are issues associated with terrestrial, aquatic, and marine environments. Pollutant loadings that originate from human activities are common causes of both human health risks and habitat degradation.
|
48 |
+
|
49 |
+
# Suspended Particulate Matter
|
50 |
+
|
51 |
+
The Santa Monica Bay, California, and the Gironde Estuary and Bourgneuf Bay in France have some similarities but also significant differences. Here are some key points to consider:
|
52 |
+
|
53 |
+
Size and Geography: The Santa Monica Bay is a bight of the Pacific Ocean, while the Gironde Estuary and Bourgneuf Bay are estuaries, which are partially enclosed coastal bodies of water where freshwater from rivers and streams meets and mixes with saltwater from the ocean. The Gironde Estuary is formed by the confluence of the Garonne and Dordogne Rivers, and the Loire River feeds into Bourgneuf Bay. The Santa Monica Bay, on the other hand, is fed by several smaller watersheds, including Malibu Creek and Ballona Creek.
|
54 |
+
|
55 |
+
Turbidity and SPM Concentration: The Gironde Estuary and Bourgneuf Bay are characterized by high Suspended Particulate Matter (SPM) concentrations, ranging from 1 to 3000 g·m−3 in the Gironde and 50 to over 1000 g·m−3 in Bourgneuf Bay. The Santa Monica Bay, according to the information found, has a lower SPM concentration, with a median value of 1.3 mg/L (or approximately 0.0013 g·m−3), which is significantly lower than the French sites.
|
56 |
+
|
57 |
+
Tidal Range: Both the Gironde Estuary and Bourgneuf Bay have a macro-tidal regime, with tidal ranges from 2 to 5 m and 2 to 6 m, respectively. The tidal range in the Santa Monica Bay is not explicitly stated in the sources found, but the Pacific coast of Southern California typically experiences a smaller tidal range, usually less than 2 meters.
|
58 |
+
|
59 |
+
Freshwater Inputs: The Gironde Estuary and Bourgneuf Bay have significant freshwater inputs from large rivers, with flow rates ranging from less than 100 m3·s−1 to more than 4000 m3·s−1. The Santa Monica Bay receives freshwater inputs from several smaller watersheds, but the flow rates are likely much lower than those of the French sites.
|
60 |
+
|
61 |
+
The Santa Monica Bay, California, and the Gironde Estuary and Bourgneuf Bay in France have some similarities but also significant differences. Here are some key points to consider:
|
62 |
+
|
63 |
+
Size and Geography: The Santa Monica Bay is a bight of the Pacific Ocean, while the Gironde Estuary and Bourgneuf Bay are estuaries, which are partially enclosed coastal bodies of water where freshwater from rivers and streams meets and mixes with saltwater from the ocean. The Gironde Estuary is formed by the confluence of the Garonne and Dordogne rivers, and the Loire River feeds into Bourgneuf Bay. The Santa Monica Bay, on the other hand, is fed by several smaller watersheds, including Malibu Creek and Ballona Creek.
|
64 |
+
|
65 |
+
Turbidity and SPM Concentration: The Gironde Estuary and Bourgneuf Bay are characterized by high Suspended Particulate Matter (SPM) concentrations, ranging from 1 to 3000 g·m−3 in the Gironde and 50 to over 1000 g·m−3 in Bourgneuf Bay. The Santa Monica Bay, according to the information found, has a lower SPM concentration, with a median value of 1.3 mg/L (or approximately 0.0013 g·m−3), which is significantly lower than the French sites.
|
66 |
+
|
67 |
+
Tidal Range: Both the Gironde Estuary and Bourgneuf Bay have a macro-tidal regime, with tidal ranges from 2 to 5 m and 2 to 6 m, respectively. The tidal range in the Santa Monica Bay is not explicitly stated in the sources found, but the Pacific coast of Southern California typically experiences a smaller tidal range, usually less than 2 meters.
|
68 |
+
|
69 |
+
In conclusion, while there are some similarities in terms of being coastal water bodies with freshwater inputs, the Gironde Estuary and Bourgneuf Bay in France appear to be significantly more turbid and have higher SPM concentrations than the Santa Monica Bay.
|
70 |
+
|
71 |
+
Freshwater Inputs: The Gironde Estuary and Bourgneuf Bay have significant freshwater inputs from large rivers, with flow rates ranging from less than 100 m3·s−1 to more than 4000 m3·s−1. The Santa Monica Bay receives freshwater inputs from several smaller watersheds, but the flow rates are likely much lower than those of the French sites.
|
72 |
+
|
73 |
+
In conclusion, while there are some similarities in terms of being coastal water bodies with freshwater inputs, the Gironde Estuary and Bourgneuf Bay in France appear to be significantly more turbid and have higher SPM concentrations than the Santa Monica Bay. Therefore, the models developed for the French sites may not be directly applicable to the Santa Monica Bay without some adjustments or recalibrations.
|
74 |
+
|
75 |
+
# References
|
76 |
+
|
77 |
+
Trinh, R. C., Fichot, C. G., Gierach, M. M., Holt, B., Malakar, N. K., Hulley, G., & Smith, J. (2017). Application of Landsat 8 for Monitoring Impacts of Wastewater Discharge on Coastal Water Quality. Frontiers in Marine Science, 4. <https://doi.org/10.3389/fmars.2017.00329>
|
78 |
+
|
79 |
+
Novoa S, Doxaran D, Ody A, Vanhellemont Q, Lafon V, Lubac B, Gernez P. Atmospheric Corrections and Multi-Conditional Algorithm for Multi-Sensor Remote Sensing of Suspended Particulate Matter in Low-to-High Turbidity Levels Coastal Waters. Remote Sensing. 2017; 9(1):61. <https://doi.org/10.3390/rs9010061>
|
__init__.py
ADDED
File without changes
|
pages/02_chl-a.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ee
|
2 |
+
import geemap
|
3 |
+
import geopandas as gpd
|
4 |
+
import solara
|
5 |
+
|
6 |
+
import os
|
7 |
+
import sys
|
8 |
+
|
9 |
+
print('entering project_root')
|
10 |
+
# Get the project root directory
|
11 |
+
project_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
12 |
+
print(f'exiting project root', {project_root})
|
13 |
+
# Define the path to the shapefile
|
14 |
+
shapefile_path = os.path.join(project_root, 'public', 'study_boundary.gpkg')
|
15 |
+
print(f'shapefile path:', {shapefile_path})
|
16 |
+
|
17 |
+
class Map(geemap.Map):
|
18 |
+
def __init__(self, **kwargs):
|
19 |
+
super().__init__(**kwargs)
|
20 |
+
print('Adding base map')
|
21 |
+
self.add_basemap(basemap='TERRAIN')
|
22 |
+
print('Adding base map')
|
23 |
+
self.load_and_process_images()
|
24 |
+
print('Adding base map')
|
25 |
+
self.add_layer_manager()
|
26 |
+
|
27 |
+
def apply_scale_factors(self, image):
|
28 |
+
optical_bands = image.select('SR_B.*').multiply(0.0000275).add(-0.2)
|
29 |
+
thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)
|
30 |
+
image = image.addBands(optical_bands, None, True)
|
31 |
+
image = image.addBands(thermal_bands, None, True)
|
32 |
+
return image
|
33 |
+
|
34 |
+
def extract_qa_bits(self, qa_band, start_bit, end_bit, band_name):
|
35 |
+
"""
|
36 |
+
Extracts QA values from an image
|
37 |
+
:param qa_band: Single-band image of the QA layer
|
38 |
+
:type qa_band: ee.Image
|
39 |
+
:param start_bit: Starting bit
|
40 |
+
:type start_bit: Integer
|
41 |
+
:param end_bit: Ending bit
|
42 |
+
:type end_bit: Integer
|
43 |
+
:param band_name: New name for the band
|
44 |
+
:type band_name: String
|
45 |
+
:return: Image with extracted QA values
|
46 |
+
:rtype: ee.Image
|
47 |
+
"""
|
48 |
+
# Initialize QA bit string/pattern to check QA band against
|
49 |
+
qa_bits = 0
|
50 |
+
# Add each specified QA bit flag value/string/pattern to the QA bits to check/extract
|
51 |
+
for bit in range(start_bit, end_bit + 1):
|
52 |
+
qa_bits += pow(2, bit) # Same as qa_bits += (1 << bit)
|
53 |
+
# Return a single band image of the extracted QA bit values
|
54 |
+
return qa_band.select([0], [band_name]).bitwiseAnd(qa_bits).rightShift(start_bit)
|
55 |
+
|
56 |
+
def trinh_et_al_chl_a(self, image):
|
57 |
+
# extract the cloud and water masks
|
58 |
+
qa_band = ee.Image(image).select('QA_PIXEL')
|
59 |
+
cloudMask = self.extract_qa_bits(qa_band, 8, 9, "cloud").neq(3) # different than 3 to remove clouds
|
60 |
+
waterMask = self.extract_qa_bits(qa_band, 7, 7, "water").eq(1) # equals 1 to keep water
|
61 |
+
|
62 |
+
# apply the masks to the image
|
63 |
+
image = image.updateMask(cloudMask).updateMask(waterMask)
|
64 |
+
image = self.apply_scale_factors(image)
|
65 |
+
|
66 |
+
a_0 = 0.9375
|
67 |
+
a_1 = -1.8862
|
68 |
+
blue_bands = image.select('SR_B2')
|
69 |
+
green_bands = image.select('SR_B3')
|
70 |
+
ln_chl_a = image.expression("a_0 + a_1 * log(blue_bands/green_bands)", {
|
71 |
+
'a_0': a_0,
|
72 |
+
'a_1': a_1,
|
73 |
+
'blue_bands': blue_bands,
|
74 |
+
'green_bands': green_bands
|
75 |
+
})
|
76 |
+
image = image.addBands(ln_chl_a.select([0], ['ln_chl_a']))
|
77 |
+
|
78 |
+
return image
|
79 |
+
|
80 |
+
def load_and_process_images(self):
|
81 |
+
# Load the study area
|
82 |
+
print('Loading study boundary')
|
83 |
+
study_boundary = gpd.read_file(shapefile_path)
|
84 |
+
ee_boundary = geemap.geopandas_to_ee(study_boundary)
|
85 |
+
aoi = ee_boundary.geometry()
|
86 |
+
TURBO_PALETTE = [
|
87 |
+
"30123b", "321543", "33184a", "341b51", "351e58", "36215f", "372466", "38276d",
|
88 |
+
"392a73", "3a2d79", "3b2f80", "3c3286", "3d358b", "3e3891", "3f3b97", "3f3e9c",
|
89 |
+
"4040a2", "4143a7", "4146ac", "4249b1", "424bb5", "434eba", "4451bf", "4454c3",
|
90 |
+
"4456c7", "4559cb", "455ccf", "455ed3", "4661d6", "4664da", "4666dd", "4669e0",
|
91 |
+
"466be3", "476ee6", "4771e9", "4773eb", "4776ee", "4778f0", "477bf2", "467df4",
|
92 |
+
"4680f6", "4682f8", "4685fa", "4687fb", "458afc", "458cfd", "448ffe", "4391fe",
|
93 |
+
"4294ff", "4196ff", "4099ff", "3e9bfe", "3d9efe", "3ba0fd", "3aa3fc", "38a5fb",
|
94 |
+
"37a8fa", "35abf8", "33adf7", "31aff5", "2fb2f4", "2eb4f2", "2cb7f0", "2ab9ee",
|
95 |
+
"28bceb", "27bee9", "25c0e7", "23c3e4", "22c5e2", "20c7df", "1fc9dd", "1ecbda",
|
96 |
+
"1ccdd8", "1bd0d5", "1ad2d2", "1ad4d0", "19d5cd", "18d7ca", "18d9c8", "18dbc5",
|
97 |
+
"18ddc2", "18dec0", "18e0bd", "19e2bb", "19e3b9", "1ae4b6", "1ce6b4", "1de7b2",
|
98 |
+
"1fe9af", "20eaac", "22ebaa", "25eca7", "27eea4", "2aefa1", "2cf09e", "2ff19b",
|
99 |
+
"32f298", "35f394", "38f491", "3cf58e", "3ff68a", "43f787", "46f884", "4af880",
|
100 |
+
"4ef97d", "52fa7a", "55fa76", "59fb73", "5dfc6f", "61fc6c", "65fd69", "69fd66",
|
101 |
+
"6dfe62", "71fe5f", "75fe5c", "79fe59", "7dff56", "80ff53", "84ff51", "88ff4e",
|
102 |
+
"8bff4b", "8fff49", "92ff47", "96fe44", "99fe42", "9cfe40", "9ffd3f", "a1fd3d", "a4fc3c", "a7fc3a", "a9fb39", "acfb38",
|
103 |
+
"affa37", "b1f936", "b4f836", "b7f735", "b9f635", "bcf534", "bef434", "c1f334",
|
104 |
+
"c3f134", "c6f034", "c8ef34", "cbed34", "cdec34", "d0ea34", "d2e935", "d4e735",
|
105 |
+
"d7e535", "d9e436", "dbe236", "dde037", "dfdf37", "e1dd37", "e3db38", "e5d938",
|
106 |
+
"e7d739", "e9d539", "ebd339", "ecd13a", "eecf3a", "efcd3a", "f1cb3a", "f2c93a",
|
107 |
+
"f4c73a", "f5c53a", "f6c33a", "f7c13a", "f8be39", "f9bc39", "faba39", "fbb838",
|
108 |
+
"fbb637", "fcb336", "fcb136", "fdae35", "fdac34", "fea933", "fea732", "fea431",
|
109 |
+
"fea130", "fe9e2f", "fe9b2d", "fe992c", "fe962b", "fe932a", "fe9029", "fd8d27",
|
110 |
+
"fd8a26", "fc8725", "fc8423", "fb8122", "fb7e21", "fa7b1f", "f9781e", "f9751d",
|
111 |
+
"f8721c", "f76f1a", "f66c19", "f56918", "f46617", "f36315", "f26014", "f15d13",
|
112 |
+
"f05b12", "ef5811", "ed5510", "ec530f", "eb500e", "ea4e0d", "e84b0c", "e7490c",
|
113 |
+
"e5470b", "e4450a", "e2430a", "e14109", "df3f08", "dd3d08", "dc3b07", "da3907",
|
114 |
+
"d83706", "d63506", "d43305", "d23105", "d02f05", "ce2d04", "cc2b04", "ca2a04",
|
115 |
+
"c82803", "c52603", "c32503", "c12302", "be2102", "bc2002", "b91e02", "b71d02",
|
116 |
+
"b41b01", "b21a01", "af1801", "ac1701", "a91601", "a71401", "a41301", "a11201",
|
117 |
+
"9e1001", "9b0f01", "980e01", "950d01", "920b01", "8e0a01", "8b0902", "880802",
|
118 |
+
"850702", "810602", "7e0502", "7a0403" ]
|
119 |
+
|
120 |
+
chloro_params= {
|
121 |
+
'bands': ['ln_chl_a'],
|
122 |
+
'min': 0,
|
123 |
+
'max': 3,
|
124 |
+
'palette': TURBO_PALETTE
|
125 |
+
}
|
126 |
+
dates = ['2021-11-11', '2021-10-26','2021-10-10', '2021-08-07','2021-07-22', '2021-07-06' ]
|
127 |
+
|
128 |
+
processed_collection = ee.ImageCollection([])
|
129 |
+
|
130 |
+
# Loop through the dates and get the imagery
|
131 |
+
for date in dates:
|
132 |
+
print(f'Processing image for date {date}')
|
133 |
+
start_date = ee.Date(date)
|
134 |
+
end_date = start_date.advance(1, 'day')
|
135 |
+
|
136 |
+
# Filter the image collection by date and area
|
137 |
+
image = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2") \
|
138 |
+
.filterDate(start_date, end_date) \
|
139 |
+
.filterBounds(ee_boundary) \
|
140 |
+
.first() # get the first image that matches the filters
|
141 |
+
|
142 |
+
if image: # check if image exists
|
143 |
+
print(f'Image found for date {date}')
|
144 |
+
clipped_image = image.clip(aoi) # Clip the image to the study boundary
|
145 |
+
processed_image = self.trinh_et_al_chl_a(clipped_image) # process the image
|
146 |
+
self.addLayer(processed_image, chloro_params, date, shown = True) # add the image to the map
|
147 |
+
processed_collection = processed_collection.merge(processed_image) # add the image to the processed collection
|
148 |
+
else:
|
149 |
+
print(f"No image found for date {date}")
|
150 |
+
|
151 |
+
# Set the map to focus on the study area
|
152 |
+
print('Setting focus on study area')
|
153 |
+
self.add_colorbar_branca(vis_params= chloro_params, colors = TURBO_PALETTE,vmin = 0, vmax = 3, label = 'mg/m³')
|
154 |
+
|
155 |
+
|
156 |
+
|
157 |
+
|
158 |
+
@solara.component
|
159 |
+
def Page():
|
160 |
+
|
161 |
+
with solara.Column(style={"min-width": "500px"}):
|
162 |
+
with solara.Card(title="Water Quality Monitoring in Santa Monica Bay", subtitle="Using Landsat 8 OLI Satellite Data"):
|
163 |
+
solara.Markdown(
|
164 |
+
'''
|
165 |
+
## Overview
|
166 |
+
|
167 |
+
This Earth Engine application focuses on monitoring water quality in the Santa Monica Bay using Landsat 8 OLI satellite data. The primary objective is to analyze Chlorophyll-a concentrations, which serve as a key indicator of water quality.
|
168 |
+
|
169 |
+
## Background
|
170 |
+
|
171 |
+
This project is an extension of the original 2015 research by Trinh et al., which focused on the capabilities of Landsat 8 OLI and Aqua MODIS to monitor water quality in the Santa Monica Bay (SMB). The research was centered on the impact of the Hyperion Treatment Plant (HTP), which discharges treated wastewater into the bay, increasing contaminant and nitrogen concentrations and the likelihood of phytoplankton blooms. The original project is available [here](https://romero61.github.io/posts/SMB/).
|
172 |
+
|
173 |
+
## Analysis
|
174 |
+
|
175 |
+
The extension applies the findings of Trinh et al. to the July 11th, 2021, HTP failure, where 17 million gallons of untreated sewage were released 1-mile offshore. Reports indicate that the plant was operating at a diminished capacity several months after the initial failure. The local OLI algorithm, developed in the original research, was used to measure the chlorophyll-a concentration in response to the 2021 HTP failure.
|
176 |
+
|
177 |
+
## Significance
|
178 |
+
|
179 |
+
Monitoring water quality is crucial for the preservation of marine ecosystems. Changes in Chlorophyll-a concentrations can indicate the presence of harmful algal blooms, which can have detrimental effects on marine life. This application provides a valuable tool for ongoing monitoring and research efforts. The results indicated possible effects of the untreated wastewater discharge on the SMB's water quality, although further studies are needed for a more in-depth analysis.
|
180 |
+
'''
|
181 |
+
)
|
182 |
+
Map.element(
|
183 |
+
center=[33.901, -118.477],
|
184 |
+
zoom=12,
|
185 |
+
height="800px"
|
186 |
+
)
|
187 |
+
|
pages/03_SPM.py
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ee
|
2 |
+
import geemap
|
3 |
+
import geopandas as gpd
|
4 |
+
import solara
|
5 |
+
|
6 |
+
import os
|
7 |
+
import sys
|
8 |
+
|
9 |
+
|
10 |
+
# Get the project root directory
|
11 |
+
project_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
12 |
+
|
13 |
+
# Define the path to the shapefile
|
14 |
+
shapefile_path = os.path.join(project_root, 'public', 'study_boundary.gpkg')
|
15 |
+
|
16 |
+
|
17 |
+
class Map(geemap.Map):
|
18 |
+
def __init__(self, **kwargs):
|
19 |
+
super().__init__(**kwargs)
|
20 |
+
|
21 |
+
self.add_basemap(basemap='TERRAIN')
|
22 |
+
|
23 |
+
self.load_and_process_images()
|
24 |
+
|
25 |
+
self.add_layer_manager()
|
26 |
+
|
27 |
+
def apply_scale_factors(self, image):
|
28 |
+
optical_bands = image.select('SR_B.*').multiply(0.0000275).add(-0.2)
|
29 |
+
thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)
|
30 |
+
image = image.addBands(optical_bands, None, True)
|
31 |
+
image = image.addBands(thermal_bands, None, True)
|
32 |
+
return image
|
33 |
+
|
34 |
+
def extract_qa_bits(self, qa_band, start_bit, end_bit, band_name):
|
35 |
+
"""
|
36 |
+
Extracts QA values from an image
|
37 |
+
:param qa_band: Single-band image of the QA layer
|
38 |
+
:type qa_band: ee.Image
|
39 |
+
:param start_bit: Starting bit
|
40 |
+
:type start_bit: Integer
|
41 |
+
:param end_bit: Ending bit
|
42 |
+
:type end_bit: Integer
|
43 |
+
:param band_name: New name for the band
|
44 |
+
:type band_name: String
|
45 |
+
:return: Image with extracted QA values
|
46 |
+
:rtype: ee.Image
|
47 |
+
"""
|
48 |
+
# Initialize QA bit string/pattern to check QA band against
|
49 |
+
qa_bits = 0
|
50 |
+
# Add each specified QA bit flag value/string/pattern to the QA bits to check/extract
|
51 |
+
for bit in range(start_bit, end_bit + 1):
|
52 |
+
qa_bits += pow(2, bit) # Same as qa_bits += (1 << bit)
|
53 |
+
# Return a single band image of the extracted QA bit values
|
54 |
+
return qa_band.select([0], [band_name]).bitwiseAnd(qa_bits).rightShift(start_bit)
|
55 |
+
|
56 |
+
|
57 |
+
|
58 |
+
# Define a function to calculate SPM from Novoa et al.(2017) based on Nechad et al. (2010) NIR (recalibrated) model
|
59 |
+
|
60 |
+
def novoa_et_al_spm(self, image):
|
61 |
+
# extract the cloud and water masks
|
62 |
+
qa_band = ee.Image(image).select('QA_PIXEL')
|
63 |
+
cloudMask = self.extract_qa_bits(qa_band, 8, 9, "cloud").neq(3) # different than 3 to remove clouds
|
64 |
+
waterMask = self.extract_qa_bits(qa_band, 7, 7, "water").eq(1) # equals 1 to keep water
|
65 |
+
|
66 |
+
# apply the masks to the image
|
67 |
+
image = image.updateMask(cloudMask).updateMask(waterMask)
|
68 |
+
image = self.apply_scale_factors(image)
|
69 |
+
|
70 |
+
# Select the NIR band (B5 for Landsat 8 OLI)
|
71 |
+
nir = image.select('SR_B5')
|
72 |
+
# Apply the Nechad et al. (2010) NIR (recalibrated) model
|
73 |
+
spm = image.expression(
|
74 |
+
"4302 * (nir / (1 - nir / 0.2115))",
|
75 |
+
{'nir': nir}
|
76 |
+
)
|
77 |
+
# Set negative SPM values to zero
|
78 |
+
spm = spm.where(spm.lt(0), 0)
|
79 |
+
image = image.addBands(spm.select([0], ['spm']))
|
80 |
+
|
81 |
+
|
82 |
+
return image
|
83 |
+
|
84 |
+
def load_and_process_images(self):
|
85 |
+
# Load the study area
|
86 |
+
|
87 |
+
study_boundary = gpd.read_file(shapefile_path)
|
88 |
+
ee_boundary = geemap.geopandas_to_ee(study_boundary)
|
89 |
+
aoi = ee_boundary.geometry()
|
90 |
+
VIRIDIS_PALETTE = [
|
91 |
+
'440154', '440256', '450457', '450559', '46075a', '46085c', '460a5d', '460b5e', '470d60',
|
92 |
+
'470e61', '471063', '471164', '471365', '481467', '481668', '481769', '48186a', '481a6c',
|
93 |
+
'481b6d', '481c6e', '481d6f', '481f70', '482071', '482173', '482374', '482475', '482576',
|
94 |
+
'482677', '482878', '482979', '472a7a', '472c7a', '472d7b', '472e7c', '472f7d', '46307e',
|
95 |
+
'46327e', '46337f', '463480', '453581', '453781', '453882', '443983', '443a83', '443b84',
|
96 |
+
'433d84', '433e85', '423f85', '424086', '424186', '414287', '414487', '404588', '404688',
|
97 |
+
'3f4788', '3f4889', '3e4989', '3e4a89', '3e4c8a', '3d4d8a', '3d4e8a', '3c4f8a', '3c508b',
|
98 |
+
'3b518b', '3b528b', '3a538b', '3a548c', '39558c', '39568c', '38588c', '38598c', '375a8c',
|
99 |
+
'375b8d', '365c8d', '365d8d', '355e8d', '355f8d', '34608d', '34618d', '33628d', '33638d',
|
100 |
+
'32648e', '32658e', '31668e', '31678e', '30688e', '30698e', '2f6a8e', '2f6b8e', '2e6c8e',
|
101 |
+
'2e6d8e', '2d6e8e', '2d6f8e', '2c708e', '2c718e', '2b728e', '2b738e', '2a748e', '2a758e',
|
102 |
+
'29768e', '29778e', '28788e', '28798e', '277a8e', '277b8e', '267c8e', '267d8e', '257e8e',
|
103 |
+
'257f8e', '24808e', '24818e', '23828e', '23828e', '22838e', '22848e', '21858e', '21868e',
|
104 |
+
'20878e', '20888e', '1f898e', '1f8a8d', '1e8b8d', '1e8c8d', '1d8d8d', '1d8e8d', '1c8f8d',
|
105 |
+
'1c8f8d', '1b908c', '1b918c', '1a928c', '1a938b', '19948b', '19958b', '18968a', '18978a',
|
106 |
+
'17988a', '179989', '169a89', '169b88', '159c88', '159d87', '149e87', '149f86', '13a086',
|
107 |
+
'13a185', '12a285', '12a384', '11a483', '11a583', '10a682', '10a781', '0fa881', '0fa980',
|
108 |
+
'0eaa7f', '0eab7e', '0dac7e', '0dad7d', '0cae7c', '0caf7b', '0bb07a', '0bb179', '0ab278',
|
109 |
+
'0ab377', '09b476', '09b575', '08b674', '08b773', '07b872', '07b971', '06ba70', '06bb6f',
|
110 |
+
'05bc6e', '05bd6d', '04be6c', '04bf6b', '03c06a', '03c169', '02c268', '02c367', '01c466',
|
111 |
+
'01c565', '00c664']
|
112 |
+
|
113 |
+
spm_params= {
|
114 |
+
'bands': ['spm'],
|
115 |
+
'min': 0,
|
116 |
+
'max': 50,
|
117 |
+
'palette': VIRIDIS_PALETTE
|
118 |
+
}
|
119 |
+
dates = ['2021-11-11', '2021-10-26','2021-10-10', '2021-08-07','2021-07-22', '2021-07-06' ]
|
120 |
+
|
121 |
+
processed_collection = ee.ImageCollection([])
|
122 |
+
|
123 |
+
# Loop through the dates and get the imagery
|
124 |
+
for date in dates:
|
125 |
+
|
126 |
+
start_date = ee.Date(date)
|
127 |
+
end_date = start_date.advance(1, 'day')
|
128 |
+
|
129 |
+
# Filter the image collection by date and area
|
130 |
+
image = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2") \
|
131 |
+
.filterDate(start_date, end_date) \
|
132 |
+
.filterBounds(ee_boundary) \
|
133 |
+
.first() # get the first image that matches the filters
|
134 |
+
|
135 |
+
if image: # check if image exists
|
136 |
+
|
137 |
+
clipped_image = image.clip(aoi) # Clip the image to the study boundary
|
138 |
+
processed_image = self.novoa_et_al_spm(clipped_image) # process the image
|
139 |
+
self.addLayer(processed_image, spm_params, date, shown = True) # add the image to the map
|
140 |
+
processed_collection = processed_collection.merge(processed_image) # add the image to the processed collection
|
141 |
+
else:
|
142 |
+
print(f"No image found for date {date}")
|
143 |
+
|
144 |
+
# Set the map to focus on the study area
|
145 |
+
|
146 |
+
self.add_colorbar_branca(vis_params= spm_params, colors = VIRIDIS_PALETTE,vmin = 0, vmax = 50, label = 'g/m³')
|
147 |
+
|
148 |
+
|
149 |
+
|
150 |
+
@solara.component
|
151 |
+
def Page():
|
152 |
+
with solara.Column(style={"min-width": "500px"}):
|
153 |
+
with solara.Card(title="SPM Analysis in Coastal Waters", subtitle="Using Landsat 8 OLI Satellite Data"):
|
154 |
+
solara.Markdown(
|
155 |
+
'''
|
156 |
+
## Overview
|
157 |
+
|
158 |
+
This application focuses on the accurate measurement of suspended particulate matter (SPM) concentrations in coastal waters using Landsat 8 OLI satellite data. The primary objective is to analyze SPM concentrations, which are crucial for ecosystem studies, sediment transport monitoring, and assessment of anthropogenic impacts in the coastal ocean.
|
159 |
+
|
160 |
+
## Background
|
161 |
+
|
162 |
+
The project is based on the SPM model from the research titled "Atmospheric Corrections and Multi-Conditional Algorithm for Multi-Sensor Remote Sensing of Suspended Particulate Matter in Low-to-High Turbidity Levels Coastal Waters" by Novoa et al. (2017), which is itself based on the Nechad et al. (2010) NIR (recalibrated) model. The research presents an assessment of existing atmospheric correction (AC) algorithms developed for turbid coastal waters and a switching method that automatically selects the most sensitive SPM vs. water reflectance relationship to avoid saturation effects when computing the SPM concentration.
|
163 |
+
|
164 |
+
## Analysis
|
165 |
+
|
166 |
+
The SPM model is applied to Landsat 8 OLI satellite data. The model uses the NIR band (B5 for Landsat 8 OLI) and applies the Nechad et al. (2010) NIR (recalibrated) model to calculate SPM. The model is applied to the same dates and images of the Chlorophyll-a analysis. Negative SPM values are set to zero.
|
167 |
+
|
168 |
+
## Significance
|
169 |
+
|
170 |
+
The accurate measurement of SPM concentrations in coastal waters is crucial for various environmental and ecological studies. This application provides a valuable tool for ongoing monitoring and research efforts. The results can be used to assess the impacts of human activities, study sediment transport dynamics, and monitor the quality of the marine environment.
|
171 |
+
'''
|
172 |
+
)
|
173 |
+
with solara.Column(style={"min-width": "500px"}):
|
174 |
+
Map.element(
|
175 |
+
center=[33.901, -118.477],
|
176 |
+
zoom=12,
|
177 |
+
height="800px"
|
178 |
+
)
|
179 |
+
solara.Title('SPM')
|
pages/04_Development.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import solara
|
2 |
+
|
3 |
+
@solara.component
|
4 |
+
def Page():
|
5 |
+
with solara.Column(style={"min-width": "500px"}):
|
6 |
+
with solara.Card(title="Work In progress", subtitle="User Defined Imagery"):
|
7 |
+
solara.Markdown(
|
8 |
+
'''
|
9 |
+
I'm working on building more interactivity:
|
10 |
+
|
11 |
+
1. Should Load with a split map of chl-a & SPM for most recent date
|
12 |
+
2. User should be able to select date from calendar (considering a range of dates but this
|
13 |
+
will slow down app will require testing).
|
14 |
+
3. I want to extend the draw feature functionality of ipyleaflet so that stats are created
|
15 |
+
for the user defined region.
|
16 |
+
|
17 |
+
Need to update Chl-A and SPM with graphs.
|
18 |
+
'''
|
19 |
+
)
|
pages/05_background.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import solara
|
2 |
+
|
3 |
+
|
4 |
+
@solara.component
|
5 |
+
def Page():
|
6 |
+
with solara.Column(align="center"):
|
7 |
+
markdown = """
|
8 |
+
## Earth Engine Web Apps
|
9 |
+
|
10 |
+
**A collection of Earth Engine web apps developed using [Solara](https://github.com/widgetti/solara) and geemap**
|
11 |
+
|
12 |
+
|
13 |
+
# WaterQualityMonitoring_SMB
|
14 |
+
Monitoring water quality in the Santa Monica Bay using Landsat 8 OLI satellite data
|
15 |
+
|
16 |
+
# Reproducing Chlorophyll-a Analysis in Santa Monica Bay Using Landsat 8
|
17 |
+
This notebook aims to reproduce and extend the analysis conducted in the ocean remote sensing project titled "Remote Sensing of Chlorophyll-a using Landsat 8". The original project, available [here](https://romero61.github.io/posts/SMB/), focused on the analysis of Chlorophyll-a concentrations in the Santa Monica Bay.
|
18 |
+
|
19 |
+
We utilize Landsat 8 satellite imagery to estimate Chlorophyll-a concentrations & Suspended Particle Matter and analyze changes over time, particularly focusing on the impact of the Hyperion Treatment Plant failure.
|
20 |
+
|
21 |
+
The notebooks include the following steps:
|
22 |
+
|
23 |
+
1. Importing necessary Python libraries for data manipulation, mathematical operations, data visualization, handling date and time data, interacting with Google Earth Engine, and handling geospatial data.
|
24 |
+
|
25 |
+
2. Initializing the Earth Engine API and creating an interactive map using the geemap library.
|
26 |
+
|
27 |
+
3. Defining the collection of satellite images to be used (Landsat 8 OLI images) and the study area.
|
28 |
+
|
29 |
+
4. Processing the satellite images, including applying scale factors and estimating Chlorophyll-a & Suspended Particle Matter concentrations.
|
30 |
+
|
31 |
+
5. Visualizing the processed images on an interactive map.
|
32 |
+
|
33 |
+
The next steps of this project will include retrieving the most recent image from the collection, calculating basic statistics for Chlorophyll-a concentrations & Suspended Particle Matter, allowing user-defined regions for analysis, and creating an interactive data exploration.
|
34 |
+
|
35 |
+
# Santa Monica Bay
|
36 |
+
|
37 |
+
Santa Monica Bay Watershed Management Area (WMA): The Santa Monica Bay WMA encompasses an area of 414 square miles and is quite diverse. Its borders reach from the crest of the Santa Monica Mountains on the north and from the Ventura-Los Angeles County line to downtown Los Angeles. The WMA includes several watersheds, the two largest being Malibu Creek to the north (west) and Ballona Creek to the south. The Malibu Creek area contains mostly undeveloped mountain areas, large acreage residential properties, and many natural stream reaches. At the same time, Ballona Creek is predominantly channelized and highly developed with both residential and commercial properties. The Santa Monica Bay was included in the National Estuary Program in 1989 and has been extensively studied by the Santa Monica Bay Restoration Project. The Santa Monica Bay Watershed Commission was established in 2002 to oversee the implementation of the Plan.
|
38 |
+
|
39 |
+
Water Quality Problems and Issues: The Santa Monica Bay WMA embraces a high diversity of geological and hydrological characteristics, habitat features, and human activities. Existing and potential beneficial use impairment problems in the watershed fall into two major categories: human health risk and natural habitat degradation. The former are issues primarily associated with recreational uses of the Santa Monica Bay. The latter are issues associated with terrestrial, aquatic, and marine environments. Pollutant loadings that originate from human activities are common causes of both human health risks and habitat degradation.
|
40 |
+
|
41 |
+
# Suspended Particulate Matter
|
42 |
+
|
43 |
+
The Santa Monica Bay, California, and the Gironde Estuary and Bourgneuf Bay in France have some similarities but also significant differences. Here are some key points to consider:
|
44 |
+
|
45 |
+
Size and Geography: The Santa Monica Bay is a bight of the Pacific Ocean, while the Gironde Estuary and Bourgneuf Bay are estuaries, which are partially enclosed coastal bodies of water where freshwater from rivers and streams meets and mixes with saltwater from the ocean. The Gironde Estuary is formed by the confluence of the Garonne and Dordogne Rivers, and the Loire River feeds into Bourgneuf Bay. The Santa Monica Bay, on the other hand, is fed by several smaller watersheds, including Malibu Creek and Ballona Creek.
|
46 |
+
|
47 |
+
Turbidity and SPM Concentration: The Gironde Estuary and Bourgneuf Bay are characterized by high Suspended Particulate Matter (SPM) concentrations, ranging from 1 to 3000 g·m−3 in the Gironde and 50 to over 1000 g·m−3 in Bourgneuf Bay. The Santa Monica Bay, according to the information found, has a lower SPM concentration, with a median value of 1.3 mg/L (or approximately 0.0013 g·m−3), which is significantly lower than the French sites.
|
48 |
+
|
49 |
+
Tidal Range: Both the Gironde Estuary and Bourgneuf Bay have a macro-tidal regime, with tidal ranges from 2 to 5 m and 2 to 6 m, respectively. The tidal range in the Santa Monica Bay is not explicitly stated in the sources found, but the Pacific coast of Southern California typically experiences a smaller tidal range, usually less than 2 meters.
|
50 |
+
|
51 |
+
Freshwater Inputs: The Gironde Estuary and Bourgneuf Bay have significant freshwater inputs from large rivers, with flow rates ranging from less than 100 m3·s−1 to more than 4000 m3·s−1. The Santa Monica Bay receives freshwater inputs from several smaller watersheds, but the flow rates are likely much lower than those of the French sites.
|
52 |
+
|
53 |
+
|
54 |
+
The Santa Monica Bay, California, and the Gironde Estuary and Bourgneuf Bay in France have some similarities but also significant differences. Here are some key points to consider:
|
55 |
+
|
56 |
+
Size and Geography: The Santa Monica Bay is a bight of the Pacific Ocean, while the Gironde Estuary and Bourgneuf Bay are estuaries, which are partially enclosed coastal bodies of water where freshwater from rivers and streams meets and mixes with saltwater from the ocean. The Gironde Estuary is formed by the confluence of the Garonne and Dordogne rivers, and the Loire River feeds into Bourgneuf Bay. The Santa Monica Bay, on the other hand, is fed by several smaller watersheds, including Malibu Creek and Ballona Creek.
|
57 |
+
|
58 |
+
Turbidity and SPM Concentration: The Gironde Estuary and Bourgneuf Bay are characterized by high Suspended Particulate Matter (SPM) concentrations, ranging from 1 to 3000 g·m−3 in the Gironde and 50 to over 1000 g·m−3 in Bourgneuf Bay. The Santa Monica Bay, according to the information found, has a lower SPM concentration, with a median value of 1.3 mg/L (or approximately 0.0013 g·m−3), which is significantly lower than the French sites.
|
59 |
+
|
60 |
+
Tidal Range: Both the Gironde Estuary and Bourgneuf Bay have a macro-tidal regime, with tidal ranges from 2 to 5 m and 2 to 6 m, respectively. The tidal range in the Santa Monica Bay is not explicitly stated in the sources found, but the Pacific coast of Southern California typically experiences a smaller tidal range, usually less than 2 meters.
|
61 |
+
|
62 |
+
In conclusion, while there are some similarities in terms of being coastal water bodies with freshwater inputs, the Gironde Estuary and Bourgneuf Bay in France appear to be significantly more turbid and have higher SPM concentrations than the Santa Monica Bay.
|
63 |
+
|
64 |
+
Freshwater Inputs: The Gironde Estuary and Bourgneuf Bay have significant freshwater inputs from large rivers, with flow rates ranging from less than 100 m3·s−1 to more than 4000 m3·s−1. The Santa Monica Bay receives freshwater inputs from several smaller watersheds, but the flow rates are likely much lower than those of the French sites.
|
65 |
+
|
66 |
+
In conclusion, while there are some similarities in terms of being coastal water bodies with freshwater inputs, the Gironde Estuary and Bourgneuf Bay in France appear to be significantly more turbid and have higher SPM concentrations than the Santa Monica Bay. Therefore, the models developed for the French sites may not be directly applicable to the Santa Monica Bay without some adjustments or recalibrations.
|
67 |
+
|
68 |
+
# References
|
69 |
+
Trinh, R. C., Fichot, C. G., Gierach, M. M., Holt, B., Malakar, N. K., Hulley, G., & Smith, J. (2017). Application of Landsat 8 for Monitoring Impacts of Wastewater Discharge on Coastal Water Quality. Frontiers in Marine Science, 4. https://doi.org/10.3389/fmars.2017.00329
|
70 |
+
|
71 |
+
Novoa S, Doxaran D, Ody A, Vanhellemont Q, Lafon V, Lubac B, Gernez P. Atmospheric Corrections and Multi-Conditional Algorithm for Multi-Sensor Remote Sensing of Suspended Particulate Matter in Low-to-High Turbidity Levels Coastal Waters. Remote Sensing. 2017; 9(1):61. https://doi.org/10.3390/rs9010061
|
72 |
+
"""
|
73 |
+
|
74 |
+
solara.Markdown(markdown)
|
pages/__init__.py
ADDED
File without changes
|
public/__init__.py
ADDED
File without changes
|
public/aoi.geojson
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:3b7c4375153759cb610ce4b4043a901854ca907a457511e0c4e9f17d21aa2883
|
3 |
+
size 130581
|
public/constants.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Place all your constants here
|
2 |
+
import os
|
3 |
+
|
4 |
+
# Note: constants should be UPPER_CASE
|
5 |
+
constants_path = os.path.realpath(__file__)
|
6 |
+
SRC_PATH = os.path.dirname(constants_path)
|
7 |
+
PROJECT_PATH = os.path.dirname(SRC_PATH)
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
# The larger study area to use for earth engine this study uses the coastline of the Santa Monica bay
|
12 |
+
|
13 |
+
STUDY_BOUNDARY_PATH = os.path.join(SRC_PATH,'study_boundary.gpkg')
|
14 |
+
|
15 |
+
TURBO_PALETTE = [
|
16 |
+
"30123b", "321543", "33184a", "341b51", "351e58", "36215f", "372466", "38276d",
|
17 |
+
"392a73", "3a2d79", "3b2f80", "3c3286", "3d358b", "3e3891", "3f3b97", "3f3e9c",
|
18 |
+
"4040a2", "4143a7", "4146ac", "4249b1", "424bb5", "434eba", "4451bf", "4454c3",
|
19 |
+
"4456c7", "4559cb", "455ccf", "455ed3", "4661d6", "4664da", "4666dd", "4669e0",
|
20 |
+
"466be3", "476ee6", "4771e9", "4773eb", "4776ee", "4778f0", "477bf2", "467df4",
|
21 |
+
"4680f6", "4682f8", "4685fa", "4687fb", "458afc", "458cfd", "448ffe", "4391fe",
|
22 |
+
"4294ff", "4196ff", "4099ff", "3e9bfe", "3d9efe", "3ba0fd", "3aa3fc", "38a5fb",
|
23 |
+
"37a8fa", "35abf8", "33adf7", "31aff5", "2fb2f4", "2eb4f2", "2cb7f0", "2ab9ee",
|
24 |
+
"28bceb", "27bee9", "25c0e7", "23c3e4", "22c5e2", "20c7df", "1fc9dd", "1ecbda",
|
25 |
+
"1ccdd8", "1bd0d5", "1ad2d2", "1ad4d0", "19d5cd", "18d7ca", "18d9c8", "18dbc5",
|
26 |
+
"18ddc2", "18dec0", "18e0bd", "19e2bb", "19e3b9", "1ae4b6", "1ce6b4", "1de7b2",
|
27 |
+
"1fe9af", "20eaac", "22ebaa", "25eca7", "27eea4", "2aefa1", "2cf09e", "2ff19b",
|
28 |
+
"32f298", "35f394", "38f491", "3cf58e", "3ff68a", "43f787", "46f884", "4af880",
|
29 |
+
"4ef97d", "52fa7a", "55fa76", "59fb73", "5dfc6f", "61fc6c", "65fd69", "69fd66",
|
30 |
+
"6dfe62", "71fe5f", "75fe5c", "79fe59", "7dff56", "80ff53", "84ff51", "88ff4e",
|
31 |
+
"8bff4b", "8fff49", "92ff47", "96fe44", "99fe42", "9cfe40", "9ffd3f", "a1fd3d", "a4fc3c", "a7fc3a", "a9fb39", "acfb38",
|
32 |
+
"affa37", "b1f936", "b4f836", "b7f735", "b9f635", "bcf534", "bef434", "c1f334",
|
33 |
+
"c3f134", "c6f034", "c8ef34", "cbed34", "cdec34", "d0ea34", "d2e935", "d4e735",
|
34 |
+
"d7e535", "d9e436", "dbe236", "dde037", "dfdf37", "e1dd37", "e3db38", "e5d938",
|
35 |
+
"e7d739", "e9d539", "ebd339", "ecd13a", "eecf3a", "efcd3a", "f1cb3a", "f2c93a",
|
36 |
+
"f4c73a", "f5c53a", "f6c33a", "f7c13a", "f8be39", "f9bc39", "faba39", "fbb838",
|
37 |
+
"fbb637", "fcb336", "fcb136", "fdae35", "fdac34", "fea933", "fea732", "fea431",
|
38 |
+
"fea130", "fe9e2f", "fe9b2d", "fe992c", "fe962b", "fe932a", "fe9029", "fd8d27",
|
39 |
+
"fd8a26", "fc8725", "fc8423", "fb8122", "fb7e21", "fa7b1f", "f9781e", "f9751d",
|
40 |
+
"f8721c", "f76f1a", "f66c19", "f56918", "f46617", "f36315", "f26014", "f15d13",
|
41 |
+
"f05b12", "ef5811", "ed5510", "ec530f", "eb500e", "ea4e0d", "e84b0c", "e7490c",
|
42 |
+
"e5470b", "e4450a", "e2430a", "e14109", "df3f08", "dd3d08", "dc3b07", "da3907",
|
43 |
+
"d83706", "d63506", "d43305", "d23105", "d02f05", "ce2d04", "cc2b04", "ca2a04",
|
44 |
+
"c82803", "c52603", "c32503", "c12302", "be2102", "bc2002", "b91e02", "b71d02",
|
45 |
+
"b41b01", "b21a01", "af1801", "ac1701", "a91601", "a71401", "a41301", "a11201",
|
46 |
+
"9e1001", "9b0f01", "980e01", "950d01", "920b01", "8e0a01", "8b0902", "880802",
|
47 |
+
"850702", "810602", "7e0502", "7a0403"
|
48 |
+
]
|
49 |
+
|
public/functions.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ee
|
2 |
+
|
3 |
+
# Define a function to apply scaling and offset
|
4 |
+
def apply_scale_factors(image):
|
5 |
+
optical_bands = image.select('SR_B.*').multiply(0.0000275).add(-0.2)
|
6 |
+
thermal_bands = image.select('ST_B.*').multiply(0.00341802).add(149.0)
|
7 |
+
image = image.addBands(optical_bands, None, True)
|
8 |
+
image = image.addBands(thermal_bands, None, True)
|
9 |
+
return image
|
10 |
+
|
11 |
+
# Define a function to calculate statistics chl-a
|
12 |
+
def calculate_stats(image, region):
|
13 |
+
stats = image.reduceRegion(
|
14 |
+
reducer=ee.Reducer.mean().combine(
|
15 |
+
reducer2=ee.Reducer.stdDev(),
|
16 |
+
sharedInputs=True
|
17 |
+
),
|
18 |
+
geometry=region,
|
19 |
+
scale=30, # scale in meters
|
20 |
+
maxPixels=1e9
|
21 |
+
)
|
22 |
+
return stats.getInfo()
|
23 |
+
|
24 |
+
# Define function to return QA bands
|
25 |
+
|
26 |
+
def extract_qa_bits(qa_band, start_bit, end_bit, band_name):
|
27 |
+
"""
|
28 |
+
Extracts QA values from an image
|
29 |
+
:param qa_band: Single-band image of the QA layer
|
30 |
+
:type qa_band: ee.Image
|
31 |
+
:param start_bit: Starting bit
|
32 |
+
:type start_bit: Integer
|
33 |
+
:param end_bit: Ending bit
|
34 |
+
:type end_bit: Integer
|
35 |
+
:param band_name: New name for the band
|
36 |
+
:type band_name: String
|
37 |
+
:return: Image with extracted QA values
|
38 |
+
:rtype: ee.Image
|
39 |
+
"""
|
40 |
+
# Initialize QA bit string/pattern to check QA band against
|
41 |
+
qa_bits = 0
|
42 |
+
# Add each specified QA bit flag value/string/pattern to the QA bits to check/extract
|
43 |
+
for bit in range(start_bit, end_bit + 1):
|
44 |
+
qa_bits += pow(2, bit) # Same as qa_bits += (1 << bit)
|
45 |
+
# Return a single band image of the extracted QA bit values
|
46 |
+
return qa_band.select([0], [band_name]).bitwiseAnd(qa_bits).rightShift(start_bit)
|
47 |
+
|
48 |
+
# Define a function to calculate Chlorphyll-a based on trinh et al.(2017)
|
49 |
+
def trinh_et_al_chl_a(image):
|
50 |
+
# extract the cloud and water masks
|
51 |
+
qa_band = ee.Image(image).select('QA_PIXEL')
|
52 |
+
cloudMask = extract_qa_bits(qa_band, 8, 9, "cloud").neq(3) # different than 3 to remove clouds
|
53 |
+
waterMask = extract_qa_bits(qa_band, 7, 7, "water").eq(1) # equals 1 to keep water
|
54 |
+
|
55 |
+
# apply the masks to the image
|
56 |
+
image = image.updateMask(cloudMask).updateMask(waterMask)
|
57 |
+
image = apply_scale_factors(image)
|
58 |
+
|
59 |
+
a_0 = 0.9375
|
60 |
+
a_1 = -1.8862
|
61 |
+
blue_bands = image.select('SR_B2')
|
62 |
+
green_bands = image.select('SR_B3')
|
63 |
+
ln_chl_a = image.expression("a_0 + a_1 * log(blue_bands/green_bands)", {
|
64 |
+
'a_0': a_0,
|
65 |
+
'a_1': a_1,
|
66 |
+
'blue_bands': blue_bands,
|
67 |
+
'green_bands': green_bands
|
68 |
+
})
|
69 |
+
image = image.addBands(ln_chl_a.select([0], ['ln_chl_a']))
|
70 |
+
|
71 |
+
|
72 |
+
return image
|
73 |
+
|
74 |
+
|
75 |
+
def extract_data(image):
|
76 |
+
stats = image.reduceRegion(ee.Reducer.mean(), aoi, 30)
|
77 |
+
valid_pixels = image.select('ln_chl_a').unmask().neq(0) # create a mask of valid pixels
|
78 |
+
valid_area = valid_pixels.multiply(ee.Image.pixelArea()).reduceRegion(ee.Reducer.sum(), aoi, 30) # calculate the area of valid pixels
|
79 |
+
return image.set('date', image.date().format()).set(stats).set('Area', valid_area)
|
80 |
+
|
81 |
+
|
82 |
+
# Define a function to calculate SPM from Novoa et al.(2017) based on Nechad et al. (2010) NIR (recalibrated) model
|
83 |
+
|
84 |
+
def novoa_et_al_spm(image):
|
85 |
+
# extract the cloud and water masks
|
86 |
+
qa_band = ee.Image(image).select('QA_PIXEL')
|
87 |
+
cloudMask = extract_qa_bits(qa_band, 8, 9, "cloud").neq(3) # different than 3 to remove clouds
|
88 |
+
waterMask = extract_qa_bits(qa_band, 7, 7, "water").eq(1) # equals 1 to keep water
|
89 |
+
|
90 |
+
# apply the masks to the image
|
91 |
+
image = image.updateMask(cloudMask).updateMask(waterMask)
|
92 |
+
image = apply_scale_factors(image)
|
93 |
+
|
94 |
+
# Select the NIR band (B5 for Landsat 8 OLI)
|
95 |
+
nir = image.select('SR_B5')
|
96 |
+
# Apply the Nechad et al. (2010) NIR (recalibrated) model
|
97 |
+
spm = image.expression(
|
98 |
+
"4302 * (nir / (1 - nir / 0.2115))",
|
99 |
+
{'nir': nir}
|
100 |
+
)
|
101 |
+
# Set negative SPM values to zero
|
102 |
+
spm = spm.where(spm.lt(0), 0)
|
103 |
+
image = image.addBands(spm.select([0], ['spm']))
|
104 |
+
|
105 |
+
|
106 |
+
return image
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
def extract_data_spm(image):
|
111 |
+
stats = image.reduceRegion(ee.Reducer.mean(), aoi, 30)
|
112 |
+
valid_pixels = image.select('spm').unmask().neq(0) # create a mask of valid pixels
|
113 |
+
valid_area = valid_pixels.multiply(ee.Image.pixelArea()).reduceRegion(ee.Reducer.sum(), aoi, 30) # calculate the area of valid pixels
|
114 |
+
return image.set('date', image.date().format()).set(stats).set('Area', valid_area)
|
115 |
+
|
116 |
+
|
117 |
+
'''def load_recent_images(STUDY_BOUNDARY_PATH, limit=1):
|
118 |
+
# Load the study area
|
119 |
+
ee_boundary = geemap.geopandas_to_ee(study_boundary)
|
120 |
+
|
121 |
+
# Define the Area of Interest (AOI)
|
122 |
+
aoi = ee_boundary.geometry()
|
123 |
+
|
124 |
+
# Define the Image Collection
|
125 |
+
image_collection = ee.ImageCollection(LANDSAT/LC08/C02/T1_L) \
|
126 |
+
.filterBounds(aoi) \
|
127 |
+
.sort('system:time_start', False)
|
128 |
+
|
129 |
+
# Get the recent images
|
130 |
+
recent_images = image_collection.limit(limit)
|
131 |
+
|
132 |
+
return recent_images'''
|
133 |
+
|
public/study_boundary.gpkg
ADDED
Binary file (209 kB). View file
|
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
geemap
|
2 |
+
solara
|
3 |
+
geopandas
|
4 |
+
pydantic< 2.0
|
5 |
+
numpy
|
6 |
+
matplotlib
|
7 |
+
pandas
|