The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

WP7 修練 DAY 08:整合 Nokia Maps 進 Bing Map 控制項

這幾天都在研究如何將 Nokia Maps 整合進 Bing Map 控制項來用,研究的過程中因為不瞭解 TileSource 類別的用法,而且 Nokia 那邊也沒有提到任何與 BingMap 控制項整合的文件,所以一直瞎子摸象,過程中一直不斷胡亂瞎猜其用法,浪費了不少時間,但今天靈感一來,雖然沒有文件參考,但還是摸索出用法,重點是:讀出來的還是全中文化的地圖喔!

我這次的研究基礎來自於 當麻許 寫的【[Silverlight] Phone 7中BingMap Control 使用中文台灣地圖(僅供研究教學用)】這篇文章,非常感謝他有這篇分享,讓我對 Nokia Maps 的整合有了一線生機。

研究過程分享

首先,我只看到一份原始碼可用,也沒有任何說明文件,但是重點是可以執行程式碼,並成功整合 Google Maps 為 Bing Maps 資料來源。以下是程式碼片段:

public abstract class GoogleMapsTileSourceBase : 
    Microsoft.Phone.Controls.Maps.TileSource
{
    protected GoogleMapsTileSourceBase(string uriFormat)
        : base(uriFormat)
    { }
    public override System.Uri GetUri(int x, int y, int zoomLevel)
    {
        return new System.Uri(string.Format(UriFormat, 
            new System.Random().Next() % 4, x, y, zoomLevel));
    }
}
public class GoogleMapsRoadTileSource : GoogleMapsTileSourceBase
{
    public GoogleMapsRoadTileSource()
        : base("http://mt{0}.google.com/vt/lyrs=m@128&hl=en&x={1}&y={2}&z={3}&s=")
    { }
}
public class GoogleMapsAerialTileSource : GoogleMapsTileSourceBase
{
    public GoogleMapsAerialTileSource()
        : base("http://khm{0}.google.com/kh/v=62&x={1}&y={2}&z={3}&s=")
    { }
}
public class GoogleMapsLabelsTileSource : GoogleMapsTileSourceBase
{
    public GoogleMapsLabelsTileSource()
        : base("http://mt{0}.google.com/vt/lyrs=h@128&hl=en&x={1}&y={2}&z={3}&s=")
    { }
}

從上述程式碼中可以發現,要定義 Map 控制項的資料來源,必須先繼承 Microsoft.Phone.Controls.Maps 命名空間下的 TileSource 型別,然後他可以從建構子傳入一個 uriFormat 字串。

而 TileSource 類別之所以要傳入 uriFormat 就是為了讓 Map 控制項可以正確取得一片片的 Tile 圖像,而 uriFormat 只是一個 Uri 的格式,真正取得 Uri 的地方是寫在 GetUri 方法裡,所以自訂的 TileSource 可以透過 覆寫 (override) GetUri 方法來達成取得不同 Tile 圖片來源的目的。

從上述程式碼,我首先傻眼的地方在於:這個網址到底怎麼來的啊?

補充說明

在研究的過程中,我曾經被 Tile 這個詞給搞糊塗了,畢竟之前沒接觸過「地圖」相關的應用,不太知道 Tile 的意思,不過到後來我終於瞭解到,原來我們無論在 Bing MapsGoogle Maps 或是 Nokia Maps 都一樣,顯示地圖時,總共分成 20 個層級 (ZoomLevel),每一個層級都有一張完整的地圖圖片,而這張圖片就被稱為 Tile。

例如 ZoomLevel = 1 代表的就是一張世界地圖,而 ZoomLevel = 20 就是一張全世界最為詳細的地圖,可想而知這是一張很大很大的圖片,不可能讓使用者一次下載的完,所以必須再切割成小小張的照片來拼裝地圖起來。

另外,Tile 在 Windows Phone 7 被稱為動態磚,跟這裡講的 Tile 是不同的東西。

當時由於看不懂這個 uriFormat  網址格式的意義,所以也就不知道怎樣才能跟 Nokia Maps 整合了,困擾多日之後,今天一個突如其來的想法,用 Fiddler 來分析 Google Maps 在讀取圖資時,到底抓了哪些資料,然而驚訝的發現這些下載 Tile 圖片的網址,竟然於上述的程式碼片段極其相似!

接著比對一下從 GetUri 方法裡傳入三個參數 (x, y, zoomlevel) 與 uriFormat 的關係,這才發現原來 x 代表的是在這張很大很大的 Tile 圖片,依據不同的 ZoomLevel 進行切割後,每一片小 Tile 是在大 Tile 的 x 軸的第幾片。y 代表的是在這張很大很大的 Tile 圖片,依據不同的 ZoomLevel 進行切割後,每一片小 Tile 是在大 Tile 的 y 軸的第幾片。 ( 這些理解都來自於對 Tile 圖像的理解後,才推敲出來的 )

注意:當網頁在下載圖資的時候,每一張圖有可能來自於不同的 Host (網域),如上圖所示,你就看到了 mt1.google.com 與 mt0.google.com 這兩個不同的網址,而這個開發技巧就是因為一般瀏覽器針對相同的網域預設同時只會開兩個 Thread 下載資源,透過多個不同的網域,就可增加瀏覽器下載圖片的速度,這技巧在 Maps 相關應用的領域用的非常多。

瞭解這些觀念後,我再開啟 Nokia Maps 並透過 Fiddler 收錄其下載 Tile 的 HTTP 要求,又再次驚覺 Nokia MapsGoogle Maps 的機制是完全一樣的:

以下是我取得的其中一張小 Tile 圖片的網址:

http://4.maptile.lbs.ovi.com/maptiler/v2/maptile/newest/normal.day/17/109783/56116/256/png8?lg=CHI&token=fee2f2a877fd4a429f17207a57658582&appId=nokiaMaps

經測試後瞭解,依序說明如下:

  • 4 主要是為了能加速下載圖片,編號沒特殊意義。你可以從 1 換到 4
  • 17 代表 ZoomLevel,也就是地圖的詳細程度,越詳細的地圖相對的 Tile 就越大張
  • 109783 代表這張大 Tile 圖片被切割後,在 x 軸的第幾張
  • 56116 代表這張大 Tile 圖片被切割後,在 y 軸的第幾張
  • 256 代表圖片的大小 ( 備註: 在 Bing Map 控制項必須選用 256 才行 )
  • lg=CHI 代表顯示「中文地圖」 ( 這很重要 )
  • tokenappId 是你在 API Registration 頁面所註冊應用程式時所核發給你的 App ID 與 Token,以下是註冊過後的圖示:

也因為瞭解了網址格式,所以我自訂了一個 TileSource 型別,而且還因此同時設計了三種不同的地圖檢視,分別是「地圖檢事」、「衛星檢視」與「地形檢視」,如下程式碼:

public abstract class NokiaMapsTileSourceBase
    : Microsoft.Phone.Controls.Maps.TileSource
{
    protected NokiaMapsTileSourceBase(string uriFormat)
        : base(uriFormat)
    {
    }

    static int _tile_count = 0;

    static string token = "<PUT YOUR Token HERE>";
    static string appId = "<PUT YOUR AppID HERE>";

    public override System.Uri GetUri(int x, int y, int zoomLevel)
    {
        return new System.Uri(
            string.Format(
                UriFormat,
                (_tile_count++ % 4) + 1,
                zoomLevel,
                x,
                y,
                token,
                appId));
    }
}

/// <summary>
/// 地圖檢視
/// </summary>
public class NokiaMapsRoadTileSource : NokiaMapsTileSourceBase
{
    public NokiaMapsRoadTileSource()
        : base("http://{0}.maptile.lbs.ovi.com/maptiler/v2/maptile/newest/normal.day/{1}/{2}/{3}/256/png8?lg=CHI&token={4}&appId={5}")
    {
    }
}

/// <summary>
/// 衛星檢視 ( Satellite View )
/// </summary>
public class NokiaMapsSatelliteTileSource : NokiaMapsTileSourceBase
{
    public NokiaMapsSatelliteTileSource()
        : base("http://{0}.maptile.lbs.ovi.com/maptiler/v2/maptile/newest/hybrid.day/{1}/{2}/{3}/256/png8?lg=CHI&token={4}&appId={5}")
    {
    }
}

/// <summary>
/// 地形檢視 ( Terrain View )
/// </summary>
public class NokiaMapsTerrainTileSource : NokiaMapsTileSourceBase
{
    public NokiaMapsTerrainTileSource()
        : base("http://{0}.maptile.lbs.ovi.com/maptiler/v2/maptile/newest/terrain.day/{1}/{2}/{3}/256/png8?lg=CHI&token={4}&appId={5}")
    {
    }
}

以下是在 XAML 中宣告地圖的語法圖示: ( 注意:你可以同時宣告多張 MapTileLayer,如果同時有兩個啟用的話,那麼這兩層的 TileSource 就會同時下載並疊在一起顯示 )

請注意:使用 Bing Map 控制項必須先到 Bing Maps Account Center 申請一組金鑰,申請到之後要放在如上圖的 CredentialsProvider 屬性中。

如此一來,在 Windows Phone 7 手機上顯示全中文化的 Nokia Maps 就再也不是什麼難事了! ;-)

 

今日修練總結

一個多星期前就開始研究如何將 Nokia Maps 整合進 Bing Maps,不過礙於對 Bing Map Control 與 Tile Image 的觀念不瞭解,導致過程中一直瞎猜碰壁,不過還好找到了 Tiles - OpenStreetMap Wiki 這篇文章才恍然大悟,再深入研究之後已經對 Map 控制項有了一定程度的瞭解,下次要再有需要替換成其他的圖資提供者應該沒太大問題才是,今日的修練 … 成功! ^_^

 

相關連結