本節將講解的Matplotlib繪製高階影象主要包含兩種影象:
Matplotlib繪製三維影象直接使用自帶的mplot3d工具箱來繪製,Matplotlib繪製地理影象則要使用Basemap工具箱
由於Basemap工具箱不是自帶的,因此需要額外下載
Matplotlib原本只能繪製二維影象,但是隨着版本的迭代逐漸開始可以繪製三維影象了
繪製三維影象的工具箱是mplot3d,我們需要從mpl_toolkits匯入這個工具箱,實際上所有基於Matplotlib開發的高階功能庫都位於這個位置,並且也能夠被稱爲工具箱
我們首先需要匯入這個工具箱
from mpl_toolkits import mplot3d
和二維影象的情況一樣,三維影象也需要三維子圖承載,爲此,我們匯入mplot3d工具箱之後,在任何可以建立二維Axes物件的函數或者方法中指定projection參數爲3d後即可建立一個三維子圖(Axes3D物件)
from mpl_toolkits import mplot3d
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
print(type(Axes3D))
plt.show()
>>>
<class 'matplotlib.axes._subplots.Axes3DSubplot'>
這個網格圖可以用滑鼠來進行互動
最基本的三維影象是(x,y,z)三維座標點構成的線圖與散點圖.我們分別可以使用Axes3D物件的plot3D()和scatter3D()方法來繪製
from mpl_toolkits import mplot3d
z=np.linspace(start=0,stop=15,num=1000)
x=np.sin(z)
y=np.cos(z)
z_scatter=15*np.random.random(100)
x_scatter=np.sin(z_scatter)+0.1*np.random.randn(100)
y_scatter=np.cos(z_scatter)+0.1*np.random.randn(100)
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.plot3D(x,y,z,'gray')
Axes3D.scatter3D(x_scatter,y_scatter,z_scatter,c=z_scatter,cmap='Greens')
plt.show()
mplot3d自動的會將一些點設定爲透明的,來表現整個影象的立體感,我們通過互動拖拽影象就能夠得到透明的點的變化
前面介紹過將三維影象投影到二維影象上繪製等高線圖,我們其實可以通過mplot3d直接繪製三維等高線圖
和Axes.contour()方法一樣,Axes3D.contour3D()物件我們傳入的xoy面的數據必須是二維網格數據
x=np.linspace(start=-6,stop=6,num=30)
y=np.linspace(start=-6,stop=6,num=30)
x_grid,y_grid=np.meshgrid(x,y)
z_grid=np.sin(np.sqrt(x_grid**2+y_grid**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.contour3D(x_grid,y_grid,z_grid,50,cmap='RdGy')
Axes3D.set_xlabel('x')
Axes3D.set_ylabel('y')
Axes3D.set_zlabel('z')
plt.show()
由於這個角度不是觀察的最佳視角
我們可以通過Axes3D.view_init()方法來調整視角
x=np.linspace(start=-6,stop=6,num=30)
y=np.linspace(start=-6,stop=6,num=30)
x_grid,y_grid=np.meshgrid(x,y)
z_grid=np.sin(np.sqrt(x_grid**2+y_grid**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.contour3D(x_grid,y_grid,z_grid,50,cmap='RdGy')
Axes3D.set_xlabel('x')
Axes3D.set_ylabel('y')
Axes3D.set_zlabel('z')
Axes3D.view_init(60,35) #調整俯視角爲60度,方位角是35度
plt.show()
線框圖和下面 下麪將要講解的曲面圖都是二維平面直接對映到三維空間中的曲面.我們可以使用plot_wireframe()
x=np.linspace(start=-6,stop=6,num=30)
y=np.linspace(start=-6,stop=6,num=30)
x_grid,y_grid=np.meshgrid(x,y)
z_grid=np.sin(np.sqrt(x_grid**2+y_grid**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.plot_wireframe(x_grid,y_grid,z_grid,color='black')
Axes3D.set_title('wireframe')
plt.show()
和前面的繪製線框圖類似,我們爲plot_surface傳入三個網格數據即可繪製
x=np.linspace(start=-6,stop=6,num=30)
y=np.linspace(start=-6,stop=6,num=30)
x_grid,y_grid=np.meshgrid(x,y)
z_grid=np.sin(np.sqrt(x_grid**2+y_grid**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.plot_surface(x_grid,y_grid,z_grid,rstride=1,cstride=1,cmap='viridis')
Axes3D.set_title('surface')
plt.show()
此外,我們實際上也可以使用柱座標來繪圖,此時只需要將x和y轉變爲r和theta
r=np.linspace(start=0,stop=6,num=20)
theta=np.linspace(start=0,stop=1.7*np.pi,num=50)
r_grid,theta_grid=np.meshgrid(r,theta)
x_grid=r_grid*np.sin(theta_grid)
y_grid=r_grid*np.cos(theta_grid)
z_grid=np.sin(np.sqrt(x_grid**2+y_grid**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.plot_surface(x_grid,y_grid,z_grid,rstride=1,cstride=1,cmap='RdGy',edgecolor='none')
plt.show()
我們通過定義域的轉變的方式來實現了對曲面的剖分
上各種繪製曲面的方法,要麼需要笛卡爾座標,要麼使用極座標,並且需要對每個數據點生成網格,而後才能 纔能後進行繪圖曲面
但在進行正常工作的時候,我們往往可能遇到的情況是由多個數據點而非嚴格的,均勻的網格數據
爲此,我們想要對這些數據點所在的曲面進行擬合,這就需要Axes3D物件的plot_trisurf()方法來繪製
我們首先建立一些隨機點,並觀察這些點的分佈來對數據的分佈具有一個大概的認識
# 隨機生成點
theta=2*np.pi*np.random.random(1000)
r=6*np.random.random(1000)
x=np.ravel(r*np.sin(theta)) #使用np.ravel來確保x是一維的陣列
y=np.ravel(r*np.cos(theta)) #同上,確保y是一維的陣列
z=np.sin(np.sqrt(x**2+y**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.scatter3D(x,y,z)
plt.show()
接下來我們呼叫plot_trisurf()來進行曲面擬合
# 隨機生成點
theta=2*np.pi*np.random.random(1000)
r=6*np.random.random(1000)
x=np.ravel(r*np.sin(theta))
y=np.ravel(r*np.cos(theta))
z=np.sin(np.sqrt(x**2+y**2))
Fig=plt.figure()
Axes3D=plt.axes(projection='3d')
Axes3D.plot_trisurf(x,y,z,cmap='viridis',edgecolor='none')
plt.show()
下面 下麪我們將使用上面講解的內容來繪製一個莫比烏斯環
繪製莫比烏斯環的關鍵就是確定每個點的三個座標資訊.
莫比烏斯環的紙帶我們可以理解有兩種旋轉關係,一種是圍繞z軸進行旋轉,另外一種是圍繞自己的座標軸旋轉
藉此,我們可以確定莫比烏斯環上每個點的座標
r = 1 + w * np.cos(phi)
x = np.ravel(r * np.cos(theta))
y = np.ravel(r * np.sin(theta))
z = np.ravel(w * np.sin(phi))
from matplotlib.tri import Triangulation
tri = Triangulation(np.ravel(w), np.ravel(theta))
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles,
cmap='viridis', linewidths=0.2)
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
plt.show()
Basemap是Matplotlib的一個第三方開發的工具箱.
Basemap用起來會比較笨重,但是對於Python使用者來來說還是很方便的
下面 下麪我們首先需要安裝Basemap,然後在此基礎上進行繪製
Basemap實際不僅僅基於Matplotlib,還基於pyproj等地圖管理庫
因此想要正確的安裝並能執行Basemap,安裝的各個依賴庫的版本必須要對,否則就要用pip重灌
下面 下麪首先指出各個依賴庫的版本
庫名 | 適用版本 |
---|---|
six | 1.15.0 |
matplotlib | 2.2.0 |
geos | 0.2.2 |
pyproj | 1.9.6 |
注意.pyproj需要使用1.9.6,否則會報錯(我一開始使用的2.6.1的pyproj),matplotlib需要使用2.x版本,我一開始使用的是matplotlib3.x,這樣就會報錯
geos和basemap的安裝步驟參考csdn博文即可
下面 下麪我們將建立一個地球的藍色彈珠影象,來講解Basemap中的基礎概念
from mpl_toolkits.basemap import Basemap
Fig=plt.figure(figsize=(8,8))
m=Basemap(projection='ortho',resolution=None,lat_0=50,lon_0=-100)
m.bluemarble(scale=0.5)
print(type(m))
plt.show()
>>>
<class 'mpl_toolkits.basemap.Basemap'>
這裏我們首先從basemap庫中匯入了Basemap物件,Basemap物件是Basemap庫中的核心物件,就像Pandas中的Series和DataFrame物件,Numpy中的Ndarry物件一樣
Basemap庫內建了全球的地理數據,包括經度(Longitude),緯度(Latitude)以及對應的影象,因此我們如果想要顯示某個地區的影象,那麼首先就需要指定顯示的地點的經緯度以及影象的範圍,所以我們範例化一個Basemap物件其實就是確定其地理數據範圍,注意Basemap僅僅作爲地理數據範圍,而非影象
顯示出具體的影象需要我們呼叫Basemap的不同方法來繪製圖像,例如我們這裏呼叫bluemarble方法來將這些數據視覺化
此外,由於地球是一個三維球體,因此其表面的地理影象自然分佈在球面上,所以這個時候就有一個至關重要的問題:如何將三維影象(地球上的地理影象)表現在二點陣圖像上
這個問題最早出現在繪圖領域,當時的人們面臨如何繪製地圖
解決這個問題的關鍵就是投影,我們可以通過各種不同的方式將三維球面投影到二維平面,從而使得二維影象能夠表示出地理影象
不同的投影方式的優點不同,因此在我們關注於不同的重點時,往往會選擇不同的投影方式來繪製地圖
因此,我們範例化一個Basemap物件的時候,不僅需要指定繪製地點的經度和緯度,還需要指定投影方式,至於圖形的大小,可以直接使用缺失值也可以指定
這裏我們首先建立了一個空白的Figure物件,接下來我們指定了投影的方式爲正射投影(ortho),這樣便於我們從高空觀察地球整體的影象
接下來我們指定顯示的地點的經緯度爲-100和50
此外,Basemap物件還具有許多方法,這裏我們呼叫了bluemarble方法來繪製地球的藍色彈珠影象
下面 下麪我們將繪製西安的位置來進一步掌握Basemap的基礎概念
from mpl_toolkits.basemap import Basemap
Fig=plt.figure(figsize=(8,8))
m=Basemap(projection='lcc',resolution=None,\
lat_0=33.8,lon_0=108.4,\
width=8E6,height=8E6)
Longitude_XiAn,Latitude_XiAn=m(108.4,33.8)
m.etopo(scale=0.5,alpha=0.5)
plt.plot(Latitude_XiAn,Longitude_XiAn,'or',markersize=5)
plt.text(Latitude_XiAn+8E4,Longitude_XiAn+8E4,"Xi'an")
plt.show())
這裏我們首先範例化一個Basemap物件,確定其數據範圍爲以西安爲中心(其實就是程式碼中的經緯度),投影方式爲蘭伯特等角圓錐投影(Lambert conforml conic projection, lcc),這種投影方式用來顯示區域性的地區效果會很好,具體將在後面講解
而實際上,我們通過各種方法視覺化的地理影象(這裏我們使用Etopo方法,這是一種能夠陸地與海底地形特徵的繪圖方式)其實是Matplotlib中的Axes物件下的一個子類AxesImage物件
但是作爲子類,AxesImage物件並不能像Axes物件一樣呼叫所有方法,例如Axes.plot()方法,所以我們不能夠使用AxesImage.plot()繪製散點圖來,進而新增西安這個點
因此實際上,我們呼叫Basemap的視覺化方法繪製的其實是背景,即地圖背景
還需要說明的是,Basemap視覺化得到的AxesImage物件是基於球面座標的,因此我們想要爲AxesImage物件新增點的時候,必須先要將點的經緯度轉換爲球面座標,這樣才能 纔能夠正確的繪製,即程式碼中的Longitude_XiAn,Latitude_XiAn=m(108.4,33.8)
語句
下面 下麪我們來檢驗一下上面的內容
from mpl_toolkits.basemap import Basemap
Fig=plt.figure(figsize=(8,8))
m=Basemap(projection='lcc',resolution=None,\
lat_0=33.8,lon_0=108.4,\
width=8E6,height=8E6)
Longitude_XiAn,Latitude_XiAn=m(108.4,33.8)
AxesImage_XiAn=m.etopo(scale=0.5,alpha=0.5)
plt.plot(Latitude_XiAn,Longitude_XiAn,'or',markersize=5)
plt.text(Latitude_XiAn+8E4,Longitude_XiAn+8E4,"Xi'an")
print(type(AxesImage_XiAn))
print(type(Longitude_XiAn),Longitude_XiAn)
print(type(Latitude_XiAn),Latitude_XiAn)
>>>
<class 'matplotlib.image.AxesImage'>
<type 'float'> 3999999.9999999986
<type 'float'> 3999999.999999997
Basemap中具有三十多種投影方式,下面 下麪我們將講解Basemap中的常見的投影方式
我們首先定義一個可以繪製經緯線的函數,在下面 下麪每種投影風格的演示中,我們將使用這個函數來檢視效果\
def DrawMap(m,scale=0.2):
from itertools import chain
#繪製地貌暈染圖
m.shadedrelief(scale=scale)
#繪製經緯度
Latitude=m.drawparallels(np.linspace(start=-90,stop=90,num=13))
Longitude=m.drawmeridians(np.linspace(start=-180,stop=180,num=13))
#生成經緯線數據以便於設定經緯線樣式
LatitudeLines=chain(*(tup[1][0] for tup in Latitude.items()))
LongitudeLines=chain(*(tup[1][0] for tup in Longitude.items()))
AllLines=chain(LatitudeLines,LongitudeLines)
#設定經緯線樣式
for line in AllLines:
line.set(linestyle='-',alpha=0.3,color='w')
plt.show()
這裏我們首先匯入了itertools這個庫,itertool的主要用途就是進行高效的迭代,主要用於後面我們繪製經緯線時使用
我們呼叫了Basemap的shadedrelief方法來繪製地貌暈染圖.在前面講過,我們如果需要在Basemap繪製的地形圖上繪製點或線,那麼首先就需要將點和線(其實就是以點爲元素的列表)的直角座標轉換爲球座標.
這裏我們使用的Basemap物件的drawparallels和drawmeridian方法來繪製緯度線和經度線,注意,通過這兩種方法得到的返回值其實是一個字典,字典的鍵是plt.Line2D物件
接下來我們使用itertools庫的chain方法,chain方法是將多個物件串聯成一個新的可迭代物件的方法,這裏我們呼叫字典物件的items()方法將所有字典的鍵值對(元組形式)的鍵取出,傳給chain方法.但是由於取出的所有的鍵是一個不定長的陣列,因此要加上*
最後,通過chain方法生成的新的可迭代物件中的所有的元素都是plt.Line2D物件,包含所有的經緯線,我們迭代設定樣式即可
圓柱投影是最簡單的地圖投影型別,我們將經度線和緯度線分別對映成水平線與豎直線,從而將球形地圖對映爲一個矩形影象
但是通過圓柱投影得到的地圖在赤道部分的表現會很好,但是在兩個極地部分的表現會因爲產生了畸變而很差
此外,根據經緯線的排布方式,我們還有等距圓柱投影(cyl),等積圓柱投影(cea,cylindrical equal-area),墨卡托圓柱投影(merc, Mercator)
Fig=plt.figure(figsize=(8,6),edgecolor='w')
m=Basemap(projection='cyl',resolution=None,llcrnrlat=-90,urcrnrlat=90,llcrnrlon=-180,urcrnrlon=180)
DrawMap(m)
這裏我們呼叫了Basemap物件的llcrnrlat,urcrnrlat,llcrnrlon,urcrnrlon參數,來分別指定左下角(llcrnr,lower left corner)和右上角(urcrnr, up right corner)的經度(lon)和緯度(lat)設定
可以看到格林蘭島的畸變非常嚴重
僞圓柱投影是在圓柱投影的基礎上進行了改進,經度線不必是豎直的,而可以是彎曲的,這樣將使得南北極地區區域的地形更加真實
例如摩爾威德投影(Moollweide,moll),他所有的經度線都是橢圓弧線
其他的僞圓柱投影還有正弦投影(sinusoidal,sinu),羅賓森投影(Robinson,robin)
Fig=plt.figure(figsize=(8,6),edgecolor='w')
m=Basemap(projection='moll',resolution=None,lat_0=0,lon_0=0)
DrawMap(m)
這裏我們指定lat_0和lon_0來指定地區中心的緯度和經度
我們可以看到格林蘭島的外觀稍微好點了
透視投影是從某一個頭十點對地球進行透視獲得的投影,就好像站在地球晶體空的某一點給地球照相一樣
一個典型的透視投影就是我們前面使用的正射投影(orthographic, ortho),即從無限遠處觀察地球的一側
此外還有球心投影(gnomonic,gnom)和球極平面投影(stereographic,stere)
Fig=plt.figure(figsize=(8,6),edgecolor='w')
m=Basemap(projection='ortho',resolution=None,lat_0=50,lon_0=0)
DrawMap(m)
圓錐投影是先將地圖投影成一個圓錐體,然後在將其展開,這樣將獲得非常好的區域性效果,但是距離圓錐頂點較遠的區域會發生嚴重的畸變
典型的例子就是蘭伯特等角圓錐投影(Lambert conforml conic projection, lcc),它指定兩個標準的緯線作爲構成圓錐(分別是lat_1與lat_2)
此外,常用的圓錐投影還有等距圓錐投影(equidistant,eqdc)和阿爾伯斯等積圓錐(Albers equal-area,aea)
Fig=plt.figure(figsize=(8,6),edgecolor='w')
m=Basemap(projection='lcc',resolution=None,lat_0=50,lon_0=0,lat_1=45,lat_2=55,width=1.6E7,height=1.2E7)
DrawMap(m)
實際上在Basemap程式包的介紹文件裡還有更多關於投影型別的介紹,自行查閱即可
前面介紹,我們可以呼叫Basemap物件的bluemarble()方法和shadedrelief()繪製地球的投影,用drawparallels()繪製緯線,drawmeridians()方法繪製經線
其實我們還有許多地理影象視覺化方法,下面 下麪將介紹一些方法
drawcoastlines()
: 繪製大陸海岸線drawlsmask()
:爲陸地和海洋設定填充色,從而可以在陸地或海洋投影其他影象drawmapboundary()
: 繪製地圖邊界,包括爲海洋填充顏色drawrivers()
: 繪製河流fillcontinents()
: 使用一種顏色填充大陸,可選參數用另外一種參數填充湖泊drawcountries()
: 繪製國界線drawstates()
: 繪製美國州界線drawcounties()
: 繪製美國縣界線drawgreatcircle()
: 在兩點間繪製圓drawparallels()
: 繪製緯線drawmeridians()
: 繪製經線drawmapscale()
: 繪製現行比例尺bluemarble()
:繪製NASA藍色彈珠地球投影shadedrelief()
: 在地圖上繪製地埋暈染圖etopo()
: 繪製地形暈染圖warpimage()
: 將使用者提供的影象投影到地圖上此外,如果我們使用邊界類影象繪製方法,就必須指定解析度.我們可以指定resolution參數來設定解析度,分別是
值 | 說明 |
---|---|
c | 原始解析度 |
l | 低解析度 |
i | 中解析度 |
h | 高解析度 |
f | 全畫質解析度 |
我們繪製一個海岸線爲例
for i,res in enumerate(['l','h']):
m=Basemap(projection='ortho',lat_0=57.3,lon_0=-6.2,width=90000,height=12000,resolution=res,ax=Axes[i])
m.fillcontinents(color='#FFDDCC',lake_color='#DDEEFF')
m.drawmapboundary(fill_color='#DDEEFF',)
m.drawcoastlines()
Axes[i].set_title("Resolution='{0}'".format(res))
plt.show()
這裏我們使用Python自帶的enumerate()函數,來將遍歷的物件的元素和索引結合起來,類似於Series
最後,Basemap中最實用的功能就是以地圖爲背景繪製各種數據
我們可以使用任意的plt函數來繪製圖形和文字,當然,我們需要將直角座標轉化爲球面座標
實際上Basemap物件中有許多方法也可以繪製地圖,只不過多了latlon參數,如果我們將其設定爲True,就表示使用原來的經緯度座標,而非直角座標
Basemap對現代格部分繪圖方法如下:
下面 下麪我們用前面使用過的美國加州城市數據爲例,繪製散點圖
cities=pd.read_csv('california_cities.csv')
cities_lat=cities['latd'].values
cities_lon=cities['longd'].values
cities_population=cities['population_total'].values
cities_area=cities['area_total_km2'].values
Fig=plt.figure(figsize=(8,8))
m=Basemap(projection='lcc',resolution='h',lat_0=37.5,lon_0=-119,width=1E6,height=1.2E6)
m.shadedrelief()
m.drawcoastlines(color='gray')
m.drawcountries(color='gray')
m.scatter(cities_lon,cities_lat,latlon=True,c=np.log10(cities_population),s=cities_area,cmap='Reds',alpha=0.5)
plt.colorbar(label=r"$\log_{10}({\rm population})$")
plt.clim(3,7)
for a in [100,300,500]:
plt.scatter([],[],c='red',alpha=0.5,s=a,label=str(a)+'km$^2$')
plt.legend(scatterpoints=1,frameon=False,labelspacing=1,loc='lower left')
plt.show()
我們可以發現,加州所有的城市都沿着平坦的中央谷地的高速公路延伸,幾乎完全避開了加州的山區地帶