maemaewaterの日記

エンジニア兼ゲーマーの人の日記です。PHP/Python/JavaScript/C#/C++などによるプログラムに関することを主に書いています。

OpenStreetMapをMapnikでレンダリングして画像ファイルに出力

OpenStreetMapというプロジェクトがあることを知り、綺麗な地図だなと思っていたらMapnikを使ってレンダリングしているよということが書かれており地図画像が出力できたら面白いなということで試してみました。

www.openstreetmap.org

mapnik.org

今回は、Ubuntu (WSL)で試してみました。全体の流れですが、まずは元となる地図のデータを用意する必要があります。そのあとにその地図をレンダリングするMapnikの準備をしていきます。また、Mapnikで地図をレンダリングするときには、地図のスタイルを指定するXMLファイルを用意するのですが、OpenStreetMapで使われているものを今回は利用していく形にします(ものすごく細かい定義が多くされているので最初からつくると本当に大変なことになると思います)。

地図データを用意する

地図データですが、OpenStreetMapによるとミラーサイトからダウンロードして下さいねと書かれているのでミラーサイトからOSM形式のファイルをダウンロードします。

全体: http://download.geofabrik.de/index.html

日本: http://download.geofabrik.de/asia/japan.html

今回は、.osm.bz2 の形式のものをダウンロードしてきました。容量も大きいのでまず試すという目的では一部だけでも大丈夫だと思います。ここでダウンロードしたファイルは解凍する必要はないのでそのままにしておきます(この後利用するツールで自動的に解凍してくれます)。

Mapnikでは .shp 形式のファイルを直接レンダリングすることができるのですが、OpenStreetMapで使用している地図のスタイルの方でPostgreSQLからデータを呼び出すような記述がされています。そのようなことからここでダウンロードしたファイルを直接利用するのではなく、一度PostgreSQLにデータをインポートしていきます。

PostgreSQLのインストールとセットアップ

aptを使ってインストールしていきます。PostGISも使用しているのでこちらもインストールしていきます。

sudo apt install postgresql
sudo apt install postgresql-contrib postgis

完了したら起動(起動していなければ再起動)します。

sudo /etc/init.d/postgresql restart

postgresユーザーとして新しくユーザーとgisという名前のDBを作成します。

createuser -d -U postgres -P {ユーザー名(WSLでのログイン名など)}
createdb --encoding=UTF8 --owner={ユーザー名} gis

次にDBにプラグイン(PostGISとhstore)を導入します。これで、gisのDBでPostGISなどが利用できるようになります。

psql --dbname=gis
CREATE EXTENSION postgis;
CREATE EXTENSION hstore;

これでPostgreSQL関連の作業はおしまいです。

地図データの取り込み

地図を取り込む際にスタイルを定義しているプロジェクト(openstreetmap-carto)のスクリプトを利用しているようですので、まずスタイルのプロジェクトファイルを取得します。

git clone https://github.com/gravitystorm/openstreetmap-carto.git

次に実際にOSM形式のファイルを取り込むためのプログラムであるosm2pgsqlを導入します。aptでインストールができるので、こちらを利用します。

sudo apt install osm2pgsql

インストールができたら次のコマンドでPostgreSQLにデータを取り込みします。時間がかかりますので待ちます。

osm2pgsql --hstore -d gis kansai-latest.osm.bz2  -S openstreetmap-carto/openstreetmap-carto.style --tag-transform-script openstreetmap-carto/openstreetmap-carto.lua -U {作成したPostgreSQLのユーザー名} --cache 1000

メモリが少ない場合は、--slim オプションもつけると良いそうです。

これで地図データの用意はおしまいです。

Mapnikのスタイルファイルを用意する

スタイルファイルの変換

OpenStreetMapで使用されているスタイルファイルを作成しているプロジェクトが、実は先ほどのopenstreetmap-cartoになります。ここで作成されているものをMapnikで読み込めるように変換を行う必要があります。このプログラムがcartoになりますが、node.jsのnpm経由でインストールできます。

aptでnode.jsをインストールします。

sudo apt install nodejs
sudo apt install npm

(WSLの場合はhomebrew経由でインストールした方がパスの問題などが起こらないので楽かもしれません)

npmでcartoをインストールします。

npm install -g carto

(もしWSLのエラーで実行できないときは次を試してみる)
npm install carto

インストールが終わったらopenstreetmap-cartoのパスに移動して変換を実行します。

cd openstreetmap-carto
carto project.mml > mapnik.xml

(もしWSLのエラーで実行できないときは次を試してみる)
../../node_modules/carto/bin/carto project.mml > mapnik.xml (nodemodulesの場所は環境に合わせて変える)
フォントのインストール

aptでインストールします。

sudo apt install fonts-noto-cjk fonts-noto-hinted fonts-noto-unhinted fonts-hanazono ttf-unifont
スタイルから参照されているファイルの準備

実は、変換されたmapnik.xmlの中で.shpファイルの参照が行われています。(他にもopenstreetmap-cartoにあるsymbolsのパスの中のファイルも参照しているのでmapnik.xmlを別のパスで利用するときには注意が必要です。)

このため、mapnik.xmlが置いてあるパスにdataディレクトリを作成してその中にファイルをダウンロードして解凍していきます。ダウンロードするファイルはこちらopensteetmap-cartoのインストールのドキュメントに書かれています。

openstreetmap-carto/INSTALL.md at master · gravitystorm/openstreetmap-carto · GitHub

(今回は直接ダウンロードしてunzipしたのですが、openstreetmap-cartoの scripts/get-shapefiles.py を使うの楽なようです。)

Mapnikの導入からレンダリング

いよいよMapnikを導入します。少し試したところnode.js版が一番楽なのではないかと思いますので、node.js版の説明になります。

node.jsのインストール(インストールを行っていない場合のみ)

aptでインストールします。

sudo apt install nodejs
sudo apt install npm

(WSLの場合はhomebrew経由でインストールした方がパスの問題などが起こらないので楽かもしれません)

mapnikのインストール

npm経由でインストールします。

npm install mapnik
Mapnikを使用してレンダリング(全体)

mapnikを使用して地図全体をレンダリングするコードは次のようになります。

var mapnik = require("mapnik");
var fs = require("fs");
mapnik.register_default_fonts();
mapnik.register_fonts("/usr/share/fonts", {recurse: true});
mapnik.register_default_input_plugins();

var map = new mapnik.Map(1024, 1024);

map.loadSync("mapnik.xml");
map.zoomAll();

map.renderFileSync('map_all.png');

ex1.js というファイル名で保存します。そして次のコマンドで実行します。

node ex1.js

同じパスに map_all.png が出力されていれば動作しています。

f:id:maemaewater:20191005130618p:plain
OpenStreetMapによる地図の全体をレンダリング

(samplesも参考にしました)

Mapnikを使用してレンダリング(拡大)

ex2.js というファイルで次のようなコードを書きます。

var mapnik = require("mapnik");
var fs = require("fs");
mapnik.register_default_fonts();
mapnik.register_fonts("/usr/share/fonts", {recurse: true});
mapnik.register_default_input_plugins();
var map = new mapnik.Map(1024, 1024);

function d2mLat(lat) {
        var y = Math.log(Math.tan((90.0 + lat) * Math.PI / 360)) / (Math.PI / 180.0);
        return y * 20037508.34 / 180.0;
}

function d2m(lon1, lat1, lon2, lat2) {
        return [
                lon1 * (20037508.34 / 180.0),
                d2mLat(lat1),
                lon2 * (20037508.34 / 180.0),
                d2mLat(lat2)
        ];
}

map.loadSync("mapnik.xml");

map.zoomToBox(
        d2m(
                135.738163,
                35.016356,
                135.783160,
                34.981814
        )
);

map.renderFileSync('map_zoom.png');

同じように実行します。

node ex2.js

map_zoom.pngが出力されていれば完了です。

f:id:maemaewater:20191005130650p:plain
OpenStreetMapによる地図を拡大してレンダリング

参考にさせていただいたページ

参考にさせていただいたページ(全体)

wiki.openstreetmap.org qiita.com www.develop-memo.com qiita.com qiita.com qiita.com