Automatizar la geolocalización de direcciones físicas

Una de las muchísimas tareas que realizamos a diario en Servicios Reunidos ha sido la de geolocalizar miles de direcciones físicas para poder dibujarlas en un mapa, una tarea simple que se torna compleja por el hecho de la cantidad de información a tratar, cualquiera puede geolocalizar una, dos, diez, quizá cien direcciones físicas en cierto tiempo pero cuando hablamos de cantidades más grandes se vuelve una tarea inmanejable que puede requerir de mucho tiempo y esfuerzo.

Para resolver esa tarea creamos un programita utilizando Kotlin al que le pasamos un fichero Excel con las direcciones a geolocalizar y tras llamar a Nominatim, la API de geolocalización de OpenStreetMap, recoge y escribe los datos de latitud y longitud para cada una de las direcciones directamente en el propio fichero Excel. Se ha optado por utilizar un fichero Excel pero podría adaptarse fácilmente para otro tipo de formatos como puede ser CSV, JSON, XML, etc.

Para la creación y modificación del fichero podemos hacer uso de la suite open source LibreOffice, el formato del mismo es muy simple, en mi repositorio dónde he publicado el código hay también un fichero de ejemplo. No hablaremos del código en su conjunto, veremos algunas partes pero nos centraremos en su funcionamiento, ejecución y uso, si te interesa el código puedes verlo en mi repositorio.

La API Nominatim de OpenStreetMap tiene una serie de requisitos que se han de cumplir para que el servicio funcione correctamente y es que al tratarse de un proyecto open source no tienen la capacidad de cómputo que pueden tener otras grandes empresas por lo que hay que tener cuidado cómo y con qué frecuencia se realizan las solicitudes entre otras cosas, este programa realiza una única solicitud por segundo.

El fichero Excel con las direcciones distribuye la información de la siguiente manera:

  • Columna 1: Dirección física (calle, plaza, etc)
  • Columna 2: Ciudad
  • Columna 3: Provincia/Condado
  • Columna 4: Código postal
  • Columna 5: País

Así es como se han definido las columnas, la clase Column del modelo es el que las refleja y se utiliza para acceder a ellas, por lo que puede ser fácilmente modificado o readaptado.

package model
 
class Column {
    companion object {
        @JvmField
        val ADDRESS = 0
        @JvmField
        val CITY = 1
        @JvmField
        val COUNTY = 2
        @JvmField
        val PC = 3
        @JvmField
        val COUNTRY = 4
    }
}

El modelo contiene otras dos clases, Place creada para instanciar el lugar (con la dirección, ciudad, centro y demás) y Location que contiene las posiciones de latitud y longitud y está referenciada a su vez en la clase del lugar.

La aplicación se encarga de abrir el fichero Excel que le hemos pasado y línea por línea leer los datos. A su vez llama a la API utilizando unas clases definidas dentro de un paquete helper:

  • Excel tiene los métodos necesarios para manejar el propio fichero.
  • Parser se encarga de leer y escribir los datos.
  • Data realiza las llamadas a Nominatim.

La clase principal Main es la encargada de orquestar todo.

import helper.Parser
import java.io.File
import java.io.FileNotFoundException
 
class Main {
    companion object {
        private const val DELAY_MILLIS: Long = 1000
 
        @JvmStatic
        fun main(args: Array) {
            if (args.isNotEmpty()) {
                val srcExcelFilePath: String = args[0]
                var delay = DELAY_MILLIS
                if (args.size > 1) {
                    delay = args[1].toLong()
                }
                try {
                    val srcExcelFile = File(srcExcelFilePath)
                    Parser.Excel.parse(srcExcelFile, delay = delay)
                } catch (ex: FileNotFoundException) {
                    printFileNotFound(srcExcelFilePath)
                }
            } else {
                printUsage()
            }
        }
 
        private fun printUsage() {
            println("addressGeolocate EXCEL_FILE_PATH [DELAY_IN_MILLIS = 500]\n" +
                    "\t     EXCEL_FILE_PATH: Path to the Parser file with the addresses\n" +
                    "\t     DELAY_IN_MILLIS: Elapsed time to wait between API calls\n")
        }
 
        private fun printFileNotFound(path: String) {
            println("File cannot be found in '$path'")
        }
    }
}

La gestión de dependencias y construcción del proyecto se realiza con Gradle. Su uso es muy sencillo.

Lo primero que haríamos sería clonar el proyecto del repositorio.

git clone https://github.com/amendezcabrera/address_geolocation.git

Descargará el código fuente a una carpeta, tras acceder a ella procedemos a construir el proyecto (necesitamos el Kit de desarrollo de Java).

./gradlew build

Añadirá las dependencias necesarias a nuestro proyecto y generará los binarios para poder ejecutar la aplicación. Éstos se generan en la carpeta build -> libs con el nombre de AddressGeolocation.jar por lo que para utilizarla accedemos al directorio y ejecutamos el jar con Java.

java -jar AddressGeolocation.jar

Al no pasarle ningún parámetro nos muestra la información de uso.

Nos dice que debemos pasarle un parámetro obligatorio que es la ruta al fichero Excel con las direcciones a geolocalizar. Copiaremos el fichero de ejemplo que se encuentra en la carpeta resources del proyecto al escritorio y lo volvemos a ejecutar pasándole esa ruta.

java -jar AddressGeolocation.jar “/home/usuario/Desktop/Addresses.xlsx”

El comando anterior es el correspondiente a un entorno Linux pero funciona también en Windows y en Mac, basta con enviarle la ruta absoluta correspondiente a nuestro entorno. Como podemos observar le podemos enviar también un segundo parámetro opcional que indicaría el tiempo de espera entre cada solicitud en milisegundos, por defecto tiene una espera de 1000 milisegundos (1 segundo).

Nos mostrará los centros de los que se ha obtenido la geolocalización y un mensaje de error en caso de que alguno no pueda encontrarse, a su vez escribirá en el fichero Excel los datos de latitud y longitud por cada una de las filas (direcciones) del fichero.

No necesitamos tener Excel ni LibreOffice instalado más que para abrir y modificar el fichero. En el fichero de ejemplo sólo se incluyen 2 direcciones que pueden ser geolocalizadas a mano muy fácilmente pero este mismo método puede emplearse para geolocalizar cientos o miles de direcciones de forma automatizada, el único requisito es cumplir con las normas establecidas por la API Nominatim.

Cualquier duda o sugerencia no dudes en dejarla en los comentarios. También son bienvenidas nuevas funcionalidades para el proyecto de forma que todos podamos beneficiarnos de ello, no dudes en enviar pull request.

Relacionado:

About the Author:

Desarrollador de software de profesión, apasionado de la informática y todo lo relacionado con la tecnología

Leave A Comment