Plinio Guzman commited on
Commit
84e5c53
2 Parent(s): 265b33b dac5eb6

Merge pull request #5 from buuck/develop

Browse files
Files changed (1) hide show
  1. app.py +102 -91
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 authenticate_gee(gee_service_account, gee_service_account_credentials_file):
206
  """
207
  Huggingface Spaces does not support secret files, therefore authenticate with an environment variable containing the JSON.
208
  """
209
- logging.info("authenticate_gee")
210
  credentials = ee.ServiceAccountCredentials(
211
- gee_service_account, key_data=os.environ["ee_service_account"]
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
- # def preview_table():
245
- # con.sql("FROM bioindicator;").show()
 
246
 
247
- # if __name__ == '__main__':
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 = con.sql(
280
- f"SELECT COUNT(1) FROM bioindicator WHERE (year = {year} AND project_name = '{project_name}')"
281
- ).fetchall()[0][0]
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
- return con.sql(
303
- f"SELECT * FROM bioindicator WHERE (year > {start_year} AND year <= {end_year} AND project_name = '{project_name}')"
304
- ).df()
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 con.sql(
325
- f"""
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
- authenticate_gee(GEE_SERVICE_ACCOUNT, GEE_SERVICE_ACCOUNT_CREDENTIALS_FILE)
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
- # map = gr.Plot().style()
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
- view_btn = gr.Button(value="View all")
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(view_all, outputs=results_df)
376
- save_btn.click(push_to_md)
 
 
 
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()