romero61 commited on
Commit
9ff7f17
·
0 Parent(s):

Duplicate from romero61/WaterQualityMonitoring_SMB

Browse files
.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