简介:这是一份针对测绘、地理信息与土木工程从业者的技术论证资料,围绕ArcGIS在土方量计算中的实用性展开,针对传统CASS不规则三角网法建模繁琐、可视化差、易出错等痛点,系统说明ArcGIS的替代优势。资源为单个PDF文件,大小约183KB,内容精炼,便于快速阅读与打印。文档依次介绍计算原理、数字高程模型(DEM)与TIN建模方式,重点讲解同一地域两期DEM叠加后获取填挖分界线并统计土方量的方法,同时给出多个工程对比案例,表明ArcGIS计算结果与CASS相差不大,且在三维可视化、自动化程度和效率上优于CASS。已有86人学习/下载。对想借助GIS工具优化土方计算流程、提升精度和效率的人而言,这份论证可作为入门参考与可行性依据,帮助理解ArcGIS土方计算的完整思路与成果可靠性。
1. ArcGIS 算土方,先承认它和 CASS 走的是两条路
土方计算这活儿,很多人第一反应是 CASS,尤其是做过 CASS 两期土方计算的人,脑子里全是“三角网法”“方格网法”“生成里程文件”这些词。但当你手头只有一套 ArcGIS 环境、一堆散乱的高程点,以及一个必须随时能回溯的测算记录时,CASS 那套流程反而显得笨重。ArcGIS 算土方的实用性,不在“算得比分专业土方软件更准”,而在它把土方计算变成了一次可重复、可批量、可审计的空间分析。只要你能控制住地表模型构建和计算边界,ArcGIS 的填挖方结果完全能用于工程报量、土方平衡和沉降观测对比。这篇文章就把这条路讲透:模型怎么选、参数怎么定、边界怎么切、结果怎么验证,顺便把那些容易让结果偏掉一两个数量级的坑也一并填上。
2. 用 ArcGIS 做土方前,把地表数据整形成能算的东西:TIN、GRID 与坐标投影
2.1 TIN 与 GRID:不是随便选,要看你手里有什么数据
土方计算本质是两个地表模型做体积累加,ArcGIS 里能装“地表”的容器无非两种:TIN 和栅格。TIN 是不规则三角网,能保留断裂线、特征点和地形突变,在高程点分布不均的施工场地上表现更好;GRID 是规则像元阵列,存储统一、计算快,但它的精度完全取决于插值和像元大小。土方日常项目里,我一般不会直接用散点算体积,而是先建 TIN,再转成 GRID,这样既保留了三角网对地形的拟合能力,又拿到了栅格运算的速度。
如果你手里的原始数据是 CAD 图纸,得先把高程点或等高线转成 shp。CAD 里的高程块、文字注记往往带着小数点对齐的格式问题,转进来后高程字段可能是字符串。用字段计算器转 double 时,注意那些“0.000”后面跟着不可见空格的值,CASS 里画得很规矩的图,导进 ArcGIS 反而最容易在这里翻车。
2.2 坐标系没设对,土方量会错得离谱:投影坐标系才是土方计算的前提
这是整个流程里优先级最高的一步,很多人一上来就在地理坐标系(经纬度)里建模,体积结果出来一个天文数字,还以为是数据问题。体积计算要求坐标单位是线性单位,地理坐标系的单位是度,像元面积算出来不是平方米。做土方前,把高程点、计算边界统一投影到当地的高斯克吕格带或 UTM 投影下。我的习惯是:先确认数据框显示坐标系不能替代几何坐标系,再用 Project 工具正式改写数据的坐标系。
import arcpy arcpy.env.workspace = r"D:\earthwork\data" arcpy.env.overwriteOutput = True points = "points_wgs84.shp" blocks = "blocks_wgs84.shp" out_sr = arcpy.SpatialReference("CGCS2000 / 3-degree Gauss-Kruger CM 114E") arcpy.management.Project(points, "points_gk.shp", out_sr) arcpy.management.Project(blocks, "blocks_gk.shp", out_sr)投影的中央经线要和测区匹配,投影带选错,面积会有一点点偏差,但玄学的地方在于它看起来“差不多”,不仔细对坐标系根本发现不了。投影完成后,再用arcpy.management.AddField给边界 shp 加上面积字段,用CalculateGeometryAttributes检查一下总面积是否和测绘成果对得上,这一步能在源头拦住大部分坐标系错误。
2.3 从高程点 shp 到可计算栅格的 arcpy 起步代码
数据准备好后,先做一步去重检查:同一个 X、Y 坐标出现多个高程值,这在 CAD 转 shp 时非常常见,ArcGIS 的 TIN 建模会自动忽略完全重复的点,但如果你用的是 IDW 插值,重复点会参与加权,结果就会抖动。用arcpy.management.DeleteIdentical按 XY 字段去重,然后再建 TIN。
arcpy.management.DeleteIdentical("points_gk.shp", ["POINT_X", "POINT_Y"]) tin_before = "tin_before" arcpy.ddd.CreateTIN( tin_before, "CGCS2000 / 3-degree Gauss-Kruger CM 114E", "points_gk.shp" + " Shape.Z HardReplace <None> masspoints", "DELAUNAY" )CreateTIN的in_features参数里,Shape.Z表示读取几何的 Z 值,HardReplace允许点数据参与构建而不被简化,masspoints表示这些点是散列质量点。DELAUNAY是构建算法,默认足够。之后转栅格:
out_raster = arcpy.ddd.TinRaster( tin_before, "FLOAT", "LINEAR", "CELLSIZE", 5 ) out_raster.save("dem_before.tif")CELLSIZE 5表示像元大小 5 米,这个值先按经验给,后面第 3 章会讲怎么按点密度精算。转出来之后用符号系统拉伸显示一下,看看有没有明显异常的高程点坑洼——这一步人工目检比任何自动过滤都管用。
3. 两期土方的关键参数:分辨率、边界裁剪与 Cut Fill 的正负号
3.1 像元大小怎么定:按点距定,不是按默认值
很多人在TinRaster里直接留默认CELLSIZE,或者干脆用.tif元数据里的某个随机值。像元设小了,TIN 转栅格会产生高频锯齿,两块场地对比时边缘噪声全被算进土方量;像元设大了,地形细节被抹平,起伏场地的填挖方会被系统性低估。
合理的做法是先统计高程点的平均最近邻距离,把像元大小设成这个距离的 0.5 到 1 倍。地形平缓、边界规整的可以取 1 倍,起伏大、需要卡边坡的取 0.5 倍。统计平均最近邻距离用 ArcGIS 的Average Nearest Neighbor工具就行,结果是点之间平均距离的直接度量。如果你想用脚本控制流程,也可以用arcpy.stats.AverageNearestNeighbor把那个Expected Mean Distance读出来当参考。场地内高程点如果分布极不均匀,比如山脚密密麻麻、山顶稀稀拉拉,那先按 1 倍点距转栅格,再把地面控制点加密一次,别指望算法能“脑补”出稀疏区域的地形。
3.2 Cut Fill 还是栅格相减:两条路线,一套验收逻辑
ArcGIS 里算两期土方有两条主线:一是 3D Analyst 的Cut Fill工具,它直接比较两个表面,输出一个表示净体积变化的栅格;二是用栅格计算器把dem_after - dem_before得到每个像元的高差,再乘以像元面积累加。两条路线我都跑过,结果是等价的,但符号约定不一样,很多人第一次用Cut Fill时会被输出值的正负搞懵:不同版本的 ArcGIS 对“cut 是挖方还是填方”的定义有差异。我不跟你赌这个,模板直接建议你走栅格相减路线,符号自己可控:
from arcpy.sa import * dem_before = Raster("dem_before.tif") dem_after = Raster("dem_after.tif") diff = dem_after - dem_before # 正值为该像元地表升高,代表填方,负值代表挖方 diff.save("diff_earthwork.tif")这段代码里Raster会自动读取两个栅格的范围和像元大小,但有个前提:两期栅格必须完全对齐。diff的范围是交集还是并集,取决于 ArcGIS 环境中arcpy.env.snapRaster的设置。算土方时一定要把snapRaster指到dem_before.tif,否则两期栅格的像元在边缘会错开一个像素,填挖量交界处会出现一条宽 1 像素的带状误差。
Cut Fill工具我一般拿来做快速预览,它的输出可以直接挂到符号系统里看填挖分布,但正式出量时总会在一个 5×5 的小方块上验证一遍正负号和单位,再决定以哪个为准。单位也是一个易错点:栅格相减后像元值是“高度差”,要变成体积得乘像元面积。上面diff里的值带没带面积?不带,diff的每个像元是高差米数,你得自己把整幅栅格乘以单像元面积再汇总。ArcGIS 里这么做:
cell_area = 5 * 5 # 像元边长 5 米 fill_volume = (diff > 0) * diff * cell_area cut_volume = (diff < 0) * diff * cell_area fill_total = float(fill_volume.maximum) # 这是错误的示例,正确的统计方式如下 zonal_stats = arcpy.sa.ZonalStatisticsAsTable( "blocks_gk.shp", "id", fill_volume, r"D:\earthwork\data\fill_zonal.dbf", "SUM" )注意我故意在中间写了一行错误的示例,这是常见误用:diff.maximum返回的是最大高差,不是体积总和。统计体积必须用ZonalStatisticsAsTable按地块边界做区域汇总。参数分别为区域定义、区域字段、待统计栅格、输出表、统计类型。
3.3 边界裁剪:算土方最容易被忽略的“范围陷阱”
两期栅格相减得到的diff_earthwork.tif默认覆盖的是两幅输入的公共范围,这个公共范围往往是被动继承的,可能比你要计算的红线范围大一圈,也可能小一圈。土方量必须严格限定在图纸边界内算,所以先做裁剪。
我一般用ExtractByMask,掩膜就是施工边界 shp。之前提过的蛇形线、“arcgis 画线步骤”常在这里卡住:用编辑工具画边界多边形时,画完线别忘了右键Finish Sketch,否则多边形没法闭合;未闭合的边界在互联网地图或 CAD 导出的数据里很常见,ExtractByMask会静默接受一个坏几何,结果范围完全错乱。导完线后先运行一次Check Geometry,把所有null shape、自相交多边形先修掉。
arcpy.management.RepairGeometry("blocks_gk.shp") diff_clip = arcpy.sa.ExtractByMask( Raster("diff_earthwork.tif"), "blocks_gk.shp" ) diff_clip.save("diff_clip.tif")裁剪完再看一眼地块面积与实测对得上不,这一步相当于把“体积计算结果到底对应哪块地”钉死了。以后出报告时,别人问你土方量怎么来的,你把裁剪后的差值栅格属性表一拉,每个地块一个统计行的逻辑足够清楚。
4. 高程点进入土方量的完整流程:清洗、批处理与异常值排查
4.1 先清洗再计算:粗差点和高程基准在第一步就该干掉
高程点不是越多越好。测绘外业常见的高程块埋点会有一种情况:点本身没错,但把房顶、树冠、电塔底座当成了地面高程,这在倾斜摄影测量数据里尤其普遍。先做一步统计过滤,把Z值落在均值加两倍标准差之外的点标出来,人工看一眼是真实断崖还是飞点。飞点直接删,别在建模时靠 TIN 的容差去扛。
高程基准是第二个坎。两期数据如果来自不同测段或不同控制网,高程基准可能相差一个常数,比如第一期基于 1985 国家高程基准,第二期用了某个独立施工坐标系,明明地面没动,算出来的填方量却大得吓人。处理方式是在场内找几个没有被扰动过的固定点,比如硬化路面、桥墩承台,用这些点的两期高程差求平均偏移量,再整体平移某一个期的高程。ArcGIS 里平移动栅格用Raster + 常数就好,动的是栅格值,不是坐标。
import arcpy.da # 读取固定控制点的两期高程差 z_before = [] z_after = [] with arcpy.da.SearchCursor("control_points.shp", ["Z_before", "Z_after"]) as cursor: for b, a in cursor: z_before.append(b) z_after.append(a) shift = sum([a - b for a, b in zip(z_before, z_after)]) / len(z_before) dem_after_corrected = Raster("dem_after.tif") - shift dem_after_corrected.save("dem_after_shifted.tif")这段脚本先算出所有固定控制点的平均高程差shift,然后把后一期栅格整体平移。注意Raster - shift这个操作里shift是浮点数,ArcGIS 会自动把它广播到所有像元。
4.2 多地块批量算:用地块边界循环调用栅格相减
施工项目往往有十几个区块,一块一块地手点会很累,而且每块都得重新选范围,后面复盘时也没法保持一致。我把地块边界带上进行循环,先按id字段循环,对每个地块做一次裁剪和统计,输出一张带id的 CSV 表:
import csv diff = Raster("diff_clip.tif") block_fc = "blocks_gk.shp" with arcpy.da.SearchCursor(block_fc, ["id", "SHAPE@"]) as cursor: for block_id, shape in cursor: # 按当前地块范围裁出差值栅格 clip = arcpy.sa.ExtractByMask(diff, block_fc, selection_set=[block_id]) stats = arcpy.sa.ZonalStatisticsAsTable( block_fc, "id", clip, r"in_memory\zonal", "SUM" )ExtractByMask的第三个参数selection_set不是所有版本都支持,稳妥的写法是先SelectLayerByAttribute,再把图层传进去。上面这段代码的重点在于循环内的每次运算都独立于前一次,不会因为地块重叠而重复计入土方量。跑完把每个地块的SUM汇总,正数填方、负数挖方,落到一张表里,这就是土方平衡表的最初形态。
4.3 异常结果怎么查:NoData、负值和边界内插三处优先看
如果某些区块算出来的填挖量明显偏离,先查三件事。
第一是 NoData。两期栅格裁剪时,如果某一块在前期有数据、后期没数据(比如测区边缘的云覆盖或扫描盲区),diff_clip对应位置是 NoData,但ZonalStatisticsAsTable默认把 NoData 当成 0 参与统计,结果会偏小。检查diff_clip的 NODATA 像素占比,超过 1% 就不要出量。
第二是边界内插。裁剪后边界是直线,但 TIN 转栅格时边界外的三角面片会把高程外推出去,导致边界带出现一圈假的高差。怎么识别?把差值栅格和高程栅格叠加,查看边界附近 10 米内有没有环形的高值或低值。
第三是负值方向错了。填方为正的约定只适用于dem_after - dem_before这条路线。如果你的结果表和现场情况方向完全相反,多半是有人把dem_before和dem_after传反了。出现这种问题,别急着重新算,先查脚本里两个变量的赋值来源,比重新跑一遍来得快。
5. 验证它“实用”:剖面线检验和分辨率敏感性分析
5.1 做一个已知小区域,把符号约定和单位对清楚
在正式出报告前,先切一块 20×20 米的平坦区域做基准测试。假设这块地不动,理论填挖量是 0。把这个区域的高程点提出来,手动改几个点的高程成固定值,比如把一块区域整体抬高 0.3 米,然后跑一遍完整流程。算出来的填方体积应该是面积乘以 0.3,误差在 1% 以内才说明流程本身没有系统性问题。这一步只花 5 分钟,但能把第 3 章埋下的正负号、单位、NoData 三个雷全部排掉。
5.2 剖面线对比:把三维问题降到二维看
填挖量出自三维体积,但验证它最好先回到二维。在场地内画几条跨过主要起伏区的剖面线,用Interpolate Shape工具分别从前、后两期栅格提取剖面高程,得到两条线并叠在一起看。
如果两条线在地形剧烈变化处的差异呈现明显锯齿状,说明像元大小不合理;如果整体差异呈现常数偏移,多半是基准平移没做干净;如果在边界处突然大角度分叉,是 TIN 外插和边界裁剪打架。剖面线检验特别适合回答“为什么填方量这么大”这个问题——外行以为你看的是横断面,实际上你在查模型层面的系统性偏差。配合“arcgis 导出 excel 表”的热门操作,把剖面交点坐标和高程差导成表格,贴在测算说明里,比任何文字解释都有说服力。
5.3 敏感性分析:分辨率变化多少,结果还稳得住
最后做一个分辨率敏感性测试。把第 2 章的CELLSIZE5 改成 2.5、10 两组,分别重跑栅格相减和区块统计,得到三个总填挖量。计算最大偏差与中间值的比值,如果填挖方总量波动超过 3%,你的高程点密度不足以支撑当前像元大小的计算精度,要么加密高程点,要么在报告里明确标注“本计算对像元大小敏感”。我看过不少工地补测,就是因为敏感性分析没做,各标段用的是不同分辨率,最后土方平衡总是差一口量——问题根本不在算法,在地表数据的采样密度。
这一招做完,ArcGIS 计算土方是不是“实用”,就有了一个可量化的结论:模型稳定、边界清楚、结果可复盘、过程可批处理,剩下的只是数据质量的管理。
本文还有配套的精品资源,点击获取