1. この記事について
やりたいこと
任意のスタイルの地図を作成して動画や印刷物に使用したい
やったこと
OpenStreetMapデータとCartoCSSスタイルからMapnikを使って地図画像を作成しました
この記事ではその手順について解説します
2. 技術選定
PCで地図を扱う場合には以下の2つのパターンがあると思います
- ブラウザで閲覧するユーザー向けにズーム可能な地図アプリを提供する
- 静止画として作成済みの地図画像をサイトや印刷物などに表示する
今回やりたいのは2です
2-1. ズーム可能な地図アプリを提供する
Google Mapsのように、縮尺別の地図タイルを動的に取得して並べて表示する方式です。オープンソースの選択肢としてはleafletがよく用いられていますが、有償サービスとしてGoogle Maps JavaScript API, MapBox APIなども選択できます。この方式では地図タイルを動的に取得しながら表示していく為、軽量な地図タイルを作成しておいてタイルサーバーで効率的に配信する必要があり、その運用コストを考えると既存の地図タイルを利用するインセンティブが大きくなりますが、自前でタイルを作成することもできます
生データから地図タイルを作成する手順
OpenStreetMapの生データから地図タイルを作成して表示する場合、以下のような手順になります
- 地理データを準備する(OpenStreetMap生データなど)
- DBに地理データを展開する(PostGIS, geopackagesなど)
- 地図描画の為のスタイルを用意する(cartoCSSなど)
- 地図タイルをレンダリングする(mapnikなど)
- 地図タイルを並べて地図を表示する(leaflet, QGISなど)
2-2. 地図画像を作成する
印刷物に貼り付けるなどの用途であれば画像として出力できれば良いので選択できる方法が増えます。データソースとしては地図タイルを使っても良いですし、地理データとスタイルからレンダリングすることも出来ます。地図作成も、Mapnikなどの地図レンダラーを使う方法、GUIで操作できるGIS(Geographic Information System)を使う方法、Google MapsなどのAPIとして提供されている地図画像生成機能を使う方法などが選択できます
生データから地図を作成する手順
先ほどの手順とほぼ同じですが、タイルが不要なので直接地図をレンダリングできます
- 地理データを準備する(OpenStreetMap PBFファイルなど)
- DBに地理データを展開する(PostGIS, geopackagesなど)
- 地図描画の為のスタイルを用意する(cartoCSSなど)
- 地図画像をレンダリングする(mapnik, QGISなど)
2-3. 最終的に何を選定したか
今回はOpenStreetMap PBFファイル、PostGIS、cartoCSS, Mapnikで地図画像を作成します
ほぼ同じ手順で地図タイルを作成することもできます
3. 基本手順
3-1. OpenStreetMap地理データを入手する
OpenStreetMapはpbf(Protocolbuffer Binary Format)で地理データを公開していますが、QGISはこれをそのまま読み込んで表示することが出来ます。このpbfファイルはすべての情報を持っているので色々なスタイルで表示することが出来るのですが表示するには処理が必要になり、また容量が大きいのでそのまま扱うには適しません。日本全域のデータだけでも2.2GByteあるので表示されるまで数十分かかり、位置やズームを変更するだけで同じ時間待機することになりますので、PostGIS, Geopackage, タイルデータなどにして運用する方が良さそうです
地球全体のデータは以下から取得できますが150GByteあります
https://planet.openstreetmap.org/
以下から地域別データを取得できます
今回はjapan-latest.pbfをダウンロードして利用しました
https://download.geofabrik.de/
特定の地域だけのデータを取得するには以下でトリミングして取得できます
https://www.openstreetmap.org/export#map=12/34.9686/138.4770
3-2. 地理データをPostGISに展開する
上記のjapan-latest.pbfはXML的な構造を持つバイナリファイルです。こちらをそのまま利用すると遅くてやってられないので、DBに展開して利用するのが通常の手順となります。GISで利用する為のpostgreSQL拡張であるPostGISを作成します
PostGISは公式からDockerイメージが用意されているので簡単に環境を構築できます。IDパスワードも環境変数から設定できますので簡単です。今回はローカル環境で使うだけなのでテキトーですが外部に公開するような場合はちゃんと設定してください
FROM postgis/postgis
ENV DBNAME=postgres \
DBUSER=postgres \
DBPASS=postgres \
DBHOST=postgis
RUN apt-get update && \
apt-get install -y osm2pgsql && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
先程ダウンロードしたjapan-latest.osm.pbfを/data/に配置してdocker execなどでCLIから以下を実行すればpbfファイルの内容をPostGISに展開することができます
osm2pgsql \
-d postgres \
-H postgis \
-P 5432 \
-U postgres \
--create \
--slim \
--hstore \
--multi-geometry \
--cache 2000 \
/data/japan-latest.osm.pbf
使用するのが地球全域のデータであればこれで問題ないのですが、japan-latest.pbfを使用する場合は存在しないレイヤーやカラムがある為にOpenStreetMap公式のcartoCSSでエラーが出てしまいますので、以下を実行してテーブルやカラムを追加してやります
for tbl in icesheet_polygons water_polygons simplified_water_polygons land_polygons icesheet_outlines ne_110m_admin_0_boundary_lines_land; do
echo "Checking table '$tbl'"
# if psql -h postgis -U postgres -d postgres -c '\dt' | grep -q "$tbl"; then
if psql -h postgis -U postgres -d postgres -c '\dt' | grep -qwE '\b'"$tbl"'\b'; then
echo "'$tbl' already exists. Skipping creation."
else
echo "Creating table '$tbl'"
psql -h postgis -U postgres -d postgres -c \
"CREATE TABLE $tbl (way geometry(Polygon, 3857));"
fi
done
echo "Ensuring 'ice_edge' column exists in 'icesheet_outlines'..."
if ! psql -h postgis -U postgres -d postgres -c "\d icesheet_outlines" | grep -q "ice_edge"; then
echo "Adding 'ice_edge' column..."
psql -h postgis -U postgres -d postgres -c \
"ALTER TABLE icesheet_outlines ADD COLUMN ice_edge text;"
else
echo "'ice_edge' column already exists."
fi
3-3. スタイルを作成する
とりあえず表示してみる為に簡単なスタイルを作ってみましょう
postgisを参照してroadsレイヤーを単色で表示するだけの簡単スタイルです
<Map>
<Style name="roads">
<Rule>
<LineSymbolizer stroke="black" stroke-width="1" />
</Rule>
</Style>
<Layer name="roads" srs="+init=epsg:3857">
<StyleName>roads</StyleName>
<Datasource>
<Parameter name="type">postgis</Parameter>
<Parameter name="host">postgis</Parameter>
<Parameter name="port">5432</Parameter>
<Parameter name="user">postgres</Parameter>
<Parameter name="password">postgres</Parameter>
<Parameter name="dbname">postgres</Parameter>
<Parameter name="table">(SELECT way FROM planet_osm_line WHERE highway IS NOT NULL) as roads</Parameter>
<Parameter name="geometry_field">way</Parameter>
<Parameter name="srid">3857</Parameter>
</Datasource>
</Layer>
</Map>
3-4. Mapnikで地図をレンダリングする
Mapnik環境はLinuxでないと作りにくいのでdockerで作成しました
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
curl \
python3 \
python3-pip \
python3-mapnik \
mapnik-utils \
fontconfig \
fonts-noto \
fonts-noto-cjk \
fonts-noto-color-emoji \
fonts-hanazono \
&& fc-cache -fv \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN pip3 install fastapi uvicorn jupyter
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
MapnikはC++で作成されていますがPythonで動きます
先ほど作成したstyle.xmlをmain.pyと同じディレクトリに置いてやればAPIとして動きます
from fastapi import FastAPI, Query
from fastapi.responses import Response
import mapnik
import math
app = FastAPI()
@app.get("/map")
def render_map(z: int = Query(10), x: int = Query(1000), y: int = Query(1000)):
m = mapnik.Map(256, 256)
mapnik.load_map(m, "style.xml") # Mapnik XMLファイルを用意しておく
m.zoom_all()
im = mapnik.Image(256, 256)
mapnik.render(m, im)
png = im.tostring("png")
return Response(content=png, media_type="image/png")
地図が作成できました
4. CartoCSSを使う
Mapnikはxmlで記載したスタイルで描画しますが、80以上あるレイヤーすべてにそれぞれ縮尺別の条件を書くのは大変なので、OpenStreetMapではCSS的な記載が可能なCartoCSSで定義してXMLファイルにビルドする形で運用されています
4-1. osm-cartoを入手する
OpenStreetMapのCartoCSSが公開されています
https://github.com/gravitystorm/openstreetmap-carto
4-2. style.xmlをビルドする
ビルドに使うcartoコマンドはnode.jsで動きます
npm install -g carto
project.mmlを指定してcartoコマンドを実行すればビルドしてくれます。project.mmlから各ファイルを参照しますが、ディレクトリ構造を変更していなければそのままで参照できるようになっています
carto /carto_css/project.mml > style.xml
ビルドされたstyle.xmlを先ほどのMapnik環境に置けばOpenStreetMapのスタイルで表示されます

5. 海のデータを足す
japan-latest.osm.pbfは海のデータを含まないようなので、OpenStreetMapも利用しているNatural Earthのデータを足してやります
https://www.naturalearthdata.com/downloads/10m-physical-vectors/10m-ocean/
shpファイルなのでshp2pgsqlで変換してPostGISに登録します
shp2pgsql -I -s 3857 -g way /data/natural_earth/ne_10m_ocean.shp simplified_water_polygons | psql -h postgis -U postgres -d postgres

6. CartoCSSをカスタマイズしてデザインを変える
@ferry-route: #66f;
@ferry-route-text: @ferry-route;
#ferry-routes {
[zoom >= 8] {
/* background prevents problems with overlapping ferry-routes, see #457 */
background/line-color: @water-color;
background/line-width: 1; /* Needs to be a bit wider than the route itself because of antialiasing */
line-color: @ferry-route;
line-width: 0.4;
line-dasharray: 4,4;
[zoom >= 11] {
background/line-width: 1;
line-width: 0.8;
line-dasharray: 6,6;
}
}
}
#ferry-routes-text {
[zoom >= 13] {
text-name: "[name]";
text-face-name: @book-fonts;
text-placement: line;
text-fill: @ferry-route-text;
text-spacing: 1000;
text-size: 10;
text-dy: -8;
}
}
こんな感じでデザインを変更できます
(ちょっとしか変わってません)

まとめ
かなり苦労しましたがなんとか公式通りのやり方でデザインを導入することができました
MapnikやCartoCSSは結構手ごわいので、QGISのように色々なデータフォーマットを自動で読んでくれるツールを使って確認しながらやった方がやりやすいと思います
OpenStreetMapはかなり細かく要素分けして情報が書かれてますので、うまく使えばいろんな地図を書くことができて面白いです。レッツトライ