透過 Routes API 試用新一代的路線規劃功能。

使用 Directions API 網路服務的最佳做法

Google 地圖平台網路服務是 Google 服務的 HTTP 介面,為地圖應用程式提供地理資料。

本指南將說明一些常用做法,協助您設定網路服務或要求服務及處理服務回應。如需 Directions API 的完整說明文件,請參閱開發人員指南

什麼是網路服務?

Google 地圖平台網路服務可要求從外部服務要求 Maps API 資料的介面,並在 Google 地圖應用程式中使用資料。根據 Google 地圖平台服務條款的授權限制,這些服務的設計可與地圖搭配使用。

Maps API 網路服務使用 HTTP(S) 要求至特定網址,並將網址參數和/或 JSON 格式 POST 資料做為引數傳遞至服務。一般而言,這些服務會以回應主體 (JSON 或 XML) 傳回資料,以供應用程式剖析和/或處理。

一般的 Directions API 要求通常如下:

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

其中 output 表示回應格式 (通常是 jsonxml)。

注意:所有 Directions API 應用程式都必須通過驗證。進一步瞭解驗證憑證

SSL/TLS 存取權

所有使用 API 金鑰或包含使用者資料的 Google 地圖平台要求,都必須使用 HTTPS。透過 HTTP 發出含有機密資料的要求可能會遭到拒絕。

建立有效網址

您可能認為網址是否「有效」一眼就能判斷,但實際情況不然。例如,在瀏覽器的網址列內輸入的網址可能包含特殊字元 (例如 "上海+中國");瀏覽器必須在內部將這些字元轉譯為不同的編碼方式才能傳送。同理可證,任何產生或接受 UTF-8 輸入值的程式碼可能會將含有 UTF-8 字元的網址視為「有效網址」,但也需要先轉譯這些字元,才能將其向外傳送至網路伺服器。這個過程稱為網址編碼百分比編碼

特殊字元

我們必須翻譯特殊字元,因為所有網址都必須符合統一資源 ID (URI) 規格指定的語法。事實上,這表示網址必須僅包含一個特殊的 ASCII 字元子集:慣用的英數字元符號,以及用做網址內控制字元的部分預留字元。下表摘要列出這些字元:

有效網址字元摘要
字元集字元網址使用情況
英數字元 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 文字字串、結構用途 (http)、連接埠 (8080) 等。
非預留 - _~ 文字字串
預留 ! * ' ( ) ; : @ & = + $ , / ? % # [ ] 控制字元和 (或) 文字字串

建立有效網址時,您必須確認網址僅包含「有效網址字元摘要」表格中列出的字元。網址如果使用這個字元集,通常會導致遺漏及代換兩個問題:

  • 您希望處理的字元不屬於上述字元集。舉例來說,外國語言的字元 (例如 上海+中國) 就需要使用上述字元加以編碼。依照普遍慣例,空格 (網址內不允許使用) 通常也用加號 '+' 字元來表示。
  • 如果是上述字元集中預留字元的字元,仍需依照字面意思使用。舉例來說,網址內會使用 ? 來表示查詢字串的開頭;如果您想使用「? and the Mysterions」這個字串,就必須對 '?' 字元進行編碼。

所有遵守網址編碼原則的字元都會利用 '%' 字元以及對應至其 UTF-8 字元的雙字元十六進位值編碼。舉例來說,採用 UTF-8 編碼的 上海+中國 改用網址編碼時,會成為 %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B。字串 ? and the Mysterians 會以網址編碼為 %3F+and+the+Mysterians%3F%20and%20the%20Mysterians

需要編碼的常見字元

必須編碼的部分常見字元如下:

不安全的字元 經過編碼的值
空格鍵 %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

轉換您從使用者輸入中取得的網址,有時並不容易處理。舉例來說,使用者輸入的地址可能是「5th&Main St.」。一般來說,您應該根據各組成部分來建立網址,並將任何使用者輸入內容當成常值字元來處理。

此外,所有 Google 地圖平台網路服務和靜態網路 API 的網址長度上限都是 8,192 個字元。對於大部分的服務而言,幾乎很少接近此字元限制的長度。但請注意,某些特定的服務具備幾個可能產生長網址的參數。

短暫使用 Google API

設計不當的 API 用戶端可在網際網路和 Google 伺服器上執行不必要的負載。本節提供一些 API 用戶端的最佳做法。遵循這些最佳做法有助於避免您的應用程式遭到無謂的濫用 API 濫用。

指數型退讓

在極少數情況下,處理要求可能會發生錯誤;您可能會收到 4XX 或 5XX HTTP 回應代碼,否則 TCP 連線可能會在用戶端與 Google 伺服器之間某些位置失敗。在重新嘗試要求之前,後續嘗試重試的要求通常相當值得,不過,請勿重複向 Google 伺服器發出要求,這種循環行為會導致用戶端與 Google 之間的網路超載,進而導致許多方發生問題。

建議在每次重試之間增加延遲來重試。一般而言,每次延遲的延遲時間會增加倍數因數,這個做法稱為「指數輪詢」

例如,假設應用程式想向 Time Zone API 發出這項要求:

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

以下 Python 範例說明如何以指數輪詢方式發出要求:

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

您也應該注意,應用程式呼叫鏈中的程式碼不會重試,因此能快速連續傳回要求。

同步要求

傳送至 Google API 的大量同步要求在 Google 基礎架構上可能會發生分散式阻斷服務 (DDoS) 攻擊,並據此進行處理。為避免這種情況,請確認 API 要求不會在用戶端之間保持同步。

舉例來說,請某個應用程式,顯示目前時區的時間。這個應用程式可能會在用戶端作業系統中,在清醒時設定鬧鐘,以便更新顯示時間。應用程式不應在與該鬧鐘相關聯的處理過程中發出任何 API 呼叫。

對固定鬧鐘發出 API 呼叫並不會產生負面影響,因為這會導致 API 呼叫在一分鐘內 (甚至是不同裝置) 同步處理,而非平均分配。設計不良的應用程式會在每分鐘開始時,正常產生六倍的正常流量。

其中一種可行的做法是將第二個鬧鐘設定為隨機選擇的時間。第二個鬧鐘觸發時,應用程式會呼叫所有必要的 API,然後儲存結果。如果應用程式在一開始啟動時更新螢幕,則會使用先前儲存的結果,而不是再次呼叫 API。使用這個方法時,API 呼叫會逐漸平均分配。此外,API 呼叫在螢幕更新時不會延遲算繪。

除了從早上開始,有些其他常見的同步處理時間需要注意,請「不要」鎖定目標的開始時間為一小時,每天開始午夜。

處理回應

本章節討論如何從網路服務回應中,以動態方式擷取這些值。

Google 地圖網路服務提供簡單易懂的回應,但並非容易使用。執行查詢時,與其顯示一組資料,您可能需要擷取一些特定的值。一般而言,建議您剖析來自網路服務的回應,並只擷取您感興趣的值。

您使用的剖析配置取決於傳回的是 XML 或 JSON 輸出內容。JSON 回應形式為 JavaScript 物件,可以在用戶端的 JavaScript 本身中處理。 XML 回應應使用 XML 處理器和 XML 查詢語言來處理,以便處理 XML 格式的元素。下列範例使用 XPath,因為 XML 處理程式庫經常支援這種變數。

利用 XPath 處理 XML

XML 是相對成熟的結構化資訊格式,適合資料交換。雖然 XML 不像 JSON 那麼輕量,但可提供更多語言支援和更強大的工具。例如,以 Java 處理 XML 的程式碼,會建構在 javax.xml 套件中。

處理 XML 回應時,應使用適當的查詢語言來選取 XML 文件中的節點,而不是假設元素會位於 XML 標記內的絕對位置。XPath 是專門用來描述 XML 文件中節點和元素的專屬語言語法。XPath 運算式可讓您在 XML 回應文件中識別特定內容。

XPath 運算式

熟悉 XPath 對於開發健全的剖析配置有長足的進步。本節著重說明 XPath 中 XML 文件內的元素處理方式,因此您可以處理多個元素並建構複雜的查詢。

XPath 使用「運算式」選取 XML 文件中的元素,使用與目錄路徑類似的語法。這些運算式可識別 XML 文件樹狀結構中的元素,後者是與 DOM 相似的階層式樹狀結構。XPath 運算式通常為灰色,表示其符合所有與指定條件相符的節點。

我們會使用以下抽象 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>

運算式中的節點選項

XPath 選項選取「Nodes」(節點)。根節點包含整個文件。您會使用特殊運算式「/」選取這個節點。請注意,根節點不是 XML 文件的頂層節點,但實際上是位於這個頂層元素上方的單一層級。

元素節點代表 XML 文件樹狀結構中的各種元素。舉例來說,<WebServiceResponse> 元素代表上述範例服務中傳回的頂層元素。您可以選擇以絕對或相對路徑來選取個別節點,要用哪一種開頭「/」字元來表示。

  • 絕對路徑:「/WebServiceResponse/result」運算式會選取 <WebServiceResponse> 節點子項的所有 <result> 節點。(請注意,這兩個元素都是從根節點「/」結尾)。
  • 目前結構定義的相對路徑:運算式「result」會比對目前情境中的任何 <result> 元素。一般而言,您不需要擔心內容,因為您通常會透過單一運算式處理網路服務結果。

其中一項運算式可能會增加萬用字元路徑,以雙斜線 (「//」) 做為擴增依據。這個萬用字元表示零個或多個元素在相鄰的路徑中可能相符。舉例來說,XPath 運算式「//formatted_address」會在目前文件中比對該名稱的所有節點。運算式 //viewport//lat 會比對所有 <lat> 元素,並將這些 <viewport> 視為父項。

根據預設,XPath 運算式會比對所有元件。透過提供以方括號 ([]) 括住的述詞,您可以限制運算式比對特定元素。XPath 運算式「/GeocodeResponse/result[2]」一律會傳回第二個結果。

運算式類型
根節點
XPath 運算式:/
選取項目:
    <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>
    
絕對路徑
XPath 運算式:/WebServiceResponse/result
選取項目:
    <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>
    
含萬用字元的路徑
XPath 運算式:/WebServiceResponse//location
選取項目:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
含述詞的路徑
XPath 運算式:/WebServiceResponse/result[2]/message
選取項目:
    <message>The secret message</message>
    
result 的所有直接子項
XPath 運算式:/WebServiceResponse/result[1]/*
選取項目:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
resultname,其中 type 文字為「sample」。
XPath 運算式:/WebServiceResponse/result[type/text()='sample']/name
選取項目:
    Sample XML
    

請注意,選取元素時,您還要選取節點,而不只是這些物件中的文字。一般而言,我們會建議您疊代所有相符的節點,並擷取文字。您也可以直接比對文字節點,請參閱下方的「文字節點 」一節。

請注意,XPath 也支援屬性節點,但所有 Google 地圖網路服務都不會提供屬性,因此不需要比對屬性。

運算式中的文字選項

XML 文件中的文字是透過「文字節點」運算子在 XPath 運算式中指定。這個運算子「text()」表示從指定節點擷取文字。例如,XPath 運算式「//formatted_address/text()」會傳回 <formatted_address> 元素中的所有文字。

運算式類型
所有文字節點 (包括空白字元)
XPath 運算式://text()
選取項目:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
文字選項
XPath 運算式:/WebServiceRequest/result[2]/message/text()
選取項目:
    The secret message
    
情境 / 敏感內容
XPath 運算式:/WebServiceRequest/result[type/text() = 'sample']/name/text()
選取項目:
    Sample XML
    

或者,您也可以評估運算式並傳回一組節點,然後反覆處理「節點集」,從各個節點擷取文字。我們會在下方的範例中採用此作法。

如要進一步瞭解 XPath,請參閱 XPath W3C 規格

在 Java 中評估 XPath

Java 支援在 javax.xml.xpath.* 套件中使用剖析 XML 及使用 XPath 運算式的廣泛支援。因此,本節中的程式碼範例使用 Java 說明如何處理 XML,以及剖析 XML 服務回應中的資料。

如要在 Java 程式碼中使用 XPath,您必須先將 XPathFactory 的執行個體執行個體化,並對該工廠呼叫 newXPath(),才能建立 XPath 物件。這樣一來,此物件就可以用 evaluate() 方法處理傳遞的 XML 和 XPath 運算式。

評估 XPath 運算式時,請務必疊代任何可能傳回的「節點集」。由於這些結果會在 Java 程式碼中以 DOM 節點的形式傳回,因此您應該在 NodeList 物件內擷取此類值,然後反覆處理該物件,以擷取這些節點的任何文字或值。

以下程式碼說明如何建立 XPath 物件、為其指派 XML 和 XPath 運算式,並評估運算式以輸出相關內容。

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