3
3

More than 1 year has passed since last update.

地理院タイルの地図を表示する

Posted at

今回は国土地理院が公開している地理院タイルを使ってRubyで地図アプリを作ってみます。

地理院タイルの仕様は「地理院タイルについて」を参考にします。
ざっくり言うとこんな感じになっています。

https://cyberjapandata.gsi.go.jp/xyz/{t}/{z}/{x}/{y}.{ext}
{t}:データID
{x}:タイル座標のX値
{y}:タイル座標のY値
{z}:ズームレベル
{ext}:拡張子

Step1 タイル画像を表示してみる

まずは、標準地図から一つだけダウンロードして表示してみます。

sample1.rb
require 'open-uri'
require 'dxruby'

url = "http://cyberjapandata.gsi.go.jp/xyz/std/7/110/51.png"
file = "51.png"
open(file, 'wb') do |path|
  URI.open(url) do |recieve|
    path.write(recieve.read)
  end
end

image = Image.load(file)

Window.loop do
  Window.draw(0, 0, image)
end

今回はWindows限定でDXRubyを使っていますが、png画像を読み込んで表示できるライブラリあれば何でも良いです。
DxRubyはコマンドラインで

>gem install dxruby

でインストールできます。

以前はこれだけで実行できたのですが、Windows10からDll等が必要になるかもしれません。
https://qiita.com/noanoa07/items/0ce14c2404df38de94b7

実行方法: コマンドラインから

>ruby sample1.rb

で実行すると、ウィンドウが開いて地図画像が一枚表示されます。
DXRuby Application 2022_10_09 20_44_00.png

Step2 タイルを並べてマップっぽく表示する

次に何枚か取得してタイル状に並べて地図っぽくしてみます。
また、ダウンロードした画像はローカルにキャッシュして少しでもサーバーに負荷をかけないようにします(ココ大事)。

sample2.rb
require 'open-uri'
require 'fileutils'
require 'dxruby'

def download(z: 0, x: 0, y: 0)
  url = "http://cyberjapandata.gsi.go.jp/xyz/std/#{z}/#{x}/#{y}.png"
  uri = URI.parse(url)
  file = Dir.pwd + uri.path
  unless FileTest.exist?(file)
    dir = File.dirname(file)
    FileUtils.mkdir_p(dir) unless FileTest.exist?(dir)
    open(file, 'wb') do |path|
      URI.open(uri) do |recieve|
        path.write(recieve.read)
      end
    end
  end
  file
end

images = []
2.times do |y|
  cols = []
  3.times do |x|
    file = download(z: 7, x: 110 + x, y: 51 + y)
    cols << Image.load(file)
  end
  images << cols
end

view_x = 0
view_y = 0

Window.loop do
  view_x += Input.x
  view_y += Input.y

  2.times do |y|
    3.times do |x|
      image = images[y][x]
      Window.draw(view_x + x * 256, view_y + y * 256, image)
    end
  end
end

DXRuby Application 2022_10_10 19_41_27.png

カーソルキーで上下左右にスクロールできるようにしたのですが、最初にダウンロードしたタイルのみしか表示されないので、端にくると真っ黒になっています。

Step3 スクロールに合わせてタイルを表示させる

という訳で今度は画面のスクロールに合わせてリアルタイムに画像をダウンロードするように改良します。
仕組みは最初はスクロールを判定してダウンロードするようにプログラムしていたのですが、どうしてもコードが長く複雑になったので、発想を変えてワールド座標の画面に表示されている部分(カメラ)のみ表示(画像がなかったら取得)するようにしたらシンプルに実装できました。
(その為に一辺が2^ズームレベルの空配列を作っているのでズームレベルが高くなると注意)

sample3.rb
require 'open-uri'
require 'fileutils'
require 'dxruby'

def download(z: 0, x: 0, y: 0)
  url = "http://cyberjapandata.gsi.go.jp/xyz/std/#{z}/#{x}/#{y}.png"
  uri = URI.parse(url)
  file = Dir.pwd + uri.path
  unless FileTest.exist?(file)
    dir = File.dirname(file)
    FileUtils.mkdir_p(dir) unless FileTest.exist?(dir)
    open(file, 'wb') do |path|
      URI.open(uri) do |recieve|
        path.write(recieve.read)
      end
    end
  end
  file
end

MAX_WIDTH = 2 ** 7
MAX_HEIGHT = 2 ** 7
tiles = Array(MAX_HEIGHT * MAX_WIDTH)

view_x = 110 * 256
view_y = 51 * 256

Window.loop do
  view_x += Input.x
  view_y += Input.y

  (Window.height / 256 + 2).times do |j|
    (Window.width / 256 + 2).times do |i|
      y = view_y / 256 + j
      x = view_x / 256 + i
      index = y * MAX_WIDTH + x
      unless tiles[index]
        file = download(z: 7, x: x, y: y)
        tiles[index] = Image.load(file)
      end
      image = tiles[index]
      Window.draw(-(view_x % 256) + i * 256, -(view_y % 256) + j * 256, image)
    end
  end
end

DXRuby Application 2022_10_10 19_42_14.png

これで縦横どこまでもスクロールできるようになりました。
非同期処理をしなくても割とスムーズに表示されます。国土地理院スゴイ
ただこのままだと表示されなくなった画像の削除(ガベージコレクト)はやっていないのでどんどんメモリを食っていくのですが。

という訳でこれだけのコードで地図アプリっぽくなりました。次はズームレベルの変更を実装してみたいです。

3
3
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
3