简介:本资源是一款面向GIS从业者与空间分析学习者的ArcGIS工具箱,专门解决在ArcGIS中快速生成正六边形渔网(蜂窝图)的需求。用户只需加载工具箱,双击运行并输入研究区范围与正六边形边长,即可自动完成蜂窝网格的构建,适用于空间采样、格网统计、景观格局分析等场景,对ArcGIS 10.2及以上版本均兼容。压缩包共83个文件,约889KB,包含tbx工具箱、lyr图层文件、mxd地图文档、py脚本以及配套的gdb地理数据库(含gdbtable、gdbtablx、atx索引等),结构完整,解压后可直接加载使用。目前已有363人学习下载,说明该工具在格网生成类需求中具有一定实用价值。整体操作门槛低、可永久使用,能帮助读者省去手动构建六边形格网的繁琐步骤,快速获得规范可用的蜂窝图成果。
1. 蜂窝渔网为什么值得单独做个工具箱
做空间抽样、格网统计、景观格局分析的人,早晚会撞上六边形渔网这个需求。方形渔网在 ArcGIS 里点一下「Create Fishnet」就出来了,但六边形不行——工具箱里没有现成按钮,很多人第一次搜「ArcGIS 六边形渔网」的时候,翻到的都是让手动算坐标、或者装一堆用不上的插件。我最早做城市热岛格网统计时也踩过这个坑:方形格网在边缘处方向性太强,同一片建成区换个网格朝向,统计结果能差出百分之十几,后来换成蜂窝图才稳定下来。
这篇讲的就是怎么把「生成六边形渔网」这件事,做成一个能反复调用、参数可控、能直接接后续分析的 ArcGIS 工具箱。核心不是教你点哪个菜单,而是把六边形的几何生成逻辑、投影选择、参数设置、批量落地这几件事串成一条能复现的路径。适合两类人:一类是手上正卡在「怎么在 ArcGIS 里画出规则六边形」的从业者,另一类是已经会用方形渔网、想升级到蜂窝格网做空间统计的分析人员。下面从原理讲到脚本,再到避坑,最后给一个能直接塞进日常流程的进阶用法。
2. 六边形渔网的几何原理与投影选型
2.1 蜂窝图不是「画六边形」,是铺满平面的正六边形镶嵌
很多人对蜂窝图的理解停在「六边形看起来比方形高级」,但真正决定它能不能用的,是正六边形镶嵌(hexagonal tiling)这个几何事实。平面上能用同一种正多边形无缝无重叠铺满的只有三种:正三角形、正方形、正六边形。六边形的优势在于:每个单元到相邻单元的中心距离完全相等,只有 6 个邻居,而方形格网有 4 个邻居但角上还斜着 4 个「次近邻」,导致邻域分析时方向性偏差明显。
正六边形镶嵌的关键参数是外接圆半径 R(也就是中心到顶点的距离)和边心距 r(中心到边中点的距离),两者关系是 r = R × √3 / 2 ≈ 0.866R。相邻两列六边形的中心水平间距是 1.5R,同一列内上下相邻六边形的中心垂直间距是 2r = √3R。这几个数字是后面写脚本的全部基础,记不住没关系,代码里会体现。
提示:如果你只想要「看起来像蜂窝」的示意图,随便画六边形就行;但只要涉及面积统计、邻域计算、格网赋值,就必须用严格的正六边形镶嵌,否则每个单元面积不一致,后续统计全是错的。
2.2 为什么投影选错,六边形会变成「扁蜂窝」
这是血泪经验里最容易被忽略的一条。ArcGIS 里生成要素的坐标是投影坐标,如果你在 Web Mercator(WKID 3857)或者地理坐标系(WGS84,WKID 4326)下直接生成六边形,高纬度地区会被拉伸得面目全非——六边形不再是正六边形,面积也完全不可比。我见过有人在 4326 下生成全国范围的蜂窝图,结果东北的六边形被压扁,海南的又被拉长,统计出来的密度图根本没法看。
正确做法是:先确定分析区域,选一个该区域变形最小的等面积投影或局部投影。国内常用的是 Albers 等面积投影(比如 CGCS2000 Albers,中央经线按区域定),或者直接用所在城市的 Gauss-Kruger 3 度带投影。判断标准很简单:生成后量一下任意两个相邻六边形中心的距离,如果水平间距和垂直间距符合 1.5R 和 √3R 的关系,说明投影没问题;如果差得离谱,赶紧换投影。
选投影时还要考虑分析尺度。做全国尺度的格网,Albers 是首选;做单个城市,用当地 Gauss-Kruger 带号投影精度更高;做跨省流域,可以自定义一个以流域中心为中央经线的 Albers。这一步没有万能答案,但有一条铁律:绝不在地理坐标系下生成规则格网。
2.3 工具箱的三种实现路线:模型构建器、ArcPy 脚本、自定义工具
把六边形渔网做成工具箱,常见有三条路。第一条是 ModelBuilder,拖几个工具连起来,适合不会写代码的人,但六边形的坐标计算用 ModelBuilder 表达很别扭,基本要靠「计算字段 + XY 转线」硬凑,维护性差。第二条是纯 ArcPy 脚本,写一个 .py 文件,用 arcpy.Polygon 直接构造六边形几何,灵活度最高,也是我推荐的方式。第三条是把 ArcPy 脚本封装成自定义工具箱(.tbx 或 .pyt),带参数界面,双击就能用,适合团队共享。
我一般会走第三条:核心逻辑用 ArcPy 写,外面套一层 .pyt 工具箱,把「范围、六边形半径、投影、输出路径」做成参数。这样既保留了脚本的可控性,又让不写代码的同事能直接用。下面两章就按这个思路,先写核心脚本,再封装成工具箱。
3. 用 ArcPy 生成六边形渔网的核心脚本
3.1 最小可运行脚本:从范围到六边形面
先看一个能直接跑的最小版本。假设你已经有一个分析范围的要素类(或者知道范围的左上角坐标和行列数),下面这段脚本会生成铺满该范围的六边形面。
# -*- coding: utf-8 -*- import arcpy import math def create_hex_fishnet(out_fc, origin_x, origin_y, radius, cols, rows, spatial_ref): """ 生成正六边形渔网 out_fc: 输出要素类路径 origin_x/y: 左上角第一个六边形的中心坐标 radius: 六边形外接圆半径(地图单位) cols: 列数 rows: 行数 spatial_ref: 投影坐标系,如 arcpy.SpatialReference(4526) """ # 六边形几何关键参数 r = radius # 外接圆半径 dx = 1.5 * r # 相邻列中心水平间距 dy = math.sqrt(3) * r # 同列相邻中心垂直间距 # 创建要素类 arcpy.env.overwriteOutput = True out_path, out_name = out_fc.rsplit("\\", 1) arcpy.CreateFeatureclass_management(out_path, out_name, "POLYGON", spatial_reference=spatial_ref) arcpy.AddField_management(out_fc, "HEX_ID", "LONG") arcpy.AddField_management(out_fc, "COL", "LONG") arcpy.AddField_management(out_fc, "ROW", "LONG") # 预计算六边形相对于中心的 6 个顶点偏移(尖顶朝上) angles = [90, 150, 210, 270, 330, 30] offsets = [(r * math.cos(math.radians(a)), r * math.sin(math.radians(a))) for a in angles] hex_id = 0 with arcpy.da.InsertCursor(out_fc, ["SHAPE@", "HEX_ID", "COL", "ROW"]) as cursor: for row in range(rows): for col in range(cols): # 奇数列整体下移半个 dy,形成蜂窝错位 cx = origin_x + col * dx cy = origin_y - row * dy - (dy / 2 if col % 2 else 0) pts = [arcpy.Point(cx + ox, cy + oy) for ox, oy in offsets] pts.append(pts[0]) # 闭合 polygon = arcpy.Polygon(arcpy.Array(pts), spatial_ref) cursor.insertRow([polygon, hex_id, col, row]) hex_id += 1 print("生成完成,共 {} 个六边形".format(hex_id)) if __name__ == "__main__": # 示例:以某投影坐标系下的原点生成 20x20 蜂窝 sr = arcpy.SpatialReference(4526) # CGCS2000 3度带 114E create_hex_fishnet(r"D:\work\hex.shp", 500000, 3500000, 1000, 20, 20, sr)这段代码的逻辑说明:offsets是六边形六个顶点相对于中心的偏移量,角度从 90 度开始每隔 60 度取一个,这样得到的是尖顶朝上的六边形。dx = 1.5r和dy = √3r是蜂窝镶嵌的数学基础,不能改。奇数列下移dy/2是形成错位的关键,少了这一句就变成方阵排列的六边形,不是蜂窝。
参数说明:radius决定六边形大小,单位跟投影一致(米制投影就是米);cols和rows是列数和行数,实际覆盖范围会比cols×dx略大,因为奇数列有偏移;origin_x/y是左上角第一个六边形的中心,不是边界角点,这点容易搞错,建议先用范围要素的左上角坐标减去半个dx和dy来估算。
3.2 从范围要素自动推算行列数
上面那个脚本要手动给行列数,实际用起来不方便。更常见的做法是给一个范围要素,自动算出需要多少行多少列才能铺满。
def fishnet_from_extent(out_fc, extent_fc, radius, spatial_ref): """根据范围要素自动计算行列数并生成六边形""" desc = arcpy.Describe(extent_fc) ext = desc.extent dx = 1.5 * radius dy = math.sqrt(3) * radius # 向外扩一圈,保证边缘铺满 cols = int(math.ceil((ext.XMax - ext.XMin) / dx)) + 1 rows = int(math.ceil((ext.YMax - ext.YMin) / dy)) + 1 # 原点取左上角,再往左上偏半个格 origin_x = ext.XMin - dx origin_y = ext.YMax + dy create_hex_fishnet(out_fc, origin_x, origin_y, radius, cols, rows, spatial_ref)这里cols和rows都加了 1 并向外扩,是为了保证范围边缘不会出现「缺角」。多生成的六边形后面用「按位置选择」裁掉就行,比边缘缺一块好处理。origin_x和origin_y往左上偏了一整个dx和dy,是留出缓冲,避免第一个六边形中心正好压在范围边界上导致覆盖不全。
注意:
extent读出来的是范围要素的外包矩形,如果范围要素本身是斜的或者不规则,生成的蜂窝会覆盖整个外包矩形,后续需要自己裁剪。这是正常行为,不是 bug。
3.3 把脚本封装成 .pyt 工具箱
脚本能跑之后,封装成工具箱就是水到渠成的事。新建一个.pyt文件,定义一个 Toolbox 类和一个 Tool 类,把参数暴露出来。
# -*- coding: utf-8 -*- import arcpy import math class Toolbox(object): def __init__(self): self.label = "六边形渔网工具箱" self.alias = "HexFishnet" self.tools = [HexFishnetTool] class HexFishnetTool(object): def __init__(self): self.label = "生成六边形渔网" self.description = "根据范围要素和六边形半径生成蜂窝格网" self.canRunInBackground = False def getParameterInfo(self): params = [ arcpy.Parameter(name="in_extent", displayName="分析范围要素", datatype="DEFeatureClass", parameterType="Required", direction="Input"), arcpy.Parameter(name="radius", displayName="六边形外接圆半径(米)", datatype="GPDouble", parameterType="Required", direction="Input"), arcpy.Parameter(name="out_fc", displayName="输出六边形渔网", datatype="DEFeatureClass", parameterType="Required", direction="Output"), ] return params def execute(self, parameters, messages): extent_fc = parameters[0].valueAsText radius = parameters[1].value out_fc = parameters[2].valueAsText sr = arcpy.Describe(extent_fc).spatialReference fishnet_from_extent(out_fc, extent_fc, radius, sr) arcpy.AddMessage("六边形渔网生成完毕:{}".format(out_fc))封装的关键点:getParameterInfo里把范围、半径、输出做成三个参数,execute里直接调用前面的函数。sr从范围要素自动读取,避免用户选错投影。这样在 ArcGIS 里双击工具,填三个框就能出结果,团队里不写代码的人也能用。
4. 六边形渔网落地时的避坑与排查
4.1 现象:生成的六边形之间有缝隙或重叠
原因:最常见的是dx或dy算错,比如把dx写成2r而不是1.5r,或者dy写成r而不是√3r。另一个原因是投影单位不是米,而半径按米填了,导致比例全乱。
解决:生成后量一下相邻六边形中心的实际距离,水平方向应该是1.5r,垂直方向应该是√3r。如果对不上,检查投影的线性单位,以及dx、dy的计算公式。缝隙和重叠在视觉上可能不明显,但做面积统计时会直接暴露。
4.2 现象:六边形方向不对,想要平顶却出来尖顶
原因:顶点角度数组决定了六边形朝向。上面代码用的是[90, 150, 210, 270, 330, 30],从 90 度开始,得到尖顶朝上。如果想要平顶朝上,角度要整体旋转 30 度,改成[0, 60, 120, 180, 240, 300]。
解决:改angles数组即可,但要注意旋转后dx和dy的角色会对调——平顶六边形的水平间距变成√3r,垂直间距变成1.5r。如果只改角度不改间距,蜂窝会错位。建议一开始就确定朝向,中途改容易乱。
4.3 现象:边缘六边形超出分析范围,统计时多算面积
原因:fishnet_from_extent为了保证铺满,向外扩了一圈,边缘六边形必然有一部分在范围外。
解决:用「按位置选择」(Select Layer By Location)选出与范围要素相交的六边形,或者用「裁剪」(Clip)把六边形裁到范围边界。如果做格网统计,更推荐用「相交」而不是「裁剪」,因为裁剪会把边缘六边形切成不规则形状,破坏面积一致性。常见做法是只保留中心点落在范围内的六边形,这样每个单元面积仍然相等。
4.4 现象:六边形数量太多,生成到一半卡死
原因:半径设得太小,或者范围太大,导致行列数爆炸。比如全国范围用 100 米半径,会生成上亿个六边形,ArcGIS 直接崩。
解决:先估算数量,cols × rows超过百万就要警惕。可以先用大半径试跑,确认流程通了再缩小半径。如果确实需要高密度格网,考虑分块生成再合并,或者改用文件地理数据库(.gdb)而不是 shapefile,后者对要素数量有 2GB 限制。
4.5 现象:生成的六边形在 ArcGIS Pro 里显示正常,导出后变形
原因:导出时坐标系被改了,比如导出成 GeoJSON 默认用 WGS84,六边形被拉伸。
解决:导出前确认目标坐标系,GeoJSON 虽然常用 WGS84,但规则格网不适合存在地理坐标系里。如果必须导出 GeoJSON,建议保留投影坐标,或者接受变形但明确标注。geojson 可以用 arcgis 打开吗?可以,但打开后如果坐标系不对,六边形就不是正的了。
5. 让蜂窝图直接接上空间统计的进阶技巧
生成六边形只是第一步,真正体现价值的是把它接进后续分析流程。我一般会在工具箱里再加一个「格网统计」工具,把点数据或栅格数据按六边形聚合,直接输出带统计值的蜂窝图。核心是用「空间连接」(Spatial Join)把点落到六边形里,或者用「分区统计」(Zonal Statistics)把栅格值汇总到六边形。
一个实用技巧是给每个六边形预计算邻域关系。因为蜂窝图每个单元只有 6 个邻居,可以提前生成一张邻接表,后面做空间自相关(Moran's I)或者邻域分析时直接查表,比每次用「生成空间权重矩阵」快得多。邻接表的生成逻辑是:对每个六边形,找中心距离等于√3r或1.5r且方位角符合 6 个方向的单元。这一步用 Python 字典做,几万个单元秒出。
| 参数 | 建议值 | 说明 |
|---|---|---|
| 六边形半径 | 分析尺度的 1/20 ~ 1/50 | 太小噪声大,太大看不出格局 |
| 投影 | Albers 或当地 Gauss-Kruger | 绝不用地理坐标系 |
| 输出格式 | 文件地理数据库 | shapefile 有 2GB 限制 |
| 边缘处理 | 保留中心点在范围内的单元 | 保证面积一致 |
| 邻接表 | 预计算并缓存 | 加速后续空间权重计算 |
验证方法很简单:生成后随机抽三个相邻六边形,量中心距离,应该都等于√3r(同列)或1.5r(跨列,实际是√(1.5r)² + (√3r/2)²,算出来也是√3r)。如果对不上,回去查投影和间距公式。另一个验证是算总面积,所有六边形面积之和应该等于n × (3√3/2) × r²,跟范围面积对比,误差在边缘处理范围内就正常。
我自己现在的习惯是:任何涉及格网统计的项目,先花十分钟用这个工具箱生成蜂窝图,确认投影和半径合适了再往下做。方形渔网不是不能用,但一旦涉及邻域、密度、空间自相关,六边形的方向无偏性省下来的解释成本,远比多写这几行脚本划算。希望帮到你。
本文还有配套的精品资源,点击获取