📖 Glossário - Casos Práticos
A
Análise Espacial: Estudo de padrões, processos e relações espaciais de fenômenos.
Automação: Uso de scripts para executar tarefas repetitivas automaticamente.
API REST: Interface de programação que usa protocolo HTTP para comunicação.
B
Biodiversidade: Variedade de vida em diferentes níveis (espécies, genes, ecossistemas).
Baseline: Linha de referência ou condição inicial para comparação.
C
Coastal Monitoring (Monitoramento Costeiro): Acompanhamento sistemático de mudanças em áreas litorâneas.
Coleta de Dados: Processo de obter informações para análise.
Concentração: Quantidade de substância em volume/área específico.
Configuração: Ajustes e parâmetros de sistema ou aplicação.
D
Dashboard: Painel visual com informações resumidas e gráficos.
Dados Ambientais: Informações sobre meio ambiente (temperatura, poluição, etc).
Dataset: Conjunto de dados relacionados.
Deploy Automático: Publicação automática de aplicação quando código é atualizado.
Distribuição Espacial: Como fenômeno está disperso geograficamente.
E
Ecossistema: Conjunto de organismos e ambiente físico interagindo.
Erosão Costeira: Desgaste e remoção de material da costa por ondas e correntes.
Espécie: Grupo de organismos que podem se reproduzir entre si.
ETL (Extract, Transform, Load): Processo de extrair, transformar e carregar dados.
F
Filtro: Operação que seleciona subconjunto de dados baseado em critério.
Folium: Biblioteca Python para criar mapas interativos.
Frontend: Interface visual de aplicação que usuário vê.
G
Geolocalização: Identificação de posição geográfica de objeto/pessoa.
Gradiente: Variação gradual de valor ao longo do espaço.
Gráfico Interativo: Visualização que permite exploração dinâmica dos dados.
H
Heatmap (Mapa de Calor): Visualização que usa cor para mostrar intensidade/densidade.
HTML Dinâmico: Página web que muda conteúdo sem recarregar.
Hospedagem: Serviço que mantém site/aplicação acessível na internet.
I
Indicador Ambiental: Métrica que representa condição ambiental.
Integração: Conexão entre diferentes sistemas/componentes.
Interface: Meio de interação entre usuário e sistema.
L
Layout Responsivo: Design que se adapta a diferentes tamanhos de tela.
Log: Registro de eventos/ações em sistema.
M
Machine Learning: Técnicas computacionais para aprender padrões em dados.
Metadados: Dados que descrevem outros dados (autor, data, fonte).
Monitoramento: Observação sistemática e contínua de fenômeno.
Multiespectral: Dados capturados em múltiplas faixas do espectro eletromagnético.
N
Normalização: Ajuste de dados para escala comum.
Notebook: Documento interativo misturando código, texto e visualizações.
O
Observação: Registro individual de medição ou evento.
Outlier: Valor atípico que destoa do padrão geral.
P
Padrão Espacial: Arranjo não-aleatório de fenômenos no espaço.
Parâmetro: Valor que controla comportamento de função/sistema.
Pipeline: Sequência automatizada de etapas de processamento.
Plotly: Biblioteca para criar gráficos interativos.
Ponto de Coleta: Local onde amostra foi obtida.
Predição: Estimativa de valor futuro baseada em modelo.
Projeto: Conjunto organizado de arquivos e configurações.
Q
Qualidade da Água: Condições químicas, físicas e biológicas de corpo d'água.
Query: Consulta para buscar dados específicos.
R
Rastro (Track): Sequência de posições mostrando movimento ao longo do tempo.
Relatório Automático: Documento gerado automaticamente com dados atualizados.
Repositório: Local onde código e arquivos de projeto são armazenados.
Resolução Espacial: Tamanho mínimo de feição detectável em imagem/mapa.
ROI (Region of Interest): Área geográfica de interesse para análise.
S
Salinidade: Quantidade de sal dissolvido em água.
Sensor: Dispositivo que detecta e mede fenômenos físicos.
Script: Programa automatizado para executar tarefas.
Série Temporal: Sequência de observações ao longo do tempo.
Sistema: Conjunto integrado de componentes interagindo.
T
Template: Modelo base reutilizável para criar documentos/páginas.
Threshold (Limiar): Valor que define limite para decisão/classificação.
Timezone: Fuso horário.
Tooltip: Dica informativa que aparece ao passar mouse sobre elemento.
Transecto: Linha ao longo da qual observações são feitas.
V
Validação: Verificação de dados/resultados para garantir qualidade.
Variável Ambiental: Característica mensurável do ambiente.
Visualização: Representação gráfica de dados.
W
Workflow (Fluxo de Trabalho): Sequência de etapas para completar tarefa.
Web App: Aplicação que roda no navegador.
Exemplos Práticos
Dashboard Completo com Plotly
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Carregar dados
df = pd.read_csv('monitoramento_costeiro.csv', parse_dates=['data'])
# Criar dashboard com subplots
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('Temperatura', 'Salinidade', 'pH', 'Tendência'),
specs=[[{"type": "scatter"}, {"type": "scatter"}],
[{"type": "scatter"}, {"type": "scatter"}]]
)
# Gráfico 1: Temperatura
fig.add_trace(
go.Scatter(x=df['data'], y=df['temperatura'], name='Temp'),
row=1, col=1
)
# Gráfico 2: Salinidade
fig.add_trace(
go.Scatter(x=df['data'], y=df['salinidade'], name='Sal'),
row=1, col=2
)
# Gráfico 3: pH
fig.add_trace(
go.Scatter(x=df['data'], y=df['ph'], name='pH'),
row=2, col=1
)
# Gráfico 4: Tendência
media_movel = df.groupby(df['data'].dt.to_period('M'))['temperatura'].mean()
fig.add_trace(
go.Scatter(x=media_movel.index.astype(str), y=media_movel.values, name='Média'),
row=2, col=2
)
# Layout
fig.update_layout(
height=800,
title_text="Dashboard de Monitoramento Costeiro",
showlegend=True
)
fig.write_html('dashboard.html')
print("Dashboard gerado: dashboard.html")
Análise de Distribuição Espacial
import geopandas as gpd
import matplotlib.pyplot as plt
from scipy.spatial import distance_matrix
# Carregar dados
pontos = gpd.read_file('coletas.geojson')
# Calcular densidade por área
from shapely.geometry import box
bbox = pontos.total_bounds
area = gpd.GeoDataFrame(geometry=[box(*bbox)], crs=pontos.crs)
area_km2 = area.to_crs('EPSG:31982').area[0] / 1_000_000
densidade = len(pontos) / area_km2
print(f"Densidade: {densidade:.2f} pontos/km²")
# Calcular distância média entre pontos
coords = list(zip(pontos.geometry.x, pontos.geometry.y))
dist_matrix = distance_matrix(coords, coords)
dist_media = dist_matrix[dist_matrix > 0].mean()
print(f"Distância média: {dist_media:.4f}°")
# Plotar mapa de calor
fig, ax = plt.subplots(figsize=(12, 10))
pontos.plot(column='abundancia', cmap='YlOrRd', legend=True, ax=ax)
ax.set_title('Distribuição Espacial de Espécies')
plt.savefig('distribuicao.png', dpi=300, bbox_inches='tight')
Automação de Relatório
from datetime import datetime
import folium
def gerar_relatorio_automatico(dados_csv, saida_html):
"""Gera relatório HTML com mapa e estatísticas"""
# Carregar dados
df = pd.read_csv(dados_csv)
# Calcular estatísticas
stats = df.describe()
ultima_coleta = df['data'].max()
# Criar mapa
mapa = folium.Map(location=[-27.5, -48.5], zoom_start=10)
for idx, row in df.iterrows():
folium.Marker(
[row['lat'], row['lon']],
popup=f"{row['nome']}<br>Temp: {row['temperatura']}°C",
icon=folium.Icon(color='blue')
).add_to(mapa)
# HTML do relatório
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>Relatório - {datetime.now().strftime('%Y-%m-%d')}</title>
<style>
body {{ font-family: Arial; margin: 20px; }}
table {{ border-collapse: collapse; width: 100%; }}
td, th {{ border: 1px solid #ddd; padding: 8px; }}
</style>
</head>
<body>
<h1>Relatório de Monitoramento Costeiro</h1>
<p><strong>Gerado em:</strong> {datetime.now().strftime('%d/%m/%Y %H:%M')}</p>
<p><strong>Última coleta:</strong> {ultima_coleta}</p>
<h2>Estatísticas</h2>
{stats.to_html()}
<h2>Mapa de Pontos</h2>
{mapa._repr_html_()}
</body>
</html>
"""
with open(saida_html, 'w', encoding='utf-8') as f:
f.write(html)
print(f"Relatório gerado: {saida_html}")
# Usar
gerar_relatorio_automatico('coletas.csv', 'relatorio.html')
Workflow Típico
Coleta → Limpeza → Análise → Visualização → Dashboard → Publicação
- Coleta: Obter dados de sensores/planilhas
- Limpeza: Remover erros e normalizar
- Análise: Calcular estatísticas e padrões
- Visualização: Criar gráficos e mapas
- Dashboard: Integrar tudo em painel
- Publicação: Disponibilizar na web
💡 Dica: Sempre documente seu workflow para facilitar automação futura!
📊 Caso Prático 1: Monitoramento Costeiro
🎯 Objetivo do Projeto
Criar um sistema completo de análise temporal de coletas de macroalgas em praias de Santa Catarina, usando Python + pandas + matplotlib.
O que faremos: - Carregar dados de múltiplas coletas - Analisar tendências temporais - Identificar padrões sazonais - Gerar relatórios visuais
📋 Contexto da Pesquisa
O LAFIC realiza coletas mensais em 5 praias de Florianópolis para monitorar: - Abundância de espécies - Parâmetros físico-químicos - Variações sazonais
Objetivo científico: Identificar como temperatura e salinidade afetam a distribuição de Ulva lactuca e Gracilaria.
🗂️ Estrutura dos Dados
Nosso dataset coletas_2025.csv:
data,praia,especie,biomassa_g,temperatura_c,salinidade_psu,profundidade_m
2025-01-15,Ingleses,Ulva lactuca,245.3,24.5,35.0,3.2
2025-01-15,Ingleses,Gracilaria,180.7,24.5,35.0,5.1
2025-01-15,Barra da Lagoa,Sargassum,310.2,23.8,34.8,4.5
2025-02-20,Ingleses,Ulva lactuca,198.5,26.1,34.5,3.0
2025-02-20,Barra da Lagoa,Gracilaria,220.4,25.5,34.7,5.3
2025-03-18,Ingleses,Ulva lactuca,302.1,22.3,35.2,3.5
💻 Código Completo - Parte 1: Análise Exploratória
Crie analise_monitoramento.py:
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
# ==========================
# 1. CARREGAMENTO DOS DADOS
# ==========================
# Criar dataset de exemplo
dados = {
'data': ['2025-01-15', '2025-01-15', '2025-01-15', '2025-02-20', '2025-02-20',
'2025-03-18', '2025-03-18', '2025-04-22', '2025-04-22', '2025-05-17',
'2025-05-17', '2025-06-14', '2025-06-14', '2025-07-19', '2025-07-19',
'2025-08-16', '2025-08-16', '2025-09-20', '2025-09-20', '2025-10-18'],
'praia': ['Ingleses', 'Ingleses', 'Barra da Lagoa', 'Ingleses', 'Barra da Lagoa',
'Ingleses', 'Barra da Lagoa', 'Ingleses', 'Barra da Lagoa', 'Ingleses',
'Barra da Lagoa', 'Ingleses', 'Barra da Lagoa', 'Ingleses', 'Barra da Lagoa',
'Ingleses', 'Barra da Lagoa', 'Ingleses', 'Barra da Lagoa', 'Ingleses'],
'especie': ['Ulva lactuca', 'Gracilaria', 'Sargassum', 'Ulva lactuca', 'Gracilaria',
'Ulva lactuca', 'Gracilaria', 'Ulva lactuca', 'Sargassum', 'Ulva lactuca',
'Gracilaria', 'Ulva lactuca', 'Sargassum', 'Ulva lactuca', 'Gracilaria',
'Ulva lactuca', 'Sargassum', 'Ulva lactuca', 'Gracilaria', 'Ulva lactuca'],
'biomassa_g': [245.3, 180.7, 310.2, 198.5, 220.4, 302.1, 195.8, 275.4, 340.5, 310.8,
210.3, 285.2, 298.7, 320.5, 185.4, 295.7, 315.2, 260.8, 205.6, 280.3],
'temperatura_c': [24.5, 24.5, 23.8, 26.1, 25.5, 22.3, 22.0, 21.5, 21.2, 19.8,
19.5, 18.2, 17.9, 17.5, 17.3, 18.0, 17.8, 19.5, 19.2, 21.0],
'salinidade_psu': [35.0, 35.0, 34.8, 34.5, 34.7, 35.2, 35.1, 35.3, 35.2, 35.4,
35.3, 35.5, 35.4, 35.6, 35.5, 35.4, 35.3, 35.2, 35.1, 35.0],
'profundidade_m': [3.2, 5.1, 4.5, 3.0, 5.3, 3.5, 5.0, 3.8, 4.2, 3.6,
5.2, 3.4, 4.0, 3.9, 5.4, 3.7, 4.3, 3.5, 5.1, 3.8]
}
df = pd.DataFrame(dados)
# Converter coluna de data para datetime
df['data'] = pd.to_datetime(df['data'])
# Extrair mês e ano
df['mes'] = df['data'].dt.month
df['mes_nome'] = df['data'].dt.strftime('%B')
print("=" * 50)
print("DATASET CARREGADO COM SUCESSO")
print("=" * 50)
print(f"Total de registros: {len(df)}")
print(f"Período: {df['data'].min().strftime('%d/%m/%Y')} até {df['data'].max().strftime('%d/%m/%Y')}")
print(f"Espécies monitoradas: {df['especie'].unique()}")
print(f"Praias: {df['praia'].unique()}")
print()
# ==========================
# 2. ESTATÍSTICAS DESCRITIVAS
# ==========================
print("=" * 50)
print("ESTATÍSTICAS POR ESPÉCIE")
print("=" * 50)
# Agrupar por espécie
stats_especies = df.groupby('especie').agg({
'biomassa_g': ['mean', 'std', 'min', 'max', 'count'],
'temperatura_c': 'mean',
'salinidade_psu': 'mean'
}).round(2)
print(stats_especies)
print()
# ==========================
# 3. ANÁLISE TEMPORAL
# ==========================
print("=" * 50)
print("ANÁLISE TEMPORAL - ULVA LACTUCA")
print("=" * 50)
# Filtrar apenas Ulva lactuca
ulva_df = df[df['especie'] == 'Ulva lactuca'].copy()
# Agrupar por mês
ulva_mensal = ulva_df.groupby('mes').agg({
'biomassa_g': 'mean',
'temperatura_c': 'mean',
'salinidade_psu': 'mean'
}).round(2)
print(ulva_mensal)
print()
# ==========================
# 4. IDENTIFICAR TENDÊNCIAS
# ==========================
print("=" * 50)
print("ANÁLISE DE CORRELAÇÕES")
print("=" * 50)
# Correlação entre variáveis
correlacao = df[['biomassa_g', 'temperatura_c', 'salinidade_psu', 'profundidade_m']].corr().round(3)
print(correlacao)
print()
# Identificar meses de maior biomassa
print("=" * 50)
print("RANKING DE BIOMASSA POR MÊS")
print("=" * 50)
biomassa_mensal = df.groupby('mes')['biomassa_g'].mean().sort_values(ascending=False).round(2)
print(biomassa_mensal)
print()
# ==========================
# 5. ALERTAS E INSIGHTS
# ==========================
print("=" * 50)
print("🔍 INSIGHTS CIENTÍFICOS")
print("=" * 50)
# Temperatura média ideal para Ulva lactuca
temp_ideal_ulva = ulva_df['temperatura_c'].mean()
print(f"✅ Temperatura média ideal para Ulva lactuca: {temp_ideal_ulva:.1f}°C")
# Meses de pico
mes_pico = biomassa_mensal.idxmax()
biomassa_pico = biomassa_mensal.max()
print(f"✅ Mês de maior biomassa: Mês {mes_pico} ({biomassa_pico:.2f}g)")
# Correlação temperatura-biomassa
corr_temp_biomassa = df[df['especie'] == 'Ulva lactuca'][['temperatura_c', 'biomassa_g']].corr().iloc[0, 1]
if corr_temp_biomassa < -0.5:
print(f"⚠️ ALERTA: Correlação negativa forte entre temperatura e biomassa ({corr_temp_biomassa:.2f})")
print(" → Ulva lactuca prefere águas mais frias!")
elif corr_temp_biomassa > 0.5:
print(f"✅ Correlação positiva entre temperatura e biomassa ({corr_temp_biomassa:.2f})")
print()
print("=" * 50)
print("ANÁLISE CONCLUÍDA!")
print("=" * 50)
Execute:
python analise_monitoramento.py
Saída esperada:
==================================================
DATASET CARREGADO COM SUCESSO
==================================================
Total de registros: 20
Período: 15/01/2025 até 18/10/2025
Espécies monitoradas: ['Ulva lactuca' 'Gracilaria' 'Sargassum']
Praias: ['Ingleses' 'Barra da Lagoa']
==================================================
🔍 INSIGHTS CIENTÍFICOS
==================================================
✅ Temperatura média ideal para Ulva lactuca: 21.3°C
✅ Mês de maior biomassa: Mês 8 (305.35g)
⚠️ ALERTA: Correlação negativa forte entre temperatura e biomassa (-0.85)
→ Ulva lactuca prefere águas mais frias!
📊 Código Completo - Parte 2: Visualizações
Adicione ao arquivo (continuação):
# ==========================
# 6. VISUALIZAÇÕES
# ==========================
print("\nGerando gráficos...")
# Configurar estilo
plt.style.use('seaborn-v0_8-darkgrid')
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('📊 Monitoramento Costeiro LAFIC - 2025', fontsize=16, fontweight='bold')
# --- GRÁFICO 1: Biomassa temporal por espécie ---
ax1 = axes[0, 0]
for especie in df['especie'].unique():
dados_especie = df[df['especie'] == especie]
ax1.plot(dados_especie['data'], dados_especie['biomassa_g'],
marker='o', label=especie, linewidth=2)
ax1.set_xlabel('Data', fontweight='bold')
ax1.set_ylabel('Biomassa (g)', fontweight='bold')
ax1.set_title('Variação Temporal de Biomassa', fontweight='bold')
ax1.legend()
ax1.grid(True, alpha=0.3)
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45)
# --- GRÁFICO 2: Temperatura vs Biomassa (Ulva) ---
ax2 = axes[0, 1]
ulva_scatter = df[df['especie'] == 'Ulva lactuca']
ax2.scatter(ulva_scatter['temperatura_c'], ulva_scatter['biomassa_g'],
s=100, alpha=0.6, c='green', edgecolors='black')
# Adicionar linha de tendência
z = np.polyfit(ulva_scatter['temperatura_c'], ulva_scatter['biomassa_g'], 1)
p = np.poly1d(z)
ax2.plot(ulva_scatter['temperatura_c'].sort_values(),
p(ulva_scatter['temperatura_c'].sort_values()),
"r--", alpha=0.8, linewidth=2, label='Tendência')
ax2.set_xlabel('Temperatura (°C)', fontweight='bold')
ax2.set_ylabel('Biomassa (g)', fontweight='bold')
ax2.set_title('Temperatura vs Biomassa - Ulva lactuca', fontweight='bold')
ax2.legend()
ax2.grid(True, alpha=0.3)
# --- GRÁFICO 3: Biomassa média por mês ---
ax3 = axes[1, 0]
biomassa_mes = df.groupby('mes')['biomassa_g'].mean()
cores = ['#FF6B6B' if x < biomassa_mes.mean() else '#4ECDC4' for x in biomassa_mes]
ax3.bar(biomassa_mes.index, biomassa_mes.values, color=cores, edgecolor='black')
ax3.axhline(y=biomassa_mes.mean(), color='red', linestyle='--',
linewidth=2, label=f'Média: {biomassa_mes.mean():.1f}g')
ax3.set_xlabel('Mês', fontweight='bold')
ax3.set_ylabel('Biomassa Média (g)', fontweight='bold')
ax3.set_title('Biomassa Média Mensal (Todas Espécies)', fontweight='bold')
ax3.legend()
ax3.grid(True, alpha=0.3, axis='y')
# --- GRÁFICO 4: Distribuição por praia ---
ax4 = axes[1, 1]
biomassa_praia = df.groupby(['praia', 'especie'])['biomassa_g'].sum().unstack()
biomassa_praia.plot(kind='bar', ax=ax4, width=0.8, edgecolor='black')
ax4.set_xlabel('Praia', fontweight='bold')
ax4.set_ylabel('Biomassa Total (g)', fontweight='bold')
ax4.set_title('Biomassa Total por Praia e Espécie', fontweight='bold')
ax4.legend(title='Espécie', loc='upper right')
ax4.grid(True, alpha=0.3, axis='y')
plt.setp(ax4.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
plt.savefig('monitoramento_costeiro.png', dpi=300, bbox_inches='tight')
print("✅ Gráfico salvo: monitoramento_costeiro.png")
plt.show()
print("\n" + "=" * 50)
print("PROJETO CONCLUÍDO COM SUCESSO! 🎉")
print("=" * 50)
Adicione no topo do arquivo:
import numpy as np # Para linha de tendência
📈 Resultados Esperados
Ao executar o código completo, você terá:
- Console:
- Estatísticas descritivas
- Correlações
-
Insights científicos automáticos
-
Arquivo gráfico:
-
monitoramento_costeiro.pngcom 4 gráficos profissionais -
Insights:
- Meses de maior/menor biomassa
- Relação temperatura-abundância
- Comparação entre praias
🔬 Interpretação Científica
Com base nos resultados:
📊 Padrão Observado
Temperatura ↑ → Biomassa Ulva ↓
(Correlação negativa: -0.85)
Conclusão científica: - Ulva lactuca prefere águas frias (17-19°C) - Maior abundância no inverno (meses 6-8) - Praias mais expostas têm maior diversidade
🎯 Recomendações para Coleta
- Intensificar amostragem: Junho-Agosto (pico de Ulva)
- Monitorar temperatura: Alerta se T > 25°C
- Expandir para outras praias: Sul da ilha
🎓 O que você aprendeu
- ✅ Carregar e processar dados reais de campo
- ✅ Calcular estatísticas descritivas
- ✅ Identificar correlações entre variáveis
- ✅ Criar visualizações profissionais
- ✅ Gerar insights científicos automaticamente
🚀 Desafio Extra
Expanda o projeto:
- Adicione mais espécies ao dataset
- Calcule índice de diversidade de Shannon
- Compare anos diferentes (2024 vs 2025)
- Exporte resultados para relatório PDF
➡️ Próximo Caso Prático
- 02-Distribuicao-Espacial.html (Análise geoespacial com mapas)
Parabéns! Você criou um sistema completo de análise científica! 🌊📊
🗺️ Caso Prático 2: Distribuição Espacial de Macroalgas
🎯 Objetivo do Projeto
Criar um sistema de análise geoespacial para mapear e analisar a distribuição de macroalgas ao longo da costa, usando GeoPandas + Folium + Shapely.
O que faremos: - Carregar dados com coordenadas geográficas - Calcular densidade de ocorrências - Identificar hotspots de biodiversidade - Criar mapas interativos profissionais
🌍 Contexto da Pesquisa
Queremos mapear a distribuição de 4 espécies de macroalgas ao longo de 50km de costa:
- Ulva lactuca (alface-do-mar)
- Gracilaria (ágar-ágar)
- Sargassum (bodelha)
- Laminaria (kombu)
Objetivo científico: Identificar áreas prioritárias para conservação baseadas em densidade de espécies.
📍 Estrutura dos Dados Geoespaciais
Arquivo coletas_geo.csv:
id,data,lat,lon,especie,biomassa_g,prof_m
1,2025-01-15,-27.4374,-48.3923,Ulva lactuca,245.3,3.2
2,2025-01-15,-27.4450,-48.3950,Gracilaria,180.7,5.1
3,2025-01-20,-27.5750,-48.4200,Sargassum,310.2,4.5
4,2025-02-10,-28.4833,-48.7833,Laminaria,425.8,10.5
💻 Código Completo - Parte 1: Preparação Geoespacial
Crie analise_distribuicao.py:
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point, Polygon
import matplotlib.pyplot as plt
import numpy as np
# ==========================
# 1. CRIAR DATASET GEOESPACIAL
# ==========================
print("=" * 60)
print("🗺️ ANÁLISE DE DISTRIBUIÇÃO ESPACIAL - LAFIC")
print("=" * 60)
# Dados de coleta (coordenadas reais de SC)
dados = {
'id': list(range(1, 31)),
'data': ['2025-01-15'] * 10 + ['2025-02-20'] * 10 + ['2025-03-18'] * 10,
'lat': [
-27.4374, -27.4450, -27.4520, -27.4600, -27.5750,
-27.5850, -27.5950, -27.7500, -27.7600, -27.7700,
-28.0200, -28.0300, -28.0400, -28.4833, -28.5000,
-28.5200, -26.2500, -26.2600, -26.2700, -26.9500,
-27.4380, -27.4470, -27.4550, -27.5800, -27.5900,
-27.6000, -27.7550, -28.0250, -28.4900, -26.9600
],
'lon': [
-48.3923, -48.3950, -48.4000, -48.4050, -48.4200,
-48.4250, -48.4300, -48.5100, -48.5150, -48.5200,
-48.6200, -48.6250, -48.6300, -48.7833, -48.7900,
-48.8000, -48.6300, -48.6350, -48.6400, -48.8100,
-48.3930, -48.3960, -48.4010, -48.4210, -48.4260,
-48.4310, -48.5110, -48.6210, -48.7850, -48.8110
],
'especie': [
'Ulva lactuca', 'Gracilaria', 'Sargassum', 'Ulva lactuca', 'Gracilaria',
'Sargassum', 'Laminaria', 'Ulva lactuca', 'Gracilaria', 'Sargassum',
'Ulva lactuca', 'Gracilaria', 'Laminaria', 'Sargassum', 'Ulva lactuca',
'Gracilaria', 'Ulva lactuca', 'Sargassum', 'Gracilaria', 'Laminaria',
'Ulva lactuca', 'Ulva lactuca', 'Gracilaria', 'Sargassum', 'Ulva lactuca',
'Gracilaria', 'Sargassum', 'Ulva lactuca', 'Gracilaria', 'Laminaria'
],
'biomassa_g': np.random.uniform(150, 450, 30).round(1),
'prof_m': np.random.uniform(2, 12, 30).round(1)
}
df = pd.DataFrame(dados)
# Converter para GeoDataFrame
geometry = [Point(lon, lat) for lon, lat in zip(df['lon'], df['lat'])]
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs='EPSG:4326')
print(f"\n✅ Dataset carregado: {len(gdf)} pontos de coleta")
print(f"📍 Extensão geográfica:")
print(f" Latitude: {gdf['lat'].min():.4f} até {gdf['lat'].max():.4f}")
print(f" Longitude: {gdf['lon'].min():.4f} até {gdf['lon'].max():.4f}")
print(f"🌿 Espécies: {gdf['especie'].unique()}")
# ==========================
# 2. ANÁLISE ESPACIAL
# ==========================
print("\n" + "=" * 60)
print("📊 ANÁLISE DE DENSIDADE ESPACIAL")
print("=" * 60)
# Contar ocorrências por espécie
contagem_especies = gdf['especie'].value_counts()
print("\nDistribuição de espécies:")
for especie, count in contagem_especies.items():
porcentagem = (count / len(gdf)) * 100
print(f" {especie}: {count} ({porcentagem:.1f}%)")
# ==========================
# 3. IDENTIFICAR HOTSPOTS
# ==========================
print("\n" + "=" * 60)
print("🔥 IDENTIFICAÇÃO DE HOTSPOTS")
print("=" * 60)
# Criar grid para análise de densidade
# Dividir área em células de 0.1° x 0.1° (~11km x 11km)
lat_min, lat_max = gdf['lat'].min(), gdf['lat'].max()
lon_min, lon_max = gdf['lon'].min(), gdf['lon'].max()
# Criar células do grid
resolucao = 0.5 # graus
lat_bins = np.arange(lat_min, lat_max + resolucao, resolucao)
lon_bins = np.arange(lon_min, lon_max + resolucao, resolucao)
# Adicionar célula do grid a cada ponto
gdf['lat_bin'] = pd.cut(gdf['lat'], bins=lat_bins, labels=False)
gdf['lon_bin'] = pd.cut(gdf['lon'], bins=lon_bins, labels=False)
gdf['celula'] = gdf['lat_bin'].astype(str) + '_' + gdf['lon_bin'].astype(str)
# Contar pontos por célula
densidade_celulas = gdf.groupby('celula').agg({
'id': 'count',
'especie': lambda x: x.nunique(),
'lat': 'mean',
'lon': 'mean'
}).rename(columns={'id': 'n_coletas', 'especie': 'n_especies'})
# Identificar hotspots (células com >3 espécies)
hotspots = densidade_celulas[densidade_celulas['n_especies'] >= 3].sort_values('n_especies', ascending=False)
print(f"\n🔥 {len(hotspots)} hotspots identificados!")
print("\nTop 3 áreas de maior biodiversidade:")
for idx, (celula, dados) in enumerate(hotspots.head(3).iterrows(), 1):
print(f"\n {idx}. Célula {celula}")
print(f" 📍 Centro: ({dados['lat']:.4f}, {dados['lon']:.4f})")
print(f" 🌿 {dados['n_especies']} espécies diferentes")
print(f" 📊 {dados['n_coletas']} coletas realizadas")
# ==========================
# 4. ANÁLISE POR ESPÉCIE
# ==========================
print("\n" + "=" * 60)
print("🌿 ANÁLISE GEOGRÁFICA POR ESPÉCIE")
print("=" * 60)
for especie in gdf['especie'].unique():
dados_especie = gdf[gdf['especie'] == especie]
# Calcular centro geográfico
centro_lat = dados_especie['lat'].mean()
centro_lon = dados_especie['lon'].mean()
# Calcular dispersão geográfica
dispersao_lat = dados_especie['lat'].std()
dispersao_lon = dados_especie['lon'].std()
print(f"\n📍 {especie}")
print(f" Ocorrências: {len(dados_especie)}")
print(f" Centro: ({centro_lat:.4f}, {centro_lon:.4f})")
print(f" Dispersão: Lat={dispersao_lat:.4f}°, Lon={dispersao_lon:.4f}°")
# Profundidade preferencial
prof_media = dados_especie['prof_m'].mean()
print(f" Profundidade média: {prof_media:.1f}m")
# ==========================
# 5. DISTÂNCIAS E PROXIMIDADE
# ==========================
print("\n" + "=" * 60)
print("📏 ANÁLISE DE PROXIMIDADE")
print("=" * 60)
# Calcular distância entre pontos consecutivos
gdf_sorted = gdf.sort_values('id')
distancias = []
for i in range(len(gdf_sorted) - 1):
p1 = gdf_sorted.iloc[i].geometry
p2 = gdf_sorted.iloc[i + 1].geometry
# Distância em graus (aproximação)
dist = p1.distance(p2)
# Converter para km (1° ≈ 111km)
dist_km = dist * 111
distancias.append(dist_km)
print(f"\nDistância média entre pontos: {np.mean(distancias):.2f} km")
print(f"Distância mínima: {np.min(distancias):.2f} km")
print(f"Distância máxima: {np.max(distancias):.2f} km")
# ==========================
# 6. BUFFER ZONES
# ==========================
print("\n" + "=" * 60)
print("🛡️ CRIAÇÃO DE ZONAS DE PROTEÇÃO")
print("=" * 60)
# Criar buffer de 5km (≈0.045°) ao redor dos hotspots
buffer_raio = 0.045 # ~5km
print(f"\nCriando buffers de ~5km ao redor dos {len(hotspots)} hotspots...")
# Área total de proteção sugerida
area_protegida_total = len(hotspots) * (np.pi * (5 ** 2)) # km²
print(f"📊 Área total sugerida para proteção: {area_protegida_total:.1f} km²")
print("\n" + "=" * 60)
print("✅ ANÁLISE ESPACIAL CONCLUÍDA")
print("=" * 60)
🗺️ Código Completo - Parte 2: Mapa Interativo
Adicione ao mesmo arquivo:
# ==========================
# 7. CRIAR MAPA INTERATIVO
# ==========================
import folium
from folium.plugins import HeatMap, MarkerCluster
print("\n🗺️ Gerando mapa interativo...")
# Criar mapa base centrado em SC
centro_lat = gdf['lat'].mean()
centro_lon = gdf['lon'].mean()
mapa = folium.Map(location=[centro_lat, centro_lon], zoom_start=8, tiles='OpenStreetMap')
# Cores por espécie
cores_especies = {
'Ulva lactuca': 'green',
'Gracilaria': 'red',
'Sargassum': 'blue',
'Laminaria': 'purple'
}
# Ícones por espécie
icones_especies = {
'Ulva lactuca': '🥬',
'Gracilaria': '🌺',
'Sargassum': '🌊',
'Laminaria': '🍃'
}
# Criar grupos de camadas por espécie
grupos_especies = {especie: folium.FeatureGroup(name=especie) for especie in gdf['especie'].unique()}
# Adicionar marcadores
for idx, row in gdf.iterrows():
popup_html = f"""
<div style="font-family: Arial; min-width: 220px;">
<h3 style="color: {cores_especies[row['especie']]}; margin: 0 0 10px 0;">
{icones_especies[row['especie']]} {row['especie']}
</h3>
<hr style="margin: 10px 0; border: none; border-top: 2px solid #ddd;">
<table style="width: 100%; font-size: 13px;">
<tr><td><strong>ID:</strong></td><td>#{row['id']}</td></tr>
<tr><td><strong>Data:</strong></td><td>{row['data']}</td></tr>
<tr><td><strong>Latitude:</strong></td><td>{row['lat']:.4f}</td></tr>
<tr><td><strong>Longitude:</strong></td><td>{row['lon']:.4f}</td></tr>
<tr><td><strong>Biomassa:</strong></td><td>{row['biomassa_g']:.1f}g</td></tr>
<tr><td><strong>Profundidade:</strong></td><td>{row['prof_m']:.1f}m</td></tr>
</table>
</div>
"""
folium.CircleMarker(
location=[row['lat'], row['lon']],
radius=8,
popup=folium.Popup(popup_html, max_width=300),
color=cores_especies[row['especie']],
fill=True,
fillColor=cores_especies[row['especie']],
fillOpacity=0.7,
weight=2
).add_to(grupos_especies[row['especie']])
# Adicionar grupos ao mapa
for grupo in grupos_especies.values():
grupo.add_to(mapa)
# Adicionar hotspots
grupo_hotspots = folium.FeatureGroup(name='🔥 Hotspots de Biodiversidade')
for celula, dados in hotspots.iterrows():
# Círculo de raio ~5km
folium.Circle(
location=[dados['lat'], dados['lon']],
radius=5000, # metros
popup=f"<b>Hotspot</b><br>{dados['n_especies']} espécies<br>{dados['n_coletas']} coletas",
color='orange',
fill=True,
fillColor='orange',
fillOpacity=0.2,
weight=3
).add_to(grupo_hotspots)
# Marcador central
folium.Marker(
location=[dados['lat'], dados['lon']],
popup=f"<b>🔥 HOTSPOT</b><br>{dados['n_especies']} espécies",
icon=folium.Icon(color='orange', icon='fire', prefix='fa')
).add_to(grupo_hotspots)
grupo_hotspots.add_to(mapa)
# Adicionar mapa de calor (densidade)
dados_heatmap = [[row['lat'], row['lon']] for idx, row in gdf.iterrows()]
HeatMap(dados_heatmap, name='🌡️ Mapa de Calor', radius=25, blur=35, max_zoom=13).add_to(mapa)
# Adicionar controle de camadas
folium.LayerControl(collapsed=False).add_to(mapa)
# Adicionar legenda
legenda_html = '''
<div style="position: fixed; bottom: 50px; right: 50px; width: 250px; background-color: white;
border:2px solid grey; z-index:9999; padding: 15px; border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h4 style="margin-top: 0; color: #2E86AB;">🗺️ Legenda</h4>
<hr style="margin: 10px 0;">
<p><span style="color: green;">⬤</span> Ulva lactuca</p>
<p><span style="color: red;">⬤</span> Gracilaria</p>
<p><span style="color: blue;">⬤</span> Sargassum</p>
<p><span style="color: purple;">⬤</span> Laminaria</p>
<hr style="margin: 10px 0;">
<p><span style="color: orange;">🔥</span> Hotspot (≥3 espécies)</p>
<p style="font-size: 11px; color: #666; margin-top: 15px;">
<strong>Total:</strong> ''' + str(len(gdf)) + ''' coletas<br>
<strong>Hotspots:</strong> ''' + str(len(hotspots)) + ''' áreas
</p>
</div>
'''
mapa.get_root().html.add_child(folium.Element(legenda_html))
# Salvar mapa
mapa.save('distribuicao_espacial.html')
print("✅ Mapa interativo salvo: distribuicao_espacial.html")
print("\n" + "=" * 60)
print("🎉 PROJETO CONCLUÍDO COM SUCESSO!")
print("=" * 60)
print("\n📂 Arquivos gerados:")
print(" - distribuicao_espacial.html (abra no navegador)")
print("\n💡 Insights principais:")
print(f" - {len(hotspots)} hotspots de biodiversidade identificados")
print(f" - Área sugerida para proteção: {area_protegida_total:.1f} km²")
print(f" - Espécie mais comum: {contagem_especies.index[0]} ({contagem_especies.iloc[0]} ocorrências)")
🎯 Execute o Projeto
python analise_distribuicao.py
Depois, abra no navegador:
distribuicao_espacial.html
📊 Resultados Esperados
Console:
🗺️ ANÁLISE DE DISTRIBUIÇÃO ESPACIAL - LAFIC
✅ Dataset carregado: 30 pontos de coleta
📍 Extensão geográfica:
Latitude: -28.5200 até -26.2500
Longitude: -48.8110 até -48.3923
🔥 3 hotspots identificados!
Mapa Interativo:
- ✅ Marcadores coloridos por espécie
- ✅ Hotspots destacados com círculos
- ✅ Mapa de calor de densidade
- ✅ Controle de camadas
- ✅ Popups informativos
🔬 Interpretação Científica
Hotspots Identificados
📍 Área 1: Florianópolis Norte (-27.44, -48.39)
→ 4 espécies | Alta diversidade
→ PRIORIDADE ALTA para conservação
📍 Área 2: Garopaba (-28.02, -48.62)
→ 3 espécies | Diversidade moderada
→ PRIORIDADE MÉDIA
📍 Área 3: Laguna (-28.48, -48.78)
→ 3 espécies | Zona produtiva
→ Potencial para aquicultura
Recomendações de Gestão
- Criar Unidades de Conservação nos 3 hotspots
- Buffers de 5km = ~235 km² protegidos
- Monitoramento intensivo nas zonas intermediárias
🎓 O que você aprendeu
- ✅ Análise espacial com GeoPandas
- ✅ Identificação de hotspots de biodiversidade
- ✅ Cálculo de densidades em grid
- ✅ Criação de buffers de proteção
- ✅ Mapas interativos com Folium
- ✅ Interpretação geográfica de dados biológicos
🚀 Desafio Extra
- Conectividade: Calcule corredores ecológicos entre hotspots
- Interpolação: Use Kriging para prever distribuição em áreas não amostradas
- Análise temporal: Compare distribuição entre anos
- Exportar para QGIS: Salve como Shapefile
➡️ Próximo Caso Prático
- 03-Dashboard-Web-Completo.html (Sistema web integrado)
Parabéns! Você domina análise geoespacial aplicada! 🗺️🌿
🌐 Caso Prático 3: Dashboard Web Completo
🎯 Objetivo do Projeto
Criar um dashboard web profissional integrando todas as tecnologias aprendidas: Python (backend) + HTML/CSS/JavaScript (frontend) + Leaflet (mapas) + API REST.
Sistema completo: - Backend Python com Flask - API REST para dados - Frontend interativo - Mapas dinâmicos - Gráficos em tempo real
🏗️ Arquitetura do Sistema
dashboard-LAFIC/
│
├── backend/
│ ├── app.py # Servidor Flask
│ ├── dados.py # Gerador de dados
│ └── requirements.txt
│
├── frontend/
│ ├── index.html # Dashboard principal
│ ├── style.css # Estilos
│ └── script.js # Lógica JavaScript
│
└── dados/
└── coletas.json # Base de dados
🐍 Parte 1: Backend Python (Flask API)
Instalar Dependências
pip install flask flask-cors pandas
Criar backend/requirements.txt:
flask==3.0.0
flask-cors==4.0.0
pandas==2.1.4
Criar backend/dados.py:
import pandas as pd
import json
from datetime import datetime, timedelta
import random
def gerar_dados_coletas(n=50):
"""Gera dados sintéticos de coletas"""
especies = ['Ulva lactuca', 'Gracilaria', 'Sargassum', 'Laminaria']
praias = ['Ingleses', 'Barra da Lagoa', 'Armação', 'Garopaba', 'Laguna']
# Coordenadas aproximadas das praias
coords_praias = {
'Ingleses': (-27.4374, -48.3923),
'Barra da Lagoa': (-27.5750, -48.4200),
'Armação': (-27.7500, -48.5100),
'Garopaba': (-28.0200, -48.6200),
'Laguna': (-28.4833, -48.7833)
}
coletas = []
data_inicial = datetime(2025, 1, 1)
for i in range(n):
praia = random.choice(praias)
lat_base, lon_base = coords_praias[praia]
# Adicionar variação pequena nas coordenadas
lat = lat_base + random.uniform(-0.01, 0.01)
lon = lon_base + random.uniform(-0.01, 0.01)
# Data aleatória nos últimos 6 meses
dias_passados = random.randint(0, 180)
data = data_inicial + timedelta(days=dias_passados)
coleta = {
'id': i + 1,
'data': data.strftime('%Y-%m-%d'),
'praia': praia,
'lat': round(lat, 4),
'lon': round(lon, 4),
'especie': random.choice(especies),
'biomassa_g': round(random.uniform(100, 500), 1),
'temperatura_c': round(random.uniform(17, 26), 1),
'salinidade_psu': round(random.uniform(34, 36), 1),
'profundidade_m': round(random.uniform(2, 12), 1)
}
coletas.append(coleta)
return coletas
def salvar_dados(filename='../dados/coletas.json'):
"""Salva dados em arquivo JSON"""
import os
os.makedirs(os.path.dirname(filename), exist_ok=True)
dados = gerar_dados_coletas(50)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(dados, f, ensure_ascii=False, indent=2)
print(f"✅ {len(dados)} coletas salvas em {filename}")
if __name__ == '__main__':
salvar_dados()
Criar backend/app.py:
from flask import Flask, jsonify, request
from flask_cors import CORS
import json
import pandas as pd
from datetime import datetime
app = Flask(__name__)
CORS(app) # Permitir requisições de outros domínios
# Carregar dados
def carregar_dados():
try:
with open('../dados/coletas.json', 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
return []
# ====================
# ROTAS DA API
# ====================
@app.route('/')
def home():
return jsonify({
'message': '🌊 API LAFIC - Dashboard de Macroalgas',
'version': '1.0',
'endpoints': [
'/api/coletas',
'/api/estatisticas',
'/api/especies',
'/api/praias',
'/api/timeline'
]
})
@app.route('/api/coletas')
def get_coletas():
"""Retorna todas as coletas"""
dados = carregar_dados()
# Filtros opcionais
especie = request.args.get('especie')
praia = request.args.get('praia')
if especie:
dados = [d for d in dados if d['especie'] == especie]
if praia:
dados = [d for d in dados if d['praia'] == praia]
return jsonify({
'total': len(dados),
'coletas': dados
})
@app.route('/api/estatisticas')
def get_estatisticas():
"""Retorna estatísticas gerais"""
dados = carregar_dados()
df = pd.DataFrame(dados)
if df.empty:
return jsonify({'error': 'Sem dados'}), 404
stats = {
'total_coletas': len(df),
'total_especies': df['especie'].nunique(),
'total_praias': df['praia'].nunique(),
'biomassa_total': round(df['biomassa_g'].sum(), 1),
'biomassa_media': round(df['biomassa_g'].mean(), 1),
'temperatura_media': round(df['temperatura_c'].mean(), 1),
'salinidade_media': round(df['salinidade_psu'].mean(), 1),
'profundidade_media': round(df['profundidade_m'].mean(), 1),
'data_primeira_coleta': df['data'].min(),
'data_ultima_coleta': df['data'].max()
}
return jsonify(stats)
@app.route('/api/especies')
def get_especies():
"""Retorna contagem e estatísticas por espécie"""
dados = carregar_dados()
df = pd.DataFrame(dados)
if df.empty:
return jsonify({'error': 'Sem dados'}), 404
especies_stats = df.groupby('especie').agg({
'id': 'count',
'biomassa_g': ['mean', 'sum', 'min', 'max'],
'temperatura_c': 'mean',
'profundidade_m': 'mean'
}).round(1)
especies_stats.columns = ['_'.join(col).strip('_') for col in especies_stats.columns]
resultado = []
for especie in especies_stats.index:
resultado.append({
'especie': especie,
'ocorrencias': int(especies_stats.loc[especie, 'id_count']),
'biomassa_media': float(especies_stats.loc[especie, 'biomassa_g_mean']),
'biomassa_total': float(especies_stats.loc[especie, 'biomassa_g_sum']),
'temperatura_media': float(especies_stats.loc[especie, 'temperatura_c_mean']),
'profundidade_media': float(especies_stats.loc[especie, 'profundidade_m_mean'])
})
return jsonify(resultado)
@app.route('/api/praias')
def get_praias():
"""Retorna estatísticas por praia"""
dados = carregar_dados()
df = pd.DataFrame(dados)
if df.empty:
return jsonify({'error': 'Sem dados'}), 404
praias_stats = df.groupby('praia').agg({
'id': 'count',
'especie': lambda x: x.nunique(),
'biomassa_g': 'sum',
'lat': 'mean',
'lon': 'mean'
}).round(4)
resultado = []
for praia in praias_stats.index:
resultado.append({
'praia': praia,
'coletas': int(praias_stats.loc[praia, 'id']),
'especies_diferentes': int(praias_stats.loc[praia, 'especie']),
'biomassa_total': float(praias_stats.loc[praia, 'biomassa_g']),
'lat': float(praias_stats.loc[praia, 'lat']),
'lon': float(praias_stats.loc[praia, 'lon'])
})
return jsonify(resultado)
@app.route('/api/timeline')
def get_timeline():
"""Retorna dados para timeline (agregado por mês)"""
dados = carregar_dados()
df = pd.DataFrame(dados)
if df.empty:
return jsonify({'error': 'Sem dados'}), 404
df['data'] = pd.to_datetime(df['data'])
df['mes'] = df['data'].dt.to_period('M')
timeline = df.groupby('mes').agg({
'id': 'count',
'biomassa_g': 'sum'
}).round(1)
resultado = []
for mes in timeline.index:
resultado.append({
'mes': str(mes),
'coletas': int(timeline.loc[mes, 'id']),
'biomassa_total': float(timeline.loc[mes, 'biomassa_g'])
})
return jsonify(resultado)
if __name__ == '__main__':
print("=" * 60)
print("🚀 Iniciando servidor Flask...")
print("=" * 60)
print("📍 Acesse: http://localhost:5000")
print("📚 API Docs: http://localhost:5000/")
print()
app.run(debug=True, host='0.0.0.0', port=5000)
🌐 Parte 2: Frontend (HTML + CSS + JavaScript)
Criar frontend/index.html:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard LAFIC - Monitoramento de Macroalgas</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<!-- CSS Customizado -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Header -->
<header>
<div class="container-header">
<h1>🌊 Dashboard LAFIC</h1>
<p>Monitoramento de Macroalgas - Costa de Santa Catarina</p>
<div id="lastUpdate">Carregando...</div>
</div>
</header>
<!-- Dashboard Grid -->
<main class="dashboard-grid">
<!-- Cards de Estatísticas -->
<section class="stats-cards">
<div class="card stat-card" id="cardColetas">
<div class="card-icon">📊</div>
<div class="card-content">
<h3>Total de Coletas</h3>
<div class="stat-number" id="totalColetas">-</div>
</div>
</div>
<div class="card stat-card" id="cardEspecies">
<div class="card-icon">🌿</div>
<div class="card-content">
<h3>Espécies</h3>
<div class="stat-number" id="totalEspecies">-</div>
</div>
</div>
<div class="card stat-card" id="cardPraias">
<div class="card-icon">📍</div>
<div class="card-content">
<h3>Praias Monitoradas</h3>
<div class="stat-number" id="totalPraias">-</div>
</div>
</div>
<div class="card stat-card" id="cardBiomassa">
<div class="card-icon">⚖️</div>
<div class="card-content">
<h3>Biomassa Total</h3>
<div class="stat-number" id="biomassamTotal">-</div>
<div class="stat-unit">gramas</div>
</div>
</div>
</section>
<!-- Mapa -->
<section class="card map-container">
<h2>🗺️ Distribuição Geográfica</h2>
<div id="map"></div>
</section>
<!-- Lista de Espécies -->
<section class="card">
<h2>🌿 Ranking de Espécies</h2>
<div id="especiesList" class="list-container"></div>
</section>
<!-- Praias -->
<section class="card">
<h2>📍 Estatísticas por Praia</h2>
<div id="praiasList" class="list-container"></div>
</section>
<!-- Timeline -->
<section class="card timeline-container">
<h2>📈 Evolução Temporal</h2>
<canvas id="timelineChart"></canvas>
</section>
</main>
<!-- Footer -->
<footer>
<p>© 2025 LAFIC - UFSC | Desenvolvido para pesquisa em Ficologia</p>
</footer>
<!-- Scripts -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="script.js"></script>
</body>
</html>
Criar frontend/style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%);
min-height: 100vh;
}
/* Header */
header {
background: linear-gradient(135deg, #006064 0%, #00838f 100%);
color: white;
padding: 30px 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.container-header {
max-width: 1400px;
margin: 0 auto;
text-align: center;
}
header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
header p {
font-size: 1.2em;
opacity: 0.9;
}
#lastUpdate {
margin-top: 15px;
font-size: 0.9em;
opacity: 0.8;
}
/* Dashboard Grid */
.dashboard-grid {
max-width: 1400px;
margin: 30px auto;
padding: 0 20px;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 20px;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0,0,0,0.15);
}
/* Stats Cards */
.stats-cards {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 20px;
}
.stat-card {
display: flex;
align-items: center;
gap: 20px;
}
.card-icon {
font-size: 3em;
}
.card-content h3 {
font-size: 0.9em;
color: #666;
margin-bottom: 10px;
}
.stat-number {
font-size: 2.5em;
font-weight: bold;
color: #006064;
}
.stat-unit {
font-size: 0.8em;
color: #999;
}
/* Mapa */
.map-container {
grid-column: 1 / 3;
grid-row: span 2;
}
#map {
height: 500px;
border-radius: 8px;
margin-top: 15px;
}
/* Listas */
.list-container {
max-height: 400px;
overflow-y: auto;
margin-top: 15px;
}
.list-item {
padding: 15px;
margin-bottom: 10px;
background: #f5f5f5;
border-radius: 8px;
border-left: 4px solid #00838f;
}
.list-item h4 {
color: #006064;
margin-bottom: 8px;
}
.list-item p {
font-size: 0.9em;
color: #666;
margin: 5px 0;
}
/* Timeline */
.timeline-container {
grid-column: 1 / -1;
}
#timelineChart {
margin-top: 20px;
}
/* Footer */
footer {
background: #006064;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
/* Responsivo */
@media (max-width: 1024px) {
.dashboard-grid {
grid-template-columns: repeat(2, 1fr);
}
.map-container {
grid-column: 1 / -1;
}
}
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
header h1 {
font-size: 1.8em;
}
}
Criar frontend/script.js:
// Configuração da API
const API_URL = 'http://localhost:5000/api';
// Cores por espécie
const CORES_ESPECIES = {
'Ulva lactuca': '#4CAF50',
'Gracilaria': '#F44336',
'Sargassum': '#2196F3',
'Laminaria': '#9C27B0'
};
// Mapa global
let mapa;
// ====================
// CARREGAR DADOS
// ====================
async function carregarDashboard() {
try {
console.log('🔄 Carregando dados da API...');
// Carregar todos os endpoints
const [stats, especies, praias, timeline, coletas] = await Promise.all([
fetch(`${API_URL}/estatisticas`).then(r => r.json()),
fetch(`${API_URL}/especies`).then(r => r.json()),
fetch(`${API_URL}/praias`).then(r => r.json()),
fetch(`${API_URL}/timeline`).then(r => r.json()),
fetch(`${API_URL}/coletas`).then(r => r.json())
]);
console.log('✅ Dados carregados!');
// Atualizar interface
atualizarStats(stats);
atualizarEspecies(especies);
atualizarPraias(praias);
criarTimeline(timeline);
inicializarMapa(coletas.coletas);
// Atualizar timestamp
document.getElementById('lastUpdate').textContent =
`Última atualização: ${new Date().toLocaleString('pt-BR')}`;
} catch (error) {
console.error('❌ Erro ao carregar dados:', error);
alert('Erro ao conectar com a API. Certifique-se de que o servidor Flask está rodando.');
}
}
// ====================
// ATUALIZAR STATS
// ====================
function atualizarStats(stats) {
document.getElementById('totalColetas').textContent = stats.total_coletas;
document.getElementById('totalEspecies').textContent = stats.total_especies;
document.getElementById('totalPraias').textContent = stats.total_praias;
document.getElementById('biomassamTotal').textContent =
stats.biomassa_total.toLocaleString('pt-BR');
}
// ====================
// LISTA DE ESPÉCIES
// ====================
function atualizarEspecies(especies) {
const container = document.getElementById('especiesList');
container.innerHTML = '';
// Ordenar por ocorrências
especies.sort((a, b) => b.ocorrencias - a.ocorrencias);
especies.forEach((especie, index) => {
const item = document.createElement('div');
item.className = 'list-item';
item.style.borderLeftColor = CORES_ESPECIES[especie.especie];
item.innerHTML = `
<h4>${index + 1}. ${especie.especie}</h4>
<p><strong>Ocorrências:</strong> ${especie.ocorrencias}</p>
<p><strong>Biomassa média:</strong> ${especie.biomassa_media.toFixed(1)}g</p>
<p><strong>Temperatura média:</strong> ${especie.temperatura_media.toFixed(1)}°C</p>
`;
container.appendChild(item);
});
}
// ====================
// LISTA DE PRAIAS
// ====================
function atualizarPraias(praias) {
const container = document.getElementById('praiasList');
container.innerHTML = '';
// Ordenar por coletas
praias.sort((a, b) => b.coletas - a.coletas);
praias.forEach((praia, index) => {
const item = document.createElement('div');
item.className = 'list-item';
item.innerHTML = `
<h4>${index + 1}. ${praia.praia}</h4>
<p><strong>Coletas:</strong> ${praia.coletas}</p>
<p><strong>Espécies:</strong> ${praia.especies_diferentes}</p>
<p><strong>Biomassa total:</strong> ${praia.biomassa_total.toFixed(1)}g</p>
`;
container.appendChild(item);
});
}
// ====================
// TIMELINE (Chart.js)
// ====================
function criarTimeline(timeline) {
const ctx = document.getElementById('timelineChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: timeline.map(t => t.mes),
datasets: [{
label: 'Número de Coletas',
data: timeline.map(t => t.coletas),
borderColor: '#2196F3',
backgroundColor: 'rgba(33, 150, 243, 0.1)',
tension: 0.4,
fill: true
}, {
label: 'Biomassa Total (g)',
data: timeline.map(t => t.biomassa_total),
borderColor: '#4CAF50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
tension: 0.4,
fill: true,
yAxisID: 'y1'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
title: {
display: false
}
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Número de Coletas'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Biomassa (g)'
},
grid: {
drawOnChartArea: false
}
}
}
}
});
}
// ====================
// MAPA LEAFLET
// ====================
function inicializarMapa(coletas) {
// Criar mapa centrado em SC
mapa = L.map('map').setView([-27.5, -48.5], 8);
// Adicionar tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap'
}).addTo(mapa);
// Adicionar marcadores
coletas.forEach(coleta => {
const cor = CORES_ESPECIES[coleta.especie];
const popup = `
<div style="font-family: Arial; min-width: 200px;">
<h3 style="color: ${cor}; margin: 0 0 10px 0;">
${coleta.especie}
</h3>
<hr style="margin: 10px 0;">
<p><strong>Praia:</strong> ${coleta.praia}</p>
<p><strong>Data:</strong> ${new Date(coleta.data).toLocaleDateString('pt-BR')}</p>
<p><strong>Biomassa:</strong> ${coleta.biomassa_g}g</p>
<p><strong>Temperatura:</strong> ${coleta.temperatura_c}°C</p>
<p><strong>Profundidade:</strong> ${coleta.profundidade_m}m</p>
</div>
`;
L.circleMarker([coleta.lat, coleta.lon], {
radius: 8,
fillColor: cor,
color: '#fff',
weight: 2,
opacity: 1,
fillOpacity: 0.8
}).bindPopup(popup).addTo(mapa);
});
}
// ====================
// INICIALIZAR
// ====================
document.addEventListener('DOMContentLoaded', () => {
console.log('🚀 Dashboard inicializado');
carregarDashboard();
// Atualizar a cada 30 segundos
setInterval(carregarDashboard, 30000);
});
🚀 Como Executar
1. Gerar dados:
cd backend
python dados.py
2. Iniciar servidor Flask:
python app.py
3. Abrir dashboard:
cd ../frontend
# Abra index.html no navegador
# Ou use um servidor simples:
python -m http.server 8000
# Acesse: http://localhost:8000
✅ Funcionalidades do Dashboard
- ✅ Cards estatísticos em tempo real
- ✅ Mapa interativo com todas as coletas
- ✅ Ranking de espécies ordenado
- ✅ Estatísticas por praia
- ✅ Timeline com gráfico Chart.js
- ✅ API REST completa
- ✅ Auto-atualização a cada 30s
- ✅ Design responsivo
🎓 O que você aprendeu
- ✅ Criar API REST com Flask
- ✅ Integrar Python (backend) + JavaScript (frontend)
- ✅ Consumir APIs com fetch()
- ✅ Manipular DOM dinamicamente
- ✅ Criar gráficos com Chart.js
- ✅ Mapas Leaflet integrados
- ✅ Dashboard profissional completo
🚀 Melhorias Futuras
- Banco de dados: Substituir JSON por PostgreSQL/PostGIS
- Autenticação: Login de usuários
- Exportação: PDF/Excel dos relatórios
- Upload: Enviar novos dados via formulário
- Deploy: Hospedar na nuvem (Heroku, AWS, Azure)
Parabéns! Você criou um sistema web completo de ponta a ponta! 🎉🌐