MapLibre GL JSでWeb地図アプリを開発している際、「データの属性値によってピンの色やサイズを動的に変えたい」「ズームレベルに合わせてテキストやアイコンの表示を滑らかに制御したい」と思ったことはありませんか?JavaScriptで1つずつ地物をループ処理してスタイルを書き換えることもできますが、データ量が増えると動作が重くなり、パフォーマンスの低下に悩まされてしまいがちです。
そんな課題を解決し、Web地図の表現力と描画速度を劇的に向上させてくれるのがMapLibre Expression(式)です。スタイル仕様(JSON)の中で条件分岐や動的な計算を行える仕組みで、大量のデータを扱う場面でも非常に高速に動作するという強力なメリットを持っています。
この記事では、MapLibre Expressionの基本概念から実践的な書き方までを分かりやすく解説します。
「Expressionの構文が少し難しそう…」と感じている初心者の方でも、基本ルールと具体的なコード例を押さえればすぐにマスターできます。ぜひ最後まで読んで、表現力豊かで快適に動くWeb地図アプリの開発に役立ててみてください!

MapLibre Expressionの基本概念と設定場所・基本構文

MapLibre GL JSを使ったWeb地図アプリ開発において、表現力を一段と高めてくれる強力な機能が「Expression(式)」です。データの属性値やズームレベルに応じて、スタイルの色やサイズを動的に変更したり、特定の条件に合致する地物を絞り込んだりすることが可能になります。

MapLibre Expressionとは?通常のスタイル設定・JavaScriptの条件分岐との違い
MapLibre Expressionとは?
Expressionとは、MapLibreのスタイル仕様(JSON)の中で、値や条件を動的に計算・評価するための仕組みです。従来のMapbox GL JS(v1互換)やMapLibre GL JSでも標準的に使われており、データの持ち方(Properties)に応じて、地図の見た目をプログラムレスに制御できます。
通常のスタイル設定との違い
通常のスタイル設定では、以下のように固定値を指定するのが基本です。
"circle-color": "#ff0000"
しかし、これではすべてのピンが赤色になってしまいます。Expressionを使用すると、データの属性(例:人口、カテゴリ、種別など)に応じて「データ駆動型」のスタイルを適用できるようになります。
JavaScriptの条件分岐との違い
「JavaScriptで条件分岐をして複数のスタイルを切り替えればいいのでは?」と思うかもしれません。しかし、JavaScriptで動的にスタイルやGeoJSONを書き換える手法は、データ量が数千件〜数万件を超えるとパフォーマンスが著しく低下します。
一方、MapLibre ExpressionはMapLibreのスタイル仕様を処理する内部エンジンで直接評価されるため、大量の地物を描画する際でも非常に高速に動作するという圧倒的なメリットがあります。
["get", "property"]の入れ子構造と基本ルールを理解する
配列形式による独特の構文
MapLibre Expressionは、JSONの配列(Array)をベースに記述します。基本の形は以下の通りです。
["演算子名", 引数1, 引数2, ...]
この「配列の最初の要素に演算子名を置き、2番目以降に引数を並べる」という入れ子構造が、初心者がつまずきやすいポイントの一つです。
属性値を取得する ["get", "property_name"]
GeoJSONの各フィーチャー(地物)が持つプロパティ(属性情報)にアクセスするには、get演算子を使用します。
["get", "population"]
上記の式は、「現在の地物が持つ population というプロパティの値を取り出す」という意味になります。これを入れ子にして、別の演算子と組み合わせることで複雑な条件を作ることができます。
データ型(number, string, boolean)の意識とエラー回避
Expressionでは、データの型(Type)が非常に厳密に扱われます。よくあるエラーに Expected number but found string がありますが、これは「数値(number)を期待している箇所に文字列(string)が渡されている」場合に発生します。
たとえば、属性値の age が文字列(例: "25")として格納されているにもかかわらず、数値として計算や比較を行おうとするとエラーになります。必要に応じて、後述する型変換の演算子(to-numberなど)を挟む意識を持つことが大切です。
paint・layout・filterの3つの設定場所と役割の違い
Expressionは、レイヤー定義の中の主に3つのプロパティ(paint、layout、filter)の内部で使用されます。それぞれの役割と違いを理解しておきましょう。
1. paint(ペイントプロパティ)
ピクセルの描画や色、不透明度、幅など、描画の見た目に関わるスタイルを定義します。ズームレベルやデータ属性に連動した連続的な変化(グラデーションや段階的な色分けなど)を記述するのに最もよく使われます。
実装例(circle-colorでの使用):
{
"id": "points-layer",
"type": "circle",
"source": "points-source",
"paint": {
"circle-color": [
"match",
["get", "category"],
"A", "#ff0000",
"B", "#00ff00",
"#0000ff" // デフォルト値
],
"circle-radius": 8
}
}
2. layout(レイアウトプロパティ)
地図上に表示されるオブジェクトの配置や構造、テキストラベル、アイコンの表示・非表示など、ジオメトリの構築やレイアウトに関わる設定を行います。
実装例(text-fieldでの使用):
{
"id": "labels-layer",
"type": "symbol",
"source": "points-source",
"layout": {
"text-field": ["get", "name"],
"text-font": ["Open Sans Regular"],
"text-size": 12
}
}
3. filter(フィルター)
レイヤーレベルで「どの地物を描画し、どの地物を非表示にするか」を条件によって絞り込むために使用します。特定のステータスのデータだけを表示したい場合などに活用します。
実装例(特定の条件に合致する地物のみ表示):
{
"id": "active-points",
"type": "circle",
"source": "points-source",
"filter": ["==", ["get", "status"], "active"],
"paint": {
"circle-color": "#3bb2d0"
}
}
このように、Expressionは記述する場所(paint / layout / filter)によって役割が異なりますが、いずれの場所でも同じ配列ベースのExpression構文が共通して利用できます。
演算子の種類と使い分け
MapLibre Expressionには非常に多様な演算子が用意されており、用途に応じて使い分けることで高度な地図表現を実現できます。ここでは、実務でよく使われる代表的な演算子を3つのカテゴリに分けて、具体的なコードスニペットとともに解説します。
get・has・geometry-typeで地物の属性や形状を取得する
地図上に描画するデータ(GeoJSONなど)の属性情報やジオメトリの種類を安全に取得・判定するための演算子です。
1. get(属性値の取得)
前項でも触れた、最も基本となる演算子です。指定したプロパティ名の値を取得します。
["get", "population"]
2. has(プロパティの存在確認)
指定したプロパティがデータ内に存在するかどうかを true / false で返します。データによってプロパティの有無がバラバラな場合に、エラーを防ぐための安全弁として使われます。
["has", "elevation"]
実践的な使い方(フィルターでの応用例):JSON elevation プロパティが存在する地物だけに絞り込む "filter": ["has", "elevation"]
3. geometry-type(地物形状の取得)
現在のフィーチャーが Point(点)、LineString(線)、Polygon(面)のどれであるかを文字列で返します。一つのソースから異なる形状のデータをまとめて読み込む場合に便利です。
["geometry-type"]
実践的な使い方(条件分岐での応用例):JSON [ "match", ["geometry-type"], "Point", "#ff0000", "LineString", "#00ff00", "#0000ff" ]
case・match・coalesceで条件分岐や値の分類を行う
複数の条件やカテゴリに応じて、異なるスタイルや値を割り当てるための演算子です。プログラムにおける if-else や switch 文に相当します。
1. case(条件式による分岐)
複数の独立した条件(比較演算子など)を上から順番に評価し、最初に真(true)になった分岐の結果を返します。
[
"case",
[">=", ["get", "population"], 1000000], "large",
[">=", ["get", "population"], 500000], "medium",
"small" // どの条件にも当てはまらない場合のデフォルト値
]
2. match(特定の値との一致判定)
1つのプロパティ値が、複数の候補値のどれに一致するかを効率よく判定します。case よりもシンプルに記述できます。
[
"match",
["get", "landuse"],
"residential", "#ffcccc",
"commercial", "#ccffcc",
"industrial", "#ccccff",
"#ffffff" // デフォルトカラー
]
3. coalesce(最初の非null値を返す)
指定した複数の式のリストから、最初に null または欠損値(undefined)ではない値を返します。多言語対応(例:英語名があれば英語、なければ日本語、どちらもなければ汎用名)などに非常に便利です。
[
"coalesce",
["get", "name_en"],
["get", "name_ja"],
"名称未設定"
]
step・interpolate・zoomで段階的・連続的にスタイルを変える
地図のズームレベル(zoom)や数値データ(人口や標高など)に応じて、サイズや色をなめらか、あるいは段階的に変化させるための演算子です。
1. zoom(現在のズームレベルの取得)
現在のマップのズームレベル(数値)を返します。これ単体だけでなく、後述の step や interpolate と組み合わせて使われます。
["zoom"]
2. step(段階的な変化:階段状)
特定の境界値(ストップ値)を境に、値が階段状に切り替わるスタイルを作ります。
[
"step",
["zoom"],
10, // ズームレベルがストップ値未満のときの初期値
10, 12, // ズーム10以上ならサイズ12
14, 16 // ズーム14以上ならサイズ16
]
3. interpolate(連続的な変化:線形補間など)
ズームレベルや数値に応じて、値をリニア(直線的)にスムーズに補間します。ピンの大きさや円の半径をズームに合わせて滑らかに拡大・縮小させたい場合に必須の演算子です。
[
"interpolate",
["linear"], // 補間の種類(linear または exponential など)
["zoom"], // 変化の基準にする値(ここではズームレベル)
5, 2, // ズーム5のとき、半径2px
15, 20 // ズーム15のとき、半径20px
]
このように interpolate を使うことで、どのズームレベルでも美しく違和感のない地図のズーム連動表現が可能になります。

色・サイズ・ラベル・アイコンを変更する実践スタイル設定

これまでに学んだExpressionの基本概念と演算子を組み合わせて、実際のWeb地図アプリでそのまま使える実用的なスタイル設定を見ていきましょう。ここでは、色分け、ズーム連動のサイズ変更、そして動的なラベルやアイコン表示の具体的な実装例を紹介します。
circle-colorやfill-colorで属性値に応じた色分け地図を作る
データの属性値(カテゴリや種別など)に応じて、ポイントの色(circle-color)やポリゴンの塗りつぶし色(fill-color)を動的に切り替える表現は、テーママップ作成の基本です。
ここでは match 演算子を使い、施設の種別(type プロパティ)に応じて色を切り替える実装例を示します。
実装例(施設タイプに応じたポイントの色分け)
{
"id": "facilities-layer",
"type": "circle",
"source": "facilities-source",
"paint": {
"circle-color": [
"match",
["get", "type"],
"hospital", "#e74c3c", // 病院:赤系
"school", "#3498db", // 学校:青系
"park", "#2ecc71", // 公園:緑系
"#95a5a6" // デフォルト(その他):グレー
],
"circle-radius": 7,
"circle-stroke-width": 1,
"circle-stroke-color": "#ffffff"
}
}
このように記述することで、コード側でフィーチャーを1つずつループ処理してスタイルを書き換える必要がなくなり、パフォーマンスを保ったまま綺麗に色分けされた地図を描画できます。
interpolateとzoomを使ったズームレベル・数値の段階的変化
広域を表示しているときは小さな丸、ズームアップしたときには大きな丸に変化させたり、人口の増減に応じて円のサイズをなめらかに拡大・縮小させたい場合には interpolate と zoom を組み合わせます。
実装例(ズームレベルに応じた円の半径の変化)
{
"id": "population-circles",
"type": "circle",
"source": "population-source",
"paint": {
"circle-radius": [
"interpolate",
["linear"],
["zoom"],
5, ["*", ["get", "pop_scaled"], 2], // ズーム5のときのサイズ設定例
12, ["*", ["get", "pop_scaled"], 5], // ズーム12のときのサイズ設定例
18, ["*", ["get", "pop_scaled"], 15] // ズーム18のときのサイズ設定例
],
"circle-color": "#ff6b6b",
"circle-opacity": 0.7
}
}
さらに、数値データそのものに対して interpolate を使うことで、人口の多さに応じて円の大きさをリニアに変化させる「プロポーショナル・シンボル図(円積図)」も簡単に作成可能です。
text-fieldやconcatを組み合わせた動的ラベルとアイコンの表示
地図上に施設名などのラベルを表示する text-field や、アイコン画像を指定する icon-image でもExpressionは大活躍します。特に複数のプロパティの文字列を連結したいときは concat 演算子が便利です。
1. concat による文字列の結合
複数の属性値(例:「施設名」と「階数」など)を結合して1つのラベルとして表示したい場合に用います。
[
"concat",
["get", "name"],
" (",
["get", "floor"],
"階)"
]
2. 実装例(動的ラベルとアイコンのレイアウト設定)
{
"id": "poi-labels",
"type": "symbol",
"source": "poi-source",
"layout": {
"icon-image": [
"match",
["get", "category"],
"cafe", "cafe-icon-15",
"restaurant", "restaurant-icon-15",
"marker-15" // デフォルトアイコン
],
"icon-size": 1.2,
"icon-allow-overlap": true,
"text-field": [
"concat",
["get", "name"],
"\n",
["coalesce", ["get", "sub_name"], ""]
],
"text-font": ["Open Sans Regular", "Arial Unicode MS Regular"],
"text-size": 11,
"text-offset": [0, 1.5]
}
}
この設定では、match 演算子でカテゴリに応じたアイコンを動的に切り替えつつ、text-field と concat、そして改行コード(\n)を組み合わせて、施設名の直下にサブ名を美しく配置するラベルレイアウトを実現しています。
条件分岐・フィルター操作とエラー対策
MapLibre Expressionを使いこなしていく中で、より複雑な条件設定や、ユーザーの操作に連動したリアルタイムなフィルタリング、そして開発時に直面しがちなエラーの解決法を知ることは非常に重要です。このセクションでは、実践で役立つテクニックとトラブルシューティングを解説します。
case・match・stepの使い分けと複雑な条件分岐の実装
これまで紹介した条件分岐演算子(case、match、step)は、それぞれ得意な場面が異なります。それぞれの特徴と、複雑な条件をネスト(入れ子)させて実装するコツを確認しましょう。
各演算子の使い分けの指針
match:1つのプロパティ値に対して、複数の固定値(カテゴリや種別など)を完全一致で振り分ける場合に最もシンプルで高速です。case:数値の大小比較(例:> 100かつ< 500)や、複数の異なるプロパティを組み合わせた複雑な条件判定を行う場合に適しています。step:ズームレベルや数値の「境界値(しきい値)」を基準にして、段階的(ステップ状)に値を変化させたい場合に最適です。
複雑な条件分岐の実装例(caseのネスト・複数条件の組み合わせ)
たとえば、「人口が10万人以上、かつ治安ステータスが safe の場合は緑、人口に関わらずステータスが danger の場合は赤」といった複雑な条件を case と比較演算子(==, > など)を組み合わせて実装できます。
[
"case",
["==", ["get", "status"], "danger"], "#e74c3c",
[
"all",
[">=", ["get", "population"], 100000],
["==", ["get", "status"], "safe"]
], "#2ecc71",
"#f1c40f" // その他のデフォルトカラー
]
このように all 演算子や any 演算子を組み合わせることで、論理積(AND)や論理和(OR)を使った高度な条件分岐を美しく表現できます。
setFilter()を使ったユーザー操作による動的フィルタリング
レイヤーの初期定義での filter 設定だけでなく、ユーザーがWeb画面上のボタンやセレクトボックス(UI)を変更した際に、動的に地図の表示内容を切り替えたい場合は、JavaScript側から map.setFilter() を呼び出します。
実装例(JavaScriptとExpressionの連携)
たとえば、ユーザーがセレクトボックスで選択したカテゴリに応じて、地図上のピンを動的に絞り込む実装は以下のようになります。
HTML / UI側:
<select id="category-filter">
<option value="all">すべて表示</option>
<option value="hospital">病院のみ</option>
<option value="school">学校のみ</option>
</select>
JavaScript側:
// セレクトボックスの変更イベントを監視
document.getElementById('category-filter').addEventListener('change', (e) => {
const selectedCategory = e.target.value;
let newFilter;
if (selectedCategory === 'all') {
// すべて表示する場合(条件なし)
newFilter = ["has", "type"]; // または null
} else {
// 選択されたカテゴリに一致するものだけに絞り込むExpression
newFilter = ["==", ["get", "type"], selectedCategory];
}
// map.setFilter()を使ってレイヤーのフィルターを動的に更新
map.setFilter('facilities-layer', newFilter);
});
このように、Expression形式の配列をそのまま map.setFilter() に渡すことで、フロントエンドのユーザーインタラクションと連動したダイナミックな地図アプリケーションが簡単に構築できます。
Expected number but found stringなど3大エラーと解決策
MapLibre Expressionを記述していると、ブラウザのコンソールにエラーが表示されて頭を抱えることがあります。ここでは特によくある3大エラーと、その具体的な解決策を紹介します。
1. Expected number but found string
- 原因: 数値(number)を期待している演算子(例:
interpolateや比較演算子など)に対して、プロパティ値が文字列(string)として渡されている場合に発生します。GeoJSONのプロパティ値がクォーテーションで囲まれている場合によく起こります。 - 解決策:
to-number演算子を使用して、明示的に数値へ変換します。JSON// 修正前: ["get", "height"] // 修正後: ["to-number", ["get", "height"]]
2. Expected expression, but found ... (構文エラー・配列の閉じ忘れ)
- 原因: JSONの配列構文(ブラケット
[]やカンマの数)が正しくない、または閉じ括弧が不足している場合に発生します。特にネストが深くなると起こりがちです。 - 解決策: エディタのJSONバリデーション機能や、エディタの括弧対応ハイライトを活用して、配列の開始・終了が正しくペアになっているかを確認します。
3. 予期しない null によるスタイル崩れ
- 原因: 参照しようとしたプロパティが特定のデータに存在しない(undefined)ため、計算結果が不正になりスタイルが適用されない現象です。
- 解決策:
coalesce演算子を使ってデフォルト値をフォールバックとして用意するか、has演算子であらかじめ存在チェックを行います。JSON[ "coalesce", ["get", "rating"], 0 // ratingがない場合は 0 をデフォルト値とする ]
よくある質問
MapLibre Expressionに関して、開発現場でよく寄せられる疑問とその回答をQ&A形式でまとめました。
-
MapLibre Expressionと旧Mapbox GL JSのスタイル仕様に違いはありますか?
-
基本的なExpressionの構文(
["get", "..."]や["match", ...]など)は、Mapbox GL JS v1系およびMapLibre GL JSの間で高い互換性があります。ただし、Mapbox GL JSがv2以降で独自仕様(プロプライエタリな変更や新しい演算子の追加など)を進めたのに対し、MapLibre GL JSはオープンソースとして独自に進化を続けています。一般的なデータ駆動型スタイリングやフィルタリングで使用する主要な演算子は共通して利用できますが、最新の仕様を確認する際はMapLibre公式のスタイル仕様リファレンスを参照することをおすすめします。
-
非常に複雑な条件分岐や計算を行いたい場合、パフォーマンスに影響はありますか?
-
JavaScriptのコードで毎回フィーチャーをループ処理してスタイルを書き換えるアプローチに比べれば、MapLibre Expressionは内部の評価エンジンで効率的に処理されるため、圧倒的に高速でパフォーマンスに優れています。
ただし、極端にネストが深い複雑な
case文や重い文字列演算を数万件のフィーチャーに対して毎フレーム評価させると、わずかに描画のカクつき(フレームレートの低下)につながる可能性があります。パフォーマンスが気になる場合は、あらかじめGeoJSONの属性側でフラグやカテゴリを整理しておくなどの前処理を組み合わせると効果的です。
-
開発中のエラーや記述ミスを効率よくデバッグする方法はありますか?
-
ブラウザの開発者ツール(コンソール)を常時開いておくことが第一歩です。MapLibre GL JSは、 Expressionの構文エラーや型不一致(例:
Expected number but found stringなど)が発生した際に、詳細なエラーメッセージをコンソールに出力してくれます。また、VS Codeなどのエディタを使用している場合は、MapLibre/Mapboxのスタイル仕様に対応したJSONスキーマ(拡張機能など)を導入すると、入力補完やリアルタイムの構文チェックが効くようになるため、コーディング段階でのタイポや括弧の閉じ忘れを大幅に減らすことができます。
まとめ
本記事では、MapLibre Expressionの基本概念から、演算子の種類、具体的なスタイル設定、条件分岐・フィルター操作、そしてよくあるエラーの解決策まで詳しく解説してきました。
- Expressionの基本: 配列形式の入れ子構造と、データ型(numberやstringなど)の意識がトラブルを防ぐカギとなる。
- 多彩な演算子:
getやmatch、interpolateなどを駆使することで、パフォーマンスを落とさずにリッチで動的な地図表現が可能になる。 - エラー対策: 型不一致によるエラーや予期せぬ
nullに対し、to-numberやcoalesceを活用して安全なコードを書く。
MapLibre Expressionをマスターすれば、データ駆動型の高度なWeb地図アプリをスムーズに開発できるようになります。ぜひ実際のプロジェクトに取り入れて、表現力豊かな地図を作成してみてください。

