Skip to content

Commit 669a3f2

Browse files
authored
Merge pull request #57 from boettiger-lab/add_llms
change to docker
2 parents a53c053 + 1978bda commit 669a3f2

6 files changed

Lines changed: 114 additions & 113 deletions

File tree

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM python:3.12-slim
2+
WORKDIR /app
3+
RUN apt-get update && apt-get install -y \
4+
build-essential \
5+
curl \
6+
git \
7+
libgdal-dev \
8+
&& rm -rf /var/lib/apt/lists/*
9+
COPY requirements.txt /app/requirements.txt
10+
RUN pip install --no-cache-dir -r /app/requirements.txt
11+
COPY app/ /app/app/
12+
ENTRYPOINT ["bash", "-lc", "streamlit run app/app.py --server.address=0.0.0.0 --server.port ${PORT:-7860}"]

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ title: CA 30x30
33
emoji: 🏞️
44
colorFrom: green
55
colorTo: yellow
6-
sdk: streamlit
6+
sdk: docker
77
app_file: app/app.py
88
pinned: false
99
license: bsd
1010
---
1111

12-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
12+
# CA 30x30 Planning & Assessment Prototype
13+
14+
This repository contains the source code for the [**CA 30x30 Planning & Assessment Prototype**](https://huggingface.co/spaces/boettiger-lab/ca-30x30) which is hosted on Hugging Face.
15+
16+
## Description
17+
Proof of concept for a decision support tool developed in partnership with California Biodiversity Network participants through a co-design process. The tool can answer complex, real world natural language queries asked by conservation partner organizations, responding with reproducible, verifiable data summaries, charts, maps and text through careful integration of open weights language models and cloud optimized data.
18+
19+
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14933818.svg)](https://doi.org/10.5281/zenodo.14933818)

app/app.py

Lines changed: 58 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import streamlit as st
22
import streamlit.components.v1 as components
33
import base64
4-
import leafmap.maplibregl as leafmap
4+
import importlib
55
import altair as alt
66
import ibis
77
from ibis import _
@@ -124,11 +124,12 @@
124124

125125
st.divider()
126126

127-
128-
m = leafmap.Map(style="positron")
129-
#############
130-
127+
# initialize map
128+
leafmap = importlib.import_module("leafmap.maplibregl")
129+
m = leafmap.Map(center=(-120, 36), style="positron", zoom=5,
130+
controls=controls, attribution_control=False, use_message_queue=True)
131131

132+
#############
132133

133134
chatbot_container = st.container()
134135
with chatbot_container:
@@ -193,7 +194,7 @@ class SQLResponse(BaseModel):
193194
'svi',
194195
]}
195196

196-
def run_sql(query,color_choice):
197+
def run_sql(query, color_choice):
197198
"""
198199
Filter data based on an LLM-generated SQL query and return matching IDs.
199200
@@ -203,43 +204,39 @@ def run_sql(query,color_choice):
203204
"""
204205
output = few_shot_structured_llm.invoke(query)
205206
sql_query = output.sql_query
206-
explanation =output.explanation
207+
explanation = output.explanation
207208
if not sql_query: # if the chatbot can't generate a SQL query.
208209
st.success(explanation)
209210
return pd.DataFrame({'id' : []}), [], []
210211

211212
result = ca.sql(sql_query).execute()
212-
if result.empty :
213+
if result.empty:
213214
explanation = "This query did not return any results. Please try again with a different query."
214215
st.warning(explanation, icon="⚠️")
215216
st.caption("SQL Query:")
216-
st.code(sql_query,language = "sql")
217+
st.code(sql_query, language = "sql")
217218
if 'geom' in result.columns:
218-
return result.drop('geom',axis = 1), sql_query, explanation
219+
return result.drop('geom', axis=1), sql_query, explanation
219220
else:
220221
return result, sql_query, explanation
221222

222223
elif ("id" and "geom" in result.columns):
223224
style = get_pmtiles_style_llm(style_options[color_choice], result["id"].tolist())
224-
legend, position, bg_color, fontsize = get_legend(style_options,color_choice)
225-
226-
m.add_legend(legend_dict = legend, position = position, bg_color = bg_color, fontsize = fontsize)
227-
m.add_pmtiles(ca_pmtiles, style=style, opacity=alpha, tooltip=True, fit_bounds=True)
225+
m.add_pmtiles(ca_pmtiles, style=style, name="30x30 Conserved Areas (Terrestrial)",
226+
attribution="CA Nature (2024)", tooltip=True)
228227
m.fit_bounds(result.total_bounds.tolist())
229-
result = result.drop('geom',axis = 1) #printing to streamlit so I need to drop geom
228+
result = result.drop('geom', axis=1) #printing to streamlit so I need to drop geom
230229
else:
231230
st.write(result) # if we aren't mapping, just print out the data
232231

233232
with st.popover("Explanation"):
234233
st.write(explanation)
235234
st.caption("SQL Query:")
236-
st.code(sql_query,language = "sql")
235+
st.code(sql_query, language="sql")
237236

238237
return result, sql_query, explanation
239238

240239

241-
242-
243240
#############
244241

245242
filters = {}
@@ -252,7 +249,6 @@ def run_sql(query,color_choice):
252249
- 💬 For a more tailored experience, query our dataset of protected areas and their precomputed mean values for each of the displayed layers, using the experimental chatbot. The language model tries to answer natural language questions by drawing only from curated datasets (listed below).
253250
'''
254251

255-
256252
st.divider()
257253
color_choice = st.radio("Group by:", style_options, key = "color", help = "Select a category to change map colors and chart groupings.")
258254
colorby_vals = get_color_vals(style_options, color_choice) #get options for selected color_by column
@@ -267,9 +263,9 @@ def run_sql(query,color_choice):
267263
prompt = st.chat_input(example_query, key="chain", max_chars=300)
268264

269265
with chatbot_container:
270-
_,log_query_col, _ = st.columns([.001, 5,1], vertical_alignment = "top")
271-
with log_query_col:
272-
log_queries = st.checkbox("Save query", value = True, help = "Saving your queries helps improve this tool and guide conservation efforts. Your data is stored in a private location. For more details, see 'Why save your queries?' at the bottom of this page.")
266+
_,log_query_col, _ = st.columns([.001, 5,1], vertical_alignment = "top")
267+
with log_query_col:
268+
log_queries = st.checkbox("Save query", value = True, help = "Saving your queries helps improve this tool and guide conservation efforts. Your data is stored in a private location. For more details, see 'Why save your queries?' at the bottom of this page.")
273269

274270

275271
with st.container():
@@ -279,7 +275,7 @@ def run_sql(query,color_choice):
279275
with st.chat_message("assistant"):
280276
with st.spinner("Invoking query..."):
281277

282-
out, sql_query, llm_explanation = run_sql(prompt,color_choice)
278+
out, sql_query, llm_explanation = run_sql(prompt, color_choice)
283279
minio_logger(log_queries, prompt, sql_query, llm_explanation, llm_choice, 'query_log_prototype.csv', "shared-ca30x30-app")
284280

285281
if ("id" in out.columns) and (not out.empty):
@@ -308,52 +304,25 @@ def run_sql(query,color_choice):
308304
a_bio = st.slider("transparency", 0.0, 1.0, 0.1, key = "biodiversity")
309305
show_richness = st.toggle("Species Richness", key = "richness", value=chatbot_toggles['richness'])
310306
show_rsr = st.toggle("Range-Size Rarity", key = "rsr", value=chatbot_toggles['rsr'])
311-
312-
if show_richness:
313-
m.add_tile_layer(url_sr, name="MOBI Species Richness",opacity=a_bio)
314-
if show_rsr:
315-
m.add_tile_layer(url_rsr, name="MOBI Range-Size Rarity", opacity=a_bio)
316307

317308
#Carbon Section
318309
with st.expander("⛅ Carbon & Climate"):
319310
a_climate = st.slider("transparency", 0.0, 1.0, 0.15, key = "climate")
320311
show_irrecoverable_carbon = st.toggle("Irrecoverable Carbon", key = "irrecoverable_carbon", value=chatbot_toggles['irrecoverable_carbon'])
321312
show_manageable_carbon = st.toggle("Manageable Carbon", key = "manageable_carbon", value=chatbot_toggles['manageable_carbon'])
322-
323-
if show_irrecoverable_carbon:
324-
m.add_cog_layer(url_irr_carbon, palette="reds", name="Irrecoverable Carbon", opacity = a_climate, fit_bounds=False)
325-
326-
if show_manageable_carbon:
327-
m.add_cog_layer(url_man_carbon, palette="purples", name="Manageable Carbon", opacity = a_climate, fit_bounds=False)
328-
329313

330314
# People Section
331315
with st.expander("👤 People"):
332316
a_people = st.slider("transparency", 0.0, 1.0, 0.1, key = "SVI")
333317
show_justice40 = st.toggle("Disadvantaged Communities (Justice40)", key = "disadvantaged_communities", value=chatbot_toggles['disadvantaged_communities'])
334318
show_sv = st.toggle("Social Vulnerability Index (SVI)", key = "svi", value=chatbot_toggles['svi'])
335319

336-
if show_justice40:
337-
m.add_pmtiles(url_justice40, style=justice40_style, name="Justice40", opacity=a_people, tooltip=False, fit_bounds = False)
338-
339-
if show_sv:
340-
m.add_pmtiles(url_svi, style = svi_style, opacity=a_people, tooltip=False, fit_bounds = False)
341-
342320
# Fire Section
343321
with st.expander("🔥 Fire"):
344322
a_fire = st.slider("transparency", 0.0, 1.0, 0.15, key = "calfire")
345323
show_fire = st.toggle("Fires (2013-2023)", key = "fire", value=chatbot_toggles['fire'])
346-
347324
show_rxburn = st.toggle("Prescribed Burns (2013-2023)", key = "rxburn", value=chatbot_toggles['rxburn'])
348325

349-
350-
if show_fire:
351-
m.add_pmtiles(url_calfire, style=fire_style, name="CALFIRE Fire Polygons (2013-2023)", opacity=a_fire, tooltip=False, fit_bounds = False)
352-
353-
if show_rxburn:
354-
m.add_pmtiles(url_rxburn, style=rx_style, name="CAL FIRE Prescribed Burns (2013-2023)", opacity=a_fire, tooltip=False, fit_bounds = False)
355-
356-
357326
st.divider()
358327
st.markdown('<p class = "medium-font-sidebar"> Filters:</p>', help = "Apply filters to adjust what data is shown on the map.", unsafe_allow_html= True)
359328

@@ -377,26 +346,10 @@ def run_sql(query,color_choice):
377346

378347
# adding github logo
379348
st.markdown(f"<div class='spacer'>{github_html}</div>", unsafe_allow_html=True)
380-
381-
# st.markdown("""
382-
# <p class='medium-font-sidebar'>
383-
# :left_speech_bubble: <a href='https://github.com/boettiger-lab/ca-30x30/issues' target='_blank'>Report an issue</a>
384-
# </p>
385-
# """, unsafe_allow_html=True)
386-
387349
st.markdown(":left_speech_bubble: [Get in touch or report an issue](https://github.com/boettiger-lab/ca-30x30/issues)")
388350

389351

390-
391-
392-
393-
# Display CA 30x30 Data
394-
if 'out' not in locals():
395-
style = get_pmtiles_style(style_options[color_choice], alpha, filter_cols, filter_vals)
396-
legend, position, bg_color, fontsize = get_legend(style_options, color_choice)
397-
m.add_legend(legend_dict = legend, position = position, bg_color = bg_color, fontsize = fontsize)
398-
m.add_pmtiles(ca_pmtiles, style=style, name="CA", opacity=alpha, tooltip=True, fit_bounds=True)
399-
352+
## filter data and get map styling
400353
column = select_column[color_choice]
401354

402355
select_colors = {
@@ -409,31 +362,56 @@ def run_sql(query,color_choice):
409362
"Access Type": access["stops"],
410363
}
411364

412-
colors = (
413-
ibis
414-
.memtable(select_colors[color_choice], columns=[column, "color"])
415-
.to_pandas()
416-
)
365+
colors = color_table(select_colors, color_choice, column)
417366

418367

419368
# get summary tables used for charts + printed table
420-
# df - charts; df_tab - printed table (omits colors)
421369
if 'out' not in locals():
422-
df, df_tab, df_percent, df_bar_30x30 = get_summary_table(ca, column, select_colors, color_choice, filter_cols, filter_vals,colorby_vals)
370+
df, df_tab, df_percent, df_bar_30x30 = get_summary_table(ca, column, select_colors, color_choice, filter_cols, filter_vals, colorby_vals)
423371
total_percent = (100*df_percent.percent_CA.sum()).round(2)
424-
425372
else:
426373
df = get_summary_table_sql(ca, column, colors, ids)
427374
total_percent = (100*df.percent_CA.sum()).round(2)
428375

376+
# build map style and legend
377+
if 'out' not in locals():
378+
style = get_pmtiles_style(style_options[color_choice], alpha, filter_cols, filter_vals)
379+
380+
legend,fontsize = get_legend(style_options, color_choice, df, column)
381+
382+
# add tile/cog/pmtiles layers
383+
if show_richness:
384+
m.add_tile_layer(url_sr, name="MOBI Species Richness", opacity=a_bio)
385+
if show_rsr:
386+
m.add_tile_layer(url_rsr, name="MOBI Range-Size Rarity", opacity=a_bio)
387+
if show_irrecoverable_carbon:
388+
m.add_cog_layer(url_irr_carbon, palette="reds", name="Irrecoverable Carbon", opacity=a_climate, fit_bounds=False)
389+
if show_manageable_carbon:
390+
m.add_cog_layer(url_man_carbon, palette="purples", name="Manageable Carbon", opacity=a_climate, fit_bounds=False)
391+
if show_justice40:
392+
m.add_pmtiles(url_justice40, style=justice40_style, name="Justice40", opacity=a_people, tooltip=False, fit_bounds=False)
393+
if show_sv:
394+
m.add_pmtiles(url_svi, style=svi_style, opacity=a_people, tooltip=False, fit_bounds=False)
395+
if show_fire:
396+
m.add_pmtiles(url_calfire, style=fire_style, name="CALFIRE Fire Polygons (2013-2023)", opacity=a_fire, tooltip=False, fit_bounds=False)
397+
if show_rxburn:
398+
m.add_pmtiles(url_rxburn, style=rx_style, name="CAL FIRE Prescribed Burns (2013-2023)", opacity=a_fire, tooltip=False, fit_bounds=False)
399+
400+
# add main CA pmtiles layer and legend
401+
if 'out' not in locals():
402+
m.add_pmtiles(ca_pmtiles, style=style, name="30x30 Conserved Areas (Terrestrial)",
403+
attribution="CA Nature (2024)", tooltip=True)
404+
m.fit_bounds([-124.42174575, 32.53428607, -114.13077782, 42.00950367])
405+
m.add_legend(title='', legend_dict=legend, fontsize=fontsize,
406+
bg_color=bg_color, position=position, shape_type=shape_type)
429407

430408
# charts displayed based on color_by variable
431409
richness_chart = bar_chart(df, column, 'mean_richness', "Species Richness (2022)")
432410
rsr_chart = bar_chart(df, column, 'mean_rsr', "Range-Size Rarity (2022)")
433411
irr_carbon_chart = bar_chart(df, column, 'mean_irrecoverable_carbon', "Irrecoverable Carbon (2018)")
434412
man_carbon_chart = bar_chart(df, column, 'mean_manageable_carbon', "Manageable Carbon (2018)")
435413
fire_10_chart = bar_chart(df, column, 'mean_fire', "Fires (2013-2023)")
436-
rx_10_chart = bar_chart(df, column, 'mean_rxburn',"Prescribed Burns (2013-2023)")
414+
rx_10_chart = bar_chart(df, column, 'mean_rxburn', "Prescribed Burns (2013-2023)")
437415
justice40_chart = bar_chart(df, column, 'mean_disadvantaged', "Disadvantaged Communities (2021)")
438416
svi_chart = bar_chart(df, column, 'mean_svi', "Social Vulnerability Index (2022)")
439417

@@ -446,9 +424,9 @@ def run_sql(query,color_choice):
446424
m.to_streamlit(height=650)
447425
with st.expander("🔍 View/download data"):
448426
if 'out' not in locals():
449-
st.dataframe(df_tab, use_container_width = True)
427+
st.dataframe(df_tab, use_container_width=True)
450428
else:
451-
st.dataframe(out, use_container_width = True)
429+
st.dataframe(out, use_container_width=True)
452430

453431
with stats_col:
454432
with st.container():
@@ -462,38 +440,27 @@ def run_sql(query,color_choice):
462440

463441
if show_richness:
464442
st.altair_chart(richness_chart, use_container_width=True)
465-
466443
if show_rsr:
467444
st.altair_chart(rsr_chart, use_container_width=True)
468-
469445
if show_irrecoverable_carbon:
470446
st.altair_chart(irr_carbon_chart, use_container_width=True)
471-
472447
if show_manageable_carbon:
473448
st.altair_chart(man_carbon_chart, use_container_width=True)
474-
475449
if show_justice40:
476450
st.altair_chart(justice40_chart, use_container_width=True)
477-
478451
if show_sv:
479452
st.altair_chart(svi_chart, use_container_width=True)
480-
481453
if show_fire:
482454
st.altair_chart(fire_10_chart, use_container_width=True)
483-
484455
if show_rxburn:
485456
st.altair_chart(rx_10_chart, use_container_width=True)
486457

487458

488459
st.caption("***The label 'established' is inferred from the California Protected Areas Database, which may introduce artifacts. For details on our methodology, please refer to our <a href='https://github.com/boettiger-lab/ca-30x30' target='_blank'>our source code</a>.", unsafe_allow_html=True)
489-
490-
491-
st.caption("***Under California’s 30x30 framework, only GAP codes 1 and 2 are counted toward the conservation goal.")
460+
st.caption("***Under California's 30x30 framework, only GAP codes 1 and 2 are counted toward the conservation goal.")
492461

493462
st.divider()
494463

495464
with open('app/footer.md', 'r') as file:
496465
footer = file.read()
497-
st.markdown(footer)
498-
499-
466+
st.markdown(footer)

0 commit comments

Comments
 (0)