cngsm commited on
Commit
b9d36c6
·
verified ·
1 Parent(s): ff75023

Upload gradio_location_tracker.py

Browse files
Files changed (1) hide show
  1. gradio_location_tracker.py +496 -0
gradio_location_tracker.py ADDED
@@ -0,0 +1,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import sqlite3
3
+ import json
4
+ import pandas as pd
5
+ from datetime import datetime, timedelta
6
+ import folium
7
+ from folium import plugins
8
+ import requests
9
+ import threading
10
+ import time
11
+ import os
12
+
13
+ # Configuração do banco de dados
14
+ def init_db():
15
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
16
+ cursor = conn.cursor()
17
+ cursor.execute('''
18
+ CREATE TABLE IF NOT EXISTS locations (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ device_id TEXT NOT NULL,
21
+ device_name TEXT NOT NULL,
22
+ latitude REAL NOT NULL,
23
+ longitude REAL NOT NULL,
24
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
25
+ accuracy REAL,
26
+ battery_level INTEGER,
27
+ ip_address TEXT
28
+ )
29
+ ''')
30
+ conn.commit()
31
+ conn.close()
32
+
33
+ # Função para adicionar localização
34
+ def add_location(device_id, device_name, latitude, longitude, accuracy=0, battery=100):
35
+ if not all([device_id, device_name, latitude, longitude]):
36
+ return "❌ Erro: Todos os campos são obrigatórios!"
37
+
38
+ try:
39
+ lat = float(latitude)
40
+ lng = float(longitude)
41
+ acc = float(accuracy) if accuracy else 0
42
+ bat = int(battery) if battery else 100
43
+
44
+ if not (-90 <= lat <= 90) or not (-180 lng <= 180):
45
+ return "❌ Erro: Coordenadas inválidas!"
46
+
47
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
48
+ cursor = conn.cursor()
49
+
50
+ cursor.execute('''
51
+ INSERT INTO locations (device_id, device_name, latitude, longitude, accuracy, battery_level)
52
+ VALUES (?, ?, ?, ?, ?, ?)
53
+ ''', (device_id, device_name, lat, lng, acc, bat))
54
+
55
+ conn.commit()
56
+ conn.close()
57
+
58
+ return f"✅ Localização adicionada com sucesso!\n📍 {lat:.6f}, {lng:.6f}\n⏰ {datetime.now().strftime('%H:%M:%S')}"
59
+
60
+ except ValueError:
61
+ return "❌ Erro: Valores numéricos inválidos!"
62
+ except Exception as e:
63
+ return f"❌ Erro: {str(e)}"
64
+
65
+ # Função para obter mapa atualizado
66
+ def get_updated_map():
67
+ try:
68
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
69
+
70
+ # Buscar localizações mais recentes de cada dispositivo
71
+ query = '''
72
+ SELECT device_id, device_name, latitude, longitude,
73
+ timestamp, accuracy, battery_level,
74
+ ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY timestamp DESC) as rn
75
+ FROM locations
76
+ WHERE timestamp > datetime('now', '-1 day')
77
+ '''
78
+
79
+ df = pd.read_sql_query(query, conn)
80
+ df_latest = df[df['rn'] == 1].copy() # Apenas a localização mais recente de cada dispositivo
81
+
82
+ conn.close()
83
+
84
+ if df_latest.empty:
85
+ # Mapa padrão centrado no Brasil
86
+ m = folium.Map(location=[-14.235, -51.9253], zoom_start=4)
87
+ folium.Marker(
88
+ [-14.235, -51.9253],
89
+ popup="Nenhum dispositivo encontrado",
90
+ icon=folium.Icon(color='gray', icon='info-sign')
91
+ ).add_to(m)
92
+ return m._repr_html_()
93
+
94
+ # Criar mapa centrado na média das localizações
95
+ center_lat = df_latest['latitude'].mean()
96
+ center_lng = df_latest['longitude'].mean()
97
+ m = folium.Map(location=[center_lat, center_lng], zoom_start=12)
98
+
99
+ # Adicionar marcadores para cada dispositivo
100
+ colors = ['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred',
101
+ 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']
102
+
103
+ for idx, row in df_latest.iterrows():
104
+ # Determinar status (online se última atualização foi há menos de 5 min)
105
+ last_update = datetime.strptime(row['timestamp'], '%Y-%m-%d %H:%M:%S')
106
+ is_online = (datetime.now() - last_update).total_seconds() < 300 # 5 minutos
107
+
108
+ # Escolher cor do marcador
109
+ color = colors[idx % len(colors)]
110
+ icon_color = 'green' if is_online else 'red'
111
+
112
+ # Informações do popup
113
+ popup_html = f"""
114
+ <div style="font-family: Arial; width: 200px;">
115
+ <h4>📱 {row['device_name']}</h4>
116
+ <p><b>ID:</b> {row['device_id']}</p>
117
+ <p><b>Status:</b> <span style="color: {'green' if is_online else 'red'};">
118
+ {'🟢 Online' if is_online else '🔴 Offline'}</span></p>
119
+ <p><b>Última atualização:</b><br>{row['timestamp']}</p>
120
+ <p><b>Coordenadas:</b><br>{row['latitude']:.6f}, {row['longitude']:.6f}</p>
121
+ <p><b>Precisão:</b> ±{row['accuracy']:.0f}m</p>
122
+ <p><b>Bateria:</b> 🔋 {row['battery_level']}%</p>
123
+ </div>
124
+ """
125
+
126
+ folium.Marker(
127
+ [row['latitude'], row['longitude']],
128
+ popup=folium.Popup(popup_html, min_width=200, max_width=300),
129
+ tooltip=f"{row['device_name']} - {'Online' if is_online else 'Offline'}",
130
+ icon=folium.Icon(color=icon_color, icon='phone', prefix='fa')
131
+ ).add_to(m)
132
+
133
+ # Adicionar círculo de precisão
134
+ folium.Circle(
135
+ [row['latitude'], row['longitude']],
136
+ radius=row['accuracy'],
137
+ popup=f"Área de precisão: ±{row['accuracy']:.0f}m",
138
+ color=color,
139
+ fill=True,
140
+ fillOpacity=0.1,
141
+ weight=1
142
+ ).add_to(m)
143
+
144
+ # Adicionar histórico de movimento (últimas 24h)
145
+ for device_id in df_latest['device_id'].unique():
146
+ device_history = df[df['device_id'] == device_id].sort_values('timestamp')
147
+ if len(device_history) > 1:
148
+ coordinates = [[row['latitude'], row['longitude']] for _, row in device_history.iterrows()]
149
+ folium.PolyLine(
150
+ coordinates,
151
+ color=colors[list(df_latest['device_id'].unique()).index(device_id) % len(colors)],
152
+ weight=2,
153
+ opacity=0.8,
154
+ popup=f"Trajeto: {device_history.iloc[0]['device_name']}"
155
+ ).add_to(m)
156
+
157
+ # Adicionar plugin de tela cheia
158
+ plugins.Fullscreen().add_to(m)
159
+
160
+ # Adicionar mini mapa
161
+ minimap = plugins.MiniMap()
162
+ m.add_child(minimap)
163
+
164
+ return m._repr_html_()
165
+
166
+ except Exception as e:
167
+ return f"<div style='color: red; padding: 20px;'>Erro ao carregar mapa: {str(e)}</div>"
168
+
169
+ # Função para obter estatísticas
170
+ def get_statistics():
171
+ try:
172
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
173
+
174
+ # Contar dispositivos únicos
175
+ cursor = conn.cursor()
176
+ cursor.execute("SELECT COUNT(DISTINCT device_id) FROM locations")
177
+ total_devices = cursor.fetchone()[0]
178
+
179
+ # Contar localizações nas últimas 24h
180
+ cursor.execute("SELECT COUNT(*) FROM locations WHERE timestamp > datetime('now', '-1 day')")
181
+ locations_24h = cursor.fetchone()[0]
182
+
183
+ # Dispositivos online (última atualização < 5 min)
184
+ cursor.execute("""
185
+ SELECT COUNT(DISTINCT device_id) FROM locations
186
+ WHERE timestamp > datetime('now', '-5 minutes')
187
+ """)
188
+ online_devices = cursor.fetchone()[0]
189
+
190
+ # Última atualização geral
191
+ cursor.execute("SELECT MAX(timestamp) FROM locations")
192
+ last_update = cursor.fetchone()[0]
193
+
194
+ conn.close()
195
+
196
+ stats = f"""
197
+ 📊 **ESTATÍSTICAS DO SISTEMA**
198
+
199
+ 🔢 **Total de Dispositivos:** {total_devices}
200
+ 🟢 **Dispositivos Online:** {online_devices}
201
+ 🔴 **Dispositivos Offline:** {total_devices - online_devices}
202
+
203
+ 📍 **Localizações (24h):** {locations_24h}
204
+ ⏰ **Última Atualização:** {last_update or 'Nunca'}
205
+
206
+ ---
207
+ *Atualizado automaticamente*
208
+ """
209
+
210
+ return stats
211
+
212
+ except Exception as e:
213
+ return f"❌ Erro ao carregar estatísticas: {str(e)}"
214
+
215
+ # Função para obter lista de dispositivos
216
+ def get_device_list():
217
+ try:
218
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
219
+
220
+ query = '''
221
+ SELECT
222
+ device_id,
223
+ device_name,
224
+ latitude,
225
+ longitude,
226
+ timestamp,
227
+ battery_level,
228
+ accuracy,
229
+ ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY timestamp DESC) as rn
230
+ FROM locations
231
+ WHERE timestamp > datetime('now', '-7 days')
232
+ '''
233
+
234
+ df = pd.read_sql_query(query, conn)
235
+ conn.close()
236
+
237
+ if df.empty:
238
+ return "Nenhum dispositivo encontrado nos últimos 7 dias."
239
+
240
+ # Filtrar apenas a localização mais recente de cada dispositivo
241
+ df_latest = df[df['rn'] == 1].copy()
242
+
243
+ # Adicionar coluna de status
244
+ df_latest['status'] = df_latest['timestamp'].apply(
245
+ lambda x: '🟢 Online' if (datetime.now() - datetime.strptime(x, '%Y-%m-%d %H:%M:%S')).total_seconds() < 300 else '🔴 Offline'
246
+ )
247
+
248
+ # Formatar coordenadas
249
+ df_latest['coordenadas'] = df_latest.apply(
250
+ lambda row: f"{row['latitude']:.6f}, {row['longitude']:.6f}", axis=1
251
+ )
252
+
253
+ # Selecionar colunas para exibição
254
+ display_df = df_latest[['device_name', 'device_id', 'status', 'coordenadas', 'timestamp', 'battery_level', 'accuracy']].copy()
255
+ display_df.columns = ['Nome', 'ID', 'Status', 'Coordenadas', 'Última Atualização', 'Bateria (%)', 'Precisão (m)']
256
+
257
+ return display_df
258
+
259
+ except Exception as e:
260
+ return f"Erro ao carregar lista de dispositivos: {str(e)}"
261
+
262
+ # Função para limpeza de dados antigos
263
+ def cleanup_old_data():
264
+ try:
265
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
266
+ cursor = conn.cursor()
267
+
268
+ # Remover dados com mais de 30 dias
269
+ cursor.execute("DELETE FROM locations WHERE timestamp < datetime('now', '-30 days')")
270
+ deleted = cursor.rowcount
271
+
272
+ conn.commit()
273
+ conn.close()
274
+
275
+ return f"✅ Limpeza concluída! {deleted} registros antigos removidos."
276
+
277
+ except Exception as e:
278
+ return f"❌ Erro na limpeza: {str(e)}"
279
+
280
+ # Função para exportar dados
281
+ def export_data():
282
+ try:
283
+ conn = sqlite3.connect('locations.db', check_same_thread=False)
284
+ df = pd.read_sql_query("SELECT * FROM locations ORDER BY timestamp DESC", conn)
285
+ conn.close()
286
+
287
+ if df.empty:
288
+ return None, "Nenhum dado para exportar."
289
+
290
+ # Salvar como CSV
291
+ filename = f"localizacoes_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
292
+ df.to_csv(filename, index=False, encoding='utf-8')
293
+
294
+ return filename, f"✅ Dados exportados com sucesso! {len(df)} registros salvos em {filename}"
295
+
296
+ except Exception as e:
297
+ return None, f"❌ Erro na exportação: {str(e)}"
298
+
299
+ # Interface principal usando Gradio
300
+ def create_interface():
301
+ init_db()
302
+
303
+ with gr.Blocks(title="📍 Monitor de Localização Familiar", theme=gr.themes.Soft()) as demo:
304
+ gr.Markdown("""
305
+ # 📍 Sistema de Monitoramento de Localização Familiar
306
+
307
+ **Monitore a localização de seus familiares com segurança e consentimento.**
308
+
309
+ > ⚠️ **Importante:** Use apenas com consentimento explícito. Respeite a privacidade!
310
+ """)
311
+
312
+ with gr.Tabs():
313
+ # Aba 1: Adicionar Localização
314
+ with gr.TabItem("📱 Enviar Localização"):
315
+ gr.Markdown("### 📡 Enviar Nova Localização")
316
+
317
+ with gr.Row():
318
+ device_id = gr.Textbox(
319
+ label="🆔 ID do Dispositivo",
320
+ placeholder="Ex: celular_joao",
321
+ info="Identificador único do dispositivo"
322
+ )
323
+ device_name = gr.Textbox(
324
+ label="📱 Nome do Dispositivo",
325
+ placeholder="Ex: Celular do João",
326
+ info="Nome amigável para identificação"
327
+ )
328
+
329
+ with gr.Row():
330
+ latitude = gr.Textbox(
331
+ label="🌐 Latitude",
332
+ placeholder="Ex: -23.550520",
333
+ info="Coordenada de latitude (GPS)"
334
+ )
335
+ longitude = gr.Textbox(
336
+ label="🌐 Longitude",
337
+ placeholder="Ex: -46.633309",
338
+ info="Coordenada de longitude (GPS)"
339
+ )
340
+
341
+ with gr.Row():
342
+ accuracy = gr.Number(
343
+ label="🎯 Precisão (metros)",
344
+ value=10,
345
+ info="Precisão da localização em metros"
346
+ )
347
+ battery = gr.Slider(
348
+ label="🔋 Nível da Bateria (%)",
349
+ minimum=0,
350
+ maximum=100,
351
+ value=100,
352
+ step=1
353
+ )
354
+
355
+ send_btn = gr.Button("📡 Enviar Localização", variant="primary", size="lg")
356
+ send_result = gr.Textbox(label="Resultado", interactive=False)
357
+
358
+ send_btn.click(
359
+ add_location,
360
+ inputs=[device_id, device_name, latitude, longitude, accuracy, battery],
361
+ outputs=send_result
362
+ )
363
+
364
+ gr.Markdown("""
365
+ **💡 Dica:** Para obter suas coordenadas atuais:
366
+ 1. Abra o Google Maps no seu celular
367
+ 2. Pressione e segure no seu local atual
368
+ 3. Copie as coordenadas que aparecem
369
+ """)
370
+
371
+ # Aba 2: Monitoramento
372
+ with gr.TabItem("🗺️ Mapa em Tempo Real"):
373
+ gr.Markdown("### 🌍 Visualização em Tempo Real")
374
+
375
+ with gr.Row():
376
+ refresh_btn = gr.Button("🔄 Atualizar Mapa", variant="secondary")
377
+ auto_refresh = gr.Checkbox(label="🔄 Atualização Automática (30s)", value=True)
378
+
379
+ map_html = gr.HTML(value=get_updated_map(), label="Mapa")
380
+
381
+ refresh_btn.click(
382
+ get_updated_map,
383
+ outputs=map_html
384
+ )
385
+
386
+ # Auto-refresh do mapa
387
+ demo.load(
388
+ get_updated_map,
389
+ outputs=map_html,
390
+ every=30
391
+ )
392
+
393
+ # Aba 3: Lista de Dispositivos
394
+ with gr.TabItem("📋 Lista de Dispositivos"):
395
+ gr.Markdown("### 📱 Dispositivos Monitorados")
396
+
397
+ refresh_devices_btn = gr.Button("🔄 Atualizar Lista", variant="secondary")
398
+ devices_table = gr.Dataframe(
399
+ value=get_device_list(),
400
+ label="Dispositivos Ativos",
401
+ interactive=False
402
+ )
403
+
404
+ refresh_devices_btn.click(
405
+ get_device_list,
406
+ outputs=devices_table
407
+ )
408
+
409
+ # Auto-refresh da tabela
410
+ demo.load(
411
+ get_device_list,
412
+ outputs=devices_table,
413
+ every=30
414
+ )
415
+
416
+ # Aba 4: Estatísticas
417
+ with gr.TabItem("📊 Estatísticas"):
418
+ gr.Markdown("### 📈 Estatísticas do Sistema")
419
+
420
+ refresh_stats_btn = gr.Button("🔄 Atualizar Estatísticas", variant="secondary")
421
+ stats_display = gr.Markdown(value=get_statistics())
422
+
423
+ refresh_stats_btn.click(
424
+ get_statistics,
425
+ outputs=stats_display
426
+ )
427
+
428
+ # Auto-refresh das estatísticas
429
+ demo.load(
430
+ get_statistics,
431
+ outputs=stats_display,
432
+ every=60
433
+ )
434
+
435
+ # Aba 5: Administração
436
+ with gr.TabItem("⚙️ Administração"):
437
+ gr.Markdown("### 🛠️ Ferramentas de Administração")
438
+
439
+ with gr.Row():
440
+ cleanup_btn = gr.Button("🧹 Limpar Dados Antigos", variant="secondary")
441
+ export_btn = gr.Button("📥 Exportar Dados", variant="secondary")
442
+
443
+ admin_result = gr.Textbox(label="Resultado da Operação", interactive=False)
444
+ export_file = gr.File(label="Arquivo Exportado", visible=False)
445
+
446
+ cleanup_btn.click(
447
+ cleanup_old_data,
448
+ outputs=admin_result
449
+ )
450
+
451
+ def handle_export():
452
+ file_path, message = export_data()
453
+ return message, file_path
454
+
455
+ export_btn.click(
456
+ handle_export,
457
+ outputs=[admin_result, export_file]
458
+ )
459
+
460
+ gr.Markdown("""
461
+ **⚠️ Informações Importantes:**
462
+ - Dados são mantidos por 30 dias automaticamente
463
+ - Use a limpeza manual se necessário
464
+ - Faça backups regulares dos dados importantes
465
+ - Este sistema respeita a privacidade - dados ficam no seu servidor
466
+ """)
467
+
468
+ gr.Markdown("""
469
+ ---
470
+ ### 📱 Como Usar no Celular:
471
+
472
+ 1. **Para Enviar Localização:** Use a aba "Enviar Localização"
473
+ 2. **Para Monitorar:** Use a aba "Mapa em Tempo Real"
474
+ 3. **Atualizações:** O sistema atualiza automaticamente
475
+
476
+ ### 🔒 Privacidade e Segurança:
477
+ - ✅ Dados armazenados localmente no seu servidor
478
+ - ✅ Sem compartilhamento com terceiros
479
+ - ✅ Use apenas com consentimento
480
+ - ✅ Transparência total sobre quando está ativo
481
+
482
+ **Desenvolvido com ❤️ para famílias que querem se manter conectadas com segurança.**
483
+ """)
484
+
485
+ return demo
486
+
487
+ # Executar aplicação
488
+ if __name__ == "__main__":
489
+ demo = create_interface()
490
+ demo.launch(
491
+ server_name="0.0.0.0", # Permite acesso de outros dispositivos na rede
492
+ server_port=7860, # Porta padrão do Gradio
493
+ share=False, # True para criar link público temporário
494
+ debug=True,
495
+ show_error=True
496
+ )