Spaces:
Build error
Build error
Added coefficients/normalizations. Added rounding.
Browse files- app.py +14 -3
- metrics/OE_Biodiversity.yaml +109 -0
- indices.yaml → unused_metrics/OE_Biodiversity_20230723.yaml +44 -2
- utils/duckdb_queries.py +9 -3
- utils/indicators.py +41 -20
app.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import gradio as gr
|
2 |
|
3 |
from utils import duckdb_queries as dq
|
@@ -5,13 +6,18 @@ from utils.gradio import get_window_url_params
|
|
5 |
from utils.indicators import IndexGenerator
|
6 |
|
7 |
# Instantiate outside gradio app to avoid re-initializing GEE, which is slow
|
8 |
-
indexgenerator = IndexGenerator(
|
|
|
|
|
|
|
|
|
9 |
|
10 |
with gr.Blocks() as demo:
|
11 |
with gr.Column():
|
12 |
m1 = gr.Plot()
|
13 |
with gr.Row():
|
14 |
project_name = gr.Dropdown([], label="Project", value="Select project")
|
|
|
15 |
start_year = gr.Number(value=2017, label="Start Year", precision=0)
|
16 |
end_year = gr.Number(value=2022, label="End Year", precision=0)
|
17 |
with gr.Row():
|
@@ -37,8 +43,7 @@ with gr.Blocks() as demo:
|
|
37 |
projects = dq.list_projects_by_author(author_id=username)
|
38 |
# Initialize the first project in the list
|
39 |
project_names = projects['name'].tolist()
|
40 |
-
|
41 |
-
return gr.Dropdown.update(choices=project_names, value=default_project)
|
42 |
|
43 |
# Change the project name in the index generator object when the
|
44 |
# user selects a new project
|
@@ -47,6 +52,12 @@ with gr.Blocks() as demo:
|
|
47 |
inputs=project_name
|
48 |
)
|
49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
# Get url params
|
51 |
url_params = gr.JSON({"username": "default"}, visible=False, label="URL Params")
|
52 |
|
|
|
1 |
+
import os
|
2 |
import gradio as gr
|
3 |
|
4 |
from utils import duckdb_queries as dq
|
|
|
6 |
from utils.indicators import IndexGenerator
|
7 |
|
8 |
# Instantiate outside gradio app to avoid re-initializing GEE, which is slow
|
9 |
+
indexgenerator = IndexGenerator()
|
10 |
+
|
11 |
+
metric_names = os.listdir('metrics')
|
12 |
+
for i in range(len(metric_names)):
|
13 |
+
metric_names[i] = metric_names[i].split('.yaml')[0].replace('_', ' ')
|
14 |
|
15 |
with gr.Blocks() as demo:
|
16 |
with gr.Column():
|
17 |
m1 = gr.Plot()
|
18 |
with gr.Row():
|
19 |
project_name = gr.Dropdown([], label="Project", value="Select project")
|
20 |
+
metric = gr.Dropdown(metric_names, label='Metric', value='Select metric')
|
21 |
start_year = gr.Number(value=2017, label="Start Year", precision=0)
|
22 |
end_year = gr.Number(value=2022, label="End Year", precision=0)
|
23 |
with gr.Row():
|
|
|
43 |
projects = dq.list_projects_by_author(author_id=username)
|
44 |
# Initialize the first project in the list
|
45 |
project_names = projects['name'].tolist()
|
46 |
+
return gr.Dropdown.update(choices=project_names)
|
|
|
47 |
|
48 |
# Change the project name in the index generator object when the
|
49 |
# user selects a new project
|
|
|
52 |
inputs=project_name
|
53 |
)
|
54 |
|
55 |
+
# Set the metric to be calculated
|
56 |
+
metric.change(
|
57 |
+
indexgenerator.set_metric,
|
58 |
+
inputs=metric
|
59 |
+
)
|
60 |
+
|
61 |
# Get url params
|
62 |
url_params = gr.JSON({"username": "default"}, visible=False, label="URL Params")
|
63 |
|
metrics/OE_Biodiversity.yaml
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
Protected:
|
3 |
+
name: Protected
|
4 |
+
description: The total PA extent, including both marine (if applicable) and terrestrial areas provided by data provider as specified in the legal text for the site.
|
5 |
+
unit: km^2
|
6 |
+
min: 0
|
7 |
+
max: roi_area
|
8 |
+
roi: ''
|
9 |
+
gee_path: WCMC/WDPA/current/polygons
|
10 |
+
gee_type: feature_collection
|
11 |
+
viz:
|
12 |
+
palette:
|
13 |
+
- 2ed033
|
14 |
+
- 5aff05
|
15 |
+
- 67b9ff
|
16 |
+
- 5844ff
|
17 |
+
- 0a7618
|
18 |
+
- 2c05ff
|
19 |
+
min: 0
|
20 |
+
max: 1550000
|
21 |
+
opacity: 0.8
|
22 |
+
select: REP_AREA
|
23 |
+
bandname: constant
|
24 |
+
coefficient: 1
|
25 |
+
show: true
|
26 |
+
Soil:
|
27 |
+
name: Soil
|
28 |
+
description: An estimate of soil organic carbon content at b{n} cm depth.
|
29 |
+
unit: 5g/kg
|
30 |
+
min: 0
|
31 |
+
max: 120
|
32 |
+
roi: ''
|
33 |
+
gee_path: OpenLandMap/SOL/SOL_ORGANIC-CARBON_USDA-6A1C_M/v02
|
34 |
+
gee_type: image
|
35 |
+
viz:
|
36 |
+
bands:
|
37 |
+
- b0
|
38 |
+
min: 0
|
39 |
+
max: 12
|
40 |
+
palette:
|
41 |
+
- ffffa0
|
42 |
+
- f7fcb9
|
43 |
+
- d9f0a3
|
44 |
+
- addd8e
|
45 |
+
- 78c679
|
46 |
+
- 41ab5d
|
47 |
+
- '238443'
|
48 |
+
- 005b29
|
49 |
+
- 004b29
|
50 |
+
- 012b13
|
51 |
+
- 00120b
|
52 |
+
select: b0
|
53 |
+
bandname: b0
|
54 |
+
coefficient: 1
|
55 |
+
show: false
|
56 |
+
NDVI:
|
57 |
+
name: NDVI
|
58 |
+
description: Normalized difference vegetation index
|
59 |
+
unit: index (continuous)
|
60 |
+
min: -1
|
61 |
+
max: 1
|
62 |
+
roi: ''
|
63 |
+
gee_path: LANDSAT/LC08/C02/T1
|
64 |
+
gee_type: algebraic
|
65 |
+
normalized_difference:
|
66 |
+
- B4
|
67 |
+
- B3
|
68 |
+
viz:
|
69 |
+
min: -1
|
70 |
+
max: 1
|
71 |
+
palette:
|
72 |
+
- "#d73027"
|
73 |
+
- "#f46d43"
|
74 |
+
- "#fdae61"
|
75 |
+
- "#fee08b"
|
76 |
+
- "#d9ef8b"
|
77 |
+
- "#a6d96a"
|
78 |
+
- "#66bd63"
|
79 |
+
- "#1a9850"
|
80 |
+
bandname: nd
|
81 |
+
coefficient: 1
|
82 |
+
show: true
|
83 |
+
NDWI:
|
84 |
+
name: NDWI
|
85 |
+
description: An estimate of the water content of leaves.
|
86 |
+
unit: index (continuous)
|
87 |
+
min: -1
|
88 |
+
max: 1
|
89 |
+
roi: ''
|
90 |
+
gee_path: LANDSAT/LC08/C02/T1
|
91 |
+
gee_type: algebraic
|
92 |
+
normalized_difference:
|
93 |
+
- B5
|
94 |
+
- B6
|
95 |
+
viz:
|
96 |
+
min: -1
|
97 |
+
max: 1
|
98 |
+
palette:
|
99 |
+
- "#ece7f2"
|
100 |
+
- "#d0d1e6"
|
101 |
+
- "#a6bddb"
|
102 |
+
- "#74a9cf"
|
103 |
+
- "#3690c0"
|
104 |
+
- "#0570b0"
|
105 |
+
- "#045a8d"
|
106 |
+
- "#023858"
|
107 |
+
bandname: nd
|
108 |
+
coefficient: 1
|
109 |
+
show: true
|
indices.yaml → unused_metrics/OE_Biodiversity_20230723.yaml
RENAMED
@@ -1,6 +1,10 @@
|
|
1 |
---
|
2 |
Water:
|
3 |
name: Water
|
|
|
|
|
|
|
|
|
4 |
roi: ''
|
5 |
gee_path: JRC/GSW1_1/GlobalSurfaceWater
|
6 |
gee_type: image
|
@@ -13,9 +17,14 @@ Water:
|
|
13 |
- 0000ff
|
14 |
bandname: occurrence
|
15 |
select: occurrence
|
|
|
16 |
show: true
|
17 |
Protected:
|
18 |
name: Protected
|
|
|
|
|
|
|
|
|
19 |
roi: ''
|
20 |
gee_path: WCMC/WDPA/current/polygons
|
21 |
gee_type: feature_collection
|
@@ -32,9 +41,14 @@ Protected:
|
|
32 |
opacity: 0.8
|
33 |
select: REP_AREA
|
34 |
bandname: constant
|
|
|
35 |
show: true
|
36 |
Air:
|
37 |
name: Air
|
|
|
|
|
|
|
|
|
38 |
roi: ''
|
39 |
gee_path: COPERNICUS/S5P/OFFL/L3_AER_AI
|
40 |
gee_type: image_collection
|
@@ -52,15 +66,20 @@ Air:
|
|
52 |
bandname: absorbing_aerosol_index
|
53 |
select: absorbing_aerosol_index
|
54 |
dates: false
|
|
|
55 |
show: false
|
56 |
Soil:
|
57 |
name: Soil
|
|
|
|
|
|
|
|
|
58 |
roi: ''
|
59 |
gee_path: OpenLandMap/SOL/SOL_ORGANIC-CARBON_USDA-6A1C_M/v02
|
60 |
gee_type: image
|
61 |
viz:
|
62 |
bands:
|
63 |
-
-
|
64 |
min: 0
|
65 |
max: 12
|
66 |
palette:
|
@@ -77,9 +96,14 @@ Soil:
|
|
77 |
- 00120b
|
78 |
select: b0
|
79 |
bandname: b0
|
|
|
80 |
show: false
|
81 |
Temperature:
|
82 |
name: Temperature
|
|
|
|
|
|
|
|
|
83 |
roi: ''
|
84 |
gee_path: MODIS/061/MYD21C1
|
85 |
gee_type: image_collection
|
@@ -119,17 +143,24 @@ Temperature:
|
|
119 |
select: LST_Day
|
120 |
bandname: LST_Day
|
121 |
dates: true
|
|
|
122 |
show: true
|
123 |
Habitat:
|
124 |
name: Habitat
|
|
|
125 |
roi: ''
|
126 |
gee_path: projects/sat-io/open-datasets/IUCN_HABITAT/iucn_habitatclassification_composite_lvl2_ver004
|
127 |
gee_type: image
|
128 |
viz: {}
|
129 |
bandname: comp_first
|
|
|
130 |
show: true
|
131 |
NDVI:
|
132 |
name: NDVI
|
|
|
|
|
|
|
|
|
133 |
roi: ''
|
134 |
gee_path: LANDSAT/LC08/C02/T1
|
135 |
gee_type: algebraic
|
@@ -137,6 +168,8 @@ NDVI:
|
|
137 |
- B4
|
138 |
- B3
|
139 |
viz:
|
|
|
|
|
140 |
palette:
|
141 |
- "#d73027"
|
142 |
- "#f46d43"
|
@@ -147,15 +180,23 @@ NDVI:
|
|
147 |
- "#66bd63"
|
148 |
- "#1a9850"
|
149 |
bandname: nd
|
|
|
|
|
150 |
NDWI:
|
151 |
name: NDWI
|
|
|
|
|
|
|
|
|
152 |
roi: ''
|
153 |
gee_path: LANDSAT/LC08/C02/T1
|
154 |
gee_type: algebraic
|
155 |
normalized_difference:
|
156 |
-
- B3
|
157 |
- B5
|
|
|
158 |
viz:
|
|
|
|
|
159 |
palette:
|
160 |
- "#ece7f2"
|
161 |
- "#d0d1e6"
|
@@ -166,4 +207,5 @@ NDWI:
|
|
166 |
- "#045a8d"
|
167 |
- "#023858"
|
168 |
bandname: nd
|
|
|
169 |
show: true
|
|
|
1 |
---
|
2 |
Water:
|
3 |
name: Water
|
4 |
+
description: The percent of a given area covered with water.
|
5 |
+
unit: percentage
|
6 |
+
min: 0
|
7 |
+
max: 100
|
8 |
roi: ''
|
9 |
gee_path: JRC/GSW1_1/GlobalSurfaceWater
|
10 |
gee_type: image
|
|
|
17 |
- 0000ff
|
18 |
bandname: occurrence
|
19 |
select: occurrence
|
20 |
+
coefficient: 1
|
21 |
show: true
|
22 |
Protected:
|
23 |
name: Protected
|
24 |
+
description: The total PA extent, including both marine (if applicable) and terrestrial areas provided by data provider as specified in the legal text for the site.
|
25 |
+
unit: km^2
|
26 |
+
min: 0
|
27 |
+
max: roi_area
|
28 |
roi: ''
|
29 |
gee_path: WCMC/WDPA/current/polygons
|
30 |
gee_type: feature_collection
|
|
|
41 |
opacity: 0.8
|
42 |
select: REP_AREA
|
43 |
bandname: constant
|
44 |
+
coefficient: 1
|
45 |
show: true
|
46 |
Air:
|
47 |
name: Air
|
48 |
+
description: A measure of the prevalence of aerosols in the atmosphere.
|
49 |
+
unit: index (continuous)
|
50 |
+
min: -21
|
51 |
+
max: 39
|
52 |
roi: ''
|
53 |
gee_path: COPERNICUS/S5P/OFFL/L3_AER_AI
|
54 |
gee_type: image_collection
|
|
|
66 |
bandname: absorbing_aerosol_index
|
67 |
select: absorbing_aerosol_index
|
68 |
dates: false
|
69 |
+
coefficient: 1
|
70 |
show: false
|
71 |
Soil:
|
72 |
name: Soil
|
73 |
+
description: An estimate of soil organic carbon content at b{n} cm depth.
|
74 |
+
unit: 5g/kg
|
75 |
+
min: 0
|
76 |
+
max: 120
|
77 |
roi: ''
|
78 |
gee_path: OpenLandMap/SOL/SOL_ORGANIC-CARBON_USDA-6A1C_M/v02
|
79 |
gee_type: image
|
80 |
viz:
|
81 |
bands:
|
82 |
+
- b0
|
83 |
min: 0
|
84 |
max: 12
|
85 |
palette:
|
|
|
96 |
- 00120b
|
97 |
select: b0
|
98 |
bandname: b0
|
99 |
+
coefficient: 1
|
100 |
show: false
|
101 |
Temperature:
|
102 |
name: Temperature
|
103 |
+
description: Average Daytime Land Surface Temperature
|
104 |
+
unit: 0.02 K
|
105 |
+
min: 7500
|
106 |
+
max: 65535
|
107 |
roi: ''
|
108 |
gee_path: MODIS/061/MYD21C1
|
109 |
gee_type: image_collection
|
|
|
143 |
select: LST_Day
|
144 |
bandname: LST_Day
|
145 |
dates: true
|
146 |
+
coefficient: 1
|
147 |
show: true
|
148 |
Habitat:
|
149 |
name: Habitat
|
150 |
+
unit: Classification index (categorical)
|
151 |
roi: ''
|
152 |
gee_path: projects/sat-io/open-datasets/IUCN_HABITAT/iucn_habitatclassification_composite_lvl2_ver004
|
153 |
gee_type: image
|
154 |
viz: {}
|
155 |
bandname: comp_first
|
156 |
+
coefficient: 1
|
157 |
show: true
|
158 |
NDVI:
|
159 |
name: NDVI
|
160 |
+
description: Normalized difference vegetation index
|
161 |
+
unit: index (continuous)
|
162 |
+
min: -1
|
163 |
+
max: 1
|
164 |
roi: ''
|
165 |
gee_path: LANDSAT/LC08/C02/T1
|
166 |
gee_type: algebraic
|
|
|
168 |
- B4
|
169 |
- B3
|
170 |
viz:
|
171 |
+
min: -1
|
172 |
+
max: 1
|
173 |
palette:
|
174 |
- "#d73027"
|
175 |
- "#f46d43"
|
|
|
180 |
- "#66bd63"
|
181 |
- "#1a9850"
|
182 |
bandname: nd
|
183 |
+
coefficient: 1
|
184 |
+
show: true
|
185 |
NDWI:
|
186 |
name: NDWI
|
187 |
+
description: An estimate of the water content of leaves.
|
188 |
+
unit: index (continuous)
|
189 |
+
min: -1
|
190 |
+
max: 1
|
191 |
roi: ''
|
192 |
gee_path: LANDSAT/LC08/C02/T1
|
193 |
gee_type: algebraic
|
194 |
normalized_difference:
|
|
|
195 |
- B5
|
196 |
+
- B6
|
197 |
viz:
|
198 |
+
min: -1
|
199 |
+
max: 1
|
200 |
palette:
|
201 |
- "#ece7f2"
|
202 |
- "#d0d1e6"
|
|
|
207 |
- "#045a8d"
|
208 |
- "#023858"
|
209 |
bandname: nd
|
210 |
+
coefficient: 1
|
211 |
show: true
|
utils/duckdb_queries.py
CHANGED
@@ -46,6 +46,12 @@ def get_project_scores(project_name, start_year, end_year):
|
|
46 |
).df()
|
47 |
|
48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
def check_if_project_exists_for_year(project_name, year):
|
50 |
return con.execute(
|
51 |
"SELECT COUNT(1) FROM bioindicator WHERE (year = ? AND project_name = ?)",
|
@@ -55,7 +61,7 @@ def check_if_project_exists_for_year(project_name, year):
|
|
55 |
|
56 |
def write_score_to_temptable(df):
|
57 |
con.sql(
|
58 |
-
"CREATE OR REPLACE TABLE _temptable AS SELECT *,
|
59 |
)
|
60 |
return True
|
61 |
|
@@ -64,7 +70,7 @@ def get_or_create_bioindicator_table():
|
|
64 |
con.sql(
|
65 |
"""
|
66 |
USE climatebase;
|
67 |
-
CREATE TABLE IF NOT EXISTS bioindicator (year BIGINT, project_name VARCHAR(255), value DOUBLE, area DOUBLE, score DOUBLE, CONSTRAINT
|
68 |
"""
|
69 |
)
|
70 |
return True
|
@@ -74,7 +80,7 @@ def upsert_project_record():
|
|
74 |
con.sql(
|
75 |
"""
|
76 |
INSERT INTO bioindicator FROM _temptable
|
77 |
-
ON CONFLICT (year, project_name) DO UPDATE SET value = excluded.value;
|
78 |
"""
|
79 |
)
|
80 |
return True
|
|
|
46 |
).df()
|
47 |
|
48 |
|
49 |
+
def check_if_table_exists(table_name):
|
50 |
+
tables = con.execute("SHOW TABLES;").fetchall()
|
51 |
+
for i in range(len(tables)):
|
52 |
+
tables[i] = tables[i][0]
|
53 |
+
return table_name in tables
|
54 |
+
|
55 |
def check_if_project_exists_for_year(project_name, year):
|
56 |
return con.execute(
|
57 |
"SELECT COUNT(1) FROM bioindicator WHERE (year = ? AND project_name = ?)",
|
|
|
61 |
|
62 |
def write_score_to_temptable(df):
|
63 |
con.sql(
|
64 |
+
"CREATE OR REPLACE TABLE _temptable AS SELECT *, (value * area) AS score FROM (SELECT year, project_name, metric, AVG(value * coefficient) AS value, area FROM df GROUP BY year, project_name, metric, area ORDER BY project_name, metric)"
|
65 |
)
|
66 |
return True
|
67 |
|
|
|
70 |
con.sql(
|
71 |
"""
|
72 |
USE climatebase;
|
73 |
+
CREATE TABLE IF NOT EXISTS bioindicator (year BIGINT, project_name VARCHAR(255), metric VARCHAR(255), value DOUBLE, area DOUBLE, score DOUBLE, CONSTRAINT unique_year_project_name_metric UNIQUE (year, project_name, metric));
|
74 |
"""
|
75 |
)
|
76 |
return True
|
|
|
80 |
con.sql(
|
81 |
"""
|
82 |
INSERT INTO bioindicator FROM _temptable
|
83 |
+
ON CONFLICT (year, project_name, metric) DO UPDATE SET value = excluded.value;
|
84 |
"""
|
85 |
)
|
86 |
return True
|
utils/indicators.py
CHANGED
@@ -16,8 +16,6 @@ from . import logging
|
|
16 |
GEE_SERVICE_ACCOUNT = (
|
17 |
"climatebase-july-2023@ee-geospatialml-aquarry.iam.gserviceaccount.com"
|
18 |
)
|
19 |
-
INDICES_FILE = "indices.yaml"
|
20 |
-
|
21 |
|
22 |
class IndexGenerator:
|
23 |
"""
|
@@ -27,20 +25,22 @@ class IndexGenerator:
|
|
27 |
indices (string[], required): Array of index names to include in aggregate index generation.
|
28 |
"""
|
29 |
|
30 |
-
def __init__(
|
31 |
-
self,
|
32 |
-
indices,
|
33 |
-
):
|
34 |
# Authenticate to GEE & DuckDB
|
35 |
self._authenticate_ee(GEE_SERVICE_ACCOUNT)
|
36 |
|
|
|
37 |
self.project_name = None
|
38 |
self.project_geometry = None
|
39 |
self.project_centroid = None
|
40 |
-
|
|
|
|
|
|
|
41 |
# Use defined subset of indices
|
42 |
-
|
43 |
-
self.indices =
|
|
|
44 |
|
45 |
def set_project(self, project_name):
|
46 |
self.project_name = project_name
|
@@ -139,6 +139,18 @@ class IndexGenerator:
|
|
139 |
if not dataset:
|
140 |
raise Exception("Failed to generate dataset.")
|
141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
logging.info(f"Generated index: {index_config['name']}")
|
143 |
return dataset
|
144 |
|
@@ -163,17 +175,17 @@ class IndexGenerator:
|
|
163 |
logging.info(f"Calculated zonal mean for {index_key}.")
|
164 |
return out
|
165 |
|
166 |
-
def generate_composite_index_df(self, year
|
167 |
data = {
|
168 |
-
"metric":
|
169 |
"year": year,
|
170 |
"centroid": "",
|
171 |
"project_name": "",
|
172 |
-
"value": list(map(self.zonal_mean_index, indices, repeat(year))),
|
173 |
# to-do: calculate with duckdb; also, should be part of project table instead
|
174 |
"area": self.roi.area().getInfo(), # m^2
|
175 |
"geojson": "",
|
176 |
-
|
177 |
}
|
178 |
|
179 |
logging.info("data", data)
|
@@ -199,9 +211,7 @@ class IndexGenerator:
|
|
199 |
# to-do: pararelize?
|
200 |
for year in years:
|
201 |
logging.info(year)
|
202 |
-
df = self.generate_composite_index_df(
|
203 |
-
year, self.project_geometry, list(self.indices.keys())
|
204 |
-
)
|
205 |
dfs.append(df)
|
206 |
|
207 |
# Concatenate all dataframes
|
@@ -299,7 +309,9 @@ class IndexGenerator:
|
|
299 |
|
300 |
def calculate_score(self, start_year, end_year):
|
301 |
years = []
|
302 |
-
|
|
|
|
|
303 |
row_exists = dq.check_if_project_exists_for_year(self.project_name, year)
|
304 |
if not row_exists:
|
305 |
years.append(year)
|
@@ -310,11 +322,20 @@ class IndexGenerator:
|
|
310 |
# Write score table to `_temptable`
|
311 |
dq.write_score_to_temptable(df)
|
312 |
|
313 |
-
# Create `bioindicator` table IF NOT EXISTS.
|
314 |
-
dq.get_or_create_bioindicator_table()
|
315 |
-
|
316 |
# UPSERT project record
|
317 |
dq.upsert_project_record()
|
318 |
logging.info("upserted records into motherduck")
|
319 |
scores = dq.get_project_scores(self.project_name, start_year, end_year)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
return scores
|
|
|
16 |
GEE_SERVICE_ACCOUNT = (
|
17 |
"climatebase-july-2023@ee-geospatialml-aquarry.iam.gserviceaccount.com"
|
18 |
)
|
|
|
|
|
19 |
|
20 |
class IndexGenerator:
|
21 |
"""
|
|
|
25 |
indices (string[], required): Array of index names to include in aggregate index generation.
|
26 |
"""
|
27 |
|
28 |
+
def __init__(self):
|
|
|
|
|
|
|
29 |
# Authenticate to GEE & DuckDB
|
30 |
self._authenticate_ee(GEE_SERVICE_ACCOUNT)
|
31 |
|
32 |
+
self.roi = None
|
33 |
self.project_name = None
|
34 |
self.project_geometry = None
|
35 |
self.project_centroid = None
|
36 |
+
self.indices = None
|
37 |
+
self.metric_name = None
|
38 |
+
|
39 |
+
def set_metric(self, metric_name):
|
40 |
# Use defined subset of indices
|
41 |
+
indices_file = f'metrics/{metric_name.replace(" ", "_")}.yaml'
|
42 |
+
self.indices = self._load_indices(indices_file)
|
43 |
+
self.metric_name = metric_name
|
44 |
|
45 |
def set_project(self, project_name):
|
46 |
self.project_name = project_name
|
|
|
139 |
if not dataset:
|
140 |
raise Exception("Failed to generate dataset.")
|
141 |
|
142 |
+
# Normalize to a range of [0, 1]
|
143 |
+
min_val = 0
|
144 |
+
max_val = 1
|
145 |
+
if type(index_config['min'])==int or type(index_config['min']==float):
|
146 |
+
min_val = index_config['min']
|
147 |
+
if str(index_config['max'])=='roi_area':
|
148 |
+
max_val = self.roi.area().getInfo() # in m^2
|
149 |
+
elif type(index_config['max'])==int or type(index_config['max']==float):
|
150 |
+
max_val = index_config['max']
|
151 |
+
dataset.subtract(min_val)\
|
152 |
+
.divide(max_val - min_val)
|
153 |
+
|
154 |
logging.info(f"Generated index: {index_config['name']}")
|
155 |
return dataset
|
156 |
|
|
|
175 |
logging.info(f"Calculated zonal mean for {index_key}.")
|
176 |
return out
|
177 |
|
178 |
+
def generate_composite_index_df(self, year):
|
179 |
data = {
|
180 |
+
"metric": self.metric_name,
|
181 |
"year": year,
|
182 |
"centroid": "",
|
183 |
"project_name": "",
|
184 |
+
"value": list(map(self.zonal_mean_index, self.indices, repeat(year))),
|
185 |
# to-do: calculate with duckdb; also, should be part of project table instead
|
186 |
"area": self.roi.area().getInfo(), # m^2
|
187 |
"geojson": "",
|
188 |
+
"coefficient": list(map(lambda x: self.indices[x]['coefficient'], self.indices))
|
189 |
}
|
190 |
|
191 |
logging.info("data", data)
|
|
|
211 |
# to-do: pararelize?
|
212 |
for year in years:
|
213 |
logging.info(year)
|
214 |
+
df = self.generate_composite_index_df(year)
|
|
|
|
|
215 |
dfs.append(df)
|
216 |
|
217 |
# Concatenate all dataframes
|
|
|
309 |
|
310 |
def calculate_score(self, start_year, end_year):
|
311 |
years = []
|
312 |
+
# Create `bioindicator` table IF NOT EXISTS.
|
313 |
+
dq.get_or_create_bioindicator_table()
|
314 |
+
for year in range(start_year, end_year+1):
|
315 |
row_exists = dq.check_if_project_exists_for_year(self.project_name, year)
|
316 |
if not row_exists:
|
317 |
years.append(year)
|
|
|
322 |
# Write score table to `_temptable`
|
323 |
dq.write_score_to_temptable(df)
|
324 |
|
|
|
|
|
|
|
325 |
# UPSERT project record
|
326 |
dq.upsert_project_record()
|
327 |
logging.info("upserted records into motherduck")
|
328 |
scores = dq.get_project_scores(self.project_name, start_year, end_year)
|
329 |
+
scores.columns = scores.columns.str.replace('_', ' ').str.title()
|
330 |
+
if 'Area' in scores.columns:
|
331 |
+
scores['Area'] /= 1000**2
|
332 |
+
scores.rename(columns={'Area':'Area (km^2)'}, inplace=True)
|
333 |
+
if 'Score' in scores.columns:
|
334 |
+
scores['Score'] /= 1000**2
|
335 |
+
scores.rename(columns={'Score': 'Score (Area * Value)'}, inplace=True)
|
336 |
+
# Round scores to 4 significant figures
|
337 |
+
scores = scores.apply(
|
338 |
+
lambda x: ['%.4g'%x_i for x_i in x]
|
339 |
+
if pd.api.types.is_numeric_dtype(x)
|
340 |
+
else x)
|
341 |
return scores
|