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 delitos """ df = pd.read_csv(ruta_incidencias) print(f"📊 Datos cargados: {len(df)} registros") # Análisis de TODOS los tipos de delitos contador_delitos = Counter(df['delito']) total_incidencias = len(df) # Ordenar por frecuencia descendente todos_tipos = [] for delito, count in contador_delitos.most_common(): porcentaje = (count / total_incidencias) * 100 todos_tipos.append({ 'tipo': delito, 'count': count, 'porcentaje': porcentaje }) print(f"🎯 Se analizaron {len(todos_tipos)} tipos de delitos diferentes") # Análisis por municipio contador_municipios = Counter(df['municipio']) top_municipios = contador_municipios.most_common(10) # 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: lat = row['latitud'] lon = row['longitud'] tipo_delito = row['delito'] # Validar coordenadas if pd.notna(lat) and pd.notna(lon) and 15.0 < lat < 18.0 and -98.0 < lon < -94.0: coordenadas_por_tipo[tipo_delito].append([lat, lon]) except: continue return df, coordenadas_por_tipo, todos_tipos, top_municipios 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 delitos """ df, coordenadas_por_tipo, todos_tipos, top_municipios = cargar_y_analizar_todos_datos(ruta_incidencias) # Calcular centro del mapa basado en las coordenadas todas_coordenadas = [] for coords in coordenadas_por_tipo.values(): todas_coordenadas.extend(coords) if todas_coordenadas: 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)] else: # Centro por defecto para Oaxaca centro = [17.0594, -96.7216] m = folium.Map(location=centro, zoom_start=12) # 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) if todas_las_coordenadas: HeatMap(todas_las_coordenadas, radius=15, blur=12, min_opacity=0.3).add_to(m) # Añadir marcadores individuales para los puntos for idx, row in df.iterrows(): try: if pd.notna(row['latitud']) and pd.notna(row['longitud']): folium.CircleMarker( location=[row['latitud'], row['longitud']], radius=4, popup=f"{row['delito']}
Municipio: {row['municipio']}", color='red', fill=True, fillOpacity=0.6 ).add_to(m) except: continue # 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, top_municipios, colores, len(df)) return m, df, todos_tipos, top_municipios def crear_panel_completo(m, todos_tipos, top_municipios, colores, total_incidencias): """ Crea panel de análisis completo de TODOS los tipos """ # HTML para el panel completo panel_html = f'''

📈 ANÁLISIS CRIMINALÍSTICO

📊 ANÁLISIS COMPLETO DE DELITOS
{len(todos_tipos)} tipos de delitos
{total_incidencias:,} incidentes registrados

🎯 DISTRIBUCIÓN DE DELITOS

Mostrando {min(30, len(todos_tipos))} de {len(todos_tipos)}
''' # Estadísticas para cada tipo (mostrar máximo 30 para no saturar) tipos_a_mostrar = todos_tipos[:30] for i, item in enumerate(tipos_a_mostrar): delito = item['tipo'] count = item['count'] porcentaje = item['porcentaje'] color = colores[i % len(colores)] # Barra de porcentaje ancho_barra = min(porcentaje * 3, 100) # Escalar para mejor visualización panel_html += f'''
#{i+1} {delito[:35]}{'...' if len(delito) > 35 else ''} {count:,}
{porcentaje:.1f}% del total Rank: {i+1}
''' # Mensaje si hay más tipos if len(todos_tipos) > 30: panel_html += f'''
⚠️ Y {len(todos_tipos) - 30} tipos de delitos más con menor frecuencia
''' panel_html += '''

🏙️ TOP 10 MUNICIPIOS

''' # Top municipios for i, (municipio, count) in enumerate(top_municipios): porcentaje = (count / total_incidencias) * 100 panel_html += f'''
#{i+1} {municipio[:25]}{'...' if len(municipio) > 25 else ''}
{porcentaje:.1f}% {count:,}
''' panel_html += '''

📋 RESUMEN ESTADÍSTICO

''' # Resumen general total_tipos = len(todos_tipos) promedio_por_tipo = total_incidencias / total_tipos if total_tipos > 0 else 0 # Calcular diversidad (delitos con más del 1% vs menos del 1%) delitos_significativos = sum(1 for item in todos_tipos if item['porcentaje'] >= 1) delitos_menores = total_tipos - delitos_significativos panel_html += f'''
Tipos de Delitos
{total_tipos}
Promedio/Delito
{promedio_por_tipo:.1f}
Concentración: {delitos_significativos} delitos ≥1% | {delitos_menores} delitos <1%
''' panel_html += '''
💡 INFORMACIÓN DEL MAPA
Heatmap muestra densidad total de delitos
Círculos rojos representan ubicaciones exactas
Ordenado por frecuencia de delitos
Análisis por tipo y municipio
''' 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 CRIMINALÍSTICO COMPLETO") print("="*60) ruta_incidencias = 'incidencias.csv' # Ajusta el nombre según tu archivo ruta_camaras_kmz = 'pmi.kmz' ruta_vehiculos_kmz = 'pmv.kmz' print("🗺️ Creando mapa con análisis criminalístico...") m, df, todos_tipos, top_municipios = crear_mapa_completo( ruta_incidencias, ruta_camaras_kmz, ruta_vehiculos_kmz ) if m: m.save('mapa_criminalistico_completo.html') print("✅ Mapa criminalístico guardado: mapa_criminalistico_completo.html") # Mostrar resumen en consola print(f"\n📊 RESUMEN CRIMINALÍSTICO:") print(f" Total de delitos analizados: {len(df):,}") print(f" Tipos de delitos diferentes: {len(todos_tipos)}") print(f" Municipios con incidencias: {len(set(df['municipio']))}") # Estadísticas adicionales promedio = len(df) / len(todos_tipos) max_delito = todos_tipos[0] min_delito = todos_tipos[-1] print(f" Promedio de ocurrencias por delito: {promedio:.1f}") print(f" Delito más frecuente: '{max_delito['tipo']}' ({max_delito['count']:,} - {max_delito['porcentaje']:.1f}%)") print(f" Delito menos frecuente: '{min_delito['tipo']}' ({min_delito['count']:,} - {min_delito['porcentaje']:.1f}%)") print(f"\n🎯 TOP 10 DELITOS MÁS COMUNES:") for i, item in enumerate(todos_tipos[:10], 1): print(f" {i:2d}. {item['tipo'][:40]:40} {item['porcentaje']:5.1f}% ({item['count']:,} casos)") print(f"\n🏙️ TOP 5 MUNICIPIOS:") for i, (municipio, count) in enumerate(top_municipios[:5], 1): porcentaje = (count / len(df)) * 100 print(f" {i:2d}. {municipio[:30]:30} {porcentaje:5.1f}% ({count:,} casos)") print(f"\n🎉 ANÁLISIS CRIMINALÍSTICO COMPLETADO EXITOSAMENTE!") print("="*60) except Exception as e: print(f"\n❌ Error: {e}") import traceback traceback.print_exc() if __name__ == "__main__": main_mapa_completo()