現地点の標高は GPSで得られるが、日本地図上の任意地点の標高が得られるようにしたい。 標高データは国土地理院サイトから一括ダウンロードすることもできるが、 個人利用では全地点の標高データを得たいわけではない。標高データはタイル地図と 同様にタイル単位でpng画像ファイル形式で提供されているものをその都度ダウンロードする。
入手した標高タイルファイルはローカルディスクに保存しておく。
国土地理院が提供する標高データにはいくつかあり、精度の高いものは得られる範囲が限られている。 記事[4]に従い、精度の高いものから順に(「DEM5A」、「DEM5B」、「DEM5C」、「DEM10B」)入手することを試みる。
DEM5A~5C は zoom 15、DEM10B は zoom 14 のタイル座標名で提供される。
ダウンロードしたファイルはディレクトリ dem/5a、dem/5b、dem/5c、dem/10b 下に保存する。 ここで na は Not Available の略で、自作地図では日本地図領域としているが、 DEM10B にも標高ファイルが存在しない場所がある。 その場合、繰り返し調べる無駄を省くために、ファイル名だけのnullファイルを格納しておく。
zoom 14 と zoom 15 に分かれるため、注意がいる。
最初に dem/10b に nullファイルがあるかどうか調べて、そこに nullファイルがあれば、 標高データは一切存在しないことになるか、どうかである。 zoom 14のファイルは zoom 15の4ファイルに相当する。 zoom 15 のあるファイルがなければ、zoom 14 の DEM10B を探しに行くが、 そこになければ nullファイルが格納される。 しかし、zoom 15の4つのうちの別のファイルが zoom 15 に存在するかも知れない。 となると、zoom 14 の nullファイルのチェックではこれが分からない。
それゆえ、nullファイルは zoom 14 の DEM5NA(dem/5na)ディレクトリにおく。 DEM10B で探すが、見つからなかった場合は zoom 15でのファイル名で nullファイルを置く。
最初に、DEM5NAディレクトリを調べて、ここにあれば、それで終了する。ここになかった場合、 「DEM5A」、「DEM5B」、「DEM5C」、「DEM10B」の順に調べ、見つかったところで終了する。
DEM5Aのタイルは https://cyberjapandata.gsi.go.jp/xyz/dem5a_png/{z}/{x}/{y}.png でダウンロードできる。
東京駅を含むタイルの場合 https://cyberjapandata.gsi.go.jp/xyz/dem5a_png/15/29105/12903.png 48.8KB(50,065バイト) となる。この場合、DEM5B、DEM5Cの URL は https://cyberjapandata.gsi.go.jp/xyz/dem5b_png/15/29105/12903.png および https://cyberjapandata.gsi.go.jp/xyz/dem5c_png/15/29105/12903.png となるが、 いずれも存在しなかった。
DEM10Bは https://cyberjapandata.gsi.go.jp/xyz/dem_png/14/14552/6451.png 50.7KB(51,960 バイト) となる。 これをそのまま画像ファイルとして表示すると下図のようになる。
DEM5A DEM10B
この場合DEM10Bの画像を縦横二分した右下部分を拡大したものが DEM5A となる。
DEM5Aでは所々に白っぽいものが見えるが、データ不正か欠落であろう。
一つのタイルには256x256地点の標高データが含まれている。タイルとしては存在していても 部分的に欠落しているとなれば、保存するタイル画像は存在すれば DEM5A、DEM5B、DEM5C のいずれか と DEM10B となる。部分的に欠落している場合、周辺の平均値の方が精度が高いかも知れない。 また、データが欠落していても問題がない用途も多いと思われる。
まずは、地図の中心点の標高を zoom 14標高タイルとzoom 15標高タイルから求めて併記してみる。 一度ダウンロードしたタイルはストレージに保存する。また、zoom 14, 15 それぞれ最大4タイルをメモリにキャッシュする。
当面、標高タイル中の全ピクセル(256x256)の値が要るわけではないので、getPixelメソッドで特定ピクセルの値を取り出す。
getPixelsメソッドで、256x256要素の4バイト配列として取り出せる。多くの画素の値が要る場合には 画素単位で値を読み出すより、標高データの取り出しが迅速に行える。
画素の値から以下に従って標高値を取り出す[2]。
画素値(RGB値)から標高値h(m)の計算式は下記のとおりです。
uは標高分解能(0.01m)を表します。また、無効値は(R, G, B)=(128, 0, 0)です。
- x = 216R + 28G + B
- x < 223の場合 h = xu
- x = 223の場合 h = NA
- x > 223の場合 h = (x-224)u
記事[4] では次のようにして標高 h を求めている。
if (r != 128 || g != 0 || b != 0) { var d = r * this.pow2_16 + g * this.pow2_8 + b; h = (d < this.pow2_23) ? d : d - this.pow2_24; if (h == -this.pow2_23) h = 0; else h *= 0.01; }
ダウンロードはメインスレッドでは実行できない。タイル地図画像ファイルのダウンロードはOSMレンダリングと同様に、 スタンバイした複数のスレッドで実行している。この方法を採ることもできるが、ここでは、動的にスレッドを生成する方法を使ってみる。 立て続けに標高データのリクエストが生まれたとき、同じ標高タイルのダウンロードが同時に複数起きるため、これを避ける対策がいるかも知れない。
別スレッドの実行自体は次のように簡単であった。setElevation処理はスレッド立ち上げだけで終わるため、 別スレッドでの処理が終わらない内に次の実行により、新たなスレッドが立ち上げられることがある。 スレッドの重なり抑制はまだいれていない。
void setElevation(int zoom, double x, double y) { // Singleの別スレッドを立ち上げる Executors.newSingleThreadExecutor().execute(() -> { Elevation ev = Elevation.getElevation(zoom, x, y); // 別スレッド内での処理を管理し実行する HandlerCompat.createAsync(getMainLooper()).post(() -> // Mainスレッドに渡す ele_view.setText(ev.src5.name() + ": " + ev.ele5 + "m dem_10b: " + ev.ele10 + "m") ); }); }
標高が表示されるようになったが、数値が間違っている。
RGB のところが RBG に間違えていた。
int pixel = bmp15.getPixel(X15%256, Y15%256); //int v = Color.red(pixel)*(1<<16) + Color.blue(pixel)*(1<<8) + Color.green(pixel); int v = Color.red(pixel)*(1<<16) + Color.green(pixel)*(1<<8) + Color.blue(pixel); ele.ele5 = v == (1<<23) ? Float.MIN_VALUE : // 無効 v < (1<<23) ? v * 0.01f : (v - (1<<24)) * 0.01f;