
简介本资源是一款面向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 MercatorWKID 3857或者地理坐标系WGS84WKID 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_referencespatial_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(rD:\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(namein_extent, displayName分析范围要素, datatypeDEFeatureClass, parameterTypeRequired, directionInput), arcpy.Parameter(nameradius, displayName六边形外接圆半径(米), datatypeGPDouble, parameterTypeRequired, directionInput), arcpy.Parameter(nameout_fc, displayName输出六边形渔网, datatypeDEFeatureClass, parameterTypeRequired, directionOutput), ] 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 个邻居可以提前生成一张邻接表后面做空间自相关Morans 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²跟范围面积对比误差在边缘处理范围内就正常。我自己现在的习惯是任何涉及格网统计的项目先花十分钟用这个工具箱生成蜂窝图确认投影和半径合适了再往下做。方形渔网不是不能用但一旦涉及邻域、密度、空间自相关六边形的方向无偏性省下来的解释成本远比多写这几行脚本划算。希望帮到你。本文还有配套的精品资源点击获取