Google Maps API Web 服務

「Google 地圖 Web 服務」是可用於存取 Google 服務的 HTTP 介面集合,能為您的地圖應用程式提供地理資料。本指南僅做為介紹普遍針對各種不同服務的 Web 服務與主機資訊之用。針對每個服務之文件的位置如下所示:

找出您所需要的 API

使用 API 挑選器以為您的專案找出適當的 API。

有關 Web 服務的進一步資訊

本指南後續將會討論設定 Web 服務要求與剖析回應的技巧。然而,對於每個服務的特定文件,您必須參考適當的文件。

Web 服務是什麼?

Google Maps API 以介面的形式提供這些 Web 服務,能讓您從外部服務要求 Maps API 資料,並讓您在您的地圖應用程式中使用它們。這些服務是基於和地圖一起使用的目的而設計,並符合 Maps API 服務條款授權限制

這些 Web 服務使用針對特定 URL 的 HTTP 要求,並將 URL 參數以引數的形式傳遞至服務。一般而言,這些服務會以 JSON 或 XML 的方式在 HTTP 要求中傳回資料,以供您的應用程式進行剖析和/或處理。

典型的 Web 服務要求通常有下列格式:

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

其中 service 指出要求的特定服務,而 output 則指出回應格式(通常是 jsonxml)。

每個服務的完整文件皆位於該服務的特定開發人員指南中。然而,本指南將提供一些對於設定 Web 服務要求與處理 Web 服務回應很有幫助的普遍做法。

SSL 存取

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

對於含有使用者資料或開發人員識別碼的所有 Maps API Web 服務,HTTPS 是必要的。透過 HTTP 傳送含有敏感資料的要求可能會被拒絕。

建置有效的 URL

您可能認為一個「有效」URL 的定義是相當理所當然,但事實上並不僅如此。例如,一個在瀏覽器網址列所輸入的 URL,可能包含特殊字元(例如 "上海+中國");瀏覽器在傳輸 URL 之前,必須先在內部將該字元轉譯為不同的編碼。同樣道理,能夠產生或接受 UTF-8 輸入的程式碼可能會將包含 UTF-8 字元的 URL 視為「有效」,但仍需要先轉譯那些字元,才能將它們傳送到 Web 伺服器。此程序稱為 URL 編碼

我們之所以需要轉譯特殊字元,是因為所有 URL 都必須符合由 W3 Uniform Resource Identifier 規格所指定的語法。實際上來說,這表示 URL 必須只包含一個特殊的 ASCII 字元子集,其中包括廣泛使用的英數符號,以及一些在 URL 內做為控制字元使用的保留字元。下表摘要說明這些字元:

有效 URL 字元摘要
集合字元URL 用途
英數 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) 等。
未保留 - _ . ~ 文字字串
保留 ! * ' ( ) ; : @ & = + $ , / ? % # [ ] 控制字元和/或文字字串

建置有效的 URL 時,您必須確定它只包含上述字元。在使 URL 符合此字元集的過程中,通常會出現兩種問題,遺漏問題與替代問題。

  • 想要處理的字元不存在於上述字元集內。例如,外國語言的字元(如上海+中國)需要使用上述字元進行編碼。此外,針對空白(URL 內不允許使用),普遍的機制是使用加號 '+' 字元來代表它。
  • 字元以保留字元的形式存在於上述字元集內,但必須以其實際字面用法使用。例如,? 原本是用於在 URL 內指出查詢字串的開頭,但如果您想要使用「? and the Mysterions,」字串,就必須對 ? 字元進行編碼。

所有要進行 URL 編碼的字元,都會使用一個 '%' 字元,以及一個對應其 UTF-8 字元的兩字元十六進位值來完成編碼。例如,UTF-8 編碼的上海+中國,在完成 URL 編碼後會是 %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B。而 ? and the Mysterians 字串,在完成 URL 編碼後則會是 %3F+and+the+Mysterians

一些必須進行編碼的常見字元為:

不安全字元 編碼值
空白 %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

有時候,轉換透過使用者輸入所收到的 URL 是一件不簡單的工作。例如,某位使用者可能會輸入 "5th&Main St." 為地址。一般而言,您應該將使用者輸入的所有內容當作實際的字元,並利用這些字元為組件來建構您的 URL。

此外,所有 Web 服務的 URL 都必須符合 8192 個字元上限的限制。對於大部分的服務而言,此字元限制通常不會造成任何麻煩。然而,請注意擁有數個參數的特定服務,可能會產生較長的 URL。

優雅地使用 Google API

設計不良的 API 用戶端會對網際網路與 Google 伺服器造成超出必要的載入。本節包含適用於 API 用戶端的一些最佳做法。遵循這些最佳做法有助於避免您因為 API 的非故意濫用而使應用程式遭到封鎖。

指數倒退

在極少數情況下,您的要求可能會遭遇錯誤;您可能會收到 4XX 或 5XX HTTP 回應程式碼,或者您的用戶端與 Google 伺服器之間的 TCP 連線可能故障。通常,當原始要求失敗時,後續要求可能會成功,因此再次嘗試要求是值得一試的做法。不過,重要的是不要只是一直重複向 Google 的伺服器發出要求。這樣的重複行為會造成您的用戶端與 Google 之間的網路負載過重,而導致許多方面的問題。

比較好的做法是以每次嘗試之間增加延遲的方式重試。一般做法是,每次嘗試的延遲都增加一個倍乘因子,這種方式稱為指數倒退

例如,假設有一個應用程式要向 Google Maps 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
import urllib2

def timezone(lat, lng, timestamp):
    # 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
    maps_key = 'YOUR_KEY_HERE'
    timezone_base_url = 'https://maps.googleapis.com/maps/api/timezone/json'

    # This joins the parts of the URL together into one string.
    url = timezone_base_url + '?' + urllib.urlencode({
        'location': "%s,%s" % (lat, lng),
        'timestamp': timestamp,
        'key': maps_key,
    })

    current_delay = 0.1  # Set the initial retry delay to 100ms.
    max_delay = 3600  # Set the maximum retry delay to 1 hour.

    while True:
        try:
            # Get the API response.
            response = str(urllib2.urlopen(url).read())
        except IOError:
            pass  # Fall through to the retry loop.
        else:
            # If we didn't get an IOError then parse the result.
            result = json.loads(response.replace('\\n', ''))
            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.

tz = timezone(39.6034810, -119.6822510, 1331161200)
print 'Timezone:', tz

您還應該小心,在導致快速連續重複要求的應用程式呼叫鏈中,沒有更高的重試程式碼。

同步處理要求

對 Google 的 API 發出大量同步處理要求會看起來像是對 Google 基礎結構發動「分散式阻斷服務」(DDos) 攻擊,因而引發相應的處理。為了避免這一點,您必須確認用戶端之間並未同步處理 API 要求。

例如,假設有一個以目前時區顯示時間的應用程式。此應用程式可能會在用戶端作業系統中設定一個鬧鐘,在每分鐘的開頭提醒它,以便更新顯示的時間。該應用程式應該將任何 API 呼叫做為與該鬧鐘相關聯處理的一部分。

使 API 呼叫回應固定鬧鐘是不理想的,因為那會導致將 API 呼叫同步處理至每分鐘的開頭,即使不同裝置之間也一樣,而不是隨時間平均分散。一個設計不良而如此執行的應用程式將在每分鐘開頭時產生比正常程度多 60 倍的流量高峰。

另一種可能的優良設計是將第二鬧鐘設定為隨機選擇的時間。當這第二鬧鐘觸發時,應用程式會呼叫所需的任何 API 並儲存結果。當應用程式要在每分鐘開頭時更新其顯示,即可使用先前儲存的結果,而不用再次呼叫 API。藉由這樣的方式,API 呼叫就會隨時間平均分散。更進一步來說,在更新顯示時,API 呼叫也不會使轉譯延遲。

除了每分鐘開頭,您應該小心不要當作目標的其他常用同步處理時間是每小時的開頭,以及每天午夜的開頭。

處理回應

由於 Web 服務要求之個別回應的確切格式是無法保證的(某些元素可能不存在或位於多個位置),因此您不應該假設由不同查詢所傳回之回應格式都是一樣的。您應該改為處理回應並透過運算式選取適當的值。本節將討論如何從 Web 服務回應動態擷取這些值。

Google 地圖 Web 服務提供易於理解,但對使用者並非十分友善的回應。執行查詢時,與其顯示一組資料集,您應該要擷取一些特定的值。一般而言,您必須剖析來自 Web 服務的回應,並只擷取您感興趣的值。

您使用的剖析配置將取決於您要傳回 XML 或 JSON 格式的輸出。由於 JSON 回應已經是 Javascript 物件格式,因此可以在用戶端透過 Javascript 本身來處理;XML 回應應該要使用 XML 處理器來處理,並利用 XML 查詢語言來處理 XML 格式內的元素。我們在下列範例中使用 XPath,因為它普遍受到 XML 處理程式庫支援。

使用 XPath 處理 XML

XML 是一個結構相對成熟的資訊格式,用來處理資料交換。雖然 XML 沒有像 JSON 一樣輕量,但卻能提供更多語言支援與更建全的工具。例如,處理 XML 的 Java 程式碼,已內建於 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 選取項目會選取「節點」。根節點將包含整個文件。您將使用特殊運算式 "/" 來選取此節點。請注意,根節點並非 XML 文件的最上層節點,它實際上位於比此頂層元素更高一層且包括它的層級。

元素節點代表 XML 文件樹狀結構內的各種元素。例如,<WebServiceResponse> 元素代表由上方範例服務所傳回的頂層元素。您可以透過絕對或相對路徑(由前方是否存在 "/" 字元來決定)來選取個別節點。

  • 絕對路徑:"/WebServiceResponse/result" 運算式會選取所有為 <WebServiceResponse> 節點之子節點的 <result> 節點(請注意,上述兩個元素皆繼承自根節點「/」)。
  • 目前內容的相對路徑:"result" 運算式將會傳回目前內容中的任何 <result> 元素。一般而言,您應該不需要擔心內容,因為您通常是透過單一運算式來處理 Web 服務結果。

上述運算式皆可以透過新增萬用字元路徑(以雙斜線「//」顯示)來進行調整。此萬用字元將指出有零或多個元素可能會在介入的路徑中傳回。例如,XPath 運算式 "//formatted_address" 將會傳回目前文件中符合該名稱的所有節點。//viewport//lat 運算式將會傳回所有可以以父元素的身分追蹤 <viewport><lat> 元素。

根據預設,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>
    
type 文字為 "sample" 之 resultname
XPath 運算式:"/WebServiceResponse/result[type/text()='sample']/name"
選取項目:
    Sample XML
    

選取元素時,請務必記得您選取的是節點,而非只是那些物件內的文字。一般而言,您應該要逐一查看所有傳回的節點並擷取文字。您也應該直接比對文字節點,請參閱下方的文字節點

請注意,XPath 同時也支援屬性節點,然而由於所有 Google 地圖 Web 服務都提供沒有屬性的元素,因此並不需要比對屬性。

運算式中的文字選取

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 針對剖析 XML,以及在 javax.xml.xpath.* 套件中使用 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");
    }
  }
}

js-v2-samples 下載程式碼

使用 Javascript 處理 JSON

JSON (Javascript Object Notation) 與 XML 相比,在回應上擁有明顯的輕量化優勢。在 JavaScript 中剖析這類結果並沒有太大的意義,因為該格式已經是有效的 Javascript 物件。例如,如果要在 JSON 結果物件內擷取 'formatted_address' 機碼的值,只要使用下列程式碼來存取它們:

for (i = 0; i < myJSONResult.results.length; i++) {
  myAddress[i] = myJSONResult.results[i].formatted_address;
}

請注意,由於 JSON 可能包含多個值,因此最好逐一檢視 results 陣列中的每個項目,以確保能擷取所有可能的值。然而在實務上,您可能會傾向只傳回第一個結果 (results[0])。

如果要以其他語言剖析 JSON,其難度僅稍微提升一些。下列 Python 範例將起始一個地理編碼 Web 服務要求,並在陣列中向使用者顯示所有 formatted_address 結果的值:

import simplejson, urllib

GEOCODE_BASE_URL = 'https://maps.googleapis.com/maps/api/geocode/json'

def geocode(address, **geo_args):
    geo_args.update({
        'address': address
    })

    url = GEOCODE_BASE_URL + '?' + urllib.urlencode(geo_args)
    result = simplejson.load(urllib.urlopen(url))

    print simplejson.dumps([s['formatted_address'] for s in result['results']], indent=2)

if __name__ == '__main__':
    geocode(address="San+Francisco")

Output: [ "San Francisco, CA, USA" ]

js-v2-samples 下載程式碼

sensor 參數

Google Maps API 先前要求您包括 sensor 參數,以指出您的應用程式是否使用感應器來判斷使用者的位置。現在已不再需要此參數。

傳送您對下列選項的寶貴意見...

這個網頁
Google Maps Web Service API
Google Maps Web Service API