内容


使用 XSLT、KML 和 Google Maps API 在地图上覆盖数据,第 1 部分

利用 Google Geocoder Web 服务

使用 Google Geocoder 查找邮政编码和坐标并将其存储在数据库中

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用 XSLT、KML 和 Google Maps API 在地图上覆盖数据,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用 XSLT、KML 和 Google Maps API 在地图上覆盖数据,第 1 部分

敬请期待该系列的后续内容。

Google Maps、Google Earth 和 Geocoder

Google Maps 已经成为网络上随处可见的地图技术,它使用户可以快速生成地图、进行拖动和缩放,以及对视线范围内的街道以 360 度视角进行浏览。Google Earth 提供了一本详细的地球 3D 百科全书,允许以各种高度对地球图像进行拖动和缩放。使用 Google MAPS API 可以在自己的 Web 页面中嵌入 Google Maps。KML 是一种用来描述地理信息(例如地标)的 XML 语言,它可以将可视文本数据覆盖到地图上。您也可以在 Google Earth 中导入 KML 数据,根据用户的拖动和缩放在 Earth 中显示自己的 3D 数据。

例如,Nine Inch Nails 乐团通过下载发行他们的最新专辑 “The Slip”,并发布使用 Google Earth 和 KML 生成的地理下载信息,如图 1 所示。

图 1. Nine Inch Nails 的专辑 “The Slip” 的下载信息,用 Google Earth 和 KML 显示
Nine Inch Nails 的专辑 “The Slip” 的下载信息,用 Google Earth 和 KML 显示
Nine Inch Nails 的专辑 “The Slip” 的下载信息,用 Google Earth 和 KML 显示

这个概念暗示可以使用 Google 的 API 和 KML。在 KML 中的下载点的经度和纬度上创建一条线,线条的高度表示该位置的下载记录数量,这些线条的高度是海拔 0 到与该位置的下载数量成比例的海拔高度之间的距离。

该可视化中缺少的一个关键细节是将地球上客户地址(或至少是邮政编码)转换为地理坐标的能力,因为所有自定义的 KML 数据都使用经度、纬度和海拔坐标在地球上定位。

为解决这一问题,Google 近期提出了 Google Geocoder Web 服务,它接收街道地址,返回以任意精度表示的地址的 KML 数据,包括经度和纬度在内。只要得到这些坐标,您就能创造性地发挥想象力,在 2D 地图和 3D 球体上覆盖文本和可视数据。

Google Maps API 和 Geocoder 服务入门

要使用 Google Maps API 和 Geocoder 的 Geocoder Web 服务,必须首先注册一个 Google 地图 API 键(参见 参考资料 获得链接),指定发送 API 请求(可以注册任意多个键或 URL)的 Web 站点的 URL。结果页面包含 API 键和一个起始 HTML 页面,页面中包含显示 Google 地图的必要 JavaScript。Google 地图的核心是 JavaScript 的 load() 函数(参见清单 1),页面载入时调用该函数。

清单 1. 在页面中显示 Google 地图的 JavaScript 函数
function load() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(37.4419, -122.1419), 13);
      }
    }

该函数确保浏览器能够显示 Google 地图,创建一个地图对象(Gmap2),并在页面中为其提供 HTML 元素作为地图的容器(“map”)。然后使用 GLatLng (经度/纬度) 参数对象将地球上的一个坐标设置为它的中心,并将缩放深度(高度)指定为 13。图 2 显示了生成的地图。

图 2. 在起始页面中显示的 Google 地图
在起始页面中显示的 Google 地图
在起始页面中显示的 Google 地图

用 KML 提供覆盖图数据

可以使用 JavaScript Google MAPS API 覆盖自定义数据,例如在某位置创建几个书签,或覆盖一些带颜色的多边形和线段。然而,对于更复杂的数据(确切地说,包含更多数据点的数据)则需要使用 KML 文档。KML 文档可以确定地址、地球上的 3D 坐标,以及地图上已覆盖的自定义文本和可视数据,或地球上的 3D 模型等地理信息。

要为 Google Maps 提供 KML 数据,必须使用 Google 服务器有权访问的 URL 获取 KML,所以必须提供服务器端的 KML。在本系列的文章中,可以使用服务器端的 XSLT,将数据库信息转换为可提交给 Google Maps 和 Google Earth 的 KML 文件,从而显示自定义数据。

在数据库中存储 Geocoder 数据

在 Google MAPS 和 Google Earth 中,地址的经度和纬度是很有用的。但是,要获得这些数据,必须调用 Google 的 Geocoder Web 服务。这需要一些时间,所以,在数据库中存储地址时,必须同时存储地址的经度和纬度。采用该方法,您可以查询数据库,直接生成 XML,并使用 XSLT 将返回的数据直接转换为可以在地图上覆盖的 KML。

为实现这些功能,可以使用两种方法调用 Geocoder 服务:

  • 在数据库中记录信息之前调用 Geocoder 服务器端
  • 在提交含有用户地址信息的表格之前,使用 Google 提供的 JavaScript 库调用 Geocoder

从 PHP 调用 Geocoder

首先,从 PHP 调用 Geocoder,并使用 SimpleXML 模块遍历结果。recordListing() 函数接收记录房地产经纪公司的公寓列表的请求参数,调用 Geocoder 服务,并利用结果来获得邮编和地址的地理坐标,然后在数据库中记录所有的信息(参见清单 2)。

清单 2. recordListing()函数 (PHP)
function recordListing($address, $aptNumber, $city, $state, 
               $aptType, $rent, $notes) {

  $geocoder = new GoogleGeocoder(GOOGLE_MAPS_API_KEY);

  $result = $geocoder->fetchAddress($address, $city, $state);

  // use the geocoder to make sure the address is accurate enough to use
  if ($result->getAccuracy() < GoogleGeocoderAccuracies::ADDRESS) {
    throw new Exception ("Address does not have enough accuracy to record the listing.");
  }
  
  // store in the database
  createListingInDb ($address, $aptNumber, $city, $state, 
             $result->getZipcode(),
             $result->getLongitude(), 
             $result->getLatitude(), 
             $aptType, $rent, $notes);
}

清单 2 中的代码演示了 Geocoder 结果信息的另一种使用方法 — 数据完整性检查。Geocoder 的结果包括 Accuracy 度,测量输入的规范性。这里,recordListing() 使用该精度进行度量 (在下面的 清单 4 中,将讨论更详细的信息) 以确保信息符合规范,可以存储到数据库。

GoogleGeocoder

为确保代码重用,在 GoogleGeocoder 类中封装对 Google Geocoder 的调用(参阅清单 3)。

清单 3. GoogleGeocoder 类 (PHP)
class GoogleGeocoder {

  private $apiKey;  
  private $googleGeocoderUrl = 'http://maps.google.com/maps/geo?';  

  public function __construct ($apiKey) {
    $this->apiKey = $apiKey;
  }

 public function fetchAddress ($address, $city, $state) {

    // encode address for google api call (plusses, commas)
    $fullAddress = $this->encodeAddress ($address, $city, $state);

    //  create the geocoder API call
    $geocoderCall = 
       $this->googleGeocoderUrl . 
       "q=$fullAddress" . 
       "&key=$this->apiKey" .
       "&output=xml";

    $result = file_get_contents ($geocoderCall);    

    return new GoogleGeocoderResult($result);
  }

  private function encodeAddress($address, $city, $state) {
    return urlencode (join (", ", array ($address, $city, $state)));  
  }
}

GoogleGeocoder 类根据给定的街道地址,通过 HTTP 来调用 geocoder 服务。在它的构造函数中,接收 Google MAPS API 键用于 HTTP 调用。FetchAddress() 首先对地址进行正确的编码以用于调用,然后构造调用的 URL,将地址作为 q 参数,将 Google MAPS API 键作为 key 参数,并指定 output=xml。其余的输出选项是 jsoncsv,如果只需要地址的经度和纬度,后者将返回简化的、用逗号分隔的响应信息。最后,函数使用 file_get_contents() 调用 URL,如果提供的字符串是以一个受支持的协议(比如 HTTP)开头的话,它将像读取文件一样读取 URL。

GoogleGeocoder 服务的响应 KML

Google Geocoder 服务将返回描述地址的 KML。例如,对于地址 “纽约州纽约市 34 大街 123 号”,调用将返回清单 4 中的 XML。

清单 4. 地址 “纽约州纽约市 34 大街 123 号” 的 Geocoder 响应 XML
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
    <Response>
        <name>123 E. 34th St., New York, NY</name>
        <Status>
            <code>200</code>
            <request>geocode</request>
        </Status>
        <Placemark id="p1">
            <address>123 E 34th St, New York, NY 10016, USA</address>
            <AddressDetails Accuracy="8" 
xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0">
                <Country>
                    <CountryNameCode>US</CountryNameCode>
                    <AdministrativeArea>
                        <AdministrativeAreaName>NY</AdministrativeAreaName>
                        <SubAdministrativeArea>
                            <SubAdministrativeAreaName>Manhattan<
/SubAdministrativeAreaName>
                            <Locality>
                                <LocalityName>New York</LocalityName>
                                <Thoroughfare>
                                    <ThoroughfareName>123 E 34th St<
/ThoroughfareName>
                                </Thoroughfare>
                                <PostalCode>
                                    <PostalCodeNumber>10016<
/PostalCodeNumber>
                                 </PostalCode>
                             </Locality>
                         </SubAdministrativeArea>
                     </AdministrativeArea>
                 </Country>
             </AddressDetails>
             <Point>
                 <coordinates>-73.980182,40.746595,0</coordinates>
              </Point>
          </Placemark>
      </Response>
</kml>

响应包含一个 Status,报告成功(代码 200)或错误代码之一 (参见 参考资料 获得 Geocoder 服务页面的错误代码表链接)。Placemark 元素表示调用中指定的地址,并且根据 XAL 提供街道地址的结构化表示。XAL 是一种可以表示世界各地的地址(这是为什么结构看起来如此复杂 —— 它必须处理不同国家的多个不同地址结构)的可扩展地址语言。

AddressDetails 元素也包含一个 Accuracy 属性,表示 XML 如何详细地描述地球上的位置。例如,如果只指定 “纽约州纽约市”,就会得到一个较低的 Accuracy 度,因为 Geocoder 只获得很少的信息,所以不能提供地址的更具体的信息(如邮编)。在这种情况下,它最多只能提供纽约州某个地方的经度和纬度,而不能提供更具体的信息。Accuracy 不是结果的信任水平的等级或度,而是输入的规范性的度量标准。

为使该 XML 响应更容易使用,GoogleGeocoder 最后创建一个 GoogleGeocoderResult 对象来封装 XML 遍历,如清单 5 所示。

清单 5. GoogleGeocoderResult 类 (PHP)
class GoogleGeocoderResult {

  public function __construct ($xml) {
    
    $this->xml = new SimpleXMLElement($xml);

    $this->resultCode = intval($this->xml->Response->Status->code); 

    if ($this->resultCode != 200) {
     throw new GoogleGeocoderException ($this->getResultCode());
    }

    $this->placemark = $this->xml->Response->Placemark[0];

    $this->accuracy = intval($this->placemark->AddressDetails['Accuracy']);

    $coordinates = $this->placemark->Point->coordinates;

    $coordinatesSplit = explode(",", $coordinates);
    $this->longitude = $coordinatesSplit[0];
    $this->latitude = $coordinatesSplit[1];
    $this->altitude = $coordinatesSplit[2];
  }

  public function getZipcode() {

    if ($this->accuracy < GoogleGeocoderAccuracies::POST_CODE) {
      throw new Exception ("Address does not have enough accuracy for zipcode.");
    }  

    return $this->placemark->AddressDetails->Country->AdministrativeArea->
      SubAdministrativeArea->Locality->PostalCode->PostalCodeNumber;
  }

GoogleGeocoderResult 使用 PHP 的 SimpleXML 模块使访问 XML 结构变得很容易。SimpleXML 将一个 XML 文档中的元素树映射为一个 PHP 对象树,用属性表示子元素。SimpleXMLElement 接收一个 XML 字符串,然后将 XML 子元素转换为 SimpleXMLElement 对象的 PHP 属性。如果该 XML 元素包含属性,也可以将 SimpleXMLElement 作为一个 PHP 关联数组进行访问,数组中的每个键表示 XML 元素的属性。

首先,GoogleGeocoderResult 构造函数从 SimpleXMLElement 获得响应代码:

$this->resultCode = intval($this->xml->Response->Status->code);

在这里,$this->xml 部分是基于 XML 文档创建的 SimpleXMLElement 。当访问它的响应属性时,返回表示 KML 响应中的 Response 元素的 SimpleXMLElement 对象(顶层 SimpleXMLElement 表示 XML 树中的顶层 KML 元素)。如果一个元素包含多个相同名称的子元素,这些子元素将转换为一个数组属性。例如,假设 Geocoder 响应返回 State 元素中的多个代码,要使用 $this->xml->Response->Status->code[0] 访问第一个元素。SimpleXMLElement 也可以显式地转换为 PHP 字符串,因此,可以直接将代码元素传递给 intval() 中,以将其转换为数字,而不是调用一种方法来获得它的文本值。

当得到响应代码后,如果不是 200,该构造函数抛出 GoogleGeocoderException(参见 示例代码),它将 Geocoder 错误代码映射到相应的错误消息(可以直接从 Google 站点的参考表中复制)。

然后,构造函数使用 Placemark 元素(作为一个 SimpleXMLElement 对象)表示地球上详细的位置。您想要的其他信息全部来自它的子元素。请注意,这里的 Placemark 作为一个数组访问。对于一个给定的地址,Geocoder 的响应可能会包含多个 Placemarks,但 SimpleXML 允许作为一个数组访问任何元素,因为它不能区别 XML 是将元素作为一个数组还是一个单独的对象。

接下来,它获得响应的 Accuracy,以及 AddressDetails 元素的 Accuracy 属性值:

$this->accuracy = intval($this->placemark->AddressDetails['Accuracy']);

SimpleXMLElement 使可用的属性值作为 SimpleXMLElement 自身的关联数组属性 —SimpleXMLElement 实际上同时是一个字符串,一个关联数组,和一个 SimpleXMLElement 对象,当遍历 XML 树时,允许使用高级的语法。随后,构造函数获得位置坐标的经度和纬度,结果显示为 <coordinates>73.980182,40.746595,0</coordinates>— 经度、纬度和海拔(当查找地址时,海拔总是 0 )。

为了获得邮编,getZipcode() 首先使用 GoogleGeocoderAccuracies 类确保响应是正确的,该类包含 Google 参考文档中的每个 Accuracy 值的常量 (参阅 参考资料)。

在 JavaScript 中调用 Geocoder

除了使用 PHP 调用 Geocoder 外,还可以使用 Google MAPS API 库提供的 GClientGeocoder JavaScript 调用它。然后,在将其提交到服务器之前,用响应信息设置表单中的隐藏输入。清单 6 演示了这个过程。

清单 6. 用 JavaScript 调用 Geocoder
function onSubmitAddressForm() {

    var addressInput = document.getElementById('addressInput');
    var cityInput = document.getElementById('cityInput');
    var stateInput = document.getElementById('stateInput');

    var fullAddress = addressInput.value + ", " + cityInput.value + ", " 
+ stateInput.value;

    var geocoder = new GClientGeocoder();

    geocoder.getLocations(fullAddress, submitAddressFromGeocoderResponse);
}

add-address.php(参见 下载)中的 HTML 表单包含要提交的地址、城市和州的文本输入,以及一个像 <button onclick="onSubmitAddressForm()">Submit</button> 这样的按钮,这个按钮调用该函数处理表单提交。onSubmitAddressForm() 函数从表单元素中获得房屋地址、城市和州,根据它们创建完整的地址字符串和创建一个新的 GClientGeocoder 对象,并调用它的 getLocations() 方法,然后传入要查找的完整地址以及要调用的带有响应对象的 callback 函数(参见清单 7)。

清单 7. 在 JavaScript 中使用 Geocoder callback 响应
function submitAddressFromGeocoderResponse(response) {

    if (! response || response.Status.code != 200) {
    alert("Geocoder did not recognize address.  Code = " + response.Status.code);
    } else {

    var coordinates = response.Placemark[0].Point.coordinates;

    var longitude = coordinates[0];
    var latitude = coordinates[1];

    document.getElementById('longitudeInput').value = longitude;
    document.getElementById('latitudeInput').value = latitude;
    document.getElementById('addressForm').submit();
    }
}

GClientGeocoder 接收到 Geocoder 服务的响应后,会以 JavaScript 对象符号(JSON)的格式(参见 参考资料 获得更多信息)调用 submitAddressFromGeocoderResponse 函数。JSON 是日益流行的从 JavaScript 中获取体系结构数据的 XML 替代方法,因为它为 JavaScript 对象树提供了一种简明的、人类可读的、明确的表示法,后者与前面所示的对应的 XML 响应数据的结构相同。在 Google 的参考文档中(参见 参考资料),可以看到 JSON 响应结构的示例。请注意,Placemark 元素实际上是一个 Placemarks 数组,可能有多个 Placemark 对应一个地址。

SubmitAddressFromGeocoderResponse 函数与 PHP 代码做法一样检查响应的状态代码。与使用 SimpleXML 一样,SubmitAddressFromGeocoderResponse 函数也使用相同的对象结构检索坐标值。然后它在页面的表单中设置隐藏输入值,接下来以编程的方式将表单提交到数据库存储起来。

结束语

现在,已经完成了应用程序的第一部分。调用了 Geocoder 服务,并在数据库中存储坐标信息。在本系列的第 2 部分中,将使用存储过程从 MySQL 查询中产生 XML 数据,并使用 XSLT 将该数据转换为 KML 覆盖数据,然后,Google MAPS API 会在 Web 站点中嵌套的地图上显示 KML。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Web development
ArticleID=352222
ArticleTitle=使用 XSLT、KML 和 Google Maps API 在地图上覆盖数据,第 1 部分: 利用 Google Geocoder Web 服务
publish-date=11172008