将用户组列表和Google Maps组合在一起的Mashup程序进入希望BuilderAU进行讨论的话题列表已经有很长时间了。上周我参加了John Allsopp在Web Directions会议上所作的关于Microformat的演讲,这真是一场及时雨。我们将从讨论Microformat是什么开始,看看它为什么会很有用,然后进一步介绍Google Maps API,并最后把所有的东西放在一起生成一个您可以在用户组页面上看到的用户组地图。
Microformat是标记HTML的一种方式,它通过使用类或rel属性,或者甚至是某些元素给予HTML一种语义结构。它的好处是能够更容易地自动分析内容和在不相关的网站之间进行共享。人们可以总是创建自己的Microformat,但是这样做的好处总是源于使用预先定义的Microformat标准,如果有的话。
一个标准的Microformat是hCard——HTML版的vCard。下面是vCard RFC 2426的一个例子:
BEGIN:VCARD
FN:Joe Friday
TEL:+1-919-555-7878
TITLE:Area Administrator, Assistant
EMAIL;TYPE=INTERNET:
jfriday@host.com
END:VCARD
下面是对应的hCard:
<div class="agent vcard">
<a class="email fn" href="mailto:jfriday@host.com">Joe Friday</a>
<div class="tel">+1-919-555-7878</div>
<div class="title">Area Administrator, Assistant</div>
</div>
大多数地方的名字是一看就懂的,唯一一个需要解释的地方是fn,也就是全名。需要注意的一件事是字段的名字没有发生改变,它们只是被简单地移到了类里面。所以如果您能够读懂vCard那么不需要费什么功夫就可以看懂hCard了。只要有可能,Microformat都应该使用现有的标准。这样做能够降低学习的难度,有助于信息的重复使用和交换。
关于Microformat及其用法的更加详细的介绍,请参看microformats.org。
使用用户组
原有的用户组非常简单,它只有两个定义列表,每个条目都链接到一个小组的Web页面。为了能够把它们放在地图上,我们需要有每个用户组的地址和一个用来显示它们的Microformat。使用hCard就是完美的方式,每个条目都采取下面的格式:
<li class="vcard"><a href="[url]" class="url fn">[Group Name]</a>
<span class="org">[Group Organsation - if any]</span>
<address class="adr">
<span class="street-address">[Street Address]</span><br />
<span class="locality">[Suburb]</span> <acronym class="region" title="[State Full Name]">[State Abbreviation]</acronym> <span class="postal-code">[postcode]</span> <span class="country-name">[country]</span>
<address>
</li>
现在我们需要用户组的地址;在花费了数小时设计和输入之后,我们把所有的地址都输入到了页面里。
这些地址还完全没有输入到数据库里,因为当前使用的用户组页面是全静态页面,所有地址是被直接输入到静态页面里的。
进入Google Maps
既然我们已经有了一个带有地址的静态页面,下一个问题就是取回这些数据并把它传递给Google Maps API了——我们还必须创建一个scraper。幸运的是PHP 5通过DOMDocument类让开发这个程序变得非常轻松和迅速。
我们的列表文件用loadHTMLFile方法被加载,然后真正有趣的内容开始了。由于我们唯一感兴趣的元素都在列表项目里,而且从上面使用的格式来看,我们只需要取回li元素就可以了。我们在地图上显示的信息将是用户组的名字和地址,所以任何一个列表项目的子项目如果有一个“url fn”类值,那么我们就知道在该元素的节点值里有这个名字。之后我们需要取回地址,这很容易就通过地址元素就办到了,所以我们只需要地址的nodeName就知道我们已经有了。
一旦进入了地址元素,我们就在里面循环,把地址的各个值保存在一个数组里,我们将用这个数组来请求Google Maps API以csv格式返回该地址的经度和纬度。
这个返回的csv文件的结构如下:
[status code], [accuracy], [latitude], [longitude]
所以我们可以轻易地利用一个带有逗号分隔符的csv文件取回所有的数据。
如果我们得到一个OK (200)相应代码,那么这个地址就被整理为便于人们阅读的格式,并保存在一个带有用户组前缀的结果数组里。这个结果数组然后被串行化和编码,供今后全局使用。
正如您可能预计到的,为页面上的每个地址向Google发送一个请求极其耗时,所以我们把取回结果的文件(在本文里是Scraper)与真正在Google Map上显示结果的页面分开了。Scraper的结果被我们用内容管理系统的缓冲函数进行缓冲以减少延迟和加速地图的显示,但是您可以把这保存到一个文件里并获得相同的结果。
下面就是我们的Scraper代码:
$doc = new DOMDocument();
$doc->loadHTMLFile("userlist.htm");
$linodes = $doc->getElementsByTagName("li");
$resultsarr = array();
for ($i = 0; $i < $linodes->length; $i++) {
$details = $linodes->item($i)->childNodes;
foreach($details as $det){
if($det->nodeName == "address"){ //need to fetch children
$addarr = array();
foreach($det->childNodes as $addrspan){
foreach($addrspan->attributes as $attrs){
$addarr[$attrs->value] = $addrspan->nodeValue;
}
}
$exp = explode(",",file_get_contents("http://maps.google.com/maps/geo?output=csv&key=ABCD1234&q=".urlencode($addarr["street-address"].",".$addarr["locality"].",".$addarr["region"].",".$addarr["postal-code"].",".$addarr["country-name"])));
if($exp[0]=="200"){
$addy = (strlen($addarr["street-address"])? $addarr["street-address"].", ":"").(strlen($addarr["locality"])?$addarr["locality"].", ":"").(strlen($addarr["region"])?$addarr["region"].", ":"").(strlen($addarr["postal-code"])?$addarr["postal-code"].", ":"").(strlen($addarr["country-name"])?$addarr["country-name"]:"");
$resultsarr[$exp[2]][$exp[3]] = "".$fn."
".(count($resultsarr[$exp[2]][$exp[3]]) ? "":$addy).$resultsarr[$exp[2]][$exp[3]];
}
}
foreach($det->attributes as $attr){
if($attr->value == "url fn"){$fn = $det->nodeValue;}
}
}
}
$GLOBALS['usergroup_array'] = base64_encode(serialize($resultsarr));