From a14ebced22317b8605ffe94cad39cf3943209a74 Mon Sep 17 00:00:00 2001 From: rafuwo Date: Thu, 23 Oct 2025 12:50:51 +0000 Subject: [PATCH] Add mapa_incidencias.py --- mapa_incidencias.py | 503 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 mapa_incidencias.py diff --git a/mapa_incidencias.py b/mapa_incidencias.py new file mode 100644 index 0000000..0d19b54 --- /dev/null +++ b/mapa_incidencias.py @@ -0,0 +1,503 @@ +import pandas as pd +import folium +from folium.plugins import HeatMap +import json +import zipfile +import tempfile +import os +import xml.etree.ElementTree as ET +from collections import Counter +from datetime import datetime + +def cargar_y_analizar_todos_datos(ruta_incidencias): + """ + Carga datos y analiza TODOS los tipos de incidentes + """ + df = pd.read_csv(ruta_incidencias) + print(f"📊 Datos cargados: {len(df)} registros") + + # Análisis de TODOS los tipos de incidente + contador_tipos = Counter(df['tipo_incidente']) + total_incidencias = len(df) + + # Ordenar por frecuencia descendente + todos_tipos = [] + for tipo, count in contador_tipos.most_common(): + porcentaje = (count / total_incidencias) * 100 + todos_tipos.append({ + 'tipo': tipo, + 'count': count, + 'porcentaje': porcentaje + }) + + print(f"🎯 Se analizaron {len(todos_tipos)} tipos de incidentes diferentes") + + # Análisis temporal por tipo (si hay fechas) + tendencias = {} + if 'fincidencia' in df.columns: + df['fecha'] = pd.to_datetime(df['fincidencia'], errors='coerce') + df = df.dropna(subset=['fecha']) + df['mes'] = df['fecha'].dt.to_period('M') + + for item in todos_tipos: + tipo = item['tipo'] + datos_tipo = df[df['tipo_incidente'] == tipo] + tendencia_mensual = datos_tipo.groupby('mes').size() + + # Calcular tendencia (último mes vs penúltimo mes) + if len(tendencia_mensual) >= 2: + ultimo_mes = tendencia_mensual.iloc[-1] + penultimo_mes = tendencia_mensual.iloc[-2] + cambio = ultimo_mes - penultimo_mes + porcentaje_cambio = (cambio / penultimo_mes * 100) if penultimo_mes > 0 else 0 + else: + ultimo_mes = tendencia_mensual.iloc[-1] if len(tendencia_mensual) > 0 else 0 + penultimo_mes = 0 + cambio = 0 + porcentaje_cambio = 0 + + tendencias[tipo] = { + 'total': item['count'], + 'porcentaje': item['porcentaje'], + 'tendencia_mensual': tendencia_mensual, + 'ultimo_mes': ultimo_mes, + 'penultimo_mes': penultimo_mes, + 'cambio': cambio, + 'porcentaje_cambio': porcentaje_cambio + } + else: + # Si no hay fechas, solo mostrar datos básicos + for item in todos_tipos: + tipo = item['tipo'] + tendencias[tipo] = { + 'total': item['count'], + 'porcentaje': item['porcentaje'], + 'ultimo_mes': 0, + 'penultimo_mes': 0, + 'cambio': 0, + 'porcentaje_cambio': 0 + } + + # Procesar coordenadas para TODOS los tipos + coordenadas_por_tipo = {} + for item in todos_tipos: + coordenadas_por_tipo[item['tipo']] = [] + + for idx, row in df.iterrows(): + try: + coord_str = str(row['lat_long']).strip('() ') + if coord_str and coord_str != 'nan': + partes = coord_str.split(',') + if len(partes) == 2: + lat = float(partes[0].strip()) + lon = float(partes[1].strip()) + tipo_incidente = row['tipo_incidente'] + + if 15.0 < lat < 18.0 and -98.0 < lon < -94.0: + coordenadas_por_tipo[tipo_incidente].append([lat, lon]) + except: + continue + + return df, coordenadas_por_tipo, todos_tipos, tendencias + +def kmz_a_geojson_simple(ruta_kmz): + """ + Convierte KMZ a GeoJSON de forma simple y segura + """ + try: + with zipfile.ZipFile(ruta_kmz, 'r') as kmz: + kml_files = [f for f in kmz.namelist() if f.endswith('.kml')] + if not kml_files: + return None + + with tempfile.TemporaryDirectory() as temp_dir: + kmz.extract(kml_files[0], temp_dir) + kml_path = os.path.join(temp_dir, kml_files[0]) + return parsear_kml_manual(kml_path) + + except Exception as e: + print(f"❌ Error procesando KMZ: {e}") + return None + +def parsear_kml_manual(ruta_kml): + """ + Parsea KML manualmente + """ + try: + tree = ET.parse(ruta_kml) + root = tree.getroot() + ns = {'kml': 'http://www.opengis.net/kml/2.2'} + + features = [] + for placemark in root.findall('.//kml:Placemark', ns): + feature = { + 'type': 'Feature', + 'properties': {}, + 'geometry': {'type': 'Point', 'coordinates': [0, 0]} + } + + name_elem = placemark.find('kml:name', ns) + if name_elem is not None: + feature['properties']['name'] = name_elem.text + + coords_elem = placemark.find('.//kml:coordinates', ns) + if coords_elem is not None and coords_elem.text: + try: + coords_text = coords_elem.text.strip() + parts = coords_text.split(',') + if len(parts) >= 2: + lon = float(parts[0]) + lat = float(parts[1]) + feature['geometry']['coordinates'] = [lon, lat] + features.append(feature) + except ValueError: + continue + + return { + 'type': 'FeatureCollection', + 'features': features + } + + except Exception as e: + print(f"❌ Error parseando KML: {e}") + return None + +def crear_mapa_completo(ruta_incidencias, ruta_camaras_kmz=None, ruta_vehiculos_kmz=None): + """ + Crea mapa con análisis completo de TODOS los tipos de incidentes + """ + df, coordenadas_por_tipo, todos_tipos, tendencias = cargar_y_analizar_todos_datos(ruta_incidencias) + + # Calcular centro del mapa + todas_coordenadas = [] + for coords in coordenadas_por_tipo.values(): + todas_coordenadas.extend(coords) + + if not todas_coordenadas: + return None, df, todos_tipos, tendencias + + lats = [coord[0] for coord in todas_coordenadas] + lons = [coord[1] for coord in todas_coordenadas] + centro = [sum(lats)/len(lats), sum(lons)/len(lons)] + + m = folium.Map(location=centro, zoom_start=11) + + # Generar paleta de colores más amplia + colores_base = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', + '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', + '#FFA07A', '#20B2AA', '#778899', '#DEB887', '#5F9EA0', + '#FF69B4', '#BA55D3', '#9370DB', '#3CB371', '#FFD700'] + + # Extender paleta si hay más tipos + colores = colores_base + if len(todos_tipos) > len(colores_base): + import colorsys + colores_extra = [] + for i in range(len(todos_tipos) - len(colores_base)): + hue = i / (len(todos_tipos) - len(colores_base)) + rgb = colorsys.hsv_to_rgb(hue, 0.8, 0.9) + color = '#{:02x}{:02x}{:02x}'.format(int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255)) + colores_extra.append(color) + colores = colores_base + colores_extra + + # Capa base de heatmap (todos los tipos) + todas_las_coordenadas = [] + for coords in coordenadas_por_tipo.values(): + todas_las_coordenadas.extend(coords) + + HeatMap(todas_las_coordenadas, radius=15, blur=12, min_opacity=0.3).add_to(m) + + # Cargar capas KMZ + capas_kmz = {} + if ruta_camaras_kmz and os.path.exists(ruta_camaras_kmz): + print("📷 Cargando cámaras KMZ...") + capas_kmz['camaras'] = kmz_a_geojson_simple(ruta_camaras_kmz) + + if ruta_vehiculos_kmz and os.path.exists(ruta_vehiculos_kmz): + print("🚗 Cargando vehículos KMZ...") + capas_kmz['vehiculos'] = kmz_a_geojson_simple(ruta_vehiculos_kmz) + + # Añadir marcadores KMZ + for capa_nombre, geojson in capas_kmz.items(): + if geojson: + color = 'black' if capa_nombre == 'camaras' else 'blue' + icono = 'camera' if capa_nombre == 'camaras' else 'car' + etiqueta = '📹' if capa_nombre == 'camaras' else '🚗' + + for feature in geojson['features']: + coords = feature['geometry']['coordinates'] + nombre = feature['properties'].get('name', capa_nombre.title()) + + folium.Marker( + [coords[1], coords[0]], + popup=f"{etiqueta} {nombre}", + icon=folium.Icon(color=color, icon=icono, prefix='fa') + ).add_to(m) + + # Crear HTML para panel completo + crear_panel_completo(m, todos_tipos, tendencias, colores, len(df)) + + return m, df, todos_tipos, tendencias + +def crear_panel_completo(m, todos_tipos, tendencias, colores, total_incidencias): + """ + Crea panel de análisis completo de TODOS los tipos + """ + # HTML para el panel completo + panel_html = f''' +
+
+

+ 📈 ANÁLISIS COMPLETO +

+
+ 📊 TODOS LOS TIPOS ANALIZADOS
+ + {len(todos_tipos)} tipos diferentes
+ {total_incidencias:,} incidencias totales +
+
+
+ +
+
+

+ 🎯 DISTRIBUCIÓN DE TIPOS +

+ + Mostrando {min(50, len(todos_tipos))} de {len(todos_tipos)} + +
+
+ ''' + + # Estadísticas para cada tipo (mostrar máximo 50 para no saturar) + tipos_a_mostrar = todos_tipos[:50] + + for i, item in enumerate(tipos_a_mostrar): + tipo = item['tipo'] + count = item['count'] + porcentaje = item['porcentaje'] + + color = colores[i % len(colores)] + tendencia = tendencias.get(tipo, {}) + + # Determinar icono de tendencia + if tendencia.get('cambio', 0) > 0: + icono_tendencia = "📈" + color_tendencia = "#e74c3c" + texto_tendencia = f"+{tendencia['cambio']}" + elif tendencia.get('cambio', 0) < 0: + icono_tendencia = "📉" + color_tendencia = "#27ae60" + texto_tendencia = f"{tendencia['cambio']}" + else: + icono_tendencia = "➡️" + color_tendencia = "#95a5a6" + texto_tendencia = "Sin cambio" + + # Barra de porcentaje + ancho_barra = min(porcentaje * 3, 100) # Escalar para mejor visualización + + panel_html += f''' +
+
+ #{i+1} {tipo[:35]}{'...' if len(tipo) > 35 else ''} + {count:,} +
+ + +
+
+
+ +
+ {porcentaje:.1f}% + + {icono_tendencia} {texto_tendencia} + +
+
+ ''' + + # Mensaje si hay más tipos + if len(todos_tipos) > 50: + panel_html += f''' +
+ + ⚠️ Y {len(todos_tipos) - 50} tipos más con menor frecuencia + +
+ ''' + + panel_html += ''' +
+
+ +
+

+ 📋 RESUMEN GENERAL +

+
+ ''' + + # Resumen general + total_tipos = len(todos_tipos) + promedio_por_tipo = total_incidencias / total_tipos if total_tipos > 0 else 0 + + # Calcular diversidad (tipos con más del 1% vs menos del 1%) + tipos_significativos = sum(1 for item in todos_tipos if item['porcentaje'] >= 1) + tipos_menores = total_tipos - tipos_significativos + + panel_html += f''' +
+
+
+
Tipos Totales
+
{total_tipos}
+
+
+
Promedio/Tipo
+
{promedio_por_tipo:.1f}
+
+
+
+ Diversidad: {tipos_significativos} tipos ≥1% | {tipos_menores} tipos <1% +
+
+ ''' + + panel_html += ''' +
+
+ +
+ 💡 INFORMACIÓN DEL ANÁLISIS
+ + • Análisis completo de todos los tipos de incidentes
+ • Heatmap muestra densidad total de incidentes
+ • Ordenado por frecuencia descendente
+ • Tendencias mensuales para cada tipo +
+
+
+ + + + + ''' + + m.get_root().html.add_child(folium.Element(panel_html)) + +def main_mapa_completo(): + """ + Función principal con análisis completo + """ + try: + print("🔥 CREANDO MAPA CON ANÁLISIS COMPLETO") + print("="*60) + + ruta_incidencias = 'incidencias.csv' + ruta_camaras_kmz = 'pmi.kmz' + ruta_vehiculos_kmz = 'pmv.kmz' + + print("🗺️ Creando mapa con análisis completo...") + m, df, todos_tipos, tendencias = crear_mapa_completo( + ruta_incidencias, ruta_camaras_kmz, ruta_vehiculos_kmz + ) + + if m: + m.save('mapa_analisis_completo.html') + print("✅ Mapa con análisis completo guardado: mapa_analisis_completo.html") + + # Mostrar resumen en consola + print(f"\n📊 RESUMEN COMPLETO:") + print(f" Total incidencias analizadas: {len(df):,}") + print(f" Tipos diferentes identificados: {len(todos_tipos)}") + + # Estadísticas adicionales + promedio = len(df) / len(todos_tipos) + max_tipo = todos_tipos[0] + min_tipo = todos_tipos[-1] + + print(f" Promedio de incidencias por tipo: {promedio:.1f}") + print(f" Tipo más frecuente: '{max_tipo['tipo']}' ({max_tipo['count']:,} - {max_tipo['porcentaje']:.1f}%)") + print(f" Tipo menos frecuente: '{min_tipo['tipo']}' ({min_tipo['count']:,} - {min_tipo['porcentaje']:.1f}%)") + + print(f"\n🎯 TOP 10 TIPOS MÁS FRECUENTES:") + for i, item in enumerate(todos_tipos[:10], 1): + tendencia = tendencias.get(item['tipo'], {}) + cambio = tendencia.get('cambio', 0) + icono = "📈" if cambio > 0 else "📉" if cambio < 0 else "➡️" + print(f" {i:2d}. {item['tipo'][:40]:40} {item['porcentaje']:5.1f}% {icono}") + + print(f"\n🎉 ANÁLISIS COMPLETO FINALIZADO EXITOSAMENTE!") + print("="*60) + + except Exception as e: + print(f"\n❌ Error: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + main_mapa_completo() \ No newline at end of file