Geocodificação Como vincular 250 mil endereços a coordenadas em 10 minutos?



Olá Habr!

Neste artigo, gostaria de compartilhar minha experiência na solução de um pequeno problema com um grande número de endereços. Se você já trabalhou com a API de geocodificação ou usou ferramentas on-line, acho que compartilha da minha dor de esperar o resultado por várias horas ou mais.

Não se trata de algoritmos de otimização complexos, mas de usar um serviço de geocodificação de pacotes que recebe uma lista de endereços como entrada e retorna um arquivo com os resultados. Isso pode reduzir o tempo de processamento de algumas horas para minutos.

Primeiras coisas primeiro:


fundo


A tarefa chegou - "Ligue-se às coordenadas de 24 mil endereços". Apenas duas soluções para o problema ocorreram:

  1. Aplicativo da Web para geocodificação, usado na universidade;
  2. Escreva um script com base na API REST do geocoder.

No primeiro caso, descobriu-se que o aplicativo da web falha após o processamento de milhares de endereços. Distribuir um conjunto de dados entre colegas é uma ideia que foi imediatamente abandonada.

Portanto, você precisa usar a API REST do geocoder para escrever seu próprio script, com os resultados salvos (essa não é uma maneira completamente legal e você precisa ler os termos de uso do serviço). Um novo problema surge - uma coisa é quando usamos a pesquisa de endereços no aplicativo e obtemos o resultado imediatamente, mas quando a tarefa é processar mais de dez mil endereços com preservação, o trabalho do script é bastante atrasado. Você pode esperar uma ou duas horas, mas um milhão de endereços precisará geocodificar “imenso tempo”, portanto, você precisa procurar outra solução e ela é!

Os grandes provedores de serviços de geolocalização, além do serviço de geocodificação usual, oferecem um geocoder de pacotes (Batch Geocoder), apenas para processar um grande número de endereços em uma solicitação.

Geocodificação em lote


O nome do serviço fala por si - temos um pacote (por exemplo, um arquivo csv com uma lista de endereços na forma de uma tabela), que carregamos no servidor e faz todo o trabalho para nós.

O processo é assim:

  1. Preparando o conjunto de dados para que o serviço o aceite sem erros;
  2. Definir os parâmetros do resultado do trabalho (seleção de colunas, separador ...);
  3. Carregar um arquivo para a nuvem;
  4. Aguardando a conclusão do processamento;
  5. Faça o download do arquivo finalizado.

Graças ao poder da computação em nuvem, o que é feito com um script auto-escrito em 1 hora é concluído em 1 minuto.

O próximo passo é escolher a empresa com os termos de uso mais fiéis do geocoder de pacotes. Em primeiro lugar, nem todo mundo tem esse serviço, outros permitem que você teste o serviço com uma limitação séria. Além disso, se você tiver volumes muito grandes, precisará prestar atenção ao custo de transações adicionais, caso o limite do pacote gratuito seja excedido.

Escolhendo um provedor de serviços de geocodificação em lote


No mercado global de serviços de geolocalização, as posições de liderança são ocupadas por:

  • Google Maps
  • HERE Technologies;
  • MapBox
  • TomTom
  • ESRI.

Obviamente, você não deve esquecer a Yandex Technologies, que tem uma posição bastante forte na Rússia.

Tomei os seguintes parâmetros como base para escolher um provedor:

  • O número de solicitações para o serviço de geocodificação por mês é gratuito;
  • Limite no número de transações por dia;
  • Disponibilidade do serviço de geocodificação em lote;
  • Capacidade de usar um geocoder de pacote em um plano gratuito.

Cada empresa possui seu próprio modelo de monetização. Dependendo do projeto, um ou outro modelo pode jogar nas mãos ou vice-versa, sendo uma limitação significativa.

Google maps


Para começar com os serviços geográficos do Google, a primeira coisa que você precisa fazer é adicionar informações de cartão de crédito à sua conta. Um limite mensal de 200 dólares virtuais, então vem o pagamento de transações adicionais do cartão vinculado. Dentro desse limite, você pode usar vários serviços, mas cada transação é considerada diferente. Por exemplo, mil solicitações de geocodificação custarão US $ 5, mas o serviço de criação de rotas é duas vezes mais caro. Mais detalhes podem ser encontrados no site, estamos interessados ​​apenas no serviço de geocodificação.

Se US $ 200 por mês, é fácil calcular o número gratuito de transações - 40.000 (serviço de geocodificação). Não há geocodificador de pacotes entre os serviços. Isso significa que você deve escrever seu próprio script e o resultado será de aproximadamente 1 endereço por segundo, ou seja, seis horas para 24 mil endereços. Para acelerar o processo, você pode tentar executar o script na plataforma de APIs do Google Cloud, mas decidi procurar soluções alternativas. Não há restrições quanto ao número de transações por dia; portanto, todos os quarenta mil podem ser gastos ao mesmo tempo.

HERE Technologies


No passado, o Nokia Maps e, no passado ainda mais profundo, a Navteq, forneciam 250.000 transações por mês gratuitamente. Da mesma forma que no Google Maps, esse número se aplica a todos os serviços e cada um é considerado de maneira diferente. Ao usar o pacote gratuito, você não precisa anexar um cartão bancário. Se você exceder o limite, para cada mil transações adicionais você precisará pagar US $ 1.

É importante ter um geocoder de pacote como um serviço separado, incluído no plano gratuito. As transações nele são levadas em consideração de acordo com o mesmo modelo que o habitual, ou seja, em cada endereço do arquivo, o geocoder de pacotes perceberá em uma transação.
Pelo título do artigo, fica claro que eu usei o geocoder em lote HERE, pois você pode gastar todas as transações no geocoder e executar 250.000 operações de geocodificação por mês. Mas essa não é a única opção, então analisamos o que as outras empresas têm.

Mapbox


Ao usar o geocodificador MapBox, 100 mil transações por mês estão disponíveis. A empresa segue o mesmo modelo de monetização com pagamento por transações adicionais. Só existe uma opção interessante para os "atacadistas" - quanto mais transações você tiver, menos elas custam (é claro que há um limite de redução de preço). Por exemplo, de 100 mil a 500 mil, mil solicitações adicionais custarão US $ 0,75, de 500 mil a 1 milhão - US $ 0,60 etc. para mais detalhes, consulte o site. Infelizmente, o geocoder de lote está disponível apenas em uma conta paga.

Tomtom


A plataforma permite realizar 2500 transações por dia, aproximadamente 75.000 por mês. Durante os testes e o desenvolvimento, o limite diário não parece muito atraente em comparação aos concorrentes, mas o pagamento por transações adicionais é o mais flexível. Existem 8 opções de pagamento para mil solicitações extras e o preço é reduzido de US $ 0,5 para US $ 0,42.

Entre os serviços, existe um geocoder em lote com capacidade para processar até 10 mil endereços por solicitação (no entanto, o limite diário deve ser levado em consideração).

Yandex Technologies


Um modelo com um limite diário de transações para o Yandex, mas com mais de 25 mil pedidos leais. Se você multiplicar esse número pelo número de dias em um mês, obtém um número impressionante de 750 mil. O site apresenta preços para mais mil transações em rublos, variando de 120 rublos. até 11 rublos

O geocoder de pacote como um serviço não é apresentado, portanto, para obter algum tipo de otimização falhará.

ESRI


Um plano gratuito muito tentador, com 1 milhão de transações por mês. A empresa também cobra 50 créditos em cada conta (equivalente aproximado de US $ 5). Vale ressaltar que este é o plano mais fiel para o uso dos serviços de geolocalização. Também existe um serviço de geocodificação em lote, mas você pode usá-lo apenas se tiver uma conta corporativa na plataforma ArcGIS Online.

O que escolher no final?


A maneira mais fácil é fazer uma escolha, compilando uma pequena tabela:



Como resultado, minha escolha caiu AQUI, pois é a melhor opção para resolver meu problema. Obviamente, eu fiz muito longe da análise completa; idealmente, você precisa executar seu conjunto de dados por todos os geocodificadores para avaliar a qualidade. Além disso, se você tiver vários milhões de endereços, pense em um pacote pago e precisará levar em consideração o custo da adição. transações.

O objetivo do artigo não é comparar empresas, mas resolver o problema de otimizar a geocodificação de um grande volume de endereços. Eu apenas mostrei meus pensamentos ao escolher um provedor de serviços.

Guia de Serviço Python


Primeiro, você precisa criar uma conta no portal para desenvolvedores e gerar uma REST API KEY na seção do projeto.

Agora você pode trabalhar com a plataforma. Descreverei apenas parte da funcionalidade do geocoder do pacote HERE: carregamento de dados, verificação de status, salvamento de resultados.

Então, vamos começar importando as bibliotecas necessárias:

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

Além disso, se nenhum erro ocorreu, crie uma classe:

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

Ou seja, durante a inicialização, a classe deve passar sua própria chave para a API REST.
A variável SERVICE_URL é a URL base para trabalhar com o serviço de geocodificação em lote.
E em jobId, o identificador do trabalho atual do geocoder será armazenado.

Um ponto importante é a estrutura de dados correta, mediante solicitação. O arquivo deve conter duas colunas obrigatórias: recId e searchText. Caso contrário, o serviço retornará uma resposta com informações sobre o erro de download.

Aqui está um exemplo de conjunto de dados:

   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
  

Função para carregar um arquivo na nuvem:

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()


Tudo é bem simples, abra um arquivo com uma lista de endereços para leitura, forme um dicionário de parâmetros de solicitação GET. Vale a pena explicar alguns parâmetros:

  • "Ação": "executar" - início do processamento de endereços;
  • “politicalView”: “RUS” – . ( );
  • “gen”: 9 – ( );
  • “maxresults”: 1 – ;
  • “header”: true – ;
  • “indelim”: “;” – , ;
  • “outdelim”: “;” – ;
  • “outcols”: “” – , ;
  • “outcombined”: true – .

Em seguida, basta enviar uma solicitação usando a biblioteca de solicitações e exibir estatísticas. Obviamente, você precisa fechar o arquivo no final da função. A função __stats analisa a resposta do servidor, que contém o ID do trabalho em execução, e também exibe informações gerais sobre a operação.

O próximo passo é verificar o status do trabalho. A solicitação é formada de maneira semelhante, apenas é necessário transferir o ID da operação. O parâmetro de ação deve conter o valor "status". A função __stats exibe estatísticas completas no console para estimar o tempo de desligamento do geocoder.

    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)

Uma das funções mais importantes é salvar o resultado. Por conveniência, é melhor descompactar imediatamente o arquivo que vem do servidor. A solicitação para salvar o arquivo é idêntica à verificação do status, basta adicionar / resultado no 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)

A função final para analisar a resposta do serviço. Sua tarefa também é salvar o identificador da tarefa de geocodificação atual:

    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 testá-lo, basta executar o interpretador Python na pasta de scripts. A classe Batch está no arquivo 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


Ótimo trabalho iniciado. Verifique o status:

>>> 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 o processamento do conjunto de dados está completo. Em apenas sete minutos, foi possível procodificar os 250 mil endereços (excluindo erros - contagem de erros). Resta salvar os resultados:

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

Descrição da classe de lote completo


Eu acho que não dói adicionar o script completamente:

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álise de Resultados


Como resultado, passei de aplicativos on-line trabalhando lentamente para serviços de geocodificação em lote. A escolha de um provedor de serviços geográficos depende inteiramente das tarefas que o confrontam. Recebo regularmente solicitações para processar um grande número de endereços, e a abordagem descrita no artigo ajudou a reduzir significativamente o tempo.

Espero que este artigo seja útil e, claro, estou aberto a comentários e acréscimos!

All Articles