Google Maps API 网络服务

Google 地图网络服务是一系列 Google 服务的 HTTP 接口,这些服务可以为您的地图应用提供地理数据。本指南只用于介绍这些 Web 服务以及所有这些不同服务所共用的主机信息。以下网址提供了各服务的单独文档:

找到您需要的 API

利用 API 选取器找到适合您的项目的 API。

有关网络服务的更多信息

本指南的其余内容介绍设置 Web 服务请求和解析响应的技巧。但如果您需要了解各服务的具体信息,则必须查阅相应的文档。

什么是网络服务?

Google Maps API 提供这些 Web 服务的目的是,将其作为从外部服务请求 Maps API 数据以及在您的地图应用内使用这些数据的接口。这些服务设计为按照 Maps API 服务条款许可证限制与地图联用。

这些 Web 服务利用向特定 URL 发送的 HTTP 请求,向服务传递自变量形式的 URL 参数。一般而言,这些服务在 HTTP 请求中返回 JSON 或 XML 格式的数据,供您的应用进行解析和/或处理。

典型的网络服务请求通常采用以下格式:

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 网络服务请求都需要通过 HTTPS 发起。通过 HTTP 发起并包含敏感数据的请求将被拒绝。

生成有效的 URL

您可能认为“有效的”URL 不言自明,但实际并非如此。例如,在浏览器地址栏内输入的 URL 可能包含特殊字符(例如 "上海+中國");浏览器需要在内部将这些字符转换成其他编码,然后再进行传输。由于同样的原因,任何生成或接受 UTF-8 输入的代码可能都会将包含 UTF-8 字符的 URL 视为“有效”,但同样需要先转换这些字符,然后再将它们向外发送给 Web 服务器。此过程称为 URL 编码

我们之所以需要转换特殊字符,是因为所有 URL 都需要符合 W3 统一资源标识符规范所规定的语法。实质上,这意味着 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 的组成部分构建 URL,将任何用户输入都视为原义字符。

此外,对于所有网络服务,网址长度均限制为 8192 个字符。对于大多数服务,很少出现接近这一字符数限制的情况。但请注意,某些服务具有若干可能导致 URL 较长的参数。

合理使用 Google API

设计拙劣的 API 客户端会对互联网和 Google 服务器造成比实际需要更大的负担。此部分包含适用于 API 客户端的一些最佳做法。按照这些最佳做法操作可以帮助您避免应用因意外滥用 API 而遭屏蔽。

指数后退

在极少的情况下,系统在处理您的请求时会出错;您可能会接收到 4XX 或 5XX 响应代码,TCP 连接可能会在您的客户端与 Google 服务器之间的某个地方出现故障。有时,按照下面的请求所示重试请求可以解决最初的故障并取得成功。不过,也要确保不能单纯地向 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 调用全都同步到分钟开始时(即使在不同设备间也是如此),而不是在一段时间内平均分配。设计拙劣的应用就会这样操作,导致每分钟开始时出现流量尖峰,达到正常水平的六倍之多。

相比之下,一种可以采取的良好设计是将第二个报警设为一个随机选择的时间。当第二个报警触发时,应用将调用它需要的任何 API 并存储结果。如果应用想要在分钟开始时更新其显示画面,它会使用之前的存储值,而不是再次调用 API。通过这种方式,API 调用将在一段时间内平均分配。此外,在更新显示画面时,API 调用也不会使显示延迟。

除了分钟开始外,您应采用的其他常用同步时间还包括小时开始和每天开始的午夜时分。

处理响应

由于无法保证对 Web 服务请求各次响应的确切格式(某些元素可能缺少或位于多个位置),您切勿假定对于不同查询,任何给定响应的返回格式均相同。相反,您应该处理响应,并通过表达式选择合适的值。此部分介绍如何以动态方式从 Web 服务响应中提取这些值。

Google 地图网络服务提供的响应虽易于理解,但并非完全易用。执行查询时,您的目的很可能是提取几个具体的值,而不是显示一组数据。一般而言,您的目的是解析 Web 服务的响应,只提取您感兴趣的那些值。

您使用的解析架构取决于您返回输出采用的是 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 选择会选择节点。根节点囊括整个文档。您可以利用特殊表达式“/”选择该节点。请注意,根节点并非您的 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 Maps Web Services 提供的元素都不带属性,因此不需要匹配属性。

表达式中的文本选择

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

js-v2-samples 下载该代码

使用 Javascript 处理 JSON

JSON(Javascript 对象标记)相比 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 示例将发起一个地理编码网络服务请求,并向数组内的用户显示获得的全部 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