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>