import streamlit as st
import pandas as pd
import json
import sqlite3
import os
from datetime import datetime, date
import folium
from streamlit_folium import st_folium
import io
import base64
from PIL import Image
import uuid
# Configuración de la página
st.set_page_config(
page_title="Ojo nativo",
page_icon="🦋",
layout="wide",
initial_sidebar_state="expanded"
)
# Inicializar base de datos SQLite
def init_db():
conn = sqlite3.connect('biodiversity.db')
cursor = conn.cursor()
# Crear tabla de observaciones
cursor.execute("""
CREATE TABLE IF NOT EXISTS observations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
species TEXT NOT NULL,
common_name TEXT,
family TEXT,
order_tax TEXT,
kingdom TEXT,
lat REAL,
lng REAL,
region TEXT,
date TEXT,
observer TEXT,
status TEXT,
habitat TEXT,
description TEXT,
image_data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Crear tabla de usuarios/forum
cursor.execute("""
CREATE TABLE IF NOT EXISTS forum_posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
author TEXT NOT NULL,
species TEXT,
lat REAL,
lng REAL,
image_data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
likes INTEGER DEFAULT 0
)
""")
conn.commit()
conn.close()
# Cargar datos de taxonomía
@st.cache_data
def load_taxonomy():
try:
with open('taxonomy_data.json', 'r', encoding='utf-8') as f:
return json.load(f)
except:
return {"Reino": {}}
# Cargar regiones de Argentina
@st.cache_data
def load_regions():
try:
with open('argentina_regions.json', 'r', encoding='utf-8') as f:
return json.load(f)
except:
return {}
# Obtener todas las especies de la taxonomía
def get_all_species(taxonomy_data):
species = []
for reino in taxonomy_data.get("Reino", {}).values():
for phylum in reino.get("Phylum", {}).values():
for clase in phylum.get("Clase", {}).values():
for orden in clase.get("Orden", {}).values():
if "especies" in orden:
species.extend(orden["especies"])
return species
# Función para convertir imagen a base64
def image_to_base64(image):
buffer = io.BytesIO()
image.save(buffer, format="PNG")
return base64.b64encode(buffer.getvalue()).decode()
# Función para mostrar mapa
def create_map(observations_df, regions):
# Centro de Argentina
center_lat, center_lng = -38.416097, -63.616672
m = folium.Map(location=[center_lat, center_lng], zoom_start=5)
# Agregar observaciones al mapa
for _, obs in observations_df.iterrows():
if pd.notna(obs['lat']) and pd.notna(obs['lng']):
popup_text = f"""
{obs['common_name']}
{obs['species']}
Familia: {obs['family']}
Región: {obs['region']}
Fecha: {obs['date']}
Observador: {obs['observer']}
"""
color = 'green' if obs['status'] == 'nativa_endemica' else 'blue'
folium.Marker(
location=[obs['lat'], obs['lng']],
popup=folium.Popup(popup_text, max_width=300),
tooltip=obs['common_name'],
icon=folium.Icon(color=color, icon='leaf')
).add_to(m)
# Agregar marcadores de regiones
for region, coords in regions.items():
folium.CircleMarker(
location=[coords['lat'], coords['lng']],
radius=8,
popup=f"Región: {region}",
color='red',
fill=True,
fillColor='red',
fillOpacity=0.6
).add_to(m)
return m
# Función principal
def main():
init_db()
# CSS personalizado
st.markdown("""
""", unsafe_allow_html=True)
# Header principal
st.markdown("""
🌿 Ojo nativo - Sistema de Información de Biodiversidad 🦋
Conservación de Flora, Fauna y Fungus Nativas y Endémicas de Argentina
""", unsafe_allow_html=True)
# Sidebar para navegación
st.sidebar.title("🧭 Navegación")
page = st.sidebar.radio("Seleccionar sección:", [
"🏠 Inicio",
"🗺️ Mapa Interactivo",
"🔍 Buscador de Especies",
"📷 Subir Observación",
"💬 Foro Comunitario",
"📊 Estadísticas",
"📚 Base de Datos"
])
# Cargar datos
taxonomy_data = load_taxonomy()
regions = load_regions()
# Conectar a base de datos
conn = sqlite3.connect('biodiversity.db')
if page == "🏠 Inicio":
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Especies Registradas", "150+")
st.metric("Observaciones", "1,247")
with col2:
st.metric("Regiones Cubiertas", "10")
st.metric("Colaboradores", "89")
with col3:
st.metric("Especies Endémicas", "45")
st.metric("En Peligro", "12")
st.markdown("### 🎯 Objetivos del Proyecto")
st.write("""
- **Conservación**: Documentar y proteger la biodiversidad argentina
- **Investigación**: Facilitar estudios científicos sobre especies nativas
- **Educación**: Promover la conciencia ambiental
- **Participación**: Involucrar a la comunidad en ciencia ciudadana
""")
st.markdown("### 📈 Últimas Actividades")
recent_observations = pd.read_sql_query(
"SELECT * FROM observations ORDER BY created_at DESC LIMIT 5",
conn
)
if not recent_observations.empty:
for _, obs in recent_observations.iterrows():
st.markdown(f"""
{obs['common_name']} ({obs['species']})
📍 {obs['region']} | 📅 {obs['date']} | 👤 {obs['observer']}
""", unsafe_allow_html=True)
elif page == "🗺️ Mapa Interactivo":
st.header("🗺️ Mapa de Observaciones de Biodiversidad")
# Filtros
col1, col2, col3 = st.columns(3)
with col1:
kingdom_filter = st.selectbox("Reino:", ["Todos"] + list(taxonomy_data.get("Reino", {}).keys()))
with col2:
region_filter = st.selectbox("Región:", ["Todas"] + list(regions.keys()))
with col3:
status_filter = st.selectbox("Estado:", ["Todos", "nativa_endemica", "nativa", "introducida"])
# Cargar observaciones
query = "SELECT * FROM observations WHERE 1=1"
params = []
if kingdom_filter != "Todos":
query += " AND kingdom = ?"
params.append(kingdom_filter)
if region_filter != "Todas":
query += " AND region = ?"
params.append(region_filter)
if status_filter != "Todos":
query += " AND status = ?"
params.append(status_filter)
observations_df = pd.read_sql_query(query, conn, params=params)
if not observations_df.empty:
# Crear y mostrar mapa
map_obj = create_map(observations_df, regions)
st_folium(map_obj, width=700, height=500)
st.write(f"**{len(observations_df)} observaciones encontradas**")
else:
st.info("No se encontraron observaciones con los filtros seleccionados.")
elif page == "🔍 Buscador de Especies":
st.header("🔍 Búsqueda Taxonómica")
col1, col2 = st.columns(2)
with col1:
search_term = st.text_input("Buscar especie:", placeholder="Ej: Puma concolor")
search_type = st.radio("Buscar por:", ["Nombre científico", "Nombre común", "Familia"])
with col2:
kingdom = st.selectbox("Reino:", ["Todos"] + list(taxonomy_data.get("Reino", {}).keys()))
order_tax = st.text_input("Orden:", placeholder="Ej: Carnivora")
if st.button("🔍 Buscar"):
query = "SELECT * FROM observations WHERE 1=1"
params = []
if search_term:
if search_type == "Nombre científico":
query += " AND species LIKE ?"
params.append(f"%{search_term}%")
elif search_type == "Nombre común":
query += " AND common_name LIKE ?"
params.append(f"%{search_term}%")
elif search_type == "Familia":
query += " AND family LIKE ?"
params.append(f"%{search_term}%")
if kingdom != "Todos":
query += " AND kingdom = ?"
params.append(kingdom)
if order_tax:
query += " AND order_tax LIKE ?"
params.append(f"%{order_tax}%")
results = pd.read_sql_query(query, conn, params=params)
if not results.empty:
st.success(f"Se encontraron {len(results)} resultados:")
for _, result in results.iterrows():
with st.expander(f"{result['common_name']} ({result['species']})"):
col1, col2 = st.columns(2)
with col1:
st.write(f"**Familia:** {result['family']}")
st.write(f"**Orden:** {result['order_tax']}")
st.write(f"**Reino:** {result['kingdom']}")
st.write(f"**Estado:** {result['status']}")
with col2:
st.write(f"**Región:** {result['region']}")
st.write(f"**Hábitat:** {result['habitat']}")
st.write(f"**Fecha:** {result['date']}")
st.write(f"**Observador:** {result['observer']}")
st.write(f"**Descripción:** {result['description']}")
else:
st.warning("No se encontraron resultados.")
elif page == "📷 Subir Observación":
st.header("📷 Nueva Observación de Biodiversidad")
with st.form("observation_form"):
col1, col2 = st.columns(2)
with col1:
species = st.text_input("Nombre científico*", placeholder="Ej: Puma concolor")
common_name = st.text_input("Nombre común*", placeholder="Ej: Puma")
family = st.text_input("Familia*", placeholder="Ej: Felidae")
order_tax = st.text_input("Orden*", placeholder="Ej: Carnivora")
kingdom = st.selectbox("Reino*", ["", "Animalia", "Plantae", "Fungi"])
with col2:
region = st.selectbox("Región*", [""] + list(regions.keys()))
lat = st.number_input("Latitud", value=0.0, format="%.6f")
lng = st.number_input("Longitud", value=0.0, format="%.6f")
observation_date = st.date_input("Fecha de observación", value=date.today())
observer = st.text_input("Nombre del observador*", placeholder="Tu nombre")
status = st.selectbox("Estado de conservación", [
"", "nativa_endemica", "nativa", "introducida", "en_peligro"
])
habitat = st.text_input("Hábitat", placeholder="Ej: Bosque andino patagónico")
description = st.text_area("Descripción", placeholder="Describe tu observación...")
uploaded_file = st.file_uploader("Subir imagen", type=['png', 'jpg', 'jpeg'])
submitted = st.form_submit_button("📤 Subir Observación")
if submitted:
if species and common_name and family and order_tax and kingdom and region and observer:
# Procesar imagen si fue subida
image_data = None
if uploaded_file is not None:
image = Image.open(uploaded_file)
# Redimensionar imagen
image.thumbnail((500, 500))
image_data = image_to_base64(image)
# Insertar en base de datos
cursor = conn.cursor()
cursor.execute("""
INSERT INTO observations
(species, common_name, family, order_tax, kingdom, lat, lng, region, date, observer, status, habitat, description, image_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (species, common_name, family, order_tax, kingdom, lat, lng, region,
str(observation_date), observer, status, habitat, description, image_data))
conn.commit()
st.success("✅ Observación registrada exitosamente!")
st.balloons()
else:
st.error("Por favor completa todos los campos obligatorios (*)")
elif page == "💬 Foro Comunitario":
st.header("💬 Foro de la Comunidad")
# Formulario para nuevo post
with st.expander("✏️ Crear nueva publicación"):
with st.form("forum_form"):
title = st.text_input("Título*")
content = st.text_area("Contenido*", height=100)
author = st.text_input("Autor*")
species = st.text_input("Especie relacionada (opcional)")
col1, col2 = st.columns(2)
with col1:
post_lat = st.number_input("Latitud (opcional)", value=0.0)
with col2:
post_lng = st.number_input("Longitud (opcional)", value=0.0)
forum_image = st.file_uploader("Imagen (opcional)", type=['png', 'jpg', 'jpeg'])
if st.form_submit_button("📝 Publicar"):
if title and content and author:
image_data = None
if forum_image is not None:
image = Image.open(forum_image)
image.thumbnail((400, 400))
image_data = image_to_base64(image)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO forum_posts (title, content, author, species, lat, lng, image_data)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (title, content, author, species, post_lat, post_lng, image_data))
conn.commit()
st.success("Publicación creada!")
st.rerun()
else:
st.error("Completa los campos obligatorios")
# Mostrar posts del foro
forum_posts = pd.read_sql_query(
"SELECT * FROM forum_posts ORDER BY created_at DESC", conn
)
if not forum_posts.empty:
for _, post in forum_posts.iterrows():
st.markdown(f"""
{post['title']}
Por: {post['author']} | {post['created_at']}
{f"
Especie: {post['species']}" if post['species'] else ""}
""", unsafe_allow_html=True)
st.write(post['content'])
if post['image_data']:
try:
image_bytes = base64.b64decode(post['image_data'])
image = Image.open(io.BytesIO(image_bytes))
st.image(image, width=300)
except:
pass
col1, col2, col3 = st.columns([1, 1, 4])
with col1:
if st.button(f"👍 {post['likes']}", key=f"like_{post['id']}"):
cursor = conn.cursor()
cursor.execute("UPDATE forum_posts SET likes = likes + 1 WHERE id = ?", (post['id'],))
conn.commit()
st.rerun()
st.divider()
else:
st.info("No hay publicaciones aún. ¡Sé el primero en compartir!")
elif page == "📊 Estadísticas":
st.header("📊 Estadísticas de Biodiversidad")
# Consultas para estadísticas
total_obs = pd.read_sql_query("SELECT COUNT(*) as count FROM observations", conn).iloc[0]['count']
total_species = pd.read_sql_query("SELECT COUNT(DISTINCT species) as count FROM observations", conn).iloc[0]['count']
total_families = pd.read_sql_query("SELECT COUNT(DISTINCT family) as count FROM observations", conn).iloc[0]['count']
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Observaciones", total_obs)
col2.metric("Especies Únicas", total_species)
col3.metric("Familias", total_families)
col4.metric("Regiones Activas", len(regions))
# Gráficos por reino
if total_obs > 0:
kingdom_stats = pd.read_sql_query(
"SELECT kingdom, COUNT(*) as count FROM observations GROUP BY kingdom", conn
)
if not kingdom_stats.empty:
st.subheader("Observaciones por Reino")
st.bar_chart(kingdom_stats.set_index('kingdom'))
# Top especies
top_species = pd.read_sql_query(
"SELECT species, common_name, COUNT(*) as observations FROM observations GROUP BY species ORDER BY observations DESC LIMIT 10",
conn
)
if not top_species.empty:
st.subheader("Top 10 Especies Más Observadas")
st.dataframe(top_species)
elif page == "📚 Base de Datos":
st.header("📚 Base de Datos Completa")
# Mostrar todas las observaciones
all_observations = pd.read_sql_query("SELECT * FROM observations ORDER BY created_at DESC", conn)
if not all_observations.empty:
st.write(f"Total de registros: {len(all_observations)}")
# Filtros avanzados
col1, col2, col3 = st.columns(3)
with col1:
filter_kingdom = st.multiselect("Filtrar por Reino:", all_observations['kingdom'].unique())
with col2:
filter_region = st.multiselect("Filtrar por Región:", all_observations['region'].unique())
with col3:
filter_status = st.multiselect("Filtrar por Estado:", all_observations['status'].unique())
# Aplicar filtros
filtered_df = all_observations.copy()
if filter_kingdom:
filtered_df = filtered_df[filtered_df['kingdom'].isin(filter_kingdom)]
if filter_region:
filtered_df = filtered_df[filtered_df['region'].isin(filter_region)]
if filter_status:
filtered_df = filtered_df[filtered_df['status'].isin(filter_status)]
# Mostrar tabla
st.dataframe(filtered_df, use_container_width=True)
# Opción de descarga
csv = filtered_df.to_csv(index=False)
st.download_button(
label="📥 Descargar datos (CSV)",
data=csv,
file_name=f"biodiversidad_argentina_{datetime.now().strftime('%Y%m%d')}.csv",
mime="text/csv"
)
else:
st.info("No hay datos en la base de datos aún.")
# Cerrar conexión
conn.close()
# Footer
st.markdown("---")
st.markdown("""
🌱 Ojo nativo - Conservando la biodiversidad para las futuras generaciones 🌱
Desarrollado para la comunidad científica y educativa de Argentina
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()