Sprawdzone metody korzystania z usług internetowych interfejsu Geocoding API

Usługi internetowe Google Maps Platform to zbiór interfejsów HTTP usług Google, które dostarczają dane geograficzne dla aplikacji z mapami.

W tym przewodniku opisano kilka typowych metod przydatnych podczas konfigurowania żądań usługi internetowej i przetwarzania odpowiedzi usługi. Pełną dokumentację interfejsu Geocoding API znajdziesz w przewodniku dla programistów.

Co to jest usługa internetowa?

Usługi internetowe Google Maps Platform to interfejs do żądania danych interfejsu API Map Google z usług zewnętrznych i wykorzystywania tych danych w aplikacjach Map Google. Te usługi zostały zaprojektowane do użytku w połączeniu z mapą zgodnie z ograniczeniami licencji w Warunkach korzystania z usługi Google Maps Platform.

Usługi internetowe interfejsów API Map Google używają żądań HTTP(S) do określonych adresów URL, przekazując do usług parametry adresu URL lub dane POST w formacie JSON jako argumenty. Ogólnie usługi te zwracają dane w treści odpowiedzi w formacie JSON lub XML, aby je przeanalizować lub przetworzyć przez aplikację.

Typowe żądanie do interfejsu Geocoding API ma ogólnie następującą postać:

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

gdzie output wskazuje format odpowiedzi (zwykle json lub xml).

Uwaga: wszystkie aplikacje Geocoding API wymagają uwierzytelniania. Dowiedz się więcej o danych uwierzytelniających.

Dostęp przez SSL/TLS

Protokół HTTPS jest wymagany w przypadku wszystkich żądań Google Maps Platform, które korzystają z kluczy interfejsu API lub zawierają dane użytkowników. Żądania przesłane przez HTTP, które zawierają dane wrażliwe, mogą zostać odrzucone.

Tworzenie prawidłowego adresu URL

Może Ci się wydawać, że „prawidłowy” adres URL jest oczywisty, ale nie do końca tak jest. Na przykład adres URL wpisany w pasku adresu w przeglądarce może zawierać znaki specjalne (np."上海+中國"). Przed przesłaniem przeglądarka musi je wewnętrznie przetłumaczyć na inne kodowanie. Na tej samej zasadzie każdy kod generujący lub akceptujący dane wejściowe w formacie UTF-8 może traktować adresy URL ze znakami UTF-8 jako „prawidłowe”, ale przed wysłaniem ich na serwer WWW musi je przetłumaczyć. Ten proces nazywa się kodowaniem adresów URL lub kodowaniem procentowym.

Znaki specjalne

Musimy tłumaczyć znaki specjalne, ponieważ wszystkie adresy URL muszą być zgodne ze składnią określoną w specyfikacji Uniform Resource Identifier (URI). Oznacza to, że adresy URL muszą zawierać wyłącznie specjalny podzbiór znaków ASCII: znane symbole alfanumeryczne i niektóre znaki zarezerwowane, które mogą być używane jako znaki kontrolne w adresach URL. Podsumowanie tych znaków znajdziesz w tej tabeli:

Podsumowanie prawidłowych znaków adresu URL
UstawznakówWykorzystanie adresu URL
Znaki alfanumeryczne a b c d e f g h i j k l m n o p q r s t u v s 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 Ciągi tekstowe, użycie schematu (http), port (8080) itp.
Niezarezerwowane - _ . ~ Ciągi tekstowe
Zarezerwowano ! * ' ( ) ; : @ & = + $ , / ? % # [ ] Znaki kontrolne i ciągi tekstowe

Tworząc prawidłowy adres URL, musisz się upewnić, że zawiera on tylko znaki widoczne w tabeli Podsumowanie prawidłowych znaków adresu URL. Stworzenie adresu URL w taki sposób, aby używał tego zestawu znaków, zwykle prowadzi do 2 problemów: pominięcia i zastąpienia:

  • Znaki, które mają być obsługiwane, istnieją poza powyższym zestawem. Na przykład znaki w językach obcych, takich jak 上海+中國, muszą być zakodowane przy użyciu powyższych znaków. Zgodnie z powszechną konwencją spacje (które nie są dozwolone w adresach URL) są często oznaczane również znakiem plusa '+'.
  • Znaki w powyższym zestawie są znakami zarezerwowanymi, ale muszą być używane dosłownie. Na przykład ? jest używany w adresach URL do wskazywania początku ciągu zapytania. Jeśli chcesz użyć ciągu znaków „? i tajemnic”, musisz zakodować znak '?'.

Wszystkie znaki przeznaczone do kodowania URL są kodowane przy użyciu znaku '%' i dwuznakowej wartości szesnastkowej odpowiadającej ich znakowi UTF-8. Na przykład atrybut 上海+中國 w UTF-8 powinien być zakodowany na potrzeby adresu URL jako %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. Ciąg ? and the Mysterians będzie zakodowany na potrzeby adresu URL jako %3F+and+the+Mysterians lub %3F%20and%20the%20Mysterians.

Typowe znaki wymagające kodowania

Oto kilka typowych znaków, które muszą być zakodowane:

Niebezpieczny znak Zakodowana wartość
Spacja %20
%22
< %3C
> %3E
# %23
% %25
| %7C

Konwersja adresu URL uzyskanego dzięki danym wprowadzanym przez użytkownika bywa podejrzana. Na przykład użytkownik może wpisać adres jako „ul. Główna 5”. Adres URL należy zazwyczaj utworzyć z części, traktując każde dane wprowadzone przez użytkownika jako znaki literackie.

Dodatkowo w przypadku wszystkich usług internetowych Google Maps Platform i statycznych interfejsów API internetowych adresy URL mogą mieć maksymalnie 16 384 znaki. W większości usług ten limit znaków rzadko jest zbliżany do limitu. Pamiętaj jednak, że niektóre usługi mają kilka parametrów, które mogą powodować długie adresy URL.

Uprzejme korzystanie z interfejsów API Google

Źle zaprojektowane klienty interfejsu API mogą powodować nadmierne obciążenie zarówno internetu, jak i serwerów Google. Ta sekcja zawiera kilka sprawdzonych metod dla klientów korzystających z interfejsów API. Stosując się do tych sprawdzonych metod, unikniesz zablokowania aplikacji z powodu niezamierzonego nadużywania interfejsów API.

Zarządzanie błędami i ponownymi próbami

Informacje o kodach odpowiedzi UNKNOWN_ERROR i OVER_QUERY_LIMIT z Geocoding API znajdziesz w artykule Zarządzanie błędami i ponownymi próbami.

Exponential Backoff

W rzadkich przypadkach coś może pójść nie tak podczas obsługi żądania. Możesz otrzymać kod odpowiedzi HTTP 4XX lub 5XX albo połączenie TCP między Twoim klientem a serwerem Google może po prostu nie działać. Często warto ponowić próbę, ponieważ kolejne żądanie może zakończyć się pomyślnie, mimo że pierwotne żądanie się nie powiodło. Pamiętaj jednak, aby nie zapętlać kolejnych żądań do serwerów Google. Takie zapętlenie może spowodować przeciążenie sieci między Twoim klientem a Google, powodując problemy u wielu stron.

Lepszym rozwiązaniem jest ponowienie próby przy rosnących opóźnieniach między próbami. Zazwyczaj opóźnienie jest zwiększane przy każdej próbie przez mnożnik

Weźmy na przykład aplikację, która chce wysłać to żądanie do interfejsu Time Zone API:

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

Poniższy przykład w Pythonie pokazuje, jak wysłać żądanie z wykładniczym ponawianiem:

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

Zwróć też uwagę na to, aby w łańcuchu wywołań aplikacji nie można było umieszczać kodu ponawiania prób, który powodowałby powtarzanie żądań w krótkich odstępach czasu.

Zsynchronizowane żądania

Duża liczba zsynchronizowanych żądań wysyłanych do interfejsów API Google może przypominać atak typu DDoS (rozproszona odmowa usługi) na infrastrukturę Google i być odpowiednio traktowana. Aby tego uniknąć, sprawdź, czy żądania do interfejsu API nie są synchronizowane między klientami.

Weźmy za przykład aplikację, która wyświetla godzinę w bieżącej strefie czasowej. Ta aplikacja prawdopodobnie ustawi w systemie operacyjnym klienta alarm, który wybudzi ją na początku minuty, aby można było zaktualizować wyświetlany czas. W ramach przetwarzania powiązanego z tym alarmem aplikacja nie powinna wykonywać żadnych wywołań interfejsu API.

Wykonywanie wywołań interfejsu API w odpowiedzi na ustalony alarm jest niewłaściwe, ponieważ powoduje to synchronizowanie wywołań interfejsu API na początku minuty, nawet między różnymi urządzeniami, a nie równomierne rozłożenie w czasie. Źle zaprojektowana aplikacja może na początku każdej minuty zwiększyć ruch na poziomie 60 razy normalnym.

Zamiast tego jeden z możliwych dobrych sposobów to ustawienie drugiego alarmu na losowo wybraną godzinę. Po uruchomieniu tego drugiego alarmu aplikacja wywołuje interfejsy API, których potrzebuje, i zapisuje wyniki. Gdy aplikacja chce zaktualizować swój ekran na początku minuty, korzysta z zapisanych wcześniej wyników, zamiast ponownie wywoływać interfejs API. Przy tym podejściu wywołania interfejsu API są rozkładane równomiernie w czasie. Co więcej, wywołania interfejsu API nie opóźniają renderowania podczas aktualizowania wyświetlacza.

Oprócz początku minuty musisz pamiętać, aby nie ustawiać kierowania na początek godziny, a na początek każdego dnia o północy.

Przetwarzam odpowiedzi

W tej sekcji omówiono sposób dynamicznego wyodrębniania tych wartości z odpowiedzi usługi internetowej.

Usługi internetowe Map Google udzielają odpowiedzi, które są łatwe do zrozumienia, ale nie są przyjazne dla użytkownika. Podczas wykonywania zapytania, zamiast wyświetlać zbiór danych, warto wyodrębnić kilka określonych wartości. Ogólnie lepiej jest przeanalizować odpowiedzi z usługi internetowej i wyodrębnić tylko te wartości, które Cię interesują.

Schemat analizy, którego użyjesz, zależy od tego, czy dane wyjściowe są zwracane w formacie XML czy JSON. Odpowiedzi JSON, które mają już formę obiektów JavaScript, mogą być przetwarzane przez kod JavaScript po stronie klienta. Odpowiedzi XML należy przetwarzać za pomocą procesora XML oraz języka zapytań XML, aby adresować elementy w formacie XML. W poniższych przykładach użyliśmy XPath, ponieważ jest ona zwykle obsługiwana w bibliotekach przetwarzających XML.

Przetwarzanie XML za pomocą XPath

XML to stosunkowo dojrzały format uporządkowanych informacji używany do wymiany danych. Chociaż nie jest on tak łatwy w obsłudze jak format JSON, XML umożliwia szerszą obsługę języków i bardziej wydajne narzędzia. W pakietach javax.xml jest na przykład wbudowany kod do przetwarzania XML w języku Java.

Podczas przetwarzania odpowiedzi XML należy używać odpowiedniego języka zapytań do wybierania węzłów w dokumencie XML zamiast zakładać, że elementy znajdują się na bezwzględnych pozycjach w znacznikach XML. XPath to składnia języka służąca do jednoznacznego opisywania węzłów i elementów w dokumencie XML. Wyrażenia XPath pozwalają identyfikować określone treści w dokumencie XML odpowiedzi.

Wyrażenia XPath

Pewna wiedza z użyciem XPath znacznie ułatwia opracowanie solidnego schematu analizy składni. W tej sekcji skupimy się na sposobie, w jaki XPath adresuje elementy w dokumencie XML, co umożliwia obsługę wielu elementów i tworzenie złożonych zapytań.

XPath używa wyrażeń do wybierania elementów w dokumencie XML przy użyciu składni podobnej do składni ścieżek do katalogów. Te wyrażenia identyfikują elementy w drzewie dokumentu XML, które jest hierarchicznym drzewem podobnym do DOM. Ogólnie wyrażenia XPath mają charakter zachłanny, co oznacza, że pasują do wszystkich węzłów spełniających podane kryteria.

Do zilustrowania naszych przykładów użyjemy poniższego abstrakcyjnego kodu XML:

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

Wybór węzłów w wyrażeniach

Wybrane ścieżki XPath powodują wybranie węzłów. Węzeł główny obejmuje cały dokument. Ten węzeł wybiera się za pomocą wyrażenia specjalnego „/”. Pamiętaj, że węzeł główny nie jest węzłem najwyższego poziomu dokumentu XML. W rzeczywistości znajduje się on o jeden poziom powyżej elementu najwyższego poziomu i zawiera go.

Węzły elementów reprezentują różne elementy w drzewie dokumentów XML. Na przykład element <WebServiceResponse> reprezentuje element najwyższego poziomu zwrócony w naszej przykładowej usłudze powyżej. Poszczególne węzły wybiera się za pomocą ścieżek bezwzględnych lub względnych wskazujących obecność lub brak początkowego znaku „/”.

  • Ścieżka bezwzględna: wyrażenie „/WebServiceResponse/result” wybiera wszystkie węzły <result>, które są elementami podrzędnymi węzła <WebServiceResponse>. (Zwróć uwagę, że oba te elementy są dziedziczone z węzła głównego „/”).
  • Ścieżka względna z bieżącego kontekstu: wyrażenie „result” pasuje do dowolnych elementów <result> w bieżącym kontekście. Ogólnie nie musisz się martwić o kontekst, ponieważ wyniki z usług internetowych przetwarzasz zwykle za pomocą jednego wyrażenia.

Każde z tych wyrażeń można wzbogacić, dodając ścieżkę z symbolem wieloznacznym o podwójnym ukośniku („//”). Ten symbol wieloznaczny wskazuje, że na ścieżce może pasować zero lub więcej elementów. Na przykład wyrażenie XPath „//formatted_address” zostanie dopasowane do wszystkich węzłów o tej nazwie w bieżącym dokumencie. Wyrażenie //viewport//lat będzie pasować do wszystkich elementów <lat>, które mogą śledzić <viewport> jako element nadrzędny.

Domyślnie wyrażenia XPath pasują do wszystkich elementów. Możesz ograniczyć dopasowanie wyrażenia do określonego elementu, podając predykat umieszczony w nawiasach kwadratowych ([]). Na przykład wyrażenie XPath „/GeocodeResponse/result[2]” zawsze zwraca drugi wynik.

Rodzaj wyrażenia
Węzeł główny
Wyrażenie XPath:/
Zaznaczenie:
    <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>
    
Ścieżka bezwzględna
Wyrażenie XPath:/WebServiceResponse/result
Zaznaczenie:
    <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>
    
Ścieżka z symbolem wieloznacznym
Wyrażenie XPath:/WebServiceResponse//location
Zaznaczenie:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
Ścieżka z predykatem
Wyrażenie XPath:/WebServiceResponse/result[2]/message
Zaznaczenie:
    <message>The secret message</message>
    
Wszystkie bezpośrednie elementy podrzędne po pierwszym result dnia
Wyrażenie XPath:/WebServiceResponse/result[1]/*
Zaznaczenie:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
Wartość name elementu result, której tekst type to „sample”.
Wyrażenie XPath:/WebServiceResponse/result[type/text()='sample']/name
Zaznaczenie:
    Sample XML
    

Warto zauważyć, że podczas wybierania elementów wybierasz węzły, a nie tylko tekst w tych obiektach. Najlepiej jest iterować wszystkie pasujące węzły i wyodrębniać tekst. Węzły tekstowe możesz też dopasowywać bezpośrednio – zobacz Węzły tekstowe poniżej.

XPath obsługuje także węzły atrybutów, jednak wszystkie usługi internetowe Map Google obsługują elementy bez atrybutów, więc dopasowywanie atrybutów nie jest konieczne.

Zaznaczanie tekstu w wyrażeniach

Tekst w dokumencie XML określa się w wyrażeniach XPath za pomocą operatora węzła tekstowego. Ten operator „text()” wskazuje wyodrębnienie tekstu ze wskazanego węzła. Na przykład wyrażenie XPath „//formatted_address/text()” zwróci cały tekst w elementach <formatted_address>.

Rodzaj wyrażenia
Wszystkie węzły tekstowe (w tym odstępy)
Wyrażenie XPath://text()
Zaznaczenie:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
Zaznaczanie tekstu
Wyrażenie XPath:/WebServiceRequest/result[2]/message/text()
Zaznaczenie:
    The secret message
    
Wybór zależny od kontekstu
Wyrażenie XPath:/WebServiceRequest/result[type/text() = 'sample']/name/text()
Zaznaczenie:
    Sample XML
    

Możesz też wykonać ocenę wyrażenia i zwrócić zestaw węzłów, a następnie wykonać powtarzanie tego „zestawu węzłów”, wyodrębniając tekst z każdego węzła. Korzystamy z tej metody w poniższym przykładzie.

Więcej informacji o XPath znajdziesz w specyfikacji XPath W3C.

Ocena XPath w Javie

Java ma szerokie możliwości analizy składni XML i używania wyrażeń XPath w pakiecie javax.xml.xpath.*. Z tego powodu w przykładowym kodzie w tej sekcji widać, jak obsługiwać pliki XML i analizować dane z odpowiedzi usługi XML.

Aby użyć XPath w kodzie Java, musisz najpierw utworzyć instancję XPathFactory i wywołać w tej fabryce newXPath(), aby utworzyć obiekt XPath . Ten obiekt może następnie przetworzyć przekazane wyrażenia XML i XPath za pomocą metody evaluate().

Podczas obliczania wyrażeń XPath musisz stosować powtórzenia obejmujące wszystkie możliwe „zbiory węzłów”, które mogą zostać zwrócone. Te wyniki są zwracane jako węzły DOM w kodzie Java, dlatego należy przechwycić takie wartości w obiekcie NodeList i iterować obiekt, aby wyodrębnić tekst lub wartości z tych węzłów.

Poniższy kod pokazuje, jak utworzyć obiekt XPath, przypisać go do pliku XML i wyrażenie XPath oraz ocenić to wyrażenie, aby wyświetlić odpowiednią treść.

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