ArcPy 脚本:批量生成郑州市 1990-2019 年空间分析结果(核密度、热点、平均中心、标准差椭圆)
背景介绍
在城市研究中,我们常常需要分析多年数据的空间分布模式,比如建筑物高度在郑州市的聚集情况、热点区域变化、整体中心位置迁移以及分布方向趋势。如果每年手动在 ArcGIS 中运行四种空间分析工具,会非常耗时且容易出错。这个脚本就是为了解决这个问题而写的------它能自动批量处理 1990 到 2019 共 30 年的数据,一键生成四类分析结果,让你把精力放在结果解读上,而不是重复操作上。
代码功能说明
这个脚本的核心功能是:自动读取每一年郑州市的点要素数据(shp 文件),依次完成四种常见空间统计分析,并将结果整齐保存到对应的文件夹中。
具体能实现的效果:
- 核密度分析 → 生成每年建筑物高度的密度栅格图(tif)
- 冷热点分析(Getis-Ord Gi*) → 生成每年显著的高值/低值聚集区域(shp)
- 平均中心 → 计算每年高度加权的中心点(shp)
- 标准差椭圆(方向分布) → 绘制每年数据的分布方向和离散程度(shp)
适用场景:城市地理、规划、人口、经济等时空格局研究,特别是需要对比多年变化的场景。
运行完成后,你会在输出文件夹里看到四个子文件夹(核密度、冷热点、平均中心、椭圆),每个文件夹里按年份命名整齐存放对应结果文件,总共约 120 个文件(30 年 × 4 类)。
运行环境准备
要运行这个脚本,你需要:
- 安装 ArcGIS Pro (推荐 2.8 或更高版本)或 ArcGIS Desktop 10.x(带 Spatial Analyst 扩展)
- 脚本使用的是 ArcGIS 自带的 Python 环境和 arcpy 模块,不需要额外用 pip 安装任何包
- 确保你的 ArcGIS 许可证已启用 Spatial Analyst 扩展(脚本会自动 checkout)
注意:普通 Python(如 Anaconda)无法运行此脚本,必须在 ArcGIS 提供的 Python 环境中执行。
详细运行步骤
按照以下顺序操作,新手也能一步步跑通。每一步都说明了"为什么要这么做"。
-
准备输入数据
为什么:脚本会按年份逐个读取 shapefile 文件,如果路径或文件名不对就会报错。
操作:
- 在你的磁盘上新建一个输入文件夹(例如
D:\CMAB_data\input) - 在里面按年份建立子文件夹:
1990、1991、...、2019 - 将每年的点数据命名为
郑州市_XXXX.shp(XXXX 为年份),放入对应年份的子文件夹中
(数据结构示例:输入文件夹\1990\郑州市_1990.shp)
- 在你的磁盘上新建一个输入文件夹(例如
-
创建输出和临时文件夹
为什么:脚本需要地方存放结果和临时文件,避免污染其他目录。
操作:
- 新建输出文件夹,例如
D:\CMAB_data\output\郑州 - 新建临时文件夹(scratch),例如
D:\CMAB_data\scratch(空间足够大即可)
- 新建输出文件夹,例如
-
保存并修改代码
为什么:路径必须指向你自己的文件夹,否则找不到文件。
操作:
- 将下面的完整代码复制到一个新文件中,保存为
zhengzhou_spatial_analysis.py - 重点修改以下三行路径(用你的实际路径替换方括号内的内容):
pythoninput_base_dir = r"【你的输入基础目录】" # 示例: r"D:\CMAB_data\input" output_base_dir = r"【你的输出基础目录】" # 示例: r"D:\CMAB_data\output\郑州" scratch_dir = r"【你的临时目录】" # 示例: r"D:\CMAB_data\scratch" - 将下面的完整代码复制到一个新文件中,保存为
-
运行脚本
为什么:在 ArcGIS 自带的 Python 环境中运行才能调用 arcpy。
操作(两种方式任选其一):
- 方式一(推荐) :打开 ArcGIS Pro → 顶部菜单"分析" → "Python" → 打开 Python 窗口 → 拖入你的
.py文件 → 回车执行 - 方式二 :在 Windows 开始菜单找到 "Python Command Prompt(ArcGIS Pro)" → 输入
python C:\path\to\zhengzhou_spatial_analysis.py
运行过程会在控制台打印进度,大约几分钟到十几分钟(取决于电脑性能和数据量)。
- 方式一(推荐) :打开 ArcGIS Pro → 顶部菜单"分析" → "Python" → 打开 Python 窗口 → 拖入你的
-
验证结果
为什么:确认脚本是否成功生成了所有文件。
操作:
- 运行结束后,控制台会打印统计信息(每个文件夹多少文件)
- 打开输出文件夹,检查四个子文件夹是否都有按年份命名的文件
- 用 ArcGIS Pro 打开任意几个结果文件查看是否正常显示
核心代码解析
下面是完整代码(已将个人路径模糊化)。我们用大白话逐段解释关键逻辑。
python
import arcpy
import os
import shutil
def clean_previous_results(output_base_dir, sub_folders):
"""删除之前的结果文件"""
print("正在清理之前的输出结果...")
for folder_name in sub_folders.values():
folder_path = os.path.join(output_base_dir, folder_name)
if os.path.exists(folder_path):
try:
# 删除文件夹内所有内容,但保留文件夹本身
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
except Exception as e:
print(f" 删除 {file_path} 失败: {e}")
print(f" 已清理: {folder_name}")
except Exception as e:
print(f" 清理 {folder_name} 文件夹时出错: {e}")
else:
# 如果文件夹不存在,创建它
os.makedirs(folder_path, exist_ok=True)
print(f" 创建了: {folder_name}")
def run_analysis():
# ================= 配置区域 =================
input_base_dir = r"【你的输入基础目录】"
output_base_dir = r"【你的输出基础目录】"
scratch_dir = r"【你的临时目录】"
arcpy.CheckOutExtension("Spatial")
arcpy.env.overwriteOutput = True
sub_folders = {
"kde": "核密度",
"hotspot": "冷热点",
"mean_center": "平均中心",
"ellipse": "椭圆"
}
# ================= 清理之前的输出结果 =================
clean_previous_results(output_base_dir, sub_folders)
# ================= 开始循环处理 (1990-2019) =================
for year in range(1990, 2020):
input_shp = os.path.join(input_base_dir, str(year), f"郑州市_{year}.shp")
if not os.path.exists(input_shp):
print(f"文件不存在,跳过: {input_shp}")
continue
print(f"\n正在处理: {year} 年数据...")
try:
# --- 任务1: 核密度 ---
temp_point_path = os.path.join(scratch_dir, f"temp_pts_{year}.shp")
arcpy.management.FeatureToPoint(input_shp, temp_point_path, "INSIDE")
out_raster_path = os.path.join(output_base_dir, sub_folders["kde"], f"{year}年郑州市.tif")
out_kde = arcpy.sa.KernelDensity(temp_point_path, "height", 30, None, "SQUARE_METERS")
out_kde.save(out_raster_path)
arcpy.management.Delete(temp_point_path)
print(f" √ 任务1完成: 核密度")
# --- 任务2: 热点分析 ---
out_hotspot_path = os.path.join(output_base_dir, sub_folders["hotspot"], f"{year}年郑州市冷热点.shp")
try:
arcpy.stats.HotSpotAnalysis(
input_shp, "height", out_hotspot_path,
"FIXED_DISTANCE_BAND", "EUCLIDEAN_DISTANCE", "NONE",
None, None, "FEATURE_CLASS"
)
except AttributeError:
try:
arcpy.stats.HotSpotAnalysis_GetisOrdGi(
input_shp, "height", out_hotspot_path,
"FIXED_DISTANCE_BAND", "EUCLIDEAN_DISTANCE", "NONE",
None, None, "FEATURE_CLASS"
)
except AttributeError:
arcpy.HotSpots_stats(input_shp, "height", out_hotspot_path)
print(f" √ 任务2完成: 热点分析")
# --- 任务3: 平均中心 ---
out_center_path = os.path.join(output_base_dir, sub_folders["mean_center"], f"{year}郑州.shp")
arcpy.stats.MeanCenter(input_shp, out_center_path, "height")
arcpy.management.AddField(out_center_path, "年份", "TEXT", field_length=10)
with arcpy.da.UpdateCursor(out_center_path, ["年份"]) as cursor:
for row in cursor:
row[0] = str(year)
cursor.updateRow(row)
print(f" √ 任务3完成: 平均中心 (已添加年份字段)")
# --- 任务4: 方向分布 ---
out_ellipse_path = os.path.join(output_base_dir, sub_folders["ellipse"], f"{year}郑州.shp")
arcpy.stats.DirectionalDistribution(input_shp, out_ellipse_path, "1_STANDARD_DEVIATION", "height")
arcpy.management.AddField(out_ellipse_path, "年份", "TEXT", field_length=10)
with arcpy.da.UpdateCursor(out_ellipse_path, ["年份"]) as cursor:
for row in cursor:
row[0] = str(year)
cursor.updateRow(row)
print(f" √ 任务4完成: 标准差椭圆 (已添加年份字段)")
print(f" ✓ {year} 年数据处理完成!")
except Exception as e:
print(f" ✗ 处理 {year} 年数据时发生错误: {str(e)}")
import traceback
traceback.print_exc()
# ================= 最终统计 =================
print("\n" + "="*50)
print("所有处理已完成!")
print("\n输出结果统计:")
print("-"*30)
total_files = 0
for folder_key, folder_name in sub_folders.items():
folder_path = os.path.join(output_base_dir, folder_name)
if os.path.exists(folder_path):
if folder_key == "kde":
files = [f for f in os.listdir(folder_path) if f.endswith('.tif')]
else:
files = [f for f in os.listdir(folder_path) if f.endswith('.shp')]
count = len(files)
total_files += count
print(f"{folder_name} 文件夹: {count} 个文件")
if count > 0:
print(f" 示例: {', '.join(files[:3])}" + ("..." if count > 3 else ""))
else:
print(f"{folder_name} 文件夹: 不存在")
print(f"\n总计生成: {total_files} 个结果文件")
print("="*50)
if __name__ == '__main__':
run_analysis()
大白话解释核心逻辑:
for year in range(1990, 2020):就像一个自动翻页的书,从 1990 年到 2019 年一张张处理。- 每一年先检查文件是否存在,不存在就跳过(避免程序直接崩溃)。
- 核密度:先把面转成点(因为核密度工具需要点),然后计算"哪里点最密集",输出一张热力图一样的栅格。
- 热点分析:直接告诉 ArcGIS "用高度字段找出显著的高值聚集和低值聚集区"。
- 平均中心:计算所有点的高度加权中心,就像找一群人的"平均位置",但高楼影响更大。
- 标准差椭圆:画一个椭圆包裹大部分点,显示整体分布的方向和范围。
- 每次分析完都会在结果文件里加一个"年份"字段,方便后期合并所有年份一起分析。
- 最后统一统计生成了多少文件,给你一个清晰的完成反馈。
常见问题解决
-
报错 "RuntimeError: Cannot check out Spatial Analyst"
原因:许可证未启用 Spatial Analyst 扩展。
解决:在 ArcGIS Pro 中 → 项目 → 许可 → 确认 Spatial Analyst 已勾选。
-
找不到输入文件,提示文件不存在
原因:路径写错或文件名不完全匹配(区分大小写、后缀完整)。
解决:仔细检查
input_base_dir和文件实际位置,确保是原始字符串(加 r 前缀)。 -
热点分析函数报 AttributeError
原因:不同 ArcGIS 版本函数名不同。
解决:脚本已内置多种尝试,通常能自动适配;若仍失败,请确认你的 ArcGIS 版本。
-
磁盘空间不足或权限问题
原因:输出/临时目录无写权限或空间不够。
解决:选择有足够空间的磁盘,确保当前用户对文件夹有写权限。
-
运行特别慢
原因:数据量大或电脑配置一般。
解决:可以先拿几年的数据测试(修改 range(1990, 1995)),确认没问题再跑全部。
按照上面步骤操作,新手也能顺利跑通并得到完整的多年空间分析结果。祝你分析愉快!