使用 Elevation API 网络服务的最佳实践

Google Maps Platform 网络服务是一系列 HTTP 接口,供 Google 为您的地图应用提供地理数据的服务。

本指南介绍了一些在设置 Google Cloud 控制台时 Web 服务 请求和处理服务响应。请参阅开发者指南 获取 Elevation API 的完整文档。

什么是网络服务?

Google Maps Platform 网络服务是一个用于从以下位置请求 Maps API 数据的接口: 外部服务和使用地图应用中的数据。这些服务 按照 许可限制

Maps API 网络服务使用对特定网址的 HTTP(S) 请求,传递网址参数和/或 JSON 格式的 POST 数据作为服务的参数。通常,这些服务会在 响应正文为 JSON 或 XML,用于解析 和/或处理

典型的 Elevation API 请求通常为 以下表单:

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

其中 output 表示响应格式(通常 jsonxml)。

注意:所有 Elevation API 应用都需要进行身份验证。 详细了解身份验证凭据

SSL/TLS 访问

对于所有使用 API 密钥或包含用户信息的 Google Maps Platform 请求,都必须使用 HTTPS 数据。通过 HTTP 发出的包含敏感数据的请求可能会被拒绝。

构建有效网址

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

特殊字符

我们之所以需要转换特殊字符,是因为所有网址都需要符合统一资源标识符 (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 文本字符串、在 scheme 中使用 (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 Maps Platform 网络服务,网址的长度不得超过 16384 个字符 和静态网络 API对于大多数服务,很少出现接近这一字符数限制的情况。但请注意,某些服务具有的若干参数可能会导致网址较长。

合理使用 Google API

设计不合理的 API 客户端可能会给互联网和 Google 的服务器。本部分包含适用于 API 客户端的一些最佳做法。已关注 这些最佳做法可帮助您避免应用因无意间滥用 这些 API。

指数后退

在极少数情况下,处理您的请求时可能会出错;您可能会收到 4XX 或 5XX HTTP 否则 TCP 连接可能只是因为您的客户端与 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 调用不会 正在更新。

除了从头到尾,其他常见的同步时间 定位到某个小时的开始和每天开始的午夜。

处理响应

此部分介绍如何以动态方式从 Web 服务响应中提取这些值。

Google 地图网络服务提供的响应易于 可以理解,但并不完全便于用户使用。执行查询时, 您可能需要提取一些特定的 值。通常,您需要从网络上解析 服务,并仅提取您感兴趣的值。

您使用的解析架构取决于您是否返回 以 XML 或 JSON 格式输出。JSON 响应,格式已是 JavaScript 对象,可在 JavaScript 本身内处理 在客户端上运行 XML 响应应使用 XML 处理器处理 和 XML 查询语言来处理 XML 格式中的元素。 我们在XPath XML 处理中通常支持该功能, 库。

使用 XPath 处理 XML

XML 是一种相对成熟的结构化信息格式, 数据交换。尽管不如 JSON 那么轻量级,XML 提供了更多语言支持和更强大的工具。适用于以下应用的代码: 处理 XML 的功能内置于 javax.xml 个软件包。

处理 XML 响应时,您应该使用 在 XML 文档中选择节点的查询语言, 而不是假设这些元素位于 XML 标记。XPath 是用于唯一描述节点和元素的语言语法 。借助 XPath 表达式,您可以识别 XML 响应文档中的特定内容。

XPath 表达式

一定程度上熟悉 XPath 对于开发 一个可靠的解析方案本部分将重点介绍 使用 XPath 进行寻址,这样您就可以 处理多个元素并构建复杂查询。

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” 选择所有 <result> 个节点, 是<WebServiceResponse>的子元素 节点。(请注意,这两个元素均来自 节点“/”。)
  • 当前上下文的相对路径:表达式 “result”将与任意<result>匹配 当前上下文中的元素。一般来说,您不应该 而无需操心上下文,因为您通常处理的是 通过单个表达式获取服务结果。

这两种表达式都可以通过加上 通配符的路径,用双斜线(“//”)表示。 该通配符表示在 一个干预路径。XPath 表达式“//formatted_address” 例如, 将匹配当前文档中具有该名称的所有节点。 表达式 //viewport//lat 将匹配全部 可跟踪 <viewport><lat> 元素 作为家长

默认情况下,XPath 表达式将与所有元素相匹配。你可以限制 通过提供谓词将表达式与特定元素匹配, 用方括号 ([]) 括起来。XPath 表达式“/GeocodeResponse/result[2]”始终会返回 例如第 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>
    
name result,其 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 为解析 XML 和使用 XPath 表达式提供了广泛支持 在 javax.xml.xpath.* 软件包中。 因此,本部分中的示例代码使用 Java 来 说明如何处理 XML 和解析来自 XML 服务响应的数据。

要在 Java 代码中使用 XPath,您首先需要将 XPathFactory 的实例并调用 针对该工厂的 newXPath() 创建一个 XPath 对象。然后,此对象可以处理传递的 XML 和 XPath 表达式结合使用。evaluate()

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