OSRMのルート検索結果をOpenStreetMapの地図上に描画する

前回はPC上にOSRMのサーバを用意し、ルート検索を実行するまでの流れを確認した。

bannyaaa.hatenablog.jp

今回はその続きとして、OSRMのサーバから返されたルート検索結果を、Leafletを用いてOpenStreetMapの地図上に描画する。

環境

  • OS: Windows 11 Home: 21H2
  • Leaflet: 1.8.0
  • Chrome: 104.0.5112.81
  • OSRMのサーバ周りの環境は前回の記事を参照

地図の表示とルートの描画

ルート描画時に背景となる、OpenStreetMapの地図タイルを表示する仕組みを用意する。LeafletのQuick Start Guideを参考に、以下のHTMLファイルをつくる。

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>OSRM routing</title>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" 
   integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
   crossorigin=""/>
  <script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
   integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
   crossorigin=""></script>
</head>
<body>
  <div id="map" style="height:600px"></div>
  <script>
    // 東京駅と羽田空港が収まる地図の初期表示位置
    let map = L.map('map').setView([35.61, 139.78], 12);
    
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);
    
  </script>
</body>
</html>

地図タイルの表示ができたところに、ルートを描画する仕組みを追加する。OSRMのサーバに対してルート検索のリクエストを実行し、そのレスポンスから抽出したGeoJSON形式のジオメトリをLeafletに渡す処理を書けばよい。上記のHTMLファイルに追記するJavaScriptのコードは以下のとおり。

// ローカルPC上のOSRMサーバで東京駅から羽田空港までのルートを検索するURL
const url = 'http://127.0.0.1:5000/route/v1/driving/139.7681482,35.6798851;139.7845835,35.5491518?geometries=geojson&overview=full'

async function addRouteLayer(){
  const response = await fetch(url);
  const json = await response.json();
  L.geoJSON(json['routes'][0]['geometry']).addTo(map);
}

addRouteLayer();

なお、json['routes'][0]['geometry']とアクセスすることで、以下のGeoJSON形式のジオメトリが抽出できている。

{"coordinates":[[139.768429,35.679767],[139.768491,35.679864],[139.768635,35.68016],[139.769065,35.680895],
...省略...
[139.784785,35.549047],[139.784681,35.549197]],"type":"LineString"}

できあがり

最終的にできあがったHTMLファイルは以下のとおり。

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>OSRM routing</title>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" 
   integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
   crossorigin=""/>
  <script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
   integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
   crossorigin=""></script>
</head>
<body>
  <div id="map" style="height:600px"></div>
  <script>
    // 東京駅と羽田空港が収まる地図の初期表示位置
    let map = L.map('map').setView([35.61, 139.78], 12);
    
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    // ローカルPC上のOSRMサーバで東京駅から羽田空港までのルートを検索するURL
    const url = 'http://127.0.0.1:5000/route/v1/driving/139.7681482,35.6798851;139.7845835,35.5491518?geometries=geojson&overview=full'

    async function addRouteLayer(){
      const response = await fetch(url);
      const json = await response.json();
      L.geoJSON(json['routes'][0]['geometry']).addTo(map);
    }

    addRouteLayer();
    
  </script>
</body>
</html>

ローカルPC上でOSRMのサーバを立ち上げた状態で、このHTMLファイルをChromeで開くと、東京駅から羽田空港までのルートが青線で描画されていることが確認できる。

© OpenStreetMap contributors

OSRMのサーバを立ち上げてルート検索を行う

OpenStreetMapのデータをもとにルート検索を行える、OSSのルート検索エンジンの1つに、OSRM (Open Source Routing Machine, http://project-osrm.org/) がある。今回は、PC上にOSRMのサーバを用意して、ルート検索を行う。

環境

  • OS: Ubuntu 20.04 LTS (Windows 11 Home: 21H2のWSL2にインストール)
  • Docker Engine: 20.10.7 (上記のUbuntuにインストールしたCommunity Edition)
  • OSRM: 5.26.0

ルート検索の準備

OSRMのサーバをPC上に立てる。OSRMのサービスを構成する複数のソフトウェアがGithubで公開されているが、今回はそのなかからルート検索エンジンの本体にあたるosrm-backendを使う。osrm-backendのリポジトリのQuick StartにDockerコンテナを用いたサーバ構築手順が書かれており、その流れに沿って作業をする。

はじめにGeofabrikからOpenStreetMapのデータを取得する。Quick Startの手順ではベルリンのデータをダウンロードしているが、ここでは日本の関東地方のデータをダウンロードする。

$ wget https://download.geofabrik.de/asia/japan/kanto-latest.osm.pbf

次に、ダウンロードしたOpenStreetMapのデータを加工する。OSRMでは、ルート検索の計算プロセスとして「Contraction Hierarchies (CH)」と「Multi-Level Dijkstra (MLD)」の2種類が用意されている。どちらの計算プロセスを選択しても、ルート検索の処理速度を向上させるために事前にデータ加工を行う必要がある。Quick Startには、巨大な距離行列 (どの程度の大きさか具体的な指標は書かれていないが) を扱うような場合を除いて、基本的にMLDを使うのがおすすめと書かれている。Quick Startの手順もMLDを使ったものになっているので、ここでもMLDを使うことにする。

先ほどダウンロードしたOpenStreetMapのデータの置かれているディレクトリで、以下のMLD用のデータ加工のコマンドを実行する。

$ docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-extract -p /opt/car.lua /data/kanto-latest.osm.pbf
$ docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-partition /data/kanto-latest.osrm
$ docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-customize /data/kanto-latest.osrm

実行結果として、以下のファイルが生成される。

$ ls kanto-latest.osrm*

kanto-latest.osrm                   kanto-latest.osrm.enw                 kanto-latest.osrm.properties
kanto-latest.osrm.cell_metrics      kanto-latest.osrm.fileIndex           kanto-latest.osrm.ramIndex
kanto-latest.osrm.cells             kanto-latest.osrm.geometry            kanto-latest.osrm.restrictions
kanto-latest.osrm.cnbg              kanto-latest.osrm.icd                 kanto-latest.osrm.timestamp
kanto-latest.osrm.cnbg_to_ebg       kanto-latest.osrm.maneuver_overrides  kanto-latest.osrm.tld
kanto-latest.osrm.datasource_names  kanto-latest.osrm.mldgr               kanto-latest.osrm.tls
kanto-latest.osrm.ebg               kanto-latest.osrm.names               kanto-latest.osrm.turn_duration_penalties
kanto-latest.osrm.ebg_nodes         kanto-latest.osrm.nbg_nodes           kanto-latest.osrm.turn_penalties_index
kanto-latest.osrm.edges             kanto-latest.osrm.partition           kanto-latest.osrm.turn_weight_penalties

OSRMのサービスを5000番ポートで起動すれば、ルート検索を実行できるようになる。

$ docker run -t -i -p 5000:5000 -v "${PWD}:/data" osrm/osrm-backend osrm-routed --algorithm mld  /data/kanto-latest.osrm

ルート検索の実行

OSRMのサーバが用意できたので、ルート検索を試してみる。検索対象は、東京駅の八重洲口から羽田空港の第1ターミナルまでの道のりとする。

OSRMのサーバの使い方は、OSRMのAPIドキュメント(2022/07/28時点でドキュメントのバージョンはv5.24.0)に書かれている。OSRMのサーバは複数のサービスを提供しており、今回はそのなかから、2点間の最速ルートを探すRoute serviceを使う。APIドキュメントのRoute serviceの箇所を参照して、OSRMのサーバに送るリクエストを準備する。

Route serviceのリクエストは、出発地点と到着地点それぞれの緯度経度が必須のパラメータとなる。出発・到着地点の緯度経度の取得方法として、OpenStreetMapのWebサイトで提供されている地物検索の機能を使い、それぞれの地点付近の適当なノードを探すアプローチをとる。

出発地点の東京駅八重洲口は、ID: 6397151768のノードの緯度経度35.6798851, 139.7681482を設定する。

© OpenStreetMap contributors

到着地点となる羽田空港第1ターミナルは、ID: 2206530973のノードの緯度経度35.5491518, 139.7845835を設定する。

© OpenStreetMap contributors

また、ルート検索結果を地図上に可視化するときなどにデータの扱いが便利になるため、以下のオプションをリクエストに付与する。

  • 検索結果のルートのジオメトリをgeojson形式で取得するためのgeometries=geojson
  • 検索結果のルートのジオメトリを詳細な部分まで取得するためのoverview=full

出発・到着地点の緯度経度 (「経度,緯度」の順番にする必要がある点に注意) と追加のオプションを設定したリクエストを、自身のPC上で起動したOSRMサーバに送る。

$ curl "http://127.0.0.1:5000/route/v1/driving/139.7681482,35.6798851;139.7845835,35.5491518?geometries=geojson&overview=full" 

以下のようなレスポンスが返ってくれば、ルート検索が実行できている。

{"code":"Ok","routes":[{"geometry":{"coordinates":[[139.768429,35.679767],[139.768491,35.679864],[139.768635,35.68016],
...省略...
[139.784681,35.549197]],"type":"LineString"},"legs":[{"steps":[],"distance":21800,"duration":1230.7,"summary":"","weight":1230.7}],"distance":21800,"duration":1230.7,"weight_name":"routability","weight":1230.7}],"waypoints":[{"hint":"SwJCgP___3-0AAAA0gAAAAAAAABVAAAAL4OWQkw1QkEAAAAAQ1YNQrQAAADSAAAAAAAAAFUAAADvAwAAbbJUCBduIAJUsVQIjW4gAgAAvwHZCL3c","distance":28.611262,"name":"","location":[139.768429,35.679767]},{"hint":"bjgzgP___38RAAAARgAAAAAAAAAAAAAAigmZQbnlaUIAAAAAAAAAABEAAABGAAAAAAAAAAAAAADvAwAA6fFUCA1wHgKI8VQI4G8eAgAArxTZCL3c","distance":10.116086,"name":"","location":[139.784681,35.549197]}]}

Pythonのスレイピングで国土数値情報の将来推計人口データをダウンロードする

概要

GISホームページ内の国土数値情報ダウンロードサービスにおいて、「1kmメッシュ別将来推計人口データ(H30国政局推計)」(以下、推計人口データ)が提供されている。 Pythonで推計人口データのWebページをスクレイピングして、ファイルを機械的にダウンロードしたい。

環境

  • Windows 11 Home: 21H2
  • Python: 3.9.0
    • requests: 2.26.0
    • beautifulsoup4: 4.10.0

アプローチの検討

推計人口データは以下のとおり都道府県別にファイルをダウンロードできるようになっている。

出典:https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-mesh1000h30.html

スクレイピングのアプローチを考えるため、ファイルをダウンロードする仕組みがどのようなHTMLソースで実現されているかをChromeでみてみる。

HTMLソースをみると、以下のような<tr>タグの単位で1つの都道府県が構成されている(これは北海道の例)ので、この中の要素を調べることにする。

<tr>
    <td class="bgc1" id="prefecture01">北海道</td>
    <td class="txtCenter">世界測地系</td>
    <td class="txtCenter">平成30年</td>
    <td class="txtCenter">9.70MB</td>
    <td class="txtCenter">1km_mesh_suikei_2018_shape_01.zip</td>
    <td class="txtCenter">
        <a class="waves-effect waves-light btn indigo btn_padding" id="menu-button" onclick="javascript:DownLd('9.70MB','1km_mesh_suikei_2018_shape_01.zip','/ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_01.zip' ,this);">
            <span id="1km_mesh_suikei_2018_shape_01.zip-open" style="display: block">
                <i class="material-icons">file_download</i>
            </span>
            <span id="1km_mesh_suikei_2018_shape_01.zip-close" style="display: none">
                <i class="material-icons">star</i>
            </span>
        </a>
    </td>
</tr>

真ん中あたりにある<a>タグのonclick属性でJavaSciptの関数を呼んでいるところに注目すると、引数の1つにファイルのパスらしき文字列 /ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_01.zipが見える。

Webページのドメインhttps://nlftp.mlit.go.jpをそのパスらしき文字列のあたまにつけ、https://nlftp.mlit.go.jp//ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_01.zipとしたURLにアクセスすると、対象のファイルがダウンロードできる。つまり、国土数値情報ダウンロードサービスで提供されている推計人口データは、ファイルのパスをHTMLソースのなかから特定できれば、JavaScriptを実行しなくても目当てのファイルが取得できることになる。

PythonでWebページのスクレイピングを行う場合には、JavaScirptの実行が必要かどうかで使うライブラリが異なる。JavaScriptを実行して動的に取得するコンテンツを対象にスクレイピングを行う場合には、Pythonからブラウザを制御するライブラリとしてSeleniumを使う必要がある。一方、今回のようにJavaScriptの実行が不要な静的なWebページのスクレイピングでは、Seleniumを使う以外にrequests + Beautiful Soupを使うことでも対応できる。ここでrequestsはWebページを取得するためのHTTPリクエストの実行、Beautiful SoupはHTMLのタグを解析してPythonから個々の要素へアクセスしやすくするといった役割をそれぞれ担う。

ファイルダウンロードの実装

推計人口データのWebページをスクレイピングし、すべての都道府県分のファイルをダウンロードするプログラムをつくる。

使用するライブラリをインストールしておく。

> py -m pip install requests beautifulsoup4

はじめに、推計人口データのWebページのHTMLソースを取得する。

import requests 

req = requests.get('https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-mesh1000h30.html')

取得したHTMLソースの中身を解析して、Beautiful Soupオブジェクトにする。Beautiful Soupオブジェクトにすることで、タグ名を指定して要素を抽出(find_all()メソッド)したり、抽出した要素の子要素(たとえば<tr>の中の<td>)にfor文で1つずつアクセスしたりといったことができるようになる。

from bs4 import BeautifulSoup

soup = BeautifulSoup(req.text, 'html.parser')

今回はファイルパスが書かれている箇所、つまり以下の<a>タグのonclick属性の値をすべての都道府県について取得したい。

<a class="waves-effect waves-light btn indigo btn_padding" id="menu-button" onclick="javascript:DownLd(<ファイルパスが書かれている>);"> ... </a>

そのためにHTMLソースからid属性の値がmenu-buttonである<a>タグの要素を抽出し、その抽出結果からonclick属性の値を取り出す。

anchors = soup.find_all('a', attrs = { 'id' : 'menu-button'})
for anchor in anchors:
    print(anchor['onclick'])

print()の結果を見ると、以下のようにJavaScript関数の呼び出し部分が文字列として都道府県ごとに抽出できていることがわかる。

javascript:DownLd('9.70MB','1km_mesh_suikei_2018_shape_01.zip','/ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_01.zip' ,this);
javascript:DownLd('2.39MB','1km_mesh_suikei_2018_shape_02.zip','/ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_02.zip' ,this);
javascript:DownLd('4.11MB','1km_mesh_suikei_2018_shape_03.zip','/ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_03.zip' ,this);
...

次に、都道府県ごとに得られたJavaScript関数の呼び出し文字列の中から、ファイルパスを抽出してファイルをダウンロードする処理を考える。

得られた文字列の中から、関数の3番目の引数にあたるファイルパスの箇所を抽出するために正規表現を用いる。 ファイルパスを抽出するためには、「スラッシュ/ではじまり.zipで終わる」文字列を最短マッチ(できるだけ短い範囲でマッチ)するパターンをつくればよさそうである。 Python正規表現を扱う標準ライブラリreを用いて、先ほど取得したJavaScript関数の呼び出し文字列の1件目を例にしてファイルパスが抽出できるか確認する。

import re

pattern = re.compile(r'/.+?\.zip')
match = pattern.search("javascript:DownLd('9.70MB','1km_mesh_suikei_2018_shape_01.zip','/ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_01.zip' ,this);")
print(match.group())

結果は以下のとおり、期待したファイルパスが抽出できた。

/ksj/gml/data/m1kh30/m1kh30-18/1km_mesh_suikei_2018_shape_01.zip

抽出したファイルパスにWebページのドメイン部分の文字列を結合してURLをつくり、ファイルをダウンロードする。ダウンロード後のファイル名はファイルパスから抽出する。

import os

dl_file = 'https://nlftp.mlit.go.jp' + match.group()
req_content = requests.get(dl_file).content
file_name = os.path.basename(dl_file)

with open(file_name ,mode='wb') as f:
  f.write(req_content)

処理をまとめたプログラム

ここまでの処理をまとめたプログラムは以下のとおり。追加でダウンロードファイルの保存先フォルダを設定するようにしている。なお、すべての都道府県のファイルをダウンロードする際にサーバに負荷をかけないよう、time.sleep()で少なくとも1秒の間隔をあけて次のファイルをダウンロードするようにする。

import requests
import re
import os
import time
from bs4 import BeautifulSoup

dl_dir = 'C:/PATH/TO/DOWNLOAD/'
target_domain = 'https://nlftp.mlit.go.jp'
target_page = target_domain + '/ksj/gml/datalist/KsjTmplt-mesh1000h30.html'

req = requests.get(target_page)
soup = BeautifulSoup(req.text, 'html.parser')
anchors = soup.find_all('a', attrs = { 'id' : 'menu-button'})

for anchor in anchors:
    pattern = re.compile(r'/.+?\.zip')
    match = pattern.search(anchor['onclick'])
    file_url = target_domain + match.group()
    
    req_content = requests.get(file_url).content
    dl_file = dl_dir + os.path.basename(file_url)
    
    with open(dl_file ,mode='wb') as f:
        f.write(req_content)
    
    time.sleep(1)

Dockerで地理空間パッケージ入りRStudioを動かす

R, RStudioをDockerコンテナとして提供するRockerプロジェクトがある。 そのプロジェクトの派生の1つとして、gstatsfといった地理空間データを扱うためのパッケージを含んだコンテナイメージがrocker/geospatialとして提供されている。

コンテナイメージの詳細や、パッケージのリストはこちらに記載されている。

使い方

コンテナイメージを取得する。

> docker pull rocker/geospatial

コンテナを実行する。

> docker run -e PASSWORD=mypassword --rm -p 8787:8787 rocker/geospatial

ブラウザでhttp://localhost:8787にアクセスするとRStudioのログイン画面が表示される。UsernamerstudioPasswordmypasswordとして設定したパスワード を入力して Sign In ボタンを押すと、以下のようにRStudioが使える。

f:id:bannyaaa:20211130002230p:plain

実際に使う場面では、データの読み書きのために-vオプションをつけてローカルPCのフォルダをコンテナと共有する機会が多そう。

> docker run -e PASSWORD=mypassword --rm -p 8787:8787 -v C:/PATH/TO/LOCAL_FOLDER:/home/rstudio rocker/geospatial

環境

  • Windows 10 Home: 20H2
  • Docker Desktop: 4.2.0
  • Docker Engine: 20.10.10

LeafletでOpenStreetMapのラスタ地図タイルを表示する

bannyaaa.hatenablog.jp

前回の「OpenStreetMapのタイルサーバをDockerで構築する」では、タイルサーバにアクセスして地図画像が1点取得できるところまで確認した。

地図画像を配信するサーバ側の仕組みはできたので、次は地図画像を並べてズームやスクロールして見るためのクライアント側の仕組みをつくる。 地図画像を並べて地図タイルとして表示するためのJavaScriptライブラリには、Leaflet, OpenLayers, MapLibre GL JSなどがあり、今回はLeafletを使う。

環境

  • Windows 10 Home: 20H2
  • Docker Desktop: 4.0.0
  • Docker Engine: 20.10.8
  • Google Chrome: 93.0.4577.82 (64 bit)

準備

ローカルPCで地図タイルを提供するための、Dockerのタイルサーバコンテナを立ち上げておく1

> docker run -p 80:80 -v openstreetmap-data:/var/lib/postgresql/12/main -d overv/openstreetmap-tile-server run

地図タイルの配信元のURLは、overv/openstreetmap-tile-serverコンテナイメージのGithubページにかかれている。 Githubにかかれた手順では8080ポートを公開しているが、今回のコンテナ実行時には80ポートを公開しているため、地図タイルのURLはhttp://localhost/tile/{z}/{x}/{y}.pngとなる。

ローカルPC上にHTMLファイルで地図タイルを表示する

地図タイルを表示する1つ目のアプローチとして、Switch2OSMのUsing a Docker containerページ内のViewing tilesに書かれた手順を試す。

こちらのGithubにおかれたsample_leaflet.htmlをローカルPC (Windows 10)に保存する。

> wget https://raw.githubusercontent.com/SomeoneElseOSM/mod_tile/switch2osm/extra/sample_leaflet.html
 -O sample_leaflet.html

次にsample_leaflet.htmlを編集する。 以下のように、地図タイル配信元のURLのhottileに、(これは必須ではないが)今回データを登録した日本の関東地方をズームレベル10で初期表示するようにする。

var map = L.map('map').setView([35.68, 139.69], 10);

L.tileLayer('http://127.0.0.1/tile/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

sample_leaflet.htmlをブラウザで開き地図タイルが表示されれば成功2

© OpenStreetMap contributors

Dockerコンテナ内のHTMLファイルで地図タイルを表示する

地図タイルを表示するための別のアプローチとして、overv/openstreetmap-tile-serverコンテナイメージのGithubのREADME.mdにのっている手順を試す。

手順では、Dockerのタイルサーバコンテナを実行後に、http://localhost:8080にアクセスすればよいと書かれている。 タイルサーバコンテナの準備のところにも書いたが、今回はポートの割り当てオプション-p 80:80としているため、アクセス先はhttp://localhost:80 (http://localhost)となる。

ブラウザでhttp://localhost:80を開き、地図タイルが表示されることを確認する。

© OpenStreetMap contributors

このアプローチでは、コンテナ内の/var/www/htmlにおかれたindex.htmlにブラウザはアクセスしている。 index.htmlでLeafletから地図タイル配信のURLを参照することにより、ブラウザで地図タイルが表示されている。

コンテナの中に入ってindex.htmlの中身を確認する場合は、以下を実行すればよい。

> docker ps  # タイルサーバコンテナの名前を確認
> docker exec -it <コンテナ名> /bin/bash
# cat /var/www/html/index.html

  1. タイルサーバコンテナの中では、Apache, PostgreSQL, 地図タイルを作成するrenderdといったプロセスが動く。

  2. 初めて表示する地域・ズームレベルは、地図画像の生成に時間がかかる。

OpenStreetMapのタイルサーバをDockerで構築する

OpenStreetMapの地図タイル配信サーバ(ラスタ形式)をDockerを使ってWindows PC上にたてる。

環境

Windows 10でDocker Desktop (WSL 2バックエンド) がインストール済み。

  • Windows 10 Home: 20H2
  • Docker Desktop: 3.5.2
  • Docker Engine: 20.10.7

参考

タイルサーバを構築する手順

1. コンテナイメージの取得

Docker Hubからイメージを取得する。

> docker pull overv/openstreetmap-tile-server
> docker images

2. OpenStreetMapのデータ取得

OpenStreetMapのデータを配信しているWebサイト(今回はGeofabrik)から、.osm.pbf形式のOpenStreetMapデータをダウンロードする。Switch2OSMの手順ではザンビアのデータを使っているが、ここでは日本の関東地方をダウンロードする。(Windows 10のPower Shellで作業を行うため、wgetコマンドのオプションがSwitch2OSMの手順と異なる点に注意)

> wget https://download.geofabrik.de/asia/japan/kanto-latest.osm.pbf -OutFile "C:/path/to/osm/data/kanto-latest.osm.pbf"

3. Dockerのボリューム作成

コンテナが削除されてもデータの格納先となるPostgreSQLが保持されるよう、ボリュームを作成する。

> docker volume create openstreetmap-data
> docker volume ls

4. データベースの構築とインポート

overv/openstreetmap-tile-serverコンテナをimportパラメータで実行し、PostgreSQLデータベースの構築とデータのインポートを行う

> docker run -v C:/path/to/osm/data/kanto-latest.osm.pbf:/data.osm.pbf -v openstreetmap-data:/var/lib/postgresql/12/main overv/openstreetmap-tile-server import

PostgreSQLデータベースの構築に関する補足

  • 上記のコマンドをそのまま実行すると、データベース名はgis、ユーザ名 / パスワードはともにrendererとなる
  • コマンドの完了時にコンテナは停止し、PostgreSQLも停止状態になる

5. タイルサーバの起動

overv/openstreetmap-tile-serverコンテナを今度はrunパラメータでバックグラウンド実行し、OpenStreetMapのタイルサーバを起動する。

> docker run -p 80:80 -v openstreetmap-data:/var/lib/postgresql/12/main -d overv/openstreetmap-tile-server run

6. タイルの表示確認

ブラウザで以下のURLにアクセスし世界地図の画像が表示されることを確認する。

http://localhost/tile/0/0/0.png

f:id:bannyaaa:20210814012931p:plain
© OpenStreetMap contributors

Windows 10でDockerコンテナのPostgreSQLを動かす(3)

前回、psqlでコンテナで実行中のPostgreSQLに接続しようとして失敗したところからの続き。

bannyaaa.hatenablog.jp

環境

Windows 10でDocker Desktop (WSL 2バックエンド) がインストール済み。

  • Windows 10 Home: 20H2
  • Docker Desktop: 3.5.2
  • Docker Engine: 20.10.7

psqlでデータベースに接続する(成功)

psqlによるデータベースへの接続について、PostgreSQL公式イメージのページに記載されていた、ネットワークを新規に作成するアプローチ以外にも方法がないか調べた。 結果的に、以下の3通りの方法でpsqlからPostgreSQLに接続できそうであることがわかり、実際に試してみた。

  1. psql実行コンテナから、同一ネットワーク内のデータベースコンテナに接続する。(前回失敗した方法)
  2. データベースを実行中のコンテナにアタッチし、コンテナの中からpsqlで接続する。
  3. データベースコンテナ実行時にコンテナからホストにポートを割り当て、ホストからpsqlで接続する1

1. 同一ネットワーク内のコンテナ間通信で接続する

まずはじめにコンテナをつなぐネットワークを作成しておく。

> docker network create some-network
> docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
de7e1d508a4e   bridge         bridge    local
8c5b594cdf11   host           host      local
9add3ef94360   none           null      local
13730a030319   some-network   bridge    local

PostgreSQLデータベース用とpsql実行用の2つのコンテナを、作成したネットワークsome-networkに接続する形で順番に実行する。psqlコマンドの接続先のホスト名には、PostgreSQLデータベース用コンテナの名前であるsome-postgres1を指定する。

> docker run --name some-postgres1 --network some-network  -e POSTGRES_PASSWORD=mysecretpassword -d postgres
> docker run -it --rm --network some-network postgres psql -h some-postgres1 -U postgres
Password for user postgres:
psql (13.3 (Debian 13.3-1.pgdg100+1))
Type "help" for help.

postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

2. 実行中のコンテナへのアタッチで接続する

実行中のコンテナでコマンドを実行できるdocker execコマンドを用いる。

はじめにPostgreSQLデータベースのコンテナを実行し、続いてdocker execコマンドでPostgreSQLデータベースコンテナを指定してbashを実行する。

> docker run --name some-postgres2 -e POSTGRES_PASSWORD=mysecretpassword -d postgres
> docker exec -it some-postgres2 /bin/bash

するとPostgreSQLデータベースコンテナの中に入れるので、postgresユーザへ切り替えてからpsqlを起動し、localhostのデータベースに接続すればよい。

root@8f64d61fc808:/# su - postgres
postgres@8f64d61fc808:~$ psql -h localhost -U postgres
postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

3. コンテナからホストへのポート割り当てで接続する

コンテナのポートに対してホストからは基本的に通信することができない。ただし、docker runコマンド実行時に-pもしくは--publishオプションを指定し、-p <ホストのポート>:<コンテナのポート>とすることでコンテナのポートをホストに割り当てて通信することができるようになる。

コンテナのポート5432をホストの54321に割り当てて、PostgreSQLデータベース用コンテナを実行する。

> docker run -p 54321:5432 --name some-postgres3 -e POSTGRES_PASSWORD=mysecretpassword -d postgres

ホストにインストール済みのpsqlを、localhostのポート54321を指定して起動する。

> psql -p 54321 -h localhost -U postgres
Password for user postgres:
psql (13.3)
Type "help" for help.

postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

ちなみにコンテナからホストへのポート割り当て状況は、docker portコマンドで確認できる。

> docker port some-postgres3
5432/tcp -> 0.0.0.0:54321
5432/tcp -> :::54321

参考


  1. ホストにpsqlのインストールが必要 (今回はPostgreSQL 13.3のインストール時にCommand Lineオプションにチェックして導入)