""" 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(""" """, 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('
🚗 eVED Trajectory Viewer
', unsafe_allow_html=True) st.markdown('
Sistema Interativo de Visualização de Trajetos Veiculares
', 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("""

FLEVEn

""", unsafe_allow_html=True)