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

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

Questa guida descrive alcune pratiche comuni utili per configurare le richieste di servizi web ed elaborare le risposte dei servizi. Consulta la guida per gli sviluppatori per la documentazione completa dell'API Geocoding.

Che cos'è un servizio web?

I servizi web di Google Maps Platform sono un'interfaccia per richiedere i dati dell'API di Google Maps da servizi esterni e utilizzare i dati all'interno delle tue applicazioni Maps. Questi servizi sono progettati per essere utilizzati insieme a una mappa, in base alle Limitazioni di licenza nei Termini di servizio di Google Maps Platform.

I servizi web delle API di Google 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 in formato JSON o XML per l'analisi e/o l'elaborazione da parte dell'applicazione.

Una tipica richiesta dell'API Geocoding ha generalmente il seguente formato:

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

dove output indica il formato della risposta (di solito json o xml).

Nota: tutte le applicazioni API Geocoding richiedono l'autenticazione. Leggi ulteriori informazioni sulle credenziali di autenticazione.

Accesso SSL/TLS

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

Creazione di un URL valido

Puoi pensare che un URL "valido" sia ovvio, ma non è così. Un URL inserito in una barra degli indirizzi di un browser, ad esempio, può contenere caratteri speciali (ad es. "上海+中國"); il browser deve tradurre internamente questi caratteri in una codifica diversa prima della trasmissione. Per lo stesso token, qualsiasi codice che genera o accetta input UTF-8 potrebbe trattare come "validi" gli URL con caratteri UTF-8, ma dovrebbe anche tradurli prima di inviarli a un server web. Questo processo è noto come codifica degli 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, ovvero i simboli alfanumerici familiari, e alcuni caratteri riservati da utilizzare come caratteri di controllo all'interno degli URL. In questa tabella sono riepilogati questi caratteri:

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

Quando crei un URL valido, devi assicurarti che contenga solo i caratteri visualizzati nella tabella. La conformità di un URL per l'utilizzo di questo set di caratteri in genere causa due problemi, uno di omissione e uno di sostituzione:

  • I caratteri che vuoi gestire non rientrano nel set riportato sopra. Ad esempio, i caratteri in lingue straniere come 上海+中國 devono essere codificati utilizzando i caratteri precedenti. Secondo le convenzioni più diffuse, gli spazi (che non sono consentiti all'interno degli URL) vengono spesso rappresentati utilizzando anche il carattere '+'.
  • I caratteri esistono all'interno del set precedente come caratteri riservati, ma devono essere utilizzati letteralmente. Ad esempio, ? viene utilizzato all'interno degli 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 nell'URL vengono codificati utilizzando un carattere '%' e un valore esadecimale di due caratteri corrispondenti al carattere UTF-8. Ad esempio, 上海+中國 in UTF-8 verrebbe codificato nell'URL %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. La stringa ? and the Mysterians avrebbe la codifica URL %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 è complessa. Ad esempio, un utente potrebbe inserire l'indirizzo"Via Roma 12". In genere, devi generare l'URL dalle sue parti, trattando qualsiasi input utente come caratteri letterali.

Inoltre, gli URL hanno un limite di 16.384 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 discreto delle API di Google

I client API progettati in modo scorretto possono applicare più carico del necessario sia su internet sia sui server di Google. Questa sezione contiene alcune best practice per i client delle API. Seguendo queste best practice puoi evitare che la tua applicazione venga bloccata per comportamento illecito delle API.

Gestione di errori e nuovi tentativi

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

Backoff esponenziale

In rari casi potrebbe verificarsi un problema di gestione della richiesta; potresti ricevere un codice di risposta HTTP 4XX o 5XX oppure la connessione TCP potrebbe non riuscire in qualche punto tra il client e il server di Google. Spesso conviene riprovare perché la richiesta di follow-up potrebbe avere esito positivo se quella originale non è andata a buon fine. Tuttavia, è importante non limitarsi a eseguire il loop ripetutamente di richieste ai server di Google. Questo comportamento di loop può sovraccaricare la rete tra il client e Google, causando problemi a molte parti.

Un approccio migliore è riprovare con un ritardo maggiore 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 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}")

Devi anche fare attenzione che non esista un codice per nuovi tentativi più in alto nella catena di chiamate dell'applicazione che porta a richieste ripetute in rapida successione.

Richieste sincronizzate

Un numero elevato di richieste sincronizzate alle API di Google può sembrare un attacco DDoS (Distributed DoS) all'infrastruttura di Google ed essere trattate di conseguenza. Per evitare che ciò accada, devi assicurarti che le richieste API non siano sincronizzate tra i client.

Ad esempio, considera un'applicazione che mostra l'ora nel fuso orario attuale. Questa applicazione probabilmente imposterà un allarme nel sistema operativo client, attivandolo 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 in questione.

Effettuare chiamate API in risposta a un allarme fisso è un'azione negativa in quanto le chiamate API vengono sincronizzate all'inizio del minuto, anche tra dispositivi diversi, anziché essere distribuite in modo uniforme nel tempo. Un'applicazione progettata in modo scorretto produrrà un picco di traffico a sessanta volte i livelli normali all'inizio di ogni minuto.

Un'ottima soluzione potrebbe essere quella di impostare una seconda sveglia su un orario selezionato in modo casuale. Quando questo secondo allarme attiva, l'applicazione chiama tutte le API di cui ha bisogno e memorizza i risultati. Quando l'applicazione vuole aggiornare la propria visualizzazione all'inizio del minuto, utilizza i risultati archiviati in precedenza anziché chiamare di nuovo l'API. Con questo approccio, le chiamate API vengono distribuite uniformemente nel tempo. Inoltre, le chiamate API non ritardano il rendering quando il display viene aggiornato.

A parte l'inizio del minuto, altri orari di sincronizzazione comuni a cui occorre fare attenzione a non eseguire il targeting sono all'inizio di un'ora e all'inizio di ogni giornata a mezzanotte.

Elaborazione delle risposte

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

I servizi web di Google Maps forniscono risposte facili da capire, ma non esattamente facili da usare. Quando esegui una query, anziché visualizzare un set di dati, probabilmente vorrai 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 utilizzato dipende dal fatto che venga restituito l'output in XML o JSON. Le risposte JSON, che sono già sotto forma di oggetti JavaScript, possono essere elaborate all'interno di JavaScript sul client. Le risposte XML devono essere elaborate utilizzando un processore XML e un linguaggio di query XML per gestire gli elementi all'interno del formato XML. Negli esempi riportati di seguito 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 l'interscambio di dati. Sebbene non sia leggero come JSON, il formato XML fornisce un supporto più linguistico e strumenti più solidi. Il codice per elaborare XML in Java, ad esempio, è integrato nei pacchetti javax.xml.

Durante l'elaborazione delle risposte XML, devi utilizzare un linguaggio di query appropriato per selezionare i nodi all'interno del documento XML, anziché presumere che gli elementi si trovino in posizioni assolute all'interno del markup XML. XPath è una sintassi del linguaggio per la descrizione univoca di nodi ed elementi all'interno di un documento XML. Le espressioni XPath consentono di identificare contenuti specifici all'interno del documento di risposta XML.

Espressioni XPath

Una certa familiarità con XPath è fondamentale per sviluppare uno schema di analisi affidabile. Questa sezione si concentra su come gli elementi all'interno di un documento XML vengono gestiti con XPath, che consente di gestire più elementi e costruire 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 una struttura ad albero di documenti XML, che è una struttura gerarchica simile a quella di un DOM. Di solito, le espressioni XPath sono greedy, ovvero corrisponderanno a tutti i nodi che corrispondono ai criteri forniti.

Utilizzeremo il seguente XML astratto per illustrare i nostri esempi:

<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 di XPath selezionano i nodi. Il nodo radice comprende l'intero documento. Selezioni questo nodo utilizzando l'espressione speciale "/". Tieni presente che il nodo principale non è il nodo di primo livello del documento XML, ma si trova un livello sopra questo elemento di primo livello e lo include.

I nodi degli elementi rappresentano i vari elementi all'interno della struttura dei documenti XML. Un elemento <WebServiceResponse>, ad esempio, rappresenta l'elemento di primo livello restituito nel nostro servizio di esempio sopra riportato. 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> secondari del nodo <WebServiceResponse>. Tieni presente che entrambi questi elementi discendono dal nodo principale "/".
  • Percorso relativo del contesto attuale: l'espressione "result" corrisponderà a qualsiasi elemento <result> nel contesto corrente. In genere, non devi preoccuparti del contesto, perché di solito elabori i risultati dei servizi web tramite una singola espressione.

Ognuna di queste espressioni può essere aumentata mediante l'aggiunta di un percorso con caratteri jolly, indicato da una doppia barra ("//"). Questo carattere jolly indica che nel percorso intermedio possono essere presenti zero o più elementi. L'espressione XPath "//formatted_address", ad esempio, corrisponderà a tutti i nodi con quel nome nel documento corrente. L'espressione //viewport//lat corrisponderà a tutti gli elementi <lat> che possono tracciare <viewport> come padre.

Per impostazione predefinita, le espressioni XPath corrispondono a tutti gli elementi. Puoi limitare l'espressione in modo che corrisponda a un determinato elemento fornendo un predicate, 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 carattere 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 si selezionano gli elementi, si selezionano 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 trovare una corrispondenza diretta dei nodi di testo; consulta Nodi di testo di seguito.

Tieni presente che XPath supporta anche i nodi degli attributi; tuttavia, tutti i servizi web di Google Maps forniscono elementi senza attributi, quindi non è necessaria la corrispondenza degli attributi.

Selezione del testo nelle espressioni

Il testo all'interno di un documento XML viene specificato nelle espressioni XPath tramite un operatore text node. Questo operatore "text()" indica l'estrazione di 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 (spazi vuoti inclusi)
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 "set di nodi", estraendo il testo da ciascun nodo. Utilizziamo questo approccio nell'esempio riportato di seguito.

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

Valutazione di XPath in Java

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

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

Durante la valutazione delle espressioni XPath, assicurati di eseguire l'iterazione su tutti i possibili "set di nodi" che potrebbero essere restituiti. Poiché questi risultati vengono restituiti come nodi DOM nel codice Java, devi acquisire più valori all'interno di un oggetto NodeList ed eseguire l'iterazione sull'oggetto per estrarre eventuale testo o valore dai nodi.

Il seguente codice illustra come creare un oggetto XPath, assegnargli un XML e un'espressione XPath e valutare l'espressione 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");
    }
  }
}