この記事は、「Google Cloud Platform Advent Calendar 2018」の9日目の記事です。
BigQuery GISとFlaskとMapbox GL JSを組み合わせて可視化してみました!
今回は、BigQueryのGIS系関数の利用方法や、BigQuery Geo Viz以外の可視化方法を試していきます。BigQueryへのデータ登録については触れません。
BigQuery GISのデータ登録の流れについては、こちらの記事がすごく参考になります。今回は、国土数値情報 都市公園データをBigQuery GISにインポートしてみました。
可視化の方法については、BigQuery Geo Vizを利用すると手軽に確認はできますが、今回はMapbox GL JSと組み合わせてより自由度が高い可視化をします。
全体の構成はできるだけシンプルに。
バックエンド - BigQuery GISとFlaskを利用して空間検索可能なAPI構築
- Flask
- BigQuery GIS
フロントエンド - Mapbox GL JSを利用して地図上にデータを可視化
- Mapbox GL JS
- webpack
バックエンド
まずは、バックエンドを構築していきます。
最初に、GIS関数を試すために、下記画像の範囲内の公園のみ抽出してみます。
クエリを作成して、BigQueryのコンソールで実行してみます。フィールドについては、経緯度・公園名・施設を抽出してみます。
SELECT xcoord, ycoord, equipment, name FROM park.sample WHERE ST_WITHIN(ST_GeogFromText(wkt),ST_MakePolygon(ST_GeogFromText("LINESTRING(141.3370943069458 43.05835290494474, 141.36644840240479 43.05835290494474, 141.36644840240479 43.07496958260876, 141.3370943069458 43.07496958260876)")));
範囲内の公園のみを抽出することができました。
次に、BigQuery GISをGeoJSONで配信するAPIを構築してみます。
事前準備として、BigQueryに接続するためには認証設定が必要になります。認証用JSONファイルを作成し記述する必要があります。
認証用JSONファイルを作成するには、GCPのコンソールから「APIとサービス」→「認証情報」→「認証情報を作成」→「サービスアカウントキー」を選択します。
キー作成のために、下記のように設定します。設定後「作成」を実行するとJSONファイルがダウンロードできます。
ダウンロードされたJSONファイルを、「api.py」と同じ場所に置いておきます。今回はkey.jsonとしました。
※JSONファイルにはGCPの認証情報が入っているので、取扱には注意してください。
各パッケージインストール
pip install Flask
pip install flask-cors
pip install google-cloud-bigquery
api.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#モジュールインポート
from flask import Flask, jsonify, abort, make_response, request
from flask_cors import CORS
from google.cloud import bigquery
#flaskのインスタンス作成
app = Flask(__name__)
#CORS対応
CORS(app)
#日本語表示対応
app.config['JSON_AS_ASCII'] = False
#JSON取得処理
@app.route('/api', methods=['GET'])
def api_get():
#認証設定
client = bigquery.Client.from_service_account_json('./key.json')
#検索条件(全データから指定ポリゴンの範囲内のポイント抜き出し検索)
query = 'SELECT xcoord, ycoord, equipment, name FROM park.sample WHERE ST_WITHIN(ST_GeogFromText(wkt),ST_MakePolygon(ST_GeogFromText("LINESTRING(141.3370943069458 43.05835290494474, 141.36644840240479 43.05835290494474, 141.36644840240479 43.07496958260876, 141.3370943069458 43.07496958260876)")));'
#データ取得
queryall = client.query(query).result()
#GeoJSON作成
result = []
for r in queryall:
add = {
"type": "Feature",
"properties": {
"name": r.name,
"equipment": r.equipment
},
"geometry": {
"type": "Point",
"coordinates": [r.xcoord, r.ycoord]
}
}
result.append(add)
#GeoJSON定義
resultall = {"type": "FeatureCollection"}
resultall["features"] = result
#結果表示
print(resultall)
#GeoJSONを出力
return make_response(jsonify(resultall))
#エラー処理
@app.errorhandler(404)
def not_found(error):
#エラーJSON作成
result = {
"error": "存在しません。",
"result":False
}
#エラーJSONを出力
return make_response(jsonify(result), 404)
#app実行
if __name__ == '__main__':
app.run(host='0.0.0.0',port=5000,debug=True)
APIの構築が完了したので、ローカルサーバーを起動してみます。
python api.py
下記アドレスにアクセスしてみます。
http://0.0.0.0:5000/api
BigQuery GISからのデータ取得と、GeoJSON形式の変換ができているのを確認できました。
フロントエンド
最後に、フロントエンドを構築していきます。
今回は、mapboxgljs-starterというMapbox GL JSを手軽に始めるビルド環境を利用します。
mapboxgljs-starterをダウンロードして「script.js」の変更と、任意のアイコン画像「./img/sample.png」を追加します。
script.js
//API取得
fetch('http://localhost:5000/api')
.then(response => {
//APIからJSON取得
return response.json();
})
.then(result => {
//MIERUNE MONO読み込み
var map = new mapboxgl.Map({
container: "map",
style: {
"version": 8,
"sources": {
"MIERUNEMAP": {
"type": "raster",
"tiles": ['https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png'],
"tileSize": 256
}
},
"layers": [{
"id": "MIERUNEMAP",
"type": "raster",
"source": "MIERUNEMAP",
"minzoom": 0,
"maxzoom": 18
}]
},
center: [141.366166, 43.06483],
zoom: 11
});
map.on('load', function () {
// アイコン画像設定
map.loadImage('./img/sample.png', function (error, res) {
map.addImage('sample', res);
});
// GeoJSON設定
map.addSource('symbol_sample', {
type: 'geojson',
data: result
});
// スタイル設定
map.addLayer({
"id": "symbol_sample",
"type": "symbol",
"source": "symbol_sample",
"layout": {
"icon-image": "sample",
"icon-allow-overlap": true,
"icon-size": 1.00
},
"paint": {}
});
// アイコンクリックイベント
map.on('click', "symbol_sample", function (e) {
var coordinates = e.lngLat;
// 属性設定
var description = '<p>属性</p>' +
'名称: ' + e.features[0].properties.name + '<br>' +
'施設: ' + e.features[0].properties.equipment;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
//カーソルON,OFF
map.on('mouseenter', "symbol_sample", function () {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', "symbol_sample", function () {
map.getCanvas().style.cursor = '';
});
});
// コントロール関係表示
map.addControl(new mapboxgl.NavigationControl());
})
.catch((error) =>
console.log(error)
);
実行環境
node v10.0.0
npm v6.4.1
パッケージインストール
npm install
ビルド
npm run build
開発用
npm run dev
開発用で確認してみます。
BigQuery GISとFlaskとMapbox GL JSを組み合わせて可視化できることを確認できました!
BigQueryが地理空間情報を扱えるようになったことで、例えば今まで分析に利用していた大量のBigQueryデータのGIS版社内ツールや、web上でビューアツールとして利用可能になりそうですね。