Spaces:
Runtime error
Runtime error
Upload 9 files
Browse files- Dockerfile +20 -20
- README.md +77 -20
- requirements.txt +17 -3
- src/__init__.py +20 -0
- src/__pycache__/eved_vizu.cpython-311.pyc +0 -0
- src/app_streamlit.py +615 -0
- src/eved_vizu.py +637 -0
- src/test_charts.py +48 -0
- src/trip_cliente_0_trip_706.html +486 -0
Dockerfile
CHANGED
|
@@ -1,20 +1,20 @@
|
|
| 1 |
-
FROM python:3.13.5-slim
|
| 2 |
-
|
| 3 |
-
WORKDIR /app
|
| 4 |
-
|
| 5 |
-
RUN apt-get update && apt-get install -y \
|
| 6 |
-
build-essential \
|
| 7 |
-
curl \
|
| 8 |
-
git \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
-
|
| 11 |
-
COPY requirements.txt ./
|
| 12 |
-
COPY src/ ./src/
|
| 13 |
-
|
| 14 |
-
RUN pip3 install -r requirements.txt
|
| 15 |
-
|
| 16 |
-
EXPOSE 8501
|
| 17 |
-
|
| 18 |
-
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
-
|
| 20 |
-
ENTRYPOINT ["streamlit", "run", "src/
|
|
|
|
| 1 |
+
FROM python:3.13.5-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
build-essential \
|
| 7 |
+
curl \
|
| 8 |
+
git \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
COPY requirements.txt ./
|
| 12 |
+
COPY src/ ./src/
|
| 13 |
+
|
| 14 |
+
RUN pip3 install -r requirements.txt
|
| 15 |
+
|
| 16 |
+
EXPOSE 8501
|
| 17 |
+
|
| 18 |
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
+
|
| 20 |
+
ENTRYPOINT ["streamlit", "run", "src/app_streamlit.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
README.md
CHANGED
|
@@ -1,20 +1,77 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🌟 eVED Trajectory Viewer
|
| 2 |
+
|
| 3 |
+
## 📦 Instalação
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
# Navegar para o diretório
|
| 7 |
+
cd data/eVED-animation
|
| 8 |
+
|
| 9 |
+
# Instalar dependências
|
| 10 |
+
pip install -r requirements.txt
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
## 🚀 Como Iniciar
|
| 14 |
+
Você pode iniciar o app eVED de duas formas.
|
| 15 |
+
|
| 16 |
+
### Opção 1: Scripts Automáticos
|
| 17 |
+
|
| 18 |
+
**Windows:**
|
| 19 |
+
```bash
|
| 20 |
+
run_app.bat
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
**Linux/Mac:**
|
| 24 |
+
```bash
|
| 25 |
+
bash run_app.sh
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### Opção 2: Comando Direto
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
streamlit run app_streamlit.py
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
A aplicação abrirá automaticamente no navegador em `http://localhost:8501`
|
| 35 |
+
|
| 36 |
+
## 🎨 Cores e Legendas
|
| 37 |
+
|
| 38 |
+
### Tipos de Veículos
|
| 39 |
+
|
| 40 |
+
| Tipo | Nome | Cor | Hex |
|
| 41 |
+
|------|------|-----|-----|
|
| 42 |
+
| EV | Elétrico | 🟢 Verde | #00ff00 |
|
| 43 |
+
| ICE | Combustão | 🔴 Vermelho | #ff0000 |
|
| 44 |
+
| HEV | Híbrido | 🟠 Laranja | #ffaa00 |
|
| 45 |
+
| PHEV | Plug-in Híbrido | 🔵 Azul | #0088ff |
|
| 46 |
+
|
| 47 |
+
### Marcadores no Mapa
|
| 48 |
+
|
| 49 |
+
- 🟢 **Verde**: Início do trajeto
|
| 50 |
+
- 🔴 **Vermelho**: Fim do trajeto
|
| 51 |
+
- **Linha colorida**: Trajeto completo (cor varia por tipo de veículo)
|
| 52 |
+
|
| 53 |
+
## 🔧 Solução de Problemas
|
| 54 |
+
|
| 55 |
+
### Aplicação não abre
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
# Verificar se Streamlit está instalado
|
| 59 |
+
pip install streamlit
|
| 60 |
+
|
| 61 |
+
# Verificar versão
|
| 62 |
+
streamlit --version
|
| 63 |
+
|
| 64 |
+
# Reinstalar se necessário
|
| 65 |
+
pip install --upgrade streamlit
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### Erro ao carregar dados
|
| 69 |
+
|
| 70 |
+
1. Verifique se a pasta `../EVED_Clients` existe
|
| 71 |
+
2. Verifique se há arquivos `.parquet` nos diretórios dos clientes
|
| 72 |
+
3. Tente outro cliente/trip
|
| 73 |
+
|
| 74 |
+
## 🆘 Suporte
|
| 75 |
+
|
| 76 |
+
- 💻 Código: [app_streamlit.py](app_streamlit.py)
|
| 77 |
+
- 🐛 Issues: Repositório do [FLEVEn](https://github.com/josewilsonsouza/fleven)
|
requirements.txt
CHANGED
|
@@ -1,3 +1,17 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependências para a aplicação Streamlit de visualização eVED
|
| 2 |
+
|
| 3 |
+
# Core
|
| 4 |
+
streamlit>=1.28.0
|
| 5 |
+
pandas>=2.0.0
|
| 6 |
+
numpy>=1.24.0
|
| 7 |
+
|
| 8 |
+
# Visualização
|
| 9 |
+
plotly>=5.17.0
|
| 10 |
+
folium>=0.14.0
|
| 11 |
+
|
| 12 |
+
# Dados
|
| 13 |
+
pyarrow>=12.0.0 # Para ler arquivos .parquet
|
| 14 |
+
|
| 15 |
+
# Análises avançadas
|
| 16 |
+
scikit-learn>=1.3.0
|
| 17 |
+
statsmodels>=0.14.0 # Para trendlines (lowess) no Plotly
|
src/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pacote de visualiza��o de trajetos do dataset eVED.
|
| 3 |
+
|
| 4 |
+
Este pacote fornece ferramentas para visualizar anima��es interativas
|
| 5 |
+
dos trajetos de ve�culos do dataset eVED usado no FLEVEn.
|
| 6 |
+
|
| 7 |
+
Exemplo de uso:
|
| 8 |
+
>>> from data.eVED_animation import visualize_trip, EVEDVisualizer
|
| 9 |
+
>>> visualize_trip(client_id=0, trip_id=706.0)
|
| 10 |
+
|
| 11 |
+
>>> viz = EVEDVisualizer()
|
| 12 |
+
>>> clientes = viz.get_available_clients('train')
|
| 13 |
+
>>> trips = viz.get_available_trips(0, 'train')
|
| 14 |
+
>>> viz.create_animated_map(0, trip_id=trips[0])
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
from .eved_vizu import EVEDVisualizer, visualize_trip
|
| 18 |
+
|
| 19 |
+
__all__ = ['EVEDVisualizer', 'visualize_trip']
|
| 20 |
+
__version__ = '1.5.0'
|
src/__pycache__/eved_vizu.cpython-311.pyc
ADDED
|
Binary file (33.1 kB). View file
|
|
|
src/app_streamlit.py
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Aplicação Streamlit para visualização interativa de trajetos eVED.
|
| 3 |
+
|
| 4 |
+
Execute com: streamlit run app_streamlit.py
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import streamlit as st
|
| 8 |
+
import streamlit.components.v1 as components
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import numpy as np
|
| 11 |
+
import plotly.express as px
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
import os
|
| 14 |
+
|
| 15 |
+
# Configuração da página
|
| 16 |
+
st.set_page_config(
|
| 17 |
+
page_title="eVED Trajectory Viewer",
|
| 18 |
+
page_icon="🚗",
|
| 19 |
+
layout="wide",
|
| 20 |
+
initial_sidebar_state="expanded"
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# Importar visualizador
|
| 24 |
+
from eved_vizu import EVEDVisualizer
|
| 25 |
+
|
| 26 |
+
# Funções auxiliares
|
| 27 |
+
def get_energy_column(df, veh_type):
|
| 28 |
+
"""
|
| 29 |
+
Determina a coluna de energia/consumo baseada no tipo de veículo.
|
| 30 |
+
EVs e PHEVs usam Energy_Consumption, ICE e HEV usam Fuel Rate.
|
| 31 |
+
"""
|
| 32 |
+
if veh_type in ['EV', 'PHEV']:
|
| 33 |
+
candidates = ['Energy_Consumption', 'Energy Consumption', 'energy_consumption']
|
| 34 |
+
else: # ICE, HEV
|
| 35 |
+
candidates = ['Fuel Rate[g/s]', 'Fuel Rate', 'fuel_rate', 'Energy_Consumption']
|
| 36 |
+
|
| 37 |
+
for col in candidates:
|
| 38 |
+
if col in df.columns:
|
| 39 |
+
return col
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
def get_numeric_columns(df):
|
| 43 |
+
"""Retorna todas as colunas numéricas do DataFrame, exceto GPS e timestamp."""
|
| 44 |
+
exclude = ['Latitude[deg]', 'Longitude[deg]', 'Timestamp(ms)', 'Unnamed: 0']
|
| 45 |
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
| 46 |
+
return [col for col in numeric_cols if col not in exclude]
|
| 47 |
+
|
| 48 |
+
def get_column_display_name(col):
|
| 49 |
+
"""Retorna um nome mais amigável para a coluna."""
|
| 50 |
+
friendly_names = {
|
| 51 |
+
'Vehicle Speed[km/h]': 'Velocidade',
|
| 52 |
+
'Energy_Consumption': 'Consumo de Energia',
|
| 53 |
+
'Fuel Rate[g/s]': 'Taxa de Combustível',
|
| 54 |
+
'Gradient': 'Inclinação',
|
| 55 |
+
'OAT[DegC]': 'Temperatura Externa',
|
| 56 |
+
'Air Conditioning Power[Watts]': 'Potência do Ar Condicionado',
|
| 57 |
+
'Heater Power[Watts]': 'Potência do Aquecedor',
|
| 58 |
+
'Elevation Smoothed[m]': 'Elevação',
|
| 59 |
+
'Accel Pedal Rate[%/s]': 'Taxa do Acelerador',
|
| 60 |
+
'Brake Pedal Status': 'Status do Freio',
|
| 61 |
+
}
|
| 62 |
+
return friendly_names.get(col, col)
|
| 63 |
+
|
| 64 |
+
# CSS customizado
|
| 65 |
+
st.markdown("""
|
| 66 |
+
<style>
|
| 67 |
+
.main-header {
|
| 68 |
+
font-size: 2.5rem;
|
| 69 |
+
font-weight: bold;
|
| 70 |
+
color: #1f77b4;
|
| 71 |
+
text-align: center;
|
| 72 |
+
margin-bottom: 1rem;
|
| 73 |
+
}
|
| 74 |
+
.sub-header {
|
| 75 |
+
font-size: 1.2rem;
|
| 76 |
+
color: #666;
|
| 77 |
+
text-align: center;
|
| 78 |
+
margin-bottom: 2rem;
|
| 79 |
+
}
|
| 80 |
+
.metric-card {
|
| 81 |
+
background-color: #f0f2f6;
|
| 82 |
+
padding: 1rem;
|
| 83 |
+
border-radius: 0.5rem;
|
| 84 |
+
border-left: 4px solid #1f77b4;
|
| 85 |
+
}
|
| 86 |
+
.vehicle-tag {
|
| 87 |
+
display: inline-block;
|
| 88 |
+
padding: 0.25rem 0.75rem;
|
| 89 |
+
border-radius: 1rem;
|
| 90 |
+
font-weight: bold;
|
| 91 |
+
font-size: 0.9rem;
|
| 92 |
+
}
|
| 93 |
+
.ev { background-color: #00ff00; color: black; }
|
| 94 |
+
.ice { background-color: #ff0000; color: white; }
|
| 95 |
+
.hev { background-color: #ffaa00; color: black; }
|
| 96 |
+
.phev { background-color: #0088ff; color: white; }
|
| 97 |
+
</style>
|
| 98 |
+
""", unsafe_allow_html=True)
|
| 99 |
+
|
| 100 |
+
# Inicializar session state
|
| 101 |
+
if 'viz' not in st.session_state:
|
| 102 |
+
st.session_state.viz = EVEDVisualizer()
|
| 103 |
+
st.session_state.current_map = None
|
| 104 |
+
st.session_state.current_data = None
|
| 105 |
+
|
| 106 |
+
viz = st.session_state.viz
|
| 107 |
+
|
| 108 |
+
# Header
|
| 109 |
+
st.markdown('<div class="main-header">🚗 eVED Trajectory Viewer</div>', unsafe_allow_html=True)
|
| 110 |
+
st.markdown('<div class="sub-header">Sistema Interativo de Visualização de Trajetos Veiculares</div>', unsafe_allow_html=True)
|
| 111 |
+
|
| 112 |
+
# Sidebar - Seleção de dados
|
| 113 |
+
st.sidebar.header("⚙️ Configurações")
|
| 114 |
+
|
| 115 |
+
# Escolher split (train/test)
|
| 116 |
+
split = st.sidebar.selectbox(
|
| 117 |
+
"📊 Conjunto de Dados",
|
| 118 |
+
options=['train', 'test'],
|
| 119 |
+
index=0,
|
| 120 |
+
help="Selecione o conjunto de dados para explorar"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Filtro por tipo de veículo
|
| 124 |
+
vehicle_filter = st.sidebar.selectbox(
|
| 125 |
+
"🚙 Filtrar por Tipo de Veículo",
|
| 126 |
+
options=['Todos', 'EV', 'ICE', 'HEV', 'PHEV'],
|
| 127 |
+
index=0
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
vehicle_type = None if vehicle_filter == 'Todos' else vehicle_filter
|
| 131 |
+
|
| 132 |
+
# Obter clientes disponíveis
|
| 133 |
+
try:
|
| 134 |
+
available_clients = viz.get_available_clients(split, vehicle_type)
|
| 135 |
+
|
| 136 |
+
if not available_clients:
|
| 137 |
+
st.error(f"❌ Nenhum cliente encontrado para o filtro selecionado!")
|
| 138 |
+
st.stop()
|
| 139 |
+
|
| 140 |
+
st.sidebar.success(f"✅ {len(available_clients)} clientes disponíveis")
|
| 141 |
+
|
| 142 |
+
# Selecionar cliente
|
| 143 |
+
client_id = st.sidebar.selectbox(
|
| 144 |
+
"🔢 Selecionar Cliente",
|
| 145 |
+
options=available_clients,
|
| 146 |
+
index=0,
|
| 147 |
+
help="Escolha um cliente para visualizar"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
# Obter trips do cliente selecionado
|
| 151 |
+
available_trips = viz.get_available_trips(client_id, split)
|
| 152 |
+
|
| 153 |
+
if not available_trips:
|
| 154 |
+
st.error(f"❌ Cliente {client_id} não possui trips disponíveis!")
|
| 155 |
+
st.stop()
|
| 156 |
+
|
| 157 |
+
st.sidebar.info(f"📍 {len(available_trips)} trips disponíveis")
|
| 158 |
+
|
| 159 |
+
# Opção para visualizar todas as trips
|
| 160 |
+
view_mode = st.sidebar.radio(
|
| 161 |
+
"🎯 Modo de Visualização",
|
| 162 |
+
options=["Trip Individual", "Todas as Trips do Cliente"],
|
| 163 |
+
index=0,
|
| 164 |
+
help="Escolha entre visualizar uma trip ou todas as trips do cliente"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
# Selecionar trip apenas se modo individual
|
| 168 |
+
if view_mode == "Trip Individual":
|
| 169 |
+
trip_id = st.sidebar.selectbox(
|
| 170 |
+
"🛣️ Selecionar Trip",
|
| 171 |
+
options=available_trips,
|
| 172 |
+
index=0,
|
| 173 |
+
help="Escolha uma viagem específica"
|
| 174 |
+
)
|
| 175 |
+
else:
|
| 176 |
+
trip_id = None # Todas as trips
|
| 177 |
+
st.sidebar.info(f"🗺️ Visualizando todas as {len(available_trips)} trips")
|
| 178 |
+
|
| 179 |
+
except Exception as e:
|
| 180 |
+
st.error(f"❌ Erro ao carregar dados: {e}")
|
| 181 |
+
st.stop()
|
| 182 |
+
|
| 183 |
+
# Geração automática da animação (sem botão)
|
| 184 |
+
st.sidebar.markdown("---")
|
| 185 |
+
st.sidebar.success("✅ Animação será gerada automaticamente")
|
| 186 |
+
generate_map = True # Sempre gerar automaticamente
|
| 187 |
+
|
| 188 |
+
# Opções avançadas
|
| 189 |
+
with st.sidebar.expander("🔧 Opções Avançadas"):
|
| 190 |
+
show_statistics = st.checkbox("Mostrar Estatísticas Detalhadas", value=True)
|
| 191 |
+
show_plots = st.checkbox("Mostrar Gráficos Analíticos", value=True)
|
| 192 |
+
show_data_table = st.checkbox("Mostrar Tabela de Dados", value=False)
|
| 193 |
+
|
| 194 |
+
st.sidebar.markdown("---")
|
| 195 |
+
st.sidebar.markdown("### 📖 Sobre")
|
| 196 |
+
st.sidebar.info("""
|
| 197 |
+
**eVED Trajectory Viewer** permite explorar e visualizar trajetos de veículos do dataset eVED de forma interativa.
|
| 198 |
+
|
| 199 |
+
**Tipos de Veículos:**
|
| 200 |
+
- 🔋 **EV**: Elétrico
|
| 201 |
+
- ⛽ **ICE**: Combustão
|
| 202 |
+
- 🔋⛽ **HEV**: Híbrido
|
| 203 |
+
- 🔌 **PHEV**: Plug-in Híbrido
|
| 204 |
+
""")
|
| 205 |
+
|
| 206 |
+
# Main content
|
| 207 |
+
tab1, tab2, tab3, tab4 = st.tabs(["🗺️ Visualização", "📊 Análise de Dados", "📈 Estatísticas", "ℹ️ Informações"])
|
| 208 |
+
|
| 209 |
+
with tab1:
|
| 210 |
+
# Título dinâmico baseado no modo
|
| 211 |
+
if trip_id is not None:
|
| 212 |
+
st.header(f"🗺️ Visualização do Trajeto - Cliente {client_id}, Trip {trip_id}")
|
| 213 |
+
else:
|
| 214 |
+
st.header(f"🗺️ Visualização de Todas as Trips - Cliente {client_id}")
|
| 215 |
+
|
| 216 |
+
# Carregar dados
|
| 217 |
+
try:
|
| 218 |
+
with st.spinner("Carregando dados do trajeto..."):
|
| 219 |
+
# Carregar uma trip específica ou todas
|
| 220 |
+
if trip_id is not None:
|
| 221 |
+
df = viz.load_client_data(client_id, split, trips=[trip_id])
|
| 222 |
+
else:
|
| 223 |
+
df = viz.load_client_data(client_id, split, trips=None)
|
| 224 |
+
|
| 225 |
+
df_clean = df.dropna(subset=['Latitude[deg]', 'Longitude[deg]']).copy()
|
| 226 |
+
df_clean = df_clean.sort_values('Timestamp(ms)').reset_index(drop=True)
|
| 227 |
+
|
| 228 |
+
st.session_state.current_data = df_clean
|
| 229 |
+
|
| 230 |
+
# Informações básicas
|
| 231 |
+
veh_type = viz.get_vehicle_type(df)
|
| 232 |
+
veh_name = viz.vehicle_types.get(veh_type, 'Desconhecido')
|
| 233 |
+
|
| 234 |
+
# Detectar coluna de energia/combustível
|
| 235 |
+
energy_col = get_energy_column(df, veh_type)
|
| 236 |
+
|
| 237 |
+
# Guardar no session state para uso em outras tabs
|
| 238 |
+
st.session_state.veh_type = veh_type
|
| 239 |
+
st.session_state.veh_name = veh_name
|
| 240 |
+
st.session_state.energy_col = energy_col
|
| 241 |
+
|
| 242 |
+
# Badges informativos
|
| 243 |
+
if trip_id is not None:
|
| 244 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 245 |
+
else:
|
| 246 |
+
col1, col2, col3, col4, col5 = st.columns(5)
|
| 247 |
+
with col5:
|
| 248 |
+
st.metric("🛣️ Trips Carregadas", f"{len(available_trips)}")
|
| 249 |
+
|
| 250 |
+
with col1:
|
| 251 |
+
st.metric("🚗 Tipo de Veículo", veh_name)
|
| 252 |
+
with col2:
|
| 253 |
+
st.metric("📍 Pontos GPS", f"{len(df_clean):,}")
|
| 254 |
+
with col3:
|
| 255 |
+
if 'Vehicle Speed[km/h]' in df.columns:
|
| 256 |
+
avg_speed = df['Vehicle Speed[km/h]'].mean()
|
| 257 |
+
st.metric("⚡ Velocidade Média", f"{avg_speed:.1f} km/h")
|
| 258 |
+
else:
|
| 259 |
+
st.metric("⚡ Velocidade Média", "N/A")
|
| 260 |
+
with col4:
|
| 261 |
+
if energy_col and energy_col in df.columns:
|
| 262 |
+
total_value = df[energy_col].sum()
|
| 263 |
+
if veh_type in ['ICE', 'HEV']:
|
| 264 |
+
st.metric("⛽ Combustível Total", f"{total_value:.2f} g")
|
| 265 |
+
else:
|
| 266 |
+
st.metric("🔋 Energia Total", f"{total_value:.2f}")
|
| 267 |
+
else:
|
| 268 |
+
st.metric("🔋/⛽ Energia/Combust.", "N/A")
|
| 269 |
+
|
| 270 |
+
st.markdown("---")
|
| 271 |
+
|
| 272 |
+
# Mapa animado interativo
|
| 273 |
+
st.subheader("🗺️ Mapa Interativo do Trajeto")
|
| 274 |
+
|
| 275 |
+
if generate_map:
|
| 276 |
+
with st.spinner("Gerando mapa animado com Folium..."):
|
| 277 |
+
# Nome do arquivo baseado no modo
|
| 278 |
+
if trip_id is not None:
|
| 279 |
+
output_file = f'trip_cliente_{client_id}_trip_{int(trip_id)}.html'
|
| 280 |
+
else:
|
| 281 |
+
output_file = f'trip_cliente_{client_id}_all_trips.html'
|
| 282 |
+
|
| 283 |
+
viz.create_animated_map(
|
| 284 |
+
client_id=client_id,
|
| 285 |
+
split=split,
|
| 286 |
+
trip_id=trip_id,
|
| 287 |
+
output_file=output_file
|
| 288 |
+
)
|
| 289 |
+
st.session_state.current_map = output_file
|
| 290 |
+
st.success(f"✅ Mapa animado gerado com sucesso!")
|
| 291 |
+
|
| 292 |
+
# Mostrar a animação se já foi gerada
|
| 293 |
+
if 'current_map' in st.session_state and st.session_state.current_map is not None:
|
| 294 |
+
output_file = st.session_state.current_map
|
| 295 |
+
if os.path.exists(output_file):
|
| 296 |
+
# Ler o HTML gerado
|
| 297 |
+
with open(output_file, 'r', encoding='utf-8') as f:
|
| 298 |
+
html_content = f.read()
|
| 299 |
+
|
| 300 |
+
# Exibir a animação diretamente no Streamlit
|
| 301 |
+
st.info("🎮 Use os controles para animação. 📊 Gráficos sincronizados aparecem no lado direito.")
|
| 302 |
+
components.html(html_content, height=800, scrolling=True)
|
| 303 |
+
|
| 304 |
+
# Botão para download
|
| 305 |
+
st.download_button(
|
| 306 |
+
label="📥 Download Mapa HTML",
|
| 307 |
+
data=html_content,
|
| 308 |
+
file_name=output_file,
|
| 309 |
+
mime='text/html',
|
| 310 |
+
width="stretch"
|
| 311 |
+
)
|
| 312 |
+
else:
|
| 313 |
+
st.warning(f"⚠️ Arquivo {output_file} não encontrado. Gere novamente a visualização.")
|
| 314 |
+
else:
|
| 315 |
+
st.info("👆 Clique em '🎬 Gerar Visualização Interativa' na sidebar para criar a animação")
|
| 316 |
+
|
| 317 |
+
except Exception as e:
|
| 318 |
+
st.error(f"❌ Erro ao carregar dados: {e}")
|
| 319 |
+
|
| 320 |
+
with tab2:
|
| 321 |
+
st.header("📊 Análise de Dados do Trajeto")
|
| 322 |
+
|
| 323 |
+
if st.session_state.current_data is not None:
|
| 324 |
+
df_clean = st.session_state.current_data
|
| 325 |
+
|
| 326 |
+
# Obter todas as colunas numéricas disponíveis
|
| 327 |
+
numeric_cols = get_numeric_columns(df_clean)
|
| 328 |
+
|
| 329 |
+
if not numeric_cols:
|
| 330 |
+
st.warning("Nenhuma variável numérica encontrada nos dados.")
|
| 331 |
+
else:
|
| 332 |
+
# Sidebar para seleção de variáveis
|
| 333 |
+
st.sidebar.markdown("---")
|
| 334 |
+
st.sidebar.markdown("### 📊 Seleção de Variáveis")
|
| 335 |
+
|
| 336 |
+
# Variáveis padrão
|
| 337 |
+
default_var1 = 'Vehicle Speed[km/h]' if 'Vehicle Speed[km/h]' in numeric_cols else numeric_cols[0]
|
| 338 |
+
energy_col = st.session_state.get('energy_col')
|
| 339 |
+
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])
|
| 340 |
+
|
| 341 |
+
var1 = st.sidebar.selectbox(
|
| 342 |
+
"Variável 1 (Principal)",
|
| 343 |
+
options=numeric_cols,
|
| 344 |
+
index=numeric_cols.index(default_var1) if default_var1 in numeric_cols else 0,
|
| 345 |
+
help="Variável para gráficos de linha, histograma e box plot"
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
var2 = st.sidebar.selectbox(
|
| 349 |
+
"Variável 2 (Secundária)",
|
| 350 |
+
options=numeric_cols,
|
| 351 |
+
index=numeric_cols.index(default_var2) if default_var2 in numeric_cols else (1 if len(numeric_cols) > 1 else 0),
|
| 352 |
+
help="Variável para gráfico de correlação com Variável 1"
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
# Mostrar todas as variáveis disponíveis
|
| 356 |
+
with st.sidebar.expander("📋 Todas as Variáveis"):
|
| 357 |
+
for col in numeric_cols:
|
| 358 |
+
display_name = get_column_display_name(col)
|
| 359 |
+
st.text(f"• {display_name}")
|
| 360 |
+
st.caption(f" {col}")
|
| 361 |
+
|
| 362 |
+
if show_plots:
|
| 363 |
+
# Gráfico de linha - Variável 1
|
| 364 |
+
st.subheader(f"📈 {get_column_display_name(var1)} ao Longo do Trajeto")
|
| 365 |
+
|
| 366 |
+
fig_line = px.line(
|
| 367 |
+
df_clean.reset_index(),
|
| 368 |
+
x='index',
|
| 369 |
+
y=var1,
|
| 370 |
+
labels={'index': 'Ponto do Trajeto', var1: get_column_display_name(var1)},
|
| 371 |
+
title=f'Variação de {get_column_display_name(var1)}'
|
| 372 |
+
)
|
| 373 |
+
fig_line.update_traces(line_color='#1f77b4')
|
| 374 |
+
st.plotly_chart(fig_line, width="stretch")
|
| 375 |
+
|
| 376 |
+
# Histograma e Box Plot
|
| 377 |
+
col1, col2 = st.columns(2)
|
| 378 |
+
|
| 379 |
+
with col1:
|
| 380 |
+
st.subheader(f"📊 Distribuição de {get_column_display_name(var1)}")
|
| 381 |
+
fig_hist = px.histogram(
|
| 382 |
+
df_clean,
|
| 383 |
+
x=var1,
|
| 384 |
+
nbins=50,
|
| 385 |
+
labels={var1: get_column_display_name(var1)},
|
| 386 |
+
title=f'Histograma de {get_column_display_name(var1)}'
|
| 387 |
+
)
|
| 388 |
+
st.plotly_chart(fig_hist, width="stretch")
|
| 389 |
+
|
| 390 |
+
with col2:
|
| 391 |
+
st.subheader(f"📦 Box Plot de {get_column_display_name(var1)}")
|
| 392 |
+
fig_box = px.box(
|
| 393 |
+
df_clean,
|
| 394 |
+
y=var1,
|
| 395 |
+
labels={var1: get_column_display_name(var1)},
|
| 396 |
+
title=f'Dispersão de {get_column_display_name(var1)}'
|
| 397 |
+
)
|
| 398 |
+
st.plotly_chart(fig_box, width="stretch")
|
| 399 |
+
|
| 400 |
+
# Gráfico de linha - Variável 2
|
| 401 |
+
st.subheader(f"📈 {get_column_display_name(var2)} ao Longo do Trajeto")
|
| 402 |
+
|
| 403 |
+
fig_line2 = px.line(
|
| 404 |
+
df_clean.reset_index(),
|
| 405 |
+
x='index',
|
| 406 |
+
y=var2,
|
| 407 |
+
labels={'index': 'Ponto do Trajeto', var2: get_column_display_name(var2)},
|
| 408 |
+
title=f'Variação de {get_column_display_name(var2)}'
|
| 409 |
+
)
|
| 410 |
+
fig_line2.update_traces(line_color='#ff7f0e')
|
| 411 |
+
st.plotly_chart(fig_line2, width="stretch")
|
| 412 |
+
|
| 413 |
+
# Scatter: Correlação entre Var1 e Var2
|
| 414 |
+
st.subheader(f"🔗 Relação {get_column_display_name(var1)} × {get_column_display_name(var2)}")
|
| 415 |
+
|
| 416 |
+
try:
|
| 417 |
+
fig_scatter = px.scatter(
|
| 418 |
+
df_clean,
|
| 419 |
+
x=var1,
|
| 420 |
+
y=var2,
|
| 421 |
+
labels={
|
| 422 |
+
var1: get_column_display_name(var1),
|
| 423 |
+
var2: get_column_display_name(var2)
|
| 424 |
+
},
|
| 425 |
+
title=f'{get_column_display_name(var1)} vs {get_column_display_name(var2)}',
|
| 426 |
+
opacity=0.6,
|
| 427 |
+
trendline="lowess"
|
| 428 |
+
)
|
| 429 |
+
st.plotly_chart(fig_scatter, width="stretch")
|
| 430 |
+
except Exception as e:
|
| 431 |
+
# Se falhar com trendline, tenta sem
|
| 432 |
+
fig_scatter = px.scatter(
|
| 433 |
+
df_clean,
|
| 434 |
+
x=var1,
|
| 435 |
+
y=var2,
|
| 436 |
+
labels={
|
| 437 |
+
var1: get_column_display_name(var1),
|
| 438 |
+
var2: get_column_display_name(var2)
|
| 439 |
+
},
|
| 440 |
+
title=f'{get_column_display_name(var1)} vs {get_column_display_name(var2)}',
|
| 441 |
+
opacity=0.6
|
| 442 |
+
)
|
| 443 |
+
st.plotly_chart(fig_scatter, width="stretch")
|
| 444 |
+
st.caption(f"⚠️ Trendline não disponível: {str(e)}")
|
| 445 |
+
|
| 446 |
+
# Tabela de dados
|
| 447 |
+
if show_data_table:
|
| 448 |
+
st.subheader("📋 Tabela de Dados Brutos")
|
| 449 |
+
st.dataframe(df_clean, width="stretch", height=400)
|
| 450 |
+
|
| 451 |
+
# Download CSV
|
| 452 |
+
csv = df_clean.to_csv(index=False).encode('utf-8')
|
| 453 |
+
st.download_button(
|
| 454 |
+
label="📥 Download CSV",
|
| 455 |
+
data=csv,
|
| 456 |
+
file_name=f'trip_cliente_{client_id}_trip_{int(trip_id)}.csv',
|
| 457 |
+
mime='text/csv',
|
| 458 |
+
width="stretch"
|
| 459 |
+
)
|
| 460 |
+
else:
|
| 461 |
+
st.info("Selecione um cliente e trip, depois clique em 'Carregar Dados' na tab de Visualização.")
|
| 462 |
+
|
| 463 |
+
with tab3:
|
| 464 |
+
st.header("📈 Estatísticas Detalhadas")
|
| 465 |
+
|
| 466 |
+
if st.session_state.current_data is not None and show_statistics:
|
| 467 |
+
df_clean = st.session_state.current_data
|
| 468 |
+
|
| 469 |
+
col1, col2 = st.columns(2)
|
| 470 |
+
|
| 471 |
+
with col1:
|
| 472 |
+
st.subheader("🌍 Estatísticas de GPS")
|
| 473 |
+
stats_gps = {
|
| 474 |
+
"Total de Pontos": f"{len(df_clean):,}",
|
| 475 |
+
"Latitude Mínima": f"{df_clean['Latitude[deg]'].min():.6f}°",
|
| 476 |
+
"Latitude Máxima": f"{df_clean['Latitude[deg]'].max():.6f}°",
|
| 477 |
+
"Longitude Mínima": f"{df_clean['Longitude[deg]'].min():.6f}°",
|
| 478 |
+
"Longitude Máxima": f"{df_clean['Longitude[deg]'].max():.6f}°",
|
| 479 |
+
}
|
| 480 |
+
st.json(stats_gps)
|
| 481 |
+
|
| 482 |
+
with col2:
|
| 483 |
+
if 'Vehicle Speed[km/h]' in df_clean.columns:
|
| 484 |
+
st.subheader("⚡ Estatísticas de Velocidade")
|
| 485 |
+
stats_speed = {
|
| 486 |
+
"Média": f"{df_clean['Vehicle Speed[km/h]'].mean():.2f} km/h",
|
| 487 |
+
"Mediana": f"{df_clean['Vehicle Speed[km/h]'].median():.2f} km/h",
|
| 488 |
+
"Desvio Padrão": f"{df_clean['Vehicle Speed[km/h]'].std():.2f} km/h",
|
| 489 |
+
"Mínima": f"{df_clean['Vehicle Speed[km/h]'].min():.2f} km/h",
|
| 490 |
+
"Máxima": f"{df_clean['Vehicle Speed[km/h]'].max():.2f} km/h",
|
| 491 |
+
"Velocidade > 80 km/h": f"{(df_clean['Vehicle Speed[km/h]'] > 80).sum()} pontos"
|
| 492 |
+
}
|
| 493 |
+
st.json(stats_speed)
|
| 494 |
+
|
| 495 |
+
# Estatísticas de energia
|
| 496 |
+
if 'Energy_Consumption' in df_clean.columns:
|
| 497 |
+
st.subheader("🔋 Estatísticas de Energia")
|
| 498 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 499 |
+
|
| 500 |
+
with col1:
|
| 501 |
+
st.metric("Consumo Total", f"{df_clean['Energy_Consumption'].sum():.4f}")
|
| 502 |
+
with col2:
|
| 503 |
+
st.metric("Consumo Médio", f"{df_clean['Energy_Consumption'].mean():.6f}")
|
| 504 |
+
with col3:
|
| 505 |
+
st.metric("Consumo Máximo", f"{df_clean['Energy_Consumption'].max():.6f}")
|
| 506 |
+
with col4:
|
| 507 |
+
st.metric("Consumo Mínimo", f"{df_clean['Energy_Consumption'].min():.6f}")
|
| 508 |
+
|
| 509 |
+
# Informações da trip
|
| 510 |
+
st.subheader("���️ Informações da Trip")
|
| 511 |
+
info = {
|
| 512 |
+
"Cliente ID": client_id,
|
| 513 |
+
"Trip ID": trip_id,
|
| 514 |
+
"Split": split,
|
| 515 |
+
"Tipo de Veículo": f"{veh_name} ({veh_type})",
|
| 516 |
+
"Total de Colunas": df_clean.shape[1],
|
| 517 |
+
"Memória Utilizada": f"{df_clean.memory_usage(deep=True).sum() / 1024:.2f} KB"
|
| 518 |
+
}
|
| 519 |
+
st.json(info)
|
| 520 |
+
|
| 521 |
+
else:
|
| 522 |
+
st.info("Ative 'Mostrar Estatísticas Detalhadas' na sidebar e carregue os dados.")
|
| 523 |
+
|
| 524 |
+
with tab4:
|
| 525 |
+
st.header("ℹ️ Informações do Sistema")
|
| 526 |
+
|
| 527 |
+
st.markdown("""
|
| 528 |
+
### 🚗 eVED Trajectory Viewer
|
| 529 |
+
|
| 530 |
+
Sistema interativo de visualização de trajetos veiculares do dataset eVED.
|
| 531 |
+
|
| 532 |
+
#### 📚 Funcionalidades:
|
| 533 |
+
|
| 534 |
+
- **Visualização Interativa**: Mapas interativos com Plotly
|
| 535 |
+
- **Animação Folium**: Gere mapas HTML com animação JavaScript
|
| 536 |
+
- **Análise de Dados**: Gráficos e estatísticas detalhadas
|
| 537 |
+
- **Filtros Avançados**: Filtre por tipo de veículo e conjunto de dados
|
| 538 |
+
- **Export de Dados**: Download de mapas HTML e dados em CSV
|
| 539 |
+
|
| 540 |
+
#### 🚙 Tipos de Veículos:
|
| 541 |
+
|
| 542 |
+
- 🔋 **EV** (Electric Vehicle): Veículos totalmente elétricos
|
| 543 |
+
- ⛽ **ICE** (Internal Combustion Engine): Veículos a combustão
|
| 544 |
+
- 🔋⛽ **HEV** (Hybrid Electric Vehicle): Veículos híbridos
|
| 545 |
+
- 🔌 **PHEV** (Plug-in Hybrid Electric Vehicle): Híbridos plug-in
|
| 546 |
+
|
| 547 |
+
#### 📊 Datasets:
|
| 548 |
+
|
| 549 |
+
- **Train**: Conjunto de treinamento para modelos FL
|
| 550 |
+
- **Test**: Conjunto de teste para validação
|
| 551 |
+
|
| 552 |
+
#### 🛠️ Tecnologias:
|
| 553 |
+
|
| 554 |
+
- Streamlit
|
| 555 |
+
- Plotly
|
| 556 |
+
- Folium
|
| 557 |
+
- Pandas
|
| 558 |
+
- Python 3.11+
|
| 559 |
+
""")
|
| 560 |
+
|
| 561 |
+
# Estatísticas gerais
|
| 562 |
+
st.subheader("📊 Estatísticas Gerais do Dataset")
|
| 563 |
+
|
| 564 |
+
try:
|
| 565 |
+
total_train = len(viz.get_available_clients('train'))
|
| 566 |
+
total_test = len(viz.get_available_clients('test'))
|
| 567 |
+
|
| 568 |
+
col1, col2, col3 = st.columns(3)
|
| 569 |
+
with col1:
|
| 570 |
+
st.metric("Clientes (Train)", total_train)
|
| 571 |
+
with col2:
|
| 572 |
+
st.metric("Clientes (Test)", total_test)
|
| 573 |
+
with col3:
|
| 574 |
+
st.metric("Total de Clientes", total_train + total_test)
|
| 575 |
+
|
| 576 |
+
# Por tipo de veículo
|
| 577 |
+
st.subheader("🚙 Distribuição por Tipo de Veículo (Train)")
|
| 578 |
+
|
| 579 |
+
vehicle_counts = {}
|
| 580 |
+
for vtype in ['EV', 'ICE', 'HEV', 'PHEV']:
|
| 581 |
+
count = len(viz.get_available_clients('train', vehicle_type=vtype))
|
| 582 |
+
vehicle_counts[vtype] = count
|
| 583 |
+
|
| 584 |
+
df_vehicles = pd.DataFrame.from_dict(
|
| 585 |
+
vehicle_counts,
|
| 586 |
+
orient='index',
|
| 587 |
+
columns=['Quantidade']
|
| 588 |
+
).reset_index()
|
| 589 |
+
df_vehicles.columns = ['Tipo', 'Quantidade']
|
| 590 |
+
|
| 591 |
+
fig_pie = px.pie(
|
| 592 |
+
df_vehicles,
|
| 593 |
+
values='Quantidade',
|
| 594 |
+
names='Tipo',
|
| 595 |
+
title='Distribuição de Tipos de Veículos',
|
| 596 |
+
color='Tipo',
|
| 597 |
+
color_discrete_map={
|
| 598 |
+
'EV': '#00ff00',
|
| 599 |
+
'ICE': '#ff0000',
|
| 600 |
+
'HEV': '#ffaa00',
|
| 601 |
+
'PHEV': '#0088ff'
|
| 602 |
+
}
|
| 603 |
+
)
|
| 604 |
+
st.plotly_chart(fig_pie, width="stretch")
|
| 605 |
+
|
| 606 |
+
except Exception as e:
|
| 607 |
+
st.error(f"Erro ao carregar estatísticas: {e}")
|
| 608 |
+
|
| 609 |
+
# Footer
|
| 610 |
+
st.markdown("---")
|
| 611 |
+
st.markdown("""
|
| 612 |
+
<div style="text-align: center; color: #666;">
|
| 613 |
+
<p>FLEVEn</p>
|
| 614 |
+
</div>
|
| 615 |
+
""", unsafe_allow_html=True)
|
src/eved_vizu.py
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import folium
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from folium import plugins
|
| 7 |
+
|
| 8 |
+
class EVEDVisualizer:
|
| 9 |
+
"""Sistema de visualização de trajetos do dataset eVED no contexto Fleven"""
|
| 10 |
+
|
| 11 |
+
def __init__(self, base_path=None):
|
| 12 |
+
# Se base_path não for fornecido, usa caminho relativo da subpasta
|
| 13 |
+
if base_path is None:
|
| 14 |
+
# Estamos em data/eVED-animation, então EVED_Clients está em ../EVED_Clients
|
| 15 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 16 |
+
base_path = os.path.join(script_dir, "..", "EVED_Clients")
|
| 17 |
+
|
| 18 |
+
self.base_path = os.path.abspath(base_path)
|
| 19 |
+
self.train_path = os.path.join(self.base_path, "train")
|
| 20 |
+
self.test_path = os.path.join(self.base_path, "test")
|
| 21 |
+
|
| 22 |
+
# Verifica se o caminho base existe
|
| 23 |
+
if not os.path.exists(self.base_path):
|
| 24 |
+
print(f"⚠️ Aviso: Caminho base não encontrado: {self.base_path}")
|
| 25 |
+
print(f" Certifique-se de que EVED_Clients existe em {os.path.dirname(self.base_path)}")
|
| 26 |
+
|
| 27 |
+
# Mapeamento de tipos de veículos
|
| 28 |
+
self.vehicle_types = {
|
| 29 |
+
'EV': 'Elétrico',
|
| 30 |
+
'ICE': 'Combustão',
|
| 31 |
+
'HEV': 'Híbrido',
|
| 32 |
+
'PHEV': 'Plug-in Híbrido'
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
# Cores por tipo de veículo
|
| 36 |
+
self.colors = {
|
| 37 |
+
'EV': '#00ff00',
|
| 38 |
+
'ICE': '#ff0000',
|
| 39 |
+
'HEV': '#ffaa00',
|
| 40 |
+
'PHEV': '#0088ff'
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
def load_client_data(self, client_id, split='train', trips=None):
|
| 44 |
+
"""Carrega dados de um cliente específico"""
|
| 45 |
+
path = self.train_path if split == 'train' else self.test_path
|
| 46 |
+
client_path = os.path.join(path, f"client_{client_id}")
|
| 47 |
+
|
| 48 |
+
if not os.path.exists(client_path):
|
| 49 |
+
raise ValueError(f"Cliente {client_id} não encontrado em {split}")
|
| 50 |
+
|
| 51 |
+
trip_files = [f for f in os.listdir(client_path)
|
| 52 |
+
if f.startswith('trip_') and f.endswith('.parquet')]
|
| 53 |
+
|
| 54 |
+
if trips is not None:
|
| 55 |
+
trip_files = [f for f in trip_files
|
| 56 |
+
if any(f == f"trip_{t}.parquet" for t in trips)]
|
| 57 |
+
|
| 58 |
+
dfs = []
|
| 59 |
+
for trip_file in trip_files:
|
| 60 |
+
try:
|
| 61 |
+
df = pd.read_parquet(os.path.join(client_path, trip_file))
|
| 62 |
+
dfs.append(df)
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f"Aviso: Falha ao ler {trip_file}: {e}")
|
| 65 |
+
continue
|
| 66 |
+
|
| 67 |
+
if not dfs:
|
| 68 |
+
raise ValueError(f"Nenhuma viagem válida encontrada para cliente {client_id}")
|
| 69 |
+
|
| 70 |
+
return pd.concat(dfs, ignore_index=True)
|
| 71 |
+
|
| 72 |
+
def get_vehicle_type(self, df):
|
| 73 |
+
"""Determina o tipo de veículo baseado nas colunas corretas"""
|
| 74 |
+
if 'EngineType' in df.columns:
|
| 75 |
+
veh_type = df['EngineType'].iloc[0]
|
| 76 |
+
if pd.notna(veh_type) and veh_type in ('EV', 'PHEV'):
|
| 77 |
+
return veh_type
|
| 78 |
+
|
| 79 |
+
if 'Vehicle Type' in df.columns:
|
| 80 |
+
veh_type = df['Vehicle Type'].iloc[0]
|
| 81 |
+
if pd.notna(veh_type) and veh_type in ('ICE', 'HEV'):
|
| 82 |
+
return veh_type
|
| 83 |
+
|
| 84 |
+
return 'Unknown'
|
| 85 |
+
|
| 86 |
+
def get_available_clients(self, split='train', vehicle_type=None):
|
| 87 |
+
"""Lista clientes disponíveis, opcionalmente filtrados por tipo"""
|
| 88 |
+
path = self.train_path if split == 'train' else self.test_path
|
| 89 |
+
|
| 90 |
+
if not os.path.exists(path):
|
| 91 |
+
return []
|
| 92 |
+
|
| 93 |
+
clients = []
|
| 94 |
+
for client_dir in os.listdir(path):
|
| 95 |
+
if not client_dir.startswith('client_'):
|
| 96 |
+
continue
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
client_id = int(client_dir.split('_')[1])
|
| 100 |
+
|
| 101 |
+
if not vehicle_type:
|
| 102 |
+
clients.append(client_id)
|
| 103 |
+
continue
|
| 104 |
+
|
| 105 |
+
metadata_path = os.path.join(path, client_dir, 'metadata.json')
|
| 106 |
+
|
| 107 |
+
if os.path.exists(metadata_path):
|
| 108 |
+
with open(metadata_path, 'r') as f:
|
| 109 |
+
metadata = json.load(f)
|
| 110 |
+
|
| 111 |
+
if metadata.get('VehicleType') == vehicle_type:
|
| 112 |
+
clients.append(client_id)
|
| 113 |
+
else:
|
| 114 |
+
df = self.load_client_data(client_id, split)
|
| 115 |
+
if self.get_vehicle_type(df) == vehicle_type:
|
| 116 |
+
clients.append(client_id)
|
| 117 |
+
|
| 118 |
+
except Exception as e:
|
| 119 |
+
continue
|
| 120 |
+
|
| 121 |
+
return sorted(clients)
|
| 122 |
+
|
| 123 |
+
def get_available_trips(self, client_id, split='train'):
|
| 124 |
+
"""Lista os IDs das viagens disponíveis para um cliente"""
|
| 125 |
+
path = self.train_path if split == 'train' else self.test_path
|
| 126 |
+
client_path = os.path.join(path, f"client_{client_id}")
|
| 127 |
+
|
| 128 |
+
if not os.path.exists(client_path):
|
| 129 |
+
return []
|
| 130 |
+
|
| 131 |
+
trips = []
|
| 132 |
+
for f in os.listdir(client_path):
|
| 133 |
+
if f.startswith('trip_') and f.endswith('.parquet'):
|
| 134 |
+
try:
|
| 135 |
+
trip_str = f.replace('trip_', '').replace('.parquet', '')
|
| 136 |
+
trip_id = float(trip_str)
|
| 137 |
+
trips.append(trip_id)
|
| 138 |
+
except:
|
| 139 |
+
continue
|
| 140 |
+
|
| 141 |
+
return sorted(trips)
|
| 142 |
+
|
| 143 |
+
def create_animated_map(self, client_id, split='train', trip_id=None,
|
| 144 |
+
output_file='trajectory_animation.html'):
|
| 145 |
+
"""
|
| 146 |
+
Cria mapa HTML interativo com animação do trajeto usando JavaScript puro.
|
| 147 |
+
Se trip_id=None, visualiza todas as trips do cliente com cores diferentes.
|
| 148 |
+
"""
|
| 149 |
+
|
| 150 |
+
print(f"Carregando dados do cliente {client_id}...")
|
| 151 |
+
|
| 152 |
+
trips_to_load = [trip_id] if trip_id is not None else None
|
| 153 |
+
df = self.load_client_data(client_id, split, trips_to_load)
|
| 154 |
+
|
| 155 |
+
df_clean = df.dropna(subset=['Latitude[deg]', 'Longitude[deg]']).copy()
|
| 156 |
+
|
| 157 |
+
if df_clean.empty:
|
| 158 |
+
raise ValueError("Nenhum dado válido de GPS encontrado")
|
| 159 |
+
|
| 160 |
+
df_clean = df_clean.sort_values('Timestamp(ms)').reset_index(drop=True)
|
| 161 |
+
|
| 162 |
+
# Detectar se temos múltiplas trips (procurar por Trip_ID, TripID ou similar)
|
| 163 |
+
trip_col = None
|
| 164 |
+
for col in ['Trip_ID', 'TripID', 'trip_id', 'Trip ID']:
|
| 165 |
+
if col in df_clean.columns:
|
| 166 |
+
trip_col = col
|
| 167 |
+
break
|
| 168 |
+
|
| 169 |
+
is_multi_trip = trip_id is None and trip_col is not None
|
| 170 |
+
if is_multi_trip:
|
| 171 |
+
unique_trips = df_clean[trip_col].nunique()
|
| 172 |
+
print(f"Múltiplas trips detectadas: {unique_trips} trips")
|
| 173 |
+
|
| 174 |
+
veh_type = self.get_vehicle_type(df)
|
| 175 |
+
color = self.colors.get(veh_type, '#888888')
|
| 176 |
+
veh_name = self.vehicle_types.get(veh_type, 'Desconhecido')
|
| 177 |
+
|
| 178 |
+
print(f"Tipo de veículo: {veh_name}")
|
| 179 |
+
print(f"Total de pontos: {len(df_clean)}")
|
| 180 |
+
|
| 181 |
+
# Reduzir pontos para performance
|
| 182 |
+
step = max(1, len(df_clean) // 300)
|
| 183 |
+
df_animation = df_clean.iloc[::step].reset_index(drop=True)
|
| 184 |
+
|
| 185 |
+
center_lat = df_animation['Latitude[deg]'].mean()
|
| 186 |
+
center_lon = df_animation['Longitude[deg]'].mean()
|
| 187 |
+
|
| 188 |
+
# Criar mapa base
|
| 189 |
+
m = folium.Map(
|
| 190 |
+
location=[center_lat, center_lon],
|
| 191 |
+
zoom_start=13,
|
| 192 |
+
tiles='OpenStreetMap'
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
# Trajeto completo (cinza claro)
|
| 196 |
+
coordinates = df_animation[['Latitude[deg]', 'Longitude[deg]']].values.tolist()
|
| 197 |
+
|
| 198 |
+
folium.PolyLine(
|
| 199 |
+
coordinates,
|
| 200 |
+
color='#cccccc',
|
| 201 |
+
weight=5,
|
| 202 |
+
opacity=0.5,
|
| 203 |
+
popup=f"Trajeto completo - {veh_name}"
|
| 204 |
+
).add_to(m)
|
| 205 |
+
|
| 206 |
+
# Marcadores de início e fim
|
| 207 |
+
folium.CircleMarker(
|
| 208 |
+
coordinates[0],
|
| 209 |
+
radius=10,
|
| 210 |
+
popup=f"<b>INÍCIO</b><br>{veh_name}",
|
| 211 |
+
color='green',
|
| 212 |
+
fill=True,
|
| 213 |
+
fillColor='green',
|
| 214 |
+
fillOpacity=0.8
|
| 215 |
+
).add_to(m)
|
| 216 |
+
|
| 217 |
+
folium.CircleMarker(
|
| 218 |
+
coordinates[-1],
|
| 219 |
+
radius=10,
|
| 220 |
+
popup=f"<b>FIM</b><br>{veh_name}",
|
| 221 |
+
color='red',
|
| 222 |
+
fill=True,
|
| 223 |
+
fillColor='red',
|
| 224 |
+
fillOpacity=0.8
|
| 225 |
+
).add_to(m)
|
| 226 |
+
|
| 227 |
+
# Preparar dados para JavaScript
|
| 228 |
+
coords_js = [[lat, lon] for lat, lon in coordinates]
|
| 229 |
+
|
| 230 |
+
# Preparar dados das variáveis para os gráficos
|
| 231 |
+
variables_data = {}
|
| 232 |
+
|
| 233 |
+
# Velocidade
|
| 234 |
+
if 'Vehicle Speed[km/h]' in df_animation.columns:
|
| 235 |
+
variables_data['speed'] = df_animation['Vehicle Speed[km/h]'].fillna(0).tolist()
|
| 236 |
+
|
| 237 |
+
# Energia/Combustível
|
| 238 |
+
energy_col = None
|
| 239 |
+
if veh_type in ['EV', 'PHEV']:
|
| 240 |
+
for col in ['Energy_Consumption', 'Energy Consumption', 'energy_consumption']:
|
| 241 |
+
if col in df_animation.columns:
|
| 242 |
+
energy_col = col
|
| 243 |
+
break
|
| 244 |
+
else: # ICE, HEV
|
| 245 |
+
for col in ['Fuel Rate[g/s]', 'Fuel Rate', 'fuel_rate']:
|
| 246 |
+
if col in df_animation.columns:
|
| 247 |
+
energy_col = col
|
| 248 |
+
break
|
| 249 |
+
|
| 250 |
+
if energy_col:
|
| 251 |
+
variables_data['energy'] = df_animation[energy_col].fillna(0).tolist()
|
| 252 |
+
variables_data['energy_label'] = 'Consumo de Energia' if veh_type in ['EV', 'PHEV'] else 'Taxa de Combustível'
|
| 253 |
+
|
| 254 |
+
# Elevação
|
| 255 |
+
if 'Elevation Smoothed[m]' in df_animation.columns:
|
| 256 |
+
variables_data['elevation'] = df_animation['Elevation Smoothed[m]'].fillna(0).tolist()
|
| 257 |
+
|
| 258 |
+
# Adicionar JavaScript customizado para animação com gráficos
|
| 259 |
+
animation_js = f"""
|
| 260 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
|
| 261 |
+
<script>
|
| 262 |
+
(function() {{
|
| 263 |
+
// Esperar o mapa carregar completamente
|
| 264 |
+
var checkMap = setInterval(function() {{
|
| 265 |
+
if (typeof {m.get_name()} !== 'undefined') {{
|
| 266 |
+
clearInterval(checkMap);
|
| 267 |
+
initAnimation();
|
| 268 |
+
}}
|
| 269 |
+
}}, 100);
|
| 270 |
+
|
| 271 |
+
function initAnimation() {{
|
| 272 |
+
var coords = {json.dumps(coords_js)};
|
| 273 |
+
var variablesData = {json.dumps(variables_data)};
|
| 274 |
+
var currentIndex = 0;
|
| 275 |
+
var isPlaying = false;
|
| 276 |
+
var animationSpeed = 50;
|
| 277 |
+
var intervalId = null;
|
| 278 |
+
|
| 279 |
+
var pathColor = '{color}';
|
| 280 |
+
var markerColor = '#ffffff';
|
| 281 |
+
|
| 282 |
+
// Função para inicializar gráficos
|
| 283 |
+
function initCharts(data) {{
|
| 284 |
+
var chartsObj = {{}};
|
| 285 |
+
|
| 286 |
+
// Gráfico de Velocidade
|
| 287 |
+
if (data.speed) {{
|
| 288 |
+
var speedCtx = document.getElementById('speedChart').getContext('2d');
|
| 289 |
+
chartsObj.speed = new Chart(speedCtx, {{
|
| 290 |
+
type: 'line',
|
| 291 |
+
data: {{
|
| 292 |
+
labels: Array.from({{length: data.speed.length}}, (_, i) => i),
|
| 293 |
+
datasets: [{{
|
| 294 |
+
label: 'Velocidade (km/h)',
|
| 295 |
+
data: data.speed,
|
| 296 |
+
borderColor: '#3498db',
|
| 297 |
+
backgroundColor: 'rgba(52, 152, 219, 0.1)',
|
| 298 |
+
borderWidth: 2,
|
| 299 |
+
tension: 0.4,
|
| 300 |
+
pointRadius: 0
|
| 301 |
+
}}]
|
| 302 |
+
}},
|
| 303 |
+
options: {{
|
| 304 |
+
responsive: false,
|
| 305 |
+
maintainAspectRatio: false,
|
| 306 |
+
animation: false,
|
| 307 |
+
plugins: {{
|
| 308 |
+
legend: {{display: false}}
|
| 309 |
+
}},
|
| 310 |
+
scales: {{
|
| 311 |
+
x: {{display: false}},
|
| 312 |
+
y: {{
|
| 313 |
+
beginAtZero: true,
|
| 314 |
+
ticks: {{color: '#666', font: {{size: 10}}}}
|
| 315 |
+
}}
|
| 316 |
+
}}
|
| 317 |
+
}}
|
| 318 |
+
}});
|
| 319 |
+
}}
|
| 320 |
+
|
| 321 |
+
// Gráfico de Energia/Combustível
|
| 322 |
+
if (data.energy) {{
|
| 323 |
+
var energyCtx = document.getElementById('energyChart').getContext('2d');
|
| 324 |
+
chartsObj.energy = new Chart(energyCtx, {{
|
| 325 |
+
type: 'line',
|
| 326 |
+
data: {{
|
| 327 |
+
labels: Array.from({{length: data.energy.length}}, (_, i) => i),
|
| 328 |
+
datasets: [{{
|
| 329 |
+
label: data.energy_label || 'Energia',
|
| 330 |
+
data: data.energy,
|
| 331 |
+
borderColor: '#e74c3c',
|
| 332 |
+
backgroundColor: 'rgba(231, 76, 60, 0.1)',
|
| 333 |
+
borderWidth: 2,
|
| 334 |
+
tension: 0.4,
|
| 335 |
+
pointRadius: 0
|
| 336 |
+
}}]
|
| 337 |
+
}},
|
| 338 |
+
options: {{
|
| 339 |
+
responsive: false,
|
| 340 |
+
maintainAspectRatio: false,
|
| 341 |
+
animation: false,
|
| 342 |
+
plugins: {{
|
| 343 |
+
legend: {{display: false}}
|
| 344 |
+
}},
|
| 345 |
+
scales: {{
|
| 346 |
+
x: {{display: false}},
|
| 347 |
+
y: {{
|
| 348 |
+
beginAtZero: true,
|
| 349 |
+
ticks: {{color: '#666', font: {{size: 10}}}}
|
| 350 |
+
}}
|
| 351 |
+
}}
|
| 352 |
+
}}
|
| 353 |
+
}});
|
| 354 |
+
}}
|
| 355 |
+
|
| 356 |
+
// Gráfico de Elevação
|
| 357 |
+
if (data.elevation) {{
|
| 358 |
+
var elevCtx = document.getElementById('elevationChart').getContext('2d');
|
| 359 |
+
chartsObj.elevation = new Chart(elevCtx, {{
|
| 360 |
+
type: 'line',
|
| 361 |
+
data: {{
|
| 362 |
+
labels: Array.from({{length: data.elevation.length}}, (_, i) => i),
|
| 363 |
+
datasets: [{{
|
| 364 |
+
label: 'Elevação (m)',
|
| 365 |
+
data: data.elevation,
|
| 366 |
+
borderColor: '#2ecc71',
|
| 367 |
+
backgroundColor: 'rgba(46, 204, 113, 0.1)',
|
| 368 |
+
borderWidth: 2,
|
| 369 |
+
tension: 0.4,
|
| 370 |
+
pointRadius: 0,
|
| 371 |
+
fill: true
|
| 372 |
+
}}]
|
| 373 |
+
}},
|
| 374 |
+
options: {{
|
| 375 |
+
responsive: false,
|
| 376 |
+
maintainAspectRatio: false,
|
| 377 |
+
animation: false,
|
| 378 |
+
plugins: {{
|
| 379 |
+
legend: {{display: false}}
|
| 380 |
+
}},
|
| 381 |
+
scales: {{
|
| 382 |
+
x: {{display: false}},
|
| 383 |
+
y: {{
|
| 384 |
+
ticks: {{color: '#666', font: {{size: 10}}}}
|
| 385 |
+
}}
|
| 386 |
+
}}
|
| 387 |
+
}}
|
| 388 |
+
}});
|
| 389 |
+
}}
|
| 390 |
+
|
| 391 |
+
return chartsObj;
|
| 392 |
+
}}
|
| 393 |
+
|
| 394 |
+
// Inicializar gráficos
|
| 395 |
+
var charts = initCharts(variablesData);
|
| 396 |
+
|
| 397 |
+
// Camada para o trajeto percorrido
|
| 398 |
+
var traveledPath = L.polyline([], {{
|
| 399 |
+
color: pathColor,
|
| 400 |
+
weight: 5,
|
| 401 |
+
opacity: 0.9
|
| 402 |
+
}}).addTo({m.get_name()});
|
| 403 |
+
|
| 404 |
+
// Marcador do carro
|
| 405 |
+
var carMarker = L.circleMarker(coords[0], {{
|
| 406 |
+
radius: 8,
|
| 407 |
+
color: pathColor,
|
| 408 |
+
weight: 3,
|
| 409 |
+
fillColor: markerColor,
|
| 410 |
+
fillOpacity: 1
|
| 411 |
+
}}).addTo({m.get_name()});
|
| 412 |
+
|
| 413 |
+
// Atualizar indicador de posição nos gráficos
|
| 414 |
+
function updateChartIndicators(index) {{
|
| 415 |
+
Object.keys(charts).forEach(function(key) {{
|
| 416 |
+
var chart = charts[key];
|
| 417 |
+
// Criar/atualizar linha vertical no gráfico
|
| 418 |
+
if (chart.options.plugins.annotation) {{
|
| 419 |
+
chart.options.plugins.annotation.annotations.line1.xMin = index;
|
| 420 |
+
chart.options.plugins.annotation.annotations.line1.xMax = index;
|
| 421 |
+
}} else {{
|
| 422 |
+
// Atualizar viewport do gráfico
|
| 423 |
+
var maxVisible = 50; // Mostrar últimos 50 pontos
|
| 424 |
+
var minX = Math.max(0, index - maxVisible);
|
| 425 |
+
chart.options.scales.x.min = minX;
|
| 426 |
+
chart.options.scales.x.max = Math.max(maxVisible, index + 10);
|
| 427 |
+
}}
|
| 428 |
+
chart.update('none');
|
| 429 |
+
}});
|
| 430 |
+
|
| 431 |
+
// Atualizar valores numéricos
|
| 432 |
+
if (variablesData.speed) {{
|
| 433 |
+
document.getElementById('speedValue').textContent =
|
| 434 |
+
variablesData.speed[index].toFixed(1) + ' km/h';
|
| 435 |
+
}}
|
| 436 |
+
if (variablesData.energy) {{
|
| 437 |
+
document.getElementById('energyValue').textContent =
|
| 438 |
+
variablesData.energy[index].toFixed(2);
|
| 439 |
+
}}
|
| 440 |
+
if (variablesData.elevation) {{
|
| 441 |
+
document.getElementById('elevationValue').textContent =
|
| 442 |
+
variablesData.elevation[index].toFixed(1) + ' m';
|
| 443 |
+
}}
|
| 444 |
+
}}
|
| 445 |
+
|
| 446 |
+
window.updateAnimation = function() {{
|
| 447 |
+
// Só atualiza se estiver realmente tocando
|
| 448 |
+
if (!isPlaying) return;
|
| 449 |
+
|
| 450 |
+
if (currentIndex < coords.length) {{
|
| 451 |
+
carMarker.setLatLng(coords[currentIndex]);
|
| 452 |
+
var traveled = coords.slice(0, currentIndex + 1);
|
| 453 |
+
traveledPath.setLatLngs(traveled);
|
| 454 |
+
|
| 455 |
+
document.getElementById('timeSlider').value = currentIndex;
|
| 456 |
+
document.getElementById('timeDisplay').textContent =
|
| 457 |
+
'Ponto: ' + (currentIndex + 1) + ' / ' + coords.length;
|
| 458 |
+
|
| 459 |
+
// Atualizar gráficos
|
| 460 |
+
updateChartIndicators(currentIndex);
|
| 461 |
+
|
| 462 |
+
currentIndex++;
|
| 463 |
+
}} else {{
|
| 464 |
+
currentIndex = 0;
|
| 465 |
+
}}
|
| 466 |
+
}};
|
| 467 |
+
|
| 468 |
+
window.playAnimation = function() {{
|
| 469 |
+
if (!isPlaying) {{
|
| 470 |
+
isPlaying = true;
|
| 471 |
+
document.getElementById('playBtn').innerHTML = '⏸ Pausar';
|
| 472 |
+
intervalId = setInterval(window.updateAnimation, animationSpeed);
|
| 473 |
+
}}
|
| 474 |
+
}};
|
| 475 |
+
|
| 476 |
+
window.pauseAnimation = function() {{
|
| 477 |
+
isPlaying = false;
|
| 478 |
+
document.getElementById('playBtn').innerHTML = '▶ Play';
|
| 479 |
+
if (intervalId) {{
|
| 480 |
+
clearInterval(intervalId);
|
| 481 |
+
intervalId = null;
|
| 482 |
+
}}
|
| 483 |
+
}};
|
| 484 |
+
|
| 485 |
+
window.togglePlay = function() {{
|
| 486 |
+
if (isPlaying) {{
|
| 487 |
+
window.pauseAnimation();
|
| 488 |
+
}} else {{
|
| 489 |
+
window.playAnimation();
|
| 490 |
+
}}
|
| 491 |
+
}};
|
| 492 |
+
|
| 493 |
+
window.resetAnimation = function() {{
|
| 494 |
+
window.pauseAnimation();
|
| 495 |
+
currentIndex = 0;
|
| 496 |
+
carMarker.setLatLng(coords[0]);
|
| 497 |
+
traveledPath.setLatLngs([coords[0]]);
|
| 498 |
+
document.getElementById('timeSlider').value = 0;
|
| 499 |
+
document.getElementById('timeDisplay').textContent = 'Ponto: 1 / ' + coords.length;
|
| 500 |
+
}};
|
| 501 |
+
|
| 502 |
+
window.changeSpeed = function(value) {{
|
| 503 |
+
animationSpeed = 200 - value;
|
| 504 |
+
document.getElementById('speedDisplay').textContent = 'Velocidade: ' + value + '%';
|
| 505 |
+
|
| 506 |
+
if (isPlaying) {{
|
| 507 |
+
clearInterval(intervalId);
|
| 508 |
+
intervalId = setInterval(window.updateAnimation, animationSpeed);
|
| 509 |
+
}}
|
| 510 |
+
}};
|
| 511 |
+
|
| 512 |
+
window.seekTo = function(index) {{
|
| 513 |
+
currentIndex = parseInt(index);
|
| 514 |
+
carMarker.setLatLng(coords[currentIndex]);
|
| 515 |
+
var traveled = coords.slice(0, currentIndex + 1);
|
| 516 |
+
traveledPath.setLatLngs(traveled);
|
| 517 |
+
document.getElementById('timeDisplay').textContent =
|
| 518 |
+
'Ponto: ' + (currentIndex + 1) + ' / ' + coords.length;
|
| 519 |
+
|
| 520 |
+
// Atualizar gráficos ao usar o slider
|
| 521 |
+
updateChartIndicators(currentIndex);
|
| 522 |
+
}};
|
| 523 |
+
|
| 524 |
+
// Auto-play
|
| 525 |
+
setTimeout(window.playAnimation, 1000);
|
| 526 |
+
}}
|
| 527 |
+
}})();
|
| 528 |
+
</script>
|
| 529 |
+
|
| 530 |
+
<!-- Painéis de Gráficos Laterais -->
|
| 531 |
+
<div style="position: fixed; right: 15px; top: 15px; width: 400px; z-index: 1000; max-height: 90vh; overflow-y: auto;">
|
| 532 |
+
{'<div style="background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); margin-bottom: 15px; border-left: 5px solid #3498db;"><h4 style="margin: 0 0 10px 0; font-size: 16px; color: #3498db; font-weight: bold;">⚡ Velocidade</h4><div id="speedValue" style="font-size: 32px; font-weight: bold; color: #3498db; margin-bottom: 15px; text-align: center;">0 km/h</div><canvas id="speedChart" width="360" height="120"></canvas></div>' if 'speed' in variables_data else ''}
|
| 533 |
+
{'<div style="background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); margin-bottom: 15px; border-left: 5px solid #e74c3c;"><h4 style="margin: 0 0 10px 0; font-size: 16px; color: #e74c3c; font-weight: bold;">' + ('🔋 ' if veh_type in ['EV', 'PHEV'] else '⛽ ') + variables_data.get('energy_label', 'Energia') + '</h4><div id="energyValue" style="font-size: 32px; font-weight: bold; color: #e74c3c; margin-bottom: 15px; text-align: center;">0</div><canvas id="energyChart" width="360" height="120"></canvas></div>' if 'energy' in variables_data else ''}
|
| 534 |
+
{'<div style="background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); border-left: 5px solid #2ecc71;"><h4 style="margin: 0 0 10px 0; font-size: 16px; color: #2ecc71; font-weight: bold;">⛰️ Elevação</h4><div id="elevationValue" style="font-size: 32px; font-weight: bold; color: #2ecc71; margin-bottom: 15px; text-align: center;">0 m</div><canvas id="elevationChart" width="360" height="120"></canvas></div>' if 'elevation' in variables_data else ''}
|
| 535 |
+
</div>
|
| 536 |
+
|
| 537 |
+
<!-- Controles de Animação -->
|
| 538 |
+
<div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
|
| 539 |
+
background: white; padding: 15px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
| 540 |
+
z-index: 1000; min-width: 400px;">
|
| 541 |
+
<div style="text-align: center; margin-bottom: 10px;">
|
| 542 |
+
<h4 style="margin: 0 0 10px 0;">🚗 {veh_name} - Cliente {client_id}{'<br><small style="color: #888;">Todas as Trips</small>' if trip_id is None else ''}</h4>
|
| 543 |
+
<div id="timeDisplay" style="font-size: 14px; color: #666;">Ponto: 1 / {len(coords_js)}</div>
|
| 544 |
+
</div>
|
| 545 |
+
|
| 546 |
+
<div style="display: flex; gap: 10px; margin-bottom: 10px; justify-content: center;">
|
| 547 |
+
<button id="playBtn" onclick="togglePlay()"
|
| 548 |
+
style="padding: 10px 20px; font-size: 16px; cursor: pointer; border: none;
|
| 549 |
+
background: {color}; color: white; border-radius: 5px;">
|
| 550 |
+
▶ Play
|
| 551 |
+
</button>
|
| 552 |
+
<button onclick="resetAnimation()"
|
| 553 |
+
style="padding: 10px 20px; font-size: 16px; cursor: pointer; border: none;
|
| 554 |
+
background: #666; color: white; border-radius: 5px;">
|
| 555 |
+
🔄 Reiniciar
|
| 556 |
+
</button>
|
| 557 |
+
</div>
|
| 558 |
+
|
| 559 |
+
<div style="margin-bottom: 10px;">
|
| 560 |
+
<input type="range" id="timeSlider" min="0" max="{len(coords_js) - 1}" value="0"
|
| 561 |
+
oninput="seekTo(this.value)"
|
| 562 |
+
style="width: 100%;">
|
| 563 |
+
</div>
|
| 564 |
+
|
| 565 |
+
<div>
|
| 566 |
+
<div id="speedDisplay" style="font-size: 12px; color: #666; margin-bottom: 5px;">
|
| 567 |
+
Velocidade: 100%
|
| 568 |
+
</div>
|
| 569 |
+
<input type="range" id="speedSlider" min="10" max="190" value="100"
|
| 570 |
+
oninput="changeSpeed(this.value)"
|
| 571 |
+
style="width: 100%;">
|
| 572 |
+
</div>
|
| 573 |
+
</div>
|
| 574 |
+
"""
|
| 575 |
+
|
| 576 |
+
# Adicionar o JavaScript ao mapa
|
| 577 |
+
m.get_root().html.add_child(folium.Element(animation_js))
|
| 578 |
+
|
| 579 |
+
# Salvar mapa
|
| 580 |
+
m.save(output_file)
|
| 581 |
+
print(f"\n✅ Mapa animado salvo em: {output_file}")
|
| 582 |
+
print(f"📍 Trip ID: {trip_id if trip_id else 'todas as trips'}")
|
| 583 |
+
print(f"🎯 Total de pontos: {len(coords_js)}")
|
| 584 |
+
print(f"\n🌐 Abra o arquivo no navegador - A animação inicia automaticamente!")
|
| 585 |
+
|
| 586 |
+
return m
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
# Função auxiliar para uso rápido
|
| 590 |
+
def visualize_trip(client_id=0, split='train', trip_id=None):
|
| 591 |
+
"""Função rápida para visualizar uma trip"""
|
| 592 |
+
viz = EVEDVisualizer()
|
| 593 |
+
|
| 594 |
+
if trip_id is None:
|
| 595 |
+
available_trips = viz.get_available_trips(client_id, split)
|
| 596 |
+
if available_trips:
|
| 597 |
+
trip_id = available_trips[0]
|
| 598 |
+
print(f"ℹ️ Usando trip {trip_id} (primeira disponível)")
|
| 599 |
+
else:
|
| 600 |
+
raise ValueError(f"Nenhuma trip encontrada para cliente {client_id}")
|
| 601 |
+
|
| 602 |
+
trip_id = float(trip_id)
|
| 603 |
+
output_name = f'trip_client_{client_id}_trip_{int(trip_id)}.html'
|
| 604 |
+
|
| 605 |
+
return viz.create_animated_map(
|
| 606 |
+
client_id=client_id,
|
| 607 |
+
split=split,
|
| 608 |
+
trip_id=trip_id,
|
| 609 |
+
output_file=output_name
|
| 610 |
+
)
|
| 611 |
+
|
| 612 |
+
|
| 613 |
+
# Exemplo de uso
|
| 614 |
+
if __name__ == "__main__":
|
| 615 |
+
print("=" * 60)
|
| 616 |
+
print("Sistema de Visualização eVED - Animação JavaScript")
|
| 617 |
+
print("=" * 60)
|
| 618 |
+
|
| 619 |
+
viz = EVEDVisualizer()
|
| 620 |
+
|
| 621 |
+
all_clients = viz.get_available_clients('train')
|
| 622 |
+
print(f"\n📋 Total de clientes: {len(all_clients)}")
|
| 623 |
+
print(f"Primeiros 5: {all_clients[:5]}")
|
| 624 |
+
|
| 625 |
+
if all_clients:
|
| 626 |
+
client_to_viz = all_clients[0]
|
| 627 |
+
trips = viz.get_available_trips(client_to_viz, 'train')
|
| 628 |
+
print(f"\n🚗 Cliente {client_to_viz}")
|
| 629 |
+
print(f"Trips: {trips[:10]}")
|
| 630 |
+
|
| 631 |
+
if trips:
|
| 632 |
+
print(f"\n🎬 Criando animação...")
|
| 633 |
+
visualize_trip(client_id=client_to_viz, trip_id=trips[0])
|
| 634 |
+
|
| 635 |
+
print("\n" + "=" * 60)
|
| 636 |
+
print("✅ Pronto! Use: visualize_trip(client_id=0, trip_id=706.0)")
|
| 637 |
+
print("=" * 60)
|
src/test_charts.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Script de teste para verificar a geração de animação com gráficos sincronizados
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from eved_vizu import EVEDVisualizer
|
| 6 |
+
|
| 7 |
+
# Criar visualizador
|
| 8 |
+
viz = EVEDVisualizer()
|
| 9 |
+
|
| 10 |
+
# Tentar gerar uma animação com o primeiro cliente disponível
|
| 11 |
+
try:
|
| 12 |
+
print("🔍 Buscando clientes disponíveis...")
|
| 13 |
+
clients = viz.get_available_clients('train')
|
| 14 |
+
|
| 15 |
+
if clients:
|
| 16 |
+
client_id = clients[0]
|
| 17 |
+
print(f"✅ Cliente {client_id} encontrado")
|
| 18 |
+
|
| 19 |
+
print("🔍 Buscando trips disponíveis...")
|
| 20 |
+
trips = viz.get_available_trips(client_id, 'train')
|
| 21 |
+
|
| 22 |
+
if trips:
|
| 23 |
+
trip_id = trips[0]
|
| 24 |
+
print(f"✅ Trip {trip_id} encontrada")
|
| 25 |
+
|
| 26 |
+
print("🎬 Gerando animação com gráficos...")
|
| 27 |
+
viz.create_animated_map(
|
| 28 |
+
client_id=client_id,
|
| 29 |
+
split='train',
|
| 30 |
+
trip_id=trip_id,
|
| 31 |
+
output_file='test_animation_with_charts.html'
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
print("\n✅ Sucesso! Abra test_animation_with_charts.html no navegador")
|
| 35 |
+
print("📊 Você deverá ver:")
|
| 36 |
+
print(" - Mapa animado no centro")
|
| 37 |
+
print(" - Painéis de gráficos no lado direito")
|
| 38 |
+
print(" - Controles de animação na parte inferior")
|
| 39 |
+
print(" - Gráficos sincronizados com a posição do veículo")
|
| 40 |
+
else:
|
| 41 |
+
print("❌ Nenhuma trip encontrada")
|
| 42 |
+
else:
|
| 43 |
+
print("❌ Nenhum cliente encontrado")
|
| 44 |
+
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"❌ Erro: {e}")
|
| 47 |
+
import traceback
|
| 48 |
+
traceback.print_exc()
|
src/trip_cliente_0_trip_706.html
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
|
| 5 |
+
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
| 6 |
+
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>
|
| 7 |
+
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"></script>
|
| 9 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
|
| 10 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css"/>
|
| 11 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"/>
|
| 12 |
+
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>
|
| 13 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.2.0/css/all.min.css"/>
|
| 14 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>
|
| 15 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>
|
| 16 |
+
|
| 17 |
+
<meta name="viewport" content="width=device-width,
|
| 18 |
+
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
| 19 |
+
<style>
|
| 20 |
+
#map_800878dfafeb753832dd2df9330a23c5 {
|
| 21 |
+
position: relative;
|
| 22 |
+
width: 100.0%;
|
| 23 |
+
height: 100.0%;
|
| 24 |
+
left: 0.0%;
|
| 25 |
+
top: 0.0%;
|
| 26 |
+
}
|
| 27 |
+
.leaflet-container { font-size: 1rem; }
|
| 28 |
+
</style>
|
| 29 |
+
|
| 30 |
+
<style>html, body {
|
| 31 |
+
width: 100%;
|
| 32 |
+
height: 100%;
|
| 33 |
+
margin: 0;
|
| 34 |
+
padding: 0;
|
| 35 |
+
}
|
| 36 |
+
</style>
|
| 37 |
+
|
| 38 |
+
<style>#map {
|
| 39 |
+
position:absolute;
|
| 40 |
+
top:0;
|
| 41 |
+
bottom:0;
|
| 42 |
+
right:0;
|
| 43 |
+
left:0;
|
| 44 |
+
}
|
| 45 |
+
</style>
|
| 46 |
+
|
| 47 |
+
<script>
|
| 48 |
+
L_NO_TOUCH = false;
|
| 49 |
+
L_DISABLE_3D = false;
|
| 50 |
+
</script>
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
</head>
|
| 54 |
+
<body>
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
|
| 58 |
+
<script>
|
| 59 |
+
(function() {
|
| 60 |
+
// Esperar o mapa carregar completamente
|
| 61 |
+
var checkMap = setInterval(function() {
|
| 62 |
+
if (typeof map_800878dfafeb753832dd2df9330a23c5 !== 'undefined') {
|
| 63 |
+
clearInterval(checkMap);
|
| 64 |
+
initAnimation();
|
| 65 |
+
}
|
| 66 |
+
}, 100);
|
| 67 |
+
|
| 68 |
+
function initAnimation() {
|
| 69 |
+
var coords = [[42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2782552778, -83.6988030556], [42.2782552778, -83.6988030556], [42.2782552778, -83.6988030556], [42.2782552778, -83.6988030556], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2798258333, -83.6990825], [42.2798258333, -83.6990825], [42.2798258333, -83.6990825], [42.2798258333, -83.6990825], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2814252778, -83.6996786111], [42.2814252778, -83.6996786111], [42.2814252778, -83.6996786111], [42.2814252778, -83.6996786111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2838672222, -83.7011169444], [42.2838672222, -83.7011169444], [42.2838672222, -83.7011169444], [42.2838672222, -83.7011169444], [42.2845911111, -83.7016091667], [42.2845911111, -83.7016091667], [42.2845911111, -83.7016091667], [42.2845911111, -83.7016091667], [42.28548, -83.7022116667], [42.28548, -83.7022116667], [42.28548, -83.7022116667], [42.28548, -83.7022116667], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2877736111, -83.7033411111], [42.2877736111, -83.7033411111], [42.2877736111, -83.7033411111], [42.2877736111, -83.7033411111], [42.2885552778, -83.70357], [42.2885552778, -83.70357], [42.2885552778, -83.70357], [42.2885552778, -83.70357], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2902852778, -83.7041030556], [42.2902852778, -83.7041030556], [42.2902852778, -83.7041030556], [42.2910025, -83.7044836111], [42.2910025, -83.7044836111], [42.2910025, -83.7044836111], [42.2916858333, -83.7049836111], [42.2916858333, -83.7049836111], [42.2916858333, -83.7049836111], [42.2916858333, -83.7049836111], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2931136111, -83.7052613889], [42.2931136111, -83.7052613889], [42.2931136111, -83.7052613889], [42.2931136111, -83.7052613889], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222]];
|
| 70 |
+
var variablesData = {"speed": [40.0, 40.0, 45.0, 47.0, 48.0, 52.0, 55.0, 59.0, 59.0, 60.0, 62.0, 63.0, 65.0, 66.0, 67.0, 67.0, 67.0, 65.0, 65.0, 65.0, 65.0, 64.0, 63.0, 63.0, 62.0, 62.0, 63.0, 63.0, 63.0, 64.0, 64.0, 65.0, 66.0, 66.0, 68.0, 69.0, 69.0, 68.0, 68.0, 66.0, 66.0, 66.0, 65.0, 65.0, 65.0, 65.0, 65.0, 65.0, 66.0, 66.0, 66.0, 67.0, 67.0, 67.0, 66.0, 65.0, 65.0, 64.0, 63.0, 63.0, 63.0, 64.0, 64.0, 65.0, 65.0, 65.0, 64.0, 64.0, 64.0, 64.0, 63.0, 63.0, 64.0, 64.0, 65.0, 64.0, 63.0, 62.0, 62.0, 62.0, 62.0, 62.0, 62.0, 62.0, 62.0, 61.0, 60.0, 60.0, 60.0, 60.0, 59.0, 59.0, 57.0, 55.0, 55.0, 50.0, 49.0, 49.0, 49.0, 47.0, 47.0, 45.0, 44.0, 44.0, 41.0, 41.0, 39.0, 36.0, 34.0, 31.0, 24.0, 24.0, 24.0, 19.0, 8.0, 8.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 11.0, 11.0, 11.0, 18.0, 20.0, 20.0, 20.0, 25.0, 25.0, 32.0, 32.0, 32.0, 37.0], "elevation": [230.5630340576172, 230.5630340576172, 230.5630340576172, 230.5952026367188, 230.62737121582032, 230.65953979492187, 230.69170837402345, 230.8247192382813, 230.9255615234375, 231.02640380859376, 231.12724609375, 231.2280883789062, 231.46845092773432, 231.70881347656243, 231.9491760253906, 232.1895385742188, 232.79419555664063, 233.1584899902344, 233.5227844238281, 233.88707885742187, 234.2513732910156, 234.2513732910156, 234.97682495117184, 235.70227661132813, 236.4277282714844, 237.15317993164064, 237.8022918701172, 237.7259521484375, 237.6496124267578, 237.57327270507807, 237.4969329833984, 237.4969329833984, 237.4969329833984, 237.4969329833984, 237.1122131347656, 236.7274932861328, 236.3427734375, 235.9580535888672, 235.57333374023443, 235.7831665039063, 235.99299926757817, 236.20283203125004, 236.4126647949219, 236.7957427978516, 236.96898803710943, 237.1422332763672, 237.315478515625, 237.63580322265625, 237.7828826904297, 237.92996215820312, 238.07704162597656, 238.3168884277344, 238.4096557617188, 238.5024230957032, 238.5951904296875, 238.687957763672, 238.687957763672, 238.687957763672, 238.7843505859375, 238.8807434082032, 238.9771362304688, 239.0735290527344, 239.169921875, 239.29015197753907, 239.4103820800781, 239.5306121826172, 239.65084228515624, 239.8400848388672, 239.90909729003903, 239.9781097412109, 240.0471221923828, 240.26297912597656, 240.40982360839843, 240.55666809082032, 240.7035125732422, 240.8503570556641, 241.26884765625005, 241.687338256836, 242.10582885742195, 242.74490661621093, 243.38398437500004, 243.6045715332032, 244.21497192382816, 244.8253723144532, 245.21518554687503, 245.6049987792969, 246.47822875976567, 246.96164550781253, 247.44506225585937, 247.92847900390623, 248.41189575195307, 248.41189575195307, 248.90110473632808, 249.3903137207031, 249.87952270507807, 250.36873168945309, 251.47001037597656, 252.082080078125, 252.69414978027345, 253.30621948242188, 253.9182891845703, 253.9182891845703, 253.9182891845703, 254.52310791015623, 255.1279266357422, 255.7327453613281, 256.3375640869141, 256.9423828125, 256.9423828125, 257.217431640625, 257.49248046875, 257.767529296875, 258.042578125, 258.317626953125, 258.317626953125, 258.34251708984374, 258.3674072265625, 258.3922973632813, 258.4171875, 258.4420776367188, 258.4420776367188, 258.4420776367188, 258.4420776367188, 258.4420776367188, 258.4420776367188, 258.4420776367188, 258.4650024414063, 258.4879272460938, 258.51085205078124, 258.5337768554688, 258.5567016601562, 258.5567016601562, 258.5567016601562, 258.5567016601562, 258.5567016601562, 258.5567016601562, 258.5567016601562]};
|
| 71 |
+
var currentIndex = 0;
|
| 72 |
+
var isPlaying = false;
|
| 73 |
+
var animationSpeed = 50;
|
| 74 |
+
var intervalId = null;
|
| 75 |
+
|
| 76 |
+
var pathColor = '#ff0000';
|
| 77 |
+
var markerColor = '#ffffff';
|
| 78 |
+
|
| 79 |
+
// Função para inicializar gráficos
|
| 80 |
+
function initCharts(data) {
|
| 81 |
+
var chartsObj = {};
|
| 82 |
+
|
| 83 |
+
// Gráfico de Velocidade
|
| 84 |
+
if (data.speed) {
|
| 85 |
+
var speedCtx = document.getElementById('speedChart').getContext('2d');
|
| 86 |
+
chartsObj.speed = new Chart(speedCtx, {
|
| 87 |
+
type: 'line',
|
| 88 |
+
data: {
|
| 89 |
+
labels: Array.from({length: data.speed.length}, (_, i) => i),
|
| 90 |
+
datasets: [{
|
| 91 |
+
label: 'Velocidade (km/h)',
|
| 92 |
+
data: data.speed,
|
| 93 |
+
borderColor: '#3498db',
|
| 94 |
+
backgroundColor: 'rgba(52, 152, 219, 0.1)',
|
| 95 |
+
borderWidth: 2,
|
| 96 |
+
tension: 0.4,
|
| 97 |
+
pointRadius: 0
|
| 98 |
+
}]
|
| 99 |
+
},
|
| 100 |
+
options: {
|
| 101 |
+
responsive: false,
|
| 102 |
+
maintainAspectRatio: false,
|
| 103 |
+
animation: false,
|
| 104 |
+
plugins: {
|
| 105 |
+
legend: {display: false}
|
| 106 |
+
},
|
| 107 |
+
scales: {
|
| 108 |
+
x: {display: false},
|
| 109 |
+
y: {
|
| 110 |
+
beginAtZero: true,
|
| 111 |
+
ticks: {color: '#666', font: {size: 10}}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
});
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// Gráfico de Energia/Combustível
|
| 119 |
+
if (data.energy) {
|
| 120 |
+
var energyCtx = document.getElementById('energyChart').getContext('2d');
|
| 121 |
+
chartsObj.energy = new Chart(energyCtx, {
|
| 122 |
+
type: 'line',
|
| 123 |
+
data: {
|
| 124 |
+
labels: Array.from({length: data.energy.length}, (_, i) => i),
|
| 125 |
+
datasets: [{
|
| 126 |
+
label: data.energy_label || 'Energia',
|
| 127 |
+
data: data.energy,
|
| 128 |
+
borderColor: '#e74c3c',
|
| 129 |
+
backgroundColor: 'rgba(231, 76, 60, 0.1)',
|
| 130 |
+
borderWidth: 2,
|
| 131 |
+
tension: 0.4,
|
| 132 |
+
pointRadius: 0
|
| 133 |
+
}]
|
| 134 |
+
},
|
| 135 |
+
options: {
|
| 136 |
+
responsive: false,
|
| 137 |
+
maintainAspectRatio: false,
|
| 138 |
+
animation: false,
|
| 139 |
+
plugins: {
|
| 140 |
+
legend: {display: false}
|
| 141 |
+
},
|
| 142 |
+
scales: {
|
| 143 |
+
x: {display: false},
|
| 144 |
+
y: {
|
| 145 |
+
beginAtZero: true,
|
| 146 |
+
ticks: {color: '#666', font: {size: 10}}
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
});
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Gráfico de Elevação
|
| 154 |
+
if (data.elevation) {
|
| 155 |
+
var elevCtx = document.getElementById('elevationChart').getContext('2d');
|
| 156 |
+
chartsObj.elevation = new Chart(elevCtx, {
|
| 157 |
+
type: 'line',
|
| 158 |
+
data: {
|
| 159 |
+
labels: Array.from({length: data.elevation.length}, (_, i) => i),
|
| 160 |
+
datasets: [{
|
| 161 |
+
label: 'Elevação (m)',
|
| 162 |
+
data: data.elevation,
|
| 163 |
+
borderColor: '#2ecc71',
|
| 164 |
+
backgroundColor: 'rgba(46, 204, 113, 0.1)',
|
| 165 |
+
borderWidth: 2,
|
| 166 |
+
tension: 0.4,
|
| 167 |
+
pointRadius: 0,
|
| 168 |
+
fill: true
|
| 169 |
+
}]
|
| 170 |
+
},
|
| 171 |
+
options: {
|
| 172 |
+
responsive: false,
|
| 173 |
+
maintainAspectRatio: false,
|
| 174 |
+
animation: false,
|
| 175 |
+
plugins: {
|
| 176 |
+
legend: {display: false}
|
| 177 |
+
},
|
| 178 |
+
scales: {
|
| 179 |
+
x: {display: false},
|
| 180 |
+
y: {
|
| 181 |
+
ticks: {color: '#666', font: {size: 10}}
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
});
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
return chartsObj;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
// Inicializar gráficos
|
| 192 |
+
var charts = initCharts(variablesData);
|
| 193 |
+
|
| 194 |
+
// Camada para o trajeto percorrido
|
| 195 |
+
var traveledPath = L.polyline([], {
|
| 196 |
+
color: pathColor,
|
| 197 |
+
weight: 5,
|
| 198 |
+
opacity: 0.9
|
| 199 |
+
}).addTo(map_800878dfafeb753832dd2df9330a23c5);
|
| 200 |
+
|
| 201 |
+
// Marcador do carro
|
| 202 |
+
var carMarker = L.circleMarker(coords[0], {
|
| 203 |
+
radius: 8,
|
| 204 |
+
color: pathColor,
|
| 205 |
+
weight: 3,
|
| 206 |
+
fillColor: markerColor,
|
| 207 |
+
fillOpacity: 1
|
| 208 |
+
}).addTo(map_800878dfafeb753832dd2df9330a23c5);
|
| 209 |
+
|
| 210 |
+
// Atualizar indicador de posição nos gráficos
|
| 211 |
+
function updateChartIndicators(index) {
|
| 212 |
+
Object.keys(charts).forEach(function(key) {
|
| 213 |
+
var chart = charts[key];
|
| 214 |
+
// Criar/atualizar linha vertical no gráfico
|
| 215 |
+
if (chart.options.plugins.annotation) {
|
| 216 |
+
chart.options.plugins.annotation.annotations.line1.xMin = index;
|
| 217 |
+
chart.options.plugins.annotation.annotations.line1.xMax = index;
|
| 218 |
+
} else {
|
| 219 |
+
// Atualizar viewport do gráfico
|
| 220 |
+
var maxVisible = 50; // Mostrar últimos 50 pontos
|
| 221 |
+
var minX = Math.max(0, index - maxVisible);
|
| 222 |
+
chart.options.scales.x.min = minX;
|
| 223 |
+
chart.options.scales.x.max = Math.max(maxVisible, index + 10);
|
| 224 |
+
}
|
| 225 |
+
chart.update('none');
|
| 226 |
+
});
|
| 227 |
+
|
| 228 |
+
// Atualizar valores numéricos
|
| 229 |
+
if (variablesData.speed) {
|
| 230 |
+
document.getElementById('speedValue').textContent =
|
| 231 |
+
variablesData.speed[index].toFixed(1) + ' km/h';
|
| 232 |
+
}
|
| 233 |
+
if (variablesData.energy) {
|
| 234 |
+
document.getElementById('energyValue').textContent =
|
| 235 |
+
variablesData.energy[index].toFixed(2);
|
| 236 |
+
}
|
| 237 |
+
if (variablesData.elevation) {
|
| 238 |
+
document.getElementById('elevationValue').textContent =
|
| 239 |
+
variablesData.elevation[index].toFixed(1) + ' m';
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
window.updateAnimation = function() {
|
| 244 |
+
// Só atualiza se estiver realmente tocando
|
| 245 |
+
if (!isPlaying) return;
|
| 246 |
+
|
| 247 |
+
if (currentIndex < coords.length) {
|
| 248 |
+
carMarker.setLatLng(coords[currentIndex]);
|
| 249 |
+
var traveled = coords.slice(0, currentIndex + 1);
|
| 250 |
+
traveledPath.setLatLngs(traveled);
|
| 251 |
+
|
| 252 |
+
document.getElementById('timeSlider').value = currentIndex;
|
| 253 |
+
document.getElementById('timeDisplay').textContent =
|
| 254 |
+
'Ponto: ' + (currentIndex + 1) + ' / ' + coords.length;
|
| 255 |
+
|
| 256 |
+
// Atualizar gráficos
|
| 257 |
+
updateChartIndicators(currentIndex);
|
| 258 |
+
|
| 259 |
+
currentIndex++;
|
| 260 |
+
} else {
|
| 261 |
+
currentIndex = 0;
|
| 262 |
+
}
|
| 263 |
+
};
|
| 264 |
+
|
| 265 |
+
window.playAnimation = function() {
|
| 266 |
+
if (!isPlaying) {
|
| 267 |
+
isPlaying = true;
|
| 268 |
+
document.getElementById('playBtn').innerHTML = '⏸ Pausar';
|
| 269 |
+
intervalId = setInterval(window.updateAnimation, animationSpeed);
|
| 270 |
+
}
|
| 271 |
+
};
|
| 272 |
+
|
| 273 |
+
window.pauseAnimation = function() {
|
| 274 |
+
isPlaying = false;
|
| 275 |
+
document.getElementById('playBtn').innerHTML = '▶ Play';
|
| 276 |
+
if (intervalId) {
|
| 277 |
+
clearInterval(intervalId);
|
| 278 |
+
intervalId = null;
|
| 279 |
+
}
|
| 280 |
+
};
|
| 281 |
+
|
| 282 |
+
window.togglePlay = function() {
|
| 283 |
+
if (isPlaying) {
|
| 284 |
+
window.pauseAnimation();
|
| 285 |
+
} else {
|
| 286 |
+
window.playAnimation();
|
| 287 |
+
}
|
| 288 |
+
};
|
| 289 |
+
|
| 290 |
+
window.resetAnimation = function() {
|
| 291 |
+
window.pauseAnimation();
|
| 292 |
+
currentIndex = 0;
|
| 293 |
+
carMarker.setLatLng(coords[0]);
|
| 294 |
+
traveledPath.setLatLngs([coords[0]]);
|
| 295 |
+
document.getElementById('timeSlider').value = 0;
|
| 296 |
+
document.getElementById('timeDisplay').textContent = 'Ponto: 1 / ' + coords.length;
|
| 297 |
+
};
|
| 298 |
+
|
| 299 |
+
window.changeSpeed = function(value) {
|
| 300 |
+
animationSpeed = 200 - value;
|
| 301 |
+
document.getElementById('speedDisplay').textContent = 'Velocidade: ' + value + '%';
|
| 302 |
+
|
| 303 |
+
if (isPlaying) {
|
| 304 |
+
clearInterval(intervalId);
|
| 305 |
+
intervalId = setInterval(window.updateAnimation, animationSpeed);
|
| 306 |
+
}
|
| 307 |
+
};
|
| 308 |
+
|
| 309 |
+
window.seekTo = function(index) {
|
| 310 |
+
currentIndex = parseInt(index);
|
| 311 |
+
carMarker.setLatLng(coords[currentIndex]);
|
| 312 |
+
var traveled = coords.slice(0, currentIndex + 1);
|
| 313 |
+
traveledPath.setLatLngs(traveled);
|
| 314 |
+
document.getElementById('timeDisplay').textContent =
|
| 315 |
+
'Ponto: ' + (currentIndex + 1) + ' / ' + coords.length;
|
| 316 |
+
|
| 317 |
+
// Atualizar gráficos ao usar o slider
|
| 318 |
+
updateChartIndicators(currentIndex);
|
| 319 |
+
};
|
| 320 |
+
|
| 321 |
+
// Auto-play
|
| 322 |
+
setTimeout(window.playAnimation, 1000);
|
| 323 |
+
}
|
| 324 |
+
})();
|
| 325 |
+
</script>
|
| 326 |
+
|
| 327 |
+
<!-- Painéis de Gráficos Laterais -->
|
| 328 |
+
<div style="position: fixed; right: 15px; top: 15px; width: 400px; z-index: 1000; max-height: 90vh; overflow-y: auto;">
|
| 329 |
+
<div style="background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); margin-bottom: 15px; border-left: 5px solid #3498db;"><h4 style="margin: 0 0 10px 0; font-size: 16px; color: #3498db; font-weight: bold;">⚡ Velocidade</h4><div id="speedValue" style="font-size: 32px; font-weight: bold; color: #3498db; margin-bottom: 15px; text-align: center;">0 km/h</div><canvas id="speedChart" width="360" height="120"></canvas></div>
|
| 330 |
+
|
| 331 |
+
<div style="background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); border-left: 5px solid #2ecc71;"><h4 style="margin: 0 0 10px 0; font-size: 16px; color: #2ecc71; font-weight: bold;">⛰️ Elevação</h4><div id="elevationValue" style="font-size: 32px; font-weight: bold; color: #2ecc71; margin-bottom: 15px; text-align: center;">0 m</div><canvas id="elevationChart" width="360" height="120"></canvas></div>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
<!-- Controles de Animação -->
|
| 335 |
+
<div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
|
| 336 |
+
background: white; padding: 15px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
| 337 |
+
z-index: 1000; min-width: 400px;">
|
| 338 |
+
<div style="text-align: center; margin-bottom: 10px;">
|
| 339 |
+
<h4 style="margin: 0 0 10px 0;">🚗 Combustão - Cliente 0</h4>
|
| 340 |
+
<div id="timeDisplay" style="font-size: 14px; color: #666;">Ponto: 1 / 137</div>
|
| 341 |
+
</div>
|
| 342 |
+
|
| 343 |
+
<div style="display: flex; gap: 10px; margin-bottom: 10px; justify-content: center;">
|
| 344 |
+
<button id="playBtn" onclick="togglePlay()"
|
| 345 |
+
style="padding: 10px 20px; font-size: 16px; cursor: pointer; border: none;
|
| 346 |
+
background: #ff0000; color: white; border-radius: 5px;">
|
| 347 |
+
▶ Play
|
| 348 |
+
</button>
|
| 349 |
+
<button onclick="resetAnimation()"
|
| 350 |
+
style="padding: 10px 20px; font-size: 16px; cursor: pointer; border: none;
|
| 351 |
+
background: #666; color: white; border-radius: 5px;">
|
| 352 |
+
🔄 Reiniciar
|
| 353 |
+
</button>
|
| 354 |
+
</div>
|
| 355 |
+
|
| 356 |
+
<div style="margin-bottom: 10px;">
|
| 357 |
+
<input type="range" id="timeSlider" min="0" max="136" value="0"
|
| 358 |
+
oninput="seekTo(this.value)"
|
| 359 |
+
style="width: 100%;">
|
| 360 |
+
</div>
|
| 361 |
+
|
| 362 |
+
<div>
|
| 363 |
+
<div id="speedDisplay" style="font-size: 12px; color: #666; margin-bottom: 5px;">
|
| 364 |
+
Velocidade: 100%
|
| 365 |
+
</div>
|
| 366 |
+
<input type="range" id="speedSlider" min="10" max="190" value="100"
|
| 367 |
+
oninput="changeSpeed(this.value)"
|
| 368 |
+
style="width: 100%;">
|
| 369 |
+
</div>
|
| 370 |
+
</div>
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
<div class="folium-map" id="map_800878dfafeb753832dd2df9330a23c5" ></div>
|
| 374 |
+
|
| 375 |
+
</body>
|
| 376 |
+
<script>
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
var map_800878dfafeb753832dd2df9330a23c5 = L.map(
|
| 380 |
+
"map_800878dfafeb753832dd2df9330a23c5",
|
| 381 |
+
{
|
| 382 |
+
center: [42.28783561840511, -83.7026126236854],
|
| 383 |
+
crs: L.CRS.EPSG3857,
|
| 384 |
+
...{
|
| 385 |
+
"zoom": 13,
|
| 386 |
+
"zoomControl": true,
|
| 387 |
+
"preferCanvas": false,
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
}
|
| 391 |
+
);
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
var tile_layer_58a14cc39991c665ede74c4e142e750a = L.tileLayer(
|
| 398 |
+
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
| 399 |
+
{
|
| 400 |
+
"minZoom": 0,
|
| 401 |
+
"maxZoom": 19,
|
| 402 |
+
"maxNativeZoom": 19,
|
| 403 |
+
"noWrap": false,
|
| 404 |
+
"attribution": "\u0026copy; \u003ca href=\"https://www.openstreetmap.org/copyright\"\u003eOpenStreetMap\u003c/a\u003e contributors",
|
| 405 |
+
"subdomains": "abc",
|
| 406 |
+
"detectRetina": false,
|
| 407 |
+
"tms": false,
|
| 408 |
+
"opacity": 1,
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
);
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
tile_layer_58a14cc39991c665ede74c4e142e750a.addTo(map_800878dfafeb753832dd2df9330a23c5);
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
var poly_line_e9c44cdc0aa4f16477e75fb82bac2504 = L.polyline(
|
| 418 |
+
[[42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2775583333, -83.6987497222], [42.2782552778, -83.6988030556], [42.2782552778, -83.6988030556], [42.2782552778, -83.6988030556], [42.2782552778, -83.6988030556], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2790125, -83.6989011111], [42.2798258333, -83.6990825], [42.2798258333, -83.6990825], [42.2798258333, -83.6990825], [42.2798258333, -83.6990825], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2806536111, -83.6993497222], [42.2814252778, -83.6996786111], [42.2814252778, -83.6996786111], [42.2814252778, -83.6996786111], [42.2814252778, -83.6996786111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2821922222, -83.7000411111], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2829597222, -83.7005038889], [42.2838672222, -83.7011169444], [42.2838672222, -83.7011169444], [42.2838672222, -83.7011169444], [42.2838672222, -83.7011169444], [42.2845911111, -83.7016091667], [42.2845911111, -83.7016091667], [42.2845911111, -83.7016091667], [42.2845911111, -83.7016091667], [42.28548, -83.7022116667], [42.28548, -83.7022116667], [42.28548, -83.7022116667], [42.28548, -83.7022116667], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2862297222, -83.7027197222], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2869772222, -83.7031025], [42.2877736111, -83.7033411111], [42.2877736111, -83.7033411111], [42.2877736111, -83.7033411111], [42.2877736111, -83.7033411111], [42.2885552778, -83.70357], [42.2885552778, -83.70357], [42.2885552778, -83.70357], [42.2885552778, -83.70357], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2895066667, -83.7038441667], [42.2902852778, -83.7041030556], [42.2902852778, -83.7041030556], [42.2902852778, -83.7041030556], [42.2910025, -83.7044836111], [42.2910025, -83.7044836111], [42.2910025, -83.7044836111], [42.2916858333, -83.7049836111], [42.2916858333, -83.7049836111], [42.2916858333, -83.7049836111], [42.2916858333, -83.7049836111], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2923916667, -83.7053377778], [42.2931136111, -83.7052613889], [42.2931136111, -83.7052613889], [42.2931136111, -83.7052613889], [42.2931136111, -83.7052613889], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2937136111, -83.7049280556], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2943641667, -83.704695], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2948241667, -83.7045802778], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2949858333, -83.7045213889], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222], [42.2950961111, -83.7045172222]],
|
| 419 |
+
{"bubblingMouseEvents": true, "color": "#cccccc", "dashArray": null, "dashOffset": null, "fill": false, "fillColor": "#cccccc", "fillOpacity": 0.2, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "noClip": false, "opacity": 0.5, "smoothFactor": 1.0, "stroke": true, "weight": 5}
|
| 420 |
+
).addTo(map_800878dfafeb753832dd2df9330a23c5);
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
var popup_49d98a7aed00d3635d09a9a11d4d362a = L.popup({
|
| 424 |
+
"maxWidth": "100%",
|
| 425 |
+
});
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
var html_8b547677f443b5ef0c2af2b87e25ad91 = $(`<div id="html_8b547677f443b5ef0c2af2b87e25ad91" style="width: 100.0%; height: 100.0%;">Trajeto completo - Combustão</div>`)[0];
|
| 430 |
+
popup_49d98a7aed00d3635d09a9a11d4d362a.setContent(html_8b547677f443b5ef0c2af2b87e25ad91);
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
poly_line_e9c44cdc0aa4f16477e75fb82bac2504.bindPopup(popup_49d98a7aed00d3635d09a9a11d4d362a)
|
| 435 |
+
;
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
var circle_marker_19b390385dc7c4c7854a5a7fc11f45e9 = L.circleMarker(
|
| 441 |
+
[42.2775583333, -83.6987497222],
|
| 442 |
+
{"bubblingMouseEvents": true, "color": "green", "dashArray": null, "dashOffset": null, "fill": true, "fillColor": "green", "fillOpacity": 0.8, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "opacity": 1.0, "radius": 10, "stroke": true, "weight": 3}
|
| 443 |
+
).addTo(map_800878dfafeb753832dd2df9330a23c5);
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
var popup_f15de743ca68f16ece2af83acd040e45 = L.popup({
|
| 447 |
+
"maxWidth": "100%",
|
| 448 |
+
});
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
var html_73e20b65ed5a888ad0008c392ccdbe93 = $(`<div id="html_73e20b65ed5a888ad0008c392ccdbe93" style="width: 100.0%; height: 100.0%;"><b>INÍCIO</b><br>Combustão</div>`)[0];
|
| 453 |
+
popup_f15de743ca68f16ece2af83acd040e45.setContent(html_73e20b65ed5a888ad0008c392ccdbe93);
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
circle_marker_19b390385dc7c4c7854a5a7fc11f45e9.bindPopup(popup_f15de743ca68f16ece2af83acd040e45)
|
| 458 |
+
;
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
var circle_marker_fc2c224a37c8956d47eb89ed4f3402b8 = L.circleMarker(
|
| 464 |
+
[42.2950961111, -83.7045172222],
|
| 465 |
+
{"bubblingMouseEvents": true, "color": "red", "dashArray": null, "dashOffset": null, "fill": true, "fillColor": "red", "fillOpacity": 0.8, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "opacity": 1.0, "radius": 10, "stroke": true, "weight": 3}
|
| 466 |
+
).addTo(map_800878dfafeb753832dd2df9330a23c5);
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
var popup_b32be036144b4cb12eaa6e6d036a5b92 = L.popup({
|
| 470 |
+
"maxWidth": "100%",
|
| 471 |
+
});
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
var html_dd691795a81c1ea059bec29c545fbb50 = $(`<div id="html_dd691795a81c1ea059bec29c545fbb50" style="width: 100.0%; height: 100.0%;"><b>FIM</b><br>Combustão</div>`)[0];
|
| 476 |
+
popup_b32be036144b4cb12eaa6e6d036a5b92.setContent(html_dd691795a81c1ea059bec29c545fbb50);
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
circle_marker_fc2c224a37c8956d47eb89ed4f3402b8.bindPopup(popup_b32be036144b4cb12eaa6e6d036a5b92)
|
| 481 |
+
;
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
</script>
|
| 486 |
+
</html>
|