Práticas recomendadas para usar os serviços da Web da API Geocoding

Os serviços da Web da Plataforma Google Maps são uma coleção de interfaces HTTP para serviços do Google que fornecem dados geográficos para seus aplicativos de mapas.

Este guia descreve algumas práticas comuns úteis para configurar suas solicitações de serviço da Web e processar respostas do serviço. Consulte o guia para desenvolvedores para conferir a documentação completa da API Geocoding.

O que é um serviço Web?

Os serviços da Web da Plataforma Google Maps são uma interface para solicitar dados da API Maps de serviços externos e usar esses dados nos seus aplicativos do Maps. Esses serviços foram projetados para serem usados com um mapa, de acordo com as Restrições de licença nos Termos de Serviço da Plataforma Google Maps.

Os serviços da Web das APIs Maps usam solicitações HTTP(S) para URLs específicos, transmitindo parâmetros de URL e/ou dados POST no formato JSON como argumentos para os serviços. Geralmente, esses serviços retornam dados no corpo da resposta como JSON ou XML para análise e/ou processamento pelo seu aplicativo.

Uma solicitação típica da API Geocoding geralmente tem o seguinte formato:

https://maps.googleapis.com/maps/api/geocode/output?parameters

em que output indica o formato da resposta (geralmente json ou xml).

Observação: todos os aplicativos da API Geocoding exigem autenticação. Saiba mais sobre as credenciais de autenticação.

Acesso SSL/TLS

O HTTPS é obrigatório para todas as solicitações da Plataforma Google Maps que usam chaves de API ou contêm dados do usuário. As solicitações feitas por HTTP que contêm dados sensíveis podem ser rejeitadas.

Criar um URL válido

Você pode achar que um URL "válido" é imediatamente identificável, mas não funciona assim. Um URL inserido em uma barra de endereço de um navegador, por exemplo, pode conter caracteres especiais (por exemplo, "上海+中國"). O navegador precisa converter internamente esses caracteres em uma codificação diferente antes de transmiti-los. Da mesma forma, qualquer código que gere ou aceite entrada UTF-8 pode tratar URLs com caracteres UTF-8 como "válidos", mas também precisa converter esses caracteres antes de enviá-los a um servidor da Web. Esse processo é chamado de codificação de URL ou codificação por cento.

Caracteres especiais

É necessário converter caracteres especiais porque todos os URLs precisam estar em conformidade com a sintaxe especificada pela especificação do localizador uniforme de recursos (URI, na sigla em inglês). Efetivamente, isso significa que os URLs devem conter apenas um subconjunto especial de caracteres ASCII: os familiares símbolos alfanuméricos e alguns caracteres reservados para uso como caracteres de controle em URLs. Esta tabela resume esses caracteres:

Resumo de caracteres válidos para URLs
Conjuntode caracteresUso em URLs
Alfanuméricos a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 Strings de texto, uso do esquema (http), porta (8080) etc.
Não reservados - _ . ~ Strings de texto
Reservados ! * ' ( ) ; : @ & = + $ , / ? % # [ ] Caracteres de controle e/ou strings de texto

Ao criar um URL válido, você precisa garantir que ele contenha apenas os caracteres mostrados na tabela. Conformar um URL ao uso desse conjunto de caracteres geralmente causa dois problemas, um de omissão e um de substituição.

  • Caracteres que você quer processar existem fora desse conjunto. Por exemplo, os caracteres de idiomas estrangeiros como 上海+中國 precisam ser codificados como mostrado acima. Por convenção popular, os espaços (que não são permitidos nos URLs) também são geralmente representados pelo caractere do sinal de adição ('+').
  • Caracteres existem no conjunto acima como caracteres reservados, mas precisam ser usados literalmente. Por exemplo, ? é usado em URLs para indicar o início da string de consulta. Se você quiser usar a string "? and the Mysterions", codifique o caractere '?'.

Todos os caracteres que precisam ser codificados para serem adicionados a URLs são codificados por meio do uso de um '%' e um valor hexadecimal de dois caracteres correspondente ao seu caractere UTF-8. Por exemplo, 上海+中國 em UTF-8 seria codificado como %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B para uso em URLs. A string ? and the Mysterians seria codificada em um URL como %3F+and+the+Mysterians ou %3F%20and%20the%20Mysterians.

Caracteres comuns que precisam de codificação

Alguns caracteres comuns que precisam ser codificados:

Caractere inválido Valor codificado
Espaço %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

Converter um URL recebido de uma entrada do usuário pode ser complicado. Por exemplo, um usuário pode inserir um endereço como "5th&Main St". Geralmente, você precisa criar o URL das partes dele, tratando quaisquer entradas do usuário como caracteres literais.

Além disso, os URLs estão limitados a 16.384 caracteres para todos os serviços da Web da Plataforma Google Maps e APIs estáticas da Web. Para a maioria dos serviços, esse limite raramente é atingido. No entanto, alguns serviços têm diversos parâmetros que podem resultar em URLs longos.

Uso adequado das Google APIs

Clientes de API mal projetados podem gerar mais carga do que o necessário na Internet e nos servidores do Google. Esta seção contém algumas das melhores práticas para cliente das APIs. Seguir essas práticas recomendadas pode ajudar a evitar que seu app seja bloqueado por abuso inadvertido das APIs.

Gerenciar erros e novas tentativas

Para informações sobre os códigos de resposta UNKNOWN_ERROR ou OVER_QUERY_LIMIT da API Geocoding, leia sobre como gerenciar erros e novas tentativas.

Retirada exponencial

Em casos raros, algo pode dar errado ao atender sua solicitação. Você pode receber um código de resposta HTTP 4XX ou 5XX ou a conexão TCP pode simplesmente falhar em algum lugar entre o cliente e o servidor do Google. Muitas vezes, vale a pena tentar novamente, já que a solicitação de acompanhamento pode ter sucesso quando a original falhar. No entanto, é importante não fazer solicitações repetidas nos servidores do Google. Esse comportamento em loop pode sobrecarregar a rede entre o cliente e o Google, causando problemas para muitas partes.

Uma melhor abordagem é tentar novamente com intervalos maiores entre as tentativas. Normalmente, o atraso é aumentado por um fator multiplicativo a cada tentativa, uma abordagem conhecida como espera exponencial.

Por exemplo, considere um aplicativo que queira fazer esta solicitação para a API Time Zone:

https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510&timestamp=1331161200&key=YOUR_API_KEY

O exemplo de Python a seguir mostra como fazer a solicitação com retirada exponencial:

import json
import time
import urllib.error
import urllib.parse
import urllib.request

# The maps_key defined below isn't a valid Google Maps API key.
# You need to get your own API key.
# See https://developers.google.com/maps/documentation/timezone/get-api-key
API_KEY = "YOUR_KEY_HERE"
TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json"


def timezone(lat, lng, timestamp):

    # Join the parts of the URL together into one string.
    params = urllib.parse.urlencode(
        {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,}
    )
    url = f"{TIMEZONE_BASE_URL}?{params}"

    current_delay = 0.1  # Set the initial retry delay to 100ms.
    max_delay = 5  # Set the maximum retry delay to 5 seconds.

    while True:
        try:
            # Get the API response.
            response = urllib.request.urlopen(url)
        except urllib.error.URLError:
            pass  # Fall through to the retry loop.
        else:
            # If we didn't get an IOError then parse the result.
            result = json.load(response)

            if result["status"] == "OK":
                return result["timeZoneId"]
            elif result["status"] != "UNKNOWN_ERROR":
                # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or
                # ZERO_RESULTS. There is no point retrying these requests.
                raise Exception(result["error_message"])

        if current_delay > max_delay:
            raise Exception("Too many retry attempts.")

        print("Waiting", current_delay, "seconds before retrying.")

        time.sleep(current_delay)
        current_delay *= 2  # Increase the delay each time we retry.


if __name__ == "__main__":
    tz = timezone(39.6034810, -119.6822510, 1331161200)
    print(f"Timezone: {tz}")

Também é preciso ter cuidado para que não haja um código de nova tentativa mais alto na cadeia de chamadas do aplicativo que leve a solicitações repetidas em sucessão rápida.

Solicitações sincronizadas

Grandes quantidades de solicitações sincronizadas para as APIs do Google podem parecer um ataque distribuído de negação de serviço (DDoS) na infraestrutura do Google e ser tratadas de acordo. Para evitar isso, verifique se as solicitações de API não são sincronizadas entre os clientes.

Por exemplo, considere um aplicativo que exibe a hora do fuso horário atual. Esse aplicativo provavelmente vai definir um alarme no sistema operacional do cliente para que ele seja ativado no início do minuto, de modo que o tempo mostrado possa ser atualizado. O aplicativo não pode fazer chamadas de API como parte do processamento associado ao alarme.

Fazer chamadas de API em resposta a um alarme fixo é ruim, porque faz com que as chamadas de API sejam sincronizadas no início do minuto, mesmo entre dispositivos diferentes, em vez de serem distribuídas uniformemente ao longo do tempo. Um aplicativo mal projetado que fizer isso vai produzir um pico de tráfego 60 vezes maior que o normal no início de cada minuto.

Em vez disso, um bom possível design é ter um segundo alarme definido a um horário escolhido aleatoriamente. Quando esse segundo alarme é acionado, o aplicativo chama todas as APIs necessárias e armazena os resultados. Quando o aplicativo quer atualizar a exibição no início do minuto, ele usa os resultados armazenados anteriormente em vez de chamar a API novamente. Com essa abordagem, as chamadas de API são distribuídas de maneira uniforme ao longo do tempo. Além disso, as chamadas de API não atrasam a renderização quando a tela está sendo atualizada.

Além do início do minuto, outros horários de sincronização comuns que você precisa não segmentar são o início de uma hora e o início de cada dia à meia-noite.

Como processar respostas

Esta seção discute como extrair esses valores das respostas dos serviços web de forma dinâmica.

Os serviços da Web do Google Maps fornecem respostas que são fáceis de entender, mas não são exatamente fáceis de usar. Ao realizar uma consulta, em vez de mostrar um conjunto de dados, você provavelmente vai querer extrair alguns valores específicos. Em geral, você vai querer analisar as respostas do serviço da Web e extrair apenas os valores que lhe interessam.

O esquema de análise usado depende se você está retornando a saída em XML ou JSON. As respostas JSON, que já estão na forma de objetos JavaScript, podem ser processadas no próprio JavaScript no cliente. As respostas XML precisam ser processadas usando um processador XML e uma linguagem de consulta XML para abordar elementos no formato XML. Usamos XPath nos exemplos abaixo, já que ele tem suporte comum em bibliotecas de processamento de XML.

Como processar XML com XPath

O XML é um formato de informação estruturado relativamente maduro usado para troca de dados. Embora não seja tão leve quanto o JSON, o XML oferece mais suporte a idiomas e ferramentas mais robustas. O código para processar XML em Java, por exemplo, é integrado aos pacotes javax.xml.

Ao processar respostas em XML, use uma linguagem de consulta adequada para selecionar nós no documento XML, em vez de sugerir que os elementos estão em posições absolutas na marcação XML. XPath é uma sintaxe de linguagem para descrever de forma exclusiva nós e elementos em um documento XML. As expressões XPath permitem identificar conteúdo específico no documento de resposta XML.

Expressões XPath

Ter algum conhecimento sobre XPath ajuda muito no desenvolvimento de um esquema de análise robusto. Esta seção vai se concentrar em como os elementos em um documento XML são endereçados com XPath, permitindo que você enderece vários elementos e construa consultas complexas.

O XPath usa expressões para selecionar elementos em um documento XML, usando uma sintaxe semelhante à usada para caminhos de diretório. Essas expressões identificam elementos em uma árvore de documentos XML, que é uma árvore hierárquica semelhante à de um DOM. Em geral, as expressões XPath são ambiciosas, indicando que vão corresponder a todos os nós que correspondem aos critérios fornecidos.

Vamos usar o XML abstrato a seguir para ilustrar nossos exemplos:

<WebServiceResponse>
 <status>OK</status>
 <result>
  <type>sample</type>
  <name>Sample XML</name>
  <location>
   <lat>37.4217550</lat>
   <lng>-122.0846330</lng>
  </location>
 </result>
 <result>
  <message>The secret message</message>
 </result>
</WebServiceResponse>

Seleção de nós em expressões

As seleções de XPath selecionam nós. O nó raiz abrange todo o documento. Você seleciona esse nó usando a expressão especial "/". O nó raiz não é o nó de nível superior do documento XML. Na verdade, ele fica um nível acima desse elemento de nível superior e o inclui.

Os nós de elemento representam os vários elementos na árvore de documentos XML. Um elemento <WebServiceResponse>, por exemplo, representa o elemento de nível superior retornado no serviço de exemplo acima. Você seleciona nós individuais por caminhos absolutos ou relativos, indicados pela presença ou ausência de um caractere "/" inicial.

  • Caminho absoluto: a expressão "/WebServiceResponse/result" seleciona todos os nós <result> que são filhos do nó <WebServiceResponse>. Observe que esses dois elementos são descendentes do nó raiz "/".
  • Caminho relativo do contexto atual: a expressão "result" corresponde a qualquer elemento <result> no contexto atual. Geralmente, não é necessário se preocupar com o contexto, já que você geralmente processa os resultados do serviço da Web com uma única expressão.

Qualquer uma dessas expressões pode ser aumentada com a adição de um caminho curinga, indicado com um caractere de barra dupla ("//"). Esse curinga indica que zero ou mais elementos podem corresponder no caminho intermediário. A expressão XPath "//formatted_address", por exemplo, vai corresponder a todos os nós desse nome no documento atual. A expressão //viewport//lat corresponde a todos os elementos <lat> que podem rastrear <viewport> como um pai.

Por padrão, as expressões XPath correspondem a todos os elementos. É possível restringir a expressão para corresponder a um determinado elemento fornecendo um predicado, que é fechado entre colchetes ([]). A expressão XPath "/GeocodeResponse/result[2] sempre retorna o segundo resultado, por exemplo.

Tipo de expressão
Nó raiz
Expressão XPath: "/"
Seleção:
    <WebServiceResponse>
     <status>OK</status>
     <result>
      <type>sample</type>
      <name>Sample XML</name>
      <location>
       <lat>37.4217550</lat>
       <lng>-122.0846330</lng>
      </location>
     </result>
     <result>
      <message>The secret message</message>
     </result>
    </WebServiceResponse>
    
Caminho absoluto
Expressão XPath: "/WebServiceResponse/result"
Seleção:
    <result>
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    </result>
    <result>
     <message>The secret message</message>
    </result>
    
Caminho com curinga
Expressão XPath: "/WebServiceResponse//location"
Seleção:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
Caminho com predicado
Expressão XPath: "/WebServiceResponse/result[2]/message"
Seleção:
    <message>The secret message</message>
    
Todos os filhos diretos do primeiro result
Expressão XPath: "/WebServiceResponse/result[1]/*"
Seleção:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
O name de um result cujo texto type é "sample".
Expressão XPath: "/WebServiceResponse/result[type/text()='sample']/name"
Seleção:
    Sample XML
    

É importante observar que, ao selecionar elementos, você seleciona nós, não apenas o texto dentro desses objetos. Em geral, você vai querer iterar sobre todos os nós correspondentes e extrair o texto. Você também pode fazer a correspondência de nós de texto diretamente. Consulte Nós de texto abaixo.

O XPath também oferece suporte a nós de atributos. No entanto, todos os serviços da Web do Google Maps exibem elementos sem atributos. Portanto, a correspondência de atributos não é necessária.

Seleção de texto em expressões

O texto em um documento XML é especificado em expressões XPath por um operador de nó de texto. Esse operador "text()" indica a extração de texto do nó indicado. Por exemplo, a expressão XPath "//formatted_address/text()" vai retornar todo o texto nos elementos <formatted_address>.

Tipo de expressão
Todos os nós de texto (incluindo espaços em branco)
Expressão XPath: "//text()"
Seleção:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
Seleção de texto
Expressão XPath: "/WebServiceRequest/result[2]/message/text()"
Seleção:
    The secret message
    
Seleção com reconhecimento de contexto
Expressão XPath: "/WebServiceRequest/result[type/text() = 'sample']/name/text()"
Seleção:
    Sample XML
    

Como alternativa, você pode avaliar uma expressão e retornar um conjunto de nós e iterar sobre esse "conjunto de nós", extraindo o texto de cada um. Usamos essa abordagem no exemplo abaixo.

Para mais informações sobre XPath, consulte a especificação XPath W3C.

Como avaliar o XPath em Java

O Java tem amplo suporte para analisar XML e usar expressões XPath no pacote javax.xml.xpath.*. Por esse motivo, o código de exemplo desta seção usa Java para ilustrar como processar XML e analisar dados de respostas de serviço XML.

Para usar o XPath no código Java, primeiro é necessário instanciar uma instância de um XPathFactory e chamar newXPath() nessa fábrica para criar um objeto XPath . Esse objeto pode processar expressões XML e XPath transmitidas usando o método evaluate().

Ao avaliar expressões XPath, itere sobre todos os possíveis "conjuntos de nós" que podem ser retornados. Como esses resultados são retornados como nós DOM no código Java, capture esses valores em um objeto NodeList e itere sobre ele para extrair qualquer texto ou valor desses nós.

O código a seguir ilustra como criar um objeto XPath, atribuir XML e uma expressão XPath e avaliar a expressão para imprimir o conteúdo relevante.

import org.xml.sax.InputSource;
import org.w3c.dom.*;
import javax.xml.xpath.*;
import java.io.*;

public class SimpleParser {

  public static void main(String[] args) throws IOException {

	XPathFactory factory = XPathFactory.newInstance();

    XPath xpath = factory.newXPath();

    try {
      System.out.print("Web Service Parser 1.0\n");

      // In practice, you'd retrieve your XML via an HTTP request.
      // Here we simply access an existing file.
      File xmlFile = new File("XML_FILE");

      // The xpath evaluator requires the XML be in the format of an InputSource
	  InputSource inputXml = new InputSource(new FileInputStream(xmlFile));

      // Because the evaluator may return multiple entries, we specify that the expression
      // return a NODESET and place the result in a NodeList.
      NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET);

      // We can then iterate over the NodeList and extract the content via getTextContent().
      // NOTE: this will only return text for element nodes at the returned context.
      for (int i = 0, n = nodes.getLength(); i < n; i++) {
        String nodeString = nodes.item(i).getTextContent();
        System.out.print(nodeString);
        System.out.print("\n");
      }
    } catch (XPathExpressionException ex) {
	  System.out.print("XPath Error");
    } catch (FileNotFoundException ex) {
      System.out.print("File Error");
    }
  }
}