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()
|
|