En el artículo anterior obtuvimos todos los datos estructurados que el portal web nos podía ofrecer. No obstante, podemos ir mucho (mucho) más allá aprovechando el resto de la web como fuente de datos. Una de las características más importantes que podemos desear conocer es la posición espacial que ocupa el item. Conocer las coordenadas exactas (o aproximadas) de un dato nos abre la puerta a un océano de datos espaciales con los que cruzar nuestros datos.
En nuestro caso de estudio, los pisos no cuentan con dicha información de forma fácilmente extraíble, pero si conocemos el nombre de la calle o la zona. Esto nos permite aplicar una técnica conocida como geoencoding: traducir de nombres a lugares.
Presentando: Nomatim (by OSM)
Nomatim es un servicio abierto de geoencoding basado en Open Street Maps. El servicio ofrece búsquedas ilimitadas y gratuitas siempre que se respeten unos acuerdos de uso, que restringen, entre otras cosas, la frecuencia de las peticiones.
Configuración
Los pre-requisitos en este caso serán:
python==3.6requests==2.22.0
La librería requests, de hecho, no sería estrictamente necesaria, pero facilitará enormemente la realización de las peticiones al servidor.
Funcionamiento
La API de Nomatim
El servicio expone una API sencilla con la que podremos consultar por nombres de calles y plazas siguiendo un formato específico:
GET https://nominatim.openstreetmap.org/search?q=
${Nombre+De+La+Calle}
&polygon_geojson=1
&viewbox=${latlon_left}%${latlon_top}%2C${latlon_right}%2${latlon_bot}
&format=json
El resultado será un objeto JSON (ya que así ha sido solicitado en la API) con varios campos de interés como el display_name que permite supervisar que el resultado es el esperado, o los campos lat y lon que, como sus nombres indican, contienen las coordenadas del centroide de la geometría. Esto puede suponer un problema en el caso de calles largas, pero es la mejor aproximación que se puede realizar sin tener los números de los portales.
El código python para llamar a la API para nuestro caso de estudio:
url = 'https://nominatim.openstreetmap.org/search?q=' + \
query.strip().replace(' ', '+')+'+Zaragoza+Aragon+Spain' + \
'&polygon_geojson=1' + \
'&viewbox=-1.30875%2C41.77592%2C-0.43327%2C41.51937' + \
'&format=json'
headers = {
'User-Agent': 'Python Script for Economics Study',
}
results = requests.get(url, headers=headers).json()
Algoritmo
El algoritmo a ejecutar no entraña misterio alguno:
- Cargar el fichero CSV con todos los items
- Iterar, para cada item 1.
- Extraer su dirección
- “Limpiar” dicha dirección
- Realizar una búsqueda en nominatim
- Comprobar si, entre la lista de resultados, hay un resultado válido (dentro de un área razonable, por ejemplo)
- Si lo hay, continuar con el siguiente item
- Si no la hay, probar con otra “limpieza” y volver al principio de este bucle
- Extraer su dirección
Limpieza de datos
El filtrado de nombres es uno de los pasos más artesanales y más guiados por la prueba-error de todo el proceso. Se han detectado que los términos que más empeoran la búsqueda son ‘calle’, ‘plaza’, ‘avenida’ y ‘paseo’. La estrategia es detectar estos términos y eliminarlos del nombre.
También se han observado problemas con el artículo ‘de’, por lo que otro de los “niveles de limpieza” implica eliminar dichos artículos.
El código de limpieza de nombres se puede condensar en las siguientes dos funciones.
def rip(query):
for pattern in ['calle', 'plaza', 'avenida', 'paseo']:
if pattern in query:
return _rip_pattern(pattern, query)
return query
def _rip_pattern(pattern, query):
if pattern+' de' not in query:
return query.replace(pattern, pattern+' de')
if pattern+' de' in query:
return query.replace(pattern+' de', '')
return query
Al final del proceso, se han logrado ubicar cerca del 80% de los pisos de nuestro caso práctico combinando las distintas técnicas de “limpieza”.