Javaを使用して日本の地理空間データから標高情報を取得する方法

地理情報の解析や研究において、標高情報は非常に重要です。この記事では、Javaを使用して日本の地理空間データから標高情報を取得する方法を解説します。

# 1. Mavenによるライブラリの設定

Javaプロジェクトで外部ライブラリを簡単に管理するために、Mavenを使用します。以下の依存関係を`pom.xml`に追記することで、OkHttpライブラリをプロジェクトに追加できます。

```xml
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version> <!-- 最新のバージョンに更新してください -->
</dependency>
```

# 2. 標高データの取得

`ElevationFetcher`クラスを使用して、指定された緯度と経度に基づいて標高データを取得します。このクラスは、OkHttpライブラリを使用して、日本の地理空間データ提供サイトから標高データを取得します。

## コードの詳細:

- **getOnlyElevationメソッド**: 指定された緯度と経度の標高データを取得します。
- **fetchElevationメソッド**: 緯度と経度をワールド座標に変換し、複数のDEMソースから標高データを取得します。
- **getElevationメソッド**: ワールド座標をピクセル座標に変換し、指定されたDEMソースから標高データを取得します。

以下は、`ElevationFetcher`クラスの一部です：

```java
import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import okhttp3.OkHttpClient;
import okhttp3.Request;

/**
 * ElevationFetcherクラスは、指定された緯度と経度の標高データを取得するためのクラスです。
 * 日本の地理空間データ提供サイトから標高データを取得します。
 */
public class ElevationFetcher {

    private static final int          CONST_NO_DATA = -1;

    private static final String       BASE_URL      = "https://cyberjapandata.gsi.go.jp/xyz/";

    private static final OkHttpClient client        = new OkHttpClient();

    /**
     * メインメソッド。東京の緯度と経度を使用して標高データを取得し、結果を出力します。
     * @param args コマンドライン引数（未使用）
     */
    public static void main(final String[] args) {

        final var lon = 139.6917; // 東京の経度
        final var lat = 35.6895; // 東京の緯度

        try {
            final var elevation = ElevationFetcher.getOnlyElevation(lat, lon);
            System.out.println(elevation);
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 指定された緯度と経度の標高データのみを取得します。
     * @param lat 緯度
     * @param lon 経度
     * @return 標高データ
     * @throws IOException データ取得中の例外
     */
    public static BigDecimal getOnlyElevation(final double lat, final double lon) throws IOException {

        try {
            final var resultMap = ElevationFetcher.fetchElevation(lat, lon);
            final var elevation = new BigDecimal(resultMap.get("elevation"));
            return elevation;
        } catch (final NumberFormatException e) {
            final var elevation = new BigDecimal(CONST_NO_DATA);
            return elevation;
        }
    }


    /**
     * 指定された緯度と経度の標高データを取得します。
     * @param lat 緯度
     * @param lon 経度
     * @return 標高データを含むMap
     * @throws IOException データ取得中の例外
     */
    public static Map <String, String> fetchElevation(final double lat, final double lon) throws IOException {

        final var lng_rad = Math.toRadians(lon);
        final var lat_rad = Math.toRadians(lat);
        final var R = 128 / Math.PI;
        final var worldCoordX = R * (lng_rad + Math.PI);
        final var worldCoordY = -1 * R / 2 * Math.log((1 + Math.sin(lat_rad)) / (1 - Math.sin(lat_rad))) + 128;

        var elevation = ElevationFetcher.getElevation(worldCoordX, worldCoordY, 15, "dem5a", 1);
        var hsrc = "5m（レーザ）";

        if (elevation == ElevationFetcher.CONST_NO_DATA) {
            elevation = ElevationFetcher.getElevation(worldCoordX, worldCoordY, 15, "dem5b", 1);
            hsrc = "5m（写真測量）";
        }

        if (elevation == ElevationFetcher.CONST_NO_DATA) {
            elevation = ElevationFetcher.getElevation(worldCoordX, worldCoordY, 14, "dem", 0);
            hsrc = "10m";
        }

        final Map <String, String> resultMap = new HashMap <>();
        if (elevation == ElevationFetcher.CONST_NO_DATA) {
            resultMap.put("elevation", "-----");
        } else {
            resultMap.put("elevation", String.format("%.2f", elevation)); // 標高データをStringに変換
        }
        resultMap.put("hsrc", hsrc);

        return resultMap;
    }


    /**
     * 指定されたワールド座標、ズームレベル、DEMソースから標高データを取得します。
     * @param worldCoordX ワールド座標X
     * @param worldCoordY ワールド座標Y
     * @param zoom ズームレベル
     * @param demSource DEMソース
     * @param dataRound 丸める小数点以下の桁数
     * @return 標高データの値
     * @throws IOException データ取得中の例外
     */
    private static Double getElevation(final double worldCoordX, final double worldCoordY, final int zoom, final String demSource,
            final int dataRound) throws IOException {

        final var PixelX = worldCoordX * Math.pow(2, zoom);
        final var TileX = (int) (PixelX / 256);
        final var PixelY = worldCoordY * Math.pow(2, zoom);
        final var TileY = (int) (PixelY / 256);
        final var PixelXint = (int) PixelX;
        final var px = PixelXint % 256;
        final var PixelYint = (int) PixelY;
        final var py = PixelYint % 256;

        final var sFileName = ElevationFetcher.BASE_URL + String.format("%s/%d/%d/%d.txt", demSource, zoom, TileX, TileY);
        final var request = new Request.Builder().url(sFileName).build();

        try (var response = ElevationFetcher.client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                return (double) ElevationFetcher.CONST_NO_DATA;
            }

            final var responseBody = response.body();
            if (responseBody == null) {
                return (double) ElevationFetcher.CONST_NO_DATA;
            }

            final var lines = responseBody.string().split("\n");
            if (lines.length <= py) {
                return (double) ElevationFetcher.CONST_NO_DATA;
            }

            final var values = lines[py].split(",");
            if (values.length <= px) {
                return (double) ElevationFetcher.CONST_NO_DATA;
            }

            final var elevation = Double.parseDouble(values[px]);
            return Math.round(elevation * Math.pow(10, dataRound)) / Math.pow(10, dataRound);

        }
    }
}

```

## 標高タイルの詳細仕様（[国土地理院より](https://maps.gsi.go.jp/development/demtile.html)）

標高タイルは、地図タイルと同一のタイル座標とピクセル座標を用いてデータを整備しています。
カンマ区切りのテキスト形式とPNG形式のファイルが提供されています。
標高タイルのデータ仕様では、256ピクセル×256ピクセルの地図タイルと関連づけられています。
テキスト形式の場合、1行にカンマ区切りで256個の標高値を表す数値データが格納されており、1枚の標高タイルは256行からなります。
PNG形式の場合、24ビットカラーのPNG形式で、一つのタイルの大きさは256ピクセル×256ピクセルです。


# 3. 結論

Javaと`ElevationFetcher`クラス、そしてMavenの設定を適切に使用することで、日本の地理空間データから正確な標高情報を効率的に取得することができます。この手法は、地理情報の解析や研究に非常に役立ちます。


# ※参考

https://github.com/gsi-cyberjapan/elevation-php/

出典: [国土地理院](https://www.gsi.go.jp/) または [地理院タイル](https://maps.gsi.go.jp/development/ichiran.html)