XI本部スマートソサエティセンターの野網です。データ連携基盤に関連する自治体案件に携わっています。この自治体案件ではデータ連携基盤だけでなく、基盤上で提供するサービスについても検討しています。防災やインフラ管理の観点では、地図上でさまざまなデータを重ね合わせて可視化するGIS(地理情報システム)の実現が重要視されています。
Leaflet
今回は地図機能を提供するオープンソースのJavaScriptライブラリである「Leaflet」を触って色々と機能を確認してみました。
- ベースとなる地図を指定できるか
- 地図上にピンを立てることができるか(Google Mapsとかでよく見るもの)
- 地図上に任意の点/線/面を表示できるか
- 地図上にシェープファイルを表示できるか
ベースマップ&ピンの表示
ベースマップとして、OpenStreetMap1と地理院地図2を使用しました。また、駅の位置にピンを表示するレイヤーを作成しました。Control.Layersを利用することで、ベースマップの切り替えやレイヤーの表示/非表示を実現しています。
// ベースマップ let osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }); let gsi = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', { attribution: '© 地理院地図' }); // ピン let shinagawa = L.marker([35.6291112, 139.7389313]).bindPopup('品川駅'); let takanawa_gateway = L.marker([35.6355406, 139.7407245]).bindPopup('高輪ゲートウェイ駅'); let stations = L.layerGroup([shinagawa, takanawa_gateway]); // 地図セットアップ map = L.map('map', { center: [35.62455060680114, 139.7395781036654], zoom: 16, layers: [osm, stations] }); let baseMaps = { "OpenStreetMap": osm, "地理院地図": gsi }; let overlayMaps = { "駅": stations }; let layerControl = L.control.layers(baseMaps, overlayMaps).addTo(map);
点/線/面、シェープファイルの表示
最も基本的な描画機能である点/線/面の表示も確認します。これらはGeoJSON形式のファイルを読み込む形としており、非同期処理になっています。このファイルは手打ちで作成しました。
また、GISでよく出てくるシェープファイルの表示も行いました。シェープファイルは国土数値情報で公開されている道路のデータを取得しています。Leafletには様々なプラグインがあり、シェープファイルの表示に対応するものもありましたが、日本語に対応していないという記事があったので、この記事で紹介されているmapshaperを利用してシェープファイルをGeoJSON形式に事前に変換するという方法をとりました。
これらを先ほどのControl.Layersに追加し、期待していた機能を試すことができました。
async function fetchGeoJson(filename) { let response = await fetch(filename); return await response.json(); } async function add_layer() { let my_geo_json = await fetchGeoJson("my_geojson_data.json"); // GeoJSONレイヤーを追加 let my_geo_json_layer = L.geoJSON(my_geo_json, { onEachFeature: function (feature, layer) { if (feature.properties && feature.properties.name) { layer.bindPopup(feature.properties.name); } }, style: function (feature) { switch (feature.geometry.type) { case 'Point': return {color: "#ff0000"}; case 'LineString': return {color: "#0000ff"}; case 'Polygon': return {color: "#00ff00"}; } }, pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, { radius: 8, fillColor: "#ff0000", color: "#000", weight: 1, opacity: 1, fillOpacity: 0.8 }); } }).addTo(map); my_geo_json_layer_group = L.layerGroup([my_geo_json_layer]); layerControl.addOverlay(my_geo_json_layer_group, "点/線/面") // 道路を追加 let road_feature = await fetchGeoJson("N01-07L-2K-13_Road.json"); let road_layer = L.geoJSON(road_feature, { style: function (feature) { return {color: "#ff00ff"}; } }).addTo(map); road_layer_group = L.layerGroup([road_layer]); layerControl.addOverlay(road_layer_group, "道路") } add_layer();
サンプルコード
参考に今回の機能確認で利用したコードの全容を記載します。
index.html:
<!DOCTYPE html> <html> <head> <title>Leaflet Sample Map</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Leaflet CSS --> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/> <!-- Leaflet Plugin CSS --> <link rel="stylesheet" href="https://unpkg.com/leaflet-switch-basemap@1.0.6/src/L.switchBasemap.css" crossorigin=""/> <style> #map { width: 100%; height: 600px; } </style> </head> <body> <h1>Leaflet Sample Map</h1> <div id="map"></div> <!-- Leaflet JavaScript --> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script> <!-- アプリケーションのJavaScript --> <script src="app.js"></script> </body> </html>
app.js:
// ベースマップ let osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }); let gsi = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', { attribution: '© 地理院地図' }); // ピン let shinagawa = L.marker([35.6291112, 139.7389313]).bindPopup('品川駅'); let takanawa_gateway = L.marker([35.6355406, 139.7407245]).bindPopup('高輪ゲートウェイ駅'); let stations = L.layerGroup([shinagawa, takanawa_gateway]); // 地図セットアップ map = L.map('map', { center: [35.62455060680114, 139.7395781036654], zoom: 16, layers: [osm, stations] }); let baseMaps = { "OpenStreetMap": osm, "地理院地図": gsi }; let overlayMaps = { "駅": stations }; let layerControl = L.control.layers(baseMaps, overlayMaps).addTo(map); async function fetchGeoJson(filename) { let response = await fetch(filename); return await response.json(); } async function add_layer() { let my_geo_json = await fetchGeoJson("my_geojson_data.json"); // GeoJSONレイヤーを追加 let my_geo_json_layer = L.geoJSON(my_geo_json, { onEachFeature: function (feature, layer) { if (feature.properties && feature.properties.name) { layer.bindPopup(feature.properties.name); } }, style: function (feature) { switch (feature.geometry.type) { case 'Point': return {color: "#ff0000"}; case 'LineString': return {color: "#0000ff"}; case 'Polygon': return {color: "#00ff00"}; } }, pointToLayer: function (feature, latlng) { return L.circleMarker(latlng, { radius: 8, fillColor: "#ff0000", color: "#000", weight: 1, opacity: 1, fillOpacity: 0.8 }); } }).addTo(map); my_geo_json_layer_group = L.layerGroup([my_geo_json_layer]); layerControl.addOverlay(my_geo_json_layer_group, "点/線/面") // 道路を追加 let road_feature = await fetchGeoJson("N01-07L-2K-13_Road.json"); let road_layer = L.geoJSON(road_feature, { style: function (feature) { return {color: "#ff00ff"}; } }).addTo(map); road_layer_group = L.layerGroup([road_layer]); layerControl.addOverlay(road_layer_group, "道路") } add_layer();
app.js
で参照しているN01-07L-2K-13_Road.json
は国土数値情報から取得した道路データです。色々なデータで試してみてください。
my_geojson_data.json:
[ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [139.7395781036654, 35.62455060680114] }, "properties": { "name": "Sample Point" } }, { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [ [139.73944850383708, 35.62501310494848], [139.73909069834616, 35.62487771334422], [139.73946289831088, 35.62424755434228] ] }, "properties": { "name": "Sample Line" } }, { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [139.74004599801373, 35.62494392209728], [139.73961416239794, 35.62473352999296], [139.7398032581179, 35.62440431429395], [139.7401841317666, 35.62449152388262] ] ] }, "properties": { "name": "Sample Polygon" } } ]
おわりに
シェープファイルをGeoJSON形式に変換が必要となる部分など、詰まることもありましたが、Leafletのような、オープンソースのツールを使うことで、手軽にGISの可能性を試すことができるのは魅力的だと感じました。プラグインも含めて機能が多数あり、サンプルも充実しているので、地図サービスに興味があればチェックしていただく価値があると思います。
私たちは一緒に働いてくれる仲間を募集しています!
社会課題解決ソリューションプロジェクト_システム開発プロジェクトマネージャー/リーダー 社会課題解決ソリューションプロジェクト_アーキテクト/テックリード クラウドアーキテクト執筆:@noami.shunsuke、レビュー:Ishizawa Kento (@kent)
(Shodoで執筆されました)