jwsouza13 commited on
Commit
1dcfc81
·
verified ·
1 Parent(s): a1b9692

Upload 9 files

Browse files
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/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
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
- title: EVED Trajectory Viewer
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: Visualização dos dados do eVED
12
- license: mit
13
- ---
14
-
15
- # Welcome to Streamlit!
16
-
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
-
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>