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'''