🗺️

MapLibre GL JS のダーク系地図スタイルを作ってみた

に公開

ドワンゴ Advent Calendar 2025 13日目の記事です。が、仕事とは完全に無関係です。

MapLibre GL JS で使えるダーク系の地図スタイルを作ってみたので、どんなことをやったのかとちょっとしたノウハウの話です。

はじめに

MapLibre GL JS を使って GIS 系の Web アプリを作る中で、ダーク系・黒ベースの地図を表示したいことはよくあると思います。しかしなぜか世に公開されているスタイルファイルは明るい色調のものばかり。なので、ダーク表示に対応したスタイルを作ってみました。

https://github.com/tris5572/map-style

GitHub Pages にスタイルとスプライトのファイルを公開しています。これを使えば誰でも簡単に MapLibre GL JS でダークモード対応の地図を表示できます。MapLibre の "style" として "https://tris5572.github.io/map-style/dark/style.json" を指定するだけです。

実際の地図での表示サンプルを確認できます。

https://tris5572.github.io/map-style/

地図スタイルとスタイルファイル style.json

MapLibre GL では、指定するスタイルファイル style.json の内容に基づいてベクトルタイルの地図の要素を表示します。表示するデータ種別(土地や道路の種類など)ごとに指定された線や塗りのスタイルなどから、地図が描画されます。

このスタイルファイルは色々なところで公開されています。日本語表示に対応したものだと OpenStreetMap のページ があり、実際の表示例も確認できます。

https://tile.openstreetmap.jp/styles/osm-bright-ja/

こうしたスタイルをそのまま使うのが王道で、例えば上記配布ページ内の「GL Style」と書かれた JSON ファイルの URL またはファイルそのものを MapLibre GL に渡すだけで、そのスタイルで地図を表示できます。

しかし、自分好みの地図スタイルが誰かの手によって公開されているとは限りません。ダークモードに対応した黒ベースのスタイルが欲しかったり、既存スタイルの道路の色が気に食わなかったり、表示要素を減らしたかったり、などがあるでしょう。そんな希望は、スタイルファイル style.json を自分で作成することで実現できます。

ただ、原理的には自由自在にスタイルを設定できるとは言え、スタイルの仕様を見れば分かるように指定項目は膨大で、イチから作るのはとても大変だと分かります。したがって公開されているスタイルの大部分を流用しつつ、一部を編集するのが現実的です。今回自分が作ったダーク系のスタイルも、OpenStreetMap で公開されている Bright (osm-bright-ja) の内容を大いに参考にさせてもらいつつ、色々とカスタマイズしました。見た目は全然違いますが、基本的な構造は適用可能です。

以下、どのようなカスタマイズを行ったのかを記します。

色・スタイル

ダーク系と言うからには、ダークな黒ベースの色にする必要があります。すなわちライト系とは逆で、地面などの塗りを暗い色にするとともに、道路や文字などを明るめの塗りと線にするなどの大作業が必要です。これに近道はなく、スタイルファイル style.json で色指定を1つ1つ行っていきます。

これは結構な作業量があるとともに、考えることが色々あります。1つの道路種別(例えば高速道路)に対しても、普通の道路部分・橋梁・トンネルで異なる指定を行うことになり、それらの統一性を保ちつつ、他の道路種別(国道や県道など)との判別性を確保するなど、試行錯誤を重ねつつ決めていく必要があります。

ちょっとしたノウハウとしては、色指定で HSL 形式を使うと統一感を出しやすくておすすめです。色相・彩度・明度で指定できるため、明度だけを変えて少し明るくする、といった調整を簡単に行えます。

そうして詰めていくことで、同じ地図データのソースを利用しつつ、全く異なる地図のように見せることが可能です。

ライト系 ダーク系

編集時、実際のマップでの表示が style.json の中のどの部分に基づいているかは、 Maputnik を使えば確認可能です。対象の style.json を読み込ませて、表示された地図で知りたい箇所をクリックすれば、その位置にどの属性・スタイルが適用されているかを確認できます。

もっと言えば、 Maputnik で地図を実際に表示しながら GUI で編集することも可能です。変更がリアルタイムで確認できるので便利ではありますが、 UI が分かりやすいとは言えないのが微妙なところです。ローカルで style.json をリアルタイムに反映できる仕組みを構築していれば、JSON を直接編集するのもアリでしょう。自分はその方法でやりました。

フォント

参考元のスタイル osm-bright-ja では、表示文字のフォントとして等幅フォントが指定されています。しかしながら地図として考えると英数字の表示に違和感があるため、プロポーショナルフォントの方が良さそうです。

また、 osm-bright-ja ではよく見ると道路番号が枠の中央ではなく上にズレていますが、これも使用しているフォントが原因です。英数字が上にズレる形になっているためで、英数字と漢字が混在する名前、例えば「CoCo壱番屋」を見るとズレが分かりやすいですね。こうした事象は、適切なフォントを指定することで回避可能です。


元のスタイルのフォントでは英数字が上にズレている

スタイルファイルでは、フォントのファイルをどの URL から読み込むかを "glyphs" で指定します。そこにはフォントを変換した .pbf 形式(not .pdf)のファイル群が配置されている必要があります。ここでは GIS で有名な MIERUNE 社が公開してくれているフォント群をありがたく使わせてもらうことにして、 "glyphs": "https://mierune.github.io/fonts/{fontstack}/{range}.pbf", と記載します。

そしてスタイルファイルで文字列描画のためにフォントを指定している "text-font" として、実際のフォント名を指定していきます。MIERUNE 社のおかげでフォントがよりどりみどりなので、好きなものを選びます。基本的には1つのフォントファミリーに統一しつつ、サイズとウェイトの違いで重要度などを表現するのが良い感じです。自分は M+ 系のプロポーショナルフォントを選択しました。これによりスマートな表示になりました。


変更後は上下が揃っている

アイコンのスプライト

地図の特定のポイント、POI (Point of Interest) と呼ばれる場所に表示されるアイコンもカスタマイズ可能です。

ファイルは基本的にスプライト形式になっており、たくさんのアイコンが配置された1枚の画像ファイルから、表示対象の要素に応じた部分が切り取って表示されます。そのために、複数のアイコンを含む画像ファイル .png と、画像内に配置されている要素の位置とサイズなどを記載した .json ファイルをセットで扱います。

スタイルファイルの "sprite" として指定した URL のディレクトリに、以下のようなファイルを配置します。見ての通り @2x が付いた高解像度版も配置できます。

  • sprite.json
  • sprite.png
  • sprite@2x.json
  • sprite@2x.png


作成したスプライト(謎の空白はアイコン追加に備えたエリア)

https://github.com/tris5572/map-style/blob/gh-pages/dark/sprite.json

スプライト自体は画像なので、画像編集ソフトで作成・編集できますが、画像自体と JSON ファイルを連動して管理する必要があり、手で管理するのはなかなか大変です。アイコン1つ1つの画像ファイルを作り、それを何らかのツールでスプライトとして並べることも可能ではありますが、結局はファイル名などを元に JSON を作る必要があります。(一応、画像を結合して JSON まで作成してくれるツールもあるらしいのですが、噂によると挙動が微妙なのだとか)

自分はこれらを楽に管理するため、アイコンをまとめて描いた SVG を PNG に変換するとともに JSON ファイルを生成する簡単なスクリプトを書きました。

https://github.com/tris5572/map-style/blob/main/scripts/create-sprite.ts

SVG をスプライトのデータソースにすると、色々なメリットがあります。

  • 変更を Git で管理可能
  • テキストを編集するだけで、画像編集ソフトを使わずとも無劣化で細かく修正可能
  • 1つの画像にすべてのアイコンを配置できるため、全体のバランスを取りやすい
  • 画像と JSON の元になるデータを一括で管理可能(後述)

今回、元になる SVG は手で書きました。座標を指定して線を引いたり塗ったりしながら、各アイコンを作っていきます。今回の地図スタイルを作るうえで、たくさんの種類のアイコンをすべて手で書くのが一番大変でした。ただ作業量は多いとは言え、いくつかのテクニックを駆使すればそれほど困難ではありません。絶対座標と相対座標の使い分け、g 要素でのグループ化、transform プロパティによる移動・変形、あたりでしょうか。あとはマスクも使えば完璧です。

SVG から JSON を生成するためのちょっとした工夫として、 SVG の view 要素を使っています。viewid としてアイコンの POI の種類を埋め込むとともに、 viewBox 属性で各アイコンの位置・サイズを指定します。これらの値を生成スクリプトから参照すれば JSON を容易に生成可能です。画像と JSON のデータソースを1箇所にまとめることによる管理容易化を狙ったものです。


SVG の記載例

この記載例を元に上記のスクリプトを動かすと、描いた画像を画像ファイル sprite.png へ変換するとともに、sprite.json"rocket_11":{"x":140,"y":0,"width":20,"height":20,"pixelRatio":1}, のように座標等の情報が出力されます。@2x 付きの高解像度版も同時に出力します。

このように手で SVG を書くのは管理を楽にできるのでおすすめの方法です。画像を作り上げていくのが大変ではありますが、ベクター系のイラストソフトを使ったことがあればなんとかなります。あとこれは完全に余談ですが、テキストで座標を打ち込んでいくと画像がどんどん出来上がっていくのは魔法の詠唱みたいで個人的に好きです。

最後に

このように、いくつかの工夫を重ねつつ、ダーク系の地図スタイルを作成してみました。ぜひ使ってみてください。

今回一応使えるレベルのものが完成しましたが、他にもやりたいことは山積みです。データビジュアライゼーションが映えるようなもっとシンプルで暗いスタイルを作ったり、POI のアイコンを追加したり、アイコンの形状を整えたり……。ということで順次手を入れていく予定です。

また、地図スタイルを自分で作るのもそんなにハードルが高いわけではないと思えたのではないでしょうか。ぜひみなさんも自分好みのスタイルをガシガシ書いてみてください。

Discussion