今回もオープンデータの活用その2です。
過去記事: 自分の町の避難所情報をGoogle Mapsに表示する
緊急事態宣言中で外出もままならない日々を過ごしていると思いますが、それでも最低限買い物には行かなければ生活はできません。そんなある日、家族から「近所の小郡イオンと甘木イオンどっちが感染のリスクが少ないの?」と聞かれました。うーんどっちだろう?
というわけでオープンデータから近隣の新型コロナ発生状況を可視化して判断してみようと思います。
最初に断っておきますが、私は医者でも感染症の専門家でもないので、この結果を踏まえて何かを訴えたり警鐘を鳴らす意図はありません。
オープンデータの取得
まず、新型コロナの発生状況のデータを取得します。
リンク:福岡県新型コロナウイルス感染症陽性者発表情報
ここからcsv形式のデータがダウンロードできます。
しかし、このままだとデータの一覧に過ぎないので集計を行ってきます。
集計と加工
今回、Rubyを使って簡易的にデータの集計と加工を行います。
まずは、古いデータを参考にしても仕方がないので過去一か月分のみを参考にします。
「公表_年月日」が本日の一か月前より新しいものを絞り込みます。空のデータも存在していたのでエラーにならないように一工夫しています。
次に「居住地」でグループ分けし件数を集計していきます。閾値として件数が1件のみの居住地は除外しています。
ここで作業は終わりなのですが、ついでにその居住区がどの座標にあるのかGeocoding APIで緯度経度を事前に取得しておきます。最初はグーグルマップ表示時にリアルタイムで処理していたのですが、あまりにも重たくAPIの料金も気になるので先に処理するようにしてみました。
require 'csv'
require 'json'
require 'geocoder'
Geocoder.configure(language: :ja, units: :km, lookup: :google, api_key: '*********************')
table = CSV.read(ARGV[0], headers: true)
from = Date.today << 1
targets = table.select{ Date.parse(_1['公表_年月日'] || '1970/01/01') > from }
groups = targets.group_by{ _1['居住地'] }.select {|k, v| v.count > 1 }
json = groups.map do |k, v|
results = Geocoder.search(k)
{
city: k,
count: v.count,
location: results.first.coordinates
}
end
File.open("covid19.json", 'w') do |file|
JSON.dump(json, file)
end
集計結果は後で自分の扱いやすいような形式で出力しておきます。
$ ruby csv2json.rb ダウンロードしたCSVファイル
Googleマップに表示
ここまでできたら後は加工後のデータを好きなように表示するだけです。今回はGoogle MapのCircle描画を使って新型コロナの発生数を円の大きさとして描画していきます。
Circleはradius(半径)を引数指定する必要があるので、円の面積から半径を求めます
r = \sqrt{\frac{S}{\pi}} \\
<!DOCTYPE html>
<html>
<head>
<title>covid-19</title>
<script
src="https://maps.googleapis.com/maps/api/js?key=*************&callback=initMap&libraries=&v=weekly"
defer
></script>
<style type="text/css">
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
<script>
function initMap() {
var currentPos = { lat: 33.3881, lng: 130.6127 };
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 10,
center: currentPos,
});
fetch('./covid19.json')
.then((response) => response.json())
.then((jsonData) => {
console.log(jsonData)
for (const city of jsonData) {
new google.maps.Circle({
map: map,
center: {lat: city.location[0], lng: city.location[1]},
radius: Math.sqrt(city.count / Math.PI) * 500,
strokeWeight: 1,
strokeColor: '#ff0000',
strokeOpacity: 0.2,
fillColor: '#ff0000',
fillOpacity: 0.2
});
}
});
}
</script>
</head>
<body>
<div id="map"></div>
</body>
</html>
表示結果
こうやって見ると都市部とその周辺がやばいことになってますが、人口に比例するので当然の結果とも考えられます。市町村の人口データも取得して発生比率を円の色で表してみると面白いかもしれません。
ちなみに朝倉市の方が若干少なそうなので買い物先は甘木イオン(朝倉市)に行くことにしました。