摘自:https://developers.google.com/maps/documentation/webservices/
本文将探讨 Google Maps API Web Services,这是一个为您的地图应用程序提供地理数据的 Google 服务的 HTTP 接口集合。本指南仅旨在介绍通用于所有不同服务的 Web 服务和托管信息。每个服务的单个文档位于以下位置:
本指南的其余部分将探讨设置 Web 服务请求和解析响应的技术。但是,如需每个服务的特定说明,则必须参阅相应文档。
Google Maps API 提供这些网络服务作为从外部服务中请求 Google Maps API 数据以及在您的地图应用程序中使用它们的接口。这些服务设计为与地图一起使用,并遵循 Google Maps API 服务许可限制条款。
这些网络服务使用特定网址的 HTTP 请求并将网址参数作为参数提供给服务。一般来讲,这些服务会在 HTTP 请求中以 JSON 或 XML 的形式传回数据,供您的应用程序进行解析和/或处理。
一个典型的网络服务请求通常采用以下形式:
http://maps.googleapis.com/maps/api/service/output?parameters
其中 service
表示所请求的特定服务,而 output
表示响应格式(通常为 json
或 xml
)。
每个服务的完整说明包含在特定服务的开发人员指南中。本指南只介绍一些用于设置网络服务请求和处理网络服务响应的通用实践方法。
您也可以通过 HTTPS 访问 Maps API 网络服务。要执行此操作,请将您请求网址中的协议更改为 https
,如下所示:
https://maps.googleapis.com/maps/api/service/output?parameters
如果应用程序包含敏感的用户数据(例如用户所处位置),推荐在请求中使用 HTTPS。
sensor
参数跟踪使用情况现在,使用 Google Maps API 时,需要指明您的应用程序在任何 Google Maps API 库或服务请求中是否使用传感器(例如 GPS 定位器)确定用户的位置。这对移动设备尤为重要。如果您的 Google Maps API 应用程序使用任何形式的传感器确定访问您的应用程序的设备的位置,那么您必须通过将 sensor
参数值设置为 true
以声明这一点。
请注意,即使您的应用程序未使用任何位置传感器,仍需要设置 sensor
参数(在本例中设置为 false
)。
您可能会认为一个“有效”网址是不言而喻的,但情况并非完全如此。例如,在浏览器的地址栏中输入的网址可能会包含特殊字符(如 "上海+中國"
);在传输之前,浏览器需要在内部将这些字符转换为不同的编码。使用相同的令牌,生成或接受 UTF-8 输入的任何代码可能会将使用 UTF-8 字符的网址视为“有效”,但也需要在将其发送给某个网络服务器之前转换这些字符。这一过程称为网址编码。
我们需要转换特殊字符,因为所有网址都需要符合由 W3 统一资源标识符规范指定的语法。实际上,这意味着网址只能包含一个特别的 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
。
转换您从用户输入中接收的网址有时会比较困难。例如,用户可能将地址输入为“5th&Main St.”。一般来讲,您应当使用其各个部分构造您的网址,将任何用户输入视为字面意义上的字符。
此外,对于所有网络服务,网址的字符数上限均为 2048 个。大多数服务很少会达到此字符数限制。但请注意,某些服务的一些参数可能会导致网址过长。
现在,通过 Maps API 专业版应用程序提出的对 Maps API 网络服务的请求,需要使用我们为您提供的用于签名的加密密钥进行数字签名。签名过程就是使用加密算法将网址与签名密钥结合起来,从而创建出唯一的签名。这个唯一的签名可让我们的网络服务执行以下验证:生成使用您的 Google Maps API Premier 客户端 ID 请求的任何网站是否已被授权这样做。此外,每个网址的签名也是唯一的,这样可确保使用您的客户端 ID 的请求无法被修改,从而无需生成一个新的签名。
网址签名既可用于向我们的网络服务验证您的身份,也可精确跟踪您的使用情况。以下网络服务要求提出的所有请求上的网址签名都使用 Maps API 专业版客户端 ID:
使用任何客户端 API(例如 JavaScript Maps API、Maps API(Flash 版)或 Earth API)时,均不要求包含网址签名。
网址签名涉及使用一个您与 Google 共享的机密签名密钥来生成一个签名,您要将其附加到任何 HTTP(或 HTTPS)请求中。请参见下面的我如何获取我的签名密钥?。您使用签名密钥为网址生成此签名的过程中,系统会创建对应于该网址和签名密钥的唯一哈希值。如果网址与用于生成签名的网址有任何不同,服务都会将其视为无效而拒绝它。
注意:试图使用无效签名访问网络服务会导致 HTTP 403
(禁止访问)错误。当您将您的应用程序转变为使用网址签名时,请务必测试您的签名以确保它们能够发起有效的请求。您应当首先测试原始网址是否有效(请参见前面的构建有效网址),以及能否生成正确的签名(请参见下面的生成有效签名)。
您的加密网址签名密钥会随您的客户端 ID 一起发送,且会成为您与 Google 之间的“机密共享密钥”。客户端 ID 将会发送给所有的 Maps API 专业版客户。此签名密钥为您个人所有,且与您的客户端 ID 唯一对应。因此,请安全保管您的签名密钥。。虽然此密钥是用来生成签名的,但也不应在任何请求中传递该密钥、将其存储在任何网站上或发布到任何公共论坛中。“毫无阻碍”地获取此签名密钥的任何人都可能会使用您的身份伪造请求。
注意:此加密签名密钥与(免费提供,通常以“ABQ..”开头)Maps API 密钥(没有 Maps API 专业版许可的开发人员在加载 Maps JavaScript API V2 和 Maps API(Flash 版)时需要使用该密钥)不同,与 Google API 控制台发布的与 Google Places API 配合使用的密钥也不同。
为帮助进行调试,请在签名之前确保您的网址已是有效的。这样,如果您在为网址签名时发现错误,便可以专注于纠正无效的签名。有关详细信息,请参见构建有效网址。
以下步骤概括了为有效网址签名所应遵循的过程。
构造您的网址,确保包含您的 client
和 sensor
参数。请注意,任何非标准字符都需要进行网址编码。
http://maps.googleapis.com/maps/api/staticmap?center=%E4%B8%8A%E6%B5%B7+%E4%B8%AD%E5%9C%8B&client=clientID&sensor=true_or_false
注意:所有 Google 服务都要求使用 UTF-8 字符编码(其中隐式包含了 ASCII)。如果您的应用程序使用其他字符集运行,请确保它们使用 UTF-8 来构造网址,并事先对其进行适当网址编码。
去掉请求的域部分,只留下路径和查询:
/maps/api/staticmap?center=%E4%B8%8A%E6%B5%B7+%E4%B8%AD%E5%9C%8B&client=clientID&sensor=true_or_false
检索您的私钥,它编码在网址的修改后的 Base64 中*,并使用 HMAC-SHA1 算法为上述网址签名。您可能需要将此密钥解码为其原始二进制格式。请注意,在多数加密库中,所获得的签名都是采用二进制格式。
使用网址的修改后的 Base64 对所获得的二进制签名进行编码*,将此签名转换为可在网址中传递的内容。
将此签名放在 signature
参数中附加到网址中:
http://maps.googleapis.com/maps/api/staticmap?center=%E4%B8%8A%E6%B5%B7+%E4%B8%AD%E5%9C%8B&client=clientID&sensor=true_or_false&signature=base64signature
* 注意:网址的修改后的 Base64 会将标准 Base64 的“+
”和“/
”字符分别替换为“-
”和“_
”,这样这些 Base64 签名就不再需要进行网址编码。
以下各部分展示了使用服务器端代码实现网址签名的方式。为简单起见,其中的很多示例使用了硬编码的网址和私钥。网址应始终在服务器端签名,以免将您的加密密钥暴露给用户。
出于测试目的,您可以测试以下网址和私钥以查看它能否生成正确的签名。请注意,此私钥仅用于测试目的,不会获得任何 Google 服务的验证。
URL: http://maps.googleapis.com/maps/api/geocode/json?address=New+York&sensor=false&client=clientID Private Key: vNIXE0xscrmjlyV-12Nj_BvUPaw= URL Portion to Sign: /maps/api/geocode/json?address=New+York&sensor=false&client=clientID Signature: KrU1TzVQM7Ur0i8i7K3huiw3MsA= Full Signed URL: http://maps.googleapis.com/maps/api/geocode/json?address=New+York&sensor=false&client=clientID&signature=KrU1TzVQM7Ur0i8i7K3huiw3MsA=
下面的示例使用标准 Python 库为网址签名并输出适当的网址请求。
#!/usr/bin/python # coding: utf8 import sys import hashlib import urllib import hmac import base64 import urlparse print("") print("URL Signer 1.0") print("") # Convert the URL string to a URL, which we can parse # using the urlparse() function into path and query # Note that this URL should already be URL-encoded url = urlparse.urlparse("YOUR_URL_TO_SIGN") privateKey = "YOUR_PRIVATE_KEY" # We only need to sign the path+query part of the string urlToSign = url.path + "?" + url.query # Decode the private key into its binary format decodedKey = base64.urlsafe_b64decode(privateKey) # Create a signature using the private key and the URL-encoded # string using HMAC SHA1. This signature will be binary. signature = hmac.new(decodedKey, urlToSign, hashlib.sha1) # Encode the binary signature into base64 for use within a URL encodedSignature = base64.urlsafe_b64encode(signature.digest()) originalUrl = url.scheme + "://" + url.netloc + url.path + "?" + url.query print("Full URL: " + originalUrl + "&signature=" + encodedSignature)
从 gmaps-samples 下载此代码
下面的示例使用此公共域 Base64 编码类为网址签名并输出适当网址请求。(在编译下面所示的 Java 代码时,请确保在您的 CLASSPATH
中包含该类。)
public class UrlSigner { // Note: Generally, you should store your private key someplace safe // and read them into your code private static String keyString = "YOUR_PRIVATE_KEY"; // The URL shown in these examples must be already // URL-encoded. In practice, you will likely have code // which assembles your URL from user or web service input // and plugs those values into its parameters. private static String urlString = "YOUR_URL_TO_SIGN"; // This variable stores the binary key, which is computed from the string (Base64) key private static byte[] key; public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException, URISyntaxException { // Convert the string to a URL so we can parse it URL url = new URL(urlString); UrlSigner signer = new UrlSigner(keyString); String request = signer.signRequest(url.getPath(),url.getQuery()); System.out.println("Signed URL :" + url.getProtocol() + "://" + url.getHost() + request); } public UrlSigner(String keyString) throws IOException { // Convert the key from 'web safe' base 64 to binary keyString = keyString.replace('-', '+'); keyString = keyString.replace('_', '/'); System.out.println("Key: " + keyString); this.key = Base64.decode(keyString); } public String signRequest(String path, String query) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, URISyntaxException { // Retrieve the proper URL components to sign String resource = path + '?' + query; // Get an HMAC-SHA1 signing key from the raw key bytes SecretKeySpec sha1Key = new SecretKeySpec(key, "HmacSHA1"); // Get an HMAC-SHA1 Mac instance and initialize it with the HMAC-SHA1 key Mac mac = Mac.getInstance("HmacSHA1"); mac.init(sha1Key); // compute the binary signature for the request byte[] sigBytes = mac.doFinal(resource.getBytes()); // base 64 encode the binary signature String signature = Base64.encodeBytes(sigBytes); // convert the signature to 'web safe' base 64 signature = signature.replace('+', '-'); signature = signature.replace('/', '_'); return resource + "&signature=" + signature; } }
从 gmaps-samples 下载此代码
下面的示例使用默认 System.Security.Cryptography
库为网址请求签名并输出适当的网址请求。请注意,我们需要转换该默认 Base64 编码以实现一个安全的网址版本。
using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; namespace SignUrl { public struct GoogleSignedUrl { public static string Sign(string url, string keyString) { ASCIIEncoding encoding = new ASCIIEncoding(); // converting key to bytes will throw an exception, need to replace '-' and '_' characters first. string usablePrivateKey = keyString.Replace("-", "+").Replace("_", "/"); byte[] privateKeyBytes = Convert.FromBase64String(usablePrivateKey); Uri uri = new Uri(url); byte[] encodedPathAndQueryBytes = encoding.GetBytes(uri.LocalPath + uri.Query); // compute the hash HMACSHA1 algorithm = new HMACSHA1(privateKeyBytes); byte[] hash = algorithm.ComputeHash(encodedPathAndQueryBytes); // convert the bytes to string and make url-safe by replacing '+' and '/' characters string signature = Convert.ToBase64String(hash).Replace("+", "-").Replace("/", "_"); // Add the signature to the existing URI. return uri.Scheme+"://"+uri.Host+uri.LocalPath + uri.Query +"&signature=" + signature; } } class Program { static void Main() { // Note: Generally, you should store your private key someplace safe // and read them into your code const string keyString = "YOUR_PRIVATE_KEY"; // The URL shown here is a static URL which should be already // URL-encoded. In practice, you will likely have code // which assembles your URL from user or web service input // and plugs those values into its parameters. const string urlString = "YOUR_URL_TO_SIGN"; Console.WriteLine(GoogleSignedUrl.Sign(urlString,keyString)); } } }
从 gmaps-samples 下载此代码
要查看涵盖更多语言的示例,请访问 gmaps-samples。
由于无法保证网络服务请求的单个响应的确切格式(某些元素可能会缺失或者位于多个位置),因此您决不能假定不同查询的任何给定响应的传回格式会是相同的。实际上,您应当处理响应并通过表达式选择适当值。本小节将探讨如何从网络服务响应中动态提取这些值。
Google 网络服务提供的响应易于理解,但欠缺用户友好性。在执行查询时,您可能希望提取一些特定值,而不是显示一组数据。一般来讲,您会希望解析来自网络服务的响应并只提取那些您感兴趣的值。
您使用的解析方案取决于您是在 XML 还是在 JSON 中传回输出。JSON 响应(已经是 JavaScript 对象的形式)可在客户端的 JavaScript 中进行处理;XML 响应的处理应使用一个 XML 处理器和一种 XML 查询语言以处理 XML 格式中的元素。我们在下面的示例中使用 XPath,因为它在 XML 处理库中得到了普遍支持。
XML 是一种用于数据交换的比较成熟的结构化信息格式。虽然其轻量程度不如 JSON,但 XML 确实提供了更多语言支持和更多强大的工具。例如,用于在 Java 中处理 XML 的代码就构建在 javax.xml
包中。
处理 XML 响应时,您应当使用一种适当的查询语言在 XML 文档中选择节点,而不是假设元素驻留在 XML 标记内的绝对位置处。XPath 是一种用于唯一描述 XML 文档中的节点和元素的语言语法。XPath 表达式允许您标识 XML 响应文档中的特定内容。
掌握一些 XPath 方面的知识对于开发强大的解析方案会大有帮助。本小节将探讨如何使用 XPath 处理 XML 文档中的元素,从而使您能够处理多个元素并构造复杂查询。
XPath 使用表达式选择 XML 文档中的元素,并使用一种类似目录路径所使用的语法。这些表达式可标识 XML 文档树中的元素,该树是一种与 DOM 树类似的层次结构树。一般来讲,XPath 表达式比较“贪心”,它们会匹配与提供的条件相匹配的所有节点。
我们将使用以下 XML 片段展示我们的示例:
<WebServiceResponse> <status>OK</status> <result> <type>sample</type> <name>Sample XML</formatted_address> <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>
元素。一般来讲,您不必担心上下文,因为您通常是通过单个表达式处理网络服务结果的。这两种表达式都可以通过添加由双斜线(“//
”)表示的通配符路径进行扩展。此通配符表示在居间路径中可能没有或有多个匹配元素。例如,XPath 表达式“//formatted_address
”将匹配当前文档中具有该名称的所有节点。表达式 //viewport//lat
将匹配以 <viewport>
为父元素的所有 <lat>
元素。
默认情况下,XPath 表达式将匹配所有元素。您可以通过提供一个放在方括号 ([]
) 中的“谓词”将表达式限定为匹配特定元素。例如,XPath 表达式“/GeocodeResponse/result[2]
”始终会传回第二个结果。
XPath 表达式 | 表达式类型 | 选择内容 |
---|---|---|
“/ ” |
根节点 | <WebServiceResponse> <status>OK</status> <result> <type>sample</type> <name>Sample XML</formatted_address> <location> <lat>37.4217550</lat> <lng>-122.0846330</lng> </location> </result> <result> <message>The secret message</message> </result> </WebServiceResponse> |
“/WebServiceResponse/result ” |
绝对路径 | <result> <type>sample</type> <name>Sample XML</formatted_address> <location> <lat>37.4217550</lat> <lng>-122.0846330</lng> </location> </result> <result> <message>The secret message</message> </result> |
“/WebServiceResponse//location ” |
带通配符的路径 | <location> <lat>37.4217550</lat> <lng>-122.0846330</lng> </location> |
“/WebServiceResponse/result[2]/message ” |
带谓词的路径 | <message>The secret message</message> |
“/WebServiceResponse/result[1]/* ” |
第一个 result 的所有直接子项 |
<type>sample</type> <name>Sample XML</formatted_address> <location> <lat>37.4217550</lat> <lng>-122.0846330</lng> </location> |
“/WebServiceResponse/result[type/text()='sample']/name ” |
其 type 文本为“sample”的 result 的 name 。 |
Sample XML |
需要注意的是,在选择元素时,您选择的是节点,而不只是这些对象中的文本。一般来讲,您会希望对所有匹配的节点进行迭代并提取文本。您也可以直接匹配文本节点;请参见下面的文本节点。
请注意,XPath 还支持属性节点;但是,所有 Google Maps 网络服务都只提供不带属性的元素,因此无需匹配属性。
XML 文档中的文本是通过一个“文本节点”操作符在 XPath 表达式中指定的。此操作符“text()
”表示从所示节点中提取文本。例如,XPath 表达式“//formatted_address/text()
”将传回 <formatted_address>
元素中的所有文本。
XPath 表达式 | 表达式类型 | 选择内容 |
---|---|---|
“//text() ” |
所有文本节点(包括空格) | sample Sample XML 37.4217550 -122.0846330 The secret message |
“/WebServiceRequest/result[2]/message/text() ” |
文本选择 | The secret message |
“/WebServiceRequest/result[type/text() = 'sample']/name/text() ” |
区分上下文的选择 | Sample XML |
或者,您也可以评估表达式并传回一组节点,然后对该“节点集”进行迭代以从每个节点中提取文本。我们在下面的示例中使用此方法。
有关 XPath 的详细信息,请参见 XPath W3C 规范。
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"); } } }
从 gmaps-samples 下载此代码
与 XML 相比,JSON (JavaScript Object Notation) 具有一个显著优点,就是其响应的轻量性。在 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 = 'http://maps.googleapis.com/maps/api/geocode/json' def geocode(address,sensor, **geo_args): geo_args.update({ 'address': address, 'sensor': sensor }) 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",sensor="false")
Output: [ "San Francisco, CA, USA" ]
从 gmaps-samples 下载此代码