2017/07/07

タイルオーバーレイの x, y 座標を緯度・経度に変更する方法 (Google Maps Android API)

Google Maps Android API v2 ではタイル オーバーレイが使えるようになり、独自の地図を重ねあわせたりするのに使えます。

実装方法は、以下のように TileProvider インターフェースの getTile() メソッドを実装したクラスを作成します。 getTIle() で対象のタイルを生成すると、後は Google Maps がよきに取り計らってくれるという仕組みです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ChartTileProvider implements TileProvider {
    @Override
    public Tile getTile(int x, int y, int zoom) {
        Bitmap tileBitmap;
 
        // tileBitmap を持ってくる(生成する)
 
        // Bitmap ではなく、直接 ByteArray が取得できる場合は以下はいらない
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            tileBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
            return new Tile(tileBitmap.getWidth(), tileBitmap.getHeight(), stream.toByteArray());
        } catch (IOException e) {
            // TODO エラーハンドリング
            return NO_TILE;
        }
    }
}

仕様を確認

ただ、この getTile() の引数が x, y, zoom となっていて、どの緯度・経度のタイルを生成すれば良いのかわかりづらいのです1

公式サイトによると、世界地図を zoom レベルに応じたグリッドで分割するとのこと。 公式サイトの画像入り具体例を見ると、 「緯度は 180.0 / (1 << zoom)、経度は 360.0 / (1 << zoom) でグリッドの一辺が出るので、それに xy をかけてオフセット計算すれば良い。」ということがわかります。

ただ、以下の2点に気をつけないと思った場所に表示されないので注意しなければなりません2

  • 緯度はメルカトル図法に換算しないといけない
  • x = 0 の時の経度は -1803

具体的な実装例

自分で書こうと思ったら、stack overflow に書いてあったので拝借しました4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static LatLngBounds boundsOfTile(int x, int y, int zoom) {
    int numTiles = (1 << zoom);
    double longitudeSpan = 360.0 / numTiles;
    double west = -180.0 + x * longitudeSpan;
 
    double northMercator = 180 - (((double) y) / numTiles) * 360;
    double southMercator = 180 - (((double) y + 1) / numTiles) * 360;
    double north = fromMercatorToLatitude(northMercator);
    double south = fromMercatorToLatitude(southMercator);
 
    return new LatLngBounds(new LatLng(south, west), new LatLng(north, west + longitudeSpan));
}
 
public static double fromMercatorToLatitude(double mercator) {
    double radians = Math.atan(Math.exp(Math.toRadians(mercator)));
    return Math.toDegrees(2 * radians) - 90;
}
参考
[android - TileProvider method getTile - need to translate x and y to lat/long - Stack Overflow](https://stackoverflow.com/a/16936539)

だけど、私は使うのを止めた

ここまで書いていて何なんですが、私は TileOverlay を使うのを止めました。

というのも、TileOverlay は拡大縮小時に消えてしまうという問題があるのです。

本来、拡大する時の動作は、現在の画像をそのまま表示してモザイク状になった後、高解像度の画像が表示されるというのが一般的だと思います。少なくとも、Google Maps のオリジナル地図はそういう動作をします。

ところが、TileOverlay は拡大・縮小時に一度消え、オリジナルの地図が表示された後、高解像度の画像に置き換わるのです。

全く使えないというわけではないのですが、やはりちょっと使いづらいし、何かダサい。

参考
[android maps v2 custom overlay disappears on zoom - Stack Overflow](https://stackoverflow.com/questions/17712898/android-maps-v2-custom-overlay-disappears-on-zoom)

というわけで、私は GroundOverlay で代わりの実装をすることにしました。 GroundOverlay は拡大縮小時に消えないので問題ありません。

自分で表示域を計算してタイルをリクエストする処理を書かなければなりませんでしたが、getTile() 自体はほぼそのまま使いまわしです。

  1. 理解してみるとそんなに難しくないのですが 
  2. そして私はハマった 
  3. 公式サイトの画像を見ると確かにそうなってるのだけど 
  4. 変数とかリネームしちゃったけど、ちゃんと動いているものを貼ります 
?

0 件のコメント: