2017/07/07

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

Google Maps Android API v2 では[タイル オーバーレイ](https://developers.google.com/maps/documentation/android-api/tileoverlay?hl=ja)が使えるようになり、独自の地図を重ねあわせたりするのに使えます。



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

```java
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` となっていて、どの緯度・経度のタイルを生成すれば良いのかわかりづらいのです理解してみるとそんなに難しくないのですが。

公式サイトによると、世界地図を zoom レベルに応じたグリッドで分割するとのこと。
公式サイトの[画像入り具体例](https://developers.google.com/maps/documentation/android-api/tileoverlay?hl=ja#coordinates)を見ると、
「緯度は `180.0 / (1 << zoom)`、経度は `360.0 / (1 << zoom)` でグリッドの一辺が出るので、それに `x`、 `y` をかけてオフセット計算すれば良い。」ということがわかります。

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

- 緯度はメルカトル図法に換算しないといけない
- `x = 0` の時の経度は `-180`度公式サイトの画像を見ると確かにそうなってるのだけど


### 具体的な実装例
自分で書こうと思ったら、stack overflow に書いてあったので拝借しました変数とかリネームしちゃったけど、ちゃんと動いているものを貼ります。

```java
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](https://developers.google.com/android/reference/com/google/android/gms/maps/model/TileOverlay?hl=ja) を使うのを止めました。 というのも、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](https://developers.google.com/android/reference/com/google/android/gms/maps/model/GroundOverlay?hl=ja) で代わりの実装をすることにしました。 GroundOverlay は拡大縮小時に消えないので問題ありません。 自分で表示域を計算してタイルをリクエストする処理を書かなければなりませんでしたが、`getTile()` 自体はほぼそのまま使いまわしです。

0 件のコメント: