Mapboxからタイル画像を取得して一枚の画像に合成する

LabsのツールではなくPythonから直接Mapboxから画像をダウンロードする方法。この方法を使うと解像度に限度がなくなる。

環境の準備

Mapboxのアカウント登録して、キーを取得しておく

Python本体のインストール
※インストール時に「Add Python to PATH」をチェックしておく

以下のパッケージをインストールしておく

Pillow:画像処理ライブラリ
Mercantile:地理座標系のライブラリ
Requests:HTTPライブラリ

コマンドプロンプトを開き、以下のコードを順に実行してインストールしていく

pip install pillow
pip install mercantile
pip install requests

コード

mapbox_combine_tile_images.pyのようなテキストファイルを適当に作成し、中身にコードを書く。

コードの流れ

・座標(緯度・経度)と解像度を設定する
・座標から該当するタイル番号を取得する
・必要なタイル数と番号を計算する
・Mapboxサーバーからタイル画像を取得する
・タイル画像を1枚にまとめる

#
# Mapboxからタイル画像を取得して一枚の画像に合成する
#
import math
access_token = 'ここにmapboxのキーを入力する'

# 緯度経度、解像度、タイル数を入力する
lat_lng = [36.17106700090759, 138.17289817315498]
z = 14          # 解像度(1:地球を2(=2^1)分割、2:地球を4(=2^2)分割・・16:地球を65536(=2^16)分割)
size_tile = 16   # タイル数

print('lat: ' + str(lat_lng[0]))
print('lng: ' + str(lat_lng[1]))

# 該当するタイル番号を取得する
import mercantile
center_tiles = mercantile.tile(lat_lng[1], lat_lng[0], z)

# タイルの範囲を設定する
x_tile_range = [int(center_tiles.x - size_tile/2 + 1),int(center_tiles.x + size_tile/2)]
y_tile_range = [int(center_tiles.y - size_tile/2 + 1),int(center_tiles.y + size_tile/2)]

#
# 緯度からタイルの長さを計算する
#
R = 6378.137    # 地球の半径(km)
C = R * 2 * math.pi * 1000  # 赤道の円周
rad = math.pi/2 * (lat_lng[0]/90.0)     # 緯度をラジアンの角度へ
S = C * math.cos(rad) / math.pow(2, z)  # 緯度における円周を地図の解像度で割り、タイルあたりの長さを求める

print('Zoom: ' + str(z))
print('Rezolution: ' + str(size_tile * 512) + 'px x '+ str(size_tile * 512)+'px')
print('width(m): ' + str(S * size_tile) + '(m) x ' 'height(m): ' + str(S * size_tile) + '(m)')
print('tile width(m): ' + str(S) + '(m)')
print('pixel width(m): ' + str(S/512) + '(m)')

# 左の座標を計算する
bounds_west_north = mercantile.bounds(x_tile_range[0], y_tile_range[0], z)
bounds_east_south = mercantile.bounds(x_tile_range[1], y_tile_range[1], z)

print('')
print('west:' + str(bounds_west_north.west))
print('east:' + str(bounds_east_south.east))
print('north:' + str(bounds_west_north.north))
print('south:' + str(bounds_east_south.south))

print('')
print('Tile X Range: ' + str(x_tile_range))
print('Tile Y Range: ' + str(y_tile_range))
print('Tiles: ' + str(x_tile_range[1] - x_tile_range[0] + 1) + ' x ' + str(y_tile_range[1] - y_tile_range[0] + 1))

print("Press Any Key to Continue...")
input()

#
# Mapboxサーバーから衛星画像のタイルを取得する
#
import requests
import shutil
import os

# ディレクトリが存在していなければ作成する
if not os.path.exists("elevation_images"):
    os.makedirs("elevation_images")

if not os.path.exists("satellite_images"):
    os.makedirs("satellite_images")

if not os.path.exists("composite_images"):
    os.makedirs("composite_images")

# テキスト書き出し
file = open('composite_images\\' + 'z' + str(z) + '.txt', 'w')
file.write('Zoom: ' + str(z) + "\n")
file.write('Rezolution: ' + str(size_tile * 512) + 'px x '+ str(size_tile * 512)+'px' + "\n")
file.write('width(m): ' + str(S * size_tile) + '(m) x ' 'height(m): ' + str(S * size_tile) + '(m)' + "\n")
file.write('west:' + str(bounds_west_north.west) + "\n")
file.write('east:' + str(bounds_east_south.east) + "\n")
file.write('north:' + str(bounds_west_north.north) + "\n")
file.write('south:' + str(bounds_east_south.south) + "\n")
file.close

# タイル画像をダウンロードしていく処理
for i,x in enumerate(range(x_tile_range[0],x_tile_range[1]+1)):
    for j,y in enumerate(range(y_tile_range[0],y_tile_range[1]+1)):
        print(x,y)

        # ハイトマップを取得する
        r = requests.get('https://api.mapbox.com/v4/mapbox.terrain-rgb/'+str(z)+'/'+str(x)+'/'+str(y)+'@2x.pngraw?access_token='+access_token, stream=True)
        with open('./elevation_images/' + str(i) + '.' + str(j) + '.png', 'wb') as f:
            r.raw.decode_content = True
            shutil.copyfileobj(r.raw, f)  
        
        # 衛星画像を取得する
        r = requests.get('https://api.mapbox.com/v4/mapbox.satellite/'+str(z)+'/'+str(x)+'/'+str(y)+'@2x.png?access_token='+access_token, stream=True)
        with open('./satellite_images/' + str(i) + '.' + str(j) + '.png', 'wb') as f:
            r.raw.decode_content = True
            shutil.copyfileobj(r.raw, f)

print("Press Any Key to Continue...")
input()

#
# 取得した画像を合成して一枚の大きな画像にする
#
import math
from PIL import Image
from os import listdir
from os.path import isfile, join

for img_name in ['elevation','satellite']:
    # ディレクトリ内の画像ファイルのリストを取得
    image_files = ['./'+img_name+'_images/' + f for f in listdir('./'+img_name+'_images/')]

    images = [Image.open(x) for x in image_files]

    edge_length_x = x_tile_range[1] - x_tile_range[0] + 1
    edge_length_y = y_tile_range[1] - y_tile_range[0] + 1
    edge_length_x = max(1,edge_length_x)
    edge_length_y = max(1,edge_length_y)
    width, height = images[0].size

    total_width = width * edge_length_x
    total_height = height * edge_length_y

    composite = Image.new('RGB', (total_width, total_height))

    y_offset = 0
    for i in range(0,edge_length_x):
        x_offset = 0
        for j in range(0,edge_length_y):
            tmp_img = Image.open('./'+img_name+'_images/' + str(i) + '.' + str(j) + '.png')
            composite.paste(tmp_img, (y_offset,x_offset))
            x_offset += width
            
        y_offset += height

    composite.save('./composite_images/'+img_name+'_'+str(int(S * size_tile))+'m'+'_z'+str(z)+'.png')

print("Press Any Key to Finish...")
input()

タイル画像は縦横512pxとなる。これを踏まえてタイル数と解像度を調整する。

satellite_imagesフォルダに衛星画像タイルがダウンロードされ

elevation_imagesにはTerrain-RGB画像がダウンロードされる。

そしてcompoiste_imagesにタイル画像を一枚に合成した画像が保存される。

Mapboxのハイトマップから地形を生成するへ続く

タイトルとURLをコピーしました