deck.glで公共交通データ(GTFS) データを可視化する
この記事はdeck.gl Advent Calendar 2021 参加記事です。
概要
GTFS(General Transit Feed Specification)は、公共交通に関するデータを扱うための世界標準データフォーマットです。多くの地域で交通データを公開する際のフォーマットとして採用されており、日本でも徐々に普及が進んでいます。
そこで、この記事ではdeck.glを使って、GTFSに含まれる経路情報と停留所情報を地図に表示する方法を解説します。
サンプルコード
「あおい交通 オープンデータ」よりダウンロードさせていただいたaoikotsu_GTFS_20211031.zipファイルから、経路データと停留所データを地図に掲載しています。
経路データはshape_idごとにグルーピングしランダムに色を塗っています。
背景地図は表示すると経路が見えにくくなるので切り替え式にしました。
解説
GTFSデータは、複数のcsvデータをzipに圧縮したものとして公開されます。
そこで、loaders.glを用いてzipファイルを読み込み、フロントエンドで解凍した上で各csvデータをパースしてJavaScrpitオブジェクトへと変換しています。
また、stops.txt(停留所データ)とshapes.txt(経路データ)については、turf.jsを用いてGeoJSONへと変換しています。
経路データをGeoJSONに変換する過程で、trips.txt(便情報)を経路データの属性データとして付加しています。
一通りの変換が終わったら、deck.glのGeojsonLayerを使って可視化します。
以下は、 GTFSファイルの読み込みと、一連のコンバート処理のコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
//配列をグルービングする const groupBy = (array, getKey) => array.reduce((obj, cur, idx, src) => { const key = getKey(cur, idx, src); (obj[key] || (obj[key] = [])).push(cur); return obj; }, {}); useEffect(() => { //経路データを情報をgeojsonに変換 const shapes2geojson = (shapes, trips) => { let lines = []; //shapes.txt(経路)の内容を、shape_idを元にグルーピング const shapesGrouped = groupBy(shapes, (d) => d.shape_id); //trips.txt(便情報)の内容を、shape_idを元にグルーピング const tripsGrouped = groupBy(trips, (d) => d.shape_id); //shapes.txtの内容をグループごとにgeojson(line)に変換 for (let key in shapesGrouped) { //ポイントを[lon, lat]形式の配列に変換 const pointArry = shapesGrouped[key] .sort((a, b) => a.shape_pt_sequence - b.shape_pt_sequence) //念の為shape_pt_sequenceを元に並べ替える .map((d) => [d.shape_pt_lon, d.shape_pt_lat]); //ポイント配列をラインに、属性値に便情報を含める const lineGeoJson = turf.lineString(pointArry, { ...tripsGrouped[key] }); lines.push(lineGeoJson); } //マルチライン化する const multiLine = turf.featureCollection(lines); return multiLine; }; //停留所データをgeojsonに変換 const stops2geojson = (stops) => { let points = []; stops.forEach((d) => { const lnglat = [d.stop_lon, d.stop_lat]; const point = turf.point(lnglat, d); points.push(point); }); //マルチポイント化する const multiPoint = turf.featureCollection(points); return multiPoint; }; /******************************************** * gtfsデータの読み込み ********************************************/ const loadData = async (url) => { const data = {}; //zipファイルを読み込み const zip = await load(url, ZipLoader); //zip内ファイルをcsvとしてパース for (let fileName in zip) { const csv = await parse(zip[fileName], CSVLoader); data[fileName] = csv; } //geojsonに変換したshapesを保管する data["stops.geojson"] = stops2geojson(data["stops.txt"]); //geojsonに変換したshapesを保管する data["shapes.geojson"] = shapes2geojson( data["shapes.txt"], data["trips.txt"] ); console.log("loaded gtfs", data); setData(data); }; loadData("./data/aoikotsu_GTFS.zip"); }, []); |