eVED-Trajectory-Viewer / src /app_streamlit.py
jwsouza13's picture
Upload 9 files
1dcfc81 verified
"""
Aplicação Streamlit para visualização interativa de trajetos eVED.
Execute com: streamlit run app_streamlit.py
"""
import streamlit as st
import streamlit.components.v1 as components
import pandas as pd
import numpy as np
import plotly.express as px
from pathlib import Path
import os
# Configuração da página
st.set_page_config(
page_title="eVED Trajectory Viewer",
page_icon="🚗",
layout="wide",
initial_sidebar_state="expanded"
)
# Importar visualizador
from eved_vizu import EVEDVisualizer
# Funções auxiliares
def get_energy_column(df, veh_type):
"""
Determina a coluna de energia/consumo baseada no tipo de veículo.
EVs e PHEVs usam Energy_Consumption, ICE e HEV usam Fuel Rate.
"""
if veh_type in ['EV', 'PHEV']:
candidates = ['Energy_Consumption', 'Energy Consumption', 'energy_consumption']
else: # ICE, HEV
candidates = ['Fuel Rate[g/s]', 'Fuel Rate', 'fuel_rate', 'Energy_Consumption']
for col in candidates:
if col in df.columns:
return col
return None
def get_numeric_columns(df):
"""Retorna todas as colunas numéricas do DataFrame, exceto GPS e timestamp."""
exclude = ['Latitude[deg]', 'Longitude[deg]', 'Timestamp(ms)', 'Unnamed: 0']
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
return [col for col in numeric_cols if col not in exclude]
def get_column_display_name(col):
"""Retorna um nome mais amigável para a coluna."""
friendly_names = {
'Vehicle Speed[km/h]': 'Velocidade',
'Energy_Consumption': 'Consumo de Energia',
'Fuel Rate[g/s]': 'Taxa de Combustível',
'Gradient': 'Inclinação',
'OAT[DegC]': 'Temperatura Externa',
'Air Conditioning Power[Watts]': 'Potência do Ar Condicionado',
'Heater Power[Watts]': 'Potência do Aquecedor',
'Elevation Smoothed[m]': 'Elevação',
'Accel Pedal Rate[%/s]': 'Taxa do Acelerador',
'Brake Pedal Status': 'Status do Freio',
}
return friendly_names.get(col, col)
# CSS customizado
st.markdown("""
<style>
.main-header {
font-size: 2.5rem;
font-weight: bold;
color: #1f77b4;
text-align: center;
margin-bottom: 1rem;
}
.sub-header {
font-size: 1.2rem;
color: #666;
text-align: center;
margin-bottom: 2rem;
}
.metric-card {
background-color: #f0f2f6;
padding: 1rem;
border-radius: 0.5rem;
border-left: 4px solid #1f77b4;
}
.vehicle-tag {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-weight: bold;
font-size: 0.9rem;
}
.ev { background-color: #00ff00; color: black; }
.ice { background-color: #ff0000; color: white; }
.hev { background-color: #ffaa00; color: black; }
.phev { background-color: #0088ff; color: white; }
</style>
""", unsafe_allow_html=True)
# Inicializar session state
if 'viz' not in st.session_state:
st.session_state.viz = EVEDVisualizer()
st.session_state.current_map = None
st.session_state.current_data = None
viz = st.session_state.viz
# Header
st.markdown('<div class="main-header">🚗 eVED Trajectory Viewer</div>', unsafe_allow_html=True)
st.markdown('<div class="sub-header">Sistema Interativo de Visualização de Trajetos Veiculares</div>', unsafe_allow_html=True)
# Sidebar - Seleção de dados
st.sidebar.header("⚙️ Configurações")
# Escolher split (train/test)
split = st.sidebar.selectbox(
"📊 Conjunto de Dados",
options=['train', 'test'],
index=0,
help="Selecione o conjunto de dados para explorar"
)
# Filtro por tipo de veículo
vehicle_filter = st.sidebar.selectbox(
"🚙 Filtrar por Tipo de Veículo",
options=['Todos', 'EV', 'ICE', 'HEV', 'PHEV'],
index=0
)
vehicle_type = None if vehicle_filter == 'Todos' else vehicle_filter
# Obter clientes disponíveis
try:
available_clients = viz.get_available_clients(split, vehicle_type)
if not available_clients:
st.error(f"❌ Nenhum cliente encontrado para o filtro selecionado!")
st.stop()
st.sidebar.success(f"✅ {len(available_clients)} clientes disponíveis")
# Selecionar cliente
client_id = st.sidebar.selectbox(
"🔢 Selecionar Cliente",
options=available_clients,
index=0,
help="Escolha um cliente para visualizar"
)
# Obter trips do cliente selecionado
available_trips = viz.get_available_trips(client_id, split)
if not available_trips:
st.error(f"❌ Cliente {client_id} não possui trips disponíveis!")
st.stop()
st.sidebar.info(f"📍 {len(available_trips)} trips disponíveis")
# Opção para visualizar todas as trips
view_mode = st.sidebar.radio(
"🎯 Modo de Visualização",
options=["Trip Individual", "Todas as Trips do Cliente"],
index=0,
help="Escolha entre visualizar uma trip ou todas as trips do cliente"
)
# Selecionar trip apenas se modo individual
if view_mode == "Trip Individual":
trip_id = st.sidebar.selectbox(
"🛣️ Selecionar Trip",
options=available_trips,
index=0,
help="Escolha uma viagem específica"
)
else:
trip_id = None # Todas as trips
st.sidebar.info(f"🗺️ Visualizando todas as {len(available_trips)} trips")
except Exception as e:
st.error(f"❌ Erro ao carregar dados: {e}")
st.stop()
# Geração automática da animação (sem botão)
st.sidebar.markdown("---")
st.sidebar.success("✅ Animação será gerada automaticamente")
generate_map = True # Sempre gerar automaticamente
# Opções avançadas
with st.sidebar.expander("🔧 Opções Avançadas"):
show_statistics = st.checkbox("Mostrar Estatísticas Detalhadas", value=True)
show_plots = st.checkbox("Mostrar Gráficos Analíticos", value=True)
show_data_table = st.checkbox("Mostrar Tabela de Dados", value=False)
st.sidebar.markdown("---")
st.sidebar.markdown("### 📖 Sobre")
st.sidebar.info("""
**eVED Trajectory Viewer** permite explorar e visualizar trajetos de veículos do dataset eVED de forma interativa.
**Tipos de Veículos:**
- 🔋 **EV**: Elétrico
- ⛽ **ICE**: Combustão
- 🔋⛽ **HEV**: Híbrido
- 🔌 **PHEV**: Plug-in Híbrido
""")
# Main content
tab1, tab2, tab3, tab4 = st.tabs(["🗺️ Visualização", "📊 Análise de Dados", "📈 Estatísticas", "ℹ️ Informações"])
with tab1:
# Título dinâmico baseado no modo
if trip_id is not None:
st.header(f"🗺️ Visualização do Trajeto - Cliente {client_id}, Trip {trip_id}")
else:
st.header(f"🗺️ Visualização de Todas as Trips - Cliente {client_id}")
# Carregar dados
try:
with st.spinner("Carregando dados do trajeto..."):
# Carregar uma trip específica ou todas
if trip_id is not None:
df = viz.load_client_data(client_id, split, trips=[trip_id])
else:
df = viz.load_client_data(client_id, split, trips=None)
df_clean = df.dropna(subset=['Latitude[deg]', 'Longitude[deg]']).copy()
df_clean = df_clean.sort_values('Timestamp(ms)').reset_index(drop=True)
st.session_state.current_data = df_clean
# Informações básicas
veh_type = viz.get_vehicle_type(df)
veh_name = viz.vehicle_types.get(veh_type, 'Desconhecido')
# Detectar coluna de energia/combustível
energy_col = get_energy_column(df, veh_type)
# Guardar no session state para uso em outras tabs
st.session_state.veh_type = veh_type
st.session_state.veh_name = veh_name
st.session_state.energy_col = energy_col
# Badges informativos
if trip_id is not None:
col1, col2, col3, col4 = st.columns(4)
else:
col1, col2, col3, col4, col5 = st.columns(5)
with col5:
st.metric("🛣️ Trips Carregadas", f"{len(available_trips)}")
with col1:
st.metric("🚗 Tipo de Veículo", veh_name)
with col2:
st.metric("📍 Pontos GPS", f"{len(df_clean):,}")
with col3:
if 'Vehicle Speed[km/h]' in df.columns:
avg_speed = df['Vehicle Speed[km/h]'].mean()
st.metric("⚡ Velocidade Média", f"{avg_speed:.1f} km/h")
else:
st.metric("⚡ Velocidade Média", "N/A")
with col4:
if energy_col and energy_col in df.columns:
total_value = df[energy_col].sum()
if veh_type in ['ICE', 'HEV']:
st.metric("⛽ Combustível Total", f"{total_value:.2f} g")
else:
st.metric("🔋 Energia Total", f"{total_value:.2f}")
else:
st.metric("🔋/⛽ Energia/Combust.", "N/A")
st.markdown("---")
# Mapa animado interativo
st.subheader("🗺️ Mapa Interativo do Trajeto")
if generate_map:
with st.spinner("Gerando mapa animado com Folium..."):
# Nome do arquivo baseado no modo
if trip_id is not None:
output_file = f'trip_cliente_{client_id}_trip_{int(trip_id)}.html'
else:
output_file = f'trip_cliente_{client_id}_all_trips.html'
viz.create_animated_map(
client_id=client_id,
split=split,
trip_id=trip_id,
output_file=output_file
)
st.session_state.current_map = output_file
st.success(f"✅ Mapa animado gerado com sucesso!")
# Mostrar a animação se já foi gerada
if 'current_map' in st.session_state and st.session_state.current_map is not None:
output_file = st.session_state.current_map
if os.path.exists(output_file):
# Ler o HTML gerado
with open(output_file, 'r', encoding='utf-8') as f:
html_content = f.read()
# Exibir a animação diretamente no Streamlit
st.info("🎮 Use os controles para animação. 📊 Gráficos sincronizados aparecem no lado direito.")
components.html(html_content, height=800, scrolling=True)
# Botão para download
st.download_button(
label="📥 Download Mapa HTML",
data=html_content,
file_name=output_file,
mime='text/html',
width="stretch"
)
else:
st.warning(f"⚠️ Arquivo {output_file} não encontrado. Gere novamente a visualização.")
else:
st.info("👆 Clique em '🎬 Gerar Visualização Interativa' na sidebar para criar a animação")
except Exception as e:
st.error(f"❌ Erro ao carregar dados: {e}")
with tab2:
st.header("📊 Análise de Dados do Trajeto")
if st.session_state.current_data is not None:
df_clean = st.session_state.current_data
# Obter todas as colunas numéricas disponíveis
numeric_cols = get_numeric_columns(df_clean)
if not numeric_cols:
st.warning("Nenhuma variável numérica encontrada nos dados.")
else:
# Sidebar para seleção de variáveis
st.sidebar.markdown("---")
st.sidebar.markdown("### 📊 Seleção de Variáveis")
# Variáveis padrão
default_var1 = 'Vehicle Speed[km/h]' if 'Vehicle Speed[km/h]' in numeric_cols else numeric_cols[0]
energy_col = st.session_state.get('energy_col')
default_var2 = energy_col if energy_col and energy_col in numeric_cols else (numeric_cols[1] if len(numeric_cols) > 1 else numeric_cols[0])
var1 = st.sidebar.selectbox(
"Variável 1 (Principal)",
options=numeric_cols,
index=numeric_cols.index(default_var1) if default_var1 in numeric_cols else 0,
help="Variável para gráficos de linha, histograma e box plot"
)
var2 = st.sidebar.selectbox(
"Variável 2 (Secundária)",
options=numeric_cols,
index=numeric_cols.index(default_var2) if default_var2 in numeric_cols else (1 if len(numeric_cols) > 1 else 0),
help="Variável para gráfico de correlação com Variável 1"
)
# Mostrar todas as variáveis disponíveis
with st.sidebar.expander("📋 Todas as Variáveis"):
for col in numeric_cols:
display_name = get_column_display_name(col)
st.text(f"• {display_name}")
st.caption(f" {col}")
if show_plots:
# Gráfico de linha - Variável 1
st.subheader(f"📈 {get_column_display_name(var1)} ao Longo do Trajeto")
fig_line = px.line(
df_clean.reset_index(),
x='index',
y=var1,
labels={'index': 'Ponto do Trajeto', var1: get_column_display_name(var1)},
title=f'Variação de {get_column_display_name(var1)}'
)
fig_line.update_traces(line_color='#1f77b4')
st.plotly_chart(fig_line, width="stretch")
# Histograma e Box Plot
col1, col2 = st.columns(2)
with col1:
st.subheader(f"📊 Distribuição de {get_column_display_name(var1)}")
fig_hist = px.histogram(
df_clean,
x=var1,
nbins=50,
labels={var1: get_column_display_name(var1)},
title=f'Histograma de {get_column_display_name(var1)}'
)
st.plotly_chart(fig_hist, width="stretch")
with col2:
st.subheader(f"📦 Box Plot de {get_column_display_name(var1)}")
fig_box = px.box(
df_clean,
y=var1,
labels={var1: get_column_display_name(var1)},
title=f'Dispersão de {get_column_display_name(var1)}'
)
st.plotly_chart(fig_box, width="stretch")
# Gráfico de linha - Variável 2
st.subheader(f"📈 {get_column_display_name(var2)} ao Longo do Trajeto")
fig_line2 = px.line(
df_clean.reset_index(),
x='index',
y=var2,
labels={'index': 'Ponto do Trajeto', var2: get_column_display_name(var2)},
title=f'Variação de {get_column_display_name(var2)}'
)
fig_line2.update_traces(line_color='#ff7f0e')
st.plotly_chart(fig_line2, width="stretch")
# Scatter: Correlação entre Var1 e Var2
st.subheader(f"🔗 Relação {get_column_display_name(var1)} × {get_column_display_name(var2)}")
try:
fig_scatter = px.scatter(
df_clean,
x=var1,
y=var2,
labels={
var1: get_column_display_name(var1),
var2: get_column_display_name(var2)
},
title=f'{get_column_display_name(var1)} vs {get_column_display_name(var2)}',
opacity=0.6,
trendline="lowess"
)
st.plotly_chart(fig_scatter, width="stretch")
except Exception as e:
# Se falhar com trendline, tenta sem
fig_scatter = px.scatter(
df_clean,
x=var1,
y=var2,
labels={
var1: get_column_display_name(var1),
var2: get_column_display_name(var2)
},
title=f'{get_column_display_name(var1)} vs {get_column_display_name(var2)}',
opacity=0.6
)
st.plotly_chart(fig_scatter, width="stretch")
st.caption(f"⚠️ Trendline não disponível: {str(e)}")
# Tabela de dados
if show_data_table:
st.subheader("📋 Tabela de Dados Brutos")
st.dataframe(df_clean, width="stretch", height=400)
# Download CSV
csv = df_clean.to_csv(index=False).encode('utf-8')
st.download_button(
label="📥 Download CSV",
data=csv,
file_name=f'trip_cliente_{client_id}_trip_{int(trip_id)}.csv',
mime='text/csv',
width="stretch"
)
else:
st.info("Selecione um cliente e trip, depois clique em 'Carregar Dados' na tab de Visualização.")
with tab3:
st.header("📈 Estatísticas Detalhadas")
if st.session_state.current_data is not None and show_statistics:
df_clean = st.session_state.current_data
col1, col2 = st.columns(2)
with col1:
st.subheader("🌍 Estatísticas de GPS")
stats_gps = {
"Total de Pontos": f"{len(df_clean):,}",
"Latitude Mínima": f"{df_clean['Latitude[deg]'].min():.6f}°",
"Latitude Máxima": f"{df_clean['Latitude[deg]'].max():.6f}°",
"Longitude Mínima": f"{df_clean['Longitude[deg]'].min():.6f}°",
"Longitude Máxima": f"{df_clean['Longitude[deg]'].max():.6f}°",
}
st.json(stats_gps)
with col2:
if 'Vehicle Speed[km/h]' in df_clean.columns:
st.subheader("⚡ Estatísticas de Velocidade")
stats_speed = {
"Média": f"{df_clean['Vehicle Speed[km/h]'].mean():.2f} km/h",
"Mediana": f"{df_clean['Vehicle Speed[km/h]'].median():.2f} km/h",
"Desvio Padrão": f"{df_clean['Vehicle Speed[km/h]'].std():.2f} km/h",
"Mínima": f"{df_clean['Vehicle Speed[km/h]'].min():.2f} km/h",
"Máxima": f"{df_clean['Vehicle Speed[km/h]'].max():.2f} km/h",
"Velocidade > 80 km/h": f"{(df_clean['Vehicle Speed[km/h]'] > 80).sum()} pontos"
}
st.json(stats_speed)
# Estatísticas de energia
if 'Energy_Consumption' in df_clean.columns:
st.subheader("🔋 Estatísticas de Energia")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Consumo Total", f"{df_clean['Energy_Consumption'].sum():.4f}")
with col2:
st.metric("Consumo Médio", f"{df_clean['Energy_Consumption'].mean():.6f}")
with col3:
st.metric("Consumo Máximo", f"{df_clean['Energy_Consumption'].max():.6f}")
with col4:
st.metric("Consumo Mínimo", f"{df_clean['Energy_Consumption'].min():.6f}")
# Informações da trip
st.subheader("ℹ️ Informações da Trip")
info = {
"Cliente ID": client_id,
"Trip ID": trip_id,
"Split": split,
"Tipo de Veículo": f"{veh_name} ({veh_type})",
"Total de Colunas": df_clean.shape[1],
"Memória Utilizada": f"{df_clean.memory_usage(deep=True).sum() / 1024:.2f} KB"
}
st.json(info)
else:
st.info("Ative 'Mostrar Estatísticas Detalhadas' na sidebar e carregue os dados.")
with tab4:
st.header("ℹ️ Informações do Sistema")
st.markdown("""
### 🚗 eVED Trajectory Viewer
Sistema interativo de visualização de trajetos veiculares do dataset eVED.
#### 📚 Funcionalidades:
- **Visualização Interativa**: Mapas interativos com Plotly
- **Animação Folium**: Gere mapas HTML com animação JavaScript
- **Análise de Dados**: Gráficos e estatísticas detalhadas
- **Filtros Avançados**: Filtre por tipo de veículo e conjunto de dados
- **Export de Dados**: Download de mapas HTML e dados em CSV
#### 🚙 Tipos de Veículos:
- 🔋 **EV** (Electric Vehicle): Veículos totalmente elétricos
- ⛽ **ICE** (Internal Combustion Engine): Veículos a combustão
- 🔋⛽ **HEV** (Hybrid Electric Vehicle): Veículos híbridos
- 🔌 **PHEV** (Plug-in Hybrid Electric Vehicle): Híbridos plug-in
#### 📊 Datasets:
- **Train**: Conjunto de treinamento para modelos FL
- **Test**: Conjunto de teste para validação
#### 🛠️ Tecnologias:
- Streamlit
- Plotly
- Folium
- Pandas
- Python 3.11+
""")
# Estatísticas gerais
st.subheader("📊 Estatísticas Gerais do Dataset")
try:
total_train = len(viz.get_available_clients('train'))
total_test = len(viz.get_available_clients('test'))
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Clientes (Train)", total_train)
with col2:
st.metric("Clientes (Test)", total_test)
with col3:
st.metric("Total de Clientes", total_train + total_test)
# Por tipo de veículo
st.subheader("🚙 Distribuição por Tipo de Veículo (Train)")
vehicle_counts = {}
for vtype in ['EV', 'ICE', 'HEV', 'PHEV']:
count = len(viz.get_available_clients('train', vehicle_type=vtype))
vehicle_counts[vtype] = count
df_vehicles = pd.DataFrame.from_dict(
vehicle_counts,
orient='index',
columns=['Quantidade']
).reset_index()
df_vehicles.columns = ['Tipo', 'Quantidade']
fig_pie = px.pie(
df_vehicles,
values='Quantidade',
names='Tipo',
title='Distribuição de Tipos de Veículos',
color='Tipo',
color_discrete_map={
'EV': '#00ff00',
'ICE': '#ff0000',
'HEV': '#ffaa00',
'PHEV': '#0088ff'
}
)
st.plotly_chart(fig_pie, width="stretch")
except Exception as e:
st.error(f"Erro ao carregar estatísticas: {e}")
# Footer
st.markdown("---")
st.markdown("""
<div style="text-align: center; color: #666;">
<p>FLEVEn</p>
</div>
""", unsafe_allow_html=True)