File size: 20,832 Bytes
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd06071
52e577b
dd06071
 
52e577b
 
0043640
52e577b
608b34b
52e577b
 
 
56784d5
52e577b
 
593d846
52e577b
 
3fac395
e1041f5
52e577b
 
 
593d846
 
 
 
 
 
 
52e577b
 
 
 
 
 
 
 
024cf8a
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
024cf8a
 
52e577b
 
 
 
 
 
 
 
 
 
 
 
3fac395
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3fac395
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
c56928e
 
 
 
52e577b
b834a5d
52e577b
 
 
 
 
 
 
 
dd06071
52e577b
 
 
 
 
 
 
 
8d226e4
dd06071
52e577b
 
ffd6aba
5571994
c3561dd
69db4d0
 
fa174ea
52e577b
fb4c9f4
 
e1041f5
52e577b
 
 
 
 
e1041f5
52e577b
 
 
 
 
 
3fac395
608b34b
52e577b
5dc4444
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52e577b
 
 
 
 
b834a5d
 
 
 
 
 
 
 
 
593d846
52e577b
 
 
 
 
 
 
 
b834a5d
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593d846
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa174ea
 
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c3561dd
52e577b
e1041f5
c3561dd
e1041f5
 
52e577b
e1041f5
 
 
 
52e577b
e1041f5
52e577b
 
 
3fac395
608b34b
52e577b
608b34b
52e577b
 
608b34b
e1041f5
52e577b
608b34b
e1041f5
52e577b
 
 
 
56784d5
52e577b
 
56784d5
593d846
52e577b
 
 
 
 
 
 
 
 
 
 
56784d5
024cf8a
593d846
024cf8a
 
 
593d846
024cf8a
52e577b
 
 
593d846
 
608b34b
 
52e577b
 
 
e1041f5
593d846
 
3fac395
52e577b
 
 
 
 
 
 
 
 
 
 
3fac395
52e577b
 
 
593d846
cc2dd99
56784d5
52e577b
dd06071
cc2dd99
52e577b
 
 
e1041f5
 
 
 
3fac395
 
608b34b
e1041f5
52e577b
 
 
 
 
 
 
 
 
e1041f5
52e577b
 
 
 
 
 
5dc4444
52e577b
593d846
 
 
5dc4444
593d846
52e577b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1041f5
608b34b
e1041f5
52e577b
608b34b
e1041f5
 
52e577b
 
 
 
 
 
 
8d226e4
dd06071
 
52e577b
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
import streamlit as st
import streamlit.components.v1 as components
import base64
import leafmap.maplibregl as leafmap
import altair as alt
import ibis
from ibis import _
import ibis.selectors as s
import os
import pandas as pd 
import geopandas as gpd
from shapely import wkb  
import sqlalchemy
import pathlib
from typing import Optional
from functools import reduce

from variables import *
from utils import *

## Create the table from remote parquet only if it doesn't already exist on disk
con = ibis.duckdb.connect("duck.db", extensions=["spatial"])
current_tables = con.list_tables()

if "mydata" not in set(current_tables):
    tbl = con.read_parquet(ca_parquet)
    con.create_table("mydata", tbl)

ca = con.table("mydata")

# session state for syncing app 
for key in [
    'richness', 'rsr', 'irrecoverable_carbon', 'manageable_carbon',
    'fire', 'rxburn', 'disadvantaged_communities',
    'svi']:
    if key not in st.session_state:
        st.session_state[key] = False

for col,val in style_options.items():
    for name in val['stops']:
        key = val['property']+str(name[0])
        if key not in st.session_state:
            st.session_state[key] = default_boxes.get(name[0], True)


st.set_page_config(layout="wide", page_title="CA Protected Areas Explorer", page_icon=":globe:")

#customizing style with CSS 
st.markdown(
    """
    <style>
        /* Customizing font size for radio text */
        div[class*="stRadio"] > label > div[data-testid="stMarkdownContainer"] > p {
            font-size: 18px !important;
        }
        /* Reduce margin below the header */
        h2 {
            margin-top: 0rem !important; 
            margin-bottom: 0rem !important; /* Reduce space below headers */
        }
        /* Customizing font size for medium-font class */
        .medium-font {
            font-size: 18px !important; 
            margin-top: 0rem !important;
            margin-bottom: 0.25rem !important; /* Reduce space below */
        }
        .medium-font-sidebar {
            font-size: 17px !important;
            margin-bottom: 0.75rem !important; /* Reduce space below */
        }
        /* Customizing layout and divider */
        hr {
            margin-top: 0rem !important;  /* Adjust to reduce top margin */
            margin-bottom: 0.5rem !important; /* Adjust to reduce bottom margin */
        }
        .stAppHeader {
            background-color: rgba(255, 255, 255, 0.0);  /* Transparent background */
            visibility: visible;  /* Ensure the header is visible */
        }
        .block-container {
            padding-top: 0.5rem;
            padding-bottom: 2rem;
            padding-left: 5rem;
            padding-right: 5rem;
        }
        /* Reduce whitespace for the overall expander container */
        .st-expander {
            margin-top: 0rem;  /* Space above the expander */
            margin-bottom: 0rem; /* Space below the expander */
        }
        /* Adjust padding for the content inside the expander */
        .st-expander-content {
            padding: 0rem 0rem;  /* Reduce padding inside */
        }
        /* Optional: Adjust the expander header if needed */
        .st-expander-header {
            margin-top: 0rem;
            margin-bottom: 0rem;
        }
    </style>
    """,
    unsafe_allow_html=True,
)


st.markdown(
    """
    <style>
        /* Remove or reduce whitespace at the top of the sidebar */
        [data-testid="stSidebar"] > div:first-child {
            padding-top: 0rem !important; 
        }
    </style>
    """,
    unsafe_allow_html=True,
)

st.markdown("<h2>CA 30x30 Planning & Assessment Prototype</h2>", unsafe_allow_html=True)


st.markdown('<p class="medium-font"> In October 2020, Governor Newsom issued <a href="https://www.gov.ca.gov/wp-content/uploads/2020/10/10.07.2020-EO-N-82-20-.pdf" target="_blank">Executive Order N-82-20</a>, which establishes a state goal of conserving 30% of California’s lands and coastal waters by 2030 – known as <a href="https://www.californianature.ca.gov/" target="_blank">CA 30x30</a>. </p>',
unsafe_allow_html=True)


st.markdown('<p class = "medium-font"> This is an interactive cloud-native geospatial tool for exploring and visualizing California\'s protected lands. </p>', unsafe_allow_html = True)

st.divider()

           
m = leafmap.Map(style="positron")
#############


##### Chatbot stuff 


from pydantic import BaseModel, Field
class SQLResponse(BaseModel):
    """Defines the structure for SQL response."""
    sql_query: str = Field(description="The SQL query generated by the assistant.")
    explanation: str = Field(description="A detailed explanation of how the SQL query answers the input question.")

with open('app/system_prompt.txt', 'r') as file:
    template = file.read()

from langchain_openai import ChatOpenAI

#llm = ChatOpenAI(model = "kosbu/Llama-3.3-70B-Instruct-AWQ", api_key = st.secrets['CIRRUS_LLM_API_KEY'], base_url = "https://llm.cirrus.carlboettiger.info/v1/",  temperature=0)
# llm = ChatOpenAI(model="gpt-4", temperature=0)
# llm = ChatOpenAI(model = "llama3", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/",  temperature=0)
llm = ChatOpenAI(model = "groq-tools", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/",  temperature=0)


managers = ca.sql("SELECT DISTINCT manager FROM mydata;").execute()
names = ca.sql("SELECT name FROM mydata GROUP BY name HAVING SUM(acres) >10000;").execute()
ecoregions = ca.sql("SELECT DISTINCT ecoregion FROM mydata;").execute()

from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", "{input}")
]).partial(dialect="duckdb", table_info = ca.schema(), managers = managers, names = names, ecoregions = ecoregions)

structured_llm = llm.with_structured_output(SQLResponse)
few_shot_structured_llm = prompt | structured_llm

chatbot_toggles = {key: False for key in [
    'richness', 'rsr', 'irrecoverable_carbon', 'manageable_carbon',
    'fire', 'rxburn', 'disadvantaged_communities',
    'svi', 
]}

def run_sql(query,color_choice):
    """
    Filter data based on an LLM-generated SQL query and return matching IDs.

    Args:
        query (str): The natural language query to filter the data.
        color_choice (str): The column used for plotting.
    """
    output = few_shot_structured_llm.invoke(query)
    sql_query = output.sql_query
    explanation =output.explanation
    if not sql_query: # if the chatbot can't generate a SQL query.
        st.success(explanation)
        return pd.DataFrame({'id' : []})
        
    result = ca.sql(sql_query).execute()
    if result.empty :
        explanation = "This query did not return any results. Please try again with a different query."
        st.warning(explanation, icon="⚠️")
        st.caption("SQL Query:")
        st.code(sql_query,language = "sql") 
        if 'geom' in result.columns:
            return result.drop('geom',axis = 1)
        else: 
            return result
    
    elif ("id" and "geom" in result.columns): 
        style = get_pmtiles_style_llm(style_options[color_choice], result["id"].tolist())
        legend, position, bg_color, fontsize = getLegend(style_options,color_choice)

        m.add_legend(legend_dict = legend, position = position, bg_color = bg_color, fontsize = fontsize)
        m.add_pmtiles(ca_pmtiles, style=style, opacity=alpha, tooltip=True, fit_bounds=True)
        m.fit_bounds(result.total_bounds.tolist())    
        result = result.drop('geom',axis = 1) #printing to streamlit so I need to drop geom
    else:   
        st.write(result)  # if we aren't mapping, just print out the data  

    with st.popover("Explanation"):
        st.write(explanation)
        st.caption("SQL Query:")
        st.code(sql_query,language = "sql") 
        
    return result




#############

filters = {}

with st.sidebar:
    with st.popover("ℹ️ Help"):
        '''
        - ❌ Safari/iOS not yet supported. For Safari/iOS users, try [this version](https://huggingface.co/spaces/boettiger-lab/ca-30x30-folium) with similar functionality. 
        - 📊 Use this sidebar to color-code the map by different attributes **(Group by)**, toggle on data layers and view summary charts **(Data Layers)**, or filter data **(Filters)**.
        - 💬 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).
        '''

    
    st.divider()
    color_choice = st.radio("Group by:", style_options, key = "color", help = "Select a category to change map colors and chart groupings.")   
    colorby_vals = getColorVals(style_options, color_choice) #get options for selected color_by column 
    alpha = 0.8
    st.divider()


##### Chatbot 
with st.container():

    with st.popover("💬 Example Queries"):
        '''
        Mapping queries:        
        - Show me areas open to the public that are in the top 10% of species richness.
        - Show me all GAP 1 and 2 lands managed by The Nature Conservancy.
        - Show me state land smaller than 1000 acres, with a social vulnerability index in the 90th percentile.
        - Show me GAP 3 and 4 lands managed by BLM in the top 5% of range-size rarity.
        - Show me Joshua Tree National Park.
        - Show me all protected lands that have experienced forest fire over at least 50% of their area.
        - Show me the biggest protected area in California. 
        - Show me all land managed by the United States Forest Service. 
        '''
        
        '''
        Exploratory data queries:
        - What is a GAP code?
        - What percentage of 30x30 conserved land has been impacted by wildfire?
        - What is the total acreage of areas designated as easements?
        - Who manages the land with the highest amount of irrecoverable carbon and highest social vulnerability index? 
        '''
        
        st.info('If the map appears blank, queried data may be too small to see at the default zoom level. Check the table below the map, as query results will also be displayed there.', icon="ℹ️")


    example_query = "👋 Input query here"
    if prompt := st.chat_input(example_query, key="chain", max_chars = 300):
        st.chat_message("user").write(prompt)

        try:
            with st.chat_message("assistant"):
                with st.spinner("Invoking query..."):

                    out = run_sql(prompt,color_choice)
                    if ("id" in out.columns) and (not out.empty):
                        ids = out['id'].tolist()
                        cols = out.columns.tolist()
                        chatbot_toggles = {
                                key: (True if key in cols else value) 
                                for key, value in chatbot_toggles.items()
                            }
                        for key, value in chatbot_toggles.items():
                            st.session_state[key] = value  # Update session state
                    else:
                        ids = []
        except Exception as e:
            error_message = f"ERROR: An unexpected error has occured with the following query:\n\n*{prompt}*\n\n which raised the following error:\n\n{type(e)}: {e}\n"
            st.warning("Please try again with a different query", icon="⚠️")
            st.write(error_message)
            st.stop()


#### Data layers 
with st.sidebar:  
    st.markdown('<p class = "medium-font-sidebar"> Data Layers:</p>', help = "Select data layers to visualize on the map. Summary charts will update based on the displayed layers.", unsafe_allow_html= True)
    # Biodiversity Section 
    with st.expander("🦜 Biodiversity"):
        a_bio = st.slider("transparency", 0.0, 1.0, 0.1, key = "biodiversity")
        show_richness = st.toggle("Species Richness", key = "richness", value=chatbot_toggles['richness'])
        show_rsr = st.toggle("Range-Size Rarity", key = "rsr", value=chatbot_toggles['rsr'])
        
        if show_richness:
            m.add_tile_layer(url_sr, name="MOBI Species Richness",opacity=a_bio)
        if show_rsr:           
            m.add_tile_layer(url_rsr, name="MOBI Range-Size Rarity", opacity=a_bio)

    #Carbon Section
    with st.expander("⛅ Carbon & Climate"):
        a_climate = st.slider("transparency", 0.0, 1.0, 0.15, key = "climate")
        show_irrecoverable_carbon = st.toggle("Irrecoverable Carbon", key = "irrecoverable_carbon", value=chatbot_toggles['irrecoverable_carbon'])
        show_manageable_carbon = st.toggle("Manageable Carbon", key = "manageable_carbon", value=chatbot_toggles['manageable_carbon'])
        
        if show_irrecoverable_carbon:
            m.add_cog_layer(url_irr_carbon, palette="reds", name="Irrecoverable Carbon", opacity = a_climate, fit_bounds=False)
        
        if show_manageable_carbon:
           m.add_cog_layer(url_man_carbon, palette="purples", name="Manageable Carbon", opacity = a_climate, fit_bounds=False)


    # People Section 
    with st.expander("👤 People"):
        a_people = st.slider("transparency", 0.0, 1.0, 0.1, key = "SVI")
        show_justice40 = st.toggle("Disadvantaged Communities (Justice40)", key = "disadvantaged_communities", value=chatbot_toggles['disadvantaged_communities'])
        show_sv = st.toggle("Social Vulnerability Index (SVI)", key = "svi", value=chatbot_toggles['svi'])
        
        if show_justice40:
            m.add_pmtiles(url_justice40, style=justice40_style, name="Justice40", opacity=a_people, tooltip=False, fit_bounds = False)
            
        if show_sv:
            m.add_pmtiles(url_svi, style = svi_style, opacity=a_people, tooltip=False, fit_bounds = False)
        
    # Fire Section
    with st.expander("🔥 Fire"):
        a_fire = st.slider("transparency", 0.0, 1.0, 0.15, key = "calfire")
        show_fire = st.toggle("Fires (2013-2023)", key = "fire", value=chatbot_toggles['fire'])

        show_rxburn = st.toggle("Prescribed Burns (2013-2023)", key = "rxburn", value=chatbot_toggles['rxburn'])


        if show_fire:
            m.add_pmtiles(url_calfire, style=fire_style, name="CALFIRE Fire Polygons (2013-2023)", opacity=a_fire, tooltip=False, fit_bounds = False)

        if show_rxburn:
            m.add_pmtiles(url_rxburn, style=rx_style, name="CAL FIRE Prescribed Burns (2013-2023)", opacity=a_fire, tooltip=False, fit_bounds = False)
                    

    st.divider()
    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)

    for label in style_options: # get selected filters (based on the buttons selected)
        with st.expander(label):  
            if label in ["GAP Code","30x30 Status"]: # gap code 1 and 2 are on by default
                opts = getButtons(style_options, label, default_boxes)
            else: # other buttons are not on by default.
                opts = getButtons(style_options, label) 
            filters.update(opts)
            
        selected = {k: v for k, v in filters.items() if v}
        if selected: 
            filter_cols = list(selected.keys())
            filter_vals = list(selected.values())
        else: 
            filter_cols = []
            filter_vals = []

    st.divider()
    # adding github logo 
    st.markdown("""
    <p class="medium-font-sidebar">
    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' class='bi bi-github ' style='height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;margin-right:4px;'  aria-hidden='true' role='img'><path d='M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z'></path></svg>Source Code: </p> <a href='https://github.com/boettiger-lab/ca-30x30' target='_blank'>https://github.com/boettiger-lab/ca-30x30</a>
    """, unsafe_allow_html=True)

# Display CA 30x30 Data
if 'out' not in locals():
    style = get_pmtiles_style(style_options[color_choice], alpha, filter_cols, filter_vals)
    legend, position, bg_color, fontsize = getLegend(style_options, color_choice)
    m.add_legend(legend_dict = legend, position = position, bg_color = bg_color, fontsize = fontsize)
    m.add_pmtiles(ca_pmtiles, style=style, name="CA", opacity=alpha, tooltip=True, fit_bounds=True)
    
column = select_column[color_choice]

select_colors = {
    "30x30 Status": status["stops"],
    "GAP Code": gap["stops"],
    "Year": year["stops"],
    "Ecoregion": ecoregion["stops"],
    "Manager Type": manager["stops"],
    "Easement": easement["stops"],
    "Access Type": access["stops"],
}

colors = (
    ibis
    .memtable(select_colors[color_choice], columns=[column, "color"])
    .to_pandas()
)


# get summary tables used for charts + printed table 
# df - charts; df_tab - printed table (omits colors) 
if 'out' not in locals():
    df, df_tab, df_percent, df_bar_30x30 = summary_table(ca, column, select_colors, color_choice, filter_cols, filter_vals,colorby_vals)
    total_percent = 100*df_percent.percent_CA.sum()

else:
    df = summary_table_sql(ca, column, colors, ids)
    total_percent = 100*df.percent_CA.sum()


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

main = st.container()

with main:
    map_col, stats_col = st.columns([2,1])

    with map_col:
        m.to_streamlit(height=650)
        if 'out' not in locals():
            st.dataframe(df_tab, use_container_width = True)  
        else:
            st.dataframe(out, use_container_width = True)

    with stats_col:
        with st.container():
            
            st.markdown(f"{total_percent}% CA Protected", help = "Total percentage of 30x30 conserved lands, updates based on displayed data")
            st.altair_chart(area_plot(df, column), use_container_width=True)
            
            if 'df_bar_30x30' in locals(): #if we use chatbot, we won't have these graphs.
                if column not in ["status", "gap_code"]:
                    st.altair_chart(stacked_bar(df_bar_30x30, column,'percent_group','status', color_choice + ' by 30x30 Status',colors), use_container_width=True)

            if show_richness:
                st.altair_chart(richness_chart, use_container_width=True)

            if show_rsr:
                st.altair_chart(rsr_chart, use_container_width=True)

            if show_irrecoverable_carbon:
                st.altair_chart(irr_carbon_chart, use_container_width=True)

            if show_manageable_carbon:
                st.altair_chart(man_carbon_chart, use_container_width=True)

            if show_justice40:
                st.altair_chart(justice40_chart, use_container_width=True)
                
            if show_sv:
                st.altair_chart(svi_chart, use_container_width=True)

            if show_fire:
                st.altair_chart(fire_10_chart, use_container_width=True)
                
            if show_rxburn:
                st.altair_chart(rx_10_chart, use_container_width=True)


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 code: https://github.com/boettiger-lab/ca-30x30.") 

st.caption("***Under California’s 30x30 framework, only GAP codes 1 and 2 are counted toward the conservation goal.") 

st.divider()

with open('app/footer.md', 'r') as file:
    footer = file.read()
st.markdown(footer)