十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python实现shp与CAD(dxf)互转:从环境配置到批量处理实战

用Python实现shp与CAD(dxf)互转:从环境配置到批量处理实战 你有没有遇到过这种情况领导扔给你一个全国道路网的 shp 文件说“三天内给我画进 CAD 底图里”或者反过来对方只发来一套 dwg 管线图你要把它和 GIS 里的地块边界叠在一起做分析。说实话我早几年做数据处理时几乎每周都要碰一次这种“格式互转”的需求每次打开 ArcGIS 或者 CAD 自带工具来回点点得我直想摔鼠标。shp 和 cad 的互转用现成工具当然能转可一旦涉及批量处理、图层拆分、字段保留、重复执行这些现实需求手动操作基本就是给自己挖坑。我自己最后沉淀下来的方案是直接用 Python 写脚本搞定轻量、可控、可复用。这篇文章就把我完整的经验整理出来包括环境配置、库选型、两套可以直接跑的代码以及我踩过的各种坑。无论你是测绘、规划、水利还是做国土数据的只要碰过这两个格式这篇应该能帮你省下不少时间。1. 为什么非要用 Python 做 shp 和 cad 互转1.1 有现成工具为什么还要自己写网上搜“shp 转 cad”能出来一堆软件ArcGIS 自带的“导出 CAD”功能也很好用QGIS 里装个插件也能转更不用说 FME 这种专业转换工具。既然有这么多现成选项为什么还要写 Python我的答案很现实因为实际工作中的需求往往不是“转一次就行”而是“转一百次、转完还要改、改完还要指定图层、指定字段”。举个我之前做过的例子当时要处理一整年的道路竣工数据甲方每个月发一批 shp每批里有十几个图层图层名还不统一转换完成后需要按道路等级放到不同的 CAD 图层里并且把路面宽度、材质等属性标注在图上。用 ArcGIS 手动操作的话每次都得重新设置输出样式、匹配字段一个月一次点得昏天黑地。而用 Python 脚本整个流程一次写好后后面每批数据丢进去跑一遍就行整个过程可以无人值守输出结果完全一致这在项目交付和验收阶段尤其重要。另外很多单位其实没有正版 FMEArcGIS 也不是人人都有但 Python 是免费的库也都是开源免费的部署成本几乎为零。1.2 转换的本质几何和属性到底发生了什么要写好转码代码先得搞清楚这两个格式的本质区别。shp 本质上是一个“空间数据库表”它由多个文件组成最常见的是 .shp几何信息、.shx空间索引、.dbf属性表、.prj坐标系信息。每个要素不仅有坐标几何还有一行属性记录比如道路名称、等级、宽度、材质。CAD 文件这里主要指 dwg 和 dxf则是“图形文件”它关心的是点、线、面这些绘图实体每个实体有图层、颜色、线型这些绘图属性。dxf 是 Autodesk 公开的图形交换格式可以用文本方式存储所以 Python 解析起来很方便dwg 是闭源二进制格式Python 直接操作很难但几乎所有工作流里我们都可以先请 CAD 把 dwg 另存为 dxf或者用 dxf 作为交换中间格式。所以所谓“shp 和 cad 互转”本质上是两件事几何映射把 GIS 里的点、线、面坐标转成 CAD 里的点、直线、多段线。属性映射把 shp 的 dbf 属性表里的字段想尽办法塞进 CAD 的实体里。最常见的做法是转成文字标注或者写入扩展数据。这里就是最大的坑所在shp 的坐标一般是经纬度单位是度或者投影坐标单位是米而 CAD 的图纸单位一般是毫米或米。直接转过去经常出现“图形看不见”“坐标大得离谱”“图形跑到天边去了”的诡异情况其实大多是坐标单位没处理好。2. 开工前准备环境搭建与库选型对比2.1 Python 环境快速准备做这个需求不需要装一堆复杂框架Python 3.8 以上的版本都行官方 Python 或者 Anaconda 都可以。建议直接建一个干净的虚拟环境免得把系统环境搞乱了。用 conda 的话是这样的conda create -n gis_swap python3.10 conda activate gis_swap然后安装核心依赖库。我给两种技术路线都装了最终日常用的最多的组合是pyshpezdxfGDAL 作为备用。安装命令很简单pip install pyshp ezdxf如果后面要用 GDAL 路线推荐用 conda 安装pip 在 Windows 上装 GDAL 的二进制包稍微麻烦点容易卡在编译环节conda install -c conda-forge gdal装好之后可以快速验证下import shapefile import ezdxf print(shapefile.__version__) print(ezdxf.__version__)能打印出版本号就算环境正常了。2.2 两条技术路线怎么选GDAL 全家桶 vs pyshpezdxf我在实际项目里试过两种主流写法各有各的适合场景用一张表总结下最直观方案类库优点缺点适用场景GDAL/OGR 方案osgeo.ogr功能全面支持几十种矢量格式自动处理坐标系、裁剪、简化等高级操作依赖安装较重命令行式使用方便但想精细控制 CAD 内部结构难度大大批量、跨格式、需要做投影转换的复杂任务pyshpezdxf 方案shapefileezdxf轻量透明pip 直接装代码逻辑完全可控方便自定义图层、属性写入方式需要自己处理坐标单位和编码大文件性能一般中小批量、需要精细控制输出、新手入门友好如果你只是想快速把几个 shp 转成 dxf用 GDAL 确实更快几行代码就完事。但如果你想控制 CAD 里每个要素放哪个图层、属性文字怎么标、颜色怎么区分那就得用pyshpezdxf这种能逐个实体处理的方案。我自己的习惯是项目要求精细输出的用后者项目只要数据格式对就行、不挑细节的用前者。GDAL 的极简互转代码可以写成这样留作备用from osgeo import ogr # shp - dxf src_ds ogr.Open(input.shp) src_lyr src_ds.GetLayer(0) drv ogr.GetDriverByName(DXF) dst_ds drv.CreateDataSource(output.dxf) dst_ds.CopyLayer(src_lyr, 要素层) dst_ds None你会发现GDAL 转出来的 dxf 在 CAD 里打开后要素都在“0”图层属性也不会自动变成文字。如果你的项目对图层、属性有要求这套方案后续的人工整理成本反而很高。3. 核心实操shp 转 dxf 完整代码与逐行讲解3.1 先看懂 shp 在 Python 里的数据结构在用代码输出 dxf 之前得先知道pyshp读进来的 shp 长什么样。核心对象是shapefile.Reader它返回的每条要素是一个shapeRecord包含两部分record属性表里的这一行可以按字段名取值。shape几何对象里面有shapeType和points、parts这些关键属性。几个容易踩坑的细节我要提前说明shape.points是一个“压平”的坐标列表例如一条由两个子线段组成的水系线它的points可能是[p1, p2, p3, p4]。哪几个点组成一个子线段要看shape.parts这个数组它记录了每个子线段在points中的起始索引。处理时一定要加一个“结束索引”否则最后一段会被漏掉。shapeType的整数值表示类型1 是点3 是线polyline5 是面polygon8 是多点。处理 parts 的常见写法是把parts后面追加一个len(points)这样每一段就能用parts[i]到parts[i1]来切分坐标了。3.2 写 dxf 时的点、线、面处理要点ezdxf写 dxf 的逻辑也很直接先建一个文档指定版本然后往模型空间modelspace()里添加实体。点要素用msp.add_point((x, y))需要设置点时通常还要设置一个点样式才能在 CAD 里看清。线要素用msp.add_lwpolyline(points)CAD 的“多段线”既能表达不闭合的线也能表达闭合的面是使用频率最高的实体。面要素本质上也是add_lwpolyline但记得首尾闭合。如果原 shp 的面外环和内环带洞的面都存在于parts里需要判断好哪一段是外环、哪一段是内环。关于属性信息写 CAD 图层是最基本的。比如道路数据里有一条属性叫road_level值有“高速”“国道”“省道”你就可以让不同等级的要素落到不同 CAD 图层。此外如果想把属性作为文字标注出来可以在要素旁边加一个TEXT实体但要控制好字高否则坐标单位一变字的大小就会失控。3.3 可直接运行的 shp 转 dxf 代码下面这段代码是我实际项目里精简出的通用版支持点、线、面三种几何类型并把指定字段作为图层的依据import shapefile import ezdxf def shp_to_dxf(shp_path, dxf_path, layer_fieldNone, encodingutf-8): shp 转 dxf 通用函数 :param shp_path: 输入 shp 文件路径 :param dxf_path: 输出 dxf 文件路径 :param layer_field: 属性字段名用于决定 CAD 图层 :param encoding: shp 的 dbf 属性表编码一般是 gbk 或 utf-8 sf shapefile.Reader(shp_path, encodingencoding) doc ezdxf.new(R2010) msp doc.modelspace() for sr in sf.shapeRecords(): geom sr.shape attrs sr.record.as_dict() if hasattr(sr.record, as_dict) else dict(zip( [f[0] for f in sf.fields[1:]], sr.record )) # 确定图层名 if layer_field and layer_field in attrs: layer_name str(attrs[layer_field]) else: layer_name default # 分类型处理 if geom.shapeType shapefile.POINT: x, y geom.points[0][0], geom.points[0][1] msp.add_point((x, y), dxfattribs{layer: layer_name}) elif geom.shapeType in (shapefile.POLYLINE, shapefile.POLYGON): parts list(geom.parts) [len(geom.points)] for i in range(len(geom.parts)): start parts[i] end parts[i 1] pts geom.points[start:end] if len(pts) 2: continue # 面要素需要闭合 if geom.shapeType shapefile.POLYGON: if pts[0] ! pts[-1]: pts.append(pts[0]) msp.add_lwpolyline(pts, dxfattribs{layer: layer_name}) else: msp.add_lwpolyline(pts, dxfattribs{layer: layer_name}) doc.saveas(dxf_path) print(f转换完成{shp_path} - {dxf_path}) if __name__ __main__: shp_to_dxf(road.shp, road.dxf, layer_fieldlevel, encodinggbk)几个我实际使用后总结的点如果属性表是中文且来自国内测绘数据编码大概率是gbk用默认utf-8读会直接报UnicodeDecodeError或者读出乱码。我一般先试gbk不行再换utf-8。sf.fields[1:]是因为pyshp的字段列表第一项是固定的“删除标记字段”不是真实属性字段。部分 CAD 老版本打不开带中文图层名的 dxf这属于 CAD 自身编码兼容问题最稳妥的做法是图层名用英文或拼音缩写比如“高速”写成“expressway”。4. 反向操作dxf 转 shp 的完整流程4.1 解析 dxf 实体类型从 dxf 转 shp 之前先要搞清楚 CAD 图里到底有哪些实体。打开一个 dxf 之后遍历模型空间每个实体都有dxftype()方法常见的实体类型有dxf 实体类型CAD 里的含义转 shp 时怎么处理LINE直线起点终点构成线要素LWPOLYLINE轻量多段线取顶点坐标闭合则转面否则转线POLYLINE旧式多段线需要先转换为顶点列表再处理POINT点直接转点要素TEXT / MTEXT文字标注一般忽略或按需转成属性这里要留意一个细节CAD 里的多段线即使看起来是一个闭合图形LWPOLYLINE实体的closed属性也可能是 0所以判断面还是线时不要只靠closed属性还要看顶点序列首尾是否重合或者绘制者是否用PEDIT命令闭合了线段。4.2 属性字段怎么保留dxf 文件本身没有属性表概念所以从 CAD 转 shp 时我们要自己定义“哪些影像信息应该变成属性”。最常见的信息源有两个实体所在的图层名比如“给水”“排水”“电力”这些不同图层转到 shp 后可以作为layer_name字段保留。实体的颜色、线型有些 CAD 图纸用颜色来区分要素类型可以转成color或linetype字段。实际操作中如果一个 CAD 图层里有多种类型的要素建议在写 shp 时再加一个etype字段记录原始实体类型是 LINE 还是 LWPOLYLINE方便后期回溯。4.3 dxf 转 shp 的完整代码下面的函数可以把 dxf 中的点、线、面实体转成 shp并把图名和实体类型写入属性表import shapefile import ezdxf def dxf_to_shp(dxf_path, shp_path): doc ezdxf.readfile(dxf_path) msp doc.modelspace() w shapefile.Writer(shp_path) w.field(layer_name, C, 50) w.field(etype, C, 20) for entity in msp: etype entity.dxftype() if etype LINE: start entity.dxf.start end entity.dxf.end w.line(parts[[(start.x, start.y), (end.x, end.y)]]) w.record(entity.dxf.layer, LINE) elif etype LWPOLYLINE: points [(p[0], p[1]) for p in entity.get_points()] if len(points) 2: continue # 判断是否闭合 closed entity.closed if points[0] ! points[-1]: # 如果首尾很近也认为闭合 dist ((points[0][0] - points[-1][0]) ** 2 (points[0][1] - points[-1][1]) ** 2) ** 0.5 if dist 1e-6: closed True if closed: if points[0] ! points[-1]: points.append(points[0]) w.poly(parts[points]) else: w.line(parts[points]) w.record(entity.dxf.layer, LWPOLYLINE) elif etype POINT: x entity.dxf.location.x y entity.dxf.location.y w.point(x, y) w.record(entity.dxf.layer, POINT) w.close() print(f转换完成{dxf_path} - {shp_path}) if __name__ __main__: dxf_to_shp(road.dxf, road_from_cad.shp)这里有个小坑必须提醒pyshp的字段名最长建议控制在 10 个字符以内虽然现在的 GIS 软件普遍支持长字段名但为了避免 ArcGIS 某些版本打不开 shp 属性表我还是保守地用的layer_name这种略长的名字实际使用中可以改成layer更保险字段类型用字符型长度 50 足够。5. 高频问题排查与避坑实录5.1 中文乱码、坐标偏移、看不见图形怎么办我把自己和周围同事踩过的坑整理成了速查表几乎覆盖了 90% 的转换问题现象可能原因解决办法读 dbf 时报UnicodeDecodeError属性表编码不是 utf-8Reader时指定encodinggbk或用chardet自动检测CAD 里打开 dxf 一片空白shp 是经纬度坐标数值在 100 度左右CAD 默认视口看不到在 CAD 里输入Z回车再输入E缩放全图或者写脚本时把坐标统一缩放转换出来的图形整体偏移、距离不对投影坐标系与 CAD 绘图单位不一致转 dxf 前用pyproj统一坐标单位或者同步记录.prj文件面要素转成 shp 后 ArcGIS 提示“自相交”原始 CAD 多段线节点顺序混乱或有回折在 ArcGIS 里用“修复几何”工具或者代码里先对顶点做简化处理转换后属性表打开为空dbf 字段数量超过 255 个或字段类型不支持拆成多个 shp或者改用 GeoPackage 格式过渡最烦人的其实是“cad 里什么都看不见”这种问题。你检查坐标发现经纬度 113.5 度、34.2 度但 CAD 默认显示视口在原点附近那自然是一片空白。后来我习惯在代码里加一个coord_scale参数统一把经纬度乘以 1000 或 10000 再写入 CAD这样至少在 CAD 里看得到相对位置关系。当然这样处理后的坐标只是“图纸坐标”并不具备真实地理意义如果需要真实坐标还是建议保留投影坐标系的 shp。5.2 批量转换与性能优化实战实际工作中没人只转一个文件更多是面对一整个目录。我写 shp 转 dxf 时一般会配合pathlib做批量遍历import pathlib src_dir pathlib.Path(shp_folder) out_dir pathlib.Path(dxf_output) out_dir.mkdir(exist_okTrue) for shp_file in src_dir.glob(*.shp): dxf_path out_dir / (shp_file.stem .dxf) shp_to_dxf(str(shp_file), str(dxf_path), layer_fieldname, encodinggbk)批量处理时最容易遇到的一个情况是文件里混着不同坐标系、不同编码的 shp所以我在函数里会把编码作为参数常驻同时在循环里打印日志哪个文件出问题能一眼定位。性能方面ezdxf在写大文件时速度不算快如果单个 dxf 要写几万个实体建议用ezdxf的fast模式或者考虑改用 GDAL 批量转换。另外内存占用也要留意读超大 shp 时不要一次性shapeRecords()读出全部内容可以改用迭代方式逐条处理pyshp支持sf.iterShapeRecords()对大文件友好很多。我个人的体会是不管是 shp 转 cad 还是反向操作最省心的路径永远是“先小后大、先简后繁”。先拿一个只有十几个要素的小文件把流程跑通确认图层、属性都没问题再丢全量数据进去批量处理。批量转换前一定记得备份原始数据转完再用 ArcGIS 或 QCAD 打开抽查一遍免得一个隐藏的编码问题导致整批成果交付时出洋相。
返回列表