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>