Best practice per l'utilizzo dei servizi web dell'API Geocoding

I servizi web di Google Maps Platform sono una raccolta di interfacce HTTP ai servizi Google che forniscono dati geografici per le tue applicazioni di mappe.

Questa guida descrive alcune pratiche comuni utili per configurare le richieste del servizio web e per elaborare le risposte del servizio. Per la documentazione completa dell'API Geocoding, consulta la guida per gli sviluppatori.

Che cos'è un servizio web?

I servizi web di Google Maps Platform sono un'interfaccia per richiedere i dati dell'API Maps da servizi esterni e utilizzarli nelle tue applicazioni Maps. Questi servizi sono progettati per essere utilizzati in combinazione con una mappa, come indicato nelle limitazioni della licenza dei Termini di servizio di Google Maps Platform.

I servizi web delle API Maps utilizzano richieste HTTP(S) a URL specifici, passando parametri URL e/o dati POST in formato JSON come argomenti ai servizi. In genere, questi servizi restituiscono i dati nel corpo della risposta come JSON o XML per l'analisi e/o l'elaborazione da parte dell'applicazione.

Una richiesta API Geocoding tipica ha generalmente il seguente formato:

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

dove output indica il formato della risposta (in genere json o xml).

Nota: tutte le applicazioni dell'API Geocoding richiedono l'autenticazione. Scopri di più sulle credenziali di autenticazione.

Accesso SSL/TLS

HTTPS è obbligatorio per tutte le richieste di Google Maps Platform che utilizzano chiavi API o contengono dati degli utenti. Le richieste effettuate tramite HTTP che contengono dati sensibili potrebbero essere rifiutate.

Creazione di un URL valido

Potresti pensare che un URL "valido" sia evidente, ma non è così. Ad esempio, un URL inserito nella barra degli indirizzi di un browser può contenere caratteri speciali (ad es."上海+中國"); il browser deve tradurre internamente questi caratteri in una codifica diversa prima della trasmissione. Allo stesso modo, qualsiasi codice che genera o accetta input UTF-8 potrebbe trattare gli URL con caratteri UTF-8 come "validi", ma dovrebbe anche tradurre questi caratteri prima di inviarli a un server web. Questa procedura è chiamata codifica URL o codifica percentuale.

Caratteri speciali

Dobbiamo tradurre i caratteri speciali perché tutti gli URL devono essere conformi alla sintassi specificata dalla specifica Uniform Resource Identifier (URI). In pratica, questo significa che gli URL devono contenere solo un sottoinsieme speciale di caratteri ASCII: i familiari simboli alfanumerici e alcuni caratteri riservati per l'utilizzo come caratteri di controllo all'interno degli URL. Questa tabella riassume questi caratteri:

Riepilogo dei caratteri validi per gli URL
PartenzacaratteriUtilizzo degli URL
Alfanumerico 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 Stringhe di testo, utilizzo dello schema (http), porta (8080) e così via.
Non prenotato - _ . ~ Stringhe di testo
Prenotato ! * ' ( ) ; : @ & = + $ , / ? % # [ ] Caratteri di controllo e/o stringhe di testo

Quando crei un URL valido, devi assicurarti che contenga solo i caratteri mostrati nella tabella. In genere, l'adeguamento di un URL all'utilizzo di questo insieme di caratteri comporta due problemi, uno di omissione e uno di sostituzione:

  • I caratteri che vuoi gestire esistono al di fuori dell'insieme riportato sopra. Ad esempio, i caratteri in lingue straniere come 上海+中國 devono essere codificati utilizzando i caratteri riportati sopra. Per convenzione, gli spazi (che non sono consentiti negli URL) sono spesso rappresentati anche utilizzando il carattere più '+'.
  • I caratteri all'interno dell'insieme riportato sopra sono caratteri riservati, ma devono essere utilizzati in modo letterale. Ad esempio, ? viene utilizzato negli URL per indicare l'inizio della stringa di query. Se vuoi utilizzare la stringa "? e i Mysterions", devi codificare il carattere ?.'?'

Tutti i caratteri da codificare per l'URL vengono codificati utilizzando un carattere '%' e un valore esadecimale di due caratteri corrispondente al carattere UTF-8. Ad esempio,上海+中國 in UTF-8 viene codificato per l'URL come%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. La stringa ? and the Mysterians viene codificata come URL come %3F+and+the+Mysterians o %3F%20and%20the%20Mysterians.

Caratteri comuni che richiedono la codifica

Alcuni caratteri comuni che devono essere codificati sono:

Carattere non sicuro Valore codificato
Spazio %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

A volte la conversione di un URL ricevuto dall'input dell'utente può essere complicata. Ad esempio, un utente potrebbe inserire un indirizzo come "5th&Main St." In genere, devi creare l'URL dalle sue parti, trattando qualsiasi input dell'utente come caratteri letterali.

Inoltre, gli URL sono limitati a 16384 caratteri per tutti i servizi web e le API web statiche di Google Maps Platform. Per la maggior parte dei servizi, questo limite di caratteri viene raramente raggiunto. Tuttavia, tieni presente che alcuni servizi hanno diversi parametri che possono generare URL lunghi.

Utilizzo corretto delle API di Google

Client API progettati male possono generare un carico maggiore del necessario sia su internet sia sui server di Google. Questa sezione contiene alcune best practice per i client delle API. Seguire queste best practice può aiutarti a evitare il blocco della tua applicazione per uso improprio involontario delle API.

Gestione di errori e tentativi di nuovo invio

Per informazioni sui codici di risposta UNKNOWN_ERROR o OVER_QUERY_LIMIT dell'API Geocoding, consulta la sezione sulla gestione di errori e nuovi tentativi.

Backoff esponenziale

In rari casi, potrebbe verificarsi un problema durante l'elaborazione della richiesta. Potresti ricevere un codice di risposta HTTP 4XX o 5XX oppure la connessione TCP potrebbe non riuscire a un certo punto tra il client e il server di Google. Spesso vale la pena riprovare a inviare la richiesta, poiché la richiesta di follow-up potrebbe andare a buon fine se quella originale non è riuscita. Tuttavia, è importante non eseguire semplicemente un loop ripetuto di richieste ai server di Google. Questo comportamento di looping può sovraccaricare la rete tra il tuo client e Google, causando problemi a molte parti.

Un approccio migliore è quello di riprovare con ritardi crescenti tra un tentativo e l'altro. In genere, il ritardo viene aumentato di un fattore moltiplicativo a ogni tentativo, un approccio noto come backoff esponenziale.

Ad esempio, considera un'applicazione che vuole effettuare questa richiesta all'API Time Zone:

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

Il seguente esempio in Python mostra come effettuare la richiesta con backoff esponenziale:

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}")

Inoltre, assicurati che nella catena di chiamate dell'applicazione non sia presente un codice di ripetizione che porti a richieste ripetute in rapida successione.

Richieste sincronizzate

Un numero elevato di richieste sincronizzate alle API di Google può sembrare un attacco DDoS (Distributed Denial of Service) all'infrastruttura di Google ed essere trattato di conseguenza. Per evitarlo, devi assicurarti che le richieste dell'API non siano sincronizzate tra i client.

Ad esempio, considera un'applicazione che mostra l'ora nel fuso orario corrente. Questa applicazione probabilmente imposterà una sveglia nel sistema operativo del client per risvegliarlo all'inizio del minuto in modo che l'ora visualizzata possa essere aggiornata. L'applicazione non deve effettuare chiamate API nell'ambito dell'elaborazione associata all'allarme.

Eseguire chiamate API in risposta a una sveglia fissa non è consigliabile perché le chiamate API vengono sincronizzate con l'inizio del minuto, anche tra dispositivi diversi, anziché essere distribuite in modo uniforme nel tempo. Un'applicazione progettata male che esegue questa operazione produrrà un picco di traffico pari a sessantacinque volte i livelli normali all'inizio di ogni minuto.

Invece, un possibile buon design è impostare una seconda sveglia su un'ora scelta in modo casuale. Quando viene attivato questo secondo avviso, l'applicazione chiama le API di cui ha bisogno e memorizza i risultati. Quando l'applicazione vuole aggiornare la visualizzazione all'inizio del minuto, utilizza i risultati memorizzati in precedenza anziché richiamare di nuovo l'API. Con questo approccio, le chiamate API vengono distribuite uniformemente nel tempo. Inoltre, le chiamate API non ritardano il rendering durante l'aggiornamento del display.

Oltre all'inizio del minuto, altri momenti comuni di sincronizzazione che devi fare attenzione non scegliere come target sono l'inizio di un'ora e l'inizio di ogni giorno a mezzanotte.

Elaborazione delle risposte

Questa sezione spiega come estrarre questi valori in modo dinamico dalle risposte del servizio web.

I servizi web di Google Maps forniscono risposte facili da comprendere, ma non esattamente user-friendly. Quando esegui una query, anziché visualizzare un insieme di dati, probabilmente vuoi estrarre alcuni valori specifici. In genere, è consigliabile analizzare le risposte del servizio web ed estrarre solo i valori che ti interessano.

Lo schema di analisi che utilizzi dipende dal fatto che tu stia restituendo un output in XML o JSON. Le risposte JSON, essendo già sotto forma di oggetti JavaScript, possono essere elaborate all'interno di JavaScript stesso sul client. Le risposte XML devono essere elaborate utilizzando un elaboratore XML e un linguaggio di query XML per gestire gli elementi all'interno del formato XML. Nei seguenti esempi utilizziamo XPath, poiché è comunemente supportato nelle librerie di elaborazione XML.

Elaborazione di XML con XPath

XML è un formato di informazioni strutturate relativamente maturo utilizzato per lo scambio di dati. Sebbene non sia leggero come JSON, XML offre un supporto per più lingue e strumenti più solidi. Ad esempio, il codice per l'elaborazione di XML in Java è integrato nei pacchetti javax.xml.

Quando elabori le risposte XML, devi utilizzare un linguaggio di query appropriato per selezionare i nodi all'interno del documento XML, anziché assumere che gli elementi si trovino in posizioni assolute all'interno del markup XML. XPath è una sintassi di linguaggio per descrivere in modo univoco i nodi e gli elementi all'interno di un documento XML. Le espressioni XPath ti consentono di identificare contenuti specifici all'interno del documento di risposta XML.

Espressioni XPath

Una certa familiarità con XPath è molto utile per sviluppare un schema di analisi affidabile. Questa sezione si concentra su come gli elementi all'interno di un documento XML vengono indirizzati con XPath, consentendoti di indirizzare più elementi e di creare query complesse.

XPath utilizza le espressioni per selezionare gli elementi all'interno di un documento XML, utilizzando una sintassi simile a quella utilizzata per i percorsi delle directory. Queste espressioni identificano gli elementi all'interno di un albero di documenti XML, che è un albero gerarchico simile a quello di un DOM. In genere, le espressioni XPath sono avide, il che significa che corrisponderanno a tutti i nodi che soddisfano i criteri specificati.

Per illustrare i nostri esempi, utilizzeremo il seguente XML astratto:

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

Selezione dei nodi nelle espressioni

Le selezioni XPath selezionano i nodi. Il nodo principale comprende l'intero documento. Seleziona questo nodo utilizzando la speciale espressione "/". Tieni presente che il nodo principale non è il nodo di primo livello del documento XML; infatti, si trova un livello sopra questo elemento di primo livello e lo include.

I nodi elemento rappresentano i vari elementi all'interno della struttura del documento XML. Un elemento <WebServiceResponse>, ad esempio, rappresenta l'elemento di primo livello restituito nel nostro servizio di esempio riportato sopra. Puoi selezionare i singoli nodi tramite percorsi assoluti o relativi, indicati dalla presenza o dall'assenza di un carattere "/" iniziale.

  • Percorso assoluto: l'espressione "/WebServiceResponse/result" seleziona tutti i nodi <result> che sono figli del nodo <WebServiceResponse>. Tieni presente che entrambi questi elementi derivano dal nodo radice "/".
  • Percorso relativo dal contesto corrente: l'espressione "result" corrisponde a qualsiasi elemento <result> all'interno del contesto corrente. In genere, non devi preoccuparti del contesto, in quanto di solito elabori i risultati dei servizi web tramite una singola espressione.

Entrambe queste espressioni possono essere aumentate con l'aggiunta di un percorso jolly, indicato con una barra doppia ("//"). Questo carattere jolly indica che nel percorso intermedio possono corrispondere zero o più elementi. Ad esempio, l'espressione XPath "//formatted_address" corrisponderà a tutti i nodi con quel nome nel documento corrente. L'espressione //viewport//lat corrisponde a tutti gli elementi <lat> che possono risalire a <viewport> come elemento principale.

Per impostazione predefinita, le espressioni XPath corrispondono a tutti gli elementi. Puoi limitare la corrispondenza dell'espressione a un determinato elemento specificando un predicatore, che è racchiuso tra parentesi quadre ([]). L'espressione XPath "/GeocodeResponse/result[2] restituisce sempre il secondo risultato, ad esempio.

Tipo di espressione
Nodo principale
Espressione XPath:  "/"
Selezione:
    <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>
    
Percorso assoluto
Espressione XPath:  "/WebServiceResponse/result"
Selezione:
    <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>
    
Percorso con caratteri jolly
Espressione XPath:  "/WebServiceResponse//location"
Selezione:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
Percorso con predicato
Espressione XPath:  "/WebServiceResponse/result[2]/message"
Selezione:
    <message>The secret message</message>
    
Tutti gli elementi secondari diretti del primo result
Espressione XPath:  "/WebServiceResponse/result[1]/*"
Selezione:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
Il name di un result il cui testo type è "sample".
Espressione XPath:  "/WebServiceResponse/result[type/text()='sample']/name"
Selezione:
    Sample XML
    

È importante notare che, quando selezioni gli elementi, selezioni i nodi, non solo il testo all'interno di questi oggetti. In genere, è consigliabile eseguire l'iterazione su tutti i nodi corrispondenti ed estrarre il testo. Puoi anche eseguire una corrispondenza diretta dei nodi di testo; consulta Nodi di testo di seguito.

Tieni presente che XPath supporta anche i nodi attributo. Tuttavia, tutti i servizi web di Google Maps pubblicano elementi senza attributi, pertanto la corrispondenza degli attributi non è necessaria.

Selezione di testo nelle espressioni

Il testo all'interno di un documento XML viene specificato nelle espressioni XPath tramite un operatore nodo di testo. Questo operatore "text()" indica l'estrazione del testo dal nodo indicato. Ad esempio, l'espressione XPath "//formatted_address/text()" restituirà tutto il testo all'interno degli elementi <formatted_address>.

Tipo di espressione
Tutti i nodi di testo (inclusi gli spazi)
Espressione XPath:  "//text()"
Selezione:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
Selezione del testo
Espressione XPath:  "/WebServiceRequest/result[2]/message/text()"
Selezione:
    The secret message
    
Selezione sensibile al contesto
Espressione XPath:  "/WebServiceRequest/result[type/text() = 'sample']/name/text()"
Selezione:
    Sample XML
    

In alternativa, puoi valutare un'espressione e restituire un insieme di nodi, quindi eseguire l'iterazione su questo "insieme di nodi" estraendo il testo da ogni nodo. Utilizziamo questo approccio nell'esempio seguente.

Per ulteriori informazioni su XPath, consulta la specifica XPath W3C.

Valutazione di XPath in Java

Java supporta ampiamente l'analisi XML e l'utilizzo di espressioni XPath all'interno del pacchetto javax.xml.xpath.*. Per questo motivo, il codice di esempio in questa sezione utilizza Java per illustrare come gestire XML e analizzare i dati delle risposte del servizio XML.

Per utilizzare XPath nel codice Java, devi prima creare un'istanza di XPathFactory e chiamare newXPath() in quella factory per creare un oggetto XPath . Questo oggetto può quindi elaborare le espressioni XML e XPath passate utilizzando il metodo evaluate().

Quando valuti le espressioni XPath, assicurati di eseguire l'iterazione su tutti i possibili "insiemi di nodi" che potrebbero essere restituiti. Poiché questi risultati vengono restituiti come nodi DOM nel codice Java, devi acquisire questi valori multipli all'interno di un oggetto NodeList e eseguire l'iterazione su quell'oggetto per estrarre testo o valori da questi nodi.

Il codice seguente illustra come creare un oggetto XPath, assegnargli XML e un'espressione XPath e valutarla per stampare i contenuti pertinenti.

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");
    }
  }
}