1. 程式人生 > 實用技巧 >(在模仿中精進資料視覺化06)常見抽象地圖的製作方法

(在模仿中精進資料視覺化06)常見抽象地圖的製作方法

本文完整程式碼及資料已上傳至我的Github倉庫https://github.com/CNFeffery/FefferyViz

1 簡介

  我們經常會在一些PPT報告或者宣傳廣告中看到一些比較抽象的地圖,它們都是在正常地圖的基礎上,通過置換幾何元素,來實現出較為抽象的效果,這類的作品非常之多,因此本文不模仿實際的某幅作品,而是製作出下面三類抽象地圖:

圖1

2 基於Python模仿常見抽象地圖

  對應圖1,我們下面來分別模仿3類抽象地圖,首先準備一下要用到的中國地圖資料,我們偷個懶直接使用高德開源的地圖資料介面:

圖2

  為了方便和簡化之後的運算,我們利用unary_union來將融合所有要素為一個:

圖3

  這樣我們的基礎資料就準備好了~

2.1 向外環形擴散的地圖

  首先我們來製作圖1左圖所示,從以某個點為圓心,向外環形擴散的地圖,原理其實很簡單,只需要定義圓心座標,接著向外按照等差數列,依次擴大半徑距離計算緩衝區的輪廓線:

from shapely.geometry import Point
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 8))

# 以重慶為圓心,生成向外的環形線,間隔為80公里
center = gpd.GeoSeries([Point((106.546737, 29.566598))], crs='EPSG:4326').to_crs(albers_proj)
circles = gpd.GeoSeries([center[0].buffer(i*1000*80).boundary for i in range(1, 45)], crs=albers_proj)

ax = china_total.plot(ax=ax, facecolor='none', edgecolor='black')
ax = circles.plot(ax=ax)
圖4

  可以看到目前生成的環形線已經可以覆蓋中國全境,最後用china_total來裁剪即可:

fig, ax = plt.subplots(figsize=(8, 8))

# 用china_total作為蒙版從circles中裁切出繪圖所需部分
ax = gpd.clip(circles, mask=china_total).plot(ax=ax, color='white')

ax.set_facecolor('#4a4db7')

fig.set_facecolor('#4a4db7')

ax.set_xticks([])
ax.set_yticks([])

ax.spines['left'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_color('none')

fig.savefig('圖5.png', dpi=500, bbox_inches='tight', pad_inches=0)
圖5

  在這幅圖的基礎上,你就可以新增其他的文字標註等元素,形成配圖,使得你的報告更加高階。

2.2 畫素風格地圖

  接著我們來製作圖1中圖所示的又方塊組成的畫素風格地圖,原理也很簡單,生成覆蓋china_total範圍的網格:

from shapely.geometry import MultiLineString
from shapely.ops import polygonize # 用於將交叉線轉換為網格面
import numpy as np

# 提取china_total左下角與右上角座標
xmin, ymin, xmax, ymax = china_total.total_bounds
xmin, ymin, xmax, ymax = int(xmin), int(ymin), int(xmax), int(ymax)

# 建立x方向上的所有座標位置,間距50公里
x = np.arange(xmin, xmax, 50*1000)

# 建立y方向上的所有座標位置,間距50公里
y = np.arange(ymin, ymax, 50*1000)

# 生成全部交叉線座標資訊
hlines = [((x1, yi), (x2, yi)) for x1, x2 in zip(x[:-1], x[1:]) for yi in y]
vlines = [((xi, y1), (xi, y2)) for y1, y2 in zip(y[:-1], y[1:]) for xi in x]

grids = gpd.GeoSeries(list(polygonize(MultiLineString(hlines + vlines))), crs=albers_proj)
grids.plot(facecolor='none', edgecolor='black')
圖6

  再向內緩衝一定的距離,即可得到結果:

圖7

2.3 由不規則多邊形拼湊的地圖

  最後我們來製作圖1右圖所示的由不規則多邊形拼湊的地圖,需要用到泰森多邊形,我們可以通過pip install geovoronoi來安裝輔助庫。

  因為泰森多邊形需要從點出發建立多邊形,因此我們可以生成目標面內部的隨機散點,再作為輸入來生成所需的多邊形:

from geovoronoi import voronoi_regions_from_coords


np.random.seed(42)
coords = gpd.GeoSeries([Point(x, y) for x, y in zip(np.random.uniform(xmin, xmax, 1000), 
                                                    np.random.uniform(ymin, ymax, 1000))],
                       crs=albers_proj)

# 得到china_total內部的散點座標陣列
coords = coords[coords.within(china_total[0])]

# 利用geovoronoi得到所需的泰森多邊形,其中poly_shapes即為我們需要的多邊形
poly_shapes, pts, poly_to_pt_assignments = voronoi_regions_from_coords(np.array(coords.apply(lambda g: (g.x, g.y)).tolist()), 
                                                                       china_total[0])

fig, ax = plt.subplots(figsize=(8, 8))

ax = china_total.plot(ax=ax, facecolor='none', 
                      edgecolor='white', linewidth=0.5)

(
    gpd
    .GeoSeries(poly_shapes)
    .buffer(-10*1000)
    .plot(ax=ax, 
          facecolor='white',
          linewidth=0.2)
)

ax.set_xticks([])
ax.set_yticks([])

ax.set_facecolor('#4a4db7')
fig.set_facecolor('#4a4db7')

ax.spines['left'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_color('none')

fig.savefig('圖8.png', dpi=500, bbox_inches='tight', pad_inches=0)
圖8

  以上就是本文的全部內容,歡迎在評論區與我進行討論~