Geocodificación ¿Cómo vincular 250 mil direcciones a coordenadas en 10 minutos?



Hola Habr!

En este artículo, me gustaría compartir mi experiencia en la resolución de un pequeño problema con una gran cantidad de direcciones. Si alguna vez trabajó con la API de geocodificación o utilizó herramientas en línea, creo que comparte mi dolor de esperar el resultado durante varias horas, o incluso más.

No se trata de algoritmos de optimización complejos, sino de usar un servicio de geocodificación de paquetes que toma una lista de direcciones como entrada y devuelve un archivo con los resultados. Esto puede reducir el tiempo de procesamiento de unas pocas horas a minutos.

Lo primero es lo primero:


Antecedentes


Llegó la tarea: "Vincularse a las coordenadas de 24 mil direcciones". Solo se produjeron dos soluciones al problema:

  1. Aplicación web para geocodificación, que se utilizó en la universidad;
  2. Escriba un script basado en la API REST del geocodificador.

En el primer caso, resultó que la aplicación web se bloquea después de procesar miles de direcciones. Distribuir un conjunto de datos entre colegas es una idea que fue abandonada de inmediato.

Por lo tanto, debe usar la API REST del geocodificador para escribir su propio script, con los resultados guardados (esta no es una forma completamente legal y necesita leer los términos de uso del servicio). Surge un nuevo problema: una cosa es cuando usamos la búsqueda de direcciones en la aplicación e inmediatamente obtenemos el resultado, pero cuando la tarea es procesar más de diez mil direcciones con preservación, el trabajo del script se retrasa considerablemente. Puede esperar una o dos horas, pero un millón de direcciones tendrán que geocodificar "tiempo inmenso", por lo que debe buscar otra solución y ¡es!

Los grandes proveedores de servicios de geolocalización, además del servicio de geocodificación habitual, ofrecen un geocodificador de paquetes (Batch Geocoder), solo para procesar una gran cantidad de direcciones en una solicitud.

Geocodificación por lotes


El nombre del servicio habla por sí mismo: tenemos un paquete (por ejemplo, un archivo csv con una lista de direcciones en forma de tabla), que cargamos en el servidor, y hace todo el trabajo por nosotros.

El proceso se ve así:

  1. Preparar el conjunto de datos para que el servicio lo acepte sin errores;
  2. Establecer los parámetros del resultado del trabajo (selección de columnas, separador ...);
  3. Sube un archivo a la nube;
  4. Esperando a que se complete el procesamiento;
  5. Descargue el archivo terminado.

Gracias a la potencia de la computación en la nube, lo que se hace con un script autoescrito en 1 hora se completa en 1 minuto.

El siguiente paso es elegir la empresa con los términos de uso más leales del geocodificador de paquetes. En primer lugar, no todos tienen dicho servicio, otros le permiten probar el servicio con una limitación seria. Además, si tiene volúmenes muy grandes, debe prestar atención al costo de las transacciones adicionales, en caso de que se exceda el límite del paquete gratuito.

Elegir un proveedor de servicios de geocodificación por lotes


En el mercado global de servicios de geolocalización, las posiciones de liderazgo están ocupadas por:

  • mapas de Google
  • AQUÍ Tecnologías;
  • MapBox
  • TomTom
  • ESRI

Por supuesto, no debe olvidarse de Yandex Technologies, que tiene una posición bastante fuerte en Rusia.

Tomé los siguientes parámetros como base para elegir un proveedor:

  • El número de solicitudes al servicio de geocodificación por mes es gratuito;
  • Límite en la cantidad de transacciones por día;
  • Disponibilidad de servicio de geocodificación por lotes;
  • Posibilidad de utilizar un geocodificador de paquetes en un plan gratuito.

Cada empresa tiene su propio modelo de monetización. Dependiendo del proyecto, uno u otro modelo puede jugar en las manos o viceversa, puede ser una limitación significativa.

Mapas de Google


Para comenzar con los servicios geográficos de Google, lo primero que debe hacer es agregar información de la tarjeta de crédito a su cuenta. Un límite mensual de 200 dólares virtuales, luego viene el pago de transacciones adicionales de la tarjeta vinculada. Dentro de este límite, puede usar varios servicios, pero cada transacción se considera de manera diferente. Por ejemplo, mil solicitudes de geocodificación costarán $ 5, pero el servicio de construcción de rutas es el doble de caro. Se pueden encontrar más detalles en el sitio, solo estamos interesados ​​en el servicio de geocodificación.

Si $ 200 por mes, entonces es fácil calcular el número gratuito de transacciones: 40,000 (servicio de geocodificación). No hay un geocodificador de paquetes entre los servicios. Esto significa que tiene que escribir su propio script y el resultado será aproximadamente 1 dirección por segundo, que es de seis horas para 24 mil direcciones. Para acelerar el proceso, puede intentar ejecutar el script en la plataforma API de Google Cloud, pero decidí buscar soluciones alternativas. No hay restricciones en el número de transacciones por día, por lo que se pueden gastar los cuarenta mil a la vez.

Tecnologías AQUÍ


En el pasado, Nokia Maps, y en el pasado aún más profundo, Navteq, proporciona 250,000 transacciones cada mes de forma gratuita. De manera similar a Google Maps, este número se aplica a todos los servicios y cada uno se considera de manera diferente. Cuando utilice el paquete gratuito, no necesita adjuntar una tarjeta bancaria. Si excede el límite, entonces por cada mil transacciones adicionales debe pagar $ 1.

Es importante tener un geocodificador de paquetes como un servicio separado, que está incluido en el plan gratuito. Las transacciones en él se tienen en cuenta de acuerdo con el mismo modelo que en el habitual, es decir, cada dirección en el archivo, el geocodificador de paquetes percibirá en una transacción.
Por el título del artículo, está claro que utilicé el geocodificador por lotes HERE, ya que puede gastar todas las transacciones en el geocodificador y realizar 250,000 operaciones de geocodificación por mes. Pero esta no es la única opción, así que miramos lo que tienen otras compañías.

Mapbox


Cuando se utiliza el geocodificador MapBox, hay disponibles 100 mil transacciones por mes. La compañía se adhiere al mismo modelo de monetización con el pago de transacciones adicionales. Solo hay una opción interesante para los "mayoristas": cuantas más transacciones tengas, menos costarán (por supuesto, hay un límite de reducción de precios). Por ejemplo, de 100 mil a 500 mil, mil solicitudes adicionales costarán $ 0.75, de 500 mil a 1 millón - $ 0.60, etc., para más detalles, visite el sitio web. Desafortunadamente, el geocodificador por lotes solo está disponible en una cuenta paga.

Tomtom


La plataforma permite realizar 2500 transacciones por día, aproximadamente 75,000 por mes. Durante las pruebas y el desarrollo, el límite diario no parece muy atractivo en comparación con la competencia, pero el pago por transacciones adicionales es el más flexible. Hay 8 opciones de pago para mil solicitudes adicionales y el precio se reduce de $ 0.5 a $ 0.42.

Entre los servicios hay un geocodificador por lotes con la capacidad de procesar hasta 10 mil direcciones por solicitud (sin embargo, se debe tener en cuenta el límite diario).

Yandex Technologies


Un modelo con un límite diario de transacciones para Yandex, pero más fiel a 25 mil solicitudes. Si multiplica este número por la cantidad de días en un mes, obtendrá una cifra impresionante de 750 mil. El sitio presenta precios para mil transacciones adicionales en rublos que van desde 120 rublos. hasta 11 rublos

El geocodificador de paquetes como servicio no se presenta, por lo que no se logrará algún tipo de optimización.

ESRI


Un plan gratuito muy tentador con 1 millón de transacciones por mes. La compañía también cobra 50 créditos a cada cuenta (equivalente aproximado de $ 5). Vale la pena señalar que este es el plan más leal para el uso de servicios de geolocalización. También hay un servicio de geocodificación por lotes, pero puede usarlo solo si tiene una cuenta corporativa en la plataforma ArcGIS Online.

¿Qué elegir al final?


La forma más fácil es hacer una elección compilando una tabla pequeña:



Como resultado, mi elección recayó AQUÍ, ya que es la mejor opción para resolver mi problema. Por supuesto, lo hice lejos de completar el análisis, idealmente necesita ejecutar su conjunto de datos a través de todos los geocodificadores para evaluar la calidad. Además, si tiene varios millones de direcciones, debe pensar en un paquete pago y luego debe tener en cuenta el costo de agregar. actas.

El propósito del artículo no es comparar empresas, sino resolver el problema de optimizar la geocodificación de un gran volumen de direcciones. Solo mostré mis pensamientos al elegir un proveedor de servicios.

Guía de servicio de Python


Primero debe crear una cuenta en el portal para desarrolladores y generar una CLAVE API REST en la sección del proyecto.

Ahora puedes trabajar con la plataforma. Describiré solo una parte de la funcionalidad que tiene el geocodificador de paquetes HERE: carga de datos, verificación de estado, almacenamiento de resultados.

Entonces, comencemos importando las bibliotecas necesarias:

import requests
import json
import time
import zipfile
import io
from bs4 import BeautifulSoup

Además, si no se han producido errores, cree una clase:

class Batch:

    SERVICE_URL = "https://batch.geocoder.ls.hereapi.com/6.2/jobs"
    jobId = None

    def __init__(self, apikey="your_api_key"):
        self.apikey = apikey

Es decir, durante la inicialización, la clase debe pasar su propia clave para la API REST.
La variable SERVICE_URL es la URL base para trabajar con el servicio de geocodificación por lotes.
Y en jobId se almacenará el identificador del trabajo actual del geocodificador.

Un punto importante es la estructura de datos correcta a pedido. El archivo debe contener dos columnas obligatorias: recId y searchText. De lo contrario, el servicio devolverá una respuesta con información sobre el error de descarga.

Aquí hay un conjunto de datos de ejemplo:

   recId; searchText
   1; -, . , 6
   2; ,  1,  -., 72
   3; 425 W Randolph St Chicago IL 60606
   4; , DJ106 20-30, Sibiu 557260
   5; 200 S Mathilda Ave Sunnyvale CA 94086
  

Función para cargar un archivo a la nube:

def start(self, filename, indelim=";", outdelim=";"):
        
        file = open(filename, 'rb')

        params = {
            "action": "run",
            "apiKey": self.apikey,
            "politicalview":"RUS",
            "gen": 9,
            "maxresults": "1",
            "header": "true",
            "indelim": indelim,
            "outdelim": outdelim,
            "outcols": "displayLatitude,displayLongitude,locationLabel,houseNumber,street,district,city,postalCode,county,state,country",
            "outputcombined": "true",
        }

        response = requests.post(self.SERVICE_URL, params=params, data=file)
        self.__stats (response)
        file.close()


Todo es bastante simple, abra un archivo con una lista de direcciones para leer, forme un diccionario de parámetros de solicitud GET. Vale la pena explicar algunos parámetros:

  • “Acción”: “ejecutar”: inicio del procesamiento de la dirección;
  • “politicalView”: “RUS” – . ( );
  • “gen”: 9 – ( );
  • “maxresults”: 1 – ;
  • “header”: true – ;
  • “indelim”: “;” – , ;
  • “outdelim”: “;” – ;
  • “outcols”: “” – , ;
  • “outcombined”: true – .

A continuación, simplemente envíe una solicitud utilizando la biblioteca de solicitudes y muestre estadísticas. Por supuesto, debe cerrar el archivo al final de la función. La función __stats analiza la respuesta del servidor, que contiene el ID del trabajo en ejecución, y también muestra información general sobre la operación.

El siguiente paso es verificar el estado del trabajo. La solicitud se forma de manera similar, solo es necesario transferir la identificación de la operación. El parámetro de acción debe contener el valor "estado". La función __stats muestra estadísticas completas en la consola para estimar el tiempo de apagado del geocodificador.

    def status (self, jobId = None):

        if jobId is not None:
            self.jobId = jobId
        
        statusUrl = self.SERVICE_URL + "/" + self.jobId
        
        params = {
            "action": "status",
            "apiKey": self.apikey,
        }
        
        response = requests.get(statusUrl, params=params)
        self.__stats (response)

Una de las funciones más importantes es guardar el resultado. Por conveniencia, es mejor descomprimir inmediatamente el archivo que viene del servidor. La solicitud para guardar el archivo es idéntica a verificar el estado, solo agregue / resultado al final.

    def result (self, jobId = None):

        if jobId is not None:
            self.jobId = jobId
        
        print("Requesting result data ...")
        
        resultUrl = self.SERVICE_URL + "/" + self.jobId + "/result"
        
        params = {
            "apiKey": self.apikey
        }
        
        response = requests.get(resultUrl, params=params, stream=True)
        
        if (response.ok):    
            zipResult = zipfile.ZipFile(io.BytesIO(response.content))
            zipResult.extractall()
            print("File saved successfully")
        
        else:
            print("Error")
            print(response.text)

La función final para analizar la respuesta del servicio. También su tarea es guardar el identificador de la tarea de geocodificación actual:

    def __stats (self, response):
        if (response.ok):
            parsedXMLResponse = BeautifulSoup(response.text, "lxml")

            self.jobId = parsedXMLResponse.find('requestid').get_text()
            
            for stat in parsedXMLResponse.find('response').findChildren():
                if(len(stat.findChildren()) == 0):
                    print("{name}: {data}".format(name=stat.name, data=stat.get_text()))

        else:
            print(response.text)

Para probarlo, simplemente ejecute el intérprete de Python en la carpeta del script. La clase Batch está en el archivo geocoder.py :

>>> from geocoder import Batch
>>> service = Batch(apikey="   REST API")
>>> service.start("big_data_addresses.csv", indelim=";", outdelim=";")

requestid: "  Id "
status: accepted
totalcount: 0
validcount: 0
invalidcount: 0
processedcount: 0
pendingcount: 0
successcount: 0
errorcount: 0


Gran trabajo comenzó. Verifica el estado:

>>> service.status()

requestid: "  Id "
status: completed
jobstarted: 2020-04-27T10:09:58.000Z
jobfinished: 2020-04-27T10:17:18.000Z
totalcount: 249999
validcount: 249999
invalidcount: 0
processedcount: 249999
pendingcount: 0
successcount: 249978
errorcount: 21

Vemos que el procesamiento del conjunto de datos está completo. En solo siete minutos, fue posible codificar las 250 mil direcciones (excluyendo errores - cuenta de errores). Queda por guardar los resultados:

>>> service.result()
Requesting result data ...
File saved successfully

Descripción completa de la clase de lote


Creo que no está de más agregar el script por completo:

import requests
import json
import time
import zipfile
import io
from bs4 import BeautifulSoup

class Batch:

    SERVICE_URL = "https://batch.geocoder.ls.hereapi.com/6.2/jobs"
    jobId = None

    def __init__(self, apikey="   REST API "):
        self.apikey = apikey
        
            
    def start(self, filename, indelim=";", outdelim=";"):
        
        file = open(filename, 'rb')

        params = {
            "action": "run",
            "apiKey": self.apikey,
            "politicalview":"RUS",
            "gen": 9,
            "maxresults": "1",
            "header": "true",
            "indelim": indelim,
            "outdelim": outdelim,
            "outcols": "displayLatitude,displayLongitude,locationLabel,houseNumber,street,district,city,postalCode,county,state,country",
            "outputcombined": "true",
        }

        response = requests.post(self.SERVICE_URL, params=params, data=file)
        self.__stats (response)
        file.close()
    

    def status (self, jobId = None):

        if jobId is not None:
            self.jobId = jobId
        
        statusUrl = self.SERVICE_URL + "/" + self.jobId
        
        params = {
            "action": "status",
            "apiKey": self.apikey,
        }
        
        response = requests.get(statusUrl, params=params)
        self.__stats (response)
        

    def result (self, jobId = None):

        if jobId is not None:
            self.jobId = jobId
        
        print("Requesting result data ...")
        
        resultUrl = self.SERVICE_URL + "/" + self.jobId + "/result"
        
        params = {
            "apiKey": self.apikey
        }
        
        response = requests.get(resultUrl, params=params, stream=True)
        
        if (response.ok):    
            zipResult = zipfile.ZipFile(io.BytesIO(response.content))
            zipResult.extractall()
            print("File saved successfully")
        
        else:
            print("Error")
            print(response.text)
    

    
    def __stats (self, response):
        if (response.ok):
            parsedXMLResponse = BeautifulSoup(response.text, "lxml")

            self.jobId = parsedXMLResponse.find('requestid').get_text()
            
            for stat in parsedXMLResponse.find('response').findChildren():
                if(len(stat.findChildren()) == 0):
                    print("{name}: {data}".format(name=stat.name, data=stat.get_text()))

        else:
            print(response.text)

Análisis de resultados


Como resultado, pasé de aplicaciones en línea que trabajaban lentamente a servicios de geocodificación por lotes. La elección de un proveedor de servicios geográficos depende completamente de las tareas que enfrenta. Recibo regularmente solicitudes para procesar una gran cantidad de direcciones y el enfoque descrito en el artículo ayudó a reducir significativamente el tiempo.

¡Espero que este artículo sea útil y, por supuesto, estoy abierto a comentarios y adiciones!

All Articles