简介:ShpEditor_x是一份基于Visual C++开发的Shapefile矢量数据编辑工具源码包,面向GIS开发者、测绘人员及VC++程序员,用于解决矢量数据读取、编辑与保存问题。工具完整覆盖点、线、面要素的创建、修改、删除和属性编辑,并提供坐标转换、批量处理等扩展功能参考,可直接编译运行或进行二次开发。压缩包内共167个文件,主体为C++头文件与源文件(h/cpp),辅以图标位图资源(ico/bmp)和VC++工程文件(dsp/dsw等),整体仅1.43MB,目录结构清晰,便于按模块查阅。目前已有335人学习浏览,适合作为GIS编程入门或同类工具开发的参考;源码包含完整的编辑器逻辑、属性数据操作以及工具条界面实现,学习者可据此快速理解Shapefile内部结构及矢量编辑流程。如需在现有代码基础上扩展更多空间分析功能,或希望参考工程级C++代码组织方式,这份源码尤其具有借鉴意义。
1. Shp Editor 是什么:一个能救急的轻量矢量编辑器
做 GIS 数据加工的人,电脑里多多少少都存着几个不成体系的小工具。Shp Editor 就是这类基于 VC(Visual C++)写成的轻量矢量编辑器,它最典型的应用场景是:项目快交付,才发现某个图层的属性字段写错了,不想为一个字段启动 ArcGIS,也不想为改一个 shp 去搭 Python 环境。双击打开,定位要素,改值,保存,一分钟完事,机器上不需要装商业 GIS 软件。它把 .shp、.shx、.dbf 三件套读进一个可编辑视图,改完按原结构写回。适合测绘内业、GIS 二次开发者和数据加工人员。它不擅长分析,只擅长快速改矢量,而这个特性恰恰让它成为返工场景里最顺手的一把刀。
2. 先拆文件再说编辑:dbf、shx 与文件头,懂了才不会改错
很多人把 shp 当作一个文件来理解,结果一打开工具报错就懵了。实际上 ESRI Shapefile 是一个“三件套”结构,缺一个都可能导致编辑器打不开或保存异常。搞懂这三件套,比记住任何按钮都管用,因为后续所有踩坑都能追溯到这个结构上。
2.1 shp、shx、dbf:三件套各管一件事
.shp 文件存的是几何坐标,记录了一条要素的 Shape 类型和顶点坐标序列;.shx 是几何索引,记录每条几何记录在 .shp 文件中的偏移量,让软件能快速跳转到某一条要素;.dbf 是属性表,存的是要素对应的字段值,格式沿用了 dBase III 的列式结构。三者通过记录号一一对应:几何记录的顺序、属性记录的顺序必须完全一致,否则就会出现“图形和属性错位”的灾难。
除了三件套,同目录下还常见 .prj(坐标系描述)和 .cpg(属性编码描述)。.prj 是纯文本的 WKT 坐标系定义,比如常见的 CGCS2000 / 3-degree Gauss-Kruger CM 111E 就是一段文本;.cpg 通常只有一行,写着 UTF-8 或 936。很多轻量编辑器不读 .prj,但它仍然是下游 GIS 软件识别坐标系的关键,编辑时最好不要删。
2.2 打开文件时先看这三个信息:坐标范围、要素类型、字段编码
用 Shp Editor 打开任何一个矢量文件,第一件事不是急着改,而是看工具界面上列出的元信息。我最常核对的三项是:坐标范围(Bounding Box)、要素类型(Shape Type)、字段列表。坐标范围对应文件头偏移第 36 字节开始的 4 个 double 值,记录 xmin、ymin、xmax、ymax;要素类型在偏移第 32 字节,1 代表点、3 代表线、5 代表面、8 代表多点;字段列表则来自 .dbf 文件的头部区,包含每个字段的字段名、类型、宽度、小数位。
我习惯在打开一个 shp 后,先看它显示的 Shape Type 是不是和自己判断的一致。举个例子,一个“村界”的 shp 如果显示 Shape Type=3,说明它其实是线数据而不是面数据,后面做面积统计时一定会出错。字段编码同样重要:如果 .cpg 写的是 UTF-8,但工具默认按 GBK 读取,属性表里的中文就会变成乱码。这些信息如果工具界面不显示,我会直接读文件头自查,具体方法在第 6 章给出。
2.3 header editor 缺失时的现场判断
检索“shp editor”时经常带出一个高频词 header editor,很多人不知道它是什么。实际使用中,它指的是文件头查看/编辑能力,或者独立的小工具。有些精简版或绿化版的 Shp Editor 把 header 解析组件裁掉了,表现是:打开 shp 后图形区空白,属性表能看到几条记录,但 Bounding Box 全是 0,Shape Type 也显示为 0。这不是你的数据坏了,而是工具没读取文件头。
遇到这种情况,我的判断顺序是:先确认 .shp 文件大小不是 0,再用十六进制工具或一个小脚本读文件头第 28~35 字节,版本号和 Shape Type 还在,就说明文件本身没坏,问题出在工具上。这时候换一个完整版工具,或者用 QGIS 打开再另存一次,就能恢复正常的文件头信息。文件头是后文的黑匣子,很多“保存后偏移”和“打开报错”都从这 100 字节开始。
3. 用 Shp Editor 做矢量编辑:顶点、属性、批量三步走
知道文件结构后,编辑就有了方向。Shp Editor 这类 VC 工具的操作逻辑和 ArcGIS 不完全一样:它没有复杂的地理数据库概念,只是把几何和属性按记录读进来,改完再写回去。所以编辑的核心就是三条:顶点怎么挪、属性怎么改、批量怎么省时间。
3.1 几何编辑:移动顶点、分割面
几何编辑常见的是移动顶点。一般的操作逻辑是:先切到“编辑”或“顶点”模式,用鼠标点击要素,这时要素的顶点会显示为小方块;拖动某个小方块即可改变局部形状,也可以选中顶点后手工输入目标坐标,精度比鼠标拖拽高得多。对于面要素,工具一般还提供“分割”功能:画一条切割线穿过面,然后选择“拆分”,原面就会被切开成两个要素。
这类编辑里最需要注意的是保存前检查要素的闭合状态。面文件要求每个面的最后一个顶点与第一个顶点坐标一致,否则保存后这个面在 ArcGIS 里可能显示为线或者空洞。我一般会在保存前把视图缩放到该要素,看一下边界有没有出现“缺口”或“飘出来”的线段。另一个高发问题是编辑过程中不小心把顶点挪到了很远的地方,比如把上海的要素拖到了海里,这时看 Bounding Box 会有明显跳变,能在保存前发现。
3.2 属性编辑:字段类型与精度先想清楚再动手
属性编辑看起来简单,就是改表格里的值,但字段类型的坑比几何还多。dbf 字段常见类型包括 C(字符型)、N(数值型)、F(浮点型)、D(日期型),每个字段在文件头里写死了宽度和小数位。比如一个面积字段定义为 N(12, 2),表示总宽度 12 位、小数 2 位,如果你在这个字段里填写超过 9999999999.99 的数,工具要么截断,要么报错。
我在改字段前会先看字段列表里的“宽度/小数位”列,尤其是给数值型字段填数据时,先数一下位数够不够。另一个常见问题是字符型字段的宽度,比如“地块名称”字段宽度只有 10,你要填“张家湾生态林示范区”这 9 个字,中文按双字节算可能超出,保存后会被截断。解决方法是先用有字段结构修改能力的工具把字段宽度改到 50 或 100,再回来填值。属性字段一旦反复改,容易出现列错位,这是最需要保持耐心的环节。
3.3 批量修改:编辑器能力有限时,pyshp 搭把手
Shp Editor 这类工具擅长单点编辑,但遇到几千条要素的批量替换、批量加空格清理、按规则改字段值,效率就很低了。这时候我会切换到 pyshp 来做批量修改,改完再用工具人工抽查。下面是一段实际用过的批量清理字段空格的代码:
import shapefile src = shapefile.Reader("land_2023.shp", encoding="utf-8") w = shapefile.Writer("land_2023_fixed.shp", encoding="utf-8") # 沿用源文件的字段定义,避免字段丢失 w.fields = src.fields for sr in src.shapeRecords(): # 按源字段顺序取值,保证记录与字段一一对应 values = [sr.record[f[0]] for f in src.fields[1:]] # 对“名称”字段做去空格处理,避免“镇 ”和“镇”匹配不上 name_idx = [f[0] for f in src.fields[1:]].index("NAME") if isinstance(values[name_idx], str): values[name_idx] = values[name_idx].strip() w.record(*values) w.shape(sr.shape) w.close()这段代码的逻辑是:读取源文件的结构和字段定义,逐条取出记录值,把目标字段里的空格去掉,再写入一个新文件。参数说明:encoding="utf-8"要与源数据 .cpg 文件的编码一致,如果源文件是 GBK,这里要改成"gbk";src.fields[1:]跳过第一个DeletionFlag字段,这是 pyshp 的固定约定,不能直接套用全部字段。新文件生成后会在同目录自动补出 .shx 和 .dbf,相当于顺便修复了缺失的索引文件。
4. 避坑篇:矢量编辑里最容易翻车的四个场景
编辑器越轻量,隐藏的坑越深。这里整理了四个我在实际数据处理中反复见到的翻车场景,每一条都是从现场总结出来的,按照“现象 → 原因 → 解决”来写,希望能帮你省下不必要的返工。
4.1 打不开却报索引错误:shx 文件弄丢了
现象是:用工具打开 shp,提示找不到 .shx 或索引无效,图形区空白,但文件管理器里明明看到 .shp 存在。经常发生在从邮件、网盘下载或拷贝单个 .shp 文件的时候。原因是:拷贝时只复制了 .shp,没有把同名的 .shx 和 .dbf 一起拷过来,或者 .shx 是 0 字节。解决方法是:把 .shp 放到一个单独目录,用 pyshp 读一遍并写出新文件,就能自动重建索引。第 3.3 节那段批量修改代码同样适用,只需要把字段处理逻辑去掉,直接原样写回即可;也可以用 QGIS 打开后另存一次,效果一样。
4.2 保存后整个图层偏移:文件头被误动了
现象是:保存后重新打开,所有要素都跑到坐标零点附近,或者整体偏移了很大的距离,而单个顶点看起来没变。原因是:编辑过程中误触了工具的文件头信息编辑功能,或者某个全局“更新范围”选项被选中后,工具用了一个错误的范围覆盖了 .shp 文件头的 Bounding Box。解决办法是先看第 6 章的文件头快照方法,比对编辑前后的 xmin、ymin、xmax、ymax,确认范围是否非预期变化;如果已经偏移,用原始备份文件头重新覆盖,或者用 QGIS 重新打开并另存一次,让它重新计算实际范围。从那以后我每次保存前都会强制核对一次 Bounding Box 显示值,尤其是处理坐标值较大的高斯投影数据时,这类错误非常隐蔽。
4.3 属性表中文变问号:dbf 编码没对齐
现象是:编辑器里属性表的中文显示为问号或者“锟斤拷”这类乱码,保存后在 ArcGIS 里依然乱码。原因是:dbf 文件的编码声明(.cpg)是 UTF-8,但编辑器按系统默认的 ANSI/GBK 读写;或者反过来,源文件是 GBK,但你在编辑器的设置里强制用了 UTF-8。解决方法是:先看同目录下有没有 .cpg 文件,里面写的是什么编码;再在打开工具前,把 .cpg 的编码与你要使用的编码保持一致。如果这个工具不支持指定编码,那么就把数据交给 pyshp 处理,pyshp 的 Reader 和 Writer 都带encoding参数,读一遍再写一遍,就能统一编码。注意:如果原文件是 GBK,新文件想转成 UTF-8,不要只在 Writer 里改参数,一定要在 Reader 里也按 GBK 读取,否则会二次乱码。
4.4 多部件面被拆成碎面:选择模式不对
现象是:编辑一个面要素后保存,打开图层发现原本一个完整的、带飞地的面变成了两个甚至几个小面。原因是:编辑状态没有把多部件(Multi-part)要素的全部部件作为一个整体选中,工具只缓存了当前激活的那一个部件,写回时把未激活的部件丢弃了。多部件面在 .shp 里对应 Shape Type=5 且文件头 parts 数组长度大于 1,常见于行政区划图斑和岛状地物。解决方法是:编辑前先确认要素不是多部件,如果数据包含多部件,用支持多部件选取的工具操作,或者先用 Python 检查 parts 数量:
import shapefile sf = shapefile.Reader("multi.shp", encoding="utf-8") for i, sr in enumerate(sf.shapeRecords()): parts = sr.shape.parts or [0] if len(parts) > 1: print(f"记录 {i + 1} 是多部件要素,共 {len(parts)} 个部件")这段逻辑是:shape.parts记录每个部件在整体坐标列表中的起点索引。解决多部件被拆散,最稳妥的做法是在编辑器里一次都不改几何,而是把整个要素删除后重新画一个,或换到专业软件里编辑。最后提醒一句:编辑前一定把 .shp、.shx、.dbf、.prj、.cpg 五个文件整体备份到一个备份目录,这是所有工具事故的后悔药。
5. shp 转 txt 与矢量互转:数据出得去,工作才闭环
编辑器不只用来改数据,很多场景下它还承担“中转站”的角色:把 shp 转成 txt、把 CAD 数据转成 shp、把二维面转成 3D Tiles。这几个转换需求在检索里出现的频率极高,但要用对工具链,不能指望一个 VC 小工具通吃所有格式。
5.1 为什么总有人问“shp 怎么转 txt”
shp 是二进制格式,不适合做文本比对、写日志、做统计复核;而 txt 可以直接用记事本打开,能用 grep、awk 处理,也能快速提交给下游的开发团队做接口联调。早期 GIS 数据交换里,txt 承担了“中间交换格式”的角色,所以现在仍有很多项目要求提供 shp 转 txt 的成果。这个转换不复杂,但要注意编码和坐标顺序,否则转出来的 txt 会让对方一头雾水。
我在给别人写转换脚本时,通常先约定输出格式:每行一条要素的某个顶点,字段间用竖线分隔,坐标先 x 后 y,保留 6 位小数。这个约定能直接减少沟通成本。
5.2 一个稳妥的 shp 转 txt 脚本:pyshp + 编码处理
下面这段代码可以直接改路径后用,输出每个要素的 ID 和所有顶点坐标:
import shapefile sf = shapefile.Reader("boundary.shp", encoding="utf-8") out_path = "boundary.txt" with open(out_path, "w", encoding="utf-8") as out: for sr in sf.shapeRecords(): rec = sr.record rid = rec["ID"] if "ID" in rec.fields else "0" for x, y in sr.shape.points: out.write(f"{rid}|{x:.6f}|{y:.6f}\n")逻辑说明:sr.shape.points返回的是该要素所有部件的顶点坐标列表,对单部件线或面直接遍历即可;对多部件要素,points 会包含所有部件的点,如果你需要按部件区分,要额外处理parts索引。参数说明:encoding="utf-8"必须和 .cpg 一致,读数不对会在客户端出现乱码;坐标保留 6 位小数,约合 0.1 米精度,适合大多数 GIS 成果,但如果你的数据是建筑厘米级坐标,改成保留 8 位小数。
5.3 dwg 转 shp、shp 转 3dtiles:换工具链而不硬扛
“dwg 转 shp”和“shp 转 3dtiles”是两类完全不同的转换,但有一个共同点:都不应该用 Shp Editor 这类矢量编辑器硬做。dwg 是 CAD 的私有格式,包含图层、线型、块定义等复杂信息,轻量编辑器根本读不进去。常见的做法是:在 CAD 里用导出功能输出为 dxf 或 dwg,再用 QGIS 或 ArcGIS 读取后另存为 shp;转换时注意单位,CAD 图纸可能是毫米或英寸,转成 shp 前要统一成米,否则后续面积计算会差几个数量级。
shp 转 3dtiles 更特殊,它是三维瓦片格式,面向 Cesium 等三维引擎。二维面数据转过去之前必须有高度资料,否则生成的是贴地平面。常规链路是:先给 shp 要素添加高度属性,再转成带 Z 值的格式,最后用三维转换类工具切片生成 3dtiles。这里提醒一点,很多人在这一步发现转出来的模型“没有高度”,大部分是原始 shp 本身就是二维数据,不是转换工具的问题。
5.4 转换参数取舍:坐标、编码、几何类型
下面列出转换时最常见的三个参数,以及我平时选用的值:
| 参数 | 推荐设置 | 说明 |
|---|---|---|
| 输出坐标顺序 | x, y | 多数文本交换格式约定 x 在前;少数平台要求纬经度 y, x,先确认下游需求 |
| 输出编码 | UTF-8 带 .cpg | 跨平台通用;如果只给 Windows 老系统用再考虑 GBK |
| 几何类型 | 保持源类型 | 面转线、线转面都靠工具显式转换,不要用 txt 中转时自动降维 |
转换前先统计源数据的 Shape Type,再决定是否做类型转换。比如你有一个面文件,但对接方要求的是线文件,你不能简单地把面文件同名后缀改一下,而是需要用空间工具提取边界线,这是另一个流程。
6. 进阶技巧:用文件头快照兜住坐标偏移
编辑矢量数据最怕的是“保存后偏了但当时不知道”。轻量编辑器不像 ArcGIS 那样有完整的历史记录,出问题只能靠备份硬扛。这里分享一个我自己常用的土办法:编辑前把文件头信息做成快照,编辑后再读一次,对比 Bounding Box 和 Shape Type,有问题立刻发现,不用等下游反馈。
原理很简单:shp 文件头就是说好的 100 字节,偏移 32 的 4 字节是 Shape Type,偏移 36 到 68 是 4 个 double 型的坐标范围。只需要用 Python 把这个固定结构读出来:
import struct, glob, os def shp_header_info(shp_path): with open(shp_path, "rb") as f: head = f.read(100) shape_type = struct.unpack("<i", head[32:36])[0] xmin, ymin, xmax, ymax = struct.unpack("<dddd", head[36:68]) return shape_type, (xmin, ymin, xmax, ymax) for shp in glob.glob("*.shp"): st, bbox = shp_header_info(shp) type_name = {1: "point", 3: "polyline", 5: "polygon", 8: "multipoint"}.get(st, f"type_{st}") print(f"{os.path.basename(shp)}: {type_name}, bbox=({bbox[0]:.6f}, {bbox[1]:.6f}, {bbox[2]:.6f}, {bbox[3]:.6f})")逻辑说明:head[32:36]按小端读取 Shape Type,head[36:68]按小端读取 4 个 double,正好覆盖 xmin、ymin、xmax、ymax。使用方法是:批量编辑前在数据目录跑一遍,把输出存成before.txt;编辑保存后跑第二遍,存成after.txt;最后用文本对比工具看这两个快照的差异。如果 bbox 发生非预期变化,比如所有要素没动但范围扩大了 1 度,或者 Shape Type 从面变成了线,说明文件头被工具改写或误操作,立刻从备份恢复。
这个办法我用了很久,帮我在一次批量编辑几百个图斑时,及时抓住了一个“所有要素坐标没变但文件头 xmax 被改成 0”的怪问题。工具界面上看不出任何异常,只有快照对比能暴露。从那以后,我每次批量编辑前都会强制跑一遍这个快照,把输出贴在任务单旁边,保存后再核对一次。习惯成自然,数据返工的概率确实小了很多。希望帮到你。
本文还有配套的精品资源,点击获取