ベクターマップを取り入れたい!

これは、地震アプリEarthQuakeの開発ログ#3です。
2はこちら↓

なぜ作る必要があるのか?

先日、私は自作アプリに地理院地図を取り入れる方法についてまとめたブログを書きました。
ここで紹介したものは一般的に地図タイルのうちの「ラスタマップ」と呼ばれているもので、それぞれのタイルは画像で提供されています。

今回アプリ内に取り入れるものは「ベクターマップ」と呼ばれるもので、こちらはタイルはPolygon(多角形)またはLine(線)で提供されています。

ラスタのデメリット

先日実装したラスタマップを使用していて不便だと感じたことがいくつかありました。

この画像を見ていただくといくつかデメリットが分かると思います。

ラスタマップ上に地震情報の震度マップが表示されている。

ラスタマップ上に通常のポリゴンを重ねてしまっているため、地名の文字が見切れてしまっています。これではせっかくのマップが台無しですね。

そしてもう1つ、ラスタでは画像でタイルが提供されるため、特に拡大時にピクセルが目立ってしまっています。単純に解像度をアンチエイリアシングで2倍程度に上げればいいのですが、そうするとなぜかとても重くなってしまいます。(理由は分かっていない)

ここで活躍するのが「ベクターマップ」というわけです。

ベクターのメリット

先ほど述べた通り、ベクターマップはいわゆるSVGのように、主に多角形と線で構成されるため、拡大してもピクセルが目立たず、品質が落ちることもありません。また、それぞれがレイヤーとして操作できるので、簡単に表示/非表示や色の変更などのカスタマイズが可能です。

サーバー側での操作も、画像を介さないベクタのほうが簡単な場合があります。

アプリで使用している描画ライブラリ”SkiaSharp”上への描画も、ベクターのほうが最適化されていて高速なんです。

ベクターのデメリット

そんな中でもデメリットはあります。たとえば、タイルのデータは画像よりも大きくなる傾向にあります。ただし、圧縮することで、ある程度は解決は可能です。

また、画像の物よりも実装が難しいです。

ベクターマップはあまり提供されていない

現時点で、残念ながらベクターマップは殆どが有料です。
その中では、Google MapsやMapBoxなどはSDKも同時に提供しているため、使いやすいです。

ChatGPT

…僕、中学生です。バイトなんてできません。無料で完結させちゃいましょう!

実装してみる

マップデータをダウンロード

ベクターマップを作成するには、元となるデータが必要です。ここでは「地理院地図Vector」を使用してみます。

開発のため、ベクトルタイルをローカル環境へまずすべてコピーします。(本サーバーへの負荷を減らすため)
コピーするためのツールをGo言語で自作しました。

https://github.com/yossy4411/gsi-tiles-downloader

コピーしたら、適当なサーバー(Nginxなど)でローカル環境で開けるようにします。

/any/directory/{z}/{x}/{y}.pbfの形式でダウンロードできるように配置してください(ツールでは自動にその形になります)

スタイルの読み込み

ベクターマップを表示するためには、そのマップのスタイルが必要です。スタイルはMaputnikなどのツールを使えば楽に作成できます。

MapBox GL Styleはjson形式で利用できるため、これを使用しましょう!

EarthQuake.Map/Tiles/Vector/VectorMapStyles.csより一部抜粋

namespace EarthQuake.Map.Tiles.Vector;

public class VectorMapStyles
{
    public string? Name { get; init; }
    public IReadOnlyList<VectorTileMapLayer> Layers { get; init; } = [];

    private static readonly JsonSerializer Serializer = JsonSerializer.Create();

    /// <summary>
    /// GL Style JSONファイルを読み込む
    /// </summary>
    /// <param name="stream">読み込むストリーム</param>
    /// <returns>マップのスタイル</returns>
    public static VectorMapStyles LoadGLJson(StreamReader stream)
    {
        using var reader = new JsonTextReader(stream);
        var jObject = Serializer.Deserialize<JObject>(reader);
        if (jObject?["layers"] is not JArray layers) return new VectorMapStyles();
        var layersList = layers.Select(NewLayer).OfType<VectorTileMapLayer>().ToList();
        var name = jObject["name"]?.ToObject<string>();

        return new VectorMapStyles
        {
            Name = name,
            Layers = layersList
        };
    }
}

普通にjsonで読み込んで、それぞれのキーでパースしているだけです

タイルをパース

それぞれのベクトルタイルは.pbf形式ですので、それらを読み込めるライブラリを探します。

…あった!今回はmapbox-vector-tileというNuGetパッケージを利用します。

pbfを読み込み、それぞれの地物をポリゴンや辺へと変換します。

EarthQuake.Map/Tiles/Vector/VectorTileFeature.cs
EarthQuake.Map/Tiles/Vector/VectorMapLayer.cs


コントローラーの作成

それぞれのタイルをキャッシュしたりリクエストを送信したりするためのコントローラーを作成します。

EarthQuake.Map/Tiles/Vector/VectorTilesController.cs

レイヤーの作成

パースしたデータを描画するためのレイヤーを作成します。

https://github.com/yossy4411/EarthQuake/blob/main/EarthQuake.Map/Layers/VectorMapLayer.cs

前回と大きく異なるところは、イベントハンドラを追加したところです。
データが読み込まれたときに再描画が実行されるようにしました。

データを読み込む

先ほど配置したタイルをURLで指定して読み込みます!

ちなみにhttps://map.okayugroup.com/gsi-v/{z}/{x}/{y}.pbfで使用できます。

実際の動作はこんな感じになりました。

ちょっと見づらい(笑)スタイルをもう少しいじったら良さそうですね!

まとめ

今回は、EarthQuakeアプリにベクターマップを実装してみました。どうでしたか?
まだまだ改善の余地はありそうですが、安定してきたらv0.1.0で公開したいと思います。

次回予告

次回はこのベクターマップを最適化していきます。
PMTiles…

ご覧いただきありがとうございました!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール