任务描述

本关任务:完成 GIS 可视化内容。

相关知识

GIS 简介

GIS 全称 Geographic Information System或 Geo-Information system,中文名为:地理信息系统。它是一种特定的十分重要的空间信息系统。它是在计算机硬、软件系统支持下,对整个或部分地球表层(包括大气层)空间中的有关地理分布数据进行采集、储存、管理、运算、分析、显示和描述的技术系统。

位置与地理信息既是 LBS 的核心,也是 LBS 的基础。一个单纯的经纬度坐标只有置于特定的地理信息中,代表为某个地点、标志、方位后,才会被用户认识和理解。用户在通过相关技术获取到位置信息之后,还需要了解所处的地理环境,查询和分析环境信息,从而为用户活动提供信息支持与服务。

通俗来说就是纪录一堆数字的地理信息,使用有合适的软件去把它表示出来。

数据介绍

数据如下(是从基维百科以及国家统计局上整理的,地理位置可能存在细微的偏差)。将数据保存在文件 major_city。

Shanghai 24870895  31.23N  121.47E  China
Mumbai   12478447  18.96N  72.82E   India
Karachi  13050000  24.86N  67.01E   Pakistan
Delhi    16314838  28.67N  77.21E   India
Manila   11855975  14.62N  120.97E  Philippines
Seoul    23616000  37.56N  126.99E  Korea(South)
Jakarta  28019545   6.18S  106.83E  Indonesia
Tokyo    35682460  35.67N  139.77E  Japan
Beijing  21893095  39.91N  116.39E  China
Changsha 10047914  27.51N  111.53E  China
Moscow   11503501  55.5N   37.4E    Russia

第一列是城市名,第二列是人口,第三列和第四列分别为纬度和经度,最后一列为城市所在国家。

GIS 可视化

在数据可视化过程中,我们常常需要将数据根据其采集的地理位置在地图上显示出来。比如说我们会想要在地图上画出城市,飞机的航线,乃至于军事基地等等。通常来说,一个地理信息系统都会带有这样的功能。今天我们讨论如何在 Python 上实现,并且使用免费的工具包。

matplotlib 是 Python 常用的数据绘制包。它基于 numpy 的数组运算功能。matplotlib 绘图功能强大,可以轻易的画出各种统计图形,比如散点图,条行图,饼图等。matplotlib 常与 numpy 和 scipy 相配合,用于许多研究领域。他们是免费工具,但其功能足可以与科研界的大佬Matlab竞争。

Basemap 是 Matplotlib 的一个子包,负责地图绘制。在数据可视化过程中,我们常需要将数据在地图上画出来。比如说我们在地图上画出城市人口,飞机航线,军事基地,矿藏分布等等。这样的地理绘图有助于读者理解空间相关的信息。

我们下面用 Basemap 画出东半球部分城市的人口。如下图,人口的数量用圆圈的大小表示:

图一 东半球部分城市图 

程序分为两个部分,第一部分为从文件读取数据并处理。第二部分才是真正用 basemap 绘图。

第一部分:

names = []
pops = []
lats = []
lons = []
countries = []
with open('major_city') as f:
    text = f.read().split('\n')
for line in text:
    info = line.split()
    names.append(info[0])
    pops.append(float(info[1]))
    lat = float(info[2][:-1])
    if info[2][-1] == 'S': lat = -lat
    lats.append(lat)
    lon = float(info[3][:-1])
    if info[3][-1] == 'W': lon = -lon + 360.0
    lons.append(lon)
    country = info[4]
    countries.append(country)

第二部分:

# 设置投影模式,初始经纬度
map = Basemap(projection='ortho', lat_0=35, lon_0=120)
# 画海岸线,国家边界,设置边界线条的粗细
map.drawcoastlines(linewidth=0.25)
map.drawcountries(linewidth=0.25)
# 绘制地图投影区域的边缘(海洋的颜色)  
map.drawmapboundary(fill_color='#689CD2')
# 每 30 度画出经纬线。
map.drawmeridians(np.arange(0, 360, 30))    
map.drawparallels(np.arange(-90, 90, 30))
# 绘制颜色,陆地使用 #BF9E30 (黄色),湖泊使用 #689CD2 (蓝色),zorder 为设置层级,本次是置于最低层
map.fillcontinents(color='#BF9E30 ', lake_color='#689CD2', zorder=0)
# 计算出地图投影坐标的纬度/经线网格
x, y = map(lons, lats)
max_pop = max(pops)
# 尺寸比例
size_factor = 80.0
# 字体显示角度
rotation = 30
for i, j, k, name in zip(x, y, pops, names):
    # 按比例计算人口显示的散点大小
    size = size_factor * k / max_pop
    cs = map.scatter(i, j, s=size, marker='o', color='#FF5600')
    plt.text(i, j, name, rotation=rotation, fontsize=10)
plt.title('Some cities in the eastern hemisphere & Population')

 地图的大小、投影方法等重要信息,是在 Basemap() 的调用中实现的:

map = Basemap(projection='ortho',lat_0=35,lon_0=120,resolution='l')

projection 参数规定了投影方法。改变投影方法,绘图结果也将非常不同。

城市所在位置是经纬度。我们想要把经纬度对应图像的像素点,需要转换:

x, y = map(lons, lats)

这个语句转换为图像上的位置。

最后,调用绘制散点图的方法 scatter():

cs = map.scatter(i,j,s=size,marker='o',color='#FF5600')

在地图上画出数据。

编程要求

根据提示,在右侧编辑器补充代码,读取地理信息,绘制地球并画出按要求信息点。 

测试说明

平台会对你编写的代码进行测试。

import matplotlib as mpl
# linux终端下没有GUI,进行设置
mpl.use('Agg')
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np

names = []
pops = []
lats = []
lons = []
countries = []
with open('step7/major_city') as f:
    text = f.read().split('\n')

for line in text:
    info = line.split()
    names.append(info[0])
    pops.append(float(info[1]))
    lat = float(info[2][:-1])
    if info[2][-1] == 'S': lat = -lat
    lats.append(lat)
    lon = float(info[3][:-1])
    if info[3][-1] == 'W': lon = -lon + 360.0
    lons.append(lon)
    country = info[4]
    countries.append(country)


# ============================================
########## Begin ##########
# 设置投影模式,初始经纬度, 纬度为 10,经度为 100
map = Basemap(projection='ortho', lat_0=10, lon_0=100)

########## End ##########

# 画海岸线,国家边界,设置边界线条的粗细
map.drawcoastlines(linewidth=0.25)
map.drawcountries(linewidth=0.25)

########## Begin ##########
# 海洋湖泊颜色为 #0099CC,陆地颜色为 #663300,经纬线间隔 10 度

# 绘制地图投影区域的边缘(海洋的颜色)
map.drawmapboundary(fill_color='#0099CC')

# 画出经纬线
map.drawmeridians(np.arange(0,360,10))
map.drawparallels(np.arange(-90,90,10))

# 绘制颜色,zorder 为设置层级,本次是置于最低层
map.fillcontinents(color='#663300', lake_color='#0099CC', zorder=0)
########## End ##########

# 计算出地图投影坐标的纬度/经线网格
x, y = map(lons, lats)
max_pop = max(pops)

# 尺寸比例
size_factor = 80.0

# 字体显示角度
rotation = 30
for i, j, k, name in zip(x, y, pops, names):
    # 按比例计算人口显示的散点大小
    size = size_factor * k / max_pop
    ########## Begin ##########
    # 根据人口绘制地区散点图,颜色设置为 #FF5600
    cs = map.scatter(i, j, s=size,marker='o',color='#FF5600')

    ########## End ##########
    plt.text(i, j, name, rotation=rotation, fontsize=10)
plt.title('Some cities in the eastern hemisphere & Population')
plt.savefig('step7/image1/test.png')