一覧に戻る

静岡県の点群データDBをVue+Leafletで表示してみた

#Python#Flask#opendata#leaflet#Vue.js

今回作ったもの

静岡県点群データマップ GitHub - shizokaPCDB

注意

この記事はLIDARデータをLeafletに表示する記事ではありません。 点群データベースの情報(データの取得地や工事日など)を表示するものです。

はじめに

Rockな静岡県庁さんは、自前のウェブサイトShizuoka Point Cloud DB(以下「静岡PCDB」)で工事成果品のLIDARデータをオープンデータとして多数公開しています。興味本位でソースを眺めてみると、外部からでもデータを取得出来そうだなとわかりました(htmlにJavaScriptが直書きだったため)。ただこのサイト、上記画像を見るとわかるとおり①マーカーの数が多くパフォーマンスに影響が出ている事と、②地図領域が変わるたびにその領域に含まれるデータをサーバーから取得しておりさらにパフォーマンスが低下している事、という2つの問題があると思いました。データが外部からでも取得出来るなら改善の余地があるかもと思い、開発に至った次第です。

そんな訳でデータベースへのアクセス・整形とフロントでの表示の二本立てでお送りします。

使用技術

  • Flask
  • Vue.js
  • Vue2-leaflet

データベースへのアクセス・整形

###アクセス 静岡PCDBのソースを見ると、サーバーへのアクセス・Leafletでの表示は100行程度で実装されていました。 サーバーからデータを取得する部分を以下に示します。


var data = { request : "MarkerSet",
                Xmax : map.getBounds().getNorthEast().lng,
                Xmin : map.getBounds().getSouthWest().lng ,
                Ymax : map.getBounds().getNorthEast().lat,
                Ymin : map.getBounds().getSouthWest().lat };
    $.ajax("ankenmapsrc",{
        type: "GET",
        data: data,
        success: function(data, dataType){
            var myIcon = L.icon({
                iconUrl: 'http://cdn.leafletjs.com/leaflet-0.5/images/marker-icon@2x.png',
                iconSize: [15,25],
                iconAnchor: [5, 5],
            });
//以下処理が続きます

dataというクエリでankenmapsrcにアクセスすれば、データが得られそうです。 ここでdataはリクエスト名"MarkerSet"と取得する領域を示すXmax,Xmin,Ymax,Yminを持ちます。 領域の4つの変数には、Leafletで表示している領域が格納されます。 この処理はLeafletの表示領域が更新されるたびに呼ばれます。つまりその都度サーバーからデータを取得している訳です。

さてこのリクエストの返り値を見てみましょう。

30XXX01010001:平成30年度韮山反射炉計測業務:138.96214537214:35.03962001009?
28XXX00030007:白糸の滝滝見橋周辺整備事業 その7:138.58870495572:35.312506370532?
28XXX00030008:白糸の滝滝見橋周辺整備事業 その8:138.58881502806:35.312596432406?
28XXX00030009:白糸の滝滝見橋周辺整備事業 その9:138.58892510063:35.312686494178?
29C2001011361:平成29年度[第29-C2001-01号] 伊豆半島の屋外広告物の実態調査業務委託(函南町道_1-2号線):138.93794860595:35.083520492945
...

本来は改行はありませんが、説明のため追加しています。 このデータを見ると、工事ごとの区切り文字は"?"で、データ要素ごとの区切りは":"である事がわかります。

###整形 Flaskで静岡PCDBにアクセスし、整形したデータをレスポンスするAPIサーバをつくります。

import urllib.request, urllib.parse
import json
@app.route('/markers')
def getMarkers():
    #全件取得するために、静岡県全域が含まれる緯度経度を整数値で設定
    xMax = 140
    xMin = 137
    yMax = 36
    yMin = 33

    params = {
        'request':'MarkerSet',
        'Xmax':xMax,
        'Xmin':xMin,
        'Ymax':yMax,
        'Ymin':yMin
    }
    p = urllib.parse.urlencode(params)
    url = "https://pointcloud.pref.shizuoka.jp/lasmap/ankenmapsrc?" + p

    #上記で生成したURLパラメータでSIZUOKA POINT CLOUD DBにリクエストし案件一覧文字列を取得
    allAnkenStr = ""
    with urllib.request.urlopen(url) as res:
        allAnkenStr = res.read().decode()

    #returnするjsonを作成
    ankensObj = {
        "ankenList":[]
    }

    ankenList = allAnkenStr.split('?')
    for anken in ankenList:
        ankenInfo = anken.split(':')
        #不適切なデータがあった場合、スキップする
        if len(ankenInfo) != 4:
            continue

        #和暦を西暦に変換
        yy = int(ankenInfo[0][:2])
        #令和
        if yy < 24:
            yyyy = 2018 + yy
        else:
            yyyy = 1988 + yy

        ankenObj = {
            "no":ankenInfo[0],
            "name":ankenInfo[1],
            "lon":ankenInfo[2],
            "lat":ankenInfo[3],
            "year":yyyy
        }
        ankensObj['ankenList'].append(ankenObj)
    return jsonify(ankensObj)

冒頭で説明した問題点②地図領域が変わるたびにその領域に含まれるデータをサーバーから取得しておりさらにパフォーマンスが低下している事については、静岡県の領域を全て含むXmax,Xmin,Ymax,Yminとする事で解決しました。というのも、全件でも1400件程度であり、実際に全件取得してみても遅くはなかったため、最初に全部取得しておけば良いという結論になりました。

取得したankenデータをパースして、工事番号、工事名、経度・緯度、工事年度をjsonとしてreturnする事としました。

フロントでの表示

残る問題点①マーカーの数が多くパフォーマンスに影響が出ている事ですが、動作の早いMapbox GL JSで表示すれば解決すると考えていました。しかしながら実装してみたところ、マーカーの表示はLeafletより遅くなってしまいました。という事で、LeafletのMarkerClusterを採用する事でこの問題を解決しました。

LeafletとVueの環境構築についてはTry #027 – Vue.jsでLeafletとMapbox GL JSの開発環境を構築してみたが詳しいので割愛します。 くわえて、vue2-leaflet-markerclusterをインストールします。

npm install vue2-leaflet-markercluster

VueでAPIサーバに非同期通信

FetchAPIを使います。 App.vueでデータを取得しMapPane.vueへ渡します。

<MapPane :ankens="ankens"/>
  data() {
    return {
      ankens:[],
    }
  },
  created() {
    let vm = this
    fetch("/markers")
    .then(response => {
        return response.json()
    })
    .then(data => {
        //sortしてからdataを渡しています
        vm.ankens = data.ankenList.sort(function (a, b) {
            if (a.no < b.no) {
                return 1
            }
            if (a.no > b.no) {
                return -1
            }
            return 0 
        }).sort(function (a, b) {
            if (a.year < b.year) {
                return 1
            }
            if (a.year > b.year) {
                return -1
            }
            return 0 
        })
    })
    .catch(error => {
        console.log(error)
        alert("エラーが発生しました。")
    });
  }
<template>
    <div class="mapPane">
        <l-map
            :zoom="zoom"
            :center="center"
            :preferCanvas="true"
        >
            <l-control-scale
                position="bottomleft"
                :imperial="false"
                :metric="true"
            ></l-control-scale>

            <l-tile-layer
                :name="tileProvider.name"
                :visible="tileProvider.visible"
                :url="tileProvider.url"
                :attribution="tileProvider.attribution"
            ></l-tile-layer>

            <Vue2LeafletMarkerCluster :options="clusterOptions" >
                <LMarker v-for="anken in ankens" :key="anken.no" :lat-lng="makeLatLng(anken)" @click="onMarkerClick(anken.no)">
                    <LPopup :content="makeMarkerContent(anken)" ></LPopup>
                </LMarker>
            </Vue2LeafletMarkerCluster>
        </l-map>
    </div>
</template>

props: {
    ankens:Array
},
<style scoped>
    @import "~leaflet.markercluster/dist/MarkerCluster.css";
    @import "~leaflet.markercluster/dist/MarkerCluster.Default.css";

</style>

LMarkerをVue2LeafletMarkerClusterで包めば良きにはからってくれます。 以上により、密接したマーカーはクラスター(束)となり、拡大するまでまとめてひとつの地物として表示されます。結果問題点①も解決しました。

おわりに

今後はデータの一覧表示をソート出来たり検索出来たり、ローディング中のアニメーションを表示したいなと思っています。LIDARデータがアツいらしく調べていると静岡PCDBに辿り着きましたが、思わぬ大脱線となりました。これほど多数の貴重なデータをオープンデータとする静岡県さんはマジでRockであり、最近では兵庫県さんもRockerの仲間入りをしたようです。オープンデータ活用の本流とは若干逸れている気がする今回の案件ですが、少なくとも自己研鑽にはなったかなと思います。これからもオープンデータ界隈にはアンテナを張っておきたいと思います。