疫情世界地圖

疫情這麼嚴重,想着說做個世界地圖實時顯示下疫情信息,結果做着做着發現有人已經做好了(可惜因爲沒有國內的相關資質不能隨意發佈數據,所以不能貼網址):
COVID-19 Dashboard by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University (JHU)
好吧,那就當學習吧:

數據來源

我在[某個無法透露的地址]找到了新冠疫情的實時數據,
每天更新,和上面的霍普金斯大學數據基本一致,還是比較靠譜的。
不過就是recovered的數字全是0。。。
數據格式如下:

{
    "latest":
        {
            "confirmed":1917319,"deaths":119482,"recovered":0
        },
    "locations":
        [
            {
                "id":0,
                "country":"Afghanistan",
                "country_code":"AF",
                "country_population":29121286,
                "province":"",
                "last_updated":"2020-04-14T03:01:19.940220Z",
                "coordinates":
                {
                    "latitude":"33.0",
                    "longitude":"65.0"
                },
                "latest":
                {
                    "confirmed":665,
                    "deaths":21,
                    "recovered":0
                }
            },
            ...
        ]

國家的數據放在locations裏面,不過有點小問題,就是有些國家的數據是分省的,這個給我後面的geojson處理帶來很多麻煩以至於沒有成功,後面再說。

實現

基於folium實現熱力圖

首先想到是用folium來做,地圖支持的比較好嘛~
廢話不多先上代碼:

# -*- coding: UTF-8 -*-
# Ver 0.1
# Author:advancedanimal

import numpy as np
import folium
import os
import pandas as pd
from folium.plugins import HeatMap
import json
import urllib.request

# GPS of current location
latitude = 31.19
longitude = 121.32
map_data = folium.Map(location=[latitude, longitude], zoom_start=4,
                      tiles=('http://www.google.cn/maps/vt?lyrs=m@189&gl=cn&x={x}&y={y}&z={z}'),
                      attr='default')

# Read Dataset
resp = urllib.request.urlopen('https://coronavirus-tracker-api.herokuapp.com/v2/locations')
org_data = (json.loads(resp.read()))['locations']
json_data = pd.json_normalize(org_data)

# Instantiate a feature group for the incidents in the dataframe
incidents = folium.map.FeatureGroup()

# Add circle on the map
for country_no in json_data.id:
    incidents.add_child(
        folium.CircleMarker(
            [json_data["coordinates.latitude"][country_no],
             json_data["coordinates.longitude"][country_no]],
            radius=7,
            color='yellow',
            fill=True,
            fill_color='red',
            fill_opacity=0.4
        )
    )
map_data.add_child(incidents)

# Add pop-up text to circles
for country_no in json_data.id:
    if json_data.province[country_no] != '':
        label_tooltip = "{}{}{}{}{}".format(
            str(json_data.province[country_no]),
            ' CONFIRM:',
            str(json_data["latest.confirmed"][country_no]),
            ' DEATH:',
            str(json_data["latest.deaths"][country_no]))
    else:
        label_tooltip = "{}{}{}{}{}".format(
            str(json_data.country[country_no]),
            ' CONFIRM:',
            str(json_data["latest.confirmed"][country_no]),
            ' DEATH:',
            str(json_data["latest.deaths"][country_no]))
    folium.RegularPolygonMarker(
        [json_data["coordinates.latitude"][country_no],
         json_data["coordinates.longitude"][country_no]],
        tooltip=label_tooltip,
        number_of_sides=10,
        radius=5).add_to(map_data)

# Convert data format
lat = np.array(json_data["coordinates.latitude"][0:255], dtype=float)
lon = np.array(json_data["coordinates.longitude"][0:255], dtype=float)
confirm = np.array(json_data["latest.confirmed"][0:255], dtype=float)
heatdata = [[lat[i], lon[i], confirm[i]] for i in range(255)]

# add incidents to map
HeatMap(heatdata, radius=40).add_to(map_data)

效果:
效果
熱力圖倒是出來了,但是我尋思着這個效果還是不太明顯,畢竟熱力圖主要是用圓圈融合起來的,
要是能按國界來把各個國家上色就好了,於是有了下面的第二版:

基於geojson的按國家邊界塗色

本來還想用folium+geojson來做的,我找了個全世界各個國家的polygon數據,可是很奇怪這個數據裏面的所有MultiPolygon都無法在地圖上描畫出來(沒有省份數據的Polygon倒是沒有問題),我試着把MultiPolygon裏面的數據都提取出來,結果好像也不行全是雜亂的線條,不得已先放一放,改用別的方法。

基於pygal的按國家邊界塗色

pygal算是上面geojson的一個低配版吧,畢竟國家數據都內嵌了,只要按照國家代碼給數據就行了。
看代碼:

# -*- coding: UTF-8 -*-
# Ver 0.2
# Author:advancedanimal

import pygal_maps_world.maps
import pygal.style
import json
import urllib.request
import pandas as pd

wm_style = pygal.style.RotateStyle('#336699', base_style=pygal.style.LightColorizedStyle)
wm = pygal_maps_world.maps.World(style=wm_style)
wm.title = 'Corona Virus Situation'
# with open('corona_situation_org_20200414') as local_data:
#     org_data = (json.load(local_data))['locations']
resp = urllib.request.urlopen('https://coronavirus-tracker-api.herokuapp.com/v2/locations')
org_data = (json.loads(resp.read()))['locations']
json_data = pd.json_normalize(org_data)

dict_data_confirmed = {}
dict_data_deaths = {}
for country_no in json_data.id:
    dict_value_confirmed = dict_data_confirmed.get(str(json_data.country_code[country_no]).lower())
    dict_value_deaths = dict_data_deaths.get(str(json_data.country_code[country_no]).lower())
    if dict_value_confirmed is not None:
        dict_value_confirmed += json_data['latest.confirmed'][country_no]
        dict_value_deaths += json_data['latest.deaths'][country_no]
    else:
        dict_value_confirmed = json_data['latest.confirmed'][country_no]
        dict_value_deaths = json_data['latest.deaths'][country_no]
    dict_data_confirmed[str(json_data.country_code[country_no]).lower()] = dict_value_confirmed
    dict_data_deaths[str(json_data.country_code[country_no]).lower()] = dict_value_deaths
# wm.add('Deaths', dict_data_deaths)
wm.add('Confirmed', dict_data_confirmed)

wm.render_to_file('corona_map.svg')

在這裏插入圖片描述
但是也還是有問題

  • 地圖無法放大縮小(pygal自身問題,好像沒法解決)
  • 顏色種類不夠多,導致很多國家的顏色都差不多(我只是按照各個國家的確認人數給了數字讓pygal自動設顏色了,理論上是可以給各個國家設不同顏色的,不過工作量好像有點大。。)
  • 對於同一個國家無法設置不同的數據,比如我希望通過點擊圖例,能看到確認和死亡人數,這個好像pygal也做不到(這也是我上面有個dict_data_deaths但是沒有用的原因)

所以呢,pygal只能大致看看,真要是仔細看內容的話,可能還得用回geojson,我再研究研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章