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

来源:developerWorks 中国 作者:Jake Miles
  
在这个分为两部分的系列文章中,您将为房地产经纪公司开发一个应用程序,将所有的公寓列表显示为 Google Maps 中可点击的 Placemark 对象。在第 1 部分中,已经创建了应用程序的前半部分,从用户中收集公寓列表信息,使用 Google Geocoder Web 服务将街道地址转换为它的地理坐标(经度/纬度),并将地理坐标和地址信息存储在数据库中。在第 2 部分中,您将使用这些数据生成 KML 覆盖图文档,并在 Google Maps 和 Google Earth 中显示。首先,在 MySQL 中使用存储过程生成 XML 数据,然后利用 XSLT 和一种叫做 Muenchian 分组的技术,将 XML 数据转化为包含覆盖图信息的 KML 文档 —— 一个 Placemark 对象对应一座公寓大楼。每个 Placemark 对象的弹出气球显示那座楼房中的公寓列表。最后,使用 Google Maps API 在 Web 站点嵌套的 Google Map 中显示 KML 覆盖图。

通过 XSLT 获取用于转换的源 XML 数据

前面已经在数据库中存储了所有的地址信息和对应的经度和纬度坐标。首先,可以用 XML 格式表示这些数据,然后使用 XSLT 样式表生成最终的 KML 数据。有多种解决方案可供选择。在本例中,数据库查询只返回单个表的行,您也许要编写一个 PHP 小函数对 XML 标记中的每一列进行打包。对于更复杂的数据,另外一种解决方案是使用 Erik Wetterberg 提出的针对 MySQL 的巧妙存储过程,可以从 Mysql Forge 下载。它允许直接在 SQL 查询中构造复杂的分级 XML 元素(参阅 参考资料 获得链接)。

使用存储过程从 Mysql 返回 XML

Wetterberg 的解决方案使用 3 种存储过程:xml_tag()、xml_escape() 和 xml_attr()。这 3 种存储过程从 select 语句的其余列构造一棵 XML 树。xml_tag() 包含四个参数:标记名称、标记值(查询中数据库的列)、标记属性和标记的子元素。标记的属性和子元素都是单个字符串,因此,可以使用 concat() 函数连接它们。清单 1 说明如何使用这些存储过程来生成每行的 XML 元素字符串,每个字符串包含每列的一个元素。您也可以很轻松地将子元素转换为行的属性。


清单 1. 在 PHP 中使用存储过程以 XML 元素的形式获取 MySQL 结果
				
function fetchListingsAsXml() {

  $sql = <<<SQL

      select xml_tag('listing', 
             null, 
             null,
             concat(xml_tag('id', id, null, null),
                xml_tag('address', address, null, null),
                xml_tag('apt_no', apt_no, null, null),
                xml_tag('city', city, null, null),
                xml_tag('state', state, null, null),
                xml_tag('zipcode', zipcode, null, null),
                xml_tag('longitude', longitude, null, null),
                xml_tag('latitude', latitude, null, null),
                xml_tag('apt_type', apt_type, null, null),
                xml_tag('rent', rent, null, null),
                xml_tag('notes', xml_escape(notes), null, null)))
      as xml
      from listing;

SQL;

 return fetchMysqlXml('listings', $sql);
}

function fetchMysqlXml($tag, $sql) {

  if (! $result = mysql_query($sql)) {
    die (mysql_error());
  }

  $xml = '';
  while ($row = mysql_fetch_array($result)) {
    $xml .= $row[0];
  }

  return "<$tag>$xml</$tag>";
}

SQL 查询使用存储过程为每行返回数据创建一个 XML 列表元素,同时传递 null 作为列以获取值,因为它的值就是它的子元素。通用,也为参数 attributes 传递 null 值。最后,subelements 参数传入一组子元素标记(每个都由进一步调用 xml_tag() 来生成),其中以一个选定的列作为标记的值,以 null 作为属性和子元素参数的值。

这样,在结果集中生成每行的 XML 标记,但是没有创建顶层元素。为简化顶层元素的创建过程,fetchMysqlXml() 函数接收顶层聚合元素的名称和查询的名称(它可能使用这些存储过程来生成 XML 元素字符串)。然后,在查询结果中,连接这些行中的字符串,以生成包含每行的子元素的聚合 XML 元素。如清单 2 所示。


清单 2. 转换为 XML 的数据库数据
				 
<listings>
<listing>
<id>10</id>
<address>3 Irving Place</address>
<apt_no>3A</apt_no>
<city>New York</city>
<state>NY</state>
<zipcode>10003</zipcode>
<longitude>-73.988639</longitude>
<latitude>40.734091</latitude>
<apt_type>studio</apt_type>
<rent>2800</rent>
<notes/>
</listing>
<listing>
<id>11</id>
<address>123 E.34th St.</address>
<apt_no>2D</apt_no>
<city>New York</city>
<state>NY</state>
<zipcode>10016</zipcode>
<longitude>-73.980182</longitude>
<latitude>40.746595</latitude>
<apt_type>1br</apt_type>
<rent>2300</rent>
<notes>great views, A/C incl.</notes>
</listing>
</listings>





将 XML 公寓列表转换为 KML 覆盖图数据

因为数据库的内容是以 XML 格式表示的,所以可以使用 XSLT 将数据转换为 KML 格式,生成的数据可以在地图上覆盖,或导入 Google Earth 中,这将在 下一节 讨论。

本例中的数据表示的是纽约市曼哈顿区公寓列表的清单,房东可能是个人,也可能是房地产经纪人。预期的效果是在曼哈顿区的地图上放置书签,公寓列表的每处房产对应一个书签。单击书签,将会打开一个包含书签名称和书签描述的气球,书签描述是想要在气球中显示的、有效的 HTML 内容。在本例中,书签弹出的气球会显示该书签对应的楼房中所有的公寓列表。

您的目的是将清单 2 中的源 XML 数据转换为清单 3 所示的 KML 数据。


清单 3. KML 输出示例
				
<?xml version="1.0"?>
<kml xmlns="http://earth.google.com/kml/2.2">
    <Document>
        <name>Jake's Apartment Rentals</name>
        <description>A map of available apartments - click a bookmark to see 
                             	available apartments there.</description>
            <Placemark xmlns="">
                <name>Address 220 E. 22nd St.  (2 listings)</name>
                <description>
            <![CDATA[
                <div>APT 1A: $2250,  new kitchen, H/W floors</div>
                        <div>
                            APT 2S: $2500, loft space, sliding glass doors
                        </div>    
                     ]]>
               </description>
               <Point>
                   <coordinates>-73.982488, 40.737675</coordinates>
               </Point>
           </Placemark>
           <Placemark xmlns="">
               <name>Address 214 E. 73rd St.  (2 listings)</name>
               <description>
            <![CDATA[
                <div>APT 2C: $2000, everything brand new</div>
                        <div>
                          APT 4A: $2400, elevator, A/C incl, gorgeous
                        </div>    
                    ]]>
               </description>
               <Point>
                   <coordinates>-73.959088, 40.769987</coordinates>
               </Point>
           </Placemark>
       </Document>
</kml>

清单 3 是一个包含名称元素、描述元素和 Placemark 元素的 KML 文档,每个 Placemark 元素表示地球上的一个位置。本文档中的每个 Placemark 元素都包含名称、描述和 Point 对象,Point 对象用经度、纬度和海拔(可选)指定 Placemark 元素的 3D 坐标。每个 Placemark 元素在 Google Map 中都表示为一个书签。单击书签,就会打开气球,气球中的名称用粗体表示,描述的内容位于名称下方,如图 1 所示。


图 1. 在 Google Map 中显示的 Placemark
在 Google Map 中显示的 Placemark

注意,气球中的每个列表都自成一行。因为 Google Maps 是按照 HTML 格式逐字地表示描述,其中,每一行要求有 <br/> 标记,或者必须打包在自己的 <div> 元素中。可以在描述中包括任何 HTML 标记,但是,必须将描述字段的内容打包在 CDATA 元素中。

创建 XSLT 样式表

要将源 XML 数据转换为 KML,则必须从清单 4 中的样式表开始。


清单 4. 转换为 KML 的 XSLT 样式表框架
				
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:key name="listings-by-address" match="listing" use="address" />

  <xsl:template match="/">
    <kml xmlns="http://earth.google.com/kml/2.2">
      <Document>

    <name>Jake's Apartment Rentals</name>
    <description>A map of available apartments - click a bookmark to see 
               available apartments there.</description>        

    <xsl:apply-templates select="listings"/>

      </Document>
    </kml>    
  </xsl:template>

</xsl:stylesheet>

<xsl:template> 标记匹配源文档的根节点,并创建包含其名称和描述的 KML 文档框架,然后在根节点的子元素 listings 上调用 <xsl:apply-templates>。

<xsl:key> 标记位于文档的上方,允许根据地址对列表进行分组。因为每个 Placemark 元素都表示一座公寓大楼,其中包括很多公寓,当表示楼房的 Placemark 元素时,需要根据公寓大楼对列表进行分组。因为输入的 XML 数据包含统一的 <listing> 元素列表,所以在样式表中可以根据地址对这些元素进行分类。

在 XSLT 中根据公寓大楼地址对公寓列表进行分类

这里将用到一种称为 Muenchian 分组(参阅 参考资料 获得更多信息)的分组技术。该技术通过 XSLT 键使分组更加方便。XSLT键创建一个索引,通过该索引,可以快速找出一组与单一标准匹配的元素。这个过程创建了一个自然的分组机制,模板可使用它在 KML 中呈现每组的一个 Placemark 元素。

分组行为需要样式表完成两件事:

  • 收集一组 group leader,每个 group leader 表示文档中的惟一的楼房地址,用来创建 Placemark 元素
  • 对于每个惟一的楼房地址,将所有的地址列表传递给模板进行处理。在本例中,即在 Placemark 中呈现每个公寓的一个 <div> 元素

收集惟一地址 (group leaders),开始分组过程

下面的 <xsl:template> 元素匹配 listings 标记。现在开始该分组行为,如清单 5 所示。


清单 5. listings 元素的 <xsl:template>
				
<xsl:template match="listings">
    <xsl:apply-templates 
       select="listing[count(. | key('listings-by-address', address)[1]) = 1]" 
       mode="first-in-group"/>      
</xsl:template> 

该模板的内容容可以直接用于第一个模板,但如果将其分开,代码就很容易理解。该模板调用 <xsl:apply-templates>,并将文档中惟一楼房地址的列表 —group leader 传递给它。选取这些 group leader 元素需要稍微复杂的技巧。Xpath 表达式 listing[count(. | key('listings-by-address', address)[1]) = 1] 会匹配符合括号内的条件的所有列表元素。表达式 key('listings-by-address', address) 访问在样式表上方创建的键。该 Xpath 表达式测试文档中的每个列表元素,使用当前测试的列表的地址调用键。键返回文档中具有该地址值的所有列表。[1] 表示选取这些匹配元素中的第一个元素。管道符 (︱) 创建第一个匹配的元素与当前测试元素(句点 (.) 表示当前元素)的并集。因为并集只包括惟一元素(不允许重复),所以并集的结果将包括一个或两个元素 — 如果当前测试的元素是包含地址的文档中的第一个元素(它所在分组的首元素),那么结果中包括一个元素。如果当前测试的元素不是第一个元素(不是首元素),那么结果中包括两个元素。因此,用来测试并集结果的 = 1 表达式只包括一个元素,这意味着当前测试的元素是包含地址的文档中的第一个元素。最后,Xpath 表达式收集文档中每个惟一的楼房地址列表,并以 first-in-group 的模式将其传递给 listing 模板。

组级别 XSLT 模板(创建 KML Placemark)

如果想要使用相同的标准对不同的环境中元素进行匹配,您可以为 <xsl:template> 指定不同的模式,这相当于定义两个函数,它们接收相同的参数,但是对参数采用不同的处理方式。在本例中,首先创建一组 Placemark 元素,然后处理组中的每个列表,它们都与 vanilla 列表元素匹配。创建两个匹配列表元素的 <xsl:templates>。一个将为 group leader(在最后一个模板中选取)处理组级别处理过程,另外一个将处理组中每个列表的处理过程。清单 6 显示第一个模板。


清单 6. 创建组级别 Placemark 元素的 <xsl:template>
				
<xsl:template match="listing" mode="first-in-group">
    <Placemark>
      <name>
    <xsl:value-of select="address"/> 
    (<xsl:value-of select="count(key('listings-by-address', address))"/> listings)
      </name>
      <description>
    CDATA-START
    <xsl:apply-templates 
       select="key('listings-by-address', address)"
       mode="listing-within-group"/>    
            CDATA-END
      </description>
      <Point>
    <coordinates>
      <xsl:value-of select="longitude"/>,
      <xsl:value-of select="latitude"/>
    </coordinates>
      </Point>
    </Placemark>
  </xsl:template>

同样,只有模式指定为 first-in-group 时,该模板才匹配列表元素,就像从前一个收集 group leaders 的模板中调用一样(在 清单 5 中)。该模板为楼房创建 Placemark 元素,这些楼房对应的书签将出现在地图上。Placemark 元素的名称是楼房的地址加上圆括号内的那座楼房中的列表号。为获得楼房的列表数量,需要再次用地址键对列表中当前地址的公寓列表计数。

<description> 元素是 Placemark 的一部分,在地图上单击 Placemark 时,它会出现在弹出的气球中,位于名称的下面。这里包括两个动作。首先,样式表将 CDATA-START 和 CDATA-END 两个标记放在结果 XML 文档中。因为结果文档是为 Google Maps 提供的 KML,KML 将接收描述标记的 HTML 内容,并将其逐字地放在 Placemark 元素弹出的气球中,然后将其作为文本(而不是 XML)处理。因此,需要在 CDATA 结构体中打包 <description> 元素的内容。但是,不能把元素逐字放在样式表中,否则 XSLT 处理器会忽略它的内容。本文的解决方案是用文本标记代替 CDATA 的开始和结束标记,在调用的 PHP 代码中,使用正则表达式代替 XSLT 转换结果中有效的 CDATA 开始和结束标记,如清单 7 所示。

处理单个列表

对描述元素来说,第二件事情是调用 <xsl:apply-templates>。这个行为选取当前 (group leader) 列表中的所有地址清单,其中包括 group leader 本身,然后将它们传递给模板,该模板以 listing-within-group 模式(参阅清单 7)匹配清单元素。在描述元素之后,模板创建 point 元素,指定地图上 Placemark 对象的经度和纬度。如果需要的话,也可以指定海拔。


清单 7. 在 Placemark 的描述中,<xsl:template> 创建每个列表的 <div> 元素
				
<xsl:template match="listing" mode="listing-within-group">
    <div>
      APT <xsl:value-of select="apt_no"/>: 
      ___FCKpd___6lt;xsl:value-of select="rent"/>, 
      <xsl:value-of select="notes"/>
    </div>
</xsl:template>

样式表的最后一个模板以 listing-within-group 模式对列表进行匹配,从而为公寓号、租金和有关公寓的各种记录的列表创建 <div> 元素。其中,可加入任意的 HTML 标记,包括 CSS 样式属性。

用 PHP 对 XML 数据应用样式表

最后一步实际上是对 XML 数据应用样式表。为提供 XSLT 转换能力,PHP 5 提供构建在 libxslt 上的 XSL 模块,它是一种 XSL 规范(参阅 参考资料 获得链接)的实现。XSL 模块可实现对 XML 文档应用 XSL 文档,如清单 8 所示。


清单 8. 用 PHP 生成 KML 数据
				
function doXslt($xml, $xsl) {

  $xmlDOM = DOMDocument::loadXML($xml);
  $xslDOM = DOMDocument::loadXML($xsl);
  
  $processor = new XSLTProcessor();  
  $processor->importStyleSheet($xslDOM);

  return $processor->transformToXML($xmlDOM);
}

$xml = fetchListingsAsXml();

$xsl = file_get_contents ('listings-xml-to-kml.xsl');

$kml = doXslt($xml, $xsl);

$kml = ereg_replace('CDATA-START', '<![CDATA[', $kml);
$kml = ereg_replace('CDATA-END', ']]>', $kml);

header("content-type: application/xml");
echo $kml;

XSLTProcessor() 函数要求源文档和样式表是一个 DOMDocument 对象,而不是一个字符串。在这里,doXslt() 根据提供的 $xml 和 $xsl 字符串创建 DOMDocuments 对象,并创建一个 XSLTProcessor 对象,然后将提供的样式表导入处理器,根据样式表转换提供的 XML 文档。使用 ereg_replace() 函数代替有效的 CDATA 标记,即样式表中引入的 CDATA-START 和 CDATA-END 标记。有了 xml 格式的内容类型之后,Google Maps 的 KML feed 就准备好了。





在 Google Maps 中查看 KML 覆盖图

为了查看覆盖在 Google Map 中的 KML 数据,将浏览器指向 http://maps.google.com/maps?q=http://YOUR-SERVER/listings-as-kml.php?v=2,YOUR-SERVER 是运行 PHP 脚本的服务器的 URL。Google 缓存了 KML 文件,以避免每次查看 KML 数据时都要访问服务器。为执行更新操作,需要指定一个每次都变化的查询参数,例如 v=2,该参数每次都指定一个不同的数字,使 Google 重新读取 KML 数据。

该 URL 将产生一个 Google Maps 页面,该页面可缩放至地图上的所有 Placemark 对象都合适的大小。如图 2 所示。


图 2. 显示 KML 数据的 Google Maps 页面
显示 KML 数据的 Google Maps 页面

在地图的左边部分,图 3 显示所有 Placemark 对象的列表,列表的上方是 KML 文档的 <name> 和 <description>。


图 3. Google Map 左部分的 Placemark 对象列表
Google Map 左部分的 Placemark 对象列表

单击地图上的一个 Placemark 对象,这将显示一个弹出气球,如图 4 所示。


图 4. Placemark 对象的弹出气球
Placemark 对象的弹出气球,显示对象的名称和描述

使用 Google Maps API 显示 KML 覆盖图

您也可以使用 Google Maps API(JavaScript)将 KML 数据加载到地图上,如清单 9 所示。


清单 9. 使用 Google Maps API 覆盖 KML 数据
				
function load() {
      if (GBrowserIsCompatible()) {

        var map = new GMap2(document.getElementById("map"));

        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());

        map.setCenter(new GLatLng(40.743884, -73.974666), 13);

        var kmlUrl = "http://YOUR-SERVER/listings-as-kml.php?v=" 
+ Math.round(Math.random() * 10000000000);

        var kml = new GGeoXml(kmlUrl); 
        map.addOverlay(kml);
      }
    }

在这里,JavaScript 创建 KML feed URL,并追加一个大的随机数(大多数情况)以保证 Google 读取最新的 KML 数据。GGeoXml 对象加载 KML 数据,并提供一些实用函数,然后调用 addOverlay() 以显示页面中地图上的 KML 数据,如图 5 所示。


图 5. 使用 Google Maps API 覆盖 KML 数据
使用 Google Maps API 覆盖 KML 数据




在 Google Earth 中查看 KML 覆盖图

您也可以在 Google Earth 中查看 KML 数据。在本地计算机(要以 .kml 为扩展名,否则在 Google Earth 中无法打开)中将 KML 数据存为一个文件。首先,下载并安装 Google Earth(参见 参考资料),然后通过 File 菜单打开 KML 文件,如图 6 所示。


图 6. 加载到 Google Earth 中的 KML 覆盖图
加载到 Google Earth 中的 KML 覆盖图

在本图中,黄色图钉表示 Placemark 对象。单击其中的一个将显示与 Google Maps 的弹出气球相同的 HTML 内容。





结束语

在这个分为两部分的系列文章中,通过从 PHP 调用 Google Geocoder Web 服务,将街道地址转换成地理坐标并存储在数据库中。然后,使用存储过程从 MySQL 生成 XML 数据。最后使用 XSLT 样式表将 XML 数据转换为 KML 覆盖图,这样就可以在 Google Maps 和 Google Earth 中查看该图。

这些知识的范围是十分广阔的。这两篇文章只涉及到一些皮毛,特别是您还可以用 KML 创建 3D 多线图和多角图,而不仅仅是显示文本信息的 Placemark。您可以利用 Google Maps、 Google Earth 和 Google Geocoder 在大部分 Web 站点上处理地址信息,并且还可以使用 XSLT 将包含坐标信息的 XML 数据转换为激动人心的 KML 覆盖图。(责任编辑:A6)


时间:2008-11-20 17:17 来源:developerWorks 中国 作者:Jake Miles 原文链接

好文,顶一下
(1)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量