Add mapa_denuncias.py
This commit is contained in:
485
mapa_denuncias.py
Normal file
485
mapa_denuncias.py
Normal file
@@ -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"<b>{row['delito']}</b><br>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'''
|
||||
<div id="completePanel" style="
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 3px solid #2c3e50;
|
||||
border-radius: 15px;
|
||||
z-index: 1000;
|
||||
width: 520px;
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
|
||||
font-family: Arial, sans-serif;
|
||||
">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<h2 style="margin: 0; color: #2c3e50; font-size: 22px;">
|
||||
📈 ANÁLISIS CRIMINALÍSTICO
|
||||
</h2>
|
||||
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px; border-radius: 8px; margin-top: 10px;">
|
||||
<strong>📊 ANÁLISIS COMPLETO DE DELITOS</strong><br>
|
||||
<span style="font-size: 12px;">
|
||||
{len(todos_tipos)} tipos de delitos<br>
|
||||
{total_incidencias:,} incidentes registrados
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h3 style="margin: 0; color: #34495e; font-size: 16px;">
|
||||
🎯 DISTRIBUCIÓN DE DELITOS
|
||||
</h3>
|
||||
<span style="font-size: 11px; background: #ecf0f1; padding: 4px 8px; border-radius: 12px;">
|
||||
Mostrando {min(30, len(todos_tipos))} de {len(todos_tipos)}
|
||||
</span>
|
||||
</div>
|
||||
<div style="max-height: 300px; overflow-y: auto; border: 1px solid #bdc3c7; border-radius: 8px; padding: 10px; margin-top: 10px;">
|
||||
'''
|
||||
|
||||
# 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'''
|
||||
<div style="margin-bottom: 8px; padding: 8px; background: #f8f9fa; border-radius: 6px; border-left: 3px solid {color};">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
|
||||
<strong style="font-size: 12px;">#{i+1} {delito[:35]}{'...' if len(delito) > 35 else ''}</strong>
|
||||
<span style="font-size: 11px; color: #2c3e50; font-weight: bold;">{count:,}</span>
|
||||
</div>
|
||||
|
||||
<!-- Barra de porcentaje -->
|
||||
<div style="background: #e9ecef; border-radius: 3px; height: 6px; margin-bottom: 4px; overflow: hidden;">
|
||||
<div style="background: {color}; height: 100%; width: {ancho_barra}%; border-radius: 3px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; font-size: 10px;">
|
||||
<span style="color: #6c757d;">{porcentaje:.1f}% del total</span>
|
||||
<span style="color: #6c757d;">Rank: {i+1}</span>
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
# Mensaje si hay más tipos
|
||||
if len(todos_tipos) > 30:
|
||||
panel_html += f'''
|
||||
<div style="text-align: center; padding: 10px; background: #fff3cd; border-radius: 6px; margin-top: 10px;">
|
||||
<span style="font-size: 11px; color: #856404;">
|
||||
⚠️ Y {len(todos_tipos) - 30} tipos de delitos más con menor frecuencia
|
||||
</span>
|
||||
</div>
|
||||
'''
|
||||
|
||||
panel_html += '''
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<h3 style="margin: 0 0 10px 0; color: #34495e; font-size: 16px;">
|
||||
🏙️ TOP 10 MUNICIPIOS
|
||||
</h3>
|
||||
<div style="max-height: 150px; overflow-y: auto; border: 1px solid #bdc3c7; border-radius: 8px; padding: 10px;">
|
||||
'''
|
||||
|
||||
# Top municipios
|
||||
for i, (municipio, count) in enumerate(top_municipios):
|
||||
porcentaje = (count / total_incidencias) * 100
|
||||
panel_html += f'''
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; padding: 4px;">
|
||||
<span style="font-size: 11px; font-weight: bold;">#{i+1} {municipio[:25]}{'...' if len(municipio) > 25 else ''}</span>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 10px; color: #6c757d;">{porcentaje:.1f}%</span>
|
||||
<span style="font-size: 11px; background: #e9ecef; padding: 2px 6px; border-radius: 10px;">{count:,}</span>
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
panel_html += '''
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<h3 style="margin: 0 0 10px 0; color: #34495e; font-size: 16px;">
|
||||
📋 RESUMEN ESTADÍSTICO
|
||||
</h3>
|
||||
<div style="background: #fff3cd; padding: 12px; border-radius: 8px; border: 1px solid #ffeaa7;">
|
||||
'''
|
||||
|
||||
# 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'''
|
||||
<div style="text-align: center;">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
|
||||
<div style="background: #e8f5e8; padding: 8px; border-radius: 6px;">
|
||||
<div style="font-size: 12px; font-weight: bold; color: #27ae60;">Tipos de Delitos</div>
|
||||
<div style="font-size: 16px; font-weight: bold;">{total_tipos}</div>
|
||||
</div>
|
||||
<div style="background: #e8f4fd; padding: 8px; border-radius: 6px;">
|
||||
<div style="font-size: 12px; font-weight: bold; color: #3498db;">Promedio/Delito</div>
|
||||
<div style="font-size: 16px; font-weight: bold;">{promedio_por_tipo:.1f}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #6c757d;">
|
||||
<strong>Concentración:</strong> {delitos_significativos} delitos ≥1% | {delitos_menores} delitos <1%
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
panel_html += '''
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background: #d4edda; padding: 12px; border-radius: 8px; border: 1px solid #c3e6cb;">
|
||||
<strong>💡 INFORMACIÓN DEL MAPA</strong><br>
|
||||
<span style="font-size: 11px;">
|
||||
• <strong>Heatmap</strong> muestra densidad total de delitos<br>
|
||||
• <strong>Círculos rojos</strong> representan ubicaciones exactas<br>
|
||||
• <strong>Ordenado</strong> por frecuencia de delitos<br>
|
||||
• <strong>Análisis</strong> por tipo y municipio
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Hacer el panel arrastrable
|
||||
const panel = document.getElementById('completePanel');
|
||||
let isDragging = false;
|
||||
let dragOffset = {x: 0, y: 0};
|
||||
|
||||
panel.addEventListener('mousedown', function(e) {
|
||||
isDragging = true;
|
||||
dragOffset.x = e.clientX - panel.getBoundingClientRect().left;
|
||||
dragOffset.y = e.clientY - panel.getBoundingClientRect().top;
|
||||
panel.style.cursor = 'grabbing';
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', function(e) {
|
||||
if (isDragging) {
|
||||
panel.style.left = (e.clientX - dragOffset.x) + 'px';
|
||||
panel.style.top = (e.clientY - dragOffset.y) + 'px';
|
||||
panel.style.right = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', function() {
|
||||
isDragging = false;
|
||||
panel.style.cursor = 'grab';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#completePanel {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
#completePanel:hover {
|
||||
box-shadow: 0 12px 35px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
#completePanel::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#completePanel::-webkit-scrollbar-thumb {
|
||||
background: #bdc3c7;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#completePanel::-webkit-scrollbar-thumb:hover {
|
||||
background: #95a5a6;
|
||||
}
|
||||
</style>
|
||||
'''
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user