今回は国土地理院が公開している地理院タイルを使ってRubyで地図アプリを作ってみます。
地理院タイルの仕様は「地理院タイルについて」を参考にします。
ざっくり言うとこんな感じになっています。
https://cyberjapandata.gsi.go.jp/xyz/{t}/{z}/{x}/{y}.{ext}
{t}:データID
{x}:タイル座標のX値
{y}:タイル座標のY値
{z}:ズームレベル
{ext}:拡張子
Step1 タイル画像を表示してみる
まずは、標準地図から一つだけダウンロードして表示してみます。
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
で実行すると、ウィンドウが開いて地図画像が一枚表示されます。
Step2 タイルを並べてマップっぽく表示する
次に何枚か取得してタイル状に並べて地図っぽくしてみます。
また、ダウンロードした画像はローカルにキャッシュして少しでもサーバーに負荷をかけないようにします(ココ大事)。
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
カーソルキーで上下左右にスクロールできるようにしたのですが、最初にダウンロードしたタイルのみしか表示されないので、端にくると真っ黒になっています。
Step3 スクロールに合わせてタイルを表示させる
という訳で今度は画面のスクロールに合わせてリアルタイムに画像をダウンロードするように改良します。
仕組みは最初はスクロールを判定してダウンロードするようにプログラムしていたのですが、どうしてもコードが長く複雑になったので、発想を変えてワールド座標の画面に表示されている部分(カメラ)のみ表示(画像がなかったら取得)するようにしたらシンプルに実装できました。
(その為に一辺が2^ズームレベルの空配列を作っているのでズームレベルが高くなると注意)
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
これで縦横どこまでもスクロールできるようになりました。
非同期処理をしなくても割とスムーズに表示されます。国土地理院スゴイ
ただこのままだと表示されなくなった画像の削除(ガベージコレクト)はやっていないのでどんどんメモリを食っていくのですが。
という訳でこれだけのコードで地図アプリっぽくなりました。次はズームレベルの変更を実装してみたいです。