刚接触Python的basemap库时,被它所能产生的效果震撼了。
但是在深入的学习时发现网上很难找到系统的中文教程,仅能搜到一些博客文章里讲到的某些知识点,不成体系,就难以运用自如。
在网上看了看官方文档,又找到了这一使用手册《Basemap tutorial Documentation Release 0.1
Roger Veciana i Rovira》,原作者在这里深入浅出的系统讲解了basemap的用法,有实例、有方法、有探讨、有研究,我在学习的时候受益匪浅,同时边学习边简单的翻译一下,供以后使用之便。
正所谓 始于颜值,陷于才华,忠于人品。在翻译的时候,我在保持原手册知识框架的同时,结合我学习的收获,侧重于以下三个层次:
1 入门
详细的安装教程在官方文档里已经有所阐述,所以这本使用指南侧重于向大家展示怎样使用Basemap。
在这些案例中,我使用的Linux系统的一些配置情况:
·在Ubuntu系统中,使用Synaptic安装库文件
·在Suse系统(一个老的Linux发行版)中,运行这些代码
以下为译者强行插入:
简单粗暴的安装方法(尤其对于Windows系统总是安装不成攻的时候):
在我经历了各种各样难以名状的坑之后,终于在moxigandashu 的文章种找到了终极解决办法(http://blog.csdn.net/moxigandashu/article/details/68945845)
通过http://www.lfd.uci.edu/~gohlke/pythonlibs/ 下载两个文件
basemap-1.1.0-cp36-cp36m-win_amd64.whl
pyproj-1.9.5.1-cp36-cp36m-win_amd64.whl
根据电脑配置和使用的Python版本下载对应的文件
pip install pyproj-1.9.5.1-cp36-cp36m-win_amd64.whl
pip install basemap-1.1.0-cp36-cp36m-win_amd64.whl
下载用户指南
包括示例代码在内的所有文件,都可以在GitHub上找到
https://github.com/rveciana/BasemapTutorial
如果安装有GIT客户端,可以直接用下面命令下载到本地:
git clone https://github.com/rveciana/BasemapTutorial.git
画一张最简单的地图(当然不是小时候每个人都画过的地图#^.^#):
from mpl_toolkits.base import Basemap
import matplotlib.pyplot as plt
map = Basemap()
map.drawcoastlines()
plt.show()
plt.savefig('test.png')
改变地图的投影方式非常简单,只用在Basemap()中加入projection参数和lat_0, lon_0参数。(关于更多的地图投影只是,可以参考百度百科https://www.baidu.com/link?url=d7mToqKOKCk3Ba60s2HtT-4pEuC4jzHhFqhytovCw2IKA6cu6GiBHuR-V7negpngoN8dKyHBNA8_8y8-GRs1tJ7Q6o2bwDLtuB9277b6L6UrIKTRv2DPJtbw87iv6NeA&wd=&eqid=8077e6880001a123000000025a600f50)
即使使用新的地图投影方式,生成的地图还是丑的一逼,用下面的代码可以给陆地和海洋填上不同的颜色:
From mpl_toolkits.basemp import Basemap
Import matplotlib.pyplot as plt
Map = Basemap(projection = 'ortho', lat_0 = 0, lon_0 = 0)
# 首先给地球涂上蓝色的一层
map.drawmapboundary(fill_color = 'aqua')
# 再给大陆涂上屎黄色,给江河湖泊涂上大海一样的颜色
map.fillcontinents(color = 'coral', lake_color = 'aqua')
map.drawcoastlines()
plt.show()
Duang~Duang~Duang~见证奇迹的时刻
所有的地图都应该设置投影模式,在创建Basemap对象时要指定投影模式以及相关属性。设置的方法和别的别的库文件有很大的不同,理解到这一点对后面使用Basemap很重要。
投影模式
projection参数用来设置投影模式
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(projection = 'cyl') map.drawmapboundary(fill_color = 'aqua') map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() plt.show()
创建的地图是酱紫的,和上面的球球有很大不同:
projection默认的值是 cyl,是cylindrical Equidistant projection (等距圆柱投影)的缩写,也叫作等距柱状投影图(Equirectangular projection)或者方格投影(Plate carree)
许多投影模式需要补充额外的参数
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
map = Basemap(projection = 'aeqd', lon_0 = 10, lat_0 = 50)
map.drawmapboundary(fill_color = 'aqua')
map.fillcontinents(color = 'coral', lake_color = 'aqua')
map.drawcoastlines()
plt.show()
(谁能告诉我这是什么鬼。。。。还是生我养我的地球吗?)
这张图是以东经10度,北纬50度为中心(欧洲大陆上)绘制的等距投影地图,不同的投影需要不同的参数,具体参阅(https://matplotlib.org/basemap/users/mapsetup.html)
利用epsg 设置地图投影
ESPG是一种标准的命名投影方式的数字编码。Basemap允许使用这些标记来创建地图,但只局限于某些特定的情况下。要使用ESPG标记,需要在Basemap()里面加上epsg参数。
Basemap中的
Basemap对带有"utm"的projection支持不太好,但是对带有"tmere"都能很好的支持。
下面的例子是用UTM投影显示祖国宝岛台湾省。
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(llcrnrlon = 119.3, llcrnrlat = 20.7, urcrnrlon = 124.6, urcrnrlat = 26, resolution = 'h', epsg = 3415) map.drawmapboundary(fill_color = 'aqua') map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() plt.show()
extension
到目前为止,所有的例子都是在整个地球上画出地图。只绘制某一个区域的地图,可以通过设置地图边界或者地图中心来实现。官方文档说大多数情况下两种方式都通用,但是还有一些例外。
注意:使用cyl,merc,mill,cea和gall投影时,如果没有设置边界,那么边界默认设置为-180,-90,180,90(也就是整个球体),其他投影都需要额外设置。
通过经纬度设置边界Passing the bounding box(以祖国第二大岛为例)
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(llcrnrlon = 108.3, llcrnrlat = 18, urcrnrlon = 111.5, urcrnrlat = 20.3, resolution = 'i', projection = 'tmerc', lat_0 = 20, lon_0 = 111) map.drawmapboundary(fill_color = 'aqua') map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() plt.show()
左下角和右上角的经纬度作为参数传入,
在这个例子中,使用UTM(通用墨卡托Transverse Mercator)投影。在这种情况下边界框的方法很好用,因为在UTM中计算地图的宽度非常麻烦。
注意:使用sinu,moll,hammer,npstere,spstere,nplaea,splaea,npaeqd, spaeqd, robin, eck4, kav7,和 mbtfpq投影中,不能使用这种方法。一是因为在有些投影中,整个地球都被绘制出来,二是因为在有些投影中,无法通过地理坐标计算extension的值
通过地图坐标设置边界
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(resolution = 'l', satellite_height = 3000000., projection = 'nsper', lat_0 = 30, lon_0 = 116, llcrnrx = 500000., llcrnry = 500000.,urcrnrx = 2700000.,urcrnry = 2700000.)
map.drawmapboundary(fill_color = 'aqua') map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() plt.show()
----------------------------------------------------------------------------------
在一些投影中(类似于卫星图像),使用地图坐标设置extension参数。projection参数必须设置(四个角的位置),然后显示球体的某一个区域。
注意:只有在ortho,geos和nsper投影中使用这种方法
通过中心坐标和长度、宽度
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(projection = 'aeqd', lat_0 = 0, lon_0 = 90, width = 10000000,height = 10000000.) map.drawmapboundary(fill_color = 'aqua') map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() for i in range(0, 10000000, 1000000): map.plot(i, i, marker = 'o', color = 'k') plt.show()
在这个例子中,投影的中心,宽度和长度作为projection的参数传入。中心点坐标好确定,就是经纬度的坐标值,但是长和宽就有点复杂了:
长度的单位是米,(0,0)是左下角,(width,height)是右上角。所以说这里的原点不像GDAL中定义的那样。在这种投影里面,之定义区域的大小,不定义原点坐标。
在例子中用plothan函数画了几个点来说明上面原点和投影的关系。
在地图上画一个点
在地图上画点通常用到plot方法:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(projection = 'ortho', lat_0 = 0, lon_0 = 120, width = 10000000,height = 10000000.) map.drawmapboundary(fill_color = 'aqua') map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() # x是经度,y是纬度 x, y = map(114, 22.4) map.plot(x, y, marker = 'D', color = 'm') plt.show()
通过上面代码,我们在中国的南海边画了一个圈【脑补BGM~~】
·用经纬度计算地图坐标的时候
--如果latlon关键字设置为true,x,y将被当做经纬度。但是在老版本的Basemap中不能这样使用。
·plot方法绘制点时需要这一点在地图上的坐标,maker和color参数如下
--默认maker是一个点,详细属性在这里http://matplotlib.org/api/markers_api.html
--默认color是黑色(k),详细属性在这里http://matplotlib.org/api/colors_api.html
如果要绘制不止一个点,那就用scatter方法吧。把一系列点的坐标收在一个数组里然后把传递到plot方法里,就能够画出一系列点,还能用线段把他们连起来。
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(projection = 'ortho', lat_0 = 0, lon_0 = 120) map.drawmapboundary(fill_color = 'aqua') # map.fillcontinents(color = 'coral', lake_color = 'aqua') map.drawcoastlines() # 北上广郑四个城市 lons = [113.14,113.4,121.29,116.24] lats = [23.08,34.46,31.14,39.55] # x是经度,y是纬度 x, y = map(lons, lats) map.scatter(x, y, marker = 'o', color = 'm') plt.show()
注意:如果map.fillcontinents(),那么填充的颜色会覆盖掉这四个点
绘制栅格数据
有两个方法可以处理栅格数据,一个是contour/contourf,用来绘制或者填充轮廓;一个是pcolor/pcolormesh,用来创造一个虚拟色彩的点
contour
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt from osgeo import gdal from numpy import linspace from numpy import meshgrid map = Basemap(projection = 'tmerc', lat_0 = 0, lon_0 = 3, llcrnrlon = 1.819757266426611, llcrnrlat = 41.583851612359275, urcrnrlon = 1.841589961763497, urcrnrlat = 41.598674173123) ds = gdal.Open('dem.tiff') data = ds.ReadAsArray() x = linspace(0, map.urcrnrx, data.shape[1]) y = linspace(0,map.urcrnry, data.shape[0]) xx, yy = meshgrid(x, y) cs = map.contour(xx, yy, data, range(400, 1500, 100), cmap = plt.cm.cubehelix) plt.clabel(cs, inline=True, fmt='%1.0f', fontsize=12, colors='k') plt.show()
用range函数设置高层数据。由于是处理高度数据,所以从400到1400米,每根等高线代表100米。
地图没有使用默认的喷绘(对比下面例子),通过设置cmap参数实现
contour方法中的labels可以通过参数设定
--inline可以设定标签(labels)是显示在等高线的上面还是下面
inline = true时, inline = false时,
fmt 格式化数据
fontsize设置标签字体大小
colors 设置标签的颜色。默认和等高线一个色儿。
contourf
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt from osgeo import gdal from numpy import linspace from numpy import meshgrid map = Basemap(projection = 'tmerc', lat_0 = 0, lon_0 = 3, llcrnrlon = 1.819757266426611, llcrnrlat = 41.583851612359275, urcrnrlon = 1.841589961763497, urcrnrlat = 41.598674173123) ds = gdal.Open('dem.tiff') data = ds.ReadAsArray() x = linspace(0, map.urcrnrx, data.shape[1]) y = linspace(0,map.urcrnry, data.shape[0]) xx, yy = meshgrid(x, y) map.contourf(xx, yy, data) plt.show()
·这个地图和栅格图片一起被绘制出来。
·在绘制轮廓之前,先创建两个矩阵,这两个放的是每一个点的x和y的坐标
-linspace是一个numpy的函数,用来创建一个数组。在这种情况下,地图的坐标从0到map.urcrnrx或map.urcrnry,data.shape[1]和data.shape[0]表示把0到map.urcrnrx或map.urcrnry平均分成多少份。
-meshgrid也是一个numpy函数,把两个数组转化为一个矩阵。
·contuef方法把x,y和矩阵绘制在默认的colormap中,这个过程被称作是喷绘,并且根据高层数据(等高线)自动渲染图像。
·高层数据(等高线)可以在data数组中指定,具体使用方法可以在contour这一章节看到,有两种方法处理这种数据
-- 一个表示等高线的int数据,数据越大,颜色越深
-- 一个包含每一根等高线数据的列表,可以用range函数设置(range(0,3000,100)表示每根等高线高程差是100个单位)
实例中dem.tiff文件下载地址https://github.com/rveciana/BasemapTutorial/blob/master/code_examples/sample_files/dem.tiff
pccoloemesh(立体效果的颜色)
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt from osgeo import gdal from numpy import linspace from numpy import meshgrid map = Basemap(projection = 'tmerc', lat_0 = 0, lon_0 = 3, llcrnrlon = 1.819757266426611, llcrnrlat = 41.583851612359275, urcrnrlon = 1.841589961763497, urcrnrlat = 41.598674173123) ds = gdal.Open('dem.tiff') data = ds.ReadAsArray() x = linspace(0, map.urcrnrx, data.shape[1]) y = linspace(0,map.urcrnry, data.shape[0]) xx, yy = meshgrid(x, y) map.pcolormesh(xx, yy, data) plt.show()
计算地图上一点的位置
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt map = Basemap(projection = 'aeqd', lat_0 = 50, lon_0 = 10,) print (map(10, 50)) print (map(20015077.3712,20015077.3712, inverse = True))
输出结果:
当invers设置为False时,输入的是经纬度,输出的是该点在以地图坐标表示的坐标值,设置为True时,效果相反
Basemap处理矢量图的方法和其他库文件有很大的不同。
基本用法
先从简单的入手,用最简单的办法显示一个矢量图(山东半岛)
frommpl_toolkits.basemap import Basemap
import matplotlib.pyplotas plt
map = Basemap(projection= 'tmerc',lat_0 = 37, lon_0 = 122,
llcrnrlon = 120, llcrnrlat = 36,
urcrnrlon = 123., urcrnrlat = 38.,
resolution = 'i')
map.drawmapboundary(fill_color = 'aqua')
map.fillcontinents(color = '#ddaa66',lake_color = 'aqua')
map.drawcoastlines()
map.readshapefile('CHN_adm_shp/CHN_adm1','comarques')
plt.show()
print (map(10,50))
print (map(20015077.3712,20015077.3712,inverse = True))
·第一个参数 CHN_adm_shp/CHN_adm1不能包含后缀名,程序会假设.shp,.sbf和.shx文件同时存在并且文件名相同
·第二个参数是读取shapfile的方式,我们将在后面详细阐述。
使用时的一些限制条件
· 文件必须符合EPSG:4326,或者纬度/经度坐标。如果你的文件不是这种格式,可以使用ogr2ogr加以转换。
· 地图必须是二维地图
· 只要是多边形或者多段线,就能以地图形式显示出来
正如例子中那样,显示的结果只是线段或着多边形。要把这些图形填充起来,可以参照后面讲到的Filling polygons(图形填充)
读取点数据
在地图上绘制一系列点的话,着实有点复杂。首先读取shapefile中的点的数据,然后把点绘制在地图上,在这个过程中使用scatter,plot或者matplotlib函数。
frommpl_toolkits.basemap import Basemap
import matplotlib.pyplotas plt
map = Basemap(projection= 'tmerc',lat_0 = 39.5, lon_0 = 1,
llcrnrlon = -0.5,llcrnrlat = 39.8,
urcrnrlon = 4., urcrnrlat = 43.,
resolution = 'i')
map.drawmapboundary(fill_color = 'aqua')
map.fillcontinents(color = '#ddaa66',lake_color = 'aqua')
map.drawcoastlines()
map.readshapefile('shapefile/comarques','comarques')
lightning_info = map.readshapefile('shapefile/lightnings','lightnings')
print (lightning_info)
for info, lightning in zip(map.lightnings_info, map.lightnings):
if float(info['amplitude']) <0:
marker = '_'
else:
marker = '+'
map.plot(lightning[0], lightning[1],marker = marker, color = 'm',
markersize = 8, markeredgewidth = 2)
plt.show()
运行结果是显示西班牙加泰罗尼亚地区在一次暴风雨中闪电分布情况
· 第二个参数被称作是 lightnings,所以shapefile文件中的元素可以被map.lightnings和map.lightning_info获取。
· shapefile方法返回一个序列,包含了这些元素的序号,形状,边界范围
· 上面加粗的那行代码,展示了一种迭代所有元素的一种方法:
--zip 用于连接。结合geometry图形
--用for循环迭代每一个元素element
· 在这个例子中,"amplitude"字段用来用来获取闪电趋势,并根据过得的结果,在地图上以不同的符号显示。
· 用plot方法绘制每一个点,用到了marker属性。
多边形信息(Polygon)
下面的例子展示了如何shapefile的属性,获取某一些特定的地图数据。
frommpl_toolkits.basemap import Basemap
import matplotlib.pyplotas plt
map = Basemap(projection = 'tmerc',lat_0 = 35, lon_0 = 118,
llcrnrlon = 110, llcrnrlat = 30,
urcrnrlon = 123., urcrnrlat = 40.,
resolution = 'i')
map.drawmapboundary(fill_color = 'aqua')
map.fillcontinents(color = '#ddaa66',lake_color = 'aqua')
map.drawcoastlines()
map.readshapefile('CHN_adm_shp/CHN_adm1','comarques',drawbounds = False)
for info, shapein zip(map.comarques_info, map.comarques):
if info['NAME_1'] =='Henan':
x, y = zip(*shape)
map.plot(x, y, marker = None, color= 'm')
plt.show()
这段代码可以单独画出老家河南的区域。
· 和之前的代码一样,用zip迭代所有的元素(elements)
· 用NAME_2这个字段过滤地名。在这个例子中,把河南"Henan"轮廓绘制出来。不同的shapefile文件中,字段关键字不同,这样在shapefile文件同名的.csv文件中具体查看。
· 要绘制出轮廓线,必须把x和y坐标数据分别放在两个独立的数组中,但是geometry给出的是每一点的坐标是(x,y)成对存在的,所以要用到zip把他们拆散。具体用法参照这里(http://stackoverflow.com/questions/13635032/what-is-the-inverse-function-of-zip-in-python)
· 用plot方法绘制边界,marker参数设置为None,只绘制边框。
填充图形(Filling polygons)
通常情况下,绘制一个图形只是绘制边框,不填充边框内部的区域。下面的例子演示如何既画边界有填充边界内部区域的颜色。
frommpl_toolkits.basemap import Basemap
import matplotlib.pyplotas plt
from matplotlib.patchesimport Polygon
from matplotlib.collectionsimport PatchCollection
from matplotlib.patchesimport PathPatch
import numpyas np
fig = plt.figure()
ax = fig.add_subplot(111)
map = Basemap(projection = 'tmerc',lat_0 = 35, lon_0 = 118,
llcrnrlon = 110, llcrnrlat = 30,
urcrnrlon = 123., urcrnrlat = 40.,
resolution = 'i')
map.drawmapboundary(fill_color = 'aqua')
map.fillcontinents(color = '#ddaa66',lake_color = 'aqua')
map.drawcoastlines()
map.readshapefile('CHN_adm_shp/CHN_adm3','comarques',drawbounds = False)
patches = []
for info, shapein zip(map.comarques_info, map.comarques):
if info['NAME_2'] =='Shanghai':
patches.append(Polygon(np.array(shape), True))
ax.add_collection(PatchCollection(patches, facecolor = 'm', edgecolor = 'k',
linewidths = 1, zorder = 2))
plt.show()
·matplotlib使用PatchCollection这个类选择地图并且给地图上色
· 在这个例子中,坐标必须是numpy数组,第二个参数设置图形为封闭线段