この記事は、「FOSS4G Advent Calendar 2020」の9日目の記事です。
Mapbox GL JS・Leaflet・OpenLayersでのポイント表示数の限界を探ってみました  
JSマップライブラリで頑張ってアプリケーションを構築したのにブラウザの表示動作が重く感じたことはありませんか?
それはアレです...ライブラリとブラウザの限界です...
一昔前は、Leafletの前バージョンで数千件のポイント表示でもブラウザが固まり限界でした。そのため、ラスタタイル化をしたりバックエンドを構築して画面範囲のみを描画したりと工夫が必要でした。
しかし、近年の充実したJSマップライブラリの登場により、驚くほど大量なデータがフロンエンドのみでも表示できるようになり選択肢が広がりました 
今回はその限界を探ってみようということで、どのマップライブラリが適しているのか、フロントエンドのみの仕組みで問題ないのか、バックエンドの構築も必要なのか等、大体この辺で最初にはまったりすると思うので技術選定の参考にしていただければと思います。
結果的には、Mapbox GL JS v1のバイナリベクトルタイル(pbf)読み込みが圧倒的勝利で、70万件以上のデータをフロントエンドのみで表示することが可能でした 
※ジオメトリタイプや属性のサイズによって最大表示件数は変わります。
事前準備
手軽に始めるビルド環境公開しているので今回はこちらを利用しています。
- 
各マップライブラリの環境準備 
 mapboxgljs-starter
 leaflet-starter
 openlayers-starter
- 
検証した各マップライブラリのバージョン 
 Mapbox GL JS v1.11.0
 Leaflet v1.60
 OpenLayers v6.3.1
- 
背景地図 
 背景地図にはMaptilerを読み込んでみました 
 読み込み方法 → 色々なマップライブラリでMapTilerを表示してみた
利用データ
膨大なポイントデータを利用したかったので、国土地理院の電子国土基本図(地名情報)「住居表示住所」データを利用してみました。サンプルでは札幌市(一部地域を除く)のデータ約70万レコードを利用しています 
データはCSVとShapeで提供されているので、今回はShapeをQGISでいい感じに結合したり、GeoPackageやGeoJSONでエクスポートしたり、tippecanoeでpbfに変換したりしてみました。この辺は話すと色々あるので、興味あるかたはゼヒこちらのイベント「GeoSaturday 2020 Online」でお話ししましょう。(宣伝)
実装例
ポイントを表示するためには、基本的にはスターターのscript.jsにコード追記するのみになります。今回はこんな感じで設定してみました。
Leaflet
// ポイント設定
fetch('https://xxxxx/sample.geojson')
    .then(data => {
        return data.json();
    })
    .then(geojson => {
        L.geoJson(geojson, {
            pointToLayer: function (feature, layer) {
                return L.circleMarker(layer, {
                    color: '#014c86',
                    radius: 3,
                    weight: 1,
                    opacity: 0.7,
                    fill: true,
                    fillColor: '#014c86',
                    fillOpacity: 0.7
                });
            }
        }).addTo(map);
    });
OpenLayers
// ポイントスタイル設定
const styles = {
    'Point': new Style({
        image: new Circle({
            radius: 3,
            stroke: new Stroke({
                color: 'rgba(1, 76, 134, 0.7)',
                width: 1
            }),
            fill: new Fill({
                color: 'rgba(1, 76, 134, 0.7)'
            })
        })
    })
};
const styleFunction = function(feature) {
    return styles[feature.getGeometry().getType()];
};
// ポイントソース設定
const vectorSource = new VectorSource({
    url: 'https://xxxxx/sample.geojson',
    format: new GeoJSON()
});
// ポイントレイヤ設定
const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: styleFunction
});
Mapbox GL JS v1 (GeoJSON)
// ポイントソース設定
map.addSource('point_sample', {
    type: 'geojson',
    data: 'https://xxxxx/sample.geojson'
});
// ポイントスタイル設定
map.addLayer({
    id: 'point_sample',
    type: 'circle',
    source: 'point_sample',
    layout: {},
    paint: {
        'circle-color': '#014c86',
        'circle-radius': 3,
        'circle-opacity': 0.7
    }
});
Mapbox GL JS v1 (pbf)
// ポイントソース設定
map.addSource('point_sample', {
    type: 'vector',
    tiles: ['https://xxxxx/sample/{z}/{x}/{y}.pbf']
});
// ポイントスタイル設定
map.addLayer({
    id: 'point_sample',
    type: 'circle',
    source: 'point_sample',
    source-layer: 'xxxxx',
    layout: {},
    paint: {
        'circle-color': '#014c86',
        'circle-radius': 3,
        'circle-opacity': 0.7
    }
});
比較検証
それでは、比較しながら表示数の限界を探っていこうと思います 
今回はこの5種類のGeoJSONとpbfで試してみました。サイズはGeoJSONのサイズになります。
| 件数 | サイズ | 
|---|---|
| 50,000件 | 11MB | 
| 100,000件 | 22MB | 
| 200,000件 | 45MB | 
| 450,000件 | 108MB | 
| 700,000件 | 162MB | 
検証環境はざっくりこんな感じです。
- ネット: テザリング環境 (18Mbps)
- ブラウザ: Chrome最新版
- OS: macOS Catalina (MacBook Pro 13インチ)
- CPU: Intel Core i7 (2.5GHz デュアルコア)
- メモリ: 16GB
- 参照先: ローカル環境ではなくサーバーにデプロイして参照
50,000件 - 11MB
Leaflet
OpenLayers
Mapbox GL JS v1 (GeoJSON)
表示OK。表示までに8秒。
Mapbox GL JS v1 (pbf)
100,000件 - 22MB
Leaflet
表示されない。
OpenLayers
Mapbox GL JS v1 (GeoJSON)
表示OK。表示までに28秒。
Mapbox GL JS v1 (pbf)
200,000件 - 45MB
OpenLayers
Mapbox GL JS v1 (GeoJSON)
表示OK。表示までに38秒。
Mapbox GL JS v1 (pbf)
表示OK。表示までに4秒。
450,000件 - 108MB
OpenLayers
Mapbox GL JS v1 (GeoJSON)
表示OK。表示までに75秒。
Mapbox GL JS v1 (pbf)
700,000件 - 162MB
OpenLayers
表示されない。
Mapbox GL JS v1 (GeoJSON)
表示されるけどブラウザ落ちる。表示までに120秒。
Mapbox GL JS v1 (pbf)
Mapbox GL JS・Leaflet・OpenLayersでのポイント表示数の限界を探ることがでました  
表にまとめるとこんな感じになります 
ポイントデータ表示可否
| マップライブラリ | 50,000件 | 100,000件 | 200,000件 | 450,000件 | 700,000件 | 
|---|---|---|---|---|---|
| Leaflet | △ | ✕ | ✕ | ✕ | ✕ | 
| OpenLayers |  |  | △ | △ | ✕ | 
| Mapbox GL JS v1 (GeoJSON) |  |  |  |  | △ | 
| Mapbox GL JS v1 (pbf) |  |  |  |  |  | 
初期表示までの時間 (秒)
| マップライブラリ | 50,000件 | 100,000件 | 200,000件 | 450,000件 | 700,000件 | 
|---|---|---|---|---|---|
| Leaflet | 6秒 | ✕ | ✕ | ✕ | ✕ | 
| OpenLayers | 7秒 | 10秒 | 23秒 | 45秒 | ✕ | 
| Mapbox GL JS v1 (GeoJSON) | 8秒 | 28秒 | 38秒 | 75秒 | 120秒 | 
| Mapbox GL JS v1 (pbf) | 2秒 | 3秒 | 4秒 | 4秒 | 5秒 | 
※検証はテザリング環境(18Mbps)であえておこなってみたので、通常の自宅でのインターネット環境を利用すると何十倍で表示される可能性はあります。
Mapbox GL JS・Leaflet・OpenLayersでのポイント表示数の限界を探ることができました  
Leafletは、約5万件程度。
OpenLayersは、約20万件程度。
Mapbox GL JS v1 (GeoJSON)は、約50万件程度。
Mapbox GL JS v1 (pbf)は、70万件以上。
Leafletは、v1になった時にレンダリングが改善されたとはいえ、やはり手軽な分他のライブラリに比べて厳しそうで、OpenLayersとMapbox GL JS v1 (GeoJSON)は表示されれば問題なく動作するのですが、読み込みに時間がかかるのがWebアプリケーションとして現実的ではない気もします。Mapbox GL JS v1 (pbf)は、表示時間等特に問題ないですが、間引き処理の問題で一部欠けたように見えたり再描画処理について少し時間かかることがあったりしました。
今回の検証で、フロントエンドのみでもMapbox GL JS v1を利用することにより、GeoJSONで約50万件程度、バイナリベクトルタイル(pbf)で70万件以上を表示できることがわかりました 
ただ、データといってもポイントをアイコンに変更したり、クラスタ化したり、バイナリベクトルタイルの変換時に簡素化したり、ポリゴンやラインもあるのでサイズは大きくなりますし、ブラウザの種類も影響しますし、検索やスタイル変更についても規模が大きくなってきたり、データの登録削除機能が必要だとバックエンドの導入も必要になってきます。
フロントエンドでできる幅は広がったとはいえ、バックエンドの処理が必要とされる機会は今後もたくさんあると思うので、要件に合わせた技術選定の参考にしていただければと思います 
これを見たどなたかが、deck.glやCesium等の他のマップライブラリでも探ってくれることを祈ります 
最後にブラウザで数千件以上表示できるだけでもすごい時代になったなと思いました  (いまさら)
 (いまさら)
Mapbox GL JS・Leaflet・OpenLayersについて、他にも記事を書いています。よろしければぜひ 
tags - Mapbox GL JS
tags - Leaflet
tags - OpenLayers
やってみたシリーズ 
tags - Try
※「このアプリケーションの作成に当たっては、国土地理院長の承認を得て、同院発行の電子国土基本図(地名情報)住居表示住所を使用した。(承認番号 平30情使、第928号)」



















