画像


画像


画像






画像






この記事は、「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上でビューアツールとして利用可能になりそうですね。









Q&A