Spaces:
Build error
Build error
Merge pull request #5 from buuck/develop
Browse files
app.py
CHANGED
|
@@ -9,6 +9,7 @@ import gradio as gr
|
|
| 9 |
import pandas as pd
|
| 10 |
import plotly.graph_objects as go
|
| 11 |
import yaml
|
|
|
|
| 12 |
from google.oauth2 import service_account
|
| 13 |
|
| 14 |
|
|
@@ -25,7 +26,6 @@ ROI_RADIUS = 20000
|
|
| 25 |
GEE_SERVICE_ACCOUNT = (
|
| 26 |
"climatebase-july-2023@ee-geospatialml-aquarry.iam.gserviceaccount.com"
|
| 27 |
)
|
| 28 |
-
GEE_SERVICE_ACCOUNT_CREDENTIALS_FILE = "ee_service_account.json"
|
| 29 |
INDICES_FILE = "indices.yaml"
|
| 30 |
START_YEAR = 2015
|
| 31 |
END_YEAR = 2022
|
|
@@ -202,13 +202,13 @@ def set_up_duckdb():
|
|
| 202 |
return con
|
| 203 |
|
| 204 |
|
| 205 |
-
def
|
| 206 |
"""
|
| 207 |
Huggingface Spaces does not support secret files, therefore authenticate with an environment variable containing the JSON.
|
| 208 |
"""
|
| 209 |
-
logging.info("
|
| 210 |
credentials = ee.ServiceAccountCredentials(
|
| 211 |
-
|
| 212 |
)
|
| 213 |
ee.Initialize(credentials)
|
| 214 |
|
|
@@ -240,51 +240,93 @@ def create_dataframe(years, project_name):
|
|
| 240 |
dfs.append(df)
|
| 241 |
return pd.concat(dfs)
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
-
#
|
| 245 |
-
#
|
|
|
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
# Map = geemap.Map()
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
# # Create a cloud-free composite with custom parameters for cloud score threshold and percentile.
|
| 254 |
-
# composite_cloudfree = ee.Algorithms.Landsat.simpleComposite(**{
|
| 255 |
-
# 'collection': collection,
|
| 256 |
-
# 'percentile': 75,
|
| 257 |
-
# 'cloudScoreRange': 5
|
| 258 |
-
# })
|
| 259 |
-
|
| 260 |
-
# Map.addLayer(composite_cloudfree, {'bands': ['B4', 'B3', 'B2'], 'max': 128}, 'Custom TOA composite')
|
| 261 |
-
# Map.centerObject(roi, 14)
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
# ig = IndexGenerator(centroid=LOCATION, year=2015, indices_file=INDICES_FILE, project_name='Test Project', map=Map)
|
| 265 |
-
# dataset = ig.generate_index(indices['Air'])
|
| 266 |
-
|
| 267 |
-
# minMax = dataset.clip(roi).reduceRegion(
|
| 268 |
-
# geometry = roi,
|
| 269 |
-
# reducer = ee.Reducer.minMax(),
|
| 270 |
-
# scale= 3000,
|
| 271 |
-
# maxPixels= 10e3,
|
| 272 |
-
# )
|
| 273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
# minMax.getInfo()
|
| 276 |
def calculate_biodiversity_score(start_year, end_year, project_name):
|
| 277 |
years = []
|
| 278 |
for year in range(start_year, end_year):
|
| 279 |
-
row_exists =
|
| 280 |
-
|
| 281 |
-
|
| 282 |
if not row_exists:
|
| 283 |
years.append(year)
|
| 284 |
|
| 285 |
if len(years) > 0:
|
| 286 |
df = create_dataframe(years, project_name)
|
| 287 |
-
# con.sql('FROM df LIMIT 5').show()
|
| 288 |
|
| 289 |
# Write score table to `_temptable`
|
| 290 |
con.sql(
|
|
@@ -296,84 +338,54 @@ def calculate_biodiversity_score(start_year, end_year, project_name):
|
|
| 296 |
"""
|
| 297 |
USE climatebase;
|
| 298 |
CREATE TABLE IF NOT EXISTS bioindicator (year BIGINT, project_name VARCHAR(255), value DOUBLE, area DOUBLE, score DOUBLE, CONSTRAINT unique_year_project_name UNIQUE (year, project_name));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
"""
|
| 300 |
)
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
def view_all():
|
| 308 |
-
logging.info("view_all")
|
| 309 |
-
return con.sql(f"SELECT * FROM bioindicator").df()
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
def push_to_md():
|
| 313 |
-
# UPSERT project record
|
| 314 |
-
con.sql(
|
| 315 |
-
"""
|
| 316 |
-
INSERT INTO bioindicator FROM _temptable
|
| 317 |
-
ON CONFLICT (year, project_name) DO UPDATE SET value = excluded.value;
|
| 318 |
-
"""
|
| 319 |
-
)
|
| 320 |
-
logging.info("upsert records into motherduck")
|
| 321 |
-
|
| 322 |
|
| 323 |
def motherduck_list_projects(author_id):
|
| 324 |
-
return
|
| 325 |
-
|
| 326 |
-
SELECT DISTINCT name FROM project WHERE authorId = '{author_id}'
|
| 327 |
-
"""
|
| 328 |
-
).df()
|
| 329 |
|
| 330 |
|
| 331 |
with gr.Blocks() as demo:
|
| 332 |
# Environment setup
|
| 333 |
-
|
| 334 |
con = set_up_duckdb()
|
| 335 |
-
|
| 336 |
-
# Create circle buffer over point
|
| 337 |
-
roi = ee.Geometry.Point(*LOCATION).buffer(ROI_RADIUS)
|
| 338 |
-
|
| 339 |
-
# # Load a raw Landsat ImageCollection for a single year.
|
| 340 |
-
# start_date = str(datetime.date(YEAR, 1, 1))
|
| 341 |
-
# end_date = str(datetime.date(YEAR, 12, 31))
|
| 342 |
-
# collection = (
|
| 343 |
-
# ee.ImageCollection('LANDSAT/LC08/C02/T1')
|
| 344 |
-
# .filterDate(start_date, end_date)
|
| 345 |
-
# .filterBounds(roi)
|
| 346 |
-
# )
|
| 347 |
-
|
| 348 |
-
# indices = load_indices(INDICES_FILE)
|
| 349 |
-
# push_to_md(START_YEAR, END_YEAR, 'Test Project')
|
| 350 |
with gr.Column():
|
| 351 |
-
|
| 352 |
with gr.Row():
|
|
|
|
| 353 |
start_year = gr.Number(value=2017, label="Start Year", precision=0)
|
| 354 |
end_year = gr.Number(value=2022, label="End Year", precision=0)
|
| 355 |
-
# project_name = gr.Textbox(label="Project Name")
|
| 356 |
-
project_name = gr.Dropdown([], label="Project", value="Select project")
|
| 357 |
-
# boroughs = gr.CheckboxGroup(choices=["Queens", "Brooklyn", "Manhattan", "Bronx", "Staten Island"], value=["Queens", "Brooklyn"], label="Select Methodology:")
|
| 358 |
-
# btn = gr.Button(value="Update Filter")
|
| 359 |
with gr.Row():
|
|
|
|
| 360 |
calc_btn = gr.Button(value="Calculate!")
|
| 361 |
-
|
| 362 |
-
save_btn = gr.Button(value="Save")
|
| 363 |
results_df = gr.Dataframe(
|
| 364 |
headers=["Year", "Project Name", "Score"],
|
| 365 |
datatype=["number", "str", "number"],
|
| 366 |
label="Biodiversity scores by year",
|
| 367 |
)
|
| 368 |
-
# demo.load(filter_map, [min_price, max_price, boroughs], map)
|
| 369 |
-
# btn.click(filter_map, [min_price, max_price, boroughs], map)
|
| 370 |
calc_btn.click(
|
| 371 |
calculate_biodiversity_score,
|
| 372 |
inputs=[start_year, end_year, project_name],
|
| 373 |
outputs=results_df,
|
| 374 |
)
|
| 375 |
-
view_btn.click(
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
def update_project_dropdown_list(url_params):
|
| 379 |
username = url_params.get("username", "default")
|
|
@@ -398,5 +410,4 @@ with gr.Blocks() as demo:
|
|
| 398 |
queue=False,
|
| 399 |
)
|
| 400 |
|
| 401 |
-
|
| 402 |
-
demo.launch()
|
|
|
|
| 9 |
import pandas as pd
|
| 10 |
import plotly.graph_objects as go
|
| 11 |
import yaml
|
| 12 |
+
import numpy as np
|
| 13 |
from google.oauth2 import service_account
|
| 14 |
|
| 15 |
|
|
|
|
| 26 |
GEE_SERVICE_ACCOUNT = (
|
| 27 |
"climatebase-july-2023@ee-geospatialml-aquarry.iam.gserviceaccount.com"
|
| 28 |
)
|
|
|
|
| 29 |
INDICES_FILE = "indices.yaml"
|
| 30 |
START_YEAR = 2015
|
| 31 |
END_YEAR = 2022
|
|
|
|
| 202 |
return con
|
| 203 |
|
| 204 |
|
| 205 |
+
def authenticate_ee(ee_service_account):
|
| 206 |
"""
|
| 207 |
Huggingface Spaces does not support secret files, therefore authenticate with an environment variable containing the JSON.
|
| 208 |
"""
|
| 209 |
+
logging.info("authenticate_ee")
|
| 210 |
credentials = ee.ServiceAccountCredentials(
|
| 211 |
+
ee_service_account, key_data=os.environ["ee_service_account"]
|
| 212 |
)
|
| 213 |
ee.Initialize(credentials)
|
| 214 |
|
|
|
|
| 240 |
dfs.append(df)
|
| 241 |
return pd.concat(dfs)
|
| 242 |
|
| 243 |
+
# h/t: https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/12
|
| 244 |
+
def get_plotting_zoom_level_and_center_coordinates_from_lonlat_tuples(longitudes=None, latitudes=None):
|
| 245 |
+
"""Function documentation:\n
|
| 246 |
+
Basic framework adopted from Krichardson under the following thread:
|
| 247 |
+
https://community.plotly.com/t/dynamic-zoom-for-mapbox/32658/7
|
| 248 |
|
| 249 |
+
# NOTE:
|
| 250 |
+
# THIS IS A TEMPORARY SOLUTION UNTIL THE DASH TEAM IMPLEMENTS DYNAMIC ZOOM
|
| 251 |
+
# in their plotly-functions associated with mapbox, such as go.Densitymapbox() etc.
|
| 252 |
|
| 253 |
+
Returns the appropriate zoom-level for these plotly-mapbox-graphics along with
|
| 254 |
+
the center coordinate tuple of all provided coordinate tuples.
|
| 255 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
+
# Check whether both latitudes and longitudes have been passed,
|
| 258 |
+
# or if the list lenghts don't match
|
| 259 |
+
if ((latitudes is None or longitudes is None)
|
| 260 |
+
or (len(latitudes) != len(longitudes))):
|
| 261 |
+
# Otherwise, return the default values of 0 zoom and the coordinate origin as center point
|
| 262 |
+
return 0, (0, 0)
|
| 263 |
+
|
| 264 |
+
# Get the boundary-box
|
| 265 |
+
b_box = {}
|
| 266 |
+
b_box['height'] = latitudes.max()-latitudes.min()
|
| 267 |
+
b_box['width'] = longitudes.max()-longitudes.min()
|
| 268 |
+
b_box['center']= (np.mean(longitudes), np.mean(latitudes))
|
| 269 |
+
|
| 270 |
+
# get the area of the bounding box in order to calculate a zoom-level
|
| 271 |
+
area = b_box['height'] * b_box['width']
|
| 272 |
+
|
| 273 |
+
# * 1D-linear interpolation with numpy:
|
| 274 |
+
# - Pass the area as the only x-value and not as a list, in order to return a scalar as well
|
| 275 |
+
# - The x-points "xp" should be in parts in comparable order of magnitude of the given area
|
| 276 |
+
# - The zpom-levels are adapted to the areas, i.e. start with the smallest area possible of 0
|
| 277 |
+
# which leads to the highest possible zoom value 20, and so forth decreasing with increasing areas
|
| 278 |
+
# as these variables are antiproportional
|
| 279 |
+
zoom = np.interp(x=area,
|
| 280 |
+
xp=[0, 5**-10, 4**-10, 3**-10, 2**-10, 1**-10, 1**-5],
|
| 281 |
+
fp=[20, 15, 14, 13, 12, 7, 5])
|
| 282 |
+
|
| 283 |
+
# Finally, return the zoom level and the associated boundary-box center coordinates
|
| 284 |
+
return zoom, b_box['center']
|
| 285 |
+
|
| 286 |
+
def show_project_map(project_name):
|
| 287 |
+
prepared_statement = \
|
| 288 |
+
con.execute("SELECT geometry FROM project WHERE name = ? LIMIT 1",
|
| 289 |
+
[project_name]).fetchall()
|
| 290 |
+
features = \
|
| 291 |
+
json.loads(prepared_statement[0][0].replace("\'", "\""))['features']
|
| 292 |
+
geometry = features[0]['geometry']
|
| 293 |
+
longitudes = np.array(geometry["coordinates"])[0, :, 0]
|
| 294 |
+
latitudes = np.array(geometry["coordinates"])[0, :, 1]
|
| 295 |
+
zoom, bbox_center = get_plotting_zoom_level_and_center_coordinates_from_lonlat_tuples(longitudes, latitudes)
|
| 296 |
+
fig = go.Figure(go.Scattermapbox(
|
| 297 |
+
mode = "markers",
|
| 298 |
+
lon = [bbox_center[0]], lat = [bbox_center[1]],
|
| 299 |
+
marker = {'size': 20, 'color': ["cyan"]}))
|
| 300 |
+
|
| 301 |
+
fig.update_layout(
|
| 302 |
+
mapbox = {
|
| 303 |
+
'style': "stamen-terrain",
|
| 304 |
+
'center': { 'lon': bbox_center[0], 'lat': bbox_center[1]},
|
| 305 |
+
'zoom': zoom, 'layers': [{
|
| 306 |
+
'source': {
|
| 307 |
+
'type': "FeatureCollection",
|
| 308 |
+
'features': [{
|
| 309 |
+
'type': "Feature",
|
| 310 |
+
'geometry': geometry
|
| 311 |
+
}]
|
| 312 |
+
},
|
| 313 |
+
'type': "fill", 'below': "traces", 'color': "royalblue"}]},
|
| 314 |
+
margin = {'l':0, 'r':0, 'b':0, 't':0})
|
| 315 |
+
|
| 316 |
+
return fig
|
| 317 |
|
| 318 |
# minMax.getInfo()
|
| 319 |
def calculate_biodiversity_score(start_year, end_year, project_name):
|
| 320 |
years = []
|
| 321 |
for year in range(start_year, end_year):
|
| 322 |
+
row_exists = \
|
| 323 |
+
con.execute("SELECT COUNT(1) FROM bioindicator WHERE (year = ? AND project_name = ?)",
|
| 324 |
+
[year, project_name]).fetchall()[0][0]
|
| 325 |
if not row_exists:
|
| 326 |
years.append(year)
|
| 327 |
|
| 328 |
if len(years) > 0:
|
| 329 |
df = create_dataframe(years, project_name)
|
|
|
|
| 330 |
|
| 331 |
# Write score table to `_temptable`
|
| 332 |
con.sql(
|
|
|
|
| 338 |
"""
|
| 339 |
USE climatebase;
|
| 340 |
CREATE TABLE IF NOT EXISTS bioindicator (year BIGINT, project_name VARCHAR(255), value DOUBLE, area DOUBLE, score DOUBLE, CONSTRAINT unique_year_project_name UNIQUE (year, project_name));
|
| 341 |
+
""")
|
| 342 |
+
# UPSERT project record
|
| 343 |
+
con.sql(
|
| 344 |
+
"""
|
| 345 |
+
INSERT INTO bioindicator FROM _temptable
|
| 346 |
+
ON CONFLICT (year, project_name) DO UPDATE SET value = excluded.value;
|
| 347 |
"""
|
| 348 |
)
|
| 349 |
+
logging.info("upsert records into motherduck")
|
| 350 |
+
scores = \
|
| 351 |
+
con.execute("SELECT * FROM bioindicator WHERE (year >= ? AND year <= ? AND project_name = ?)",
|
| 352 |
+
[start_year, end_year, project_name]).df()
|
| 353 |
+
return scores
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
|
| 355 |
def motherduck_list_projects(author_id):
|
| 356 |
+
return \
|
| 357 |
+
con.execute("SELECT DISTINCT name FROM project WHERE authorId = ? AND geometry != 'null'", [author_id]).df()
|
|
|
|
|
|
|
|
|
|
| 358 |
|
| 359 |
|
| 360 |
with gr.Blocks() as demo:
|
| 361 |
# Environment setup
|
| 362 |
+
authenticate_ee(GEE_SERVICE_ACCOUNT)
|
| 363 |
con = set_up_duckdb()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
with gr.Column():
|
| 365 |
+
m1 = gr.Plot()
|
| 366 |
with gr.Row():
|
| 367 |
+
project_name = gr.Dropdown([], label="Project", value="Select project")
|
| 368 |
start_year = gr.Number(value=2017, label="Start Year", precision=0)
|
| 369 |
end_year = gr.Number(value=2022, label="End Year", precision=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
with gr.Row():
|
| 371 |
+
view_btn = gr.Button(value="Show project map")
|
| 372 |
calc_btn = gr.Button(value="Calculate!")
|
| 373 |
+
# save_btn = gr.Button(value="Save")
|
|
|
|
| 374 |
results_df = gr.Dataframe(
|
| 375 |
headers=["Year", "Project Name", "Score"],
|
| 376 |
datatype=["number", "str", "number"],
|
| 377 |
label="Biodiversity scores by year",
|
| 378 |
)
|
|
|
|
|
|
|
| 379 |
calc_btn.click(
|
| 380 |
calculate_biodiversity_score,
|
| 381 |
inputs=[start_year, end_year, project_name],
|
| 382 |
outputs=results_df,
|
| 383 |
)
|
| 384 |
+
view_btn.click(
|
| 385 |
+
fn=show_project_map,
|
| 386 |
+
inputs=[project_name],
|
| 387 |
+
outputs=[m1],
|
| 388 |
+
)
|
| 389 |
|
| 390 |
def update_project_dropdown_list(url_params):
|
| 391 |
username = url_params.get("username", "default")
|
|
|
|
| 410 |
queue=False,
|
| 411 |
)
|
| 412 |
|
| 413 |
+
demo.launch()
|
|
|