commit 5e869365f14eb8754bf183d4c3d58c9308e11227 Author: rafuwo Date: Thu Oct 23 12:48:15 2025 +0000 Add mapa_denuncias.py diff --git a/mapa_denuncias.py b/mapa_denuncias.py new file mode 100644 index 0000000..98f8db7 --- /dev/null +++ b/mapa_denuncias.py @@ -0,0 +1,485 @@ +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() \ No newline at end of file