プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

UWPで地図にTileSourceを追加したりしてみる

f:id:puni-o:20160420162605j:plain

してみます

UWPに地図を出すのは@okazukiさんのブログ

blog.okazuki.jp

を見てください。私が説明するよりわかりやすいと思うので割愛します。

f:id:puni-o:20160420162839p:plain

とりあえずこんな感じなところまで準備します。日本が残念な感じになっているのがいい感じですね。 (そういえばVisual Studio Update2でデバッグをするとヴィジュアルツリーをいじる系のツールバーっぽいのが出るんですね)

Open Street Mapを追加する

UWPの地図(に限らず最近の地図)はTMS(Tile Map Service)を使います。(TMSがどこを指しているのかいまいちわかってません) TMSの詳しいことはググってください。地図をx,y,z(zoom)で指定できる256×256ピクセルのタイル画像にして並べていると思えばいいんじゃないでしょうか。 (256ピクセルは固定ではないようですが256以外のサイズを見たことが無いので固定でいいんじゃない?)

ということでTMSで有名なOpen Street MapをMapのTileSourceに追加してみます

こちら Slippy map tilenames - OpenStreetMap Wiki にあるstandardスタイルを使ってみます。

URL templateは http://[abc].tile.openstreetmap.org/zoom/x/y.png と書いてありますね。

このサーバーから地図画像をもってきて地図コントロールに重ねてみます。 コードはこんな感じ

var osmTileSource = new HttpMapTileDataSource("http://tile.openstreetmap.org/{zoomlevel}/{x}/{y}.png");
var tileSource = new MapTileSource(osmTileSource);
Map.TileSources.Add(tileSource);

MapはMapControlのインスタンスです。

HttpMapTileDataSourceがHTTP経由でタイル画像を取得してくれる人です。引数にURLのテンプレートを書きます。({x} {y} {zoomlevel}がUWPのAPIが置換する部分になります) UriRequestedをハンドルすると呼び出している状況がわかるのでお好みでどうぞ。

実行すると

f:id:puni-o:20160420165720p:plain

こんな感じになります。残念だった日本が残念じゃなくなったぞ!

カスタムソースのタイルを追加

ローカル記憶域ってのもありますがほとんど使い道無い気がするのでそっちはパス。

カスタムソースでは自分でBitmapを作ります。

var customDataSource = new CustomMapTileDataSource();
customDataSource.BitmapRequested += CustomDataSource_BitmapRequested;
var customTileSource = new MapTileSource(customDataSource);
Map.TileSources.Add(customTileSource);

private async void CustomDataSource_BitmapRequested(CustomMapTileDataSource sender, MapTileBitmapRequestedEventArgs args)
{
    var deferral = args.Request.GetDeferral();

    var a = (byte)0x80;
    var r = ((args.X + args.Y) % 3) == 0 ? (byte)0xFF : (byte)0;
    var g = ((args.X + args.Y + 1) % 3) == 0 ? (byte)0xFF : (byte)0;
    var b = ((args.X + args.Y + 2) % 3) == 0 ? (byte)0xFF : (byte)0;
    var bytes = new byte[256 * 256 * 4];

    for (var y = 0; y < 256; y++)
    {
        for (var x = 0; x < 256; x++)
        {
            var index = (y * 256 + x) * 4;
            bytes[index] = r;
            bytes[index + 1] = g;
            bytes[index + 2] = b;
            bytes[index + 3] = a;
        }
    }

    var randomAccessStream = new InMemoryRandomAccessStream();
    var stream = randomAccessStream.GetOutputStreamAt(0);
    var writer = new DataWriter(stream);
    writer.WriteBytes(bytes);
    await writer.StoreAsync();
    await writer.FlushAsync();

    args.Request.PixelData = RandomAccessStreamReference.CreateFromStream(randomAccessStream);

    deferral.Complete();
}

こんな感じ。BitmapRequestedイベントでビットマップデータを入れてあげます。 今回のソースではX座標とY座標からRGBを作って半透明なビットマップを作って返しています。

実行すると

f:id:puni-o:20160420175901p:plain

半透明のタイルが元の地図の上に重なって表示されていることがわかりますね。

既定の地図を変更する

上のスクリーンショットでもわかるように普通に追加するとデフォルトの地図の上に、追加したTileSourceが置かれます。重ねるのではなくデフォルトの地図を入れ替えることもできます。

var osmTileSource = new HttpMapTileDataSource("http://tile.openstreetmap.org/{zoomlevel}/{x}/{y}.png");
var tileSource = new MapTileSource(osmTileSource);
tileSource.Layer = MapTileLayer.BackgroundReplacement;
Map.Style = MapStyle.None;
Map.TileSources.Add(tileSource);

MapTileSource.LayerをBackgroundReplacementに、Map.StyleをNoneにします。 これでOpenStreetMapだけを表示するようになります。

分からなくもないよくわからない現象

上のスクショももっとズームした方が見栄えがいいんですがあえてあのレンジで撮ってます。というもの日本である程度ズームすると追加したタイルソースが表示されなくなるんですよね。

f:id:puni-o:20160420181842p:plain

出てる

f:id:puni-o:20160420181852p:plain

ZoomLevelが8を超える(?)と消えます。

f:id:puni-o:20160420181921p:plain

台湾を表示すると出ている地域と出ていない地域の境界線がわかります。

f:id:puni-o:20160420182012g:plain

そういうことですか・・・

北朝鮮が出ていて韓国や日本が出ていないのがよくわからないんですがどういうルールなんでしょうかね・・・(長方形の領域でのとばっちりとかそんな感じでもなさそうだし)

上でMapTileSource.Layerを変更しましたが、デフォルトではこの値はRoadOverlayになっています。

定義はこんな感じ。

public enum MapTileLayer
{
    LabelOverlay = 0,
    RoadOverlay = 1,
    AreaOverlay = 2,
    BackgroundOverlay = 3,
    BackgroundReplacement = 4
}

先ほどのgif動画を見るとラベル以外(Road/Area/Background(も?))が表示されていないようなのでMapTileSource.Layerの値を変更します。

var customDataSource = new CustomMapTileDataSource();
customDataSource.BitmapRequested += CustomDataSource_BitmapRequested;
var customTileSource = new MapTileSource(customDataSource);
customTileSource.Layer = MapTileLayer.LabelOverlay;
Map.TileSources.Add(customTileSource);

f:id:puni-o:20160420182846p:plain

表示されました。今まではラベルの下にあったタイルがラベルの上に来てしまっていますが出ないよりましでしょう。

日本をズームするとTileSourceが表示されないっていうのは問題アリだと思うんですがどうなんでしょう・・・?