mstadeo / segvial

dashboard de seguridad vial
GNU Affero General Public License v3.0
0 stars 0 forks source link

Geocodificar intersecciones #7

Open abrilr1604 opened 3 months ago

abrilr1604 commented 3 months ago

Buenas! Comento un poco lo que estuve haciendo para geocodificar los accidentes ocurridos en intersecciones de calles. Utilicé como base la notebook 'geocode_intersections', donde mediante la función 'buscar_intersecciones_2_calles' se agrega como valor a la columna 'inter':

corrí el código para todos los accidentes en intersecciones en Rosario, llevó bastante tiempo (un poco más de 3 horas) y guardé el resultado en un csv.

De los 30108 accidentes en intersecciones entre 2 calles, sólo 8760 se pudieron geocodificar.

Como una gran parte de las calles están mal escritas y no se pudieron geocodificar, hay que ir viendo en los resultados estos errores y modificarlos. Para eso yo creé esta función, que de momento va bien:

correciones = {
    'nicolas': 'Nicolás',
    'Bv.': '',
    'Av.': '',
    'Av': '',
    'Avenida': '',
    'pcias.': 'Provincias',
    'pcias' : 'Provincias',
    'prov' : 'Provincias',
    'ov': 'Ovidio'
}

def corregir_nombres(lista):
    '''Recorre las palabras de cada elemento de la lista. Si alguna tiene un error,
    la corrige según el diccionario correciones'''
    lista_corregida = []
    for item in lista:
        palabras = item.split() 
        palabras_corregidas = []
        for word in palabras:
            for palabra_mal in correciones:
                if palabra_mal.lower() == word.lower():
                    word = correciones[palabra_mal]  
            palabras_corregidas.append(word)
        lista_corregida.append(" ".join(palabras_corregidas))  
    return lista_corregida

test_intersecciones['calle_lista'] = test_intersecciones['calle_lista'].apply(corregir_nombres)

Lo probé con las primeras 20 filas, pero como son muchas calles mal escritas, no se bien cómo ir filtrando para no perder tanto tiempo. Otra cosa, yo supongo que las correcciones que estoy haciendo van a funcionar para geocodificar, pero esto no lo voy a poder confirmar hasta que corra de nuevo la primer función 'buscar_intersecciones_2_calles'.

¿Lo mejor sería que agregue algunas correcciones más y vuelva a correr la primer función para las observaciones que no pudieron ser geocodificadas y así sucesivamente?

mstadeo commented 3 months ago

@abrilr1604 Creo que vamos a tener que hacer las correcciones que tienen "typos" a mano. Pero pensemos alguna estrategia para trabajar menos :nerd_face: . Te comparto algunas ideas:

Otra cosa que se me ocurre es que busques algún código para detectar similitudes entre registros, como lo que vimos en fundamentos. Luego, podías buscar a qué se parece el nombre de una calle en la DB de accidentes en la DB de OSM. No sé si te va a ahorrar tiempo, pero es más interesante :nerd_face: Tal vez puede ser útil para casos más difíciles.

abrilr1604 commented 3 months ago

@mstadeo hola! estuve probando bastantes cosas, pero voy a comentar lo que más me resultó para que estén al tanto:

primero, recorro el df y guardo en una lista 'calles_equivocadas' el nombre de la calle que no reconoció osm.

calles_equivocadas = []
for index, value in intersecciones['inter'].items():
    if value == 'ppal calles no encontradas':
        calles_equivocadas.append(intersecciones.loc[index, 'calle_lista'][0])
    elif str(value) == 'sec calles no encontradas':
        calles_equivocadas.append(intersecciones.loc[index, 'calle_lista'][1])
    elif str(value) == 'ambas calles no encontradas':
        calles_equivocadas.extend(intersecciones.loc[index, 'calle_lista'])

calles_unicas = list(set(calles_equivocadas))
len(calles_unicas)

de ahí obtuve el número 2152, es decir que en total hay 2152 calles mal escritas.

Por otro lado, en una lista 'calles_osm' guardé todas las calles de Rosario que OSM tiene en su bbdd, para obtener el nombre correcto y que se puedan geocodificar.

Por último, y acá es donde tardé un poco porque estuve intentando varias técnicas, traté de encontrar la forma de 'matchear' cada palabra mal escrita (de la lista calles_equivocadas') con su palabra correcta de la lista 'calles_osm'. Intenté con la distancia de Jaro Winkler y la distancia de Levenshtein que se ve en la carrera, pero los resultaron fueron bastante malos.

Entonces, encontré en internet el método _get_closematches de difflib y los resultados fueron mejores. De todas formas, decidí asegurarme con una probabilidad del 0.7 que establecí por parámetro, que todas las palabras que encontró similaridad sean por acentos o palabras abreviadas, porque con una probabilidad más baja da mayor cantidad de errores.

import difflib

matches = {}

for calle_erronea in calles_equivocadas:
    closest_match = difflib.get_close_matches(calle_erronea, calles_osm, cutoff=0.7, n=1)
    if closest_match:
        matches[calle_erronea] = closest_match[0] # si encuentro un match --> lo guardo en el diccionario

for calle_erronea, closest_match in matches.items():
    print(f"Closest match for '{calle_erronea}': '{closest_match}'")

Estos son algunos ejemplos de los resultados: Closest match for 'olavarria': 'Olavarría' Closest match for 'Rio De Janeiro': 'Río de Janeiro' Closest match for 'san martin': 'San Martín' Closest match for 'san sebastian': 'San Sebastián' Closest match for 'av. belgrano': 'Belgrano' Closest match for 'av. 27 de febrero': '27 de Febrero' Closest match for 'av. pellegrini': 'Pellegrini'

Ahora, me queda chequear uno por uno, eliminar del diccionario los que no me convencen o están mal, a mano corregir otros nombres que no haya encontrado coincidencia, y volver a correr el código de las geocodificaciones. Apenas lo tenga, vuelvo a escribir por acá!

mstadeo commented 3 months ago

@abrilr1604 me parece muy bien lo que hiciste :partying_face:

Además, necesito que guardemos en otra columna los nombres de las calles según OSM. Es decir, si en la DB de accidentes tenemos la intersección orono y 9 de julio y en la DB de OSM es Oroño y Nueve de Julio, necesitamos guardar en una nueva columna algo así como [Oroño, Nueve de Julio]. Esto nos va a servir para un futuro paso donde queremos calcular la cantidad de accidentes por calle/avenida.

Esto lo necesitamos para todos los puntos que pudieron ser geocodificados. Creo que lo ideal sería tener un código final y luego volver a correrlo. Pero lo hablamos en la reunión de hoy.

mstadeo commented 3 months ago

@abrilr1604 mencionó que la mayoría de calles en OSM no tienen el bis. Debido a que geocodificamos intersecciones, en este momento no es relevante. Por ejemplo, Moreno Bis y Brown es lo mimso que Moreno y Brown

abrilr1604 commented 2 months ago

@mstadeo Buenas! Estuve toda la semana trabajando en este issue y si bien todavía no ejecuté el código para los 30.108 accidentes en intersecciones (si probé que funcione en los primeros 20) paso a comentar alguno de los problemas con los que me fui encontrando porque el código final (que todavía me queda organizarlo para subir) es bastante largo porque tuve que ir yendo por partes ya que había bastantes problemas.

Primero, establecí un "criterio" para unificar algunos nombres de calles más comunes que se pueden escribir de distintas formas (ej: OROÑO, oroño, Oroño, bv. Oroño, nicasio O), lo cual redujo muchísimo la cantidad de calles mal escritas. Después, como había comentado antes, busqué de encontrar similitudes automáticas con una librería. Esto me ayudó bastante a agilizar el proceso, pero así como había bastantes que estaban bien, otras estaban mal (ya sea porque estaban muy abreviadas, con errores de ortografía o habían cambiado de nombre) y las tuve que revisar una por una.

Por otro lado, de aquellas calles que no detectó similitud, también las revisé una por una. Hubo muchos casos en donde pude determinar el nombre correcto (siempre chequeando que se cumpla la intersección), en otras fue imposible (ej. 'Vias Ffcc All', 'Cruce Ferroviario', 'Vias Ffcc', 'Paso Nivel', etc.) y en otras pocas existía la calle, pero o no se intersecan o no se encuentra el nombre de la calle en la bbdd de OSM (aunque sí podía visualizarla desde su página de internet).

Otra observación, es que hay algunos accidentes (sobre todo ocurridos en 2012-2013) que no suceden en Rosario, sino en alrededores como en Villa Gobernador Galvez. Algunas calles sí se encuentran en la bbdd de OSM, otras pocas no.

Solucionado en su mayoría el problema de los cambios de nombres, tuve que tratar los casos de las calles 'Avenida Battle y Ordoñez' y 'Previsión y Hogar' que se separaron mal debido a la 'y' en el medio y en algunos casos no se trataba de intersecciones sino de direcciones puntuales. Estos casos tendré q tratarlos a lo último con nominatim, aunque lo ideal sería volver a correr todo el código de las geocodificaciones (direcciones puntuales e intersecciones) para que quede más prolijo y entendible, teniendo en cuenta que existen estas dos calles con la 'y'.

Por último recién descubrí que pasó lo mismo con la calle 'godoy cruz' que se separó en 'godo' y 'cruz', así que también tendré que tratar estos casos antes de ejecutar el código. La idea es que en la columna 'calle_lista' queden únicamente dos valores por cada fila (uno para cada calle de la intersección) pero en algunos casos están dando 3, así que todavía los tengo que revisar.

Apenas tenga todo el código ordenado y lo corra para las 30 mil intersecciones, vuelvo a escribir por acá. No me falta mucho, la mayor demora fue en mirar una por una las calles equivocadas

abrilr1604 commented 2 months ago

@mstadeo buen día! el resultado del código de geocodificaciones ya está listo. De 30023 siniestros en intersecciones, 21610 se geocodificaron correctamente. De las que dieron error, que fueron 8413: 8237 fueron por puntos distantes, 4 dieron 'chequear', y 172 por alguna o ambas calles incorrectas.

De las que dieron error por puntos distantes, no comprendo bien por qué, ya que veo intersecciones que sí existen. Ejemplo: ['Avellaneda', 'Gaboto'], ['Avenida de la Costa Estanislao López', 'Nicasio Oroño'], ['Rondeau', 'Washington']. Supongo que será porque hay involucrada alguna avenida y el parámetro de tolerancia que definimos como 0.0001 no es suficiente ?

Luego, de aquellas que dieron error porque alguna o ambas calles se escribieron incorrectamente, son las que yo decidí dejar de lado porque no se pueden geocodificar. En la mayoría de los casos esto sucede porque osm (y creo que ninguna otra aplicación) no las reconoce, como es el caso de 'Limite De Propiedad', 'PASO A NIVEL', 'Club Nob', 'Vias Ffcc' y muchas más. Otras pocas (creo que encontré 2 o 3 casos así) sí se cortan, sí existen porque las puedo visualizar en la página de OSM, pero no se encuentran en la base de datos. Ej: [1373, Oriente]. En este caso, 'Pasaje 1373' no se encuentra en la bbdd (ni tampoco calle 1373), pero como se ve en la siguiente imagen que saqué de la página de OSM, el nombre es correcto y existe la intersección.

image

En el caso de chequear, estos son los siniestros involucrados: ['URIBURU', 'N...'], ['COLECTORA', 'LAMADRID'], ['Córdoba', 'COLECTORA'], ['Córdoba', 'COLECTORA']. En tres de los cuatro casos, está involucrada la colectora.

mstadeo commented 2 months ago

@abrilr1604 que bueno que corriste el código. Gracias por el comentario, te respondo por partes abajo

De las que dieron error por puntos distantes, no comprendo bien por qué, ya que veo intersecciones que sí existen. Ejemplo: ['Avellaneda', 'Gaboto'], ['Avenida de la Costa Estanislao López', 'Nicasio Oroño'], ['Rondeau', 'Washington']. Supongo que será porque hay involucrada alguna avenida y el parámetro de tolerancia que definimos como 0.0001 no es suficiente ?

Podrías filtrar ambas calles en la base de datos de OSM y plotearlas para ver si se intersectan? Puede ser que haya datos faltantes. Entonces, en la realidad si se intersectan pero en DB de OSM no.

Luego, de aquellas que dieron error porque alguna o ambas calles se escribieron incorrectamente, son las que yo decidí dejar de lado porque no se pueden geocodificar. En la mayoría de los casos esto sucede porque osm (y creo que ninguna otra aplicación) no las reconoce, como es el caso de 'Limite De Propiedad', 'PASO A NIVEL', 'Club Nob', 'Vias Ffcc' y muchas más. Otras pocas (creo que encontré 2 o 3 casos así) sí se cortan, sí existen porque las puedo visualizar en la página de OSM, pero no se encuentran en la base de datos. Ej: [1373, Oriente]. En este caso, 'Pasaje 1373' no se encuentra en la bbdd (ni tampoco calle 1373), pero como se ve en la siguiente imagen que saqué de la página de OSM, el nombre es correcto y existe la intersección.

En estos casos no vamos a poder hacer la geocodificación porque no existen en la bbdd.

En el caso de chequear, estos son los siniestros involucrados: ['URIBURU', 'N...'], ['COLECTORA', 'LAMADRID'], ['Córdoba', 'COLECTORA'], ['Córdoba', 'COLECTORA']. En tres de los cuatro casos, está involucrada la colectora.

Sabés por qué pasa eso?

abrilr1604 commented 2 months ago

@mstadeo hola sol! grafiqué las calles y ahí me di cuenta de que, por ejemplo, distingue las calles 'Rondeau' de 'Bulevar General José Rondeau', si bien estén ambos nombres de calles en la bbdd de OSM (también está 'General José Rondeau' y lo toma como otra calle cualquiera) y en la página de OSM sólo muestra el Bulevar aunque escribas rondeau o general josé rondeau.

Por ejemplo si ploteamos 'Rondeau' y 'Washington', se ve que 'Rondeau' no lo toma como la calle que todos conocemos. image

En cambio, si escribimos 'Bulevar General José Rondeau' sí, y se ve claramente la intersección. image

Lo mismo sucede con 'Nicasio Oroño' que debe ser escrito exactamente como 'Bulevar Nicasio Oroño', y 'Avellaneda' como 'Bulevar Nicolás Avellaneda'.

Tendré que cambiar el código y ejecutar de nuevo las filas que no se geocoficaron, porque supongo que la mayoría será casos de bulevares y de avenidas.

También veo qué paso con las colectoras y las calles córdoba y lamadrid que dieron 'chequear', supongo que será porque la colectora tiene otro nombre como juan pablo II o 25 de mayo.

Ahora me pongo con esto, y cuando tenga resultados vuelvo a escribir.

abrilr1604 commented 2 months ago

@mstadeo al final son pocos los casos para cambiar los nombres. En la mayoría de los casos, existe la intersección pero la tolerancia que definimos para las avenidas como 0.0001 es demasiado baja ya que la subí a 0.001 y la gran mayoría de las intersecciones me las geocodifica. Ejemplos: ['Avenida Travesía Albert Sabin', 'CASILDA'], ['Avenida de Circunvalación 25 de Mayo', 'Avenida Jorge Newbery'], ['Avenida Jorge Newbery', 'Bulevar Wilde']

El problema ahora es el siguiente: el mapa de OSM que estamos utilizando es el de Gran Rosario, no el de Rosario. Como se ve en la siguiente imagen, la forma es exactamente igual que el de los plots que adjunté en el comentario anterior. image Entonces, como había mencionado hace unas semanas, es capaz de geocodificar también accidentes ocurridos en otros pueblos, ya que en la bbdd de accidentes con la que estuve todo este tiempo trabajando (filtrado con desc_depto = 'Rosario') no sólo hay accidentes ocurridos en la ciudad de Rosario (sino también en Funes, Capital Bermúdez, Villa Gobernador Gálvez, Zavalla).

Por ejemplo, recién me encontré con este problema: un accidente ocurrido en 'Avenida Belgrano Y 25 de Mayo' (según escribieron). Como tal, la intersección en Rosario no existe. Sin embargo, sí en Zavalla y en Piñero. La base de datos de OSM tiene todos estos nombres para belgrano en Gran Rosario: ['General Manuel Belgrano', 'Belgrano', 'Avenida Manuel Belgrano', 'Belgrano Bis', 'Manuel Belgrano', 'Bulevar Manuel Belgrano'].

Dicho accidente, ocurrió en Piñero, según la columna 'desc_loc'. Esto me trae problemas (no en este caso en particular), ya que si bajo la tolerancia para los accidentes en avenidas ocurridos en Rosario, también me detecta otros accidentes en las mismas intersecciones pero en otros pueblos.

Entonces mi pregunta ahora es (la debería haber preguntado antes) ¿Los resultados finales, deben ser únicamente de Rosario o del Gran Rosario? Porque si es únicamente sobre Rosario, directamente filtro 'desc_loc' ignorando Piñero, Alvear, Zavalla, etc.

De lo contrario, tendría que nuevamente revisar los casos uno por uno o ir trabajando primero por Rosario y otros pueblos por separado.

msoltadeo commented 2 months ago

@abrilr1604 para obtener la red de calles de cada municipio vas a tener que cambiar esta parte de código:

#bajar los datos de la red de Rosario según OSM.
G1 = ox.graph_from_bbox(north, south, east, west, network_type = 'all_private', 
                       simplify = False, retain_all = True)
G1 = ox.project_graph(G1)
#pasar los datos de formato red a geodataframe
nodes, edges = ox.utils_graph.graph_to_gdfs(G1)

el north, south, east, west va a ser el bounding box de cada municipio

Otra forma en la que podes bajar la red de calles de cada municipio es con osmnx.graph_from_polygon, mirá la documentación

abrilr1604 commented 2 months ago

@msoltadeo hola sol, te escribo porque estoy trabada. Estuve intentando usar el método graph_from_polygon, pero me devuelve lo mismo que antes con graph_from_bbox. Entiendo que al trabajar con ese json que contiene información sobre los departamentos y al filtrar Rosario, nos devuelve una única fila, en donde la columna 'geometry' contiene un multipolígono.

Bueno, yo pensaba que a partir de ese multipolígono podía obtener los límites de cada municipio, pero me parece que no es así, porque solo contiene los limites o 'bounds' (como está definido en el código) con los que ya estábamos trabajando para el departamento de Rosario.

Entonces, no estoy segura si ese json contiene información detallada sobre cada municipio, sino que creo que sólo contiene información sobre los límites del departamento entero.

Tendría que buscar alguna otra solución, así que si me demoro va a ser por eso. Sino, lo más sencillo sería googlear las coordenadas de cada municipio y obtener cada red a mano

msoltadeo commented 2 months ago

@abrilr1604 descargá de este link el geojson Polígono Gobierno Local ahí vas a encontrar todos los municipios de argentina. Lo malo es que no están por departamento :woman_shrugging:. O te fijas si hay otro archivo que tenga los municipios por departamento así primero filtras Rosario y luego ya tenes lo dptos de Rosario. O haces una lista con los deptos de Rosario a mano y filtras.

abrilr1604 commented 2 months ago

@msoltadeo perfecto! funciona bien, excepto por 'Alberdi', que no figura como municipio/comuna (me fije en internet y tampoco encuentro nada con ese nombre en el depto Rosario). De Alberdi, tenemos 3 siniestros en la base de datos, y dos de ellos se geocodificaron.

Estoy terminando de armar el código para Rosario (es la parte más pesada, porque la mayoría de los siniestros ocurren allí). Cuando lo corra, vuelvo a escribir. Después, la idea es adaptar el código para el resto de las comunas/municipios, pero supongo que será más rápido debido a la menor cantidad de siniestros.

abrilr1604 commented 2 months ago

@msoltadeo hola! estuve trabajando toda la semana en las intersecciones de Rosario, estoy tardando porque son muchos los casos que tengo que revisar uno por uno.

Primero, acorde a lo último que habíamos hablado, agregué una pequeña parte en la función para geocodificar porque había muchos casos que no encontraba la intersección (aunque sí existe) por la tolerancia muy baja. Me fijé y una tolerancia de 0.001 es aprox 110 metros.

if isinstance(inter, MultiPoint):
            if inter.geoms[0].equals_exact(inter.geoms[1], tolerance = 0.0001):
                return inter.centroid, calles_osm
            elif inter.geoms[0].equals_exact(inter.geoms[1], tolerance = 0.001): #agregado
                return inter.centroid, calles_osm 
            else:
                return "chequear puntos distantes", 'error'

Al agregar esto, muchísimas intersecciones se pudieron geocodificar. Pero, además de las que eran imposibles y yo dejé a un lado (como vías férreas, club nob, s/n o nombres irreconocibles), también están las que dan 'LINESTRING Z EMPTY'. Esto significa que no encuentra la intersección, son aproximadamente 2850 siniestros, y las razones que fui encontrando son las siguientes:

Algunos casos ya los solucioné, por ahora me quedan 2006.

mstadeo commented 2 months ago

@abrilr1604 , cuantificar lo que no se geocodifico por año