地图制作是地理空间数据的可视化的一种方式。为了确保视觉效果与它们所基于的数据准确一致,它试图以一种非技术性又受众更容易理解和解释的形式来表达数据。无论你哪里得到的数据,当它被绘制成地图以更直接地方式来描述它所指的地点时,它将更有影响力。
1. 介绍
首先我们先了解,地理空间数据可视化的基本地图类型大概可以分为:
point Map(点式地图)、Proportional symbol map(比例符号地图)、cluster map(集群地图)、choropleth map(等值区域图)、cartogram map(变形地图)、hexagonal binning map(六边形分箱图)、heat map(热力图)、topographic map(地形图)、flow map(流向图)、spider-map (蛛状图)、Time-space distribution map(时空分布图)、data space distribution map(数据空间分布图)等。
本文主要介绍如何绘制Proportional symbol map(比例符号地图) ,比例符号图是点地图的一个变体。它使用大小不同的地图符号来表示一个定量的变量(通常是圆形),如图所示。
本文目的是用一个 Python 程序为给定的一个多边形 shapefile 和一个定量属性,绘制一个比例点符号地图。该地图会包括显示多边形的基础地图和点符号。
2. 导入包
首先导入numpy 和matplotlib 包。在下面导入的包中,numpy 对于这个项目不是必需的,但是它可以通过在数组级别上进行计算而不是使用循环来引入更高的性能。
注意:目标路径“geom”已经存在,并且不是空目录,请解压我链接中的文件保存到本地导入。
链接:python - 腾讯 iWiki (woa.com)
3. 数据处理过程
3.1. 获取多边形和属性
找到上面链接的 uscnty48area 文件下载解压到本地
然后我们可以看到 regions 的数量为:3109
然后我们看一下列表 mapdata 中字典的数据结构。我们将使用递归函数输出字典中的所有键。
All keys: ['type', 'geometry', 'properties', 'id']
----Keys in sub-dictionary 'geometry': ----['type', 'coordinates']
----Keys in sub-dictionary 'properties': ----['NAME', 'STATE_NAME', 'FIPS', 'UrbanPop', 'Area', 'AreaKM2', 'GEO_ID', 'PopDensity']
我们看看 shapex 文件中的一个字典示例
为了正确处理数据并得到所需的比例点符号图,请确保输入的 shapex 文件是以下结构
如果不是这样,文件至少要包含下面两个部分
其他结构会在处理过程中引起 keyerror。我们可以通过下面的函数指出我们需要的键来使它更灵活地接受不同格式的字典,但这将显著的增加工作负载,这不是本文的目的。我们将首先创建一个函数来从 shapex 对象中提取我们需要的信息。函数的逻辑和过程将在函数中讨论。
def get_coordinates_features(mapdata, attribute=None, verbose=False):
"""
这个函数将一个地图数据作为输入,并提取坐标,得到数据中所有多边形和组合多边形的属性(如果有指定)。
属性的返回列表与多边形/组合多边形的坐标的返回列表相同
输入:
Mapdata:一个shapex类对象或一个字典列表。
Attribute: mapdata中属性的名称。
Verbose:是否打印信息。
输出:
polygons:所有多边形的坐标列表。
multipolygons:所有组合多边形的坐标列表。
Attribute_poly:所有多边形属性值的列表。
Attribute_multipoly:与所有组合多边形的相似性。
X_min, x_max, y_min, y_max: x和y坐标的最小值和最大值。
"""
# 初始化我们所需要的输出
polygons = []
multipolygons = []
attribute_poly = []
attribute_multipoly = []
regions = []
# 这可能因数据而异
x_min = 500
x_max = -500
y_min = 500
y_max = -500
# 检查我们是否按要求拥有有效的属性
if attribute is None:
print('No attribute is selected, please specify manually afterwards.')
elif attribute not in mapdata[0]['properties'] and attribute is not None:
print('The attribute selected is not in the first data, please check and select another!')
else:
print(f"'{attribute}' selected as the attribute.")
# 遍历mapdata,每个元素都是代表一个区域的
for i in range(len(mapdata)):
"""
注意:我们将把多边形和组合多边形分开,是因为它们具有不同的结构。
每个组合多边形都是由多个多边形组成的列表,但它们在地理数据的意义上是不可分离的。
"""
if mapdata[i]['geometry']['type'].lower() == 'polygon':
# 如果有,则附加必要的属性。
if attribute in mapdata[i]['properties']:
attr = mapdata[i]['properties'][attribute]
attribute_poly.append(attr)
else:
attribute_poly.append(None)
# 得到坐标的列表,转换成多边形附加上
coodi = mapdata[i]['geometry']['coordinates'][0]
polygon = [Point(p[0], p[1]) for p in coodi]
polygons.append(polygon)
# 更新x y 的最小值和最大值坐标
coodi_x = [p[0] for p in coodi]
coodi_y = [p[1] for p in coodi]
if min(coodi_x) < x_min:
x_min = min(coodi_x)
if max(coodi_x) > x_max:
x_max = max(coodi_x)
if min(coodi_y) < y_min:
y_min = min(coodi_y)
if max(coodi_y) > y_max:
y_max = max(coodi_y)
# 如果我们想打印信息(前面注释写道)
if verbose:
city = mapdata[i]['properties']['NAME']
state = mapdata[i]['properties']['STATE_NAME']
regions.append(city, state)
print(f"Polygon appended: {city}, {state}")
elif mapdata[i]['geometry']['type'].lower() == 'multipolygon':
# 对于组合多边形,我们需要额外的循环
# 如果有需要,我们追加必须的属性
if attribute in mapdata[i]['properties']:
attr = mapdata[i]['properties'][attribute]
attribute_multipoly.append(attr)
else:
attribute_multipoly.append(None)
# 初始化一个列表来收集组合多边形
multipolygon = []
coodis = mapdata[i]['geometry']['coordinates'] # 坐标列表
for j in range(len(mapdata[i]['geometry']['coordinates'])):
# 循环遍历所有多边形,转换并追加到集合
coodi = coodis[j][0]
polygon = [Point(p[0], p[1]) for p in coodi]
multipolygon.append(polygon)
# 更新x y 的最小值和最大值坐标
coodi_x = [p[0] for p in coodi]
coodi_y = [p[1] for p in coodi]
if min(coodi_x) < x_min:
x_min = min(coodi_x)
if max(coodi_x) > x_max:
x_max = max(coodi_x)
if min(coodi_y) < y_min:
y_min = min(coodi_y)
if max(coodi_y) > y_max:
y_max = max(coodi_y)
# 将多边形集合附加到组合多边形列表。
multipolygons.append(multipolygon)
# 如果我们想打印信息
if verbose:
city = mapdata[i]['properties']['NAME']
state = mapdata[i]['properties']['STATE_NAME']
regions.append(city, state)
print(f"MultiPolygon appended: {city}, {state}")
else:
# 如果没有多边形或组合多边形,跳转到下一个循环。
print(f"Make sure it is a polygon shape file for {i}th county.")
continue
# 打印摘要并返回
print(f"Number of polygons: {len(polygons)}")
print(f"Number of multipolygons: {len(multipolygons)}")
print(f"The canvas we need: ")
print(f"X axis from {round(x_min, 2)} to {round(x_max, 2)}")
print(f"Y axis from {round(y_min, 2)} to {round(y_max, 2)}")
return polygons, multipolygons, attribute_poly, attribute_multipoly, \
regions, x_min, x_max, y_min, y_max
让我们测试函数,并获得进一步处理所需的数据格式。
该函数可以返回城市和州的列表,以便在图形中提供额外的文本,或者匹配来自其他数据源的自定义属性。
我将regions.append()方法保留在verbose (详细信息)中,因为我可以在其他国家的数据中想象到这一点。对于其他国家的数据源,可能没有state (州),从而导致KeyError 。 因此,在这种情况下,对于该数据,我们可以简单地关闭 verbose。必须有一个更灵活的方法来指定,我们想在函数参数中返回哪些键,但这已经超出了本文的目的范围。
polygons, multipolygons, attribute_poly, attribute_multipoly, \
regions, x_min, x_max, y_min, y_max = get_coordinates_features(mapdata,
attribute='UrbanPop', verbose=False)
3.2. 变化量化属性
在这一部分,我们需要缩放属性,以便在最终的图形中具有比例符号。我遵循了维基百科 中介绍的三种比例缩放方法,并加入了一个简单的乘数:
1. Absolute Scaling(绝对量表法)
图中任意两个数值属性的面积应该与这两个属性的值具有相同的比例。从形式上看,它会是这样的:
这是我们比例所有属性所需要的关系。
Prev Chapter:暂不考虑训练GPT-5 OpenAI选择了发力移动端
Next Chapter:电子书《从零开始的UEFI裸机编程》