dayjournal memo

Total 975 articles!!

Try #018 – BigQuery GISとFlaskとMapbox GL JSを組み合わせて可視化してみた

Yasunori Kirimoto's avatar

画像


画像


画像




画像




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



book

Q&A