Spaces:
Build error
Build error
Merge pull request #13 from buuck/default_projects
Browse files- app.py +13 -6
- utils/duckdb_queries.py +1 -1
- utils/indicators.py +33 -25
app.py
CHANGED
|
@@ -17,27 +17,34 @@ with gr.Blocks() as demo:
|
|
| 17 |
with gr.Row():
|
| 18 |
view_btn = gr.Button(value="Show project map")
|
| 19 |
calc_btn = gr.Button(value="Calculate!")
|
| 20 |
-
# save_btn = gr.Button(value="Save")
|
| 21 |
results_df = gr.Dataframe(
|
| 22 |
headers=["Year", "Project Name", "Score"],
|
| 23 |
datatype=["number", "str", "number"],
|
| 24 |
-
label="
|
| 25 |
)
|
| 26 |
calc_btn.click(
|
| 27 |
-
indexgenerator.
|
| 28 |
-
inputs=[start_year, end_year
|
| 29 |
outputs=results_df,
|
| 30 |
)
|
| 31 |
view_btn.click(
|
| 32 |
fn=indexgenerator.show_project_map,
|
| 33 |
-
inputs=[project_name],
|
| 34 |
outputs=[m1],
|
| 35 |
)
|
| 36 |
|
| 37 |
def update_project_dropdown_list(url_params):
|
| 38 |
username = url_params.get("username", "default")
|
| 39 |
projects = dq.list_projects_by_author(author_id=username)
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# Get url params
|
| 43 |
url_params = gr.JSON({"username": "default"}, visible=False, label="URL Params")
|
|
|
|
| 17 |
with gr.Row():
|
| 18 |
view_btn = gr.Button(value="Show project map")
|
| 19 |
calc_btn = gr.Button(value="Calculate!")
|
|
|
|
| 20 |
results_df = gr.Dataframe(
|
| 21 |
headers=["Year", "Project Name", "Score"],
|
| 22 |
datatype=["number", "str", "number"],
|
| 23 |
+
label="Scores by year",
|
| 24 |
)
|
| 25 |
calc_btn.click(
|
| 26 |
+
indexgenerator.calculate_score,
|
| 27 |
+
inputs=[start_year, end_year],
|
| 28 |
outputs=results_df,
|
| 29 |
)
|
| 30 |
view_btn.click(
|
| 31 |
fn=indexgenerator.show_project_map,
|
|
|
|
| 32 |
outputs=[m1],
|
| 33 |
)
|
| 34 |
|
| 35 |
def update_project_dropdown_list(url_params):
|
| 36 |
username = url_params.get("username", "default")
|
| 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 |
+
return gr.Dropdown.update(choices=project_names)
|
| 41 |
+
|
| 42 |
+
# Change the project name in the index generator object when the
|
| 43 |
+
# user selects a new project
|
| 44 |
+
project_name.change(
|
| 45 |
+
indexgenerator.set_project,
|
| 46 |
+
inputs=project_name
|
| 47 |
+
)
|
| 48 |
|
| 49 |
# Get url params
|
| 50 |
url_params = gr.JSON({"username": "default"}, visible=False, label="URL Params")
|
utils/duckdb_queries.py
CHANGED
|
@@ -18,7 +18,7 @@ else:
|
|
| 18 |
# to-do: pass con through decorator
|
| 19 |
def list_projects_by_author(author_id):
|
| 20 |
return con.execute(
|
| 21 |
-
"SELECT DISTINCT name FROM project WHERE authorId = ? AND geometry
|
| 22 |
[author_id],
|
| 23 |
).df()
|
| 24 |
|
|
|
|
| 18 |
# to-do: pass con through decorator
|
| 19 |
def list_projects_by_author(author_id):
|
| 20 |
return con.execute(
|
| 21 |
+
"SELECT DISTINCT name FROM project WHERE (authorId = ? OR authorId = 'default') AND (geometry IS NOT NULL)",
|
| 22 |
[author_id],
|
| 23 |
).df()
|
| 24 |
|
utils/indicators.py
CHANGED
|
@@ -34,10 +34,26 @@ class IndexGenerator:
|
|
| 34 |
# Authenticate to GEE & DuckDB
|
| 35 |
self._authenticate_ee(GEE_SERVICE_ACCOUNT)
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
# Use defined subset of indices
|
| 38 |
all_indices = self._load_indices(INDICES_FILE)
|
| 39 |
self.indices = {k: all_indices[k] for k in indices}
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
def _cloudfree(self, gee_path, daterange):
|
| 42 |
"""
|
| 43 |
Internal method to generate a cloud-free composite.
|
|
@@ -59,7 +75,8 @@ class IndexGenerator:
|
|
| 59 |
)
|
| 60 |
return composite_cloudfree.clip(self.roi)
|
| 61 |
|
| 62 |
-
|
|
|
|
| 63 |
# Read index configurations
|
| 64 |
with open(indices_file, "r") as stream:
|
| 65 |
try:
|
|
@@ -146,7 +163,7 @@ class IndexGenerator:
|
|
| 146 |
logging.info(f"Calculated zonal mean for {index_key}.")
|
| 147 |
return out
|
| 148 |
|
| 149 |
-
def generate_composite_index_df(self, year,
|
| 150 |
data = {
|
| 151 |
"metric": indices,
|
| 152 |
"year": year,
|
|
@@ -175,36 +192,28 @@ class IndexGenerator:
|
|
| 175 |
ee.Initialize(credentials)
|
| 176 |
logging.info("Authenticated to Google Earth Engine.")
|
| 177 |
|
| 178 |
-
def _calculate_yearly_index(self, years
|
| 179 |
dfs = []
|
| 180 |
logging.info(years)
|
| 181 |
-
project_geometry = dq.get_project_geometry(project_name)
|
| 182 |
-
project_centroid = dq.get_project_centroid(project_name)
|
| 183 |
-
# to-do: refactor to involve less transformations
|
| 184 |
-
_polygon = json.dumps(
|
| 185 |
-
json.loads(project_geometry[0][0])["features"][0]["geometry"]
|
| 186 |
-
)
|
| 187 |
-
# to-do: don't use self.roi and instead pass patameter strategically
|
| 188 |
-
self.roi = ee.Geometry.Polygon(json.loads(_polygon)["coordinates"])
|
| 189 |
|
| 190 |
# to-do: pararelize?
|
| 191 |
for year in years:
|
| 192 |
logging.info(year)
|
| 193 |
-
self.project_name = project_name
|
| 194 |
df = self.generate_composite_index_df(
|
| 195 |
-
year, project_geometry, list(self.indices.keys())
|
| 196 |
)
|
| 197 |
dfs.append(df)
|
| 198 |
|
| 199 |
# Concatenate all dataframes
|
| 200 |
df_concat = pd.concat(dfs)
|
| 201 |
-
df_concat["centroid"] = str(project_centroid)
|
| 202 |
-
df_concat["project_name"] = project_name
|
| 203 |
-
df_concat["geojson"] = str(project_geometry)
|
| 204 |
return df_concat.round(2)
|
| 205 |
|
| 206 |
-
# h/t: https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/12
|
| 207 |
-
|
|
|
|
| 208 |
"""Function documentation:\n
|
| 209 |
Basic framework adopted from Krichardson under the following thread:
|
| 210 |
https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/7
|
|
@@ -249,9 +258,8 @@ class IndexGenerator:
|
|
| 249 |
# Finally, return the zoom level and the associated boundary-box center coordinates
|
| 250 |
return zoom, b_box["center"]
|
| 251 |
|
| 252 |
-
def show_project_map(self
|
| 253 |
-
|
| 254 |
-
features = json.loads(project_geometry[0][0].replace("'", '"'))["features"]
|
| 255 |
geometry = features[0]["geometry"]
|
| 256 |
longitudes = np.array(geometry["coordinates"])[0, :, 0]
|
| 257 |
latitudes = np.array(geometry["coordinates"])[0, :, 1]
|
|
@@ -287,15 +295,15 @@ class IndexGenerator:
|
|
| 287 |
|
| 288 |
return fig
|
| 289 |
|
| 290 |
-
def
|
| 291 |
years = []
|
| 292 |
for year in range(start_year, end_year):
|
| 293 |
-
row_exists = dq.check_if_project_exists_for_year(project_name, year)
|
| 294 |
if not row_exists:
|
| 295 |
years.append(year)
|
| 296 |
|
| 297 |
if len(years) > 0:
|
| 298 |
-
df = self._calculate_yearly_index(years
|
| 299 |
|
| 300 |
# Write score table to `_temptable`
|
| 301 |
dq.write_score_to_temptable(df)
|
|
@@ -306,5 +314,5 @@ class IndexGenerator:
|
|
| 306 |
# UPSERT project record
|
| 307 |
dq.upsert_project_record()
|
| 308 |
logging.info("upserted records into motherduck")
|
| 309 |
-
scores = dq.get_project_scores(project_name, start_year, end_year)
|
| 310 |
return scores
|
|
|
|
| 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 |
all_indices = self._load_indices(INDICES_FILE)
|
| 43 |
self.indices = {k: all_indices[k] for k in indices}
|
| 44 |
|
| 45 |
+
def set_project(self, project_name):
|
| 46 |
+
self.project_name = project_name
|
| 47 |
+
self.project_geometry = dq.get_project_geometry(self.project_name)
|
| 48 |
+
self.project_centroid = dq.get_project_centroid(self.project_name)
|
| 49 |
+
|
| 50 |
+
# to-do: refactor to involve fewer transformations
|
| 51 |
+
_polygon = json.dumps(
|
| 52 |
+
json.loads(self.project_geometry[0][0])["features"][0]["geometry"]
|
| 53 |
+
)
|
| 54 |
+
# to-do: don't use self.roi and instead pass patameter strategically
|
| 55 |
+
self.roi = ee.Geometry.Polygon(json.loads(_polygon)["coordinates"])
|
| 56 |
+
|
| 57 |
def _cloudfree(self, gee_path, daterange):
|
| 58 |
"""
|
| 59 |
Internal method to generate a cloud-free composite.
|
|
|
|
| 75 |
)
|
| 76 |
return composite_cloudfree.clip(self.roi)
|
| 77 |
|
| 78 |
+
@staticmethod
|
| 79 |
+
def _load_indices(indices_file):
|
| 80 |
# Read index configurations
|
| 81 |
with open(indices_file, "r") as stream:
|
| 82 |
try:
|
|
|
|
| 163 |
logging.info(f"Calculated zonal mean for {index_key}.")
|
| 164 |
return out
|
| 165 |
|
| 166 |
+
def generate_composite_index_df(self, year, indices=[]):
|
| 167 |
data = {
|
| 168 |
"metric": indices,
|
| 169 |
"year": year,
|
|
|
|
| 192 |
ee.Initialize(credentials)
|
| 193 |
logging.info("Authenticated to Google Earth Engine.")
|
| 194 |
|
| 195 |
+
def _calculate_yearly_index(self, years):
|
| 196 |
dfs = []
|
| 197 |
logging.info(years)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 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
|
| 208 |
df_concat = pd.concat(dfs)
|
| 209 |
+
df_concat["centroid"] = str(self.project_centroid)
|
| 210 |
+
df_concat["project_name"] = self.project_name
|
| 211 |
+
df_concat["geojson"] = str(self.project_geometry)
|
| 212 |
return df_concat.round(2)
|
| 213 |
|
| 214 |
+
# h/t: https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/12\
|
| 215 |
+
@staticmethod
|
| 216 |
+
def _latlon_to_config(longitudes=None, latitudes=None):
|
| 217 |
"""Function documentation:\n
|
| 218 |
Basic framework adopted from Krichardson under the following thread:
|
| 219 |
https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/7
|
|
|
|
| 258 |
# Finally, return the zoom level and the associated boundary-box center coordinates
|
| 259 |
return zoom, b_box["center"]
|
| 260 |
|
| 261 |
+
def show_project_map(self):
|
| 262 |
+
features = json.loads(self.project_geometry[0][0].replace("'", '"'))["features"]
|
|
|
|
| 263 |
geometry = features[0]["geometry"]
|
| 264 |
longitudes = np.array(geometry["coordinates"])[0, :, 0]
|
| 265 |
latitudes = np.array(geometry["coordinates"])[0, :, 1]
|
|
|
|
| 295 |
|
| 296 |
return fig
|
| 297 |
|
| 298 |
+
def calculate_score(self, start_year, end_year):
|
| 299 |
years = []
|
| 300 |
for year in range(start_year, end_year):
|
| 301 |
+
row_exists = dq.check_if_project_exists_for_year(self.project_name, year)
|
| 302 |
if not row_exists:
|
| 303 |
years.append(year)
|
| 304 |
|
| 305 |
if len(years) > 0:
|
| 306 |
+
df = self._calculate_yearly_index(years)
|
| 307 |
|
| 308 |
# Write score table to `_temptable`
|
| 309 |
dq.write_score_to_temptable(df)
|
|
|
|
| 314 |
# UPSERT project record
|
| 315 |
dq.upsert_project_record()
|
| 316 |
logging.info("upserted records into motherduck")
|
| 317 |
+
scores = dq.get_project_scores(self.project_name, start_year, end_year)
|
| 318 |
return scores
|