聊一个有点年头,但至今还在很多内网系统、老业务系统里活着的组合:VB6.0 + MapWinGIS + shp。这套技术栈听起来很“怀旧”,但实际价值一点不过时。很多测绘、规划、水利、电力部门的早期桌面工具,以及现在不少轻量级GIS二次开发需求,都还在用它解决实际问题。原因很简单:VB6足够轻、足够快,MapWinGIS是开源免费的ActiveX组件,shp又是最通用的矢量数据交换格式,三者一拼,就能在不安装ArcGIS等重型平台的前提下,独立完成shp数据的读取、显示、查询和简单编辑。
这篇内容就是围绕“VB6.0中使用MapWinGIS组件访问shp数据”这个主题,把从环境搭建、组件注册、编码实现到问题排查的完整过程拆开讲清楚。适合正准备接手老系统维护、或者想快速做一个轻量GIS工具的开发者参考,里面的代码和步骤都是可以直接拿来用的。
1. 整体思路拆解:为什么是VB6、MapWinGIS和shp这个组合
1.1 选型背后的现实逻辑
先说一个很多人会问的问题:都什么年代了,为什么还要用VB6做GIS二次开发?
真实原因往往很朴素:老系统是VB6写的,数据库连接、业务逻辑、报表打印全在里头,不可能推倒重来。你只需要在现有系统里加一个“地图查看”功能,用VB6继续扩展是最平滑的路径。另一个场景是:单位内网环境受限,装不了大型GIS软件,但又必须查看shp数据。这时候,一个VB6写的小工具加上一个开源的MapWinGIS控件,绿色部署、免安装,往领导桌面一拷就能用,这种优势在项目验收和生产环境里太重要了。
再说为什么选MapWinGIS而不是其他组件。市面上能在VB6里用的GIS组件,主流就几类:
| 组件方案 | 授权方式 | 部署难度 | 学习成本 | 适用场景 |
|---|---|---|---|---|
| ArcGIS Engine | 商业授权,费用高 | 需要License,部署麻烦 | 高 | 大型专业系统,功能要求极高 |
| MapObjects | 已被ESRI淘汰,官方不再更新 | 依赖旧运行库,跑新系统容易出问题 | 中 | 老项目遗留,不建议新项目用 |
| MapWinGIS | LGPL开源免费 | 一个OCX/DLL搞定,免安装 | 低 | 轻量级工具、内网小系统、老平台扩展 |
| GMap.NET | 开源,但偏在线地图 | 依赖.NET环境,VB6集成别扭 | 中 | 在线底图+叠加业务点,不是shp场景 |
MapWinGIS是其中唯一在VB6环境里能做到“免安装、零授权、纯本地打开shp”的组件。它由美国爱达华大学等机构维护,核心是一个ActiveX控件MapWinGIS.OCX,源码开放,支持shp、dbf、shx的完整读写,也支持渲染、投影、空间查询。对于VB6这种老开发环境来说,它的接口设计也足够友好——对象模型清晰,属性、方法命名直观,从入门到写出第一个能显示shp的小程序,一个下午就能搞定。
1.2 要理清的三个概念:shp、dbf和shx
真正操作shp之前,必须搞清楚一个事:shp不是单文件,而是“一组文件”的统称。我们在文件夹里看到的“xx.shp”,实际上是整个Shapefile数据集的一部分。一个完整的Shapefile至少包含三个文件:
- .shp:几何信息文件,存储坐标和图形拓扑,是打开时最核心的文件。
- .shx:几何索引文件,记录每个要素在.shp中的偏移位置,没有它,遍历要素会非常慢甚至打不开。
- .dbf:属性表文件,dBASE格式,存储要素的字段属性,相当于一个简化版的数据库表。
- 可能还有**.prj**(坐标系描述)、.cpg(字符集编码)、.sbn/.sbx(空间索引)等辅助文件。
很多新手只拷了一个.shp文件过来说“数据怎么打不开”,这就是没搞明白Shapefile是“文件家族”。用代码访问shp时,MapWinGIS内部会同时读取.shp、.shx和.dbf三个文件,三个文件必须放在同一目录、同名同路径,缺一个都不行。
理解了这一点,再去写代码就顺了:读取几何信息从Shapefile对象入手,读取属性信息则通过Table属性访问dbf内容。
2. 环境准备与组件接入:搞定429错误的前提
2.1 获取和注册MapWinGIS组件
MapWinGIS的获取方式有两种。一是去官网(mapwindow.org)下载完整的MapWindow GIS安装包,安装后系统里就有了MapWinGIS.OCX;二是只下载MapWinGIS的运行时文件,包括MapWinGIS.OCX和对应的依赖DLL(如GDAL相关库),这种方式绿色便携,适合做部署包。
下载时要注意版本问题。MapWinGIS有32位和64位之分,VB6本身是纯32位的环境,所以必须使用32位版本的MapWinGIS组件。我有一次在64位的Windows 10上注册了64位的OCX,VB6里怎么引用都报错,折腾半天才反应过来是位数不匹配。这一点务必刻在脑子里:VB6项目永远配32位组件,哪怕操作系统是64位的。
注册组件是最容易翻车的环节。把MapWinGIS.OCX放到一个固定目录(比如C:\Windows\SysWOW64,或者放在你的项目目录下),以管理员身份打开命令提示符,执行:
regsvr32 C:\Windows\SysWOW64\MapWinGIS.OCX系统会弹出一个注册成功的提示。如果没有弹出,通常有三种可能:文件路径不对、缺少依赖DLL(如MSVCRT运行库)、或者你用了64位版本的regsvr32去注册32位组件。对于最后一种情况,要确保从C:\Windows\SysWOW64目录下的regsvr32.exe来执行注册。
2.2 在VB6工程中正确引用组件
注册完成后,打开VB6开发环境,在菜单“工程-引用”对话框里,勾选“MapWinGIS”相关的条目。正常情况下会出现类似“MapWinGIS”的项目,勾选后点确定。
但这里有一个注意事项:如果引用对话框里看不到MapWinGIS,不要急着重装。先关闭VB6,重新打开再看一次;还不行,就检查注册表里HKEY_CLASSES_ROOT\MapWinGIS.Map有没有指向正确的OCX路径。另一种更直接的验证方式是,直接在VB6代码里声明对象,看看能否通过编译:
Dim mgMap As New MapWinGIS.Map如果提示“用户定义类型未定义”,说明引用没生效;如果提示“ActiveX部件不能创建对象”或“错误429”,说明组件虽然引用了但运行时无法实例化,这就是典型的注册或位数问题。
另外一个提升开发效率的方法:在VB6窗体上放置MapWinGIS控件。操作路径是“工程-部件”,在“控件”选项卡里找到“MapWinGIS”相关条目,勾选后工具栏会出现“Map”等控件图标。直接拖到窗体上,这个控件就作为地图显示容器用了。
3. 核心代码实现:从打开shp到遍历每一个要素
3.1 用Shapefile对象打开数据文件
MapWinGIS访问shp的核心入口是Shapefile对象。下面这段代码是打开shp并读取图层信息的基础框架:
Dim sf As New MapWinGIS.Shapefile Dim bOK As Boolean bOK = sf.Open("D:\GISData\县域行政区划边界.shp") If bOK = False Then MsgBox "打开失败,请检查文件路径和文件完整性" Exit Sub End If MsgBox "要素个数:" & sf.NumShapes & vbCrLf & _ "字段个数:" & sf.NumFields & vbCrLf & _ "坐标类型:" & sf.ShapefileType这里的“坐标类型”指的是shp是点(Point)、线(PolyLine)还是面(Polygon),MapWinGIS用ShapefileType枚举表示。判断要素类型在后续编码时很关键,因为读取点坐标和读取面坐标的代码逻辑不同。比如点要素直接从Shape对象里取单个坐标,线面要素则要先取Part再取Point。
3.2 读取属性表(dbf)中的字段和记录
shp的属性信息存在dbf文件里,MapWinGIS里通过sf.Table访问。
Dim tbl As MapWinGIS.Table Set tbl = sf.Table Dim i As Integer, j As Integer Dim sFieldName As String Dim sValue As String ' 打印所有字段名 For i = 0 To tbl.NumFields - 1 sFieldName = tbl.Field(i).Name Debug.Print "字段" & i & ": " & sFieldName Next i ' 读取每条要素的属性值 For j = 0 To sf.NumShapes - 1 sValue = tbl.CellValue(0, j) ' 第0个字段,第j条记录 Debug.Print "要素" & j & " 第0个字段值=" & sValue Next j这里有个小细节:tbl.CellValue返回的是Variant类型,如果你知道这个字段是数值型,比如面积、人口,建议直接用Val()或者CDbl()做类型转换,再参与计算。不然字符串和数字混在一起,统计时容易出莫名其妙的类型错误。
还有一个必须注意的字段编号顺序:字段编号从0开始。如果dbf文件里第一个字段是FID,第二个是NAME,那读“县名”就要用编号1,不是2。写代码前建议先动态打印一遍字段名,确认序号再写死逻辑。
3.3 遍历几何要素:点、线、面的读取差异
几何信息是所有Shape对象的集合。下面这段代码演示了如何分类型读取要素坐标:
Dim shp As MapWinGIS.Shape Dim pt As MapWinGIS.Point Dim k As Integer, l As Integer For k = 0 To sf.NumShapes - 1 Set shp = sf.Shape(k) ' 判断类型 If shp.ShapeType = MapWinGIS.ShpShapeType.SHP_POINT Then Set pt = shp.Point(0) Debug.Print "点要素,坐标:" & pt.x & ", " & pt.y ElseIf shp.ShapeType = MapWinGIS.ShpShapeType.SHP_POLYLINE Then ' 线要素:可能有多个部分(Part) Dim nParts As Integer nParts = shp.NumParts Debug.Print "线要素,共" & nParts & "段,点数" & shp.NumPoints ' 需要配合PartStart索引读取 ElseIf shp.ShapeType = MapWinGIS.ShpShapeType.SHP_POLYGON Then ' 面要素,和线的读取方式类似 Set pt = shp.Point(0) Debug.Print "面要素第一个点:" & pt.x & ", " & pt.y End If Next k初次接触的人很容易把Point和Vertex搞混。在MapWinGIS里,Point是坐标点对象,而Shape对象的Points集合里存的是这个要素的所有顶点坐标。读多段线的时候,还不能简单地顺序遍历所有点,因为一条多段线可能由多个不相连的段构成,需要根据PartStart来确定每段的起点。
经验之谈:大多数业务系统里,shp数据的几何精度需求并没有GIS专业软件那么苛刻,很多工具只是为了在地图上画一下轮廓、挂接属性。遇到复杂几何时,先“按要素遍历、再按点遍历”即可,不要过早陷进拓扑细节。
3.4 用Map控件显示shp图层
只读文件、不显示地图,那工具就没有意义。MapWinGIS的Map控件负责图形的呈现。将Shapefile加载到地图上,最简单的方法是:
Map1.AddLayer(sf, True) Map1.ZoomToMaxExtentsAddLayer的第一个参数是Shapefile对象,第二个参数是是否将它设置为可选中。这行代码执行后,shp里的要素就会直接渲染到地图窗口上。如果你希望图层按照属性字段的值做分类配色,可以进一步设置Layer对象的样式,比如:
Dim layer As MapWinGIS.Layer Set layer = Map1.GetLayer(0) layer.DefaultDrawingOptions.FillColor = MapWinGIS.ColorUtils.ColorToUInteger(RGB(255, 0, 0)) layer.DefaultDrawingOptions.PointSize = 8 layer.DefaultDrawingOptions.LineWidth = 2这里有个容易忽略的点:MapWinGIS里面的颜色值不是VB6里常用的RGB或者OLE颜色值,而是用ColorToUInteger做转换。如果直接传一个整数进去,出来的颜色十有八九不对。我自己第一次做点符号配色时,传了vbRed进去,结果画出来的点是蓝色,查了半天文档才发现要过一层转换。
地图上叠加多个图层时,图层的叠放顺序会影响显示结果。MapWinGIS里图层索引靠前的是底层,靠后的是顶层。如果你要在地图底图上叠加shp边界,则底图先加载,边界后加载。
3.5 坐标转换与投影处理
很多shp数据自带.prj投影文件。MapWinGIS读取shp后,常见的两种情况:其一,数据本身就是经纬度,即WGS84地理坐标系,这种情况下不用转换,直接显示即可;其二,数据是投影坐标系,比如北京54、西安80、CGCS2000的3度分带高斯投影,此时需要看清楚坐标数值是几位数,千万不能把投影坐标当经纬度用。
判断一个shp是不是经纬度,最简单的方法就是看坐标值。经纬度的范围X在-180到180、Y在-90到90;投影坐标的X通常是6位或8位,Y是7位左右。我看到长江中游地区的水系shp数据,坐标动辄几十万上百万,那基本可以确定是投影坐标。
如果要做坐标转换,MapWinGIS里可以用GeoProjection对象实现,代码大约是:
Dim gpSrc As New MapWinGIS.GeoProjection Dim gpDst As New MapWinGIS.GeoProjection gpSrc.ImportFromEPSG(4490) ' CGCS2000地理坐标 gpDst.ImportFromEPSG(3857) ' Web墨卡托投影坐标然后对每个要素的每个顶点做转换操作。但说实话,坐标转换在MapWinGIS的VB6环境里不是最顺畅的体验,类库方法名称在不同版本之间还有差异。如果只是做简单显示,更实用的方案是直接用支持动态投影的地图组件,或者把shp提前用GIS软件转好坐标再给VB6程序用。
4. 从静态显示到交互操作:查询、定位和导出
4.1 根据属性值定位要素并高亮显示
做行政区划类工具时最常用的一个功能是:输入县名,地图定位到该县域并高亮。实现思路是先遍历属性表,找到匹配记录,再通过该记录的索引去获取对应几何,最后设置地图视野到该要素的外接矩形。
Dim targetName As String targetName = "武汉市" Dim idx As Integer Dim found As Boolean found = False For idx = 0 To sf.NumShapes - 1 If tbl.CellValue(1, idx) = targetName Then found = True Exit For End If Next idx If found Then Dim shpTarget As MapWinGIS.Shape Set shpTarget = sf.Shape(idx) ' 把视野缩放到目标要素的外接矩形 Dim ext As MapWinGIS.Extents Set ext = shpTarget.Extents Map1.ZoomTo(ext.Center.x, ext.Center.y, ext.Width * 2) ' 高亮:把选中状态设置给它 sf.SelectionIndex = idx End If这里提醒一个效率问题:如果shp有几十万条记录,每次查询都用for循环全表扫描,速度会慢到让人怀疑人生。MapWinGIS的Table对象提供了Search函数,可以直接走属性索引,速度会快非常多。但需要提前建索引,而且VB6里调用Search的返回值类型判断有点绕。如果数据量在几万条以内,全表遍历是够用的,不需要过度优化。
4.2 鼠标点击查询要素属性
结合Map控件的MouseDown事件,可以做出“点击地图,弹出该位置的属性信息”的效果。
Private Sub Map1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Long, ByVal y As Long, ByVal MapX As Double, ByVal MapY As Double) ' MapX和MapY是鼠标点击处的地图坐标 Dim result As Integer ' 在sf形状集中,用指定的容差搜索包含该坐标的要素 result = sf.ShapesInRect(MapX, MapY, MapX, MapY, 0.0) If result >= 0 Then Dim selectedIdx As Integer selectedIdx = result ' 读取选中要素的属性 MsgBox tbl.CellValue(1, selectedIdx) End If End Sub这里比较坑的是ShapesInRect这个函数在不同版本的MapWinGIS里返回值含义不完全一致。有些版本返回的是“搜索到的数量”,有些返回的是“第一个要素的索引”。如果返回结果明显不对劲,建议翻一下当前版本的SDK文档,或者改用Selection+Layer.SelectAt方法绕过。
4.3 把shp坐标导出为txt,做数据交换
shp转txt是很多数据交换场景里的刚需。本质就是遍历所有要素的点坐标,按自己的格式写文本文件。下面的示例演示了最简单的点要素导出:
Open "D:\GISData\output.txt" For Output As #1 Dim m As Integer For m = 0 To sf.NumShapes - 1 Set shp = sf.Shape(m) Set pt = shp.Point(0) Write #1, pt.x, pt.y Next m Close #1如果你做的是线、面要素导出,就要先读取Part和Point层级。把坐标循环写出来本身不是难事,难点在于编码和格式约定:UTF-8还是ANSI、逗号分隔还是制表符分隔、是否包含属性字段,这些都要根据接收方的要求先定好,不然做出来的txt文件没法直接用。
5. 典型问题排查:429错误、数据损坏和格式兼容
5.1 错误429“ActiveX部件不能创建对象”的完整排查路径
错误429是VB6使用ActiveX组件时,出现频率最高的运行时错误。从MapWinGIS的角度,出现这个错误,排查顺序建议是这样:
第一,确认组件已正确注册。用regsvr32重新注册一次,然后检查注册是否提示成功。如果提示Success,排除注册环节。
第二,确认位数匹配。VB6是32位应用,只能实例化32位组件。如果你注册了64位的MapWinGIS.OCX,就会报429。强行在64位系统上操作时,只认SysWOW64目录下那条注册路径。
第三,检查依赖项缺失。MapWinGIS依赖GDAL和几个运行库,比如VC++ Redistributable。如果这些依赖缺失,即使OCX注册成功,运行时也可能报429。最简单的验证方法是用Dependency Walker看一下OCX的依赖列表,但这个工具有点过时,更实用的做法是直接用“regsvr32注册成功后,用一个空VB6工程先测试New MapWinGIS.Map,通过再往下走”。
第四,检查文件路径有没有莫名其妙的问题。有些公司电脑上杀毒软件会把OCX里的关键DLL隔离到沙箱,导致运行时找不到组件。遇到这种情况,把文件夹加入安全软件白名单就好。
可以把上面这些整理成一张速查表,贴在自己项目的调试笔记里:
| 错误现象 | 优先级 | 检查项 | 解决方案 |
|---|---|---|---|
| 引用时找不到控件 | 1 | 注册表项 | 重新regsvr32 |
| 编译通过但运行报429 | 2 | 位数匹配 | 换32位OCX |
| 运行报429且有GDAL字样 | 3 | 缺少依赖库 | 安装VC运行库,补齐DLL |
| 换机器后报429 | 4 | 绿色部署未注册 | 编写注册.bat,随工具分发 |
5.2 shp数据打开失败或乱码
打开shp失败原因很集中。首先是缺文件,用代码打开shp前先检查目录下是否有同名.shx和.dbf。不少人是通过U盘拷贝数据的,拷掉了一个文件都不知道。其次,如果程序能打开shp但属性字段中文乱码,那是dbf文件字符集问题。MapWinGIS对高版本的dbf支持情况尚可,但对老式GBK编码支持不全,属性读取时需要根据字段值的字节特征尝试转码。
如果shp本身已经损坏,最直接的办法是用shapechk工具修复。shapechk是shp数据修复的经典工具,它能检测并修复.shp和.shx文件的头部信息、属性记录长度错误、几何偏移越界等问题。用法不复杂:下载运行shapechk,选择损坏的shp文件,它会列出检测出的错误条目,执行修复后生成修复后的文件。
但这里必须说一个忠告:修复工具只能解决结构性损坏,比如文件截断、长度字段错误;如果是几何坐标本身错误,比如出现零面积多边形、自相交、闭合点不在起点,shapechk做不到“智能修复”。这类几何逻辑错误,还是要用GIS平台的重构几何工具来处理。所以在用shapechk之前,备份原文件,修复完仔细对比修复前后要素数量和数据精度。
5.3 dwg转shp和shp转3dtiles的衔接思路
很多项目的原始数据是CAD的dwg格式,最终却要用shp存储。dwg转shp,MapWinGIS本身不直接支持,需要先用CAD软件或FME、Global Mapper等工具把dwg导出成dxf,再通过GIS软件把dxf转成shp。如果是批量转换,ogr2ogr命令行工具是最佳选择,一条命令搞定:
ogr2ogr -f "ESRI Shapefile" output.shp input.dxf -skipfailures做了dwg转shp之后,经常会出现线要素变成密密麻麻碎段、图层名丢失、属性字段为空这类问题。主要原因是dwg里的实体没有正确的图层整理。在CAD里转出前,先把图层整理干净、闭合多段线、去掉重复线,转换出来的shp质量会好很多。
至于shp转3dtiles,这是这些年做三维可视化的常用动作。先把shp的要素用意匠GIS、cesiumlab或者用户自写脚本转成带高度的GeoJSON或多面体,再做3dtiles切片。这个流程里shp的每个要素必须带高度属性或者基于建筑物楼层数推算高度。如果原始shp只有二维边界,那转换后的三维白模不可能有准确高度,只能做示意效果。所以做shp转3dtiles之前,务必先确认属性表里有没有高度字段(如“层数”“楼高”)。
5.4 行政区划shp和渔网分割shp的实用场景
域行政区划边界shp数据经常出现在两个场景:一是做“按行政区分区展示统计数据”,二是做“可视化底图”。这里有个数据精度问题:用来做年报图、示意图,下载的shp一般够用;但如果做空间叠加分析、面积计算,还是应该使用测绘部门提供的标准边界数据。县域行政区划边界shp取名虽然类似,不同来源的边界精度可能差出好几个等级。
渔网分割shp是空间分析里的高频操作。把一个大的研究区按给定行数、列数或者给定网格大小,切成无数个小网格。MapWinGIS里有做渔网的函数,即Grid对象的方法,但VB6环境下调用起来相对繁琐。更成熟的做法是先用GIS软件生成渔网shp,然后用这个渔网shp作为裁剪工具,遍历原始shp并结合MapWinGIS的Intersect函数做分割。如果你正好需要做“渔网分割shp”,建议先用GIS软件生成网格,再把任务拆成“按网格批量裁剪”来编程,稳定性和速度都会好很多。
6. 一个完整的迷你案例:县区边界加载与属性查询工具
前面代码都比较琐碎。我在这里组合一个完整的小案例,帮助你把整个流程串起来。
场景:VB6窗体上放置一个Map控件、一个文本框、一个按钮。程序启动时自动加载名为“县域行政区划边界.shp”的文件;用户在文本框输入一个区县名称,点击按钮,地图缩放到该区县并高亮显示。
窗体上放置控件:Map控件(命名为Map1)、TextBox(命名为txtName)、CommandButton(命名为cmdLocate)。启动Form_Load事件中加载数据:
Option Explicit Private sf As New MapWinGIS.Shapefile Private tbl As MapWinGIS.Table Private Sub Form_Load() Dim bOK As Boolean bOK = sf.Open(App.Path & "\data\县域行政区划边界.shp") If bOK Then Set tbl = sf.Table Map1.AddLayer(sf, True) Map1.ZoomToMaxExtents End If End Sub点击定位按钮:
Private Sub cmdLocate_Click() Dim sName As String sName = Trim(txtName.Text) If Len(sName) = 0 Then Exit Sub Dim idx As Integer Dim bFound As Boolean bFound = False For idx = 0 To sf.NumShapes - 1 If StrComp(tbl.CellValue(1, idx), sName, vbTextCompare) = 0 Then bFound = True Exit For End If Next idx If bFound Then Map1.ZoomTo(sf.Shape(idx).Extents.Center.x, sf.Shape(idx).Extents.Center.y, sf.Shape(idx).Extents.Width * 3) sf.SelectionChanged = True sf.SelectionIndex = idx Else MsgBox "没有找到该名称" End If End Sub这个案例里的关键技巧是StrComp用vbTextCompare做不区分大小写的比较,避免用户输入大小写不一致导致查找失败。另外,调用ZoomTo后地图比例尺会变化,如果你希望保持地图旋转角度或其他视图状态,记得在ZoomTo前后暂存和恢复。
7. 我给初学者的几条实操建议
如果你刚从零开始走这条路,我的建议是从“读”开始,不要急着“写”。先用MapWinGIS写一个能打开shp的小工具,把每个要素的坐标和属性都打印出来,对数据结构建立感觉,再考虑做显示、查询、编辑。
其次,养成“看错误行号”的习惯。VB6的调试器在运行时错误上只会弹出一个对话框,很多人看到错误就直接懵了。实际上错误对话框里的“调试”按钮会直接定位到出错那一行,大多数情况下是对象没有Set实例化、或者对象类型不对。在代码里多用“Set xxx = New MapWinGIS.xxx”,少用或不用“Dim xxx As New MapWinGIS.xxx”,后者有时会因为初始化时机的问题产生负面效果。
最后,千万要备份数据。MapWinGIS是一个开源组件,提供了强大的写能力,但它没有ArcGIS那么完善的容错机制。你对着shp做编辑、删除要素、写回文件时,一步错就可能把几十万条数据的文件搞坏。写回前先备份,修复工具shapechk只能救结构性损坏,救不了错误逻辑造成的数据丢失。
我拿这个组合做过不少小工具,从最初的“看数据”到后面的“点选查询”“按字段定位”,再到写回分析结果,一路走下来,最大的感受是:这套技术栈的核心不是图形界面的花哨程度,而是你能不能把shp的结构、坐标系统和属性的关系理解透。这些东西吃透了,不管以后换什么语言、什么GIS平台,其实都是相通的。