3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

緯度、経度をもとに国土地理院タイルを表示する方法(ライブラリ未使用)

Last updated at Posted at 2023-03-21

はじめに

Leafletなど、地図を表示するJavaScriptライブラリを用いると簡単に表示できますが、仕組みを理解するためあえて自力で計算してタイルを表示します。

探してみると、計算方法は見つかるのですが、数学が苦手な僕にはちょっと難しい・・・ので、できるだけ分かりやすい式で作りました。

最後に計算結果の確認をするため、富士山山頂の座標(35.36072, 138.72743)をもとに、タイル画像を表示してみます。

富士山

参考ページ

国土地理院タイルの特徴

  • タイルはメルカトル図法

  • 地図画像は縦横256pixcelの正方形

  • ズームレベル0は全世界を1枚(256×256)で表示する

    • 緯度85度まで(256pxに収まる範囲。発散してしまうので)
    • 左上を原点とする座標系(西経180度北緯85度)
    • 緯度0経度0が画像の中心になる
  • レベル1はそれを縦横2分割(2×2=4枚)、レベル2はさらに2分割(4×4=16枚)

    • レベルが上がるごとに詳細な地図に分割されていく(上限は提供される地図毎に異なる)
  • 参考画像(国土地理院:地理院タイルについて)
    img10.png

タイルのURL

  https://cyberjapandata.gsi.go.jp/xyz/{t}/{z}/{x}/{y}.{ext}
  {t}:データID
  {x}:左上を原点にしたタイルの位置(X)
  {y}:左上を原点にしたタイルの位置(Y)
  {z}:ズームレベル
  {ext}:拡張子
    ex. zoomlevel:6 左上からx方向に57枚目、y方向に23枚目のタイルを取得
    https://cyberjapandata.gsi.go.jp/xyz/std/6/57/23.png

タイルの計算方法

経度

レベル0のタイル(地球一周256px)を元に、zoomlevelをかけて算出する

/**
 * 経度からX方向の座標情報を計算します
 *
 * 指定された経度とズームレベルに基づき、以下の4つの値を返します:
 * - 世界座標系でのX(0〜1の範囲、グリニッジ子午線が0)
 * - ズームレベルを考慮したピクセル単位のグローバルX座標
 * - タイルグリッドにおけるX方向のタイル番号(tileNumberX)
 * - タイル内(256×256ピクセル)でのX方向ピクセル位置(tileInnerX)
 */
export function calcLatToTileX(lng: number, z: number) {
  // 経度(-180〜180)を世界座標系のX(0〜1)に変換
  const worldCoordX = (lng + 180) / 360;

  // ズームレベル0(256×256px)の座標の位置を計算
  let globalPixelX = worldCoordX * 256;

  // ズームレベル(2^z)を考慮した座標を計算
  globalPixelX = globalPixelX * Math.pow(2, z);

  // 1つの画像タイルが256pxなので、256で割って左端からのタイルの枚数(位置)を求める
  const tileNumberX = Math.floor(globalPixelX / 256);

  // 該当タイル内のピクセル位置を算出する(タイル幅合計を引いた余り)
  const tileInnerX = Math.floor(globalPixelX - tileNumberX * 256);

  // 計算した値を返す
  return {
    worldCoordX, // 世界座標(0~1, グリニッジ子午線が原点(0))
    globalPixelX, // ズームレベルを考慮したピクセル単位のY座標
    tileNumberX, // 該当するタイル番号(0オリジン)
    tileInnerX, // タイル内のピクセルY座標(0〜255)
  };
}

緯度

  • レベル0のタイル(北極~南極が256px)を元に、zoomlevelをかけて算出する

/**
 * 緯度からWebメルカトル投影によるY方向の座標情報を計算します。
 *
 * 指定された緯度とズームレベルに基づき、以下の4つの値を返します:
 * - 世界座標系でのY(0〜1の範囲、北極が0)
 * - ズームレベルを考慮したピクセル単位のグローバルY座標
 * - タイルグリッドにおけるY方向のタイル番号(tileNumberY)
 * - タイル内(256×256ピクセル)でのY方向ピクセル位置(tileInnerY)
 *
 * メルカトル図法で緯度から位置を算出する式 (https://qiita.com/Seo-4d696b75/items/aa6adfbfba404fcd65aa)
 *  R ln(tan(π/4 + ϕ/2))
 *    R: 半径
 *    ϕ: 緯度(ラジアン)
 * @param lat - 緯度(-85〜+85度の範囲が推奨)
 * @param z - ズームレベル(0以上の整数)
 * @returns {{
 *   worldCoordY: number,    // 世界座標系Y(0〜1)
 *   globalPixelY: number,   // ズームレベルを考慮したピクセル単位のY座標
 *   tileNumberY: number,    // Y方向のタイル番号(0オリジン)
 *   tileInnerY: number      // タイル内のピクセルY座標(0〜255)
 * }}
 */
export function calcLatToTileY(lat: number, z: number) {
  // 緯度からメルカトル図法の座標に変換して、世界座標系のY(0~1)を計算
  let worldCoordY = latToWorldY(lat);

  // ズームレベル0(256×256px)の座標の位置を計算
  let globalPixelY = worldCoordY * 256;

  // ズームレベル(2^z)を考慮した座標を計算
  globalPixelY = globalPixelY * Math.pow(2, z);

  // 1つの画像タイルが256pxなので、256で割って上端からのタイルの枚数(位置)を求める
  const tileNumberY = Math.floor(globalPixelY / 256);

  // 該当タイル内のピクセル位置を算出する(タイル幅合計を引いた余り)
  const tileInnerY = Math.floor(globalPixelY - tileNumberY * 256);

  // 計算した値を返す
  return {
    worldCoordY, // 世界座標(0~1, 北極側が原点(0))
    globalPixelY, // ズームレベルを考慮したピクセル単位のY座標
    tileNumberY, // 該当するタイル番号(0オリジン)
    tileInnerY, // タイル内のピクセルY座標(0〜255)
  };
}

/**
 * メルカトル図法で投影した位置を元に世界座標系でのY(0〜1、北極が0)を計算する
 *
 * メルカトル図法の式(半径1の球体を想定)
 *   ϕ = (π / 180) * 緯度
 *   y = ln(tan(π/4 + ϕ/2))
 *   ϕ:緯度(ラジアン)
 *   y:投影した位置
 *
 * * 赤道を中心(0)にして、約-85〜+85度の範囲で-π~πの値になる
 * * 高緯度では値が急増し無限大になる
 * * y = ln((tan(ϕ) + 1) / cos(ϕ)) という式もある(同じ値になる)
 *
 * 地図で位置を計算するため、北極側が原点(0)で南極側が1となるように正規化した値を返す
 *
 * @param lat 緯度(-85~85)
 * @returns {number} 世界座標系でのY(0〜1、北極が0)
 */
export function latToWorldY(lat: number) {
  // 経度をラジアンに変換
  const latRad = (Math.PI / 180) * lat;
  // 緯度からメルカトル図法の座標に変換する
  const mercatorY = Math.log(Math.tan(Math.PI / 4 + latRad / 2));

  // -1~1 の範囲になるように調整(-85度~85度の範囲)
  let worldCoordY = mercatorY / Math.PI;

  // 原点(0)を赤道から、北極へ変換(0~1)
  worldCoordY = 1 - (worldCoordY + 1) / 2;

  return worldCoordY;
}

上記を元にタイルの位置を求める

  • データID(地図の種類): std
  • zoomlevel: 10

富士山山頂の座標(35.36072, 138.72743)を入れて計算

座標 タイル タイル内座標
経度 138.72743 906 154
緯度 35.36072 404 89

計算よりURLは下記の通りとなり、タイル画像には富士山が表示されました

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?