XQuery/National Grid 和 Google 地图
外观
< XQuery
在英国,用于交换时间表信息的 XML 标准是 TransXchange。例如,公交车站的位置以英国国家网格上的南北向和东西向表示。为了在 Google 地图上绘制这些位置,需要使用 WGS84 基准将这些坐标转换为经纬度。
以下是一个典型时间表文档开头部分的摘录,其中显示了一个单独的 StopPoint 定义
<TransXChange xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apd="http://www.govtalk.gov.uk/people/AddressAndPersonalDetails" xmlns="http://www.transxchange.org.uk/" xsi:SchemaLocation="http://www.transxchange.org.uk/ TransXChange_general.xsd" CreationDateTime="2006-12-07T14:47:00-00:00" ModificationDateTime="2006-12-07T14:47:00-00:00" Modification="new" RevisionNumber="0" FileName="SVRSGAO070-20051210-5580.xml" SchemaVersion="2.1" RegistrationDocument="false">
<StopPoints>
<StopPoint CreationDateTime="2006-12-07T14:47:00-00:00">
<AtcoCode>0100BRP90340</AtcoCode>
<NaptanCode>BSTGAJT</NaptanCode>
<Descriptor>
<CommonName>Rupert Street (CA)</CommonName>
<Landmark>NONE</Landmark>
<Street>Rupert Street</Street>
<Crossing>Colston Avenue</Crossing>
</Descriptor>
<Place>
<NptgLocalityRef>N0076879</NptgLocalityRef>
<Location>
<Easting>358664</Easting>
<Northing>173160</Northing>
</Location>
</Place>
<StopClassification>
<StopType>BCT</StopType>
<OnStreet>
<Bus>
<BusStopType>MKD</BusStopType>
<TimingStatus>OTH</TimingStatus>
<MarkedPoint>
<Bearing>
<CompassPoint>N</CompassPoint>
</Bearing>
</MarkedPoint>
</Bus>
</OnStreet>
</StopClassification>
<AdministrativeAreaRef>010</AdministrativeAreaRef>
</StopPoint>
从 OS 国家网格坐标转换为 Google 地图中使用的 WSG84 经纬度需要两种转换
- 在地球椭球模型上的经纬度与 OS 使用的横轴墨卡托投影之间
- 基于 OS 坐标中使用的不同椭球体的经纬度坐标与全球 WGS84 坐标之间。
包含这些函数和其他实用函数的 XQuery 模块可在 Github 中找到。
作为这些函数使用示例,以下脚本将 TransXChange 文件中的 StopPoints 转换为具有经纬度坐标的更简单格式。这里需要进行局部校正以获得更精确的局部注册。
(: Transforms the Stopcodes in a TransXchange file to a simpler format with National grid references converted to latitude and longitude :)
declare namespace tx="http://www.transxchange.org.uk/";
import module namespace geo="http://kitwallace.me/geo" at "/db/lib/geo.xqm";
declare option exist:serialize "method=xml media-type=text/xml highlight-matches=none";
declare function local:camelCase($s) {
string-join(
for $word in tokenize($s,' ')
return concat(upper-case(substring($word,1,1)), lower-case(substring($word,2))),
' ')
};
<StopPointSet>
{for $stopCode in distinct-values(//tx:StopPoint/tx:AtcoCode)
let $stop := (//tx:StopPoint[tx:AtcoCode=$stopCode])[1]
let $d := $stop/tx:Descriptor
let $l := $stop/tx:Place/tx:Location
return
<StopPoint>
<AtcoCode>{string($stop/tx:AtcoCode)}</AtcoCode>
<CommonName>{string($d/tx:CommonName)}</CommonName>
{if ($d/tx:Landmark ne 'NONE')
then <LandMark>{local:camelCase($d/tx:Landmark)}</LandMark>
else ()
}
<Street>{local:camelCase($d/tx:Street)}</Street>
<Crossing>{local:camelCase($d/tx:Crossing)}</Crossing>
{geo:round-LatLong(geo:OS-to-LatLong(geo:Mercator($l/tx:Easting, $l/tx:Northing)),6)}
</StopPoint>
}
</StopPointSet>
这种转换的输出包含 StopPoints,例如
<StopPointSet>
<StopPoint>
<AtcoCode>0100BRP90340</AtcoCode>
<CommonName>Rupert Street (CA)</CommonName>
<Street>Rupert Street</Street>
<Crossing>Colston Avenue</Crossing>
<geo:LatLong xmlns:geo="http://kitwallace.me/geo" latitude="51.455901" longitude="-2.596312" height="49.136187"/>
</StopPoint>
</StopPointSet>
提取数据的应用之一是在给定位置的范围内绘制车站。这需要一种足够好的距离计算方法,适用于短距离
declare function geo:plain-distance ($f, $s as element(geo:LatLong)) as xs:double {
let $longCorr := math:cos(math:radians(($f/@latitude +$s/@latitude) div 2))
let $dlat := ($f/@latitude - $s/@latitude) * 60
let $dlong := ($f/@longitude - $s/@longitude) * 60 * $longCorr
return math:sqrt(($dlat * $dlat) + ($dlong * $dlong))
};
生成 kml 文件
(: return the StopPoints within $range of $latitude and $longitude :)
import module namespace geo="http://kitwallace.me/geo" at "/db/lib/geo.xqm";
declare option exist:serialize "method=xhtml media-type=application/vnd.google-earth.kml+xml highlight-matches=none";
let $latitude := xs:decimal(request:get-parameter("latitude", 51.4771))
let $longitude := xs:decimal(request:get-parameter ("longitude",-2.5886))
let $range := xs:decimal(request:get-parameter("range",0.5))
let $focus := geo:LatLong($latitude,$longitude)
let $x := response:set-header('Content-Disposition','attachment;filename=stops.kml;')
return
<Document>
<name>Bus Stops within {$range} miles of {geo:LatLong-as-string($focus)}</name>
<Style id="home">
<IconStyle>
<Icon><href>http://maps.google.com/mapfiles/kml/pal2/icon2.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="stop">
<IconStyle>
<Icon><href>http://maps.google.com/mapfiles/kml/pal5/icon13.png</href>
</Icon>
</IconStyle>
</Style>
<Placemark>
<name>Home</name>
<Point>
<coordinates>{geo:LatLong-as-kml($focus)}</coordinates>
</Point>
<styleUrl>#home</styleUrl>
</Placemark>
{ for $stop in doc("/db/apps/xqbook/geo/stopPoints.xml")//StopPoint
let $latlong := geo:LatLong($stop/LatLong/@latitude,$stop/LatLong/@longitude)
let $dist := geo:plain-distance($focus,$latlong) * 0.868976242 (: distance is in nautical miles :)
where $dist < $range
return
<Placemark>
<name>{string($stop/CommonName)}</name>
<description>
{concat($stop/CommonName,' ',$stop/Landmark,' on ', $stop/Street, ' near ', $stop/Crossing)} is {geo:round($dist,2)} miles away.
</description>
<Point>
<coordinates>{geo:LatLong-as-kml($latlong)}</coordinates>
</Point>
<styleUrl>#stop</styleUrl>
</Placemark>
}
</Document>
我家附近半英里内的车站 作为 KML。在 Google 地图上,车站似乎与公交车站叠加层密切对齐,据推测是从相同的基准位置生成的。
如果您能够轻松浏览图标,那么选择 kml 图标就会变得容易。以下是在 XQuery 中的一个简单的浏览器
declare variable $base := "http://maps.google.com/mapfiles/kml/";
declare option exist:serialize "method=xhtml media-type=text/html";
<html>
<h2>Google Earth icons</h2>
<p>Base url {$base}</p>
{for $pal in (2 to 5)
return
<div>
<h2>Palette pal{$pal}</h2>
{for $i in (0 to 63)
let $icon := concat('pal',$pal,'/icon',$i,'.png')
return
<img src="{$base}{$icon}" title="{$icon}"/>
}
</div>
}
</html>