cngsm commited on
Commit
6fb7671
·
verified ·
1 Parent(s): 5fc83a5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -251
app.py CHANGED
@@ -4,10 +4,7 @@ import pandas as pd
4
  from datetime import datetime
5
  import folium
6
  from folium import plugins
7
- from fastapi import FastAPI, Request
8
- from fastapi.responses import HTMLResponse
9
- import uvicorn
10
- from threading import Thread
11
  from geopy.distance import geodesic
12
 
13
  # Configuração do banco de dados
@@ -23,306 +20,187 @@ def init_db():
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
  init_db()
34
- app = FastAPI()
35
-
36
- # Função para verificar movimento significativo
37
- def is_significant_movement(device_id, new_lat, new_lng):
38
- conn = sqlite3.connect('locations.db', check_same_thread=False)
39
- cursor = conn.cursor()
40
- cursor.execute('''
41
- SELECT latitude, longitude FROM locations
42
- WHERE device_id = ?
43
- ORDER BY timestamp DESC LIMIT 1
44
- ''', (device_id,))
45
- last_location = cursor.fetchone()
46
- conn.close()
47
-
48
- if not last_location:
49
- return True
50
-
51
- old_lat, old_lng = last_location
52
- distance = geodesic((old_lat, old_lng), (new_lat, new_lng)).meters
53
- return distance > 50
54
 
55
  # Função para adicionar localização
56
- def add_location(device_id, device_name, latitude, longitude, accuracy=0, battery=100):
57
  try:
58
  lat = float(latitude)
59
  lng = float(longitude)
60
- acc = float(accuracy) if accuracy else 0
61
- bat = int(battery) if battery else 100
62
 
63
  if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
64
  return "❌ Erro: Coordenadas inválidas!"
65
 
66
- if not is_significant_movement(device_id, lat, lng):
67
- return "✅ Localização recebida (sem movimento significativo)"
68
-
69
  conn = sqlite3.connect('locations.db', check_same_thread=False)
70
  cursor = conn.cursor()
71
 
72
  cursor.execute('''
73
  INSERT INTO locations (device_id, device_name, latitude, longitude, accuracy, battery_level)
74
  VALUES (?, ?, ?, ?, ?, ?)
75
- ''', (device_id, device_name, lat, lng, acc, bat))
76
 
77
  conn.commit()
78
  conn.close()
79
 
80
- return f"✅ Localização adicionada com sucesso!\n📍 {lat:.6f}, {lng:.6f}\n⏰ {datetime.now().strftime('%H:%M:%S')}"
81
 
82
- except ValueError:
83
- return "❌ Erro: Valores numéricos inválidos!"
84
  except Exception as e:
85
  return f"❌ Erro: {str(e)}"
86
 
87
- # Endpoint API
88
- @app.post("/api/location")
89
- async def receive_location(request: Request):
90
- data = await request.json()
91
- result = add_location(
92
- data.get('device_id'),
93
- data.get('device_name'),
94
- data.get('latitude'),
95
- data.get('longitude'),
96
- data.get('accuracy', 10),
97
- data.get('battery', 100)
98
- )
99
- return {"status": "success" if "✅" in result else "error", "message": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  # Função para obter mapa atualizado
102
  def get_updated_map():
103
  try:
104
  conn = sqlite3.connect('locations.db', check_same_thread=False)
105
- query = '''
106
  SELECT device_id, device_name, latitude, longitude,
107
- timestamp, accuracy, battery_level,
108
- ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY timestamp DESC) as rn
109
- FROM locations
110
- WHERE timestamp > datetime('now', '-1 day')
111
- '''
112
- df = pd.read_sql_query(query, conn)
113
- df_latest = df[df['rn'] == 1].copy()
114
  conn.close()
115
 
116
- if df_latest.empty:
117
  m = folium.Map(location=[-14.235, -51.9253], zoom_start=4)
118
- folium.Marker(
119
- [-14.235, -51.9253],
120
- popup="Nenhum dispositivo encontrado",
121
- icon=folium.Icon(color='gray', icon='info-sign')
122
- ).add_to(m)
123
  return m._repr_html_()
124
 
125
- center_lat = df_latest['latitude'].mean()
126
- center_lng = df_latest['longitude'].mean()
127
- m = folium.Map(location=[center_lat, center_lng], zoom_start=12)
128
 
129
- colors = ['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred',
130
- 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white',
131
- 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']
132
-
133
- for idx, row in df_latest.iterrows():
134
- last_update = datetime.strptime(row['timestamp'], '%Y-%m-%d %H:%M:%S')
135
- is_online = (datetime.now() - last_update).total_seconds() < 300
136
-
137
- popup_html = f"""
138
- <div style="font-family: Arial; width: 200px;">
139
- <h4>📱 {row['device_name']}</h4>
140
- <p><b>ID:</b> {row['device_id']}</p>
141
- <p><b>Status:</b> <span style="color: {'green' if is_online else 'red'};">
142
- {'🟢 Online' if is_online else '🔴 Offline'}</span></p>
143
- <p><b>Última atualização:</b><br>{row['timestamp']}</p>
144
- <p><b>Coordenadas:</b><br>{row['latitude']:.6f}, {row['longitude']:.6f}</p>
145
- <p><b>Precisão:</b> ±{row['accuracy']:.0f}m</p>
146
- <p><b>Bateria:</b> 🔋 {row['battery_level']}%</p>
147
- </div>
148
- """
149
-
150
  folium.Marker(
151
  [row['latitude'], row['longitude']],
152
- popup=folium.Popup(popup_html, min_width=200, max_width=300),
153
- tooltip=f"{row['device_name']} - {'Online' if is_online else 'Offline'}",
154
- icon=folium.Icon(color='green' if is_online else 'red', icon='phone', prefix='fa')
155
- ).add_to(m)
156
-
157
- folium.Circle(
158
- [row['latitude'], row['longitude']],
159
- radius=row['accuracy'],
160
- popup=f"Área de precisão: ±{row['accuracy']:.0f}m",
161
- color=colors[idx % len(colors)],
162
- fill=True,
163
- fillOpacity=0.1,
164
- weight=1
165
  ).add_to(m)
166
 
167
- plugins.Fullscreen().add_to(m)
168
- minimap = plugins.MiniMap()
169
- m.add_child(minimap)
170
-
171
  return m._repr_html_()
172
  except Exception as e:
173
- return f"<div style='color: red; padding: 20px;'>Erro ao carregar mapa: {str(e)}</div>"
174
 
175
  # Interface Gradio
176
  def create_interface():
177
- with gr.Blocks(title="📍 Monitor GPS em Tempo Real", theme=gr.themes.Soft()) as demo:
178
- gr.Markdown("""
179
- # 📍 Monitoramento GPS em Tempo Real
180
- **Visualize a localização de dispositivos com atualização automática**
181
- """)
182
-
183
- with gr.Tabs():
184
- with gr.TabItem("🗺️ Mapa em Tempo Real"):
185
- map_html = gr.HTML(label="Mapa GPS em Tempo Real")
186
- gr.Markdown("""
187
- ### Instruções para Celular:
188
- 1. Acesse: http://[SEU-SERVIDOR]:8000/mobile
189
- 2. Permita o acesso à localização
190
- 3. Sua posição será atualizada automaticamente
191
- """)
192
- gr.Timer(10.0, get_updated_map, [], [map_html])
 
 
193
 
194
- with gr.TabItem("📋 Dispositivos"):
195
- devices_table = gr.Dataframe(label="Dispositivos Conectados")
196
- gr.Timer(15.0, lambda: get_device_list(), [], [devices_table])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
  return demo
199
 
200
- # Página mobile
201
- mobile_page = """
202
- <!DOCTYPE html>
203
- <html>
204
- <head>
205
- <title>Enviar Localização</title>
206
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
207
- <style>
208
- body { font-family: Arial, sans-serif; padding: 20px; }
209
- button { padding: 10px 15px; font-size: 16px; margin: 10px 0; }
210
- #status { margin: 15px 0; font-weight: bold; }
211
- </style>
212
- </head>
213
- <body>
214
- <h1>Compartilhar Localização</h1>
215
- <div id="status">Pressione o botão para começar</div>
216
- <button id="startBtn">Iniciar Compartilhamento</button>
217
- <p id="coordinates"></p>
218
-
219
- <script>
220
- const deviceId = "celular_" + Math.random().toString(36).substring(2, 8);
221
- const deviceName = prompt("Digite seu nome:", "Usuário") || "Usuário";
222
- let watchId = null;
223
- let batteryLevel = 100;
224
-
225
- // Verificar suporte a bateria
226
- if (navigator.getBattery) {
227
- navigator.getBattery().then(battery => {
228
- batteryLevel = Math.round(battery.level * 100);
229
- battery.addEventListener('levelchange', () => {
230
- batteryLevel = Math.round(battery.level * 100);
231
- });
232
- });
233
- }
234
-
235
- document.getElementById("startBtn").addEventListener("click", async () => {
236
- if (watchId) {
237
- navigator.geolocation.clearWatch(watchId);
238
- watchId = null;
239
- document.getElementById("status").textContent = "Compartilhamento parado";
240
- document.getElementById("startBtn").textContent = "Iniciar Compartilhamento";
241
- return;
242
- }
243
-
244
- document.getElementById("status").textContent = "Obtendo localização...";
245
- document.getElementById("startBtn").textContent = "Parar Compartilhamento";
246
-
247
- watchId = navigator.geolocation.watchPosition(
248
- async (position) => {
249
- const { latitude, longitude, accuracy } = position.coords;
250
- document.getElementById("coordinates").innerHTML =
251
- `<strong>Coordenadas:</strong> ${latitude.toFixed(6)}, ${longitude.toFixed(6)}<br>
252
- <strong>Precisão:</strong> ${Math.round(accuracy)} metros<br>
253
- <strong>Bateria:</strong> ${batteryLevel}%`;
254
-
255
- try {
256
- const response = await fetch("/api/location", {
257
- method: "POST",
258
- headers: { "Content-Type": "application/json" },
259
- body: JSON.stringify({
260
- device_id: deviceId,
261
- device_name: deviceName,
262
- latitude,
263
- longitude,
264
- accuracy,
265
- battery: batteryLevel
266
- })
267
- });
268
- console.log("Localização enviada:", await response.json());
269
- } catch (error) {
270
- console.error("Erro ao enviar localização:", error);
271
- }
272
- },
273
- (error) => {
274
- document.getElementById("status").textContent = "Erro: " + error.message;
275
- },
276
- {
277
- enableHighAccuracy: true,
278
- maximumAge: 10000,
279
- timeout: 5000
280
- }
281
- );
282
- });
283
- </script>
284
- </body>
285
- </html>
286
- """
287
-
288
- @app.get("/mobile")
289
- async def mobile_client():
290
- return HTMLResponse(mobile_page)
291
-
292
- def get_device_list():
293
- try:
294
- conn = sqlite3.connect('locations.db', check_same_thread=False)
295
- query = '''
296
- SELECT device_id, device_name, latitude, longitude,
297
- timestamp, battery_level, accuracy,
298
- ROW_NUMBER() OVER (PARTITION BY device_id ORDER BY timestamp DESC) as rn
299
- FROM locations
300
- WHERE timestamp > datetime('now', '-1 day')
301
- '''
302
- df = pd.read_sql_query(query, conn)
303
- conn.close()
304
-
305
- if df.empty:
306
- return pd.DataFrame([{"Mensagem": "Nenhum dispositivo encontrado nas últimas 24 horas"}])
307
-
308
- df_latest = df[df['rn'] == 1].copy()
309
- df_latest['status'] = df_latest['timestamp'].apply(
310
- lambda x: '🟢 Online' if (datetime.now() - datetime.strptime(x, '%Y-%m-%d %H:%M:%S')).total_seconds() < 300 else '🔴 Offline'
311
- )
312
-
313
- display_df = df_latest[['device_name', 'device_id', 'status', 'latitude', 'longitude', 'timestamp', 'battery_level', 'accuracy']]
314
- display_df.columns = ['Nome', 'ID', 'Status', 'Latitude', 'Longitude', 'Última Atualização', 'Bateria (%)', 'Precisão (m)']
315
-
316
- return display_df
317
- except Exception as e:
318
- return pd.DataFrame([{"Erro": f"Erro ao carregar dispositivos: {str(e)}"}])
319
-
320
- def run_gradio():
321
- demo = create_interface()
322
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
323
 
324
  if __name__ == "__main__":
325
- gradio_thread = Thread(target=run_gradio)
326
- gradio_thread.start()
327
-
328
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
4
  from datetime import datetime
5
  import folium
6
  from folium import plugins
7
+ import webbrowser
 
 
 
8
  from geopy.distance import geodesic
9
 
10
  # Configuração do banco de dados
 
20
  longitude REAL NOT NULL,
21
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
22
  accuracy REAL,
23
+ battery_level INTEGER
 
24
  )
25
  ''')
26
  conn.commit()
27
  conn.close()
28
 
29
  init_db()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  # Função para adicionar localização
32
+ def add_location(device_id, device_name, latitude, longitude, accuracy=10, battery=100):
33
  try:
34
  lat = float(latitude)
35
  lng = float(longitude)
 
 
36
 
37
  if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
38
  return "❌ Erro: Coordenadas inválidas!"
39
 
 
 
 
40
  conn = sqlite3.connect('locations.db', check_same_thread=False)
41
  cursor = conn.cursor()
42
 
43
  cursor.execute('''
44
  INSERT INTO locations (device_id, device_name, latitude, longitude, accuracy, battery_level)
45
  VALUES (?, ?, ?, ?, ?, ?)
46
+ ''', (device_id, device_name, lat, lng, accuracy, battery))
47
 
48
  conn.commit()
49
  conn.close()
50
 
51
+ return f"✅ Localização atualizada!\n📍 {lat:.6f}, {lng:.6f}\n⏰ {datetime.now().strftime('%H:%M:%S')}"
52
 
 
 
53
  except Exception as e:
54
  return f"❌ Erro: {str(e)}"
55
 
56
+ # Função para capturar coordenadas via navegador
57
+ def start_gps_capture(device_id, device_name):
58
+ # Cria uma página HTML temporária para captura do GPS
59
+ html_content = f"""
60
+ <!DOCTYPE html>
61
+ <html>
62
+ <head>
63
+ <title>Captura de Localização</title>
64
+ <script>
65
+ function captureLocation() {{
66
+ if (navigator.geolocation) {{
67
+ navigator.geolocation.watchPosition(
68
+ position => {{
69
+ document.getElementById('coords').value =
70
+ `${{position.coords.latitude}},${{position.coords.longitude}}`;
71
+ document.getElementById('accuracy').value = position.coords.accuracy;
72
+ document.getElementById('battery').value =
73
+ navigator.getBattery ? (await navigator.getBattery()).level * 100 : 100;
74
+ document.forms['locForm'].submit();
75
+ }},
76
+ error => {{
77
+ alert("Erro: " + error.message);
78
+ }},
79
+ {{ enableHighAccuracy: true }}
80
+ );
81
+ }} else {{
82
+ alert("Geolocalização não suportada neste navegador.");
83
+ }}
84
+ }}
85
+ </script>
86
+ </head>
87
+ <body onload="captureLocation()">
88
+ <form id="locForm" action="http://localhost:7860/api/location" method="post">
89
+ <input type="hidden" name="device_id" value="{device_id}">
90
+ <input type="hidden" name="device_name" value="{device_name}">
91
+ <input type="hidden" id="coords" name="coords">
92
+ <input type="hidden" id="accuracy" name="accuracy" value="10">
93
+ <input type="hidden" id="battery" name="battery" value="100">
94
+ </form>
95
+ <h2>Capturando localização...</h2>
96
+ <p>Permaneça nesta página para atualizações automáticas.</p>
97
+ </body>
98
+ </html>
99
+ """
100
+
101
+ with open("gps_capture.html", "w") as f:
102
+ f.write(html_content)
103
+
104
+ webbrowser.open("gps_capture.html")
105
+ return "✔️ Abrindo captura de GPS no navegador..."
106
 
107
  # Função para obter mapa atualizado
108
  def get_updated_map():
109
  try:
110
  conn = sqlite3.connect('locations.db', check_same_thread=False)
111
+ df = pd.read_sql_query('''
112
  SELECT device_id, device_name, latitude, longitude,
113
+ timestamp, accuracy, battery_level
114
+ FROM locations
115
+ WHERE timestamp = (SELECT MAX(timestamp) FROM locations l2
116
+ WHERE l2.device_id = locations.device_id)
117
+ ''', conn)
 
 
118
  conn.close()
119
 
120
+ if df.empty:
121
  m = folium.Map(location=[-14.235, -51.9253], zoom_start=4)
122
+ folium.Marker([-14.235, -51.9253], popup="Nenhum dado disponível").add_to(m)
 
 
 
 
123
  return m._repr_html_()
124
 
125
+ m = folium.Map(location=[df.iloc[0]['latitude'], df.iloc[0]['longitude']], zoom_start=15)
 
 
126
 
127
+ for _, row in df.iterrows():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  folium.Marker(
129
  [row['latitude'], row['longitude']],
130
+ popup=f"{row['device_name']}<br>Atualizado: {row['timestamp']}",
131
+ icon=folium.Icon(color='blue')
 
 
 
 
 
 
 
 
 
 
 
132
  ).add_to(m)
133
 
 
 
 
 
134
  return m._repr_html_()
135
  except Exception as e:
136
+ return f"Erro: {str(e)}"
137
 
138
  # Interface Gradio
139
  def create_interface():
140
+ with gr.Blocks(title="📍 Monitor GPS Automático") as demo:
141
+ gr.Markdown("## 📍 Monitor de Localização Familiar")
142
+
143
+ with gr.Row():
144
+ device_id = gr.Textbox(label="ID do Dispositivo", value="celular_principal")
145
+ device_name = gr.Textbox(label="Nome para Exibição", value="Celular Principal")
146
+
147
+ with gr.Row():
148
+ with gr.Column():
149
+ gr.Markdown("### Captura Automática")
150
+ gps_btn = gr.Button("📡 Iniciar Captura GPS")
151
+ gps_status = gr.Textbox(interactive=False)
152
+
153
+ gps_btn.click(
154
+ start_gps_capture,
155
+ inputs=[device_id, device_name],
156
+ outputs=gps_status
157
+ )
158
 
159
+ with gr.Column():
160
+ gr.Markdown("### Inserção Manual")
161
+ with gr.Row():
162
+ latitude = gr.Number(label="Latitude")
163
+ longitude = gr.Number(label="Longitude")
164
+ manual_btn = gr.Button("📌 Inserir Manualmente")
165
+ manual_status = gr.Textbox(interactive=False)
166
+
167
+ manual_btn.click(
168
+ add_location,
169
+ inputs=[device_id, device_name, latitude, longitude],
170
+ outputs=manual_status
171
+ )
172
+
173
+ with gr.Tab("Mapa em Tempo Real"):
174
+ map_html = gr.HTML(get_updated_map())
175
+ gr.Timer(10.0, lambda: None, None, map_html, get_updated_map)
176
+
177
+ with gr.Tab("Dados"):
178
+ gr.Dataframe(
179
+ lambda: pd.read_sql_query(
180
+ "SELECT device_name, latitude, longitude, timestamp FROM locations ORDER BY timestamp DESC",
181
+ sqlite3.connect('locations.db')
182
+ ),
183
+ interactive=False
184
+ )
185
 
186
  return demo
187
 
188
+ # API simples para receber as coordenadas
189
+ app = gr.Interface(lambda: None, None, None, title="API").app
190
+ @app.post("/api/location")
191
+ async def api_receive_location(request: Request):
192
+ form = await request.form()
193
+ coords = form["coords"].split(",")
194
+ result = add_location(
195
+ form["device_id"],
196
+ form["device_name"],
197
+ coords[0],
198
+ coords[1],
199
+ form["accuracy"],
200
+ form["battery"]
201
+ )
202
+ return {"status": "success" if "✅" in result else "error"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
  if __name__ == "__main__":
205
+ demo = create_interface()
206
+ demo.launch(server_name="0.0.0.0", server_port=7860)