From 2b5f6b2acf1c909e36afa34ee7e4b6bf434f0844 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 23 Jan 2024 22:05:10 +0000 Subject: [PATCH 001/175] getting a sketching related error that I won't worry about yet, serving a python .py thru pyscript w/ FastAPI --- app.py | 6 +++++- src/static/pyscript.toml | 1 + src/static/sketch.html | 18 ++++++++++++++++++ src/static/sketch.py | 11 +++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/static/pyscript.toml create mode 100644 src/static/sketch.html create mode 100644 src/static/sketch.py diff --git a/app.py b/app.py index faf8d8c..2a5bb71 100644 --- a/app.py +++ b/app.py @@ -37,7 +37,7 @@ from src.util.ingest_burn_zip import ingest_esri_zip_file, shp_to_geojson from src.lib.titiler_algorithms import algorithms from src.lib.query_soil import sdm_get_ecoclassid_from_mu_info, sdm_get_esa_mapunitid_poly, edit_get_ecoclass_info -# app = Flask(__name__) + app = FastAPI() cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix='/cog', tags=["Cloud Optimized GeoTIFF"]) @@ -343,3 +343,7 @@ def serve_map(request: Request, fire_event_name: str, burn_metric: str, affiliat @app.get("/upload", response_class=HTMLResponse) def upload(request: Request): return templates.TemplateResponse("upload.html", {"request": request}) + +@app.get("/sketch", response_class=HTMLResponse) +def sketch(request: Request): + return templates.TemplateResponse("sketch.html", {"request": request}) \ No newline at end of file diff --git a/src/static/pyscript.toml b/src/static/pyscript.toml new file mode 100644 index 0000000..abc10fb --- /dev/null +++ b/src/static/pyscript.toml @@ -0,0 +1 @@ +packages = ["numpy","sketchingpy"] \ No newline at end of file diff --git a/src/static/sketch.html b/src/static/sketch.html new file mode 100644 index 0000000..88525d7 --- /dev/null +++ b/src/static/sketch.html @@ -0,0 +1,18 @@ + + + + + + Sketch! + + + + + Hello we are running sketching in pyscript! + + + + + + + \ No newline at end of file diff --git a/src/static/sketch.py b/src/static/sketch.py new file mode 100644 index 0000000..ff44217 --- /dev/null +++ b/src/static/sketch.py @@ -0,0 +1,11 @@ +import sketchingpy + +sketch = sketchingpy.Sketch2D(500, 500) + +sketch.clear('#F0F0F0') + +sketch.set_fill('#C0C0C0') +sketch.set_stroke('#000000') +sketch.draw_ellipse(250, 250, 20, 20) + +sketch.show() \ No newline at end of file From 06e75353f5465bff1461cfb16c222d12a45442ab Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 23 Jan 2024 22:10:49 +0000 Subject: [PATCH 002/175] re-org for clarity --- app.py | 6 +++--- src/static/{ => map}/burn_metric_text.json | 0 src/static/{ => map}/map.html | 0 src/static/{ => sketch}/pyscript.toml | 0 src/static/{ => sketch}/sketch.html | 4 ++-- src/static/{ => sketch}/sketch.py | 0 src/static/{ => upload}/checkmark.png | Bin src/static/{ => upload}/spinner.gif | Bin src/static/{ => upload}/upload.html | 12 ++++++------ 9 files changed, 11 insertions(+), 11 deletions(-) rename src/static/{ => map}/burn_metric_text.json (100%) rename src/static/{ => map}/map.html (100%) rename src/static/{ => sketch}/pyscript.toml (100%) rename src/static/{ => sketch}/sketch.html (82%) rename src/static/{ => sketch}/sketch.py (100%) rename src/static/{ => upload}/checkmark.png (100%) rename src/static/{ => upload}/spinner.gif (100%) rename src/static/{ => upload}/upload.html (87%) diff --git a/app.py b/app.py index 2a5bb71..7294048 100644 --- a/app.py +++ b/app.py @@ -325,7 +325,7 @@ def serve_map(request: Request, fire_event_name: str, burn_metric: str, affiliat fire_metadata = manifest[fire_event_name] fire_metadata_json = json.dumps(fire_metadata) - with open('src/static/burn_metric_text.json') as json_file: + with open('src/static/map/burn_metric_text.json') as json_file: burn_metric_text = json.load(json_file) return templates.TemplateResponse("map.html", { @@ -342,8 +342,8 @@ def serve_map(request: Request, fire_event_name: str, burn_metric: str, affiliat @app.get("/upload", response_class=HTMLResponse) def upload(request: Request): - return templates.TemplateResponse("upload.html", {"request": request}) + return templates.TemplateResponse("upload/upload.html", {"request": request}) @app.get("/sketch", response_class=HTMLResponse) def sketch(request: Request): - return templates.TemplateResponse("sketch.html", {"request": request}) \ No newline at end of file + return templates.TemplateResponse("sketch/sketch.html", {"request": request}) \ No newline at end of file diff --git a/src/static/burn_metric_text.json b/src/static/map/burn_metric_text.json similarity index 100% rename from src/static/burn_metric_text.json rename to src/static/map/burn_metric_text.json diff --git a/src/static/map.html b/src/static/map/map.html similarity index 100% rename from src/static/map.html rename to src/static/map/map.html diff --git a/src/static/pyscript.toml b/src/static/sketch/pyscript.toml similarity index 100% rename from src/static/pyscript.toml rename to src/static/sketch/pyscript.toml diff --git a/src/static/sketch.html b/src/static/sketch/sketch.html similarity index 82% rename from src/static/sketch.html rename to src/static/sketch/sketch.html index 88525d7..63ed460 100644 --- a/src/static/sketch.html +++ b/src/static/sketch/sketch.html @@ -10,8 +10,8 @@ Hello we are running sketching in pyscript! - - + + diff --git a/src/static/sketch.py b/src/static/sketch/sketch.py similarity index 100% rename from src/static/sketch.py rename to src/static/sketch/sketch.py diff --git a/src/static/checkmark.png b/src/static/upload/checkmark.png similarity index 100% rename from src/static/checkmark.png rename to src/static/upload/checkmark.png diff --git a/src/static/spinner.gif b/src/static/upload/spinner.gif similarity index 100% rename from src/static/spinner.gif rename to src/static/upload/spinner.gif diff --git a/src/static/upload.html b/src/static/upload/upload.html similarity index 87% rename from src/static/upload.html rename to src/static/upload/upload.html index 55f24d0..b307d18 100644 --- a/src/static/upload.html +++ b/src/static/upload/upload.html @@ -30,32 +30,32 @@ From d5245b4fc274f16bc3297381bfb6d34f93613999 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 24 Jan 2024 17:43:37 +0000 Subject: [PATCH 003/175] AOI draw for upload --- app.py | 2 +- src/static/upload/upload.html | 232 ++++++++++++++++++++++------------ 2 files changed, 155 insertions(+), 79 deletions(-) diff --git a/app.py b/app.py index 7294048..f44fb8b 100644 --- a/app.py +++ b/app.py @@ -328,7 +328,7 @@ def serve_map(request: Request, fire_event_name: str, burn_metric: str, affiliat with open('src/static/map/burn_metric_text.json') as json_file: burn_metric_text = json.load(json_file) - return templates.TemplateResponse("map.html", { + return templates.TemplateResponse("map/map.html", { "request": request, "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 "fire_event_name": fire_event_name, diff --git a/src/static/upload/upload.html b/src/static/upload/upload.html index b307d18..86069ad 100644 --- a/src/static/upload/upload.html +++ b/src/static/upload/upload.html @@ -3,6 +3,11 @@ Fire Analysis Form + + + + + -
-
-
- -
- -
-
- -
- -
-
-
-
- -
- -
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+
+
From d3f4d1e39dbead2d3fabe8742515a79f2b37d552 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 8 Feb 2024 20:09:26 +0000 Subject: [PATCH 083/175] test homepage from md --- .deployment/prod_environment.yml | 1 + .devcontainer/dev_environment.yml | 1 + README.md | 51 ++++++++++-------- app.py | 20 ++++++- src/static/home/home.html | 16 ++++++ src/static/home/home.md | 87 +++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 23 deletions(-) create mode 100644 src/static/home/home.html create mode 100644 src/static/home/home.md diff --git a/.deployment/prod_environment.yml b/.deployment/prod_environment.yml index 52167cb..53c68c8 100644 --- a/.deployment/prod_environment.yml +++ b/.deployment/prod_environment.yml @@ -17,6 +17,7 @@ dependencies: - ipython - boto3 - pystac-client + - markdown - planetary-computer - fastapi <0.108.0 #to avoid AssertionError bug w/ titiler - smart_open diff --git a/.devcontainer/dev_environment.yml b/.devcontainer/dev_environment.yml index fa89294..0b00c48 100644 --- a/.devcontainer/dev_environment.yml +++ b/.devcontainer/dev_environment.yml @@ -23,6 +23,7 @@ dependencies: - ipython - jupyter - pystac-client + - markdown - boto3 - planetary-computer - fastapi <0.108.0 #to avoid AssertionError bug w/ titiler diff --git a/README.md b/README.md index 6cdd584..45ae768 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,21 @@ ### Goals Near-term + - Enable NPS land managers to rapidly understand the severity of fires in arid landscapes, using rapidly-updated `Sentinel-2` and/or `LANDSAT` imagery, using a DSE-developed modelling approach and web application collaboratively designed with NPS end-users Long-term + - Develop and refine the tool to support NPS efforts in post-fire recovery, by investigating the post-fire effects of firebreak strategies, effectiveness of re-vegetation efforts, community range migration, among others ### Current Approach: BAER / BARC Classifications -BAER is generated rapidly after a fire occurs, but is not a great representation of 'true' vegetation loss for a few reasons (motivated in part by *Safford et. al. 2008*). +BAER is generated rapidly after a fire occurs, but is not a great representation of 'true' vegetation loss for a few reasons (motivated in part by _Safford et. al. 2008_). -1) **BARC framework and validation are not designed around arid ecosystems, and thus lack sensitivity to low biomass - comparisons of pre and post-fire comparisons are absolute, not relative** -2) BARC is not very useful at low spatial resolutions, due to the BAER minimum delineations being around 40 acres -3) BARC severity classifications vary from fire to fire, and classification thresholds are determined manually using opaque methodology, making spatial and temporal comparisons across fires difficult -4) BARC is not validated with quantitative field data, so while relative comparisons might be valid, it is hard to make accurate **absolute** estimations of vegetation loss post-fire. +1. **BARC framework and validation are not designed around arid ecosystems, and thus lack sensitivity to low biomass - comparisons of pre and post-fire comparisons are absolute, not relative** +2. BARC is not very useful at low spatial resolutions, due to the BAER minimum delineations being around 40 acres +3. BARC severity classifications vary from fire to fire, and classification thresholds are determined manually using opaque methodology, making spatial and temporal comparisons across fires difficult +4. BARC is not validated with quantitative field data, so while relative comparisons might be valid, it is hard to make accurate **absolute** estimations of vegetation loss post-fire. ### Best Practice: Relative Spectral Metrics @@ -22,48 +24,52 @@ To account for the fact that JOTR and other arid parks exist have relatively lit ### Step 1 - Develop Data / Modelling Infrastructure Inputs: + - High frequency, low spatial resolution, highest spectral resolution remote sensing data - - `Sentinel-2`, `LANDSAT` -- Low frequency, higher spatial resolution, low spectral resolution - - National Agricultural Imagery Program (`NAIP`) + - `Sentinel-2`, soon-to-be `LANDSAT` and potentially `PLANET` +- Low frequency, higher spatial resolution, low spectral resolution + - National Agricultural Imagery Program (`NAIP`) - One-off frequency, extremely high spatial resolution, variable spectral resolution - - LIDAR imagery from previous survey work + - LIDAR imagery from previous survey work -In the context of fire severity modelling, absolute metrics, such as the *Normalized Burn Ratio* ($NBR$), tend to be more effective for comparison across high and low biomass areas, but tend to be biased low in low biomass areas since the absolute changes ($dNBR$) are small in magnitude according to lack of adjustment. +In the context of fire severity modelling, absolute metrics, such as the _Normalized Burn Ratio_ ($NBR$), tend to be more effective for comparison across high and low biomass areas, but tend to be biased low in low biomass areas since the absolute changes ($dNBR$) are small in magnitude according to lack of adjustment. $$ NBR = \frac{NIR - SWIR}{NIR + SWIR} $$ -$$ dNBR = NBR_{prefire} - NBR_{postfire}$$ +$$ dNBR = NBR*{prefire} - NBR*{postfire}$$ -To address these issues, some commonly accepted adjustments include the *Relative Difference in Normalized Burn Ratio* ($RdNBR$), as well as the *Relativized Burn Ratio* ($RBR$), both of which attempt to adjust the relative change by the reflectance of the area pre-fire, such that fire severity is scaled to local reflectance in each given pixel. +To address these issues, some commonly accepted adjustments include the _Relative Difference in Normalized Burn Ratio_ ($RdNBR$), as well as the _Relativized Burn Ratio_ ($RBR$), both of which attempt to adjust the relative change by the reflectance of the area pre-fire, such that fire severity is scaled to local reflectance in each given pixel. $$RdNBR = \frac{dNBR}{|(NBR_{prefire})^{0.5}|}$$ -$$ RBR = \frac{dNBR}{NBR_{prefire} +1.001}$$ +$$ RBR = \frac{dNBR}{NBR\_{prefire} +1.001}$$ + +Each of these metrics can be derived from satellite imagery, at various degrees of temporal and spatial resolution. The simplest approach would be to settle on one source (likely `Sentinel-2`, as it has the higher temporal resolution than `LANDSAT`), and interpolate values between collection. However, a particular challenge identified in the case of immediate post-fire analysis is the potential occlusion of smoke - most approaches employ some manual approaches to find the best available pre-fire and post-fire images, which could lead to (1) some vegetative cover/biomass bias due phenology between image dates or (2) lag in analysis time in waiting for smoke-free imagery to be collected, since satellites return at best every 5 days. -Each of these metrics can be derived from satellite imagery, at various degrees of temporal and spatial resolution. The simplest approach would be to settle on one source (likely `Sentinel-2`, as it has the higher temporal resolution than `LANDSAT`), and interpolate values between collection. However, a particular challenge identified in the case of immediate post-fire analysis is the potential occlusion of smoke - most approaches employ some manual approaches to find the best available pre-fire and post-fire images, which could lead to (1) some vegetative cover/biomass bias due phenology between image dates or (2) lag in analysis time in waiting for smoke-free imagery to be collected, since satellites return at best every 5 days. +If we discover that relying on a imaging source is insufficient, either due to a lack of timeliness in imaging after fire (due to smoke or cloud occlusion) or simply due to issues with ground-truth accuracy (described in step 2), we may investigate a method which incorporates multiple imagery sources, as illustrated with `ESTARFM` fusion model employed in _Liu et. al. 2022_. -If we discover that relying on a imaging source is insufficient, either due to a lack of timeliness in imaging after fire (due to smoke or cloud occlusion) or simply due to issues with ground-truth accuracy (described in step 2), we may investigate a method which incorporates multiple imagery sources, as illustrated with `ESTARFM` fusion model employed in *Liu et. al. 2022*. ### Step 2 - Validate Using NPS, BLM, USGS Ground-Truth Inputs: + - Remote Sensing data as discussed above - Vegetation cover data - - Assessment, Inventory and Monitoring data from BLM / USGS, used to train [USDA's Rangeland Analysis Platform](https://rangelands.app/) - - Any and All NPS data that resemble AIM format (*specific fields and aggregations to be discussed with the JOTR/NPS team and through further investigation*) + - Assessment, Inventory and Monitoring data from BLM / USGS, used to train [USDA's Rangeland Analysis Platform](https://rangelands.app/) + - Any and All NPS data that resemble AIM format (_specific fields and aggregations to be discussed with the JOTR/NPS team and through further investigation_) - Exogenous Land Features - - Soil / Lithography inputs using SSURGO, WebSoilSurvey, etc - - Terrain information using Digital Elevation Models (DEM) or other finer sources + - Soil / Lithography inputs using SSURGO, WebSoilSurvey, etc + - Terrain information using Digital Elevation Models (DEM) or other finer sources -Using the approach described above, we will perform an initial analysis describing how well each relative metric (using *just* a remote sensing approach) approximates vegetation metrics derived through ground truth data. An example of this approach, which focuses instead on the *Composite Burn Index* (CBI), is illustrated in *Cardil et. al. 2019*. +Using the approach described above, we will perform an initial analysis describing how well each relative metric (using _just_ a remote sensing approach) approximates vegetation metrics derived through ground truth data. An example of this approach, which focuses instead on the _Composite Burn Index_ (CBI), is illustrated in _Cardil et. al. 2019_. -Depending on the results here, we may discover that one of the metrics above is a good enough approximation of reality to prove useful to the NPS in fire recovery efforts, and thus would move on to step 3. +Depending on the results here, we may discover that one of the metrics above is a good enough approximation of reality to prove useful to the NPS in fire recovery efforts, and thus would move on to step 3. -However, if we are not satisfied with the accuracy of such a model, another approach to this step would be to use vegetation cover as a target variable in a supervised learning model. Instead of treating the spectral indices as the output themselves, we might use these spectral indices as inputs (potentially including exogenous land features) to directly predict vegetation cover percentage. Such an approach would require more complete and land-cover-balanced vegetation data, but might result in better performance on NPS-managed lands in Southern California. +However, if we are not satisfied with the accuracy of such a model, another approach to this step would be to use vegetation cover as a target variable in a supervised learning model. Instead of treating the spectral indices as the output themselves, we might use these spectral indices as inputs (potentially including exogenous land features) to directly predict vegetation cover percentage. Such an approach would require more complete and land-cover-balanced vegetation data, but might result in better performance on NPS-managed lands in Southern California. ### Step 3 – Develop Web Application and Interactive Elements Alongside modelling efforts, following guidance and feedback from JOTR staff and NPS stakeholders, we plan to develop an interactive web application which allows managers to visualize, for example: + - (1) Point-in-time estimates of vegetative cover - (2) Trends in green-up and senescence - (3) Vegetative effects of disturbance @@ -71,6 +77,7 @@ Alongside modelling efforts, following guidance and feedback from JOTR staff and The metrics visualized within this tool will be highly dependent on evidence of utility to park managers, from steps 1 and 2, and are subject to change throughout the design process and/or after project 'completion'. ### References + Cardil, A., Mola-Yudego, B., Blázquez-Casado, Á. & González-Olabarria, J. R. Fire and burn severity assessment: Calibration of Relative Differenced Normalized Burn Ratio (RdNBR) with field data. _Journal of Environmental Management_ **235**, 342–349 (2019). Parks, S., Dillon, G. & Miller, C. A New Metric for Quantifying Burn Severity: The Relativized Burn Ratio. _Remote Sensing_ **6**, 1827–1844 (2014). diff --git a/app.py b/app.py index 7cbb87a..f1070c4 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,8 @@ from pydantic import BaseModel import pandas as pd import sentry_sdk - +from markdown import markdown +from pathlib import Path # For network debugging import socket import requests @@ -612,3 +613,20 @@ def directory(request: Request, manifest: dict = Depends(get_manifest)): @app.get("/sketch", response_class=HTMLResponse) def sketch(request: Request): return templates.TemplateResponse("sketch/sketch.html", {"request": request}) + +@app.get("/", response_class=HTMLResponse) +def home(request: Request): + # Read the markdown file + with open(Path("src/static/home/home.md")) as f: + md_content = f.read() + + # Convert markdown to HTML + html_content = markdown(md_content) + + return templates.TemplateResponse( + "home.html", + { + "request": request, + "content": html_content, + }, + ) \ No newline at end of file diff --git a/src/static/home/home.html b/src/static/home/home.html new file mode 100644 index 0000000..ff879e6 --- /dev/null +++ b/src/static/home/home.html @@ -0,0 +1,16 @@ + + + + Home (DSE Burn Severity V0) + + + + {{ content | safe }} + Upload + Directory + Example Map + + diff --git a/src/static/home/home.md b/src/static/home/home.md new file mode 100644 index 0000000..45ae768 --- /dev/null +++ b/src/static/home/home.md @@ -0,0 +1,87 @@ +### Goals + +Near-term + +- Enable NPS land managers to rapidly understand the severity of fires in arid landscapes, using rapidly-updated `Sentinel-2` and/or `LANDSAT` imagery, using a DSE-developed modelling approach and web application collaboratively designed with NPS end-users + +Long-term + +- Develop and refine the tool to support NPS efforts in post-fire recovery, by investigating the post-fire effects of firebreak strategies, effectiveness of re-vegetation efforts, community range migration, among others + +### Current Approach: BAER / BARC Classifications + +BAER is generated rapidly after a fire occurs, but is not a great representation of 'true' vegetation loss for a few reasons (motivated in part by _Safford et. al. 2008_). + +1. **BARC framework and validation are not designed around arid ecosystems, and thus lack sensitivity to low biomass - comparisons of pre and post-fire comparisons are absolute, not relative** +2. BARC is not very useful at low spatial resolutions, due to the BAER minimum delineations being around 40 acres +3. BARC severity classifications vary from fire to fire, and classification thresholds are determined manually using opaque methodology, making spatial and temporal comparisons across fires difficult +4. BARC is not validated with quantitative field data, so while relative comparisons might be valid, it is hard to make accurate **absolute** estimations of vegetation loss post-fire. + +### Best Practice: Relative Spectral Metrics + +To account for the fact that JOTR and other arid parks exist have relatively little biomass in vegetation compared to forest counterparts considered by BARC, we plan to analyze a suite of relative spectral metrics that adjust the fire severity metric of interest (usually the difference in spectral metrics before and after a fire). + +### Step 1 - Develop Data / Modelling Infrastructure + +Inputs: + +- High frequency, low spatial resolution, highest spectral resolution remote sensing data + - `Sentinel-2`, soon-to-be `LANDSAT` and potentially `PLANET` +- Low frequency, higher spatial resolution, low spectral resolution + - National Agricultural Imagery Program (`NAIP`) +- One-off frequency, extremely high spatial resolution, variable spectral resolution + - LIDAR imagery from previous survey work + +In the context of fire severity modelling, absolute metrics, such as the _Normalized Burn Ratio_ ($NBR$), tend to be more effective for comparison across high and low biomass areas, but tend to be biased low in low biomass areas since the absolute changes ($dNBR$) are small in magnitude according to lack of adjustment. + +$$ NBR = \frac{NIR - SWIR}{NIR + SWIR} $$ + +$$ dNBR = NBR*{prefire} - NBR*{postfire}$$ + +To address these issues, some commonly accepted adjustments include the _Relative Difference in Normalized Burn Ratio_ ($RdNBR$), as well as the _Relativized Burn Ratio_ ($RBR$), both of which attempt to adjust the relative change by the reflectance of the area pre-fire, such that fire severity is scaled to local reflectance in each given pixel. + +$$RdNBR = \frac{dNBR}{|(NBR_{prefire})^{0.5}|}$$ + +$$ RBR = \frac{dNBR}{NBR\_{prefire} +1.001}$$ + +Each of these metrics can be derived from satellite imagery, at various degrees of temporal and spatial resolution. The simplest approach would be to settle on one source (likely `Sentinel-2`, as it has the higher temporal resolution than `LANDSAT`), and interpolate values between collection. However, a particular challenge identified in the case of immediate post-fire analysis is the potential occlusion of smoke - most approaches employ some manual approaches to find the best available pre-fire and post-fire images, which could lead to (1) some vegetative cover/biomass bias due phenology between image dates or (2) lag in analysis time in waiting for smoke-free imagery to be collected, since satellites return at best every 5 days. + +If we discover that relying on a imaging source is insufficient, either due to a lack of timeliness in imaging after fire (due to smoke or cloud occlusion) or simply due to issues with ground-truth accuracy (described in step 2), we may investigate a method which incorporates multiple imagery sources, as illustrated with `ESTARFM` fusion model employed in _Liu et. al. 2022_. + +### Step 2 - Validate Using NPS, BLM, USGS Ground-Truth + +Inputs: + +- Remote Sensing data as discussed above +- Vegetation cover data + - Assessment, Inventory and Monitoring data from BLM / USGS, used to train [USDA's Rangeland Analysis Platform](https://rangelands.app/) + - Any and All NPS data that resemble AIM format (_specific fields and aggregations to be discussed with the JOTR/NPS team and through further investigation_) +- Exogenous Land Features + - Soil / Lithography inputs using SSURGO, WebSoilSurvey, etc + - Terrain information using Digital Elevation Models (DEM) or other finer sources + +Using the approach described above, we will perform an initial analysis describing how well each relative metric (using _just_ a remote sensing approach) approximates vegetation metrics derived through ground truth data. An example of this approach, which focuses instead on the _Composite Burn Index_ (CBI), is illustrated in _Cardil et. al. 2019_. + +Depending on the results here, we may discover that one of the metrics above is a good enough approximation of reality to prove useful to the NPS in fire recovery efforts, and thus would move on to step 3. + +However, if we are not satisfied with the accuracy of such a model, another approach to this step would be to use vegetation cover as a target variable in a supervised learning model. Instead of treating the spectral indices as the output themselves, we might use these spectral indices as inputs (potentially including exogenous land features) to directly predict vegetation cover percentage. Such an approach would require more complete and land-cover-balanced vegetation data, but might result in better performance on NPS-managed lands in Southern California. + +### Step 3 – Develop Web Application and Interactive Elements + +Alongside modelling efforts, following guidance and feedback from JOTR staff and NPS stakeholders, we plan to develop an interactive web application which allows managers to visualize, for example: + +- (1) Point-in-time estimates of vegetative cover +- (2) Trends in green-up and senescence +- (3) Vegetative effects of disturbance + +The metrics visualized within this tool will be highly dependent on evidence of utility to park managers, from steps 1 and 2, and are subject to change throughout the design process and/or after project 'completion'. + +### References + +Cardil, A., Mola-Yudego, B., Blázquez-Casado, Á. & González-Olabarria, J. R. Fire and burn severity assessment: Calibration of Relative Differenced Normalized Burn Ratio (RdNBR) with field data. _Journal of Environmental Management_ **235**, 342–349 (2019). + +Parks, S., Dillon, G. & Miller, C. A New Metric for Quantifying Burn Severity: The Relativized Burn Ratio. _Remote Sensing_ **6**, 1827–1844 (2014). + +Safford, H. D., Miller, J., Schmidt, D., Roath, B. & Parsons, A. BAER Soil Burn Severity Maps Do Not Measure Fire Effects to Vegetation: A Comment on Odion and Hanson (2006). _Ecosystems_ **11**, 1–11 (2008). + +Liu, Z. _et al._ Research on Vegetation Cover Changes in Arid and Semi-Arid Region Based on a Spatio-Temporal Fusion Model. _Forests_ **13**, 2066 (2022). From e0f5ab67b21466b56d9d490ac8c131127ed22d11 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 8 Feb 2024 21:40:37 +0000 Subject: [PATCH 084/175] basic homepage with streamlined proposal / basic explainer on 'what this is even' --- app.py | 15 +----- src/static/home/directory.png | Bin 0 -> 582 bytes src/static/home/home.html | 22 +++++++-- src/static/home/home.md | 90 +++++++++++++--------------------- src/static/home/map.png | Bin 0 -> 670 bytes src/static/home/nir_swir.jpg | Bin 0 -> 20935 bytes src/static/home/upload.png | Bin 0 -> 587 bytes 7 files changed, 55 insertions(+), 72 deletions(-) create mode 100644 src/static/home/directory.png create mode 100644 src/static/home/map.png create mode 100644 src/static/home/nir_swir.jpg create mode 100644 src/static/home/upload.png diff --git a/app.py b/app.py index f1070c4..fde62d6 100644 --- a/app.py +++ b/app.py @@ -45,17 +45,6 @@ ) from src.lib.query_rap import rap_get_biomass -sentry_sdk.init( - dsn="https://3660129e232b3c796208a5e46945d838@o4506701219364864.ingest.sentry.io/4506701221199872", - # Set traces_sample_rate to 1.0 to capture 100% - # of transactions for performance monitoring. - traces_sample_rate=1.0, - # Set profiles_sample_rate to 1.0 to profile 100% - # of sampled transactions. - # We recommend adjusting this value in production. - profiles_sample_rate=1.0, -) - app = FastAPI() cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["Cloud Optimized GeoTIFF"]) @@ -72,7 +61,7 @@ ### HELPERS ### -@app.get("/") +@app.get("/healthz") def index(): logger.log_text("ping pong") return "Alive", 200 @@ -624,7 +613,7 @@ def home(request: Request): html_content = markdown(md_content) return templates.TemplateResponse( - "home.html", + "home/home.html", { "request": request, "content": html_content, diff --git a/src/static/home/directory.png b/src/static/home/directory.png new file mode 100644 index 0000000000000000000000000000000000000000..7792bfc06b1ced89ca760d6f955192f9aea86023 GIT binary patch literal 582 zcmV-M0=fN(P)wW_L2$0#R%0u9rUAsXShvbX^ z|8DTnY-hB9Rr_w=T1d{c`*?P8yuIy^PV>y&nXV2=I@clHa7P-zN*ho6>@HWZtRUpI zfMiy61uF_UX!B`*rz=<%g$&sI6nzG~YaB9V^F8#L@~&~nlFjpf?bTWGu5rk=&0nI= zws(y~e%O2%eZG5FAJPY00)OpUqD)`x>O;otd>?(D#;!hO!Ol0)XFhiIAs_905q&tsE8sEO^Z@6vYX}*#<01MyDRK=VFYLI3J~KtGA!ON(Z_uY% zg6G{J6>YX~{A;~@GR7P*FyD_gbl6+xLtDOdlK@iY2Nq+ET-d-PdMx%!Yf z^w~_g`jAod*-yEKh)a7_piD3DC*>MK@^>BT441ivkS+9?EprVauh3_$%r%5eqR(EL zYY54A?bJCbb3L;^=KHA1Joo#&MXYvs=lrIlnF?vxmnhse)$z+-AE@uQ)!Z-s1LXn5 UxV=Y=SO5S307*qoM6N<$f-aU1tN;K2 literal 0 HcmV?d00001 diff --git a/src/static/home/home.html b/src/static/home/home.html index ff879e6..d9d2417 100644 --- a/src/static/home/home.html +++ b/src/static/home/home.html @@ -6,11 +6,27 @@ rel="icon" href="data:image/svg+xml,🌿" /> + + + {{ content | safe }} - Upload - Directory - Example Map diff --git a/src/static/home/home.md b/src/static/home/home.md index 45ae768..4b2f662 100644 --- a/src/static/home/home.md +++ b/src/static/home/home.md @@ -1,80 +1,58 @@ -### Goals +## Wildfire Severity and Recovery Tool, Version 0 -Near-term +**Note: This tool and website are under ongoing development - it is recommended that you save any and all analytical outputs you deem essential.** -- Enable NPS land managers to rapidly understand the severity of fires in arid landscapes, using rapidly-updated `Sentinel-2` and/or `LANDSAT` imagery, using a DSE-developed modelling approach and web application collaboratively designed with NPS end-users + -Long-term +#### Introduction -- Develop and refine the tool to support NPS efforts in post-fire recovery, by investigating the post-fire effects of firebreak strategies, effectiveness of re-vegetation efforts, community range migration, among others +This is a very early development prototype of a _Wildfire Severity and Recovery Tool_, devloped by Eric and Wendy Schmidt Center for Data Science & Environment (DSE) at Berkeley in Collaboration with the National Parks Service, meant to gather feedback from interested collaborators and users. -### Current Approach: BAER / BARC Classifications +The core of this collaboration comes from a need to better understand the severity of fires occuring in low biomass areas - in our initial case, in Southern California (specifically, Joshua Tree National Park and Mojave National Preserve). -BAER is generated rapidly after a fire occurs, but is not a great representation of 'true' vegetation loss for a few reasons (motivated in part by _Safford et. al. 2008_). +This tool uses publicly available satellite imagery (currently, the European Space Agency's _Sentinel 2_ imagery) to estimate fire severity metrics on-demand. -1. **BARC framework and validation are not designed around arid ecosystems, and thus lack sensitivity to low biomass - comparisons of pre and post-fire comparisons are absolute, not relative** -2. BARC is not very useful at low spatial resolutions, due to the BAER minimum delineations being around 40 acres -3. BARC severity classifications vary from fire to fire, and classification thresholds are determined manually using opaque methodology, making spatial and temporal comparisons across fires difficult -4. BARC is not validated with quantitative field data, so while relative comparisons might be valid, it is hard to make accurate **absolute** estimations of vegetation loss post-fire. +#### Methodology -### Best Practice: Relative Spectral Metrics +The process of deriving burn severity metrics from satellite imagery typically involves comparing imagery from before and after a fire event, to determine how 'different' these images are in terms of reflective vegetation. Specifically, these metrics exploit the difference in two spectral bands provided by satellite imagery: -To account for the fact that JOTR and other arid parks exist have relatively little biomass in vegetation compared to forest counterparts considered by BARC, we plan to analyze a suite of relative spectral metrics that adjust the fire severity metric of interest (usually the difference in spectral metrics before and after a fire). +- **Near Infrared**, which is highly reflective in healthy vegetation +- **Shortwave Infrared**, which is highly reflective in burned areas -### Step 1 - Develop Data / Modelling Infrastructure +![Exploting Spectral Response Curves](static/home/nir_swir.jpg) -Inputs: +##### Absolute Metrics -- High frequency, low spatial resolution, highest spectral resolution remote sensing data - - `Sentinel-2`, soon-to-be `LANDSAT` and potentially `PLANET` -- Low frequency, higher spatial resolution, low spectral resolution - - National Agricultural Imagery Program (`NAIP`) -- One-off frequency, extremely high spatial resolution, variable spectral resolution - - LIDAR imagery from previous survey work - -In the context of fire severity modelling, absolute metrics, such as the _Normalized Burn Ratio_ ($NBR$), tend to be more effective for comparison across high and low biomass areas, but tend to be biased low in low biomass areas since the absolute changes ($dNBR$) are small in magnitude according to lack of adjustment. +Absolute metrics, such as the _Normalized Burn Ratio_ ($NBR$), tend to be more effective for comparison across high and low biomass areas, since they essentially describe the _absolute difference_ in reflectiveness of healthy vegetation, regardless of how much vegetation existed there in the first place. $$ NBR = \frac{NIR - SWIR}{NIR + SWIR} $$ -$$ dNBR = NBR*{prefire} - NBR*{postfire}$$ - -To address these issues, some commonly accepted adjustments include the _Relative Difference in Normalized Burn Ratio_ ($RdNBR$), as well as the _Relativized Burn Ratio_ ($RBR$), both of which attempt to adjust the relative change by the reflectance of the area pre-fire, such that fire severity is scaled to local reflectance in each given pixel. - -$$RdNBR = \frac{dNBR}{|(NBR_{prefire})^{0.5}|}$$ - -$$ RBR = \frac{dNBR}{NBR\_{prefire} +1.001}$$ - -Each of these metrics can be derived from satellite imagery, at various degrees of temporal and spatial resolution. The simplest approach would be to settle on one source (likely `Sentinel-2`, as it has the higher temporal resolution than `LANDSAT`), and interpolate values between collection. However, a particular challenge identified in the case of immediate post-fire analysis is the potential occlusion of smoke - most approaches employ some manual approaches to find the best available pre-fire and post-fire images, which could lead to (1) some vegetative cover/biomass bias due phenology between image dates or (2) lag in analysis time in waiting for smoke-free imagery to be collected, since satellites return at best every 5 days. +$$ dNBR = NBR\_{prefire} - NBR\_{postfire} $$ -If we discover that relying on a imaging source is insufficient, either due to a lack of timeliness in imaging after fire (due to smoke or cloud occlusion) or simply due to issues with ground-truth accuracy (described in step 2), we may investigate a method which incorporates multiple imagery sources, as illustrated with `ESTARFM` fusion model employed in _Liu et. al. 2022_. +In theory, this means that higher differences represent larger magnitudes of lost healthy vegetation. However, these tend to be biased low in low biomass areas or areas with sparse vegetation, because the absolute loss of vegetation, even in what would be considered an extreme fire event, is lower than the possible loss of vegetation in a higher biomass area. -### Step 2 - Validate Using NPS, BLM, USGS Ground-Truth +##### Relative Metrics -Inputs: +Relative metrics, in contrast, scale the estimate of fire intensity at any particular point to its own prefire conditions - essentially, the intensity of a fire is expressed as a percentage of how much healthy vegetative signal was lost. -- Remote Sensing data as discussed above -- Vegetation cover data - - Assessment, Inventory and Monitoring data from BLM / USGS, used to train [USDA's Rangeland Analysis Platform](https://rangelands.app/) - - Any and All NPS data that resemble AIM format (_specific fields and aggregations to be discussed with the JOTR/NPS team and through further investigation_) -- Exogenous Land Features - - Soil / Lithography inputs using SSURGO, WebSoilSurvey, etc - - Terrain information using Digital Elevation Models (DEM) or other finer sources - -Using the approach described above, we will perform an initial analysis describing how well each relative metric (using _just_ a remote sensing approach) approximates vegetation metrics derived through ground truth data. An example of this approach, which focuses instead on the _Composite Burn Index_ (CBI), is illustrated in _Cardil et. al. 2019_. - -Depending on the results here, we may discover that one of the metrics above is a good enough approximation of reality to prove useful to the NPS in fire recovery efforts, and thus would move on to step 3. - -However, if we are not satisfied with the accuracy of such a model, another approach to this step would be to use vegetation cover as a target variable in a supervised learning model. Instead of treating the spectral indices as the output themselves, we might use these spectral indices as inputs (potentially including exogenous land features) to directly predict vegetation cover percentage. Such an approach would require more complete and land-cover-balanced vegetation data, but might result in better performance on NPS-managed lands in Southern California. - -### Step 3 – Develop Web Application and Interactive Elements - -Alongside modelling efforts, following guidance and feedback from JOTR staff and NPS stakeholders, we plan to develop an interactive web application which allows managers to visualize, for example: +$$RdNBR = \frac{dNBR}{|(NBR_{prefire})^{0.5}|}$$ -- (1) Point-in-time estimates of vegetative cover -- (2) Trends in green-up and senescence -- (3) Vegetative effects of disturbance +$$ RBR = \frac{dNBR}{NBR\_{prefire} +1.001}$$ -The metrics visualized within this tool will be highly dependent on evidence of utility to park managers, from steps 1 and 2, and are subject to change throughout the design process and/or after project 'completion'. +This means that, between low and high biomass areas, you would observe equivalent $RBR$ values even if the high biomass area experienced higher $dNBR$ values than the low biomass area. ### References diff --git a/src/static/home/map.png b/src/static/home/map.png new file mode 100644 index 0000000000000000000000000000000000000000..1f79ba74624c88b5ba56177bfab93f7e37d61f67 GIT binary patch literal 670 zcmV;P0%84$P)3Tj17@@tm2%SF&ke(^1Q zxyAS+*5h3Ap#hI@2q$V==LROb@pntq>+rVOdcF7~R^lXHH{^JLeQo&LBuW+P#JBx3 zXv-h597mHiQ#t%OiT*}>?wV3aD2lX4C|k>Dy46p7L{TJYkD^EhLOJY4)2)8uBZ?wH zdlW@75XxaUnr`(IA5jzu+M_6vflv;+(R8by_=uuN&>lsR41{vnjiy`u#77iGg7zqi zWFVBoZumAq-G(?;*#^T@WGaoF%QQiIC24ZHI?r32#_9nJ*^PTi+?~Y|_hY3s@kD?9 zaOH=mi9g~3Hf9zwSy^ytV`l2#*{`|TwC?31mfRj(O5ppubK>d_ zY&Yi=P{)=ouC8RR?&`+EFJrACUI0kP&v69HauQ$z&R1Kl!}Ty3cU;}yxQ49;z@V;f z8+2UVHw`)NW3Lfil9?aDC7g)ysxk4B&y(s;qQL)%KP>e@BT2p;(f|Me07*qoM6N<$ Ef-56JuK)l5 literal 0 HcmV?d00001 diff --git a/src/static/home/nir_swir.jpg b/src/static/home/nir_swir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..832b157019d103349daaba97bf2fd33953017a05 GIT binary patch literal 20935 zcmb4q1yo#1v+m#|5Xj)}5OfG6xP{;z+}+*XLU6YL0fGdA!w?1r8zi_xaEIVJxa;Gb z|3CNK|DOBSTW_;guih=Sd-tZQzxt}?apv(i;H9jjj3fX70Rdq2^apsH2Z#YskdTp) zkx-rr6ciLxG)#1~rwbbc;~6Fp8wUr74Fuv6kP+hIk>CS?MASqiX6bTkxn zWE7MXe+WT%nu>;shJ}ufMS%yzqxk>69)ALW=m=bh7Dxy*07M`H5)k3B6F>$)03e_| zt@iH=6#)?m1q~VfsaNeK009XZfP{$t3>^*iY4rd^01`3+3Mvj7kOr3%51)vd&uXY<#`C-)E;VG0{Y)dDFk*RQs**xf@5P~Z1TAnneE4D=~Vq5Ue%VtV6 z+O*52;V5*VGbq8~N(MmCE^)|oiTem(Bo z)&;fl#RM#G5NKB3aU!~(oC~dNLb-Lx1nZ)SYiutx-D<0`G^&E4gWmF&h!2u%bu=w? z`M1Ht8RquHp2P8T^a5A!3)Z{5GzqRd_q$iVtPS(wh-dw7+_C-f2w;ApNbTI9Q|L^I zA0C+IQKMTCa;B}=+EC+j>8UUBdxt?Dj5YIpz<+v~JKogog669IJk>WF!|cb=&Anac z{$RI<-WzfqiXu6K9R4jow!Z3eDXdykhoiY2s=QOX?EFtOdVQ#J<)mpquQmVfBEtiX zdG(Cfr1I^a?>X6gC$)d@>m9Z5$+qb>D>mrR={7Mn%6qz6J&d*NpmB6uRk;nZ@eb6L zxgRiMl)pUyLrhE0jWqPCsaDs^A07dwEOZ|52MiLv>~0A-&I=_}ySObPZZW&YCGZfs zp?uq>SwcPk4a}ECv|W$L1#^q3PQDKU>h9xx&s`l|>~WqZeZ@QD$W6+1Ue-itLVBsG zZ>2Pa13KwwllM8d8aYj$Cc^5D=5{!aX?C806?0t>SGS5ZlyjytRLxXJz8&S)iF7$c z2fMf3-k;+2+FkBd4BNrzuP==$L--|G`9W^r0?VFC`QrhOrUpkh`%|{}-Ms8BAe9GWt1AZ_gr0Q-&%+xjqgSs8(kr3 zE8^m^_Ep2?alyfwQre5jqIGWKnmM7oan2rz83F3LS)nvL*68m}o5wkJq}j}lj_P?W zd94|4ZT=VuB_T0~l0@=}RXA67zYTuwy#HMST{nk@tG6O{nNAf#U+EDaH`V0*4k5DqL9jMwgn-j|E zF;;NfVZ4d_Hh;)+0u$Qe%drTyT*;j2>-mb^%6s-g+&@cWRRlAmw|+56vA;Z=;g@qq zn*sj`j_?juoEC@&Lu^5&3qE~ar>llxX-go}BB?+*uaiyQwX!inR6Edm%zub;uzlwj zNR%UEzc}f!_kd(sbu>=}HPO(R39wTkCpm017#{d?bbq@f5~FL6ThD#AquirOub#Wh z2J7W%*b3j!Gp=L(n|Y=B6 ztKe6TRA`%LMiC6tXgQz2-a4VO79080&z-Mbf*3aRBy08Pbj;Sg_TN>Q^>}qgo~(bu z==zFjM+|0h4F7DVAA8`<^~$VNDvB#(+24+f3&`&fiqeLb%%s`MbIj-plHN|D7(V<1B>sOA%pMJl2 zWeC6YqQfn0q@hCM~LofJq>iJzf%9XZ2yY`I1!09CbxxIIMXe66xpPG za82Jo>1eVvpUXP9FJ-<|l|FW$!B}0SyHMBb$L;c_U@(I{-FdGFi?lD`C82Magc%?A zn@@-xg?hpMAG}_teqUYiij6hB#=KojRm1Tx>P?>akf@Zg%e~53ub8_}e$Y8a+_ChJ z&iqE4TTk>1wX@NIBUrwp9xm2xuO!i3AUY*u|HJW4O#nM6VKuEOx?7LUQG)> zDeVc}UzmnL7Pad2vF^K?#x~MS4}_jw{>*JOdrv<;2`&xrgK3f z3tD35QJfU*e=STYJ8fJYI}7c>Q8(U zls6BVsTJMnbezs`s^)4tIHe@L4-?44#tm(N?xrqoZ1g$uTm)bfMOT4wbu5%?3Hd63 zxVh8ix}K@a@m?0PJYQxl$kEpaQ!sP&X0i32XKfrf%;wD23q|$(XhTfFFeIAK0@C@ z>cZmlZl$eZsqFdL7Vbc}|3OOm)w|U+y&_C2)(WI)bevDRY(U$VmQwWC@!;uVf`~dj zB~UZW-zsQ%#}z%pb#6vOZ_|Dibt0p<-+ojNX=nr(S9M;2nDtyu>3To0VB?%~K}wMgr5X$DWjZU|v}Lc}p)^ zT?j?@4o+9R6NLOS>bhWn?Fa6!tR#Araps7$*-6xJoKSb4(@rw1$-w%B3VAWyaMtxi zT*bZGJR9ZcWAyne9_f|=zb0cuj&{xp572@S+I3ofs8{R&aCJ(-v2H#qHnP{cI<_tg zA|z!WIG8(&ja^MS;9VbrVOkKiP^q9to**=eb^ILDd!9#t64@!Y!2k@*$#N#Nls@_d zM^-9sZ0S!w>YwdMJ-Le4;th9c(jir8P}5aEF|KLVbS`5Zd;IB)KIxHfvB{3Lhb z#r8t>E#pS>%y^I^|Fj~b#e;*Bm5Y_*j>h{;!*bs(X7mt zI`aEE+MxUrGvnF?`XYmCPSciP`ThONN5FpYkG#Ku?;kaG`Y~j=uY^X!W`=RwyG90( z^E~u6qxx^+7EH6IxU5+URiFTq(j;!gc{e~|-rEgXmeK!)^Hq~dshC~V#gWBD6=gG` z8eZy_HULrhm~EKX--$o9{#iVA6-S^)eEOQgh3`UDM6dS8aEH8%vcx||g2nOHOP@NE zpG48nJbx1QGzo9Lr1VKxaarQar$_wR`fn*8Bc4S4zly5#2n6DUi1m|80vT#0)ey#W_>q*|`P zmCSzO6tlS$^4528iX5p(kiEk-o@r`dOHz6{+ zs;m6p*HDH-b-O*g2{U?(;(UDNm?^hJRWvz_o*DK-A!X_M_s4*|`c2TT|7$Zz|* zx*z;mMpz=Q{G4fA4wk-=Hz12Cy8XIqmrK55x})PP!%NNbc#TO6P{MRr@M+ff-$3jm zNyb8#@EG6$1|JUFaIFgaq#}T&0IoND^R^qJU6M5K)x4yLL}C=dTjON3yh( z!Fi5XmO(qS(`PLsSL|8+i|&q5c(33G_%$v>w-Gh^;QKV0XTBJQ5%Uhp^-$};HbbIQ zPQpc@Oi&hj>#pcog0mb3N8`)Il8VZkMK@4fg~l3I|Gu{wQo6}k&Uvdd*P-5zt@eb2 zRYP;e#OF1OZZTksqqZZ?d4uWJ)`|H5A3WRe#xqkf-7ArhS#!_`c+7W&2QL2nVwisr}tgYwp??+2>pDa*^; ztWgwXra}g;M$!ID%6~bvct~%36!z$1qr2RKGU84m{8I!u*YPU{bG?YJI@p>Gn02x4 zlWkGK-thPdnf+P}{gNnT-ip`&Rq9Q3UE!ftqk!GsXa%RSlsU9d`i1kX-ZEhwIaUN= z7HCF3u(xg9>}nZYYCMP*NvOBdJ$@3G>NgnwK3r1lSt}B zcVgj-$vFB#8-gUKnye2iae}p9Zpyk4S<+r2LyzU(UpADY3`D%r_fvf-bYSBb_zFoX(qP>uu+u6_&<(+{W~xGi`Ds8qb(4~i?k7LQ6R`%$v{s^0h!Fy`!f zLP4cd3mOv|6w2~RYaGe!c99BN%;&RDQJDs3F`J&SDTXx67MIBVn`K~9SG1O` zB=7bk!o)o-c-tYS=+1+!Lk;BKKUZmOEE$eYF>7+@gdbdy7n?0@*cXS0&IFu{UhTzP z2P%Nay7@340kyto8IJ(YtzlsW_h0MtzGv~*9_g+$9X4RugmDBo@mLgY;OfOQs~LkP zDE;U;?YN6FZxeo+D@S7bh9^tajZCgf-gEo7qaJifEDKiK%eZVvbosCRZzy$^3m9DmXygN)HX`5+Nu%`Lx?gLlovLrGYrF z?DP)?dk%O3$+jyZ8vIS9YUT1QVZ=L4p@!K0f>oL*`|e+-Gz45|!2 z*IT+6fey8^wGLJVn7bf=%bf}KsrS=39|7qekAUA_liN%G6qs`~joE6dS|H-lzhR)` zh72I5&-> z(iHyFHk!cuV$XgU>4khSc|M|Dgv`Z&iO8lwO!Ozs!A!BHMf-g(w(2$$E|feg#)HLG zN^`W@^G=MTW45mWlMXdJ&!)b3YSWe_h1|{&dKP$mfd?D^nyI^GlIS}0W9SE6X5vCz zh(aCfknJ-VgkL`cUp)EJaQpFu4*_O`e;vouhISXX8_)sw;n%Ii!$&wXdFUv->MpDR^!Q)2LmlftSfl-5LrxNF0&m6{AsD$AxxS1(?~@+D|HV2RNrx->+{g2r3; z^T;UyFqFd=(1oA1@U>Xk*19QOcxp9SweCJaYK2EPjIqM?7xz}_EETv9l1JlJHZlxh zm1Bk=i);Rg6JBOB%#)Q%Iwsthx3gVPepU-7Zh(5!aPT#bm;02Q681Nbk)UO8e=R`A z`Wlt;K@Yvmq7SUKS8(j>QSNHs_JErA>e~KNb90+9q8UHrPY|J2!FLxG+8@00A!aS| z3zz|UVW%cQ-?!?^9;bSy^Fghk=74|-kAk&9#%TqfCEEyxpLP;U^JwbGq?w#4`-a}n zN8eI5a=3glAP{_3f-ZLo^t8--OYCD3|64yxI?ou+wA2ghu_-P;!* zZy515By!eI)s;ebE1*q2Cg5c^PvQ>eCie^;>zZ!U5>K z?W#N%F|wT(T_I=5%;;~C&D1Y7`yT;lU+b6Gnm2kDuF7TwiW7a>b+NA{{MpiU7Mo2~ z;g~WjIp+_Xi?OdGpoxpbm%&rNUiQ8ObZ^M0FF*@NYbX zG&Dc{8&~?Te==;=Q}=quuK^=9FDs;c&Q9;k(hba)?GtDh4w@uPsANGOEf|UH_al8- z3iSJaLi-s$fyqhO-hbYzOZVF|hODVlwRpz-$Y{XKYUj}HB{6DFvVtvJqx~Xk7!i2$ z$~lojZbQ+{JHxF}xP+~7+$0SZ%&lQ)TtsRUoO>{BS=;g>ez1=5pD?R}-le9j*E3f&RS0Zj2FmO>i+2A+TYgO|@SYQ{vdgZ11jtT`t4mY` z$bvk(e$zIXG~Uz!=||O&qD8S~1n5YIG7(Rn2^PC= z;1O_Dl8xQYYD?~tOkbm^o9;cV1_oEou;g)$+w;5w07<+{WR=a=Q;JH{Cg;0c5#9B&g4zpUzG?=voOz}P%i ziv!;*?AB?EU23q7@R?huG2D>CqZ6>3)kxj+EE*m@xToOIW$JDr)R_q|#}dRx`~N2K zDFf@~l$ZH={|PAjb6O&Vr2o@t!O%_UKN29DH9X3oc?6(9K0Gk@b30p-I5fLp>aJJs zI(FcHR}JNWkr~n9^RM$u%4!*;taPtlkw=jQ=xqP`RdPqrqpnZSYaA5~G%PO6t8pe^ z3=5>J$+TcMK7T!F<3hC96d?hgqv-3jSJo>lE&`gcoa0TXMpSQTt2dT?Mx^}5vz*ZCAKh?78!SNRV`>GP& zj$`IEPej$4U(iwzL1WOT%hVU-^`_8Q8v^`BZGP1?^n&k4?7Yljbqyj-N%wdbcbo2d z?aOhSe%cv})2!}$lDzaVf+LXl_HDmtM+-jDLRs<_r=X*+2P}eb0u$6A3Z%$z(Ycyi zoV(Mp4j|LFoIbNThP|kytV70-m&})X?)lDNq}l#K{FHh87wompxb>3kUpA`iB&om3 z?|T_lL{xOf^|Zjsz0zs;=R|D3P&cI%QKwY5p%dJTNIb#!7X& z1|WXIUqb#XJcM3}OFM72>IiiY;&dg?eF=T(;SpXZgMFaqvgcc^%g5u|?2P(4N4iF0 z@>0`nz#hwBe|SElVW2j-^$-}Xir=sFtcOE*Ozuu5hj{KXa)-+D!3_5R7v-zdrma~J zKc0$Hw07)-KIgT3%h>WdL9HWq8vL+v@7?z0oRD;&JwZ`8j)`n{(2Qn@W@;U?idxXR z#3SIW0G)5Ii4Dr%)2(+v@zNJF9nZDo2y{f)SmdnbAm}k;lM!r0B~q{9_O}oIFGpMg zah0Olg_J(u;S`pFcl7zWke_*%=YakvT#`os&wWx`#hzACdF1w4EQ(%n31ksrEQ{$! zVS9dpTRDzGZn4MbvozTO$f((gL^{Foe9J*euQWctI*g?jZ=IZJ>?y>>)XT3vKr!&w zk-y z$+VMgg12>SOGsh)%i-6BM*v(RV=YPt{sl8ysF2i-b3ivD;(AwXPxGQO5Nk`tg9*RA z!qXuu(e+Y;%zdRKV`QYX!4>{S-k&+?3HsBVef<}4At8=0E{+oYq%inqrjuOSK;CGy zvAnAVf6i!F#$}}J6nLJpx>%OY(ZT(4aTH7jgi%C6x>i&9YE(MQtg_dAr0QcII=Phgq# z8te5CKWGZSRov12pUagl3 zm;8Y~g{~nM9w)qZOyoDu$s@fd#k53W_PzL@78bXgt=7UIsE7-7-^R#k)r=33EXbzmBr z_4;pql7Uq@X1j+nsuCk!)vYnkL;ltod9`lrH_E{05fRZC%w7BCrRN{SC5*1|1^br* z%nSNCZ9!as!lhT$#HBvmpuGDDu>R!mi2c~iFb1e zoAQ!RzOF^7kkuKF8dPi5uEYD5If!n{z8x)xrIK-TAAc-ZP&%uo*mY|4gfzH9VLaVr zI)Tyb+GqD4n%F}x1r{eb<7lPJXH&rYqFcvy59hP9=CwKmItT#ZIt}e+USFTS#29oo zjXUqUXw}pWd7g@0R5dBsX}ip^)qvH3aC-r+`{X~XBlHH&3QE@LaTh~beQCA00#SC7 z#{%|Ga!SiY^_riT)6gX7l${^E$e@<~a!_;4qv^=Z&#Sfc7eV`mPlcX1;%1cM{Lb$*i@UR2@Y? z$c-5LT$C4$Ig_|4$vc)osKf6rK3B(GR(A!JkauKZn1ka4S759I`!>y9`ZN zHl5WpFH7>OH1Im8b>u}_qBt3VjA@&Bqzwe?dOqW$ZjrF)gSaT+@0L;Y|JHcF{x72N zKg<;n^h&a7utzsKzo{PRyG^!|QzfZY&>+fb#3C)E&a2L@G6MS*OK3zl7oPEi;T4u6! z7${U|g%E6wo{OXx?I3~cj^2g*eQ_4u4g4ZAvMQ@vCICe-)_fr69>LZgGqN`_e{#jM zlMC;EkV?p}q7EBYwT^T^4JycdzNMqFoNGPFi&UP6T7_Sg-;^6LFonp`M|~KCb|=x-+5Aen^xLIwZ&3Fo z`EGjH#b2YQ`rHT4x^e_hn}((aRwf;KEa@Fuk*P}RF|Pme)R$So_!GgNxRw2-j>D&K zO<~tv*fv5nB14aW8-0LP6P#ytqeUsV0Z5p~SQ-(mkTR#+G_(Fr_!ygeZ$GW~$I1a^ zWO~#i;5=d_|LfGX6v%va9;S*`5bmR3+RSU%dGq3iA%7W$aHG#aU@Xk|4UFVF|tZVZZnlr@U=^Z-r!CyGpoupP0>c?#->AiF{$t zs@bgQB*qNe#D354ioK%CF(Xx|Aq)FOETXOds;;cImmh>{Qe}D}t9!gTkFJ||Mscrx zc0Hxn-rg-^lxUT7Th4t6p7zp64jHpMoH2)*d|Y5uwrKqMkz&>7W?^C!O(e4;N+FiK zLGwCfPa&DS_$@*;{f?tLU&K$TxOs#HJ7lK2vOoEbywX^6Vl@^X24q?J@q^dohpY7) z1MaB5|Kk(^!W^dzlDwV#2s- zVXk&`*{JRM(RdDKc5@*Ne>Q^H-%lqulZ0)`I`iTC$@S_IM~Gay%5=3=L%{h6ef_jI zP>?^HW4KaNC-vua*Po7Lj!G15dv7Qu+4pd%WXRW7GIBfH+BNF)nDTcwW$GH7l>EKL zwdK`nlLk@T1U4;7SDG_%JV2x27?Rl0Z&*26Jq7+2xlIb~;Vkv0kOrQIp=|bsx^jws zXVL|N2?BY^X&XEDeBejf?2Bf@)jF<%0CR<(q$R))?{nDHFgRAHXyepom|Do+`c1?W zmY2#_cQ-F`n6zlmB{urJ-B6OE|JHYk=}Q`?XaJJuFfg#V*fhT*WMqL&>J%x$1`p;+rpW(Zwq=cXJF2EkMIvx?)W>`lQlC48534ShD98cZG2fK}7&>wL*!CKnG0AJtKpXnm^{A97{Lk`uC56bD$5U#DEEt&R*n~KsuQt;R$ z6};jtYWX;Bu6qU%;5Zw}^v=`XB!kPe$KpQXVL}yc52=|&eYcu9FS?pA%RCyV>l&IT z!~^n9jZ$TBzBeQpFf|qJ4dqr|m=qyxuUD;f@g1(N-!Ov9FpJ?bMst41KX-b=%6>(A zl7Ke{FRp~eBqK^Stc%7U0flmT+$L*ouPZq!_{Pc%6X%GU=9WV4nR-jc4J}&vV~=k3 z=p(V#G&!f#3`g<(owYauYX??Ftl>9|U-M|r(wK9xzg@EYc~%+*$9wQRzAEn4n<)wE)9 zj{ROlR!oC211W^8{k+qbHp;pjS}u;4&r|a_x*P2;Qo(2#CkrjoN~@svhvp;3brRq_ z5o>o=UEmK><*sNhXv9SBo+@yUeMuI!q{C}{)l@jd6aIXeoTm_4l+lth9I;`{bFJw0 z24epP!+HM^P))yOc}F&yA>fgMOICO=nK5|RBb$kS^4xE|#*e#(e^Ap=wWMaYpfzU_ zzbuqwEqxi$y)B$p$24{5ZQvXzVg~&MvbAV|u(_Tdl-N(sBW}5LKO&;}^`CL+KR{F| zbML76)H1O)xpnI}ny#Uh1}nwJ8p~^p(0l5*aI3nz&(F09*fXN<=lwgO>}E1XBUjp{ zuLf!&JB!j|+C=DoS?8W6rrW&L8xOyTZ1B5Rt?KiOshr$Yp$nzvJG_s@ zdEN#z6Q{?aKklBdlfO3}dv%dY+>xwwYy#i8dFIf6V1paA;ozkidARa>8nA=Q{<^U+ zSU+`;cqf%7$b|;<`#t<~VUp#YJD&}TvkRPTp9PY48 z!(nAGlc^JMN|EBG20Av)+zu4J3vADoN>rYPS}CO+=3#<~0KgP?67N0r5n6=Jbfpl! zM?lC=P?TRB&WtnS7X4*Y{_19HR8!P55aH)n&MO*mdsQGFLRQyoLHgij`jOA5Q4D>N1tj_^VG(2(?8B_qqM zX*jy8%Q^#fuWU>E{_}FP&ZuT4Cvjv=+RY|=6zBM8bxdQ>@7YbDbfUpzSKg<={r)>b z;&M{%FB{eG4Ck0qx-YGuP}NN5NFt_30B|%f=FOVfaZ#Psz3kLfc5Kd7sds6E`6D2X z=rb+@n*pv*%5I3{1i2p2(z|A9Ixv|c*%s>cBPV!=h@?>uo zRm|c?7ZZ6^AT)$|~1 zVA-QY?3JPIt@(vI`5DXi+fH&#YRUmb#%RW?U~@m`sA!x;Y!q-yi96wS>gk)B!CL*` zSJib@;peF(d-21~*p{g|IG+pSTJXerO;2`bjoONjSXMT&s~*(6PH2t~e4qa!S6;cZ z-SvCB&P>_iE4MEdaOhOv2eNjY+>J{2PSYp1wC-~_eFS`SRtGvhrNmJ7AAj+jX>3sJ ze-{wBa`-%c?ecm`=B&z07rVDJ<5F7f-o}N6j%9(lD;4h&E9-Y@Tc(o0)tB4*?q0=E zMbk-%#%*_g`_UjS30Rs^EjD4QIk*~TerszK(0nk8) zJoXI(zku2Ho%GLksq<;|XGIQ{Ou6&9S<}V7r3xLSIoK7+E}nMUZ(J3{lj{|6*hhVJ zZj1Rd7`vi*y4mBn7fa8HPu!FIxUS#Hev-J~5Ep&*uk(ejPU;JH^dJLQfj8^+Cp3X@ z4ue|VmF|YY*x$}^Ohgfh{G*p4wJOIJRTXC@uTRSqtR`6w;$<)LD)JLnHT5VXF6A1A z!u?E!=1$>CXW@pB;vQ7?WESY9NR!O<_N!G2SMtWH7o1FN(O%CvP6mB1T@0l z$)It$M}Wrr%XlO&-^vH#SC4>FuJH!Vi&vMRmZuQn)K<<`8dK)a81l_WK;9dE+|OsE zDo-=qi(G@#*~)9XP6s~Q_~KwZ<#V9SZ$Cx!L(3{h3{G1wcURzP6s+n6&r|33XiraY zw9z8{q$5Y2R+`eV=i=0&|*F9ixVmeXoI6Toet4joN$v{gJ&1_J-{Hy{6*1ouB+GNk64>WDywj>fQ>wfe6<(=~m`_(&+ z;&k-0q>o1og1z1-3l17ZL^hX338&(wKUzzG_6L>acO)W8opodU@k)77!33`-+|3(p z{d#TUp-vZhDfaE$Ev>1g<;d!6 zd{rzTHomPRR>pF^(PC?{C8MS;-!tUwn1&M&#(-ACW{#&c=;| zAiZr78mhDZvD5#!MNiev*5ZJD^)UkAH@kH ziPs3-Vu#Yhl4fYBekSW^P%ZG*+NKsCsGymoG4@S))c9t zB+=~#{d^F5mS`%oPo$@8*|_r{>n{k=1tFRDK=92p#6FJ=8TQ?v|I-xL^@Ff}e-A&M zMc5(<1ZGi3OCAl|FG0l>t;BJC?-8uo)!Q!MSu=jmD0FlcY?ShaFABO6@Yb%B0pAEp z>Fa3DCMe$R;EkZQCePmwfA)>WArIAb2}oa5vv+;d^286xVPqejfpA?j<%zp+xJucE z?dn1C{-^UQh)9p25S^$hpBhL&u44lW%Xg7S zj$4JG2RO59n?W}=?OWa0=FGlm2RUAW{lSo@tlIb`a1z_aSn73EefkAT(d$EZRMz zn;D}iU z$Is8z&dqxM%!s3(%kC7Ll77Xz_0swF9zLL6{=H>S(muI4$io-gx5S6mqE<$;&%?^% z_Jlf8{RB1Qqm6Ll{M?5)gsKFuTC=1q+|CDbMx8h|KdUq=14!bLMzEf&=$G+YmapLH zxp^q)1Y1&|Gsfmd6C1%p0dWDJPjU^JhU79F&U#hoA7|F`l6w~9NDiI}n%c!L_7#Ia z4>t9Aka2$Fa2PIs=ATl&g1)f1P&nA)_14;n^@T(%c@A8QYDQmlGIx`k_?ry_gB_3Q z>+tbqs>3H(RLf+)7#lfsQp2k-G+zXvK6KT~1Wy0FGJOP$$UkL|*(`K`>Y}@iIVW9? zq~CzBtGsOI5q8M~$AVkdF1>gL-r7Q;2Xl0Vmsa=R3?6J#S5JqBe#)6eaP5~)Sj^cK z3$>MHz%SKYjyr8*Ke^rU^^FWPk~V+l>qZ*=5s9_DDY}^Gyd$WcXxE?hLj>pT5dip1 z9=7B;!|rVZ5oRAL_@qBfC)@$mQrQf_+K-NsS`%F5UEc_iNqnd=+Je?L z`pUgnj!d`9L`q`&#a7P0^6T9t%{m>a>UD8&ecr}Q(MtzKV@Tnm5rST>a0gZdq0B{r zD2`599U(jBVqr(HvGMN+A706WW{KL?9nslDmwgPD#zo#w&XoS`q1Dx@AV%B&TR36* zc~stYBQ~+i*4}Y2X5f7R9K>cWwkLK+Kf(in64^GdY&`;s-TdkgZh(Zt@MB+Ap6#Vy zPZ{qr<+BG;Z_=#pjORCv{SJ!ewA_|F3yP*zN7g+k+^>_b7^w}}k+7jh-A{DG3L|u0 zsP`K@e>D2CrfEO0=7ubFjv??>b@1$LwYPo>;ov2hYvU+dqZ=XMBK1f8b;kIuK;R0y zsa#oMQSZ6+EfXAL`m=a|V`%`SyWMsSTAB z@i_xgGcG!5hu9FHpYw)9#yzR+ZAiA7cI9YoYrQO&p;VbTKTYk;~1PQ(6} z4U5!D45DeWtg$Gg6bBOqnT5FanMoXh&s0|x5P)v^t+_3YeHP9C5p3c?f(x0~>!O$fy*LZyhVw22KBQ6-BHx6reA{c9j=w`s5pv}>g zHZjmnsl(7n3k1$W8{IVIlU{q!8%GVmaNHq>h2*!W5m$~*wbn}uX9c2FweoK8NnHv= zEGT9beaQk&1g2fEtJ!Ic4Xwgm^DPt?KfeE<)`+cRyJ*Pjt!&XMIrp&gDuuxI%)avJ zRlYm7kj?D78YXp|<}7iD+tG>C{}}ZN+K)P$f6eNHyeGe@?b*)8N(ziC+NJc8n=|CR z{-=y%JH+LLQ2PK%7;uPnGZXBsuSQK_xM3C_vCc~&wW4SiodQ~W1oECWZfLW!GMQnt4&Nyz$AGpFIJ3V0 zqR0H4At{G7Da=+jVzsRmz^v{V3C~WJvT2Vs@p#F>~l7 ze4vVHeu5P(rhiTT`z4e+I@y5sm-TEOi?JKy9-42?W8B&NQ|tsehX^(t>R+>C{Gz_X z_4mJMPQFMRSo*gr$o!=n!8@#h;U1*iqH>+48EdhLpDpwQE%FmD&nPv`8=-3&1HL@m zUvQ_^Z{z!B8uq1@44MA95k(dzuXlnq2|`xhkk_6Y4c1b#)l#0aKXW?hampa{?I5rC z6&dXIvjGe4>COICu(;ar4}DGUkLJ4Jo_ac=G+kIqje)N;zOIw-@))#t-bK+MrVXESCqTM;kN@JgzeE>MGyM+w;|sNo(LxfeFhAr&vIS|0=BbB zSVPIbF;!nS>N+1xHmDAZSeYGqR#Wd%nnAVmk6MX|?$cxH$ho`Y8WqopvC9oEvz%N1 zVKkh&kEU3!-=s+{UZv*6NPpWyx397{tSMyqUkuCyV~fFGDw^|etxxcO|FSgURgM1BJ1P9Nor@y`KN4S)R(1Bfif)geWbI#_t%LeGoK-(_mXYbNis=$|SpCJGa)LXVo=nucYSb&ayU=sqd|Cp_!j%C$j$N;*cqo19sA z?Z{XT^!UApEH6o^zXZEghrux(u z-?{kV#f(Q$CA3!~X{E#0ZK&?c6@34f+C|jYR-3N)866VEI6MrLbD8A z@;Re(zmF_G4bjR2@*e>)Bex$LH>CtCJcy@XI@U0jlh$C{>M)vzzi#r%yE&f{R84e< z!ugm#)tK-F_#yDq*^yCKaG!prE&LH+rMVIV*(Asb&KB`!QYkP1%M%GJykJC_6g^LC z%t7(f-Fq>8fVd>Ks(-$zWf7L;w}SFOtz zLiBeH@gg&g*Vz{GseRtfw*yhvEGElZ4$EQB)+?g@jbQ;q?B6B}7FF`MD(UT>#LpkK zX%b%v&*&sN1+PViB`%U<SbYr=V{ zvsq&&!cs*J`(TCEWH>bTNof5#{+>(Zs7)}3<0-UL^gzTkf(c>Leo->LB^|S#Kn*9c z&VLSt3Vf$4WaO<(-V`}|qLDg~PyBiwGIFkrU@BCrElOV=0pqHV08HGzQ_35gz?DyM z>ENhq;U|Qupmg_I>D6H1PRS!63i98~G`aM9<^S|@T~SS7TXaw{Xb4pa5<>~7Lq`UPbdi7*5fKq3kZ1y^)DR&cL~uZg zR6*%gI*thtilKv`AWceW35Z0RkkAbv$UJ7f#anN^^7-EPx%YnTz0X;9-E;S;Y`JmS zV8L!{lDq!yD7*GHsL!O$#c(Wm9&S(nx-E^RmI=W0@nmv?Ow@i)hEei>O*C(?hj?HG zb6%P(m9>#fs=cGDMs;a|*#}tZB$j)=fqS-cZ>an@d5ySw0P4AfYQ^ti?5#VE;Rp$x zNB@p_Kj4$N+34G0$lIylgD`O3+hD!@U}x4r_y<(r|9h}qbgpBEhURq=TV*<_HN|R9 zjq7ItIu*bh@TDROcojSY8p$``Q9(t+pH!P(*my`-#<{iM9id3vcsV?hIRSt)^}M9X zk{Ld%d<5()v;_Z+F)`*+bg#y!k+r0++}2;97NDut5&An6KEjq<{||%~O~K51-;NZgZIP(A)PxA3pYI;aPmMfS^p3V*yU!j4*+}+$R+JU72wgqvqGL=<6uo5 zE*`-31;Y62HKO6_iE8%JWdeDD=1cJTo$p(A4`sx0jBM#tr>BHu|Il(4#1Jwjr38P< z1{6!74(Rv?TW@f~KjkIMUenS{8U3mN;>@$FA*9;#P~E4|+66t{67ep%>273p8O?c#bT&>4AlxtWUaMGt-_7e?2j+KkUJQCS@%(l^>~H*cukj5tTcI4v4;IJmD*27X1K?-4S@v{oA0HkFdV> zyh0dSK(}usAstIdQXuTNNUC5M`5RoYjUl&wE<3aRKMzp5iXNOKgVs4WZn$$fON7Tw*jvWBDuo|xYBRkkgsnowUF=bPw9aIu3Z%C4w2{@k+h+x5gk zz3FZEvFjbCBa=#G!-?Dkw?>1+bO&_Hc}r2p5o2|N^>s&ts33^*3%cSblGFXfd5$?=yGtXLzEdos_UxhcEn^6KcS>$(fX>>V;7`ZFFvb_zAD zn^^X&WS61kke?cn8!sA{D^tWR6OvEw07|XJ!(nVWS)<>^UU7+MUL%xTP|1uvQ>e(a zcgBPsQ5RHfd|b1c90>=q`(stgAEu0Zo$WPLn|br%(5T5vfI8FI5Su#nO8*CBxjSl< zWB7**ckxLeTVb0JlyEWY3U?muZ8STE7a0pF;>SOKrukQhfi$)*!1ZvJ&$F@{y$923Bjjg|tsS!8l1i zkKgKW8>ol0yTvy--JM@*$Kv1}wNVq_3^vkePB)D`Imd2>Px?H0t`%Q}di`jd%q9P< z=Dn(F3RokIHz3;YGJhG=(rYxz@tt3I2Ku)k;aH>7yXVJZg!Oo}FYVu!jRQFy+Pp-W z-HYE7d={>xkmF!t8d5uTO*O7vVMAVTQ4gi5H(lo{D93}5v9*0ZBt55g?5acrq02s? zr&Y$;RW&nFz;49=WI_I>eD%zGxBmIk#NOF4?c;+|+iI+*fpCwf62@Pvi-*nCTyN;s ziIV0aijmuKrgm6^nN1wJGobK8cS>y_(?48GXnKEmNyW4EuShajkd3aPA(fgD zF)_fsySeo)!`yoCSwg%rL~#5=XvqV;}LPcFwe`R;*5b~nO~8_nvzAhm)Y9q=;}FZ0lQQ<`V9h`aT-^ja75cF*JA{#v zF_=3XYSEs`Z^qHiaDorN zO!z6b zE15MN5z;XUz-B7X$)BNi63iW*T$l&B^bs<)1BYHnO`UMfa#W3zOd33LzRsCDe)@4z z?e-o5aVk8_=u)RUHsMM^?Yi|jEaz~agPvFEiglx?UBQ^wowsJSHTZlyE6wXeC`F(s z$KOi8JpM1wH#o};mk8P-KkV;2+m+m72a}vjTH<9U?KGs#iBn2o1D~T4?U#KU^gq|L zaF(JXi>GJDCqhjd!6vc#JtWTD1i~OO7KBkS&ks9lPR!T&>fX1fSX-+Tl_lDaiCk}WCEpi0gQDY^@ zjadn7;d3Ipo!T^sLyEB=XR~Lh?w{fnhLwb|8pQr?QgG5Vz1A}V(j;>B@xmIyV9JxL z0_6er18Y%tcy(rw62=WA4dqp2>bP2ap(#8&Klq$w;Ge&ESIzWBM5y98!wL>cn}>)K zeiVzrKD-3bNIaIynQ_iA>!_igo zZ<5)uyKf^Ko)3^YStYi!xiEh2k&vWs)Ha}XatTIoncrME&#=nHs5fsXvEqWd#|kU; zL+N!oBpq`E*0*+_uyGv5kDm4D5qh66CTZ{ZAryZUIuH{RAk2R&$iI2=SlZj$tHIUN zs(GfB!c~C!)4(b=@DCRo!6PGzZDRG)em_6%gfz1&PtO<}$NQ$NEnK@`FIs)M?v{t* zXD32mI{xL>tF7Ju|DXoJqidIe?#nCmqbk1O01>6ba%7HlPbVki`+gxTzy>Fo;-%m3 zum4$(N?7;OKA)p;c|C9%A%1^Xp!&2asVN{~F^i{-0R45ULQQDSERykCt5~_Vl$)xi z-ZB4s8A&>#I`4}IGl2sUA)fCV#Vew?2bkZPHUn3vEf8O%0E@vjtycJl&7CUEc*Y&< zayQF;B|ocK5oAjL-FqavBO+uh0+%*)_tXH^-Z7m*Dn;P)56_p9c6JwKqUAJnb~Dms z=B^?Ikx^D>+ik^iTuDKDRsv>Qw!D}xNyE+C1Fx=c5Ub>d%;Nt(3ee7BsIzq3D%o?( nVAKyaQId=K%3GkVQEHpACQPp6LoFAj!QxUD<|Fch{v7)oLLkB| literal 0 HcmV?d00001 diff --git a/src/static/home/upload.png b/src/static/home/upload.png new file mode 100644 index 0000000000000000000000000000000000000000..c32f567ee1aba9aadc099d571d0515976ca45c80 GIT binary patch literal 587 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#S9GG!XV7ZFl&wk0|Vn< zPZ!6KiaBrR_;XoL8w`$Kl^+bziYA z>TGDK&11LD^ti|ev6otxG$-u+Ec&pLY06cNtx8kk^ww_Id+oDl^CQ(g`xh0n?C@Kc zv$x1{{oIwJ_rrHHzPnwuUf1OEomsx7m$x&hf795QGjC%Wuj%pa6B&+AlB=7Mz9!Q7 z?8ib*cEtl?GB>`bRlPq@I`JwGpAQQ=+rdV@f&>AX8612(P0X?n4k%a{2+ByXuv;By zbSy|vvM}J_D`{YEesI7Ar0fL)NJpp1(*wR2rZ8{5A;q4y(Uk8F3kwb%+eB`jSW8!}h#+lQ;GC8Q$xMXX^e>P5-{| z*24>jexJDWNxxdr`TCC?fkw{ON0-}Xl*K+iA6Lx;ObrYS3k>2-xMNrk*w{LQxSp Date: Thu, 8 Feb 2024 22:15:28 +0000 Subject: [PATCH 085/175] Dev notes and how to --- src/static/home/home.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/static/home/home.md b/src/static/home/home.md index 4b2f662..8e5708d 100644 --- a/src/static/home/home.md +++ b/src/static/home/home.md @@ -17,7 +17,36 @@
-#### Introduction +### How To + +Begin by visiting the [Upload](/upload) page - here you will be able to enter: + +- An area of interest, either: + - A shapefile (containing `.shp`, `.shx`, `.prj` and opitonally, `.dbf`) defining a fire boundary + - A rough area of interest, where we attempt to derive the fire boundary using the `RBR` metric defined below +- Pre-fire and post-fire date ranges (approximately 1-3 weeks before ignition date, and 1-3 weeks after suppression) +- Fire event name +- Affiliation (for file organization / naming purposes) + +After submission, the tool will: + +1. Upload the burn boundary (or derive it using your defined AOI) +2. Collect satellite imagery for your selected dates within the burn boundary, producing fire severity metrics +3. Attempt to fetch [Ecological Dynamics Interpretive Tool](https://edit.jornada.nmsu.edu/) dominant cover information +4. Attempt to fetch [Rangeland Analysis Platform](https://rangelands.app/rap/) biomass data + +If all four of these processes succeed, you will be presented download links for all of the analytical products generated, including an interactive map visualizing the results. If, at any time, you'd like to access these again, you can simply visit the [Directory](/directory), where you can select your affiliation and fire event name once more to access these files again. + +#### Developer Notes + +As this is a work in progress, there are a few things to note regarding usage of the tool: + +- Pages take a few seconds to load upon changing pages - this is expected +- Especially on first map view, the burn metrics may take several seconds to render properly. This can typically be resolved by refreshing the page, or switching between the `Continious` and `Categorical` burn severity layer, which will trigger a re-render. +- The combinaton of `Fire Event Name` and `Affiliation` _must be unique_. If you re-submit a request using the same `Fire Event Name` and `Affiliation`, you will overwrite the existing products within the server. Please feel free to do so if you've made a mistake or if you are experimenting! +- Within the map view, the left side legend may be obscured on some screens - this may occur if your burn area is sufficently rich with cover type information and is a known issue. You may be able to resolve this by zooming out within your browser. + +## Background This is a very early development prototype of a _Wildfire Severity and Recovery Tool_, devloped by Eric and Wendy Schmidt Center for Data Science & Environment (DSE) at Berkeley in Collaboration with the National Parks Service, meant to gather feedback from interested collaborators and users. From 4af690671987c22df84c0077e18043bb70e56956 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 8 Feb 2024 22:21:09 +0000 Subject: [PATCH 086/175] mailto --- src/static/home/home.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/static/home/home.md b/src/static/home/home.md index 8e5708d..5753eb9 100644 --- a/src/static/home/home.md +++ b/src/static/home/home.md @@ -46,6 +46,8 @@ As this is a work in progress, there are a few things to note regarding usage of - The combinaton of `Fire Event Name` and `Affiliation` _must be unique_. If you re-submit a request using the same `Fire Event Name` and `Affiliation`, you will overwrite the existing products within the server. Please feel free to do so if you've made a mistake or if you are experimenting! - Within the map view, the left side legend may be obscured on some screens - this may occur if your burn area is sufficently rich with cover type information and is a known issue. You may be able to resolve this by zooming out within your browser. +If something breaks, or otherwise goes awry, we will receive logs of your session and will hopefully be able to resolve your issue - feel free to reach out to [mweltman-fahs@berkeley.edu](mailto:mweltman-fahs@berkeley.edu) and/or [npg@berkeley.edu](mailto:npg@berkeley.edu) with any additional context you think may be useful! + ## Background This is a very early development prototype of a _Wildfire Severity and Recovery Tool_, devloped by Eric and Wendy Schmidt Center for Data Science & Environment (DSE) at Berkeley in Collaboration with the National Parks Service, meant to gather feedback from interested collaborators and users. From 47a40189b08180a14f1f6cc0612a73f1f2adf5c1 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 8 Feb 2024 22:26:09 +0000 Subject: [PATCH 087/175] make geology the eg --- src/static/home/home.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/home/home.md b/src/static/home/home.md index 5753eb9..d87290d 100644 --- a/src/static/home/home.md +++ b/src/static/home/home.md @@ -11,7 +11,7 @@ Directory

Directory

- + Example Map

Example Map

From 49f6dcf79adc3e57a03422400c619ad2cb8186b4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 18:08:42 +0000 Subject: [PATCH 088/175] re-org to put app within src --- .deployment/prod.Dockerfile | 2 +- .vscode/launch.json | 31 +++++++++++++------------------ app.py => src/app.py | 26 ++++++++++++++++---------- src/routers/__init__.py | 0 src/routers/debug/__init__.py | 0 src/routers/debug/health.py | 10 ++++++++++ src/routers/dependencies.py | 8 ++++++++ 7 files changed, 48 insertions(+), 29 deletions(-) rename app.py => src/app.py (98%) create mode 100644 src/routers/__init__.py create mode 100644 src/routers/debug/__init__.py create mode 100644 src/routers/debug/health.py create mode 100644 src/routers/dependencies.py diff --git a/.deployment/prod.Dockerfile b/.deployment/prod.Dockerfile index 57016f3..dda9a86 100644 --- a/.deployment/prod.Dockerfile +++ b/.deployment/prod.Dockerfile @@ -26,4 +26,4 @@ SHELL ["conda", "run", "-n", "burn-severity-prod", "/bin/bash", "-c"] EXPOSE 8080 # Start the REST API w/ the new environment: -ENTRYPOINT ["conda", "run", "-n", "burn-severity-prod", "uvicorn", "app:app", "--host=0.0.0.0", "--port=8080"] \ No newline at end of file +ENTRYPOINT ["conda", "run", "-n", "burn-severity-prod", "uvicorn", "src.app:app", "--host=0.0.0.0", "--port=8080"] \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 9ac3039..8d397b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,19 +1,14 @@ { - "version": "0.2.0", - "configurations": [ - { - "name": "Python: FastAPI", - "type": "python", - "request": "launch", - "module": "uvicorn", - "args": [ - "app:app", - "--host=0.0.0.0", - "--port=5050", - "--reload" - ], - "justMyCode": true, - "envFile": "/workspace/.devcontainer/.env" - } - ] -} \ No newline at end of file + "version": "0.2.0", + "configurations": [ + { + "name": "Python: FastAPI", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": ["src.app:app", "--host=0.0.0.0", "--port=5050", "--reload"], + "justMyCode": true, + "envFile": "/workspace/.devcontainer/.env" + } + ] +} diff --git a/app.py b/src/app.py similarity index 98% rename from app.py rename to src/app.py index fde62d6..3faf46b 100644 --- a/app.py +++ b/src/app.py @@ -30,6 +30,8 @@ from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles +from src.routers.debug import health + from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers @@ -46,25 +48,29 @@ from src.lib.query_rap import rap_get_biomass app = FastAPI() +app.include_router(health.router) + cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["Cloud Optimized GeoTIFF"]) add_exception_handlers(app, DEFAULT_STATUS_CODES) - -logging_client = logging.Client(project="dse-nps") -log_name = "burn-backend" -logger = logging_client.logger(log_name) - app.mount("/static", StaticFiles(directory="src/static"), name="static") templates = Jinja2Templates(directory="src/static") ### HELPERS ### +# def get_cloud_logger(): +# logging_client = logging.Client(project="dse-nps") +# log_name = "burn-backend" +# logger = logging_client.logger(log_name) +# return logger -@app.get("/healthz") -def index(): - logger.log_text("ping pong") - return "Alive", 200 +# @app.get("/healthz") +# def index(logger = Depends(get_cloud_logger)): +# logger.log_text("ping pong") +# return "Alive", 200 + +# app.get("/health")(health) @app.get("/sentry-debug") async def trigger_error(): @@ -82,7 +88,6 @@ def check_connectivity(): logger.log_text(f"Connectivity check: Error {e}") raise HTTPException(status_code=400, detail=str(e)) - @app.get("/check-dns") def check_dns(): try: @@ -119,6 +124,7 @@ def init_sentry(): logger.log_text("Sentry initialized") + ### API ENDPOINTS ### class AnaylzeBurnPOSTBody(BaseModel): diff --git a/src/routers/__init__.py b/src/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/routers/debug/__init__.py b/src/routers/debug/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/routers/debug/health.py b/src/routers/debug/health.py new file mode 100644 index 0000000..ce80c6a --- /dev/null +++ b/src/routers/debug/health.py @@ -0,0 +1,10 @@ +from fastapi import Depends, APIRouter +from ..dependencies import get_cloud_logger +from logging import Logger + +router = APIRouter() + +@router.get("/health") +def health(logger: Logger = Depends(get_cloud_logger)): + logger.log_text("Health check endpoint called") + return "Alive", 200 \ No newline at end of file diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py new file mode 100644 index 0000000..4edd31c --- /dev/null +++ b/src/routers/dependencies.py @@ -0,0 +1,8 @@ +from google.cloud import logging + +def get_cloud_logger(): + logging_client = logging.Client(project="dse-nps") + log_name = "burn-backend" + logger = logging_client.logger(log_name) + + return logger From ff1aaa6e17cf89e1e1e4bca8c92e3616821933d1 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 18:29:34 +0000 Subject: [PATCH 089/175] refactor debug and dependencies --- src/app.py | 1035 +++++++++++------------ src/routers/debug/check_connectivity.py | 20 + src/routers/debug/check_dns.py | 19 + src/routers/debug/health.py | 2 +- src/routers/debug/trigger_error.py | 10 + src/routers/dependencies.py | 38 + 6 files changed, 577 insertions(+), 547 deletions(-) create mode 100644 src/routers/debug/check_connectivity.py create mode 100644 src/routers/debug/check_dns.py create mode 100644 src/routers/debug/trigger_error.py diff --git a/src/app.py b/src/app.py index 3faf46b..39bccd9 100644 --- a/src/app.py +++ b/src/app.py @@ -12,7 +12,6 @@ from markdown import markdown from pathlib import Path # For network debugging -import socket import requests from fastapi import HTTPException @@ -30,7 +29,12 @@ from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles -from src.routers.debug import health +from src.routers.debug import ( + health, + trigger_error, + check_connectivity, + check_dns +) from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers @@ -47,8 +51,9 @@ ) from src.lib.query_rap import rap_get_biomass -app = FastAPI() +app = FastAPI(docs_url="/documentation") app.include_router(health.router) +app.include_router(trigger_error.router) cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["Cloud Optimized GeoTIFF"]) @@ -57,571 +62,509 @@ app.mount("/static", StaticFiles(directory="src/static"), name="static") templates = Jinja2Templates(directory="src/static") -### HELPERS ### -# def get_cloud_logger(): -# logging_client = logging.Client(project="dse-nps") -# log_name = "burn-backend" -# logger = logging_client.logger(log_name) - -# return logger - -# @app.get("/healthz") -# def index(logger = Depends(get_cloud_logger)): -# logger.log_text("ping pong") -# return "Alive", 200 - -# app.get("/health")(health) - -@app.get("/sentry-debug") -async def trigger_error(): - __division_by_zero = 1 / 0 - -@app.get("/check-connectivity") -def check_connectivity(): - try: - response = requests.get("http://example.com") - logger.log_text( - f"Connectivity check: Got response {response.status_code} from http://example.com" - ) - return {"status_code": response.status_code, "response_body": response.text} - except Exception as e: - logger.log_text(f"Connectivity check: Error {e}") - raise HTTPException(status_code=400, detail=str(e)) - -@app.get("/check-dns") -def check_dns(): - try: - SFTP_SERVER_ENDPOINT = os.getenv("SFTP_SERVER_ENDPOINT") - ip_address = socket.gethostbyname(SFTP_SERVER_ENDPOINT) - logger.log_text(f"DNS check: Resolved {SFTP_SERVER_ENDPOINT} to {ip_address}") - return {"ip_address": ip_address} - except Exception as e: - logger.log_text(f"DNS check: Error {e}") - raise HTTPException(status_code=400, detail=str(e)) - - -### DEPENDENCIES ### - -def get_cloud_static_io_client(): - return CloudStaticIOClient('burn-severity-backend', "s3") - -def get_manifest(cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client)): - manifest = cloud_static_io_client.get_manifest() - return manifest - -def init_sentry(): - sentry_sdk.init( - dsn="https://3660129e232b3c796208a5e46945d838@o4506701219364864.ingest.sentry.io/4506701221199872", - # Set traces_sample_rate to 1.0 to capture 100% - # of transactions for performance monitoring. - traces_sample_rate=1.0, - # Set profiles_sample_rate to 1.0 to profile 100% - # of sampled transactions. - # We recommend adjusting this value in production. - profiles_sample_rate=1.0, - ) - sentry_sdk.set_context("env", {"env": os.getenv('ENV')}) - logger.log_text("Sentry initialized") - - +### DEBUG ### +app.include_router(health.router) +app.include_router(trigger_error.router) +app.include_router(check_connectivity.router) +app.include_router(check_dns.router) ### API ENDPOINTS ### -class AnaylzeBurnPOSTBody(BaseModel): - geojson: Any - derive_boundary: bool - date_ranges: dict - fire_event_name: str - affiliation: str +# class AnaylzeBurnPOSTBody(BaseModel): +# geojson: Any +# derive_boundary: bool +# date_ranges: dict +# fire_event_name: str +# affiliation: str # TODO [#5]: Decide on / implement cloud tasks or other async batch # This is a long running process, and users probably don't mind getting an email notification # or something similar when the process is complete. Esp if the frontend remanins static. -@app.post("/api/query-satellite/analyze-burn") -def analyze_burn( - body: AnaylzeBurnPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) -): - geojson_boundary = json.loads(body.geojson) - - date_ranges = body.date_ranges - fire_event_name = body.fire_event_name - affiliation = body.affiliation - derive_boundary = body.derive_boundary - derived_boundary = None - - sentry_sdk.set_context("analyze_burn", {"request": body}) - logger.log_text(f"Received analyze-burn request for {fire_event_name}") - - try: - # create a Sentinel2Client instance - geo_client = Sentinel2Client(geojson_boundary=geojson_boundary, buffer=0.1) - - # get imagery data before and after the fire - geo_client.query_fire_event( - prefire_date_range=date_ranges["prefire"], - postfire_date_range=date_ranges["postfire"], - from_bbox=True, - ) - logger.log_text(f"Obtained imagery for {fire_event_name}") - - # calculate burn metrics - geo_client.calc_burn_metrics() - logger.log_text(f"Calculated burn metrics for {fire_event_name}") - - if derive_boundary: - # Derive a boundary from the imagery - # TODO [#16]: Derived boundary hardcoded for rbr / .025 threshold - # Not sure yet but we will probably want to make this configurable - geo_client.derive_boundary("rbr", 0.025) - logger.log_text(f"Derived boundary for {fire_event_name}") - - # Upload the derived boundary - - with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: - tmp_geojson = tmp.name - with open(tmp_geojson, "w") as f: - f.write(geo_client.geojson_boundary.to_json()) - - cloud_static_io_client.upload( - source_local_path=tmp_geojson, - remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", - ) - - # Return the derived boundary - derived_boundary = geo_client.geojson_boundary.to_json() - - # save the cog to the FTP server - cloud_static_io_client.upload_fire_event( - metrics_stack=geo_client.metrics_stack, - affiliation=affiliation, - fire_event_name=fire_event_name, - prefire_date_range=date_ranges["prefire"], - postfire_date_range=date_ranges["postfire"], - derive_boundary=derive_boundary, - ) - logger.log_text(f"Cogs uploaded for {fire_event_name}") - - return JSONResponse( - status_code=200, - content={ - "message": f"Cogs uploaded for {fire_event_name}", - "fire_event_name": fire_event_name, - "derived_boundary": derived_boundary, - }, - ) +# @app.post("/api/query-satellite/analyze-burn") +# def analyze_burn( +# body: AnaylzeBurnPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) +# ): +# geojson_boundary = json.loads(body.geojson) + +# date_ranges = body.date_ranges +# fire_event_name = body.fire_event_name +# affiliation = body.affiliation +# derive_boundary = body.derive_boundary +# derived_boundary = None + +# sentry_sdk.set_context("analyze_burn", {"request": body}) +# logger.log_text(f"Received analyze-burn request for {fire_event_name}") + +# try: +# # create a Sentinel2Client instance +# geo_client = Sentinel2Client(geojson_boundary=geojson_boundary, buffer=0.1) + +# # get imagery data before and after the fire +# geo_client.query_fire_event( +# prefire_date_range=date_ranges["prefire"], +# postfire_date_range=date_ranges["postfire"], +# from_bbox=True, +# ) +# logger.log_text(f"Obtained imagery for {fire_event_name}") + +# # calculate burn metrics +# geo_client.calc_burn_metrics() +# logger.log_text(f"Calculated burn metrics for {fire_event_name}") + +# if derive_boundary: +# # Derive a boundary from the imagery +# # TODO [#16]: Derived boundary hardcoded for rbr / .025 threshold +# # Not sure yet but we will probably want to make this configurable +# geo_client.derive_boundary("rbr", 0.025) +# logger.log_text(f"Derived boundary for {fire_event_name}") + +# # Upload the derived boundary + +# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: +# tmp_geojson = tmp.name +# with open(tmp_geojson, "w") as f: +# f.write(geo_client.geojson_boundary.to_json()) + +# cloud_static_io_client.upload( +# source_local_path=tmp_geojson, +# remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", +# ) + +# # Return the derived boundary +# derived_boundary = geo_client.geojson_boundary.to_json() + +# # save the cog to the FTP server +# cloud_static_io_client.upload_fire_event( +# metrics_stack=geo_client.metrics_stack, +# affiliation=affiliation, +# fire_event_name=fire_event_name, +# prefire_date_range=date_ranges["prefire"], +# postfire_date_range=date_ranges["postfire"], +# derive_boundary=derive_boundary, +# ) +# logger.log_text(f"Cogs uploaded for {fire_event_name}") + +# return JSONResponse( +# status_code=200, +# content={ +# "message": f"Cogs uploaded for {fire_event_name}", +# "fire_event_name": fire_event_name, +# "derived_boundary": derived_boundary, +# }, +# ) - except Exception as e: - sentry_sdk.capture_exception(e) - logger.log_text(f"Error: {e}") - raise HTTPException(status_code=400, detail=str(e)) +# except Exception as e: +# sentry_sdk.capture_exception(e) +# logger.log_text(f"Error: {e}") +# raise HTTPException(status_code=400, detail=str(e)) -class QuerySoilPOSTBody(BaseModel): - geojson: Any - fire_event_name: str - affiliation: str +# class QuerySoilPOSTBody(BaseModel): +# geojson: Any +# fire_event_name: str +# affiliation: str -@app.post("/api/query-soil/get-esa-mapunitid-poly") -def get_esa_mapunitid_poly(body: QuerySoilPOSTBody): - geojson = body.geojson - fire_event_name = body.fire_event_name - mapunitpoly_geojson = sdm_get_esa_mapunitid_poly(geojson) - return JSONResponse( - status_code=200, - content={"mapunitpoly_geojson": json.loads(mapunitpoly_geojson)}, - ) - # return polygon_response +# @app.post("/api/query-soil/get-esa-mapunitid-poly") +# def get_esa_mapunitid_poly(body: QuerySoilPOSTBody): +# geojson = body.geojson +# fire_event_name = body.fire_event_name +# mapunitpoly_geojson = sdm_get_esa_mapunitid_poly(geojson) +# return JSONResponse( +# status_code=200, +# content={"mapunitpoly_geojson": json.loads(mapunitpoly_geojson)}, +# ) +# # return polygon_response -class MUPair(BaseModel): - mu_pair: Tuple[str, str] +# class MUPair(BaseModel): +# mu_pair: Tuple[str, str] -class QueryEcoclassidPOSTBody(BaseModel): - mu_pair_tuples: List[MUPair] +# class QueryEcoclassidPOSTBody(BaseModel): +# mu_pair_tuples: List[MUPair] -@app.post("/api/query-soil/get-ecoclassid-from-mu-info") -def get_ecoclassid_from_mu_info(body: QueryEcoclassidPOSTBody): - mu_pair_tuples = body.mu_pair_tuples - mrla = sdm_get_ecoclassid_from_mu_info(mu_pair_tuples) - return JSONResponse(status_code=200, content={"mrla": json.loads(mrla)}) +# @app.post("/api/query-soil/get-ecoclassid-from-mu-info") +# def get_ecoclassid_from_mu_info(body: QueryEcoclassidPOSTBody): +# mu_pair_tuples = body.mu_pair_tuples +# mrla = sdm_get_ecoclassid_from_mu_info(mu_pair_tuples) +# return JSONResponse(status_code=200, content={"mrla": json.loads(mrla)}) -@app.get("/api/query-soil/get-ecoclass-info") -def get_ecoclass_info(ecoclassid: str = Query(...)): - status_code, ecoclass_info = edit_get_ecoclass_info(ecoclassid) - return JSONResponse( - status_code=status_code, content={"ecoclass_info": ecoclass_info} - ) +# @app.get("/api/query-soil/get-ecoclass-info") +# def get_ecoclass_info(ecoclassid: str = Query(...)): +# status_code, ecoclass_info = edit_get_ecoclass_info(ecoclassid) +# return JSONResponse( +# status_code=status_code, content={"ecoclass_info": ecoclass_info} +# ) -# TODO [#6]: Restrucutre FastAPI endpoints to seperate user-facing endpoints from internal endpoints -# refactor out the low level endpoints (/api) and rename others (this isn't really an `analysis` but it does compose a lot of logic like `analyze-burn`) -@app.post("/api/query-soil/analyze-ecoclass") -def analyze_ecoclass( - body: QuerySoilPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) -): - fire_event_name = body.fire_event_name - geojson = json.loads(body.geojson) - affiliation = body.affiliation +# # TODO [#6]: Restrucutre FastAPI endpoints to seperate user-facing endpoints from internal endpoints +# # refactor out the low level endpoints (/api) and rename others (this isn't really an `analysis` but it does compose a lot of logic like `analyze-burn`) +# @app.post("/api/query-soil/analyze-ecoclass") +# def analyze_ecoclass( +# body: QuerySoilPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) +# ): +# fire_event_name = body.fire_event_name +# geojson = json.loads(body.geojson) +# affiliation = body.affiliation - sentry_sdk.set_context("analyze_ecoclass", {"request": body}) +# sentry_sdk.set_context("analyze_ecoclass", {"request": body}) - try: +# try: - mapunit_gdf = sdm_get_esa_mapunitid_poly(geojson) - mu_polygon_keys = [ - mupolygonkey - for __musym, __nationalmusym, __mukey, mupolygonkey in mapunit_gdf.index - ] - mrla_df = sdm_get_ecoclassid_from_mu_info(mu_polygon_keys) - - # join mapunitids with link table for ecoclassids - mapunit_with_ecoclassid_df = mapunit_gdf.join(mrla_df).set_index("ecoclassid") - - edit_ecoclass_df_row_dicts = [] - ecoclass_ids = mrla_df["ecoclassid"].unique() - - n_ecoclasses = len(ecoclass_ids) - n_within_edit = 0 - - for ecoclass_id in ecoclass_ids: - edit_success, edit_ecoclass_json = edit_get_ecoclass_info(ecoclass_id) - if edit_success: - n_within_edit += 1 - logger.log_text(f"Success: {ecoclass_id} exists within EDIT backend") - edit_ecoclass_df_row_dict = edit_ecoclass_json["generalInformation"][ - "dominantSpecies" - ] - edit_ecoclass_df_row_dict["ecoclassid"] = ecoclass_id - edit_ecoclass_df_row_dicts.append(edit_ecoclass_df_row_dict) - else: - logger.log_text( - f"Missing: {edit_ecoclass_json} doesn't exist within EDIT backend" - ) - - logger.log_text( - f"Found {n_within_edit} of {n_ecoclasses} ecoclasses ({100*round(n_within_edit/n_ecoclasses, 2)}%) within EDIT backend" - ) - - if n_within_edit > 0: - edit_ecoclass_df = pd.DataFrame(edit_ecoclass_df_row_dicts).set_index( - "ecoclassid" - ) - else: - # Populate with empty dataframe, for consistency's sake (so that the frontend doesn't have to handle this case) - edit_ecoclass_df = pd.DataFrame( - [], - columns=[ - "dominantTree1", - "dominantShrub1", - "dominantHerb1", - "dominantTree2", - "dominantShrub2", - "dominantHerb2", - ], - ) - - # join ecoclassids with edit ecoclass info, to get spatial ecoclass info - edit_ecoclass_geojson = mapunit_with_ecoclassid_df.join( - edit_ecoclass_df, how="left" - ).to_json() - - # save the ecoclass_geojson to the FTP server - with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: - tmp_geojson_path = tmp.name - with open(tmp_geojson_path, "w") as f: - f.write(edit_ecoclass_geojson) - - cloud_static_io_client.upload( - source_local_path=tmp_geojson_path, - remote_path=f"public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson", - ) - - logger.log_text(f"Ecoclass GeoJSON uploaded for {fire_event_name}") - return f"Ecoclass GeoJSON uploaded for {fire_event_name}", 200 - - except Exception as e: - sentry_sdk.capture_exception(e) - logger.log_text(f"Error: {e}") - raise HTTPException(status_code=400, detail=str(e)) - - -class AnaylzeRapPOSTBody(BaseModel): - geojson: Any - ignition_date: str - fire_event_name: str - affiliation: str - -@app.post("/api/query-biomass/analyze-rap") -def analyze_rap( - body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) -): - boundary_geojson = json.loads(body.geojson) - ignition_date = body.ignition_date - fire_event_name = body.fire_event_name - affiliation = body.affiliation - - sentry_sdk.set_context("analyze_rap", {"request": body}) - - try: - rap_estimates = rap_get_biomass( - boundary_geojson=boundary_geojson, - ignition_date=ignition_date - ) - - # save the cog to the FTP server - cloud_static_io_client.upload_rap_estimates( - rap_estimates=rap_estimates, - affiliation=affiliation, - fire_event_name=fire_event_name, - ) - logger.log_text(f"RAP estimates uploaded for {fire_event_name}") - - return JSONResponse( - status_code=200, - content={ - "message": f"RAP estimates uploaded for {fire_event_name}", - "fire_event_name": fire_event_name, - }, - ) - - except Exception as e: - sentry_sdk.capture_exception(e) - logger.log_text(f"Error: {e}") - raise HTTPException(status_code=400, detail=str(e)) - - -@app.post("/api/upload-shapefile-zip") -async def upload_shapefile( - fire_event_name: str = Form(...), - affiliation: str = Form(...), - file: UploadFile = File(...), - cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), - __sentry = Depends(init_sentry) -): - sentry_sdk.set_context("upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}) - - try: - # Read the file - zip_content = await file.read() - - # Write the content to a temporary file - with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp: - tmp.write(zip_content) - tmp_zip = tmp.name - - valid_shp, __valid_tiff = ingest_esri_zip_file(tmp_zip) - - # For now assert that there is only one shapefile - assert ( - len(valid_shp) == 1 - ), "Zip must contain exactly one shapefile (with associated files: .shx, .prj and optionally, .dbf)" - __shp_paths, geojson = valid_shp[0] - - # Upload the zip and a geojson to SFTP - cloud_static_io_client.upload( - source_local_path=tmp_zip, - remote_path=f"public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}", - ) - - with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: - tmp_geojson = tmp.name - with open(tmp_geojson, "w") as f: - f.write(geojson) - cloud_static_io_client.upload( - source_local_path=tmp_geojson, - remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", - ) - - return JSONResponse(status_code=200, content={"geojson": geojson}) - - except Exception as e: - sentry_sdk.capture_exception(e) - logger.log_text(f"Error: {e}") - raise HTTPException(status_code=400, detail=str(e)) - - -@app.post("/api/upload-drawn-aoi") -async def upload_drawn_aoi( - fire_event_name: str = Form(...), - affiliation: str = Form(...), - geojson: str = Form(...), - cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), - __sentry = Depends(init_sentry) -): - sentry_sdk.set_context("upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}) - - try: - with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: - tmp_geojson = tmp.name - with open(tmp_geojson, "w") as f: - f.write(geojson) - cloud_static_io_client.upload( - source_local_path=tmp_geojson, - remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", - ) - return JSONResponse(status_code=200, content={"geojson": geojson}) - - except Exception as e: - sentry_sdk.capture_exception(e) - logger.log_text(f"Error: {e}") - raise HTTPException(status_code=400, detail=str(e)) - -class GetDerivedProductsPOSTBody(BaseModel): - fire_event_name: str - affiliation: str - -@app.post("/api/get-derived-products") -async def get_derived_products( - body: GetDerivedProductsPOSTBody, - cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), - __sentry = Depends(init_sentry) -): - fire_event_name = body.fire_event_name - affiliation = body.affiliation - - sentry_sdk.set_context("get_derived_products", {"fire_event_name": fire_event_name, "affiliation": affiliation}) - - try: - derived_products = cloud_static_io_client.get_derived_products( - affiliation=affiliation, fire_event_name=fire_event_name - ) - return JSONResponse(status_code=200, content=derived_products) - - except Exception as e: - sentry_sdk.capture_exception(e) - logger.log_text(f"Error: {e}") - raise HTTPException(status_code=400, detail=str(e)) - -### WEB PAGES ### - - -@app.get( - "/map/{affiliation}/{fire_event_name}/{burn_metric}", response_class=HTMLResponse -) -def serve_map( - request: Request, - fire_event_name: str, - burn_metric: str, - affiliation: str, - manifest: dict = Depends(get_manifest), -): - mapbox_token = get_mapbox_secret() - - tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") - # tileserver_endpoint = "http://localhost:5050" - - ## TODO [#21]: Use Tofu Output to construct hardocded cog and geojson urls (in case we change s3 bucket name) - cog_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" - burn_boundary_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" - ecoclass_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" - severity_obs_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" - cog_tileserver_url_prefix = ( - tileserver_endpoint - + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={cog_url}&nodata=-99&return_mask=true" - ) - - rap_cog_annual_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" - rap_tileserver_annual_url = ( - tileserver_endpoint - + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_annual_url}&nodata=-99&return_mask=true" - ) - - rap_cog_perennial_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" - rap_tileserver_perennial_url = ( - tileserver_endpoint - + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_perennial_url}&nodata=-99&return_mask=true" - ) - - rap_cog_shrub_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" - rap_tileserver_shrub_url = ( - tileserver_endpoint - + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_shrub_url}&nodata=-99&return_mask=true" - ) - - rap_cog_tree_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" - rap_tileserver_tree_url = ( - tileserver_endpoint - + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" - ) - - - fire_metadata = manifest[affiliation][fire_event_name] - fire_metadata_json = json.dumps(fire_metadata) - - with open("src/static/map/burn_metric_text.json") as json_file: - burn_metric_text = json.load(json_file) - - return templates.TemplateResponse( - "map/map.html", - { - "request": request, - "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 - "fire_event_name": fire_event_name, - "burn_metric": burn_metric, - "burn_metric_text": burn_metric_text, - "fire_metadata_json": fire_metadata_json, - "cog_tileserver_url_prefix": cog_tileserver_url_prefix, - "burn_boundary_geojson_url": burn_boundary_geojson_url, - "ecoclass_geojson_url": ecoclass_geojson_url, - "severity_obs_geojson_url": severity_obs_geojson_url, - "rap_tileserver_annual_url": rap_tileserver_annual_url, - "rap_tileserver_perennial_url": rap_tileserver_perennial_url, - "rap_tileserver_shrub_url": rap_tileserver_shrub_url, - "rap_tileserver_tree_url": rap_tileserver_tree_url, - }, - ) - - -@app.get("/upload", response_class=HTMLResponse) -def upload(request: Request): - mapbox_token = get_mapbox_secret() - tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") - - return templates.TemplateResponse( - "upload/upload.html", - { - "request": request, - "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 - "tileserver_endpoint": tileserver_endpoint, - } - ) - -@app.get("/directory", response_class=HTMLResponse) -def directory(request: Request, manifest: dict = Depends(get_manifest)): - mapbox_token = get_mapbox_secret() - manifest_json = json.dumps(manifest) - cloud_run_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") - return templates.TemplateResponse( - "directory/directory.html", - { - "request": request, - "manifest": manifest_json, - "mapbox_token": mapbox_token, - "cloud_run_endpoint": cloud_run_endpoint - } - ) - -@app.get("/sketch", response_class=HTMLResponse) -def sketch(request: Request): - return templates.TemplateResponse("sketch/sketch.html", {"request": request}) - -@app.get("/", response_class=HTMLResponse) -def home(request: Request): - # Read the markdown file - with open(Path("src/static/home/home.md")) as f: - md_content = f.read() - - # Convert markdown to HTML - html_content = markdown(md_content) - - return templates.TemplateResponse( - "home/home.html", - { - "request": request, - "content": html_content, - }, - ) \ No newline at end of file +# mapunit_gdf = sdm_get_esa_mapunitid_poly(geojson) +# mu_polygon_keys = [ +# mupolygonkey +# for __musym, __nationalmusym, __mukey, mupolygonkey in mapunit_gdf.index +# ] +# mrla_df = sdm_get_ecoclassid_from_mu_info(mu_polygon_keys) + +# # join mapunitids with link table for ecoclassids +# mapunit_with_ecoclassid_df = mapunit_gdf.join(mrla_df).set_index("ecoclassid") + +# edit_ecoclass_df_row_dicts = [] +# ecoclass_ids = mrla_df["ecoclassid"].unique() + +# n_ecoclasses = len(ecoclass_ids) +# n_within_edit = 0 + +# for ecoclass_id in ecoclass_ids: +# edit_success, edit_ecoclass_json = edit_get_ecoclass_info(ecoclass_id) +# if edit_success: +# n_within_edit += 1 +# logger.log_text(f"Success: {ecoclass_id} exists within EDIT backend") +# edit_ecoclass_df_row_dict = edit_ecoclass_json["generalInformation"][ +# "dominantSpecies" +# ] +# edit_ecoclass_df_row_dict["ecoclassid"] = ecoclass_id +# edit_ecoclass_df_row_dicts.append(edit_ecoclass_df_row_dict) +# else: +# logger.log_text( +# f"Missing: {edit_ecoclass_json} doesn't exist within EDIT backend" +# ) + +# logger.log_text( +# f"Found {n_within_edit} of {n_ecoclasses} ecoclasses ({100*round(n_within_edit/n_ecoclasses, 2)}%) within EDIT backend" +# ) + +# if n_within_edit > 0: +# edit_ecoclass_df = pd.DataFrame(edit_ecoclass_df_row_dicts).set_index( +# "ecoclassid" +# ) +# else: +# # Populate with empty dataframe, for consistency's sake (so that the frontend doesn't have to handle this case) +# edit_ecoclass_df = pd.DataFrame( +# [], +# columns=[ +# "dominantTree1", +# "dominantShrub1", +# "dominantHerb1", +# "dominantTree2", +# "dominantShrub2", +# "dominantHerb2", +# ], +# ) + +# # join ecoclassids with edit ecoclass info, to get spatial ecoclass info +# edit_ecoclass_geojson = mapunit_with_ecoclassid_df.join( +# edit_ecoclass_df, how="left" +# ).to_json() + +# # save the ecoclass_geojson to the FTP server +# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: +# tmp_geojson_path = tmp.name +# with open(tmp_geojson_path, "w") as f: +# f.write(edit_ecoclass_geojson) + +# cloud_static_io_client.upload( +# source_local_path=tmp_geojson_path, +# remote_path=f"public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson", +# ) + +# logger.log_text(f"Ecoclass GeoJSON uploaded for {fire_event_name}") +# return f"Ecoclass GeoJSON uploaded for {fire_event_name}", 200 + +# except Exception as e: +# sentry_sdk.capture_exception(e) +# logger.log_text(f"Error: {e}") +# raise HTTPException(status_code=400, detail=str(e)) + + +# class AnaylzeRapPOSTBody(BaseModel): +# geojson: Any +# ignition_date: str +# fire_event_name: str +# affiliation: str + +# @app.post("/api/query-biomass/analyze-rap") +# def analyze_rap( +# body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) +# ): +# boundary_geojson = json.loads(body.geojson) +# ignition_date = body.ignition_date +# fire_event_name = body.fire_event_name +# affiliation = body.affiliation + +# sentry_sdk.set_context("analyze_rap", {"request": body}) + +# try: +# rap_estimates = rap_get_biomass( +# boundary_geojson=boundary_geojson, +# ignition_date=ignition_date +# ) + +# # save the cog to the FTP server +# cloud_static_io_client.upload_rap_estimates( +# rap_estimates=rap_estimates, +# affiliation=affiliation, +# fire_event_name=fire_event_name, +# ) +# logger.log_text(f"RAP estimates uploaded for {fire_event_name}") + +# return JSONResponse( +# status_code=200, +# content={ +# "message": f"RAP estimates uploaded for {fire_event_name}", +# "fire_event_name": fire_event_name, +# }, +# ) + +# except Exception as e: +# sentry_sdk.capture_exception(e) +# logger.log_text(f"Error: {e}") +# raise HTTPException(status_code=400, detail=str(e)) + + +# @app.post("/api/upload-shapefile-zip") +# async def upload_shapefile( +# fire_event_name: str = Form(...), +# affiliation: str = Form(...), +# file: UploadFile = File(...), +# cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), +# __sentry = Depends(init_sentry) +# ): +# sentry_sdk.set_context("upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + +# try: +# # Read the file +# zip_content = await file.read() + +# # Write the content to a temporary file +# with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp: +# tmp.write(zip_content) +# tmp_zip = tmp.name + +# valid_shp, __valid_tiff = ingest_esri_zip_file(tmp_zip) + +# # For now assert that there is only one shapefile +# assert ( +# len(valid_shp) == 1 +# ), "Zip must contain exactly one shapefile (with associated files: .shx, .prj and optionally, .dbf)" +# __shp_paths, geojson = valid_shp[0] + +# # Upload the zip and a geojson to SFTP +# cloud_static_io_client.upload( +# source_local_path=tmp_zip, +# remote_path=f"public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}", +# ) + +# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: +# tmp_geojson = tmp.name +# with open(tmp_geojson, "w") as f: +# f.write(geojson) +# cloud_static_io_client.upload( +# source_local_path=tmp_geojson, +# remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", +# ) + +# return JSONResponse(status_code=200, content={"geojson": geojson}) + +# except Exception as e: +# sentry_sdk.capture_exception(e) +# logger.log_text(f"Error: {e}") +# raise HTTPException(status_code=400, detail=str(e)) + + +# @app.post("/api/upload-drawn-aoi") +# async def upload_drawn_aoi( +# fire_event_name: str = Form(...), +# affiliation: str = Form(...), +# geojson: str = Form(...), +# cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), +# __sentry = Depends(init_sentry) +# ): +# sentry_sdk.set_context("upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + +# try: +# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: +# tmp_geojson = tmp.name +# with open(tmp_geojson, "w") as f: +# f.write(geojson) +# cloud_static_io_client.upload( +# source_local_path=tmp_geojson, +# remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", +# ) +# return JSONResponse(status_code=200, content={"geojson": geojson}) + +# except Exception as e: +# sentry_sdk.capture_exception(e) +# logger.log_text(f"Error: {e}") +# raise HTTPException(status_code=400, detail=str(e)) + +# class GetDerivedProductsPOSTBody(BaseModel): +# fire_event_name: str +# affiliation: str + +# @app.post("/api/get-derived-products") +# async def get_derived_products( +# body: GetDerivedProductsPOSTBody, +# cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), +# __sentry = Depends(init_sentry) +# ): +# fire_event_name = body.fire_event_name +# affiliation = body.affiliation + +# sentry_sdk.set_context("get_derived_products", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + +# try: +# derived_products = cloud_static_io_client.get_derived_products( +# affiliation=affiliation, fire_event_name=fire_event_name +# ) +# return JSONResponse(status_code=200, content=derived_products) + +# except Exception as e: +# sentry_sdk.capture_exception(e) +# logger.log_text(f"Error: {e}") +# raise HTTPException(status_code=400, detail=str(e)) + +# ### WEB PAGES ### + + +# @app.get( +# "/map/{affiliation}/{fire_event_name}/{burn_metric}", response_class=HTMLResponse +# ) +# def serve_map( +# request: Request, +# fire_event_name: str, +# burn_metric: str, +# affiliation: str, +# manifest: dict = Depends(get_manifest), +# ): +# mapbox_token = get_mapbox_secret() + +# tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") +# # tileserver_endpoint = "http://localhost:5050" + +# ## TODO [#21]: Use Tofu Output to construct hardocded cog and geojson urls (in case we change s3 bucket name) +# cog_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" +# burn_boundary_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" +# ecoclass_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" +# severity_obs_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" +# cog_tileserver_url_prefix = ( +# tileserver_endpoint +# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={cog_url}&nodata=-99&return_mask=true" +# ) + +# rap_cog_annual_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" +# rap_tileserver_annual_url = ( +# tileserver_endpoint +# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_annual_url}&nodata=-99&return_mask=true" +# ) + +# rap_cog_perennial_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" +# rap_tileserver_perennial_url = ( +# tileserver_endpoint +# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_perennial_url}&nodata=-99&return_mask=true" +# ) + +# rap_cog_shrub_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" +# rap_tileserver_shrub_url = ( +# tileserver_endpoint +# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_shrub_url}&nodata=-99&return_mask=true" +# ) + +# rap_cog_tree_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" +# rap_tileserver_tree_url = ( +# tileserver_endpoint +# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" +# ) + + +# fire_metadata = manifest[affiliation][fire_event_name] +# fire_metadata_json = json.dumps(fire_metadata) + +# with open("src/static/map/burn_metric_text.json") as json_file: +# burn_metric_text = json.load(json_file) + +# return templates.TemplateResponse( +# "map/map.html", +# { +# "request": request, +# "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 +# "fire_event_name": fire_event_name, +# "burn_metric": burn_metric, +# "burn_metric_text": burn_metric_text, +# "fire_metadata_json": fire_metadata_json, +# "cog_tileserver_url_prefix": cog_tileserver_url_prefix, +# "burn_boundary_geojson_url": burn_boundary_geojson_url, +# "ecoclass_geojson_url": ecoclass_geojson_url, +# "severity_obs_geojson_url": severity_obs_geojson_url, +# "rap_tileserver_annual_url": rap_tileserver_annual_url, +# "rap_tileserver_perennial_url": rap_tileserver_perennial_url, +# "rap_tileserver_shrub_url": rap_tileserver_shrub_url, +# "rap_tileserver_tree_url": rap_tileserver_tree_url, +# }, +# ) + + +# @app.get("/upload", response_class=HTMLResponse) +# def upload(request: Request): +# mapbox_token = get_mapbox_secret() +# tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") + +# return templates.TemplateResponse( +# "upload/upload.html", +# { +# "request": request, +# "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 +# "tileserver_endpoint": tileserver_endpoint, +# } +# ) + +# @app.get("/directory", response_class=HTMLResponse) +# def directory(request: Request, manifest: dict = Depends(get_manifest)): +# mapbox_token = get_mapbox_secret() +# manifest_json = json.dumps(manifest) +# cloud_run_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") +# return templates.TemplateResponse( +# "directory/directory.html", +# { +# "request": request, +# "manifest": manifest_json, +# "mapbox_token": mapbox_token, +# "cloud_run_endpoint": cloud_run_endpoint +# } +# ) + +# @app.get("/sketch", response_class=HTMLResponse) +# def sketch(request: Request): +# return templates.TemplateResponse("sketch/sketch.html", {"request": request}) + +# @app.get("/", response_class=HTMLResponse) +# def home(request: Request): +# # Read the markdown file +# with open(Path("src/static/home/home.md")) as f: +# md_content = f.read() + +# # Convert markdown to HTML +# html_content = markdown(md_content) + +# return templates.TemplateResponse( +# "home/home.html", +# { +# "request": request, +# "content": html_content, +# }, +# ) \ No newline at end of file diff --git a/src/routers/debug/check_connectivity.py b/src/routers/debug/check_connectivity.py new file mode 100644 index 0000000..8b7887b --- /dev/null +++ b/src/routers/debug/check_connectivity.py @@ -0,0 +1,20 @@ +from fastapi import Depends, APIRouter, HTTPException +from ..dependencies import get_cloud_logger +from logging import Logger + +import requests + +router = APIRouter() + +@router.get("/check-connectivity", tags=["debug"], description="Check connectivity to example.com") +def check_connectivity(logger: Logger = Depends(get_cloud_logger)): + try: + response = requests.get("http://example.com") + logger.log_text( + f"Connectivity check: Got response {response.status_code} from http://example.com" + ) + return 200, f"Connectivity check: Got response {response.status_code} from http://example.com" + + except Exception as e: + logger.log_text(f"Connectivity check: Error {e}") + raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file diff --git a/src/routers/debug/check_dns.py b/src/routers/debug/check_dns.py new file mode 100644 index 0000000..6f952ef --- /dev/null +++ b/src/routers/debug/check_dns.py @@ -0,0 +1,19 @@ +from fastapi import Depends, APIRouter, HTTPException +from ..dependencies import get_cloud_logger +from logging import Logger + +import socket +import os + +router = APIRouter() + +@router.get("/check-dns", tags=["debug"], summary="Check DNS resolution") +def check_dns(logger: Logger = Depends(get_cloud_logger)): + try: + TEST_DOMAIN = "www.google.com" + ip_address = socket.gethostbyname(TEST_DOMAIN) + logger.log_text(f"DNS check: Resolved {TEST_DOMAIN} to {ip_address}") + return {"ip_address": ip_address} + except Exception as e: + logger.log_text(f"DNS check: Error {e}") + raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file diff --git a/src/routers/debug/health.py b/src/routers/debug/health.py index ce80c6a..fa172e2 100644 --- a/src/routers/debug/health.py +++ b/src/routers/debug/health.py @@ -4,7 +4,7 @@ router = APIRouter() -@router.get("/health") +@router.get("/health", tags=["debug"], description="Health check endpoint") def health(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Health check endpoint called") return "Alive", 200 \ No newline at end of file diff --git a/src/routers/debug/trigger_error.py b/src/routers/debug/trigger_error.py new file mode 100644 index 0000000..61d3f11 --- /dev/null +++ b/src/routers/debug/trigger_error.py @@ -0,0 +1,10 @@ +from fastapi import Depends, APIRouter +from ..dependencies import get_cloud_logger +from logging import Logger + +router = APIRouter() + +@router.get("/trigger_error", tags=["debug"], summary="Trigger a division by zero error for Sentry to catch.") +async def trigger_error(logger: Logger = Depends(get_cloud_logger)): + logger.log_text("Triggering a division by zero error for Sentry to catch.") + __division_by_zero = 1 / 0 \ No newline at end of file diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index 4edd31c..0723605 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -1,4 +1,10 @@ +from fastapi import Depends +from logging import Logger + from google.cloud import logging +import sentry_sdk +from src.util.cloud_static_io import CloudStaticIOClient +import os def get_cloud_logger(): logging_client = logging.Client(project="dse-nps") @@ -6,3 +12,35 @@ def get_cloud_logger(): logger = logging_client.logger(log_name) return logger + +def get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger)): + logger.log_text("Creating CloudStaticIOClient") + return CloudStaticIOClient('burn-severity-backend', "s3") + +def get_manifest( + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + logger: Logger = Depends(get_cloud_logger) +): + logger.log_text("Getting manifest") + manifest = cloud_static_io_client.get_manifest() + return manifest + +def init_sentry( + logger: Logger = Depends(get_cloud_logger) +): + logger.log_text("Initializing Sentry client") + + ## TODO: Move to sentry to environment variable if we keep sentry + sentry_sdk.init( + dsn="https://3660129e232b3c796208a5e46945d838@o4506701219364864.ingest.sentry.io/4506701221199872", + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for performance monitoring. + traces_sample_rate=1.0, + # Set profiles_sample_rate to 1.0 to profile 100% + # of sampled transactions. + # We recommend adjusting this value in production. + profiles_sample_rate=1.0, + ) + sentry_sdk.set_context("env", {"env": os.getenv('ENV')}) + logger.log_text("Sentry initialized") + From 7a50f48a0b608aaf6238c606c530ae40ebb2f44f Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:05:11 +0000 Subject: [PATCH 090/175] upload shapefile zip reorg --- src/app.py | 92 +----------------- src/routers/analysis/analyze_fire_event.py | 107 +++++++++++++++++++++ src/routers/ingest/upload_shapefile_zip.py | 65 +++++++++++++ 3 files changed, 176 insertions(+), 88 deletions(-) create mode 100644 src/routers/analysis/analyze_fire_event.py create mode 100644 src/routers/ingest/upload_shapefile_zip.py diff --git a/src/app.py b/src/app.py index 39bccd9..2741efb 100644 --- a/src/app.py +++ b/src/app.py @@ -35,6 +35,9 @@ check_connectivity, check_dns ) +from src.routers.analysis import ( + analyze_fire_event +) from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers @@ -70,94 +73,7 @@ ### API ENDPOINTS ### -# class AnaylzeBurnPOSTBody(BaseModel): -# geojson: Any -# derive_boundary: bool -# date_ranges: dict -# fire_event_name: str -# affiliation: str - - -# TODO [#5]: Decide on / implement cloud tasks or other async batch -# This is a long running process, and users probably don't mind getting an email notification -# or something similar when the process is complete. Esp if the frontend remanins static. -# @app.post("/api/query-satellite/analyze-burn") -# def analyze_burn( -# body: AnaylzeBurnPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) -# ): -# geojson_boundary = json.loads(body.geojson) - -# date_ranges = body.date_ranges -# fire_event_name = body.fire_event_name -# affiliation = body.affiliation -# derive_boundary = body.derive_boundary -# derived_boundary = None - -# sentry_sdk.set_context("analyze_burn", {"request": body}) -# logger.log_text(f"Received analyze-burn request for {fire_event_name}") - -# try: -# # create a Sentinel2Client instance -# geo_client = Sentinel2Client(geojson_boundary=geojson_boundary, buffer=0.1) - -# # get imagery data before and after the fire -# geo_client.query_fire_event( -# prefire_date_range=date_ranges["prefire"], -# postfire_date_range=date_ranges["postfire"], -# from_bbox=True, -# ) -# logger.log_text(f"Obtained imagery for {fire_event_name}") - -# # calculate burn metrics -# geo_client.calc_burn_metrics() -# logger.log_text(f"Calculated burn metrics for {fire_event_name}") - -# if derive_boundary: -# # Derive a boundary from the imagery -# # TODO [#16]: Derived boundary hardcoded for rbr / .025 threshold -# # Not sure yet but we will probably want to make this configurable -# geo_client.derive_boundary("rbr", 0.025) -# logger.log_text(f"Derived boundary for {fire_event_name}") - -# # Upload the derived boundary - -# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: -# tmp_geojson = tmp.name -# with open(tmp_geojson, "w") as f: -# f.write(geo_client.geojson_boundary.to_json()) - -# cloud_static_io_client.upload( -# source_local_path=tmp_geojson, -# remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", -# ) - -# # Return the derived boundary -# derived_boundary = geo_client.geojson_boundary.to_json() - -# # save the cog to the FTP server -# cloud_static_io_client.upload_fire_event( -# metrics_stack=geo_client.metrics_stack, -# affiliation=affiliation, -# fire_event_name=fire_event_name, -# prefire_date_range=date_ranges["prefire"], -# postfire_date_range=date_ranges["postfire"], -# derive_boundary=derive_boundary, -# ) -# logger.log_text(f"Cogs uploaded for {fire_event_name}") - -# return JSONResponse( -# status_code=200, -# content={ -# "message": f"Cogs uploaded for {fire_event_name}", -# "fire_event_name": fire_event_name, -# "derived_boundary": derived_boundary, -# }, -# ) - -# except Exception as e: -# sentry_sdk.capture_exception(e) -# logger.log_text(f"Error: {e}") -# raise HTTPException(status_code=400, detail=str(e)) +app.include_router(analyze_fire_event.router) # class QuerySoilPOSTBody(BaseModel): # geojson: Any diff --git a/src/routers/analysis/analyze_fire_event.py b/src/routers/analysis/analyze_fire_event.py new file mode 100644 index 0000000..3f163c2 --- /dev/null +++ b/src/routers/analysis/analyze_fire_event.py @@ -0,0 +1,107 @@ +from fastapi import Depends, APIRouter, HTTPException +from fastapi.responses import JSONResponse +from logging import Logger +from typing import Any +from pydantic import BaseModel +import tempfile +import sentry_sdk +import json + +from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry +from src.lib.query_sentinel import Sentinel2Client +from src.util.cloud_static_io import CloudStaticIOClient + +router = APIRouter() + + +class AnaylzeBurnPOSTBody(BaseModel): + geojson: Any + derive_boundary: bool + date_ranges: dict + fire_event_name: str + affiliation: str + + +# TODO [#5]: Decide on / implement cloud tasks or other async batch +# This is a long running process, and users probably don't mind getting an email notification +# or something similar when the process is complete. Esp if the frontend remanins static. +@router.post("/api/query-satellite/analyze-fire-event", tags=["analysis"], description="Analyze a fire event") +def analyze_burn( + body: AnaylzeBurnPOSTBody, + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + __sentry = Depends(init_sentry), + logger: Logger = Depends(get_cloud_logger), +): + geojson_boundary = json.loads(body.geojson) + + date_ranges = body.date_ranges + fire_event_name = body.fire_event_name + affiliation = body.affiliation + derive_boundary = body.derive_boundary + derived_boundary = None + + sentry_sdk.set_context("fire-event", {"request": body}) + logger.log_text(f"Received analyze-fire-event request for {fire_event_name}") + + try: + # create a Sentinel2Client instance + geo_client = Sentinel2Client(geojson_boundary=geojson_boundary, buffer=0.1) + + # get imagery data before and after the fire + geo_client.query_fire_event( + prefire_date_range=date_ranges["prefire"], + postfire_date_range=date_ranges["postfire"], + from_bbox=True, + ) + logger.log_text(f"Obtained imagery for {fire_event_name}") + + # calculate burn metrics + geo_client.calc_burn_metrics() + logger.log_text(f"Calculated burn metrics for {fire_event_name}") + + if derive_boundary: + # Derive a boundary from the imagery + # TODO [#16]: Derived boundary hardcoded for rbr / .025 threshold + # Not sure yet but we will probably want to make this configurable + geo_client.derive_boundary("rbr", 0.025) + logger.log_text(f"Derived boundary for {fire_event_name}") + + # Upload the derived boundary + + with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: + tmp_geojson = tmp.name + with open(tmp_geojson, "w") as f: + f.write(geo_client.geojson_boundary.to_json()) + + cloud_static_io_client.upload( + source_local_path=tmp_geojson, + remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", + ) + + # Return the derived boundary + derived_boundary = geo_client.geojson_boundary.to_json() + + # save the cog to the FTP server + cloud_static_io_client.upload_fire_event( + metrics_stack=geo_client.metrics_stack, + affiliation=affiliation, + fire_event_name=fire_event_name, + prefire_date_range=date_ranges["prefire"], + postfire_date_range=date_ranges["postfire"], + derive_boundary=derive_boundary, + ) + logger.log_text(f"Cogs uploaded for {fire_event_name}") + + return JSONResponse( + status_code=200, + content={ + "message": f"Cogs uploaded for {fire_event_name}", + "fire_event_name": fire_event_name, + "derived_boundary": derived_boundary, + }, + ) + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.log_text(f"Error: {e}") + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/routers/ingest/upload_shapefile_zip.py b/src/routers/ingest/upload_shapefile_zip.py new file mode 100644 index 0000000..1c22902 --- /dev/null +++ b/src/routers/ingest/upload_shapefile_zip.py @@ -0,0 +1,65 @@ + +from fastapi import Depends, APIRouter, HTTPException, Form, File, UploadFile +from fastapi.responses import JSONResponse +from logging import Logger +from typing import Any +from pydantic import BaseModel +import tempfile +import sentry_sdk + +from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry +from src.util.cloud_static_io import CloudStaticIOClient +from src.util.ingest_burn_zip import ingest_esri_zip_file + +router = APIRouter() + +@router.post("/api/upload-shapefile-zip") +async def upload_shapefile( + fire_event_name: str = Form(...), + affiliation: str = Form(...), + file: UploadFile = File(...), + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + logger: Logger = Depends(get_cloud_logger), + __sentry = Depends(init_sentry) +): + sentry_sdk.set_context("upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + + try: + # Read the file + zip_content = await file.read() + + # Write the content to a temporary file + with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp: + tmp.write(zip_content) + tmp_zip = tmp.name + + valid_shp, __valid_tiff = ingest_esri_zip_file(tmp_zip) + + # For now assert that there is only one shapefile + assert ( + len(valid_shp) == 1 + ), "Zip must contain exactly one shapefile (with associated files: .shx, .prj and optionally, .dbf)" + __shp_paths, geojson = valid_shp[0] + + # Upload the zip and a geojson to SFTP + cloud_static_io_client.upload( + source_local_path=tmp_zip, + remote_path=f"public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}", + ) + + with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: + tmp_geojson = tmp.name + with open(tmp_geojson, "w") as f: + f.write(geojson) + cloud_static_io_client.upload( + source_local_path=tmp_geojson, + remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", + ) + + return JSONResponse(status_code=200, content={"geojson": geojson}) + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.log_text(f"Error: {e}") + raise HTTPException(status_code=400, detail=str(e)) + From 78580a9a7727ebd8cbbed50f40bfc210cdfd36f0 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:18:04 +0000 Subject: [PATCH 091/175] reorg for upload drawn aoi and shapefile --- src/app.py | 50 ---------------------- src/routers/analysis/analyze_fire_event.py | 1 - src/routers/ingest/upload_drawn_aoi.py | 37 ++++++++++++++++ src/routers/ingest/upload_shapefile_zip.py | 14 +++--- 4 files changed, 46 insertions(+), 56 deletions(-) create mode 100644 src/routers/ingest/upload_drawn_aoi.py diff --git a/src/app.py b/src/app.py index 2741efb..6dcd322 100644 --- a/src/app.py +++ b/src/app.py @@ -253,56 +253,6 @@ # raise HTTPException(status_code=400, detail=str(e)) -# @app.post("/api/upload-shapefile-zip") -# async def upload_shapefile( -# fire_event_name: str = Form(...), -# affiliation: str = Form(...), -# file: UploadFile = File(...), -# cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), -# __sentry = Depends(init_sentry) -# ): -# sentry_sdk.set_context("upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}) - -# try: -# # Read the file -# zip_content = await file.read() - -# # Write the content to a temporary file -# with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp: -# tmp.write(zip_content) -# tmp_zip = tmp.name - -# valid_shp, __valid_tiff = ingest_esri_zip_file(tmp_zip) - -# # For now assert that there is only one shapefile -# assert ( -# len(valid_shp) == 1 -# ), "Zip must contain exactly one shapefile (with associated files: .shx, .prj and optionally, .dbf)" -# __shp_paths, geojson = valid_shp[0] - -# # Upload the zip and a geojson to SFTP -# cloud_static_io_client.upload( -# source_local_path=tmp_zip, -# remote_path=f"public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}", -# ) - -# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: -# tmp_geojson = tmp.name -# with open(tmp_geojson, "w") as f: -# f.write(geojson) -# cloud_static_io_client.upload( -# source_local_path=tmp_geojson, -# remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", -# ) - -# return JSONResponse(status_code=200, content={"geojson": geojson}) - -# except Exception as e: -# sentry_sdk.capture_exception(e) -# logger.log_text(f"Error: {e}") -# raise HTTPException(status_code=400, detail=str(e)) - - # @app.post("/api/upload-drawn-aoi") # async def upload_drawn_aoi( # fire_event_name: str = Form(...), diff --git a/src/routers/analysis/analyze_fire_event.py b/src/routers/analysis/analyze_fire_event.py index 3f163c2..69f448a 100644 --- a/src/routers/analysis/analyze_fire_event.py +++ b/src/routers/analysis/analyze_fire_event.py @@ -13,7 +13,6 @@ router = APIRouter() - class AnaylzeBurnPOSTBody(BaseModel): geojson: Any derive_boundary: bool diff --git a/src/routers/ingest/upload_drawn_aoi.py b/src/routers/ingest/upload_drawn_aoi.py new file mode 100644 index 0000000..d28a9d1 --- /dev/null +++ b/src/routers/ingest/upload_drawn_aoi.py @@ -0,0 +1,37 @@ +from fastapi import Depends, APIRouter, HTTPException, Form +from fastapi.responses import JSONResponse +from logging import Logger +import tempfile +import sentry_sdk + +from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry +from src.util.cloud_static_io import CloudStaticIOClient + +router = APIRouter() + +@router.post("/api/upload-drawn-aoi") +async def upload_drawn_aoi( + fire_event_name: str = Form(...), + affiliation: str = Form(...), + geojson: str = Form(...), + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + logger: Logger = Depends(get_cloud_logger), + __sentry = Depends(init_sentry) +): + sentry_sdk.set_context("upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + + try: + with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: + tmp_geojson = tmp.name + with open(tmp_geojson, "w") as f: + f.write(geojson) + cloud_static_io_client.upload( + source_local_path=tmp_geojson, + remote_path=f"public/{affiliation}/{fire_event_name}/drawn_aoi_boundary.geojson", + ) + return JSONResponse(status_code=200, content={"geojson": geojson}) + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.log_text(f"Error: {e}") + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/routers/ingest/upload_shapefile_zip.py b/src/routers/ingest/upload_shapefile_zip.py index 1c22902..ceedf04 100644 --- a/src/routers/ingest/upload_shapefile_zip.py +++ b/src/routers/ingest/upload_shapefile_zip.py @@ -1,4 +1,3 @@ - from fastapi import Depends, APIRouter, HTTPException, Form, File, UploadFile from fastapi.responses import JSONResponse from logging import Logger @@ -41,25 +40,30 @@ async def upload_shapefile( ), "Zip must contain exactly one shapefile (with associated files: .shx, .prj and optionally, .dbf)" __shp_paths, geojson = valid_shp[0] - # Upload the zip and a geojson to SFTP + user_uploaded_s3_path = "public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}" + # Upload the zip and a geojson to s3 cloud_static_io_client.upload( source_local_path=tmp_zip, - remote_path=f"public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}", + remote_path=user_uploaded_s3_path, ) + logger.info(f"Uploaded zip file ({user_uploaded_s3_path})") + with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: tmp_geojson = tmp.name with open(tmp_geojson, "w") as f: f.write(geojson) + boundary_s3_path = f"public/{affiliation}/{fire_event_name}/boundary.geojson" cloud_static_io_client.upload( source_local_path=tmp_geojson, - remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", + remote_path=boundary_s3_path, ) + logger.info(f"Uploaded geojson file ({boundary_s3_path})") + return JSONResponse(status_code=200, content={"geojson": geojson}) except Exception as e: sentry_sdk.capture_exception(e) logger.log_text(f"Error: {e}") raise HTTPException(status_code=400, detail=str(e)) - From 716ad300eb36f40ef968f2c1c89597a82c06bb1b Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:20:38 +0000 Subject: [PATCH 092/175] reorg for upload drawn aoi and shapefile --- src/app.py | 26 ---------------------- src/routers/analysis/analyze_fire_event.py | 1 - 2 files changed, 27 deletions(-) diff --git a/src/app.py b/src/app.py index 6dcd322..6387115 100644 --- a/src/app.py +++ b/src/app.py @@ -253,32 +253,6 @@ # raise HTTPException(status_code=400, detail=str(e)) -# @app.post("/api/upload-drawn-aoi") -# async def upload_drawn_aoi( -# fire_event_name: str = Form(...), -# affiliation: str = Form(...), -# geojson: str = Form(...), -# cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), -# __sentry = Depends(init_sentry) -# ): -# sentry_sdk.set_context("upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}) - -# try: -# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: -# tmp_geojson = tmp.name -# with open(tmp_geojson, "w") as f: -# f.write(geojson) -# cloud_static_io_client.upload( -# source_local_path=tmp_geojson, -# remote_path=f"public/{affiliation}/{fire_event_name}/boundary.geojson", -# ) -# return JSONResponse(status_code=200, content={"geojson": geojson}) - -# except Exception as e: -# sentry_sdk.capture_exception(e) -# logger.log_text(f"Error: {e}") -# raise HTTPException(status_code=400, detail=str(e)) - # class GetDerivedProductsPOSTBody(BaseModel): # fire_event_name: str # affiliation: str diff --git a/src/routers/analysis/analyze_fire_event.py b/src/routers/analysis/analyze_fire_event.py index 69f448a..f541d28 100644 --- a/src/routers/analysis/analyze_fire_event.py +++ b/src/routers/analysis/analyze_fire_event.py @@ -66,7 +66,6 @@ def analyze_burn( logger.log_text(f"Derived boundary for {fire_event_name}") # Upload the derived boundary - with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: tmp_geojson = tmp.name with open(tmp_geojson, "w") as f: From 636c8fd1519ea059a6b30d9574fe09b91d29fd38 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:38:17 +0000 Subject: [PATCH 093/175] rename for consistency and for easier re-use and testing --- src/app.py | 53 ++++---- .../fire_event.py} | 0 src/routers/{debug => check}/__init__.py | 0 .../connectivity.py} | 2 +- .../{debug/check_dns.py => check/dns.py} | 2 +- src/routers/{debug => check}/health.py | 2 +- .../sentry_error.py} | 2 +- src/routers/fetch/ecoclass.py | 114 ++++++++++++++++++ .../fetch/rangeland_analysis_platform.py | 0 .../drawn_aoi.py} | 2 +- .../shapefile_zip.py} | 2 +- 11 files changed, 148 insertions(+), 31 deletions(-) rename src/routers/{analysis/analyze_fire_event.py => analyze/fire_event.py} (100%) rename src/routers/{debug => check}/__init__.py (100%) rename src/routers/{debug/check_connectivity.py => check/connectivity.py} (91%) rename src/routers/{debug/check_dns.py => check/dns.py} (90%) rename src/routers/{debug => check}/health.py (76%) rename src/routers/{debug/trigger_error.py => check/sentry_error.py} (72%) create mode 100644 src/routers/fetch/ecoclass.py create mode 100644 src/routers/fetch/rangeland_analysis_platform.py rename src/routers/{ingest/upload_drawn_aoi.py => upload/drawn_aoi.py} (92%) rename src/routers/{ingest/upload_shapefile_zip.py => upload/shapefile_zip.py} (95%) diff --git a/src/app.py b/src/app.py index 6387115..f887377 100644 --- a/src/app.py +++ b/src/app.py @@ -29,51 +29,54 @@ from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles -from src.routers.debug import ( +from src.routers.check import ( + connectivity, + dns, health, - trigger_error, - check_connectivity, - check_dns + sentry_error ) -from src.routers.analysis import ( - analyze_fire_event +from src.routers.analyze import ( + fire_event +) +from src.routers.upload import ( + drawn_aoi, + shapefile_zip ) from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers -from src.lib.query_sentinel import Sentinel2Client -from src.util.cloud_static_io import CloudStaticIOClient -from src.util.gcp_secrets import get_mapbox_secret -from src.util.ingest_burn_zip import ingest_esri_zip_file from src.lib.titiler_algorithms import algorithms -from src.lib.query_soil import ( - sdm_get_ecoclassid_from_mu_info, - sdm_get_esa_mapunitid_poly, - edit_get_ecoclass_info, -) -from src.lib.query_rap import rap_get_biomass app = FastAPI(docs_url="/documentation") -app.include_router(health.router) -app.include_router(trigger_error.router) + cog = TilerFactory(process_dependency=algorithms.dependency) -app.include_router(cog.router, prefix="/cog", tags=["Cloud Optimized GeoTIFF"]) +app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) add_exception_handlers(app, DEFAULT_STATUS_CODES) app.mount("/static", StaticFiles(directory="src/static"), name="static") templates = Jinja2Templates(directory="src/static") -### DEBUG ### +### CHECK ### app.include_router(health.router) -app.include_router(trigger_error.router) -app.include_router(check_connectivity.router) -app.include_router(check_dns.router) +app.include_router(sentry_error.router) +app.include_router(connectivity.router) +app.include_router(dns.router) -### API ENDPOINTS ### +### ANALYZE ### +app.include_router(fire_event.router) -app.include_router(analyze_fire_event.router) +## UPLOAD ## +app.include_router(drawn_aoi.router) +app.include_router(shapefile_zip.router) + +### FETCH ### + +## TILESERVER ## +cog = TilerFactory(process_dependency=algorithms.dependency) +app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) +add_exception_handlers(app, DEFAULT_STATUS_CODES) # class QuerySoilPOSTBody(BaseModel): # geojson: Any diff --git a/src/routers/analysis/analyze_fire_event.py b/src/routers/analyze/fire_event.py similarity index 100% rename from src/routers/analysis/analyze_fire_event.py rename to src/routers/analyze/fire_event.py diff --git a/src/routers/debug/__init__.py b/src/routers/check/__init__.py similarity index 100% rename from src/routers/debug/__init__.py rename to src/routers/check/__init__.py diff --git a/src/routers/debug/check_connectivity.py b/src/routers/check/connectivity.py similarity index 91% rename from src/routers/debug/check_connectivity.py rename to src/routers/check/connectivity.py index 8b7887b..ee07d2f 100644 --- a/src/routers/debug/check_connectivity.py +++ b/src/routers/check/connectivity.py @@ -6,7 +6,7 @@ router = APIRouter() -@router.get("/check-connectivity", tags=["debug"], description="Check connectivity to example.com") +@router.get("/check-connectivity", tags=["check"], description="Check connectivity to example.com") def check_connectivity(logger: Logger = Depends(get_cloud_logger)): try: response = requests.get("http://example.com") diff --git a/src/routers/debug/check_dns.py b/src/routers/check/dns.py similarity index 90% rename from src/routers/debug/check_dns.py rename to src/routers/check/dns.py index 6f952ef..90f0d2b 100644 --- a/src/routers/debug/check_dns.py +++ b/src/routers/check/dns.py @@ -7,7 +7,7 @@ router = APIRouter() -@router.get("/check-dns", tags=["debug"], summary="Check DNS resolution") +@router.get("/check-dns", tags=["check"], summary="Check DNS resolution") def check_dns(logger: Logger = Depends(get_cloud_logger)): try: TEST_DOMAIN = "www.google.com" diff --git a/src/routers/debug/health.py b/src/routers/check/health.py similarity index 76% rename from src/routers/debug/health.py rename to src/routers/check/health.py index fa172e2..af68a65 100644 --- a/src/routers/debug/health.py +++ b/src/routers/check/health.py @@ -4,7 +4,7 @@ router = APIRouter() -@router.get("/health", tags=["debug"], description="Health check endpoint") +@router.get("/check-health", tags=["check"], description="Health check endpoint") def health(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Health check endpoint called") return "Alive", 200 \ No newline at end of file diff --git a/src/routers/debug/trigger_error.py b/src/routers/check/sentry_error.py similarity index 72% rename from src/routers/debug/trigger_error.py rename to src/routers/check/sentry_error.py index 61d3f11..471e22d 100644 --- a/src/routers/debug/trigger_error.py +++ b/src/routers/check/sentry_error.py @@ -4,7 +4,7 @@ router = APIRouter() -@router.get("/trigger_error", tags=["debug"], summary="Trigger a division by zero error for Sentry to catch.") +@router.get("/check-sentry-error", tags=["check"], summary="Trigger a division by zero error for Sentry to catch.") async def trigger_error(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Triggering a division by zero error for Sentry to catch.") __division_by_zero = 1 / 0 \ No newline at end of file diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py new file mode 100644 index 0000000..039d677 --- /dev/null +++ b/src/routers/fetch/ecoclass.py @@ -0,0 +1,114 @@ +from fastapi import Depends, APIRouter, HTTPException +from logging import Logger +from typing import Any +from pydantic import BaseModel +import tempfile +import sentry_sdk +import json + +from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry +from src.lib.query_soil import ( + sdm_get_esa_mapunitid_poly, + sdm_get_ecoclassid_from_mu_info, + edit_get_ecoclass_info +) +from src.util.cloud_static_io import CloudStaticIOClient + +router = APIRouter() + +class QuerySoilPOSTBody(BaseModel): + geojson: Any + fire_event_name: str + affiliation: str + +@router.post("/api/fetch/fetch-ecoclass") +def analyze_ecoclass( + body: QuerySoilPOSTBody, + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + __sentry: None = Depends(init_sentry), + logger: Logger = Depends(get_cloud_logger) +): + fire_event_name = body.fire_event_name + geojson = json.loads(body.geojson) + affiliation = body.affiliation + + sentry_sdk.set_context("analyze_ecoclass", {"request": body}) + + try: + + mapunit_gdf = sdm_get_esa_mapunitid_poly(geojson) + mu_polygon_keys = [ + mupolygonkey + for __musym, __nationalmusym, __mukey, mupolygonkey in mapunit_gdf.index + ] + mrla_df = sdm_get_ecoclassid_from_mu_info(mu_polygon_keys) + + # join mapunitids with link table for ecoclassids + mapunit_with_ecoclassid_df = mapunit_gdf.join(mrla_df).set_index("ecoclassid") + + edit_ecoclass_df_row_dicts = [] + ecoclass_ids = mrla_df["ecoclassid"].unique() + + n_ecoclasses = len(ecoclass_ids) + n_within_edit = 0 + + for ecoclass_id in ecoclass_ids: + edit_success, edit_ecoclass_json = edit_get_ecoclass_info(ecoclass_id) + if edit_success: + n_within_edit += 1 + logger.log_text(f"Success: {ecoclass_id} exists within EDIT backend") + edit_ecoclass_df_row_dict = edit_ecoclass_json["generalInformation"][ + "dominantSpecies" + ] + edit_ecoclass_df_row_dict["ecoclassid"] = ecoclass_id + edit_ecoclass_df_row_dicts.append(edit_ecoclass_df_row_dict) + else: + logger.log_text( + f"Missing: {edit_ecoclass_json} doesn't exist within EDIT backend" + ) + + logger.log_text( + f"Found {n_within_edit} of {n_ecoclasses} ecoclasses ({100*round(n_within_edit/n_ecoclasses, 2)}%) within EDIT backend" + ) + + if n_within_edit > 0: + edit_ecoclass_df = pd.DataFrame(edit_ecoclass_df_row_dicts).set_index( + "ecoclassid" + ) + else: + # Populate with empty dataframe, for consistency's sake (so that the frontend doesn't have to handle this case) + edit_ecoclass_df = pd.DataFrame( + [], + columns=[ + "dominantTree1", + "dominantShrub1", + "dominantHerb1", + "dominantTree2", + "dominantShrub2", + "dominantHerb2", + ], + ) + + # join ecoclassids with edit ecoclass info, to get spatial ecoclass info + edit_ecoclass_geojson = mapunit_with_ecoclassid_df.join( + edit_ecoclass_df, how="left" + ).to_json() + + # save the ecoclass_geojson to the FTP server + with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: + tmp_geojson_path = tmp.name + with open(tmp_geojson_path, "w") as f: + f.write(edit_ecoclass_geojson) + + cloud_static_io_client.upload( + source_local_path=tmp_geojson_path, + remote_path=f"public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson", + ) + + logger.log_text(f"Ecoclass GeoJSON uploaded for {fire_event_name}") + return f"Ecoclass GeoJSON uploaded for {fire_event_name}", 200 + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.log_text(f"Error: {e}") + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py new file mode 100644 index 0000000..e69de29 diff --git a/src/routers/ingest/upload_drawn_aoi.py b/src/routers/upload/drawn_aoi.py similarity index 92% rename from src/routers/ingest/upload_drawn_aoi.py rename to src/routers/upload/drawn_aoi.py index d28a9d1..d06fcfd 100644 --- a/src/routers/ingest/upload_drawn_aoi.py +++ b/src/routers/upload/drawn_aoi.py @@ -9,7 +9,7 @@ router = APIRouter() -@router.post("/api/upload-drawn-aoi") +@router.post("/api/upload-drawn-aoi", tags=["upload"], description="Upload a drawn AOI boundary to cloud storage") async def upload_drawn_aoi( fire_event_name: str = Form(...), affiliation: str = Form(...), diff --git a/src/routers/ingest/upload_shapefile_zip.py b/src/routers/upload/shapefile_zip.py similarity index 95% rename from src/routers/ingest/upload_shapefile_zip.py rename to src/routers/upload/shapefile_zip.py index ceedf04..8d7fc25 100644 --- a/src/routers/ingest/upload_shapefile_zip.py +++ b/src/routers/upload/shapefile_zip.py @@ -12,7 +12,7 @@ router = APIRouter() -@router.post("/api/upload-shapefile-zip") +@router.post("/api/upload-shapefile-zip", tags=["upload"], description="Upload a shapefile zip of a predefined fire event area") async def upload_shapefile( fire_event_name: str = Form(...), affiliation: str = Form(...), From 4b41f8e2e86da6cc5e50afbb28860e20e9595d6f Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:38:53 +0000 Subject: [PATCH 094/175] whoops missing pandas import --- src/routers/fetch/ecoclass.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index 039d677..88b3118 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -5,6 +5,7 @@ import tempfile import sentry_sdk import json +import pandas as pd from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry from src.lib.query_soil import ( From b399df1e20b95875d6efb5a5b7d1d5cc27a8dbe0 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:43:31 +0000 Subject: [PATCH 095/175] rename endpoints for consistency --- src/app.py | 4 +- ...fire_event.py => spectral_burn_metrics.py} | 4 +- src/routers/check/connectivity.py | 2 +- src/routers/check/dns.py | 2 +- src/routers/check/health.py | 2 +- src/routers/check/sentry_error.py | 2 +- src/routers/fetch/ecoclass.py | 4 +- .../fetch/rangeland_analysis_platform.py | 66 +++++++++++++++++++ 8 files changed, 76 insertions(+), 10 deletions(-) rename src/routers/analyze/{fire_event.py => spectral_burn_metrics.py} (96%) diff --git a/src/app.py b/src/app.py index f887377..a0d8818 100644 --- a/src/app.py +++ b/src/app.py @@ -36,7 +36,7 @@ sentry_error ) from src.routers.analyze import ( - fire_event + spectral_burn_metrics ) from src.routers.upload import ( drawn_aoi, @@ -65,7 +65,7 @@ app.include_router(dns.router) ### ANALYZE ### -app.include_router(fire_event.router) +app.include_router(spectral_burn_metrics.router) ## UPLOAD ## app.include_router(drawn_aoi.router) diff --git a/src/routers/analyze/fire_event.py b/src/routers/analyze/spectral_burn_metrics.py similarity index 96% rename from src/routers/analyze/fire_event.py rename to src/routers/analyze/spectral_burn_metrics.py index f541d28..9a7b7cb 100644 --- a/src/routers/analyze/fire_event.py +++ b/src/routers/analyze/spectral_burn_metrics.py @@ -24,8 +24,8 @@ class AnaylzeBurnPOSTBody(BaseModel): # TODO [#5]: Decide on / implement cloud tasks or other async batch # This is a long running process, and users probably don't mind getting an email notification # or something similar when the process is complete. Esp if the frontend remanins static. -@router.post("/api/query-satellite/analyze-fire-event", tags=["analysis"], description="Analyze a fire event") -def analyze_burn( +@router.post("/api/analyze/burn-metrics", tags=["analysis"], description="Analyze a fire event") +def analyze_burn_metrics( body: AnaylzeBurnPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry), diff --git a/src/routers/check/connectivity.py b/src/routers/check/connectivity.py index ee07d2f..6bdbc41 100644 --- a/src/routers/check/connectivity.py +++ b/src/routers/check/connectivity.py @@ -6,7 +6,7 @@ router = APIRouter() -@router.get("/check-connectivity", tags=["check"], description="Check connectivity to example.com") +@router.get("/api/check/connectivity", tags=["check"], description="Check connectivity to example.com") def check_connectivity(logger: Logger = Depends(get_cloud_logger)): try: response = requests.get("http://example.com") diff --git a/src/routers/check/dns.py b/src/routers/check/dns.py index 90f0d2b..a2777b5 100644 --- a/src/routers/check/dns.py +++ b/src/routers/check/dns.py @@ -7,7 +7,7 @@ router = APIRouter() -@router.get("/check-dns", tags=["check"], summary="Check DNS resolution") +@router.get("/api/check/dns", tags=["check"], summary="Check DNS resolution") def check_dns(logger: Logger = Depends(get_cloud_logger)): try: TEST_DOMAIN = "www.google.com" diff --git a/src/routers/check/health.py b/src/routers/check/health.py index af68a65..b43b881 100644 --- a/src/routers/check/health.py +++ b/src/routers/check/health.py @@ -4,7 +4,7 @@ router = APIRouter() -@router.get("/check-health", tags=["check"], description="Health check endpoint") +@router.get("/api/check/health", tags=["check"], description="Health check endpoint") def health(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Health check endpoint called") return "Alive", 200 \ No newline at end of file diff --git a/src/routers/check/sentry_error.py b/src/routers/check/sentry_error.py index 471e22d..b5eb05b 100644 --- a/src/routers/check/sentry_error.py +++ b/src/routers/check/sentry_error.py @@ -4,7 +4,7 @@ router = APIRouter() -@router.get("/check-sentry-error", tags=["check"], summary="Trigger a division by zero error for Sentry to catch.") +@router.get("/api/check/sentry-error", tags=["check"], summary="Trigger a division by zero error for Sentry to catch.") async def trigger_error(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Triggering a division by zero error for Sentry to catch.") __division_by_zero = 1 / 0 \ No newline at end of file diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index 88b3118..5213a2a 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -22,8 +22,8 @@ class QuerySoilPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/fetch/fetch-ecoclass") -def analyze_ecoclass( +@router.post("/api/fetch/ecoclass") +def fetch_ecoclass( body: QuerySoilPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry: None = Depends(init_sentry), diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index e69de29..8e33266 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -0,0 +1,66 @@ +from fastapi import Depends, APIRouter, HTTPException +from fastapi.responses import JSONResponse +from logging import Logger +from typing import Any +from pydantic import BaseModel +import tempfile +import sentry_sdk +import json +import pandas as pd + +from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry +from src.lib.query_rap import ( + rap_get_biomass, +) +from src.util.cloud_static_io import CloudStaticIOClient + +router = APIRouter() + + +class AnaylzeRapPOSTBody(BaseModel): + geojson: Any + ignition_date: str + fire_event_name: str + affiliation: str + +@router.post("/api/fetch/rangeland_analysis_platform") +def fetch_rangeland_analysis_platform( + body: AnaylzeRapPOSTBody, + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + __sentry = Depends(init_sentry), + logger: Logger = Depends(get_cloud_logger), +): + boundary_geojson = json.loads(body.geojson) + ignition_date = body.ignition_date + fire_event_name = body.fire_event_name + affiliation = body.affiliation + + sentry_sdk.set_context("analyze_rap", {"request": body}) + + try: + rap_estimates = rap_get_biomass( + boundary_geojson=boundary_geojson, + ignition_date=ignition_date + ) + + # save the cog to the FTP server + cloud_static_io_client.upload_rap_estimates( + rap_estimates=rap_estimates, + affiliation=affiliation, + fire_event_name=fire_event_name, + ) + logger.log_text(f"RAP estimates uploaded for {fire_event_name}") + + return JSONResponse( + status_code=200, + content={ + "message": f"RAP estimates uploaded for {fire_event_name}", + "fire_event_name": fire_event_name, + }, + ) + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.log_text(f"Error: {e}") + raise HTTPException(status_code=400, detail=str(e)) + From 760d7dfc04abce698cfcc634b11f2aeca2343ba0 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:49:22 +0000 Subject: [PATCH 096/175] explicit that init_sentry returns none --- src/app.py | 194 +----------------- .../fetch/rangeland_analysis_platform.py | 4 +- src/routers/upload/drawn_aoi.py | 2 +- src/routers/upload/shapefile_zip.py | 2 +- 4 files changed, 12 insertions(+), 190 deletions(-) diff --git a/src/app.py b/src/app.py index a0d8818..d4ce2ff 100644 --- a/src/app.py +++ b/src/app.py @@ -42,19 +42,19 @@ drawn_aoi, shapefile_zip ) +from src.routers.fetch import ( + rangeland_analysis_platform, + ecoclass +) from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers from src.lib.titiler_algorithms import algorithms +## APP SETUP ## app = FastAPI(docs_url="/documentation") - - -cog = TilerFactory(process_dependency=algorithms.dependency) -app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) add_exception_handlers(app, DEFAULT_STATUS_CODES) - app.mount("/static", StaticFiles(directory="src/static"), name="static") templates = Jinja2Templates(directory="src/static") @@ -67,193 +67,17 @@ ### ANALYZE ### app.include_router(spectral_burn_metrics.router) -## UPLOAD ## +### UPLOAD ### app.include_router(drawn_aoi.router) app.include_router(shapefile_zip.router) ### FETCH ### +app.include_router(rangeland_analysis_platform.router) +app.include_router(ecoclass.router) -## TILESERVER ## +### TILESERVER ### cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) -add_exception_handlers(app, DEFAULT_STATUS_CODES) - -# class QuerySoilPOSTBody(BaseModel): -# geojson: Any -# fire_event_name: str -# affiliation: str - - -# @app.post("/api/query-soil/get-esa-mapunitid-poly") -# def get_esa_mapunitid_poly(body: QuerySoilPOSTBody): -# geojson = body.geojson -# fire_event_name = body.fire_event_name -# mapunitpoly_geojson = sdm_get_esa_mapunitid_poly(geojson) -# return JSONResponse( -# status_code=200, -# content={"mapunitpoly_geojson": json.loads(mapunitpoly_geojson)}, -# ) -# # return polygon_response - - -# class MUPair(BaseModel): -# mu_pair: Tuple[str, str] - - -# class QueryEcoclassidPOSTBody(BaseModel): -# mu_pair_tuples: List[MUPair] - - -# @app.post("/api/query-soil/get-ecoclassid-from-mu-info") -# def get_ecoclassid_from_mu_info(body: QueryEcoclassidPOSTBody): -# mu_pair_tuples = body.mu_pair_tuples -# mrla = sdm_get_ecoclassid_from_mu_info(mu_pair_tuples) -# return JSONResponse(status_code=200, content={"mrla": json.loads(mrla)}) - - -# @app.get("/api/query-soil/get-ecoclass-info") -# def get_ecoclass_info(ecoclassid: str = Query(...)): -# status_code, ecoclass_info = edit_get_ecoclass_info(ecoclassid) -# return JSONResponse( -# status_code=status_code, content={"ecoclass_info": ecoclass_info} -# ) - - -# # TODO [#6]: Restrucutre FastAPI endpoints to seperate user-facing endpoints from internal endpoints -# # refactor out the low level endpoints (/api) and rename others (this isn't really an `analysis` but it does compose a lot of logic like `analyze-burn`) -# @app.post("/api/query-soil/analyze-ecoclass") -# def analyze_ecoclass( -# body: QuerySoilPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) -# ): -# fire_event_name = body.fire_event_name -# geojson = json.loads(body.geojson) -# affiliation = body.affiliation - -# sentry_sdk.set_context("analyze_ecoclass", {"request": body}) - -# try: - -# mapunit_gdf = sdm_get_esa_mapunitid_poly(geojson) -# mu_polygon_keys = [ -# mupolygonkey -# for __musym, __nationalmusym, __mukey, mupolygonkey in mapunit_gdf.index -# ] -# mrla_df = sdm_get_ecoclassid_from_mu_info(mu_polygon_keys) - -# # join mapunitids with link table for ecoclassids -# mapunit_with_ecoclassid_df = mapunit_gdf.join(mrla_df).set_index("ecoclassid") - -# edit_ecoclass_df_row_dicts = [] -# ecoclass_ids = mrla_df["ecoclassid"].unique() - -# n_ecoclasses = len(ecoclass_ids) -# n_within_edit = 0 - -# for ecoclass_id in ecoclass_ids: -# edit_success, edit_ecoclass_json = edit_get_ecoclass_info(ecoclass_id) -# if edit_success: -# n_within_edit += 1 -# logger.log_text(f"Success: {ecoclass_id} exists within EDIT backend") -# edit_ecoclass_df_row_dict = edit_ecoclass_json["generalInformation"][ -# "dominantSpecies" -# ] -# edit_ecoclass_df_row_dict["ecoclassid"] = ecoclass_id -# edit_ecoclass_df_row_dicts.append(edit_ecoclass_df_row_dict) -# else: -# logger.log_text( -# f"Missing: {edit_ecoclass_json} doesn't exist within EDIT backend" -# ) - -# logger.log_text( -# f"Found {n_within_edit} of {n_ecoclasses} ecoclasses ({100*round(n_within_edit/n_ecoclasses, 2)}%) within EDIT backend" -# ) - -# if n_within_edit > 0: -# edit_ecoclass_df = pd.DataFrame(edit_ecoclass_df_row_dicts).set_index( -# "ecoclassid" -# ) -# else: -# # Populate with empty dataframe, for consistency's sake (so that the frontend doesn't have to handle this case) -# edit_ecoclass_df = pd.DataFrame( -# [], -# columns=[ -# "dominantTree1", -# "dominantShrub1", -# "dominantHerb1", -# "dominantTree2", -# "dominantShrub2", -# "dominantHerb2", -# ], -# ) - -# # join ecoclassids with edit ecoclass info, to get spatial ecoclass info -# edit_ecoclass_geojson = mapunit_with_ecoclassid_df.join( -# edit_ecoclass_df, how="left" -# ).to_json() - -# # save the ecoclass_geojson to the FTP server -# with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: -# tmp_geojson_path = tmp.name -# with open(tmp_geojson_path, "w") as f: -# f.write(edit_ecoclass_geojson) - -# cloud_static_io_client.upload( -# source_local_path=tmp_geojson_path, -# remote_path=f"public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson", -# ) - -# logger.log_text(f"Ecoclass GeoJSON uploaded for {fire_event_name}") -# return f"Ecoclass GeoJSON uploaded for {fire_event_name}", 200 - -# except Exception as e: -# sentry_sdk.capture_exception(e) -# logger.log_text(f"Error: {e}") -# raise HTTPException(status_code=400, detail=str(e)) - - -# class AnaylzeRapPOSTBody(BaseModel): -# geojson: Any -# ignition_date: str -# fire_event_name: str -# affiliation: str - -# @app.post("/api/query-biomass/analyze-rap") -# def analyze_rap( -# body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry = Depends(init_sentry) -# ): -# boundary_geojson = json.loads(body.geojson) -# ignition_date = body.ignition_date -# fire_event_name = body.fire_event_name -# affiliation = body.affiliation - -# sentry_sdk.set_context("analyze_rap", {"request": body}) - -# try: -# rap_estimates = rap_get_biomass( -# boundary_geojson=boundary_geojson, -# ignition_date=ignition_date -# ) - -# # save the cog to the FTP server -# cloud_static_io_client.upload_rap_estimates( -# rap_estimates=rap_estimates, -# affiliation=affiliation, -# fire_event_name=fire_event_name, -# ) -# logger.log_text(f"RAP estimates uploaded for {fire_event_name}") - -# return JSONResponse( -# status_code=200, -# content={ -# "message": f"RAP estimates uploaded for {fire_event_name}", -# "fire_event_name": fire_event_name, -# }, -# ) - -# except Exception as e: -# sentry_sdk.capture_exception(e) -# logger.log_text(f"Error: {e}") -# raise HTTPException(status_code=400, detail=str(e)) # class GetDerivedProductsPOSTBody(BaseModel): diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index 8e33266..b61d3be 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -3,10 +3,8 @@ from logging import Logger from typing import Any from pydantic import BaseModel -import tempfile import sentry_sdk import json -import pandas as pd from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry from src.lib.query_rap import ( @@ -27,7 +25,7 @@ class AnaylzeRapPOSTBody(BaseModel): def fetch_rangeland_analysis_platform( body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), - __sentry = Depends(init_sentry), + __sentry: None = Depends(init_sentry), logger: Logger = Depends(get_cloud_logger), ): boundary_geojson = json.loads(body.geojson) diff --git a/src/routers/upload/drawn_aoi.py b/src/routers/upload/drawn_aoi.py index d06fcfd..880157d 100644 --- a/src/routers/upload/drawn_aoi.py +++ b/src/routers/upload/drawn_aoi.py @@ -16,7 +16,7 @@ async def upload_drawn_aoi( geojson: str = Form(...), cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), logger: Logger = Depends(get_cloud_logger), - __sentry = Depends(init_sentry) + __sentry: None = Depends(init_sentry), ): sentry_sdk.set_context("upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}) diff --git a/src/routers/upload/shapefile_zip.py b/src/routers/upload/shapefile_zip.py index 8d7fc25..b2df791 100644 --- a/src/routers/upload/shapefile_zip.py +++ b/src/routers/upload/shapefile_zip.py @@ -19,7 +19,7 @@ async def upload_shapefile( file: UploadFile = File(...), cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), logger: Logger = Depends(get_cloud_logger), - __sentry = Depends(init_sentry) + __sentry: None = Depends(init_sentry), ): sentry_sdk.set_context("upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}) From 638648e5a4ec17440d675d41a3f372cb7c60a5b3 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 19:56:19 +0000 Subject: [PATCH 097/175] list derived products and add descriptions to analyze and fetch endpoints --- src/app.py | 35 ++++------------- src/routers/analyze/spectral_burn_metrics.py | 6 +-- src/routers/fetch/ecoclass.py | 2 +- .../fetch/rangeland_analysis_platform.py | 2 +- src/routers/list/derived_products.py | 38 +++++++++++++++++++ 5 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 src/routers/list/derived_products.py diff --git a/src/app.py b/src/app.py index d4ce2ff..e03b9e1 100644 --- a/src/app.py +++ b/src/app.py @@ -46,6 +46,9 @@ rangeland_analysis_platform, ecoclass ) +from src.routers.list import ( + derived_products +) from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers @@ -75,38 +78,14 @@ app.include_router(rangeland_analysis_platform.router) app.include_router(ecoclass.router) +### LIST ### +app.include_router(derived_products.router) + ### TILESERVER ### cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) - -# class GetDerivedProductsPOSTBody(BaseModel): -# fire_event_name: str -# affiliation: str - -# @app.post("/api/get-derived-products") -# async def get_derived_products( -# body: GetDerivedProductsPOSTBody, -# cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), -# __sentry = Depends(init_sentry) -# ): -# fire_event_name = body.fire_event_name -# affiliation = body.affiliation - -# sentry_sdk.set_context("get_derived_products", {"fire_event_name": fire_event_name, "affiliation": affiliation}) - -# try: -# derived_products = cloud_static_io_client.get_derived_products( -# affiliation=affiliation, fire_event_name=fire_event_name -# ) -# return JSONResponse(status_code=200, content=derived_products) - -# except Exception as e: -# sentry_sdk.capture_exception(e) -# logger.log_text(f"Error: {e}") -# raise HTTPException(status_code=400, detail=str(e)) - -# ### WEB PAGES ### +### WEB PAGES ### # @app.get( diff --git a/src/routers/analyze/spectral_burn_metrics.py b/src/routers/analyze/spectral_burn_metrics.py index 9a7b7cb..665a671 100644 --- a/src/routers/analyze/spectral_burn_metrics.py +++ b/src/routers/analyze/spectral_burn_metrics.py @@ -24,11 +24,11 @@ class AnaylzeBurnPOSTBody(BaseModel): # TODO [#5]: Decide on / implement cloud tasks or other async batch # This is a long running process, and users probably don't mind getting an email notification # or something similar when the process is complete. Esp if the frontend remanins static. -@router.post("/api/analyze/burn-metrics", tags=["analysis"], description="Analyze a fire event") -def analyze_burn_metrics( +@router.post("/api/analyze/spectral-burn-metrics", tags=["analysis"], description="Derive spectral burn metrics from satellite imagery within a boundary.") +def analyze_spectral_burn_metrics( body: AnaylzeBurnPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), - __sentry = Depends(init_sentry), + __sentry: None = Depends(init_sentry), logger: Logger = Depends(get_cloud_logger), ): geojson_boundary = json.loads(body.geojson) diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index 5213a2a..e5eeea3 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -22,7 +22,7 @@ class QuerySoilPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/fetch/ecoclass") +@router.post("/api/fetch/ecoclass", tags=["fetch"], description="Fetch ecoclass data (using Soil Data Mart / Web Soil Survey for Map Unit polygons, and the Ecological Site Description database for ecoclass info)") def fetch_ecoclass( body: QuerySoilPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index b61d3be..85a56a8 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -21,7 +21,7 @@ class AnaylzeRapPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/fetch/rangeland_analysis_platform") +@router.post("/api/fetch/rangeland_analysis_platform", tags=["fetch"], description="Fetch Rangeland Analysis Platform (RAP) biomass estimates" def fetch_rangeland_analysis_platform( body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), diff --git a/src/routers/list/derived_products.py b/src/routers/list/derived_products.py new file mode 100644 index 0000000..68c04aa --- /dev/null +++ b/src/routers/list/derived_products.py @@ -0,0 +1,38 @@ +from fastapi import Depends, APIRouter, HTTPException +from fastapi.responses import JSONResponse +from logging import Logger +from typing import Any +from pydantic import BaseModel +import sentry_sdk + +from ..dependencies import get_cloud_logger, get_cloud_static_io_client, init_sentry +from src.util.cloud_static_io import CloudStaticIOClient + +router = APIRouter() + +class GetDerivedProductsPOSTBody(BaseModel): + fire_event_name: str + affiliation: str + +@router.post("/api/list/derived-products", tags=["list"], description="List derived products of a fiven fire event / affiliation combination.") +async def list_derived_products( + body: GetDerivedProductsPOSTBody, + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + __sentry: None = Depends(init_sentry), + logger: Logger = Depends(get_cloud_logger), +): + fire_event_name = body.fire_event_name + affiliation = body.affiliation + + sentry_sdk.set_context("get_derived_products", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + + try: + derived_products = cloud_static_io_client.get_derived_products( + affiliation=affiliation, fire_event_name=fire_event_name + ) + return JSONResponse(status_code=200, content=derived_products) + + except Exception as e: + sentry_sdk.capture_exception(e) + logger.log_text(f"Error: {e}") + raise HTTPException(status_code=400, detail=str(e)) From c1dd4bf03a2b3023a87030eaa3393170a9c64828 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 20:26:34 +0000 Subject: [PATCH 098/175] pages reorg to suit --- src/app.py | 179 ++---------------- src/routers/dependencies.py | 3 + .../fetch/rangeland_analysis_platform.py | 2 +- src/routers/pages/directory.py | 27 +++ src/routers/pages/home.py | 26 +++ src/routers/pages/map.py | 86 +++++++++ src/routers/pages/upload.py | 25 +++ 7 files changed, 185 insertions(+), 163 deletions(-) create mode 100644 src/routers/pages/directory.py create mode 100644 src/routers/pages/home.py create mode 100644 src/routers/pages/map.py create mode 100644 src/routers/pages/upload.py diff --git a/src/app.py b/src/app.py index e03b9e1..d2aa3fc 100644 --- a/src/app.py +++ b/src/app.py @@ -1,34 +1,9 @@ -import os -import json -from pathlib import Path -import uvicorn -from pydantic import BaseModel -from google.cloud import logging -import tempfile -from typing import Tuple, List, Any -from pydantic import BaseModel -import pandas as pd -import sentry_sdk -from markdown import markdown -from pathlib import Path -# For network debugging -import requests -from fastapi import HTTPException - -from fastapi import ( - FastAPI, - Depends, - HTTPException, - Request, - UploadFile, - File, - Form, - Query, -) -from fastapi.responses import HTMLResponse, JSONResponse -from fastapi.templating import Jinja2Templates +from fastapi import FastAPI from fastapi.staticfiles import StaticFiles +from titiler.core.factory import TilerFactory +from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers + from src.routers.check import ( connectivity, dns, @@ -49,9 +24,12 @@ from src.routers.list import ( derived_products ) - -from titiler.core.factory import TilerFactory -from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers +from src.routers.pages import ( + home, + map, + upload, + directory +) from src.lib.titiler_algorithms import algorithms @@ -59,7 +37,13 @@ app = FastAPI(docs_url="/documentation") add_exception_handlers(app, DEFAULT_STATUS_CODES) app.mount("/static", StaticFiles(directory="src/static"), name="static") -templates = Jinja2Templates(directory="src/static") +# templates = Jinja2Templates(directory="src/static") + +### WEB PAGES ### +app.include_router(home.router) +app.include_router(map.router) +app.include_router(upload.router) +app.include_router(directory.router) ### CHECK ### app.include_router(health.router) @@ -85,132 +69,3 @@ cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) -### WEB PAGES ### - - -# @app.get( -# "/map/{affiliation}/{fire_event_name}/{burn_metric}", response_class=HTMLResponse -# ) -# def serve_map( -# request: Request, -# fire_event_name: str, -# burn_metric: str, -# affiliation: str, -# manifest: dict = Depends(get_manifest), -# ): -# mapbox_token = get_mapbox_secret() - -# tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") -# # tileserver_endpoint = "http://localhost:5050" - -# ## TODO [#21]: Use Tofu Output to construct hardocded cog and geojson urls (in case we change s3 bucket name) -# cog_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" -# burn_boundary_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" -# ecoclass_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" -# severity_obs_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" -# cog_tileserver_url_prefix = ( -# tileserver_endpoint -# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={cog_url}&nodata=-99&return_mask=true" -# ) - -# rap_cog_annual_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" -# rap_tileserver_annual_url = ( -# tileserver_endpoint -# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_annual_url}&nodata=-99&return_mask=true" -# ) - -# rap_cog_perennial_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" -# rap_tileserver_perennial_url = ( -# tileserver_endpoint -# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_perennial_url}&nodata=-99&return_mask=true" -# ) - -# rap_cog_shrub_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" -# rap_tileserver_shrub_url = ( -# tileserver_endpoint -# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_shrub_url}&nodata=-99&return_mask=true" -# ) - -# rap_cog_tree_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" -# rap_tileserver_tree_url = ( -# tileserver_endpoint -# + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" -# ) - - -# fire_metadata = manifest[affiliation][fire_event_name] -# fire_metadata_json = json.dumps(fire_metadata) - -# with open("src/static/map/burn_metric_text.json") as json_file: -# burn_metric_text = json.load(json_file) - -# return templates.TemplateResponse( -# "map/map.html", -# { -# "request": request, -# "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 -# "fire_event_name": fire_event_name, -# "burn_metric": burn_metric, -# "burn_metric_text": burn_metric_text, -# "fire_metadata_json": fire_metadata_json, -# "cog_tileserver_url_prefix": cog_tileserver_url_prefix, -# "burn_boundary_geojson_url": burn_boundary_geojson_url, -# "ecoclass_geojson_url": ecoclass_geojson_url, -# "severity_obs_geojson_url": severity_obs_geojson_url, -# "rap_tileserver_annual_url": rap_tileserver_annual_url, -# "rap_tileserver_perennial_url": rap_tileserver_perennial_url, -# "rap_tileserver_shrub_url": rap_tileserver_shrub_url, -# "rap_tileserver_tree_url": rap_tileserver_tree_url, -# }, -# ) - - -# @app.get("/upload", response_class=HTMLResponse) -# def upload(request: Request): -# mapbox_token = get_mapbox_secret() -# tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") - -# return templates.TemplateResponse( -# "upload/upload.html", -# { -# "request": request, -# "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 -# "tileserver_endpoint": tileserver_endpoint, -# } -# ) - -# @app.get("/directory", response_class=HTMLResponse) -# def directory(request: Request, manifest: dict = Depends(get_manifest)): -# mapbox_token = get_mapbox_secret() -# manifest_json = json.dumps(manifest) -# cloud_run_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") -# return templates.TemplateResponse( -# "directory/directory.html", -# { -# "request": request, -# "manifest": manifest_json, -# "mapbox_token": mapbox_token, -# "cloud_run_endpoint": cloud_run_endpoint -# } -# ) - -# @app.get("/sketch", response_class=HTMLResponse) -# def sketch(request: Request): -# return templates.TemplateResponse("sketch/sketch.html", {"request": request}) - -# @app.get("/", response_class=HTMLResponse) -# def home(request: Request): -# # Read the markdown file -# with open(Path("src/static/home/home.md")) as f: -# md_content = f.read() - -# # Convert markdown to HTML -# html_content = markdown(md_content) - -# return templates.TemplateResponse( -# "home/home.html", -# { -# "request": request, -# "content": html_content, -# }, -# ) \ No newline at end of file diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index 0723605..10716df 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -4,6 +4,7 @@ from google.cloud import logging import sentry_sdk from src.util.cloud_static_io import CloudStaticIOClient +from src.util.gcp_secrets import get_mapbox_secret as gcp_get_mapbox_secret import os def get_cloud_logger(): @@ -44,3 +45,5 @@ def init_sentry( sentry_sdk.set_context("env", {"env": os.getenv('ENV')}) logger.log_text("Sentry initialized") +def get_mapbox_secret(): + return gcp_get_mapbox_secret() \ No newline at end of file diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index 85a56a8..3cd28fb 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -21,7 +21,7 @@ class AnaylzeRapPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/fetch/rangeland_analysis_platform", tags=["fetch"], description="Fetch Rangeland Analysis Platform (RAP) biomass estimates" +@router.post("/api/fetch/rangeland_analysis_platform", tags=["fetch"], description="Fetch Rangeland Analysis Platform (RAP) biomass estimates") def fetch_rangeland_analysis_platform( body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), diff --git a/src/routers/pages/directory.py b/src/routers/pages/directory.py new file mode 100644 index 0000000..a3617e2 --- /dev/null +++ b/src/routers/pages/directory.py @@ -0,0 +1,27 @@ +from fastapi import Depends, APIRouter, Request +from fastapi.responses import HTMLResponse +import os +from fastapi.templating import Jinja2Templates +import json +from ..dependencies import get_mapbox_secret, get_manifest + +router = APIRouter() +templates = Jinja2Templates(directory="src/static") + +@router.get("/directory", response_class=HTMLResponse) +def directory( + request: Request, + manifest: dict = Depends(get_manifest), + mapbox_token: str = Depends(get_mapbox_secret) +): + manifest_json = json.dumps(manifest) + cloud_run_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") + return templates.TemplateResponse( + "directory/directory.html", + { + "request": request, + "manifest": manifest_json, + "mapbox_token": mapbox_token, + "cloud_run_endpoint": cloud_run_endpoint + } + ) \ No newline at end of file diff --git a/src/routers/pages/home.py b/src/routers/pages/home.py new file mode 100644 index 0000000..f6b8a32 --- /dev/null +++ b/src/routers/pages/home.py @@ -0,0 +1,26 @@ +from fastapi import Depends, APIRouter, Request +from fastapi.responses import HTMLResponse +from fastapi.templating import Jinja2Templates +from ..dependencies import get_mapbox_secret, get_manifest +from pathlib import Path +from markdown import markdown + +router = APIRouter() +templates = Jinja2Templates(directory="src/static") + +@router.get("/", response_class=HTMLResponse) +def home(request: Request): + # Read the markdown file + with open(Path("src/static/home/home.md")) as f: + md_content = f.read() + + # Convert markdown to HTML + html_content = markdown(md_content) + + return templates.TemplateResponse( + "home/home.html", + { + "request": request, + "content": html_content, + }, + ) \ No newline at end of file diff --git a/src/routers/pages/map.py b/src/routers/pages/map.py new file mode 100644 index 0000000..85f9364 --- /dev/null +++ b/src/routers/pages/map.py @@ -0,0 +1,86 @@ +from fastapi import Depends, APIRouter, Request +from fastapi.responses import HTMLResponse +import os +import json +from fastapi.templating import Jinja2Templates + +from ..dependencies import get_manifest, get_mapbox_secret + +router = APIRouter() +templates = Jinja2Templates(directory="src/static") + +@router.get( + "/map/{affiliation}/{fire_event_name}/{burn_metric}", response_class=HTMLResponse +) +def serve_map( + request: Request, + fire_event_name: str, + burn_metric: str, + affiliation: str, + manifest: dict = Depends(get_manifest), + mapbox_token: str = Depends(get_mapbox_secret), +): + + tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") + # tileserver_endpoint = "http://localhost:5050" + + ## TODO [#21]: Use Tofu Output to construct hardocded cog and geojson urls (in case we change s3 bucket name) + cog_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" + burn_boundary_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" + ecoclass_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" + severity_obs_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" + cog_tileserver_url_prefix = ( + tileserver_endpoint + + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={cog_url}&nodata=-99&return_mask=true" + ) + + rap_cog_annual_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" + rap_tileserver_annual_url = ( + tileserver_endpoint + + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_annual_url}&nodata=-99&return_mask=true" + ) + + rap_cog_perennial_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" + rap_tileserver_perennial_url = ( + tileserver_endpoint + + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_perennial_url}&nodata=-99&return_mask=true" + ) + + rap_cog_shrub_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" + rap_tileserver_shrub_url = ( + tileserver_endpoint + + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_shrub_url}&nodata=-99&return_mask=true" + ) + + rap_cog_tree_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" + rap_tileserver_tree_url = ( + tileserver_endpoint + + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" + ) + + + fire_metadata = manifest[affiliation][fire_event_name] + fire_metadata_json = json.dumps(fire_metadata) + + with open("src/static/map/burn_metric_text.json") as json_file: + burn_metric_text = json.load(json_file) + + return templates.TemplateResponse( + "map/map.html", + { + "request": request, + "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 + "fire_event_name": fire_event_name, + "burn_metric": burn_metric, + "burn_metric_text": burn_metric_text, + "fire_metadata_json": fire_metadata_json, + "cog_tileserver_url_prefix": cog_tileserver_url_prefix, + "burn_boundary_geojson_url": burn_boundary_geojson_url, + "ecoclass_geojson_url": ecoclass_geojson_url, + "severity_obs_geojson_url": severity_obs_geojson_url, + "rap_tileserver_annual_url": rap_tileserver_annual_url, + "rap_tileserver_perennial_url": rap_tileserver_perennial_url, + "rap_tileserver_shrub_url": rap_tileserver_shrub_url, + "rap_tileserver_tree_url": rap_tileserver_tree_url, + }, + ) diff --git a/src/routers/pages/upload.py b/src/routers/pages/upload.py new file mode 100644 index 0000000..06616f3 --- /dev/null +++ b/src/routers/pages/upload.py @@ -0,0 +1,25 @@ +from fastapi import Depends, APIRouter, Request +from fastapi.responses import HTMLResponse +import os +from fastapi.templating import Jinja2Templates + +from ..dependencies import get_mapbox_secret + +router = APIRouter() +templates = Jinja2Templates(directory="src/static") + +@router.get("/upload", response_class=HTMLResponse) +def upload( + request: Request, + mapbox_token: str = Depends(get_mapbox_secret) +): + tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") + + return templates.TemplateResponse( + "upload/upload.html", + { + "request": request, + "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 + "tileserver_endpoint": tileserver_endpoint, + } + ) \ No newline at end of file From 264da63541e73225148617a4dc96ad6e5d2688e4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 20:34:10 +0000 Subject: [PATCH 099/175] finish converting api endpoints, plus a log_text rather than info for cloud logger --- src/routers/upload/drawn_aoi.py | 2 +- src/routers/upload/shapefile_zip.py | 6 +++--- src/static/directory/directory.html | 2 +- src/static/upload/upload.html | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/routers/upload/drawn_aoi.py b/src/routers/upload/drawn_aoi.py index 880157d..935bbd6 100644 --- a/src/routers/upload/drawn_aoi.py +++ b/src/routers/upload/drawn_aoi.py @@ -9,7 +9,7 @@ router = APIRouter() -@router.post("/api/upload-drawn-aoi", tags=["upload"], description="Upload a drawn AOI boundary to cloud storage") +@router.post("/api/upload/drawn-aoi", tags=["upload"], description="Upload a drawn AOI boundary to cloud storage") async def upload_drawn_aoi( fire_event_name: str = Form(...), affiliation: str = Form(...), diff --git a/src/routers/upload/shapefile_zip.py b/src/routers/upload/shapefile_zip.py index b2df791..f62129e 100644 --- a/src/routers/upload/shapefile_zip.py +++ b/src/routers/upload/shapefile_zip.py @@ -12,7 +12,7 @@ router = APIRouter() -@router.post("/api/upload-shapefile-zip", tags=["upload"], description="Upload a shapefile zip of a predefined fire event area") +@router.post("/api/upload/shapefile-zip", tags=["upload"], description="Upload a shapefile zip of a predefined fire event area") async def upload_shapefile( fire_event_name: str = Form(...), affiliation: str = Form(...), @@ -47,7 +47,7 @@ async def upload_shapefile( remote_path=user_uploaded_s3_path, ) - logger.info(f"Uploaded zip file ({user_uploaded_s3_path})") + logger.log_text(f"Uploaded zip file ({user_uploaded_s3_path})") with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: tmp_geojson = tmp.name @@ -59,7 +59,7 @@ async def upload_shapefile( remote_path=boundary_s3_path, ) - logger.info(f"Uploaded geojson file ({boundary_s3_path})") + logger.log_text(f"Uploaded geojson file ({boundary_s3_path})") return JSONResponse(status_code=200, content={"geojson": geojson}) diff --git a/src/static/directory/directory.html b/src/static/directory/directory.html index 16ee550..61b8888 100644 --- a/src/static/directory/directory.html +++ b/src/static/directory/directory.html @@ -108,7 +108,7 @@ // If all four requests are successful, get the map url and all derived products var derivedProductsResponse = $.ajax({ - url: "/api/get-derived-products", + url: "/api/list/derived-products", type: "post", dataType: "json", contentType: "application/json", diff --git a/src/static/upload/upload.html b/src/static/upload/upload.html index 5ebcb4d..e0442e4 100644 --- a/src/static/upload/upload.html +++ b/src/static/upload/upload.html @@ -251,7 +251,7 @@

Derived Products

function getProducts(tileserverEndpoint, affiliation, fireEventName) { // If all four requests are successful, get the map url and all derived products var derivedProductsResponse = $.ajax({ - url: "/api/get-derived-products", + url: "/api/list/derived-products", type: "post", dataType: "json", contentType: "application/json", @@ -440,7 +440,7 @@

Derived Products

// Make a request to upload the drawn shape var upload = $.ajax({ - url: `/api/upload-drawn-aoi`, + url: `/api/upload/drawn-aoi`, type: "post", processData: false, contentType: false, @@ -463,7 +463,7 @@

Derived Products

// Make a request to upload the shapefile var upload = $.ajax({ - url: `/api/upload-shapefile-zip`, + url: `/api/upload/shapefile-zip`, type: "post", processData: false, contentType: false, @@ -523,7 +523,7 @@

Derived Products

// Make a request to analyze the burn var analyzeBurn = $.ajax({ - url: "/api/query-satellite/analyze-burn", + url: "/api/analyze/spectral-burn-metrics", type: "post", dataType: "json", contentType: "application/json", @@ -588,7 +588,7 @@

Derived Products

} $.ajax({ - url: "/api/query-soil/analyze-ecoclass", + url: "/api/fetch/ecoclass", type: "post", dataType: "json", contentType: "application/json", @@ -616,7 +616,7 @@

Derived Products

}); $.ajax({ - url: "/api/query-biomass/analyze-rap", + url: "/api/fetch/rangeland-analysis-platform", type: "post", dataType: "json", contentType: "application/json", From f8bb8640307eddb035845110b4bb8c9cf8d48789 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 12 Feb 2024 20:38:11 +0000 Subject: [PATCH 100/175] dashes over underscore for endpoint rap --- src/routers/fetch/rangeland_analysis_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index 3cd28fb..fed4f7e 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -21,7 +21,7 @@ class AnaylzeRapPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/fetch/rangeland_analysis_platform", tags=["fetch"], description="Fetch Rangeland Analysis Platform (RAP) biomass estimates") +@router.post("/api/fetch/rangeland-analysis-platform", tags=["fetch"], description="Fetch Rangeland Analysis Platform (RAP) biomass estimates") def fetch_rangeland_analysis_platform( body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), From 5558c168cab1b590650081e190d1902aa10c38f3 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 17:01:56 +0000 Subject: [PATCH 101/175] test assets from stac --- tests/assets/test_nir.tif | Bin 0 -> 23778 bytes tests/assets/test_nir_reduced.tif | Bin 0 -> 3730 bytes tests/assets/test_stac_items.json | 1507 ++++++++++++++++++++++++++++ tests/assets/test_swir.tif | Bin 0 -> 23778 bytes tests/assets/test_swir_reduced.tif | Bin 0 -> 3730 bytes tests/conftest.py | 0 tests/unit/lib/burn_severity.py | 0 7 files changed, 1507 insertions(+) create mode 100644 tests/assets/test_nir.tif create mode 100644 tests/assets/test_nir_reduced.tif create mode 100644 tests/assets/test_stac_items.json create mode 100644 tests/assets/test_swir.tif create mode 100644 tests/assets/test_swir_reduced.tif create mode 100644 tests/conftest.py create mode 100644 tests/unit/lib/burn_severity.py diff --git a/tests/assets/test_nir.tif b/tests/assets/test_nir.tif new file mode 100644 index 0000000000000000000000000000000000000000..e83fce4658198c44855d24f0f1e4d86490e02888 GIT binary patch literal 23778 zcmeIy&ui0Q7zgktZS9H_VK6I*l$Xv!E0v~cnkCSdPAj9ZO=%|{C2il1;rfFngG~k# z6?*eO5QYdIrFZ`fFZvgF@+>>~yy?n1*+q~Z$zl{eyUpapshe>cx$-00nXI^&Z!Fy&4-Qplb!nG?UeA`-f*8^S5Jx2K!uL<+M~VU z^u;~jZNE^L`*y*$E!j03*|nu~-J&Ur1+Lw8%j~RK@-If^7dOgS+^E9hMwyEnRb1RC z>mQ9Os^rgt>w~wd0$7%o{zfMr`JK%qS?*Iozf!Xj-uD(pX9xuw{b?(ngygjkmjtHn3Z4S2P4+ zTF@;JOyng>d_+r8G1^wtNQ@68K8VH#@ttUlNmZgTDh7fYoxArtO?Q(u(LcK1{LY-2 z^XuH1xi=b}DWxJZMZ{MmKHHjV|9oTGe%kdmUPRl?D;d+C>?_WWo#GdnHYR=^ZFkP| z$uR+ui^t?N(=M_(6E_9KWtT0`yEz~ww!PK1pUo6W(6$fT_FyI;wv3#y?V*f6>%Pne zOut{Y+4zY4>#^-(%aoI{zaq6w3mUFnoJc!yC+*ag1%tKGbRro{ImyJ_(1L54=P!#c zZHX?qE)?u`l51AmU{xsC)#*0kiSAfe$C`9UXDU?JopxfY+@CAjBeSdKM6Ozyh*dlB zL``*#6A#aF!jbTm?eSPR5~;0pE7XGW%sW~{OdVO|!Gilh3+Xvy%82A|Rr_jUbOdhXB&xgz4h45@R zK9A>kdbIvmL!OW0@B$#YIXw(i_L;Pxl@~Bh*W)JiBB=lOpZItt zk1wOk$p4J`r-(m|)^&UYt>-AfxEtWkTuLJvAEM`F1#!-wT#9C&8Ra2-un*)p^vC$< zM!x`85+6XHK{t5w%Z^-{4spjW(8Qrg~UcVUfo=3N#*FjxJeNIq()MY(n zIeucqo6wwd^9A!k;a)Q5lJ`0F<@Cnvdt*7e7wZ4DiFw+Izk$~K^Ch(MZ}QZ?o;&uR zQF5;Je4Fj~x)rJ~JGtg60yhj=|UH{^ork`X1z76s4Yb^^?y}sCx=O`rUAn_}g$j{XADQ89z|wp>-ct zvMzdmUxtq7G2c&~HV;z&5Y&56&%;B+hu}w0&!tlDLC%+OuZ#Ns%Nu8Of9Cm+LV64J zw!_#53v`o3(WU%xB#I|7;y z>DPNe-%lsJxCD8w1$-1-KZo(qtfh|bL+%-~ggnlvnc&q^isrw_c%|-#|LTi;vy}Ou gbl`*U2HA_=L7sje>vMymKHennamM{mU+#MT0|ROCivR!s literal 0 HcmV?d00001 diff --git a/tests/assets/test_stac_items.json b/tests/assets/test_stac_items.json new file mode 100644 index 0000000..041fa09 --- /dev/null +++ b/tests/assets/test_stac_items.json @@ -0,0 +1,1507 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "stac_version": "1.0.0", + "id": "S2B_MSIL2A_20230515T181919_R127_T11SNT_20230516T003018", + "properties": { + "datetime": "2023-05-15T18:19:19.024000Z", + "platform": "Sentinel-2B", + "proj:epsg": 32611, + "instruments": [ + "msi" + ], + "s2:mgrs_tile": "11SNT", + "constellation": "Sentinel 2", + "s2:granule_id": "S2B_OPER_MSI_L2A_TL_MSFT_20230516T003019_A032328_T11SNT_N05.09", + "eo:cloud_cover": 37.159356, + "s2:datatake_id": "GS2B_20230515T181919_032328_N05.09", + "s2:product_uri": "S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE", + "s2:datastrip_id": "S2B_OPER_MSI_L2A_DS_MSFT_20230516T003019_S20230515T182328_N05.09", + "s2:product_type": "S2MSI2A", + "sat:orbit_state": "descending", + "s2:datatake_type": "INS-NOBS", + "s2:generation_time": "2023-05-16T00:30:18.518582Z", + "sat:relative_orbit": 127, + "s2:water_percentage": 1.141692, + "s2:mean_solar_zenith": 21.0784572215242, + "s2:mean_solar_azimuth": 130.791556117879, + "s2:processing_baseline": "05.09", + "s2:snow_ice_percentage": 0.283777, + "s2:vegetation_percentage": 7.752956, + "s2:thin_cirrus_percentage": 9.908076, + "s2:cloud_shadow_percentage": 0.027254, + "s2:nodata_pixel_percentage": 0.383078, + "s2:unclassified_percentage": 0.727199, + "s2:dark_features_percentage": 0.00848, + "s2:not_vegetated_percentage": 52.899283, + "s2:degraded_msi_data_percentage": 0.0212, + "s2:high_proba_clouds_percentage": 14.416718, + "s2:reflectance_conversion_factor": 0.98043845555219, + "s2:medium_proba_clouds_percentage": 12.834562, + "s2:saturated_defective_pixel_percentage": 0.0 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -117.00021, + 34.1797164 + ], + [ + -116.98247, + 34.2421181 + ], + [ + -116.95686, + 34.3414522 + ], + [ + -115.80655, + 34.335847 + ], + [ + -115.82024, + 33.3457571 + ], + [ + -117.00021, + 33.3513614 + ], + [ + -117.00021, + 34.1797164 + ] + ] + ] + }, + "links": [ + { + "rel": "collection", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a", + "type": "application/json" + }, + { + "rel": "parent", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a", + "type": "application/json" + }, + { + "rel": "root", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1", + "type": "application/json", + "title": "Microsoft Planetary Computer STAC API" + }, + { + "rel": "self", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2B_MSIL2A_20230515T181919_R127_T11SNT_20230516T003018", + "type": "application/geo+json" + }, + { + "rel": "license", + "href": "https://sentinel.esa.int/documents/247904/690755/Sentinel_Data_Legal_Notice" + }, + { + "rel": "preview", + "href": "https://planetarycomputer.microsoft.com/api/data/v1/item/map?collection=sentinel-2-l2a&item=S2B_MSIL2A_20230515T181919_R127_T11SNT_20230516T003018", + "type": "text/html", + "title": "Map of item" + } + ], + "assets": { + "AOT": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_AOT_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Aerosol optical thickness (AOT)", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "roles": [ + "data" + ] + }, + "B01": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R60m/T11SNT_20230515T181919_B01_60m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 1 - Coastal aerosol - 60m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3800040.0 + ], + "gsd": 60.0, + "eo:bands": [ + { + "name": "B01", + "common_name": "coastal", + "description": "Band 1 - Coastal aerosol", + "center_wavelength": 0.443, + "full_width_half_max": 0.027 + } + ], + "roles": [ + "data" + ] + }, + "B02": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_B02_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 2 - Blue - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "description": "Band 2 - Blue", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "roles": [ + "data" + ] + }, + "B03": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_B03_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 3 - Green - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B03", + "common_name": "green", + "description": "Band 3 - Green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "roles": [ + "data" + ] + }, + "B04": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_B04_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 4 - Red - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "description": "Band 4 - Red", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + } + ], + "roles": [ + "data" + ] + }, + "B05": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_B05_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 5 - Vegetation red edge 1 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B05", + "common_name": "rededge", + "description": "Band 5 - Vegetation red edge 1", + "center_wavelength": 0.704, + "full_width_half_max": 0.019 + } + ], + "roles": [ + "data" + ] + }, + "B06": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_B06_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 6 - Vegetation red edge 2 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B06", + "common_name": "rededge", + "description": "Band 6 - Vegetation red edge 2", + "center_wavelength": 0.74, + "full_width_half_max": 0.018 + } + ], + "roles": [ + "data" + ] + }, + "B07": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_B07_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 7 - Vegetation red edge 3 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B07", + "common_name": "rededge", + "description": "Band 7 - Vegetation red edge 3", + "center_wavelength": 0.783, + "full_width_half_max": 0.028 + } + ], + "roles": [ + "data" + ] + }, + "B08": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_B08_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 8 - NIR - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B08", + "common_name": "nir", + "description": "Band 8 - NIR", + "center_wavelength": 0.842, + "full_width_half_max": 0.145 + } + ], + "roles": [ + "data" + ] + }, + "B09": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R60m/T11SNT_20230515T181919_B09_60m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 9 - Water vapor - 60m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3800040.0 + ], + "gsd": 60.0, + "eo:bands": [ + { + "name": "B09", + "description": "Band 9 - Water vapor", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "roles": [ + "data" + ] + }, + "B11": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_B11_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 11 - SWIR (1.6) - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B11", + "common_name": "swir16", + "description": "Band 11 - SWIR (1.6)", + "center_wavelength": 1.61, + "full_width_half_max": 0.143 + } + ], + "roles": [ + "data" + ] + }, + "B12": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_B12_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 12 - SWIR (2.2) - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B12", + "common_name": "swir22", + "description": "Band 12 - SWIR (2.2)", + "center_wavelength": 2.19, + "full_width_half_max": 0.242 + } + ], + "roles": [ + "data" + ] + }, + "B8A": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_B8A_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 8A - Vegetation red edge 4 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B8A", + "common_name": "rededge", + "description": "Band 8A - Vegetation red edge 4", + "center_wavelength": 0.865, + "full_width_half_max": 0.033 + } + ], + "roles": [ + "data" + ] + }, + "SCL": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R20m/T11SNT_20230515T181919_SCL_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Scene classfication map (SCL)", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "roles": [ + "data" + ] + }, + "WVP": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_WVP_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Water vapour (WVP)", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "roles": [ + "data" + ] + }, + "visual": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/IMG_DATA/R10m/T11SNT_20230515T181919_TCI_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "True color image", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "description": "Band 4 - Red", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "description": "Band 3 - Green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "description": "Band 2 - Blue", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "roles": [ + "data" + ] + }, + "preview": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/QI_DATA/T11SNT_20230515T181919_PVI.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Thumbnail", + "roles": [ + "thumbnail" + ] + }, + "safe-manifest": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/manifest.safe?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "SAFE manifest", + "roles": [ + "metadata" + ] + }, + "granule-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/GRANULE/L2A_T11SNT_A032328_20230515T182328/MTD_TL.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "Granule metadata", + "roles": [ + "metadata" + ] + }, + "inspire-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/INSPIRE.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "INSPIRE metadata", + "roles": [ + "metadata" + ] + }, + "product-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/MTD_MSIL2A.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "Product metadata", + "roles": [ + "metadata" + ] + }, + "datastrip-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/15/S2B_MSIL2A_20230515T181919_N0509_R127_T11SNT_20230516T003018.SAFE/DATASTRIP/DS_MSFT_20230516T003019_S20230515T182328/MTD_DS.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "Datastrip metadata", + "roles": [ + "metadata" + ] + }, + "tilejson": { + "href": "https://planetarycomputer.microsoft.com/api/data/v1/item/tilejson.json?collection=sentinel-2-l2a&item=S2B_MSIL2A_20230515T181919_R127_T11SNT_20230516T003018&assets=visual&asset_bidx=visual%7C1%2C2%2C3&nodata=0&format=png", + "type": "application/json", + "title": "TileJSON with default rendering", + "roles": [ + "tiles" + ] + }, + "rendered_preview": { + "href": "https://planetarycomputer.microsoft.com/api/data/v1/item/preview.png?collection=sentinel-2-l2a&item=S2B_MSIL2A_20230515T181919_R127_T11SNT_20230516T003018&assets=visual&asset_bidx=visual%7C1%2C2%2C3&nodata=0&format=png", + "type": "image/png", + "title": "Rendered preview", + "rel": "preview", + "roles": [ + "overview" + ] + } + }, + "bbox": [ + -117.00021, + 33.34575708, + -115.80655, + 34.34145222 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "collection": "sentinel-2-l2a" + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "id": "S2A_MSIL2A_20230510T181921_R127_T11SNT_20230511T023248", + "properties": { + "datetime": "2023-05-10T18:19:21.024000Z", + "platform": "Sentinel-2A", + "proj:epsg": 32611, + "instruments": [ + "msi" + ], + "s2:mgrs_tile": "11SNT", + "constellation": "Sentinel 2", + "s2:granule_id": "S2A_OPER_MSI_L2A_TL_MSFT_20230511T023249_A041165_T11SNT_N05.09", + "eo:cloud_cover": 20.108809, + "s2:datatake_id": "GS2A_20230510T181921_041165_N05.09", + "s2:product_uri": "S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE", + "s2:datastrip_id": "S2A_OPER_MSI_L2A_DS_MSFT_20230511T023249_S20230510T183239_N05.09", + "s2:product_type": "S2MSI2A", + "sat:orbit_state": "descending", + "s2:datatake_type": "INS-NOBS", + "s2:generation_time": "2023-05-11T02:32:48.834801Z", + "sat:relative_orbit": 127, + "s2:water_percentage": 2.814128, + "s2:mean_solar_zenith": 22.03715528814, + "s2:mean_solar_azimuth": 132.924617701217, + "s2:processing_baseline": "05.09", + "s2:snow_ice_percentage": 0.857083, + "s2:vegetation_percentage": 2.626031, + "s2:thin_cirrus_percentage": 0.013514, + "s2:cloud_shadow_percentage": 0.066322, + "s2:nodata_pixel_percentage": 0.493406, + "s2:unclassified_percentage": 0.387745, + "s2:dark_features_percentage": 0.016141, + "s2:not_vegetated_percentage": 73.123735, + "s2:degraded_msi_data_percentage": 0.0066, + "s2:high_proba_clouds_percentage": 17.905851, + "s2:reflectance_conversion_factor": 0.982759674106256, + "s2:medium_proba_clouds_percentage": 2.189445, + "s2:saturated_defective_pixel_percentage": 0.0 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -117.00021, + 34.1387355 + ], + [ + -116.97423, + 34.2292992 + ], + [ + -116.94568, + 34.3413977 + ], + [ + -115.80655, + 34.335847 + ], + [ + -115.82024, + 33.3457571 + ], + [ + -117.00021, + 33.3513614 + ], + [ + -117.00021, + 34.1387355 + ] + ] + ] + }, + "links": [ + { + "rel": "collection", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a", + "type": "application/json" + }, + { + "rel": "parent", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a", + "type": "application/json" + }, + { + "rel": "root", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1", + "type": "application/json", + "title": "Microsoft Planetary Computer STAC API" + }, + { + "rel": "self", + "href": "https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2A_MSIL2A_20230510T181921_R127_T11SNT_20230511T023248", + "type": "application/geo+json" + }, + { + "rel": "license", + "href": "https://sentinel.esa.int/documents/247904/690755/Sentinel_Data_Legal_Notice" + }, + { + "rel": "preview", + "href": "https://planetarycomputer.microsoft.com/api/data/v1/item/map?collection=sentinel-2-l2a&item=S2A_MSIL2A_20230510T181921_R127_T11SNT_20230511T023248", + "type": "text/html", + "title": "Map of item" + } + ], + "assets": { + "AOT": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_AOT_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Aerosol optical thickness (AOT)", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "roles": [ + "data" + ] + }, + "B01": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R60m/T11SNT_20230510T181921_B01_60m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 1 - Coastal aerosol - 60m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3800040.0 + ], + "gsd": 60.0, + "eo:bands": [ + { + "name": "B01", + "common_name": "coastal", + "description": "Band 1 - Coastal aerosol", + "center_wavelength": 0.443, + "full_width_half_max": 0.027 + } + ], + "roles": [ + "data" + ] + }, + "B02": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_B02_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 2 - Blue - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "description": "Band 2 - Blue", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "roles": [ + "data" + ] + }, + "B03": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_B03_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 3 - Green - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B03", + "common_name": "green", + "description": "Band 3 - Green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "roles": [ + "data" + ] + }, + "B04": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_B04_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 4 - Red - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "description": "Band 4 - Red", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + } + ], + "roles": [ + "data" + ] + }, + "B05": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_B05_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 5 - Vegetation red edge 1 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B05", + "common_name": "rededge", + "description": "Band 5 - Vegetation red edge 1", + "center_wavelength": 0.704, + "full_width_half_max": 0.019 + } + ], + "roles": [ + "data" + ] + }, + "B06": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_B06_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 6 - Vegetation red edge 2 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B06", + "common_name": "rededge", + "description": "Band 6 - Vegetation red edge 2", + "center_wavelength": 0.74, + "full_width_half_max": 0.018 + } + ], + "roles": [ + "data" + ] + }, + "B07": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_B07_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 7 - Vegetation red edge 3 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B07", + "common_name": "rededge", + "description": "Band 7 - Vegetation red edge 3", + "center_wavelength": 0.783, + "full_width_half_max": 0.028 + } + ], + "roles": [ + "data" + ] + }, + "B08": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_B08_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 8 - NIR - 10m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B08", + "common_name": "nir", + "description": "Band 8 - NIR", + "center_wavelength": 0.842, + "full_width_half_max": 0.145 + } + ], + "roles": [ + "data" + ] + }, + "B09": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R60m/T11SNT_20230510T181921_B09_60m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 9 - Water vapor - 60m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3800040.0 + ], + "gsd": 60.0, + "eo:bands": [ + { + "name": "B09", + "description": "Band 9 - Water vapor", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "roles": [ + "data" + ] + }, + "B11": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_B11_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 11 - SWIR (1.6) - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B11", + "common_name": "swir16", + "description": "Band 11 - SWIR (1.6)", + "center_wavelength": 1.61, + "full_width_half_max": 0.143 + } + ], + "roles": [ + "data" + ] + }, + "B12": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_B12_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 12 - SWIR (2.2) - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B12", + "common_name": "swir22", + "description": "Band 12 - SWIR (2.2)", + "center_wavelength": 2.19, + "full_width_half_max": 0.242 + } + ], + "roles": [ + "data" + ] + }, + "B8A": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_B8A_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Band 8A - Vegetation red edge 4 - 20m", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "eo:bands": [ + { + "name": "B8A", + "common_name": "rededge", + "description": "Band 8A - Vegetation red edge 4", + "center_wavelength": 0.865, + "full_width_half_max": 0.033 + } + ], + "roles": [ + "data" + ] + }, + "SCL": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R20m/T11SNT_20230510T181921_SCL_20m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Scene classfication map (SCL)", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3800040.0 + ], + "gsd": 20.0, + "roles": [ + "data" + ] + }, + "WVP": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_WVP_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Water vapour (WVP)", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "roles": [ + "data" + ] + }, + "visual": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/IMG_DATA/R10m/T11SNT_20230510T181921_TCI_10m.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "True color image", + "proj:bbox": [ + 499980.0, + 3690240.0, + 609780.0, + 3800040.0 + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3800040.0 + ], + "gsd": 10.0, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "description": "Band 4 - Red", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "description": "Band 3 - Green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "description": "Band 2 - Blue", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "roles": [ + "data" + ] + }, + "preview": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/QI_DATA/T11SNT_20230510T181921_PVI.tif?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "Thumbnail", + "roles": [ + "thumbnail" + ] + }, + "safe-manifest": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/manifest.safe?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "SAFE manifest", + "roles": [ + "metadata" + ] + }, + "granule-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/GRANULE/L2A_T11SNT_A041165_20230510T183239/MTD_TL.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "Granule metadata", + "roles": [ + "metadata" + ] + }, + "inspire-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/INSPIRE.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "INSPIRE metadata", + "roles": [ + "metadata" + ] + }, + "product-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/MTD_MSIL2A.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "Product metadata", + "roles": [ + "metadata" + ] + }, + "datastrip-metadata": { + "href": "https://sentinel2l2a01.blob.core.windows.net/sentinel2-l2/11/S/NT/2023/05/10/S2A_MSIL2A_20230510T181921_N0509_R127_T11SNT_20230511T023248.SAFE/DATASTRIP/DS_MSFT_20230511T023249_S20230510T183239/MTD_DS.xml?st=2024-02-12T16%3A41%3A24Z&se=2024-02-13T17%3A26%3A24Z&sp=rl&sv=2021-06-08&sr=c&skoid=c85c15d6-d1ae-42d4-af60-e2ca0f81359b&sktid=72f988bf-86f1-41af-91ab-2d7cd011db47&skt=2024-02-13T12%3A01%3A44Z&ske=2024-02-20T12%3A01%3A44Z&sks=b&skv=2021-06-08&sig=e48oe7sP555ads266/qojoxdHOldj5nx25k/UtQkgF8%3D", + "type": "application/xml", + "title": "Datastrip metadata", + "roles": [ + "metadata" + ] + }, + "tilejson": { + "href": "https://planetarycomputer.microsoft.com/api/data/v1/item/tilejson.json?collection=sentinel-2-l2a&item=S2A_MSIL2A_20230510T181921_R127_T11SNT_20230511T023248&assets=visual&asset_bidx=visual%7C1%2C2%2C3&nodata=0&format=png", + "type": "application/json", + "title": "TileJSON with default rendering", + "roles": [ + "tiles" + ] + }, + "rendered_preview": { + "href": "https://planetarycomputer.microsoft.com/api/data/v1/item/preview.png?collection=sentinel-2-l2a&item=S2A_MSIL2A_20230510T181921_R127_T11SNT_20230511T023248&assets=visual&asset_bidx=visual%7C1%2C2%2C3&nodata=0&format=png", + "type": "image/png", + "title": "Rendered preview", + "rel": "preview", + "roles": [ + "overview" + ] + } + }, + "bbox": [ + -117.00021, + 33.34575708, + -115.80655, + 34.34139768 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "collection": "sentinel-2-l2a" + } + ] +} \ No newline at end of file diff --git a/tests/assets/test_swir.tif b/tests/assets/test_swir.tif new file mode 100644 index 0000000000000000000000000000000000000000..e83fce4658198c44855d24f0f1e4d86490e02888 GIT binary patch literal 23778 zcmeIy&ui0Q7zgktZS9H_VK6I*l$Xv!E0v~cnkCSdPAj9ZO=%|{C2il1;rfFngG~k# z6?*eO5QYdIrFZ`fFZvgF@+>>~yy?n1*+q~Z$zl{eyUpapshe>cx$-00nXI^&Z!Fy&4-Qplb!nG?UeA`-f*8^S5Jx2K!uL<+M~VU z^u;~jZNE^L`*y*$E!j03*|nu~-J&Ur1+Lw8%j~RK@-If^7dOgS+^E9hMwyEnRb1RC z>mQ9Os^rgt>w~wd0$7%o{zfMr`JK%qS?*I3$g6vxj@YpJy=HUcJsj&0P6*0PjN8%mi*uo=tJ(yCRV$uKiW?S|74qk>+F zp)86E1S={rrYa}~N)sgbg%|@Lh)cu|Zh;sgG1WxHn&9qz^L~f)y{f4nW`6f9@0|ZT z_r3d~(W#OvA_XFWED6}v<@OgCu@171Sbr94CogBj`m#WF=Guaw$b=F8iL9NPlgKgo zB9lktG_uaJIhU-@7l$d^py%;?$+7FrcD=7ROG0*i*sk~Y=8J73zuNUcZ!n`i&pJ$h zP_|hA8T(sj*V&exu}=FhQdK*zX2HT_I+lo~W7T<~P*pUY>OSv8vrVrxk`$UCttr?2e~eSEXCKIt#12)3JD)^H;JwJY!l#c-D$!yeyVT zR+d%966Mol<>B&~%M&)L^L&!nfN$yZZu#gV#jM~hhn!gAZNx0atC^CRB|%*)}jk@_xC#Si!v11 z*pc2br9b7=i@es z3_l|OAf%qzPW%wuOT67nxeogp@*?myxS04IaJr9Ev7r=VPjV?cv0Gs=EGkh zq`ee95A?qDd6sjJIo}QWiTY>ZTDXutb+7?{3a<9C4_lvK_FYz>F9pB!@d$P`R9(so z@U!2t$?YR;a3!pVkHC#ynipN$9K=7tt!wUrYw4rkBjs7*yg#D4lzLA6O#No+FU4Ml z-GcoLeAP$Qw}<@G#3S_I3x|Cia_v#N&}a0$;G8nd%XCtI9sT0)75JW)<_YW_F3rbq zB6{|qZFf(iTcdL=)Mo)AMjFC*XyodzJt0yccb%wm;W|5^`HMf zenvl?5Bj7B|MT=a0T1~|+-%4HflH(B<05qRF%RFIRL>}0MLzoEFm+X@o`<{-M)!3W zI-oIJ_X7Uo@CT^p$5?fdr}GoeFLR7O{qRBf0Da!X?(=aC@tyEl;?xuU9{*o`N2vpa z^Hy{|3F2Y6)ulY-rP29rBad@Z_hZz)jaBE%^nvDGY`zzU_twmzZxlBBsJb?A{k?Fk II{qQ=Z_V-R4*&oF literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/lib/burn_severity.py b/tests/unit/lib/burn_severity.py new file mode 100644 index 0000000..e69de29 From 1bc6632763aafc6b1711d49542364bdf12a273dc Mon Sep 17 00:00:00 2001 From: TODO Date: Tue, 13 Feb 2024 17:02:16 +0000 Subject: [PATCH 102/175] Collect TODO comments --- src/routers/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index 10716df..12403a3 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -31,7 +31,7 @@ def init_sentry( ): logger.log_text("Initializing Sentry client") - ## TODO: Move to sentry to environment variable if we keep sentry + ## TODO [$65cba09842ca860008e06391]: Move to sentry to environment variable if we keep sentry sentry_sdk.init( dsn="https://3660129e232b3c796208a5e46945d838@o4506701219364864.ingest.sentry.io/4506701221199872", # Set traces_sample_rate to 1.0 to capture 100% From 3a4ad8abcfe39896116785e3066d7aa762bc6d89 Mon Sep 17 00:00:00 2001 From: TODO Date: Tue, 13 Feb 2024 17:02:18 +0000 Subject: [PATCH 103/175] Update TODO references: #28 --- src/routers/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index 12403a3..3c8fbc7 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -31,7 +31,7 @@ def init_sentry( ): logger.log_text("Initializing Sentry client") - ## TODO [$65cba09842ca860008e06391]: Move to sentry to environment variable if we keep sentry + ## TODO [#28]: Move to sentry to environment variable if we keep sentry sentry_sdk.init( dsn="https://3660129e232b3c796208a5e46945d838@o4506701219364864.ingest.sentry.io/4506701221199872", # Set traces_sample_rate to 1.0 to capture 100% From 153ec66aa167a693003ea4f59ba2ea8f4e500cbc Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 18:07:25 +0000 Subject: [PATCH 104/175] no longer need to explicitly copy app in dockerfile as it is included in /src --- .deployment/prod.Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/.deployment/prod.Dockerfile b/.deployment/prod.Dockerfile index 57016f3..73d908f 100644 --- a/.deployment/prod.Dockerfile +++ b/.deployment/prod.Dockerfile @@ -13,7 +13,6 @@ RUN apt-get update && apt-get install -y \ # Copy necessary files into container COPY src/ /src/ -COPY app.py / COPY .deployment/prod_environment.yml / # Create a new conda environment from the environment.yml file From 534be433a3cfbf3eb66038bb2b3bf64591e2ed4c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 18:24:54 +0000 Subject: [PATCH 105/175] conftest setup for stac items and valid/invalid rasters --- .deployment/prod.Dockerfile | 1 - tests/conftest.py | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.deployment/prod.Dockerfile b/.deployment/prod.Dockerfile index dda9a86..1c31e42 100644 --- a/.deployment/prod.Dockerfile +++ b/.deployment/prod.Dockerfile @@ -13,7 +13,6 @@ RUN apt-get update && apt-get install -y \ # Copy necessary files into container COPY src/ /src/ -COPY app.py / COPY .deployment/prod_environment.yml / # Create a new conda environment from the environment.yml file diff --git a/tests/conftest.py b/tests/conftest.py index e69de29..c3bbe75 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,66 @@ +import pytest +import pystac +import rasterio as rio +import xarray as xr +import numpy as np + +### PyStac ItemCollection + +@pytest.fixture +def test_stac_items(): + with open('assets/test_stac_items.json') as f: + test_stac_items = pystac.read_file(f.read()) + return test_stac_items + +### Stacked xarray (time, band, y, x) + +@pytest.fixture +def test_valid_xarray(): + with open('assets/test_nir.tif') as f: + test_xarray_nir = rio.open(f) + with open('assets/test_swir.tif') as f: + test_xarray_swir = rio.open(f) + return xr.concat([test_xarray_nir, test_xarray_swir], dim='band') + +@pytest.fixture +def test_nan_xarray(): + with open('assets/test_nir.tif') as f: + test_xarray_nir = rio.open(f) + nan_array = xr.full_like(test_xarray_nir, np.nan) + test_nan_xarray = xr.concat([nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim='band') + return test_nan_xarray + +@pytest.fixture +def test_zero_xarray(): + with open('assets/test_nir.tif') as f: + test_xarray_nir = rio.open(f) + zero_array = xr.full_like(test_xarray_nir, 0) + test_zero_xarray = xr.concat([zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim='band') + return test_zero_xarray + +### Reduced across time window (only spatial dims + band remain) + +@pytest.fixture +def test_reduced_xarray(): + with open('assets/test_nir_reduced.tif') as f: + test_xarray_nir = rio.open(f) + with open('assets/test_swir_reduced.tif') as f: + test_xarray_swir = rio.open(f) + test_reduced_xarray = xr.concat([test_xarray_nir, test_xarray_swir], dim='band') + return test_reduced_xarray + +@pytest.fixture +def test_reduced_nan_xarray(): + with open('assets/test_nir_reduced.tif') as f: + test_xarray_nir = rio.open(f) + nan_array = xr.full_like(test_xarray_nir, np.nan) + test_reduced_nan_xarray = xr.concat([nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim='band') + return test_reduced_nan_xarray + +@pytest.fixture +def test_reduced_zero_xarray(): + with open('assets/test_nir_reduced.tif') as f: + test_xarray_nir = rio.open(f) + zero_array = xr.full_like(test_xarray_nir, 0) + test_reduced_zero_xarray = xr.concat([zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim='band') + return test_reduced_zero_xarray From 1284e948d3fe2ca129e7309e0e17afcd2a47c574 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 18:42:16 +0000 Subject: [PATCH 106/175] boundaries samples and added pytest to dev container --- .devcontainer/dev_environment.yml | 1 + tests/assets/boundary_geology.geojson | 1 + tests/assets/boundary_geology_split.geojson | 9 +++++++++ 3 files changed, 11 insertions(+) create mode 100644 tests/assets/boundary_geology.geojson create mode 100644 tests/assets/boundary_geology_split.geojson diff --git a/.devcontainer/dev_environment.yml b/.devcontainer/dev_environment.yml index 0b00c48..e8c1f5a 100644 --- a/.devcontainer/dev_environment.yml +++ b/.devcontainer/dev_environment.yml @@ -29,6 +29,7 @@ dependencies: - fastapi <0.108.0 #to avoid AssertionError bug w/ titiler - uvicorn - pip + - pytest - smart_open - sentry-sdk[fastapi] - pip: diff --git a/tests/assets/boundary_geology.geojson b/tests/assets/boundary_geology.geojson new file mode 100644 index 0000000..587e104 --- /dev/null +++ b/tests/assets/boundary_geology.geojson @@ -0,0 +1 @@ +{"type": "FeatureCollection", "features": [{"id": "0", "type": "Feature", "properties": {"Event_ID": "CA3389711605420230610", "irwinID": "D7FDF4ED-E454-4CC5-BAC9-DB6B2046AF8E", "Incid_Name": "GEOLOGY", "Incid_Type": "Wildfire", "Map_ID": 10024332, "Map_Prog": "BAER", "Asmnt_Type": "Emergency", "BurnBndAc": 1059, "BurnBndLat": "33.904", "BurnBndLon": "-116.044", "Ig_Date": "2023-06-10", "Pre_ID": "804003620230603", "Post_ID": "804003620230619", "Perim_ID": null, "dNBR_offst": -14, "dNBR_stdDv": 6, "NoData_T": 0, "IncGreen_T": 0, "Low_T": 0, "Mod_T": 0, "High_T": 0, "Comment": "NIFS with minor edits"}, "geometry": {"type": "Polygon", "coordinates": [[[-116.05444777736352, 33.89647327071014], [-116.0544276863475, 33.89648972608923], [-116.0545407504617, 33.8968326690166], [-116.05438366859592, 33.89743283144047], [-116.05449068562571, 33.89804376026763], [-116.05425865915545, 33.89855692218856], [-116.05396477551997, 33.89896646635977], [-116.05365902932036, 33.89915778017659], [-116.0536433357814, 33.89918607738], [-116.05361752736195, 33.89926145106721], [-116.05352747244281, 33.89934688816401], [-116.05341257108431, 33.90020019122268], [-116.05343897825902, 33.900282605293945], [-116.05343237448312, 33.90038506873756], [-116.05337499325935, 33.90043725584249], [-116.0533356106609, 33.900646467076406], [-116.05335331203787, 33.900672608096386], [-116.05337038700779, 33.900812561519345], [-116.05334272927955, 33.900866941558796], [-116.05334456078808, 33.900996693432816], [-116.05337370298977, 33.90113007611214], [-116.0532659330424, 33.90125290165806], [-116.05330408664597, 33.90129570406541], [-116.05327107401438, 33.90134174352204], [-116.05333158403849, 33.901411036430886], [-116.05340498527443, 33.9014218918415], [-116.05347221003349, 33.9014557130592], [-116.05348129241521, 33.90148652930335], [-116.05345956634274, 33.90156348218908], [-116.05341921533075, 33.901640690863246], [-116.05339034244862, 33.90178052022672], [-116.05351033417774, 33.90191468348281], [-116.05352553195652, 33.90198036347248], [-116.05361218970145, 33.902088933035046], [-116.05360573144054, 33.902202849121956], [-116.05363720056378, 33.90236414035922], [-116.05365256394813, 33.90245230881987], [-116.05368640766329, 33.902519465416766], [-116.05364881210994, 33.90252688998945], [-116.05357981078464, 33.90254439110441], [-116.05352238503852, 33.90263465261566], [-116.05347733486393, 33.902722738858984], [-116.0534471528571, 33.9028052001791], [-116.0534245979866, 33.902907533559464], [-116.05340839537035, 33.90299225341478], [-116.05339857264572, 33.903051604057744], [-116.05336320705256, 33.90313699554175], [-116.0533367599622, 33.903264964915785], [-116.05328741760704, 33.903417620452075], [-116.05311947891876, 33.903558035847794], [-116.05314705419241, 33.90360431529124], [-116.05316122300107, 33.90372397262852], [-116.05312684219071, 33.90387522495537], [-116.0530963871313, 33.9039588038386], [-116.0531145176952, 33.904027546030825], [-116.05311701230573, 33.904070005161195], [-116.0531022632323, 33.90417141111533], [-116.05312749806194, 33.90422003533678], [-116.05319211507602, 33.90426382025203], [-116.05324985339112, 33.90435391577105], [-116.05330221942656, 33.90435980747777], [-116.05336136777518, 33.90436566697928], [-116.05341045176314, 33.904368869876954], [-116.05346646900782, 33.904396494675915], [-116.05344940761071, 33.904478758970825], [-116.05336355804108, 33.90448897071975], [-116.05328732524521, 33.904532295044], [-116.05337892136538, 33.90464390186631], [-116.05343866041649, 33.90463898618852], [-116.05357148469743, 33.90468495779678], [-116.05360808442666, 33.90473283757492], [-116.05365434642427, 33.904794627495065], [-116.0537105218995, 33.904820020582314], [-116.053733509309, 33.90489009258072], [-116.05367702504874, 33.90492918845321], [-116.05375976044522, 33.904968674478745], [-116.05374415183566, 33.90502723479636], [-116.05361444922963, 33.90515266884988], [-116.0536007483764, 33.905196350865324], [-116.05363013147867, 33.905240477125766], [-116.0535963701331, 33.9053615816708], [-116.05353960168574, 33.905401562968954], [-116.05356573489243, 33.90548883162287], [-116.05368226145542, 33.90551489487177], [-116.05372870136566, 33.905553934587914], [-116.05368793012198, 33.90568022730886], [-116.05364106434521, 33.90569073082382], [-116.0535948554463, 33.90562910816799], [-116.05350057301234, 33.90567823908147], [-116.05358365040588, 33.90574019182239], [-116.05361673156274, 33.905784969627355], [-116.0535388652697, 33.905870360102476], [-116.05335427787848, 33.90586584965817], [-116.05331353611125, 33.90590161927282], [-116.05329900122523, 33.90598786732808], [-116.0533864185797, 33.90599428831339], [-116.05342641366339, 33.90603095892853], [-116.05346288390837, 33.90609001689367], [-116.05337104402206, 33.906164882298135], [-116.05324262406465, 33.90616928817539], [-116.05323161428015, 33.90623110332409], [-116.05327175073127, 33.90626812800407], [-116.05326780219947, 33.906310446012704], [-116.05322634426096, 33.90636388862602], [-116.05326991157054, 33.90640678274766], [-116.05334340004345, 33.90651272078702], [-116.05337685991795, 33.90656730331626], [-116.05336255684377, 33.906630330399906], [-116.05322792625515, 33.9067300906206], [-116.05319827363259, 33.90688587317527], [-116.05319374734084, 33.906961280131156], [-116.05314467949121, 33.907031463583735], [-116.05318142700847, 33.907133904343695], [-116.05317408179981, 33.90718789937227], [-116.05317660189709, 33.90750180541789], [-116.05320983187468, 33.90757771404727], [-116.05322232086807, 33.90766773321817], [-116.0532490210847, 33.90772995833533], [-116.05320294289953, 33.907819996958175], [-116.05307123298947, 33.90788648492832], [-116.05311012801653, 33.908012427007826], [-116.05311990462097, 33.9080525300788], [-116.0530976114707, 33.90814684587734], [-116.05301329401804, 33.9082005698857], [-116.0529926862676, 33.90832907995138], [-116.05313590515463, 33.908339065046306], [-116.05316868182288, 33.90836043280711], [-116.05315987767257, 33.90841873236297], [-116.05302484858596, 33.908504201350276], [-116.05296205316608, 33.90859418595386], [-116.05295845371745, 33.908628281566294], [-116.05296523903813, 33.90867968098732], [-116.05291631565217, 33.908716113667474], [-116.05286727077497, 33.90876962415119], [-116.05266744903271, 33.90881838495583], [-116.05269139889775, 33.90889043818473], [-116.05274711727898, 33.90895459494958], [-116.05284211782151, 33.90892411353766], [-116.05294360771539, 33.908963055167916], [-116.05297862338921, 33.909018312143616], [-116.0529293106377, 33.90908141337621], [-116.0528765798062, 33.909060517445205], [-116.05267187935122, 33.90910166558041], [-116.05271364055064, 33.90917215143402], [-116.05275925458959, 33.90921361031272], [-116.05274479195317, 33.909266035050855], [-116.05269490813569, 33.909303588329955], [-116.05276523159277, 33.90940729684974], [-116.0527108988515, 33.90952526474099], [-116.05264969902106, 33.90958122679371], [-116.05272056390092, 33.90965045814018], [-116.0526952013916, 33.90968911167607], [-116.05259118371445, 33.90970325724944], [-116.05252711322635, 33.90966078114481], [-116.05246966904298, 33.90966328847564], [-116.05243004267795, 33.909671944229984], [-116.05224160912555, 33.90975445000688], [-116.05216476659379, 33.90980213521342], [-116.05208456422778, 33.90982875172985], [-116.05219087034412, 33.9099356808814], [-116.052247786834, 33.909990848534406], [-116.05246499118738, 33.91000419020144], [-116.05254031328764, 33.91005336435641], [-116.05252119867264, 33.910135465323265], [-116.05231980188566, 33.91024477728977], [-116.05225361387537, 33.910275368079645], [-116.05225954594589, 33.91035243575753], [-116.05220770038163, 33.91039222732464], [-116.05214602253433, 33.91037016244661], [-116.0519458073527, 33.91042845431032], [-116.05201906381038, 33.91050759313812], [-116.05204762761107, 33.9105404587608], [-116.05207241840645, 33.91058816912169], [-116.0520826630753, 33.91064177573729], [-116.05214389585636, 33.91068698359212], [-116.05217998792517, 33.910711151782905], [-116.0521571418166, 33.91080678032789], [-116.05212018852811, 33.91085451083697], [-116.05219618257169, 33.91090043617264], [-116.05224692424567, 33.91092509895676], [-116.05223184913487, 33.91097030599324], [-116.0521621589239, 33.911008835250634], [-116.05205662669586, 33.91098640556088], [-116.0519719476606, 33.91103666260644], [-116.05192335229836, 33.91110865858118], [-116.05194105406055, 33.911154487261655], [-116.05188914981305, 33.91119962805738], [-116.05181981106426, 33.91118074584147], [-116.05175366335642, 33.911195390129656], [-116.05173402293039, 33.91128357124325], [-116.05170021519532, 33.91133201923662], [-116.0516483389558, 33.911358980230325], [-116.05143842995112, 33.91135378015831], [-116.05141343490394, 33.91131383168192], [-116.0514334194084, 33.911240903065014], [-116.05132058516864, 33.91119728138986], [-116.05127383545752, 33.91116043666211], [-116.05109146169916, 33.91112850735868], [-116.05086559867058, 33.9113872361367], [-116.05086856292498, 33.91154933031594], [-116.05088167297696, 33.91162817230494], [-116.05101727953853, 33.911678800238384], [-116.05121557719427, 33.911696160654515], [-116.05126020528432, 33.91166025898781], [-116.05133366170246, 33.91165789128352], [-116.05139199029405, 33.91168866460855], [-116.05156169411684, 33.91175915218424], [-116.05157383212241, 33.91186308394004], [-116.05149598130792, 33.911908013285746], [-116.05127802701945, 33.91193960656871], [-116.05118916501422, 33.91196016417611], [-116.05129076264001, 33.912074089108614], [-116.05134612501527, 33.91208069540671], [-116.05143416542933, 33.91213665546651], [-116.05142599113047, 33.91219809056552], [-116.05137406593117, 33.91220379189494], [-116.05134018910617, 33.91224353371165], [-116.05126586057523, 33.91225829171442], [-116.05104758200368, 33.912234808824], [-116.05097381827319, 33.912215681366824], [-116.05087327476163, 33.9122425239057], [-116.05082066506611, 33.912279058093276], [-116.0508973703264, 33.912347669243445], [-116.05096355865463, 33.91238534239203], [-116.05095879055933, 33.912453348550834], [-116.0508802797498, 33.91248361408577], [-116.050807527497, 33.91255183312253], [-116.05067270681567, 33.912614257388924], [-116.05069825794901, 33.91263956676695], [-116.05076826537133, 33.91266131382309], [-116.05083832761296, 33.912683065841115], [-116.05088715503629, 33.9127118915557], [-116.05094137029472, 33.91275960045066], [-116.0509927720821, 33.91283589409189], [-116.05097740964652, 33.91292095166882], [-116.05090289517035, 33.91295079222035], [-116.05088765028947, 33.91299950998856], [-116.05089839010813, 33.913054662942365], [-116.05081179568435, 33.913155262031815], [-116.05071881627018, 33.91313000539086], [-116.05065917655007, 33.91312963866374], [-116.05076629631348, 33.913225800680564], [-116.05074699626286, 33.913284338835716], [-116.05054821615377, 33.91335158357664], [-116.05059974436705, 33.91340749226998], [-116.05064342532859, 33.91350389831612], [-116.05057932078589, 33.91354067631992], [-116.05055228540064, 33.91360742726923], [-116.0505115167014, 33.913677306370516], [-116.05045939543989, 33.91372885378943], [-116.05038378762052, 33.91375147867716], [-116.05033231419992, 33.913755314330736], [-116.05013795833705, 33.913695458074855], [-116.05008192356918, 33.91369191254844], [-116.05006677957142, 33.91374687397082], [-116.05010023278977, 33.913814649676375], [-116.05015683450405, 33.91384225259999], [-116.05028011697785, 33.91388033481617], [-116.05033962541916, 33.913948883107004], [-116.05032815079839, 33.913992112495926], [-116.05030543093365, 33.914032189762814], [-116.05023770202486, 33.91407191317586], [-116.05028990290646, 33.91411582093905], [-116.05029701412585, 33.91418108524887], [-116.05022564803954, 33.91423610422896], [-116.05018438322256, 33.91419867107356], [-116.05013927076884, 33.91423534987335], [-116.05022564068855, 33.91433825256043], [-116.05021057601213, 33.91438777937889], [-116.05017288359217, 33.91442474975291], [-116.05010883566136, 33.914421080057544], [-116.05005619944154, 33.91438332285526], [-116.04998014832843, 33.91438989617388], [-116.0499511929421, 33.91456610791545], [-116.04994724040867, 33.914631306094705], [-116.04986772770913, 33.91468664230781], [-116.049875059348, 33.914742847751626], [-116.04988985789544, 33.91483357344097], [-116.04992755141753, 33.91489343823457], [-116.0499540322049, 33.914918910044484], [-116.04995385778508, 33.914984950824596], [-116.04989680797358, 33.91500316356462], [-116.04978268903474, 33.915083064280246], [-116.0496870609447, 33.9151085753201], [-116.04963343316534, 33.91514921728106], [-116.04953601021391, 33.9152032488217], [-116.04945863629995, 33.91532770420953], [-116.04947362469593, 33.91540030975016], [-116.0494925610294, 33.915444679386475], [-116.0494731365113, 33.9155394578887], [-116.0494155089344, 33.91559607029943], [-116.04945764374216, 33.91566644077023], [-116.04948824434791, 33.915695385844884], [-116.04948811127554, 33.91575282203305], [-116.04944188824803, 33.91579716807216], [-116.04933540266074, 33.915753763984995], [-116.04931435349005, 33.91584080756603], [-116.0492796458696, 33.91591430692068], [-116.04920390157531, 33.9159734801881], [-116.0491969456179, 33.916096706110565], [-116.04920380605584, 33.91618233123643], [-116.04915315494038, 33.91625453062284], [-116.04909864872896, 33.91630264437102], [-116.04899712301975, 33.91640248295437], [-116.0489418170237, 33.916499841046814], [-116.04892569536045, 33.91655201876594], [-116.04882398380359, 33.916613364531216], [-116.04875320794031, 33.91667182982892], [-116.0487079977056, 33.91678388031438], [-116.04845797076494, 33.916917097320756], [-116.04831087735218, 33.91702692922983], [-116.0480598324595, 33.917161203624616], [-116.0480529275029, 33.9174715399428], [-116.0474708835566, 33.917954090019485], [-116.0468665934199, 33.918038017505246], [-116.04564312313637, 33.9182532071916], [-116.04440850041253, 33.918425619051284], [-116.04326311296603, 33.91862704904842], [-116.04179045161135, 33.91893074050714], [-116.04142783771027, 33.919106315824216], [-116.0410446839146, 33.91962093904936], [-116.04109841930972, 33.92034945295759], [-116.0409755004577, 33.921131389330824], [-116.04067686179557, 33.92136752579494], [-116.04064103098679, 33.92179081433133], [-116.0404620052571, 33.92203096163545], [-116.04025271891916, 33.9223039446841], [-116.03981658617683, 33.92224465991371], [-116.03948709264769, 33.9218579256863], [-116.0385796390362, 33.92187981314448], [-116.03802517932922, 33.92178008584459], [-116.03767360866554, 33.921838051905304], [-116.03721913011782, 33.92180137245205], [-116.03706893689912, 33.921604628163685], [-116.03630475474087, 33.921503085007174], [-116.03595721412155, 33.92154245370862], [-116.03521734955905, 33.921395810095504], [-116.03493001014158, 33.921171199237456], [-116.03480797318231, 33.921028113231095], [-116.03444083773947, 33.92084099728719], [-116.03428332848293, 33.92077718699025], [-116.03396235555488, 33.920627241608315], [-116.03376944336263, 33.920733072718846], [-116.03363865397664, 33.920859507359076], [-116.03353120194848, 33.92083787387952], [-116.03347031423228, 33.9206466300561], [-116.03338541467714, 33.920510563387275], [-116.03320334276196, 33.92060580075814], [-116.03308782074363, 33.920558879750494], [-116.03290391486222, 33.920507723005834], [-116.03280282646807, 33.92046405501416], [-116.03269267549604, 33.92039883042564], [-116.03259095893662, 33.920333747731966], [-116.03248927632781, 33.92024760259896], [-116.03245451118225, 33.92014086068932], [-116.0324957497645, 33.92007736200079], [-116.03266323865519, 33.92007881342722], [-116.03274735747515, 33.92012217498938], [-116.03289031212772, 33.92011690710226], [-116.0330425939645, 33.920035015008374], [-116.0331952489557, 33.91998196174914], [-116.03342084426103, 33.91990224098101], [-116.03358569016675, 33.91980940372706], [-116.0337451666484, 33.91981962868571], [-116.03386407066769, 33.91976097317258], [-116.03401069221313, 33.91973030924039], [-116.03407201515306, 33.9196220440862], [-116.03415107511522, 33.919521760029276], [-116.03424421598083, 33.919386000790624], [-116.03447505127326, 33.91947733186118], [-116.0346989780018, 33.91937867602784], [-116.0349033647483, 33.91941720265338], [-116.03512518984496, 33.91930901525439], [-116.03556407494501, 33.91933740204573], [-116.0357132114454, 33.91919189214866], [-116.03593634530758, 33.918998202198985], [-116.03595339112971, 33.91886389500991], [-116.03635579996963, 33.918585811696026], [-116.03637641147282, 33.91836600474814], [-116.03671929628402, 33.91814932510385], [-116.03668040190568, 33.91794229874323], [-116.03630304220145, 33.91785396999703], [-116.03605256332071, 33.91787611424409], [-116.03582820272983, 33.91759496511126], [-116.03590991847116, 33.91726892077335], [-116.0362072220534, 33.91701809735604], [-116.03611582755353, 33.91654712349384], [-116.03586152819008, 33.91626832938544], [-116.03589452875318, 33.915538467774354], [-116.03607230940474, 33.915312600067935], [-116.03603452177524, 33.91508586219656], [-116.03574584112054, 33.91509519759651], [-116.03565020624038, 33.91486787610132], [-116.03569022220425, 33.91442896515818], [-116.03546732793356, 33.913708693720565], [-116.03508386659945, 33.91307084624441], [-116.03492037703526, 33.912740012957286], [-116.03484555178215, 33.91234134948459], [-116.03481224043175, 33.911966361231755], [-116.03444287649535, 33.91145685810178], [-116.03437875351395, 33.91118392725596], [-116.03437308160265, 33.91084092029673], [-116.03453325657976, 33.91071242842175], [-116.0346427822578, 33.910581324908144], [-116.0345963560864, 33.91023664624528], [-116.03470925187625, 33.90981728579027], [-116.03477030749534, 33.909326504750645], [-116.03505214470596, 33.90915121602491], [-116.03519842510147, 33.90870919600392], [-116.03536039907812, 33.90834302730592], [-116.03571883213625, 33.90754132423913], [-116.03623113188185, 33.90675854441099], [-116.03626903620585, 33.90648287185373], [-116.03659939070752, 33.90629681466454], [-116.0366556056993, 33.90586196447441], [-116.03705419534668, 33.905560911215], [-116.03750765111046, 33.904787953422954], [-116.03771542767383, 33.90453170570225], [-116.03797530007728, 33.90409057603939], [-116.03849098552206, 33.903539283953904], [-116.03846324575343, 33.90343695259148], [-116.03848616465801, 33.90334269932864], [-116.03850792214479, 33.90331933070676], [-116.0385602751143, 33.90322737465406], [-116.03881685498904, 33.9029461877109], [-116.0391069394239, 33.90266591203084], [-116.039158270345, 33.902313685283005], [-116.03934533742107, 33.90203388297344], [-116.0393368157747, 33.901906256114266], [-116.03934118179455, 33.90185030881714], [-116.03940190208269, 33.9017773637883], [-116.0394127876274, 33.90175105454619], [-116.03951469344972, 33.90172851686863], [-116.03955427612215, 33.901635274881336], [-116.03954871353109, 33.90153064933977], [-116.03953193807494, 33.90145179027712], [-116.03958460925537, 33.901411733445784], [-116.03966459285712, 33.90142112297912], [-116.03971672080937, 33.90143605471548], [-116.03984609500966, 33.90128369031052], [-116.03991550952702, 33.90129056388238], [-116.03995963792337, 33.9013298156285], [-116.04002240146052, 33.90133353372712], [-116.04007197763707, 33.90130740903486], [-116.04036242560986, 33.90108941664383], [-116.0403958396066, 33.900825755722146], [-116.04033583370244, 33.900708374788096], [-116.04028716655496, 33.90056834043622], [-116.04023711848869, 33.90048704156932], [-116.04003161386986, 33.900034261951426], [-116.04021484939554, 33.899743708357455], [-116.0404618341683, 33.899527840706426], [-116.04057182725563, 33.899381473901], [-116.04052981103588, 33.899304661721885], [-116.04052121009299, 33.89925052423732], [-116.04056686972899, 33.89920819637711], [-116.04063411860719, 33.899245472578365], [-116.04065903118068, 33.89920873110902], [-116.04075004425543, 33.89902410356977], [-116.04079657266111, 33.8989573732344], [-116.0407845509455, 33.89891356482372], [-116.04076931503849, 33.898862571071646], [-116.04075390848693, 33.898817305242716], [-116.04081325139808, 33.89876974446561], [-116.04088185960336, 33.8987509361887], [-116.04126079767332, 33.89803809029453], [-116.041686865079, 33.897358379125734], [-116.04204798883217, 33.8971062126627], [-116.04205085043175, 33.89702069595633], [-116.04209481517313, 33.896959434343636], [-116.04216200785183, 33.89687723925704], [-116.0423654668854, 33.896669096376286], [-116.04250773868412, 33.89638821255023], [-116.04277218254288, 33.89613233161682], [-116.04299162558317, 33.895844252181966], [-116.04327779968136, 33.89581828783162], [-116.04331525948473, 33.89571394133429], [-116.04332065270786, 33.89564359654707], [-116.04336821947598, 33.89553911479316], [-116.04339330951528, 33.89549106394306], [-116.04342035129727, 33.89537991518343], [-116.04345741540088, 33.89524694966223], [-116.04353307413724, 33.89520302786973], [-116.04359548442706, 33.895190791371114], [-116.04367478946287, 33.89512256301595], [-116.04372236804389, 33.89507194329392], [-116.04379088619267, 33.895054858845455], [-116.04389240388004, 33.89509706294437], [-116.04398896179714, 33.8950600236616], [-116.04406667111348, 33.895030624443336], [-116.04418182440214, 33.89497440302234], [-116.0442437411145, 33.89491133350485], [-116.04428436683335, 33.89484629965341], [-116.04435359092703, 33.89473952257614], [-116.04454145371864, 33.89454692794829], [-116.04481446518479, 33.894231331357496], [-116.04506891995452, 33.89411457088653], [-116.04514873772186, 33.89409523385847], [-116.04524821332181, 33.894125071132386], [-116.04540537775688, 33.894052644390264], [-116.04573236253331, 33.89389559876001], [-116.04597290065155, 33.89395873912784], [-116.04601178270401, 33.89404371707724], [-116.0461073486209, 33.89408941732957], [-116.04622430891831, 33.894130259657004], [-116.04627389494598, 33.894183044775204], [-116.04668424911245, 33.89419639368301], [-116.04680855328273, 33.89415744407644], [-116.0469367244284, 33.89408615720491], [-116.04760053381426, 33.89346247867229], [-116.04832440677541, 33.89296633259941], [-116.04855366733926, 33.89272495686242], [-116.04905990648444, 33.89210944238481], [-116.0491905868923, 33.891841440811824], [-116.04941531833761, 33.89141732346323], [-116.0495887296558, 33.89134245377683], [-116.04967218867644, 33.89126209676118], [-116.04966336653432, 33.89117820461781], [-116.04960624119296, 33.891071233722606], [-116.04955804100396, 33.89095379450228], [-116.04954266595291, 33.89088637336538], [-116.04964842598766, 33.89084080495902], [-116.04975681003724, 33.89086236825555], [-116.04989009913405, 33.89088398365348], [-116.04997418803124, 33.89081040507339], [-116.0499873830316, 33.89073689984524], [-116.05016355930677, 33.890623605027415], [-116.05036984190002, 33.89048282765872], [-116.05056041580542, 33.890328077922426], [-116.05067104923572, 33.890158750650905], [-116.050781615324, 33.88960606393035], [-116.05085628199537, 33.88962964025148], [-116.05086022757745, 33.88962194264196], [-116.05091853007514, 33.8895422572643], [-116.05107072271326, 33.88941074572652], [-116.05113793679034, 33.88919859615895], [-116.05119126230257, 33.8891205122106], [-116.05120387042506, 33.88906953610631], [-116.05132152761325, 33.88898221900997], [-116.05157105451798, 33.88903194516129], [-116.05167145419367, 33.889046070685346], [-116.0517237074677, 33.88898187309705], [-116.05193081357365, 33.88885759420948], [-116.0519370823851, 33.88877089811542], [-116.05197394209299, 33.88867652787055], [-116.0519734494835, 33.88850302660736], [-116.05194970593547, 33.888350143744994], [-116.05192259976987, 33.88821422317919], [-116.05184395008956, 33.888153945701816], [-116.05175318408223, 33.88805088565154], [-116.05153678982141, 33.8880127261163], [-116.05142612500376, 33.88793305034666], [-116.05138460436547, 33.88778553405183], [-116.05134989186155, 33.88765182350589], [-116.05128608763602, 33.88776837084964], [-116.05123395547476, 33.8878298948119], [-116.05118525518026, 33.88792101678741], [-116.05110374122225, 33.88799708007797], [-116.05100077833849, 33.88804713818047], [-116.0509125055169, 33.88807904017122], [-116.05082848477521, 33.888088259707125], [-116.05082161732551, 33.88803849281501], [-116.05080276709559, 33.88798557973621], [-116.05078812567692, 33.88792293667531], [-116.05075253444929, 33.88781982721217], [-116.05094842712464, 33.88764803573068], [-116.05103222009443, 33.88743553202948], [-116.05140628302654, 33.88707995136963], [-116.05123385282985, 33.88683970945934], [-116.05127145550968, 33.88652089351577], [-116.05135819654794, 33.88649288105697], [-116.05141110278817, 33.88637080248104], [-116.05147029586824, 33.886355957922184], [-116.05159246116223, 33.88623131418873], [-116.05172346766783, 33.88585335136072], [-116.05195044156697, 33.885756359592186], [-116.0522879375952, 33.88535643866094], [-116.05249532357992, 33.885177429979336], [-116.05269910262304, 33.885337227896855], [-116.05279154036518, 33.88562515033127], [-116.05310407262033, 33.88624315071169], [-116.05343684920567, 33.88626015846266], [-116.05342243984333, 33.88588114009585], [-116.05335468013166, 33.88568329167533], [-116.0532825531578, 33.885307934111424], [-116.05355161734462, 33.884987939522624], [-116.05386532774659, 33.88510911967558], [-116.05386536433261, 33.885328414433616], [-116.05381811175835, 33.88559954604254], [-116.05397549236679, 33.88578369473316], [-116.05424338099932, 33.885514287606604], [-116.05446427075546, 33.885620483831985], [-116.05452737485824, 33.885933851169874], [-116.0548122048876, 33.886014754171185], [-116.05516168719184, 33.88606932655646], [-116.0552581017714, 33.88629219619267], [-116.05497351092573, 33.88705240665027], [-116.05478517267773, 33.88745975400416], [-116.05464965726108, 33.887635359004975], [-116.05456036178438, 33.88767564637959], [-116.05460643326786, 33.88771112910732], [-116.05460532493247, 33.88778569512645], [-116.05467065777657, 33.88782363634565], [-116.05472452974664, 33.88786145527485], [-116.05476682576159, 33.88790567026043], [-116.05482325508557, 33.88793454754217], [-116.0548865158039, 33.888020515455906], [-116.05491712264553, 33.8881006238879], [-116.0548553362147, 33.888168016429084], [-116.05489168090267, 33.88833892066812], [-116.05488699350036, 33.88843271310467], [-116.05490317048677, 33.88851974103024], [-116.05503168647095, 33.888767517882634], [-116.05509631412654, 33.88921240535258], [-116.05527453066074, 33.889592745809], [-116.05533796476124, 33.890024860734364], [-116.05539052137871, 33.89030388772929], [-116.05550890195909, 33.89062774693671], [-116.05547438189312, 33.89070035379842], [-116.05548271052928, 33.89078689154658], [-116.05544938747349, 33.8908290579457], [-116.0554299096392, 33.89089856650208], [-116.05540826479992, 33.890939149351226], [-116.05541492692188, 33.89098591578564], [-116.05539787617322, 33.89103455105903], [-116.05531879170158, 33.89110434764621], [-116.05531846499996, 33.89113182969778], [-116.05529885716456, 33.891178872294304], [-116.0552653718551, 33.89119040455503], [-116.05520062616402, 33.891219431747146], [-116.05520214503456, 33.89127679020987], [-116.05533273304556, 33.89133942945313], [-116.05533483754338, 33.89136528733512], [-116.055308701708, 33.8913491556443], [-116.05540377901471, 33.89142567421954], [-116.0554276000868, 33.89145188265939], [-116.05540800381699, 33.8915018747972], [-116.05539577366775, 33.89152592431808], [-116.05533743463099, 33.89157388351422], [-116.05539466852925, 33.891667889898784], [-116.05540127330009, 33.891758128294015], [-116.05539538835073, 33.891890560317755], [-116.0553336529522, 33.89193800623477], [-116.05529773303383, 33.89206800999571], [-116.05533702066396, 33.89208961418228], [-116.05538127632553, 33.89211759876309], [-116.05544038600277, 33.89216710010975], [-116.05542289247552, 33.89221883983904], [-116.05537454268043, 33.892273143842104], [-116.05518690041178, 33.89238549366311], [-116.05509732161563, 33.8924034467096], [-116.05508997279283, 33.892456572713925], [-116.05511379735078, 33.89248879632818], [-116.05525251226682, 33.89254645338452], [-116.05526374336814, 33.89261707233258], [-116.05522717250518, 33.892675681813316], [-116.05514871407858, 33.89278101805763], [-116.05515938329262, 33.89283844236808], [-116.05509546747933, 33.892946918580044], [-116.05504342383774, 33.89301675299201], [-116.05497967356925, 33.89314272813859], [-116.0550002144872, 33.89316164229115], [-116.0550111343201, 33.89320557061671], [-116.05498451326295, 33.89327666191538], [-116.05497775277156, 33.893327213180974], [-116.05490745520393, 33.893396545711894], [-116.05490275810857, 33.89342630613744], [-116.05486764816213, 33.89348154552595], [-116.05475731769964, 33.89345918388831], [-116.05473749291576, 33.89355774695223], [-116.05476862717381, 33.893670332119456], [-116.0548151287091, 33.89370975721812], [-116.0547764638273, 33.89376810891167], [-116.05478956692959, 33.893831541447106], [-116.05484405312862, 33.89384734331314], [-116.05485989734171, 33.89392258010954], [-116.05483194510661, 33.894015157757366], [-116.05480491261545, 33.89405739775849], [-116.05476251223854, 33.89411466160201], [-116.05470647650858, 33.89417434381745], [-116.05474109900452, 33.894213451072275], [-116.05471572062037, 33.89424996257731], [-116.05466383177337, 33.8942689940839], [-116.05461241907808, 33.894289907630046], [-116.05474775019117, 33.89472306491123], [-116.05457441589616, 33.89505255309228], [-116.05455513983617, 33.89538292861673], [-116.0547140887102, 33.895822256691076], [-116.05476657515172, 33.896173696024114], [-116.05466531815206, 33.89627240247347], [-116.05453896350822, 33.896371296191056], [-116.05449490391022, 33.896398267029156], [-116.05448651270436, 33.89643471695136], [-116.05448080971978, 33.89648270860004], [-116.05444777736352, 33.89647327071014]]]}}]} \ No newline at end of file diff --git a/tests/assets/boundary_geology_split.geojson b/tests/assets/boundary_geology_split.geojson new file mode 100644 index 0000000..805c5c7 --- /dev/null +++ b/tests/assets/boundary_geology_split.geojson @@ -0,0 +1,9 @@ +{ +"type": "FeatureCollection", +"name": "boundary_split_out", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "id": "0", "Event_ID": "CA3389711605420230610", "irwinID": "D7FDF4ED-E454-4CC5-BAC9-DB6B2046AF8E", "Incid_Name": "GEOLOGY", "Incid_Type": "Wildfire", "Map_ID": 10024332, "Map_Prog": "BAER", "Asmnt_Type": "Emergency", "BurnBndAc": 1059, "BurnBndLat": "33.904", "BurnBndLon": "-116.044", "Ig_Date": "2023-06-10", "Pre_ID": "804003620230603", "Post_ID": "804003620230619", "Perim_ID": null, "dNBR_offst": -14, "dNBR_stdDv": 6, "NoData_T": 0, "IncGreen_T": 0, "Low_T": 0, "Mod_T": 0, "High_T": 0, "Comment": "NIFS with minor edits" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -116.054725581721385, 33.895899211730722 ], [ -116.048546262058906, 33.892732753471549 ], [ -116.048553667339263, 33.892724956862423 ], [ -116.049059906484445, 33.892109442384807 ], [ -116.049190586892294, 33.891841440811824 ], [ -116.049415318337608, 33.89141732346323 ], [ -116.049588729655795, 33.891342453776829 ], [ -116.049672188676439, 33.891262096761182 ], [ -116.049663366534318, 33.891178204617809 ], [ -116.049606241192961, 33.891071233722606 ], [ -116.049558041003962, 33.890953794502281 ], [ -116.049542665952913, 33.890886373365383 ], [ -116.049648425987655, 33.890840804959019 ], [ -116.049756810037238, 33.890862368255547 ], [ -116.049890099134046, 33.890883983653481 ], [ -116.049974188031243, 33.890810405073388 ], [ -116.049987383031606, 33.890736899845237 ], [ -116.050163559306768, 33.890623605027415 ], [ -116.050369841900022, 33.890482827658722 ], [ -116.050560415805421, 33.890328077922426 ], [ -116.050671049235717, 33.890158750650905 ], [ -116.050781615323999, 33.889606063930351 ], [ -116.050856281995365, 33.889629640251478 ], [ -116.050860227577445, 33.889621942641959 ], [ -116.050918530075137, 33.889542257264303 ], [ -116.051070722713263, 33.889410745726522 ], [ -116.05113793679034, 33.889198596158948 ], [ -116.051191262302567, 33.889120512210603 ], [ -116.051203870425056, 33.889069536106312 ], [ -116.051321527613254, 33.888982219009968 ], [ -116.051571054517979, 33.889031945161292 ], [ -116.051671454193666, 33.889046070685346 ], [ -116.051723707467701, 33.888981873097052 ], [ -116.051930813573648, 33.888857594209483 ], [ -116.051937082385095, 33.888770898115418 ], [ -116.051973942092985, 33.888676527870551 ], [ -116.051973449483498, 33.88850302660736 ], [ -116.051949705935471, 33.888350143744994 ], [ -116.051922599769867, 33.88821422317919 ], [ -116.051843950089562, 33.888153945701816 ], [ -116.05175318408223, 33.888050885651538 ], [ -116.051536789821412, 33.888012726116301 ], [ -116.051426125003758, 33.88793305034666 ], [ -116.051384604365467, 33.887785534051829 ], [ -116.051349891861548, 33.887651823505891 ], [ -116.051286087636015, 33.887768370849642 ], [ -116.051233955474757, 33.887829894811901 ], [ -116.051185255180258, 33.887921016787409 ], [ -116.051103741222249, 33.887997080077973 ], [ -116.051000778338491, 33.888047138180468 ], [ -116.050912505516905, 33.88807904017122 ], [ -116.050828484775209, 33.888088259707125 ], [ -116.050821617325511, 33.888038492815006 ], [ -116.05080276709559, 33.88798557973621 ], [ -116.050788125676917, 33.887922936675309 ], [ -116.050752534449288, 33.887819827212169 ], [ -116.050948427124638, 33.887648035730678 ], [ -116.051032220094427, 33.887435532029478 ], [ -116.051406283026537, 33.887079951369628 ], [ -116.051233852829853, 33.886839709459338 ], [ -116.051271455509678, 33.886520893515772 ], [ -116.051358196547937, 33.886492881056967 ], [ -116.051411102788165, 33.886370802481039 ], [ -116.051470295868242, 33.886355957922184 ], [ -116.05159246116223, 33.886231314188727 ], [ -116.051723467667827, 33.885853351360723 ], [ -116.051950441566973, 33.885756359592186 ], [ -116.052287937595196, 33.885356438660942 ], [ -116.052495323579919, 33.885177429979336 ], [ -116.052699102623038, 33.885337227896855 ], [ -116.052791540365178, 33.885625150331272 ], [ -116.053104072620329, 33.886243150711692 ], [ -116.053436849205667, 33.886260158462662 ], [ -116.053422439843331, 33.885881140095847 ], [ -116.053354680131662, 33.885683291675328 ], [ -116.0532825531578, 33.885307934111424 ], [ -116.053551617344624, 33.884987939522624 ], [ -116.053865327746593, 33.885109119675583 ], [ -116.053865364332609, 33.885328414433616 ], [ -116.053818111758346, 33.885599546042542 ], [ -116.053975492366789, 33.885783694733163 ], [ -116.054243380999324, 33.885514287606604 ], [ -116.05446427075546, 33.885620483831985 ], [ -116.054527374858239, 33.885933851169874 ], [ -116.054812204887597, 33.886014754171185 ], [ -116.055161687191841, 33.886069326556459 ], [ -116.0552581017714, 33.88629219619267 ], [ -116.054973510925734, 33.88705240665027 ], [ -116.054785172677725, 33.887459754004162 ], [ -116.054649657261081, 33.887635359004975 ], [ -116.054560361784382, 33.887675646379591 ], [ -116.054606433267864, 33.887711129107323 ], [ -116.054605324932467, 33.887785695126453 ], [ -116.054670657776569, 33.887823636345651 ], [ -116.054724529746636, 33.887861455274852 ], [ -116.05476682576159, 33.88790567026043 ], [ -116.054823255085566, 33.887934547542173 ], [ -116.054886515803901, 33.888020515455906 ], [ -116.054917122645534, 33.888100623887901 ], [ -116.0548553362147, 33.888168016429084 ], [ -116.054891680902671, 33.888338920668119 ], [ -116.054886993500361, 33.88843271310467 ], [ -116.054903170486767, 33.888519741030237 ], [ -116.055031686470954, 33.888767517882634 ], [ -116.055096314126544, 33.889212405352581 ], [ -116.055274530660739, 33.889592745808997 ], [ -116.055337964761236, 33.890024860734364 ], [ -116.055390521378712, 33.890303887729289 ], [ -116.055508901959087, 33.890627746936708 ], [ -116.05547438189312, 33.890700353798422 ], [ -116.055482710529276, 33.890786891546583 ], [ -116.055449387473487, 33.890829057945702 ], [ -116.055429909639201, 33.890898566502081 ], [ -116.055408264799922, 33.890939149351226 ], [ -116.055414926921884, 33.890985915785642 ], [ -116.055397876173217, 33.891034551059029 ], [ -116.05531879170158, 33.891104347646213 ], [ -116.055318464999957, 33.891131829697777 ], [ -116.055298857164559, 33.891178872294304 ], [ -116.0552653718551, 33.891190404555033 ], [ -116.055200626164023, 33.891219431747146 ], [ -116.055202145034556, 33.891276790209872 ], [ -116.05533273304556, 33.891339429453133 ], [ -116.055334837543384, 33.89136528733512 ], [ -116.055308701708, 33.891349155644299 ], [ -116.055403779014711, 33.891425674219541 ], [ -116.055427600086801, 33.891451882659389 ], [ -116.055408003816993, 33.8915018747972 ], [ -116.055395773667755, 33.891525924318081 ], [ -116.055337434630985, 33.891573883514219 ], [ -116.055394668529246, 33.891667889898784 ], [ -116.055401273300092, 33.891758128294015 ], [ -116.055395388350732, 33.891890560317755 ], [ -116.0553336529522, 33.891938006234767 ], [ -116.055297733033825, 33.892068009995711 ], [ -116.055337020663956, 33.892089614182282 ], [ -116.055381276325534, 33.892117598763093 ], [ -116.055440386002772, 33.892167100109752 ], [ -116.055422892475519, 33.892218839839039 ], [ -116.055374542680426, 33.892273143842104 ], [ -116.055186900411783, 33.892385493663113 ], [ -116.055097321615634, 33.892403446709601 ], [ -116.055089972792828, 33.892456572713925 ], [ -116.055113797350785, 33.89248879632818 ], [ -116.055252512266819, 33.892546453384519 ], [ -116.055263743368144, 33.892617072332577 ], [ -116.055227172505184, 33.892675681813316 ], [ -116.055148714078584, 33.892781018057633 ], [ -116.055159383292619, 33.892838442368081 ], [ -116.055095467479333, 33.892946918580044 ], [ -116.055043423837745, 33.893016752992011 ], [ -116.05497967356925, 33.893142728138592 ], [ -116.055000214487194, 33.893161642291147 ], [ -116.055011134320097, 33.893205570616708 ], [ -116.054984513262951, 33.89327666191538 ], [ -116.054977752771563, 33.893327213180974 ], [ -116.054907455203931, 33.893396545711894 ], [ -116.054902758108568, 33.893426306137442 ], [ -116.05486764816213, 33.893481545525951 ], [ -116.054757317699639, 33.893459183888311 ], [ -116.054737492915763, 33.893557746952233 ], [ -116.054768627173814, 33.893670332119456 ], [ -116.054815128709095, 33.893709757218119 ], [ -116.054776463827295, 33.893768108911672 ], [ -116.054789566929585, 33.893831541447106 ], [ -116.054844053128619, 33.893847343313141 ], [ -116.054859897341714, 33.893922580109539 ], [ -116.054831945106613, 33.894015157757366 ], [ -116.054804912615452, 33.894057397758488 ], [ -116.05476251223854, 33.894114661602011 ], [ -116.054706476508585, 33.894174343817447 ], [ -116.054741099004517, 33.894213451072275 ], [ -116.054715720620365, 33.894249962577312 ], [ -116.054663831773368, 33.894268994083902 ], [ -116.054612419078083, 33.894289907630046 ], [ -116.054747750191169, 33.894723064911233 ], [ -116.054574415896155, 33.895052553092277 ], [ -116.054555139836168, 33.895382928616733 ], [ -116.054714088710199, 33.895822256691076 ], [ -116.054725581721385, 33.895899211730722 ] ] ] } }, +{ "type": "Feature", "properties": { "id": "0", "Event_ID": "CA3389711605420230610", "irwinID": "D7FDF4ED-E454-4CC5-BAC9-DB6B2046AF8E", "Incid_Name": "GEOLOGY", "Incid_Type": "Wildfire", "Map_ID": 10024332, "Map_Prog": "BAER", "Asmnt_Type": "Emergency", "BurnBndAc": 1059, "BurnBndLat": "33.904", "BurnBndLon": "-116.044", "Ig_Date": "2023-06-10", "Pre_ID": "804003620230603", "Post_ID": "804003620230619", "Perim_ID": null, "dNBR_offst": -14, "dNBR_stdDv": 6, "NoData_T": 0, "IncGreen_T": 0, "Low_T": 0, "Mod_T": 0, "High_T": 0, "Comment": "NIFS with minor edits" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ -116.042599112112455, 33.896299797874079 ], [ -116.054351984478487, 33.898350519005398 ], [ -116.054258659155451, 33.898556922188561 ], [ -116.053964775519972, 33.898966466359767 ], [ -116.053659029320357, 33.899157780176587 ], [ -116.053643335781402, 33.899186077380001 ], [ -116.053617527361951, 33.899261451067211 ], [ -116.053527472442809, 33.899346888164011 ], [ -116.053412571084309, 33.90020019122268 ], [ -116.053438978259024, 33.900282605293945 ], [ -116.053432374483123, 33.900385068737563 ], [ -116.053374993259354, 33.90043725584249 ], [ -116.053335610660895, 33.900646467076406 ], [ -116.053353312037871, 33.900672608096386 ], [ -116.053370387007789, 33.900812561519345 ], [ -116.053342729279549, 33.900866941558796 ], [ -116.053344560788076, 33.900996693432816 ], [ -116.053373702989774, 33.901130076112139 ], [ -116.053265933042397, 33.901252901658061 ], [ -116.053304086645966, 33.901295704065411 ], [ -116.053271074014376, 33.901341743522039 ], [ -116.053331584038489, 33.901411036430886 ], [ -116.053404985274426, 33.901421891841501 ], [ -116.05347221003349, 33.9014557130592 ], [ -116.053481292415213, 33.901486529303348 ], [ -116.053459566342738, 33.901563482189083 ], [ -116.053419215330749, 33.901640690863246 ], [ -116.053390342448623, 33.901780520226723 ], [ -116.053510334177744, 33.901914683482808 ], [ -116.053525531956524, 33.901980363472482 ], [ -116.053612189701454, 33.902088933035046 ], [ -116.053605731440541, 33.902202849121956 ], [ -116.053637200563784, 33.902364140359218 ], [ -116.053652563948134, 33.902452308819868 ], [ -116.053686407663292, 33.902519465416766 ], [ -116.053648812109941, 33.902526889989453 ], [ -116.05357981078464, 33.902544391104414 ], [ -116.053522385038519, 33.90263465261566 ], [ -116.053477334863928, 33.902722738858984 ], [ -116.053447152857103, 33.902805200179102 ], [ -116.053424597986606, 33.902907533559464 ], [ -116.053408395370354, 33.90299225341478 ], [ -116.053398572645719, 33.903051604057744 ], [ -116.053363207052556, 33.90313699554175 ], [ -116.053336759962207, 33.903264964915785 ], [ -116.053287417607038, 33.903417620452075 ], [ -116.053119478918759, 33.903558035847794 ], [ -116.053147054192408, 33.90360431529124 ], [ -116.053161223001069, 33.903723972628519 ], [ -116.053126842190707, 33.903875224955371 ], [ -116.053096387131305, 33.903958803838599 ], [ -116.053114517695207, 33.904027546030825 ], [ -116.053117012305734, 33.904070005161195 ], [ -116.053102263232304, 33.904171411115328 ], [ -116.05312749806194, 33.904220035336778 ], [ -116.053192115076016, 33.904263820252027 ], [ -116.053249853391122, 33.904353915771047 ], [ -116.053302219426556, 33.904359807477768 ], [ -116.053361367775182, 33.904365666979281 ], [ -116.053410451763142, 33.904368869876954 ], [ -116.053466469007816, 33.904396494675915 ], [ -116.053449407610714, 33.904478758970825 ], [ -116.053363558041085, 33.904488970719747 ], [ -116.053287325245208, 33.904532295044 ], [ -116.053378921365379, 33.904643901866308 ], [ -116.053438660416489, 33.904638986188523 ], [ -116.053571484697429, 33.904684957796782 ], [ -116.053608084426656, 33.904732837574919 ], [ -116.053654346424267, 33.904794627495065 ], [ -116.053710521899504, 33.904820020582314 ], [ -116.053733509308998, 33.904890092580722 ], [ -116.053677025048742, 33.90492918845321 ], [ -116.053759760445217, 33.904968674478745 ], [ -116.053744151835659, 33.905027234796357 ], [ -116.053614449229627, 33.90515266884988 ], [ -116.053600748376397, 33.905196350865324 ], [ -116.05363013147867, 33.905240477125766 ], [ -116.053596370133107, 33.905361581670803 ], [ -116.053539601685742, 33.905401562968954 ], [ -116.05356573489243, 33.905488831622868 ], [ -116.053682261455421, 33.905514894871772 ], [ -116.053728701365657, 33.905553934587914 ], [ -116.053687930121981, 33.905680227308864 ], [ -116.053641064345214, 33.905690730823821 ], [ -116.053594855446306, 33.905629108167993 ], [ -116.053500573012343, 33.905678239081467 ], [ -116.053583650405884, 33.905740191822389 ], [ -116.053616731562741, 33.905784969627355 ], [ -116.053538865269701, 33.905870360102476 ], [ -116.053354277878483, 33.90586584965817 ], [ -116.053313536111247, 33.905901619272818 ], [ -116.053299001225227, 33.905987867328079 ], [ -116.053386418579706, 33.905994288313387 ], [ -116.053426413663388, 33.906030958928532 ], [ -116.053462883908367, 33.906090016893671 ], [ -116.053371044022057, 33.906164882298135 ], [ -116.053242624064652, 33.906169288175391 ], [ -116.053231614280151, 33.906231103324089 ], [ -116.05327175073127, 33.906268128004072 ], [ -116.053267802199471, 33.906310446012704 ], [ -116.053226344260963, 33.906363888626018 ], [ -116.053269911570538, 33.906406782747659 ], [ -116.053343400043445, 33.90651272078702 ], [ -116.053376859917947, 33.906567303316258 ], [ -116.053362556843766, 33.906630330399906 ], [ -116.053227926255147, 33.906730090620599 ], [ -116.053198273632589, 33.90688587317527 ], [ -116.053193747340842, 33.906961280131156 ], [ -116.053144679491211, 33.907031463583735 ], [ -116.053181427008468, 33.907133904343695 ], [ -116.05317408179981, 33.907187899372268 ], [ -116.053176601897093, 33.907501805417887 ], [ -116.053209831874682, 33.907577714047271 ], [ -116.053222320868073, 33.907667733218169 ], [ -116.053249021084696, 33.907729958335331 ], [ -116.053202942899532, 33.907819996958175 ], [ -116.053071232989467, 33.907886484928319 ], [ -116.053110128016527, 33.908012427007826 ], [ -116.053119904620971, 33.908052530078798 ], [ -116.053097611470704, 33.90814684587734 ], [ -116.053013294018044, 33.9082005698857 ], [ -116.052992686267601, 33.908329079951379 ], [ -116.053135905154633, 33.908339065046306 ], [ -116.053168681822882, 33.908360432807108 ], [ -116.05315987767257, 33.908418732362968 ], [ -116.053024848585963, 33.908504201350276 ], [ -116.052962053166084, 33.908594185953859 ], [ -116.052958453717451, 33.908628281566294 ], [ -116.05296523903813, 33.908679680987319 ], [ -116.052916315652169, 33.908716113667474 ], [ -116.052867270774968, 33.908769624151191 ], [ -116.052667449032711, 33.908818384955829 ], [ -116.05269139889775, 33.908890438184727 ], [ -116.052747117278983, 33.908954594949577 ], [ -116.052842117821513, 33.908924113537658 ], [ -116.052943607715392, 33.908963055167916 ], [ -116.052978623389208, 33.909018312143616 ], [ -116.052929310637694, 33.909081413376207 ], [ -116.052876579806195, 33.909060517445205 ], [ -116.052671879351223, 33.909101665580408 ], [ -116.052713640550635, 33.909172151434021 ], [ -116.052759254589589, 33.909213610312719 ], [ -116.052744791953174, 33.909266035050855 ], [ -116.052694908135692, 33.909303588329955 ], [ -116.052765231592772, 33.909407296849743 ], [ -116.052710898851501, 33.909525264740992 ], [ -116.05264969902106, 33.909581226793712 ], [ -116.05272056390092, 33.909650458140177 ], [ -116.0526952013916, 33.909689111676073 ], [ -116.052591183714455, 33.90970325724944 ], [ -116.052527113226347, 33.909660781144808 ], [ -116.052469669042978, 33.909663288475642 ], [ -116.052430042677955, 33.909671944229984 ], [ -116.052241609125545, 33.909754450006879 ], [ -116.052164766593791, 33.909802135213418 ], [ -116.052084564227783, 33.909828751729847 ], [ -116.05219087034412, 33.909935680881397 ], [ -116.052247786834002, 33.909990848534406 ], [ -116.052464991187378, 33.910004190201441 ], [ -116.052540313287636, 33.910053364356408 ], [ -116.052521198672636, 33.910135465323265 ], [ -116.052319801885659, 33.910244777289769 ], [ -116.052253613875365, 33.910275368079645 ], [ -116.052259545945887, 33.910352435757531 ], [ -116.052207700381629, 33.910392227324643 ], [ -116.05214602253433, 33.910370162446611 ], [ -116.051945807352695, 33.91042845431032 ], [ -116.052019063810377, 33.910507593138121 ], [ -116.052047627611074, 33.910540458760799 ], [ -116.052072418406453, 33.910588169121688 ], [ -116.052082663075296, 33.91064177573729 ], [ -116.052143895856361, 33.910686983592122 ], [ -116.052179987925172, 33.910711151782905 ], [ -116.052157141816593, 33.910806780327889 ], [ -116.052120188528107, 33.91085451083697 ], [ -116.052196182571691, 33.910900436172639 ], [ -116.052246924245665, 33.91092509895676 ], [ -116.05223184913487, 33.910970305993239 ], [ -116.052162158923906, 33.911008835250634 ], [ -116.052056626695858, 33.910986405560877 ], [ -116.051971947660604, 33.911036662606442 ], [ -116.05192335229836, 33.911108658581178 ], [ -116.051941054060549, 33.911154487261655 ], [ -116.05188914981305, 33.911199628057382 ], [ -116.051819811064263, 33.91118074584147 ], [ -116.051753663356422, 33.911195390129656 ], [ -116.05173402293039, 33.911283571243253 ], [ -116.051700215195325, 33.911332019236617 ], [ -116.0516483389558, 33.911358980230325 ], [ -116.051438429951119, 33.911353780158308 ], [ -116.051413434903935, 33.911313831681923 ], [ -116.051433419408397, 33.911240903065014 ], [ -116.051320585168639, 33.91119728138986 ], [ -116.051273835457522, 33.911160436662108 ], [ -116.051091461699158, 33.911128507358683 ], [ -116.050865598670583, 33.911387236136697 ], [ -116.050868562924975, 33.911549330315943 ], [ -116.050881672976956, 33.911628172304937 ], [ -116.051017279538527, 33.911678800238384 ], [ -116.05121557719427, 33.911696160654515 ], [ -116.051260205284322, 33.911660258987808 ], [ -116.051333661702458, 33.911657891283518 ], [ -116.05139199029405, 33.911688664608548 ], [ -116.051561694116842, 33.911759152184239 ], [ -116.051573832122415, 33.911863083940041 ], [ -116.051495981307923, 33.911908013285746 ], [ -116.05127802701945, 33.911939606568708 ], [ -116.051189165014222, 33.911960164176108 ], [ -116.051290762640008, 33.912074089108614 ], [ -116.051346125015272, 33.912080695406708 ], [ -116.051434165429328, 33.912136655466512 ], [ -116.051425991130472, 33.912198090565518 ], [ -116.051374065931171, 33.912203791894939 ], [ -116.05134018910617, 33.912243533711653 ], [ -116.051265860575228, 33.912258291714423 ], [ -116.051047582003676, 33.912234808824003 ], [ -116.050973818273192, 33.912215681366824 ], [ -116.050873274761628, 33.912242523905697 ], [ -116.050820665066112, 33.912279058093276 ], [ -116.050897370326396, 33.912347669243445 ], [ -116.05096355865463, 33.912385342392028 ], [ -116.050958790559335, 33.912453348550834 ], [ -116.050880279749805, 33.912483614085772 ], [ -116.050807527497, 33.912551833122528 ], [ -116.050672706815675, 33.912614257388924 ], [ -116.050698257949009, 33.912639566766948 ], [ -116.050768265371332, 33.912661313823087 ], [ -116.050838327612965, 33.912683065841115 ], [ -116.050887155036293, 33.912711891555702 ], [ -116.050941370294723, 33.912759600450663 ], [ -116.050992772082097, 33.912835894091891 ], [ -116.050977409646521, 33.912920951668823 ], [ -116.050902895170353, 33.91295079222035 ], [ -116.050887650289468, 33.912999509988559 ], [ -116.050898390108131, 33.913054662942365 ], [ -116.050811795684353, 33.913155262031815 ], [ -116.050718816270177, 33.913130005390862 ], [ -116.05065917655007, 33.91312963866374 ], [ -116.050766296313483, 33.913225800680564 ], [ -116.050746996262859, 33.913284338835716 ], [ -116.050548216153771, 33.913351583576642 ], [ -116.050599744367048, 33.913407492269982 ], [ -116.050643425328587, 33.913503898316122 ], [ -116.050579320785886, 33.913540676319919 ], [ -116.050552285400641, 33.913607427269227 ], [ -116.050511516701405, 33.913677306370516 ], [ -116.050459395439887, 33.913728853789429 ], [ -116.050383787620518, 33.913751478677163 ], [ -116.05033231419992, 33.913755314330736 ], [ -116.050137958337046, 33.913695458074855 ], [ -116.050081923569181, 33.913691912548437 ], [ -116.050066779571424, 33.913746873970823 ], [ -116.050100232789774, 33.913814649676375 ], [ -116.050156834504051, 33.913842252599991 ], [ -116.050280116977845, 33.91388033481617 ], [ -116.050339625419156, 33.913948883107004 ], [ -116.050328150798393, 33.913992112495926 ], [ -116.050305430933648, 33.914032189762814 ], [ -116.050237702024859, 33.91407191317586 ], [ -116.050289902906457, 33.914115820939053 ], [ -116.050297014125846, 33.91418108524887 ], [ -116.050225648039543, 33.914236104228962 ], [ -116.050184383222557, 33.914198671073557 ], [ -116.050139270768838, 33.914235349873351 ], [ -116.050225640688552, 33.914338252560427 ], [ -116.050210576012134, 33.914387779378892 ], [ -116.050172883592168, 33.914424749752911 ], [ -116.050108835661362, 33.914421080057544 ], [ -116.050056199441542, 33.914383322855258 ], [ -116.04998014832843, 33.91438989617388 ], [ -116.049951192942103, 33.914566107915448 ], [ -116.04994724040867, 33.914631306094705 ], [ -116.049867727709128, 33.914686642307807 ], [ -116.049875059347997, 33.914742847751626 ], [ -116.049889857895437, 33.914833573440973 ], [ -116.049927551417525, 33.914893438234571 ], [ -116.049954032204894, 33.914918910044484 ], [ -116.049953857785084, 33.914984950824596 ], [ -116.049896807973582, 33.91500316356462 ], [ -116.049782689034743, 33.915083064280246 ], [ -116.049687060944706, 33.915108575320097 ], [ -116.04963343316534, 33.915149217281062 ], [ -116.049536010213913, 33.915203248821697 ], [ -116.049458636299946, 33.915327704209531 ], [ -116.049473624695935, 33.915400309750162 ], [ -116.049492561029396, 33.915444679386475 ], [ -116.049473136511295, 33.915539457888698 ], [ -116.049415508934402, 33.915596070299429 ], [ -116.049457643742159, 33.915666440770231 ], [ -116.049488244347913, 33.915695385844884 ], [ -116.049488111275537, 33.915752822033049 ], [ -116.049441888248026, 33.91579716807216 ], [ -116.049335402660745, 33.915753763984995 ], [ -116.049314353490047, 33.915840807566028 ], [ -116.049279645869603, 33.915914306920683 ], [ -116.049203901575311, 33.9159734801881 ], [ -116.049196945617894, 33.916096706110565 ], [ -116.049203806055843, 33.916182331236428 ], [ -116.049153154940385, 33.916254530622837 ], [ -116.049098648728958, 33.916302644371022 ], [ -116.048997123019745, 33.916402482954368 ], [ -116.048941817023703, 33.916499841046814 ], [ -116.048925695360452, 33.916552018765941 ], [ -116.048823983803587, 33.916613364531216 ], [ -116.048753207940308, 33.916671829828921 ], [ -116.048707997705606, 33.916783880314377 ], [ -116.048457970764943, 33.916917097320756 ], [ -116.048310877352179, 33.917026929229827 ], [ -116.048059832459501, 33.917161203624616 ], [ -116.048052927502894, 33.917471539942802 ], [ -116.047470883556599, 33.917954090019485 ], [ -116.046866593419907, 33.918038017505246 ], [ -116.045643123136372, 33.918253207191597 ], [ -116.04440850041253, 33.918425619051284 ], [ -116.043263112966031, 33.918627049048418 ], [ -116.041790451611348, 33.918930740507143 ], [ -116.041427837710273, 33.919106315824216 ], [ -116.041044683914606, 33.919620939049359 ], [ -116.041098419309719, 33.920349452957588 ], [ -116.040975500457705, 33.921131389330824 ], [ -116.040676861795575, 33.921367525794942 ], [ -116.040641030986791, 33.921790814331331 ], [ -116.040462005257098, 33.92203096163545 ], [ -116.040252718919163, 33.922303944684103 ], [ -116.039816586176826, 33.922244659913709 ], [ -116.03948709264769, 33.921857925686297 ], [ -116.0385796390362, 33.921879813144479 ], [ -116.038025179329225, 33.921780085844588 ], [ -116.037673608665543, 33.921838051905304 ], [ -116.037219130117819, 33.921801372452052 ], [ -116.037068936899118, 33.921604628163685 ], [ -116.036304754740868, 33.921503085007174 ], [ -116.035957214121552, 33.921542453708618 ], [ -116.035217349559048, 33.921395810095504 ], [ -116.034930010141579, 33.921171199237456 ], [ -116.034807973182311, 33.921028113231095 ], [ -116.034440837739467, 33.920840997287193 ], [ -116.034283328482928, 33.92077718699025 ], [ -116.033962355554877, 33.920627241608315 ], [ -116.03376944336263, 33.920733072718846 ], [ -116.033638653976638, 33.920859507359076 ], [ -116.033531201948477, 33.92083787387952 ], [ -116.033470314232275, 33.920646630056098 ], [ -116.033385414677142, 33.920510563387275 ], [ -116.033203342761965, 33.920605800758139 ], [ -116.033087820743631, 33.920558879750494 ], [ -116.032903914862217, 33.920507723005834 ], [ -116.032802826468071, 33.920464055014158 ], [ -116.032692675496037, 33.92039883042564 ], [ -116.032590958936623, 33.920333747731966 ], [ -116.032489276327809, 33.920247602598963 ], [ -116.032454511182252, 33.920140860689322 ], [ -116.032495749764493, 33.920077362000789 ], [ -116.032663238655189, 33.920078813427217 ], [ -116.032747357475145, 33.92012217498938 ], [ -116.032890312127719, 33.920116907102262 ], [ -116.033042593964495, 33.920035015008374 ], [ -116.033195248955707, 33.919981961749137 ], [ -116.03342084426103, 33.91990224098101 ], [ -116.033585690166746, 33.919809403727058 ], [ -116.033745166648401, 33.919819628685708 ], [ -116.03386407066769, 33.919760973172579 ], [ -116.03401069221313, 33.919730309240393 ], [ -116.034072015153058, 33.919622044086204 ], [ -116.034151075115219, 33.919521760029276 ], [ -116.034244215980834, 33.919386000790624 ], [ -116.034475051273262, 33.919477331861181 ], [ -116.034698978001799, 33.91937867602784 ], [ -116.034903364748303, 33.919417202653378 ], [ -116.035125189844962, 33.919309015254392 ], [ -116.035564074945015, 33.919337402045727 ], [ -116.035713211445398, 33.91919189214866 ], [ -116.035936345307576, 33.918998202198985 ], [ -116.035953391129709, 33.918863895009913 ], [ -116.036355799969627, 33.918585811696026 ], [ -116.036376411472816, 33.918366004748137 ], [ -116.036719296284019, 33.918149325103848 ], [ -116.036680401905684, 33.917942298743228 ], [ -116.036303042201453, 33.917853969997033 ], [ -116.036052563320709, 33.917876114244088 ], [ -116.035828202729832, 33.917594965111263 ], [ -116.035909918471162, 33.917268920773353 ], [ -116.036207222053406, 33.917018097356042 ], [ -116.03611582755353, 33.916547123493842 ], [ -116.035861528190082, 33.916268329385439 ], [ -116.035894528753175, 33.915538467774354 ], [ -116.036072309404744, 33.915312600067935 ], [ -116.036034521775235, 33.915085862196563 ], [ -116.035745841120544, 33.915095197596507 ], [ -116.035650206240376, 33.914867876101319 ], [ -116.035690222204252, 33.91442896515818 ], [ -116.03546732793356, 33.913708693720565 ], [ -116.03508386659945, 33.913070846244409 ], [ -116.034920377035263, 33.912740012957286 ], [ -116.034845551782155, 33.912341349484592 ], [ -116.034812240431748, 33.911966361231755 ], [ -116.034442876495348, 33.911456858101779 ], [ -116.034378753513948, 33.911183927255962 ], [ -116.034373081602652, 33.910840920296728 ], [ -116.034533256579763, 33.910712428421753 ], [ -116.034642782257805, 33.910581324908144 ], [ -116.034596356086396, 33.910236646245281 ], [ -116.034709251876251, 33.909817285790268 ], [ -116.034770307495336, 33.909326504750645 ], [ -116.035052144705958, 33.90915121602491 ], [ -116.03519842510147, 33.908709196003919 ], [ -116.035360399078115, 33.908343027305918 ], [ -116.035718832136254, 33.907541324239133 ], [ -116.036231131881848, 33.906758544410991 ], [ -116.036269036205852, 33.906482871853733 ], [ -116.036599390707522, 33.906296814664543 ], [ -116.036655605699295, 33.905861964474411 ], [ -116.037054195346684, 33.905560911214998 ], [ -116.03750765111046, 33.904787953422954 ], [ -116.037715427673831, 33.904531705702247 ], [ -116.037975300077278, 33.904090576039387 ], [ -116.03849098552206, 33.903539283953904 ], [ -116.038463245753434, 33.903436952591477 ], [ -116.038486164658011, 33.90334269932864 ], [ -116.038507922144788, 33.903319330706758 ], [ -116.038560275114307, 33.903227374654058 ], [ -116.038816854989037, 33.902946187710903 ], [ -116.039106939423903, 33.902665912030841 ], [ -116.039158270344998, 33.902313685283005 ], [ -116.039345337421068, 33.902033882973441 ], [ -116.039336815774703, 33.901906256114266 ], [ -116.039341181794555, 33.901850308817139 ], [ -116.039401902082687, 33.901777363788298 ], [ -116.039412787627398, 33.901751054546189 ], [ -116.039514693449718, 33.90172851686863 ], [ -116.039554276122146, 33.901635274881336 ], [ -116.03954871353109, 33.901530649339769 ], [ -116.039531938074944, 33.901451790277122 ], [ -116.039584609255371, 33.901411733445784 ], [ -116.039664592857122, 33.901421122979123 ], [ -116.039716720809366, 33.901436054715482 ], [ -116.039846095009665, 33.901283690310521 ], [ -116.03991550952702, 33.901290563882377 ], [ -116.039959637923374, 33.9013298156285 ], [ -116.040022401460519, 33.901333533727119 ], [ -116.040071977637069, 33.901307409034857 ], [ -116.040362425609857, 33.901089416643828 ], [ -116.040395839606603, 33.900825755722146 ], [ -116.040335833702443, 33.900708374788096 ], [ -116.040287166554961, 33.900568340436223 ], [ -116.04023711848869, 33.90048704156932 ], [ -116.040031613869857, 33.900034261951426 ], [ -116.040214849395539, 33.899743708357455 ], [ -116.040461834168298, 33.899527840706426 ], [ -116.040571827255633, 33.899381473901002 ], [ -116.040529811035881, 33.899304661721885 ], [ -116.04052121009299, 33.899250524237317 ], [ -116.040566869728991, 33.89920819637711 ], [ -116.040634118607187, 33.899245472578365 ], [ -116.040659031180681, 33.89920873110902 ], [ -116.040750044255432, 33.899024103569772 ], [ -116.040796572661108, 33.898957373234403 ], [ -116.040784550945503, 33.898913564823722 ], [ -116.040769315038489, 33.898862571071646 ], [ -116.040753908486934, 33.898817305242716 ], [ -116.040813251398077, 33.898769744465611 ], [ -116.04088185960336, 33.898750936188698 ], [ -116.041260797673317, 33.898038090294527 ], [ -116.041686865079001, 33.897358379125734 ], [ -116.042047988832167, 33.897106212662699 ], [ -116.04205085043175, 33.897020695956328 ], [ -116.04209481517313, 33.896959434343636 ], [ -116.042162007851829, 33.896877239257037 ], [ -116.0423654668854, 33.896669096376286 ], [ -116.042507738684122, 33.896388212550228 ], [ -116.042599112112455, 33.896299797874079 ] ] ] } } +] +} From 1280a6de81d1216088b35527bee33ff20099dcf7 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 20:03:10 +0000 Subject: [PATCH 107/175] black format working again --- src/app.py | 35 +++---------- src/lib/burn_severity.py | 4 +- src/lib/query_rap.py | 16 +++--- src/lib/query_sentinel.py | 4 +- src/lib/query_soil.py | 6 +-- src/routers/analyze/spectral_burn_metrics.py | 9 +++- src/routers/check/connectivity.py | 14 ++++-- src/routers/check/dns.py | 3 +- src/routers/check/health.py | 3 +- src/routers/check/sentry_error.py | 9 +++- src/routers/dependencies.py | 19 ++++--- src/routers/fetch/ecoclass.py | 14 ++++-- .../fetch/rangeland_analysis_platform.py | 11 ++-- src/routers/list/derived_products.py | 13 ++++- src/routers/pages/directory.py | 9 ++-- src/routers/pages/home.py | 3 +- src/routers/pages/map.py | 2 +- src/routers/pages/upload.py | 10 ++-- src/routers/upload/drawn_aoi.py | 12 ++++- src/routers/upload/shapefile_zip.py | 20 ++++++-- src/static/sketch/sketch.py | 8 +-- src/util/cloud_static_io.py | 50 +++++++++---------- src/util/gcp_secrets.py | 6 ++- src/util/ingest_burn_zip.py | 25 +++++++--- tests/conftest.py | 47 +++++++++++------ tests/unit/lib/burn_severity.py | 27 ++++++++++ 26 files changed, 242 insertions(+), 137 deletions(-) diff --git a/src/app.py b/src/app.py index d2aa3fc..ff08ee5 100644 --- a/src/app.py +++ b/src/app.py @@ -4,32 +4,12 @@ from titiler.core.factory import TilerFactory from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers -from src.routers.check import ( - connectivity, - dns, - health, - sentry_error -) -from src.routers.analyze import ( - spectral_burn_metrics -) -from src.routers.upload import ( - drawn_aoi, - shapefile_zip -) -from src.routers.fetch import ( - rangeland_analysis_platform, - ecoclass -) -from src.routers.list import ( - derived_products -) -from src.routers.pages import ( - home, - map, - upload, - directory -) +from src.routers.check import connectivity, dns, health, sentry_error +from src.routers.analyze import spectral_burn_metrics +from src.routers.upload import drawn_aoi, shapefile_zip +from src.routers.fetch import rangeland_analysis_platform, ecoclass +from src.routers.list import derived_products +from src.routers.pages import home, map, upload, directory from src.lib.titiler_algorithms import algorithms @@ -54,7 +34,7 @@ ### ANALYZE ### app.include_router(spectral_burn_metrics.router) -### UPLOAD ### +### UPLOAD ### app.include_router(drawn_aoi.router) app.include_router(shapefile_zip.router) @@ -68,4 +48,3 @@ ### TILESERVER ### cog = TilerFactory(process_dependency=algorithms.dependency) app.include_router(cog.router, prefix="/cog", tags=["tileserver"]) - diff --git a/src/lib/burn_severity.py b/src/lib/burn_severity.py index f1db419..ac2c0c0 100644 --- a/src/lib/burn_severity.py +++ b/src/lib/burn_severity.py @@ -69,14 +69,14 @@ def calc_burn_metrics(prefire_nir, prefire_swir, postfire_nir, postfire_swir): rbr = calc_rbr(dnbr, nbr_prefire) # stack these arrays together, naming them by their source - # TODO [#22]: Look into other sources of useful info that come with satellite imagery - cloud cover, defective pixels, etc. + # TODO [#22]: Look into other sources of useful info that come with satellite imagery - cloud cover, defective pixels, etc. # Some of these might help us filter out bad data (especially as it relates to cloud occlusion) burn_stack = xr.concat( [nbr_prefire, nbr_postfire, dnbr, rdnbr, rbr], pd.Index( ["nbr_prefire", "nbr_postfire", "dnbr", "rdnbr", "rbr"], name="burn_metric" ), - coords="minimal" + coords="minimal", ) return burn_stack diff --git a/src/lib/query_rap.py b/src/lib/query_rap.py index ae246ef..b395536 100644 --- a/src/lib/query_rap.py +++ b/src/lib/query_rap.py @@ -5,7 +5,8 @@ import numpy as np import json -def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance = 0.01): + +def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): boundary_gdf = gpd.GeoDataFrame.from_features(boundary_geojson) @@ -29,19 +30,22 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance = 0.01): # Open the GeoTIFF file as a rioxarray with the window and out_shape parameters rap_estimates = rxr.open_rasterio(rap_url, masked=True).rio.isel_window(window) - # Rename for RAP bands based on README: + # Rename for RAP bands based on README: # - Band 1 - annual forb and grass # - Band 2 - perennial forb and grass # - Band 3 - shrub # - Band 4 - tree rap_estimates = rap_estimates.assign_coords( - band=("band", - ["annual_forb_and_grass", "perennial_forb_and_grass", "shrub", "tree"] + band=( + "band", + ["annual_forb_and_grass", "perennial_forb_and_grass", "shrub", "tree"], ) ) - rap_estimates = rap_estimates.rio.clip(boundary_gdf_buffer.geometry.values, 'EPSG:4326') + rap_estimates = rap_estimates.rio.clip( + boundary_gdf_buffer.geometry.values, "EPSG:4326" + ) # add np.nan where 65535, also based on readme rap_estimates = rap_estimates.where(rap_estimates != 65535, np.nan) - return rap_estimates \ No newline at end of file + return rap_estimates diff --git a/src/lib/query_sentinel.py b/src/lib/query_sentinel.py index 294ce6f..a2ad23e 100644 --- a/src/lib/query_sentinel.py +++ b/src/lib/query_sentinel.py @@ -165,7 +165,9 @@ def query_fire_event( ) if len(prefire_items) == 0 or len(postfire_items) == 0: - raise ValueError('Date ranges insufficient for enough imagery to calculate burn metrics') + raise ValueError( + "Date ranges insufficient for enough imagery to calculate burn metrics" + ) self.prefire_stack = self.arrange_stack(prefire_items) self.postfire_stack = self.arrange_stack(postfire_items) diff --git a/src/lib/query_soil.py b/src/lib/query_soil.py index 8648d52..b55140d 100644 --- a/src/lib/query_soil.py +++ b/src/lib/query_soil.py @@ -207,9 +207,9 @@ def edit_get_ecoclass_info(ecoclass_id): edit_json = json.loads(response.content) # Add hyperlink to EDIT human readable page - edit_json[ - "hyperlink" - ] = f"https://edit.jornada.nmsu.edu/catalogs/esd/{geoUnit}/{ecoclass_id}" + edit_json["hyperlink"] = ( + f"https://edit.jornada.nmsu.edu/catalogs/esd/{geoUnit}/{ecoclass_id}" + ) return True, edit_json elif response.status_code == 404: diff --git a/src/routers/analyze/spectral_burn_metrics.py b/src/routers/analyze/spectral_burn_metrics.py index 665a671..761c18c 100644 --- a/src/routers/analyze/spectral_burn_metrics.py +++ b/src/routers/analyze/spectral_burn_metrics.py @@ -13,6 +13,7 @@ router = APIRouter() + class AnaylzeBurnPOSTBody(BaseModel): geojson: Any derive_boundary: bool @@ -24,7 +25,11 @@ class AnaylzeBurnPOSTBody(BaseModel): # TODO [#5]: Decide on / implement cloud tasks or other async batch # This is a long running process, and users probably don't mind getting an email notification # or something similar when the process is complete. Esp if the frontend remanins static. -@router.post("/api/analyze/spectral-burn-metrics", tags=["analysis"], description="Derive spectral burn metrics from satellite imagery within a boundary.") +@router.post( + "/api/analyze/spectral-burn-metrics", + tags=["analysis"], + description="Derive spectral burn metrics from satellite imagery within a boundary.", +) def analyze_spectral_burn_metrics( body: AnaylzeBurnPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), @@ -98,7 +103,7 @@ def analyze_spectral_burn_metrics( "derived_boundary": derived_boundary, }, ) - + except Exception as e: sentry_sdk.capture_exception(e) logger.log_text(f"Error: {e}") diff --git a/src/routers/check/connectivity.py b/src/routers/check/connectivity.py index 6bdbc41..30b2cde 100644 --- a/src/routers/check/connectivity.py +++ b/src/routers/check/connectivity.py @@ -6,15 +6,23 @@ router = APIRouter() -@router.get("/api/check/connectivity", tags=["check"], description="Check connectivity to example.com") + +@router.get( + "/api/check/connectivity", + tags=["check"], + description="Check connectivity to example.com", +) def check_connectivity(logger: Logger = Depends(get_cloud_logger)): try: response = requests.get("http://example.com") logger.log_text( f"Connectivity check: Got response {response.status_code} from http://example.com" ) - return 200, f"Connectivity check: Got response {response.status_code} from http://example.com" + return ( + 200, + f"Connectivity check: Got response {response.status_code} from http://example.com", + ) except Exception as e: logger.log_text(f"Connectivity check: Error {e}") - raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/routers/check/dns.py b/src/routers/check/dns.py index a2777b5..94a4d41 100644 --- a/src/routers/check/dns.py +++ b/src/routers/check/dns.py @@ -7,6 +7,7 @@ router = APIRouter() + @router.get("/api/check/dns", tags=["check"], summary="Check DNS resolution") def check_dns(logger: Logger = Depends(get_cloud_logger)): try: @@ -16,4 +17,4 @@ def check_dns(logger: Logger = Depends(get_cloud_logger)): return {"ip_address": ip_address} except Exception as e: logger.log_text(f"DNS check: Error {e}") - raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file + raise HTTPException(status_code=400, detail=str(e)) diff --git a/src/routers/check/health.py b/src/routers/check/health.py index b43b881..2e8c53b 100644 --- a/src/routers/check/health.py +++ b/src/routers/check/health.py @@ -4,7 +4,8 @@ router = APIRouter() + @router.get("/api/check/health", tags=["check"], description="Health check endpoint") def health(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Health check endpoint called") - return "Alive", 200 \ No newline at end of file + return "Alive", 200 diff --git a/src/routers/check/sentry_error.py b/src/routers/check/sentry_error.py index b5eb05b..f630e75 100644 --- a/src/routers/check/sentry_error.py +++ b/src/routers/check/sentry_error.py @@ -4,7 +4,12 @@ router = APIRouter() -@router.get("/api/check/sentry-error", tags=["check"], summary="Trigger a division by zero error for Sentry to catch.") + +@router.get( + "/api/check/sentry-error", + tags=["check"], + summary="Trigger a division by zero error for Sentry to catch.", +) async def trigger_error(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Triggering a division by zero error for Sentry to catch.") - __division_by_zero = 1 / 0 \ No newline at end of file + __division_by_zero = 1 / 0 diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index 10716df..a74ca5f 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -7,6 +7,7 @@ from src.util.gcp_secrets import get_mapbox_secret as gcp_get_mapbox_secret import os + def get_cloud_logger(): logging_client = logging.Client(project="dse-nps") log_name = "burn-backend" @@ -14,21 +15,22 @@ def get_cloud_logger(): return logger + def get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Creating CloudStaticIOClient") - return CloudStaticIOClient('burn-severity-backend', "s3") + return CloudStaticIOClient("burn-severity-backend", "s3") + def get_manifest( - cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), - logger: Logger = Depends(get_cloud_logger) + cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), + logger: Logger = Depends(get_cloud_logger), ): logger.log_text("Getting manifest") manifest = cloud_static_io_client.get_manifest() return manifest -def init_sentry( - logger: Logger = Depends(get_cloud_logger) -): + +def init_sentry(logger: Logger = Depends(get_cloud_logger)): logger.log_text("Initializing Sentry client") ## TODO: Move to sentry to environment variable if we keep sentry @@ -42,8 +44,9 @@ def init_sentry( # We recommend adjusting this value in production. profiles_sample_rate=1.0, ) - sentry_sdk.set_context("env", {"env": os.getenv('ENV')}) + sentry_sdk.set_context("env", {"env": os.getenv("ENV")}) logger.log_text("Sentry initialized") + def get_mapbox_secret(): - return gcp_get_mapbox_secret() \ No newline at end of file + return gcp_get_mapbox_secret() diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index e5eeea3..6af5799 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -11,23 +11,29 @@ from src.lib.query_soil import ( sdm_get_esa_mapunitid_poly, sdm_get_ecoclassid_from_mu_info, - edit_get_ecoclass_info + edit_get_ecoclass_info, ) from src.util.cloud_static_io import CloudStaticIOClient router = APIRouter() + class QuerySoilPOSTBody(BaseModel): geojson: Any fire_event_name: str affiliation: str -@router.post("/api/fetch/ecoclass", tags=["fetch"], description="Fetch ecoclass data (using Soil Data Mart / Web Soil Survey for Map Unit polygons, and the Ecological Site Description database for ecoclass info)") + +@router.post( + "/api/fetch/ecoclass", + tags=["fetch"], + description="Fetch ecoclass data (using Soil Data Mart / Web Soil Survey for Map Unit polygons, and the Ecological Site Description database for ecoclass info)", +) def fetch_ecoclass( body: QuerySoilPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry: None = Depends(init_sentry), - logger: Logger = Depends(get_cloud_logger) + logger: Logger = Depends(get_cloud_logger), ): fire_event_name = body.fire_event_name geojson = json.loads(body.geojson) @@ -36,7 +42,7 @@ def fetch_ecoclass( sentry_sdk.set_context("analyze_ecoclass", {"request": body}) try: - + mapunit_gdf = sdm_get_esa_mapunitid_poly(geojson) mu_polygon_keys = [ mupolygonkey diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index fed4f7e..dd3ba0f 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -21,7 +21,12 @@ class AnaylzeRapPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/fetch/rangeland-analysis-platform", tags=["fetch"], description="Fetch Rangeland Analysis Platform (RAP) biomass estimates") + +@router.post( + "/api/fetch/rangeland-analysis-platform", + tags=["fetch"], + description="Fetch Rangeland Analysis Platform (RAP) biomass estimates", +) def fetch_rangeland_analysis_platform( body: AnaylzeRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), @@ -37,8 +42,7 @@ def fetch_rangeland_analysis_platform( try: rap_estimates = rap_get_biomass( - boundary_geojson=boundary_geojson, - ignition_date=ignition_date + boundary_geojson=boundary_geojson, ignition_date=ignition_date ) # save the cog to the FTP server @@ -61,4 +65,3 @@ def fetch_rangeland_analysis_platform( sentry_sdk.capture_exception(e) logger.log_text(f"Error: {e}") raise HTTPException(status_code=400, detail=str(e)) - diff --git a/src/routers/list/derived_products.py b/src/routers/list/derived_products.py index 68c04aa..1410570 100644 --- a/src/routers/list/derived_products.py +++ b/src/routers/list/derived_products.py @@ -10,11 +10,17 @@ router = APIRouter() + class GetDerivedProductsPOSTBody(BaseModel): fire_event_name: str affiliation: str -@router.post("/api/list/derived-products", tags=["list"], description="List derived products of a fiven fire event / affiliation combination.") + +@router.post( + "/api/list/derived-products", + tags=["list"], + description="List derived products of a fiven fire event / affiliation combination.", +) async def list_derived_products( body: GetDerivedProductsPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), @@ -24,7 +30,10 @@ async def list_derived_products( fire_event_name = body.fire_event_name affiliation = body.affiliation - sentry_sdk.set_context("get_derived_products", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + sentry_sdk.set_context( + "get_derived_products", + {"fire_event_name": fire_event_name, "affiliation": affiliation}, + ) try: derived_products = cloud_static_io_client.get_derived_products( diff --git a/src/routers/pages/directory.py b/src/routers/pages/directory.py index a3617e2..dba5a17 100644 --- a/src/routers/pages/directory.py +++ b/src/routers/pages/directory.py @@ -8,11 +8,12 @@ router = APIRouter() templates = Jinja2Templates(directory="src/static") + @router.get("/directory", response_class=HTMLResponse) def directory( request: Request, manifest: dict = Depends(get_manifest), - mapbox_token: str = Depends(get_mapbox_secret) + mapbox_token: str = Depends(get_mapbox_secret), ): manifest_json = json.dumps(manifest) cloud_run_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") @@ -22,6 +23,6 @@ def directory( "request": request, "manifest": manifest_json, "mapbox_token": mapbox_token, - "cloud_run_endpoint": cloud_run_endpoint - } - ) \ No newline at end of file + "cloud_run_endpoint": cloud_run_endpoint, + }, + ) diff --git a/src/routers/pages/home.py b/src/routers/pages/home.py index f6b8a32..ea6939b 100644 --- a/src/routers/pages/home.py +++ b/src/routers/pages/home.py @@ -8,6 +8,7 @@ router = APIRouter() templates = Jinja2Templates(directory="src/static") + @router.get("/", response_class=HTMLResponse) def home(request: Request): # Read the markdown file @@ -23,4 +24,4 @@ def home(request: Request): "request": request, "content": html_content, }, - ) \ No newline at end of file + ) diff --git a/src/routers/pages/map.py b/src/routers/pages/map.py index 85f9364..b463de0 100644 --- a/src/routers/pages/map.py +++ b/src/routers/pages/map.py @@ -9,6 +9,7 @@ router = APIRouter() templates = Jinja2Templates(directory="src/static") + @router.get( "/map/{affiliation}/{fire_event_name}/{burn_metric}", response_class=HTMLResponse ) @@ -58,7 +59,6 @@ def serve_map( + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" ) - fire_metadata = manifest[affiliation][fire_event_name] fire_metadata_json = json.dumps(fire_metadata) diff --git a/src/routers/pages/upload.py b/src/routers/pages/upload.py index 06616f3..b0b52b6 100644 --- a/src/routers/pages/upload.py +++ b/src/routers/pages/upload.py @@ -8,11 +8,9 @@ router = APIRouter() templates = Jinja2Templates(directory="src/static") + @router.get("/upload", response_class=HTMLResponse) -def upload( - request: Request, - mapbox_token: str = Depends(get_mapbox_secret) -): +def upload(request: Request, mapbox_token: str = Depends(get_mapbox_secret)): tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") return templates.TemplateResponse( @@ -21,5 +19,5 @@ def upload( "request": request, "mapbox_token": mapbox_token, # for NAIP and Satetllite in V0 "tileserver_endpoint": tileserver_endpoint, - } - ) \ No newline at end of file + }, + ) diff --git a/src/routers/upload/drawn_aoi.py b/src/routers/upload/drawn_aoi.py index 935bbd6..c654977 100644 --- a/src/routers/upload/drawn_aoi.py +++ b/src/routers/upload/drawn_aoi.py @@ -9,7 +9,12 @@ router = APIRouter() -@router.post("/api/upload/drawn-aoi", tags=["upload"], description="Upload a drawn AOI boundary to cloud storage") + +@router.post( + "/api/upload/drawn-aoi", + tags=["upload"], + description="Upload a drawn AOI boundary to cloud storage", +) async def upload_drawn_aoi( fire_event_name: str = Form(...), affiliation: str = Form(...), @@ -18,7 +23,10 @@ async def upload_drawn_aoi( logger: Logger = Depends(get_cloud_logger), __sentry: None = Depends(init_sentry), ): - sentry_sdk.set_context("upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + sentry_sdk.set_context( + "upload_drawn_aoi", + {"fire_event_name": fire_event_name, "affiliation": affiliation}, + ) try: with tempfile.NamedTemporaryFile(suffix=".geojson", delete=False) as tmp: diff --git a/src/routers/upload/shapefile_zip.py b/src/routers/upload/shapefile_zip.py index f62129e..da38543 100644 --- a/src/routers/upload/shapefile_zip.py +++ b/src/routers/upload/shapefile_zip.py @@ -12,7 +12,12 @@ router = APIRouter() -@router.post("/api/upload/shapefile-zip", tags=["upload"], description="Upload a shapefile zip of a predefined fire event area") + +@router.post( + "/api/upload/shapefile-zip", + tags=["upload"], + description="Upload a shapefile zip of a predefined fire event area", +) async def upload_shapefile( fire_event_name: str = Form(...), affiliation: str = Form(...), @@ -21,7 +26,10 @@ async def upload_shapefile( logger: Logger = Depends(get_cloud_logger), __sentry: None = Depends(init_sentry), ): - sentry_sdk.set_context("upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}) + sentry_sdk.set_context( + "upload_shapefile", + {"fire_event_name": fire_event_name, "affiliation": affiliation}, + ) try: # Read the file @@ -40,7 +48,9 @@ async def upload_shapefile( ), "Zip must contain exactly one shapefile (with associated files: .shx, .prj and optionally, .dbf)" __shp_paths, geojson = valid_shp[0] - user_uploaded_s3_path = "public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}" + user_uploaded_s3_path = ( + "public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}" + ) # Upload the zip and a geojson to s3 cloud_static_io_client.upload( source_local_path=tmp_zip, @@ -53,7 +63,9 @@ async def upload_shapefile( tmp_geojson = tmp.name with open(tmp_geojson, "w") as f: f.write(geojson) - boundary_s3_path = f"public/{affiliation}/{fire_event_name}/boundary.geojson" + boundary_s3_path = ( + f"public/{affiliation}/{fire_event_name}/boundary.geojson" + ) cloud_static_io_client.upload( source_local_path=tmp_geojson, remote_path=boundary_s3_path, diff --git a/src/static/sketch/sketch.py b/src/static/sketch/sketch.py index ff44217..50c738a 100644 --- a/src/static/sketch/sketch.py +++ b/src/static/sketch/sketch.py @@ -2,10 +2,10 @@ sketch = sketchingpy.Sketch2D(500, 500) -sketch.clear('#F0F0F0') +sketch.clear("#F0F0F0") -sketch.set_fill('#C0C0C0') -sketch.set_stroke('#000000') +sketch.set_fill("#C0C0C0") +sketch.set_stroke("#000000") sketch.draw_ellipse(250, 250, 20, 20) -sketch.show() \ No newline at end of file +sketch.show() diff --git a/src/util/cloud_static_io.py b/src/util/cloud_static_io.py index 22654a5..e05c26a 100644 --- a/src/util/cloud_static_io.py +++ b/src/util/cloud_static_io.py @@ -20,6 +20,7 @@ ## TODO [#26]: Make bucket https a tofu output BUCKET_HTTPS_PREFIX = "https://burn-severity-backend.s3.us-east-2.amazonaws.com" + class CloudStaticIOClient: def __init__(self, bucket_name, provider): @@ -35,7 +36,7 @@ def __init__(self, bucket_name, provider): log_name = "burn-backend" self.logger = logging_client.logger(log_name) - self.sts_client = boto3.client('sts') + self.sts_client = boto3.client("sts") if provider == "s3": self.prefix = f"s3://{self.bucket_name}" @@ -47,7 +48,9 @@ def __init__(self, bucket_name, provider): self.s3_session = None self.validate_credentials() - self.logger.log_text(f"Initialized CloudStaticIOClient for {self.bucket_name} with provider {provider}") + self.logger.log_text( + f"Initialized CloudStaticIOClient for {self.bucket_name} with provider {provider}" + ) def impersonate_service_account(self): # Load the credentials of the user @@ -61,7 +64,7 @@ def impersonate_service_account(self): source_credentials=source_credentials, target_principal=self.service_account_email, target_scopes=target_scopes, - lifetime=3600 + lifetime=3600, ) # Refresh the client @@ -89,11 +92,13 @@ def local_fetch_id_token(self, audience): def validate_credentials(self): - if not self.role_assumed_credentials or (self.role_assumed_credentials['Expiration'].timestamp() - time.time() < 300): + if not self.role_assumed_credentials or ( + self.role_assumed_credentials["Expiration"].timestamp() - time.time() < 300 + ): oidc_token = None request = gcp_requests.Request() - if self.env == 'LOCAL': + if self.env == "LOCAL": if not self.iam_credentials or self.iam_credentials.expired: self.impersonate_service_account() self.iam_credentials.refresh(request) @@ -107,16 +112,16 @@ def validate_credentials(self): sts_response = self.sts_client.assume_role_with_web_identity( RoleArn=self.role_arn, RoleSessionName=self.role_session_name, - WebIdentityToken=oidc_token + WebIdentityToken=oidc_token, ) - self.role_assumed_credentials = sts_response['Credentials'] + self.role_assumed_credentials = sts_response["Credentials"] self.boto_session = boto3.Session( - aws_access_key_id=self.role_assumed_credentials['AccessKeyId'], - aws_secret_access_key=self.role_assumed_credentials['SecretAccessKey'], - aws_session_token=self.role_assumed_credentials['SessionToken'], - region_name='us-east-2' + aws_access_key_id=self.role_assumed_credentials["AccessKeyId"], + aws_secret_access_key=self.role_assumed_credentials["SecretAccessKey"], + aws_session_token=self.role_assumed_credentials["SessionToken"], + region_name="us-east-2", ) def download(self, remote_path, target_local_path): @@ -138,7 +143,7 @@ def download(self, remote_path, target_local_path): with smart_open.open( f"{self.prefix}/{remote_path}", "rb", - transport_params={"client": self.boto_session.client('s3')}, + transport_params={"client": self.boto_session.client("s3")}, ) as remote_file: with open(target_local_path, "wb") as local_file: local_file.write(remote_file.read()) @@ -161,7 +166,7 @@ def upload(self, source_local_path, remote_path): with smart_open.open( f"{self.prefix}/{remote_path}", "wb", - transport_params={"client": self.boto_session.client('s3')}, + transport_params={"client": self.boto_session.client("s3")}, ) as remote_file: remote_file.write(local_file.read()) print("upload completed") @@ -209,12 +214,7 @@ def upload_cogs( remote_path=f"public/{affiliation}/{fire_event_name}/pct_change_dnbr_rbr.tif", ) - def upload_rap_estimates( - self, - rap_estimates, - fire_event_name, - affiliation - ): + def upload_rap_estimates(self, rap_estimates, fire_event_name, affiliation): with tempfile.TemporaryDirectory() as tmpdir: for band_name in rap_estimates.band.to_index(): # TODO [#23]: This is the same logic as in upload_cogs. Refactor to avoid duplication @@ -308,13 +308,13 @@ def get_manifest(self): return manifest def get_derived_products(self, affiliation, fire_event_name): - s3_client = self.boto_session.client('s3') - paginator = s3_client.get_paginator('list_objects_v2') + s3_client = self.boto_session.client("s3") + paginator = s3_client.get_paginator("list_objects_v2") derived_products = {} bucket_prefix = f"public/{affiliation}/{fire_event_name}/" for page in paginator.paginate(Bucket=self.bucket_name, Prefix=bucket_prefix): - for obj in page['Contents']: - full_https_url = BUCKET_HTTPS_PREFIX + '/' + obj['Key'] - filename = os.path.basename(obj['Key']) + for obj in page["Contents"]: + full_https_url = BUCKET_HTTPS_PREFIX + "/" + obj["Key"] + filename = os.path.basename(obj["Key"]) derived_products[filename] = full_https_url - return derived_products \ No newline at end of file + return derived_products diff --git a/src/util/gcp_secrets.py b/src/util/gcp_secrets.py index e04730e..5785030 100644 --- a/src/util/gcp_secrets.py +++ b/src/util/gcp_secrets.py @@ -1,6 +1,7 @@ from google.cloud import secretmanager import json + def get_ssh_secret(): # GCP project and secret details project_id = "dse-nps" @@ -16,10 +17,13 @@ def get_ssh_secret(): response = client.access_secret_version(request={"name": name}) # Parse the secret value as a string. - ssh_private_key = json.loads(response.payload.data.decode("UTF-8"))['SSH_KEY_ADMIN_PRIVATE'] + ssh_private_key = json.loads(response.payload.data.decode("UTF-8"))[ + "SSH_KEY_ADMIN_PRIVATE" + ] return ssh_private_key.replace("\\n", "\n") + def get_mapbox_secret(): # GCP project and secret details project_id = "dse-nps" diff --git a/src/util/ingest_burn_zip.py b/src/util/ingest_burn_zip.py index d61d407..2bc0461 100644 --- a/src/util/ingest_burn_zip.py +++ b/src/util/ingest_burn_zip.py @@ -5,6 +5,7 @@ import os import tempfile + def ingest_esri_zip_file(zip_file_path): valid_shapefiles = [] valid_tifs = [] @@ -22,24 +23,33 @@ def ingest_esri_zip_file(zip_file_path): print("Found shapefile: {}".format(file_name)) # Check if all required files exist - if all(os.path.exists(os.path.join(tmp_dir, shp_base + ext)) for ext in [".shp", ".shx", ".prj"]): + if all( + os.path.exists(os.path.join(tmp_dir, shp_base + ext)) + for ext in [".shp", ".shx", ".prj"] + ): # Read the shapefile from the temporary directory valid_shapefile = ( os.path.join(tmp_dir, shp_base + ".shp"), os.path.join(tmp_dir, shp_base + ".shx"), - os.path.join(tmp_dir, shp_base + ".prj") + os.path.join(tmp_dir, shp_base + ".prj"), ) # If .dbf file exists, add it to the tuple if os.path.exists(os.path.join(tmp_dir, shp_base + ".dbf")): - valid_shapefile += (os.path.join(tmp_dir, shp_base + ".dbf"),) + valid_shapefile += ( + os.path.join(tmp_dir, shp_base + ".dbf"), + ) shp_geojson = shp_to_geojson(valid_shapefile[0]) valid_shapefiles.append((valid_shapefile, shp_geojson)) - + else: - print("Shapefile {} is missing required files (shp, shx, and proj).".format(file_name)) + print( + "Shapefile {} is missing required files (shp, shx, and proj).".format( + file_name + ) + ) if file_name.endswith(".tif"): print("Found tif file: {}".format(file_name)) @@ -47,7 +57,7 @@ def ingest_esri_zip_file(zip_file_path): with tempfile.TemporaryDirectory() as tmp_dir: # Extract the related files to the temporary directory tif_base = os.path.splitext(file_name)[0] - zip_ref.extract(tif_base + '.tif', path=tmp_dir) + zip_ref.extract(tif_base + ".tif", path=tmp_dir) # Read the shapefile from the temporary directory valid_tif = rxr.open_rasterio(os.path.join(tmp_dir, file_name)) @@ -56,6 +66,7 @@ def ingest_esri_zip_file(zip_file_path): return valid_shapefiles, valid_tifs + def shp_to_geojson(shp_file_path): gdf = gpd.read_file(shp_file_path).to_crs("EPSG:4326") - return gdf.to_json() \ No newline at end of file + return gdf.to_json() diff --git a/tests/conftest.py b/tests/conftest.py index c3bbe75..78c1f33 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,61 +6,78 @@ ### PyStac ItemCollection + @pytest.fixture def test_stac_items(): - with open('assets/test_stac_items.json') as f: + with open("assets/test_stac_items.json") as f: test_stac_items = pystac.read_file(f.read()) return test_stac_items + ### Stacked xarray (time, band, y, x) + @pytest.fixture def test_valid_xarray(): - with open('assets/test_nir.tif') as f: + with open("assets/test_nir.tif") as f: test_xarray_nir = rio.open(f) - with open('assets/test_swir.tif') as f: + with open("assets/test_swir.tif") as f: test_xarray_swir = rio.open(f) - return xr.concat([test_xarray_nir, test_xarray_swir], dim='band') + return xr.concat([test_xarray_nir, test_xarray_swir], dim="band") + @pytest.fixture def test_nan_xarray(): - with open('assets/test_nir.tif') as f: + with open("assets/test_nir.tif") as f: test_xarray_nir = rio.open(f) nan_array = xr.full_like(test_xarray_nir, np.nan) - test_nan_xarray = xr.concat([nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim='band') + test_nan_xarray = xr.concat( + [nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim="band" + ) return test_nan_xarray + @pytest.fixture def test_zero_xarray(): - with open('assets/test_nir.tif') as f: + with open("assets/test_nir.tif") as f: test_xarray_nir = rio.open(f) zero_array = xr.full_like(test_xarray_nir, 0) - test_zero_xarray = xr.concat([zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim='band') + test_zero_xarray = xr.concat( + [zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim="band" + ) return test_zero_xarray + ### Reduced across time window (only spatial dims + band remain) + @pytest.fixture def test_reduced_xarray(): - with open('assets/test_nir_reduced.tif') as f: + with open("assets/test_nir_reduced.tif") as f: test_xarray_nir = rio.open(f) - with open('assets/test_swir_reduced.tif') as f: + with open("assets/test_swir_reduced.tif") as f: test_xarray_swir = rio.open(f) - test_reduced_xarray = xr.concat([test_xarray_nir, test_xarray_swir], dim='band') + test_reduced_xarray = xr.concat([test_xarray_nir, test_xarray_swir], dim="band") return test_reduced_xarray + @pytest.fixture def test_reduced_nan_xarray(): - with open('assets/test_nir_reduced.tif') as f: + with open("assets/test_nir_reduced.tif") as f: test_xarray_nir = rio.open(f) nan_array = xr.full_like(test_xarray_nir, np.nan) - test_reduced_nan_xarray = xr.concat([nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim='band') + test_reduced_nan_xarray = xr.concat( + [nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim="band" + ) return test_reduced_nan_xarray + @pytest.fixture def test_reduced_zero_xarray(): - with open('assets/test_nir_reduced.tif') as f: + with open("assets/test_nir_reduced.tif") as f: test_xarray_nir = rio.open(f) zero_array = xr.full_like(test_xarray_nir, 0) - test_reduced_zero_xarray = xr.concat([zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim='band') + test_reduced_zero_xarray = xr.concat( + [zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim="band" + ) return test_reduced_zero_xarray diff --git a/tests/unit/lib/burn_severity.py b/tests/unit/lib/burn_severity.py index e69de29..aa8a7ba 100644 --- a/tests/unit/lib/burn_severity.py +++ b/tests/unit/lib/burn_severity.py @@ -0,0 +1,27 @@ +import pytest +import src.lib.burn_severity as burn_severity + + +def test_calc_nbr(test_valid_xarray, test_zero_xarray, test_nan_xarray): + """Test calc_nbr function.""" + # Test valid input + xr_nir = test_valid_xarray.sel(band="B8A") + xr_swir = test_valid_xarray.sel(band="B12") + result = burn_severity.calc_nbr(xr_nir, xr_swir) + assert result is not None + assert result.shape == xr_swir.shape == xr_nir.shape + + # Test zero input - should work, but not meaningful + xr_nir = test_zero_xarray.sel(band="B8A") + xr_swir = test_zero_xarray.sel(band="B12") + result = burn_severity.calc_nbr(xr_nir, xr_swir) + assert result is not None + assert result.shape == xr_swir.shape == xr_nir.shape + + # Test NaN input + xr_nir = test_nan_xarray.sel(band="B8A") + xr_swir = test_nan_xarray.sel(band="B12") + result = burn_severity.calc_nbr(xr_nir, xr_swir) + assert result is not None + assert result.shape == xr_swir.shape == xr_nir.shape + assert result.isnull().all() From d28a1066c32917c6524db79b8ec13b28f56535c6 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 20:40:48 +0000 Subject: [PATCH 108/175] tests running but something fishy with the demo tiffs I made... for some reason, time seems to have been renamed or hnever existed in the first place. Will be better to just save them as multiband in the first place. --- pytest.ini | 2 ++ tests/conftest.py | 36 +++++++++---------- ...burn_severity.py => test_burn_severity.py} | 0 3 files changed, 18 insertions(+), 20 deletions(-) create mode 100644 pytest.ini rename tests/unit/lib/{burn_severity.py => test_burn_severity.py} (100%) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..aaf283f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . diff --git a/tests/conftest.py b/tests/conftest.py index 78c1f33..fd6cfed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,15 @@ import pytest import pystac -import rasterio as rio -import xarray as xr +import rioxarray as rxr import numpy as np +import xarray as xr ### PyStac ItemCollection @pytest.fixture def test_stac_items(): - with open("assets/test_stac_items.json") as f: + with open("tests/assets/test_stac_items.json") as f: test_stac_items = pystac.read_file(f.read()) return test_stac_items @@ -19,17 +19,18 @@ def test_stac_items(): @pytest.fixture def test_valid_xarray(): - with open("assets/test_nir.tif") as f: - test_xarray_nir = rio.open(f) - with open("assets/test_swir.tif") as f: - test_xarray_swir = rio.open(f) - return xr.concat([test_xarray_nir, test_xarray_swir], dim="band") + test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir.tif") + test_xarray_swir = rxr.open_rasterio("tests/assets/test_swir.tif") + + return xr.concat( + [test_xarray_nir, test_xarray_swir], + dim="band", + ) @pytest.fixture def test_nan_xarray(): - with open("assets/test_nir.tif") as f: - test_xarray_nir = rio.open(f) + test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir.tif") nan_array = xr.full_like(test_xarray_nir, np.nan) test_nan_xarray = xr.concat( [nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim="band" @@ -39,8 +40,7 @@ def test_nan_xarray(): @pytest.fixture def test_zero_xarray(): - with open("assets/test_nir.tif") as f: - test_xarray_nir = rio.open(f) + test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir.tif") zero_array = xr.full_like(test_xarray_nir, 0) test_zero_xarray = xr.concat( [zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim="band" @@ -53,18 +53,15 @@ def test_zero_xarray(): @pytest.fixture def test_reduced_xarray(): - with open("assets/test_nir_reduced.tif") as f: - test_xarray_nir = rio.open(f) - with open("assets/test_swir_reduced.tif") as f: - test_xarray_swir = rio.open(f) + test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir_reduced.tif") + test_xarray_swir = rxr.open_rasterio("tests/assets/test_swir_reduced.tif") test_reduced_xarray = xr.concat([test_xarray_nir, test_xarray_swir], dim="band") return test_reduced_xarray @pytest.fixture def test_reduced_nan_xarray(): - with open("assets/test_nir_reduced.tif") as f: - test_xarray_nir = rio.open(f) + test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir_reduced.tif") nan_array = xr.full_like(test_xarray_nir, np.nan) test_reduced_nan_xarray = xr.concat( [nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim="band" @@ -74,8 +71,7 @@ def test_reduced_nan_xarray(): @pytest.fixture def test_reduced_zero_xarray(): - with open("assets/test_nir_reduced.tif") as f: - test_xarray_nir = rio.open(f) + test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir_reduced.tif") zero_array = xr.full_like(test_xarray_nir, 0) test_reduced_zero_xarray = xr.concat( [zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim="band" diff --git a/tests/unit/lib/burn_severity.py b/tests/unit/lib/test_burn_severity.py similarity index 100% rename from tests/unit/lib/burn_severity.py rename to tests/unit/lib/test_burn_severity.py From 7611bfbe7f93d81bc525ae55ac6701c9eebd612c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 21:00:01 +0000 Subject: [PATCH 109/175] replace wonky tiffs with more legit pkls of subset of imagery data --- .gitignore | 1 + .../assets/test_imagery_time_reduced_xarray.pkl | Bin 0 -> 20531 bytes tests/assets/test_imagery_with_time_xarray.pkl | Bin 0 -> 31941 bytes tests/assets/test_nir.tif | Bin 23778 -> 0 bytes tests/assets/test_nir_reduced.tif | Bin 3730 -> 0 bytes tests/assets/test_swir.tif | Bin 23778 -> 0 bytes tests/assets/test_swir_reduced.tif | Bin 3730 -> 0 bytes 7 files changed, 1 insertion(+) create mode 100644 tests/assets/test_imagery_time_reduced_xarray.pkl create mode 100644 tests/assets/test_imagery_with_time_xarray.pkl delete mode 100644 tests/assets/test_nir.tif delete mode 100644 tests/assets/test_nir_reduced.tif delete mode 100644 tests/assets/test_swir.tif delete mode 100644 tests/assets/test_swir_reduced.tif diff --git a/.gitignore b/.gitignore index 19c6f2c..8e6bc5c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ src/__pycache__ *.pyc .devcontainer/.env +.vscode/settings.json # Ignore generated credentials from google-github-actions/auth gha-creds-*.json \ No newline at end of file diff --git a/tests/assets/test_imagery_time_reduced_xarray.pkl b/tests/assets/test_imagery_time_reduced_xarray.pkl new file mode 100644 index 0000000000000000000000000000000000000000..0669b49177b2e981f4d945cec4240720dcd608e2 GIT binary patch literal 20531 zcmeHPYm6LMR_^gUZI2&`?GWuI+16x}bXL5%HKtQtb$hC6 z`lYIBzeJR*h#gxhv5`_NEA1k7VHd#%z>odGZ&(%~gb*T&SQKf0us`wx1S|N? zt*V}Wd8Qq}@t|q@)~&wxoO{ka_uO;Oy>o8AHTKJ~3laWtPux%}OSw6xHY_%$DUQOw zdhY&L@t&h9?>+CuFL{MGyqG&#QX36RvpxDUR#xho=N|8D;F@9?O1a8B_s~*ZV>d2^ z-?!QrFKI^2_Vmfyo{M)->1o3tM5DgpxhF1wf@1TQP>|Dh#Z1|J7Xb#}ePn*gHzggYL-^FH|WPt;qrW*~!FtLhj*yVE6$sg1hrSS{57x;xGR zJ$Dj(1+vLUZhL1&%HBKPsu#3+EI7p7ra8DaPGhG z;g9@j$1C5v3UQ9UlF4~^ijv1r=8n~s8iSZxchImqQSuwqUv&3flsg;zX=*S(ogd3j z0S(Y)04t7T*Rw= zhPtMIKJ3^5d;#@JM|bp--JOY0XU=fdI{J^F%aMk?9^}6teTv!b*d1s{MGND=PdtHkiQ+|w}QMG z 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 T=hVWzy$ah8o59{uq?Z2!X|46h literal 0 HcmV?d00001 diff --git a/tests/assets/test_nir.tif b/tests/assets/test_nir.tif deleted file mode 100644 index e83fce4658198c44855d24f0f1e4d86490e02888..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23778 zcmeIy&ui0Q7zgktZS9H_VK6I*l$Xv!E0v~cnkCSdPAj9ZO=%|{C2il1;rfFngG~k# z6?*eO5QYdIrFZ`fFZvgF@+>>~yy?n1*+q~Z$zl{eyUpapshe>cx$-00nXI^&Z!Fy&4-Qplb!nG?UeA`-f*8^S5Jx2K!uL<+M~VU z^u;~jZNE^L`*y*$E!j03*|nu~-J&Ur1+Lw8%j~RK@-If^7dOgS+^E9hMwyEnRb1RC z>mQ9Os^rgt>w~wd0$7%o{zfMr`JK%qS?*Iozf!Xj-uD(pX9xuw{b?(ngygjkmjtHn3Z4S2P4+ zTF@;JOyng>d_+r8G1^wtNQ@68K8VH#@ttUlNmZgTDh7fYoxArtO?Q(u(LcK1{LY-2 z^XuH1xi=b}DWxJZMZ{MmKHHjV|9oTGe%kdmUPRl?D;d+C>?_WWo#GdnHYR=^ZFkP| z$uR+ui^t?N(=M_(6E_9KWtT0`yEz~ww!PK1pUo6W(6$fT_FyI;wv3#y?V*f6>%Pne zOut{Y+4zY4>#^-(%aoI{zaq6w3mUFnoJc!yC+*ag1%tKGbRro{ImyJ_(1L54=P!#c zZHX?qE)?u`l51AmU{xsC)#*0kiSAfe$C`9UXDU?JopxfY+@CAjBeSdKM6Ozyh*dlB zL``*#6A#aF!jbTm?eSPR5~;0pE7XGW%sW~{OdVO|!Gilh3+Xvy%82A|Rr_jUbOdhXB&xgz4h45@R zK9A>kdbIvmL!OW0@B$#YIXw(i_L;Pxl@~Bh*W)JiBB=lOpZItt zk1wOk$p4J`r-(m|)^&UYt>-AfxEtWkTuLJvAEM`F1#!-wT#9C&8Ra2-un*)p^vC$< zM!x`85+6XHK{t5w%Z^-{4spjW(8Qrg~UcVUfo=3N#*FjxJeNIq()MY(n zIeucqo6wwd^9A!k;a)Q5lJ`0F<@Cnvdt*7e7wZ4DiFw+Izk$~K^Ch(MZ}QZ?o;&uR zQF5;Je4Fj~x)rJ~JGtg60yhj=|UH{^ork`X1z76s4Yb^^?y}sCx=O`rUAn_}g$j{XADQ89z|wp>-ct zvMzdmUxtq7G2c&~HV;z&5Y&56&%;B+hu}w0&!tlDLC%+OuZ#Ns%Nu8Of9Cm+LV64J zw!_#53v`o3(WU%xB#I|7;y z>DPNe-%lsJxCD8w1$-1-KZo(qtfh|bL+%-~ggnlvnc&q^isrw_c%|-#|LTi;vy}Ou gbl`*U2HA_=L7sje>vMymKHennamM{mU+#MT0|ROCivR!s diff --git a/tests/assets/test_swir.tif b/tests/assets/test_swir.tif deleted file mode 100644 index e83fce4658198c44855d24f0f1e4d86490e02888..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23778 zcmeIy&ui0Q7zgktZS9H_VK6I*l$Xv!E0v~cnkCSdPAj9ZO=%|{C2il1;rfFngG~k# z6?*eO5QYdIrFZ`fFZvgF@+>>~yy?n1*+q~Z$zl{eyUpapshe>cx$-00nXI^&Z!Fy&4-Qplb!nG?UeA`-f*8^S5Jx2K!uL<+M~VU z^u;~jZNE^L`*y*$E!j03*|nu~-J&Ur1+Lw8%j~RK@-If^7dOgS+^E9hMwyEnRb1RC z>mQ9Os^rgt>w~wd0$7%o{zfMr`JK%qS?*I3$g6vxj@YpJy=HUcJsj&0P6*0PjN8%mi*uo=tJ(yCRV$uKiW?S|74qk>+F zp)86E1S={rrYa}~N)sgbg%|@Lh)cu|Zh;sgG1WxHn&9qz^L~f)y{f4nW`6f9@0|ZT z_r3d~(W#OvA_XFWED6}v<@OgCu@171Sbr94CogBj`m#WF=Guaw$b=F8iL9NPlgKgo zB9lktG_uaJIhU-@7l$d^py%;?$+7FrcD=7ROG0*i*sk~Y=8J73zuNUcZ!n`i&pJ$h zP_|hA8T(sj*V&exu}=FhQdK*zX2HT_I+lo~W7T<~P*pUY>OSv8vrVrxk`$UCttr?2e~eSEXCKIt#12)3JD)^H;JwJY!l#c-D$!yeyVT zR+d%966Mol<>B&~%M&)L^L&!nfN$yZZu#gV#jM~hhn!gAZNx0atC^CRB|%*)}jk@_xC#Si!v11 z*pc2br9b7=i@es z3_l|OAf%qzPW%wuOT67nxeogp@*?myxS04IaJr9Ev7r=VPjV?cv0Gs=EGkh zq`ee95A?qDd6sjJIo}QWiTY>ZTDXutb+7?{3a<9C4_lvK_FYz>F9pB!@d$P`R9(so z@U!2t$?YR;a3!pVkHC#ynipN$9K=7tt!wUrYw4rkBjs7*yg#D4lzLA6O#No+FU4Ml z-GcoLeAP$Qw}<@G#3S_I3x|Cia_v#N&}a0$;G8nd%XCtI9sT0)75JW)<_YW_F3rbq zB6{|qZFf(iTcdL=)Mo)AMjFC*XyodzJt0yccb%wm;W|5^`HMf zenvl?5Bj7B|MT=a0T1~|+-%4HflH(B<05qRF%RFIRL>}0MLzoEFm+X@o`<{-M)!3W zI-oIJ_X7Uo@CT^p$5?fdr}GoeFLR7O{qRBf0Da!X?(=aC@tyEl;?xuU9{*o`N2vpa z^Hy{|3F2Y6)ulY-rP29rBad@Z_hZz)jaBE%^nvDGY`zzU_twmzZxlBBsJb?A{k?Fk II{qQ=Z_V-R4*&oF From 756e2ae569d68ce2c20d9ee632a3d90dbd48a0f9 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 21:10:06 +0000 Subject: [PATCH 110/175] working with proper pickling of xarrays --- tests/conftest.py | 50 ++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fd6cfed..17dbd95 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import rioxarray as rxr import numpy as np import xarray as xr +import pickle ### PyStac ItemCollection @@ -19,32 +20,24 @@ def test_stac_items(): @pytest.fixture def test_valid_xarray(): - test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir.tif") - test_xarray_swir = rxr.open_rasterio("tests/assets/test_swir.tif") - - return xr.concat( - [test_xarray_nir, test_xarray_swir], - dim="band", - ) + with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: + test_valid_xarray = pickle.load(f) + return test_valid_xarray @pytest.fixture def test_nan_xarray(): - test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir.tif") - nan_array = xr.full_like(test_xarray_nir, np.nan) - test_nan_xarray = xr.concat( - [nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim="band" - ) + with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: + test_valid_xarray = pickle.load(f) + test_nan_xarray = xr.full_like(test_valid_xarray, np.nan) return test_nan_xarray @pytest.fixture def test_zero_xarray(): - test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir.tif") - zero_array = xr.full_like(test_xarray_nir, 0) - test_zero_xarray = xr.concat( - [zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim="band" - ) + with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: + test_valid_xarray = pickle.load(f) + test_zero_xarray = xr.full_like(test_valid_xarray, 0) return test_zero_xarray @@ -53,27 +46,22 @@ def test_zero_xarray(): @pytest.fixture def test_reduced_xarray(): - test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir_reduced.tif") - test_xarray_swir = rxr.open_rasterio("tests/assets/test_swir_reduced.tif") - test_reduced_xarray = xr.concat([test_xarray_nir, test_xarray_swir], dim="band") + with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: + test_reduced_xarray = pickle.load(f) return test_reduced_xarray @pytest.fixture def test_reduced_nan_xarray(): - test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir_reduced.tif") - nan_array = xr.full_like(test_xarray_nir, np.nan) - test_reduced_nan_xarray = xr.concat( - [nan_array.rename(band="B8A"), nan_array.rename(band="B12")], dim="band" - ) - return test_reduced_nan_xarray + with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: + test_reduced_xarray = pickle.load(f) + test_nan_array = xr.full_like(test_reduced_xarray, np.nan) + return test_nan_array @pytest.fixture def test_reduced_zero_xarray(): - test_xarray_nir = rxr.open_rasterio("tests/assets/test_nir_reduced.tif") - zero_array = xr.full_like(test_xarray_nir, 0) - test_reduced_zero_xarray = xr.concat( - [zero_array.rename(band="B8A"), zero_array.rename(band="B12")], dim="band" - ) + with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: + test_reduced_xarray = pickle.load(f) + test_reduced_zero_xarray = xr.full_like(test_reduced_xarray, 0) return test_reduced_zero_xarray From 6a4c0bef4758c93a212891fd94ed0fcab194185f Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 22:00:51 +0000 Subject: [PATCH 111/175] use prod dockerfile and run tests on PR --- .github/workflows/pytest.yml | 20 ++++++++++++++++ src/lib/query_rap.py | 3 +++ tests/conftest.py | 36 ++++++++++++++++++---------- tests/unit/lib/test_burn_severity.py | 23 +++++++++++++++++- 4 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..99eb6ab --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,20 @@ +name: Build Prod and PyTest + +on: + pull_request: + branches: + - main + - dev + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Build Docker image + run: docker build -t my-image -f .deployment/prod.Dockerfile . + + - name: Run tests + run: docker run my-image pytest -kv diff --git a/src/lib/query_rap.py b/src/lib/query_rap.py index b395536..afcbd22 100644 --- a/src/lib/query_rap.py +++ b/src/lib/query_rap.py @@ -18,6 +18,9 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): if rap_year > 2022: print("RAP data is only available up to 2022 - falling back to 2022 RAP data") rap_year = 2022 + elif rap_year < 1986: + raise ValueError("RAP data is only available from 1986") + rap_url = f"http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-npp/v3/vegetation-npp-v3-{rap_year}.tif" # Create a buffer around the boundary diff --git a/tests/conftest.py b/tests/conftest.py index 17dbd95..f37b060 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import numpy as np import xarray as xr import pickle +import random ### PyStac ItemCollection @@ -26,17 +27,13 @@ def test_valid_xarray(): @pytest.fixture -def test_nan_xarray(): - with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: - test_valid_xarray = pickle.load(f) +def test_nan_xarray(test_valid_xarray): test_nan_xarray = xr.full_like(test_valid_xarray, np.nan) return test_nan_xarray @pytest.fixture -def test_zero_xarray(): - with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: - test_valid_xarray = pickle.load(f) +def test_zero_xarray(test_valid_xarray): test_zero_xarray = xr.full_like(test_valid_xarray, 0) return test_zero_xarray @@ -52,16 +49,31 @@ def test_reduced_xarray(): @pytest.fixture -def test_reduced_nan_xarray(): - with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: - test_reduced_xarray = pickle.load(f) +def test_reduced_nan_xarray(test_reduced_xarray): test_nan_array = xr.full_like(test_reduced_xarray, np.nan) return test_nan_array @pytest.fixture -def test_reduced_zero_xarray(): - with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: - test_reduced_xarray = pickle.load(f) +def test_reduced_zero_xarray(test_reduced_xarray): test_reduced_zero_xarray = xr.full_like(test_reduced_xarray, 0) return test_reduced_zero_xarray + + +### Dummy NBR xarray + + +@pytest.fixture +def test_nbr_xarrays(test_reduced_xarray): + nbr_shape = test_reduced_xarray.sel(band="B12").shape + prefire_test_nbr_xarray = xr.DataArray( + np.random.rand(*nbr_shape), dims=test_reduced_xarray.sel(band="B12").dims + ) + postfire_test_nbr_xarray = ( + xr.DataArray( + np.random.rand(*nbr_shape), dims=test_reduced_xarray.sel(band="B12").dims + ) + - 0.25 # to simulate a postfire NBR, which is lower than prefire + ) + test_dnbr_xarray = prefire_test_nbr_xarray - postfire_test_nbr_xarray + return prefire_test_nbr_xarray, postfire_test_nbr_xarray, test_dnbr_xarray diff --git a/tests/unit/lib/test_burn_severity.py b/tests/unit/lib/test_burn_severity.py index aa8a7ba..daa7426 100644 --- a/tests/unit/lib/test_burn_severity.py +++ b/tests/unit/lib/test_burn_severity.py @@ -18,10 +18,31 @@ def test_calc_nbr(test_valid_xarray, test_zero_xarray, test_nan_xarray): assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape - # Test NaN input + # Test NaN input - also should work, but not meaningful xr_nir = test_nan_xarray.sel(band="B8A") xr_swir = test_nan_xarray.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape assert result.isnull().all() + + +def test_calc_dnbr(test_nbr_xarrays): + test_nbr_prefire, test_nbr_postfire, __test_dnbr = test_nbr_xarrays + result = burn_severity.calc_dnbr(test_nbr_prefire, test_nbr_postfire) + assert result is not None + assert result.shape == test_nbr_prefire.shape == test_nbr_postfire.shape + + +def test_calc_rdnbr(test_nbr_xarrays): + test_nbr_prefire, __test_nbr_postfire, test_dnbr = test_nbr_xarrays + result = burn_severity.calc_rdnbr(test_dnbr, test_nbr_prefire) + assert result is not None + assert result.shape == test_dnbr.shape == test_nbr_prefire.shape + + +def test_calc_rbr(test_nbr_xarrays): + test_nbr_prefire, __test_nbr_postfire, test_dnbr = test_nbr_xarrays + result = burn_severity.calc_rbr(test_dnbr, test_nbr_prefire) + assert result is not None + assert result.shape == test_nbr_prefire.shape == test_nbr_prefire.shape From c4a583fd526b6935b27408d4a4da2298c4595eeb Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 22:06:27 +0000 Subject: [PATCH 112/175] whoops prod doesn't have pytest, duh --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 99eb6ab..86a20f5 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - name: Build Docker image - run: docker build -t my-image -f .deployment/prod.Dockerfile . + run: docker build -t prod -f .deployment/prod.Dockerfile . - name: Run tests - run: docker run my-image pytest -kv + run: docker run prod /bin/bash -c "pip install pytest && pytest" From 1e6b9288413801623e093bbe9088a918c5589be6 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 22:13:17 +0000 Subject: [PATCH 113/175] install pytest in conda env then use it, also ignore the entrypoint defined in prod dockerfile --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 86a20f5..693142e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -16,5 +16,5 @@ jobs: - name: Build Docker image run: docker build -t prod -f .deployment/prod.Dockerfile . - - name: Run tests - run: docker run prod /bin/bash -c "pip install pytest && pytest" + - name: Run tests (override entrypoint to run tests rather than deploy) + run: docker run --entrypoint /bin/bash prod -c "conda install -n burn-severity-prod pytest && conda run -n burn-severity-prod pytest" From af35e68b1050e4dc27ec5d6674b04a29b621f31e Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 13 Feb 2024 22:44:27 +0000 Subject: [PATCH 114/175] mocking and patching for upload_fire_event --- tests/unit/util/test_cloud_static_io.py | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/unit/util/test_cloud_static_io.py diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py new file mode 100644 index 0000000..8c1615d --- /dev/null +++ b/tests/unit/util/test_cloud_static_io.py @@ -0,0 +1,47 @@ +import pytest +from src.util.cloud_static_io import CloudStaticIOClient +from unittest.mock import patch, MagicMock, ANY + + +@patch("src.util.cloud_static_io.CloudStaticIOClient.update_manifest") +@patch("src.util.cloud_static_io.CloudStaticIOClient.upload_cogs") +@patch.object(CloudStaticIOClient, "__init__", return_value=None) +def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): + # Create an instance of CloudStaticIOClient + client = CloudStaticIOClient() + + # Mock the logger + client.logger = MagicMock() + + # Define the arguments for upload_fire_event + metrics_stack = MagicMock() + fire_event_name = "test_event" + prefire_date_range = "test_prefire_range" + postfire_date_range = "test_postfire_range" + affiliation = "test_affiliation" + derive_boundary = "test_boundary" + + # Call upload_fire_event + client.upload_fire_event( + metrics_stack, + fire_event_name, + prefire_date_range, + postfire_date_range, + affiliation, + derive_boundary, + ) + + # Assert that upload_cogs and update_manifest were called with the correct arguments + mock_upload_cogs.assert_called_once_with( + metrics_stack=metrics_stack, + fire_event_name=fire_event_name, + affiliation=affiliation, + ) + mock_update_manifest.assert_called_once_with( + fire_event_name=fire_event_name, + bounds=ANY, + prefire_date_range=prefire_date_range, + postfire_date_range=postfire_date_range, + affiliation=affiliation, + derive_boundary=derive_boundary, + ) From eabe1a32864794345d43dd9a6153bf97c0b999c4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 21:41:54 +0000 Subject: [PATCH 115/175] add pytest to prod deployment - see if this fixes hang issue, as its pretty lightweight so matters little overall --- .deployment/prod_environment.yml | 1 + .github/workflows/pytest.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.deployment/prod_environment.yml b/.deployment/prod_environment.yml index 53c68c8..ddbe1c2 100644 --- a/.deployment/prod_environment.yml +++ b/.deployment/prod_environment.yml @@ -23,6 +23,7 @@ dependencies: - smart_open - sentry-sdk[fastapi] - uvicorn + - pytest - pip - pip: - titiler.core diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 693142e..64fbcf0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,4 +17,4 @@ jobs: run: docker build -t prod -f .deployment/prod.Dockerfile . - name: Run tests (override entrypoint to run tests rather than deploy) - run: docker run --entrypoint /bin/bash prod -c "conda install -n burn-severity-prod pytest && conda run -n burn-severity-prod pytest" + run: docker run --entrypoint /bin/bash prod -c "conda run -n burn-severity-prod pytest" From 726650073c4fcb89e1d8b2909536aa621df6785e Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 22:00:22 +0000 Subject: [PATCH 116/175] update conftest to be more generic for later tests (we don't really care if a 4d array is a nbr or a metric for eg) --- tests/conftest.py | 39 +++++++------------------ tests/unit/lib/test_burn_severity.py | 30 ++++++++++--------- tests/unit/util/test_cloud_static_io.py | 11 ++++++- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f37b060..d69080c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,21 +20,21 @@ def test_stac_items(): @pytest.fixture -def test_valid_xarray(): +def test_4d_xarray(): with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: test_valid_xarray = pickle.load(f) return test_valid_xarray @pytest.fixture -def test_nan_xarray(test_valid_xarray): - test_nan_xarray = xr.full_like(test_valid_xarray, np.nan) +def test_4d_nan_xarray(test_4d_xarray): + test_nan_xarray = xr.full_like(test_4d_xarray, np.nan) return test_nan_xarray @pytest.fixture -def test_zero_xarray(test_valid_xarray): - test_zero_xarray = xr.full_like(test_valid_xarray, 0) +def test_4d_zero_xarray(test_4d_xarray): + test_zero_xarray = xr.full_like(test_4d_xarray, 0) return test_zero_xarray @@ -42,38 +42,19 @@ def test_zero_xarray(test_valid_xarray): @pytest.fixture -def test_reduced_xarray(): +def test_3d_xarray(): with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: test_reduced_xarray = pickle.load(f) return test_reduced_xarray @pytest.fixture -def test_reduced_nan_xarray(test_reduced_xarray): - test_nan_array = xr.full_like(test_reduced_xarray, np.nan) +def test_3d_nan_xarray(test_3d_xarray): + test_nan_array = xr.full_like(test_3d_xarray, np.nan) return test_nan_array @pytest.fixture -def test_reduced_zero_xarray(test_reduced_xarray): - test_reduced_zero_xarray = xr.full_like(test_reduced_xarray, 0) +def test_3d_zero_xarray(test_3d_xarray): + test_reduced_zero_xarray = xr.full_like(test_3d_xarray, 0) return test_reduced_zero_xarray - - -### Dummy NBR xarray - - -@pytest.fixture -def test_nbr_xarrays(test_reduced_xarray): - nbr_shape = test_reduced_xarray.sel(band="B12").shape - prefire_test_nbr_xarray = xr.DataArray( - np.random.rand(*nbr_shape), dims=test_reduced_xarray.sel(band="B12").dims - ) - postfire_test_nbr_xarray = ( - xr.DataArray( - np.random.rand(*nbr_shape), dims=test_reduced_xarray.sel(band="B12").dims - ) - - 0.25 # to simulate a postfire NBR, which is lower than prefire - ) - test_dnbr_xarray = prefire_test_nbr_xarray - postfire_test_nbr_xarray - return prefire_test_nbr_xarray, postfire_test_nbr_xarray, test_dnbr_xarray diff --git a/tests/unit/lib/test_burn_severity.py b/tests/unit/lib/test_burn_severity.py index daa7426..5ebd976 100644 --- a/tests/unit/lib/test_burn_severity.py +++ b/tests/unit/lib/test_burn_severity.py @@ -1,48 +1,52 @@ import pytest import src.lib.burn_severity as burn_severity +import xarray as xr -def test_calc_nbr(test_valid_xarray, test_zero_xarray, test_nan_xarray): +def test_calc_nbr(test_4d_xarray, test_4d_zero_xarray, test_4d_nan_xarray): """Test calc_nbr function.""" # Test valid input - xr_nir = test_valid_xarray.sel(band="B8A") - xr_swir = test_valid_xarray.sel(band="B12") + xr_nir = test_4d_xarray.sel(band="B8A") + xr_swir = test_4d_xarray.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape # Test zero input - should work, but not meaningful - xr_nir = test_zero_xarray.sel(band="B8A") - xr_swir = test_zero_xarray.sel(band="B12") + xr_nir = test_4d_zero_xarray.sel(band="B8A") + xr_swir = test_4d_zero_xarray.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape # Test NaN input - also should work, but not meaningful - xr_nir = test_nan_xarray.sel(band="B8A") - xr_swir = test_nan_xarray.sel(band="B12") + xr_nir = test_4d_nan_xarray.sel(band="B8A") + xr_swir = test_4d_nan_xarray.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape assert result.isnull().all() -def test_calc_dnbr(test_nbr_xarrays): - test_nbr_prefire, test_nbr_postfire, __test_dnbr = test_nbr_xarrays +def test_calc_dnbr(test_3d_xarray): + test_nbr_prefire = test_3d_xarray + test_nbr_postfire = test_3d_xarray + 0.15 result = burn_severity.calc_dnbr(test_nbr_prefire, test_nbr_postfire) assert result is not None assert result.shape == test_nbr_prefire.shape == test_nbr_postfire.shape -def test_calc_rdnbr(test_nbr_xarrays): - test_nbr_prefire, __test_nbr_postfire, test_dnbr = test_nbr_xarrays +def test_calc_rdnbr(test_3d_xarray): + test_nbr_prefire = test_3d_xarray + test_dnbr = xr.full_like(test_3d_xarray, 0.15) result = burn_severity.calc_rdnbr(test_dnbr, test_nbr_prefire) assert result is not None assert result.shape == test_dnbr.shape == test_nbr_prefire.shape -def test_calc_rbr(test_nbr_xarrays): - test_nbr_prefire, __test_nbr_postfire, test_dnbr = test_nbr_xarrays +def test_calc_rbr(test_3d_xarray): + test_nbr_prefire = test_3d_xarray + test_dnbr = xr.full_like(test_3d_xarray, 0.15) result = burn_severity.calc_rbr(test_dnbr, test_nbr_prefire) assert result is not None assert result.shape == test_nbr_prefire.shape == test_nbr_prefire.shape diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index 8c1615d..9812fd3 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -31,7 +31,8 @@ def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): derive_boundary, ) - # Assert that upload_cogs and update_manifest were called with the correct arguments + # Assert that __init__, upload_cogs and update_manifest were called with the correct arguments + mock_init.assert_called_once_with() mock_upload_cogs.assert_called_once_with( metrics_stack=metrics_stack, fire_event_name=fire_event_name, @@ -45,3 +46,11 @@ def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): affiliation=affiliation, derive_boundary=derive_boundary, ) + + +# @patch("src.util.cloud_static_io.CloudStaticIOClient.update_manifest") +# @patch("src.util.cloud_static_io.CloudStaticIOClient.upload") +# @patch.object(CloudStaticIOClient, "__init__", return_value=None) +# def test_upload_cogs( +# mock_init, mock_upload_cogs, mock_update_manifest +# ): From 0b49da7f9dcb9377b63062a104834c1b7863ce85 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 22:01:07 +0000 Subject: [PATCH 117/175] hopefully more verbose pytest prints --- .deployment/prod.Dockerfile | 4 ++-- .github/workflows/pytest.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.deployment/prod.Dockerfile b/.deployment/prod.Dockerfile index 1c31e42..97134ae 100644 --- a/.deployment/prod.Dockerfile +++ b/.deployment/prod.Dockerfile @@ -9,14 +9,14 @@ RUN apt-get update && apt-get install -y \ unzip \ curl \ ssh \ - --no-install-recommends && rm -rf /var/lib/apt/lists/* + --no-install-recommends && rm -rf /var/lib/apt/lists/* && echo "apt-get install completed" # Copy necessary files into container COPY src/ /src/ COPY .deployment/prod_environment.yml / # Create a new conda environment from the environment.yml file -RUN mamba env create -f prod_environment.yml +RUN mamba env create -f prod_environment.yml && echo "conda env create completed" # Make 'RUN' use the new environment: SHELL ["conda", "run", "-n", "burn-severity-prod", "/bin/bash", "-c"] diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 64fbcf0..bbae52e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,4 +17,4 @@ jobs: run: docker build -t prod -f .deployment/prod.Dockerfile . - name: Run tests (override entrypoint to run tests rather than deploy) - run: docker run --entrypoint /bin/bash prod -c "conda run -n burn-severity-prod pytest" + run: docker run --entrypoint /bin/bash prod -c "conda run -n burn-severity-prod pytest -svv tests/" From 16395bd0e5137dbd818b8713d56353c695f7cd38 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 22:29:42 +0000 Subject: [PATCH 118/175] test upload cogs --- tests/unit/util/test_cloud_static_io.py | 53 +++++++++++++++++++++---- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index 9812fd3..0e95d95 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -1,6 +1,6 @@ import pytest from src.util.cloud_static_io import CloudStaticIOClient -from unittest.mock import patch, MagicMock, ANY +from unittest.mock import patch, MagicMock, ANY, call @patch("src.util.cloud_static_io.CloudStaticIOClient.update_manifest") @@ -48,9 +48,48 @@ def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): ) -# @patch("src.util.cloud_static_io.CloudStaticIOClient.update_manifest") -# @patch("src.util.cloud_static_io.CloudStaticIOClient.upload") -# @patch.object(CloudStaticIOClient, "__init__", return_value=None) -# def test_upload_cogs( -# mock_init, mock_upload_cogs, mock_update_manifest -# ): +@patch("rioxarray.raster_array.RasterArray.to_raster") +@patch("rasterio.open") +@patch("src.util.cloud_static_io.CloudStaticIOClient.upload") +@patch.object(CloudStaticIOClient, "__init__", return_value=None) +def test_upload_cogs( + mock_init, mock_upload, mock_rio_open, mock_to_raster, test_3d_xarray +): + # Create an instance of CloudStaticIOClient + client = CloudStaticIOClient() + + # Mock the logger + client.logger = MagicMock() + + # Define the arguments for upload_cogs + fire_event_name = "test_event" + affiliation = "test_affiliation" + + # Give xarray the `burn_metric` band, with rbr and dnbr as values in `burn_metric` + test_3d_xarray = test_3d_xarray.rename({"band": "burn_metric"}) + test_3d_xarray["burn_metric"] = ["rbr", "dnbr"] + + # Call upload_cogs + client.upload_cogs( + test_3d_xarray, + fire_event_name, + affiliation, + ) + + mock_init.assert_called_once_with() + mock_upload.assert_has_calls( + [ + call( + source_local_path=ANY, + remote_path=f"public/{affiliation}/{fire_event_name}/rbr.tif", + ), + call( + source_local_path=ANY, + remote_path=f"public/{affiliation}/{fire_event_name}/dnbr.tif", + ), + call( + source_local_path=ANY, + remote_path=f"public/{affiliation}/{fire_event_name}/pct_change_dnbr_rbr.tif", + ), + ] + ) From 987e9552a1c46c29fd4410b8bfa7649cd441b462 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 22:41:32 +0000 Subject: [PATCH 119/175] try mounting tests as a volume - really don't want to have a seperate tests dockerfile.... --- .github/workflows/pytest.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index bbae52e..1ca5910 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,4 +17,9 @@ jobs: run: docker build -t prod -f .deployment/prod.Dockerfile . - name: Run tests (override entrypoint to run tests rather than deploy) - run: docker run --entrypoint /bin/bash prod -c "conda run -n burn-severity-prod pytest -svv tests/" + run: | + docker run + --entrypoint /bin/bash + -v ${{ github.workspace }}/tests:/tests + prod + -c "conda run -n burn-severity-prod pytest -svv /tests" From 8cb4c5abda767082a24dc3bc5efa57b1a86e93c4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 22:45:11 +0000 Subject: [PATCH 120/175] add \ --- .github/workflows/pytest.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 1ca5910..c1e8755 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -18,8 +18,8 @@ jobs: - name: Run tests (override entrypoint to run tests rather than deploy) run: | - docker run - --entrypoint /bin/bash - -v ${{ github.workspace }}/tests:/tests - prod + docker run \ + --entrypoint /bin/bash \ + -v ${{ github.workspace }}/tests:/tests \ + prod \ -c "conda run -n burn-severity-prod pytest -svv /tests" From 94cc67f5c226b72f2e98e1beee80efa4337eddf4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 23:09:36 +0000 Subject: [PATCH 121/175] test upload_rap --- tests/unit/util/test_cloud_static_io.py | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index 0e95d95..e37dfa1 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -93,3 +93,53 @@ def test_upload_cogs( ), ] ) + + +@patch("tempfile.TemporaryDirectory") +@patch("os.path.join") +@patch("rasterio.open") +@patch("rioxarray.raster_array.RasterArray.to_raster") +@patch("src.util.cloud_static_io.CloudStaticIOClient.upload") +@patch.object(CloudStaticIOClient, "__init__", return_value=None) +def test_upload_rap_estimates( + mock_init, + mock_upload, + mock_to_raster, + mock_rio_open, + mock_os_join, + mock_temp_dir, + test_3d_xarray, +): + # Create an instance of CloudStaticIOClient + client = CloudStaticIOClient() + + # Mock the logger + client.logger = MagicMock() + + # Define the arguments for upload_rap_estimates + fire_event_name = "test_event" + affiliation = "test_affiliation" + + # Give xarray the `band` band, with some band names as values in `band` + test_3d_xarray["band"] = ["tree", "shrub"] + + # Call upload_rap_estimates + client.upload_rap_estimates( + test_3d_xarray, + fire_event_name, + affiliation, + ) + + mock_init.assert_called_once_with() + mock_upload.assert_has_calls( + [ + call( + source_local_path=ANY, + remote_path=f"public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif", + ), + call( + source_local_path=ANY, + remote_path=f"public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif", + ), + ] + ) From e1fa30cbfe2f179f8341ec4ff97e264aa7c9ded4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 23:10:09 +0000 Subject: [PATCH 122/175] try adding pytest ini using the docker cp command and committing it --- .deployment/prod_environment.yml | 1 + .devcontainer/dev_environment.yml | 1 + .github/workflows/pytest.yml | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.deployment/prod_environment.yml b/.deployment/prod_environment.yml index ddbe1c2..9445561 100644 --- a/.deployment/prod_environment.yml +++ b/.deployment/prod_environment.yml @@ -24,6 +24,7 @@ dependencies: - sentry-sdk[fastapi] - uvicorn - pytest + - pyarrow - pip - pip: - titiler.core diff --git a/.devcontainer/dev_environment.yml b/.devcontainer/dev_environment.yml index e8c1f5a..b8861a2 100644 --- a/.devcontainer/dev_environment.yml +++ b/.devcontainer/dev_environment.yml @@ -30,6 +30,7 @@ dependencies: - uvicorn - pip - pytest + - pyarrow - smart_open - sentry-sdk[fastapi] - pip: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index c1e8755..86a2402 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -16,10 +16,16 @@ jobs: - name: Build Docker image run: docker build -t prod -f .deployment/prod.Dockerfile . + - name: Create Docker container with pytest.ini + run: | + docker create --name temp_container prod + docker cp pytest.ini temp_container:/ + docker commit temp_container prod:with_pytest_ini + - name: Run tests (override entrypoint to run tests rather than deploy) run: | docker run \ --entrypoint /bin/bash \ -v ${{ github.workspace }}/tests:/tests \ - prod \ + prod:with_pytest_ini \ -c "conda run -n burn-severity-prod pytest -svv /tests" From b392868a6ccbd24713550f3f5032ed155ee7da1c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 23:22:40 +0000 Subject: [PATCH 123/175] actions v4 to avoid node warnings --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 86a2402..4351c2a 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Build Docker image run: docker build -t prod -f .deployment/prod.Dockerfile . From 873166e8113ee25fd0c4d5d341d2b28eb1d2eb6c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 23:32:40 +0000 Subject: [PATCH 124/175] test_update_manifest --- tests/unit/util/test_cloud_static_io.py | 57 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index e37dfa1..c50fdc8 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -50,7 +50,7 @@ def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): @patch("rioxarray.raster_array.RasterArray.to_raster") @patch("rasterio.open") -@patch("src.util.cloud_static_io.CloudStaticIOClient.upload") +@patch.object(CloudStaticIOClient, "upload", return_value=None) @patch.object(CloudStaticIOClient, "__init__", return_value=None) def test_upload_cogs( mock_init, mock_upload, mock_rio_open, mock_to_raster, test_3d_xarray @@ -99,7 +99,7 @@ def test_upload_cogs( @patch("os.path.join") @patch("rasterio.open") @patch("rioxarray.raster_array.RasterArray.to_raster") -@patch("src.util.cloud_static_io.CloudStaticIOClient.upload") +@patch.object(CloudStaticIOClient, "upload", return_value=None) @patch.object(CloudStaticIOClient, "__init__", return_value=None) def test_upload_rap_estimates( mock_init, @@ -143,3 +143,56 @@ def test_upload_rap_estimates( ), ] ) + + +@patch("tempfile.TemporaryDirectory") +@patch("os.path.join") +@patch("builtins.open", new_callable=MagicMock) +@patch("json.dump") +@patch.object(CloudStaticIOClient, "upload", return_value=None) +@patch.object(CloudStaticIOClient, "__init__", return_value=None) +@patch.object(CloudStaticIOClient, "get_manifest") +def test_update_manifest( + mock_get_manifest, + mock_init, + mock_upload, + mock_json_dump, + mock_open, + mock_os_join, + mock_temp_dir, +): + # Create an instance of CloudStaticIOClient + client = CloudStaticIOClient() + + # Mock the logger + client.logger = MagicMock() + + # Define the arguments for update_manifest + fire_event_name = "test_event" + bounds = "test_bounds" + prefire_date_range = "test_prefire_range" + postfire_date_range = "test_postfire_range" + affiliation = "test_affiliation" + derive_boundary = "test_boundary" + + # Mock the return value of get_manifest + mock_get_manifest.return_value = {} + + # Call update_manifest + client.update_manifest( + fire_event_name, + bounds, + prefire_date_range, + postfire_date_range, + affiliation, + derive_boundary, + ) + + # Assert that __init__, get_manifest, json.dump and upload were called with the correct arguments + mock_init.assert_called_once_with() + mock_get_manifest.assert_called_once_with() + mock_json_dump.assert_called_once() + mock_upload.assert_called_once_with( + source_local_path=mock_os_join.return_value, + remote_path="manifest.json", + ) From c164a46e01877a19e46c1322c9ec1f5182df569c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 14 Feb 2024 23:37:00 +0000 Subject: [PATCH 125/175] use test fixture for upload fire event --- tests/unit/util/test_cloud_static_io.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index c50fdc8..6a94f6d 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -6,7 +6,9 @@ @patch("src.util.cloud_static_io.CloudStaticIOClient.update_manifest") @patch("src.util.cloud_static_io.CloudStaticIOClient.upload_cogs") @patch.object(CloudStaticIOClient, "__init__", return_value=None) -def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): +def test_upload_fire_event( + mock_init, mock_upload_cogs, mock_update_manifest, test_3d_xarray +): # Create an instance of CloudStaticIOClient client = CloudStaticIOClient() @@ -14,7 +16,10 @@ def test_upload_fire_event(mock_init, mock_upload_cogs, mock_update_manifest): client.logger = MagicMock() # Define the arguments for upload_fire_event - metrics_stack = MagicMock() + metrics_stack = test_3d_xarray + metrics_stack = metrics_stack.rename({"band": "burn_metric"}) + metrics_stack["burn_metric"] = ["rbr", "dnbr"] + fire_event_name = "test_event" prefire_date_range = "test_prefire_range" postfire_date_range = "test_postfire_range" From a18bd7f7ca9e6e422c4d0d34ccd60c063b176a84 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 00:00:57 +0000 Subject: [PATCH 126/175] complicated mocking for get_derived_products --- tests/unit/util/test_cloud_static_io.py | 94 ++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index 6a94f6d..cf910c3 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -1,6 +1,7 @@ import pytest -from src.util.cloud_static_io import CloudStaticIOClient -from unittest.mock import patch, MagicMock, ANY, call +from src.util.cloud_static_io import CloudStaticIOClient, BUCKET_HTTPS_PREFIX +from unittest.mock import patch, MagicMock, ANY, call, mock_open +from boto3.session import Session @patch("src.util.cloud_static_io.CloudStaticIOClient.update_manifest") @@ -152,7 +153,7 @@ def test_upload_rap_estimates( @patch("tempfile.TemporaryDirectory") @patch("os.path.join") -@patch("builtins.open", new_callable=MagicMock) +@patch("builtins.open", new_callable=mock_open) @patch("json.dump") @patch.object(CloudStaticIOClient, "upload", return_value=None) @patch.object(CloudStaticIOClient, "__init__", return_value=None) @@ -201,3 +202,90 @@ def test_update_manifest( source_local_path=mock_os_join.return_value, remote_path="manifest.json", ) + + +@patch("json.load") +@patch.object(CloudStaticIOClient, "download", return_value=None) +@patch("builtins.open", new_callable=mock_open) +@patch.object(CloudStaticIOClient, "__init__", return_value=None) +def test_get_manifest(mock_init, mock_open, mock_download, mock_json_load): + # Create an instance of CloudStaticIOClient + client = CloudStaticIOClient() + + # Mock the logger + client.logger = MagicMock() + + # Define the return value for json.load + mock_json_load.return_value = {"key": "value"} + + # Call get_manifest + result = client.get_manifest() + + # Assert that __init__, download and json.load were called with the correct arguments + mock_init.assert_called_once_with() + mock_download.assert_called_once() + mock_json_load.assert_called_once() + + # Assert that the result is as expected + assert result == {"key": "value"} + + +@patch.object(CloudStaticIOClient, "__init__", return_value=None) +def test_get_derived_products(mock_init): + # Define test affiliation and fire event name + affiliation = "test_affiliation" + fire_event_name = "test_event" + + # Define test pages + test_pages = [ + { + "Contents": [ + {"Key": f"public/{affiliation}/{fire_event_name}/test_file1.tif"} + ] + }, + { + "Contents": [ + {"Key": f"public/{affiliation}/{fire_event_name}/test_file2.tif"} + ] + }, + ] + + # Define expected derived products + expected_derived_products = { + "test_file1.tif": BUCKET_HTTPS_PREFIX + + "/public/test_affiliation/test_event/test_file1.tif", + "test_file2.tif": BUCKET_HTTPS_PREFIX + + "/public/test_affiliation/test_event/test_file2.tif", + } + + # Mock s3 client and paginator + mock_s3_client = MagicMock() + mock_paginator = MagicMock() + + # Mock boto_session and its client method + mock_boto_session = MagicMock() + mock_boto_session.client.return_value = mock_s3_client + mock_paginator.paginate.return_value = test_pages + mock_s3_client.get_paginator.return_value = mock_paginator + + # Create an instance of CloudStaticIOClient, and give it an s3 client + client = CloudStaticIOClient() + client.bucket_name = "bucket_name" + client.boto_session = mock_boto_session + + # Call get_derived_products + derived_products = client.get_derived_products(affiliation, fire_event_name) + + # Assert that boto_session.client was called with "s3" + mock_boto_session.client.assert_called_once_with("s3") + + # Assert that get_paginator was called with "list_objects_v2" + mock_s3_client.get_paginator.assert_called_once_with("list_objects_v2") + + # Assert that paginate was called with the correct Bucket and Prefix + mock_paginator.paginate.assert_called_once_with( + Bucket=client.bucket_name, Prefix=f"public/{affiliation}/{fire_event_name}/" + ) + + # Assert that the returned derived products match the expected derived products + assert derived_products == expected_derived_products From f11de3353ab18ea992829e29f258bb2819d7fbd6 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 20:36:41 +0000 Subject: [PATCH 127/175] initial sphinx docstrings --- src/routers/dependencies.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index a74ca5f..06e684f 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -9,6 +9,12 @@ def get_cloud_logger(): + """ + Get a logger from the Google Cloud Logging service + + :return: A logger from the Google Cloud Logging service + :rtype: Logger + """ logging_client = logging.Client(project="dse-nps") log_name = "burn-backend" logger = logging_client.logger(log_name) @@ -17,6 +23,16 @@ def get_cloud_logger(): def get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger)): + """ + Get an instance of CloudStaticIOClient. + + Args: + logger (Logger): The logger instance to use for logging. + + Returns: + CloudStaticIOClient: An instance of CloudStaticIOClient. + + """ logger.log_text("Creating CloudStaticIOClient") return CloudStaticIOClient("burn-severity-backend", "s3") @@ -25,12 +41,31 @@ def get_manifest( cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), logger: Logger = Depends(get_cloud_logger), ): + """ + Get the manifest from the cloud static IO client. + + Parameters: + - cloud_static_io_client: The cloud static IO client instance, used to download the manifest from clouds storage. + - logger: The logger instance. + + Returns: + - The manifest obtained from the cloud static IO client. + """ logger.log_text("Getting manifest") manifest = cloud_static_io_client.get_manifest() return manifest def init_sentry(logger: Logger = Depends(get_cloud_logger)): + """ + Initializes the Sentry client. + + Args: + logger (Logger): The logger object used for logging. + + Returns: + None + """ logger.log_text("Initializing Sentry client") ## TODO: Move to sentry to environment variable if we keep sentry From e265593242662de7332d5f3e8608e9b152976d7c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 20:41:22 +0000 Subject: [PATCH 128/175] add sphinx as dep in dev --- .devcontainer/dev_environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/dev_environment.yml b/.devcontainer/dev_environment.yml index b8861a2..fdfd474 100644 --- a/.devcontainer/dev_environment.yml +++ b/.devcontainer/dev_environment.yml @@ -33,6 +33,7 @@ dependencies: - pyarrow - smart_open - sentry-sdk[fastapi] + - sphinx - pip: - titiler.core - python-multipart From 18a9fe48d979ce292c7813775c4a9d5c895b477a Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 21:54:41 +0000 Subject: [PATCH 129/175] try out sphinx --- .sphinx/Makefile | 20 + .sphinx/doctrees/_autosummary/src.app.doctree | Bin 0 -> 2681 bytes .sphinx/doctrees/_autosummary/src.doctree | Bin 0 -> 6811 bytes .../src.lib.burn_severity.doctree | Bin 0 -> 15321 bytes .sphinx/doctrees/_autosummary/src.lib.doctree | Bin 0 -> 8141 bytes .../_autosummary/src.lib.query_rap.doctree | Bin 0 -> 4658 bytes .../src.lib.query_sentinel.doctree | Bin 0 -> 4711 bytes .../_autosummary/src.lib.query_soil.doctree | Bin 0 -> 8519 bytes .../src.lib.titiler_algorithms.doctree | Bin 0 -> 7352 bytes .../src.routers.check.connectivity.doctree | Bin 0 -> 4815 bytes .../src.routers.check.dns.doctree | Bin 0 -> 4608 bytes .../_autosummary/src.routers.check.doctree | Bin 0 -> 7611 bytes .../src.routers.check.health.doctree | Bin 0 -> 4635 bytes .../src.routers.check.sentry_error.doctree | Bin 0 -> 4782 bytes .../src.routers.dependencies.doctree | Bin 0 -> 9506 bytes .../doctrees/_autosummary/src.routers.doctree | Bin 0 -> 5599 bytes .../src.util.cloud_static_io.doctree | Bin 0 -> 4765 bytes .../doctrees/_autosummary/src.util.doctree | Bin 0 -> 7355 bytes .../_autosummary/src.util.gcp_secrets.doctree | Bin 0 -> 5459 bytes .../src.util.ingest_burn_zip.doctree | Bin 0 -> 5644 bytes .../src.util.raster_to_poly.doctree | Bin 0 -> 4743 bytes .sphinx/doctrees/environment.pickle | Bin 0 -> 183596 bytes .sphinx/doctrees/index.doctree | Bin 0 -> 22692 bytes .sphinx/html/.buildinfo | 4 + .sphinx/html/_autosummary/src.app.html | 115 +++ .sphinx/html/_autosummary/src.html | 130 +++ .../_autosummary/src.lib.burn_severity.html | 147 +++ .sphinx/html/_autosummary/src.lib.html | 142 +++ .../html/_autosummary/src.lib.query_rap.html | 132 +++ .../_autosummary/src.lib.query_sentinel.html | 132 +++ .../html/_autosummary/src.lib.query_soil.html | 144 +++ .../src.lib.titiler_algorithms.html | 143 +++ .../src.routers.check.connectivity.html | 137 +++ .../_autosummary/src.routers.check.dns.html | 137 +++ .../src.routers.check.health.html | 137 +++ .../html/_autosummary/src.routers.check.html | 144 +++ .../src.routers.check.sentry_error.html | 137 +++ .../src.routers.dependencies.html | 120 +++ .sphinx/html/_autosummary/src.routers.html | 111 +++ .../src.util.cloud_static_io.html | 131 +++ .../_autosummary/src.util.gcp_secrets.html | 134 +++ .sphinx/html/_autosummary/src.util.html | 138 +++ .../src.util.ingest_burn_zip.html | 134 +++ .../_autosummary/src.util.raster_to_poly.html | 129 +++ .../_sources/_autosummary/src.app.rst.txt | 23 + .../src.lib.burn_severity.rst.txt | 34 + .../_autosummary/src.lib.query_rap.rst.txt | 29 + .../src.lib.query_sentinel.rst.txt | 29 + .../_autosummary/src.lib.query_soil.rst.txt | 33 + .../_sources/_autosummary/src.lib.rst.txt | 35 + .../src.lib.titiler_algorithms.rst.txt | 36 + .../src.routers.check.connectivity.rst.txt | 29 + .../src.routers.check.dns.rst.txt | 29 + .../src.routers.check.health.rst.txt | 29 + .../_autosummary/src.routers.check.rst.txt | 34 + .../src.routers.check.sentry_error.rst.txt | 29 + .../src.routers.dependencies.rst.txt | 33 + .../_sources/_autosummary/src.routers.rst.txt | 32 + .../html/_sources/_autosummary/src.rst.txt | 34 + .../src.util.cloud_static_io.rst.txt | 29 + .../_autosummary/src.util.gcp_secrets.rst.txt | 30 + .../src.util.ingest_burn_zip.rst.txt | 30 + .../src.util.raster_to_poly.rst.txt | 29 + .../_sources/_autosummary/src.util.rst.txt | 34 + .sphinx/html/_sources/index.rst.txt | 28 + .sphinx/html/_static/alabaster.css | 708 ++++++++++++++ .sphinx/html/_static/basic.css | 925 ++++++++++++++++++ .sphinx/html/_static/custom.css | 1 + .sphinx/html/_static/doctools.js | 156 +++ .sphinx/html/_static/documentation_options.js | 13 + .sphinx/html/_static/file.png | Bin 0 -> 286 bytes .sphinx/html/_static/language_data.js | 199 ++++ .sphinx/html/_static/minus.png | Bin 0 -> 90 bytes .sphinx/html/_static/plus.png | Bin 0 -> 90 bytes .sphinx/html/_static/pygments.css | 84 ++ .sphinx/html/_static/searchtools.js | 574 +++++++++++ .sphinx/html/_static/sphinx_highlight.js | 154 +++ .sphinx/html/genindex.html | 300 ++++++ .sphinx/html/index.html | 171 ++++ .sphinx/html/objects.inv | Bin 0 -> 732 bytes .sphinx/html/py-modindex.html | 209 ++++ .sphinx/html/search.html | 118 +++ .sphinx/html/searchindex.js | 1 + .sphinx/make.bat | 35 + .sphinx/source/_autosummary/src.app.rst | 23 + .../_autosummary/src.lib.burn_severity.rst | 34 + .../source/_autosummary/src.lib.query_rap.rst | 29 + .../_autosummary/src.lib.query_sentinel.rst | 29 + .../_autosummary/src.lib.query_soil.rst | 33 + .sphinx/source/_autosummary/src.lib.rst | 35 + .../src.lib.titiler_algorithms.rst | 36 + .../src.routers.check.connectivity.rst | 29 + .../_autosummary/src.routers.check.dns.rst | 29 + .../_autosummary/src.routers.check.health.rst | 29 + .../source/_autosummary/src.routers.check.rst | 34 + .../src.routers.check.sentry_error.rst | 29 + .../_autosummary/src.routers.dependencies.rst | 33 + .sphinx/source/_autosummary/src.routers.rst | 32 + .sphinx/source/_autosummary/src.rst | 34 + .../_autosummary/src.util.cloud_static_io.rst | 29 + .../_autosummary/src.util.gcp_secrets.rst | 30 + .../_autosummary/src.util.ingest_burn_zip.rst | 30 + .../_autosummary/src.util.raster_to_poly.rst | 29 + .sphinx/source/_autosummary/src.util.rst | 34 + .sphinx/source/conf.py | 39 + .sphinx/source/index.rst | 28 + 106 files changed, 7681 insertions(+) create mode 100644 .sphinx/Makefile create mode 100644 .sphinx/doctrees/_autosummary/src.app.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.lib.burn_severity.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.lib.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.lib.query_rap.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.lib.query_sentinel.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.lib.query_soil.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.lib.titiler_algorithms.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.check.connectivity.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.check.dns.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.check.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.check.health.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.check.sentry_error.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.dependencies.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.routers.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.util.cloud_static_io.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.util.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.util.gcp_secrets.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.util.ingest_burn_zip.doctree create mode 100644 .sphinx/doctrees/_autosummary/src.util.raster_to_poly.doctree create mode 100644 .sphinx/doctrees/environment.pickle create mode 100644 .sphinx/doctrees/index.doctree create mode 100644 .sphinx/html/.buildinfo create mode 100644 .sphinx/html/_autosummary/src.app.html create mode 100644 .sphinx/html/_autosummary/src.html create mode 100644 .sphinx/html/_autosummary/src.lib.burn_severity.html create mode 100644 .sphinx/html/_autosummary/src.lib.html create mode 100644 .sphinx/html/_autosummary/src.lib.query_rap.html create mode 100644 .sphinx/html/_autosummary/src.lib.query_sentinel.html create mode 100644 .sphinx/html/_autosummary/src.lib.query_soil.html create mode 100644 .sphinx/html/_autosummary/src.lib.titiler_algorithms.html create mode 100644 .sphinx/html/_autosummary/src.routers.check.connectivity.html create mode 100644 .sphinx/html/_autosummary/src.routers.check.dns.html create mode 100644 .sphinx/html/_autosummary/src.routers.check.health.html create mode 100644 .sphinx/html/_autosummary/src.routers.check.html create mode 100644 .sphinx/html/_autosummary/src.routers.check.sentry_error.html create mode 100644 .sphinx/html/_autosummary/src.routers.dependencies.html create mode 100644 .sphinx/html/_autosummary/src.routers.html create mode 100644 .sphinx/html/_autosummary/src.util.cloud_static_io.html create mode 100644 .sphinx/html/_autosummary/src.util.gcp_secrets.html create mode 100644 .sphinx/html/_autosummary/src.util.html create mode 100644 .sphinx/html/_autosummary/src.util.ingest_burn_zip.html create mode 100644 .sphinx/html/_autosummary/src.util.raster_to_poly.html create mode 100644 .sphinx/html/_sources/_autosummary/src.app.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.lib.burn_severity.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.lib.query_rap.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.lib.query_sentinel.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.lib.query_soil.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.lib.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.lib.titiler_algorithms.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.check.connectivity.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.check.dns.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.check.health.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.check.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.check.sentry_error.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.dependencies.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.routers.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.util.cloud_static_io.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.util.ingest_burn_zip.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.util.raster_to_poly.rst.txt create mode 100644 .sphinx/html/_sources/_autosummary/src.util.rst.txt create mode 100644 .sphinx/html/_sources/index.rst.txt create mode 100644 .sphinx/html/_static/alabaster.css create mode 100644 .sphinx/html/_static/basic.css create mode 100644 .sphinx/html/_static/custom.css create mode 100644 .sphinx/html/_static/doctools.js create mode 100644 .sphinx/html/_static/documentation_options.js create mode 100644 .sphinx/html/_static/file.png create mode 100644 .sphinx/html/_static/language_data.js create mode 100644 .sphinx/html/_static/minus.png create mode 100644 .sphinx/html/_static/plus.png create mode 100644 .sphinx/html/_static/pygments.css create mode 100644 .sphinx/html/_static/searchtools.js create mode 100644 .sphinx/html/_static/sphinx_highlight.js create mode 100644 .sphinx/html/genindex.html create mode 100644 .sphinx/html/index.html create mode 100644 .sphinx/html/objects.inv create mode 100644 .sphinx/html/py-modindex.html create mode 100644 .sphinx/html/search.html create mode 100644 .sphinx/html/searchindex.js create mode 100644 .sphinx/make.bat create mode 100644 .sphinx/source/_autosummary/src.app.rst create mode 100644 .sphinx/source/_autosummary/src.lib.burn_severity.rst create mode 100644 .sphinx/source/_autosummary/src.lib.query_rap.rst create mode 100644 .sphinx/source/_autosummary/src.lib.query_sentinel.rst create mode 100644 .sphinx/source/_autosummary/src.lib.query_soil.rst create mode 100644 .sphinx/source/_autosummary/src.lib.rst create mode 100644 .sphinx/source/_autosummary/src.lib.titiler_algorithms.rst create mode 100644 .sphinx/source/_autosummary/src.routers.check.connectivity.rst create mode 100644 .sphinx/source/_autosummary/src.routers.check.dns.rst create mode 100644 .sphinx/source/_autosummary/src.routers.check.health.rst create mode 100644 .sphinx/source/_autosummary/src.routers.check.rst create mode 100644 .sphinx/source/_autosummary/src.routers.check.sentry_error.rst create mode 100644 .sphinx/source/_autosummary/src.routers.dependencies.rst create mode 100644 .sphinx/source/_autosummary/src.routers.rst create mode 100644 .sphinx/source/_autosummary/src.rst create mode 100644 .sphinx/source/_autosummary/src.util.cloud_static_io.rst create mode 100644 .sphinx/source/_autosummary/src.util.gcp_secrets.rst create mode 100644 .sphinx/source/_autosummary/src.util.ingest_burn_zip.rst create mode 100644 .sphinx/source/_autosummary/src.util.raster_to_poly.rst create mode 100644 .sphinx/source/_autosummary/src.util.rst create mode 100644 .sphinx/source/conf.py create mode 100644 .sphinx/source/index.rst diff --git a/.sphinx/Makefile b/.sphinx/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/.sphinx/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/.sphinx/doctrees/_autosummary/src.app.doctree b/.sphinx/doctrees/_autosummary/src.app.doctree new file mode 100644 index 0000000000000000000000000000000000000000..58b8af42d4a6173c742f897aa1d266da5892d250 GIT binary patch literal 2681 zcmZWrOK%%D5O!=!vSeGf^C$`=P7?%aoHmw?_(Gybtj}9t?u_M%bd`vz0Nzl%X{zR z8SjUV6Hc;?SD|N3Hlk%2lL|C;e`m|&di~;4tfT0b6h2+#C!tT;Ao)({u40>nT`3fc z6F;S4K%FyU>K$@QIqZnsrg(dqq}D=+=Z>O9{j`_+a($T6Y+=|e>G;rdbe>*Ce#9U0 z1OABb@YC?)_)?pNEosKaqZ-3;9mbeY@3i$rLCw{8mEFi#1pfG9+fVsc=m?dwQm7%AW#;XZSzI{{{XpQ_#rUh$_^xuv=1LVmK;v?j`%>NdsUQ){^r^z`GL{spsGG z^L6AC{uubM*4G(-Q$c;6YQP%LYxHRoM6xr0ot+v1{;E!gww9W^&4sou{8hN`3{_Ur zL<~|IJvYqOb?G?msEij|VbJKX97mu0-3i0Yn^bku*7%^2)U^(KmN_TX9MnB&xi~Xg zIi~U$x_?mVfm}=GuJ*38H-B=qM@F+-g;5r>?DEsho?4*Ru30xt_32dkqu5~o9_ zGm==6-okMw)#oaPyECnw(vEGsytoq%Q%fjckmXF9B0@5>sAowsU8GL4F!`{SFv%(j zxfG6*hO6+ny09YeBpeu4YU7Ax9~j`<4g*eQd+dZ0wiH0kQRi)=)CrxOP5m71hL_YR zpau=YXaPwd@MkO~6+efeP{4>VmFXN>G4#Ego&S7p1)d+>d`Xxw+7M1vE*Xf~O`&EE z46Tvgu+pil#Bh4+%`FpLg<)-U9YAyS(ajgPZieHO0ElKe@R3L;hC@l!+|xM&+&RwM z7+m3=VVO2L$yo`Sz)k9|N$6%;&5$GD5IKm-W?~t6Hw;rxleH*p3vlZ&_eDus{sGlW zAV58%s7wZrDoMrcRQt84w9Mz4EQ`daW40`{Wn}YaMUl*r55kI?pGPDYmQH0#WEEwv z=?nDO0g|!kgsRD4^l+s{W-T0`^VfB63+ULPSHZS zB4UWa3Qr5s%nF6La~5AyCxop^s>F~%MF)1#>^fVx+m>m!jm zqbq1nGfoi?3ST!HP_7`v?S(JlBJIg)`K^Y)HQ1vwp=RJfGZpTpIbr?umYygQd8MXx zE2bs4h~P{yWjXry3L$XD9GpDJptT^Zph6O?^fYCwuuoP?8Dy2SdZt~69i~2{Wf}JB zS%T&T6|tKHn!^!C&75I2VgH;UC{W8`(PMt+X*GVtw7Vg#*C)iVHfs;v)I!+2koS_X ze&+A^TO0aG;I2wU_%ZT#*Oos`3-B$L>>GRubn5`x$=!TMtQXE3`oEpi5f)o8eoout zU+n4cHug03;uu>Fw0sTd)09&4CwkT{P%N}N&7$;E9}YJeQ>Ad23GgmqfV65!8VrqI zjn$^4)&qiISTWVIC1Is{|CX@T?%%N3Z@gBQ?%uGE0dHK|C=YJiTD`$u#n{TUPeUn& z{Y*=V>LLg1t=_wp{0l1ZDgK5Q#77pI8J|TKq^lja)9Z$KzZF-!*%vC4K2JQIC&~=+ OG+c>F7VM#wgJM literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.doctree b/.sphinx/doctrees/_autosummary/src.doctree new file mode 100644 index 0000000000000000000000000000000000000000..da483f2db35833058e10c23934325632695773de GIT binary patch literal 6811 zcmb_h-EUk+6?bg!uD$+FlcgYc9_L^ z>T<1Spy3KX@KPS>n_8`sy7Yr=rx@SJxGViQ8VnaTQ5TJydP^*-n)GGBD_v?XOI=Of z=$(I?_sOgk-qF^2Jz=mVwb)RL&R}t3PSuMwwbXpzM_jk^Hd9R|W$L$c314XKbH10+ zIvZ}lvJ66e*CH0$uLUnphK)9J4^rMCom@2}>r$5%YvQ6<7MH}F*i<{ahjDt4CCuf! zJ2rN^b{V@4%VnJ9VaU>>U3j&VW?1y)>$7=V%&3|ldAwio+y+H_28$HW!Tsm)dlkPg z;`b$5FnmsZv&=0upD;hwYAKApJm6otLP1ka+uAk}thc0Qxp-M@4_Dg~mk|>Lww~tg z)ORb^RpU)FRc2#&LEAaVK#^-g(+IhfI$BhFgOL%nw zC%Hdw^ws)}Z@&Ba+wXqicKKJ6xz^B}di~LMWbf72_Vz0~bq!mDAgK#QA^?Fqm2Ryz zDlG>Njl%cUCVptMS(22Wf!{7a(-=TZv$9Ev(rlJ+uZ_=E#02{#yBL{N_G_3z4FvNM zW4VAKhrTC;zTTwpg$Q2{I~3XcE)#U(4kP0S6MU;W0VQ-h_KqI9(77}|d}v3Fkp18o z;B*nG&blc}L}j45w3i(1#qHkSJ4K+c>=z%`-nn6dxrGE0xI4YI0^ESIq*{Y;vSkJvrLO$~CX1Hbw9k*n$sGuO`R5 z4mVKkv|1l8EE)xw`a?t-WfW2vjL-eeI6n{c> zp?Q8ZEqJ8-9+Z9vWABfF{N7l5QUSIGFN(jM($Ms!-^FU~U^U01*0&}vZw&S*Di-$m z7{dPN<#cKk<)hjb<-aU-#XqXZhH3@b|E%uDC_1;qZ%zT~zfTJn4k8O`@w+O9d$VU| zW=;=d@dxpd__!thXi-WIJZ+SIGLF(us&SdV)j(nB8~Ky`258h*kG z+`IKi=B9h{7}&l%f>OG|Szy!G!%)3BRev1MA5d5=o)-wuG;0OSA5!-dg8BXl@tXwZ zUt`?kfO+W0(?+s3o{S=yWYq{w-+w~#?`ecZ@=wf!qvzcCRSg%3-uu?@lOp-gk7RDT zCk2wHD}5i=4&3x7E2(*UPG~$*ab*(F=Ho|*I$wDJT?yi-Yo9G_JWeh~JL99PN@}qm z27Bi13=$zpdB)RQ+??GvF}8SOi*MUk>kYM5G3-cOg-b$W@L&NvA(ttyQVJ)%ESLSr z4RVin{GfPUpBukn(`!zv2gP*NP|rS~U7kVe`~zJko%zUPzSUcuotgQ8ZuQoRzw5={ z4fB_)g^=|$i1%=+f zNehGigTD`ieu$PWv2VE;UeXh`$x4=jP}9T&b2bCb+aG+#Xul;JE+SeJO*W)(xb>@ppqmD6Ev2rK?hb2&}38BZ)R3%Zpn4L}Bq0%R878}E zB|P9p;vD)?I3ZlqYd2oq*rs2;8h{3Kk5SHMPO}KH?>r%(GIdrfME< z=Utdvr3WHCo-r?HAtZ|kyvxZQJRw*82+?tP)^Hbw6(KwGWad53E#M_(H47#pnC$p@e;69`cN zpR`&DSk%o~mm}gH+OwcC)iWt~tx|Dv zh9WiV16V6w9wyEJdX$&dyn!!YSeq2`pOPhFrd7(pdAS;!-r&BK;2 zp}=6PY!eXOpv(etQ2qt*AA(wVbNxlNn()LSz5)5@nt*|aNq~2(ozg-*E2K=az1>}i zvz_Efia6Sd)9$WT=SkBn0Gf{EA+|?iI~E|{zWTY>5YN@LG_+1g;7x=UJ3FjZvbWuD zKzngSZLzd!W;#1EKWcs|>5w@;K3CC$Ll$MsjdLneU=ML246xhSSDc=WuNLxq*o!;* wEuNmNV2{IBr-^#*c^05{~a>!}%y5;OKK~lPKx!HOlxL?z%J_HTaMmbv&DG79^&H$?pc){2s{YUi{vN-v{u!KrBECfujhYmq)v%6U6dd z$G0PwJz5*MU@Xt-=Szbh^RZPCn-TvIU&?u0<#$1n(9l>AH3G*fP$(y#j67unp;qHu zu_aL$W_nCZOV4DMazqPX$QR~|kj=D`%}HT4BG(KI%Xf{i%ZfzoCZ?mRzdX5i<<$qT zz539#^nXmtt>(+g&%9Gzt~~VM%K3uUFF?5v9V}YA9t^x*cpS^w!kqyYdmQG<)Wp!x z6n-qzwG46^qr;$ZE1d7bDxH8Co)awA(wlID?&|gW!qn7r+zD&VL^m~IuCuUahBe3Q zMxrJ-qd`kG)3a+`KNL$eS#9;H%c|wGFXEzsXBdJG0TWm{RHIfeUS6!(>TmFb`lota zysT$lsb$1~vKsxEL11px=Dpfxt+Sw7RG)msq%9jrT&T9nk&4D_C|F3vQ9iVX&mBXn zm(9kib^S;{j4L&-5g3W^OPBThpkunu7nofm4b}n^h?%agXGo|&;1o8fC>=_w$`%XK zu7e~SFc*|vWlFpG5IuM0fSj$wQfZm_f@~Ih*gU9lf{}h}RNN_(hT#{`zhcfv>Ze_Y0=vVbb-T~xIx=AcggJqeti>Rn9iU>;GI>)#I z3TD2Nn|>z6T}>jBld?NaS{aiHr89#~tq$DLfB{MZRwrJZQ8B=Ha8GtmnCs9T87@^E|{fzv+YrU?w$ zaBRWjOH&jobr34G5si8JtdIu#rO_Nt57`8tD^5U%gNAQ!4J33V@HYlN7^P@;=NQDL zlQ29s~jrPhbJL0F>`GMhYeMiR&udLP9(r9aq;Cn5Lw{U_1hT|R) z*V=;8&OD;{<6Y+2j@L3a;oC)eKKZWDlUl*J>j)N@Zea!fU3HS0le0Vci1SeV2j!tS z4iSoIIo#cff;`}*6tIK1!XPaHuu^bWKP>(mvz>Bw`_N^6tnu+D^RS4#{)V-r9Tuhs#D4Bqs2QvS|o25ZX4jh zu&;1Bxovae(`L_dwD*ux&nv(tT zeZ_KirfDye*{JU^d-=iuLpwZBZZCqCJ(_N{KO6v;pXr550A1xt7tb{+(>u8IbgQq` z(KbEQ=6L*(11!*o#_bnKS@g+o4lfAk%SI0#x35&{a!-)ler^{Xl%aA1;|_w#cU7P= zad+sT+Im6>dKbYaxZM7fCZ{xlxbh4-3`FPK z@4oYoODa;;uMT@YpbdWaogR^Hi}qdrtWa>D8{`bP$X0WGU*1^l=m*$sKHa++(0Qur z5P;`;x1l&Si~SKEPK<)FRb~YrL*(|4#*l!v&U{jND+OSgq7J27@ZMm140BYS(#l}_PK-MUY!6Py)8Cske}@m;!;0tK zft&gj&fyW9Qqnwau%`}N*%yO70ni{wwu$y=Gk92IZ&sk2%)*o65@i7vmfFJ0~l!>T)d z1s@QR7gpX$9;M{77-bIlIK|PM^@m>abRiMG=K5YsC%KvZxkna88~vk;o#f&P3Uyi3Ox(Y^4giHveq@m4==BA|fy97@JKbDaUA;2~p(GMWsiFYLh}tc1E`_m#G$G!x7v_>vynLGJud7 ze5}gj0qgpKFkF-vL2$!zikohBJ1S?{rUQ8iQ$%6fX;hBdtP!;^e1;OtD1{I6GP8r0 zNeJFz#12x>W>ik6!92^SL_Z|3MWVU%$WrLw`fTq*g9U*f7#ts1T!!+TF(TDM0@?{J zz8JjHU_HN94Z^l?bs z#{Dp=z2qH0Za=UMn^8I=mZ37ERXK)~?IzfSL>yax$KQ`1HvKFqmbuJ(6sudr(Y2s>?Qf8>5+O#fBV&166&I?C!2}1k zVLiU(cum+q3Jj+RD#EDI*H4upU@=pBBh~(`$FP}BgV}K5Es($&l0LWPX$!s94TfA( z*FYU4L98NsHmPKUvARtSXxnmxc~_~1P)=w#f&ao3QKW|#)-XJsN~#QJ7DU0c1gawe z-ZoVXUW?_ao>pB|h-S>z%x)+>gbEVTK2DALy8Q-xy!{9K3FRa;Q$j3tQV+I6eh*3l z)pKlU=czI;$!kp^d}nU<{0?LVjc=SrSn{{_NXenj|%#AF0DE(`%G z$HqGTiKP7|)O0<*3~xnNI{Y4mr6IsoFPH&cPxG;?S((@|$FtnX2EO<$f~6^&usDG& J3N4~W{Xe(d8S(%C literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.lib.doctree b/.sphinx/doctrees/_autosummary/src.lib.doctree new file mode 100644 index 0000000000000000000000000000000000000000..308090f9f962e0d13675aa3cb6194cc7915b5e7f GIT binary patch literal 8141 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 literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.lib.query_rap.doctree b/.sphinx/doctrees/_autosummary/src.lib.query_rap.doctree new file mode 100644 index 0000000000000000000000000000000000000000..27e46f94fadb3d74cffa4f651741b0cb62b3e551 GIT binary patch literal 4658 zcmb_g|7#n`6?be)vgGf+i@B0Gbtu<3G?IFygyOoPQ0On_^g}7-XdU)OyCcomyE}U` zv$4cA&{6_%nQNh$LMf&6_x>0C7y1wNGqbzWT8?u)?hLXuZ{ECl^S*rEd;VSHuaEYY z{EyjI8RO}sN1`Yd5mnZ#MIy`-nri#TKK{LZX^%`>l1n9W8B%MOz!5SYN0O%Yxiwpk zUGrG=%JVHnL(N4xJI)%c$y(3t5o?=<<~pXeEwiU&*o*nF_gPNm^em7hvyL0gtG}cZ zr^N!#lIMA43*DL-fhO9rUDFO`s;bF?k2GaI zlsv3yX4*f`Q}1t8xNW~qDV+pKKP@a&NFuks#9jPseldFjn{0ZU>}WE~VR`x4f&e|@KElf$9N(+B|t9&xTj`nc1nlBcmK zurv~!sK-*|S@h_b_h<{titf zQp=i$H+UBv?Q!C7!1r$)A5QPUa2DCJZywGf>9*vqdCb;5MsKycbIo7a0s9YN)ZO&w zce%(he*vaHLY+S?B=4Jr^=*}?xbPnP@BewxyH$S;ZC-tuq(6WOcyqfXv+6+2qPF6J z4BT{4q`6x|;=@>^;{rU%Vwi_G9W0)%Gnw`ziI05)-~vukMdb_XyvNd8)%==oBRK>&> zl}w5;R4!Vhh87w~a=K(X6%UQD%-wx=%vP97*LVf9Hfy7z6z!H-Q&ekU9eT>$S(7-D zBGte^V9BpYoTFu#b(u|(>nzuHz_yW~W10fVw2&@aUA0r1BpDX##f1TGxxlhhE^`@U zwW&oIxJJ$Gq9ggj?2UxbsnE1WKD}c$U6-2XNiZ1+nOIP^6k4bZ?M336Uj zrv#T=v!H4eaaVbu>>LMXL()u09mMnnh2fS>hmm-;Y?xg-;jot`I#(4_!!#l~%*VKH z%i)--4UkJo0!kZ(526siZurdI}0MtpWazoGFye*xenFo``0%X4x1hK21J=+Fj0&*S+g0F zbexkhg~cPRmoZM5_aqI4jDm=|I*DssPdhNpP^2R~6Bgl7fUFwviDgZ*<}KD6!zF+UpM^Aa(jvHg9dj34 zRV)#ryDqa3Ilw1|_)ky)Ki+v{wlbOpt}G%SLvJv&85!aoy1TS6A26*mb=>cRta{m0 zGm-X$9QUoc>o~mv;0!cP;2sTktbyMB@cTc6J-6g2ToCUlbMfD;#3N0K3)u@Qdz|W# zzXPe?XF7>-SsSp5Eb_hLnmIsBCc!K{XAeMk?;_;N?5Js~$s}NW%wl{HzpDXt-o`Iw+K%k{F303It`vKS`P1*Cke#@3uxz0! z2*26upOC7ARn-c^twNV(U!#ni{}y+lt$J1)yU>jFQ3dz5G)-ZiqTu< z*|NLw)iodHX0skt{*{~P7K<8(mlqU=9Ti_;&N{^fyr&`vlOaaVYpm}t5NF)&Yi#Z6 zRxfGqntYT2sa@TpYvJG~Rt+0LbD{>f^iEx=aF6?^%lUvUnFdcII<7gb@F)8=>}K!5OYh_N z0e&Ch_rTo&ZjjtterQ%R!lgC$6A|Sx{o%ozPqb#gP&isJ&zfi2lAL{q9nG$J#_q$J zh*DGL1Ifdhji&XabfgdDo!PjI7k2qk;jCg8vhY6c(8sew*+Y0{*L!A1lR+L68H6GZ zR7UFte3_G0y}sG$|LXJaKL7mD^XgygjyszjXFmDbp*(){@$pG5>W>jy_|4oYiw_yz ztxa3ATN?$qc%=oLRCus3-T;{C2EVRv;P7K0 zqRDH9A_G^XxOz?QA;%TN8<(^J7-S?##%c>qc$`g+#o*%j(>WI(oV>X-x^sGuKkXm% zt9bOM0qH;O`;hd{`wx6b4pG`08BHUej)F1pq;ADezP=Sz+*>i%RFb&1J^OoEl-qN* z+gWk`b8&G3RJ^@rIh$D1%qBa?czW;acdy8HSd1~P_*bnPCX$! zwd`M}g^`|Q=t(Q?c(aZ%9cM8QxwbF;^c9h*y*6z`v_LnDnydGlDdM85LCIskJ@W@? zB1cqP^WmEp%tzhB#$SQ_{~x$Gy#;4l6ww{?ttz*ssa4f_Eqb;dJ-RRgE=+%B`|RI< zRCmIk-WD#W`~{T$05kqLSIe*F#*2DWao}C{pRah(+f9E7YhHZWPQQl|;AinpX4PSw zMYqKT8MNvAmgaU1kPl*!jtVR#^C=%aOhn3v-)P%)ni+gJV{T?C_lw9OG>jT5>1$(7dZ9 zNmWc7xeGEWMlinMP7N(I@a1&LbSfPhVVJw~ZkeqxmyVeQQy#WaNeVt@))duXeIwA% z-6@|ON|9>7BM|17B+fx+ri8R9a-HScp0RBd>xibnH!Y;A+=zhDX_90ZtQQ9cIOP(< zPC3kFjM1hRVc?jYn^l&5VfKbX=u~K06QABPn~uxiL=TDB_ldB8L8}iKy^=-O^1<*jZp#6iYa7^?k_3`A93N759YEg}PvM<_rlD}n1;Cl>;n5F{6vy|yo09>RQpkXj zG>R#BV$+!#0{6BHAJ=wi_+7vh=Vs=%xcL69r6sdf>^rleT7B>4#N1-DbTc~Saq|BW+LqhIqF$++i7|Opc!bIAUqo3ScClNgO9%le{RWPIJdo%%(nlq+8$*} zT*_Wj+2vFZ{TV3z9@9yT!`c8?RFNl5%{9s;AQVZtiA*_zV;7Y|9UNP z;2PN@LY@wh0~IjrIjB(iU`~%GE_q`?ifVMP&`XP)Nd`1R2~S`Gqu?6X=5`3J%YcI8 z!~x#q4r0C8yXC7I$r=v}K)W_8G=1h?vu3S;6VxwK5wAs#;?Q_FI0oYrXBS2xwKQJp z0Po1XU7uUCcSBmdZz0BR!|A|LH1{-yyyb+&_r(Tk{GyG&lxaJ$BVta;Ph2bZ9^R&Y z9xd&}JrGMeRYCackAM?W^|7j3;o+-5+w4oUk&EBpEU>Ge)*fRZWp-{X_bO-5fq`)$-hfDCP+}097^#4uqqVl4l=4>y z)C>Cw>J={_R6hQbLm@+p1E)}TL%co}`y^ZEX&C2`OX`sWrcUJ0xCAaoF7iS5zv~bj ArT_o{ literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.lib.query_soil.doctree b/.sphinx/doctrees/_autosummary/src.lib.query_soil.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6ed6c65be1f9610ed077760d0f215bd94f18a9ce GIT binary patch literal 8519 zcmcIqTW=gm6?S5e?QwifHWqA>jU%+Xj)=!gv=7LbXb}hjS>^#Ps}<|eThm=L)#JWx zcXjO1DiRk(icu{H(uh0DEARt&KwQKV54`cfA3#C^X~iNTgv0~isp_8T8PALzdu?g# zsp?a8uHUKZbNrRcceig$=pVI|M8XaBYs_(i(BVm@W}VPZeICf{adz{o*@J9dRb#f7 zglTN^Oih5o7Ov;SJjm{4YLUpL>m{{({Y=7b>4rgfxu}R~F>^0l7gbe}uJm~6k-C(` zcFl7ewKr29cXq5KbiItoK|S>f-zPB!h$alt!^}uG9WlrvGqIwoR#$j2sit$8>uSn# z1D@5>22*oP%GhnB61tLZldhAH9y7Mbk_40ZUJ00QzRfym)EhLIy⋘(WGidvKBqc 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 literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.connectivity.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.connectivity.doctree new file mode 100644 index 0000000000000000000000000000000000000000..73f24eaf25de4c89f857dc2b5d18fa721289906c GIT binary patch literal 4815 zcmcIo{c9t~8TQ$hWXa!7&J^x^brLS|kyz@L5Q=M#LZMJhMJXkO#Aa`_JJO83yR(^@ zwIz~+mU3_|lNNeYD5aGC-v6TXKj=TyXJ%K@TCVLLBnI7S-g)Qi{dnHz)o*Hl{`c0L z|1;|}(UNff0bqRN`3NQ8MpQ*B?{C%?5X?4D^#a-l>nLu$<&G(yJXNYd1vShMET zHIG%hT;EVM)Lf*K<*df)tZ`!ZSku%r*D7Z7Fh1rD}(ahG*?iq^aBEoa>Qw zE3t6#*L3J^bf^AHTim&x5!_H)wrQHd2W*8sWD9J^{Je7^0%)Sqgv3Fp!_wad) z&jTl2V3+`mJQ)w|k~oPEglr_b-QM=&y+ zSeJQE^00De)BIVU`fyQ&r?;z;(wErjCxs!4oyd)EbO%419?U+1wN|~gHZ=j4_}P`x9+&^^f>eRx8~-9r=J`gRwCmW{0RGvI(wZ@57n_XO52j0^I;A-c*uMa{Zp%O3=0d0Z4U~S5bN?`t?XPFXH&wIZ zzz6I<|K~xsU;Pzs^YX5peg`E0)Xh7YMTdeG^%)Oj(5ACnn)?-)-i<{%DA1P7ra_3+ z!R+d)l4%Z;_`ov?5VA~CMddm5QFDT|CGlWi__8s*@*X?%3~Ik*T>Czq_p3z#IaxA? zJGmAaa|`$27LHvgqQ%!;u$0@fxMX4MBlEVFBvmnSq%p{(7~nAl3#)0N0YgV~rZv`~ z5r(-t@19u;bLlusX(DmNt}ZaO^9wCJT%L- z?XqNCZf4Xl7 z(QwBS(ZRMgD+(Q(4!+Eh*9wMu%a4m;5Q~tM zrrik{_+tpi7x@M&dlO>y8OZddZ=iiwT;jkbqDO>0?IQ-pz_8_@Lgl>~J)XGWjRh&P z(e<-u7BQ3bXoM6V;R=j`e_WdTA+*i{3jPxZ==B}M`fG2N-8zCb?iYY|X%=XD?)qZ0 zRKN-H7omuLlOs7a+6%{Od{4ZIOChv0egOb@N3IKgVa?VRY0)V|jNST^funJ*$%ee) zgvIyM73BD76aOgFbYe%+oRXioRO}sew|*Wa?$EWACB+&;`1Q8okc{iuxLTogR-knD z70Srz*EkC_>$6Jh2C~*E>tn~)$4<3AB6ttrAO~mZU|00-YMo0) literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.dns.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.dns.doctree new file mode 100644 index 0000000000000000000000000000000000000000..510f2042eb92c1ea2316875ca51746949dad7893 GIT binary patch literal 4608 zcmcIoZEs^o70xEH9XpAWY?~EmH*HGQHmf!^MNpv-ttuh&>>we85a0P1`~`js-+9j5d+i%nf6)sPWoZHQ zSAWJQbdW`APi=9+X2x(HZN-jhd(#Vu71J!wJus_Lm~cDDhs<=C)-oKzrgHaN2?IrU zY55UT3POBuBrGnztp#~D8w{C$A^C`Oa?{dmOjA~DiTh$hJP<2l&wSj!Oyz~jn9uvY z!oU6E7=4fBI#qcbGkMj&_2xaPaE&)kmh+)lGL0|^_@w5q!jEps%#ZGMDjmYq$nPKq(e6#6%v#r@MkC^oQH1br& z>xX!in^v8^**^X3%MYJ?`JE@#UrXi&GjrxA|7$FdKK$tDxEA$C2rgV_?v;gy{O;AJ zt=XxKJY4Z+xqP#OA6F(G%H-hlC>@WvJbT#bR3+1xUsL)5vC?A{T2_nItT0<};(zGF zH7vOd11;=Hhujv!ZRdzgQlLgv_6s8m9GPzL^ZEt^6T>vPdf}kRw-uRQz94tVLWzIt zk~Xk`jU~%OZK3(mQFe8d4$qIin0j;n_|^(2P>J0A=!?_+(}UB}>8EF>51rEwP^cRj zPl7NRdlP^|-Ol&^D?3#%TTuvLWKnHL@fU}UsH*9~?X0T)wz#T*2kvlYIlHo^nOz;? z`j*$>gQD&`4Iux>?7VPqHZV zLv5eA=}RUPdu`f?PXSB|(ci2=$6>Y|I6IojG1u0-`HDcgGXE2x`u|eiCwJgNi%Pg> zIqS5W#b?EY@90-w)~7j=)q;C=DWuW9Jz zM}LOXJbl$pzkw1!WARRAm5|AzAL4=n+I)UXvs**V{U}Yw1p>19%=WR`n_pd5GVMtm z9l6F0mXk@YxO~iAvYcUU+26(I4#8(fJ`l%_pB$Dn=Fk=9A<-CKHRweIhE<)LuGfK) zl_ED>p92SYSs9kpMtopi*ODbFV#K?=Or~QTy`VM?ooc|x$&%?*Iy}HICwq5H*Uu#p zCvQrVHY!O$m&}^tS_9a?40onfZzR)11MmPNpRyrGStGWh#qW}3rglUFdLF*snlM?A9FZv*>nVp zro)EW;gb;l(oE;7Qfin+z=!!5=DGx5)CjOIXE7vgINs-U9ROT6OyC`lCw@xp0`QpY z(c$+GRfz8oZcch!%2axSB|*f&D0OMsm3e0Pg`>71PfKyY18RDY@)AZA1 zglobhVSp^M*WTk$g++31c|oX%Y~dp1DO*)hpuje88HF-%6^2bWOEqs4 zMviQod-M%P02!_dc|yAd2>Ci*PY6{pM2hZEWg&BbQv&%Pqw)QC`#samc;- zVc--Q@*QWVvM}EeT4(C0--lTBvMVjpq?gKZ-cA5x+IFhn$SPX?adB07gz3 za&5NNRifF%6X94y_#l5*38byrPnJovM2S!WP}{3fZ;Adh02F>dSOh-8>OtMMtUS(SmaDRyEusK6sI=(Wn5lYqP?W$Mo1VYXzL3evyiJ&4nlq z4PH+~#XU54a44jf#v>Zw9njPAi8Xg`NQ>tu#Mo^#9S{$rmn-Bg5*FXjH&El}ZTwQE zO=6;3q~s>9m3RwpM>mg#c1%yWk~LKj{vvTdW>p)jx)mO#3gpZ_LmN5&C2WC9eNuZW zfq(VOZrFF-u#awtoIOHZ2*r}|emYw49xd69>$dqYH=Fh3a<|IuESy1=*4}?N~76zw4cSF2B6}uE$4--Gi14`*J{Trhb O5EhrPMbsi6_Wli=r1Y@> literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.doctree new file mode 100644 index 0000000000000000000000000000000000000000..01c012086797857ad4f74cc5c7d74cfbdbb3cb86 GIT binary patch literal 7611 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 QbyPCW@|HSVq|Ms@0n|xL<^TWy literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.health.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.health.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5605497eebe7258520b44a0ed574612b6b3ddf8b GIT binary patch literal 4635 zcmcIo?`tHt8TQ%UUGMt0lQV%kCl2KrAI6-p_k-}_(mU+6#7N7Bshc(3bApf#+$di6@L^!|9>NB(`|&pUfd z?niISoW5tT--MLaB0nknRmjW~!+H@Y;QDj&oLgGwS9UIzBH(WechQ=sF_8} z)T2cgdfbNPPwasG3y^2G{Nr8WvC7{->1){i!(0WvoEtCdA>QBv_U~_L=dzc&lHdksPZe46rdhc0@(z=%L`=&Y#YTnrWv42abYVpBH&+& zq&Wub-e3SLml$@+VIdNXHWd#8%e(9>xWqSlZ^XIExS}=j={>z^320WN!DPfmY9QH? zph7u_L27xe#AXk?geKHUf~#1upyGY7RW2xr;6QH(nscFogg&Kk+_LU4aMWtp(7SXJ z!(WQ1Le8X(hz^S}%xzgHvmF8ZQj$W_hT}tOuLH2#iZgg8pjpUm-vCq=YSjI4SH}4M z@b+Xth2SD!B#ROXPS>} z-m!N;mPrN|CAgE(n+eIr1sPL#Ji>SxF9rREpdlAg5K-G5Va9UPfo_I88{wMpNE{(c z=_sBU*3?_Jh=Z9(FULr%&8SH8V48eztP{k@JCPCK!a#33iA!r6{+TG3w1{|sl7`j} zS`C*psIPQc5~|>7EMuRX+d&jd%eQcm@|3NrC{SPT~xA0kC}ZDk>IfL09opP~u=V&^@*mD4=1oe}jII){N=^a9}JHbZXoj=atk znFK5zvjiXH@2Z7rqche5(aQo8maWZ-cj0`Kb$UUn|T>PwK? z8}~4Eugww%Zje1ZjI$APpaO5xV!;cFa$QE-bJ zeLsZOWkA7dk^paL3$gClyX7kw$(oEjpxx*dnmx5|Prc^h1oewl#LF&5aVSuG%UImI za~Fp~YAL*|0p5{)X+AT0@0QfRMj^&-qv^oXGW(E)yk&*{`^5%o{GyFtQn#(xQZK9I zCT=8q7w3QeYB{uz? z6e*bw@pQbU`sSQ)s@=Y&)~>9&o%U|+kES4$?LznG_bO-5g@JP+FD#0I2!r4xNCyNS z?X~quDSwGzJvYBZ&Eoxn>c_t&B(l^SoWk8L@%mKk5^X)s!la07Vvp_rHad|-#=O!c9Z5E7f4(Y+(hweMSI z=B_2ug_g3gE|V6zQz)gBLO=Fj^e^-e^_iI~=}KGK4WtIynloqS%$%3!JmX(i{`8N7 zHT$D>MaIHp+#x}bqyZILZ3U^H$25`pwSMv&{X(CpIw#j6&ACstT7!nq!YJT0(HB~^ zjJgaX(J9u~1odT@CbQ+N!m6xxp-)&{Rb(h5TAEVt2<~@ynoG(>$7j^P>IfL;lXH)9 zo^oxLV& >DU};^@6ox!AtE3KOdP`GBYfk&=f4 zoI}_S8sriE@$v1JwR-Fn+jr=(YF}!SvkzE% ze*Hds1ScbkRh|!c=$Fo{>YwC^O&H-qeOQ(hvBXY4agKC5k(GBehM&z1v#0P@(|T)9 zl0hC3?)hotiHw#HahKyZt-jhj|Lp6JUVQz77t_DmnCs8Ysn5T&F!w(CxOZC0jAw{D ze5ekKf<-lSO4C~Hmqs4$x>#mM4g4VZqY>pUmrlNjQZu@E(r8SJtg^hWa0~(`M<~*) z6u5a&*Wl4_>Ci1Kxef!#^jX9BFoq8=5vIf;&J;3Mh8Y+%+u-x^1_ojVX)t-?ph&(A zo?gEpx2R+R=Zz^WsRKjEh?9(!X4R*?Y|={ym%T3+JbHY3du?etdp%UPR&i z;{1t?!U+m{C!{C@%C>>gaLQCEkmF|YShFm*YYbY(zH)c(kh*uQ|LX3Ia^7Coc<4U~QdyT4zm z=hsW)RjtYye8B$wKN`CI=&x{^mv`;-TPOj7R_~-X4bEvaUtEwun=Wsu?w2t5I7*X| z!&9dRo}~*W<2V(RKc_ZpF0i&>73>S!6Xr)gVW&2M+XbO&TSC=# zgsScM6rIw7J5!xZiG#Ut17QmQ4Dh)VbqkV?-{~pW>QyyzbS^}7wfq%q?UJGpYz)5ICt;D+yCG)0)6Sj&4 z1q1D?9Zs{9OE03&DIB-18jM7Zd&L0&03Ju4?FTUse4y8RDAoNEjeX=`b8?R#h!i(B536)R+7U6^Z-88VR)sDb(OnP7CsA~a7y=BL4 z=tZed3fCU_3=Gjn;9b4J%hrWheFeIFW8cU2(Ocla4YDWo!(@mYm;%FrfeMiimh{5d zByW@wSB>VedZm#w@qh*>;R%kwD5%Gcy6;2l%A=q@k%yWQ~Rn&~DTQ zO`e-auiA2Og8D@&;xQSbI3(T-KN)RK7M1H1$C#C@UF!GhF1gCWLl!`Z+v zIP-#ryk>;%`{fR5{IZT;Le-7f@H3-iCvF6L7f)9^j}~`o-irmhnnL)S4}()O?Pt?& zg?F(4o71n*MlOE^TcBB=mEL9GY2Bhfc5Q#`y8h@O11OZW1mASs4?b}cgDO)!=jgJ;l!BEaJ>vmhs|%`cF9~Pb^#!&1)T-U-U}1kW z2rf-Wy1%?vF@uf_%=6RSAR3r6h);ynKoX$U)_z*ZUn6iY^)FDrcqIV|@V^m(jCBTQ j(6=Doo{4SJZHI{;<$+1^k@*KpBQWS(0GS~h`JnS3O>98a literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.routers.dependencies.doctree b/.sphinx/doctrees/_autosummary/src.routers.dependencies.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ea5ce976ce6655814b2ea6870745df195c94a818 GIT binary patch literal 9506 zcmcgy?TZ{q8Q<*7>`V4Fm#l|*xjA(&yPBQ3I|XH3f)KbfCfPgg!V&H^bb7jLrh2xg zyX~&d?s^0i31p>C!L*=pya3568-&55+)7#nUosHQ9 z_HL@`d7i50?N`g=xL=($# zVYX-};dA^fpXTTI2tO~c)VKX`D+)}D)oa?yb-j$bVaCFb;!ej5ckBK1uZ0mbxo~Yb zZtx*F=D;pHIm2n2^5@_m{yYrzN&GG0?;`$INDCMuBs0SBax^fVFp<@cZ^thCVzu9# ziF{6ztoEhgl?{=YG5-u-?J2v#FTkX5RwaxZp=0INDJNfvJrxkCM;G%wsplZ+OWJH& zlG#@;n)q^WVtxr0no$;-7iJ@N&CsxX*N6g^U&LX?8ddaie)HD5FWr0h<$Kxx6Pj+L zH=Vrs1f06|@{4Qhxd^xnFT;9rF-=8;do4Gd$c0?XfR_6b=oULMG&F^OEhY@h^<&#` z{Z@;G%PUh;Z!i&uUQ{)!S7@Yq9V3p{s%na=<3+;sELO{Nqf0oJ;rNi|Ac&UV(bTsg z?^PzMrq|`G`b{nPO~rHdwJVAv71uF6r^zA+(90?czLYK_6D&BZW<1qq=y$5rqj;;D zw|ez8v}sY@DT0Q-V*qhNCQ^kF^mkdLcyE|AT-C!`MLmjQ&+v_l|tFTVO@~}-_`VrjxJ~pWSeyu=tY;TEN8fYg; zuz?dp$8@}e|3Z$VC3XXB<|DKKc@k~56S$7$h~$A9-Zn!o*@I(u*Yt)^w*ATpkf}{G z*-%pnGlYc_xiYYu70`YkyXJ8L?6niHYJ%ihl*7v_Ie#U2f3dzcC&zr3pXYzVenXUxfdS5u=iou zz08mq#`d>#`v7Pt?nVP)QjsY|r*wzhyxqmAA7qP@Zy(>>IxarQrsDri;pyW&cq%S9 z7x*9bkBnvh^8qmG-(~K^Nl;@H|4kl!+LJh~3=Tm2cl`JK4;%cC^0Na#$s@{AR3AI2 ze5gQWs&|ga+4#e}jmyIt;L<(|y zgPhx;;;{lu`x2Hjpxw%y@qc4X_Z(Y((#gm&98rheO+yFrCeGlS=^=bGJ1ifFF_-sY zOeXXYM9au7-znfo`z`V+eS9y^OgP7lf?{OgJ(}zGzUel+7Ge1)XD1P*`srzU+@@L}E zIPy~^vpo9c&k*bWs|Pz{QsjA%@b z*{l(_Fx@O&E7RRQBgY8B6v z942z1QzBONI?uv3#!yHDM9QMwGIqvyq6?H+h#a65NBnnCkhn2_QO*V|FsMp_e6%2QgUUbw;vH+Jw2;qo zA%bYFUPpIS3w8zfy_z4k>WMrGz9CwJp%5SK>zB}i=Tr%XTzpd5Klcl@h@cJ zk)|ev>=p}a4iil^2U5SzMaRWtlK@#{k*e8E$n%KFj?qoet^z1eQ-tiv`DoV@=8nOg z7I*Q7_#GvvPULii#{t6lYK**&V5nzQTQ^bQwanDC=Pi!fn+3<~e1nyh3BkREO5~n; zpP?RIq&TpL=^qvF#U~JlH|^5Rznc z+PL{13V+p$knA+@f?^-(=|X3hdU7AxWHzgkbZVb`Yzm>EUFb|vRyu-R7&vG7F`*cW zk~rYFI(V?Xj$cXpAHrGplQ)pFs5vA1@j53$lf1V3G literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.routers.doctree b/.sphinx/doctrees/_autosummary/src.routers.doctree new file mode 100644 index 0000000000000000000000000000000000000000..11a99cad75c421412d8724250e5c77794381dedd GIT binary patch literal 5599 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 literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.util.doctree b/.sphinx/doctrees/_autosummary/src.util.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ce677922c3a3ae812998ae01aa8e699322dc56e4 GIT binary patch literal 7355 zcmcIp-EUk+6?bg!uI>0UO+r!Pl8A=7qFw`S5pqpHfDmfbeW)sgAkppYy|X)aeZQDH zH(pak0->N7O^YBGDO6SAi3fP$4PN*VPdxKyAR+YuAp{8cow@h!-o4pfCpNLP-kmca zXXc#qJ2Uf}Z`XeOtIIRyPp`$1aD#r6IZhBdJWln36WWQ-1C`!SpZ`vJC*9JEk{!fh zB5j`P8MLs4>p7AK>8(_+Qgh{caWn7Vh`FuYFc@?fHBlFhTj`cq)HUTQkC&?SLM-hj z4UkeBG?{;c_epoD=5CtJqaHD6RjJs}i`HP8Vould*|qe%=LS4&C2gjcm{QVhCkm^g zRm{0gOfzoS9*biL@w*law>^POg9#hq*#I~Ge+7$?5Z1KXek0zjCF}k?OTfv^|iBdP}7&5g!-Z!^yYA zGcXa1U6)B)x^`(Ey78JBG|v2Saahj1fKfi5nIId5O`J0+y*^YbUcl-J0oq^>nor@wRp2kpN2((Yc#d)MJHSV~{a zVS>|Q7Tzj)o5`0OZY2X(X_oVajaM|<=U1Tj?{JS$O3RASMiA44Z1v5Ry&b_ z^({9Vsgo>LTv{r$qR=~BS}G7!tIi`gDcr5PGEAb9yNA$hz!#6{iUo8za2+Mm8%tz; zAJ*@|!9kWc3gAx+5M+|U0ADW;K#8IqI)`H?Iw!+}u@yCf$isaQ=8H%itSeb0N*(p} z-RN*PZ1;BGoaCY_dy{Iezj@P`dka`zj(Fg}QC1%*qpaYwr&3V3v*EdLGxJI#i}y^j zyr`d^G{1C+)}KvAD>8|hv!0C(Q(cb^w_%QymlF@U<$d_|U8K~>j;v$lNXV(aFgEw4 z*g~j*&)gstztjzs)M13}WR4W-vnca^qKlyu0@W{2o~?ihRJKCt$B6R(-%sY)+=5Zbh8U1tJ=v% zZ>!q7+(<9>eQ(!rAUK{!lE+-$=H@1T3w?8{M0_Ko8*b{gl49Ij$6WgnvXCpu%Oufp zl)$nZ*k0oBj_c(F$+fZ9km?OQ)mO*2E1w4Sgt4moh<o(W`bMi5|2DWZ-fk$w)y= zl`93mf#uv7(3Tw;1_~E=+<(8yyaWL>;63YMqM}5lEwP4cW|s%JhO1E0ozr2U6&3R% z6wYP}Eoj+CaVak*(nDzl;cO9|NVOo<-qzpRnVExm3PSb&6Zx4JPM_6*jaNf+u?=@Jfmp@rnkhh!0kw>d2b*Ys64fORY$*dfs$xa23Qv;CRv z*v0S5qm>qyGL)8Jf#Y#dmvx$|jT`$~X5-Q*H8V=8H!C%jA$qSP$n|PA?r;u8@5`eV zmCkyd)_^FB1x(~&O{re-SkO&am&4)?%CjK2^;44Dp>!-j5HSKZM8URn-425emI;fv z4x$vPr=NUw6V~MZp(u)Z;)E9R zELyFDBne|~m3x{fv6Xe(jomCb*DS}4iHgKR@>w52I!tDwVAH?rG?VqV$jzRV->zfI19m7yhnQ~UNywGOZ&ZIYO0V>ia-qwH;RyHO? z?|l%nN9H}nJd)&j;0VzZ+HTN63>1ms9HojlX;<30J_WBAvJ8#%6ta&;0`9jt(awjM zg5Y4uj`Rgk(QM!1;Nv_CPd}7m&9pc2#~TEz*U1vvk)GqhZF*AC3t2h=ei4dzz;FQ$ z1=gFmH}jmbfk`2>6t)N?Z*0hvxb?XaY4*qhF-GeQ3Pgp_`wZj_5@x@9Tflhl8vez4 zk;FtAQX^Aw6pO2PE;02uYwXc`Mot74Dg0pydXE)5SFu~+EhYxVnBJpJ@B!_doBHL_ z+X0UGJGAHRSa257rGGxQXZZY*QyS*nle81PQjR6FxD_y*+17>yXA(*o zMZ3(3WeLT(ks|i|Hl9mHnx3x+2dx)Jnk^Pu%}D1)#z*#-inf~z)v@v#Y_~{gc9>AQ z0#}RUr3b&m!`Md~U(DtA;g!4T>i{RXcEB9ZYB9nwQ#i+vx z&{{l%gE{ zcovq^I|b+v+nvg^(OZ>~gA-0erM-;5Ay-bwl72iYLZ-D}lG9XFXeESdv-=e0f&0`@ z##vHv-vp=)*!6!j>k^in`JNKyXqhY-!jdO&R+MLvG7VhoY~+>k2H&l2Kxv~Nd*@dw zx*+4TD<^6Mh0ALYwoN1pHk2$8m4*6i2g&(C+&?*Z=aPx{4zB`7=MvxJcaHatTgUs> z^ey0bHQ|xxM?+_Xlu;G-_Wwgz>ETA;D=t}35g>kT6X=fKx}>C;pzwFsqY&A{>a6FJ zbEE6Yc^jrNyqavtMZbZMe}$BKy;JKrITEtb4?i^b8C*2P95O#L;t#rkkvdOs=`4_N zy@D|xC4uky%Dk}CXG})s0vMx+pb7*rhExmQUdU_f^lf-ueQYTC^F#TmIRzIFXfdP)wZ!jlfxUm-tQPDP#@3C4^@*YGlrCcf01!%Xn#{JVk#*sq3fa~_#EXYvAj~lRhoT(&JrYAOVdkuMn z>t4l@t}>6Ii%~30Fj&b2I*@XT;c_v|WPs6{id~1g7n((N{+_-&h+`GSidV=-ck~)H za8VXIqd_b~1Il`erXoS99a3MIVzc`>g$LYHoHJhur|g~}t5{I9Q$4*Zc@j(I1pFz7 z;pX+SV8M7;(_4Jx!(NK1EG?O8y5{kIHiWo!y635m!oHk^pfq84m(y`j39S1OtmE*= zjj7r~9g(R)`}6J8$M=KjNry`rOGmKC3pl!?I;rZS5ZK6VT-l{&cQJZ*W@cuKlMBkH z>-oMD9m>@YrYB|=>rFZXqAV3KQGhcUy%w-&n6V*;#XXGY(P`^fBzI%!IUc8)5@M*! z>*=~1M*|!a7V$kqDIfSFBkFpc!0Sv%dfrE1t$A6PphqZz4~De_A8{umhJ4V|8cu1YDI55u#g!EJO~nltBE4s1U!t`G#Ik zc;Zmm2t2yhU?|BG#5;5+Sm;j(rIPfZ+XXr8B1XX%zscqj(7SD7a$@ z^!DqY{w(adE(h++_C%R%|ESy^Fl7|7r(AY?t_D^Hpx+fL3?QtDrxu+Kd}_#7$-j^g`5Z@b_+bvR1?z`+|7rC6)>7VS)lH|4<~PiPl+&iDB_L82RIZOW-94z;A}xDgqFh9fjvBWuX$$l-6?ARrUNo|8;l23GtqMp=nW#~ z-zN?*esYX|Qr#pn^(Dk)CoWR)5HB)z9@p6+J&hFoMVZ1ccfW_Myv54f3eP<$x)}2U z*T~7=APeor+m$B>lwV!C3%j@ri$9uNT*|V;p zr^;_SoIj^f1}XV+qgE*t_LhoJ4EuP5nPR;+LmYEAr`VdMRJ+sNsr_*oNJY2L_UvB8 z3~pcKH#g2Gd!ai-K@;RbgKM?%<%0eaPI_*>39O>)2kP;tmm)fIf#WncMP3<`ZGf%# ZksD+l1@w@T(y~XHxJX)5SY-Xqe*y9NI@SOH literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.util.ingest_burn_zip.doctree b/.sphinx/doctrees/_autosummary/src.util.ingest_burn_zip.doctree new file mode 100644 index 0000000000000000000000000000000000000000..86a220bf6c1c6b4dde6c4109c2bc16cac74c47e5 GIT binary patch literal 5644 zcmcIo-D_k?70)E;PCDP2orJ}i>}HYK$>65j6+ww%5Epzf+7GgYO^~ z{LV-Hpz-q?+cWN;Sy!0|lk+wUf+P*NvSu+z{XFK0w$JSSAKJ%u&$J{vQ)w=JZp{oh zd=W;0B*)=A$s- zwww2uSz=ntu$ODxoNhW728wRA;73d;5b?W_u(E79e{|Wdcm*bfyP7iZ$+6BiV!@acA)QhE+2eYr4OaT1$) zFB1GMU_ zKL)2By!nNL!&(G9fR|xCvt7aw0pG5Dwq~=o@^H&>5PfY4{|fwgT*;7P$Log?->ZIj z8PnoEu_=*xI`sxTJyB`0M@!;CWVytgMalvrg>KL?+D)uF#P{%_eE?u3j;0e z(Gr^RrZ(~4M<1B1HMx~fU-?54KA)!bWWycSAJ*^W}e@JxNae{uz_Tm zsBJXJ$Uzc_n3Tk?0jH)=?#HV|6$A@WsJUcOO_cbx%gWp4)>ZelBQXDNI+&4++y%^Rc4193yV!>TEwAS; za^!Ci3BN*?p6>c4%zzwk&D|GDrxulrHpDDUtoVajz)D|asA_YhfLX?xpJ!3%huS`M zpU;>~>?M4EaZqGMYhIq_QdCB;xyoU?PLyVHz_m3~Q|Xma;4;oHP(D7XOmlPtmbJjx zwppLZ5H$zdx&!%CCy=LpCU(T%kcH{opI+xNVtyY?KZfw1OaT1Z#Clp;D+Jyae}6$i z*Q@>%+B|_am*dj+rmJVZH3ZvuF1-fqzpOMdU-{^Q3MBuwI#T?*MDN&Cr4FoCI)Em)<5)jzMZY6Yu*d~{fyybhT!DC_zQl*qcC z=|_Mcs{M2(GfyprMGFiE3UKpD<;+&CH`;=8U_8>ze}4qZ{$r!i)~D2w|v#q6V!qpX!}`89=uRV_PRatWg5 zia2qtEt>yv7>=o2jWyy8b4yE>sEE<@1)6l6#6+fCWOBD|fu*^x`4YTIw zl7=R4I3lf7lwwpei;8QFn_)iO91eVanI;+y2}<}oEXpxw3@65Bn(Hjrwky_A2?snu z7ok&0WienFwls?~ES3v_9;BRNxl}H58DX`mQ{ST@i@suTc;DRYr>Ra-&1>YN8)lUz z&Loe$^L{F03(A_p+>>Dnh-r{jIP7*o;SqNf?<~~9tA;$tDmRoet}3R6X#~8N4v%lz zQ)=hXm*l#?|HXY3;`g2Lj~_XD6!L^l3*ho)yrIBV#S$U9NytLvAX^EA)g_h`6d5%m4L zpZz@SxhDJm#PLKK9shcDJYdQwWKX$lhg|oa3_!mlbR0og3rQ9za&z0F*+ERk-VmO{ z3t$MR2)Q&H>LStX+!NtIMEFDe&Qq$kW<}u%M0?-Mfolarz2w%#(u-1`MIhrbW5*Ym zzkE2pz#ClYOo-j5m_{$%OO$(FD)Yc4q9^skq>mV=62nbO6)Nvd@P#o2ZE_t^C<;eX7TO-^y3XuAxaB@!*n-BULKNNpe=`q XALRiB_J9zy6i`MklNU7@d9VFnZVQPv literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/_autosummary/src.util.raster_to_poly.doctree b/.sphinx/doctrees/_autosummary/src.util.raster_to_poly.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5046a4742366c36f0f41dcb299865bcd4ea33f31 GIT binary patch literal 4743 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 literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/environment.pickle b/.sphinx/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..7ef5062569c743c9bc40e589ec6d7b2ea960adc0 GIT binary patch literal 183596 zcmeFa2b?5Fbw7T2_x5s-bf-9{bbHe6?gb@IS*O71q?M2mU~ytJJKMW6b+a?838y=O zFu@Xcq5%nw0AT?J3?`WWk6`2U0|#t!aQGp>1Ovt<8DqdC6a4?aSJmBBoo2eHckV3f zK2rB=SM{r^m+FP;suwJNWXZhw^XQ+wwlP~OR}YR_)dS^vtvX{>n{9jbRjqPm@*3^Y z!|g3EY~R}+wigy=XWQfU&}6OHtXtM(x!$(dD;TY2xiXrcEi_A&@Z=rtvcS3 z#l>26syyAcSEyEp=RtAk|&b*o|(8bHv31KZm6 z%BD3lTPZZH2ICbSwukcWvv$E=Z5=FDT9XzPH?2C4$sQ^-XDaz-$(jM2rG-jiqR>Dg z8svgTv(PLTU4R35`2f&XTC=T*e6i8UPn9dc8=70muNaS3@FSsBT2XDyfGGK?T79O_ zWJVci07*bHI()1Al{>3}v7=h6PV~RFJUz;f7B>qM6+9VyI^FrvP_Z^?jZ9Q(#r=5y zhH|4>u1-@)%U*JzP-!tqhfAAEr`rp`x$W_nv`c4|&MsYpANCqWp}a(~(O0+e`V+{M1adJuWK@RtnYWR$-b5WdQ)U zybK*KRIEJMmvJ{B|8BGkvV4OaeBODNv0N<>m5zUIy;f`9JNjIw?B3^sPw!QLbJG^6 zd$5@=R4TQ*^HZ&R?m3ii)(h1Jc!N3lh&`a{&=BpDwZc5`Fz^MuWiau++I<;BJ0E$&gTT16h=3eEfiTnhVAQ*LAK3}olvS#yl z7wT0I#qG?RMr(GKnZzx*Lp63hP{_;yfts1ARl!dd@c_!X!$aVMyfs^{)TSNM@FTFD z_Gm35ic~%LB|lXuplwB`m9xMhv?~~nF>5cFh3K(FEw5sZ0fKmvYLAZ-Z?BfmG&O!N zN>H$Trcj-QNH+4VIwV~}7_63?vrxf@+DEw3O7(ZXJYB6p5#1r*U2i@HA*Ue?s&I)^ zZ;sroQ`!z14!`n~)-2|@z4N)f(o;&iWW%S-6b_bWP!4{t7Dznum2wpqt2&MPs(Y)T zFH3oVnXu|4-A)^8__=i#Q0-_{^~N&i4JZ=_gG0?qBM)U(o;u__T1JnGm}=|IhSS(k zt3j$ac*cmA#m)k#MTHp9@Mm zOXtZJCC(gs!>D9SR6utsVOS`EO0>~qm|jZS-Kq`D&%p3Y^!A}w^3Vm4VMh?w^Ee?( z@_GL5@+3*yJo%O!4Qp4(JZa~0m848zP8Ngf^Ky1OEJw1MVZtyh*2}Cu35SeoSpt(1 z+G+yEAgHHG*6=%b*UMyz(5!+aEgELf`*Xq+cC zsVrm8vriA0m$?Jma!{ZxnlyOYR|;5Jb?8hp-%R?lv=xmRXeoCCy1ND~!5v%9Po@e7 z%0P{JSUSIS0hg98EL|jjY%6V-^;dCyXy|&u9Q|<;H0+LiYjzTLEoN{ws<;_xST?|| z69woCOlkaBzJ$pfxUW?U6@VlLaf;V5OgVQDoy$d5W&En_$gBmk6l}4dR2)BF&+mB8 z^E@zn!C$>!wJFVBopbm-h3eT#xv5y{hy*AL$l}Z^Z^dYdIaC%ktW}^R6P9><~5PE0Ey0L!N=GI=eykhwL5M4&IfH zkE-CZ8Y;*+SrteQA;BRN&>@XPbFI~!ZIPwr7=nx05LCmH0s+$kej!XA*!nJ&B;n;W zOY@^LKJ5jgIrXw^K=swt#0mv!q>M0K%QNC+v(6}sFa;p%d*qy=$U?UKq*6ON)#O-d zCL%k-p~T6IJ91puZ{f+lT62s$F#v~Kl1#SDvp7se%#3JR&BhG*ygX?cJXEX&v!+=R zP=MrFx9y8d&E{-l*VtHbvN|dnlUAjCpgvl)nq$@3nK4!Oik)LtILp-f7*!OIzbca*P1q6;D zJ4I2%d3Og)$*|`;{m&i0N3jRwCQsA%YUepQSnOr0g5^#@i)O|nvi4f%A-VT?HbJG5*WGsIb$IWF8?LC7M{D(I=bh!`?jMA-Ow&AO z56(`)SjH1D8V!AwEOzDrwyH{#_HlvPVwBzq4N`=J9hSf>Yhe~^!sFs*hGa8p65vOx z?!J|ibs|Zq)I%5*bAPs$>H%_R6X*z*oWLA0iNtttV0x`+GjBTJ6*l*iuzx$ z(;+J_g(xWt1)KrV42u)44~Q#_JHqDKg}w7#AzVSAJl0mMdm=rZDeT9LFJ*<|P54zq z08&+1*MR&~EIFGy=9Cz9r=T^^4dv=ojY2$ZPC9BNq_1#+&K5t$tM+L(RV~7qmD5b; zTx}}nhD2A~SZ)-p3MOI@vEgZxwN85>%44-t7L-M&BwX~}L3kLl2c1Za;>+V45LJxT zY8J{BhG<_2$EPNF874w{Q4GD_`%b1ofu{^{r5sb+^2T%~H=O^8A9 zqE;2L5jk_L<1&guxi$=eW^avp#-Nq&Z`d}{pF4;kE@Casrt8$ND(1_Rc|>~IJ3({c z7_PgP!nv>}!pc?`^U@OEZykaVO){xcm^Zq4l^+rxn4ZT|_`mD9+AGTRIqDVnA!S zLg=v9Q&Hr=H&JOd+_iUM+@CD4PxDzp`fl=euDR|jEXw5&k+~IMz6|DxeA-FKI4Y{+)5e(8?b() zWR`Ao%e6krGy(!lZs97g1?BM{9~g1&si2}a=99cv$Vs87B$NpSouFSl!f6z7tI25X z9UiRXJy?tb_-4{MSvA&b)W;^u)v;2U(t+M98rodA@|`vAJ5GW3R?ce(%uD$p(q+hV z5#K^tfp1-$tOlb=#`R)^6gcOH%(H7~LNu#{a0=!`HG8mk$@N1VY_E{41IIUY9G$yh zxRDHbT~YqJD&TwsXF}B*%T)mzd`MST5$QPKc)5C6z-e1UZVj7)8$HDnDa!m>F{byi0p|p@Mkpgau2T;#aJIPH?J9cy8z#5NFBcNoxIMZpytpqLZM)mU2 zlWQYsjljAO7pzjDUqK)?Z@JqWg78#I6ZvCOnhMJCPCb<@LChK!f8CkePjrgQRRn-x zD!N#3r~i#k6-v5T^#e#6$`qHYiPupL6@HO~m4OwnqfQ|)C-D912?O5y}K;HU0<-SR4R%4 zFjpYpO(`rHhI0hDBzR$h%p1M}F5anmZ3tsnq%ewA4x{jOkhrP?@%Fia3Uzoga9Kv# zDH}!NqKUeaQ|z+>wR!nOu~KWnT$CC5a;EMXJF`tOf|_w$ZD$eMSI^ zH!kWj%v#ul!R{JhM&a#MC^{9a$HRgGAfIpo=41Yzau>(~&8y_y9H7}L1gg_I@(mP2 zs}?DTYo8W3mKvrb`^-QM?q6MI!Vw;ttyR$A`am^IL~Z-Hz+;}|LaOr$m>_`&wTy%Y zm=~G>O>GXqq)M2TA=Q);oJNkJS(?Gdo%VxxMiVaP zP0~l4M7tM!y_9EgPUefpA=M!qhq24@zoYWA{h(P8j8Vzj|Bq^iXgS@W-{W;h~b zO@|R>^^a5aSiS|VW!2A6)u0R!iXroD@efoUl-zI<$X7l~uZ#td;BcfAcAbCaYoDUm zg19qZgUHI-AEnw`Iy54MPQLI7dLf8x6Fd+3W8L&$S5~rtzlraWE{O7lPtyx0M-GAe z@)4>N6gDpVe0YP*>pnFp8~iZ65TSrH;gFR-7X#g=t>g=zq!)$*!wFFzvi{#v{f!az ziS_;-6J9h$T=XSY#+}aUK#1tI^~m)ej#Ub62o7ouXb@3 z^kHEnFFY(Qv^%W;vsXBINzfOo21>Wt%NbKmIr4Dn(BS% zIjT?fclwQMXS)xCvf$kbKH$)Kg+_A=#wBemJYigmee4f+*D3uy*= zxP2d%rw@zM7S5YT{@`(=qht6Fj^J1x@oRYbd1%A-gCeIYtoAC90nKHbRM|$a4D#m# zaJs5=s#(bpQVH%1Rr5r%re|Gqqi3pGr-jvWEo-iNma2MEXjQVRx!T#P+8JiGpis){ zn^pBwyy}X(eIp+Z!gVULS?#O7MOEJ%ge$A-qA9y_j(SDIt=R%u;5^22Rn1LNz?JpQ zmA7{4i7t*3{)l=-qa8__Z-}G8UT`hH0nEJ^Q-_Vy7@5`;k@13T60OlKS!B;`kn4V^AW%5$I{&Mb z4GIgbN@08`D!jl+T&O7ZmCn+7;cgkuqCnYTv4j=4$POZ12t$#ogV7qJ>U~Q0(IN=< zb#Vaww;vBC6r6^5$xTjvJ&+^AX=@H)2gDaAa}$S#WCus?YGI(jY`vMQRdY`-wQ|>5 z6S?i@=XPw{b>8+}=RXBMcAPgl^c%HSu2`t%3X_G|X0BN(HyC72CLxXNZUVAM|)qJzT2{waFP^G=kqY?#zes$(vryJJ}fMAUKqJw z^^FKD+{H5ZiC=?Qzw7-$X-i&9gOa64QtwiqbPYPOY<#9O&@3Rkj4xZnJ!nzfPnRZP zd@1p^WLJJrT4SFv=6)A8roIXrliWMz#ind>T`ZXDtqCSw>0*c5nO#eJ6kUfUVHdLt z;A82LXId4ol|>8{;O@;0b8+n^ms4Iy{T_>Z_v{IDGQ8)*Ema*$A3GcI0( zKgL@V1@RL600qPm+`*PFmA9A4+biVlA-=W5Be;v#(S;HETKc6y6|bfT1bOd(LKB1^ z$^05pxh5`Le2KeI9psXd5>=`DPRgEfAaQ^K-V~PwpDb4@lC%Xs7)0h8Q-XA?2a1q= zP7p8MEmK&HU09AVU8zmLRa;UjPa?#NUn>+8_raiCJpo5;?>u{mY2K(+5y6Fn2OX$& zfc|t^5g)>s6*k|CpQSY#=9bD4{2!_kMIL*V!q>>%2>oLjal0U5DE2PE>O?W{g5~1{ zd+XhjUq_m?kpRDrG)5drl1m^9ChyoI>g_JMy&yuEt#q;MH;HZxr#*^dIraF3@%3hj zy>^dWJD4j}Cv&`t4yyr_MrJt+t6+%ISA1Q`MwGtjgQ_vaYDbioO|;-I$Ti9lf}*s~ zQd-8Ghm|p7*0Wdbg5unDC-QNu+3s}5&a=@{GC`)nQS`~IhEMTh>Yc(8%PV3sz#~FN z2LUgpGJ)l#$Iw`{@;Np{(gOSeUX*?Mcls4NDL}=DAEH#_ zEQcYTPy9TF{Fzx%j1ns-7W`FA*?iQ~8Zh3Itmd?3vf2enSxqb_0;glNop?1fM!SOg z(KVxqQ>pr(i-af+TPLZxEpbnyI7|WDnigPM4qGWzp8V3{&LLOh`54SzVbpW0`FU*; z{df4#&ue-a^V$;Gs>x|(f}jaVN2S&ar%feBcZ^DF09aB^b5Ib)XX_=OO(gDT6rU-8 z&q)igOFr`(OQ!K-+A{VMIdiw|`RBA%zyA$7EKH@Fh5$4s&$p{#s;LRivt{Lhlz*5Q=*NF>g8MAUdA#nOIK;KXFS`Q zuPJ*_nhcj6SKCX$UPS8!Bj`LVtE{9d+VVxCvo?KPhK)67)fPo2t@`j5N6^&!$U5MC ziq}y*#C&@rts_$b=usm=>HrZuk;T+A4EBYK6V7e+vPOA&1c4jwoV|kj${`^BJKSy$ z5PLqg$`OfQ5!to5)xJZE?NF-33AP(!ojtS*HoROXO5c-2BE^sEwhVU6chvM!Ms*0D z>4QOoN+x=P#RgW7PmI+;!m32+AoVE;HrGdDQ>>#Rj_b%-_1kN_4c*~e_%oVDu8`YJdLws(%xFMKlm5sd<=DhG1zM<@RY3Qz8N7QfAwiQ!SN}!K z8=>l5VMltceUfR3_}xbLfq0&tS0PtRDOXyl5#)Fm_p6srm<xH1txzw zfn;T}P~v(Y9yk~t@{nfs+e5COudmG(b#mg%3aEk1R))w_ksJ@1izG7ZjLZ~5HcOr3 z$yCxKLZ8OT>Fod!M&M_r?bB^;pGJXQZ#;mt*|GtdeeV zewLJPOC}*r8)yD5v%mP;=<3fBcXc+tt3L_ts_Vh4T^W6y%=tQ*GdsC-Nm6{?9pA|T z0;uEjU(suwMVCm{al7SQ>n9X7i;v`c=uI<96$%DhQjZNP}4b2dK&3gbdsBEZCh zfTtsUeg`(VFeygYin7%b=gJ}$tvtF#$| z_K|^W6?%zrsdtlj0)g<&Ke?Lmi})2SNMXxZJYe=l-r9*iqM$9Ov0##dPSOW=j9D*G z6hAXZ3};xJPhGtLmk3i}7m7N^hQuWV<}-A;lrAqJ3eunVx5eeS*(XO^J&2eo{i6ax znn@ug;g1NUJIP{Byb2FAb>iyiVEkwEbygG0U)(qkMLanWbSr=LyXU6eY%_Z+6CnmnCG|&}l%SLPdZ2ZLdmvKVS?5_Sk9JPWE4j| zbV6>lR0?~qH;mtdG&CD*kY#4|_UX&Ie=5W)4F);K88DdC9WC9p^4`V0=%Dsq;L~?}Uij$q&K^BBG z0)2W+Qq9k@S30r5p%BHeD|co0$|K0MUVzbAM#1{Udu$Nq?=vJ!fOVHh+5WUdLieoO zD^jHMbWv(B28RAQLktC2cR*t3tBj$ZSXUxB3)WRsBi7Ytz!adpu%fBnbxG+ptY}Wu zfUHgB8H5#=4*&~qhIES+^y_!sUvI&$!{V)c{XJZdh_~_e?RFcm^?tYc6)S z0D6fqp5|-I{*BBdj1GxoVv4jAugeOi&0rQ8%5;aX?{gV9^y5ivr^X}WhQD*Hv$KQb zVewVy70nm1pBB~i)bi%F<&Rw*AKsSpf}DDzDjB@+)P%es62yS625eZ%KlLH1Q@rvS zNxQTVlVofR$O~AiE+vhvOeXD8g4l}clApxuLfFQoZ+xsW^i_lE2a=8_EMU=n;Bsq{ zxhO8IDaJtR*Z^joc`nL`OU}GWIpx8mm`%Q9pns}_mrZiAMR07={eFV3ipVlQZU1^N$# z?Vs6FCENV^%*mn|tm-^8u=o#2v6w)rKO;w&bV~YK(k>;Kl2n(HDw^*n?NR~}Qe6s= zP)7A!DT$wjocx+W+xa*-Dv6068t&pZpmig_o4sU;=FPfCirYS_$u&ub(EBtZk)7!@k`9=70j5MJTyr)&2N|1- zcOxU3$~MV$9w}T-a!nC^=uaofwNiJ(18FVIaJx@yZKt#rZN1?H2Xa&O z+6+HomoO)7n%th7a&l3aFTs`KeSV6y8G`4zDro(gx|l>FHZRJum6<^zje~QoSmp0 zRC~{R9~%yf1-|fl*6a2cEY&%(vq8{HR_)gY_hZ+x=gdJ?O~YeMYHdvb>5hVznk}T< zP+)5kqb>d7d_UU6YHB<($(H%Is`2edXvb{5vvo9L_RhxW7yapYXQASwEL64fP{Ws; z8h&9gVj>sqEZ7d%knMogU}OHq0`Xf;o*5y%wd;1@>~qVye8*t0^J$z3vfXi0hMXb$ zC2bPLJBkvNAZyAg1Jmk0qB)yZ_tP&nt@zjfO4I5J-cwxe8-0v_KlvDuD}lc6#SVLo zuPysonc86*(qk;JWk$8T@r@F8ZsqWbQDSJccltU$mrI-cND}X$#v`q+o<4~@$|>2u zOo-MbtAsuP*q8v5rer^pv`Yz6vZ_l-Q?g%9+NA`kC)K4nNXdRbX|KZ~bw?>B`(5Up zDB~i*x`31vUpkabCnx#lPIW11O7?`LT}qIWO^Kzml6EP%T+DS!c4yKd^q!KH$WCJQ z#;ol5lUP9slV}fEL`pV(`itmywod=MNOiH(|8$}c{pmRU*u)%%#VEth|DmBaQA=O+ z5d!4eR*BsGQ}@soNjT&79_8@g%KMOnr(Zi&Hf)*Olk!{`8O>bloSShHozFCS9^={% z2YmaBJAX$g_cfEL(~Q3%oHaV0iNjwakZcqxhq!_VNgV$3;=Un_!i5>DOb5bX#usLY z85JrBVCMOZnW))3hP%B4p+s(k^64T}5a?4`Qp+ELTH%JE#%nk*LoM^^HQw}w43Vtj zP2Pai7iGUK2h?@yZ;?Jg>3+#a27A0a!=4A&!%13l&wFAIIruEtLt&HHL!UTOfc9ca zrrJd0$0c5`F(q?;17vLm%pj)p1;PTyl>U)^aZHJS%|T4bk70W)b`UEUt;PrryBJZ2 zUHDJwaO&s>Vx&Ea11DAZ`w+JU8_prcsY*rYh*u+mVXw!5oYh8!_wm6gqP1x{0hAA< zT->ZR3pgf|j#yE3_{5c#n!@Y!`pROfj%{R}bJTGx`*PL89ev{y+KpuWjEFrF3 zG1;1-W6{+~xxghqD1+zf)dKd_9=BKV6J~5WM%H^Y+pt=bwLDI^Lao&}Hl&6#aP>Et zF^bLnfig~%!#+KLm7gp(XrFC5%UAYtAa6+7a*U^eVP2_Gw+^^6nzx!GMr)L zo=gkIT*KS2R4nZ6T>|gV;7pSnS8onGhSxa4S1c#Y97oUi)Z13QlE+asAVd>{7{v3D zv$mZFi42~?Yo6yTRuQ13&}@)^Njl1Du=Ii>INJFT4q`i)FO{cD75ooATSOa>;}j_K z;M?%Cb;SmPdP=c`?PZF*y9^!D3&itiXA1TG*qQ!F$mzc;pj67$DNu}$0|tV^&ja=y ziYx}Hy+lVMhn*-~x&~9BJt!p|LI##0^$#X)=HQ8B#Vtx#0Q|=#h)D4}|hUx?KQ2rhkpGWt_XX*0KG{t=tmzKShYVcN*q`|q)5iZ99 zW;$t%9`ZYUh8VpgXv4{ty~5RsXpS3OfwOYtq~JcGt>vA1a#Z>gI=Gpif)QpWaA1N_ zb-nMQTYITdZK=~LLt1OVhoY?Wk>JPqVW|>KRL~7Y9D4@-X^Wj0nLV&u{rNC3!lyH? z#0Ex?XyCd*2SL&?WounEU_4&tK4!uq$X~3qCN0|Wo!-J`xzJQfqIH{#4~rEIG_QRX z=k3YzXhgxV@E@DC5T7YJro6Ot`H5lZ-UW>;8{dy#VIE@IQ~u;MZ>ux)q$6GRwPufM zc^Ka*etGRE0y;2vl#WN2p63Gin;myHy$5X}!SXzLd%nEAK;B-&H+}zq96~i)C}Wme zCU=~>nD&zgk>p<^^hasWc(Yw_h9&oT4Ih@9rIC9{FR8aCFmt*#iaaWF;6~oC+;@_h z`_!P)YtY|#Dv8d+RXull6W(=kA2r4P{YGHNS#)l;(~%{1@R*eFuGyC- zW0qYz&)ap0#zdD%?t#J;zh@51-Z_MS#A7&@&-sZHWSyVt`~l}rvO%?zQGVY9R&oxN z6z_>B818&5Nf&6z# z>kI3(yG;>rF(o%V7ype3#j)efBLM>&QJuUCn?52~wCheqwcI7@$7Of!b9COY@O{x| zpA_?&^Ols^feH@dtHXaWIwbyu2Cb)V2UN4>Zi?TgY8-!!(yFGsx<}yg#?(t@57A)< z*p@m>Ybh~yJ1A-J&rEACn9v1H-Ba-BaB@B2CH@n@yp22Uh141I6g+Hojrp2S!87;i zu&$F4z~ z;)0OfPJvX`7P1@vAcy|Bh@lUQZjoY}S_2ARY7P8Ipf$|bT&?jfvwLDU;UJ(X@Up2X zuAyp4G==yP8j>5>ccUl7&rx0C@yf*H^iGuvsEC^Yta}w9me4Q<=rV}QxLC@slkOyp zNQM{7@Gx^7a4CRd5~OuDJ{>?Mp{P&Jt}HYV{sMv2g?|^fo)FDlZ@$vi`|V@Xs{nePzx`;a7jeDu6S(cs)`PJVN8q6~QuMd)2I|3|>uD z`=bmVPE6q#W$;D->s1*TSeZ+O@CqJzm_neEQ1qwZVwy#v5Z*^1b%kJa>pdxiBMhSJ zSuYcX@S&`_odT6}p%6|rB$^yLsSW5?tSUGq_Qgmg@J-?aT?q`55}?*h4e)KM*B=e= zFNsMSqXB*dV7+Pp4<&Oc{$Jx^hlxLxgyKF0&nod>yuv5`UrCJgfIaXdoco7;s`asnf*M6}) zbX9`?gE;V^0Rnp|l25xDvz#Ft#3`K(tA4;zMt0b-zp^2JY*xIFJO`S)5HZ7f!K<`T z8l8)1Mn=3@=6$_;qV{qXD|gac!}j_>I2fV2cF1G$C+rP@+K#RV;Bj#W zIR1Vlk)NaPd3C>&?-tHtFC0XU5$UVz{~Q18Mbnkq1kyJH#lkElY4CFhi8sm`mpMxy z<=7qo=8%s?`kYPNn*c7EV2SJTir7o^6P_3Q#bwr%U*Yj~jgBS207vjpD+qLp`h|qihmxg$%_vA>!O@UIGV0B4SqjEed#A$?!J*DVNm(Y6! zQ_f5Qvlo{eNIkU=D@j|Tz#}|zu9gLXypH9OT9 zn{We)o+fX%%G+)7_Dp&E4S9PuZXm!uy3G@J;3o38$P}q4;wRu(@-~gzxG2d#1pnb0 zxCqSTh`1RfJKfN-VP?Bejp*+OiGbXRKgO@Hq^B9457kkUGUB%|B8lEV^Fg(DXq4)d z+{A1)#|cjH5PE8_($brh?ZSEuFU83Z^faeXKB$TGKKj^yz0W?9uJ`Fdgx>d$ z&ei+k)zB*%zuWH86_XOqG8bb8-tNPh3pI(Cpl0B=l78bE!{1kNIyJ*HMn;QS#Jj1n z_u!%$e{W>t_HIpHJ5B$J4R-B3zuP4%&mY1+r~yi#jZU=cRjgM%VAadbLwdnoEvtSg zUoYsZT1MqV`j!j_@?j;52S=6EXnl+K6blc_SOJ_bRHkcapfuAs7FWNBkGiC|8D#Ir z!WY#}^um|Vp-Y;Gl$@nO33ApK83E@2vE2-|$pf)5>P&hPv4p=1jMq{*FMBQY( zmXQ$_0H>1B>CtcE$9b-*`ziqb2!Uh;tXiMp){~Gz0rV2$LJr`ICKI}{)FD!l;sv>bT`T4h^61-LVXr%~KTvD#!TA*e_#@-d9g&?0Lb zE#h{>)F7Xr7ZR9rJ!p`RB&L6i2Kg+2CDb6|VH&Hpb|wTjbFDZf3398?lYh!ote z6vsCSq^>yrf?MxNaeP1yps$MKr&)D71uEx4aqNnacE_6ZYY*pntO|KbYM_zI#5H#0n)*1*KM=LLhauvX)!#Nv*7s1L&(-*^^bbQ=oD#)XI)14FFd(pi=NGR-J553@lQO z%n-lmYGkcXji4{43aQZ>{ZS$N6Ei`KQE~{tdQ~A|$eC;PG0jK_Qy)|kN{JLauhhp& z38bz*Cb{*V)JH)Mps(uVx3lVY3RKR8`nW0}@xhptr$&NhvFhZ?o`FZImp>LU}5nGY`^tv*V~27S9$LEcz4A2Jk!D!}jTc6?%cFciTQS zw2~Jnvd@UA;5vWyX>qm4wzD^f)(BD0mMF*pjEi$u%f*9R)qOFy;7YFYO@t#w>AqMoT-~h!mx__qq9P*P7izAkZ`0?N_<_Ihi zS$b`&$}7fwyPjyz4Z1z-!>|iEg$e8}6Y=*~{1!FCVzmP7o57wZG0Q~!joVM_m1%bE z*xu!$k!KCz7$7X{7%CPj*xim6jbOhgY)#V`#%Cf^*mf#kE!Vf^oIe_O(_f>bqg#fC zo?a?9a#M2HX#A>Mw&o`JTECiaT;r-+B;;K=w=?Kjj{X(u^}?ata5Z-@H?xJGa2>Bj z^~`;dM#0Zw-{d~JuVM%eI-*}S#u<1uzFGHOZdY!bw<}kjsACgFrbJ}3RctrM+cQt* zh=MuZLdt1fLQuU=31SkxhKnP*6icm?O83xZ>=ZePoh<>lJcI4cJc0LPYTV<(t2{0oMh~~RlIwI3 z*bAYze1vWQe~l)qhn#HG8lBEvb_r)u3Lex(8`Ach&YCE`EWxC|ZWf z)u_UE9rz~ND9)228ij3LD>6q>ha9o?beyIIU0Ly1-^F4_mT>XJeAhvH7!2!h9Y#k$ zLWl%it)$rNnS&CR%6=flkjj3b!)YTHAxau)yVmm5A)ZZ#`Gu_zEPQlXs#PW%0T#i| zj`9tB6$tgCaPFua*`Q0%+(CXYdUc^$xRR$m6v-J^)^rIdp}H@Iy{EN2DVuc->0@RG zGi?p$dtsCHV-2TjXHM5}&bS$bfZ^q3B}l+r_hmFt5h;q?KF3VkM|yvsYpB6Dg7U}T zFxB>LamX6n{_L7e`r*JA{$-$_Co@{lW~>Q}!c1cPV?y)3hXq`ZGdm@58@wb2`Lo7y zi*Eo}!X$t{BADKUI{yh4}pVCsuciEZyp3y&Qc-WfL#%#{`ej9QJ@sMsqE@c~%`e~XL=Otve9&E>LiJ2tEcDxM0655X9 z$7J(pi~moHF%%h7<|>%EHW0Tmex!kTQcRrABp|(&v2A!Wfz)loE!?vd>c_JU&z6Jh z);6@ilgcps9~p~ug_sj@|CfeDTuyov zDt2Ni@;3)kVnQf8Q?%sDiGQ+tDRGWI*$wh!7nti_mXO1GnCp%t=8TxR?$rR+>s)tg z%v?u^>hDx{AEQT3b;pHfV9ZI5>SsL3y`4bnliZ8APbrj*H_82`99FlJ+_P?io8rP?Np>H6eTTFk^o`F|**E9WToym;QH@mdp0~>r zJC$;+UhD)bn{vg*Klfcq?4!?pav3QDHnr&G2^p=2+3(`S3=%W@?E$b}XFobOGPoz3 zjJ8Wu^>_ZekP+1F{71DzRYVH;kt#wwn?UOR>UrFw6n3`fuWpxv>U#d`9GgtIPVd-V zlIDtUmqFPl&cKbit`^0}Dfq5Wb6q7Xxkw?a7tq-!yggiYxyakRxlUgs89CkQmE?@a z!4tT8-g?S7NAJ>}+H>W`KkdDWI7y%OR;U%6a?PhX^Sve^+x0N>wG*>T%*^*D0PA(; zJ0oJ|)9Ko0=kGzrku&)*JL%lC#Uvip&Uo_s0|Kc}elO!5rBFiNR9fmF}Zv+icwO~Rar^zpcKE(D1Va`qhv4}qyVwg{nT78uucLZde4Oa z!wY4iu$*Aegm=aQO1Ni&|C2u95)*qS_y;~L9zxr^9C!qaaz<#a?})Q?bKukZh*iU# zKD4K=Ty;&Rec_n;Lwo*6^2rbFGx*S+G5Li4(0<-J@ZYbX!Q2n+>52BCJ(UTT8lRtkS;Z!WSAJ4hk0X3jiL#}xac{p~%8JieDXBcpHc&8^(_(H8Qq zh0K$+Sz#VmFeb_KhzB^@IEr6ceRIFv2i3krBW*X|+`C)E^yPc|jsEZL-R5yfo$p(F zuPEX>d;i$oui<QpL5r@9h2k6<1L+Y~;-SclQ3q?HhYxlwI2| z=<*wT`vUsF9^#Upwwn2ga%~152o9I0aYh$T%F9m{n%36bM6FewEZ}5@X{#n0wJJUn zbfxQ9{1|T*gzLx0c&eSKkMTqXE2J$qO5mfB7ng7aanx5uMkO3t?qgyiJmFAtpF_^> zq7**hUA6CWNgzr=qNPVER{6l2uI_6R3Vbhd3g(?8io}ak&}?RWdG{s&OBkoHL$*rS z7gKL?!WNNa_XREHLXkM%BRt5+k^zfN9kqr@Q+hXa!u7i#-G6NNhXhjhN?*qPOG3;* z?)m{a#BReECFjf((Zlf#AI_@VDNyN!R$r2~7oIaM6=afLL_5tW6C;>Y2V5l>F6G~aa*_mb$9)acyIMN|Xppz3#W~(kwn)#C2iDnDvNhdl3 zCz>(&gm$8TN&NS7TwEubo@h=ql?j#?l7g7dV3-uNbDcbD&9&axgEni?)^)0u;YM#;?`WZO@GC1fdc+6SzEUG@ z_inV)<-YjRr-u8|P9NH~+T3o$z`lpp2(Q|Y3fK1yc+~^DDV2rO+zmt9eQJ>HMjwJ* zs3~kG&!hJDRa{2Ruz|J){5!Wh*ZCW_Z|Rw5cJ0{Kty_(qn1+UKRU)!uccqMl3qkR*5%mlByg;)SU?FK zYyb2uCILO#vEGYPn*?2RtX)C3%n%aS<3+DNh0w{xCVA2&OS5NEyVz_JJ?Ua+;9@f- zpU^J$&xrqijEn1H(-Y0brZT~jL-NqIi%pf&xY$qCENX}z=c!GR8jbg}T?sWN7gO|) zwzEw_59{}=jGXOthM@sxn~v=;3^%g|9PPlNZCj6X^w8P(m6emd#Rt{CR3mQ!CmY@E zi-Ub)goDlJpr}l?-?^r;2*=t#eAfjIIM&+s?*`$t=Z15=$A?%Katiy&bFTgU6}zaJ zKBcSujoafr@k#>6`g7!1Ta)D`C#Fj27pp=s5z-*6M#fY+*xVZ4!V%wT6}4;H=Pv{9bS(>B!knU-uD z_t{$I&@__f`>jK?ldf!^-)R)`Glkg}CDkXnn$ws6ArXKQSgRiq0M$-Z1YkIejQVrj z5I{H=1$=SRXW><4Fu>u6ePES1@8A1?JRDl0hTSQ{9|w3jX#qVBaC~@crqB3bLKW3R zIKXCJN}~F_Z~#rhMmXTL0G2cyU@iqy6R@^j*%o7l=JE*o`=SAJq63{sz+pz4j0CJO zG@VIFswaIEpKXxDSv0->cM(Y4`@fI-l7xf-@Bc+|IDPg0KbTdwQ=roG{(n=BJcG4C zdt6OsPIRG)#H?b5F=IB6x}=vjB9+wFfdfNHtu(u5vh+8oHZRTev2+9n_Mn`;nwUjm zl+(WhSVH9_-brKCl#y?_5BUP4K3pl;T83}pN^9oE5Ob8!ml!=#2`zPO7~f|}R40^L zDWpb)ALktpkh%i;0{0_={Bg%ndY>gdE{D=r1+)g^=y7`h+3ggloC^iiG?YWEIUT8! zV&9T#3YIwhaqPp3&iKH}kaHf*GkO1^Y$?Z(chA~R+N&xFs z$(j+K+>~#H!)O&}CKXtu# zT8H*ciFtrt?~m5IKQRZ!XuURo^{VxD#Av+;;`P(vd=cYI>by0>{_vJbMyeZ1#uU=3 z6yIA3q^|gWll#+?;(MMPQePF{A7$0;6sVjF#kW7G_yW<*cpVd{ZaSk~t)zMST=XJR z$$gP{QCD&&x=JoE7*jC5OmFr_zkMMwqs8dAZvt4a`Yj~)!mew0M7*D>?X!$3soGXM zZmbiEHwj2pLkXBd9F=DKIf2wQ+uw6{deUs4kt6D>W*a)8XKz+^I|VBJnk@wgZ!wL` zIwS!Ry_m*|jZ7q1OjC#jlyEVPKV2v81a#)X(63akL#a(+a(yukrB3||X~IJ3ofgqh z3{RIREpM565e=t4pIk(f!6F*QiQ}^~T0C=-<1#goNv~Kq0;*7%uGP!U z(v0sj2Y=0ZdQD?>Mw7_RCR!E0vRX`Ymk+9ajYj5f7SpH^YTfS3qMFnFi)w=XXxp27 zTS0`>iCARgM~k}@rsqXA5BU)8LQY{79K(q0sk`8UBCFKS`8{=U&tFXDnO!I)Ei~uk90kbDuUd7+tVYjAFo=l(JR<%*9Us;{J6^9a7!{;DI zSgy{tn)xanzt?a--$hNeZE#4P8lSP{f5n{vk40nY8LU>s`xnidx8;RxIx+bGrm#|q zy>0A47!-GxC!3}A-rJSR{OB_9EAQT|+KDojGFk6C<=QJIHDm1+v#PX~_MO9D`7#6G z6h`KNZoc~U3?>t5q7Jz1;y0L{N5XFyOlB+QRQmjcs-g#j=^o~xBq}JzU^)z72@NLk zBhs(hQo1<9$cHm?X-QUwQK;^545I^#E@>ET2+<<=xcm;^N;`TbRPQI5MoqiuwFFYP ziyGXmBvcI8MYD2beYK0;nN_z_pwhF8X5`2-SnBI#U7d+0UVEt7C{}UFc`1n|yU0_e zs(~(h5vjdC0}L73YtR{=ski=)>Lm!$^`N&tm6&y6^wt*vtXI9|9#jzG(7D(;3L5KU zj3}wGHiu}eFuTX3DnSUPY6_`S$<^->NL_(_n0wii0{f60XkQiBue0iQ3RLw*JC2=y;KSk-FiSX?!^u@8(<53Q!=Gf03;c9S zwo*z%moP1UojTddA@C=Ytr;X+8Iw=wWb1hw!GGIvag(j|L`$|(nP6|9oN*J*DyFRdUtxLA5W?$l58nsz%V4Z0b5ao9dv>UaO@~ore+mQvXQZtY$D^cw=p{RSE3& z=R7s2a-R=>F4Poek{2-a_f;IAX4n?nIOhRFf8+Lb=~!ad&K=!G2zOG1P?L$#V#zA* z_dlGpX07U^RV|jS#xb5E{aw(jA1PAR&YVX5wz;lqfI`w|b<_Y84SurYq7I8+L0@W7 zPkSsOC>$QzJ zL=W4AAjx!gvz2rw9{1Ob5cY0MdkpPKC82Yg-_|#G9TP7=*!zyQcoE;su-mr@r0%u; zCAXf09D%Ug2ju|z>b3qnt8S-2rRTN&nH+frVYkadMPFCxrZUk>Vs(ml_@}O(^w;A6 zM{1Z;PV{M*4N>4tMROXxm%vQxLD8IqrjmUF9iwQr09ZmrBfbgl(Dh3+iu$2uPGEF| zsTnE>rAZ3eP{FZ_38b!O)^qDUshKr$0DV<6d$a0x3RL>lObQTQa70 z;Oa_?RPcvPdOt5Q_Xf@%X}G{*@#|--Vew*MpG~b1$MN(>8x1?GQ+Sb_dR<3pE??^K zB72Qbh8HskFES>d(BZ|`LX^a-adE?o^h66UQkh^GAtSeR(TGybk46*>i`Sba@`y9j?@i_+1}HU8pH6PA|^r z@2mI{HPfd!qrdS)=X4sRAST(h^So}OjnDN57TI{AW&KsF*eo9?HxGr^n5-i17!s%) zEm?(1vlL!N8#Cj)Ppe+9)sOM0<x*> z3bi$ho$zLcD3ToY*{v>e;-Tj-rosXhR1!)qzx&aBJoF|4sRt@HbL&ay6o`kODF@Kk zKt(>QZl^${7pT}IN1j1E)LQ@;lxNH{VHyLEW3|zRF)A+(WTftSF-B|Xp5sFMYO0+3 z=%xOsoEIkMi5Qjh3II!}a>R`^R!zr5;HQ78yYLTXmv{b{ii!6y%FyX0|ZUINCna>g*>B(FpkD!A$ zHO=Sfz5ZyLe@M(CF`DLU0M@IfiAGWXl+9-tDPhWnNYqZ|#Ha~VNK_I^pA=H06w);WQddZ)a_c=Q zq?6Q|P0wOvCJP+q|QEbF5$`Sa|YRG#^e(^ z`}{VDm3Rv-ZuXg;XxV2f6D%(z1zl&KsdAd^v%U~k&pxxjb(MpbSpf5!7E|7jb|#u+ z9+~JZh`nc#i4HLX^^BTpc)yTh{hcZstAxKqv}fb`as0|ENBspKRC|xM;-K3vUxJff zUlP`5hkOhZl6Jz}8vH&ctX@Pa+m9kQ=^RL9Yv*+uPL%fCAnDJ2$af*9FrU42w!gpP zJJbx@=ySitpuchZ_RRQc*Y*p#&267fNoeNEye@si&#u&_r>*)O$NJua8&2s@3R<-j zm4ePa~bGmNc`I1 zV9ij;74${|ZQesl`VwZQBr3%VmC&?kq@=G0u!NzKj?3eu5XL$x7pno?iMaR&J}iC;dxM4E^w39iK@Pa< zoowMwzB?Jkx}<4VPio(t4W}o4_Y8b@#^e*)cmG`yi8tcn`tI~Z^WCXTuq2UubnUxS z4M{YB=T6haWVqAU z0$5UaI%3Arz39_|UbI0{pU*s3I>2$2&tsHzaFwZ2C<*-zM)$7rZxBe`Ro>1WNkY4T zt2`>l(pOhmWYz5ysPtUrbJO<1bCsorOVW#IS9wircM`bDD`Ej9bd~+{t5^=`(XR53 zF!kFc&^}k06rBG<>Ik{-0A1j`LcL)VTmRZim=U#l6sLC2V;$*|2D&99qCJdKIP6#Jv?29wCc4L&y`H#mBg zwPBk@S2LX9|3~CzQ|;6Em6bF6cRr~0ZjH>z{2KWQKwtdfT(mzdd(pN}i*$)qjR>#U zj}X^)?b$2-fe+O#

(5=N0?=E51(6^vNssH*P;Y1MW%S7#~lLv83n!7_WHsY5nnv zRXcO(6<_6gt&#>|F0m|8-?%4niFYIi9_bHX0Gt~-cZpI0hK9X}Y9-LHJvhVXF%u*a ze9sxC>B(@0uL7{7&T#ak)4?A$=;`x^=TQ0`mv|c^u7gWVl|l)b#Dh{EtXi*s8-dha z;t}ph5()-f;&bF!`sxx-W!3EzsPtUo&1rk#xx`YvCFw=9OS~esI|*FkrLlk#y2Sp; zRSW`pv`hSb%=`|L-N>*V(AK7Y@Ld>3*G8_aW`{rweRp=SEzKKmQ*VbQlv8{($~PV_0{ zL^tbYTH!5a>3@tby*$?+Us|;@m%j8fTzA(XMVL!%(Aub-#J|2an+9Di?O)ry1GF)G z?iEH?%{=sZR5^jZ?!oom#ypcmX?U(TO?`&zeHnlyb-hDYZ0Zy>&XC^yDZzRUYQDlEPeIK zC$j2x3RHSN`59?@;rZlJO(y9@v`@Y?wmS)Y^2M=$68hx+8Cxs@^k|>_yD%ogR&+}l z9^qX*1NrJs`k|A5P8P2&X_`rs+COKj=t=)P1OJ>c`Goe*Uqd4C2rjOFPER!doXP}C z63Iu`{y9}nZAl(6WyMuki{3=MnKUxufebaqpKMX z{HKUV*dY7}euX;lM_`QM7^j!Y+sowb74r5F-_-2>I{YC|eE7HzYN~9vKDMN;d*?pe z1GM8a&ZukCkr|6-3*~y-4$XJ#{Rq!@8#Q!?y)+q)57(#e*<1gv57RDW&pPXj3q`+I z;h?VY{)#VBGu?aZotqTDy9f~9f{SYWQTqFS;-CQYq)B(_Bw;^&$F?p%&VH6Pf8IQD z)u*jyzF4WXCiC*E>|uPBeT&s>)v+eIklRfqxf}4kcDXv8lQnYXYNJ`G7OheJZ9Hn0 zi}`X5(8^#s-Rr;3YUT=6_q|+gN&>!>-`R81Zhk|7n<-SwQ&s~2WVif)x2kT?=i>yp zS+a7@JGrTPZHAxlyE*x8ZqH3Q*(tY)*@@c0e8Vc%t)?0%-fFLmlgmYd_XBM;v zi?zz#<;iBLy?01);UBtOIJBv|kVmq1ZjWjwD&#So!8h)Y41Q`J|Fds!V-bGlHsc?L z^!Tf);Kvq+e8B@Gy^x3h-9*he4Ft}W+y>ehlig~6J7&~l2h~q7$qkF;-~)TX?4h>3 zWcJWVZ9>3%h?)7m>1xhVW}76+*Nb|5dVw$MaS4ERAN8=8!mN_3v2Y-U9?;Oq*|rB! ztyn2E8rW^?R2t`&sJf#8v?})fCG<2RA9JXfotVctj87T!STMZBq$?GM($#Z~JBz0p z(ocCv;%Nj@k9BP34kRI{0{GuUfW#BpnQ{!>#yafbZf6-+Ksa;L{cDB=Xhk70#h4`$ z`@t6zS&=0Lt32*X`zA@*3^tk2eb;N@eftLS+ZA*LUT8KRS#=HfyKI zEl8+R#!CDh1X5SxFXoP;D(17gqPDZ1w|{o@KQW@DDn7k) z0Kp_Ey_~U%UU<4sMSm$VB?ET$7vvbbR?*u@MJEc9xZ=UeI>q~j+CG7BkiadC^FaHj zw%4QA`rgW2dsyCFDhYLrsO@pX=$+vRmkNM&RGV6`taDW4saZATa-m;IUq%e2E9o^- zNo(CS1^bGG9N2@Vz9cc*#Axd40jyU|eNG2Wts|}Pmh(l7t%$G(6^81K6cQ!%hS*0S zb&L6Y?m!Cr(UbBWatvLop{gnl9)we zRP37ptXCC#W~7SM(bae5`Vix(Ls)_;WvpcXkU;86_T}7>6l%v)vM-fm=~~I2x~IF8 z9BbE3l?}D}u_%P47@wMz7LebeBkaRjAuIzWm;%HneA&e$Ap9%%-tEE~i1nPlih6|@ z&!Kd=Oj0b2g%Xzg5)V_hzKx5L;J3P6@~7~HOGzAo;~zDSOu^~`^OHIwV`Z6ZPhxUg z1rf3?rF5|zAuVvxC0bk5V64~c?9kXt8nsrvXtgDX?;+E3Z+lpLANRfU#DDOurI1o5 zB>e!7#>Ef$`XgM$PjFEg@1OHiT01i?{)>PAg0H{i>#z9wYg`2j{XFCzkQm* zdUzHthsD{rVzirayK(hqF^v1T*n+EAP7R$yzs{x0RxTN#pR1^36uo3{sVg`0uCSdRXkD-=%Bl45y3nb6i}4 zE7nC_hTFYuafQ5HC2v>D+qLpG{|vzTHFTG|QjeZ!EA^;Mu6rR-=)6)-u_#q8n8INS zfW5ffKt{NISTW;cD>!c(^(CDyb+Nn5=oQV?c^@}~>}apfBasKaMu?UTTtXL_JQ`6j zEM9Mx*n?!xw3_9LYZ&r0={+_k#PiQUs~p}L!>`b#b@zZjz(UWHx3D$Rig3S$8d8hx z@IkeAYM(`R+DL}_o^&Z)=RO)s>BK|ms=Zp@IEFE1uhKS(;V1e+Iq^v30NoXBfwgi2 zH`XRw6|CShOmX+AL5y2`Xmg=@)=B4qI`dp)U&U@}hAozj;JepiLJVa&XOU9pCdIp{ zvG?Gj8h>wOV-xV-^Dd+`Po|K&@sN_>Rli+=-Y7-T4(y|i_o0KVIOr#tlF8&#bLIWF^ZzC zbeCCo_^1v~+!4OuDlqdL>af@206TC4i@#n?BBuM^ywoWSyw4F&r~!KT^z9L5k0hGK zbHr(`GCqBKGk_&<#FyZsG<~Ad&b^ODl+rEggSHIyh_j6AA;t*1+@(E+Vy2SNS<-V8 zI!lr2zuH6Q{RC2X8(+??C!vP|cnXjyr`?yz0d(tUm-aX;+P$jR1|i%0a8}(;flBXt zwZn4c8LV--z>USZnns%`G-aSYjny#TZq7*nMrxMtV5AP&*sxn42G-On|3>d5F!FlP zD&I=X4l!EgCjgdEtB60Kv1yoc=>pkg` zugC%PRhMkQIP|P-*E-cxfwJ2vP&pU6B*bd*S(lnV@s-5tln|@M_j(-QNZoP?@lm+d z;sbB$m&@qA{^*xoiCH8@zdRMddetwUz2ZmFoa>ki7#CqWhDt&Sl0q<)j=7yc>N;j8 zx89SE*(L|jR~;j=>UIiL&V`Qga)+)RV0x*VCNK+PHO$39Gst{925h8$c{%Zru3t`w z=(DL_9;Da$qh9R9+!3Q*UJGEos+UL%&AE0t%%}*{E>sdqj}#)Iw9D@kNL{FO9~L)?j16ylLUl6_jfVE_|iqJ##*BZcJC0efWjiv;(F@E zUR;#C+~D@gAEOp!KxZDL?8!Llg+@i+w=klhdmman8^GynJG!GOzwT0L58zuIc;q^@Ajt>b7vX4mjfu--qS9rfr_g|{}ulR0>X;i%a zzwu9$@M~O@`6~Ezh4c#kkgV`Myp+ts4^Xh-0*j@fpLK`D&7dy_oklpa@81K}OOSNh zQ-v}my8hDw>PC*Xv-{%ZXgMbT~%K2JK2TE*=#A_^{=jBuR&=f5TpfTT@ zvV62tvLxY{1V$GEdktJdXM*livL~9VUH@{%wUs%R=c_nRePS~YpV*it@ou6po5Yvm zS5|4;D|}EB?T8YV+~`Xh*VqpwtQY2fD4{hX(!73zxCz-{ns+yStR6`7I!}#k!ks=e zyHHb@yWZxd{=SN*Q8R2)ubJ~SufOp`Uss1wB-wT-_BKk>?xx*NZZ$R~8J=hr_v0J0 zYGKCOnwzcH4wNUYI<_8h6mATDeR}Z`F z#t}lOqs0(>Ht%RiZNw{b`xHUlfTS(mW z;KsU;)=Vw_dkGam4|$n4GfO2=FkWDTra>dy`yK#G7}${OA;Tm+xW#E$tuTggK2TH{ z;UpVAtC5P|8)boC}FI`KfQ~) z0o{p}_y;~Lo`X`kRGQ>$gmwFf{91ejmaO0N_EIQZQyINv^LFyY$@tZ!OS5@Wd*W<1 zJ?V*O;E6LPpU|H8va`W|gSfb!I6cukaVirmJtPxdd*W0%jVJD3Gfm<*<`b$&huptf z(p)obs(_>Ipp)RkLLSS=LFZF}*(N|t74A9jEa+RZYt(^fuId<`-Q`4XcFUiFUs<{3 zm-wLCmumr+u2xfPk>b9%fF}t>3*zF4J5pu-?pY+o< zo$_&`QBuM~&s-86i+=cXL9%}M;i{b|KRk==HSOzNM?63q%>~xPl=oFMcAAh5c$}ww zbry}dEaCUC-$)XqOLV85Bi@`)vU~8a-@vSrL}PgVHO*p%fBh~1OX^>TuXEH_LZ0nL zp<}7+^Qz}ab2uLLZ!_McM;+KYlL|wpQ@_j6z4QDR1X6dNU&9?pLb!nQ{D>SwU!CW_ z%c|QcQ0Y0(uaqOtVA-{)IlMU{MmKqtIx8k`B_?S<@69g3N2;VB0|ACgT4};JgF!!~ z+6m0V9`w)USlsaC=e$|ulx(woV zjL9c-yzX{V!Oz6Sjn~l=EnY`uf~AIJq3d`ZRZbJH(?4Q_*vXYMx^*6Q29Xn|azjwY zq+*Kv(T>xRz{7*ymQkF}TOT9WHSXijwD-|>+OCP_TSy}-&f|*nxS|pKc{vfBO|rxI zl~u&qW>y8h?$5=$#)SZj7zx=-oSgsCFTzaOu40oxi{0 zF>0ny(K~W{Ue&lBh%@RlS8l_p<4q@7= ziAJqDOn*{Mr-Cb$$6|Qz-N;)Q;WRhb&l+LXr9TZ~?_*-_V`|35 z{k-jI!@wnO-Q^m81n@sd3iSx!`mpv)r~8Kq6+{nC_rEhkB~d({(@j&P5d{1tfF%tA zdYN<|nT4_Fp zq%y(MLo(5IP?0L92`Yvz0`yzqk%2{DMRO6LsRE96aFGNb{`>Zff{PnnOGADPy$^v6 zxW3rv(KX{Zw&<@n5B28BdPZpR%S3jz+CGP0S%nt=(FfJOD(TWL^HcA>L>5mAtOqnZ z(Y8}th${7m7YJoEp0D6e_X21B)y3C$B&Qg zP6B^?Z7iUK{#Kcge_dZJ07VII(g${>*}(lSvaY^ zaW;~k^u{yr#u<}OXm9*2Bm{55#r4MNiRO({nPAx=iRjuJr^;!(@#{4k8`Gy;;-jn& zXZnnE#?5-BFK&we(e}kj?9m5%aK>sDCJW6%TbxgR`2}R0ESfAAo9%}&3E$GTmo&>w zIuC;mBRY+$_zo~Bxx13Hx4jTZQ?s|s$NeMGb|S^ZO2?BRT#)PB<^b6_h1pr7-Ea2T z?xrr=t&}H>cK@@-c2Doz?(sBUv^z@CS^Bi`fK@Ly4;kH z&CAydCIwdZ*o8AYcY&up!i4HV!>TsRRjXp4;-nt?aaQMktn;CwQNwvYGP)$CV7SLV zoZYz(Cpmq9z+y>HeO|s$nXZ8`N;3_kFX#8zm(88~vW)stueEUcirMy6J+?jEx$QMt z+oQ#jRorik{-z##u%&YkP7U;6v{Ua0Vp4D;<*-vL}DtV^2Q4prb)&$gOckpU$p-=j(_%Oz@=;N1R3XGI@K2 zygkG>6+WReDq-b4-nk=V&$KGV+KiQJ)^d{#YhS(|jy=Ql;5Z@DxY0l;Ej`CJ4b=o|Lp;O-ou*u&N{dB#$B_g&}vqBzcuvZIH~! z#r(vwGV?|(Bbe?)Gl_V@PRm}@tQ9po|9hZ}wqcrQ=l?LGU|8IR5_=7ZG+QV(u?n?b zn1)eUf-4aU=nz$3TDPXGx>d!=rxDe{{fb|r&&m7>d$3%EK~gPL+J`wD+p3q_?9L(z=YF71)5srO7u+fNBiDO{N_lt_p_WQZaX)$j9AtyZ5Mz!Q+-5!?jt)0}W0 zYq5_u!=>G##ROfVw}Te@QQndPD$0i5Pfe~rHzf*|O|&W%e9K!YH=1pI@JT{`4q*sL zzTC8C6r}H`B#bzlha*Kek_b`!E`EY$LmB%QQ3cG(q3-IX=t&mrOzv_hJB#ILK_Y%v zN+ODNE{F@$f*5f@oR=1aA{uMk#6%nJiE4glzl1SlK{XmnC#qR#@#Rr%fhlG){$lWQ5_acaJoUck6vYalVFre)M_qC;=Nh_c{Y z8d)d4eN$TOd3^hZv>-ehK9&}Q(l%_z^^Jy|LTi7U5&dXXo#^+ew3zeg_wlqKJoqN-$v?%l>ac^1>9wDEa z7K9=shbs~jGPUs?nNN6LMwFxRbfVm$v^eu9cYs0k)c`+yqGN!+lzzn+;4eqH-vEDQ z+A$NKNCRAsxhDg>(MvC2fOAN#f85$31NA2uO%}#Cjj9vRzCSJIJf3|YgXk;IzVc*; zXJ1XfVtDp3l>2%1Gik@{@vIzkPdxkXUU~tZ<=}5(o=xjCCP`WT1S89We`$oB`1eO? zvFP#df20NB&6eLw3qsA79KP=xExX9~ZW_skgfy^DBs>*u^=P@Cloo_X!VPIbC=zns zrEerOl5V_)VKgLJenUo7q_K6P;-0kl^91qQv>-ex?oJCrQIT_IeWPNcl?HcbL`)iD zCt^0!qR}JfU1>ph#H^+Tp@=zxkw%*(QCXYRK1ldFjW=aPHX2MPvb`ZK!aTA)#vr1x zHU|mEpW_T!nF07U{faT>7SbsZzJ%i+ryVmfiF82Zn0qqj9_^(UFy?qQPT%y7w~i%# zVb4EdR9P5ueBfAE3SG-Nekm;mJ>LC7S`ePfc|0u$rE+*FQ{U*=#j2>G(QHV_d)jv( z;Q-p|(U4n|7KBH_`DsBY67o8*zLC%q#rP#<=VwGm-j=Kb9e1R~peKrBX+d~&+?p1I zq9ZS&>l+=rT#t8WMr5SHb+QKTNQ*#^jJKx+;gRu~X+bD5@=CP6kv~aI5dQ`cuy1(zW)u~N zMdr46PisNnpAmU!(4ENpzO;z-$op;vk=A5?FVWapBiNa_Bs(xUKR|DOkS>2tm%pIP zU*fWNf%q%>{UN&iH7-ZQ-|+RreEkTnd*_Ld@*f|=ReYQ-pP&o9A`Se#OG|6StLYi@ z-zU?eiTF=~kZAI({LW)lg5*iTyCkNgxHWBTb?u%iKB%)Axk7a^*DOrX*L3>Y>F=Of z-3;&C^Kf+&;75Di8D-h@2$~*IO{-yhD(IVOk?-;7H`0ReBR>WGqzY=;tFU}IUv16g z>0^$v_QfK$8=zH@xY05@+-TJjZnXFXH(EY{8ztd!qZBS~ln=y>QY^S_kvEEV;x7u9 z;YNWL+(zV$d?5Tqb~J8ehT%rjIBql#;70V>yP##Sq>kdC2K04;zK^=MEgwylr?p3` zQL9ryRCE?q01;;#&EHCSXAQ( zr2-$o#o{5-qlYQe(x%HJba@pn77vaJ|BF2LU(n^3bomu77LVtScFSH(`zSVObBVe& z(JEIa`CC!y;VbYeeKYzXch&og`t1Eho%jBt&eLB+s|R@$-d{8v?=Kpc_ZJP&`-=uh ze-RxYUoCsZbfq={ z{4hNm?N_wzHImTQr15lUvNltI#M0-u_qK=a`9;1htJi8c1nJPsM6J>m|4FmXl3V3p z?uvQRqT}}RMtPzFN8`!~d=%04{=Ukp)b7ULi_4P`4*Xa)X%#D2o=+c2;LibkIl*h+ z)vihQs&eBNORsapUYakKKwGTTZ{N|jSL6%TT6MSgq%~m=)FuSJaU+HnKO|nqHw8*^WiKq^dtZ9tqOt$Kt+VNo zqKLzI0?r^Aml$D1L)0}~_9AkH1R@IJM%fvI#semkUbZu{m1bs`m$)0_fuLX|mEcQ% z3OSIdpTKXSR}*ysUy{`TH=pOJnyT*2UV8S~Usre4f1dhx&Foec4EYGRGpF!VJs{=` zezj`O^5_n#@$GSYgccvdKaY=!Y?^lv1fJ*(Y#J= zp*{W&HVK%=FrRv+O`Y&PSSetd%e+gS@LiZIVA{=Gq)vDTb_I z{v8Gkn3gm*sT00*EMl6}Z1d5S@D=znl;%^|Jc4N}_#-$qVEvJGho`5~m*L%jX>Id2 zPfxfFGX_kvn+H5S;UAAjO#7P-b;3Wv{-LzK-_@4U32R3+|BmTvL+wzLuoh1Cn4QLG zt;vKx<}@;o*^P`AzesrX)rj>8dYdgd-3WG=nNQW9mdzJ?ekGMJ~)V2R3Ko<@HqDtmbv?Ukt96 zfqzPxUtzcYidzT?vKU^?iX0=P?g!&+Fu@}lLzK>VxXJX0$yToEPsFnV*T~11%;y>_ zdf?tJ*KU_s(PX&pFO7<9y%rTF&HaX3;S%N#yqaws+%vvYELAVioaEW~YQHo)s@Wc; zT9rN3+KR=aQP4tJF9mv-F^vr0LNN^wyzLPTzr=1hWbtLj0vITMpUJH_HB&TIcE8AuyOsoU+4uvxl>{Qw_a!{ zUz)~A1ZUy=@RLC?%+^_A62ku+R+Q*r^~a*C%Xc}45)bZ!96fcz$knwlOzUFFgK54= ztGFZhxWky@vkXQ?-8hZb9jyc2eLkFWi*<&rjYk;T@^Z)OM3=+zg^|Bd=UGuyZn>+l buTdJGx##PJF{YhvjWod91I;TtTRZVTyC(7n literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/index.doctree b/.sphinx/doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..041339d032c19726ced4858ffa957ec8ffab00d1 GIT binary patch literal 22692 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 literal 0 HcmV?d00001 diff --git a/.sphinx/html/.buildinfo b/.sphinx/html/.buildinfo new file mode 100644 index 0000000..742e61c --- /dev/null +++ b/.sphinx/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: c6d0e880c5de320878e6fe3dd37a9cea +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.sphinx/html/_autosummary/src.app.html b/.sphinx/html/_autosummary/src.app.html new file mode 100644 index 0000000..1a789d9 --- /dev/null +++ b/.sphinx/html/_autosummary/src.app.html @@ -0,0 +1,115 @@ + + + + + + + + src.app — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +

+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.html b/.sphinx/html/_autosummary/src.html new file mode 100644 index 0000000..a6845b2 --- /dev/null +++ b/.sphinx/html/_autosummary/src.html @@ -0,0 +1,130 @@ + + + + + + + + src — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src

+

Modules

+ + + + + + + + + + + + + + + +

src.app

src.lib

src.routers

src.util

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.lib.burn_severity.html b/.sphinx/html/_autosummary/src.lib.burn_severity.html new file mode 100644 index 0000000..bae7897 --- /dev/null +++ b/.sphinx/html/_autosummary/src.lib.burn_severity.html @@ -0,0 +1,147 @@ + + + + + + + + src.lib.burn_severity — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.lib.burn_severity

+

Functions

+ + + + + + + + + + + + + + + + + + + + + +

calc_burn_metrics(prefire_nir, prefire_swir, ...)

This function takes as input the pre- and post-fire NIR and SWIR bands and returns the NBR, dNBR, rDNBR, and rBR input: prefire_nir array (n x m) pre-fire NIR prefire_swir array (n x m) pre-fire SWIR postfire_nir array (n x m) post-fire NIR postfire_swir array (n x m) post-fire SWIR output: nbr_prefire array (n x m) normalized burn ratio nbr_postfire array (n x m) normalized burn ratio dnbr array (n x m) dNBR rdnbr array (n x m) relative dNBR rbr array (n x m) relative burn ratio

calc_dnbr(nbr_prefire, nbr_postfire)

This function takes as input the pre- and post-fire NBR and returns the dNBR input: nbr_prefire array (n x m) pre-fire NBR nbr_postfire array (n x m) post-fire NBR output: dnbr array (n x m) dNBR

calc_nbr(band_nir, band_swir)

This function takes an input the arrays of the bands from the read_band_image function and returns the Normalized Burn ratio (NBR) input: band_nir array (n x m) array of first band image e.g B8A band_swir array (n x m) array of second band image e.g. B12 output: nbr array (n x m) normalized burn ratio.

calc_rbr(dnbr, nbr_prefire)

This function takes as input the dNBR and prefire NBR, and returns the relative burn ratio input: dnbr array (n x m) dNBR nbr_prefire array (n x m) pre-fire NBR output: rbr array (n x m) relative burn ratio

calc_rdnbr(dnbr, nbr_prefire)

This function takes as input the dNBR and prefire NBR, and returns the relative dNBR input: dnbr array (n x m) dNBR nbr_prefire array (n x m) pre-fire NBR output: rdnbr array (n x m) relative dNBR

classify_burn(array, thresholds)

This function reclassifies an array input: array xarray.DataArray input array output: reclass xarray.DataArray reclassified array

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.lib.html b/.sphinx/html/_autosummary/src.lib.html new file mode 100644 index 0000000..01dbbec --- /dev/null +++ b/.sphinx/html/_autosummary/src.lib.html @@ -0,0 +1,142 @@ + + + + + + + + src.lib — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.lib.query_rap.html b/.sphinx/html/_autosummary/src.lib.query_rap.html new file mode 100644 index 0000000..fdd842a --- /dev/null +++ b/.sphinx/html/_autosummary/src.lib.query_rap.html @@ -0,0 +1,132 @@ + + + + + + + + src.lib.query_rap — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.lib.query_rap

+

Functions

+ + + + + + +

rap_get_biomass(ignition_date, boundary_geojson)

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.lib.query_sentinel.html b/.sphinx/html/_autosummary/src.lib.query_sentinel.html new file mode 100644 index 0000000..0bc5201 --- /dev/null +++ b/.sphinx/html/_autosummary/src.lib.query_sentinel.html @@ -0,0 +1,132 @@ + + + + + + + + src.lib.query_sentinel — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.lib.query_sentinel

+

Classes

+ + + + + + +

Sentinel2Client(geojson_boundary[, ...])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.lib.query_soil.html b/.sphinx/html/_autosummary/src.lib.query_soil.html new file mode 100644 index 0000000..780a3a2 --- /dev/null +++ b/.sphinx/html/_autosummary/src.lib.query_soil.html @@ -0,0 +1,144 @@ + + + + + + + + src.lib.query_soil — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.lib.query_soil

+

Functions

+ + + + + + + + + + + + + + + + + + +

edit_get_ecoclass_info(ecoclass_id)

sdm_create_aoi(geojson)

sdm_get_available_interpretations(aoi_smd_id)

sdm_get_ecoclassid_from_mu_info(mu_polygon_keys)

sdm_get_esa_mapunitid_poly(geojson[, ...])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html b/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html new file mode 100644 index 0000000..c0948a6 --- /dev/null +++ b/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html @@ -0,0 +1,143 @@ + + + + + + + + src.lib.titiler_algorithms — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.lib.titiler_algorithms

+

Functions

+ + + + + + +

convert_to_rgb(classified, mask, color)

+

Classes

+ + + + + + + + + +

CensorAndScale(*[, input_nbands, ...])

Classify(*[, input_nbands, output_nbands, ...])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.check.connectivity.html b/.sphinx/html/_autosummary/src.routers.check.connectivity.html new file mode 100644 index 0000000..8e1a383 --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.check.connectivity.html @@ -0,0 +1,137 @@ + + + + + + + + src.routers.check.connectivity — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.check.connectivity

+

Functions

+ + + + + + +

check_connectivity([logger])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.check.dns.html b/.sphinx/html/_autosummary/src.routers.check.dns.html new file mode 100644 index 0000000..6bbe639 --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.check.dns.html @@ -0,0 +1,137 @@ + + + + + + + + src.routers.check.dns — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.check.dns

+

Functions

+ + + + + + +

check_dns([logger])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.check.health.html b/.sphinx/html/_autosummary/src.routers.check.health.html new file mode 100644 index 0000000..aa645c1 --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.check.health.html @@ -0,0 +1,137 @@ + + + + + + + + src.routers.check.health — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.check.health

+

Functions

+ + + + + + +

health([logger])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.check.html b/.sphinx/html/_autosummary/src.routers.check.html new file mode 100644 index 0000000..1432353 --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.check.html @@ -0,0 +1,144 @@ + + + + + + + + src.routers.check — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.check

+

Modules

+ + + + + + + + + + + + + + + +

src.routers.check.connectivity

src.routers.check.dns

src.routers.check.health

src.routers.check.sentry_error

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.check.sentry_error.html b/.sphinx/html/_autosummary/src.routers.check.sentry_error.html new file mode 100644 index 0000000..5053522 --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.check.sentry_error.html @@ -0,0 +1,137 @@ + + + + + + + + src.routers.check.sentry_error — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.check.sentry_error

+

Functions

+ + + + + + +

trigger_error([logger])

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.dependencies.html b/.sphinx/html/_autosummary/src.routers.dependencies.html new file mode 100644 index 0000000..a71fba0 --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.dependencies.html @@ -0,0 +1,120 @@ + + + + + + + + src.routers.dependencies — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.dependencies

+

Functions

+ + + + + + + + + + + + + + + + + + +

get_cloud_logger()

Returns a Cloud Logging logger instance.

get_cloud_static_io_client([logger])

Get an instance of CloudStaticIOClient.

get_manifest([cloud_static_io_client, logger])

Get the manifest from the cloud static IO client.

get_mapbox_secret()

Retrieves the Mapbox secret from GCP.

init_sentry([logger])

Initializes the Sentry client.

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.routers.html b/.sphinx/html/_autosummary/src.routers.html new file mode 100644 index 0000000..a0d30dc --- /dev/null +++ b/.sphinx/html/_autosummary/src.routers.html @@ -0,0 +1,111 @@ + + + + + + + + src.routers — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers

+

Modules

+ + + + + + + + + +

src.routers.check

src.routers.dependencies

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.util.cloud_static_io.html b/.sphinx/html/_autosummary/src.util.cloud_static_io.html new file mode 100644 index 0000000..bfc6e40 --- /dev/null +++ b/.sphinx/html/_autosummary/src.util.cloud_static_io.html @@ -0,0 +1,131 @@ + + + + + + + + src.util.cloud_static_io — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.util.cloud_static_io

+

Classes

+ + + + + + +

CloudStaticIOClient(bucket_name, provider)

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.util.gcp_secrets.html b/.sphinx/html/_autosummary/src.util.gcp_secrets.html new file mode 100644 index 0000000..35f0508 --- /dev/null +++ b/.sphinx/html/_autosummary/src.util.gcp_secrets.html @@ -0,0 +1,134 @@ + + + + + + + + src.util.gcp_secrets — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.util.gcp_secrets

+

Functions

+ + + + + + + + + +

get_mapbox_secret()

get_ssh_secret()

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.util.html b/.sphinx/html/_autosummary/src.util.html new file mode 100644 index 0000000..3f40e18 --- /dev/null +++ b/.sphinx/html/_autosummary/src.util.html @@ -0,0 +1,138 @@ + + + + + + + + src.util — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.util

+

Modules

+ + + + + + + + + + + + + + + +

src.util.cloud_static_io

src.util.gcp_secrets

src.util.ingest_burn_zip

src.util.raster_to_poly

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html b/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html new file mode 100644 index 0000000..d2e29a5 --- /dev/null +++ b/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html @@ -0,0 +1,134 @@ + + + + + + + + src.util.ingest_burn_zip — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.util.ingest_burn_zip

+

Functions

+ + + + + + + + + +

ingest_esri_zip_file(zip_file_path)

shp_to_geojson(shp_file_path)

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_autosummary/src.util.raster_to_poly.html b/.sphinx/html/_autosummary/src.util.raster_to_poly.html new file mode 100644 index 0000000..6d37c59 --- /dev/null +++ b/.sphinx/html/_autosummary/src.util.raster_to_poly.html @@ -0,0 +1,129 @@ + + + + + + + + src.util.raster_to_poly — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.util.raster_to_poly

+

Functions

+ + + + + + +

raster_mask_to_geojson(binary_mask)

+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/_sources/_autosummary/src.app.rst.txt b/.sphinx/html/_sources/_autosummary/src.app.rst.txt new file mode 100644 index 0000000..e227d4b --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.app.rst.txt @@ -0,0 +1,23 @@ +src.app +======= + +.. automodule:: src.app + + + + + + + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.lib.burn_severity.rst.txt b/.sphinx/html/_sources/_autosummary/src.lib.burn_severity.rst.txt new file mode 100644 index 0000000..aa48322 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.lib.burn_severity.rst.txt @@ -0,0 +1,34 @@ +src.lib.burn\_severity +====================== + +.. automodule:: src.lib.burn_severity + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + calc_burn_metrics + calc_dnbr + calc_nbr + calc_rbr + calc_rdnbr + classify_burn + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.lib.query_rap.rst.txt b/.sphinx/html/_sources/_autosummary/src.lib.query_rap.rst.txt new file mode 100644 index 0000000..d412cc7 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.lib.query_rap.rst.txt @@ -0,0 +1,29 @@ +src.lib.query\_rap +================== + +.. automodule:: src.lib.query_rap + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + rap_get_biomass + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.lib.query_sentinel.rst.txt b/.sphinx/html/_sources/_autosummary/src.lib.query_sentinel.rst.txt new file mode 100644 index 0000000..730e7aa --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.lib.query_sentinel.rst.txt @@ -0,0 +1,29 @@ +src.lib.query\_sentinel +======================= + +.. automodule:: src.lib.query_sentinel + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Sentinel2Client + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.lib.query_soil.rst.txt b/.sphinx/html/_sources/_autosummary/src.lib.query_soil.rst.txt new file mode 100644 index 0000000..ec35d02 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.lib.query_soil.rst.txt @@ -0,0 +1,33 @@ +src.lib.query\_soil +=================== + +.. automodule:: src.lib.query_soil + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + edit_get_ecoclass_info + sdm_create_aoi + sdm_get_available_interpretations + sdm_get_ecoclassid_from_mu_info + sdm_get_esa_mapunitid_poly + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.lib.rst.txt b/.sphinx/html/_sources/_autosummary/src.lib.rst.txt new file mode 100644 index 0000000..2e2b0b6 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.lib.rst.txt @@ -0,0 +1,35 @@ +src.lib +======= + +.. automodule:: src.lib + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.lib.burn_severity + src.lib.query_rap + src.lib.query_sentinel + src.lib.query_soil + src.lib.titiler_algorithms + diff --git a/.sphinx/html/_sources/_autosummary/src.lib.titiler_algorithms.rst.txt b/.sphinx/html/_sources/_autosummary/src.lib.titiler_algorithms.rst.txt new file mode 100644 index 0000000..54b8cca --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.lib.titiler_algorithms.rst.txt @@ -0,0 +1,36 @@ +src.lib.titiler\_algorithms +=========================== + +.. automodule:: src.lib.titiler_algorithms + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + convert_to_rgb + + + + + + .. rubric:: Classes + + .. autosummary:: + + CensorAndScale + Classify + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.check.connectivity.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.check.connectivity.rst.txt new file mode 100644 index 0000000..d3bf9da --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.check.connectivity.rst.txt @@ -0,0 +1,29 @@ +src.routers.check.connectivity +============================== + +.. automodule:: src.routers.check.connectivity + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + check_connectivity + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.check.dns.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.check.dns.rst.txt new file mode 100644 index 0000000..5926358 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.check.dns.rst.txt @@ -0,0 +1,29 @@ +src.routers.check.dns +===================== + +.. automodule:: src.routers.check.dns + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + check_dns + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.check.health.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.check.health.rst.txt new file mode 100644 index 0000000..1fe927a --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.check.health.rst.txt @@ -0,0 +1,29 @@ +src.routers.check.health +======================== + +.. automodule:: src.routers.check.health + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + health + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.check.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.check.rst.txt new file mode 100644 index 0000000..7550c65 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.check.rst.txt @@ -0,0 +1,34 @@ +src.routers.check +================= + +.. automodule:: src.routers.check + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.routers.check.connectivity + src.routers.check.dns + src.routers.check.health + src.routers.check.sentry_error + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.check.sentry_error.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.check.sentry_error.rst.txt new file mode 100644 index 0000000..c127c2f --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.check.sentry_error.rst.txt @@ -0,0 +1,29 @@ +src.routers.check.sentry\_error +=============================== + +.. automodule:: src.routers.check.sentry_error + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + trigger_error + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.dependencies.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.dependencies.rst.txt new file mode 100644 index 0000000..696015d --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.dependencies.rst.txt @@ -0,0 +1,33 @@ +src.routers.dependencies +======================== + +.. automodule:: src.routers.dependencies + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_cloud_logger + get_cloud_static_io_client + get_manifest + get_mapbox_secret + init_sentry + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.routers.rst.txt b/.sphinx/html/_sources/_autosummary/src.routers.rst.txt new file mode 100644 index 0000000..7b5ea81 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.routers.rst.txt @@ -0,0 +1,32 @@ +src.routers +=========== + +.. automodule:: src.routers + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.routers.check + src.routers.dependencies + diff --git a/.sphinx/html/_sources/_autosummary/src.rst.txt b/.sphinx/html/_sources/_autosummary/src.rst.txt new file mode 100644 index 0000000..3b20aea --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.rst.txt @@ -0,0 +1,34 @@ +src +=== + +.. automodule:: src + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.app + src.lib + src.routers + src.util + diff --git a/.sphinx/html/_sources/_autosummary/src.util.cloud_static_io.rst.txt b/.sphinx/html/_sources/_autosummary/src.util.cloud_static_io.rst.txt new file mode 100644 index 0000000..b24bea9 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.util.cloud_static_io.rst.txt @@ -0,0 +1,29 @@ +src.util.cloud\_static\_io +========================== + +.. automodule:: src.util.cloud_static_io + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + CloudStaticIOClient + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt b/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt new file mode 100644 index 0000000..6fab0d0 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt @@ -0,0 +1,30 @@ +src.util.gcp\_secrets +===================== + +.. automodule:: src.util.gcp_secrets + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_mapbox_secret + get_ssh_secret + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.util.ingest_burn_zip.rst.txt b/.sphinx/html/_sources/_autosummary/src.util.ingest_burn_zip.rst.txt new file mode 100644 index 0000000..17f7c07 --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.util.ingest_burn_zip.rst.txt @@ -0,0 +1,30 @@ +src.util.ingest\_burn\_zip +========================== + +.. automodule:: src.util.ingest_burn_zip + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + ingest_esri_zip_file + shp_to_geojson + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.util.raster_to_poly.rst.txt b/.sphinx/html/_sources/_autosummary/src.util.raster_to_poly.rst.txt new file mode 100644 index 0000000..0e22e0d --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.util.raster_to_poly.rst.txt @@ -0,0 +1,29 @@ +src.util.raster\_to\_poly +========================= + +.. automodule:: src.util.raster_to_poly + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + raster_mask_to_geojson + + + + + + + + + + + + + diff --git a/.sphinx/html/_sources/_autosummary/src.util.rst.txt b/.sphinx/html/_sources/_autosummary/src.util.rst.txt new file mode 100644 index 0000000..118c4ae --- /dev/null +++ b/.sphinx/html/_sources/_autosummary/src.util.rst.txt @@ -0,0 +1,34 @@ +src.util +======== + +.. automodule:: src.util + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.util.cloud_static_io + src.util.gcp_secrets + src.util.ingest_burn_zip + src.util.raster_to_poly + diff --git a/.sphinx/html/_sources/index.rst.txt b/.sphinx/html/_sources/index.rst.txt new file mode 100644 index 0000000..f005b8e --- /dev/null +++ b/.sphinx/html/_sources/index.rst.txt @@ -0,0 +1,28 @@ +.. dse-fire-recovery documentation master file, created by + sphinx-quickstart on Thu Feb 15 20:41:59 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to dse-fire-recovery's documentation! +============================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. autosummary:: + :toctree: _autosummary + :recursive: + +.. automodule:: src.routers.dependencies + :members: + :no-index: \ No newline at end of file diff --git a/.sphinx/html/_static/alabaster.css b/.sphinx/html/_static/alabaster.css new file mode 100644 index 0000000..e3174bf --- /dev/null +++ b/.sphinx/html/_static/alabaster.css @@ -0,0 +1,708 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Georgia, serif; + font-size: 17px; + background-color: #fff; + color: #000; + margin: 0; + padding: 0; +} + + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; + font-size: 14px; + line-height: 1.5; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #fff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar { + max-height: 100%; + overflow-y: auto; +} + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 160px; +} + +div.sphinxsidebar .search > div { + display: table-cell; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +div.sphinxsidebar .badge { + border-bottom: none; +} + +div.sphinxsidebar .badge:hover { + border-bottom: none; +} + +/* To address an issue with donation coming after search */ +div.sphinxsidebar h3.donation { + margin-top: 10px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin-left: 0; + margin-right: 0; + margin-top: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Hide ugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/.sphinx/html/_static/basic.css b/.sphinx/html/_static/basic.css new file mode 100644 index 0000000..4157edf --- /dev/null +++ b/.sphinx/html/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: inherit; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/.sphinx/html/_static/custom.css b/.sphinx/html/_static/custom.css new file mode 100644 index 0000000..2a924f1 --- /dev/null +++ b/.sphinx/html/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/.sphinx/html/_static/doctools.js b/.sphinx/html/_static/doctools.js new file mode 100644 index 0000000..d06a71d --- /dev/null +++ b/.sphinx/html/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/.sphinx/html/_static/documentation_options.js b/.sphinx/html/_static/documentation_options.js new file mode 100644 index 0000000..fd1b42a --- /dev/null +++ b/.sphinx/html/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: 'v0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/.sphinx/html/_static/file.png b/.sphinx/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHDfiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/.sphinx/html/_static/language_data.js b/.sphinx/html/_static/language_data.js new file mode 100644 index 0000000..250f566 --- /dev/null +++ b/.sphinx/html/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, is available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/.sphinx/html/_static/minus.png b/.sphinx/html/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/.sphinx/html/_static/plus.png b/.sphinx/html/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/.sphinx/html/_static/pygments.css b/.sphinx/html/_static/pygments.css new file mode 100644 index 0000000..04a4174 --- /dev/null +++ b/.sphinx/html/_static/pygments.css @@ -0,0 +1,84 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #8f5902; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #000000 } /* Generic */ +.highlight .k { color: #004461; font-weight: bold } /* Keyword */ +.highlight .l { color: #000000 } /* Literal */ +.highlight .n { color: #000000 } /* Name */ +.highlight .o { color: #582800 } /* Operator */ +.highlight .x { color: #000000 } /* Other */ +.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ +.highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #8f5902 } /* Comment.Preproc */ +.highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .ges { color: #000000 } /* Generic.EmphStrong */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #745334 } /* Generic.Prompt */ +.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #000000 } /* Literal.Date */ +.highlight .m { color: #990000 } /* Literal.Number */ +.highlight .s { color: #4e9a06 } /* Literal.String */ +.highlight .na { color: #c4a000 } /* Name.Attribute */ +.highlight .nb { color: #004461 } /* Name.Builtin */ +.highlight .nc { color: #000000 } /* Name.Class */ +.highlight .no { color: #000000 } /* Name.Constant */ +.highlight .nd { color: #888888 } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #000000 } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #000000 } /* Name.Namespace */ +.highlight .nx { color: #000000 } /* Name.Other */ +.highlight .py { color: #000000 } /* Name.Property */ +.highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #000000 } /* Name.Variable */ +.highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ +.highlight .pm { color: #000000; font-weight: bold } /* Punctuation.Marker */ +.highlight .w { color: #f8f8f8 } /* Text.Whitespace */ +.highlight .mb { color: #990000 } /* Literal.Number.Bin */ +.highlight .mf { color: #990000 } /* Literal.Number.Float */ +.highlight .mh { color: #990000 } /* Literal.Number.Hex */ +.highlight .mi { color: #990000 } /* Literal.Number.Integer */ +.highlight .mo { color: #990000 } /* Literal.Number.Oct */ +.highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ +.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ +.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ +.highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ +.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ +.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ +.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ +.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ +.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ +.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ +.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ +.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #000000 } /* Name.Function.Magic */ +.highlight .vc { color: #000000 } /* Name.Variable.Class */ +.highlight .vg { color: #000000 } /* Name.Variable.Global */ +.highlight .vi { color: #000000 } /* Name.Variable.Instance */ +.highlight .vm { color: #000000 } /* Name.Variable.Magic */ +.highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/.sphinx/html/_static/searchtools.js b/.sphinx/html/_static/searchtools.js new file mode 100644 index 0000000..7918c3f --- /dev/null +++ b/.sphinx/html/_static/searchtools.js @@ -0,0 +1,574 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/.sphinx/html/_static/sphinx_highlight.js b/.sphinx/html/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/.sphinx/html/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/.sphinx/html/genindex.html b/.sphinx/html/genindex.html new file mode 100644 index 0000000..2ce090d --- /dev/null +++ b/.sphinx/html/genindex.html @@ -0,0 +1,300 @@ + + + + + + + Index — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ + +

Index

+ +
+ M + | S + +
+

M

+ + +
+ +

S

+ + + +
    +
  • + src + +
  • +
  • + src.app + +
  • +
  • + src.lib + +
  • +
  • + src.lib.burn_severity + +
  • +
  • + src.lib.query_rap + +
  • +
  • + src.lib.query_sentinel + +
  • +
  • + src.lib.query_soil + +
  • +
  • + src.lib.titiler_algorithms + +
  • +
  • + src.routers + +
  • +
  • + src.routers.check + +
  • +
    +
  • + src.routers.check.connectivity + +
  • +
  • + src.routers.check.dns + +
  • +
  • + src.routers.check.health + +
  • +
  • + src.routers.check.sentry_error + +
  • +
  • + src.routers.dependencies + +
  • +
  • + src.util + +
  • +
  • + src.util.cloud_static_io + +
  • +
  • + src.util.gcp_secrets + +
  • +
  • + src.util.ingest_burn_zip + +
  • +
  • + src.util.raster_to_poly + +
  • +
+ + + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/index.html b/.sphinx/html/index.html new file mode 100644 index 0000000..2078738 --- /dev/null +++ b/.sphinx/html/index.html @@ -0,0 +1,171 @@ + + + + + + + + Welcome to dse-fire-recovery’s documentation! — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

Welcome to dse-fire-recovery’s documentation!

+
+
+
+
+

Indices and tables

+ + + + +
+
+
+src.routers.dependencies.get_cloud_logger()
+

Returns a Cloud Logging logger instance.

+
+
Returns:

google.cloud.logging.logger.Logger: The Cloud Logging logger instance.

+
+
+
+ +
+
+src.routers.dependencies.get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger))
+

Get an instance of CloudStaticIOClient.

+
+
Args:

logger (Logger): The logger instance to use for logging.

+
+
Returns:

CloudStaticIOClient: An instance of CloudStaticIOClient.

+
+
+
+ +
+
+src.routers.dependencies.get_manifest(cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), logger: Logger = Depends(get_cloud_logger))
+

Get the manifest from the cloud static IO client.

+
+
Args:

cloud_static_io_client (CloudStaticIOClient): The cloud static IO client instance, used to download the manifest from clouds storage. +logger: The logger instance.

+
+
Returns:

dict: The manifest from the cloud storage.

+
+
+
+ +
+
+src.routers.dependencies.get_mapbox_secret()
+

Retrieves the Mapbox secret from GCP.

+
+
Returns:

str: The Mapbox secret.

+
+
+
+ +
+
+src.routers.dependencies.init_sentry(logger: Logger = Depends(get_cloud_logger))
+

Initializes the Sentry client.

+
+
Args:

logger (Logger): The logger object used for logging.

+
+
Returns:

None

+
+
+
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/objects.inv b/.sphinx/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..70e538d7ed1c33d69ac73dfe4d71c50ad6796060 GIT binary patch literal 732 zcmV<20wet+AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkpb7d`N zX>w&Pa%E$0c4cyT3L_v^WpZ8b#rNMXCQiPX<{x4c-obgO^%x|6vy}P6t8Nx&~`gF=%Ug{ zU37^Y^BSxgo3YKPbk%G0dOb;<5GD}(#SZ-bACY7DVGXa@FyJsf^|~D;;U{d^=(HXC zo|);RodtF0dij2qR3ET{%eoY&w9yM<#`-iG)mVBZBixja*~p`zFC&?$F>J^OzDYA- zmXs5UWNG4}&&6s{9_h8wI47KtWEv*V+V4%*+kEWa6O1={bYwDlH&H5}<{j~$WieN2 zKCI?iDdJANW=~JtS5VU`dnDb<3qm_$(shUSalICV@~i!=z^#RM?Ct>2me5GL(Ue&uGH~Jq0VGobA6xv z7DGYogCibonDsl}INc0dP6f$Z!!G#Igx0lBt>##L-UnXH=r16IkEC2jF<1 zddE~uYoG|BML%H2T#QX2NQPv|8;x + + + + + + Python Module Index — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ + +

Python Module Index

+ +
+ s +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ s
+ src +
    + src.app +
    + src.lib +
    + src.lib.burn_severity +
    + src.lib.query_rap +
    + src.lib.query_sentinel +
    + src.lib.query_soil +
    + src.lib.titiler_algorithms +
    + src.routers +
    + src.routers.check +
    + src.routers.check.connectivity +
    + src.routers.check.dns +
    + src.routers.check.health +
    + src.routers.check.sentry_error +
    + src.routers.dependencies +
    + src.util +
    + src.util.cloud_static_io +
    + src.util.gcp_secrets +
    + src.util.ingest_burn_zip +
    + src.util.raster_to_poly +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/search.html b/.sphinx/html/search.html new file mode 100644 index 0000000..e05e320 --- /dev/null +++ b/.sphinx/html/search.html @@ -0,0 +1,118 @@ + + + + + + + Search — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + + +
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/searchindex.js b/.sphinx/html/searchindex.js new file mode 100644 index 0000000..7888d2b --- /dev/null +++ b/.sphinx/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20], "search": 20, "page": 20, "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19], "class": [5, 7, 16], "src": 20, "router": 20, "depend": 20, "get_cloud_logg": 20, "get": 20, "logger": 20, "from": 20, "googl": 20, "cloud": 20, "log": 20, "servic": [], "return": 20, "A": [], "type": [], "get_cloud_static_io_cli": 20, "an": 20, "instanc": 20, "cloudstaticiocli": 20, "arg": 20, "The": 20, "us": 20, "get_manifest": 20, "cloud_static_io_cli": 20, "manifest": 20, "static": 20, "io": 20, "client": 20, "paramet": [], "download": 20, "storag": 20, "obtain": [], "init_sentri": 20, "initi": 20, "sentri": 20, "object": 20, "none": 20, "dict": 20, "get_mapbox_secret": 20, "retriev": 20, "mapbox": 20, "secret": 20, "gcp": 20, "str": 20}, "objects": {"src": [[1, 0, 0, "-", "app"], [2, 0, 0, "-", "lib"], [8, 0, 0, "-", "routers"], [15, 0, 0, "-", "util"]], "src.lib": [[3, 0, 0, "-", "burn_severity"], [4, 0, 0, "-", "query_rap"], [5, 0, 0, "-", "query_sentinel"], [6, 0, 0, "-", "query_soil"], [7, 0, 0, "-", "titiler_algorithms"]], "src.routers": [[9, 0, 0, "-", "check"], [14, 0, 0, "-", "dependencies"]], "src.routers.check": [[10, 0, 0, "-", "connectivity"], [11, 0, 0, "-", "dns"], [12, 0, 0, "-", "health"], [13, 0, 0, "-", "sentry_error"]], "src.util": [[16, 0, 0, "-", "cloud_static_io"], [17, 0, 0, "-", "gcp_secrets"], [18, 0, 0, "-", "ingest_burn_zip"], [19, 0, 0, "-", "raster_to_poly"]]}, "objtypes": {"0": "py:module"}, "objnames": {"0": ["py", "module", "Python module"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "app": 1, "lib": [2, 3, 4, 5, 6, 7], "burn_sever": 3, "query_rap": 4, "query_sentinel": 5, "query_soil": 6, "titiler_algorithm": 7, "router": [8, 9, 10, 11, 12, 13, 14], "check": [9, 10, 11, 12, 13], "connect": 10, "dn": 11, "health": 12, "sentry_error": 13, "depend": 14, "util": [15, 16, 17, 18, 19], "cloud_static_io": 16, "gcp_secret": 17, "ingest_burn_zip": 18, "raster_to_poli": 19}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]]}, "indexentries": {"module": [[8, "module-src.routers"], [14, "module-src.routers.dependencies"]], "src.routers": [[8, "module-src.routers"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]]}}) \ No newline at end of file diff --git a/.sphinx/make.bat b/.sphinx/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/.sphinx/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/.sphinx/source/_autosummary/src.app.rst b/.sphinx/source/_autosummary/src.app.rst new file mode 100644 index 0000000..e227d4b --- /dev/null +++ b/.sphinx/source/_autosummary/src.app.rst @@ -0,0 +1,23 @@ +src.app +======= + +.. automodule:: src.app + + + + + + + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.lib.burn_severity.rst b/.sphinx/source/_autosummary/src.lib.burn_severity.rst new file mode 100644 index 0000000..aa48322 --- /dev/null +++ b/.sphinx/source/_autosummary/src.lib.burn_severity.rst @@ -0,0 +1,34 @@ +src.lib.burn\_severity +====================== + +.. automodule:: src.lib.burn_severity + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + calc_burn_metrics + calc_dnbr + calc_nbr + calc_rbr + calc_rdnbr + classify_burn + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.lib.query_rap.rst b/.sphinx/source/_autosummary/src.lib.query_rap.rst new file mode 100644 index 0000000..d412cc7 --- /dev/null +++ b/.sphinx/source/_autosummary/src.lib.query_rap.rst @@ -0,0 +1,29 @@ +src.lib.query\_rap +================== + +.. automodule:: src.lib.query_rap + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + rap_get_biomass + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.lib.query_sentinel.rst b/.sphinx/source/_autosummary/src.lib.query_sentinel.rst new file mode 100644 index 0000000..730e7aa --- /dev/null +++ b/.sphinx/source/_autosummary/src.lib.query_sentinel.rst @@ -0,0 +1,29 @@ +src.lib.query\_sentinel +======================= + +.. automodule:: src.lib.query_sentinel + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Sentinel2Client + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.lib.query_soil.rst b/.sphinx/source/_autosummary/src.lib.query_soil.rst new file mode 100644 index 0000000..ec35d02 --- /dev/null +++ b/.sphinx/source/_autosummary/src.lib.query_soil.rst @@ -0,0 +1,33 @@ +src.lib.query\_soil +=================== + +.. automodule:: src.lib.query_soil + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + edit_get_ecoclass_info + sdm_create_aoi + sdm_get_available_interpretations + sdm_get_ecoclassid_from_mu_info + sdm_get_esa_mapunitid_poly + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.lib.rst b/.sphinx/source/_autosummary/src.lib.rst new file mode 100644 index 0000000..2e2b0b6 --- /dev/null +++ b/.sphinx/source/_autosummary/src.lib.rst @@ -0,0 +1,35 @@ +src.lib +======= + +.. automodule:: src.lib + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.lib.burn_severity + src.lib.query_rap + src.lib.query_sentinel + src.lib.query_soil + src.lib.titiler_algorithms + diff --git a/.sphinx/source/_autosummary/src.lib.titiler_algorithms.rst b/.sphinx/source/_autosummary/src.lib.titiler_algorithms.rst new file mode 100644 index 0000000..54b8cca --- /dev/null +++ b/.sphinx/source/_autosummary/src.lib.titiler_algorithms.rst @@ -0,0 +1,36 @@ +src.lib.titiler\_algorithms +=========================== + +.. automodule:: src.lib.titiler_algorithms + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + convert_to_rgb + + + + + + .. rubric:: Classes + + .. autosummary:: + + CensorAndScale + Classify + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.routers.check.connectivity.rst b/.sphinx/source/_autosummary/src.routers.check.connectivity.rst new file mode 100644 index 0000000..d3bf9da --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.check.connectivity.rst @@ -0,0 +1,29 @@ +src.routers.check.connectivity +============================== + +.. automodule:: src.routers.check.connectivity + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + check_connectivity + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.routers.check.dns.rst b/.sphinx/source/_autosummary/src.routers.check.dns.rst new file mode 100644 index 0000000..5926358 --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.check.dns.rst @@ -0,0 +1,29 @@ +src.routers.check.dns +===================== + +.. automodule:: src.routers.check.dns + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + check_dns + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.routers.check.health.rst b/.sphinx/source/_autosummary/src.routers.check.health.rst new file mode 100644 index 0000000..1fe927a --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.check.health.rst @@ -0,0 +1,29 @@ +src.routers.check.health +======================== + +.. automodule:: src.routers.check.health + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + health + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.routers.check.rst b/.sphinx/source/_autosummary/src.routers.check.rst new file mode 100644 index 0000000..7550c65 --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.check.rst @@ -0,0 +1,34 @@ +src.routers.check +================= + +.. automodule:: src.routers.check + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.routers.check.connectivity + src.routers.check.dns + src.routers.check.health + src.routers.check.sentry_error + diff --git a/.sphinx/source/_autosummary/src.routers.check.sentry_error.rst b/.sphinx/source/_autosummary/src.routers.check.sentry_error.rst new file mode 100644 index 0000000..c127c2f --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.check.sentry_error.rst @@ -0,0 +1,29 @@ +src.routers.check.sentry\_error +=============================== + +.. automodule:: src.routers.check.sentry_error + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + trigger_error + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.routers.dependencies.rst b/.sphinx/source/_autosummary/src.routers.dependencies.rst new file mode 100644 index 0000000..696015d --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.dependencies.rst @@ -0,0 +1,33 @@ +src.routers.dependencies +======================== + +.. automodule:: src.routers.dependencies + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_cloud_logger + get_cloud_static_io_client + get_manifest + get_mapbox_secret + init_sentry + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.routers.rst b/.sphinx/source/_autosummary/src.routers.rst new file mode 100644 index 0000000..7b5ea81 --- /dev/null +++ b/.sphinx/source/_autosummary/src.routers.rst @@ -0,0 +1,32 @@ +src.routers +=========== + +.. automodule:: src.routers + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.routers.check + src.routers.dependencies + diff --git a/.sphinx/source/_autosummary/src.rst b/.sphinx/source/_autosummary/src.rst new file mode 100644 index 0000000..3b20aea --- /dev/null +++ b/.sphinx/source/_autosummary/src.rst @@ -0,0 +1,34 @@ +src +=== + +.. automodule:: src + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.app + src.lib + src.routers + src.util + diff --git a/.sphinx/source/_autosummary/src.util.cloud_static_io.rst b/.sphinx/source/_autosummary/src.util.cloud_static_io.rst new file mode 100644 index 0000000..b24bea9 --- /dev/null +++ b/.sphinx/source/_autosummary/src.util.cloud_static_io.rst @@ -0,0 +1,29 @@ +src.util.cloud\_static\_io +========================== + +.. automodule:: src.util.cloud_static_io + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + CloudStaticIOClient + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.util.gcp_secrets.rst b/.sphinx/source/_autosummary/src.util.gcp_secrets.rst new file mode 100644 index 0000000..6fab0d0 --- /dev/null +++ b/.sphinx/source/_autosummary/src.util.gcp_secrets.rst @@ -0,0 +1,30 @@ +src.util.gcp\_secrets +===================== + +.. automodule:: src.util.gcp_secrets + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_mapbox_secret + get_ssh_secret + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.util.ingest_burn_zip.rst b/.sphinx/source/_autosummary/src.util.ingest_burn_zip.rst new file mode 100644 index 0000000..17f7c07 --- /dev/null +++ b/.sphinx/source/_autosummary/src.util.ingest_burn_zip.rst @@ -0,0 +1,30 @@ +src.util.ingest\_burn\_zip +========================== + +.. automodule:: src.util.ingest_burn_zip + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + ingest_esri_zip_file + shp_to_geojson + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.util.raster_to_poly.rst b/.sphinx/source/_autosummary/src.util.raster_to_poly.rst new file mode 100644 index 0000000..0e22e0d --- /dev/null +++ b/.sphinx/source/_autosummary/src.util.raster_to_poly.rst @@ -0,0 +1,29 @@ +src.util.raster\_to\_poly +========================= + +.. automodule:: src.util.raster_to_poly + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + raster_mask_to_geojson + + + + + + + + + + + + + diff --git a/.sphinx/source/_autosummary/src.util.rst b/.sphinx/source/_autosummary/src.util.rst new file mode 100644 index 0000000..118c4ae --- /dev/null +++ b/.sphinx/source/_autosummary/src.util.rst @@ -0,0 +1,34 @@ +src.util +======== + +.. automodule:: src.util + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + src.util.cloud_static_io + src.util.gcp_secrets + src.util.ingest_burn_zip + src.util.raster_to_poly + diff --git a/.sphinx/source/conf.py b/.sphinx/source/conf.py new file mode 100644 index 0000000..538bd84 --- /dev/null +++ b/.sphinx/source/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import os +import sys + +print("Current working directory:", os.getcwd()) +print("System path:", sys.path) +sys.path.insert(0, "/workspace") + +import src + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "dse-fire-recovery" +copyright = "2024, Nick Gondek, DSE" +author = "Nick Gondek, DSE" +release = "v0" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", +] + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" +html_static_path = ["_static"] diff --git a/.sphinx/source/index.rst b/.sphinx/source/index.rst new file mode 100644 index 0000000..f005b8e --- /dev/null +++ b/.sphinx/source/index.rst @@ -0,0 +1,28 @@ +.. dse-fire-recovery documentation master file, created by + sphinx-quickstart on Thu Feb 15 20:41:59 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to dse-fire-recovery's documentation! +============================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. autosummary:: + :toctree: _autosummary + :recursive: + +.. automodule:: src.routers.dependencies + :members: + :no-index: \ No newline at end of file From f12907bb2a3915e8eda158372fd66cc1ee620bc2 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 21:54:51 +0000 Subject: [PATCH 130/175] clean up docstrings --- src/routers/dependencies.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index 06e684f..a73f84a 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -10,10 +10,10 @@ def get_cloud_logger(): """ - Get a logger from the Google Cloud Logging service + Returns a Cloud Logging logger instance. - :return: A logger from the Google Cloud Logging service - :rtype: Logger + Returns: + google.cloud.logging.logger.Logger: The Cloud Logging logger instance. """ logging_client = logging.Client(project="dse-nps") log_name = "burn-backend" @@ -31,7 +31,6 @@ def get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger)): Returns: CloudStaticIOClient: An instance of CloudStaticIOClient. - """ logger.log_text("Creating CloudStaticIOClient") return CloudStaticIOClient("burn-severity-backend", "s3") @@ -44,12 +43,12 @@ def get_manifest( """ Get the manifest from the cloud static IO client. - Parameters: - - cloud_static_io_client: The cloud static IO client instance, used to download the manifest from clouds storage. - - logger: The logger instance. + Args: + cloud_static_io_client (CloudStaticIOClient): The cloud static IO client instance, used to download the manifest from clouds storage. + logger: The logger instance. Returns: - - The manifest obtained from the cloud static IO client. + dict: The manifest from the cloud storage. """ logger.log_text("Getting manifest") manifest = cloud_static_io_client.get_manifest() @@ -84,4 +83,10 @@ def init_sentry(logger: Logger = Depends(get_cloud_logger)): def get_mapbox_secret(): + """ + Retrieves the Mapbox secret from GCP. + + Returns: + str: The Mapbox secret. + """ return gcp_get_mapbox_secret() From b520bfbbf299cffa617bf164d1bf21488c256c16 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 22:01:19 +0000 Subject: [PATCH 131/175] make docs conform in burn_severity --- src/lib/burn_severity.py | 85 +++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/src/lib/burn_severity.py b/src/lib/burn_severity.py index ac2c0c0..8d9bbdf 100644 --- a/src/lib/burn_severity.py +++ b/src/lib/burn_severity.py @@ -5,11 +5,14 @@ def calc_nbr(band_nir, band_swir): """ - This function takes an input the arrays of the bands from the read_band_image - function and returns the Normalized Burn ratio (NBR) - input: band_nir array (n x m) array of first band image e.g B8A - band_swir array (n x m) array of second band image e.g. B12 - output: nbr array (n x m) normalized burn ratio + Get the Normalized Burn Ratio (NBR) from the input arrays of NIR and SWIR bands. + + Args: + band_nir (xr.DataArray): Array of the first band image (e.g., B8A). + band_swir (xr.DataArray): Array of the second band image (e.g., B12). + + Returns: + array: Normalized Burn Ratio (NBR). """ nbr = (band_nir - band_swir) / (band_nir + band_swir) return nbr @@ -17,10 +20,14 @@ def calc_nbr(band_nir, band_swir): def calc_dnbr(nbr_prefire, nbr_postfire): """ - This function takes as input the pre- and post-fire NBR and returns the dNBR - input: nbr_prefire array (n x m) pre-fire NBR - nbr_postfire array (n x m) post-fire NBR - output: dnbr array (n x m) dNBR + Get the difference Normalized Burn Ratio (dNBR) from the pre-fire and post-fire NBR. + + Args: + nbr_prefire (xr.DataArray): Pre-fire NBR. + nbr_postfire (xr.DataArray): Post-fire NBR. + + Returns: + array: Difference Normalized Burn Ratio (dNBR). """ dnbr = nbr_prefire - nbr_postfire return dnbr @@ -28,10 +35,14 @@ def calc_dnbr(nbr_prefire, nbr_postfire): def calc_rdnbr(dnbr, nbr_prefire): """ - This function takes as input the dNBR and prefire NBR, and returns the relative dNBR - input: dnbr array (n x m) dNBR - nbr_prefire array (n x m) pre-fire NBR - output: rdnbr array (n x m) relative dNBR + Get the relative difference Normalized Burn Ratio (rdNBR) from the dNBR and pre-fire NBR. + + Args: + dnbr (xr.DataArray): Difference Normalized Burn Ratio (dNBR). + nbr_prefire (xr.DataArray): Pre-fire NBR. + + Returns: + array: Relative difference Normalized Burn Ratio (rdNBR). """ rdnbr = dnbr / np.abs(np.sqrt(nbr_prefire)) return rdnbr @@ -39,10 +50,14 @@ def calc_rdnbr(dnbr, nbr_prefire): def calc_rbr(dnbr, nbr_prefire): """ - This function takes as input the dNBR and prefire NBR, and returns the relative burn ratio - input: dnbr array (n x m) dNBR - nbr_prefire array (n x m) pre-fire NBR - output: rbr array (n x m) relative burn ratio + Get the relative burn ratio (rBR) from the dNBR and pre-fire NBR. + + Args: + dnbr (xr.DataArray): Difference Normalized Burn Ratio (dNBR). + nbr_prefire (xr.DataArray): Pre-fire NBR. + + Returns: + array: Relative burn ratio (rBR). """ rbr = dnbr / (nbr_prefire + 1.001) return rbr @@ -50,17 +65,16 @@ def calc_rbr(dnbr, nbr_prefire): def calc_burn_metrics(prefire_nir, prefire_swir, postfire_nir, postfire_swir): """ - This function takes as input the pre- and post-fire NIR and SWIR bands and returns the - NBR, dNBR, rDNBR, and rBR - input: prefire_nir array (n x m) pre-fire NIR - prefire_swir array (n x m) pre-fire SWIR - postfire_nir array (n x m) post-fire NIR - postfire_swir array (n x m) post-fire SWIR - output: nbr_prefire array (n x m) normalized burn ratio - nbr_postfire array (n x m) normalized burn ratio - dnbr array (n x m) dNBR - rdnbr array (n x m) relative dNBR - rbr array (n x m) relative burn ratio + Get the NBR, dNBR, rdNBR, and rBR from the pre- and post-fire NIR and SWIR bands. + + Args: + prefire_nir (xr.DataArray): Pre-fire NIR. + prefire_swir (xr.DataArray): Pre-fire SWIR. + postfire_nir (xr.DataArray): Post-fire NIR. + postfire_swir (xr.DataArray): Post-fire SWIR. + + Returns: + xr.DataArray: Stack of NBR, dNBR, rdNBR, and rBR. """ nbr_prefire = calc_nbr(prefire_nir, prefire_swir) nbr_postfire = calc_nbr(postfire_nir, postfire_swir) @@ -68,9 +82,6 @@ def calc_burn_metrics(prefire_nir, prefire_swir, postfire_nir, postfire_swir): rdnbr = calc_rdnbr(dnbr, nbr_prefire) rbr = calc_rbr(dnbr, nbr_prefire) - # stack these arrays together, naming them by their source - # TODO [#22]: Look into other sources of useful info that come with satellite imagery - cloud cover, defective pixels, etc. - # Some of these might help us filter out bad data (especially as it relates to cloud occlusion) burn_stack = xr.concat( [nbr_prefire, nbr_postfire, dnbr, rdnbr, rbr], pd.Index( @@ -84,11 +95,15 @@ def calc_burn_metrics(prefire_nir, prefire_swir, postfire_nir, postfire_swir): def classify_burn(array, thresholds): """ - This function reclassifies an array - input: array xarray.DataArray input array - output: reclass xarray.DataArray reclassified array - """ + Reclassify an array based on the given thresholds. + + Args: + array (xr.DataArray): Input array. + thresholds (dict): Dictionary of thresholds and their corresponding values. + Returns: + xr.DataArray: Reclassified array. + """ reclass = xr.full_like(array, np.nan) for threshold, value in sorted(thresholds.items()): From 9f098d9506a5ddfa348ac78c091b088074b87a10 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 22:07:51 +0000 Subject: [PATCH 132/175] add sphinx commands to dev readme --- .devcontainer/README.md | 55 +-- .../src.lib.burn_severity.doctree | Bin 15321 -> 11166 bytes .sphinx/doctrees/environment.pickle | Bin 183596 -> 460885 bytes .sphinx/doctrees/index.doctree | Bin 22692 -> 5684 bytes .sphinx/doctrees/modules.doctree | Bin 0 -> 2706 bytes .sphinx/doctrees/src.doctree | Bin 0 -> 4204 bytes .sphinx/doctrees/src.lib.doctree | Bin 0 -> 115507 bytes .sphinx/doctrees/src.routers.check.doctree | Bin 0 -> 16067 bytes .sphinx/doctrees/src.routers.doctree | Bin 0 -> 21954 bytes .sphinx/doctrees/src.util.doctree | Bin 0 -> 33305 bytes .../_autosummary/src.lib.burn_severity.html | 36 +- .sphinx/html/_autosummary/src.lib.html | 22 -- .sphinx/html/_sources/index.rst.txt | 4 - .sphinx/html/_sources/modules.rst.txt | 7 + .sphinx/html/_sources/src.lib.rst.txt | 53 +++ .../html/_sources/src.routers.check.rst.txt | 45 +++ .sphinx/html/_sources/src.routers.rst.txt | 29 ++ .sphinx/html/_sources/src.rst.txt | 31 ++ .sphinx/html/_sources/src.util.rst.txt | 45 +++ .sphinx/html/genindex.html | 331 +++++++++++++--- .sphinx/html/index.html | 57 --- .sphinx/html/modules.html | 139 +++++++ .sphinx/html/objects.inv | Bin 732 -> 1419 bytes .sphinx/html/py-modindex.html | 40 +- .sphinx/html/searchindex.js | 2 +- .sphinx/html/src.html | 239 ++++++++++++ .sphinx/html/src.lib.html | 364 ++++++++++++++++++ .sphinx/html/src.routers.check.html | 138 +++++++ .sphinx/html/src.routers.html | 194 ++++++++++ .sphinx/html/src.util.html | 207 ++++++++++ .sphinx/source/index.rst | 4 - .sphinx/source/modules.rst | 7 + .sphinx/source/src.lib.rst | 53 +++ .sphinx/source/src.routers.check.rst | 45 +++ .sphinx/source/src.routers.rst | 29 ++ .sphinx/source/src.rst | 31 ++ .sphinx/source/src.util.rst | 45 +++ 37 files changed, 2037 insertions(+), 215 deletions(-) create mode 100644 .sphinx/doctrees/modules.doctree create mode 100644 .sphinx/doctrees/src.doctree create mode 100644 .sphinx/doctrees/src.lib.doctree create mode 100644 .sphinx/doctrees/src.routers.check.doctree create mode 100644 .sphinx/doctrees/src.routers.doctree create mode 100644 .sphinx/doctrees/src.util.doctree create mode 100644 .sphinx/html/_sources/modules.rst.txt create mode 100644 .sphinx/html/_sources/src.lib.rst.txt create mode 100644 .sphinx/html/_sources/src.routers.check.rst.txt create mode 100644 .sphinx/html/_sources/src.routers.rst.txt create mode 100644 .sphinx/html/_sources/src.rst.txt create mode 100644 .sphinx/html/_sources/src.util.rst.txt create mode 100644 .sphinx/html/modules.html create mode 100644 .sphinx/html/src.html create mode 100644 .sphinx/html/src.lib.html create mode 100644 .sphinx/html/src.routers.check.html create mode 100644 .sphinx/html/src.routers.html create mode 100644 .sphinx/html/src.util.html create mode 100644 .sphinx/source/modules.rst create mode 100644 .sphinx/source/src.lib.rst create mode 100644 .sphinx/source/src.routers.check.rst create mode 100644 .sphinx/source/src.routers.rst create mode 100644 .sphinx/source/src.rst create mode 100644 .sphinx/source/src.util.rst diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 349cd05..413563b 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,4 +1,5 @@ #### To serve tiles via TiTiler, pointed at S3 url from burn severity backend: + ``` http://localhost:8000/cog/map?url=https://burn-severity.s3.us-east-2.amazonaws.com/public/geology/rbr.tif&rescale=-.15,.15 ``` @@ -7,33 +8,43 @@ http://localhost:8000/cog/map?url=https://burn-severity.s3.us-east-2.amazonaws.c ### Dev Container -Within the `.devcontainer` directory, there are a few configurations that are used either by the local ![DevPod Client](https://devpod.sh/) or the VS Code `Dev Container` extension. +Within the `.devcontainer` directory, there are a few configurations that are used either by the local ![DevPod Client](https://devpod.sh/) or the VS Code `Dev Container` extension. The former allows you to deploy your dev container to cloud infrastructure without too much hassle, a la GitPod. Both are based on the `devcontainer.json` standard. Either approach will build a Docker container containing the repo itself and its dependencies defined below. Then, VSCode will SSH into that container, treating it as a remote entity, which can be a bit weird at first but is great for reproducibility of your environments across machines! - `.devcontainer/devcontainer.json` contains some launch configurations, most notably: - - includes VSCode extensions that are installed **within the container** upon build (your other extensions, most of which will be installed on your 'local' side, should still be there). - - allows for `postCreateCommands` which allow runtime logic to be performed. The initial iteration of this repo downloaded BARC data on build from a public gcs bucket, but could be anything. This is handled by `.devcontainer/init_runtime.sh`, which in turn executes files from `.devcontainer/runtime/`. -- `.devcontainer/Dockerfile` contains the build instructions for the dev container. Primary responsibilities here are to install necessary linux utilities (bash, curl, etc), setup cloud SDKs/CLIs and OpenTofu to manage those cloud resources, and build a conda environment to manage python dependencies. Note that **this Dockerfile build the dev environment, not the prod environment - so if you need a package to be running in prod, you will add it to the `Dockerfile` found in the root dir as well as this one**. - - by convention, files in `.devcontainer/prebuild` are run here - this means that their effects are baked into the resultant docker image and can take advantage of caching on subsequent runs. This has the benefit of saving time for chunky installs, at the expense of a larger docker image. + - includes VSCode extensions that are installed **within the container** upon build (your other extensions, most of which will be installed on your 'local' side, should still be there). + - allows for `postCreateCommands` which allow runtime logic to be performed. The initial iteration of this repo downloaded BARC data on build from a public gcs bucket, but could be anything. This is handled by `.devcontainer/init_runtime.sh`, which in turn executes files from `.devcontainer/runtime/`. + +- `.devcontainer/Dockerfile` contains the build instructions for the dev container. Primary responsibilities here are to install necessary linux utilities (bash, curl, etc), setup cloud SDKs/CLIs and OpenTofu to manage those cloud resources, and build a conda environment to manage python dependencies. Note that **this Dockerfile build the dev environment, not the prod environment - so if you need a package to be running in prod, you will add it to the `Dockerfile` found in the root dir as well as this one**. + + - by convention, files in `.devcontainer/prebuild` are run here - this means that their effects are baked into the resultant docker image and can take advantage of caching on subsequent runs. This has the benefit of saving time for chunky installs, at the expense of a larger docker image. + +- `devcontainer/dev_environment.yml` contains the Conda env requirements. As above, **this is just for the dev environment, if you need a package in prod, you must add to the `prod_environment.yaml` in the root dir**. + +### Generating Sphinx Docs -- `devcontainer/dev_environment.yml` contains the Conda env requirements. As above, **this is just for the dev environment, if you need a package in prod, you must add to the `prod_environment.yaml` in the root dir**. +``` +sphinx-apidoc -o .sphinx/source src +sphinx-build -M html .sphinx/source .sphinx +``` ### Debugging w/ VSCode For local development (inside or outside of a dev container, though the former is recommended), VSCode should detect the launch configuration (`.vscode/launch.json`), allowing for local breakpoints/stepthrough. Port configuration can be found within the same file (default is port `5050`). -### Deploying to Cloud +### Deploying to Cloud + +If you want to deploy to the cloud, you can use OpenTofu to do so - but you need to auth with both `AWS` and `GCP` to do so. -If you want to deploy to the cloud, you can use OpenTofu to do so - but you need to auth with both `AWS` and `GCP` to do so. ``` aws configure sso gcloud auth application-default login ``` -*Note*: This SSO auth process must be performed peridically, as the authentication token generated are short-lived (important, as the scope of this auth is broad for provisioning resources and could be used by nefarious actors). So, if you run into an credentials-related issue running any `tofu` command, you may need to re-auth. Both will provide you a URL to login via SSO. You can accept all defaults. +_Note_: This SSO auth process must be performed peridically, as the authentication token generated are short-lived (important, as the scope of this auth is broad for provisioning resources and could be used by nefarious actors). So, if you run into an credentials-related issue running any `tofu` command, you may need to re-auth. Both will provide you a URL to login via SSO. You can accept all defaults. Once this is done, run: @@ -43,28 +54,4 @@ tofu plan -out .terraform/tfplan tofu apply ".terraform/tfplan" ``` -As alluded to above in the Dev Container section, cloud deployments are pointed to `/prod.Dockerfile` and `/prod_environment.yml` - best practice to avoid the import of dev-related resources here (for eg, most visualization libraries, some pretty printing tools, etc) to keep deployments lighter. - -## Dev ToDo - -- Investigate DevPod's ability to deploy a browser-based instance of VSCode - if not, can we simply spin one up on a VM to emulate gitpod? - -- Investigate deploying DevPod to remote cloud providers (very very briefly attempted this but ran into a network issue, see below) - -- Address IPv4 issues with opentofu deployment / state refresh (this is a Nick's house specific issue with Sonic ISP) - -- Store OpenTofu state in remote bucket (best practice to avoid multiple state files from being out of sync) - -- Validate Cloud Build workflow on Dev branch - -- Implement basic branch protections - -- VSCode inside dev container doesn't properly launch web broswer for SSO auth - -- Catalog any/all analytical products with Source Coop - -- Sphinx to auto generate docs from docstrings - -- Look into more platform agnostic secrets management (more so for DSE in general) - - Pass w/ tofu on deployment? - - Use vault-style, a la 1Password? +As alluded to above in the Dev Container section, cloud deployments are pointed to `/prod.Dockerfile` and `/prod_environment.yml` - best practice to avoid the import of dev-related resources here (for eg, most visualization libraries, some pretty printing tools, etc) to keep deployments lighter. diff --git a/.sphinx/doctrees/_autosummary/src.lib.burn_severity.doctree b/.sphinx/doctrees/_autosummary/src.lib.burn_severity.doctree index c4ed5ac6bfc3c57834982dc0c3602c1309450391..386936815dbcae40da422f1775e2d0287d745d22 100644 GIT binary patch delta 1295 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 J3N4~W{Xe(d8S(%C diff --git a/.sphinx/doctrees/environment.pickle b/.sphinx/doctrees/environment.pickle index 7ef5062569c743c9bc40e589ec6d7b2ea960adc0..2ea00bff1d0f10b1b071d869b5e12ba05f1f3de1 100644 GIT binary patch literal 460885 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

~Y?W4I#UjjL34lyY- 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 M0Y!CTD+96r2ND*|BLDyZ literal 183596 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 buTdJGx##PJF{YhvjWod91I;TtTRZVTyC(7n diff --git a/.sphinx/doctrees/index.doctree b/.sphinx/doctrees/index.doctree index 041339d032c19726ced4858ffa957ec8ffab00d1..c57ca030d8e92c838153b4e136dbd56967cd50e7 100644 GIT binary patch delta 85 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 diff --git a/.sphinx/doctrees/modules.doctree b/.sphinx/doctrees/modules.doctree new file mode 100644 index 0000000000000000000000000000000000000000..844a1c317ca5750adef2df977cbe3d97de54dbef GIT binary patch literal 2706 zcmZ8jTWcIQ6n1Ry)w}CUl2SMC6I?8v@_b3utu6nnt1&X zXrXCq&|B31+fSp}@vaSvM@L8J`h7?Gqx0WC!@c^?AKQ`(wVu*ES2|}l_7vw?%C~Y(<=}g8p)+ULc0HQC zFA+L_hN66h-*fz4;P(o@C%)JCK}x8K5p^Q-RI|qW&+(#EuO!)t8}!3hO*W~%8+g2u#@RO`E5u2n% z;%FmDi1dBSoD*sVVx6{3SsJYz115a;4V4wz)KaahD_2%7%=z)$4^~VuL!CAOi<|<} zjus^rN68_9mn$rX&9X8Qt1;?KB9`MW9Cu=qZh}8sYVDMEY-{uKfgeZ0qRV8x)TRiK zOe}C!5V@cJjqAP}S=UDH9hW?>pnKn5AX8{wqi7cP{Zl=`|IiegQ2T;NBb>Lk; zp_S9*PUQCmur06n9qj0TTuNsa#4`#Vu-S(R(DE+U>z*t*n~k?oT; zL^4O-3o8;nk4Y{pUC5Y76J>A13vg_NWGt9alMF`3jT+g^DgRmgf)mS3O+==mwH*CE z!k|H)(h~SQjpPDs)Cwq+q7W|pa`M7YN>&mS1=C7pb-_kavY?1PGQ)q)oh$8+vl+y8 zTHZLW)l{3+EcnM!vkm~7#IXYHacD;$@{8x+evf)i%rd)YK1%M)f4wmerqYPlD`uvG zxn=DG?4NO0NH`lfx&VdpIFI}O9DpoH%gr_cQr|L|vnTdOIl3lXthmGv;60Q&;(lZc z3YPs|fz%d-dRVX1l1QD=bOreHj8nve#wW6&%5@2G`l>47BHrTlY;T4DE&@n|S^@)` zsc;tOgsm3$^h6QCD>Y42j92kh0A_G&x!^~3Q6$bh*P!>`?Ps7 z0IYJEX4c(8 zhPMe|5&Ti6r9@$o1FnqT&yD;OlK&Wf1^V#{1CQ`-vgmhm*ba^z@lh+TTXxjEC;*XF S?A9=kv#l8e@wToO)BgbxkT;nC literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/src.doctree b/.sphinx/doctrees/src.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4e61fe4f23d96dcd04b10daea1de0def84cbdf45 GIT binary patch literal 4204 zcmb7HNsA=M74DhtTD!Y?k!CQSX}Jd@tF=^>4mt=4j6ugi1(IyvL=u%5RS{E}899-W z)71xo4cc0SPYR#xA7JKS5D5G`<{;2Tpr7I|B66!5&Dhh>Q2Ane?|a|d{?hq7Io@%9 z`Y6vBPuCL?MX88rZgf8qVUf^OnQzVOe>I<(3q6qJG8csmsnI*w5i%Y}lBVWUqephT z;&DDH&-Zc~DlXFcIP0)3>wRi2*g$s_S21lZ>HS=W=NSoCWJ!(PhFyDqq-*>9=HeH| zYtzjL-d2X4=s{2`%XW3Q)N!WwVxCenE9OKGiBgi!3kBP(ExSC*t=_#bCV37aes@xm z_+R~~$eNEi)RJ^z_Y}IP$kHwucEs+m19q3~vNL^edMV^8hXHgt@mNm18dDfHkva5y z<^IiL&h|Z+4I0OlK<|H2%u5JzkHPItS!p`necQh~W3O22*l%DGy9dyIi~m>g{~G>Z zhq~-dr{>siXKz7?4wM+cc_PU~kwz^ic>k~+*eF6JY1={N+Or;Fcl0g{1)gw;Sa_`u%PVR@KP{3ul{7N7@cH0(rSVNs5 zsFR4Ou+X{axvkrxhB)fQi1V;ToTD}-dI*E67aMyvrOz=3mpv~OMwS}D`P@HI*|aTg z?E2e_+4W7!u2ZP8qdPo}==$kscoyR~n6CFS!lltiUfuW4OZAZ#y<}VoSa+rjv2^rKN9o>oOyjbG7(It^7P{R&MNdn0L zE()nN%6zRyJOxGpO+yi3m(hblEynMTbB^D4Hx~mcrH}z5X%wTn!0oUtRLChF>DDbB zcZ<_tFq{{*g8W4~0eG^4j zza(iWWE7x6Q)JLIw8ej>y9lxc%mgAl0#{Mi*N9X-B83t`BhoiH=yezsNfy);1h7uP zk(6XgVte6{lehq|?r%g{PK!tc>%>y(n65LC)1ZCQu_RQ%Bc5|l(W4;ZIhn`SkW$ND z6&eV$0~nJd7+jp;(!+9FP_-udw*7@iaw=Uc($wA-!R7mCE8r^s35uTB_yTj%C5RaF zge&vm@oRdN(JTNc=uOhl0d`;|a{}FMaOht#rLz3J=@jC8l3gh#(ut7EsnNIWPNx8N z28t$dkAge;kdI$|`yJppl8bQbdMjzJ|6O%Gf|XdxuBe=Fsuu1IB!DSXNsPw^ip&v3 zk?p?v`V5>*f;v2F0|M9iqN_U2uTn+U0pm*+;~)Ipi-o2BL7or<+QR~Iodc+s+_(P> zVi6MG_35*a5$FTQ`*;J&4nmw>6&aqelZNZ$$~=I!4-%d(z=0|;oZ3`@24!m>Pb_(3 zL3}jY@nB`Z8T5z|Lih?QFiI63xfNp9DxeABae$%7CR(TVNjcVltntF9wQIdg(?@p9 z(0!jy5Wk=z8VZi!P=O+HNs*o~Vw^xJP)nifMG8l=Yty$r-H`gh17h5^s6V^t^v_9OeD&;z?eU1d<&P6j_;v-FyMbPd2Ni%X6`)TO^DmH>y?${lO3Hd-mkP$GGQNjlz@vONBSLR0u#Mr1IdG z^suv;)xq@+#k|mmZHhb04?~VXu}*^@yu@a*YM*y*TjKY@27CWNvms1x4YiT(?jBG_Hlc#NSiuvTfT#;0> literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/src.lib.doctree b/.sphinx/doctrees/src.lib.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b6306a1c653196748b64716e36fb5aec11e9461f GIT binary patch literal 115507 zcmeHw378y5d8n>kX;;$fuq_|5D`(3?94KU zq%{_KCgBitf&*g+XW((YKpsa(AOyddD?lIwoB$t30s$Q0g#?TPB#^|(|JTuVboW2i zJ+o2{>-)CV(_K~nS^rU8$Nw6-cgd2aOYmQ?t~Fn&){jmW%jJ5b?6tbVs&b>$ne*!H z?yI^x?(QDz=7W)D@kp!DX_mZhumnn!D%D!K>D9XjyTKS1Z&z!rNl|}g%PY03je1zU zGE^C^tUTDwS4M)NcC}sev{!&Yvou+&PUq%}r8|nVUKfi($rU$yM-hc+ffsc-R>Si} zz@go(YzRgQA<~uQ!LYz}f3Tuft$W@5o#|pQT5PwQ)#*+fXhfQpSIaHLe`TpwY_;Ga z+z-`@bNp^qxihbROap37ZwAYBf|c#!EWYYi)>SrF)>O`}EU#<{&cEnLqj^UQi103& zQ&}&62ctK2rso>vP7Uy2oY-VM60`&0ogDD~ z%2p(=at_c`ITtv$4gSuDzwPk11Msapmf<)=C9rbz#l}v`el0j$FQ47wnG5cSkqa9*gN@q_r-E`;uS< z7?4tgw|QXu7``dAs?c-Vou**`o*IFYpNGy?ZF+@zwK<-XKUzoFuSTn_y_3K2oi5GAv!EKsR0n9G~GkFmcuIxsW^9xg8`C1_A`fRQ-6EEO6 zIc~jfDeDg-j8!(DHbi71Srqd|ap$!ZcUB>HIBQlR&?r|b-O9b#EncHoPfgo@)A+jA1ai8kQ(I&v!T2;L?DjMac?&T*3%GcA49a%i2rK} z#JxnXhk6v8IoGMRs|5_93Sqy1pV04)qc;9dw3})zc33O{aTfHEE`$@%`5V(dgoa(y z5)O+%3&~B6vuXd~JpNFh^{foO02;x(2Wlo70 zvHT8ZV(+SK;dTWGSU?UlOURu@sZnq1v9Ao#FnAq7F3dKI^A+7XJo8$wooiRT-2S~c zj_1nk+T{Ng>*ZW??~S>cW@C;OflfG)!>6bx6X?fs`=5FvdvepW;AR@$wI;c)ZweNn zWu;xaY3(vYNPj1m-m=IJsj}Ih5LkgkMuK%^2m|U>OeVP6DRZwfic_sZg=*WI)8+HQ ztDCc}sZnYFz?S6lN1KyRDz=MPH=D(U9aFg*KrB)ilVM5h!pTKZUQkvuIMK4YkhQY$ z4Tpt!%S;JS>sElXGYA@D1W6?kzm#D2rxNVbnPB~^@V~9stppJ~1#TJpM-^u^esa$H zyP#C)Ki>OajJc9`|K;#jQZAyvSnBDJ0gCP`rMv>9{9GnwGH>*6M;KBeG}9hUG5u?> z=oSe8)o33B;9m>>g?ch|qX*G{9eShQ0jF{|wIRXA@HNqda+{d_au+E|#u&HI`HMKhz}{+@LEymR00_T>5ppPCi&RzW|BWOsCF=rmPU8+?Xp?>s|lD z&{nV#!j$&Hyw~;r2!FC%MgLuIOz<&;

$L*9H~F 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 C7Oxoq literal 0 HcmV?d00001 diff --git a/.sphinx/doctrees/src.routers.doctree b/.sphinx/doctrees/src.routers.doctree new file mode 100644 index 0000000000000000000000000000000000000000..459c75e4267f6eba513bd96b22e0e0f2baa20e91 GIT binary patch literal 21954 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 CBS$0v literal 0 HcmV?d00001 diff --git a/.sphinx/html/_autosummary/src.lib.burn_severity.html b/.sphinx/html/_autosummary/src.lib.burn_severity.html index bae7897..62aa76d 100644 --- a/.sphinx/html/_autosummary/src.lib.burn_severity.html +++ b/.sphinx/html/_autosummary/src.lib.burn_severity.html @@ -13,8 +13,6 @@ - - @@ -38,22 +36,22 @@ - + - + - + - + - + - +

calc_burn_metrics(prefire_nir, prefire_swir, ...)

This function takes as input the pre- and post-fire NIR and SWIR bands and returns the NBR, dNBR, rDNBR, and rBR input: prefire_nir array (n x m) pre-fire NIR prefire_swir array (n x m) pre-fire SWIR postfire_nir array (n x m) post-fire NIR postfire_swir array (n x m) post-fire SWIR output: nbr_prefire array (n x m) normalized burn ratio nbr_postfire array (n x m) normalized burn ratio dnbr array (n x m) dNBR rdnbr array (n x m) relative dNBR rbr array (n x m) relative burn ratio

Get the NBR, dNBR, rdNBR, and rBR from the pre- and post-fire NIR and SWIR bands.

calc_dnbr(nbr_prefire, nbr_postfire)

This function takes as input the pre- and post-fire NBR and returns the dNBR input: nbr_prefire array (n x m) pre-fire NBR nbr_postfire array (n x m) post-fire NBR output: dnbr array (n x m) dNBR

Get the difference Normalized Burn Ratio (dNBR) from the pre-fire and post-fire NBR.

calc_nbr(band_nir, band_swir)

This function takes an input the arrays of the bands from the read_band_image function and returns the Normalized Burn ratio (NBR) input: band_nir array (n x m) array of first band image e.g B8A band_swir array (n x m) array of second band image e.g. B12 output: nbr array (n x m) normalized burn ratio.

Get the Normalized Burn Ratio (NBR) from the input arrays of NIR and SWIR bands.

calc_rbr(dnbr, nbr_prefire)

This function takes as input the dNBR and prefire NBR, and returns the relative burn ratio input: dnbr array (n x m) dNBR nbr_prefire array (n x m) pre-fire NBR output: rbr array (n x m) relative burn ratio

Get the relative burn ratio (rBR) from the dNBR and pre-fire NBR.

calc_rdnbr(dnbr, nbr_prefire)

This function takes as input the dNBR and prefire NBR, and returns the relative dNBR input: dnbr array (n x m) dNBR nbr_prefire array (n x m) pre-fire NBR output: rdnbr array (n x m) relative dNBR

Get the relative difference Normalized Burn Ratio (rdNBR) from the dNBR and pre-fire NBR.

classify_burn(array, thresholds)

This function reclassifies an array input: array xarray.DataArray input array output: reclass xarray.DataArray reclassified array

Reclassify an array based on the given thresholds.

@@ -76,33 +74,11 @@

dse-fire-recovery

Navigation

-

Related Topics

diff --git a/.sphinx/html/_autosummary/src.lib.html b/.sphinx/html/_autosummary/src.lib.html index 01dbbec..65a34ef 100644 --- a/.sphinx/html/_autosummary/src.lib.html +++ b/.sphinx/html/_autosummary/src.lib.html @@ -13,8 +13,6 @@ - - @@ -73,31 +71,11 @@

dse-fire-recovery

Navigation

-

Related Topics

diff --git a/.sphinx/html/_sources/index.rst.txt b/.sphinx/html/_sources/index.rst.txt index f005b8e..e3d13c7 100644 --- a/.sphinx/html/_sources/index.rst.txt +++ b/.sphinx/html/_sources/index.rst.txt @@ -22,7 +22,3 @@ Indices and tables .. autosummary:: :toctree: _autosummary :recursive: - -.. automodule:: src.routers.dependencies - :members: - :no-index: \ No newline at end of file diff --git a/.sphinx/html/_sources/modules.rst.txt b/.sphinx/html/_sources/modules.rst.txt new file mode 100644 index 0000000..e9ff8ac --- /dev/null +++ b/.sphinx/html/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +src +=== + +.. toctree:: + :maxdepth: 4 + + src diff --git a/.sphinx/html/_sources/src.lib.rst.txt b/.sphinx/html/_sources/src.lib.rst.txt new file mode 100644 index 0000000..8a86c2e --- /dev/null +++ b/.sphinx/html/_sources/src.lib.rst.txt @@ -0,0 +1,53 @@ +src.lib package +=============== + +Submodules +---------- + +src.lib.burn\_severity module +----------------------------- + +.. automodule:: src.lib.burn_severity + :members: + :undoc-members: + :show-inheritance: + +src.lib.query\_rap module +------------------------- + +.. automodule:: src.lib.query_rap + :members: + :undoc-members: + :show-inheritance: + +src.lib.query\_sentinel module +------------------------------ + +.. automodule:: src.lib.query_sentinel + :members: + :undoc-members: + :show-inheritance: + +src.lib.query\_soil module +-------------------------- + +.. automodule:: src.lib.query_soil + :members: + :undoc-members: + :show-inheritance: + +src.lib.titiler\_algorithms module +---------------------------------- + +.. automodule:: src.lib.titiler_algorithms + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.lib + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/html/_sources/src.routers.check.rst.txt b/.sphinx/html/_sources/src.routers.check.rst.txt new file mode 100644 index 0000000..6376ac3 --- /dev/null +++ b/.sphinx/html/_sources/src.routers.check.rst.txt @@ -0,0 +1,45 @@ +src.routers.check package +========================= + +Submodules +---------- + +src.routers.check.connectivity module +------------------------------------- + +.. automodule:: src.routers.check.connectivity + :members: + :undoc-members: + :show-inheritance: + +src.routers.check.dns module +---------------------------- + +.. automodule:: src.routers.check.dns + :members: + :undoc-members: + :show-inheritance: + +src.routers.check.health module +------------------------------- + +.. automodule:: src.routers.check.health + :members: + :undoc-members: + :show-inheritance: + +src.routers.check.sentry\_error module +-------------------------------------- + +.. automodule:: src.routers.check.sentry_error + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.routers.check + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/html/_sources/src.routers.rst.txt b/.sphinx/html/_sources/src.routers.rst.txt new file mode 100644 index 0000000..32a8d6f --- /dev/null +++ b/.sphinx/html/_sources/src.routers.rst.txt @@ -0,0 +1,29 @@ +src.routers package +=================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + src.routers.check + +Submodules +---------- + +src.routers.dependencies module +------------------------------- + +.. automodule:: src.routers.dependencies + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.routers + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/html/_sources/src.rst.txt b/.sphinx/html/_sources/src.rst.txt new file mode 100644 index 0000000..1470781 --- /dev/null +++ b/.sphinx/html/_sources/src.rst.txt @@ -0,0 +1,31 @@ +src package +=========== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + src.lib + src.routers + src.util + +Submodules +---------- + +src.app module +-------------- + +.. automodule:: src.app + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/html/_sources/src.util.rst.txt b/.sphinx/html/_sources/src.util.rst.txt new file mode 100644 index 0000000..9774500 --- /dev/null +++ b/.sphinx/html/_sources/src.util.rst.txt @@ -0,0 +1,45 @@ +src.util package +================ + +Submodules +---------- + +src.util.cloud\_static\_io module +--------------------------------- + +.. automodule:: src.util.cloud_static_io + :members: + :undoc-members: + :show-inheritance: + +src.util.gcp\_secrets module +---------------------------- + +.. automodule:: src.util.gcp_secrets + :members: + :undoc-members: + :show-inheritance: + +src.util.ingest\_burn\_zip module +--------------------------------- + +.. automodule:: src.util.ingest_burn_zip + :members: + :undoc-members: + :show-inheritance: + +src.util.raster\_to\_poly module +-------------------------------- + +.. automodule:: src.util.raster_to_poly + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.util + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/html/genindex.html b/.sphinx/html/genindex.html index 2ce090d..e0b2208 100644 --- a/.sphinx/html/genindex.html +++ b/.sphinx/html/genindex.html @@ -33,209 +33,454 @@

Index

- M + A + | C + | D + | E + | G + | H + | I + | L + | M + | Q + | R | S + | T + | U + | V
+

A

+ + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + +
+ +

G

+ + + +
+ +

H

+ + +
+ +

I

+ + + +
+ +

L

+ + +
+

M

+

Q

+ + +
+ +

R

+ + + +
+

S

+ -
+

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + +
+ diff --git a/.sphinx/html/index.html b/.sphinx/html/index.html index 2078738..043d397 100644 --- a/.sphinx/html/index.html +++ b/.sphinx/html/index.html @@ -46,63 +46,6 @@

Indices and tables -
-src.routers.dependencies.get_cloud_logger()
-

Returns a Cloud Logging logger instance.

-
-
Returns:

google.cloud.logging.logger.Logger: The Cloud Logging logger instance.

-
-
-
- -
-
-src.routers.dependencies.get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger))
-

Get an instance of CloudStaticIOClient.

-
-
Args:

logger (Logger): The logger instance to use for logging.

-
-
Returns:

CloudStaticIOClient: An instance of CloudStaticIOClient.

-
-
-
- -
-
-src.routers.dependencies.get_manifest(cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), logger: Logger = Depends(get_cloud_logger))
-

Get the manifest from the cloud static IO client.

-
-
Args:

cloud_static_io_client (CloudStaticIOClient): The cloud static IO client instance, used to download the manifest from clouds storage. -logger: The logger instance.

-
-
Returns:

dict: The manifest from the cloud storage.

-
-
-
- -
-
-src.routers.dependencies.get_mapbox_secret()
-

Retrieves the Mapbox secret from GCP.

-
-
Returns:

str: The Mapbox secret.

-
-
-
- -
-
-src.routers.dependencies.init_sentry(logger: Logger = Depends(get_cloud_logger))
-

Initializes the Sentry client.

-
-
Args:

logger (Logger): The logger object used for logging.

-
-
Returns:

None

-
-
-
- diff --git a/.sphinx/html/modules.html b/.sphinx/html/modules.html new file mode 100644 index 0000000..b0d8297 --- /dev/null +++ b/.sphinx/html/modules.html @@ -0,0 +1,139 @@ + + + + + + + + src — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/objects.inv b/.sphinx/html/objects.inv index 70e538d7ed1c33d69ac73dfe4d71c50ad6796060..f4170a48abf282d6eb1b342877723229b4e2d1ef 100644 GIT binary patch delta 1309 zcmV+&1>*YL1&a%ie}A=^y^`c65XbX<3c1ed$afAeb6s?S%U895LkdBUgcpGfXnfumEeqE*0avFVt-C(m3#_mGZ?Niv%`I# zx7p{tBX&ux73XU+ic1lH)?lv0&}!Ax-Mmu#BRM#d30>Mb*+#is8W-BRGfHMa17pDIR2bNtglZfKV+q0(>C!LWQK61dPOi zsSLRLa@ff1J100f8(osJ-qX#7XelXaRV(2TT4{M*3V(CxA;AzTbB%{c(S1it=Wgnr zegS2*`9<+p-1K14c05Z-nU=aa6{*7wDytpr<6&1o5~j-vQ8*0(B)9LtS7(tQjy*hD zR~WE`-U}U^Mw_O*^FLs+d(!dKJSOj+;1GCVgvU%O_7Jm6=TiZbXG?Y!qfy07w~?+r zx|Q@cR)0X3aK$_u>%6~@Spmj)i4`P!U~Ll_^G72RQHZ0tvb)1WMi&*2o=J;?DS%2> z?2f0!3}{>9@p%azb6`xn7wn2^2utxp$a7rfH}v`tIaHuHC?;UrLLUN}6qZR{b0W2m z>`6?s(F|bLXDW;cjn92$v09{{Q{{eK-(PfXGF(FA5Zq@CTRoZ3b5Q{Nb! zu*NVN&wbvwL*w&n^Z50*c+q+Xt|g^h|D~j+e3G*7VEQ?0Yd1_T4R(<^do+KzxQ^nA zvcl=t%9jBa%tc_8L>a@P=1>`_F^eK+aI7GhemngJ>PUWFao>aY7@@j4dablXJR@`a z4u9AumGgeMzskb#6(x@Lp}}9M6wMUke#d>8P32;K&Z=&sv@7?Wf#;%KcRqjyV{NgT z(O;|h3$VXMwX!24@BSIA5&jt$MO$ZXiY2v0<+Ixy^dGoYEaw<%@o@33eZ9S+OQCcB zSW^48CKf_#r)^!9)Lef;b7CF;%r#3+pMR3Et?}}ViC6Q>z%x&@-@N_9l;D3ect+@p zguY^k5=Ory%<;mz?;$-j#kKC&-g7$ltL{MK*8GfryWd_Wa=VL*DD}3da{lUVp_CSJ z8ElVBL~e@aq$2fXAUU;9rTcR7nbt7R8zz1aXZ(m_@DAb{aJkHZM+!6g9%Ma)Ec zXq}2S1f|NC-(trH)-@jFPSOGunfxP_C? zs80r%#?$ip-#`D_1YPP9mwod)#}6-!~-i*k9Y^r`2y$o?w2vrh6JPAk3h@ zT`^5op@Ic!SP?q5ZLQC1YyU=bco}6z!(=F_D0}PP8D}xE#3gt?ax^_VjTU0Q(}QT~ T*%Q#lTS9ynxViifSYVU_JDiJs delta 616 zcmV-u0+;=Z3)}^ee}9#hO^%x|6vy}P6t8Nx&~`gF=%Ug{U37^Y^BSxgo3YKPbk%G0 zdOb;<5GD}(#SZ-bACY7DVGXa@FyJsf^|~D;;U{d^=(HXCo|);RodtF0dij2qR3ET{ z%eoY&w9yM<#`-iG)mVBZBixja*~p`zFC&?$F>J^OzDYA-mVcBJiezcxqR+)@Q6A~F z(KsiZkYpMr&)V-z*V}yT-V=;BdURwmc{foipynO%pJg#uX+EsxS}EdAyk<{N+*eT3 zDtjc|%L_s~V$yYo_T%a(h`yXYe`3fOZH^SIeaV`Z22w34E~s#4){bEG=yH$3MG>{u zTuN_|b&cwNI_)Sbg3HUd-q(AcT*kbHNMVZXoo|N+v9v!P^bw5t7Z@0|$d6 zNYf?Tv*i%(mZ5+(g(Vv_VR!}^4wY6d$in?P6f!AW6@S4P#^(@wOC`J3h{M$aq+hB< znRnjdZW#`+>_FKLftc1n9(o7b7er!k1Vtb`va@$`Vr&Wr;CP^V$5c#fpa`KwKVZjP zj7=d(hGfYbjd8hyBFyGPLtMt#6tWP`7d{djLlY>%a=t9Yag0r&gxLAc0O2t*gfl7O zkHxQRs$pa&&lR7$tj^vqmrJPython Module Index

- src + src     - src.app + src.app     - src.lib + src.lib     - src.lib.burn_severity + src.lib.burn_severity     - src.lib.query_rap + src.lib.query_rap     - src.lib.query_sentinel + src.lib.query_sentinel     - src.lib.query_soil + src.lib.query_soil     - src.lib.titiler_algorithms + src.lib.titiler_algorithms     - src.routers + src.routers     - src.routers.check + src.routers.check     - src.routers.check.connectivity + src.routers.check.connectivity     - src.routers.check.dns + src.routers.check.dns     - src.routers.check.health + src.routers.check.health     - src.routers.check.sentry_error + src.routers.check.sentry_error     - src.routers.dependencies + src.routers.dependencies     - src.util + src.util     - src.util.cloud_static_io + src.util.cloud_static_io     - src.util.gcp_secrets + src.util.gcp_secrets     - src.util.ingest_burn_zip + src.util.ingest_burn_zip     - src.util.raster_to_poly + src.util.raster_to_poly diff --git a/.sphinx/html/searchindex.js b/.sphinx/html/searchindex.js index 7888d2b..d6b073c 100644 --- a/.sphinx/html/searchindex.js +++ b/.sphinx/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20], "search": 20, "page": 20, "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19], "class": [5, 7, 16], "src": 20, "router": 20, "depend": 20, "get_cloud_logg": 20, "get": 20, "logger": 20, "from": 20, "googl": 20, "cloud": 20, "log": 20, "servic": [], "return": 20, "A": [], "type": [], "get_cloud_static_io_cli": 20, "an": 20, "instanc": 20, "cloudstaticiocli": 20, "arg": 20, "The": 20, "us": 20, "get_manifest": 20, "cloud_static_io_cli": 20, "manifest": 20, "static": 20, "io": 20, "client": 20, "paramet": [], "download": 20, "storag": 20, "obtain": [], "init_sentri": 20, "initi": 20, "sentri": 20, "object": 20, "none": 20, "dict": 20, "get_mapbox_secret": 20, "retriev": 20, "mapbox": 20, "secret": 20, "gcp": 20, "str": 20}, "objects": {"src": [[1, 0, 0, "-", "app"], [2, 0, 0, "-", "lib"], [8, 0, 0, "-", "routers"], [15, 0, 0, "-", "util"]], "src.lib": [[3, 0, 0, "-", "burn_severity"], [4, 0, 0, "-", "query_rap"], [5, 0, 0, "-", "query_sentinel"], [6, 0, 0, "-", "query_soil"], [7, 0, 0, "-", "titiler_algorithms"]], "src.routers": [[9, 0, 0, "-", "check"], [14, 0, 0, "-", "dependencies"]], "src.routers.check": [[10, 0, 0, "-", "connectivity"], [11, 0, 0, "-", "dns"], [12, 0, 0, "-", "health"], [13, 0, 0, "-", "sentry_error"]], "src.util": [[16, 0, 0, "-", "cloud_static_io"], [17, 0, 0, "-", "gcp_secrets"], [18, 0, 0, "-", "ingest_burn_zip"], [19, 0, 0, "-", "raster_to_poly"]]}, "objtypes": {"0": "py:module"}, "objnames": {"0": ["py", "module", "Python module"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], "app": 1, "lib": [2, 3, 4, 5, 6, 7], "burn_sever": 3, "query_rap": 4, "query_sentinel": 5, "query_soil": 6, "titiler_algorithm": 7, "router": [8, 9, 10, 11, 12, 13, 14], "check": [9, 10, 11, 12, 13], "connect": 10, "dn": 11, "health": 12, "sentry_error": 13, "depend": 14, "util": [15, 16, 17, 18, 19], "cloud_static_io": 16, "gcp_secret": 17, "ingest_burn_zip": 18, "raster_to_poli": 19}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]]}, "indexentries": {"module": [[8, "module-src.routers"], [14, "module-src.routers.dependencies"]], "src.routers": [[8, "module-src.routers"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index", "modules", "src", "src.lib", "src.routers", "src.routers.check", "src.util"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst", "modules.rst", "src.rst", "src.lib.rst", "src.routers.rst", "src.routers.check.rst", "src.util.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!", "src", "src package", "src.lib package", "src.routers package", "src.routers.check package", "src.util package"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20, 21], "search": 20, "page": 20, "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19], "class": [5, 7, 16, 23, 26], "src": [], "router": [21, 22], "depend": [21, 22, 25], "get_cloud_logg": [22, 24, 25], "get": [23, 24], "logger": [24, 25], "from": [23, 24, 26], "googl": 24, "cloud": 24, "log": 24, "servic": [], "return": [23, 24], "A": 23, "type": [], "get_cloud_static_io_cli": [22, 24], "an": [23, 24], "instanc": 24, "cloudstaticiocli": [22, 24, 26], "arg": [23, 24], "The": 24, "us": 24, "get_manifest": [22, 24, 26], "cloud_static_io_cli": 24, "manifest": 24, "static": 24, "io": 24, "client": 24, "paramet": [], "download": [22, 24, 26], "storag": 24, "obtain": [], "init_sentri": [22, 24], "initi": 24, "sentri": 24, "object": [23, 24, 26], "none": [23, 24], "dict": [23, 24], "get_mapbox_secret": [22, 24, 26], "retriev": 24, "mapbox": 24, "secret": 24, "gcp": 24, "str": [23, 24], "packag": 21, "subpackag": 21, "lib": [21, 22], "submodul": 21, "burn_sever": [21, 22], "query_rap": [21, 22], "query_sentinel": [21, 22], "query_soil": [21, 22], "titiler_algorithm": [21, 22], "content": 21, "util": [21, 22], "cloud_static_io": [21, 22], "gcp_secret": [21, 22], "ingest_burn_zip": [21, 22], "raster_to_poli": [21, 22], "app": 21, "calc_burn_metr": [22, 23], "calc_dnbr": [22, 23], "calc_nbr": [22, 23], "calc_rbr": [22, 23], "calc_rdnbr": [22, 23], "classify_burn": [22, 23], "rap_get_biomass": [22, 23], "sentinel2cli": [22, 23], "arrange_stack": [22, 23], "classifi": [22, 23], "clip_metrics_stack_to_boundari": [22, 23], "derive_boundari": [22, 23, 26], "get_item": [22, 23], "ingest_barc_classif": [22, 23], "query_fire_ev": [22, 23], "reduce_time_rang": [22, 23], "set_boundari": [22, 23], "edit_get_ecoclass_info": [22, 23], "sdm_create_aoi": [22, 23], "sdm_get_available_interpret": [22, 23], "sdm_get_ecoclassid_from_mu_info": [22, 23], "sdm_get_esa_mapunitid_poli": [22, 23], "censorandscal": [22, 23], "color": [22, 23], "model_computed_field": [22, 23], "model_config": [22, 23], "model_field": [22, 23], "threshold": [22, 23], "convert_to_rgb": [22, 23], "check": [22, 24], "connect": [22, 24], "dn": [22, 24], "health": [22, 24], "sentry_error": [22, 24], "get_derived_product": [22, 26], "impersonate_service_account": [22, 26], "local_fetch_id_token": [22, 26], "update_manifest": [22, 26], "upload": [22, 26], "upload_cog": [22, 26], "upload_fire_ev": [22, 26], "upload_rap_estim": [22, 26], "validate_credenti": [22, 26], "get_ssh_secret": [22, 26], "ingest_esri_zip_fil": [22, 26], "shp_to_geojson": [22, 26], "raster_mask_to_geojson": [22, 26], "prefire_nir": 23, "prefire_swir": 23, "postfire_nir": 23, "postfire_swir": 23, "nbr": 23, "dnbr": 23, "rdnbr": 23, "rbr": 23, "pre": 23, "post": 23, "fire": 23, "nir": 23, "swir": 23, "band": 23, "xr": 23, "dataarrai": 23, "stack": 23, "nbr_prefir": 23, "nbr_postfir": 23, "differ": 23, "normal": 23, "burn": 23, "ratio": 23, "arrai": 23, "band_nir": 23, "band_swir": 23, "input": 23, "first": 23, "imag": 23, "e": 23, "g": 23, "b8a": 23, "second": 23, "b12": 23, "rel": 23, "reclassifi": 23, "base": [23, 26], "given": 23, "dictionari": 23, "correspond": 23, "valu": 23, "ignition_d": 23, "boundary_geojson": 23, "buffer_dist": 23, "0": 23, "01": 23, "geojson_boundari": 23, "barc_classif": 23, "buffer": 23, "1": 23, "cr": 23, "epsg": 23, "4326": 23, "item": 23, "resolut": 23, "20": 23, "threshold_sourc": 23, "burn_metr": 23, "metric_nam": 23, "025": 23, "date_rang": 23, "from_bbox": 23, "true": 23, "max_item": 23, "barc_classifications_xarrai": 23, "prefire_date_rang": [23, 26], "postfire_date_rang": [23, 26], "range_stack": 23, "ecoclass_id": 23, "geojson": 23, "aoi_smd_id": 23, "mu_polygon_kei": 23, "backoff_max": 23, "200": 23, "backoff_valu": 23, "backoff_incr": 23, "25": 23, "input_nband": 23, "int": 23, "output_nband": 23, "output_dtyp": 23, "output_min": 23, "sequenc": 23, "output_max": 23, "red": 23, "extra_data": 23, "ani": 23, "basealgorithm": 23, "classvar": 23, "computedfieldinfo": 23, "comput": 23, "field": 23, "name": 23, "configdict": 23, "extra": 23, "allow": 23, "configur": 23, "model": 23, "should": 23, "conform": 23, "pydant": 23, "config": 23, "fieldinfo": 23, "annot": 23, "requir": 23, "fals": 23, "default": [23, 26], "union": 23, "nonetyp": 23, "metadata": 23, "about": 23, "defin": 23, "map": 23, "thi": 23, "replac": 23, "__fields__": 23, "v1": 23, "ndarrai": 23, "mask": 23, "check_connect": [24, 25], "check_dn": [24, 25], "trigger_error": [24, 25], "async": 25, "bucket_nam": 26, "provid": 26, "remote_path": 26, "target_local_path": 26, "file": 26, "remot": 26, "s3": 26, "server": 26, "local": 26, "also": 26, "extract": 26, "specifi": 26, "affili": 26, "fire_event_nam": 26, "audienc": 26, "bound": 26, "source_local_path": 26, "sourc": 26, "metrics_stack": 26, "rap_estim": 26, "zip_file_path": 26, "shp_file_path": 26, "binary_mask": 26}, "objects": {"": [[22, 0, 0, "-", "src"]], "src": [[22, 0, 0, "-", "app"], [23, 0, 0, "-", "lib"], [24, 0, 0, "-", "routers"], [26, 0, 0, "-", "util"]], "src.lib": [[23, 0, 0, "-", "burn_severity"], [23, 0, 0, "-", "query_rap"], [23, 0, 0, "-", "query_sentinel"], [23, 0, 0, "-", "query_soil"], [23, 0, 0, "-", "titiler_algorithms"]], "src.lib.burn_severity": [[23, 1, 1, "", "calc_burn_metrics"], [23, 1, 1, "", "calc_dnbr"], [23, 1, 1, "", "calc_nbr"], [23, 1, 1, "", "calc_rbr"], [23, 1, 1, "", "calc_rdnbr"], [23, 1, 1, "", "classify_burn"]], "src.lib.query_rap": [[23, 1, 1, "", "rap_get_biomass"]], "src.lib.query_sentinel": [[23, 2, 1, "", "Sentinel2Client"]], "src.lib.query_sentinel.Sentinel2Client": [[23, 3, 1, "", "arrange_stack"], [23, 3, 1, "", "calc_burn_metrics"], [23, 3, 1, "", "classify"], [23, 3, 1, "", "clip_metrics_stack_to_boundary"], [23, 3, 1, "", "derive_boundary"], [23, 3, 1, "", "get_items"], [23, 3, 1, "", "ingest_barc_classifications"], [23, 3, 1, "", "query_fire_event"], [23, 3, 1, "", "reduce_time_range"], [23, 3, 1, "", "set_boundary"]], "src.lib.query_soil": [[23, 1, 1, "", "edit_get_ecoclass_info"], [23, 1, 1, "", "sdm_create_aoi"], [23, 1, 1, "", "sdm_get_available_interpretations"], [23, 1, 1, "", "sdm_get_ecoclassid_from_mu_info"], [23, 1, 1, "", "sdm_get_esa_mapunitid_poly"]], "src.lib.titiler_algorithms": [[23, 2, 1, "", "CensorAndScale"], [23, 2, 1, "", "Classify"], [23, 1, 1, "", "convert_to_rgb"]], "src.lib.titiler_algorithms.CensorAndScale": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.lib.titiler_algorithms.Classify": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.routers": [[25, 0, 0, "-", "check"], [24, 0, 0, "-", "dependencies"]], "src.routers.check": [[25, 0, 0, "-", "connectivity"], [25, 0, 0, "-", "dns"], [25, 0, 0, "-", "health"], [25, 0, 0, "-", "sentry_error"]], "src.routers.check.connectivity": [[25, 1, 1, "", "check_connectivity"]], "src.routers.check.dns": [[25, 1, 1, "", "check_dns"]], "src.routers.check.health": [[25, 1, 1, "", "health"]], "src.routers.check.sentry_error": [[25, 1, 1, "", "trigger_error"]], "src.routers.dependencies": [[24, 1, 1, "", "get_cloud_logger"], [24, 1, 1, "", "get_cloud_static_io_client"], [24, 1, 1, "", "get_manifest"], [24, 1, 1, "", "get_mapbox_secret"], [24, 1, 1, "", "init_sentry"]], "src.util": [[26, 0, 0, "-", "cloud_static_io"], [26, 0, 0, "-", "gcp_secrets"], [26, 0, 0, "-", "ingest_burn_zip"], [26, 0, 0, "-", "raster_to_poly"]], "src.util.cloud_static_io": [[26, 2, 1, "", "CloudStaticIOClient"]], "src.util.cloud_static_io.CloudStaticIOClient": [[26, 3, 1, "", "download"], [26, 3, 1, "", "get_derived_products"], [26, 3, 1, "", "get_manifest"], [26, 3, 1, "", "impersonate_service_account"], [26, 3, 1, "", "local_fetch_id_token"], [26, 3, 1, "", "update_manifest"], [26, 3, 1, "", "upload"], [26, 3, 1, "", "upload_cogs"], [26, 3, 1, "", "upload_fire_event"], [26, 3, 1, "", "upload_rap_estimates"], [26, 3, 1, "", "validate_credentials"]], "src.util.gcp_secrets": [[26, 1, 1, "", "get_mapbox_secret"], [26, 1, 1, "", "get_ssh_secret"]], "src.util.ingest_burn_zip": [[26, 1, 1, "", "ingest_esri_zip_file"], [26, 1, 1, "", "shp_to_geojson"]], "src.util.raster_to_poly": [[26, 1, 1, "", "raster_mask_to_geojson"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26], "app": [1, 22], "lib": [2, 3, 4, 5, 6, 7, 23], "burn_sever": [3, 23], "query_rap": [4, 23], "query_sentinel": [5, 23], "query_soil": [6, 23], "titiler_algorithm": [7, 23], "router": [8, 9, 10, 11, 12, 13, 14, 24, 25], "check": [9, 10, 11, 12, 13, 25], "connect": [10, 25], "dn": [11, 25], "health": [12, 25], "sentry_error": [13, 25], "depend": [14, 24], "util": [15, 16, 17, 18, 19, 26], "cloud_static_io": [16, 26], "gcp_secret": [17, 26], "ingest_burn_zip": [18, 26], "raster_to_poli": [19, 26], "packag": [22, 23, 24, 25, 26], "subpackag": [22, 24], "submodul": [22, 23, 24, 25, 26], "modul": [22, 23, 24, 25, 26], "content": [22, 23, 24, 25, 26]}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"], [21, "src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "src package": [[22, "src-package"]], "Subpackages": [[22, "subpackages"], [24, "subpackages"]], "Submodules": [[22, "submodules"], [23, "submodules"], [24, "submodules"], [25, "submodules"], [26, "submodules"]], "src.app module": [[22, "module-src.app"]], "Module contents": [[22, "module-src"], [23, "module-src.lib"], [24, "module-src.routers"], [25, "module-src.routers.check"], [26, "module-src.util"]], "src.lib package": [[23, "src-lib-package"]], "src.lib.burn_severity module": [[23, "module-src.lib.burn_severity"]], "src.lib.query_rap module": [[23, "module-src.lib.query_rap"]], "src.lib.query_sentinel module": [[23, "module-src.lib.query_sentinel"]], "src.lib.query_soil module": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms module": [[23, "module-src.lib.titiler_algorithms"]], "src.routers package": [[24, "src-routers-package"]], "src.routers.dependencies module": [[24, "module-src.routers.dependencies"]], "src.routers.check package": [[25, "src-routers-check-package"]], "src.routers.check.connectivity module": [[25, "module-src.routers.check.connectivity"]], "src.routers.check.dns module": [[25, "module-src.routers.check.dns"]], "src.routers.check.health module": [[25, "module-src.routers.check.health"]], "src.routers.check.sentry_error module": [[25, "module-src.routers.check.sentry_error"]], "src.util package": [[26, "src-util-package"]], "src.util.cloud_static_io module": [[26, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets module": [[26, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip module": [[26, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly module": [[26, "module-src.util.raster_to_poly"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]]}, "indexentries": {}}) \ No newline at end of file diff --git a/.sphinx/html/src.html b/.sphinx/html/src.html new file mode 100644 index 0000000..50a8153 --- /dev/null +++ b/.sphinx/html/src.html @@ -0,0 +1,239 @@ + + + + + + + + src package — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+
+
+

src.app module

+
+
+

Module contents

+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/src.lib.html b/.sphinx/html/src.lib.html new file mode 100644 index 0000000..eb01ee0 --- /dev/null +++ b/.sphinx/html/src.lib.html @@ -0,0 +1,364 @@ + + + + + + + + src.lib package — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.lib package

+
+

Submodules

+
+
+

src.lib.burn_severity module

+
+
+src.lib.burn_severity.calc_burn_metrics(prefire_nir, prefire_swir, postfire_nir, postfire_swir)
+

Get the NBR, dNBR, rdNBR, and rBR from the pre- and post-fire NIR and SWIR bands.

+
+
Args:

prefire_nir (xr.DataArray): Pre-fire NIR. +prefire_swir (xr.DataArray): Pre-fire SWIR. +postfire_nir (xr.DataArray): Post-fire NIR. +postfire_swir (xr.DataArray): Post-fire SWIR.

+
+
Returns:

xr.DataArray: Stack of NBR, dNBR, rdNBR, and rBR.

+
+
+
+ +
+
+src.lib.burn_severity.calc_dnbr(nbr_prefire, nbr_postfire)
+

Get the difference Normalized Burn Ratio (dNBR) from the pre-fire and post-fire NBR.

+
+
Args:

nbr_prefire (xr.DataArray): Pre-fire NBR. +nbr_postfire (xr.DataArray): Post-fire NBR.

+
+
Returns:

array: Difference Normalized Burn Ratio (dNBR).

+
+
+
+ +
+
+src.lib.burn_severity.calc_nbr(band_nir, band_swir)
+

Get the Normalized Burn Ratio (NBR) from the input arrays of NIR and SWIR bands.

+
+
Args:

band_nir (xr.DataArray): Array of the first band image (e.g., B8A). +band_swir (xr.DataArray): Array of the second band image (e.g., B12).

+
+
Returns:

array: Normalized Burn Ratio (NBR).

+
+
+
+ +
+
+src.lib.burn_severity.calc_rbr(dnbr, nbr_prefire)
+

Get the relative burn ratio (rBR) from the dNBR and pre-fire NBR.

+
+
Args:

dnbr (xr.DataArray): Difference Normalized Burn Ratio (dNBR). +nbr_prefire (xr.DataArray): Pre-fire NBR.

+
+
Returns:

array: Relative burn ratio (rBR).

+
+
+
+ +
+
+src.lib.burn_severity.calc_rdnbr(dnbr, nbr_prefire)
+

Get the relative difference Normalized Burn Ratio (rdNBR) from the dNBR and pre-fire NBR.

+
+
Args:

dnbr (xr.DataArray): Difference Normalized Burn Ratio (dNBR). +nbr_prefire (xr.DataArray): Pre-fire NBR.

+
+
Returns:

array: Relative difference Normalized Burn Ratio (rdNBR).

+
+
+
+ +
+
+src.lib.burn_severity.classify_burn(array, thresholds)
+

Reclassify an array based on the given thresholds.

+
+
Args:

array (xr.DataArray): Input array. +thresholds (dict): Dictionary of thresholds and their corresponding values.

+
+
Returns:

xr.DataArray: Reclassified array.

+
+
+
+ +
+
+

src.lib.query_rap module

+
+
+src.lib.query_rap.rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01)
+
+ +
+
+

src.lib.query_sentinel module

+
+
+class src.lib.query_sentinel.Sentinel2Client(geojson_boundary, barc_classifications=None, buffer=0.1, crs='EPSG:4326', band_nir='B8A', band_swir='B12')
+

Bases: object

+
+
+arrange_stack(items, resolution=20)
+
+ +
+
+calc_burn_metrics()
+
+ +
+
+classify(thresholds, threshold_source, burn_metric='dnbr')
+
+ +
+
+clip_metrics_stack_to_boundary()
+
+ +
+
+derive_boundary(metric_name='rbr', threshold=0.025)
+
+ +
+
+get_items(date_range, from_bbox=True, max_items=None)
+
+ +
+
+ingest_barc_classifications(barc_classifications_xarray)
+
+ +
+
+query_fire_event(prefire_date_range, postfire_date_range, from_bbox=True, max_items=None)
+
+ +
+
+reduce_time_range(range_stack)
+
+ +
+
+set_boundary(geojson_boundary)
+
+ +
+ +
+
+

src.lib.query_soil module

+
+
+src.lib.query_soil.edit_get_ecoclass_info(ecoclass_id)
+
+ +
+
+src.lib.query_soil.sdm_create_aoi(geojson)
+
+ +
+
+src.lib.query_soil.sdm_get_available_interpretations(aoi_smd_id)
+
+ +
+
+src.lib.query_soil.sdm_get_ecoclassid_from_mu_info(mu_polygon_keys)
+
+ +
+
+src.lib.query_soil.sdm_get_esa_mapunitid_poly(geojson, backoff_max=200, backoff_value=0, backoff_increment=25)
+
+ +
+
+

src.lib.titiler_algorithms module

+
+
+class src.lib.titiler_algorithms.CensorAndScale(*, input_nbands: int | None = None, output_nbands: int | None = None, output_dtype: str | None = None, output_min: Sequence | None = None, output_max: Sequence | None = None, thresholds: dict, color: str = 'red', **extra_data: Any)
+

Bases: BaseAlgorithm

+
+
+color: str
+
+ +
+
+model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
+

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ +
+
+model_config: ClassVar[ConfigDict] = {'extra': 'allow'}
+

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ +
+
+model_fields: ClassVar[dict[str, FieldInfo]] = {'color': FieldInfo(annotation=str, required=False, default='red'), 'input_nbands': FieldInfo(annotation=Union[int, NoneType], required=False), 'output_dtype': FieldInfo(annotation=Union[str, NoneType], required=False), 'output_max': FieldInfo(annotation=Union[Sequence, NoneType], required=False), 'output_min': FieldInfo(annotation=Union[Sequence, NoneType], required=False), 'output_nbands': FieldInfo(annotation=Union[int, NoneType], required=False), 'thresholds': FieldInfo(annotation=dict, required=True)}
+

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

+

This replaces Model.__fields__ from Pydantic V1.

+
+ +
+
+thresholds: dict
+
+ +
+ +
+
+class src.lib.titiler_algorithms.Classify(*, input_nbands: int | None = None, output_nbands: int | None = None, output_dtype: str | None = None, output_min: Sequence | None = None, output_max: Sequence | None = None, thresholds: dict, color: str = 'red', **extra_data: Any)
+

Bases: BaseAlgorithm

+
+
+color: str
+
+ +
+
+model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}
+

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

+
+ +
+
+model_config: ClassVar[ConfigDict] = {'extra': 'allow'}
+

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

+
+ +
+
+model_fields: ClassVar[dict[str, FieldInfo]] = {'color': FieldInfo(annotation=str, required=False, default='red'), 'input_nbands': FieldInfo(annotation=Union[int, NoneType], required=False), 'output_dtype': FieldInfo(annotation=Union[str, NoneType], required=False), 'output_max': FieldInfo(annotation=Union[Sequence, NoneType], required=False), 'output_min': FieldInfo(annotation=Union[Sequence, NoneType], required=False), 'output_nbands': FieldInfo(annotation=Union[int, NoneType], required=False), 'thresholds': FieldInfo(annotation=dict, required=True)}
+

Metadata about the fields defined on the model, +mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

+

This replaces Model.__fields__ from Pydantic V1.

+
+ +
+
+thresholds: dict
+
+ +
+ +
+
+src.lib.titiler_algorithms.convert_to_rgb(classified: ndarray, mask: ndarray, color: str) ndarray
+
+ +
+
+

Module contents

+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/src.routers.check.html b/.sphinx/html/src.routers.check.html new file mode 100644 index 0000000..d203e46 --- /dev/null +++ b/.sphinx/html/src.routers.check.html @@ -0,0 +1,138 @@ + + + + + + + + src.routers.check package — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers.check package

+
+

Submodules

+
+
+

src.routers.check.connectivity module

+
+
+src.routers.check.connectivity.check_connectivity(logger: Logger = Depends(get_cloud_logger))
+
+ +
+
+

src.routers.check.dns module

+
+
+src.routers.check.dns.check_dns(logger: Logger = Depends(get_cloud_logger))
+
+ +
+
+

src.routers.check.health module

+
+
+src.routers.check.health.health(logger: Logger = Depends(get_cloud_logger))
+
+ +
+
+

src.routers.check.sentry_error module

+
+
+async src.routers.check.sentry_error.trigger_error(logger: Logger = Depends(get_cloud_logger))
+
+ +
+
+

Module contents

+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/src.routers.html b/.sphinx/html/src.routers.html new file mode 100644 index 0000000..25cf561 --- /dev/null +++ b/.sphinx/html/src.routers.html @@ -0,0 +1,194 @@ + + + + + + + + src.routers package — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.routers package

+
+

Subpackages

+ +
+
+

Submodules

+
+
+

src.routers.dependencies module

+
+
+src.routers.dependencies.get_cloud_logger()
+

Returns a Cloud Logging logger instance.

+
+
Returns:

google.cloud.logging.logger.Logger: The Cloud Logging logger instance.

+
+
+
+ +
+
+src.routers.dependencies.get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger))
+

Get an instance of CloudStaticIOClient.

+
+
Args:

logger (Logger): The logger instance to use for logging.

+
+
Returns:

CloudStaticIOClient: An instance of CloudStaticIOClient.

+
+
+
+ +
+
+src.routers.dependencies.get_manifest(cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), logger: Logger = Depends(get_cloud_logger))
+

Get the manifest from the cloud static IO client.

+
+
Args:

cloud_static_io_client (CloudStaticIOClient): The cloud static IO client instance, used to download the manifest from clouds storage. +logger: The logger instance.

+
+
Returns:

dict: The manifest from the cloud storage.

+
+
+
+ +
+
+src.routers.dependencies.get_mapbox_secret()
+

Retrieves the Mapbox secret from GCP.

+
+
Returns:

str: The Mapbox secret.

+
+
+
+ +
+
+src.routers.dependencies.init_sentry(logger: Logger = Depends(get_cloud_logger))
+

Initializes the Sentry client.

+
+
Args:

logger (Logger): The logger object used for logging.

+
+
Returns:

None

+
+
+
+ +
+
+

Module contents

+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/html/src.util.html b/.sphinx/html/src.util.html new file mode 100644 index 0000000..5319908 --- /dev/null +++ b/.sphinx/html/src.util.html @@ -0,0 +1,207 @@ + + + + + + + + src.util package — dse-fire-recovery v0 documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

src.util package

+
+

Submodules

+
+
+

src.util.cloud_static_io module

+
+
+class src.util.cloud_static_io.CloudStaticIOClient(bucket_name, provider)
+

Bases: object

+
+
+download(remote_path, target_local_path)
+

Downloads the file from remote s3 server to local. +Also, by default extracts the file to the specified target_local_path

+
+ +
+
+get_derived_products(affiliation, fire_event_name)
+
+ +
+
+get_manifest()
+
+ +
+
+impersonate_service_account()
+
+ +
+
+local_fetch_id_token(audience)
+
+ +
+
+update_manifest(fire_event_name, bounds, prefire_date_range, postfire_date_range, affiliation, derive_boundary)
+
+ +
+
+upload(source_local_path, remote_path)
+

Uploads the source files from local to the s3 server.

+
+ +
+
+upload_cogs(metrics_stack, fire_event_name, affiliation)
+
+ +
+
+upload_fire_event(metrics_stack, fire_event_name, prefire_date_range, postfire_date_range, affiliation, derive_boundary)
+
+ +
+
+upload_rap_estimates(rap_estimates, fire_event_name, affiliation)
+
+ +
+
+validate_credentials()
+
+ +
+ +
+
+

src.util.gcp_secrets module

+
+
+src.util.gcp_secrets.get_mapbox_secret()
+
+ +
+
+src.util.gcp_secrets.get_ssh_secret()
+
+ +
+
+

src.util.ingest_burn_zip module

+
+
+src.util.ingest_burn_zip.ingest_esri_zip_file(zip_file_path)
+
+ +
+
+src.util.ingest_burn_zip.shp_to_geojson(shp_file_path)
+
+ +
+
+

src.util.raster_to_poly module

+
+
+src.util.raster_to_poly.raster_mask_to_geojson(binary_mask)
+
+ +
+
+

Module contents

+
+
+ + +
+ +
+
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/.sphinx/source/index.rst b/.sphinx/source/index.rst index f005b8e..e3d13c7 100644 --- a/.sphinx/source/index.rst +++ b/.sphinx/source/index.rst @@ -22,7 +22,3 @@ Indices and tables .. autosummary:: :toctree: _autosummary :recursive: - -.. automodule:: src.routers.dependencies - :members: - :no-index: \ No newline at end of file diff --git a/.sphinx/source/modules.rst b/.sphinx/source/modules.rst new file mode 100644 index 0000000..e9ff8ac --- /dev/null +++ b/.sphinx/source/modules.rst @@ -0,0 +1,7 @@ +src +=== + +.. toctree:: + :maxdepth: 4 + + src diff --git a/.sphinx/source/src.lib.rst b/.sphinx/source/src.lib.rst new file mode 100644 index 0000000..8a86c2e --- /dev/null +++ b/.sphinx/source/src.lib.rst @@ -0,0 +1,53 @@ +src.lib package +=============== + +Submodules +---------- + +src.lib.burn\_severity module +----------------------------- + +.. automodule:: src.lib.burn_severity + :members: + :undoc-members: + :show-inheritance: + +src.lib.query\_rap module +------------------------- + +.. automodule:: src.lib.query_rap + :members: + :undoc-members: + :show-inheritance: + +src.lib.query\_sentinel module +------------------------------ + +.. automodule:: src.lib.query_sentinel + :members: + :undoc-members: + :show-inheritance: + +src.lib.query\_soil module +-------------------------- + +.. automodule:: src.lib.query_soil + :members: + :undoc-members: + :show-inheritance: + +src.lib.titiler\_algorithms module +---------------------------------- + +.. automodule:: src.lib.titiler_algorithms + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.lib + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/source/src.routers.check.rst b/.sphinx/source/src.routers.check.rst new file mode 100644 index 0000000..6376ac3 --- /dev/null +++ b/.sphinx/source/src.routers.check.rst @@ -0,0 +1,45 @@ +src.routers.check package +========================= + +Submodules +---------- + +src.routers.check.connectivity module +------------------------------------- + +.. automodule:: src.routers.check.connectivity + :members: + :undoc-members: + :show-inheritance: + +src.routers.check.dns module +---------------------------- + +.. automodule:: src.routers.check.dns + :members: + :undoc-members: + :show-inheritance: + +src.routers.check.health module +------------------------------- + +.. automodule:: src.routers.check.health + :members: + :undoc-members: + :show-inheritance: + +src.routers.check.sentry\_error module +-------------------------------------- + +.. automodule:: src.routers.check.sentry_error + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.routers.check + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/source/src.routers.rst b/.sphinx/source/src.routers.rst new file mode 100644 index 0000000..32a8d6f --- /dev/null +++ b/.sphinx/source/src.routers.rst @@ -0,0 +1,29 @@ +src.routers package +=================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + src.routers.check + +Submodules +---------- + +src.routers.dependencies module +------------------------------- + +.. automodule:: src.routers.dependencies + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.routers + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/source/src.rst b/.sphinx/source/src.rst new file mode 100644 index 0000000..1470781 --- /dev/null +++ b/.sphinx/source/src.rst @@ -0,0 +1,31 @@ +src package +=========== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + src.lib + src.routers + src.util + +Submodules +---------- + +src.app module +-------------- + +.. automodule:: src.app + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src + :members: + :undoc-members: + :show-inheritance: diff --git a/.sphinx/source/src.util.rst b/.sphinx/source/src.util.rst new file mode 100644 index 0000000..9774500 --- /dev/null +++ b/.sphinx/source/src.util.rst @@ -0,0 +1,45 @@ +src.util package +================ + +Submodules +---------- + +src.util.cloud\_static\_io module +--------------------------------- + +.. automodule:: src.util.cloud_static_io + :members: + :undoc-members: + :show-inheritance: + +src.util.gcp\_secrets module +---------------------------- + +.. automodule:: src.util.gcp_secrets + :members: + :undoc-members: + :show-inheritance: + +src.util.ingest\_burn\_zip module +--------------------------------- + +.. automodule:: src.util.ingest_burn_zip + :members: + :undoc-members: + :show-inheritance: + +src.util.raster\_to\_poly module +-------------------------------- + +.. automodule:: src.util.raster_to_poly + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: src.util + :members: + :undoc-members: + :show-inheritance: From 5ed0490c0d250012da86b19713c90a1872dada26 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 22:11:49 +0000 Subject: [PATCH 133/175] rap docstring --- src/lib/query_rap.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/query_rap.py b/src/lib/query_rap.py index afcbd22..3803e2e 100644 --- a/src/lib/query_rap.py +++ b/src/lib/query_rap.py @@ -7,7 +7,25 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): + """ + Retrieves biomass estimates from the Rangeland Analysis Platform for a given ignition year and boundary location. + RAP provides estimates of biomass for four categories: annual forb and grass, perennial forb and grass, shrub, and tree. + http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-biomass/v3/README + Data are available from 1986 to 2022, if a pre-1986 date is provided, a ValueError is raised., if a post-2022 date is provided, + the 2022 data is used. + Parameters: + ignition_date (str): The ignition date in the format 'YYYY-MM-DD'. + boundary_geojson (dict): The boundary geometry in GeoJSON format. + buffer_distance (float, optional): The buffer distance around the boundary. Defaults to 0.01. + + Returns: + xr.DataArray: Biomass estimates from the RAP dataset. + + Raises: + ValueError: If the ignition date is before 1986, where RAP data is not available. + + """ boundary_gdf = gpd.GeoDataFrame.from_features(boundary_geojson) # Load boundary geometry (in GeoJSON format) From 79c2cf91da8f5f095f9db116b6f0748857874027 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 15 Feb 2024 22:23:45 +0000 Subject: [PATCH 134/175] docstring query_sentinel --- src/lib/query_sentinel.py | 110 +++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/src/lib/query_sentinel.py b/src/lib/query_sentinel.py index a2ad23e..344210a 100644 --- a/src/lib/query_sentinel.py +++ b/src/lib/query_sentinel.py @@ -55,6 +55,15 @@ def __init__( print("Initialized Sentinel2Client with bounds: {}".format(self.bbox)) def set_boundary(self, geojson_boundary): + """ + Sets the boundary for later query to STAC API. + + Args: + geojson_boundary (GeoJSON): The boundary of the query area. + + Returns: + None + """ boundary_gpd = gpd.GeoDataFrame.from_features(geojson_boundary) # TODO [#7]: Generalize Sentinel2Client to accept any CRS # This is hard-coded to assume 4326 - when we draw an AOI, we will change this logic depending on what makes frontend sense @@ -71,6 +80,17 @@ def set_boundary(self, geojson_boundary): ] def get_items(self, date_range, from_bbox=True, max_items=None): + """ + Retrieves items from the Sentinel-2-L2A collection based on the specified date range and optional parameters. + + Args: + date_range (tuple): A tuple containing the start and end dates of the desired date range in the format (start_date, end_date). + from_bbox (bool, optional): Specifies whether to search for items within the bounding box defined by the `bbox` attribute. Defaults to True. + max_items (int, optional): The maximum number of items to retrieve. Defaults to None, which retrieves all available items. + + Returns: + pystac.ItemCollection: A collection of items matching the specified criteria. + """ date_range_fmt = "{}/{}".format(date_range[0], date_range[1]) # TODO [#14]: Cloud cover response to smoke @@ -95,6 +115,16 @@ def get_items(self, date_range, from_bbox=True, max_items=None): return items def ingest_barc_classifications(self, barc_classifications_xarray): + """ + Ingests and processes BARC (Burned Area Reflectance Classification) classifications, such that + they conform to our desired CRS and boundary, same as our derived classifications. + + Args: + barc_classifications_xarray (xarray.DataArray): The BARC classifications as an xarray DataArray. + + Returns: + xarray.DataArray: The processed barc classifications. + """ barc_classifications = barc_classifications_xarray.rio.reproject( dst_crs=self.crs, nodata=0 ) @@ -109,11 +139,20 @@ def ingest_barc_classifications(self, barc_classifications_xarray): return barc_classifications def arrange_stack(self, items, resolution=20): - # TODO [#13]: More appropriate error handling - seperate legit from expected - # Right now, many of the requests to STAC are wrapped in try/excepts at the top level, to - # ensure no problems with timeouts and whatnot, which was an artifact of early development, - # but this is problematic now that we use form submission + """ + Arrange and process (reduce the time dimension, according to `reduce_time_range`) a stack of Sentinel items. + + Args: + items (list): List of Sentinel items to stack. + resolution (int): Resolution of the stacked data. + + Returns: + stack (xarray.DataArray): Stacked and processed Sentinel data, in our desired CRS, clipped to the boundary. + + Raises: + None + """ # Get CRS from first item (this isn't inferred by stackstac, for some reason) stac_endpoint_crs = items[0].properties["proj:epsg"] @@ -145,17 +184,42 @@ def arrange_stack(self, items, resolution=20): return stack def reduce_time_range(self, range_stack): + """ + Reduces the time range of the given range stack by taking the median along the time dimension. + + Args: + range_stack (xarray.DataArray): The range stack to be reduced. + + Returns: + xarray.DataArray: The reduced range stack. + """ + + # TODO: Think about best practice for reducing time dimension pre/post fire # This will probably get a bit more sophisticated, but for now, just take the median # We will probably run into issues of cloud occlusion, and for really long fire events, # we might want to look into time-series effects of greenup, drying, etc, in the adjacent # non-burned areas so attempt to isolate fire effects vs exogenous seasonal stuff. Ultimately, # we just want a decent reducer to squash the time dim, so median works for now. - return range_stack.median(dim="time") def query_fire_event( self, prefire_date_range, postfire_date_range, from_bbox=True, max_items=None ): + """ + Queries the fire event by retrieving prefire and postfire items based on the given date ranges. + + Args: + prefire_date_range (tuple): A tuple representing the date range for prefire items. + postfire_date_range (tuple): A tuple representing the date range for postfire items. + from_bbox (bool, optional): Flag indicating whether to retrieve items from bounding box. Defaults to True. + max_items (int, optional): Maximum number of items to retrieve. Defaults to None. + + Raises: + ValueError: If there are insufficient imagery in the date ranges to calculate burn metrics. Note that + this might also happen if there are no items in the date range, so it's not necessarily a problem with + the date ranges themselves (but it will definitely be an issue if the date range is too narrow for any imagery) + + """ # Get items for pre and post fire range prefire_items = self.get_items( prefire_date_range, from_bbox=from_bbox, max_items=max_items @@ -173,6 +237,13 @@ def query_fire_event( self.postfire_stack = self.arrange_stack(postfire_items) def calc_burn_metrics(self): + """ + Calculates burn metrics using prefire and postfire Sentinel satellite data. + + Returns: + metrics_stack (xarray.DataArray): Stack of burn metrics, wiht bands of nir and swir, + named according to self.band_nir and self.band_swir. + """ self.metrics_stack = calc_burn_metrics( prefire_nir=self.prefire_stack.sel(band=self.band_nir), prefire_swir=self.prefire_stack.sel(band=self.band_swir), @@ -181,6 +252,20 @@ def calc_burn_metrics(self): ) def classify(self, thresholds, threshold_source, burn_metric="dnbr"): + """ + Classify the metrics stack based on the given thresholds and threshold source. Note that, + in v0, we are not actually calling this classify method, we are classifing at runtime using + titiler's algorithms (`src.lib.titiler_algorithms`). After classification, we save the + classification to the `derived_classifications` attribute of the Sentinel2Client. + + Parameters: + thresholds (list): List of threshold values. + threshold_source (str): Source of the thresholds. + burn_metric (str): Metric to be used for classification (default: "dnbr"). + + Returns: + None + """ new_classification = classify_burn( self.metrics_stack.sel(burn_metric=burn_metric), thresholds=thresholds ) @@ -204,6 +289,18 @@ def classify(self, thresholds, threshold_source, burn_metric="dnbr"): ) def derive_boundary(self, metric_name="rbr", threshold=0.025): + """ + Derive a boundary from the given metric layer based on the specified threshold, and set it as the boundary of the Sentinel2Client. + This means that, when we derive boundary, we use the derived boundary for visualization (and this boundary is saved as `boundary.geojson` + within the s3 bucket), and we clip the metrics stack to this boundary. + + Args: + metric_name (str): Name of the metric layer. + threshold (float): Threshold value for the metric layer. + + Returns: + None + """ metric_layer = self.metrics_stack.sel(burn_metric=metric_name) # Threshold the metric layer to get a binary boundary @@ -235,9 +332,6 @@ def derive_boundary(self, metric_name="rbr", threshold=0.025): boundary_geojson = raster_mask_to_geojson(boundary_xr) self.set_boundary(boundary_geojson) - self.clip_metrics_stack_to_boundary() - - def clip_metrics_stack_to_boundary(self): self.metrics_stack = self.metrics_stack.rio.clip( self.geojson_boundary.geometry.values, self.geojson_boundary.crs ) From bdfbc969b2062410f7980b1d26966d540ef56a5e Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 20:35:51 +0000 Subject: [PATCH 135/175] docs for query soil --- .../_autosummary/src.lib.query_rap.doctree | Bin 4658 -> 5035 bytes .sphinx/doctrees/environment.pickle | Bin 460885 -> 487742 bytes .sphinx/doctrees/src.lib.doctree | Bin 115507 -> 138055 bytes .sphinx/html/_autosummary/src.lib.html | 10 +- .../html/_autosummary/src.lib.query_rap.html | 28 +--- .../_autosummary/src.lib.query_sentinel.html | 26 +--- .sphinx/html/genindex.html | 2 - .sphinx/html/objects.inv | Bin 1419 -> 1408 bytes .sphinx/html/searchindex.js | 2 +- .sphinx/html/src.html | 1 - .sphinx/html/src.lib.html | 125 +++++++++++++++--- src/lib/query_soil.py | 81 +++++++++++- src/routers/fetch/ecoclass.py | 4 +- 13 files changed, 195 insertions(+), 84 deletions(-) diff --git a/.sphinx/doctrees/_autosummary/src.lib.query_rap.doctree b/.sphinx/doctrees/_autosummary/src.lib.query_rap.doctree index 27e46f94fadb3d74cffa4f651741b0cb62b3e551..c9e941e0bdf46cb4602b9fc8de11808bb1d4f5f4 100644 GIT binary patch delta 485 zcmdm_vRa+BfpzKv;f<^dnHiT)Ue0V*ACy{Bl$lzVTC9+inV*|jT&$23NwY znfZANm8pq9QK0&y{L;LX#G*=toc!cOkd)q(4EES58Js<=C5cHnsheLj2eQ=n6b_Ix z#K1P!(>VlMr!-EfouZK;opCKgCWAR+aut&*=cEkTjOYwGZ^q3D99Nkctv8$TJZ4rC wWMp8-;sX-JJ)C)oxv3?U1*yeA%~>Fc&f*M3pgCE5lkEk(85=iu3oK^@0D6kBQ2+n{ delta 109 zcmZ3jzDb3(fpx07&_>pU%#3D}mouAf_G5`>nS4c1VDbkRe$LVi?Tkqo(ixX0Z{Sqe z{FiehP}S!3ypNgHco-QNvN(Z6aSvx+Vs2_lWkG83l-em-Ac@Z648__h8d;o^cL;hj L)@)`GTFwXnqjw`9 diff --git a/.sphinx/doctrees/environment.pickle b/.sphinx/doctrees/environment.pickle index 2ea00bff1d0f10b1b071d869b5e12ba05f1f3de1..704a6d8c2ed0fa0b1f48b3ff0d6f5f196eb0f2bc 100644 GIT binary patch literal 487742 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

$CLb}yMi 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 NWDCf0n5_-;|9?7QIZ^-s literal 460885 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

~Y?W4I#UjjL34lyY- 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 M0Y!CTD+96r2ND*|BLDyZ diff --git a/.sphinx/doctrees/src.lib.doctree b/.sphinx/doctrees/src.lib.doctree index b6306a1c653196748b64716e36fb5aec11e9461f..5d7cd3ee4c20d13d40585b7ae75992e4df89a78d 100644 GIT binary patch literal 138055 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 literal 115507 zcmeHw378y5d8n>kX;;$fuq_|5D`(3?94KU zq%{_KCgBitf&*g+XW((YKpsa(AOyddD?lIwoB$t30s$Q0g#?TPB#^|(|JTuVboW2i zJ+o2{>-)CV(_K~nS^rU8$Nw6-cgd2aOYmQ?t~Fn&){jmW%jJ5b?6tbVs&b>$ne*!H z?yI^x?(QDz=7W)D@kp!DX_mZhumnn!D%D!K>D9XjyTKS1Z&z!rNl|}g%PY03je1zU zGE^C^tUTDwS4M)NcC}sev{!&Yvou+&PUq%}r8|nVUKfi($rU$yM-hc+ffsc-R>Si} zz@go(YzRgQA<~uQ!LYz}f3Tuft$W@5o#|pQT5PwQ)#*+fXhfQpSIaHLe`TpwY_;Ga z+z-`@bNp^qxihbROap37ZwAYBf|c#!EWYYi)>SrF)>O`}EU#<{&cEnLqj^UQi103& zQ&}&62ctK2rso>vP7Uy2oY-VM60`&0ogDD~ z%2p(=at_c`ITtv$4gSuDzwPk11Msapmf<)=C9rbz#l}v`el0j$FQ47wnG5cSkqa9*gN@q_r-E`;uS< z7?4tgw|QXu7``dAs?c-Vou**`o*IFYpNGy?ZF+@zwK<-XKUzoFuSTn_y_3K2oi5GAv!EKsR0n9G~GkFmcuIxsW^9xg8`C1_A`fRQ-6EEO6 zIc~jfDeDg-j8!(DHbi71Srqd|ap$!ZcUB>HIBQlR&?r|b-O9b#EncHoPfgo@)A+jA1ai8kQ(I&v!T2;L?DjMac?&T*3%GcA49a%i2rK} z#JxnXhk6v8IoGMRs|5_93Sqy1pV04)qc;9dw3})zc33O{aTfHEE`$@%`5V(dgoa(y z5)O+%3&~B6vuXd~JpNFh^{foO02;x(2Wlo70 zvHT8ZV(+SK;dTWGSU?UlOURu@sZnq1v9Ao#FnAq7F3dKI^A+7XJo8$wooiRT-2S~c zj_1nk+T{Ng>*ZW??~S>cW@C;OflfG)!>6bx6X?fs`=5FvdvepW;AR@$wI;c)ZweNn zWu;xaY3(vYNPj1m-m=IJsj}Ih5LkgkMuK%^2m|U>OeVP6DRZwfic_sZg=*WI)8+HQ ztDCc}sZnYFz?S6lN1KyRDz=MPH=D(U9aFg*KrB)ilVM5h!pTKZUQkvuIMK4YkhQY$ z4Tpt!%S;JS>sElXGYA@D1W6?kzm#D2rxNVbnPB~^@V~9stppJ~1#TJpM-^u^esa$H zyP#C)Ki>OajJc9`|K;#jQZAyvSnBDJ0gCP`rMv>9{9GnwGH>*6M;KBeG}9hUG5u?> z=oSe8)o33B;9m>>g?ch|qX*G{9eShQ0jF{|wIRXA@HNqda+{d_au+E|#u&HI`HMKhz}{+@LEymR00_T>5ppPCi&RzW|BWOsCF=rmPU8+?Xp?>s|lD z&{nV#!j$&Hyw~;r2!FC%MgLuIOz<&;

$L*9H~F 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

- + - + - + - + - + diff --git a/.sphinx/html/_autosummary/src.lib.query_rap.html b/.sphinx/html/_autosummary/src.lib.query_rap.html index fdd842a..4fe1f35 100644 --- a/.sphinx/html/_autosummary/src.lib.query_rap.html +++ b/.sphinx/html/_autosummary/src.lib.query_rap.html @@ -13,8 +13,6 @@ - - @@ -37,8 +35,8 @@

Functions

src.lib.burn_severity

src.lib.burn_severity

src.lib.query_rap

src.lib.query_rap

src.lib.query_sentinel

src.lib.query_sentinel

src.lib.query_soil

src.lib.query_soil

src.lib.titiler_algorithms

src.lib.titiler_algorithms

- - + +

rap_get_biomass(ignition_date, boundary_geojson)

rap_get_biomass(ignition_date, boundary_geojson)

Retrieves biomass estimates from the Rangeland Analysis Platform for a given ignition year and boundary location.

@@ -61,33 +59,11 @@

Navigation

- diff --git a/.sphinx/html/_autosummary/src.lib.query_sentinel.html b/.sphinx/html/_autosummary/src.lib.query_sentinel.html index 0bc5201..4dd3cd8 100644 --- a/.sphinx/html/_autosummary/src.lib.query_sentinel.html +++ b/.sphinx/html/_autosummary/src.lib.query_sentinel.html @@ -13,8 +13,6 @@ - - @@ -37,7 +35,7 @@

Classes

- + @@ -61,33 +59,11 @@

dse-fire-recovery

Navigation

-

Related Topics

diff --git a/.sphinx/html/genindex.html b/.sphinx/html/genindex.html index e0b2208..937401e 100644 --- a/.sphinx/html/genindex.html +++ b/.sphinx/html/genindex.html @@ -88,8 +88,6 @@

C

  • classify() (src.lib.query_sentinel.Sentinel2Client method)
  • classify_burn() (in module src.lib.burn_severity) -
  • -
  • clip_metrics_stack_to_boundary() (src.lib.query_sentinel.Sentinel2Client method)
  • CloudStaticIOClient (class in src.util.cloud_static_io)
  • diff --git a/.sphinx/html/objects.inv b/.sphinx/html/objects.inv index f4170a48abf282d6eb1b342877723229b4e2d1ef..10f6ae275e3007b28833371274f0d992f3e02af8 100644 GIT binary patch delta 777 zcmV+k1NQuj3xErdvKhb(Fm| zv4JZz-$9Hn?)}tm!ViIQTtpWjnXVP5<`|}57{Wn$Om_v&iN%#h4I^FG1*3kY9m8+U zXJLTgC)F;c-B1I(WV8v#BLdZ9wnOaeyYz*Gj@eK~Ao_MH=)oQ*C?S?}p)L$s8Xw5pYG2(7fd zE`>StkYEUvxyBv7=)R++b2oKQzkssZ{G#|P?ntm`ubic%OiSGyS=8a)memgS@vti( z3DaeTD4YfXlG}IStFy=t#~vQ7D-762LhpqRj*3lF-uWLe**)p_X&#ezPjCo4Fv4Rd z6?=%OpCe8alV?kI6{AtbOt+D)J-U_jHda8F4aGbg>%6~@Spmj)iB$`ei~?W+*|L-F z0y6?dd6OdpT>)p4fdeiA+o_YW15bZ*ZU4*@?Kf}#FeUg4{bz)}Na!nuC}DKXKgSC% zrib*<6xX`nNzdur@1X;YTk|vi?eck<$n7pJqSV`-%K59eg;H9?Ww1Ri5xFUvlZw=n zf#lRamF~;QXIjHNZ-hu<9CoumykG}a0@4&QJ)Mjji=@HzkmL< z3A)rJF8k*H96!8Z`VvvCuBYD6Vt;LupH{z3d4l=vn(k@HfG~spcEvPVg$fp^VMXZJ zwzWR5t^FI#;boK=4U?gyqU^1EXPm{v5|`lp$kFudG>Z9752B@KPe2VDZwc{T;O6o_ H=(BGoSzUkr delta 788 zcmV+v1MB>N3yTYov z#0IX=dp$u(Go{6*?-|`v;KDV#2{7G% z3_lU-tXJ+S9%Ok*m;vg5P%SnBd?55fg`}ATjKqPd47mGp*vRZVCpbA9U6Qii)6Irx zDJf}HE8!4YX?a}=bLb(#5Gr$xhe*+VM@#2!>Yjc9WwrT5@mJjRV9|CwOG%lQx;Yi8 z!wo8{9qi*_S3nY`%L-9A4FV*$@4#14XOSO{Jv>@h7_f!j3mu$Bo2I<;KVY(Z((%(g zChwl$5O`pO$4n~r5VK3?Qvs7_OLi5bQN>KRk*+a%lcxhufAh=0Gf%YNy#2$J;D0lCM(B%# zzG8?HM!zJ?@xr_BAw4w3weHv6b2|5{?m*+#{EUCQ-(DtiyNin`^|q&S{_1U^looLr zY>!JsZi?olBK2e-Ikiuv`*QM`)-cZ-CVmfTU7@*zd9MPd#lZ|eEJT-!1tn)(Ie2F- z3||>8e^>VvAfYQ^Q!K~?x5fZ;oj!wBlZB>@CQ%tU%< zor*RE%rlYOA#po0;*d#QGYcIjJAug9`apD;(98-BlaEkrV0j=kPC91A#+gAVII=zv z9c2ZxnxjM@lnjk8IVge4;(J4tG{bWRxqWNvfBV({`*MG+6Cb?sJ4mKWNSsc%g_F;y zPX?IA)AIV?KmXbUUFs5-ee*lV4=}UV3Y!6L40)p diff --git a/.sphinx/html/searchindex.js b/.sphinx/html/searchindex.js index d6b073c..b9a7637 100644 --- a/.sphinx/html/searchindex.js +++ b/.sphinx/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index", "modules", "src", "src.lib", "src.routers", "src.routers.check", "src.util"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst", "modules.rst", "src.rst", "src.lib.rst", "src.routers.rst", "src.routers.check.rst", "src.util.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!", "src", "src package", "src.lib package", "src.routers package", "src.routers.check package", "src.util package"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20, 21], "search": 20, "page": 20, "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19], "class": [5, 7, 16, 23, 26], "src": [], "router": [21, 22], "depend": [21, 22, 25], "get_cloud_logg": [22, 24, 25], "get": [23, 24], "logger": [24, 25], "from": [23, 24, 26], "googl": 24, "cloud": 24, "log": 24, "servic": [], "return": [23, 24], "A": 23, "type": [], "get_cloud_static_io_cli": [22, 24], "an": [23, 24], "instanc": 24, "cloudstaticiocli": [22, 24, 26], "arg": [23, 24], "The": 24, "us": 24, "get_manifest": [22, 24, 26], "cloud_static_io_cli": 24, "manifest": 24, "static": 24, "io": 24, "client": 24, "paramet": [], "download": [22, 24, 26], "storag": 24, "obtain": [], "init_sentri": [22, 24], "initi": 24, "sentri": 24, "object": [23, 24, 26], "none": [23, 24], "dict": [23, 24], "get_mapbox_secret": [22, 24, 26], "retriev": 24, "mapbox": 24, "secret": 24, "gcp": 24, "str": [23, 24], "packag": 21, "subpackag": 21, "lib": [21, 22], "submodul": 21, "burn_sever": [21, 22], "query_rap": [21, 22], "query_sentinel": [21, 22], "query_soil": [21, 22], "titiler_algorithm": [21, 22], "content": 21, "util": [21, 22], "cloud_static_io": [21, 22], "gcp_secret": [21, 22], "ingest_burn_zip": [21, 22], "raster_to_poli": [21, 22], "app": 21, "calc_burn_metr": [22, 23], "calc_dnbr": [22, 23], "calc_nbr": [22, 23], "calc_rbr": [22, 23], "calc_rdnbr": [22, 23], "classify_burn": [22, 23], "rap_get_biomass": [22, 23], "sentinel2cli": [22, 23], "arrange_stack": [22, 23], "classifi": [22, 23], "clip_metrics_stack_to_boundari": [22, 23], "derive_boundari": [22, 23, 26], "get_item": [22, 23], "ingest_barc_classif": [22, 23], "query_fire_ev": [22, 23], "reduce_time_rang": [22, 23], "set_boundari": [22, 23], "edit_get_ecoclass_info": [22, 23], "sdm_create_aoi": [22, 23], "sdm_get_available_interpret": [22, 23], "sdm_get_ecoclassid_from_mu_info": [22, 23], "sdm_get_esa_mapunitid_poli": [22, 23], "censorandscal": [22, 23], "color": [22, 23], "model_computed_field": [22, 23], "model_config": [22, 23], "model_field": [22, 23], "threshold": [22, 23], "convert_to_rgb": [22, 23], "check": [22, 24], "connect": [22, 24], "dn": [22, 24], "health": [22, 24], "sentry_error": [22, 24], "get_derived_product": [22, 26], "impersonate_service_account": [22, 26], "local_fetch_id_token": [22, 26], "update_manifest": [22, 26], "upload": [22, 26], "upload_cog": [22, 26], "upload_fire_ev": [22, 26], "upload_rap_estim": [22, 26], "validate_credenti": [22, 26], "get_ssh_secret": [22, 26], "ingest_esri_zip_fil": [22, 26], "shp_to_geojson": [22, 26], "raster_mask_to_geojson": [22, 26], "prefire_nir": 23, "prefire_swir": 23, "postfire_nir": 23, "postfire_swir": 23, "nbr": 23, "dnbr": 23, "rdnbr": 23, "rbr": 23, "pre": 23, "post": 23, "fire": 23, "nir": 23, "swir": 23, "band": 23, "xr": 23, "dataarrai": 23, "stack": 23, "nbr_prefir": 23, "nbr_postfir": 23, "differ": 23, "normal": 23, "burn": 23, "ratio": 23, "arrai": 23, "band_nir": 23, "band_swir": 23, "input": 23, "first": 23, "imag": 23, "e": 23, "g": 23, "b8a": 23, "second": 23, "b12": 23, "rel": 23, "reclassifi": 23, "base": [23, 26], "given": 23, "dictionari": 23, "correspond": 23, "valu": 23, "ignition_d": 23, "boundary_geojson": 23, "buffer_dist": 23, "0": 23, "01": 23, "geojson_boundari": 23, "barc_classif": 23, "buffer": 23, "1": 23, "cr": 23, "epsg": 23, "4326": 23, "item": 23, "resolut": 23, "20": 23, "threshold_sourc": 23, "burn_metr": 23, "metric_nam": 23, "025": 23, "date_rang": 23, "from_bbox": 23, "true": 23, "max_item": 23, "barc_classifications_xarrai": 23, "prefire_date_rang": [23, 26], "postfire_date_rang": [23, 26], "range_stack": 23, "ecoclass_id": 23, "geojson": 23, "aoi_smd_id": 23, "mu_polygon_kei": 23, "backoff_max": 23, "200": 23, "backoff_valu": 23, "backoff_incr": 23, "25": 23, "input_nband": 23, "int": 23, "output_nband": 23, "output_dtyp": 23, "output_min": 23, "sequenc": 23, "output_max": 23, "red": 23, "extra_data": 23, "ani": 23, "basealgorithm": 23, "classvar": 23, "computedfieldinfo": 23, "comput": 23, "field": 23, "name": 23, "configdict": 23, "extra": 23, "allow": 23, "configur": 23, "model": 23, "should": 23, "conform": 23, "pydant": 23, "config": 23, "fieldinfo": 23, "annot": 23, "requir": 23, "fals": 23, "default": [23, 26], "union": 23, "nonetyp": 23, "metadata": 23, "about": 23, "defin": 23, "map": 23, "thi": 23, "replac": 23, "__fields__": 23, "v1": 23, "ndarrai": 23, "mask": 23, "check_connect": [24, 25], "check_dn": [24, 25], "trigger_error": [24, 25], "async": 25, "bucket_nam": 26, "provid": 26, "remote_path": 26, "target_local_path": 26, "file": 26, "remot": 26, "s3": 26, "server": 26, "local": 26, "also": 26, "extract": 26, "specifi": 26, "affili": 26, "fire_event_nam": 26, "audienc": 26, "bound": 26, "source_local_path": 26, "sourc": 26, "metrics_stack": 26, "rap_estim": 26, "zip_file_path": 26, "shp_file_path": 26, "binary_mask": 26}, "objects": {"": [[22, 0, 0, "-", "src"]], "src": [[22, 0, 0, "-", "app"], [23, 0, 0, "-", "lib"], [24, 0, 0, "-", "routers"], [26, 0, 0, "-", "util"]], "src.lib": [[23, 0, 0, "-", "burn_severity"], [23, 0, 0, "-", "query_rap"], [23, 0, 0, "-", "query_sentinel"], [23, 0, 0, "-", "query_soil"], [23, 0, 0, "-", "titiler_algorithms"]], "src.lib.burn_severity": [[23, 1, 1, "", "calc_burn_metrics"], [23, 1, 1, "", "calc_dnbr"], [23, 1, 1, "", "calc_nbr"], [23, 1, 1, "", "calc_rbr"], [23, 1, 1, "", "calc_rdnbr"], [23, 1, 1, "", "classify_burn"]], "src.lib.query_rap": [[23, 1, 1, "", "rap_get_biomass"]], "src.lib.query_sentinel": [[23, 2, 1, "", "Sentinel2Client"]], "src.lib.query_sentinel.Sentinel2Client": [[23, 3, 1, "", "arrange_stack"], [23, 3, 1, "", "calc_burn_metrics"], [23, 3, 1, "", "classify"], [23, 3, 1, "", "clip_metrics_stack_to_boundary"], [23, 3, 1, "", "derive_boundary"], [23, 3, 1, "", "get_items"], [23, 3, 1, "", "ingest_barc_classifications"], [23, 3, 1, "", "query_fire_event"], [23, 3, 1, "", "reduce_time_range"], [23, 3, 1, "", "set_boundary"]], "src.lib.query_soil": [[23, 1, 1, "", "edit_get_ecoclass_info"], [23, 1, 1, "", "sdm_create_aoi"], [23, 1, 1, "", "sdm_get_available_interpretations"], [23, 1, 1, "", "sdm_get_ecoclassid_from_mu_info"], [23, 1, 1, "", "sdm_get_esa_mapunitid_poly"]], "src.lib.titiler_algorithms": [[23, 2, 1, "", "CensorAndScale"], [23, 2, 1, "", "Classify"], [23, 1, 1, "", "convert_to_rgb"]], "src.lib.titiler_algorithms.CensorAndScale": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.lib.titiler_algorithms.Classify": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.routers": [[25, 0, 0, "-", "check"], [24, 0, 0, "-", "dependencies"]], "src.routers.check": [[25, 0, 0, "-", "connectivity"], [25, 0, 0, "-", "dns"], [25, 0, 0, "-", "health"], [25, 0, 0, "-", "sentry_error"]], "src.routers.check.connectivity": [[25, 1, 1, "", "check_connectivity"]], "src.routers.check.dns": [[25, 1, 1, "", "check_dns"]], "src.routers.check.health": [[25, 1, 1, "", "health"]], "src.routers.check.sentry_error": [[25, 1, 1, "", "trigger_error"]], "src.routers.dependencies": [[24, 1, 1, "", "get_cloud_logger"], [24, 1, 1, "", "get_cloud_static_io_client"], [24, 1, 1, "", "get_manifest"], [24, 1, 1, "", "get_mapbox_secret"], [24, 1, 1, "", "init_sentry"]], "src.util": [[26, 0, 0, "-", "cloud_static_io"], [26, 0, 0, "-", "gcp_secrets"], [26, 0, 0, "-", "ingest_burn_zip"], [26, 0, 0, "-", "raster_to_poly"]], "src.util.cloud_static_io": [[26, 2, 1, "", "CloudStaticIOClient"]], "src.util.cloud_static_io.CloudStaticIOClient": [[26, 3, 1, "", "download"], [26, 3, 1, "", "get_derived_products"], [26, 3, 1, "", "get_manifest"], [26, 3, 1, "", "impersonate_service_account"], [26, 3, 1, "", "local_fetch_id_token"], [26, 3, 1, "", "update_manifest"], [26, 3, 1, "", "upload"], [26, 3, 1, "", "upload_cogs"], [26, 3, 1, "", "upload_fire_event"], [26, 3, 1, "", "upload_rap_estimates"], [26, 3, 1, "", "validate_credentials"]], "src.util.gcp_secrets": [[26, 1, 1, "", "get_mapbox_secret"], [26, 1, 1, "", "get_ssh_secret"]], "src.util.ingest_burn_zip": [[26, 1, 1, "", "ingest_esri_zip_file"], [26, 1, 1, "", "shp_to_geojson"]], "src.util.raster_to_poly": [[26, 1, 1, "", "raster_mask_to_geojson"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26], "app": [1, 22], "lib": [2, 3, 4, 5, 6, 7, 23], "burn_sever": [3, 23], "query_rap": [4, 23], "query_sentinel": [5, 23], "query_soil": [6, 23], "titiler_algorithm": [7, 23], "router": [8, 9, 10, 11, 12, 13, 14, 24, 25], "check": [9, 10, 11, 12, 13, 25], "connect": [10, 25], "dn": [11, 25], "health": [12, 25], "sentry_error": [13, 25], "depend": [14, 24], "util": [15, 16, 17, 18, 19, 26], "cloud_static_io": [16, 26], "gcp_secret": [17, 26], "ingest_burn_zip": [18, 26], "raster_to_poli": [19, 26], "packag": [22, 23, 24, 25, 26], "subpackag": [22, 24], "submodul": [22, 23, 24, 25, 26], "modul": [22, 23, 24, 25, 26], "content": [22, 23, 24, 25, 26]}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"], [21, "src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "src package": [[22, "src-package"]], "Subpackages": [[22, "subpackages"], [24, "subpackages"]], "Submodules": [[22, "submodules"], [23, "submodules"], [24, "submodules"], [25, "submodules"], [26, "submodules"]], "src.app module": [[22, "module-src.app"]], "Module contents": [[22, "module-src"], [23, "module-src.lib"], [24, "module-src.routers"], [25, "module-src.routers.check"], [26, "module-src.util"]], "src.lib package": [[23, "src-lib-package"]], "src.lib.burn_severity module": [[23, "module-src.lib.burn_severity"]], "src.lib.query_rap module": [[23, "module-src.lib.query_rap"]], "src.lib.query_sentinel module": [[23, "module-src.lib.query_sentinel"]], "src.lib.query_soil module": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms module": [[23, "module-src.lib.titiler_algorithms"]], "src.routers package": [[24, "src-routers-package"]], "src.routers.dependencies module": [[24, "module-src.routers.dependencies"]], "src.routers.check package": [[25, "src-routers-check-package"]], "src.routers.check.connectivity module": [[25, "module-src.routers.check.connectivity"]], "src.routers.check.dns module": [[25, "module-src.routers.check.dns"]], "src.routers.check.health module": [[25, "module-src.routers.check.health"]], "src.routers.check.sentry_error module": [[25, "module-src.routers.check.sentry_error"]], "src.util package": [[26, "src-util-package"]], "src.util.cloud_static_io module": [[26, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets module": [[26, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip module": [[26, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly module": [[26, "module-src.util.raster_to_poly"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]]}, "indexentries": {}}) \ No newline at end of file +Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index", "modules", "src", "src.lib", "src.routers", "src.routers.check", "src.util"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst", "modules.rst", "src.rst", "src.lib.rst", "src.routers.rst", "src.routers.check.rst", "src.util.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!", "src", "src package", "src.lib package", "src.routers package", "src.routers.check package", "src.util package"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20, 21], "search": [20, 23], "page": 20, "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19], "class": [5, 7, 16, 23, 26], "src": [], "router": [21, 22], "depend": [21, 22, 25], "get_cloud_logg": [22, 24, 25], "get": [23, 24], "logger": [24, 25], "from": [23, 24, 26], "googl": 24, "cloud": 24, "log": 24, "servic": [], "return": [23, 24], "A": 23, "type": [], "get_cloud_static_io_cli": [22, 24], "an": [23, 24], "instanc": 24, "cloudstaticiocli": [22, 24, 26], "arg": [23, 24], "The": [23, 24], "us": [23, 24], "get_manifest": [22, 24, 26], "cloud_static_io_cli": 24, "manifest": 24, "static": 24, "io": 24, "client": 24, "paramet": 23, "download": [22, 24, 26], "storag": 24, "obtain": [], "init_sentri": [22, 24], "initi": 24, "sentri": 24, "object": [23, 24, 26], "none": [23, 24], "dict": [23, 24], "get_mapbox_secret": [22, 24, 26], "retriev": [23, 24], "mapbox": 24, "secret": 24, "gcp": 24, "str": [23, 24], "packag": 21, "subpackag": 21, "lib": [21, 22], "submodul": 21, "burn_sever": [21, 22], "query_rap": [21, 22], "query_sentinel": [21, 22], "query_soil": [21, 22], "titiler_algorithm": [21, 22], "content": 21, "util": [21, 22], "cloud_static_io": [21, 22], "gcp_secret": [21, 22], "ingest_burn_zip": [21, 22], "raster_to_poli": [21, 22], "app": 21, "calc_burn_metr": [22, 23], "calc_dnbr": [22, 23], "calc_nbr": [22, 23], "calc_rbr": [22, 23], "calc_rdnbr": [22, 23], "classify_burn": [22, 23], "rap_get_biomass": [22, 23], "sentinel2cli": [22, 23], "arrange_stack": [22, 23], "classifi": [22, 23], "clip_metrics_stack_to_boundari": [], "derive_boundari": [22, 23, 26], "get_item": [22, 23], "ingest_barc_classif": [22, 23], "query_fire_ev": [22, 23], "reduce_time_rang": [22, 23], "set_boundari": [22, 23], "edit_get_ecoclass_info": [22, 23], "sdm_create_aoi": [22, 23], "sdm_get_available_interpret": [22, 23], "sdm_get_ecoclassid_from_mu_info": [22, 23], "sdm_get_esa_mapunitid_poli": [22, 23], "censorandscal": [22, 23], "color": [22, 23], "model_computed_field": [22, 23], "model_config": [22, 23], "model_field": [22, 23], "threshold": [22, 23], "convert_to_rgb": [22, 23], "check": [22, 24], "connect": [22, 24], "dn": [22, 24], "health": [22, 24], "sentry_error": [22, 24], "get_derived_product": [22, 26], "impersonate_service_account": [22, 26], "local_fetch_id_token": [22, 26], "update_manifest": [22, 26], "upload": [22, 26], "upload_cog": [22, 26], "upload_fire_ev": [22, 26], "upload_rap_estim": [22, 26], "validate_credenti": [22, 26], "get_ssh_secret": [22, 26], "ingest_esri_zip_fil": [22, 26], "shp_to_geojson": [22, 26], "raster_mask_to_geojson": [22, 26], "prefire_nir": 23, "prefire_swir": 23, "postfire_nir": 23, "postfire_swir": 23, "nbr": 23, "dnbr": 23, "rdnbr": 23, "rbr": 23, "pre": 23, "post": 23, "fire": 23, "nir": 23, "swir": 23, "band": 23, "xr": 23, "dataarrai": 23, "stack": 23, "nbr_prefir": 23, "nbr_postfir": 23, "differ": 23, "normal": 23, "burn": 23, "ratio": 23, "arrai": 23, "band_nir": 23, "band_swir": 23, "input": 23, "first": 23, "imag": 23, "e": 23, "g": 23, "b8a": 23, "second": 23, "b12": 23, "rel": 23, "reclassifi": 23, "base": [23, 26], "given": 23, "dictionari": 23, "correspond": 23, "valu": 23, "ignition_d": 23, "boundary_geojson": 23, "buffer_dist": 23, "0": 23, "01": 23, "geojson_boundari": 23, "barc_classif": 23, "buffer": 23, "1": 23, "cr": 23, "epsg": 23, "4326": 23, "item": 23, "resolut": 23, "20": 23, "threshold_sourc": 23, "burn_metr": 23, "metric_nam": 23, "025": 23, "date_rang": 23, "from_bbox": 23, "true": 23, "max_item": 23, "barc_classifications_xarrai": 23, "prefire_date_rang": [23, 26], "postfire_date_rang": [23, 26], "range_stack": 23, "ecoclass_id": 23, "geojson": 23, "aoi_smd_id": 23, "mu_polygon_kei": 23, "backoff_max": 23, "200": 23, "backoff_valu": 23, "backoff_incr": 23, "25": 23, "input_nband": 23, "int": 23, "output_nband": 23, "output_dtyp": 23, "output_min": 23, "sequenc": 23, "output_max": 23, "red": 23, "extra_data": 23, "ani": 23, "basealgorithm": 23, "classvar": 23, "computedfieldinfo": 23, "comput": 23, "field": 23, "name": 23, "configdict": 23, "extra": 23, "allow": 23, "configur": 23, "model": 23, "should": 23, "conform": 23, "pydant": 23, "config": 23, "fieldinfo": 23, "annot": 23, "requir": 23, "fals": 23, "default": [23, 26], "union": 23, "nonetyp": 23, "metadata": 23, "about": 23, "defin": 23, "map": 23, "thi": 23, "replac": 23, "__fields__": 23, "v1": 23, "ndarrai": 23, "mask": 23, "check_connect": [24, 25], "check_dn": [24, 25], "trigger_error": [24, 25], "async": 25, "bucket_nam": 26, "provid": [23, 26], "remote_path": 26, "target_local_path": 26, "file": 26, "remot": 26, "s3": [23, 26], "server": 26, "local": 26, "also": [23, 26], "extract": 26, "specifi": [23, 26], "affili": 26, "fire_event_nam": 26, "audienc": 26, "bound": [23, 26], "source_local_path": 26, "sourc": [23, 26], "metrics_stack": [23, 26], "rap_estim": 26, "zip_file_path": 26, "shp_file_path": 26, "binary_mask": 26, "biomass": 23, "estim": 23, "rangeland": 23, "analysi": 23, "platform": 23, "ignit": 23, "year": 23, "boundari": 23, "locat": 23, "rap": 23, "four": 23, "categori": 23, "annual": 23, "forb": 23, "grass": 23, "perenni": 23, "shrub": 23, "tree": 23, "http": 23, "ntsg": 23, "umt": 23, "edu": 23, "data": 23, "veget": 23, "v3": 23, "readm": 23, "ar": 23, "avail": 23, "1986": 23, "2022": 23, "date": 23, "i": 23, "valueerror": 23, "rais": 23, "format": 23, "yyyi": 23, "mm": 23, "dd": 23, "geometri": 23, "float": 23, "option": 23, "distanc": 23, "around": 23, "dataset": 23, "If": 23, "befor": 23, "where": 23, "arrang": 23, "process": 23, "reduc": 23, "time": 23, "dimens": 23, "accord": 23, "sentinel": 23, "list": 23, "xarrai": 23, "our": 23, "desir": 23, "clip": 23, "calcul": 23, "metric": 23, "prefir": 23, "postfir": 23, "satellit": 23, "wiht": 23, "self": 23, "note": 23, "v0": 23, "we": 23, "actual": 23, "call": 23, "method": 23, "classif": 23, "runtim": 23, "titil": 23, "": 23, "algorithm": 23, "after": 23, "save": 23, "derived_classif": 23, "attribut": 23, "deriv": 23, "layer": 23, "set": 23, "mean": 23, "when": 23, "visual": 23, "within": 23, "bucket": 23, "2": 23, "l2a": 23, "collect": 23, "rang": 23, "tupl": 23, "contain": 23, "start": 23, "end": 23, "start_dat": 23, "end_dat": 23, "bool": 23, "whether": 23, "box": 23, "bbox": 23, "maximum": 23, "number": 23, "which": 23, "all": 23, "pystac": 23, "itemcollect": 23, "match": 23, "criteria": 23, "ingest": 23, "barc": 23, "area": 23, "reflect": 23, "thei": 23, "same": 23, "queri": 23, "event": 23, "repres": 23, "flag": 23, "indic": 23, "insuffici": 23, "imageri": 23, "might": 23, "happen": 23, "so": 23, "necessarili": 23, "problem": 23, "themselv": 23, "definit": 23, "issu": 23, "too": 23, "narrow": 23, "take": 23, "median": 23, "along": 23, "later": 23, "stac": 23, "api": 23}, "objects": {"": [[22, 0, 0, "-", "src"]], "src": [[22, 0, 0, "-", "app"], [23, 0, 0, "-", "lib"], [24, 0, 0, "-", "routers"], [26, 0, 0, "-", "util"]], "src.lib": [[23, 0, 0, "-", "burn_severity"], [23, 0, 0, "-", "query_rap"], [23, 0, 0, "-", "query_sentinel"], [23, 0, 0, "-", "query_soil"], [23, 0, 0, "-", "titiler_algorithms"]], "src.lib.burn_severity": [[23, 1, 1, "", "calc_burn_metrics"], [23, 1, 1, "", "calc_dnbr"], [23, 1, 1, "", "calc_nbr"], [23, 1, 1, "", "calc_rbr"], [23, 1, 1, "", "calc_rdnbr"], [23, 1, 1, "", "classify_burn"]], "src.lib.query_rap": [[23, 1, 1, "", "rap_get_biomass"]], "src.lib.query_sentinel": [[23, 2, 1, "", "Sentinel2Client"]], "src.lib.query_sentinel.Sentinel2Client": [[23, 3, 1, "", "arrange_stack"], [23, 3, 1, "", "calc_burn_metrics"], [23, 3, 1, "", "classify"], [23, 3, 1, "", "derive_boundary"], [23, 3, 1, "", "get_items"], [23, 3, 1, "", "ingest_barc_classifications"], [23, 3, 1, "", "query_fire_event"], [23, 3, 1, "", "reduce_time_range"], [23, 3, 1, "", "set_boundary"]], "src.lib.query_soil": [[23, 1, 1, "", "edit_get_ecoclass_info"], [23, 1, 1, "", "sdm_create_aoi"], [23, 1, 1, "", "sdm_get_available_interpretations"], [23, 1, 1, "", "sdm_get_ecoclassid_from_mu_info"], [23, 1, 1, "", "sdm_get_esa_mapunitid_poly"]], "src.lib.titiler_algorithms": [[23, 2, 1, "", "CensorAndScale"], [23, 2, 1, "", "Classify"], [23, 1, 1, "", "convert_to_rgb"]], "src.lib.titiler_algorithms.CensorAndScale": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.lib.titiler_algorithms.Classify": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.routers": [[25, 0, 0, "-", "check"], [24, 0, 0, "-", "dependencies"]], "src.routers.check": [[25, 0, 0, "-", "connectivity"], [25, 0, 0, "-", "dns"], [25, 0, 0, "-", "health"], [25, 0, 0, "-", "sentry_error"]], "src.routers.check.connectivity": [[25, 1, 1, "", "check_connectivity"]], "src.routers.check.dns": [[25, 1, 1, "", "check_dns"]], "src.routers.check.health": [[25, 1, 1, "", "health"]], "src.routers.check.sentry_error": [[25, 1, 1, "", "trigger_error"]], "src.routers.dependencies": [[24, 1, 1, "", "get_cloud_logger"], [24, 1, 1, "", "get_cloud_static_io_client"], [24, 1, 1, "", "get_manifest"], [24, 1, 1, "", "get_mapbox_secret"], [24, 1, 1, "", "init_sentry"]], "src.util": [[26, 0, 0, "-", "cloud_static_io"], [26, 0, 0, "-", "gcp_secrets"], [26, 0, 0, "-", "ingest_burn_zip"], [26, 0, 0, "-", "raster_to_poly"]], "src.util.cloud_static_io": [[26, 2, 1, "", "CloudStaticIOClient"]], "src.util.cloud_static_io.CloudStaticIOClient": [[26, 3, 1, "", "download"], [26, 3, 1, "", "get_derived_products"], [26, 3, 1, "", "get_manifest"], [26, 3, 1, "", "impersonate_service_account"], [26, 3, 1, "", "local_fetch_id_token"], [26, 3, 1, "", "update_manifest"], [26, 3, 1, "", "upload"], [26, 3, 1, "", "upload_cogs"], [26, 3, 1, "", "upload_fire_event"], [26, 3, 1, "", "upload_rap_estimates"], [26, 3, 1, "", "validate_credentials"]], "src.util.gcp_secrets": [[26, 1, 1, "", "get_mapbox_secret"], [26, 1, 1, "", "get_ssh_secret"]], "src.util.ingest_burn_zip": [[26, 1, 1, "", "ingest_esri_zip_file"], [26, 1, 1, "", "shp_to_geojson"]], "src.util.raster_to_poly": [[26, 1, 1, "", "raster_mask_to_geojson"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26], "app": [1, 22], "lib": [2, 3, 4, 5, 6, 7, 23], "burn_sever": [3, 23], "query_rap": [4, 23], "query_sentinel": [5, 23], "query_soil": [6, 23], "titiler_algorithm": [7, 23], "router": [8, 9, 10, 11, 12, 13, 14, 24, 25], "check": [9, 10, 11, 12, 13, 25], "connect": [10, 25], "dn": [11, 25], "health": [12, 25], "sentry_error": [13, 25], "depend": [14, 24], "util": [15, 16, 17, 18, 19, 26], "cloud_static_io": [16, 26], "gcp_secret": [17, 26], "ingest_burn_zip": [18, 26], "raster_to_poli": [19, 26], "packag": [22, 23, 24, 25, 26], "subpackag": [22, 24], "submodul": [22, 23, 24, 25, 26], "modul": [22, 23, 24, 25, 26], "content": [22, 23, 24, 25, 26]}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"], [21, "src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "src package": [[22, "src-package"]], "Subpackages": [[22, "subpackages"], [24, "subpackages"]], "Submodules": [[22, "submodules"], [24, "submodules"], [25, "submodules"], [26, "submodules"], [23, "submodules"]], "src.app module": [[22, "module-src.app"]], "Module contents": [[22, "module-src"], [24, "module-src.routers"], [25, "module-src.routers.check"], [26, "module-src.util"], [23, "module-src.lib"]], "src.routers package": [[24, "src-routers-package"]], "src.routers.dependencies module": [[24, "module-src.routers.dependencies"]], "src.routers.check package": [[25, "src-routers-check-package"]], "src.routers.check.connectivity module": [[25, "module-src.routers.check.connectivity"]], "src.routers.check.dns module": [[25, "module-src.routers.check.dns"]], "src.routers.check.health module": [[25, "module-src.routers.check.health"]], "src.routers.check.sentry_error module": [[25, "module-src.routers.check.sentry_error"]], "src.util package": [[26, "src-util-package"]], "src.util.cloud_static_io module": [[26, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets module": [[26, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip module": [[26, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly module": [[26, "module-src.util.raster_to_poly"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib package": [[23, "src-lib-package"]], "src.lib.burn_severity module": [[23, "module-src.lib.burn_severity"]], "src.lib.query_rap module": [[23, "module-src.lib.query_rap"]], "src.lib.query_sentinel module": [[23, "module-src.lib.query_sentinel"]], "src.lib.query_soil module": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms module": [[23, "module-src.lib.titiler_algorithms"]]}, "indexentries": {"module": [[2, "module-src.lib"], [4, "module-src.lib.query_rap"], [5, "module-src.lib.query_sentinel"], [22, "module-src"], [22, "module-src.app"], [23, "module-src.lib"], [23, "module-src.lib.burn_severity"], [23, "module-src.lib.query_rap"], [23, "module-src.lib.query_sentinel"], [23, "module-src.lib.query_soil"], [23, "module-src.lib.titiler_algorithms"]], "src.lib": [[2, "module-src.lib"], [23, "module-src.lib"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"], [23, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"], [23, "module-src.lib.query_sentinel"]], "src": [[22, "module-src"]], "src.app": [[22, "module-src.app"]], "censorandscale (class in src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.CensorAndScale"]], "classify (class in src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.Classify"]], "sentinel2client (class in src.lib.query_sentinel)": [[23, "src.lib.query_sentinel.Sentinel2Client"]], "arrange_stack() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.arrange_stack"]], "calc_burn_metrics() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_burn_metrics"]], "calc_burn_metrics() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.calc_burn_metrics"]], "calc_dnbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_dnbr"]], "calc_nbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_nbr"]], "calc_rbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_rbr"]], "calc_rdnbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_rdnbr"]], "classify() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.classify"]], "classify_burn() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.classify_burn"]], "color (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.color"]], "color (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.color"]], "convert_to_rgb() (in module src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.convert_to_rgb"]], "derive_boundary() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.derive_boundary"]], "edit_get_ecoclass_info() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.edit_get_ecoclass_info"]], "get_items() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.get_items"]], "ingest_barc_classifications() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.ingest_barc_classifications"]], "model_computed_fields (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_computed_fields"]], "model_computed_fields (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_computed_fields"]], "model_config (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_config"]], "model_config (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_config"]], "model_fields (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_fields"]], "model_fields (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_fields"]], "query_fire_event() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.query_fire_event"]], "rap_get_biomass() (in module src.lib.query_rap)": [[23, "src.lib.query_rap.rap_get_biomass"]], "reduce_time_range() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.reduce_time_range"]], "sdm_create_aoi() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_create_aoi"]], "sdm_get_available_interpretations() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_available_interpretations"]], "sdm_get_ecoclassid_from_mu_info() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_ecoclassid_from_mu_info"]], "sdm_get_esa_mapunitid_poly() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_esa_mapunitid_poly"]], "set_boundary() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.set_boundary"]], "src.lib.burn_severity": [[23, "module-src.lib.burn_severity"]], "src.lib.query_soil": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[23, "module-src.lib.titiler_algorithms"]], "thresholds (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.thresholds"]], "thresholds (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.thresholds"]]}}) \ No newline at end of file diff --git a/.sphinx/html/src.html b/.sphinx/html/src.html index 50a8153..ecd5a36 100644 --- a/.sphinx/html/src.html +++ b/.sphinx/html/src.html @@ -56,7 +56,6 @@

    SubpackagesSentinel2Client.arrange_stack()
  • Sentinel2Client.calc_burn_metrics()
  • Sentinel2Client.classify()
  • -
  • Sentinel2Client.clip_metrics_stack_to_boundary()
  • Sentinel2Client.derive_boundary()
  • Sentinel2Client.get_items()
  • Sentinel2Client.ingest_barc_classifications()
  • diff --git a/.sphinx/html/src.lib.html b/.sphinx/html/src.lib.html index eb01ee0..5de4ce1 100644 --- a/.sphinx/html/src.lib.html +++ b/.sphinx/html/src.lib.html @@ -123,7 +123,22 @@

    Submodules
    src.lib.query_rap.rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01)
    -
    +

    Retrieves biomass estimates from the Rangeland Analysis Platform for a given ignition year and boundary location. +RAP provides estimates of biomass for four categories: annual forb and grass, perennial forb and grass, shrub, and tree. +http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-biomass/v3/README +Data are available from 1986 to 2022, if a pre-1986 date is provided, a ValueError is raised., if a post-2022 date is provided, +the 2022 data is used.

    +
    +
    Parameters:

    ignition_date (str): The ignition date in the format ‘YYYY-MM-DD’. +boundary_geojson (dict): The boundary geometry in GeoJSON format. +buffer_distance (float, optional): The buffer distance around the boundary. Defaults to 0.01.

    +
    +
    Returns:

    xr.DataArray: Biomass estimates from the RAP dataset.

    +
    +
    Raises:

    ValueError: If the ignition date is before 1986, where RAP data is not available.

    +
    +
    +
    @@ -135,52 +150,132 @@

    Submodules
    arrange_stack(items, resolution=20)
    -
    +

    Arrange and process (reduce the time dimension, according to reduce_time_range) a stack of Sentinel items.

    +
    +
    Args:

    items (list): List of Sentinel items to stack. +resolution (int): Resolution of the stacked data.

    +
    +
    Returns:

    stack (xarray.DataArray): Stacked and processed Sentinel data, in our desired CRS, clipped to the boundary.

    +
    +
    Raises:

    None

    +
    +
    +
    calc_burn_metrics()
    -
    +

    Calculates burn metrics using prefire and postfire Sentinel satellite data.

    +
    +
    Returns:
    +
    metrics_stack (xarray.DataArray): Stack of burn metrics, wiht bands of nir and swir,

    named according to self.band_nir and self.band_swir.

    +
    +
    +
    +
    +
    classify(thresholds, threshold_source, burn_metric='dnbr')
    -
    - -
    -
    -clip_metrics_stack_to_boundary()
    -
    +

    Classify the metrics stack based on the given thresholds and threshold source. Note that, +in v0, we are not actually calling this classify method, we are classifing at runtime using +titiler’s algorithms (src.lib.titiler_algorithms). After classification, we save the +classification to the derived_classifications attribute of the Sentinel2Client.

    +
    +
    Parameters:

    thresholds (list): List of threshold values. +threshold_source (str): Source of the thresholds. +burn_metric (str): Metric to be used for classification (default: “dnbr”).

    +
    +
    Returns:

    None

    +
    +
    +
    derive_boundary(metric_name='rbr', threshold=0.025)
    -
    +

    Derive a boundary from the given metric layer based on the specified threshold, and set it as the boundary of the Sentinel2Client. +This means that, when we derive boundary, we use the derived boundary for visualization (and this boundary is saved as boundary.geojson +within the s3 bucket), and we clip the metrics stack to this boundary.

    +
    +
    Args:

    metric_name (str): Name of the metric layer. +threshold (float): Threshold value for the metric layer.

    +
    +
    Returns:

    None

    +
    +
    +
    get_items(date_range, from_bbox=True, max_items=None)
    -
    +

    Retrieves items from the Sentinel-2-L2A collection based on the specified date range and optional parameters.

    +
    +
    Args:

    date_range (tuple): A tuple containing the start and end dates of the desired date range in the format (start_date, end_date). +from_bbox (bool, optional): Specifies whether to search for items within the bounding box defined by the bbox attribute. Defaults to True. +max_items (int, optional): The maximum number of items to retrieve. Defaults to None, which retrieves all available items.

    +
    +
    Returns:

    pystac.ItemCollection: A collection of items matching the specified criteria.

    +
    +
    +
    ingest_barc_classifications(barc_classifications_xarray)
    -
    +

    Ingests and processes BARC (Burned Area Reflectance Classification) classifications, such that +they conform to our desired CRS and boundary, same as our derived classifications.

    +
    +
    Args:

    barc_classifications_xarray (xarray.DataArray): The BARC classifications as an xarray DataArray.

    +
    +
    Returns:

    xarray.DataArray: The processed barc classifications.

    +
    +
    +
    query_fire_event(prefire_date_range, postfire_date_range, from_bbox=True, max_items=None)
    -
    +

    Queries the fire event by retrieving prefire and postfire items based on the given date ranges.

    +
    +
    Args:

    prefire_date_range (tuple): A tuple representing the date range for prefire items. +postfire_date_range (tuple): A tuple representing the date range for postfire items. +from_bbox (bool, optional): Flag indicating whether to retrieve items from bounding box. Defaults to True. +max_items (int, optional): Maximum number of items to retrieve. Defaults to None.

    +
    +
    Raises:
    +
    ValueError: If there are insufficient imagery in the date ranges to calculate burn metrics. Note that

    this might also happen if there are no items in the date range, so it’s not necessarily a problem with +the date ranges themselves (but it will definitely be an issue if the date range is too narrow for any imagery)

    +
    +
    +
    +
    +
    reduce_time_range(range_stack)
    -
    +

    Reduces the time range of the given range stack by taking the median along the time dimension.

    +
    +
    Args:

    range_stack (xarray.DataArray): The range stack to be reduced.

    +
    +
    Returns:

    xarray.DataArray: The reduced range stack.

    +
    +
    +
    set_boundary(geojson_boundary)
    -
    +

    Sets the boundary for later query to STAC API.

    +
    +
    Args:

    geojson_boundary (GeoJSON): The boundary of the query area.

    +
    +
    Returns:

    None

    +
    +
    +
    diff --git a/src/lib/query_soil.py b/src/lib/query_soil.py index b55140d..ad490d6 100644 --- a/src/lib/query_soil.py +++ b/src/lib/query_soil.py @@ -10,7 +10,6 @@ import tempfile import urllib from shapely.ops import transform -import sentry_sdk SDM_ENDPOINT_TABULAR = "https://SDMDataAccess.sc.egov.usda.gov/Tabular/post.rest" SDM_ENDPOINT_SPATIAL = ( @@ -20,6 +19,20 @@ def sdm_create_aoi(geojson): + """ + DEPRECATED: `sdm_get_esa_mapunitid_poly` circumvents the need for creating an AOI within the SDM service. + + Create an Area of Interest (AOI) within the Soil Data Mart (SDM) using the given GeoJSON. + + Args: + geojson (dict): The GeoJSON object containing the AOI geometry. This assumes a single + feature in the GeoJSON, a polygon. + + Returns: + requests.Response or None: The response object if the AOI creation is successful, + None otherwise. The response object contains the AOI ID, which can be used to query + for available interpretations within SDM. + """ try: aoi = geojson["features"][0]["geometry"] @@ -39,6 +52,15 @@ def sdm_create_aoi(geojson): def sdm_get_available_interpretations(aoi_smd_id): + """ + Retrieves the available interpretations for a given AOI ID from the SDM service. + + Parameters: + aoi_smd_id (str): The ID of the AOI. + + Returns: + list or None: A list of available interpretations if successful, None otherwise. + """ try: get_available_interpretations_data = { "SERVICE": "interpretation", @@ -53,7 +75,6 @@ def sdm_get_available_interpretations(aoi_smd_id): # Check status and print available interpretations if response.status_code == 200: available_interpretations = response.json() - print(available_interpretations) return available_interpretations else: @@ -65,10 +86,27 @@ def sdm_get_available_interpretations(aoi_smd_id): return None -# def sdm_get_esa_mapunitid_poly(aoi_smd_id): def sdm_get_esa_mapunitid_poly( geojson, backoff_max=200, backoff_value=0, backoff_increment=25 ): + """ + Retrieves the map unit polygons from the SDM endpoint based on the provided GeoJSON geometry. + These Map Unit polygons are used to query for ecological site information within the EDIT database. + SDM endpoint occasionally refuses traffic, so this function will backoff and retry if necessary in a linear fashion. + + Args: + geojson (dict): The GeoJSON geometry to filter the map unit polygons. + backoff_max (int, optional): The maximum backoff value in seconds. Defaults to 200. + backoff_value (int, optional): The current backoff value in seconds. Defaults to 0. + backoff_increment (int, optional): The increment value for backoff in seconds. Defaults to 25. + + Returns: + geopandas.GeoDataFrame: The map unit polygons as a GeoDataFrame, or None if an error occurs. + + Raises: + Exception: If the SDM endpoint refuses traffic and the backoff max is reached. + """ + geometry = geojson["features"][0]["geometry"] shapely_geom = shape(geometry) bounds = shapely_geom.bounds @@ -145,6 +183,19 @@ def sdm_get_esa_mapunitid_poly( def sdm_get_ecoclassid_from_mu_info(mu_polygon_keys): + """ + Retrieves the ecological site information from the SDM endpoint based on the provided map unit polygon keys. This is + essentially a direct query to the SDM endpoint to retrieve the `ecoclassid`. This `ecoclassid` is used to query the + EDIT database for ecological site information, such as dominant cover species. This query page + (https://sdmdataaccess.sc.egov.usda.gov/Query.aspx) can be used to test out queries interactively, + which is how this query was constructed. + + Args: + mu_polygon_keys (list): The list of map unit polygon keys to filter the ecological site information. + + Returns: + pandas.DataFrame: The ecological site information as a DataFrame, or None if an error occurs. + """ SQL_QUERY = """ SELECT DISTINCT ecoclassid, @@ -193,7 +244,21 @@ def sdm_get_ecoclassid_from_mu_info(mu_polygon_keys): def edit_get_ecoclass_info(ecoclass_id): + """ + Retrieves information about an EcoClass from the EDIT database. + + Args: + ecoclass_id (str): The ID of the EcoClass to retrieve. + + Returns: + tuple: A tuple containing a boolean value indicating the success of the retrieval + and a dictionary containing the retrieved EcoClass information. + + Raises: + Exception: If there is an error in retrieving the EcoClass information from the EDIT database. + """ try: + # Get the 1:5 characters of the EcoClass ID to determine the geoUnit, which is essntially just the prefix for the GET request geoUnit = ecoclass_id[1:5] edit_endpoint_fmt = ( EDIT_ECOCLASS_ENDPOINT + f"/esd/{geoUnit}/{ecoclass_id}.json" @@ -211,12 +276,14 @@ def edit_get_ecoclass_info(ecoclass_id): f"https://edit.jornada.nmsu.edu/catalogs/esd/{geoUnit}/{ecoclass_id}" ) - return True, edit_json + return edit_json + elif response.status_code == 404: + # If the EcoClass ID is not found, return None, this is relatively common + # and we want to continue processing the other EcoClass IDs print(f"EcoClass ID not found within EDIT database: {ecoclass_id}") - return False, { - "error": f"EcoClass ID not found within EDIT database: {ecoclass_id}" - } + return None + else: print("Error:", response.status_code) raise Exception( diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index 6af5799..97013c3 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -60,8 +60,8 @@ def fetch_ecoclass( n_within_edit = 0 for ecoclass_id in ecoclass_ids: - edit_success, edit_ecoclass_json = edit_get_ecoclass_info(ecoclass_id) - if edit_success: + edit_ecoclass_json = edit_get_ecoclass_info(ecoclass_id) + if edit_ecoclass_json: n_within_edit += 1 logger.log_text(f"Success: {ecoclass_id} exists within EDIT backend") edit_ecoclass_df_row_dict = edit_ecoclass_json["generalInformation"][ From e00c9b85ab1500518f7291ab465a200efb99f604 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 20:42:53 +0000 Subject: [PATCH 136/175] document titiler algorithms --- src/lib/titiler_algorithms.py | 45 ++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/lib/titiler_algorithms.py b/src/lib/titiler_algorithms.py index 74ed9cc..a997978 100644 --- a/src/lib/titiler_algorithms.py +++ b/src/lib/titiler_algorithms.py @@ -5,7 +5,22 @@ def convert_to_rgb(classified: np.ndarray, mask: np.ndarray, color: str) -> np.ndarray: - # Convert to a red rgb image, from grayscale + """ + Convert a classified image to RGB format, essentially creating a false-color image. This is used + extensively by Titiler to create the final image that is returned to the user, upon a tile request. + We will likely do this on the front-end in v1. + + Args: + classified (np.ndarray): The classified image. + mask (np.ndarray): The mask indicating which pixels to include in the final image. + color (str): The color to use for the RGB image. Must be one of "red", "green", or "blue". + + Returns: + np.ndarray: The RGB image. + + Raises: + ValueError: If an invalid color is provided. + """ if color == "red": r_channel = np.full_like(classified, 255) classified_rgb = np.stack([r_channel, classified, classified], axis=0) @@ -29,6 +44,16 @@ class Classify(BaseAlgorithm): color: str = "red" # Default to red def __call__(self, img: ImageData) -> ImageData: + """ + Apply classification algorithm to the input image. Essentially, this converts floats to + integers based on a set of thresholds, input by the GET request for a tile. + + Args: + img (ImageData): Input image data. + + Returns: + ImageData: Classified image data. + """ float_burn_data = img.data.squeeze() float_thresholds = {float(k): v for k, v in self.thresholds.items()} png_int_values = list(float_thresholds.values()) @@ -52,10 +77,23 @@ def __call__(self, img: ImageData) -> ImageData: class CensorAndScale(BaseAlgorithm): - thresholds: dict # There is no default, which means calls to this algorithm without any parameter will fail - color: str = "red" # Default to red + thresholds: dict + color: str = "red" def __call__(self, img: ImageData) -> ImageData: + """ + Algorithm for censoring and scaling image data. Everything between the min and max thresholds + will be scaled to 0-255, while everything below the min threshold will be set to 255 and everything + above the max threshold will be set to 0. + + Args: + thresholds (dict): Dictionary containing the minimum and maximum threshold values. + color (str, optional): Color to use for censoring. Defaults to "red". + + Returns: + ImageData: The processed image data. + + """ scale_min = float(self.thresholds["min"]) scale_max = float(self.thresholds["max"]) @@ -89,6 +127,7 @@ def __call__(self, img: ImageData) -> ImageData: ) +# Register the algorithms with Titiler algorithms = default_algorithms.register( {"classify": Classify, "censor_and_scale": CensorAndScale} ) From a46e59a5cc45bc5e2e695040963f4ea58a937798 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 20:55:27 +0000 Subject: [PATCH 137/175] document cloud static io --- src/util/cloud_static_io.py | 154 +++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 2 deletions(-) diff --git a/src/util/cloud_static_io.py b/src/util/cloud_static_io.py index e05c26a..077ae4a 100644 --- a/src/util/cloud_static_io.py +++ b/src/util/cloud_static_io.py @@ -22,6 +22,36 @@ class CloudStaticIOClient: + """ + A client class for interacting with cloud storage services like S3, GCS, etc. + This uses the `smart_open` library to interact with cloud storage services, in a + relatively provider-agnostic way. It also uses the `boto3` library to interact with + AWS services, specifically so that we can assume a role in AWS and then use the + assumed role credentials to interact with S3. + + Args: + bucket_name (str): The name of the bucket in the cloud storage service. + provider (str): The provider of the cloud storage service (currently only supports "s3"). + + Attributes: + env (str): The environment variable for the environment (e.g. "LOCAL", "PROD"). + role_arn (str): The role ARN for assuming the role with AWS. + service_account_email (str): The email address of the service account for GCP, + authorized to impersonate the role in AWS. + role_session_name (str): The name of the role session. Arbitrary. + logger (logging.Logger): The logger for logging messages. + sts_client (botocore.client.STS): The STS client for assuming the AWS role using GCP credentials. + prefix (str): The prefix for the bucket URL. We get this from tofu, once the bucket is created. + iam_credentials (google.auth.impersonated_credentials.Credentials): The impersonated IAM credentials. + role_assumed_credentials (dict): The assumed role credentials, once we assume the AWS role. + boto_session (boto3.Session): The Boto3 session, using the assumed role credentials, which + at long last allows us to interact with S3. + + Raises: + Exception: If the provider is not supported. + + """ + def __init__(self, bucket_name, provider): self.env = os.environ.get("ENV") @@ -45,7 +75,6 @@ def __init__(self, bucket_name, provider): self.iam_credentials = None self.role_assumed_credentials = None - self.s3_session = None self.validate_credentials() self.logger.log_text( @@ -53,6 +82,13 @@ def __init__(self, bucket_name, provider): ) def impersonate_service_account(self): + """ + Impersonates a service account by creating impersonated credentials, using the + service account email provided in initialization. + + Returns: + None + """ # Load the credentials of the user source_credentials, project = google.auth.default() @@ -71,6 +107,21 @@ def impersonate_service_account(self): self.iam_credentials = iam_credentials def local_fetch_id_token(self, audience): + """ + Fetches an ID token from the Google IAM service. This is used to authenticate + with AWS STS, when we are running in the local environment. On GCP, we use the + `google.auth` library to fetch the ID token since we are already authenticated + with the service account we need. + + Args: + audience (str): The audience for the ID token. + + Returns: + str: The fetched ID token. + + Raises: + exceptions.DefaultCredentialsError: If the ID token fetch fails. + """ if not self.iam_credentials or not self.iam_credentials.valid: # Refresh the credentials self.iam_credentials.refresh(gcp_requests.Request()) @@ -91,7 +142,19 @@ def local_fetch_id_token(self, audience): return response.json()["token"] def validate_credentials(self): + """ + Validates the credentials by checking if the role assumed credentials are expired or not. + If expired or not available, it retrieves the OIDC token and assumes the role with web identity. + Sets the assumed credentials in the boto3 session for further use. If the environment is local, + it also impersonates the service account to get the credentials, otherwise we assume google.auth + will handle the credentials for us if we're on GCP. + + Raises: + ValueError: If failed to retrieve OIDC token. + Returns: + None + """ if not self.role_assumed_credentials or ( self.role_assumed_credentials["Expiration"].timestamp() - time.time() < 300 ): @@ -127,7 +190,14 @@ def validate_credentials(self): def download(self, remote_path, target_local_path): """ Downloads the file from remote s3 server to local. - Also, by default extracts the file to the specified target_local_path + + Args: + remote_path (str): The path of the file on the remote server. + target_local_path (str): The path where the file will be downloaded to. + + Raises: + Exception: If there is an error during the download process. + """ self.validate_credentials() try: @@ -154,6 +224,13 @@ def download(self, remote_path, target_local_path): def upload(self, source_local_path, remote_path): """ Uploads the source files from local to the s3 server. + + Args: + source_local_path (str): The local path of the source file to be uploaded. + remote_path (str): The remote path where the file will be uploaded to. + + Raises: + Exception: If there is an error during the upload process. """ self.validate_credentials() try: @@ -180,6 +257,19 @@ def upload_cogs( fire_event_name, affiliation, ): + """ + Uploads COGs (Cloud-Optimized GeoTIFFs) to a remote location, according to + `public/{affiliation}/{fire_event_name}/{band_name}.tif`. Also adds + overviews to the COGs for faster loading at lower zoom levels. + + Args: + metrics_stack (xarray.DataArray): Stack of metrics data. + fire_event_name (str): Name of the fire event. + affiliation (str): Affiliation of the data. + + Returns: + None + """ with tempfile.TemporaryDirectory() as tmpdir: for band_name in metrics_stack.burn_metric.to_index(): # Save the band as a local COG @@ -215,6 +305,19 @@ def upload_cogs( ) def upload_rap_estimates(self, rap_estimates, fire_event_name, affiliation): + """ + Uploads RAP estimates to a remote location, according to + f"public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_{band_name}.tif". + Also adds overviews to the COGs for faster loading at lower zoom levels. + + Args: + rap_estimates (xarray.DataArray): RAP estimates data. + fire_event_name (str): Name of the fire event. + affiliation (str): Affiliation of the data. + + Returns: + None + """ with tempfile.TemporaryDirectory() as tmpdir: for band_name in rap_estimates.band.to_index(): # TODO [#23]: This is the same logic as in upload_cogs. Refactor to avoid duplication @@ -243,6 +346,21 @@ def update_manifest( affiliation, derive_boundary, ): + """ + Updates the manifest with the given fire event information for the specified affiliation. If the fire event + already exists in the manifest, it will be overwritten. + + Args: + fire_event_name (str): The name of the fire event. + bounds (tuple): The bounds of the fire event. + prefire_date_range (tuple): The prefire date range of the fire event. + postfire_date_range (tuple): The postfire date range of the fire event. + affiliation (str): The affiliation for which the manifest is being updated. + derive_boundary (bool): Flag indicating whether to derive the boundary. + + Returns: + None + """ with tempfile.TemporaryDirectory() as tmpdir: manifest = self.get_manifest() @@ -281,6 +399,20 @@ def upload_fire_event( affiliation, derive_boundary, ): + """ + Uploads a fire event to the cloud storage location (uploads COGs and updates the manifest.json file). + + Args: + metrics_stack (xr.DataArray): The metrics stack containing the fire event data. + fire_event_name (str): The name of the fire event. + prefire_date_range (tuple): The date range before the fire event. + postfire_date_range (tuple): The date range after the fire event. + affiliation (str): The affiliation of the fire event. + derive_boundary (bool): Whether to derive the boundary of the fire event. + + Returns: + None + """ self.logger.log_text(f"Uploading fire event {fire_event_name}") self.upload_cogs( @@ -301,6 +433,12 @@ def upload_fire_event( ) def get_manifest(self): + """ + Retrieves the manifest file from the cloud storage. + + Returns: + dict: The contents of the manifest file. + """ with tempfile.TemporaryDirectory() as tmpdir: self.download("manifest.json", tmpdir + "tmp_manifest.json") self.logger.log_text(f"Got manifest.json") @@ -308,6 +446,18 @@ def get_manifest(self): return manifest def get_derived_products(self, affiliation, fire_event_name): + """ + Retrieves the derived products associated with a specific affiliation and fire event. We basically + assume anything within the folder for a given affiliation and fire event is a derived product. Valyes + returned are public HTTPS URLs. + + Args: + affiliation (str): The affiliation of the derived products. + fire_event_name (str): The name of the fire event. + + Returns: + dict: A dictionary containing the filenames as keys and the corresponding full HTTPS URLs as values. + """ s3_client = self.boto_session.client("s3") paginator = s3_client.get_paginator("list_objects_v2") derived_products = {} From 984c735c066882a725adee0f98b6a760d6d2441f Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 20:56:38 +0000 Subject: [PATCH 138/175] document secrets --- src/util/gcp_secrets.py | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/util/gcp_secrets.py b/src/util/gcp_secrets.py index 5785030..82c8231 100644 --- a/src/util/gcp_secrets.py +++ b/src/util/gcp_secrets.py @@ -1,30 +1,14 @@ from google.cloud import secretmanager -import json - - -def get_ssh_secret(): - # GCP project and secret details - project_id = "dse-nps" - secret_id = "burn_sftp_ssh_keys" - - # Create the Secret Manager client. - client = secretmanager.SecretManagerServiceClient() - - # Build the resource name of the secret version. - name = f"projects/{project_id}/secrets/{secret_id}/versions/latest" - - # Access the secret version. - response = client.access_secret_version(request={"name": name}) - - # Parse the secret value as a string. - ssh_private_key = json.loads(response.payload.data.decode("UTF-8"))[ - "SSH_KEY_ADMIN_PRIVATE" - ] - - return ssh_private_key.replace("\\n", "\n") def get_mapbox_secret(): + """ + Retrieves the Mapbox API key from Google Cloud Secret Manager. This assumes you are + authenticated locally with `gcloud auth application-default login`. + + Returns: + str: The Mapbox API key. + """ # GCP project and secret details project_id = "dse-nps" secret_id = "mapbox_api_key" From 2926e32c2ee794399171487615fe6bef428fd537 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 21:00:13 +0000 Subject: [PATCH 139/175] document ingest_buyrn_zip and raster_to_poly --- src/util/ingest_burn_zip.py | 26 +++++++++++++++++++++++++- src/util/raster_to_poly.py | 9 +++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/util/ingest_burn_zip.py b/src/util/ingest_burn_zip.py index 2bc0461..6c1cb62 100644 --- a/src/util/ingest_burn_zip.py +++ b/src/util/ingest_burn_zip.py @@ -1,12 +1,26 @@ import zipfile import geopandas as gpd -import rasterio import rioxarray as rxr import os import tempfile +import zipfile def ingest_esri_zip_file(zip_file_path): + """ + Ingests an ESRI zip file containing shapefiles and tif files. This assumes that there are a set of + shapefiles and tif files in the zip file. The shapefiles are expected to be in the form of a .shp, .shx, and .prj file, + with a .dbf file being optional. + + Args: + zip_file_path (str): The path to the ESRI zip file. + + Returns: + tuple: A tuple containing two lists - valid_shapefiles and valid_tifs. + - valid_shapefiles: A list of tuples, where each tuple contains the paths to the valid shapefiles and their corresponding GeoJSON representation. + - valid_tifs: A list of valid tif files. + + """ valid_shapefiles = [] valid_tifs = [] @@ -68,5 +82,15 @@ def ingest_esri_zip_file(zip_file_path): def shp_to_geojson(shp_file_path): + """ + Convert a shapefile to GeoJSON format, assuming we know the CRS + of the shapefile based on its helper files. + + Args: + shp_file_path (str): The file path of the shapefile. + + Returns: + str: The GeoJSON representation of the shapefile. + """ gdf = gpd.read_file(shp_file_path).to_crs("EPSG:4326") return gdf.to_json() diff --git a/src/util/raster_to_poly.py b/src/util/raster_to_poly.py index 840a18c..706f2fc 100644 --- a/src/util/raster_to_poly.py +++ b/src/util/raster_to_poly.py @@ -9,6 +9,15 @@ def raster_mask_to_geojson(binary_mask): + """ + Converts a binary raster mask to a GeoJSON representation. + + Args: + binary_mask (xr.DataArray): Binary mask representing the raster. + + Returns: + dict: GeoJSON representation of the mask boundary. + """ mask = binary_mask.values transform = binary_mask.rio.transform() From d998633850a5fb0df2549985cf5163ad9e33fdf4 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 21:02:01 +0000 Subject: [PATCH 140/175] document routers spectral burn metrics --- src/routers/analyze/spectral_burn_metrics.py | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/routers/analyze/spectral_burn_metrics.py b/src/routers/analyze/spectral_burn_metrics.py index 761c18c..72730b3 100644 --- a/src/routers/analyze/spectral_burn_metrics.py +++ b/src/routers/analyze/spectral_burn_metrics.py @@ -15,6 +15,18 @@ class AnaylzeBurnPOSTBody(BaseModel): + """ + Represents the request body for analyzing burn metrics. + + Attributes: + geojson (str): The GeoJSON data in string format. + derive_boundary (bool): Flag indicating whether to derive the boundary. If a shapefile is provided, this will be False. + If an AOI is drawn, this will be True. + date_ranges (dict): The date ranges for analysis. + fire_event_name (str): The name of the fire event. + affiliation (str): The affiliation of the analysis. + """ + geojson: Any derive_boundary: bool date_ranges: dict @@ -36,6 +48,18 @@ def analyze_spectral_burn_metrics( __sentry: None = Depends(init_sentry), logger: Logger = Depends(get_cloud_logger), ): + """ + Analyzes spectral burn metrics for a given fire event. + + Args: + body (AnaylzeBurnPOSTBody): The request body containing the necessary information for analysis. + cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). + __sentry (None, optional): The sentry dependency. Defaults to Depends(init_sentry). + logger (Logger, optional): The logger dependency. Defaults to Depends(get_cloud_logger). + + Returns: + JSONResponse: The response containing the analysis results and derived boundary, if applicable. + """ geojson_boundary = json.loads(body.geojson) date_ranges = body.date_ranges From 06082f7d1ea11f452b7639579f8a4f829c888a88 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 21:04:40 +0000 Subject: [PATCH 141/175] document checks --- src/routers/check/connectivity.py | 12 ++++++++++++ src/routers/check/dns.py | 12 ++++++++++++ src/routers/check/health.py | 9 +++++++++ src/routers/check/sentry_error.py | 9 +++++++++ 4 files changed, 42 insertions(+) diff --git a/src/routers/check/connectivity.py b/src/routers/check/connectivity.py index 30b2cde..d86d6e4 100644 --- a/src/routers/check/connectivity.py +++ b/src/routers/check/connectivity.py @@ -13,6 +13,18 @@ description="Check connectivity to example.com", ) def check_connectivity(logger: Logger = Depends(get_cloud_logger)): + """ + Check the connectivity to http://example.com. + + Args: + logger (Logger): The logger object used for logging. + + Returns: + Tuple[int, str]: A tuple containing the HTTP status code and a message. + + Raises: + HTTPException: If there is an error during the connectivity check. + """ try: response = requests.get("http://example.com") logger.log_text( diff --git a/src/routers/check/dns.py b/src/routers/check/dns.py index 94a4d41..22a7026 100644 --- a/src/routers/check/dns.py +++ b/src/routers/check/dns.py @@ -10,6 +10,18 @@ @router.get("/api/check/dns", tags=["check"], summary="Check DNS resolution") def check_dns(logger: Logger = Depends(get_cloud_logger)): + """ + Check the DNS resolution for www.google.com. + + Args: + logger (Logger): The logger object for logging messages. + + Returns: + dict: A dictionary containing the resolved IP address. + + Raises: + HTTPException: If there is an error during the DNS resolution. + """ try: TEST_DOMAIN = "www.google.com" ip_address = socket.gethostbyname(TEST_DOMAIN) diff --git a/src/routers/check/health.py b/src/routers/check/health.py index 2e8c53b..b0e1451 100644 --- a/src/routers/check/health.py +++ b/src/routers/check/health.py @@ -7,5 +7,14 @@ @router.get("/api/check/health", tags=["check"], description="Health check endpoint") def health(logger: Logger = Depends(get_cloud_logger)): + """ + Ping pong! + + Args: + logger (Logger): The logger object for logging messages. + + Returns: + Tuple[str, int]: A tuple containing the response message and status code. + """ logger.log_text("Health check endpoint called") return "Alive", 200 diff --git a/src/routers/check/sentry_error.py b/src/routers/check/sentry_error.py index f630e75..80a44ad 100644 --- a/src/routers/check/sentry_error.py +++ b/src/routers/check/sentry_error.py @@ -11,5 +11,14 @@ summary="Trigger a division by zero error for Sentry to catch.", ) async def trigger_error(logger: Logger = Depends(get_cloud_logger)): + """ + Triggers a division by zero error for Sentry to catch. + + Parameters: + logger (Logger): The logger object used for logging. + + Raises: + ZeroDivisionError: If a division by zero occurs (it definitely will). + """ logger.log_text("Triggering a division by zero error for Sentry to catch.") __division_by_zero = 1 / 0 From ee17916bdf166babbea4a5f8a8a8fdf6c87a8238 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 21:09:02 +0000 Subject: [PATCH 142/175] document ecocclass, rap, and derived products --- src/routers/fetch/ecoclass.py | 25 ++++++++++++++++-- .../fetch/rangeland_analysis_platform.py | 26 +++++++++++++++++-- src/routers/list/derived_products.py | 25 ++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index 97013c3..5d86513 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -18,7 +18,16 @@ router = APIRouter() -class QuerySoilPOSTBody(BaseModel): +class FetchEcoclassPOSTBody(BaseModel): + """ + Represents the request body for querying soil data. + + Attributes: + geojson (str): The GeoJSON data. + fire_event_name (str): The name of the fire event. + affiliation (str): The affiliation of the user. + """ + geojson: Any fire_event_name: str affiliation: str @@ -30,11 +39,23 @@ class QuerySoilPOSTBody(BaseModel): description="Fetch ecoclass data (using Soil Data Mart / Web Soil Survey for Map Unit polygons, and the Ecological Site Description database for ecoclass info)", ) def fetch_ecoclass( - body: QuerySoilPOSTBody, + body: FetchEcoclassPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry: None = Depends(init_sentry), logger: Logger = Depends(get_cloud_logger), ): + """ + Fetches ecoclass information from EDIT based on the provided parameters. + + Args: + body (QuerySoilPOSTBody): The request body containing the necessary parameters. + cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). + __sentry (None, optional): The sentry dependency. Defaults to Depends(init_sentry). + logger (Logger, optional): The logger dependency. Defaults to Depends(get_cloud_logger). + + Returns: + tuple: A tuple containing the success message and the HTTP status code. + """ fire_event_name = body.fire_event_name geojson = json.loads(body.geojson) affiliation = body.affiliation diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index dd3ba0f..2f079d6 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -15,7 +15,17 @@ router = APIRouter() -class AnaylzeRapPOSTBody(BaseModel): +class FetchRapPOSTBody(BaseModel): + """ + Represents the request body for fetching Rangeland Analysis Platform (RAP) data. + + Attributes: + geojson (str): The GeoJSON data for the analysis. + ignition_date (str): The date of ignition for the fire event. + fire_event_name (str): The name of the fire event. + affiliation (str): The affiliation associated with the analysis. + """ + geojson: Any ignition_date: str fire_event_name: str @@ -28,11 +38,23 @@ class AnaylzeRapPOSTBody(BaseModel): description="Fetch Rangeland Analysis Platform (RAP) biomass estimates", ) def fetch_rangeland_analysis_platform( - body: AnaylzeRapPOSTBody, + body: FetchRapPOSTBody, cloud_static_io_client: CloudStaticIOClient = Depends(get_cloud_static_io_client), __sentry: None = Depends(init_sentry), logger: Logger = Depends(get_cloud_logger), ): + """ + Fetches rangeland data for a given fire event, withing the given boundary. + + Args: + body (AnaylzeRapPOSTBody): The request body containing the necessary data for analysis. + cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). + __sentry (None, optional): The Sentry dependency. Defaults to Depends(init_sentry). + logger (Logger, optional): The logger for logging messages. Defaults to Depends(get_cloud_logger). + + Returns: + JSONResponse: The response containing the status and message. + """ boundary_geojson = json.loads(body.geojson) ignition_date = body.ignition_date fire_event_name = body.fire_event_name diff --git a/src/routers/list/derived_products.py b/src/routers/list/derived_products.py index 1410570..bc55edb 100644 --- a/src/routers/list/derived_products.py +++ b/src/routers/list/derived_products.py @@ -12,6 +12,15 @@ class GetDerivedProductsPOSTBody(BaseModel): + """ + Represents the request body for the GetDerivedProducts endpoint, which + essentially lists the files available for download for a given fire event. + + Attributes: + fire_event_name (str): The name of the fire event. + affiliation (str): The affiliation of the derived products. + """ + fire_event_name: str affiliation: str @@ -27,6 +36,22 @@ async def list_derived_products( __sentry: None = Depends(init_sentry), logger: Logger = Depends(get_cloud_logger), ): + """ + Retrieves a list of derived products within a given fire event and affiliation. This hits our own + backend service to list the available derived products. + + Args: + body (GetDerivedProductsPOSTBody): The request body containing the parameters. + cloud_static_io_client (CloudStaticIOClient): The client for interacting with the cloud static IO. + __sentry (None): The sentry dependency. + logger (Logger): The logger for logging messages. + + Returns: + JSONResponse: The response containing the derived products. + + Raises: + HTTPException: If an error occurs during the retrieval of derived products. + """ fire_event_name = body.fire_event_name affiliation = body.affiliation From 53580d683adeced5b3918bf55573e5803b1a8fb9 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 21:23:58 +0000 Subject: [PATCH 143/175] note where fast api injects dependencies in docs --- src/routers/analyze/spectral_burn_metrics.py | 6 +++--- src/routers/fetch/ecoclass.py | 6 +++--- .../fetch/rangeland_analysis_platform.py | 4 ++-- src/routers/list/derived_products.py | 6 +++--- src/routers/pages/map.py | 17 +++++++++++++++++ src/routers/upload/drawn_aoi.py | 17 +++++++++++++++++ src/routers/upload/shapefile_zip.py | 17 +++++++++++++++++ 7 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/routers/analyze/spectral_burn_metrics.py b/src/routers/analyze/spectral_burn_metrics.py index 72730b3..e0d6b2e 100644 --- a/src/routers/analyze/spectral_burn_metrics.py +++ b/src/routers/analyze/spectral_burn_metrics.py @@ -53,9 +53,9 @@ def analyze_spectral_burn_metrics( Args: body (AnaylzeBurnPOSTBody): The request body containing the necessary information for analysis. - cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). - __sentry (None, optional): The sentry dependency. Defaults to Depends(init_sentry). - logger (Logger, optional): The logger dependency. Defaults to Depends(get_cloud_logger). + cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. FastAPI handles this as a dependency injection. + __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. + logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. Returns: JSONResponse: The response containing the analysis results and derived boundary, if applicable. diff --git a/src/routers/fetch/ecoclass.py b/src/routers/fetch/ecoclass.py index 5d86513..4d05620 100644 --- a/src/routers/fetch/ecoclass.py +++ b/src/routers/fetch/ecoclass.py @@ -49,9 +49,9 @@ def fetch_ecoclass( Args: body (QuerySoilPOSTBody): The request body containing the necessary parameters. - cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). - __sentry (None, optional): The sentry dependency. Defaults to Depends(init_sentry). - logger (Logger, optional): The logger dependency. Defaults to Depends(get_cloud_logger). + cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. FastAPI handles this as a dependency injection. + __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. + logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. Returns: tuple: A tuple containing the success message and the HTTP status code. diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index 2f079d6..d63a4a4 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -49,8 +49,8 @@ def fetch_rangeland_analysis_platform( Args: body (AnaylzeRapPOSTBody): The request body containing the necessary data for analysis. cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). - __sentry (None, optional): The Sentry dependency. Defaults to Depends(init_sentry). - logger (Logger, optional): The logger for logging messages. Defaults to Depends(get_cloud_logger). + __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. + logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. Returns: JSONResponse: The response containing the status and message. diff --git a/src/routers/list/derived_products.py b/src/routers/list/derived_products.py index bc55edb..42894f0 100644 --- a/src/routers/list/derived_products.py +++ b/src/routers/list/derived_products.py @@ -42,9 +42,9 @@ async def list_derived_products( Args: body (GetDerivedProductsPOSTBody): The request body containing the parameters. - cloud_static_io_client (CloudStaticIOClient): The client for interacting with the cloud static IO. - __sentry (None): The sentry dependency. - logger (Logger): The logger for logging messages. + cloud_static_io_client (CloudStaticIOClient): The client for interacting with the cloud static IO. FastAPI handles this as a dependency injection. + __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. + logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. Returns: JSONResponse: The response containing the derived products. diff --git a/src/routers/pages/map.py b/src/routers/pages/map.py index b463de0..7b53485 100644 --- a/src/routers/pages/map.py +++ b/src/routers/pages/map.py @@ -21,6 +21,23 @@ def serve_map( manifest: dict = Depends(get_manifest), mapbox_token: str = Depends(get_mapbox_secret), ): + """ + Serves the map page for the given fire event / affiliation and burn metric. Note that this is + pretty hard-coded to the current structure of the S3 bucket and the tileserver endpoint, and will + more than likely be deprecated for v1 when we serve these maps from the frontend using the tiff + itself. + + Args: + request (Request): The HTTP request object. + fire_event_name (str): The name of the fire event. + burn_metric (str): The burn metric. + affiliation (str): The affiliation. + manifest (dict, optional): The manifest dictionary. Defaults to Depends(get_manifest). + mapbox_token (str, optional): The Mapbox token. Defaults to Depends(get_mapbox_secret). + + Returns: + TemplateResponse: The template response for the map page. + """ tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") # tileserver_endpoint = "http://localhost:5050" diff --git a/src/routers/upload/drawn_aoi.py b/src/routers/upload/drawn_aoi.py index c654977..b3ee004 100644 --- a/src/routers/upload/drawn_aoi.py +++ b/src/routers/upload/drawn_aoi.py @@ -23,6 +23,23 @@ async def upload_drawn_aoi( logger: Logger = Depends(get_cloud_logger), __sentry: None = Depends(init_sentry), ): + """ + Uploads a drawn area of interest (AOI) in GeoJSON format to the cloud storage. + + Args: + fire_event_name (str): The name of the fire event. + affiliation (str): The affiliation of the user. + geojson (str): The GeoJSON representation of the drawn AOI. + cloud_static_io_client (CloudStaticIOClient): The client for interacting with the cloud storage. FastAPI handles this as a dependency injection. + __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. + logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. + + Returns: + JSONResponse: The response containing the uploaded GeoJSON. + + Raises: + HTTPException: If there is an error during the upload process. + """ sentry_sdk.set_context( "upload_drawn_aoi", {"fire_event_name": fire_event_name, "affiliation": affiliation}, diff --git a/src/routers/upload/shapefile_zip.py b/src/routers/upload/shapefile_zip.py index da38543..33c1f75 100644 --- a/src/routers/upload/shapefile_zip.py +++ b/src/routers/upload/shapefile_zip.py @@ -26,6 +26,23 @@ async def upload_shapefile( logger: Logger = Depends(get_cloud_logger), __sentry: None = Depends(init_sentry), ): + """ + Uploads a shapefile to a remote storage location. + + Args: + fire_event_name (str): The name of the fire event. + affiliation (str): The affiliation of the uploader. + file (UploadFile): The shapefile to be uploaded. + cloud_static_io_client (CloudStaticIOClient): The client for interacting with the remote storage. FastAPI handles this as a dependency injection. + __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. + logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. + + Returns: + JSONResponse: The response containing the uploaded geojson. + + Raises: + HTTPException: If there is an error during the upload process. + """ sentry_sdk.set_context( "upload_shapefile", {"fire_event_name": fire_event_name, "affiliation": affiliation}, From 9b50286a3622cb454553f8b993ea400b36455598 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 16 Feb 2024 21:26:01 +0000 Subject: [PATCH 144/175] cleanup and sphinx --- .../_autosummary/src.lib.query_soil.doctree | Bin 8519 -> 10554 bytes .../src.lib.titiler_algorithms.doctree | Bin 7352 -> 7642 bytes .../src.routers.check.connectivity.doctree | Bin 4815 -> 5142 bytes .../src.routers.check.dns.doctree | Bin 4608 -> 4778 bytes .../src.routers.check.health.doctree | Bin 4635 -> 4703 bytes .../src.routers.check.sentry_error.doctree | Bin 4782 -> 4982 bytes .../src.util.cloud_static_io.doctree | Bin 4765 -> 5034 bytes .../_autosummary/src.util.gcp_secrets.doctree | Bin 5459 -> 4848 bytes .../src.util.ingest_burn_zip.doctree | Bin 5644 -> 6312 bytes .../src.util.raster_to_poly.doctree | Bin 4743 -> 4955 bytes .sphinx/doctrees/environment.pickle | Bin 487742 -> 560456 bytes .sphinx/doctrees/src.lib.doctree | Bin 138055 -> 154379 bytes .sphinx/doctrees/src.routers.check.doctree | Bin 16067 -> 23282 bytes .sphinx/doctrees/src.util.doctree | Bin 33305 -> 67194 bytes .../html/_autosummary/src.lib.query_soil.html | 44 +--- .../src.lib.titiler_algorithms.html | 32 +-- .../src.routers.check.connectivity.html | 33 +-- .../_autosummary/src.routers.check.dns.html | 33 +-- .../src.routers.check.health.html | 33 +-- .../html/_autosummary/src.routers.check.html | 35 +-- .../src.routers.check.sentry_error.html | 33 +-- .../src.util.cloud_static_io.html | 27 +-- .../_autosummary/src.util.gcp_secrets.html | 30 +-- .sphinx/html/_autosummary/src.util.html | 29 +-- .../src.util.ingest_burn_zip.html | 31 +-- .../_autosummary/src.util.raster_to_poly.html | 25 +-- .../_autosummary/src.util.gcp_secrets.rst.txt | 3 +- .sphinx/html/genindex.html | 2 - .sphinx/html/objects.inv | Bin 1408 -> 1401 bytes .sphinx/html/searchindex.js | 2 +- .sphinx/html/src.html | 1 - .sphinx/html/src.lib.html | 80 ++++++- .sphinx/html/src.routers.check.html | 40 +++- .sphinx/html/src.util.html | 204 ++++++++++++++++-- .../_autosummary/src.util.gcp_secrets.rst | 3 +- .../fetch/rangeland_analysis_platform.py | 2 +- 36 files changed, 338 insertions(+), 384 deletions(-) diff --git a/.sphinx/doctrees/_autosummary/src.lib.query_soil.doctree b/.sphinx/doctrees/_autosummary/src.lib.query_soil.doctree index 6ed6c65be1f9610ed077760d0f215bd94f18a9ce..38fe7a76765d7dad768b9653d6b7f5685e14b948 100644 GIT binary patch delta 2104 zcmcJQ?`vCC7{_}~5+qrgq)n5KCQVKiXMjIIe_i{P+ ze!t)6bDs0uZ%^LLdEWF~nMvL7eCG0;c^?lA!F5-vlw+pOS)Cb#t0hgZP?Kvap?R%l z5~`AHL0eF$VUUupRfs7ulFclhCPiw}JT+L_=QE0xwSvy5$=0aGr^`%}4NXn?e3|S@ zF1s*)I-5C8;KJC%JSlL!P^;9LY8sA|%2<&IQ{sHj)w1n4e`=Au#!ZQ<&O57_7l^_1 zIxny^Y`bQ2y}))FTBls0d4-AiCey1rGo1=VV4+J%nF~8db1~DapZxTSwxJ?+b|c+-+g)-unkRZD{dMekpDpQ; zbCwkSpE}WA!&vWF7pym|X(=ZC0Bey6Sl{o5tJsY;;m5$st&sOog#LjezA1E1ET6#A z->|ZO61F1a$P0%?=K$|}D}JYz7g@aBX6qBZ15@F&KZb^eq{I>uE`Nyc+ z_x8mDO+L_^FOJ0;>$~nw2hp_1+eqdkT9*tL7!^Qn z==V09bm%2`cI;Oi$8az*gA*9Ghfm@O44*`f;%N-&(W4NIUdB-jA4eavZ^`iC;p5_; z);{!d7y}Zm8^W*^Of;H%J7POv|I20d{@~x`B=2j|HE1S0vOxEM{ZO2r>yqsk01j%9n{k|9lr$t+)lTyhEBZGDuy GhW-VX7Sx>p delta 565 zcmdlLbli!xfpw~l;zrhG%uGGkCvRZZl;z?oPRWf=E=o--NsUj;&(ugy&Ce>%&(q`r zg2@Gfij()U@G$|Ieq!>IZCHgt%#Hj~ldrQ1ZFXZzW0`z|O@8tMCKlF78PXY-Ctu`{ z0r3_;=VoO5KKTWYBID1=j@(j{`@~h5GQI+(RVNql@-s3|Zs6U?_;IoopA?XMD51wA z%E-WwA?M8qH02%46xLoK@Ac#fTrx0Q*nqZVsWDC#lmi;~dh$on;K^x%KN-CyCkUA{ z`b;hqcV_ZnoV-!Sj4=#ogZX4{;i-%+lOGBz0Lgn2hP)YYTe2KMrl_&l0Yxxus07O~ z+HHO(>chx5ce1s(DdT*w>9ZlGuLPT}A~Bh9`s5uFsz4HC`fh~jQy`{K#Af;#i0Kn2 zzY`6c93lOP=>y|r8yS7ZPhcC~L2UQ~wn1LDkLeZTE0o0xq3M7hqIP(&7Q%fofQj4e5PRRmE ZbQWhQ)=tsL3Z3k#?#*~&^F;NPi~xyou|WU; diff --git a/.sphinx/doctrees/_autosummary/src.lib.titiler_algorithms.doctree b/.sphinx/doctrees/_autosummary/src.lib.titiler_algorithms.doctree index cff813e132472f4c0c2628ddd30d060901d180b6..ac524ff941e2fc9c954df04b4bd8e10192f19fb9 100644 GIT binary patch delta 701 zcmdmCdCQu$fpzM6*^R8%nHd`<-(}VfcFxZ$OD!r6uW$xO`2sZ>ZVN=+=u%u5GqOH0ftPSs7$&&e->=+oQG z#Zt#o-$M(dr)03lPRS4>!fZRz-94psO5>E;DH<8l8P_JS7m%8KfJ1vyq4RUQG4@8Zhl6_z{!lfDvZHES!YK7$sYvF8Dl4F z2%1m6$vcVBdvXq+4vRM<1H)ti4qe`?d>{*CVuo~<2T-XzXK99ZMlX=(I{6=;8!%o_ z#d7)88J#9i<^RsOWOA>79OE*uC5yn8Yyetvm~r0ZH-gqI^WYX7LRc^x#jqJ*3ofIH zK`hASS7vPnIzoGLF2DTbY+*rt4WKclNr@TqpfFJchP!g?lq?3IU}AaQvsPOoy7%|&*BE@1d7K3gOVphCWARcHcNT)BjMvrjK-T+i9Kdk<75IVV*?V! oJ)C)oxv3?U1*yfrz|R6nbQWhQ0`1IVo4i@do3V29f2o~}02=M+jsO4v delta 401 zcmca*y~C2VfpzLynT@R1nHhB_-(}X_9LrM2GTBBzZSz^q5JpD!$)^QnH`{afFfyu5 zKEvb8sJ2;vm!FZ*XR-pH3ZoxT)|t_3GQW^HW5ncxd?y**CU^7e0LdNvhCEq}3=A1^ z-i#U2SWsFNw+MV^oIiPupd8~uu;FvThOY)1 zZa%qAsE2XpWNu+=APKTyFT#RpD27c2TW}6d3}Qi#fHG?{&=G2rdj#YscZmq1tnblaBfXWzwL~##iUSe))No7H5@s!#rSs;nd;tWNg Tg;|V~uS$C}mTXp(*~th121;}f diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.connectivity.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.connectivity.doctree index 73f24eaf25de4c89f857dc2b5d18fa721289906c..462726b5cb00d3d5b9f6991363210f6dcc4db73f 100644 GIT binary patch delta 395 zcmX@FI!%MMfpsdk$VS$;%#8k%KQk-oI%lLNXDgIsq$(um=jEj)mt>Y@mQ*T~!(&E<`(3n>Lusr>TMQfnZQ!tLz<>38SJrBG&00`q;T6crFBZ5OX` zG8xPnvVIwIex*qnoIRXHscET2sd>q%QzlQ*1gf$JDr4;t!gLkU} zHTfJ9k9rRq(CE^l%qbanHiNPe`Z$N?8GvGW%6}K`N<6&Y?}qS-Z3+pZQ^^(tj5F0 wz>viWB#L`D^AdAYODYRei>K61$pT4q7H25dPSMEXoXjcY%~-QJROlch0FK)o6aWAK diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.dns.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.dns.doctree index 510f2042eb92c1ea2316875ca51746949dad7893..af8f9347dc3e3b301f10bd969d62f51b6a7d0551 100644 GIT binary patch delta 276 zcmZorS*6O_z&drl&_>qn%#5y+_cBZBIA^3LXDgIsq$;@h1uGP#7U$=bmSpDVDWv5W zDU_F&>!s)Cr{|>VCFkerZNA2=!cyNulAb9U?6Ffa#7NZCI;C++?G%j+>5OX`G8xPn zvVIwIex*r?8S=H0-?DN|=H(D#m(7UIkn?8TEXQ$|nbCUlAMVG@YJ!Xm3|V|YqPT}M lFEKZ@q_QBj7-&)!NTRbiLlJ0b7T;u60dK~}&4B{z838D#U8w*7 delta 105 zcmZ3b+MvSPz&iD-;6~Q%%#4zg_cBXvW@1rcnLLF>p0hMVJ7ZFYbjIb$6PXk?zv0-) z%xJcG8P8*8H6BI=hAd7XQQX6smzbMcQdy8%Jf(I@7D%GAI76{^ibfXay*YRwNo@Qq%*E%$Yd~Q$ogf-`As(DP~e=DA)67MA?MAwS(oE6Go$rpL7vCVYJ!Xm t3|V|YqPT}MFEKZ@q_QBj7-&KkNTRbiLlJ0L7T;tk0dK~}&1nK#83CN`G=2a8 delta 110 zcmcbwGFyeEfpx0DMwU~|jAE0|GmCA0&#b~S`6#12YiWjd#-z=K9FLi~%@`RNdiYX{ zit>vlEAj|$F5)@Hti}Tr%;E$R#XX#PiMgpIl?AEAQ);JVfh0PMGZbs5Xk>9tjuY@^ Ltl7LuU@Id4V^tya diff --git a/.sphinx/doctrees/_autosummary/src.routers.check.sentry_error.doctree b/.sphinx/doctrees/_autosummary/src.routers.check.sentry_error.doctree index 0309c89cb1d66c9743743cb166466dc846ae07c6..9f3b3e3f57d4d0d18669375d3e4c775b35a28abc 100644 GIT binary patch delta 311 zcmZ3d`b~|sfpu!O@J816%pt*CKu}VYnVz0n6rWmDlwYI~os$pZ#cFbKg+L^V6%rLv zGRrcHGxPHlk}4IdQj78xz={>p@{1ILQ}arSDiuod6_OK6k~8!+3$YZj)c2T?r+Z2U zd+d}9F|xI{PHCJ{J4J&fLptNyWP3KL$rqRecqe7ZW<+Pmc{2h&-KG9y7BEGBPk^@l9?N5atCjJBu?EfhGV&77BPXHg0|^aF7uIUE*xW delta 122 zcmeySwoa9`fpzL)p^dEXndM};fS{x(Gd(@EC_c5QD8EP}Iwv2*i`C@f+U&tn#4`CN zqx|FvoGgr!HWzTdXJ$0poWT2-S&fI0fgy_%NEG*Q<|XE)mQ)s`7Eh_2k_D3JEY47@ UouZM&IXP3%o3Uo|R>6ae0C8|8>Hq)$ diff --git a/.sphinx/doctrees/_autosummary/src.util.cloud_static_io.doctree b/.sphinx/doctrees/_autosummary/src.util.cloud_static_io.doctree index a2893d2ebba3c81274dff82360e7d85780923962..0d3a77f322995481a74d654ff01df14f45e05ec7 100644 GIT binary patch delta 362 zcmbQMx=Nk3fpzM9;f<^(nHg&*pJz7kbyP^s$xO{F0aA&@#R_TpMGBdDC8$|jl4QNj|CxJO z>U*eb=#&ih*eMxeM4M_voLi@~PHCJ{J4GWyI^)`8Z#JpP8<+$*CuPWHL}$o(Gj3kb z@tB#>dh=wS$INPij0_A}d_bbOhchoRH?^d)Ahj5%Hwz@uS)8E=G$xC0@*Dwg#>UMr I1hz5)0EA0^y#N3J delta 95 zcmZ3bK3A2sfpzL6p^dC3nHdcxpJz7M?8DN-GWjH<{N(kVY@7FTK4NAx+q{7HF|!&E yBLhPgCy*%a;mk|SO)aS`NG+aHJ0%Mw(OI0KSUW`{i*xcyL2t&I&0ht#G6De2C?7BY diff --git a/.sphinx/doctrees/_autosummary/src.util.gcp_secrets.doctree b/.sphinx/doctrees/_autosummary/src.util.gcp_secrets.doctree index b02d517798c61468887708dc4349618a8f29e41f..0c58e0916861aba7acf79b007c734f12c29f8f63 100644 GIT binary patch delta 377 zcmcbt^+A=jfpzLrp^dC*%#3D}bC`W1!nuGTJ+&l0H?bfozaqXkHMuCYL_?E{D=4+3 zC^NMzwOFAfBUQl{EUn-e;Hi+ETB(p$l%K2Mo}Ztdld9mHlV6&m5DeA`RF{{So?4_g z`5E)$&8JwlG6C&rU|(F{V@H{JQ!?0Nr(}qcYm5lYT~k`8G)}3VqLCq;acy!pr_|&G zHXgy!4DF0b8L}DC8FJo?K$**v1K4CH|6}K#T*RR|IfzYa^L_3@W=8AHf&7n|)dU$C u7_#_)L~##iUSe))No7H5G0@B`kVI#3h9c1TEWXK6!rqLHo0kc%Wds1HOM(ah delta 565 zcmeyMdRdFLfpw~f=tkBwW=8YLIm|wKx?Dhzo>~&0n^=&PUlCuNnp~7xqM->BE-ubM z5}Pc*@|aO$^JkW=OhElB*%wb<#3IjGnxUOBDMLEr^5l=4GLwxs4Qw+yH5;eYPSMC< z&0x!5j|DRNPz5q3!}vWeRt1$-`AJz;2_RVeCOV z=2kX|r6u{rrMbC@MU{3_GUU7&fg$y=GzsVoCJjv_t3OV@!mi7kB?>eas_GpmJi(#c z3)J&^vH^z-D7<-~;hm)hasf~YSOBb~G%0oRL?&5|4xsv0C}(p6XD%~i=jL}jkD1lt vfm*U+fJAW*XI^4%YDr~5YB4aTvOp4@#TkmgNXv?u{9VAC@%(0I!L^J4LGQ@` diff --git a/.sphinx/doctrees/_autosummary/src.util.ingest_burn_zip.doctree b/.sphinx/doctrees/_autosummary/src.util.ingest_burn_zip.doctree index 86a220bf6c1c6b4dde6c4109c2bc16cac74c47e5..dd7c06db14b36b86941d5247a14ce9762ca245df 100644 GIT binary patch delta 817 zcmeCtSz*Z9z&drV#75S$%#5EVUuM>{^~_68EiNfmNX%1k4G!{DsLCu*NXyJgRY=ay zD@n}E%gjqxD9%VMNCgRkl%*(?WTt_Y6zfg?!>pL?oS#>gT2!Kth)}0clCR*Nn(r0t z@28NKUzD3zqN9*lTwIz9GOavSAv-U>T%jZ*RlzwZSRp?R%!FH_kd#=Qnxc@Or;rJB zS4L`1L241yzRj{M%`EjjlsS4z27Bz33^B4z5dw#V-jvoUjZUfyc~hSwN$*(t$*A4`*ItZfZ$oL25BD3bQ~Goy8f7z}U=6pUf@d&3J2b IxX4yU0Pb}gH~;_u delta 208 zcmZ2s*rUVRz&e#tY$NMgW=5~cmzgyuTe2u_KFZR}GWjH<{Nw{nER2&jCvsUZGJc;t zi(8TL=VX3%>B-l*RT;ld?&Fi0EXgCm!pz9PFnI&J*5r7e8H^t%|KL#ok{391d9y@; zG8uB-j2Y4y?CPDiw+ni%U|A6mk=b zvlUA6f#U9|`Ch^PehNjY1x2aFsd*)dC7JnodYiv7H?!3DP+-E84EES58Df+eAvUFT zO5>E;DH<8l8P_soGMF=D{W9eICNJbr;GC2pn-QHM=gqizF2_S=M(fS>Jdc^x1Q{6^ tviN{RaSvx+Vs2_lWkG5&(1a|IL}zh^BG9lbzR4W|-i(c#uLx{r1OOEQZCd~U delta 97 zcmcbu)~?Fhz&f=?Xd~-!W=74)XPGrO8?ZF9Os)_Vn7o`*m~qnPLe7WGjAomIc^@;Y z@h~zlWN`wC;vUYt#N5=9%7WD5DYa9wKoXtB8H%-2G_p7+#|U~e)@)uOxS0_Ey=5K` diff --git a/.sphinx/doctrees/environment.pickle b/.sphinx/doctrees/environment.pickle index 704a6d8c2ed0fa0b1f48b3ff0d6f5f196eb0f2bc..b29955200bea361f8d8898938609c7e62668df90 100644 GIT binary patch literal 560456 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

    ;vs~3=h>kZ3JESI@%F*&!DRA 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


    @0uUqvtuH}Nn*mN^W>?#)|K9=meA0%%P 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 KdB`>gv;Pk^OCrSp literal 487742 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

    $CLb}yMi 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 NWDCf0n5_-;|9?7QIZ^-s diff --git a/.sphinx/doctrees/src.lib.doctree b/.sphinx/doctrees/src.lib.doctree index 5d7cd3ee4c20d13d40585b7ae75992e4df89a78d..f3e28a450857254742d354d2035c1452b0da0690 100644 GIT binary patch delta 23637 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 delta 13013 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%0Amv@sfMBpu?!SrG$ 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 diff --git a/.sphinx/doctrees/src.routers.check.doctree b/.sphinx/doctrees/src.routers.check.doctree index cf6d892e3c376cf854c9ea4f4a270f75ca7b3862..146e90df6d23a1239a0e65275f88ccec439b2e8a 100644 GIT binary patch literal 23282 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 F{U5R7`}_a^ delta 2118 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 Rq&`*}WR3{UoiFv({|mi{^)LVc delta 5361 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 diff --git a/.sphinx/html/_autosummary/src.lib.query_soil.html b/.sphinx/html/_autosummary/src.lib.query_soil.html index 780a3a2..3862faa 100644 --- a/.sphinx/html/_autosummary/src.lib.query_soil.html +++ b/.sphinx/html/_autosummary/src.lib.query_soil.html @@ -13,8 +13,6 @@ - - @@ -37,20 +35,20 @@

    Functions

    Sentinel2Client(geojson_boundary[, ...])

    Sentinel2Client(geojson_boundary[, ...])

    - - + + - - + + - - + + - - + + - - + +

    edit_get_ecoclass_info(ecoclass_id)

    edit_get_ecoclass_info(ecoclass_id)

    Retrieves information about an EcoClass from the EDIT database.

    sdm_create_aoi(geojson)

    sdm_create_aoi(geojson)

    DEPRECATED: sdm_get_esa_mapunitid_poly circumvents the need for creating an AOI within the SDM service.

    sdm_get_available_interpretations(aoi_smd_id)

    sdm_get_available_interpretations(aoi_smd_id)

    Retrieves the available interpretations for a given AOI ID from the SDM service.

    sdm_get_ecoclassid_from_mu_info(mu_polygon_keys)

    sdm_get_ecoclassid_from_mu_info(mu_polygon_keys)

    Retrieves the ecological site information from the SDM endpoint based on the provided map unit polygon keys.

    sdm_get_esa_mapunitid_poly(geojson[, ...])

    sdm_get_esa_mapunitid_poly(geojson[, ...])

    Retrieves the map unit polygons from the SDM endpoint based on the provided GeoJSON geometry.

    @@ -73,33 +71,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html b/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html index c0948a6..83f0a2f 100644 --- a/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html +++ b/.sphinx/html/_autosummary/src.lib.titiler_algorithms.html @@ -13,8 +13,6 @@ - - @@ -37,18 +35,18 @@

    Functions

    - - + +

    convert_to_rgb(classified, mask, color)

    convert_to_rgb(classified, mask, color)

    Convert a classified image to RGB format, essentially creating a false-color image.

    Classes

    - + - + @@ -72,33 +70,11 @@

    dse-fire-recovery

    Navigation

    -

    Related Topics

    diff --git a/.sphinx/html/_autosummary/src.routers.check.connectivity.html b/.sphinx/html/_autosummary/src.routers.check.connectivity.html index 8e1a383..8a7fff2 100644 --- a/.sphinx/html/_autosummary/src.routers.check.connectivity.html +++ b/.sphinx/html/_autosummary/src.routers.check.connectivity.html @@ -13,8 +13,6 @@ - - @@ -37,8 +35,8 @@

    Functions

    CensorAndScale(*[, input_nbands, ...])

    CensorAndScale(*[, input_nbands, ...])

    Classify(*[, input_nbands, output_nbands, ...])

    Classify(*[, input_nbands, output_nbands, ...])

    - - + +

    check_connectivity([logger])

    check_connectivity([logger])

    Check the connectivity to http://example.com.

    @@ -61,38 +59,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.routers.check.dns.html b/.sphinx/html/_autosummary/src.routers.check.dns.html index 6bbe639..2212606 100644 --- a/.sphinx/html/_autosummary/src.routers.check.dns.html +++ b/.sphinx/html/_autosummary/src.routers.check.dns.html @@ -13,8 +13,6 @@ - - @@ -37,8 +35,8 @@

    Functions

    - - + +

    check_dns([logger])

    check_dns([logger])

    Check the DNS resolution for www.google.com.

    @@ -61,38 +59,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.routers.check.health.html b/.sphinx/html/_autosummary/src.routers.check.health.html index aa645c1..773630d 100644 --- a/.sphinx/html/_autosummary/src.routers.check.health.html +++ b/.sphinx/html/_autosummary/src.routers.check.health.html @@ -13,8 +13,6 @@ - - @@ -37,8 +35,8 @@

    Functions

    - - + +

    health([logger])

    health([logger])

    Ping pong!

    @@ -61,38 +59,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.routers.check.html b/.sphinx/html/_autosummary/src.routers.check.html index 1432353..f2a8ab4 100644 --- a/.sphinx/html/_autosummary/src.routers.check.html +++ b/.sphinx/html/_autosummary/src.routers.check.html @@ -13,8 +13,6 @@ - - @@ -37,16 +35,16 @@

    Modules

    - + - + - + - + @@ -70,36 +68,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.routers.check.sentry_error.html b/.sphinx/html/_autosummary/src.routers.check.sentry_error.html index 5053522..2650103 100644 --- a/.sphinx/html/_autosummary/src.routers.check.sentry_error.html +++ b/.sphinx/html/_autosummary/src.routers.check.sentry_error.html @@ -13,8 +13,6 @@ - - @@ -37,8 +35,8 @@

    Functions

    src.routers.check.connectivity

    src.routers.check.connectivity

    src.routers.check.dns

    src.routers.check.dns

    src.routers.check.health

    src.routers.check.health

    src.routers.check.sentry_error

    src.routers.check.sentry_error

    - - + +

    trigger_error([logger])

    trigger_error([logger])

    Triggers a division by zero error for Sentry to catch.

    @@ -61,38 +59,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.util.cloud_static_io.html b/.sphinx/html/_autosummary/src.util.cloud_static_io.html index bfc6e40..1300278 100644 --- a/.sphinx/html/_autosummary/src.util.cloud_static_io.html +++ b/.sphinx/html/_autosummary/src.util.cloud_static_io.html @@ -13,8 +13,6 @@ - - @@ -37,8 +35,8 @@

    Classes

    - - + +

    CloudStaticIOClient(bucket_name, provider)

    CloudStaticIOClient(bucket_name, provider)

    A client class for interacting with cloud storage services like S3, GCS, etc.

    @@ -61,32 +59,11 @@

    dse-fire-recovery

    Navigation

    -

    Related Topics

    diff --git a/.sphinx/html/_autosummary/src.util.gcp_secrets.html b/.sphinx/html/_autosummary/src.util.gcp_secrets.html index 35f0508..6d755cc 100644 --- a/.sphinx/html/_autosummary/src.util.gcp_secrets.html +++ b/.sphinx/html/_autosummary/src.util.gcp_secrets.html @@ -13,8 +13,6 @@ - - @@ -37,11 +35,8 @@

    Functions

    - - - - - + +

    get_mapbox_secret()

    get_ssh_secret()

    get_mapbox_secret()

    Retrieves the Mapbox API key from Google Cloud Secret Manager.

    @@ -64,32 +59,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.util.html b/.sphinx/html/_autosummary/src.util.html index 3f40e18..982e675 100644 --- a/.sphinx/html/_autosummary/src.util.html +++ b/.sphinx/html/_autosummary/src.util.html @@ -13,8 +13,6 @@ - - @@ -37,16 +35,16 @@

    Modules

    - + - + - + - + @@ -70,30 +68,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html b/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html index d2e29a5..d5d4f87 100644 --- a/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html +++ b/.sphinx/html/_autosummary/src.util.ingest_burn_zip.html @@ -13,8 +13,6 @@ - - @@ -37,11 +35,11 @@

    Functions

    src.util.cloud_static_io

    src.util.cloud_static_io

    src.util.gcp_secrets

    src.util.gcp_secrets

    src.util.ingest_burn_zip

    src.util.ingest_burn_zip

    src.util.raster_to_poly

    src.util.raster_to_poly

    - - + + - - + +

    ingest_esri_zip_file(zip_file_path)

    ingest_esri_zip_file(zip_file_path)

    Ingests an ESRI zip file containing shapefiles and tif files.

    shp_to_geojson(shp_file_path)

    shp_to_geojson(shp_file_path)

    Convert a shapefile to GeoJSON format, assuming we know the CRS of the shapefile based on its helper files.

    @@ -64,32 +62,11 @@

    dse-fire-recovery

    Navigation

    - diff --git a/.sphinx/html/_autosummary/src.util.raster_to_poly.html b/.sphinx/html/_autosummary/src.util.raster_to_poly.html index 6d37c59..f0c013c 100644 --- a/.sphinx/html/_autosummary/src.util.raster_to_poly.html +++ b/.sphinx/html/_autosummary/src.util.raster_to_poly.html @@ -13,7 +13,6 @@ - @@ -36,8 +35,8 @@

    Functions

    - - + +

    raster_mask_to_geojson(binary_mask)

    raster_mask_to_geojson(binary_mask)

    Converts a binary raster mask to a GeoJSON representation.

    @@ -60,31 +59,11 @@

    dse-fire-recovery

    Navigation

    -

    Related Topics

    diff --git a/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt b/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt index 6fab0d0..e24a000 100644 --- a/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt +++ b/.sphinx/html/_sources/_autosummary/src.util.gcp_secrets.rst.txt @@ -1,4 +1,4 @@ -src.util.gcp\_secrets +src.util.gcp\_secrets ===================== .. automodule:: src.util.gcp_secrets @@ -14,7 +14,6 @@ src.util.gcp\_secrets .. autosummary:: get_mapbox_secret - get_ssh_secret diff --git a/.sphinx/html/genindex.html b/.sphinx/html/genindex.html index 937401e..b72bd74 100644 --- a/.sphinx/html/genindex.html +++ b/.sphinx/html/genindex.html @@ -147,8 +147,6 @@

    G

  • (in module src.util.gcp_secrets)
  • -
  • get_ssh_secret() (in module src.util.gcp_secrets) -
  • diff --git a/.sphinx/html/objects.inv b/.sphinx/html/objects.inv index 10f6ae275e3007b28833371274f0d992f3e02af8..de3314e654a67673b7be964ace293dc725853fdf 100644 GIT binary patch delta 1290 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 H=(BGoO2Ljc diff --git a/.sphinx/html/searchindex.js b/.sphinx/html/searchindex.js index b9a7637..d4fd1c0 100644 --- a/.sphinx/html/searchindex.js +++ b/.sphinx/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index", "modules", "src", "src.lib", "src.routers", "src.routers.check", "src.util"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst", "modules.rst", "src.rst", "src.lib.rst", "src.routers.rst", "src.routers.check.rst", "src.util.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!", "src", "src package", "src.lib package", "src.routers package", "src.routers.check package", "src.util package"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20, 21], "search": [20, 23], "page": 20, "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19], "class": [5, 7, 16, 23, 26], "src": [], "router": [21, 22], "depend": [21, 22, 25], "get_cloud_logg": [22, 24, 25], "get": [23, 24], "logger": [24, 25], "from": [23, 24, 26], "googl": 24, "cloud": 24, "log": 24, "servic": [], "return": [23, 24], "A": 23, "type": [], "get_cloud_static_io_cli": [22, 24], "an": [23, 24], "instanc": 24, "cloudstaticiocli": [22, 24, 26], "arg": [23, 24], "The": [23, 24], "us": [23, 24], "get_manifest": [22, 24, 26], "cloud_static_io_cli": 24, "manifest": 24, "static": 24, "io": 24, "client": 24, "paramet": 23, "download": [22, 24, 26], "storag": 24, "obtain": [], "init_sentri": [22, 24], "initi": 24, "sentri": 24, "object": [23, 24, 26], "none": [23, 24], "dict": [23, 24], "get_mapbox_secret": [22, 24, 26], "retriev": [23, 24], "mapbox": 24, "secret": 24, "gcp": 24, "str": [23, 24], "packag": 21, "subpackag": 21, "lib": [21, 22], "submodul": 21, "burn_sever": [21, 22], "query_rap": [21, 22], "query_sentinel": [21, 22], "query_soil": [21, 22], "titiler_algorithm": [21, 22], "content": 21, "util": [21, 22], "cloud_static_io": [21, 22], "gcp_secret": [21, 22], "ingest_burn_zip": [21, 22], "raster_to_poli": [21, 22], "app": 21, "calc_burn_metr": [22, 23], "calc_dnbr": [22, 23], "calc_nbr": [22, 23], "calc_rbr": [22, 23], "calc_rdnbr": [22, 23], "classify_burn": [22, 23], "rap_get_biomass": [22, 23], "sentinel2cli": [22, 23], "arrange_stack": [22, 23], "classifi": [22, 23], "clip_metrics_stack_to_boundari": [], "derive_boundari": [22, 23, 26], "get_item": [22, 23], "ingest_barc_classif": [22, 23], "query_fire_ev": [22, 23], "reduce_time_rang": [22, 23], "set_boundari": [22, 23], "edit_get_ecoclass_info": [22, 23], "sdm_create_aoi": [22, 23], "sdm_get_available_interpret": [22, 23], "sdm_get_ecoclassid_from_mu_info": [22, 23], "sdm_get_esa_mapunitid_poli": [22, 23], "censorandscal": [22, 23], "color": [22, 23], "model_computed_field": [22, 23], "model_config": [22, 23], "model_field": [22, 23], "threshold": [22, 23], "convert_to_rgb": [22, 23], "check": [22, 24], "connect": [22, 24], "dn": [22, 24], "health": [22, 24], "sentry_error": [22, 24], "get_derived_product": [22, 26], "impersonate_service_account": [22, 26], "local_fetch_id_token": [22, 26], "update_manifest": [22, 26], "upload": [22, 26], "upload_cog": [22, 26], "upload_fire_ev": [22, 26], "upload_rap_estim": [22, 26], "validate_credenti": [22, 26], "get_ssh_secret": [22, 26], "ingest_esri_zip_fil": [22, 26], "shp_to_geojson": [22, 26], "raster_mask_to_geojson": [22, 26], "prefire_nir": 23, "prefire_swir": 23, "postfire_nir": 23, "postfire_swir": 23, "nbr": 23, "dnbr": 23, "rdnbr": 23, "rbr": 23, "pre": 23, "post": 23, "fire": 23, "nir": 23, "swir": 23, "band": 23, "xr": 23, "dataarrai": 23, "stack": 23, "nbr_prefir": 23, "nbr_postfir": 23, "differ": 23, "normal": 23, "burn": 23, "ratio": 23, "arrai": 23, "band_nir": 23, "band_swir": 23, "input": 23, "first": 23, "imag": 23, "e": 23, "g": 23, "b8a": 23, "second": 23, "b12": 23, "rel": 23, "reclassifi": 23, "base": [23, 26], "given": 23, "dictionari": 23, "correspond": 23, "valu": 23, "ignition_d": 23, "boundary_geojson": 23, "buffer_dist": 23, "0": 23, "01": 23, "geojson_boundari": 23, "barc_classif": 23, "buffer": 23, "1": 23, "cr": 23, "epsg": 23, "4326": 23, "item": 23, "resolut": 23, "20": 23, "threshold_sourc": 23, "burn_metr": 23, "metric_nam": 23, "025": 23, "date_rang": 23, "from_bbox": 23, "true": 23, "max_item": 23, "barc_classifications_xarrai": 23, "prefire_date_rang": [23, 26], "postfire_date_rang": [23, 26], "range_stack": 23, "ecoclass_id": 23, "geojson": 23, "aoi_smd_id": 23, "mu_polygon_kei": 23, "backoff_max": 23, "200": 23, "backoff_valu": 23, "backoff_incr": 23, "25": 23, "input_nband": 23, "int": 23, "output_nband": 23, "output_dtyp": 23, "output_min": 23, "sequenc": 23, "output_max": 23, "red": 23, "extra_data": 23, "ani": 23, "basealgorithm": 23, "classvar": 23, "computedfieldinfo": 23, "comput": 23, "field": 23, "name": 23, "configdict": 23, "extra": 23, "allow": 23, "configur": 23, "model": 23, "should": 23, "conform": 23, "pydant": 23, "config": 23, "fieldinfo": 23, "annot": 23, "requir": 23, "fals": 23, "default": [23, 26], "union": 23, "nonetyp": 23, "metadata": 23, "about": 23, "defin": 23, "map": 23, "thi": 23, "replac": 23, "__fields__": 23, "v1": 23, "ndarrai": 23, "mask": 23, "check_connect": [24, 25], "check_dn": [24, 25], "trigger_error": [24, 25], "async": 25, "bucket_nam": 26, "provid": [23, 26], "remote_path": 26, "target_local_path": 26, "file": 26, "remot": 26, "s3": [23, 26], "server": 26, "local": 26, "also": [23, 26], "extract": 26, "specifi": [23, 26], "affili": 26, "fire_event_nam": 26, "audienc": 26, "bound": [23, 26], "source_local_path": 26, "sourc": [23, 26], "metrics_stack": [23, 26], "rap_estim": 26, "zip_file_path": 26, "shp_file_path": 26, "binary_mask": 26, "biomass": 23, "estim": 23, "rangeland": 23, "analysi": 23, "platform": 23, "ignit": 23, "year": 23, "boundari": 23, "locat": 23, "rap": 23, "four": 23, "categori": 23, "annual": 23, "forb": 23, "grass": 23, "perenni": 23, "shrub": 23, "tree": 23, "http": 23, "ntsg": 23, "umt": 23, "edu": 23, "data": 23, "veget": 23, "v3": 23, "readm": 23, "ar": 23, "avail": 23, "1986": 23, "2022": 23, "date": 23, "i": 23, "valueerror": 23, "rais": 23, "format": 23, "yyyi": 23, "mm": 23, "dd": 23, "geometri": 23, "float": 23, "option": 23, "distanc": 23, "around": 23, "dataset": 23, "If": 23, "befor": 23, "where": 23, "arrang": 23, "process": 23, "reduc": 23, "time": 23, "dimens": 23, "accord": 23, "sentinel": 23, "list": 23, "xarrai": 23, "our": 23, "desir": 23, "clip": 23, "calcul": 23, "metric": 23, "prefir": 23, "postfir": 23, "satellit": 23, "wiht": 23, "self": 23, "note": 23, "v0": 23, "we": 23, "actual": 23, "call": 23, "method": 23, "classif": 23, "runtim": 23, "titil": 23, "": 23, "algorithm": 23, "after": 23, "save": 23, "derived_classif": 23, "attribut": 23, "deriv": 23, "layer": 23, "set": 23, "mean": 23, "when": 23, "visual": 23, "within": 23, "bucket": 23, "2": 23, "l2a": 23, "collect": 23, "rang": 23, "tupl": 23, "contain": 23, "start": 23, "end": 23, "start_dat": 23, "end_dat": 23, "bool": 23, "whether": 23, "box": 23, "bbox": 23, "maximum": 23, "number": 23, "which": 23, "all": 23, "pystac": 23, "itemcollect": 23, "match": 23, "criteria": 23, "ingest": 23, "barc": 23, "area": 23, "reflect": 23, "thei": 23, "same": 23, "queri": 23, "event": 23, "repres": 23, "flag": 23, "indic": 23, "insuffici": 23, "imageri": 23, "might": 23, "happen": 23, "so": 23, "necessarili": 23, "problem": 23, "themselv": 23, "definit": 23, "issu": 23, "too": 23, "narrow": 23, "take": 23, "median": 23, "along": 23, "later": 23, "stac": 23, "api": 23}, "objects": {"": [[22, 0, 0, "-", "src"]], "src": [[22, 0, 0, "-", "app"], [23, 0, 0, "-", "lib"], [24, 0, 0, "-", "routers"], [26, 0, 0, "-", "util"]], "src.lib": [[23, 0, 0, "-", "burn_severity"], [23, 0, 0, "-", "query_rap"], [23, 0, 0, "-", "query_sentinel"], [23, 0, 0, "-", "query_soil"], [23, 0, 0, "-", "titiler_algorithms"]], "src.lib.burn_severity": [[23, 1, 1, "", "calc_burn_metrics"], [23, 1, 1, "", "calc_dnbr"], [23, 1, 1, "", "calc_nbr"], [23, 1, 1, "", "calc_rbr"], [23, 1, 1, "", "calc_rdnbr"], [23, 1, 1, "", "classify_burn"]], "src.lib.query_rap": [[23, 1, 1, "", "rap_get_biomass"]], "src.lib.query_sentinel": [[23, 2, 1, "", "Sentinel2Client"]], "src.lib.query_sentinel.Sentinel2Client": [[23, 3, 1, "", "arrange_stack"], [23, 3, 1, "", "calc_burn_metrics"], [23, 3, 1, "", "classify"], [23, 3, 1, "", "derive_boundary"], [23, 3, 1, "", "get_items"], [23, 3, 1, "", "ingest_barc_classifications"], [23, 3, 1, "", "query_fire_event"], [23, 3, 1, "", "reduce_time_range"], [23, 3, 1, "", "set_boundary"]], "src.lib.query_soil": [[23, 1, 1, "", "edit_get_ecoclass_info"], [23, 1, 1, "", "sdm_create_aoi"], [23, 1, 1, "", "sdm_get_available_interpretations"], [23, 1, 1, "", "sdm_get_ecoclassid_from_mu_info"], [23, 1, 1, "", "sdm_get_esa_mapunitid_poly"]], "src.lib.titiler_algorithms": [[23, 2, 1, "", "CensorAndScale"], [23, 2, 1, "", "Classify"], [23, 1, 1, "", "convert_to_rgb"]], "src.lib.titiler_algorithms.CensorAndScale": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.lib.titiler_algorithms.Classify": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.routers": [[25, 0, 0, "-", "check"], [24, 0, 0, "-", "dependencies"]], "src.routers.check": [[25, 0, 0, "-", "connectivity"], [25, 0, 0, "-", "dns"], [25, 0, 0, "-", "health"], [25, 0, 0, "-", "sentry_error"]], "src.routers.check.connectivity": [[25, 1, 1, "", "check_connectivity"]], "src.routers.check.dns": [[25, 1, 1, "", "check_dns"]], "src.routers.check.health": [[25, 1, 1, "", "health"]], "src.routers.check.sentry_error": [[25, 1, 1, "", "trigger_error"]], "src.routers.dependencies": [[24, 1, 1, "", "get_cloud_logger"], [24, 1, 1, "", "get_cloud_static_io_client"], [24, 1, 1, "", "get_manifest"], [24, 1, 1, "", "get_mapbox_secret"], [24, 1, 1, "", "init_sentry"]], "src.util": [[26, 0, 0, "-", "cloud_static_io"], [26, 0, 0, "-", "gcp_secrets"], [26, 0, 0, "-", "ingest_burn_zip"], [26, 0, 0, "-", "raster_to_poly"]], "src.util.cloud_static_io": [[26, 2, 1, "", "CloudStaticIOClient"]], "src.util.cloud_static_io.CloudStaticIOClient": [[26, 3, 1, "", "download"], [26, 3, 1, "", "get_derived_products"], [26, 3, 1, "", "get_manifest"], [26, 3, 1, "", "impersonate_service_account"], [26, 3, 1, "", "local_fetch_id_token"], [26, 3, 1, "", "update_manifest"], [26, 3, 1, "", "upload"], [26, 3, 1, "", "upload_cogs"], [26, 3, 1, "", "upload_fire_event"], [26, 3, 1, "", "upload_rap_estimates"], [26, 3, 1, "", "validate_credentials"]], "src.util.gcp_secrets": [[26, 1, 1, "", "get_mapbox_secret"], [26, 1, 1, "", "get_ssh_secret"]], "src.util.ingest_burn_zip": [[26, 1, 1, "", "ingest_esri_zip_file"], [26, 1, 1, "", "shp_to_geojson"]], "src.util.raster_to_poly": [[26, 1, 1, "", "raster_mask_to_geojson"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26], "app": [1, 22], "lib": [2, 3, 4, 5, 6, 7, 23], "burn_sever": [3, 23], "query_rap": [4, 23], "query_sentinel": [5, 23], "query_soil": [6, 23], "titiler_algorithm": [7, 23], "router": [8, 9, 10, 11, 12, 13, 14, 24, 25], "check": [9, 10, 11, 12, 13, 25], "connect": [10, 25], "dn": [11, 25], "health": [12, 25], "sentry_error": [13, 25], "depend": [14, 24], "util": [15, 16, 17, 18, 19, 26], "cloud_static_io": [16, 26], "gcp_secret": [17, 26], "ingest_burn_zip": [18, 26], "raster_to_poli": [19, 26], "packag": [22, 23, 24, 25, 26], "subpackag": [22, 24], "submodul": [22, 23, 24, 25, 26], "modul": [22, 23, 24, 25, 26], "content": [22, 23, 24, 25, 26]}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"], [21, "src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "src package": [[22, "src-package"]], "Subpackages": [[22, "subpackages"], [24, "subpackages"]], "Submodules": [[22, "submodules"], [24, "submodules"], [25, "submodules"], [26, "submodules"], [23, "submodules"]], "src.app module": [[22, "module-src.app"]], "Module contents": [[22, "module-src"], [24, "module-src.routers"], [25, "module-src.routers.check"], [26, "module-src.util"], [23, "module-src.lib"]], "src.routers package": [[24, "src-routers-package"]], "src.routers.dependencies module": [[24, "module-src.routers.dependencies"]], "src.routers.check package": [[25, "src-routers-check-package"]], "src.routers.check.connectivity module": [[25, "module-src.routers.check.connectivity"]], "src.routers.check.dns module": [[25, "module-src.routers.check.dns"]], "src.routers.check.health module": [[25, "module-src.routers.check.health"]], "src.routers.check.sentry_error module": [[25, "module-src.routers.check.sentry_error"]], "src.util package": [[26, "src-util-package"]], "src.util.cloud_static_io module": [[26, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets module": [[26, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip module": [[26, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly module": [[26, "module-src.util.raster_to_poly"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib package": [[23, "src-lib-package"]], "src.lib.burn_severity module": [[23, "module-src.lib.burn_severity"]], "src.lib.query_rap module": [[23, "module-src.lib.query_rap"]], "src.lib.query_sentinel module": [[23, "module-src.lib.query_sentinel"]], "src.lib.query_soil module": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms module": [[23, "module-src.lib.titiler_algorithms"]]}, "indexentries": {"module": [[2, "module-src.lib"], [4, "module-src.lib.query_rap"], [5, "module-src.lib.query_sentinel"], [22, "module-src"], [22, "module-src.app"], [23, "module-src.lib"], [23, "module-src.lib.burn_severity"], [23, "module-src.lib.query_rap"], [23, "module-src.lib.query_sentinel"], [23, "module-src.lib.query_soil"], [23, "module-src.lib.titiler_algorithms"]], "src.lib": [[2, "module-src.lib"], [23, "module-src.lib"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"], [23, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"], [23, "module-src.lib.query_sentinel"]], "src": [[22, "module-src"]], "src.app": [[22, "module-src.app"]], "censorandscale (class in src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.CensorAndScale"]], "classify (class in src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.Classify"]], "sentinel2client (class in src.lib.query_sentinel)": [[23, "src.lib.query_sentinel.Sentinel2Client"]], "arrange_stack() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.arrange_stack"]], "calc_burn_metrics() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_burn_metrics"]], "calc_burn_metrics() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.calc_burn_metrics"]], "calc_dnbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_dnbr"]], "calc_nbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_nbr"]], "calc_rbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_rbr"]], "calc_rdnbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_rdnbr"]], "classify() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.classify"]], "classify_burn() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.classify_burn"]], "color (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.color"]], "color (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.color"]], "convert_to_rgb() (in module src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.convert_to_rgb"]], "derive_boundary() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.derive_boundary"]], "edit_get_ecoclass_info() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.edit_get_ecoclass_info"]], "get_items() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.get_items"]], "ingest_barc_classifications() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.ingest_barc_classifications"]], "model_computed_fields (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_computed_fields"]], "model_computed_fields (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_computed_fields"]], "model_config (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_config"]], "model_config (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_config"]], "model_fields (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_fields"]], "model_fields (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_fields"]], "query_fire_event() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.query_fire_event"]], "rap_get_biomass() (in module src.lib.query_rap)": [[23, "src.lib.query_rap.rap_get_biomass"]], "reduce_time_range() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.reduce_time_range"]], "sdm_create_aoi() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_create_aoi"]], "sdm_get_available_interpretations() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_available_interpretations"]], "sdm_get_ecoclassid_from_mu_info() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_ecoclassid_from_mu_info"]], "sdm_get_esa_mapunitid_poly() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_esa_mapunitid_poly"]], "set_boundary() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.set_boundary"]], "src.lib.burn_severity": [[23, "module-src.lib.burn_severity"]], "src.lib.query_soil": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[23, "module-src.lib.titiler_algorithms"]], "thresholds (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.thresholds"]], "thresholds (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.thresholds"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["_autosummary/src", "_autosummary/src.app", "_autosummary/src.lib", "_autosummary/src.lib.burn_severity", "_autosummary/src.lib.query_rap", "_autosummary/src.lib.query_sentinel", "_autosummary/src.lib.query_soil", "_autosummary/src.lib.titiler_algorithms", "_autosummary/src.routers", "_autosummary/src.routers.check", "_autosummary/src.routers.check.connectivity", "_autosummary/src.routers.check.dns", "_autosummary/src.routers.check.health", "_autosummary/src.routers.check.sentry_error", "_autosummary/src.routers.dependencies", "_autosummary/src.util", "_autosummary/src.util.cloud_static_io", "_autosummary/src.util.gcp_secrets", "_autosummary/src.util.ingest_burn_zip", "_autosummary/src.util.raster_to_poly", "index", "modules", "src", "src.lib", "src.routers", "src.routers.check", "src.util"], "filenames": ["_autosummary/src.rst", "_autosummary/src.app.rst", "_autosummary/src.lib.rst", "_autosummary/src.lib.burn_severity.rst", "_autosummary/src.lib.query_rap.rst", "_autosummary/src.lib.query_sentinel.rst", "_autosummary/src.lib.query_soil.rst", "_autosummary/src.lib.titiler_algorithms.rst", "_autosummary/src.routers.rst", "_autosummary/src.routers.check.rst", "_autosummary/src.routers.check.connectivity.rst", "_autosummary/src.routers.check.dns.rst", "_autosummary/src.routers.check.health.rst", "_autosummary/src.routers.check.sentry_error.rst", "_autosummary/src.routers.dependencies.rst", "_autosummary/src.util.rst", "_autosummary/src.util.cloud_static_io.rst", "_autosummary/src.util.gcp_secrets.rst", "_autosummary/src.util.ingest_burn_zip.rst", "_autosummary/src.util.raster_to_poly.rst", "index.rst", "modules.rst", "src.rst", "src.lib.rst", "src.routers.rst", "src.routers.check.rst", "src.util.rst"], "titles": ["src", "src.app", "src.lib", "src.lib.burn_severity", "src.lib.query_rap", "src.lib.query_sentinel", "src.lib.query_soil", "src.lib.titiler_algorithms", "src.routers", "src.routers.check", "src.routers.check.connectivity", "src.routers.check.dns", "src.routers.check.health", "src.routers.check.sentry_error", "src.routers.dependencies", "src.util", "src.util.cloud_static_io", "src.util.gcp_secrets", "src.util.ingest_burn_zip", "src.util.raster_to_poly", "Welcome to dse-fire-recovery\u2019s documentation!", "src", "src package", "src.lib package", "src.routers package", "src.routers.check package", "src.util package"], "terms": {"index": 20, "modul": [0, 2, 8, 9, 15, 20, 21], "search": [20, 23], "page": [20, 23], "function": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19, 23], "class": [5, 7, 16, 23, 26], "src": [], "router": [21, 22], "depend": [21, 22, 25], "get_cloud_logg": [22, 24, 25], "get": [23, 24, 26], "logger": [24, 25, 26], "from": [23, 24, 26], "googl": [24, 25, 26], "cloud": [24, 26], "log": [24, 25, 26], "servic": [23, 26], "return": [23, 24, 25, 26], "A": [23, 25, 26], "type": [], "get_cloud_static_io_cli": [22, 24], "an": [23, 24, 25, 26], "instanc": 24, "cloudstaticiocli": [22, 24, 26], "arg": [23, 24, 25, 26], "The": [23, 24, 25, 26], "us": [23, 24, 25, 26], "get_manifest": [22, 24, 26], "cloud_static_io_cli": 24, "manifest": [24, 26], "static": 24, "io": 24, "client": [24, 26], "paramet": [23, 25], "download": [22, 24, 26], "storag": [24, 26], "obtain": [], "init_sentri": [22, 24], "initi": [24, 26], "sentri": [24, 25], "object": [23, 24, 25, 26], "none": [23, 24, 26], "dict": [23, 24, 25, 26], "get_mapbox_secret": [22, 24, 26], "retriev": [23, 24, 26], "mapbox": [24, 26], "secret": [24, 26], "gcp": [24, 26], "str": [23, 24, 25, 26], "packag": 21, "subpackag": 21, "lib": [21, 22], "submodul": 21, "burn_sever": [21, 22], "query_rap": [21, 22], "query_sentinel": [21, 22], "query_soil": [21, 22], "titiler_algorithm": [21, 22], "content": 21, "util": [21, 22], "cloud_static_io": [21, 22], "gcp_secret": [21, 22], "ingest_burn_zip": [21, 22], "raster_to_poli": [21, 22], "app": 21, "calc_burn_metr": [22, 23], "calc_dnbr": [22, 23], "calc_nbr": [22, 23], "calc_rbr": [22, 23], "calc_rdnbr": [22, 23], "classify_burn": [22, 23], "rap_get_biomass": [22, 23], "sentinel2cli": [22, 23], "arrange_stack": [22, 23], "classifi": [22, 23], "clip_metrics_stack_to_boundari": [], "derive_boundari": [22, 23, 26], "get_item": [22, 23], "ingest_barc_classif": [22, 23], "query_fire_ev": [22, 23], "reduce_time_rang": [22, 23], "set_boundari": [22, 23], "edit_get_ecoclass_info": [22, 23], "sdm_create_aoi": [22, 23], "sdm_get_available_interpret": [22, 23], "sdm_get_ecoclassid_from_mu_info": [22, 23], "sdm_get_esa_mapunitid_poli": [22, 23], "censorandscal": [22, 23], "color": [22, 23], "model_computed_field": [22, 23], "model_config": [22, 23], "model_field": [22, 23], "threshold": [22, 23], "convert_to_rgb": [22, 23], "check": [22, 24, 26], "connect": [22, 24], "dn": [22, 24], "health": [22, 24], "sentry_error": [22, 24], "get_derived_product": [22, 26], "impersonate_service_account": [22, 26], "local_fetch_id_token": [22, 26], "update_manifest": [22, 26], "upload": [22, 26], "upload_cog": [22, 26], "upload_fire_ev": [22, 26], "upload_rap_estim": [22, 26], "validate_credenti": [22, 26], "get_ssh_secret": [], "ingest_esri_zip_fil": [22, 26], "shp_to_geojson": [22, 26], "raster_mask_to_geojson": [22, 26], "prefire_nir": 23, "prefire_swir": 23, "postfire_nir": 23, "postfire_swir": 23, "nbr": 23, "dnbr": 23, "rdnbr": 23, "rbr": 23, "pre": 23, "post": 23, "fire": [23, 26], "nir": 23, "swir": 23, "band": 23, "xr": [23, 26], "dataarrai": [23, 26], "stack": [23, 26], "nbr_prefir": 23, "nbr_postfir": 23, "differ": 23, "normal": 23, "burn": 23, "ratio": 23, "arrai": 23, "band_nir": 23, "band_swir": 23, "input": 23, "first": 23, "imag": 23, "e": [23, 26], "g": [23, 26], "b8a": 23, "second": 23, "b12": 23, "rel": [23, 26], "reclassifi": 23, "base": [23, 26], "given": [23, 26], "dictionari": [23, 25, 26], "correspond": [23, 26], "valu": [23, 26], "ignition_d": 23, "boundary_geojson": 23, "buffer_dist": 23, "0": 23, "01": 23, "geojson_boundari": 23, "barc_classif": 23, "buffer": 23, "1": 23, "cr": [23, 26], "epsg": 23, "4326": 23, "item": 23, "resolut": [23, 25], "20": 23, "threshold_sourc": 23, "burn_metr": 23, "metric_nam": 23, "025": 23, "date_rang": 23, "from_bbox": 23, "true": 23, "max_item": 23, "barc_classifications_xarrai": 23, "prefire_date_rang": [23, 26], "postfire_date_rang": [23, 26], "range_stack": 23, "ecoclass_id": 23, "geojson": [23, 26], "aoi_smd_id": 23, "mu_polygon_kei": 23, "backoff_max": 23, "200": 23, "backoff_valu": 23, "backoff_incr": 23, "25": 23, "input_nband": 23, "int": [23, 25], "output_nband": 23, "output_dtyp": 23, "output_min": 23, "sequenc": 23, "output_max": 23, "red": 23, "extra_data": 23, "ani": 23, "basealgorithm": 23, "classvar": 23, "computedfieldinfo": 23, "comput": 23, "field": 23, "name": [23, 26], "configdict": 23, "extra": 23, "allow": [23, 26], "configur": 23, "model": 23, "should": 23, "conform": 23, "pydant": 23, "config": 23, "fieldinfo": 23, "annot": 23, "requir": 23, "fals": 23, "default": [23, 26], "union": 23, "nonetyp": 23, "metadata": 23, "about": 23, "defin": 23, "map": 23, "thi": [23, 26], "replac": 23, "__fields__": 23, "v1": 23, "ndarrai": 23, "mask": [23, 26], "check_connect": [24, 25], "check_dn": [24, 25], "trigger_error": [24, 25], "async": 25, "bucket_nam": 26, "provid": [23, 26], "remote_path": 26, "target_local_path": 26, "file": 26, "remot": 26, "s3": [23, 26], "server": 26, "local": 26, "also": [23, 26], "extract": [], "specifi": [23, 26], "affili": 26, "fire_event_nam": 26, "audienc": 26, "bound": [23, 26], "source_local_path": 26, "sourc": [23, 26], "metrics_stack": [23, 26], "rap_estim": 26, "zip_file_path": 26, "shp_file_path": 26, "binary_mask": 26, "biomass": 23, "estim": [23, 26], "rangeland": 23, "analysi": 23, "platform": 23, "ignit": 23, "year": 23, "boundari": [23, 26], "locat": [23, 26], "rap": [23, 26], "four": 23, "categori": 23, "annual": 23, "forb": 23, "grass": 23, "perenni": 23, "shrub": 23, "tree": 23, "http": [23, 25, 26], "ntsg": 23, "umt": 23, "edu": 23, "data": [23, 26], "veget": 23, "v3": 23, "readm": 23, "ar": [23, 26], "avail": [23, 26], "1986": 23, "2022": 23, "date": [23, 26], "i": [23, 25, 26], "valueerror": [23, 26], "rais": [23, 25, 26], "format": [23, 26], "yyyi": 23, "mm": 23, "dd": 23, "geometri": 23, "float": 23, "option": [23, 26], "distanc": 23, "around": 23, "dataset": 23, "If": [23, 25, 26], "befor": [23, 26], "where": [23, 26], "arrang": 23, "process": [23, 26], "reduc": 23, "time": 23, "dimens": 23, "accord": [23, 26], "sentinel": 23, "list": [23, 26], "xarrai": [23, 26], "our": 23, "desir": 23, "clip": 23, "calcul": 23, "metric": [23, 26], "prefir": [23, 26], "postfir": [23, 26], "satellit": 23, "wiht": 23, "self": 23, "note": 23, "v0": 23, "we": [23, 26], "actual": 23, "call": 23, "method": 23, "classif": 23, "runtim": 23, "titil": 23, "": 23, "algorithm": 23, "after": [23, 26], "save": 23, "derived_classif": 23, "attribut": [23, 26], "deriv": [23, 26], "layer": 23, "set": [23, 26], "mean": 23, "when": [23, 26], "visual": 23, "within": [23, 26], "bucket": [23, 26], "2": 23, "l2a": 23, "collect": 23, "rang": [23, 26], "tupl": [23, 25, 26], "contain": [23, 25, 26], "start": 23, "end": 23, "start_dat": 23, "end_dat": 23, "bool": [23, 26], "whether": [23, 26], "box": 23, "bbox": 23, "maximum": 23, "number": 23, "which": [23, 26], "all": 23, "pystac": 23, "itemcollect": 23, "match": 23, "criteria": 23, "ingest": [23, 26], "barc": 23, "area": 23, "reflect": 23, "thei": 23, "same": 23, "queri": 23, "event": [23, 26], "repres": [23, 26], "flag": [23, 26], "indic": [23, 26], "insuffici": 23, "imageri": 23, "might": 23, "happen": 23, "so": [23, 26], "necessarili": 23, "problem": 23, "themselv": 23, "definit": [23, 25], "issu": 23, "too": 23, "narrow": 23, "take": 23, "median": 23, "along": 23, "later": 23, "stac": 23, "api": [23, 26], "inform": [23, 26], "ecoclass": 23, "edit": 23, "databas": 23, "id": [23, 26], "boolean": 23, "success": 23, "except": [23, 26], "error": [23, 25, 26], "deprec": 23, "circumv": 23, "need": [23, 26], "creat": [23, 26], "aoi": 23, "sdm": 23, "interest": 23, "soil": 23, "mart": 23, "assum": [23, 26], "singl": 23, "featur": 23, "polygon": 23, "request": 23, "respons": [23, 25], "creation": 23, "otherwis": [23, 26], "can": [23, 26], "interpret": 23, "ecolog": 23, "site": 23, "endpoint": 23, "unit": 23, "kei": [23, 26], "essenti": 23, "direct": 23, "ecoclassid": 23, "domin": 23, "cover": 23, "speci": 23, "sdmdataaccess": 23, "sc": 23, "egov": 23, "usda": 23, "gov": 23, "aspx": 23, "test": 23, "out": 23, "interact": [23, 26], "how": 23, "wa": 23, "construct": 23, "filter": 23, "panda": 23, "datafram": 23, "occur": [23, 25], "These": 23, "occasion": 23, "refus": 23, "traffic": 23, "backoff": 23, "retri": 23, "necessari": 23, "linear": 23, "fashion": 23, "current": [23, 26], "increment": 23, "geopanda": 23, "geodatafram": 23, "max": 23, "reach": 23, "convert": [23, 26], "rgb": 23, "extens": 23, "final": 23, "user": 23, "upon": 23, "tile": 23, "like": [23, 26], "do": 23, "front": 23, "np": 23, "pixel": 23, "includ": 23, "must": 23, "one": 23, "green": 23, "blue": 23, "invalid": 23, "exampl": 25, "com": 25, "statu": 25, "code": 25, "messag": [25, 26], "httpexcept": 25, "dure": [25, 26], "www": 25, "resolv": 25, "ip": 25, "address": [25, 26], "ping": 25, "pong": 25, "trigger": 25, "divis": 25, "zero": 25, "catch": 25, "zerodivisionerror": 25, "gc": 26, "etc": 26, "smart_open": 26, "librari": 26, "agnost": 26, "wai": 26, "It": 26, "boto3": 26, "aw": 26, "specif": 26, "role": 26, "credenti": 26, "onli": 26, "support": 26, "env": 26, "environ": 26, "variabl": 26, "prod": 26, "role_arn": 26, "arn": 26, "service_account_email": 26, "email": 26, "account": 26, "author": 26, "imperson": 26, "role_session_nam": 26, "session": 26, "arbitrari": 26, "sts_client": 26, "botocor": 26, "st": 26, "prefix": 26, "url": 26, "tofu": 26, "onc": 26, "iam_credenti": 26, "auth": 26, "impersonated_credenti": 26, "iam": 26, "role_assumed_credenti": 26, "boto_sess": 26, "long": 26, "last": 26, "u": 26, "path": 26, "product": 26, "associ": 26, "basic": 26, "anyth": 26, "folder": 26, "valy": 26, "public": 26, "filenam": 26, "full": 26, "fetch": 26, "token": 26, "authent": 26, "run": 26, "On": 26, "sinc": 26, "alreadi": 26, "defaultcredentialserror": 26, "fail": 26, "updat": 26, "exist": 26, "overwritten": 26, "being": 26, "cog": 26, "optim": 26, "geotiff": 26, "band_nam": 26, "tif": 26, "add": 26, "overview": 26, "faster": 26, "load": 26, "lower": 26, "zoom": 26, "level": 26, "json": 26, "f": 26, "rangeland_analysis_platform_": 26, "valid": 26, "expir": 26, "oidc": 26, "web": 26, "ident": 26, "further": 26, "handl": 26, "re": 26, "manag": 26, "you": 26, "gcloud": 26, "applic": 26, "login": 26, "esri": 26, "zip": 26, "shapefil": 26, "expect": 26, "form": 26, "shp": 26, "shx": 26, "prj": 26, "dbf": 26, "two": 26, "valid_shapefil": 26, "valid_tif": 26, "each": 26, "represent": 26, "know": 26, "its": 26, "helper": 26, "binari": 26, "raster": 26}, "objects": {"": [[22, 0, 0, "-", "src"]], "src": [[22, 0, 0, "-", "app"], [23, 0, 0, "-", "lib"], [24, 0, 0, "-", "routers"], [26, 0, 0, "-", "util"]], "src.lib": [[23, 0, 0, "-", "burn_severity"], [23, 0, 0, "-", "query_rap"], [23, 0, 0, "-", "query_sentinel"], [23, 0, 0, "-", "query_soil"], [23, 0, 0, "-", "titiler_algorithms"]], "src.lib.burn_severity": [[23, 1, 1, "", "calc_burn_metrics"], [23, 1, 1, "", "calc_dnbr"], [23, 1, 1, "", "calc_nbr"], [23, 1, 1, "", "calc_rbr"], [23, 1, 1, "", "calc_rdnbr"], [23, 1, 1, "", "classify_burn"]], "src.lib.query_rap": [[23, 1, 1, "", "rap_get_biomass"]], "src.lib.query_sentinel": [[23, 2, 1, "", "Sentinel2Client"]], "src.lib.query_sentinel.Sentinel2Client": [[23, 3, 1, "", "arrange_stack"], [23, 3, 1, "", "calc_burn_metrics"], [23, 3, 1, "", "classify"], [23, 3, 1, "", "derive_boundary"], [23, 3, 1, "", "get_items"], [23, 3, 1, "", "ingest_barc_classifications"], [23, 3, 1, "", "query_fire_event"], [23, 3, 1, "", "reduce_time_range"], [23, 3, 1, "", "set_boundary"]], "src.lib.query_soil": [[23, 1, 1, "", "edit_get_ecoclass_info"], [23, 1, 1, "", "sdm_create_aoi"], [23, 1, 1, "", "sdm_get_available_interpretations"], [23, 1, 1, "", "sdm_get_ecoclassid_from_mu_info"], [23, 1, 1, "", "sdm_get_esa_mapunitid_poly"]], "src.lib.titiler_algorithms": [[23, 2, 1, "", "CensorAndScale"], [23, 2, 1, "", "Classify"], [23, 1, 1, "", "convert_to_rgb"]], "src.lib.titiler_algorithms.CensorAndScale": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.lib.titiler_algorithms.Classify": [[23, 4, 1, "", "color"], [23, 4, 1, "", "model_computed_fields"], [23, 4, 1, "", "model_config"], [23, 4, 1, "", "model_fields"], [23, 4, 1, "", "thresholds"]], "src.routers": [[25, 0, 0, "-", "check"], [24, 0, 0, "-", "dependencies"]], "src.routers.check": [[25, 0, 0, "-", "connectivity"], [25, 0, 0, "-", "dns"], [25, 0, 0, "-", "health"], [25, 0, 0, "-", "sentry_error"]], "src.routers.check.connectivity": [[25, 1, 1, "", "check_connectivity"]], "src.routers.check.dns": [[25, 1, 1, "", "check_dns"]], "src.routers.check.health": [[25, 1, 1, "", "health"]], "src.routers.check.sentry_error": [[25, 1, 1, "", "trigger_error"]], "src.routers.dependencies": [[24, 1, 1, "", "get_cloud_logger"], [24, 1, 1, "", "get_cloud_static_io_client"], [24, 1, 1, "", "get_manifest"], [24, 1, 1, "", "get_mapbox_secret"], [24, 1, 1, "", "init_sentry"]], "src.util": [[26, 0, 0, "-", "cloud_static_io"], [26, 0, 0, "-", "gcp_secrets"], [26, 0, 0, "-", "ingest_burn_zip"], [26, 0, 0, "-", "raster_to_poly"]], "src.util.cloud_static_io": [[26, 2, 1, "", "CloudStaticIOClient"]], "src.util.cloud_static_io.CloudStaticIOClient": [[26, 3, 1, "", "download"], [26, 3, 1, "", "get_derived_products"], [26, 3, 1, "", "get_manifest"], [26, 3, 1, "", "impersonate_service_account"], [26, 3, 1, "", "local_fetch_id_token"], [26, 3, 1, "", "update_manifest"], [26, 3, 1, "", "upload"], [26, 3, 1, "", "upload_cogs"], [26, 3, 1, "", "upload_fire_event"], [26, 3, 1, "", "upload_rap_estimates"], [26, 3, 1, "", "validate_credentials"]], "src.util.gcp_secrets": [[26, 1, 1, "", "get_mapbox_secret"]], "src.util.ingest_burn_zip": [[26, 1, 1, "", "ingest_esri_zip_file"], [26, 1, 1, "", "shp_to_geojson"]], "src.util.raster_to_poly": [[26, 1, 1, "", "raster_mask_to_geojson"]]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:method", "4": "py:attribute"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"]}, "titleterms": {"welcom": 20, "dse": 20, "fire": 20, "recoveri": 20, "": 20, "document": 20, "indic": 20, "tabl": 20, "src": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26], "app": [1, 22], "lib": [2, 3, 4, 5, 6, 7, 23], "burn_sever": [3, 23], "query_rap": [4, 23], "query_sentinel": [5, 23], "query_soil": [6, 23], "titiler_algorithm": [7, 23], "router": [8, 9, 10, 11, 12, 13, 14, 24, 25], "check": [9, 10, 11, 12, 13, 25], "connect": [10, 25], "dn": [11, 25], "health": [12, 25], "sentry_error": [13, 25], "depend": [14, 24], "util": [15, 16, 17, 18, 19, 26], "cloud_static_io": [16, 26], "gcp_secret": [17, 26], "ingest_burn_zip": [18, 26], "raster_to_poli": [19, 26], "packag": [22, 23, 24, 25, 26], "subpackag": [22, 24], "submodul": [22, 23, 24, 25, 26], "modul": [22, 23, 24, 25, 26], "content": [22, 23, 24, 25, 26]}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"src": [[0, "module-src"], [21, "src"]], "src.app": [[1, "module-src.app"]], "src.lib": [[2, "module-src.lib"]], "src.routers": [[8, "module-src.routers"]], "src.routers.check": [[9, "module-src.routers.check"]], "src.util": [[15, "module-src.util"]], "src.lib.burn_severity": [[3, "module-src.lib.burn_severity"]], "src.routers.dependencies": [[14, "module-src.routers.dependencies"]], "src package": [[22, "src-package"]], "Subpackages": [[22, "subpackages"], [24, "subpackages"]], "Submodules": [[22, "submodules"], [23, "submodules"], [24, "submodules"], [25, "submodules"], [26, "submodules"]], "src.app module": [[22, "module-src.app"]], "Module contents": [[22, "module-src"], [23, "module-src.lib"], [24, "module-src.routers"], [25, "module-src.routers.check"], [26, "module-src.util"]], "Welcome to dse-fire-recovery\u2019s documentation!": [[20, "welcome-to-dse-fire-recovery-s-documentation"]], "Indices and tables": [[20, "indices-and-tables"]], "src.lib.query_rap": [[4, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[5, "module-src.lib.query_sentinel"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"]], "src.lib package": [[23, "src-lib-package"]], "src.lib.burn_severity module": [[23, "module-src.lib.burn_severity"]], "src.lib.query_rap module": [[23, "module-src.lib.query_rap"]], "src.lib.query_sentinel module": [[23, "module-src.lib.query_sentinel"]], "src.lib.query_soil module": [[23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms module": [[23, "module-src.lib.titiler_algorithms"]], "src.routers package": [[24, "src-routers-package"]], "src.routers.dependencies module": [[24, "module-src.routers.dependencies"]], "src.routers.check package": [[25, "src-routers-check-package"]], "src.routers.check.connectivity module": [[25, "module-src.routers.check.connectivity"]], "src.routers.check.dns module": [[25, "module-src.routers.check.dns"]], "src.routers.check.health module": [[25, "module-src.routers.check.health"]], "src.routers.check.sentry_error module": [[25, "module-src.routers.check.sentry_error"]], "src.util package": [[26, "src-util-package"]], "src.util.cloud_static_io module": [[26, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets module": [[26, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip module": [[26, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly module": [[26, "module-src.util.raster_to_poly"]]}, "indexentries": {"module": [[2, "module-src.lib"], [6, "module-src.lib.query_soil"], [7, "module-src.lib.titiler_algorithms"], [9, "module-src.routers.check"], [10, "module-src.routers.check.connectivity"], [11, "module-src.routers.check.dns"], [12, "module-src.routers.check.health"], [13, "module-src.routers.check.sentry_error"], [15, "module-src.util"], [16, "module-src.util.cloud_static_io"], [17, "module-src.util.gcp_secrets"], [18, "module-src.util.ingest_burn_zip"], [19, "module-src.util.raster_to_poly"], [22, "module-src"], [22, "module-src.app"], [23, "module-src.lib"], [23, "module-src.lib.burn_severity"], [23, "module-src.lib.query_rap"], [23, "module-src.lib.query_sentinel"], [23, "module-src.lib.query_soil"], [23, "module-src.lib.titiler_algorithms"], [24, "module-src.routers"], [24, "module-src.routers.dependencies"], [25, "module-src.routers.check"], [25, "module-src.routers.check.connectivity"], [25, "module-src.routers.check.dns"], [25, "module-src.routers.check.health"], [25, "module-src.routers.check.sentry_error"], [26, "module-src.util"], [26, "module-src.util.cloud_static_io"], [26, "module-src.util.gcp_secrets"], [26, "module-src.util.ingest_burn_zip"], [26, "module-src.util.raster_to_poly"]], "src.lib": [[2, "module-src.lib"], [23, "module-src.lib"]], "src.lib.query_soil": [[6, "module-src.lib.query_soil"], [23, "module-src.lib.query_soil"]], "src.lib.titiler_algorithms": [[7, "module-src.lib.titiler_algorithms"], [23, "module-src.lib.titiler_algorithms"]], "src.routers.check": [[9, "module-src.routers.check"], [25, "module-src.routers.check"]], "src.routers.check.connectivity": [[10, "module-src.routers.check.connectivity"], [25, "module-src.routers.check.connectivity"]], "src.routers.check.dns": [[11, "module-src.routers.check.dns"], [25, "module-src.routers.check.dns"]], "src.routers.check.health": [[12, "module-src.routers.check.health"], [25, "module-src.routers.check.health"]], "src.routers.check.sentry_error": [[13, "module-src.routers.check.sentry_error"], [25, "module-src.routers.check.sentry_error"]], "src.util": [[15, "module-src.util"], [26, "module-src.util"]], "src.util.cloud_static_io": [[16, "module-src.util.cloud_static_io"], [26, "module-src.util.cloud_static_io"]], "src.util.gcp_secrets": [[17, "module-src.util.gcp_secrets"], [26, "module-src.util.gcp_secrets"]], "src.util.ingest_burn_zip": [[18, "module-src.util.ingest_burn_zip"], [26, "module-src.util.ingest_burn_zip"]], "src.util.raster_to_poly": [[19, "module-src.util.raster_to_poly"], [26, "module-src.util.raster_to_poly"]], "src": [[22, "module-src"]], "src.app": [[22, "module-src.app"]], "censorandscale (class in src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.CensorAndScale"]], "classify (class in src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.Classify"]], "sentinel2client (class in src.lib.query_sentinel)": [[23, "src.lib.query_sentinel.Sentinel2Client"]], "arrange_stack() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.arrange_stack"]], "calc_burn_metrics() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_burn_metrics"]], "calc_burn_metrics() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.calc_burn_metrics"]], "calc_dnbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_dnbr"]], "calc_nbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_nbr"]], "calc_rbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_rbr"]], "calc_rdnbr() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.calc_rdnbr"]], "classify() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.classify"]], "classify_burn() (in module src.lib.burn_severity)": [[23, "src.lib.burn_severity.classify_burn"]], "color (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.color"]], "color (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.color"]], "convert_to_rgb() (in module src.lib.titiler_algorithms)": [[23, "src.lib.titiler_algorithms.convert_to_rgb"]], "derive_boundary() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.derive_boundary"]], "edit_get_ecoclass_info() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.edit_get_ecoclass_info"]], "get_items() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.get_items"]], "ingest_barc_classifications() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.ingest_barc_classifications"]], "model_computed_fields (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_computed_fields"]], "model_computed_fields (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_computed_fields"]], "model_config (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_config"]], "model_config (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_config"]], "model_fields (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.model_fields"]], "model_fields (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.model_fields"]], "query_fire_event() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.query_fire_event"]], "rap_get_biomass() (in module src.lib.query_rap)": [[23, "src.lib.query_rap.rap_get_biomass"]], "reduce_time_range() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.reduce_time_range"]], "sdm_create_aoi() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_create_aoi"]], "sdm_get_available_interpretations() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_available_interpretations"]], "sdm_get_ecoclassid_from_mu_info() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_ecoclassid_from_mu_info"]], "sdm_get_esa_mapunitid_poly() (in module src.lib.query_soil)": [[23, "src.lib.query_soil.sdm_get_esa_mapunitid_poly"]], "set_boundary() (src.lib.query_sentinel.sentinel2client method)": [[23, "src.lib.query_sentinel.Sentinel2Client.set_boundary"]], "src.lib.burn_severity": [[23, "module-src.lib.burn_severity"]], "src.lib.query_rap": [[23, "module-src.lib.query_rap"]], "src.lib.query_sentinel": [[23, "module-src.lib.query_sentinel"]], "thresholds (src.lib.titiler_algorithms.censorandscale attribute)": [[23, "src.lib.titiler_algorithms.CensorAndScale.thresholds"]], "thresholds (src.lib.titiler_algorithms.classify attribute)": [[23, "src.lib.titiler_algorithms.Classify.thresholds"]], "get_cloud_logger() (in module src.routers.dependencies)": [[24, "src.routers.dependencies.get_cloud_logger"]], "get_cloud_static_io_client() (in module src.routers.dependencies)": [[24, "src.routers.dependencies.get_cloud_static_io_client"]], "get_manifest() (in module src.routers.dependencies)": [[24, "src.routers.dependencies.get_manifest"]], "get_mapbox_secret() (in module src.routers.dependencies)": [[24, "src.routers.dependencies.get_mapbox_secret"]], "init_sentry() (in module src.routers.dependencies)": [[24, "src.routers.dependencies.init_sentry"]], "src.routers": [[24, "module-src.routers"]], "src.routers.dependencies": [[24, "module-src.routers.dependencies"]], "check_connectivity() (in module src.routers.check.connectivity)": [[25, "src.routers.check.connectivity.check_connectivity"]], "check_dns() (in module src.routers.check.dns)": [[25, "src.routers.check.dns.check_dns"]], "health() (in module src.routers.check.health)": [[25, "src.routers.check.health.health"]], "trigger_error() (in module src.routers.check.sentry_error)": [[25, "src.routers.check.sentry_error.trigger_error"]], "cloudstaticioclient (class in src.util.cloud_static_io)": [[26, "src.util.cloud_static_io.CloudStaticIOClient"]], "download() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.download"]], "get_derived_products() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.get_derived_products"]], "get_manifest() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.get_manifest"]], "get_mapbox_secret() (in module src.util.gcp_secrets)": [[26, "src.util.gcp_secrets.get_mapbox_secret"]], "impersonate_service_account() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.impersonate_service_account"]], "ingest_esri_zip_file() (in module src.util.ingest_burn_zip)": [[26, "src.util.ingest_burn_zip.ingest_esri_zip_file"]], "local_fetch_id_token() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.local_fetch_id_token"]], "raster_mask_to_geojson() (in module src.util.raster_to_poly)": [[26, "src.util.raster_to_poly.raster_mask_to_geojson"]], "shp_to_geojson() (in module src.util.ingest_burn_zip)": [[26, "src.util.ingest_burn_zip.shp_to_geojson"]], "update_manifest() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.update_manifest"]], "upload() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.upload"]], "upload_cogs() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.upload_cogs"]], "upload_fire_event() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.upload_fire_event"]], "upload_rap_estimates() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.upload_rap_estimates"]], "validate_credentials() (src.util.cloud_static_io.cloudstaticioclient method)": [[26, "src.util.cloud_static_io.CloudStaticIOClient.validate_credentials"]]}}) \ No newline at end of file diff --git a/.sphinx/html/src.html b/.sphinx/html/src.html index ecd5a36..f405653 100644 --- a/.sphinx/html/src.html +++ b/.sphinx/html/src.html @@ -143,7 +143,6 @@

    Subpackagessrc.util.gcp_secrets module
  • src.util.ingest_burn_zip module
      diff --git a/.sphinx/html/src.lib.html b/.sphinx/html/src.lib.html index 5de4ce1..21b9266 100644 --- a/.sphinx/html/src.lib.html +++ b/.sphinx/html/src.lib.html @@ -285,27 +285,82 @@

      Submodules
      src.lib.query_soil.edit_get_ecoclass_info(ecoclass_id)
      -
      +

      Retrieves information about an EcoClass from the EDIT database.

      +
      +
      Args:

      ecoclass_id (str): The ID of the EcoClass to retrieve.

      +
      +
      Returns:
      +
      tuple: A tuple containing a boolean value indicating the success of the retrieval

      and a dictionary containing the retrieved EcoClass information.

      +
      +
      +
      +
      Raises:

      Exception: If there is an error in retrieving the EcoClass information from the EDIT database.

      +
      +
      +
      src.lib.query_soil.sdm_create_aoi(geojson)
      -
      +

      DEPRECATED: sdm_get_esa_mapunitid_poly circumvents the need for creating an AOI within the SDM service.

      +

      Create an Area of Interest (AOI) within the Soil Data Mart (SDM) using the given GeoJSON.

      +
      +
      Args:

      geojson (dict): The GeoJSON object containing the AOI geometry. This assumes a single +feature in the GeoJSON, a polygon.

      +
      +
      Returns:

      requests.Response or None: The response object if the AOI creation is successful, +None otherwise. The response object contains the AOI ID, which can be used to query +for available interpretations within SDM.

      +
      +
      +
      src.lib.query_soil.sdm_get_available_interpretations(aoi_smd_id)
      -
      +

      Retrieves the available interpretations for a given AOI ID from the SDM service.

      +
      +
      Parameters:

      aoi_smd_id (str): The ID of the AOI.

      +
      +
      Returns:

      list or None: A list of available interpretations if successful, None otherwise.

      +
      +
      +
      src.lib.query_soil.sdm_get_ecoclassid_from_mu_info(mu_polygon_keys)
      -
      +

      Retrieves the ecological site information from the SDM endpoint based on the provided map unit polygon keys. This is +essentially a direct query to the SDM endpoint to retrieve the ecoclassid. This ecoclassid is used to query the +EDIT database for ecological site information, such as dominant cover species. This query page +(https://sdmdataaccess.sc.egov.usda.gov/Query.aspx) can be used to test out queries interactively, +which is how this query was constructed.

      +
      +
      Args:

      mu_polygon_keys (list): The list of map unit polygon keys to filter the ecological site information.

      +
      +
      Returns:

      pandas.DataFrame: The ecological site information as a DataFrame, or None if an error occurs.

      +
      +
      +
      src.lib.query_soil.sdm_get_esa_mapunitid_poly(geojson, backoff_max=200, backoff_value=0, backoff_increment=25)
      -
      +

      Retrieves the map unit polygons from the SDM endpoint based on the provided GeoJSON geometry. +These Map Unit polygons are used to query for ecological site information within the EDIT database. +SDM endpoint occasionally refuses traffic, so this function will backoff and retry if necessary in a linear fashion.

      +
      +
      Args:

      geojson (dict): The GeoJSON geometry to filter the map unit polygons. +backoff_max (int, optional): The maximum backoff value in seconds. Defaults to 200. +backoff_value (int, optional): The current backoff value in seconds. Defaults to 0. +backoff_increment (int, optional): The increment value for backoff in seconds. Defaults to 25.

      +
      +
      Returns:

      geopandas.GeoDataFrame: The map unit polygons as a GeoDataFrame, or None if an error occurs.

      +
      +
      Raises:

      Exception: If the SDM endpoint refuses traffic and the backoff max is reached.

      +
      +
      +
      @@ -385,7 +440,20 @@

      Submodules
      src.lib.titiler_algorithms.convert_to_rgb(classified: ndarray, mask: ndarray, color: str) ndarray
      -
      +

      Convert a classified image to RGB format, essentially creating a false-color image. This is used +extensively by Titiler to create the final image that is returned to the user, upon a tile request. +We will likely do this on the front-end in v1.

      +
      +
      Args:

      classified (np.ndarray): The classified image. +mask (np.ndarray): The mask indicating which pixels to include in the final image. +color (str): The color to use for the RGB image. Must be one of “red”, “green”, or “blue”.

      +
      +
      Returns:

      np.ndarray: The RGB image.

      +
      +
      Raises:

      ValueError: If an invalid color is provided.

      +
      +
      +

      diff --git a/.sphinx/html/src.routers.check.html b/.sphinx/html/src.routers.check.html index d203e46..aa82d67 100644 --- a/.sphinx/html/src.routers.check.html +++ b/.sphinx/html/src.routers.check.html @@ -40,7 +40,16 @@

      Submodules
      src.routers.check.connectivity.check_connectivity(logger: Logger = Depends(get_cloud_logger))
      -
      +

      Check the connectivity to http://example.com.

      +
      +
      Args:

      logger (Logger): The logger object used for logging.

      +
      +
      Returns:

      Tuple[int, str]: A tuple containing the HTTP status code and a message.

      +
      +
      Raises:

      HTTPException: If there is an error during the connectivity check.

      +
      +
      +

      @@ -48,7 +57,16 @@

      Submodules
      src.routers.check.dns.check_dns(logger: Logger = Depends(get_cloud_logger))
      -
      +

      Check the DNS resolution for www.google.com.

      +
      +
      Args:

      logger (Logger): The logger object for logging messages.

      +
      +
      Returns:

      dict: A dictionary containing the resolved IP address.

      +
      +
      Raises:

      HTTPException: If there is an error during the DNS resolution.

      +
      +
      +

      @@ -56,7 +74,14 @@

      Submodules
      src.routers.check.health.health(logger: Logger = Depends(get_cloud_logger))
      -
      +

      Ping pong!

      +
      +
      Args:

      logger (Logger): The logger object for logging messages.

      +
      +
      Returns:

      Tuple[str, int]: A tuple containing the response message and status code.

      +
      +
      +

      @@ -64,7 +89,14 @@

      Submodules
      async src.routers.check.sentry_error.trigger_error(logger: Logger = Depends(get_cloud_logger))
      -
      +

      Triggers a division by zero error for Sentry to catch.

      +
      +
      Parameters:

      logger (Logger): The logger object used for logging.

      +
      +
      Raises:

      ZeroDivisionError: If a division by zero occurs (it definitely will).

      +
      +
      +

      diff --git a/.sphinx/html/src.util.html b/.sphinx/html/src.util.html index 5319908..584ac7f 100644 --- a/.sphinx/html/src.util.html +++ b/.sphinx/html/src.util.html @@ -41,63 +41,196 @@

      Submodules class src.util.cloud_static_io.CloudStaticIOClient(bucket_name, provider)

      Bases: object

      +

      A client class for interacting with cloud storage services like S3, GCS, etc. +This uses the smart_open library to interact with cloud storage services, in a +relatively provider-agnostic way. It also uses the boto3 library to interact with +AWS services, specifically so that we can assume a role in AWS and then use the +assumed role credentials to interact with S3.

      +
      +
      Args:

      bucket_name (str): The name of the bucket in the cloud storage service. +provider (str): The provider of the cloud storage service (currently only supports “s3”).

      +
      +
      Attributes:

      env (str): The environment variable for the environment (e.g. “LOCAL”, “PROD”). +role_arn (str): The role ARN for assuming the role with AWS. +service_account_email (str): The email address of the service account for GCP,

      +
      +

      authorized to impersonate the role in AWS.

      +
      +

      role_session_name (str): The name of the role session. Arbitrary. +logger (logging.Logger): The logger for logging messages. +sts_client (botocore.client.STS): The STS client for assuming the AWS role using GCP credentials. +prefix (str): The prefix for the bucket URL. We get this from tofu, once the bucket is created. +iam_credentials (google.auth.impersonated_credentials.Credentials): The impersonated IAM credentials. +role_assumed_credentials (dict): The assumed role credentials, once we assume the AWS role. +boto_session (boto3.Session): The Boto3 session, using the assumed role credentials, which

      +
      +

      at long last allows us to interact with S3.

      +
      +
      +
      Raises:

      Exception: If the provider is not supported.

      +
      +
      download(remote_path, target_local_path)
      -

      Downloads the file from remote s3 server to local. -Also, by default extracts the file to the specified target_local_path

      +

      Downloads the file from remote s3 server to local.

      +
      +
      Args:

      remote_path (str): The path of the file on the remote server. +target_local_path (str): The path where the file will be downloaded to.

      +
      +
      Raises:

      Exception: If there is an error during the download process.

      +
      +
      get_derived_products(affiliation, fire_event_name)
      -
      +

      Retrieves the derived products associated with a specific affiliation and fire event. We basically +assume anything within the folder for a given affiliation and fire event is a derived product. Valyes +returned are public HTTPS URLs.

      +
      +
      Args:

      affiliation (str): The affiliation of the derived products. +fire_event_name (str): The name of the fire event.

      +
      +
      Returns:

      dict: A dictionary containing the filenames as keys and the corresponding full HTTPS URLs as values.

      +
      +
      +
      get_manifest()
      -
      +

      Retrieves the manifest file from the cloud storage.

      +
      +
      Returns:

      dict: The contents of the manifest file.

      +
      +
      +
      impersonate_service_account()
      -
      +

      Impersonates a service account by creating impersonated credentials, using the +service account email provided in initialization.

      +
      +
      Returns:

      None

      +
      +
      +
      local_fetch_id_token(audience)
      -
      +

      Fetches an ID token from the Google IAM service. This is used to authenticate +with AWS STS, when we are running in the local environment. On GCP, we use the +google.auth library to fetch the ID token since we are already authenticated +with the service account we need.

      +
      +
      Args:

      audience (str): The audience for the ID token.

      +
      +
      Returns:

      str: The fetched ID token.

      +
      +
      Raises:

      exceptions.DefaultCredentialsError: If the ID token fetch fails.

      +
      +
      +
      update_manifest(fire_event_name, bounds, prefire_date_range, postfire_date_range, affiliation, derive_boundary)
      -
      +

      Updates the manifest with the given fire event information for the specified affiliation. If the fire event +already exists in the manifest, it will be overwritten.

      +
      +
      Args:

      fire_event_name (str): The name of the fire event. +bounds (tuple): The bounds of the fire event. +prefire_date_range (tuple): The prefire date range of the fire event. +postfire_date_range (tuple): The postfire date range of the fire event. +affiliation (str): The affiliation for which the manifest is being updated. +derive_boundary (bool): Flag indicating whether to derive the boundary.

      +
      +
      Returns:

      None

      +
      +
      +
      upload(source_local_path, remote_path)

      Uploads the source files from local to the s3 server.

      +
      +
      Args:

      source_local_path (str): The local path of the source file to be uploaded. +remote_path (str): The remote path where the file will be uploaded to.

      +
      +
      Raises:

      Exception: If there is an error during the upload process.

      +
      +
      upload_cogs(metrics_stack, fire_event_name, affiliation)
      -
      +

      Uploads COGs (Cloud-Optimized GeoTIFFs) to a remote location, according to +public/{affiliation}/{fire_event_name}/{band_name}.tif. Also adds +overviews to the COGs for faster loading at lower zoom levels.

      +
      +
      Args:

      metrics_stack (xarray.DataArray): Stack of metrics data. +fire_event_name (str): Name of the fire event. +affiliation (str): Affiliation of the data.

      +
      +
      Returns:

      None

      +
      +
      +
      upload_fire_event(metrics_stack, fire_event_name, prefire_date_range, postfire_date_range, affiliation, derive_boundary)
      -
      +

      Uploads a fire event to the cloud storage location (uploads COGs and updates the manifest.json file).

      +
      +
      Args:

      metrics_stack (xr.DataArray): The metrics stack containing the fire event data. +fire_event_name (str): The name of the fire event. +prefire_date_range (tuple): The date range before the fire event. +postfire_date_range (tuple): The date range after the fire event. +affiliation (str): The affiliation of the fire event. +derive_boundary (bool): Whether to derive the boundary of the fire event.

      +
      +
      Returns:

      None

      +
      +
      +
      upload_rap_estimates(rap_estimates, fire_event_name, affiliation)
      -
      +

      Uploads RAP estimates to a remote location, according to +f”public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_{band_name}.tif”. +Also adds overviews to the COGs for faster loading at lower zoom levels.

      +
      +
      Args:

      rap_estimates (xarray.DataArray): RAP estimates data. +fire_event_name (str): Name of the fire event. +affiliation (str): Affiliation of the data.

      +
      +
      Returns:

      None

      +
      +
      +
      validate_credentials()
      -
      +

      Validates the credentials by checking if the role assumed credentials are expired or not. +If expired or not available, it retrieves the OIDC token and assumes the role with web identity. +Sets the assumed credentials in the boto3 session for further use. If the environment is local, +it also impersonates the service account to get the credentials, otherwise we assume google.auth +will handle the credentials for us if we’re on GCP.

      +
      +
      Raises:

      ValueError: If failed to retrieve OIDC token.

      +
      +
      Returns:

      None

      +
      +
      +
      @@ -107,12 +240,13 @@

      Submodules
      src.util.gcp_secrets.get_mapbox_secret()
      -
      - -
      -
      -src.util.gcp_secrets.get_ssh_secret()
      -
      +

      Retrieves the Mapbox API key from Google Cloud Secret Manager. This assumes you are +authenticated locally with gcloud auth application-default login.

      +
      +
      Returns:

      str: The Mapbox API key.

      +
      +
      +

      @@ -120,12 +254,35 @@

      Submodules
      src.util.ingest_burn_zip.ingest_esri_zip_file(zip_file_path)
      -
      +

      Ingests an ESRI zip file containing shapefiles and tif files. This assumes that there are a set of +shapefiles and tif files in the zip file. The shapefiles are expected to be in the form of a .shp, .shx, and .prj file, +with a .dbf file being optional.

      +
      +
      Args:

      zip_file_path (str): The path to the ESRI zip file.

      +
      +
      Returns:
      +
      tuple: A tuple containing two lists - valid_shapefiles and valid_tifs.
        +
      • valid_shapefiles: A list of tuples, where each tuple contains the paths to the valid shapefiles and their corresponding GeoJSON representation.

      • +
      • valid_tifs: A list of valid tif files.

      • +
      +
      +
      +
      +
      +
      src.util.ingest_burn_zip.shp_to_geojson(shp_file_path)
      -
      +

      Convert a shapefile to GeoJSON format, assuming we know the CRS +of the shapefile based on its helper files.

      +
      +
      Args:

      shp_file_path (str): The file path of the shapefile.

      +
      +
      Returns:

      str: The GeoJSON representation of the shapefile.

      +
      +
      +

      @@ -133,7 +290,14 @@

      Submodules
      src.util.raster_to_poly.raster_mask_to_geojson(binary_mask)
      -
      +

      Converts a binary raster mask to a GeoJSON representation.

      +
      +
      Args:

      binary_mask (xr.DataArray): Binary mask representing the raster.

      +
      +
      Returns:

      dict: GeoJSON representation of the mask boundary.

      +
      +
      +

      diff --git a/.sphinx/source/_autosummary/src.util.gcp_secrets.rst b/.sphinx/source/_autosummary/src.util.gcp_secrets.rst index 6fab0d0..e24a000 100644 --- a/.sphinx/source/_autosummary/src.util.gcp_secrets.rst +++ b/.sphinx/source/_autosummary/src.util.gcp_secrets.rst @@ -1,4 +1,4 @@ -src.util.gcp\_secrets +src.util.gcp\_secrets ===================== .. automodule:: src.util.gcp_secrets @@ -14,7 +14,6 @@ src.util.gcp\_secrets .. autosummary:: get_mapbox_secret - get_ssh_secret diff --git a/src/routers/fetch/rangeland_analysis_platform.py b/src/routers/fetch/rangeland_analysis_platform.py index d63a4a4..347889d 100644 --- a/src/routers/fetch/rangeland_analysis_platform.py +++ b/src/routers/fetch/rangeland_analysis_platform.py @@ -48,7 +48,7 @@ def fetch_rangeland_analysis_platform( Args: body (AnaylzeRapPOSTBody): The request body containing the necessary data for analysis. - cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. Defaults to Depends(get_cloud_static_io_client). + cloud_static_io_client (CloudStaticIOClient, optional): The client for interacting with the cloud storage service. FastAPI handles this as a dependency injection. __sentry (None, optional): Sentry client, just needs to be initialized. FastAPI handles this as a dependency injection. logger (Logger, optional): Google cloud logger. FastAPI handles this as a dependency injection. From 586cef375753ab5fc53ecc9912432007c55c245e Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 20 Feb 2024 18:05:15 +0000 Subject: [PATCH 145/175] reorg --- {img => exploratory/img}/BARC_Portal.png | Bin .../img}/percent_adjustment_dome_with_expanded.png | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename {img => exploratory/img}/BARC_Portal.png (100%) rename {img => exploratory/img}/percent_adjustment_dome_with_expanded.png (100%) diff --git a/img/BARC_Portal.png b/exploratory/img/BARC_Portal.png similarity index 100% rename from img/BARC_Portal.png rename to exploratory/img/BARC_Portal.png diff --git a/img/percent_adjustment_dome_with_expanded.png b/exploratory/img/percent_adjustment_dome_with_expanded.png similarity index 100% rename from img/percent_adjustment_dome_with_expanded.png rename to exploratory/img/percent_adjustment_dome_with_expanded.png From 0df8ba44d2da543eb6ec465d2cde291b2c0cd890 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 20 Feb 2024 18:12:52 +0000 Subject: [PATCH 146/175] fixture for geojsons --- ...y.geojson => test_boundary_geology.geojson} | 0 ...son => test_boundary_geology_split.geojson} | 0 tests/conftest.py | 18 ++++++++++++++++++ 3 files changed, 18 insertions(+) rename tests/assets/{boundary_geology.geojson => test_boundary_geology.geojson} (100%) rename tests/assets/{boundary_geology_split.geojson => test_boundary_geology_split.geojson} (100%) diff --git a/tests/assets/boundary_geology.geojson b/tests/assets/test_boundary_geology.geojson similarity index 100% rename from tests/assets/boundary_geology.geojson rename to tests/assets/test_boundary_geology.geojson diff --git a/tests/assets/boundary_geology_split.geojson b/tests/assets/test_boundary_geology_split.geojson similarity index 100% rename from tests/assets/boundary_geology_split.geojson rename to tests/assets/test_boundary_geology_split.geojson diff --git a/tests/conftest.py b/tests/conftest.py index d69080c..4ed94f3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import xarray as xr import pickle import random +import json ### PyStac ItemCollection @@ -58,3 +59,20 @@ def test_3d_nan_xarray(test_3d_xarray): def test_3d_zero_xarray(test_3d_xarray): test_reduced_zero_xarray = xr.full_like(test_3d_xarray, 0) return test_reduced_zero_xarray + + +### GeoJSON + + +@pytest.fixture +def test_geojson(): + with open("tests/assets/test_boundary_geology.geojson") as f: + test_geojson = json.load(f) + return test_geojson + + +@pytest.fixture +def test_geojson_split(): + with open("tests/assets/test_boundary_geology_split.geojson") as f: + test_geojson = json.load(f) + return test_geojson From a9633d9e92dd68ede02ec7ce82028f5edb1fdfc8 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 20 Feb 2024 18:27:31 +0000 Subject: [PATCH 147/175] mock return from RAP - but need to properly isel window... --- tests/conftest.py | 1 + tests/unit/lib/test_query_rap.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/unit/lib/test_query_rap.py diff --git a/tests/conftest.py b/tests/conftest.py index 4ed94f3..86fca9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ import pickle import random import json +import unittest.mock as mock ### PyStac ItemCollection diff --git a/tests/unit/lib/test_query_rap.py b/tests/unit/lib/test_query_rap.py new file mode 100644 index 0000000..9a611fc --- /dev/null +++ b/tests/unit/lib/test_query_rap.py @@ -0,0 +1,25 @@ +import pytest +from src.lib.query_rap import rap_get_biomass +import unittest.mock as mock +import xarray as xr + + +@mock.patch("rioxarray.open_rasterio") +def test_rap_get_biomass(mock_get, test_geojson, test_3d_xarray): + # Define the arguments for upload_fire_event + rap_estimates = test_3d_xarray + + # Duplicate the xarray to add two new bands + rap_estimates = xr.concat([rap_estimates, rap_estimates], dim="band") + rap_estimates["band"] = [ + "annual_forb_and_grass", + "perennial_forb_and_grass", + "shrub", + "tree", + ] + + mock_get.return_value.content = rap_estimates + result = rap_get_biomass("2020-01-01", test_geojson) + # Add assertions to check the result + + assert isinstance(result, xr.DataArray) From 221bea16237885be1805474ed3c66e985c37e905 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 22 Feb 2024 17:50:13 +0000 Subject: [PATCH 148/175] pickle only the metadata and coords - that way, dynamically reconstruct xarrays with those coords that look just like whatever we've computed within pytests, using as fixtures --- .../test_imagery_time_reduced_xarray.pkl | Bin 20531 -> 0 bytes .../assets/test_imagery_with_time_xarray.pkl | Bin 31941 -> 0 bytes .../assets/test_metadata_and_other_coords.pkl | Bin 0 -> 5936 bytes tests/conftest.py | 184 ++++++++++++++++-- tests/unit/lib/test_burn_severity.py | 30 ++- tests/unit/lib/test_query_rap.py | 28 ++- 6 files changed, 219 insertions(+), 23 deletions(-) delete mode 100644 tests/assets/test_imagery_time_reduced_xarray.pkl delete mode 100644 tests/assets/test_imagery_with_time_xarray.pkl create mode 100644 tests/assets/test_metadata_and_other_coords.pkl diff --git a/tests/assets/test_imagery_time_reduced_xarray.pkl b/tests/assets/test_imagery_time_reduced_xarray.pkl deleted file mode 100644 index 0669b49177b2e981f4d945cec4240720dcd608e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20531 zcmeHPYm6LMR_^gUZI2&`?GWuI+16x}bXL5%HKtQtb$hC6 z`lYIBzeJR*h#gxhv5`_NEA1k7VHd#%z>odGZ&(%~gb*T&SQKf0us`wx1S|N? zt*V}Wd8Qq}@t|q@)~&wxoO{ka_uO;Oy>o8AHTKJ~3laWtPux%}OSw6xHY_%$DUQOw zdhY&L@t&h9?>+CuFL{MGyqG&#QX36RvpxDUR#xho=N|8D;F@9?O1a8B_s~*ZV>d2^ z-?!QrFKI^2_Vmfyo{M)->1o3tM5DgpxhF1wf@1TQP>|Dh#Z1|J7Xb#}ePn*gHzggYL-^FH|WPt;qrW*~!FtLhj*yVE6$sg1hrSS{57x;xGR zJ$Dj(1+vLUZhL1&%HBKPsu#3+EI7p7ra8DaPGhG z;g9@j$1C5v3UQ9UlF4~^ijv1r=8n~s8iSZxchImqQSuwqUv&3flsg;zX=*S(ogd3j z0S(Y)04t7T*Rw= zhPtMIKJ3^5d;#@JM|bp--JOY0XU=fdI{J^F%aMk?9^}6teTv!b*d1s{MGND=PdtHkiQ+|w}QMG z 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 T=hVWzy$ah8o59{uq?Z2!X|46h diff --git a/tests/assets/test_metadata_and_other_coords.pkl b/tests/assets/test_metadata_and_other_coords.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9ae6edf3013d4c6a012072367116585d4b024c1f GIT binary patch literal 5936 zcmd57&-6}En3*|K9>wBx2u%Cbl;p)yM@|AiJZEi05ln{p*mc3~KsK~3 zOG^$aNCm1k0wzg;PFn}PR}`$ZzU~v8WhL!V7CpeG-=qst%#tfi5a#n$)M_5oSbO_#I&s$yw;gEN=_Dg0SRI_}o38eqEybc!q3!9-qD8Xj>v z@C*cC&o%_1uY}l7+-OyC0n8EEBFnO10UPOSY@jtph5CK@r>&;h2y9?B4NnjF4$BTv zZOiqXrs~0VHXsN&dkJs_wkaOk;8Q(S{tG_OM~gkh-VILAvjNS!+aS25cP`5}-}6`d zSkF&*sZ@IFKk$gIIO*}~<}HBo#wE}poXv+XgcnJTc#5anR@fn0m}oT;PQtttGdX)z z(8VSAUbsFxA0CyW(pa!afEnwz6qE2$^XCAF4azGzZV!}j3z+85Bca- z=kzZXkVG%9a{8vv^r$Y3YZA+IIejaLv*$62vEE8zO9h%o1%?9!Hm8P72y_>k4vlw7 zIwt%`3`5v;FS7b7@mRO8(YCPB1rS1@Slc042>8rWgUc~ndJV?i zP>zeR@f^i+mu$z}9Z3)^1>hx~LjKCX|B0i*ACs+_*MAlhGZ%xIZE32;Xn9w`Z@JX~8 zapYy?4l#&T^XPstep-0^Zs^mmCO5wN(H~yl=Rj+wb-93s_Km0SQZ~<7-=%m92n~UddG3eA z^9$z|l2wjl_OhQwG&u9TKq7mtrkhjeFo9t$yb2A+nAq?;53T!;83yTQ&2eQDs?Jeyt5r21JBAyl}hz1oPFTdmQ8_^y}Xw!3r zsfC-7>xG4IHW?YeoG(|dicdM7!-<7!Gt={PvzNC>(uqtqp2&<#xolKQWh5z)N+-ti zmGaEo{A~HhHzU*677CH^NG6tuMQ+{NVI`fA(ztSMe)j4#NGmvmSj)OfBjd3h$g{av z9LX!C+49YZMruF~l+jo`lT0O~oRo^^a>>L_7;YyPj#27SM9_D!P&Qnr`m zhNGKAHi@Hax?=J65rmCAyAp62Y8#ldks7gUj?$pIDq|#w6tgAgG_@W5T^kCMfmfcU z84$dB-F6HuTp+eaJY5Yh+;u%-hA~DsJ6QuDV3bA!tGUnd6dq~N*m4^=ud5JobrU)^ z44lgCvlhE`As$ahW2sbHl5+8EI+>IbVeoJ)7SCpKxwI6IOWAZr0)OLlIFX3vAn?*@ zDV2&R6R}iMOkdb}3vUL;_$FK&RKterR*4;!gZmfUGHHm7bY2l#E9_{;#jf)@CkS}E z1lBX46P*b180&-EBl3ha4%gV=VpGop4W=y@Zj*juA7n#v`!-5Xiu_igL#+}!jOBHP z7HNepQk81-Hg)J4U8nEU59mGm5xq}8p%3V1^iTB9^dWsj|4RQ>KZYhgj$Hw}8SE<9 zEn+7epuwnFUxU6>Ux$8`*FS(4t<*on*P_6o9vlwU$Ds)vw+{}-Z6AjtaI761jx`_0 zy1;So;BegYaeO3j+&?%R_kA3n2pkU%4#xu@$7ce^!-K=|(8uve;CS4HW00PF(Hue_ z(=X@~`m`gLyaI`#eipMssQ!H%zd&+NrwF!=Hu!qu z6%Ykv;VXd}9o2>U|5sA{Fu|%K3XuSW#pvJwiBc#4VLcRpuqGZHAW@M8AW@bf(3;KY Fe*xLtxwZfR literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py index 86fca9b..5b08e77 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,37 @@ import json import unittest.mock as mock + +### Helpers +def extract_metadata_and_other_coords(data_array): + metadata = {"attrs": {}, "coords": {}} + for attr in data_array.attrs: + metadata["attrs"][attr] = data_array.attrs[attr] + for coord in data_array.coords: + if coord not in ["band", "time", "y", "x", "epsg"] and not coord.startswith( + "proj" + ): + metadata["coords"][coord] = data_array.coords[coord] + return metadata + + +def construct_dataarray(metadata, values, bands, x, y, epsg, time=None): + coords = metadata["coords"] + coords.update({"x": x, "y": y, "epsg": epsg, "band": bands}) + if time is not None: + coords.update({"time": time}) + new_data_array = xr.DataArray( + data=values, coords=coords, dims=["time", "band", "y", "x"] + ) + else: + new_data_array = xr.DataArray( + data=values, coords=coords, dims=["band", "y", "x"] + ) + for attr, value in metadata["attrs"].items(): + new_data_array.attrs[attr] = value + return new_data_array + + ### PyStac ItemCollection @@ -18,25 +49,149 @@ def test_stac_items(): return test_stac_items +### Coords and time windows + + +@pytest.fixture +def test_spatial_coords_epsg_4326(): + x = np.array( + [ + -116.04612911, + -116.04594111, + -116.04575312, + -116.04556512, + -116.04537713, + -116.04518913, + -116.04500113, + -116.04481314, + -116.04462514, + -116.04443714, + ] + ) + + y = np.array( + [ + 33.90347264, + 33.90328464, + 33.90309664, + 33.90290865, + 33.90272065, + 33.90253265, + 33.90234466, + 33.90215666, + 33.90196867, + 33.90178067, + ] + ) + + return x, y + + +@pytest.fixture +def test_spatial_coords_epsg_32611(): + x = np.array( + [ + 500980.0, + 501000.0, + 501020.0, + 501040.0, + 501060.0, + 501080.0, + 501100.0, + 501120.0, + 501140.0, + 501160.0, + ] + ) + + y = np.array( + [ + 3798040.0, + 3798020.0, + 3798000.0, + 3797980.0, + 3797960.0, + 3797940.0, + 3797920.0, + 3797900.0, + 3797880.0, + 3797860.0, + ] + ) + + return x, y + + +@pytest.fixture +def test_time_coords(): + prefire = np.array( + [ + "2023-05-10T18:19:21.024000000", + "2023-05-15T18:19:19.024000000", + "2023-05-20T18:19:21.024000000", + "2023-05-25T18:19:19.024000000", + "2023-05-30T18:19:21.024000000", + "2023-06-04T18:19:19.024000000", + "2023-06-09T18:19:21.024000000", + ], + dtype="datetime64[ns]", + ) + + postfire = np.array( + [ + "2023-06-19T18:19:21.024000000", + "2023-06-24T18:19:19.024000000", + "2023-06-29T18:19:21.024000000", + "2023-07-04T18:19:19.024000000", + "2023-07-09T18:19:21.024000000", + "2023-07-14T18:19:19.024000000", + ], + dtype="datetime64[ns]", + ) + + return prefire, postfire + + +@pytest.fixture +def test_metadata_and_other_coords(): + with open("tests/assets/test_metadata_and_other_coords.pkl", "rb") as f: + test_metadata_and_other_coords = pickle.load(f) + return test_metadata_and_other_coords + + ### Stacked xarray (time, band, y, x) @pytest.fixture -def test_4d_xarray(): - with open("tests/assets/test_imagery_with_time_xarray.pkl", "rb") as f: - test_valid_xarray = pickle.load(f) +def test_4d_valid_xarray_epsg_4326( + test_spatial_coords_epsg_4326, test_time_coords, test_metadata_and_other_coords +): + x, y = test_spatial_coords_epsg_4326 + prefire, postfire = test_time_coords + metadata = test_metadata_and_other_coords + bands = ["band1", "band2"] + values = np.random.rand(len(prefire) + len(postfire), len(bands), len(y), len(x)) + test_valid_xarray = construct_dataarray( + metadata=metadata, + values=values, + bands=bands, + x=x, + y=y, + epsg=4326, + time=np.concatenate((prefire, postfire)), + ) return test_valid_xarray @pytest.fixture -def test_4d_nan_xarray(test_4d_xarray): - test_nan_xarray = xr.full_like(test_4d_xarray, np.nan) +def test_4d_nan_xarray_epsg_4326(test_4d_valid_xarray_epsg_4326): + test_nan_xarray = xr.full_like(test_4d_valid_xarray_epsg_4326, np.nan) return test_nan_xarray @pytest.fixture -def test_4d_zero_xarray(test_4d_xarray): - test_zero_xarray = xr.full_like(test_4d_xarray, 0) +def test_4d_zero_xarray_epsg_4326(test_4d_valid_xarray_epsg_4326): + test_zero_xarray = xr.full_like(test_4d_valid_xarray_epsg_4326, 0) return test_zero_xarray @@ -44,10 +199,17 @@ def test_4d_zero_xarray(test_4d_xarray): @pytest.fixture -def test_3d_xarray(): - with open("tests/assets/test_imagery_time_reduced_xarray.pkl", "rb") as f: - test_reduced_xarray = pickle.load(f) - return test_reduced_xarray +def test_3d_valid_xarray_epsg_4326( + test_spatial_coords_epsg_4326, test_metadata_and_other_coords +): + x, y = test_spatial_coords_epsg_4326 + metadata = test_metadata_and_other_coords + bands = ["band1", "band2"] + values = np.random.rand(len(bands), len(y), len(x)) + test_3d_xarray = construct_dataarray( + metadata=metadata, values=values, bands=bands, x=x, y=y, epsg=4326 + ) + return test_3d_xarray @pytest.fixture diff --git a/tests/unit/lib/test_burn_severity.py b/tests/unit/lib/test_burn_severity.py index 5ebd976..327b6e6 100644 --- a/tests/unit/lib/test_burn_severity.py +++ b/tests/unit/lib/test_burn_severity.py @@ -3,25 +3,41 @@ import xarray as xr -def test_calc_nbr(test_4d_xarray, test_4d_zero_xarray, test_4d_nan_xarray): +def test_calc_nbr( + test_4d_valid_xarray_epsg_4326, + test_4d_nan_xarray_epsg_4326, + test_4d_zero_xarray_epsg_4326, +): """Test calc_nbr function.""" + + # Rename `band1` and `band2` to `B8A` and `B12` respectively + test_4d_valid_xarray_epsg_4326["band"] = [ + "B8A", + "B12", + ] + test_4d_nan_xarray_epsg_4326["band"] = ["B8A", "B12"] + test_4d_zero_xarray_epsg_4326["band"] = [ + "B8A", + "B12", + ] + # Test valid input - xr_nir = test_4d_xarray.sel(band="B8A") - xr_swir = test_4d_xarray.sel(band="B12") + xr_nir = test_4d_valid_xarray_epsg_4326.sel(band="B8A") + xr_swir = test_4d_valid_xarray_epsg_4326.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape # Test zero input - should work, but not meaningful - xr_nir = test_4d_zero_xarray.sel(band="B8A") - xr_swir = test_4d_zero_xarray.sel(band="B12") + xr_nir = test_4d_zero_xarray_epsg_4326.sel(band="B8A") + xr_swir = test_4d_zero_xarray_epsg_4326.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape # Test NaN input - also should work, but not meaningful - xr_nir = test_4d_nan_xarray.sel(band="B8A") - xr_swir = test_4d_nan_xarray.sel(band="B12") + xr_nir = test_4d_nan_xarray_epsg_4326.sel(band="B8A") + xr_swir = test_4d_nan_xarray_epsg_4326.sel(band="B12") result = burn_severity.calc_nbr(xr_nir, xr_swir) assert result is not None assert result.shape == xr_swir.shape == xr_nir.shape diff --git a/tests/unit/lib/test_query_rap.py b/tests/unit/lib/test_query_rap.py index 9a611fc..aeeac63 100644 --- a/tests/unit/lib/test_query_rap.py +++ b/tests/unit/lib/test_query_rap.py @@ -2,14 +2,16 @@ from src.lib.query_rap import rap_get_biomass import unittest.mock as mock import xarray as xr +import geopandas as gpd +from shapely.geometry import Polygon @mock.patch("rioxarray.open_rasterio") -def test_rap_get_biomass(mock_get, test_geojson, test_3d_xarray): +def test_rap_get_biomass(mock_open, test_3d_valid_xarray_epsg_4326): # Define the arguments for upload_fire_event - rap_estimates = test_3d_xarray + rap_estimates = test_3d_valid_xarray_epsg_4326 - # Duplicate the xarray to add two new bands + # Duplicate the xarray to add two new bands (just to have four, like RAP) rap_estimates = xr.concat([rap_estimates, rap_estimates], dim="band") rap_estimates["band"] = [ "annual_forb_and_grass", @@ -18,8 +20,24 @@ def test_rap_get_biomass(mock_get, test_geojson, test_3d_xarray): "tree", ] - mock_get.return_value.content = rap_estimates - result = rap_get_biomass("2020-01-01", test_geojson) + # Get the bounds + bounds = rap_estimates.rio.bounds() + + # Create a square polygon using the bounds + square_polygon = Polygon( + [ + (bounds[0], bounds[1]), + (bounds[0], bounds[3]), + (bounds[2], bounds[3]), + (bounds[2], bounds[1]), + ] + ) + + # Convert the polygon to a GeoJSON + square_geojson = gpd.GeoSeries([square_polygon]).__geo_interface__ + + mock_open.return_value = rap_estimates + result = rap_get_biomass("2020-01-01", square_geojson, buffer_distance=1) # Add assertions to check the result assert isinstance(result, xr.DataArray) From 5ac35bf0999cda221e9c7a147e91c882d20f4b99 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 22 Feb 2024 17:57:42 +0000 Subject: [PATCH 149/175] closer on rap test - window still erroneously clioping out all valid data, but crs/coords are correct now --- src/lib/query_rap.py | 7 ++++--- tests/unit/lib/test_query_rap.py | 7 +------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/query_rap.py b/src/lib/query_rap.py index 3803e2e..c1951c5 100644 --- a/src/lib/query_rap.py +++ b/src/lib/query_rap.py @@ -41,9 +41,6 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): rap_url = f"http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-npp/v3/vegetation-npp-v3-{rap_year}.tif" - # Create a buffer around the boundary - boundary_gdf_buffer = boundary_gdf.buffer(buffer_distance) - # Create a window from the buffered boundary with rasterio.open(rap_url) as src: window = rasterio.windows.from_bounds(minx, miny, maxx, maxy, src.transform) @@ -62,6 +59,10 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): ["annual_forb_and_grass", "perennial_forb_and_grass", "shrub", "tree"], ) ) + + # Create a buffer around the boundary + boundary_gdf_buffer = boundary_gdf.buffer(buffer_distance) + rap_estimates = rap_estimates.rio.clip( boundary_gdf_buffer.geometry.values, "EPSG:4326" ) diff --git a/tests/unit/lib/test_query_rap.py b/tests/unit/lib/test_query_rap.py index aeeac63..8fc8cbb 100644 --- a/tests/unit/lib/test_query_rap.py +++ b/tests/unit/lib/test_query_rap.py @@ -13,12 +13,7 @@ def test_rap_get_biomass(mock_open, test_3d_valid_xarray_epsg_4326): # Duplicate the xarray to add two new bands (just to have four, like RAP) rap_estimates = xr.concat([rap_estimates, rap_estimates], dim="band") - rap_estimates["band"] = [ - "annual_forb_and_grass", - "perennial_forb_and_grass", - "shrub", - "tree", - ] + rap_estimates["band"] = [1, 2, 3, 4] # Get the bounds bounds = rap_estimates.rio.bounds() From 4807ed48dcbeb709c7d95700857c9eac5b0ce9e2 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Thu, 22 Feb 2024 20:49:36 +0000 Subject: [PATCH 150/175] working - setting the return of mock_open to and then mocking specifically rio.isel_window. Issue was that the window was actually working fine, but the offset was inaccurate due to mocking the return from RAP (since a much tinier xarray). --- tests/unit/lib/test_query_rap.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit/lib/test_query_rap.py b/tests/unit/lib/test_query_rap.py index 8fc8cbb..fd8a0b4 100644 --- a/tests/unit/lib/test_query_rap.py +++ b/tests/unit/lib/test_query_rap.py @@ -1,12 +1,14 @@ import pytest from src.lib.query_rap import rap_get_biomass -import unittest.mock as mock +from unittest.mock import patch +from rioxarray.raster_array import RasterArray import xarray as xr import geopandas as gpd from shapely.geometry import Polygon -@mock.patch("rioxarray.open_rasterio") +@patch("rioxarray.open_rasterio") +# @patch.object(RasterArray, "isel_window") def test_rap_get_biomass(mock_open, test_3d_valid_xarray_epsg_4326): # Define the arguments for upload_fire_event rap_estimates = test_3d_valid_xarray_epsg_4326 @@ -31,7 +33,8 @@ def test_rap_get_biomass(mock_open, test_3d_valid_xarray_epsg_4326): # Convert the polygon to a GeoJSON square_geojson = gpd.GeoSeries([square_polygon]).__geo_interface__ - mock_open.return_value = rap_estimates + mock_instance = mock_open.return_value + mock_instance.rio.isel_window.return_value = rap_estimates result = rap_get_biomass("2020-01-01", square_geojson, buffer_distance=1) # Add assertions to check the result From 2c4e2391fa4ff70b9161ec43c5b43def63131622 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 23 Feb 2024 16:31:34 +0000 Subject: [PATCH 151/175] tests for getting mapunitidpoly from sdm - plus bugfix to backoff that reset to defaults after first debounce" --- src/lib/query_soil.py | 8 ++- ...st_sdm_get_esa_mapunitid_poly_response.pkl | Bin 0 -> 100788 bytes tests/conftest.py | 10 ++++ tests/unit/lib/test_query_soil.py | 49 ++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/assets/test_sdm_get_esa_mapunitid_poly_response.pkl create mode 100644 tests/unit/lib/test_query_soil.py diff --git a/src/lib/query_soil.py b/src/lib/query_soil.py index ad490d6..6fc175b 100644 --- a/src/lib/query_soil.py +++ b/src/lib/query_soil.py @@ -115,7 +115,6 @@ def sdm_get_esa_mapunitid_poly( top_right = f"{bounds[2]},{bounds[3]}" # # format the filter as GML2 / XML - # filter_GML2_fmt = f"Geometry{bottom_left} {top_right}" filter_fmt = ET.Element("Filter") bbox = ET.SubElement(filter_fmt, "BBOX") prop_name = ET.SubElement(bbox, "PropertyName") @@ -174,7 +173,12 @@ def sdm_get_esa_mapunitid_poly( if backoff_value < backoff_max: backoff_value += backoff_increment time.sleep(backoff_value) - return sdm_get_esa_mapunitid_poly(geojson, backoff_value=backoff_value) + return sdm_get_esa_mapunitid_poly( + geojson, + backoff_value=backoff_value, + backoff_max=backoff_max, + backoff_increment=backoff_increment, + ) else: raise Exception("SDM Refused Traffic. Backoff max reached.") except Exception as e: diff --git a/tests/assets/test_sdm_get_esa_mapunitid_poly_response.pkl b/tests/assets/test_sdm_get_esa_mapunitid_poly_response.pkl new file mode 100644 index 0000000000000000000000000000000000000000..0d0d8377acc30448046fd016c79e62dfd43db4b5 GIT binary patch literal 100788 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 M*4obWXnM5zzyAOc(EtDd literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py index 5b08e77..da89960 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -239,3 +239,13 @@ def test_geojson_split(): with open("tests/assets/test_boundary_geology_split.geojson") as f: test_geojson = json.load(f) return test_geojson + + +### Responses + + +@pytest.fixture +def test_sdm_get_esa_mapunitid_poly_response(): + with open("tests/assets/test_sdm_get_esa_mapunitid_poly_response.pkl", "rb") as f: + test_sdm_get_esa_mapunitid_poly_response = pickle.load(f) + return test_sdm_get_esa_mapunitid_poly_response diff --git a/tests/unit/lib/test_query_soil.py b/tests/unit/lib/test_query_soil.py new file mode 100644 index 0000000..7814e1c --- /dev/null +++ b/tests/unit/lib/test_query_soil.py @@ -0,0 +1,49 @@ +import pytest +import requests +from unittest.mock import patch, Mock +from src.lib.query_soil import sdm_get_esa_mapunitid_poly +from shapely.geometry import Polygon +import geopandas as gpd + + +@patch("requests.get") +def test_sdm_get_esa_mapunitid_poly_success( + mock_get, test_geojson, test_sdm_get_esa_mapunitid_poly_response +): + # Set the mock response + mock_get.return_value = test_sdm_get_esa_mapunitid_poly_response + + # Call the function + result = sdm_get_esa_mapunitid_poly(test_geojson) + + # Add assertions to check the result + assert result is not None + assert isinstance(result, gpd.GeoDataFrame) + + +@patch("requests.get") +def test_sdm_get_esa_mapunitid_poly_failure(mock_get, test_geojson): + # Define the mock response + mock_response = Mock() + mock_response.status_code = 400 + + # Set the mock response + mock_get.return_value = mock_response + + # Call the function + result = sdm_get_esa_mapunitid_poly(test_geojson) + + # Add assertions to check the result + assert result is None + + +@patch("requests.get") +def test_sdm_get_esa_mapunitid_poly_exception(mock_get, test_geojson): + # Set the mock to raise an exception + mock_get.side_effect = requests.exceptions.ConnectionError + + # Call the function and expect an exception + with pytest.raises(Exception): + sdm_get_esa_mapunitid_poly( + test_geojson, backoff_max=2, backoff_increment=1, backoff_value=0 + ) From 12bc33fc14e14208a6ab018964dd679153f09ee1 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 23 Feb 2024 16:34:41 +0000 Subject: [PATCH 152/175] simple tests for proper status code handling from EDIT, once we have the mapunitid_polys --- src/lib/query_soil.py | 3 +- tests/unit/lib/test_query_soil.py | 64 ++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/lib/query_soil.py b/src/lib/query_soil.py index 6fc175b..cfb31dd 100644 --- a/src/lib/query_soil.py +++ b/src/lib/query_soil.py @@ -295,5 +295,4 @@ def edit_get_ecoclass_info(ecoclass_id): ) except Exception as e: - print("Error:", str(e)) - return None + raise Exception(f"Error in EDIT: {str(e)}") diff --git a/tests/unit/lib/test_query_soil.py b/tests/unit/lib/test_query_soil.py index 7814e1c..fa6c017 100644 --- a/tests/unit/lib/test_query_soil.py +++ b/tests/unit/lib/test_query_soil.py @@ -1,9 +1,10 @@ import pytest import requests from unittest.mock import patch, Mock -from src.lib.query_soil import sdm_get_esa_mapunitid_poly +from src.lib.query_soil import sdm_get_esa_mapunitid_poly, edit_get_ecoclass_info from shapely.geometry import Polygon import geopandas as gpd +import json @patch("requests.get") @@ -47,3 +48,64 @@ def test_sdm_get_esa_mapunitid_poly_exception(mock_get, test_geojson): sdm_get_esa_mapunitid_poly( test_geojson, backoff_max=2, backoff_increment=1, backoff_value=0 ) + + +@patch("requests.get") +def test_edit_get_ecoclass_info_success(mock_get): + # Define the mock response + mock_response = Mock() + mock_response.status_code = 200 + mock_response.content = json.dumps({"key": "value"}) + + # Set the mock response + mock_get.return_value = mock_response + + # Call the function + result = edit_get_ecoclass_info("E1234") + + # Add assertions to check the result + assert result is not None + assert result["key"] == "value" + assert ( + result["hyperlink"] == "https://edit.jornada.nmsu.edu/catalogs/esd/1234/E1234" + ) + + +@patch("requests.get") +def test_edit_get_ecoclass_info_not_found(mock_get): + # Define the mock response + mock_response = Mock() + mock_response.status_code = 404 + + # Set the mock response + mock_get.return_value = mock_response + + # Call the function + result = edit_get_ecoclass_info("E1234") + + # Add assertions to check the result + assert result is None + + +@patch("requests.get") +def test_edit_get_ecoclass_info_failure(mock_get): + # Define the mock response + mock_response = Mock() + mock_response.status_code = 500 + + # Set the mock response + mock_get.return_value = mock_response + + # Call the function and expect an exception + with pytest.raises(Exception): + edit_get_ecoclass_info("E1234") + + +@patch("requests.get") +def test_edit_get_ecoclass_info_exception(mock_get): + # Set the mock to raise an exception + mock_get.side_effect = requests.exceptions.ConnectionError + + # Call the function and expect an exception + with pytest.raises(Exception): + edit_get_ecoclass_info("E1234") From 94e69686bd5ba497e437d973a082918a59795375 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 23 Feb 2024 17:07:18 +0000 Subject: [PATCH 153/175] rewrite query rap test - now using a small tif to emulate the behavior of RAP tiff from server side, which has the nice feature of ensuring windowing is working as expected. --- src/lib/query_rap.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/lib/query_rap.py b/src/lib/query_rap.py index c1951c5..334b3de 100644 --- a/src/lib/query_rap.py +++ b/src/lib/query_rap.py @@ -5,8 +5,15 @@ import numpy as np import json +RAP_URL_YEAR_FSTRING = "http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-npp/v3/vegetation-npp-v3-{year}.tif" -def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): + +def rap_get_biomass( + ignition_date, + boundary_geojson, + buffer_distance=0.01, + rap_url_year_fstring=RAP_URL_YEAR_FSTRING, +): """ Retrieves biomass estimates from the Rangeland Analysis Platform for a given ignition year and boundary location. RAP provides estimates of biomass for four categories: annual forb and grass, perennial forb and grass, shrub, and tree. @@ -32,21 +39,24 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): minx, miny, maxx, maxy = boundary_gdf.total_bounds # Get the RAP URL - rap_year = time.strptime(ignition_date, "%Y-%m-%d").tm_year - if rap_year > 2022: + year = time.strptime(ignition_date, "%Y-%m-%d").tm_year + if year > 2022: print("RAP data is only available up to 2022 - falling back to 2022 RAP data") - rap_year = 2022 - elif rap_year < 1986: + year = 2022 + elif year < 1986: raise ValueError("RAP data is only available from 1986") - rap_url = f"http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-npp/v3/vegetation-npp-v3-{rap_year}.tif" + # Format the RAP URL with the year to grab the proper tif + rap_url_year_fstring = rap_url_year_fstring.format(year=year) # Create a window from the buffered boundary - with rasterio.open(rap_url) as src: + with rasterio.open(rap_url_year_fstring) as src: window = rasterio.windows.from_bounds(minx, miny, maxx, maxy, src.transform) # Open the GeoTIFF file as a rioxarray with the window and out_shape parameters - rap_estimates = rxr.open_rasterio(rap_url, masked=True).rio.isel_window(window) + rap_estimates = rxr.open_rasterio( + rap_url_year_fstring, masked=True + ).rio.isel_window(window) # Rename for RAP bands based on README: # - Band 1 - annual forb and grass From de9b8633c57602f9b4909c4bab1c9000345cdf8c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 23 Feb 2024 17:07:18 +0000 Subject: [PATCH 154/175] rewrite query rap test - now using a small tif to emulate the behavior of RAP tiff from server side, which has the nice feature of ensuring windowing is working as expected. --- src/lib/query_rap.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/lib/query_rap.py b/src/lib/query_rap.py index c1951c5..334b3de 100644 --- a/src/lib/query_rap.py +++ b/src/lib/query_rap.py @@ -5,8 +5,15 @@ import numpy as np import json +RAP_URL_YEAR_FSTRING = "http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-npp/v3/vegetation-npp-v3-{year}.tif" -def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): + +def rap_get_biomass( + ignition_date, + boundary_geojson, + buffer_distance=0.01, + rap_url_year_fstring=RAP_URL_YEAR_FSTRING, +): """ Retrieves biomass estimates from the Rangeland Analysis Platform for a given ignition year and boundary location. RAP provides estimates of biomass for four categories: annual forb and grass, perennial forb and grass, shrub, and tree. @@ -32,21 +39,24 @@ def rap_get_biomass(ignition_date, boundary_geojson, buffer_distance=0.01): minx, miny, maxx, maxy = boundary_gdf.total_bounds # Get the RAP URL - rap_year = time.strptime(ignition_date, "%Y-%m-%d").tm_year - if rap_year > 2022: + year = time.strptime(ignition_date, "%Y-%m-%d").tm_year + if year > 2022: print("RAP data is only available up to 2022 - falling back to 2022 RAP data") - rap_year = 2022 - elif rap_year < 1986: + year = 2022 + elif year < 1986: raise ValueError("RAP data is only available from 1986") - rap_url = f"http://rangeland.ntsg.umt.edu/data/rap/rap-vegetation-npp/v3/vegetation-npp-v3-{rap_year}.tif" + # Format the RAP URL with the year to grab the proper tif + rap_url_year_fstring = rap_url_year_fstring.format(year=year) # Create a window from the buffered boundary - with rasterio.open(rap_url) as src: + with rasterio.open(rap_url_year_fstring) as src: window = rasterio.windows.from_bounds(minx, miny, maxx, maxy, src.transform) # Open the GeoTIFF file as a rioxarray with the window and out_shape parameters - rap_estimates = rxr.open_rasterio(rap_url, masked=True).rio.isel_window(window) + rap_estimates = rxr.open_rasterio( + rap_url_year_fstring, masked=True + ).rio.isel_window(window) # Rename for RAP bands based on README: # - Band 1 - annual forb and grass From eb095c1888b39feb029a07748ca97b3d54fb9c24 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 23 Feb 2024 18:04:21 +0000 Subject: [PATCH 155/175] rename for new fixtures, add small test_rap_tif --- tests/assets/test_rap_small_2020.tif | Bin 0 -> 111052 bytes tests/unit/lib/test_burn_severity.py | 18 +++++++------- tests/unit/lib/test_query_rap.py | 30 +++++++++++++++--------- tests/unit/util/test_cloud_static_io.py | 24 ++++++++++++------- 4 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 tests/assets/test_rap_small_2020.tif diff --git a/tests/assets/test_rap_small_2020.tif b/tests/assets/test_rap_small_2020.tif new file mode 100644 index 0000000000000000000000000000000000000000..4d6e2d80526033a6b38264a420eba979773947be GIT binary patch literal 111052 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%0Dr__~2l z81(MB=Kb}bn7*CEyT|r9IX%+*-7$a8&F`&a=e_ptYt!u)0$f z#@*j*arb*${^MEQ&+6Si>(=jnA-3cD#iDfVd^xAO5pT~k_8-@W`^EOPFBtfi;-Zeu z%TMRS*%L1r)$bV-FCNA9f{B-p?u+-dpSb)@=eqfe()}UvBR%KP!)fuw@sInQ#`)?Q zgRbd#e2&vSn^p|{#<2C#>KFw*uW0s zX%oA~UbS#ZaK@e*jF*pjk762}S6<9rkj~hKt+{9Xsd)T7lKToB(64yPHJz&wHfhTz zo%89);5sc>*XJ=C$J?jhc>`N#huW*hhh~TU{4QsW*yjsssE+|hI{en~JVWp1UD^3# z{H49WVwZmFTtDcY{?&`~pPt9hG1~7L`acjm&*}V>bRb90b^L{RUQ8eOuI%IIufyiy z-oSS`7h5r26wV6Hf-eR!F`gEF8@3zq?EUhW`6=$dXnlVECZ^Y4$DfPuYd`13mnP

      0v+=a>nQ%;y<0A$>Jl;5{Be8K^Z0Hr! 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 literal 0 HcmV?d00001 diff --git a/tests/unit/lib/test_burn_severity.py b/tests/unit/lib/test_burn_severity.py index 327b6e6..67acd30 100644 --- a/tests/unit/lib/test_burn_severity.py +++ b/tests/unit/lib/test_burn_severity.py @@ -44,25 +44,25 @@ def test_calc_nbr( assert result.isnull().all() -def test_calc_dnbr(test_3d_xarray): - test_nbr_prefire = test_3d_xarray - test_nbr_postfire = test_3d_xarray + 0.15 +def test_calc_dnbr(test_3d_valid_xarray_epsg_4326): + test_nbr_prefire = test_3d_valid_xarray_epsg_4326 + test_nbr_postfire = test_3d_valid_xarray_epsg_4326 + 0.15 result = burn_severity.calc_dnbr(test_nbr_prefire, test_nbr_postfire) assert result is not None assert result.shape == test_nbr_prefire.shape == test_nbr_postfire.shape -def test_calc_rdnbr(test_3d_xarray): - test_nbr_prefire = test_3d_xarray - test_dnbr = xr.full_like(test_3d_xarray, 0.15) +def test_calc_rdnbr(test_3d_valid_xarray_epsg_4326): + test_nbr_prefire = test_3d_valid_xarray_epsg_4326 + test_dnbr = xr.full_like(test_3d_valid_xarray_epsg_4326, 0.15) result = burn_severity.calc_rdnbr(test_dnbr, test_nbr_prefire) assert result is not None assert result.shape == test_dnbr.shape == test_nbr_prefire.shape -def test_calc_rbr(test_3d_xarray): - test_nbr_prefire = test_3d_xarray - test_dnbr = xr.full_like(test_3d_xarray, 0.15) +def test_calc_rbr(test_3d_valid_xarray_epsg_4326): + test_nbr_prefire = test_3d_valid_xarray_epsg_4326 + test_dnbr = xr.full_like(test_3d_valid_xarray_epsg_4326, 0.15) result = burn_severity.calc_rbr(test_dnbr, test_nbr_prefire) assert result is not None assert result.shape == test_nbr_prefire.shape == test_nbr_prefire.shape diff --git a/tests/unit/lib/test_query_rap.py b/tests/unit/lib/test_query_rap.py index fd8a0b4..bef4d47 100644 --- a/tests/unit/lib/test_query_rap.py +++ b/tests/unit/lib/test_query_rap.py @@ -7,9 +7,7 @@ from shapely.geometry import Polygon -@patch("rioxarray.open_rasterio") -# @patch.object(RasterArray, "isel_window") -def test_rap_get_biomass(mock_open, test_3d_valid_xarray_epsg_4326): +def test_rap_get_biomass_success(test_3d_valid_xarray_epsg_4326): # Define the arguments for upload_fire_event rap_estimates = test_3d_valid_xarray_epsg_4326 @@ -18,24 +16,34 @@ def test_rap_get_biomass(mock_open, test_3d_valid_xarray_epsg_4326): rap_estimates["band"] = [1, 2, 3, 4] # Get the bounds - bounds = rap_estimates.rio.bounds() + minx, miny, maxx, maxy = rap_estimates.rio.bounds() # Create a square polygon using the bounds square_polygon = Polygon( [ - (bounds[0], bounds[1]), - (bounds[0], bounds[3]), - (bounds[2], bounds[3]), - (bounds[2], bounds[1]), + (minx, miny), + (minx, maxy), + (maxx, maxy), + (maxx, miny), + (minx, miny), ] ) # Convert the polygon to a GeoJSON square_geojson = gpd.GeoSeries([square_polygon]).__geo_interface__ - mock_instance = mock_open.return_value - mock_instance.rio.isel_window.return_value = rap_estimates - result = rap_get_biomass("2020-01-01", square_geojson, buffer_distance=1) + result = rap_get_biomass( + "2020-01-01", + square_geojson, + buffer_distance=1, + rap_url_year_fstring="tests/assets/test_rap_small_{year}.tif", + ) # Add assertions to check the result assert isinstance(result, xr.DataArray) + + result_minx, result_miny, result_maxx, result_maxy = result.rio.bounds() + assert result_minx <= minx + assert result_miny <= miny + assert result_maxx >= maxx + assert result_maxy >= maxy diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index cf910c3..0b11537 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -8,7 +8,7 @@ @patch("src.util.cloud_static_io.CloudStaticIOClient.upload_cogs") @patch.object(CloudStaticIOClient, "__init__", return_value=None) def test_upload_fire_event( - mock_init, mock_upload_cogs, mock_update_manifest, test_3d_xarray + mock_init, mock_upload_cogs, mock_update_manifest, test_3d_valid_xarray_epsg_4326 ): # Create an instance of CloudStaticIOClient client = CloudStaticIOClient() @@ -17,7 +17,7 @@ def test_upload_fire_event( client.logger = MagicMock() # Define the arguments for upload_fire_event - metrics_stack = test_3d_xarray + metrics_stack = test_3d_valid_xarray_epsg_4326 metrics_stack = metrics_stack.rename({"band": "burn_metric"}) metrics_stack["burn_metric"] = ["rbr", "dnbr"] @@ -59,7 +59,11 @@ def test_upload_fire_event( @patch.object(CloudStaticIOClient, "upload", return_value=None) @patch.object(CloudStaticIOClient, "__init__", return_value=None) def test_upload_cogs( - mock_init, mock_upload, mock_rio_open, mock_to_raster, test_3d_xarray + mock_init, + mock_upload, + mock_rio_open, + mock_to_raster, + test_3d_valid_xarray_epsg_4326, ): # Create an instance of CloudStaticIOClient client = CloudStaticIOClient() @@ -72,12 +76,14 @@ def test_upload_cogs( affiliation = "test_affiliation" # Give xarray the `burn_metric` band, with rbr and dnbr as values in `burn_metric` - test_3d_xarray = test_3d_xarray.rename({"band": "burn_metric"}) - test_3d_xarray["burn_metric"] = ["rbr", "dnbr"] + test_3d_valid_xarray_epsg_4326 = test_3d_valid_xarray_epsg_4326.rename( + {"band": "burn_metric"} + ) + test_3d_valid_xarray_epsg_4326["burn_metric"] = ["rbr", "dnbr"] # Call upload_cogs client.upload_cogs( - test_3d_xarray, + test_3d_valid_xarray_epsg_4326, fire_event_name, affiliation, ) @@ -114,7 +120,7 @@ def test_upload_rap_estimates( mock_rio_open, mock_os_join, mock_temp_dir, - test_3d_xarray, + test_3d_valid_xarray_epsg_4326, ): # Create an instance of CloudStaticIOClient client = CloudStaticIOClient() @@ -127,11 +133,11 @@ def test_upload_rap_estimates( affiliation = "test_affiliation" # Give xarray the `band` band, with some band names as values in `band` - test_3d_xarray["band"] = ["tree", "shrub"] + test_3d_valid_xarray_epsg_4326["band"] = ["tree", "shrub"] # Call upload_rap_estimates client.upload_rap_estimates( - test_3d_xarray, + test_3d_valid_xarray_epsg_4326, fire_event_name, affiliation, ) From 0abe344e95d638aa1beb4cb8a65ed7eb00e01a10 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Fri, 23 Feb 2024 18:06:53 +0000 Subject: [PATCH 156/175] test invalid rap year --- tests/unit/lib/test_query_rap.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/lib/test_query_rap.py b/tests/unit/lib/test_query_rap.py index bef4d47..8784087 100644 --- a/tests/unit/lib/test_query_rap.py +++ b/tests/unit/lib/test_query_rap.py @@ -47,3 +47,37 @@ def test_rap_get_biomass_success(test_3d_valid_xarray_epsg_4326): assert result_miny <= miny assert result_maxx >= maxx assert result_maxy >= maxy + + +def test_rap_get_biomass_invalid_year(test_3d_valid_xarray_epsg_4326): + # Define the arguments for upload_fire_event + rap_estimates = test_3d_valid_xarray_epsg_4326 + + # Duplicate the xarray to add two new bands (just to have four, like RAP) + rap_estimates = xr.concat([rap_estimates, rap_estimates], dim="band") + rap_estimates["band"] = [1, 2, 3, 4] + + # Get the bounds + minx, miny, maxx, maxy = rap_estimates.rio.bounds() + + # Create a square polygon using the bounds + square_polygon = Polygon( + [ + (minx, miny), + (minx, maxy), + (maxx, maxy), + (maxx, miny), + (minx, miny), + ] + ) + + # Convert the polygon to a GeoJSON + square_geojson = gpd.GeoSeries([square_polygon]).__geo_interface__ + + with pytest.raises(ValueError): + result = rap_get_biomass( + "1916-01-01", + square_geojson, + buffer_distance=1, + rap_url_year_fstring="tests/assets/test_rap_small_{year}.tif", + ) From 1bade91b059c281ebd8bd0355c9d68ebe842a54b Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 26 Feb 2024 19:16:56 +0000 Subject: [PATCH 157/175] test query sentinel --- tests/assets/test_stac_item_collection.pkl | Bin 0 -> 241482 bytes tests/assets/test_stac_items.json | 1507 -------------------- tests/conftest.py | 9 +- tests/unit/lib/test_query_sentinel.py | 72 + 4 files changed, 76 insertions(+), 1512 deletions(-) create mode 100644 tests/assets/test_stac_item_collection.pkl delete mode 100644 tests/assets/test_stac_items.json create mode 100644 tests/unit/lib/test_query_sentinel.py diff --git a/tests/assets/test_stac_item_collection.pkl b/tests/assets/test_stac_item_collection.pkl new file mode 100644 index 0000000000000000000000000000000000000000..0ecd5a4bf2e1b60e58c451a5c3e16749020c29d3 GIT binary patch literal 241482 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 Date: Mon, 26 Feb 2024 19:49:13 +0000 Subject: [PATCH 158/175] test query_fire_event --- tests/unit/lib/test_query_sentinel.py | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/unit/lib/test_query_sentinel.py b/tests/unit/lib/test_query_sentinel.py index c4e463a..d7063c2 100644 --- a/tests/unit/lib/test_query_sentinel.py +++ b/tests/unit/lib/test_query_sentinel.py @@ -1,7 +1,7 @@ # FILEPATH: /workspace/tests/unit/lib/test_query_sentinel.py import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, call from src.lib.query_sentinel import Sentinel2Client import geopandas as gpd from shapely.geometry import Polygon @@ -70,3 +70,32 @@ def test_reduce_time_range(test_geojson, test_4d_valid_xarray_epsg_4326): reduced = client.reduce_time_range(test_4d_valid_xarray_epsg_4326) assert not "time" in reduced.dims + + +def test_query_fire_event(test_geojson, test_stac_item_collection): + # Initialize Sentinel2Client + client = Sentinel2Client(test_geojson) + + # Mock the client.get_items() method to return a test item collection + client.get_items = MagicMock() + client.get_items.return_value = test_stac_item_collection + + # Mock arrange stack method + client.arrange_stack = MagicMock() + + # Call the query_fire_event method + client.query_fire_event( + prefire_date_range=("2020-01-01", "2020-02-01"), + postfire_date_range=("2020-03-01", "2020-04-01"), + ) + + # Check that the get_items method was called with the correct arguments + client.get_items.assert_has_calls( + [ + call(("2020-01-01", "2020-02-01"), from_bbox=True, max_items=None), + call(("2020-03-01", "2020-04-01"), from_bbox=True, max_items=None), + ] + ) + + assert client.prefire_stack is not None + assert client.postfire_stack is not None From 87a96a8a4001670c31e4f9f822217e9fd63d1e54 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 26 Feb 2024 19:51:37 +0000 Subject: [PATCH 159/175] test query_fire_event --- tests/unit/lib/test_query_sentinel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/lib/test_query_sentinel.py b/tests/unit/lib/test_query_sentinel.py index d7063c2..b21219f 100644 --- a/tests/unit/lib/test_query_sentinel.py +++ b/tests/unit/lib/test_query_sentinel.py @@ -97,5 +97,7 @@ def test_query_fire_event(test_geojson, test_stac_item_collection): ] ) + # Check that the arrange_stack method was called with the correct arguments + client.arrange_stack.assert_called_with(test_stac_item_collection) assert client.prefire_stack is not None assert client.postfire_stack is not None From 23a4619a3805ad941b2f00eef6393814de328867 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 26 Feb 2024 20:32:21 +0000 Subject: [PATCH 160/175] test_derive_boundary --- tests/unit/lib/test_query_sentinel.py | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/unit/lib/test_query_sentinel.py b/tests/unit/lib/test_query_sentinel.py index b21219f..c83d4ca 100644 --- a/tests/unit/lib/test_query_sentinel.py +++ b/tests/unit/lib/test_query_sentinel.py @@ -3,6 +3,7 @@ import pytest from unittest.mock import MagicMock, patch, call from src.lib.query_sentinel import Sentinel2Client +from src.lib.burn_severity import calc_burn_metrics import geopandas as gpd from shapely.geometry import Polygon import xarray as xr @@ -101,3 +102,56 @@ def test_query_fire_event(test_geojson, test_stac_item_collection): client.arrange_stack.assert_called_with(test_stac_item_collection) assert client.prefire_stack is not None assert client.postfire_stack is not None + + +def test_calc_burn_metrics(test_geojson, test_3d_valid_xarray_epsg_4326): + # Initialize Sentinel2Client + client = Sentinel2Client(test_geojson) + + # Initialize prefire and postfire stacks + client.band_nir = "B8A" + client.band_swir = "B12" + + # Init prefire stack + test_3d_valid_xarray_epsg_4326["band"] = ["B8A", "B12"] + prefire_stack = test_3d_valid_xarray_epsg_4326 + client.prefire_stack = prefire_stack + + # Init postfire stack + postfire_stack = test_3d_valid_xarray_epsg_4326.copy(deep=True) + postfire_stack.loc[dict(band=client.band_swir)] += 0.25 + postfire_stack.loc[dict(band=client.band_nir)] -= 0.25 + client.postfire_stack = postfire_stack + + # Call the calc_burn_metrics method + client.calc_burn_metrics() + + # Check that the metrics stack was created and that it contains the correct metrics + assert client.metrics_stack is not None + assert all( + [ + metric in client.metrics_stack.burn_metric + for metric in ["nbr_prefire", "nbr_postfire", "dnbr", "rdnbr", "rbr"] + ] + ) + + +def test_derive_boundary(test_geojson, test_3d_valid_xarray_epsg_4326): + # Initialize Sentinel2Client + client = Sentinel2Client(test_geojson) + + # Initialize metrics stack + metrics_stack = test_3d_valid_xarray_epsg_4326.rename({"band": "burn_metric"}) + metrics_stack["burn_metric"] = ["rbr", "dnbr"] + client.metrics_stack = metrics_stack + + # Save the initial boundary + initial_boundary = client.geojson_boundary + + # Call the derive_boundary method + client.derive_boundary(metric_name="rbr", threshold=0.025) + + # Check that the boundary was updated + assert all( + (initial_boundary.bounds != client.geojson_boundary.bounds).values[0].tolist() + ) From 7ef78ab1e3ea3aab34004c8a67c256ae6f14db19 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Mon, 26 Feb 2024 21:49:23 +0000 Subject: [PATCH 161/175] disable arrange stack test - weird rio.reproject issue that seems to be hitting original stac items at time of reprojection --- tests/unit/lib/test_query_sentinel.py | 45 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/unit/lib/test_query_sentinel.py b/tests/unit/lib/test_query_sentinel.py index c83d4ca..35b7b0a 100644 --- a/tests/unit/lib/test_query_sentinel.py +++ b/tests/unit/lib/test_query_sentinel.py @@ -8,6 +8,7 @@ from shapely.geometry import Polygon import xarray as xr import numpy as np +from rioxarray.raster_array import RasterArray def test_set_boundary(test_geojson): @@ -37,30 +38,36 @@ def test_get_items(test_geojson, test_stac_item_collection): ) -def test_arrange_stack(test_geojson, test_stac_item_collection): - # Initialize Sentinel2Client - client = Sentinel2Client(test_geojson) +## TODO Why does rio.reproject call the original stac assets from planetary computer, even when reprojecting a local computed RasterArray?: +## rio.reproject fails with picked item collection because it is calling the original stac assets from planetary computer +## This makes very little sense since the thing being reprojected has already had some computation done on it, so we aren't +## reprojecting the original stac assets at all. - # Stack the test item collection - stack = client.arrange_stack(test_stac_item_collection) +# @patch.object(RasterArray, "reproject", MagicMock()) +# def test_arrange_stack(test_geojson, test_stac_item_collection): +# # Initialize Sentinel2Client +# client = Sentinel2Client(test_geojson) - assert all(stack.band.values == ["B8A", "B12"]) - assert stack.dims == ("band", "y", "x") +# # Stack the test item collection +# stack = client.arrange_stack(test_stac_item_collection) - test_bounds = gpd.GeoDataFrame.from_features(test_geojson["features"]).bounds - test_minx, test_miny, test_maxx, test_maxy = ( - test_bounds.minx[0], - test_bounds.miny[0], - test_bounds.maxx[0], - test_bounds.maxy[0], - ) +# assert all(stack.band.values == ["B8A", "B12"]) +# assert stack.dims == ("band", "y", "x") + +# test_bounds = gpd.GeoDataFrame.from_features(test_geojson["features"]).bounds +# test_minx, test_miny, test_maxx, test_maxy = ( +# test_bounds.minx[0], +# test_bounds.miny[0], +# test_bounds.maxx[0], +# test_bounds.maxy[0], +# ) - stack_minx, stack_miny, stack_maxx, stack_maxy = stack.rio.bounds() +# stack_minx, stack_miny, stack_maxx, stack_maxy = stack.rio.bounds() - assert np.isclose(stack_minx, test_minx, rtol=1e-5) - assert np.isclose(stack_miny, test_miny, rtol=1e-5) - assert np.isclose(stack_maxx, test_maxx, rtol=1e-5) - assert np.isclose(stack_maxy, test_maxy, rtol=1e-5) +# assert np.isclose(stack_minx, test_minx, rtol=1e-5) +# assert np.isclose(stack_miny, test_miny, rtol=1e-5) +# assert np.isclose(stack_maxx, test_maxx, rtol=1e-5) +# assert np.isclose(stack_maxy, test_maxy, rtol=1e-5) def test_reduce_time_range(test_geojson, test_4d_valid_xarray_epsg_4326): From 2a52e6c97e3412132b360a75d54b18c44f6cf565 Mon Sep 17 00:00:00 2001 From: TODO Date: Tue, 27 Feb 2024 00:48:55 +0000 Subject: [PATCH 162/175] Collect TODO comments --- src/lib/query_sentinel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/query_sentinel.py b/src/lib/query_sentinel.py index 344210a..b6a1d9d 100644 --- a/src/lib/query_sentinel.py +++ b/src/lib/query_sentinel.py @@ -194,7 +194,7 @@ def reduce_time_range(self, range_stack): xarray.DataArray: The reduced range stack. """ - # TODO: Think about best practice for reducing time dimension pre/post fire + # TODO [$65dd3177b6782d0008007816]: Think about best practice for reducing time dimension pre/post fire # This will probably get a bit more sophisticated, but for now, just take the median # We will probably run into issues of cloud occlusion, and for really long fire events, # we might want to look into time-series effects of greenup, drying, etc, in the adjacent From 2d0c5a0fe98b605bc3e793d5c54bb79e294f9d86 Mon Sep 17 00:00:00 2001 From: TODO Date: Tue, 27 Feb 2024 00:48:57 +0000 Subject: [PATCH 163/175] Update TODO references: #30 --- src/lib/query_sentinel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/query_sentinel.py b/src/lib/query_sentinel.py index b6a1d9d..d8ddda3 100644 --- a/src/lib/query_sentinel.py +++ b/src/lib/query_sentinel.py @@ -194,7 +194,7 @@ def reduce_time_range(self, range_stack): xarray.DataArray: The reduced range stack. """ - # TODO [$65dd3177b6782d0008007816]: Think about best practice for reducing time dimension pre/post fire + # TODO [#30]: Think about best practice for reducing time dimension pre/post fire # This will probably get a bit more sophisticated, but for now, just take the median # We will probably run into issues of cloud occlusion, and for really long fire events, # we might want to look into time-series effects of greenup, drying, etc, in the adjacent From 101680670539226b0f080813dd5d5b4bb768e4dc Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 27 Feb 2024 02:08:19 +0000 Subject: [PATCH 164/175] using tofu workspace, init a second environment , and add logic to push to or cloud run depending on where the github action (build and deploy) is running --- .deployment/tofu/modules/burn_backend/main.tf | 24 +++++++++---------- .deployment/tofu/modules/static_io/main.tf | 4 ++-- .devcontainer/README.md | 22 ++++++++++++++++- .github/workflows/build_and_deploy.yml | 9 ++++++- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/.deployment/tofu/modules/burn_backend/main.tf b/.deployment/tofu/modules/burn_backend/main.tf index 9ba1fc2..57647d4 100644 --- a/.deployment/tofu/modules/burn_backend/main.tf +++ b/.deployment/tofu/modules/burn_backend/main.tf @@ -1,7 +1,7 @@ # Create a VPC access connector, to let the Cloud Run service access the AWS Transfer server resource "google_vpc_access_connector" "burn_backend_vpc_connector" { - name = "vpc-burn2023" # just to match aws naming reqs + name = "vpc-burn2023-${terraform.workspace}" # just to match aws naming reqs # network = google_compute_network.burn_backend_network.id subnet { name = google_compute_subnetwork.burn_backend_subnetwork.name @@ -12,7 +12,7 @@ resource "google_vpc_access_connector" "burn_backend_vpc_connector" { } resource "google_compute_subnetwork" "burn_backend_subnetwork" { - name = "run-subnetwork" + name = "run-subnetwork-${terraform.workspace}" ip_cidr_range = "10.2.0.0/28" region = "us-central1" network = google_compute_network.burn_backend_network.id @@ -20,26 +20,26 @@ resource "google_compute_subnetwork" "burn_backend_subnetwork" { } resource "google_compute_network" "burn_backend_network" { - name = "burn-backend-run-network" + name = "burn-backend-run-network-${terraform.workspace}" auto_create_subnetworks = false } # Create a Cloud Router resource "google_compute_router" "burn_backend_router" { - name = "burn-backend-router" + name = "burn-backend-router-${terraform.workspace}" network = google_compute_network.burn_backend_network.name region = "us-central1" } # Reserve a static IP address resource "google_compute_address" "burn_backend_static_ip" { - name = "burn-backend-static-ip" + name = "burn-backend-static-ip-${terraform.workspace}" region = "us-central1" } # Set up Cloud NAT resource "google_compute_router_nat" "burn_backend_nat" { - name = "burn-backend-nat" + name = "burn-backend-nat-${terraform.workspace}" router = google_compute_router.burn_backend_router.name region = "us-central1" @@ -98,7 +98,7 @@ resource "google_compute_router_nat" "burn_backend_nat" { # Create a Cloud Run service for burn-backend services resource "google_cloud_run_v2_service" "tf-rest-burn-severity" { - name = "tf-rest-burn-severity" + name = "tf-rest-burn-severity-${terraform.workspace}" location = "us-central1" template { @@ -194,14 +194,14 @@ resource "google_cloud_run_service_iam_member" "public" { # Create the IAM workload identity pool and provider to auth GitHub Actions resource "google_iam_workload_identity_pool" "pool" { - workload_identity_pool_id = "github-actions" + workload_identity_pool_id = "github-actions-${terraform.workspace}" display_name = "Github Actions Pool" description = "Workload identity pool for GitHub actions" } resource "google_iam_workload_identity_pool_provider" "oidc" { depends_on = [google_iam_workload_identity_pool.pool] - workload_identity_pool_provider_id = "oidc-provider" + workload_identity_pool_provider_id = "oidc-provider-${terraform.workspace}" workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id display_name = "GitHub OIDC Provider" @@ -231,7 +231,7 @@ resource "google_service_account_iam_binding" "workload_identity_user" { # Create the IAM service account for GitHub Actions resource "google_service_account" "github_actions" { - account_id = "github-actions-service-account" + account_id = "github-actions-sa-${terraform.workspace}" display_name = "Github Actions Service Account" description = "This service account is used by GitHub Actions" project = "dse-nps" @@ -239,7 +239,7 @@ resource "google_service_account" "github_actions" { # Create the IAM service account for the Cloud Run service resource "google_service_account" "burn-backend-service" { - account_id = "burn-backend-service" + account_id = "burn-backend-service-${terraform.workspace}" display_name = "Cloud Run Service Account for burn backend" description = "This service account is used by the Cloud Run service to access GCP Secrets Manager and authenticate with OIDC for AWS S3 access" project = "dse-nps" @@ -296,7 +296,7 @@ resource "google_project_iam_member" "artifact_registry_writer" { # Create an Artifact Registry repo for the container image resource "google_artifact_registry_repository" "burn-backend" { - repository_id = "burn-backend" + repository_id = "burn-backend-${terraform.workspace}" format = "DOCKER" location = "us-central1" } \ No newline at end of file diff --git a/.deployment/tofu/modules/static_io/main.tf b/.deployment/tofu/modules/static_io/main.tf index 8acf01e..98dbe56 100644 --- a/.deployment/tofu/modules/static_io/main.tf +++ b/.deployment/tofu/modules/static_io/main.tf @@ -2,7 +2,7 @@ # Then, the s3 bucket for the server resource "aws_s3_bucket" "burn-severity-backend" { - bucket = "burn-severity-backend" + bucket = "burn-severity-backend-${terraform.workspace}" } resource "aws_s3_bucket_versioning" "burn-severity-backend" { @@ -289,7 +289,7 @@ data "aws_iam_policy_document" "session_policy" { # Create the IAM role with both the assume-role and the session policy. resource "aws_iam_role" "aws_s3_from_gcp" { - name = "aws_s3_from_gcp" + name = "aws_s3_from_gcp_${terraform.workspace}" assume_role_policy = data.aws_iam_policy_document.oidc_assume_role_policy.json # Inline policy for session diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 413563b..e8263db 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -46,7 +46,27 @@ gcloud auth application-default login _Note_: This SSO auth process must be performed peridically, as the authentication token generated are short-lived (important, as the scope of this auth is broad for provisioning resources and could be used by nefarious actors). So, if you run into an credentials-related issue running any `tofu` command, you may need to re-auth. Both will provide you a URL to login via SSO. You can accept all defaults. -Once this is done, run: +#### Dev / Prod split + +To ensure that we can safely develop in a live environment, without breaking existing functionality, we split dev and prod environments using tofu's `workspace`s. + +To select the `prod` environment: + +``` +tofu workspace select default +``` + +To select the `dev` environment: + +``` +tofu workspace select dev +``` + +By doing this, we avoid having to duplicate tofu source files, such that the deployments are more or less identical between environments. + +#### Creating / updating deployments + +Once you've selected a workspace and configured sso for both aws and gcloud: ``` tofu init diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 769f6cf..c8aeb28 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -24,7 +24,14 @@ jobs: project_id: "dse-nps" service_account: "github-actions-service-account@dse-nps.iam.gserviceaccount.com" + - name: Set environment variable for branch name + run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV + - name: Build and Deploy run: | gcloud builds submit --config .deployment/cloudbuild.yml - gcloud beta run deploy tf-rest-burn-severity --image us-central1-docker.pkg.dev/dse-nps/burn-backend/prod --platform managed --region us-central1 + if [ "${{ env.BRANCH_NAME }}" = "master" ]; then + gcloud beta run deploy tf-rest-burn-severity-prod --image us-central1-docker.pkg.dev/dse-nps/burn-backend/prod --platform managed --region us-central1 + else + gcloud beta run deploy tf-rest-burn-severity-dev --image us-central1-docker.pkg.dev/dse-nps/burn-backend/dev --platform managed --region us-central1 + fi From ec8c5be9110aa3bbf716434372e3fe0fd03dd81b Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 27 Feb 2024 02:10:08 +0000 Subject: [PATCH 165/175] temporarily push branch to dev cloud run for testing --- .github/workflows/build_and_deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index c8aeb28..924e46e 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -4,6 +4,7 @@ on: branches: - master - dev + - dev-prod-split jobs: setup-build-deploy: From 9973e9a9059a618f92c41d8b881cf998dc638059 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 27 Feb 2024 02:22:37 +0000 Subject: [PATCH 166/175] fix path to container registry --- .devcontainer/scripts/export_tofu_dotenv.sh | 1 + .github/workflows/build_and_deploy.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.devcontainer/scripts/export_tofu_dotenv.sh b/.devcontainer/scripts/export_tofu_dotenv.sh index c15bfdc..51ab16a 100755 --- a/.devcontainer/scripts/export_tofu_dotenv.sh +++ b/.devcontainer/scripts/export_tofu_dotenv.sh @@ -11,6 +11,7 @@ export gcp_service_account_s3_email=$(tofu output gcp_service_account_s3_email | echo "# TOFU ENV VARS" >> /workspace/.devcontainer/.env echo "ENV=LOCAL" >> /workspace/.devcontainer/.env +echo "DEPLOYMENT=DEV" >> /workspace/.devcontainer/.env echo "S3_FROM_GCP_ROLE_ARN=$s3_from_gcp_role_arn" >> /workspace/.devcontainer/.env echo "GCP_CLOUD_RUN_ENDPOINT=$gcp_cloud_run_endpoint" >> /workspace/.devcontainer/.env echo "GCP_SERVICE_ACCOUNT_S3_EMAIL=$gcp_service_account_s3_email" >> /workspace/.devcontainer/.env \ No newline at end of file diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 924e46e..ee3537f 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -32,7 +32,7 @@ jobs: run: | gcloud builds submit --config .deployment/cloudbuild.yml if [ "${{ env.BRANCH_NAME }}" = "master" ]; then - gcloud beta run deploy tf-rest-burn-severity-prod --image us-central1-docker.pkg.dev/dse-nps/burn-backend/prod --platform managed --region us-central1 + gcloud beta run deploy tf-rest-burn-severity-prod --image us-central1-docker.pkg.dev/dse-nps/burn-backend-prod/prod --platform managed --region us-central1 else - gcloud beta run deploy tf-rest-burn-severity-dev --image us-central1-docker.pkg.dev/dse-nps/burn-backend/dev --platform managed --region us-central1 + gcloud beta run deploy tf-rest-burn-severity-dev --image us-central1-docker.pkg.dev/dse-nps/burn-backend-dev/dev --platform managed --region us-central1 fi From 912c9dffc32b0998e5e7a681b092e75abf1ca06c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 27 Feb 2024 02:35:14 +0000 Subject: [PATCH 167/175] make a diff cloud build yaml for dev and prod --- .deployment/cloudbuild.yml | 16 ---------------- .deployment/prod_cloudbuild.yml | 16 ++++++++++++++++ .devcontainer/dev_cloudbuild.yml | 16 ++++++++++++++++ .github/workflows/build_and_deploy.yml | 3 ++- 4 files changed, 34 insertions(+), 17 deletions(-) delete mode 100644 .deployment/cloudbuild.yml create mode 100644 .deployment/prod_cloudbuild.yml create mode 100644 .devcontainer/dev_cloudbuild.yml diff --git a/.deployment/cloudbuild.yml b/.deployment/cloudbuild.yml deleted file mode 100644 index 9cbc269..0000000 --- a/.deployment/cloudbuild.yml +++ /dev/null @@ -1,16 +0,0 @@ -steps: -- name: 'gcr.io/cloud-builders/docker' - args: - - 'build' - - '--tag=us-central1-docker.pkg.dev/dse-nps/burn-backend/prod:latest' - - '--file=.deployment/prod.Dockerfile' - - '--no-cache' - - '.' - -- name: 'gcr.io/cloud-builders/docker' - args: - - 'push' - - 'us-central1-docker.pkg.dev/dse-nps/burn-backend/prod:latest' - -options: - logging: CLOUD_LOGGING_ONLY \ No newline at end of file diff --git a/.deployment/prod_cloudbuild.yml b/.deployment/prod_cloudbuild.yml new file mode 100644 index 0000000..05deae2 --- /dev/null +++ b/.deployment/prod_cloudbuild.yml @@ -0,0 +1,16 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + args: + - "build" + - "--tag=us-central1-docker.pkg.dev/dse-nps/burn-backend-prod/prod:latest" + - "--file=.deployment/prod.Dockerfile" + - "--no-cache" + - "." + + - name: "gcr.io/cloud-builders/docker" + args: + - "push" + - "us-central1-docker.pkg.dev/dse-nps/burn-backend-prod/prod:latest" + +options: + logging: CLOUD_LOGGING_ONLY diff --git a/.devcontainer/dev_cloudbuild.yml b/.devcontainer/dev_cloudbuild.yml new file mode 100644 index 0000000..2833413 --- /dev/null +++ b/.devcontainer/dev_cloudbuild.yml @@ -0,0 +1,16 @@ +steps: + - name: "gcr.io/cloud-builders/docker" + args: + - "build" + - "--tag=us-central1-docker.pkg.dev/dse-nps/burn-backend-dev/dev:latest" + - "--file=.deployment/prod.Dockerfile" + - "--no-cache" + - "." + + - name: "gcr.io/cloud-builders/docker" + args: + - "push" + - "us-central1-docker.pkg.dev/dse-nps/burn-backend-dev/dev:latest" + +options: + logging: CLOUD_LOGGING_ONLY diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index ee3537f..4ab6945 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -30,9 +30,10 @@ jobs: - name: Build and Deploy run: | - gcloud builds submit --config .deployment/cloudbuild.yml if [ "${{ env.BRANCH_NAME }}" = "master" ]; then + gcloud builds submit --config .deployment/prod_cloudbuild.yml gcloud beta run deploy tf-rest-burn-severity-prod --image us-central1-docker.pkg.dev/dse-nps/burn-backend-prod/prod --platform managed --region us-central1 else + gcloud builds submit --config .devcontainer/dev_cloudbuild.yml gcloud beta run deploy tf-rest-burn-severity-dev --image us-central1-docker.pkg.dev/dse-nps/burn-backend-dev/dev --platform managed --region us-central1 fi From 53ebc2556962fffdf3cf98d5ce8c40e1d1b03ce0 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 27 Feb 2024 03:22:43 +0000 Subject: [PATCH 168/175] add conditional for hardcoded-ish service account unique id. there must be a better way, seems very weird that this cant be a tofu output! --- .deployment/tofu/main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.deployment/tofu/main.tf b/.deployment/tofu/main.tf index 663442d..700088e 100644 --- a/.deployment/tofu/main.tf +++ b/.deployment/tofu/main.tf @@ -36,7 +36,8 @@ locals { aws_region = data.aws_region.current.name # oidc_provider_domain_url = "https://accounts.google.com" oidc_provider_domain_url = "accounts.google.com" - gcp_cloud_run_client_id = "117526146749746854545" ## This is the ClientID of the cloud run instance, and can't be output from terraform! + ## This is the ClientID of the cloud run service account, and can't be output from terraform! + gcp_cloud_run_client_id = "${terraform.workspace}" == "prod" ? "117526146749746854545" : "101023653831248304550" } # Initialize the modules From d39f6feea025172c8b7b770984fc93b2ae33c37a Mon Sep 17 00:00:00 2001 From: nick gondek Date: Tue, 27 Feb 2024 03:32:59 +0000 Subject: [PATCH 169/175] egg on face - remove the dumb conditional and realize that the uuid attribute is from the relevant service account, not the cloud run instance - all is right in the world. --- .deployment/tofu/main.tf | 5 ++--- .deployment/tofu/modules/burn_backend/main.tf | 2 +- .deployment/tofu/modules/burn_backend/outputs.tf | 5 +++++ .deployment/tofu/outputs.tf | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.deployment/tofu/main.tf b/.deployment/tofu/main.tf index 700088e..d94bf29 100644 --- a/.deployment/tofu/main.tf +++ b/.deployment/tofu/main.tf @@ -36,8 +36,7 @@ locals { aws_region = data.aws_region.current.name # oidc_provider_domain_url = "https://accounts.google.com" oidc_provider_domain_url = "accounts.google.com" - ## This is the ClientID of the cloud run service account, and can't be output from terraform! - gcp_cloud_run_client_id = "${terraform.workspace}" == "prod" ? "117526146749746854545" : "101023653831248304550" + # gcp_cloud_run_client_id = "${terraform.workspace}" == "prod" ? "117526146749746854545" : "101023653831248304550" } # Initialize the modules @@ -45,7 +44,7 @@ module "static_io" { source = "./modules/static_io" google_project_number = local.google_project_number gcp_service_account_s3_email = module.burn_backend.gcp_service_account_s3_email - gcp_cloud_run_client_id = local.gcp_cloud_run_client_id + gcp_cloud_run_client_id = module.burn_backend.gcp_burn_backend_service_account_unique_id aws_account_id = local.aws_account_id oidc_provider_domain_url = local.oidc_provider_domain_url } diff --git a/.deployment/tofu/modules/burn_backend/main.tf b/.deployment/tofu/modules/burn_backend/main.tf index 57647d4..c38ac5a 100644 --- a/.deployment/tofu/modules/burn_backend/main.tf +++ b/.deployment/tofu/modules/burn_backend/main.tf @@ -240,7 +240,7 @@ resource "google_service_account" "github_actions" { # Create the IAM service account for the Cloud Run service resource "google_service_account" "burn-backend-service" { account_id = "burn-backend-service-${terraform.workspace}" - display_name = "Cloud Run Service Account for burn backend" + display_name = "Cloud Run Service Account for burn backend - ${terraform.workspace}" description = "This service account is used by the Cloud Run service to access GCP Secrets Manager and authenticate with OIDC for AWS S3 access" project = "dse-nps" } diff --git a/.deployment/tofu/modules/burn_backend/outputs.tf b/.deployment/tofu/modules/burn_backend/outputs.tf index 21b978e..3fa06fe 100644 --- a/.deployment/tofu/modules/burn_backend/outputs.tf +++ b/.deployment/tofu/modules/burn_backend/outputs.tf @@ -12,3 +12,8 @@ output "gcp_service_account_s3_email" { description = "The email of the service account used by the backend service on GCP Cloud Run" value = google_service_account.burn-backend-service.email } + +output "gcp_burn_backend_service_account_unique_id" { + description = "The unique ID of the service account used by the backend service on GCP Cloud Run" + value = google_service_account.burn-backend-service.unique_id +} \ No newline at end of file diff --git a/.deployment/tofu/outputs.tf b/.deployment/tofu/outputs.tf index e225864..6259cc8 100644 --- a/.deployment/tofu/outputs.tf +++ b/.deployment/tofu/outputs.tf @@ -13,7 +13,7 @@ output "s3_from_gcp_role_arn" { value = module.static_io.s3_from_gcp_role_arn } -output "gcp_cloud_run_uuid" { +output "gcp_burn_backend_service_account_unique_id" { description = "The UUID of the Cloud Run burn-backend service" - value = module.burn_backend.burn_backend_server_uuid + value = module.burn_backend.gcp_burn_backend_service_account_unique_id } \ No newline at end of file From e8d9f38bb1ac5b6653771693e6ca4f8fca1f9c7c Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 28 Feb 2024 21:20:25 +0000 Subject: [PATCH 170/175] add bucket name (with dev/prod split) as a tofu output, such that we can use it as an env var to point https to the right bucket --- .deployment/tofu/main.tf | 1 + .deployment/tofu/modules/burn_backend/main.tf | 4 ++++ .deployment/tofu/modules/static_io/outputs.tf | 5 +++++ .deployment/tofu/outputs.tf | 5 +++++ .devcontainer/README.md | 2 +- src/routers/pages/map.py | 17 +++++++++-------- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.deployment/tofu/main.tf b/.deployment/tofu/main.tf index d94bf29..3931046 100644 --- a/.deployment/tofu/main.tf +++ b/.deployment/tofu/main.tf @@ -53,4 +53,5 @@ module "burn_backend" { source = "./modules/burn_backend" google_project_number = local.google_project_number s3_from_gcp_role_arn = module.static_io.s3_from_gcp_role_arn + s3_bucket_name = module.static_io.s3_bucket_name } \ No newline at end of file diff --git a/.deployment/tofu/modules/burn_backend/main.tf b/.deployment/tofu/modules/burn_backend/main.tf index c38ac5a..4222a8b 100644 --- a/.deployment/tofu/modules/burn_backend/main.tf +++ b/.deployment/tofu/modules/burn_backend/main.tf @@ -114,6 +114,10 @@ resource "google_cloud_run_v2_service" "tf-rest-burn-severity" { name = "S3_FROM_GCP_ROLE_ARN" value = var.s3_from_gcp_role_arn } + env { + name = "S3_BUCKET_NAME" + value = var.s3_bucket_name + } ## TODO [#24]: self-referential endpoint, will be solved by refactoring out titiler and/or making fully static env { name = "GCP_CLOUD_RUN_ENDPOINT" diff --git a/.deployment/tofu/modules/static_io/outputs.tf b/.deployment/tofu/modules/static_io/outputs.tf index 1fd13b9..2713ae1 100644 --- a/.deployment/tofu/modules/static_io/outputs.tf +++ b/.deployment/tofu/modules/static_io/outputs.tf @@ -2,3 +2,8 @@ output "s3_from_gcp_role_arn" { value = aws_iam_role.aws_s3_from_gcp.arn description = "The ARN of the IAM role for S3 Access from GCP" } + +output "s3_bucket_name" { + value = aws_s3_bucket.burn-severity-backend.id + description = "The name of the burn-backend bucket (with dev or prod suffix)" +} \ No newline at end of file diff --git a/.deployment/tofu/outputs.tf b/.deployment/tofu/outputs.tf index 6259cc8..fefad4d 100644 --- a/.deployment/tofu/outputs.tf +++ b/.deployment/tofu/outputs.tf @@ -16,4 +16,9 @@ output "s3_from_gcp_role_arn" { output "gcp_burn_backend_service_account_unique_id" { description = "The UUID of the Cloud Run burn-backend service" value = module.burn_backend.gcp_burn_backend_service_account_unique_id +} + +output "s3_bucket_name" { + description = "The name of the S3 bucket" + value = module.static_io.s3_bucket_name } \ No newline at end of file diff --git a/.devcontainer/README.md b/.devcontainer/README.md index e8263db..d5e3573 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -44,7 +44,7 @@ gcloud auth application-default login ``` -_Note_: This SSO auth process must be performed peridically, as the authentication token generated are short-lived (important, as the scope of this auth is broad for provisioning resources and could be used by nefarious actors). So, if you run into an credentials-related issue running any `tofu` command, you may need to re-auth. Both will provide you a URL to login via SSO. You can accept all defaults. +_Note_: This SSO auth process must be performed periodically, as the authentication token generated are short-lived (important, as the scope of this auth is broad for provisioning resources and could be used by nefarious actors). So, if you run into an credentials-related issue running any `tofu` command, you may need to re-auth. Both will provide you a URL to login via SSO. You can accept all defaults. #### Dev / Prod split diff --git a/src/routers/pages/map.py b/src/routers/pages/map.py index 7b53485..8fba13f 100644 --- a/src/routers/pages/map.py +++ b/src/routers/pages/map.py @@ -40,37 +40,38 @@ def serve_map( """ tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") + bucket_name = os.getenv("S3_BUCKET_NAME") # tileserver_endpoint = "http://localhost:5050" ## TODO [#21]: Use Tofu Output to construct hardocded cog and geojson urls (in case we change s3 bucket name) - cog_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" - burn_boundary_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" - ecoclass_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" - severity_obs_geojson_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" + cog_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" + burn_boundary_geojson_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" + ecoclass_geojson_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" + severity_obs_geojson_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" cog_tileserver_url_prefix = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={cog_url}&nodata=-99&return_mask=true" ) - rap_cog_annual_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" + rap_cog_annual_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" rap_tileserver_annual_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_annual_url}&nodata=-99&return_mask=true" ) - rap_cog_perennial_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" + rap_cog_perennial_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" rap_tileserver_perennial_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_perennial_url}&nodata=-99&return_mask=true" ) - rap_cog_shrub_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" + rap_cog_shrub_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" rap_tileserver_shrub_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_shrub_url}&nodata=-99&return_mask=true" ) - rap_cog_tree_url = f"https://burn-severity-backend.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" + rap_cog_tree_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" rap_tileserver_tree_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" From 585405981c827c719d0772c3c7ecff66617393b9 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 28 Feb 2024 21:22:42 +0000 Subject: [PATCH 171/175] add s3_bucket_name as tofu var for burn_backedn --- .deployment/tofu/modules/burn_backend/variables.tf | 5 +++++ .devcontainer/scripts/export_tofu_dotenv.sh | 2 ++ 2 files changed, 7 insertions(+) diff --git a/.deployment/tofu/modules/burn_backend/variables.tf b/.deployment/tofu/modules/burn_backend/variables.tf index cd8caf4..6ef22da 100644 --- a/.deployment/tofu/modules/burn_backend/variables.tf +++ b/.deployment/tofu/modules/burn_backend/variables.tf @@ -6,4 +6,9 @@ variable "google_project_number" { variable "s3_from_gcp_role_arn" { description = "Role ARN to assume to access S3 from GCP" type = string +} + +variable "s3_bucket_name" { + description = "S3 bucket name" + type = string } \ No newline at end of file diff --git a/.devcontainer/scripts/export_tofu_dotenv.sh b/.devcontainer/scripts/export_tofu_dotenv.sh index 51ab16a..22abcbd 100755 --- a/.devcontainer/scripts/export_tofu_dotenv.sh +++ b/.devcontainer/scripts/export_tofu_dotenv.sh @@ -8,10 +8,12 @@ export s3_from_gcp_role_arn="$(tofu output s3_from_gcp_role_arn)" # Remove quotes from the email to avoid issue with the impersonation below export gcp_service_account_s3_email=$(tofu output gcp_service_account_s3_email | tr -d '"') +export s3_bucket_name=$(tofu output s3_bucket_name) echo "# TOFU ENV VARS" >> /workspace/.devcontainer/.env echo "ENV=LOCAL" >> /workspace/.devcontainer/.env echo "DEPLOYMENT=DEV" >> /workspace/.devcontainer/.env echo "S3_FROM_GCP_ROLE_ARN=$s3_from_gcp_role_arn" >> /workspace/.devcontainer/.env +echo "BUCKET_NAME=$s3_bucket_name" >> /workspace/.devcontainer/.env echo "GCP_CLOUD_RUN_ENDPOINT=$gcp_cloud_run_endpoint" >> /workspace/.devcontainer/.env echo "GCP_SERVICE_ACCOUNT_S3_EMAIL=$gcp_service_account_s3_email" >> /workspace/.devcontainer/.env \ No newline at end of file From eb94bf1667b8b3d69e8739ba3843680a3d882c9f Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 28 Feb 2024 22:10:47 +0000 Subject: [PATCH 172/175] change to throughout for consistency - fix tests to reflect --- .devcontainer/scripts/export_tofu_dotenv.sh | 2 +- src/routers/dependencies.py | 3 ++- src/routers/pages/map.py | 18 ++++++++-------- src/util/cloud_static_io.py | 24 +++++++++++---------- tests/unit/util/test_cloud_static_io.py | 24 +++++++++++---------- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/.devcontainer/scripts/export_tofu_dotenv.sh b/.devcontainer/scripts/export_tofu_dotenv.sh index 22abcbd..286a44e 100755 --- a/.devcontainer/scripts/export_tofu_dotenv.sh +++ b/.devcontainer/scripts/export_tofu_dotenv.sh @@ -14,6 +14,6 @@ echo "# TOFU ENV VARS" >> /workspace/.devcontainer/.env echo "ENV=LOCAL" >> /workspace/.devcontainer/.env echo "DEPLOYMENT=DEV" >> /workspace/.devcontainer/.env echo "S3_FROM_GCP_ROLE_ARN=$s3_from_gcp_role_arn" >> /workspace/.devcontainer/.env -echo "BUCKET_NAME=$s3_bucket_name" >> /workspace/.devcontainer/.env +echo "S3_BUCKET_NAME=$s3_bucket_name" >> /workspace/.devcontainer/.env echo "GCP_CLOUD_RUN_ENDPOINT=$gcp_cloud_run_endpoint" >> /workspace/.devcontainer/.env echo "GCP_SERVICE_ACCOUNT_S3_EMAIL=$gcp_service_account_s3_email" >> /workspace/.devcontainer/.env \ No newline at end of file diff --git a/src/routers/dependencies.py b/src/routers/dependencies.py index e91e629..cf56140 100644 --- a/src/routers/dependencies.py +++ b/src/routers/dependencies.py @@ -33,7 +33,8 @@ def get_cloud_static_io_client(logger: Logger = Depends(get_cloud_logger)): CloudStaticIOClient: An instance of CloudStaticIOClient. """ logger.log_text("Creating CloudStaticIOClient") - return CloudStaticIOClient("burn-severity-backend", "s3") + s3_bucket_name = os.getenv("S3_BUCKET_NAME") + return CloudStaticIOClient(s3_bucket_name, "s3") def get_manifest( diff --git a/src/routers/pages/map.py b/src/routers/pages/map.py index 8fba13f..fb44eff 100644 --- a/src/routers/pages/map.py +++ b/src/routers/pages/map.py @@ -40,38 +40,38 @@ def serve_map( """ tileserver_endpoint = os.getenv("GCP_CLOUD_RUN_ENDPOINT") - bucket_name = os.getenv("S3_BUCKET_NAME") + s3_bucket_name = os.getenv("S3_BUCKET_NAME") # tileserver_endpoint = "http://localhost:5050" ## TODO [#21]: Use Tofu Output to construct hardocded cog and geojson urls (in case we change s3 bucket name) - cog_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" - burn_boundary_geojson_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" - ecoclass_geojson_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" - severity_obs_geojson_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" + cog_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/{burn_metric}.tif" + burn_boundary_geojson_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/boundary.geojson" + ecoclass_geojson_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/ecoclass_dominant_cover.geojson" + severity_obs_geojson_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/burn_field_observations.geojson" cog_tileserver_url_prefix = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={cog_url}&nodata=-99&return_mask=true" ) - rap_cog_annual_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" + rap_cog_annual_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_annual_forb_and_grass.tif" rap_tileserver_annual_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_annual_url}&nodata=-99&return_mask=true" ) - rap_cog_perennial_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" + rap_cog_perennial_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_perennial_forb_and_grass.tif" rap_tileserver_perennial_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_perennial_url}&nodata=-99&return_mask=true" ) - rap_cog_shrub_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" + rap_cog_shrub_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_shrub.tif" rap_tileserver_shrub_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_shrub_url}&nodata=-99&return_mask=true" ) - rap_cog_tree_url = f"https://{bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" + rap_cog_tree_url = f"https://{s3_bucket_name}.s3.us-east-2.amazonaws.com/public/{affiliation}/{fire_event_name}/rangeland_analysis_platform_tree.tif" rap_tileserver_tree_url = ( tileserver_endpoint + f"/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?url={rap_cog_tree_url}&nodata=-99&return_mask=true" diff --git a/src/util/cloud_static_io.py b/src/util/cloud_static_io.py index 077ae4a..21eebdc 100644 --- a/src/util/cloud_static_io.py +++ b/src/util/cloud_static_io.py @@ -17,8 +17,7 @@ from google.oauth2 import id_token from google.auth import impersonated_credentials, exceptions -## TODO [#26]: Make bucket https a tofu output -BUCKET_HTTPS_PREFIX = "https://burn-severity-backend.s3.us-east-2.amazonaws.com" +BUCKET_HTTPS_PREFIX = "https://{s3_bucket_name}.s3.us-east-2.amazonaws.com" class CloudStaticIOClient: @@ -52,14 +51,15 @@ class CloudStaticIOClient: """ - def __init__(self, bucket_name, provider): + def __init__(self, s3_bucket_name, provider): self.env = os.environ.get("ENV") self.role_arn = os.environ.get("S3_FROM_GCP_ROLE_ARN") self.service_account_email = os.environ.get("GCP_SERVICE_ACCOUNT_S3_EMAIL") self.role_session_name = "burn-backend-session" - self.bucket_name = bucket_name + self.s3_bucket_name = s3_bucket_name + self.https_prefix = BUCKET_HTTPS_PREFIX.format(s3_bucket_name=s3_bucket_name) # Set up logging logging_client = cloud_logging.Client(project="dse-nps") @@ -69,7 +69,7 @@ def __init__(self, bucket_name, provider): self.sts_client = boto3.client("sts") if provider == "s3": - self.prefix = f"s3://{self.bucket_name}" + self.s3_prefix = f"s3://{self.s3_bucket_name}" else: raise Exception(f"Provider {provider} not supported") @@ -78,7 +78,7 @@ def __init__(self, bucket_name, provider): self.validate_credentials() self.logger.log_text( - f"Initialized CloudStaticIOClient for {self.bucket_name} with provider {provider}" + f"Initialized CloudStaticIOClient for {self.s3_bucket_name} with provider {provider}" ) def impersonate_service_account(self): @@ -211,7 +211,7 @@ def download(self, remote_path, target_local_path): # Download from remote s3 server to local with smart_open.open( - f"{self.prefix}/{remote_path}", + f"{self.s3_prefix}/{remote_path}", "rb", transport_params={"client": self.boto_session.client("s3")}, ) as remote_file: @@ -241,7 +241,7 @@ def upload(self, source_local_path, remote_path): # Upload file from local to S3 with open(source_local_path, "rb") as local_file: with smart_open.open( - f"{self.prefix}/{remote_path}", + f"{self.s3_prefix}/{remote_path}", "wb", transport_params={"client": self.boto_session.client("s3")}, ) as remote_file: @@ -461,10 +461,12 @@ def get_derived_products(self, affiliation, fire_event_name): s3_client = self.boto_session.client("s3") paginator = s3_client.get_paginator("list_objects_v2") derived_products = {} - bucket_prefix = f"public/{affiliation}/{fire_event_name}/" - for page in paginator.paginate(Bucket=self.bucket_name, Prefix=bucket_prefix): + intra_bucket_prefix = f"public/{affiliation}/{fire_event_name}/" + for page in paginator.paginate( + Bucket=self.s3_bucket_name, Prefix=intra_bucket_prefix + ): for obj in page["Contents"]: - full_https_url = BUCKET_HTTPS_PREFIX + "/" + obj["Key"] + full_https_url = self.https_prefix + "/" + obj["Key"] filename = os.path.basename(obj["Key"]) derived_products[filename] = full_https_url return derived_products diff --git a/tests/unit/util/test_cloud_static_io.py b/tests/unit/util/test_cloud_static_io.py index 0b11537..3be504f 100644 --- a/tests/unit/util/test_cloud_static_io.py +++ b/tests/unit/util/test_cloud_static_io.py @@ -241,7 +241,7 @@ def test_get_derived_products(mock_init): # Define test affiliation and fire event name affiliation = "test_affiliation" fire_event_name = "test_event" - + s3_bucket_name = "test_bucket" # Define test pages test_pages = [ { @@ -256,14 +256,6 @@ def test_get_derived_products(mock_init): }, ] - # Define expected derived products - expected_derived_products = { - "test_file1.tif": BUCKET_HTTPS_PREFIX - + "/public/test_affiliation/test_event/test_file1.tif", - "test_file2.tif": BUCKET_HTTPS_PREFIX - + "/public/test_affiliation/test_event/test_file2.tif", - } - # Mock s3 client and paginator mock_s3_client = MagicMock() mock_paginator = MagicMock() @@ -276,7 +268,10 @@ def test_get_derived_products(mock_init): # Create an instance of CloudStaticIOClient, and give it an s3 client client = CloudStaticIOClient() - client.bucket_name = "bucket_name" + client.s3_bucket_name = s3_bucket_name + client.https_prefix = BUCKET_HTTPS_PREFIX.format( + s3_bucket_name=client.s3_bucket_name + ) client.boto_session = mock_boto_session # Call get_derived_products @@ -290,8 +285,15 @@ def test_get_derived_products(mock_init): # Assert that paginate was called with the correct Bucket and Prefix mock_paginator.paginate.assert_called_once_with( - Bucket=client.bucket_name, Prefix=f"public/{affiliation}/{fire_event_name}/" + Bucket=client.s3_bucket_name, Prefix=f"public/{affiliation}/{fire_event_name}/" ) # Assert that the returned derived products match the expected derived products + expected_derived_products = { + "test_file1.tif": client.https_prefix + + "/public/test_affiliation/test_event/test_file1.tif", + "test_file2.tif": client.https_prefix + + "/public/test_affiliation/test_event/test_file2.tif", + } + assert derived_products == expected_derived_products From d48b74d270471223b1634a2c0a66309c547177c2 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 28 Feb 2024 22:37:57 +0000 Subject: [PATCH 173/175] one last change from to - in log --- src/util/cloud_static_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/cloud_static_io.py b/src/util/cloud_static_io.py index 21eebdc..9dbc48d 100644 --- a/src/util/cloud_static_io.py +++ b/src/util/cloud_static_io.py @@ -235,7 +235,7 @@ def upload(self, source_local_path, remote_path): self.validate_credentials() try: print( - f"uploading to {self.bucket_name} [(remote path: {remote_path});(source local path: {source_local_path})]" + f"uploading to {self.s3_bucket_name} [(remote path: {remote_path});(source local path: {source_local_path})]" ) # Upload file from local to S3 From 6e89ea8d911e2d408eeaa892ca24056732a8b207 Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 28 Feb 2024 23:08:42 +0000 Subject: [PATCH 174/175] update access policy for s3_from_gcp role, to reflect new bucket name using var instead of hardoce --- .deployment/tofu/modules/burn_backend/main.tf | 2 +- .deployment/tofu/modules/static_io/main.tf | 127 +----------------- src/routers/upload/shapefile_zip.py | 2 +- src/util/cloud_static_io.py | 2 +- 4 files changed, 4 insertions(+), 129 deletions(-) diff --git a/.deployment/tofu/modules/burn_backend/main.tf b/.deployment/tofu/modules/burn_backend/main.tf index 4222a8b..4e0853d 100644 --- a/.deployment/tofu/modules/burn_backend/main.tf +++ b/.deployment/tofu/modules/burn_backend/main.tf @@ -121,7 +121,7 @@ resource "google_cloud_run_v2_service" "tf-rest-burn-severity" { ## TODO [#24]: self-referential endpoint, will be solved by refactoring out titiler and/or making fully static env { name = "GCP_CLOUD_RUN_ENDPOINT" - value = "https://tf-rest-burn-severity-ohi6r6qs2a-uc.a.run.app" + value = "${terraform.workspace}" == "prod" ? "https://tf-rest-burn-severity-ohi6r6qs2a-uc.a.run.app" : "https://tf-rest-burn-severity-dev-ohi6r6qs2a-uc.a.run.appz" } env { name = "CPL_VSIL_CURL_ALLOWED_EXTENSIONS" diff --git a/.deployment/tofu/modules/static_io/main.tf b/.deployment/tofu/modules/static_io/main.tf index 98dbe56..b60cd11 100644 --- a/.deployment/tofu/modules/static_io/main.tf +++ b/.deployment/tofu/modules/static_io/main.tf @@ -87,131 +87,6 @@ resource "aws_s3_bucket_object" "assets" { source = "../assets/${each.value}" } -# Then, the user for the server, allowing it access to Transfer Family - -# data "aws_iam_policy_document" "assume_role" { -# statement { -# effect = "Allow" - -# principals { -# type = "Service" -# identifiers = ["transfer.amazonaws.com"] -# } - -# actions = ["sts:AssumeRole"] -# } -# } - -# resource "aws_iam_role" "admin" { -# name = "tf-sftp-admin-iam-role" -# assume_role_policy = data.aws_iam_policy_document.assume_role.json -# } - -# data "aws_iam_policy_document" "s3_policy" { -# statement { -# sid = "ReadWriteS3" -# effect = "Allow" -# actions = [ -# "s3:ListBucket", -# ] -# resources = [ -# "arn:aws:s3:::burn-severity-backend", -# ] -# } - -# statement { -# effect = "Allow" -# actions = [ -# "s3:PutObject", -# "s3:GetObject", -# "s3:GetObjectTagging", -# "s3:DeleteObject", -# "s3:DeleteObjectVersion", -# "s3:GetObjectVersion", -# "s3:GetObjectVersionTagging", -# "s3:GetObjectACL", -# "s3:PutObjectACL", -# ] -# resources = [ -# "arn:aws:s3:::burn-severity-backend/*", -# ] -# } -# } - -# # Create the s3_policy -# resource "aws_iam_policy" "s3_admin_policy" { -# name = "s3_admin_policy" -# description = "S3 policy for admin user" -# policy = data.aws_iam_policy_document.s3_policy.json -# } - -# # Attach the policy to the role -# resource "aws_iam_role_policy_attachment" "s3_policy_attachment" { -# role = aws_iam_role.admin.name -# policy_arn = aws_iam_policy.s3_admin_policy.arn -# } - -# # Add the necessary session policy to the user -# data "aws_iam_policy_document" "session_policy" { -# statement { -# sid = "AllowListingOfUserFolder" -# effect = "Allow" -# actions = [ -# "s3:ListBucket", -# ] -# resources = [ -# "arn:aws:s3:::burn-severity-backend", -# ] -# condition { -# test = "StringLike" -# variable = "s3:prefix" -# values = [ -# "/public/*", -# "/public", -# "/" -# ] -# } -# } - -# statement { -# sid = "HomeDirObjectAccess" -# effect = "Allow" -# actions = [ -# "s3:PutObject", -# "s3:GetObject", -# "s3:DeleteObject", -# "s3:GetObjectVersion", -# ] -# resources = [ -# "arn:aws:s3:::burn-severity-backend/*", -# ] -# } -# } - -# # Finally, create the user within Transfer Family -# resource "aws_transfer_user" "tf-sftp-burn-severity" { -# server_id = aws_transfer_server.tf-sftp-burn-severity.id -# user_name = "admin" -# role = aws_iam_role.admin.arn -# home_directory_mappings { -# entry = "/" -# target = "/burn-severity-backend/public" -# } -# home_directory_type = "LOGICAL" -# policy = data.aws_iam_policy_document.session_policy.json -# } - -# resource "aws_transfer_ssh_key" "sftp_ssh_key_public" { -# depends_on = [aws_transfer_user.tf-sftp-burn-severity] -# server_id = aws_transfer_server.tf-sftp-burn-severity.id -# user_name = "admin" -# body = var.ssh_pairs["SSH_KEY_ADMIN_PUBLIC"] -# } - - -## TODO [#4]: This is OIDC stuff, which is not yet working -# Set up STS to allow the GCP server to assume a role for AWS secrets - # Defines who can assume the role. # Confusing string mapping for the OIDC provider URL (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#ck_aud) # example paylod of our token looks like:/ @@ -282,7 +157,7 @@ data "aws_iam_policy_document" "session_policy" { "s3:GetObjectVersion", ] resources = [ - "arn:aws:s3:::burn-severity-backend/*", + "arn:aws:s3:::${aws_s3_bucket.burn-severity-backend.id}/*", ] } } diff --git a/src/routers/upload/shapefile_zip.py b/src/routers/upload/shapefile_zip.py index 33c1f75..3952262 100644 --- a/src/routers/upload/shapefile_zip.py +++ b/src/routers/upload/shapefile_zip.py @@ -66,7 +66,7 @@ async def upload_shapefile( __shp_paths, geojson = valid_shp[0] user_uploaded_s3_path = ( - "public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}" + f"public/{affiliation}/{fire_event_name}/user_uploaded_{file.filename}" ) # Upload the zip and a geojson to s3 cloud_static_io_client.upload( diff --git a/src/util/cloud_static_io.py b/src/util/cloud_static_io.py index 9dbc48d..7f22019 100644 --- a/src/util/cloud_static_io.py +++ b/src/util/cloud_static_io.py @@ -90,7 +90,7 @@ def impersonate_service_account(self): None """ # Load the credentials of the user - source_credentials, project = google.auth.default() + source_credentials, __project = google.auth.default() # Define the scopes of the impersonated credentials target_scopes = ["https://www.googleapis.com/auth/cloud-platform"] From 20deadb2eb1a0cdd9e3dee46f9a13d5c9b57a15a Mon Sep 17 00:00:00 2001 From: nick gondek Date: Wed, 28 Feb 2024 23:18:22 +0000 Subject: [PATCH 175/175] disable workflow for this branch --- .github/workflows/build_and_deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 4ab6945..1f7e169 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -4,7 +4,6 @@ on: branches: - master - dev - - dev-prod-split jobs: setup-build-deploy:

  • dse-fire-recovery