今回作ったもの
静岡県点群データマップ
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の仲間入りをしたようです。オープンデータ活用の本流とは若干逸れている気がする今回の案件ですが、少なくとも自己研鑽にはなったかなと思います。これからもオープンデータ界隈にはアンテナを張っておきたいと思います。