Mapbox + Vue.js + OpenStreetMap で道路ネットワークグラフの描画
概要
以前某インターンで Vue.js を初めて使いました.自分の専門が地理情報関係なので, Vue.js と地図ライブラリを使って簡単なアプリを作ってみようと思いました.地図ライブラリには Mapbox を採用しました. Mapbox は自由度が非常に高く,使いこなせるようになりたいと思っているためです.
何をしようかと考えたのですが,Mapbox の長所は先ほど述べた通りの自由度の高さと描画できる数の多さだと思っているので,たくさん描画できるものにしようと思いました.そこで, OpenStreetMap が提供している道路ネットワークグラフにしました.
環境構築はこちら の通りでした.
実装
道路ネットワークグラフの取得
道路ネットワークグラフは OSMnx を使う方法 で取得しました.今回は新宿区のデータを使用します. 自分は既に環境構築していたのですが,苦戦した記憶があります.ただ,これはかなり便利なのでおすすめです.
道路ネットワークグラフの geojson への変換
取得した道路ネットワークグラフの csv を geojson へ変換します.以前の記事では csv2geojson というものを使いましたが,今回は自力で変換してみました.割と力業でなんとかした感がありますが,最後にコードを残しておきます.
Vue.js で道路ネットワークグラフの描画
ただグラフを描画するだけでは面白くないため,別の場所から新宿駅へスクロールするボタンをつけます.UI フレームワークには Element Plus を使用しました.
Element UI と Element Plus を間違えたり,main.ts の app.use のあたりで沼ったりしました.
グラフの描画はこちら を参考にして作成しました.data のところに先ほど変換した geojson に指定して完成です.
Vue で作成したディレクトリには assets というのがあって,そこに geojson を入れていたのですが,ここではなく public に入れる,というところでまた詰まってました.
完成
道路ネットワークグラフはピンクで表示しています.マップを移動させ後にボタンを押すと新宿駅が中心になるように移動します.ほぼ SPA になったのですが,これを基本にしていけばもっといろいろできるので試していこうと思います.
コード
MyMap.vue
<script setup lang="ts"> import { onMounted, ref } from "vue"; import mapboxgl from "mapbox-gl"; import "mapbox-gl/dist/mapbox-gl.css"; const visibility = ref("visible"); mapboxgl.accessToken = "API key"; let mapObj; onMounted(() => { mapObj = new mapboxgl.Map({ container: "japan-shinjuku", // container ID style: "mapbox://styles/mapbox/streets-v11", // style URL center: [139.723845, 35.654858], // starting position [lng, lat] // center: [-122.486052, 37.830348], zoom: 14, // starting zoom }); mapObj.on("load", () => { mapObj.addSource("road-network", { type: "geojson", data: "/road_network_edges.geojson", }); mapObj.addLayer({ id: "road-network", type: "line", source: "road-network", layout: { "line-join": "round", "line-cap": "round", visibility: visibility.value, }, paint: { "line-color": "#ff00ff", "line-width": 4, }, }); }); }); const toShinjuku = (e: Event) => { mapObj.flyTo({ center: [139.7, 35.689], essential: true, }); }; </script> <template> <div id="japan-shinjuku" :style="{ height: '60em' }"></div> <div class="buttons"> <el-row class="mb-4"> <el-button type="primary" @click="toShinjuku" >Move Shinjuku Station</el-button > </el-row> </div> </template> <style scoped> .buttons { margin-top: 20px; justify-content: center; display: flex; } </style>
csv2geojson.js
// read csv file and convert to geojson const fs = require('fs'); const csv = require('csv'); const FOLDER_NAME = '/Shinjuku,Tokyo,Japan'; const FILE_NAME = '/road_network_edges.csv'; function genGeoJson() { return new Promise(function (resolve, reject) { let geojson = { "type": "FeatureCollection"}; fs.createReadStream(__dirname + FOLDER_NAME + FILE_NAME) .pipe(csv.parse(function (err, data) { let features = []; data.forEach((element, index) => { if (index == 0) { return; } element.forEach((item) => { const result = item.indexOf('LINESTRING'); if (result !== -1) { const replacementWords = [ ['LINESTRING', ''], [' (', ''], [')', ''], [/, /g, '],['], // [/ /g, ','], ] replacementWords.forEach((replacementWord) => { item = item.replace(replacementWord[0], replacementWord[1]); }); item = item.split('],['); item = item.map((item) => { return item.split(' ').map(Number); }); // console.log(item); const obj = { "type": "Feature", "geometry": { "type": "LineString", "coordinates": item }, "properties": {} }; features.push(obj); } }) }); geojson['features'] = features; resolve(geojson); })); }); } genGeoJson().then(function (geojson) { fs.writeFileSync(__dirname + FOLDER_NAME + '/road_network_edges.geojson', JSON.stringify(geojson, null, 2)); console.log('done'); });