又要跟空间数据打交道了吧。不管是做GIS开发、给地图后端写接口还是折腾PostGIS、搞数据迁移你迟早会遇到三个格式WKT、WKB和GeoJSON。这三兄弟几乎是所有地理空间系统的基本盘从底层存储到前端可视化都绕不开它们。这篇就把它们的来龙去脉、语法结构、内部二进制排列、互相转换的坑一次性说透。我最早把这些格式真正搞明白是在给某跨平台系统的地图模块做数据层的时候。当时要从一堆遗留数据库里把几何字段导出来再喂给前端地图引擎渲染数据库里存的是WKB二进制日志打印是WKT文本接口返回又要GeoJSON三种格式来回倒腾期间踩了不少坑。这篇可以算作我当时踩坑之后整理的完整笔记希望能帮后来人少走弯路。1. WKT、WKB和GeoJSON到底各是什么来路先明确一个概念这三种格式都是用来描述同一个东西几何对象也就是点、线、面这些空间要素。区别在于它们诞生的年代、承载的载体和设计目标完全不同。WKT全称Well-Known Text直译是“众所周知的文本”由开放地理空间联盟OGC提出。它的特点是纯文本、人类可读很像一段带括号的数学表达式。数据库里存空间字段、SQL语句里写空间查询条件、空间分析函数输入输出经常见到它的身影。比如你写一条SQL查某个点是否落在某个多边形里查询条件里那个多边形一般就是WKT格式。WKB全称Well-Known Binary同样出自OGC可以理解为WKT的二进制兄弟。它把几何对象编码成紧凑的字节序列专为计算机高效读写而生。很多数据库在底层存储空间数据时用的就是WKB或其变体比如PostGIS里的geometry类型在物理存储层面就大量使用了类似WKB的结构MySQL里的geometry字段直接就是WKB格式字节流。GeoJSON是后来者基于JSONJavaScript Object Notation标准于2016年发布了正式标准RFC 7946。它的设计目标非常明确面向Web应用。因为JSON本身就是互联网数据传输的事实标准前端JavaScript天然能解析所以GeoJSON几乎成了WebGIS领域的数据交换默认格式。三种格式对照起来看关系是这样的WKT和WKB可以互相转换它们都是OGC标准体系内描述同一几何模型的不同编码GeoJSON在几何类型划分上与WKT/WKB高度一致但封装结构更贴合Web开发习惯有一套独立的坐标系规则。特性WKTWKBGeoJSON本质文本字符串二进制字节流JSON文本设计方OGCOGCRFC 7946互联网工程任务组人类可读性高几乎不可读高计算机解析效率中极高中典型应用SQL查询、调试、文档数据库存储、协议传输Web API、前端展示坐标维度支持2D、Z3D、M测量值支持2D、Z、M支持2D可选Z但官方规范有争议2. WKT文本格式深度拆解WKT的设计哲学就是“像数学公式一样直观”。它用类型关键字加括号包裹的坐标序列来表达几何体简洁到甚至有点简陋但这恰恰是它长盛不衰的原因。2.1 核心几何类型的WKT长什么样先从最简单的点开始。POINT (116.3 39.9)表示经纬度坐标为(116.3, 39.9)的点坐标用空格分隔不是逗号这个细节很多人第一次写会弄错。线串由一串有序坐标点构成形成一条连续折线。LINESTRING (0 0, 1 1, 2 3)表示经过(0,0)、(1,1)、(2,3)三个点的折线。每个点之间用逗号隔开多段折线就是这么串起来的。多边形稍微复杂一层。POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))是一个矩形外环注意首尾坐标相同形成闭合环。带洞的多边形长这样POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))外环后面跟的每个括号就是内环也就是洞。这里有个必须遵守的规则外环坐标必须逆时针排列内环必须顺时针排列。这是OGC规范明确要求的虽然很多库不强制检查但一旦遇到严格校验的环境顺序反了就直接报错或者渲染异常。其实我当时做渲染时就踩过这个坑表现是区域填充时洞被填上了或者整个图形无法正确显示。多点、多线、多多边形是把上面几种用MULTI前缀包起来。MULTIPOINT ((1 1), (2 2))是两个点的集合MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((10 10, 11 10, 11 11, 10 10)))是两个多边形。注意MULTIPOINT的标准写法每个子点有独立的括号虽然有些解析器支持简写成MULTIPOINT (1 1, 2 2)但标准格式还是带括号更稳妥。最后是GEOMETRYCOLLECTION几何集合就是把这些类型混在一起装进一个对象GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 3 3))。这种类型在WKT里很常见但在GeoJSON里对应的GeometryCollection却很少真用。2.2 带Z轴和M值的扩展WKT真实项目里空间数据不只是二维平面。高程、时间戳、测量值都可能作为额外维度附加到坐标上。WKT用后缀字母标识这些额外维度Z表示三维高程坐标M表示测量值MeasureZM表示同时包含高程和测量值。举例来说POINT Z (1 2 3)是三维点xy坐标是(1,2)z值是3LINESTRING M (1 2 10, 3 4 20)则每个点都带一个测量值比如里程数。如果项目需要同时记录经纬度和海拔、时间戳用POINT ZM (1 2 3 100)四个分量齐活。处理带Z、M的数据时有个老问题很多解析库和数据库默认只认二维。一个典型的例子是当你给某个库喂入POINT Z (1 2 3)时有的库会悄悄把Z丢掉有的库根本解析不了直接报错。我做数据迁移时就遇到过源数据库导出的数据带Z目标库不支持三维结果几何对象被自动降维轨迹里程统计直接少了量。遇到这种情况如果对Z坐标有硬性需求务必确认目标库支持带Z的类型比如PostGIS里用geometry(PointZ)MySQL里用SRID加空间索引配合存储。这只是我从项目经验里的一个体会未必所有场景都一样。2.3 SRID与坐标系的关联WKT文本本身只描述坐标数值不携带坐标系信息。所以在数据库系统里SRIDSpatial Reference Identifier空间参考标识符通常单独存在元数据中或者以特定前缀形式出现在WKT前面。PostGIS里常见的完整形式是SRID4326;POINT (116.3 39.9)分号前面是坐标系编号后面才是几何体本身。SRID4326对应的是WGS84经纬度坐标系也就是GPS使用的那个标准SRID3857对应Web墨卡托投影是各种在线地图底图最常用的投影坐标。坐标值一样的两个点如果SRID不同在地图上的实际位置差了十万八千里。做空间运算时坐标系不一致直接导致错误结果比如用两个不同SRID的几何体做距离计算出来的结果毫无意义甚至库会直接报错。3. WKB二进制格式逐字节拆解WKB是WKT的二进制形态设计目标是不浪费一个字节的空间读写极快。想真正理解WKB必须从二进制层面看它的排列方式。好在它的结构足够简单一旦看懂排查问题时非常有底气。3.1 字节序、类型编码和坐标数据WKB由三大部分组成字节序标记、几何类型编码、坐标二进制数据。第一个字节表示字节序也就是多字节整数在内存里的排列方式。0x01代表小端序NDRLittle Endian0x00代表大端序XDRBig Endian。这直接决定后面所有数值怎么解读。很多第一次用WKB的人在这里就栽了拿着一个大端序的二进制去按小端序解析出来的坐标不知道跑到哪里去了这一点是我实际排查时最深的体会之一。紧接着是4字节的无符号整数代表几何类型编码。标准代码是1Point2LineString3Polygon4MultiPoint5MultiLineString6MultiPolygon7GeometryCollection。如果几何体带Z轴类型编码要加1000即1001到1007带M值加2000同时带ZM加3000。这一点解释了为什么有些工具解析出来的WKB类型编码是个看着不常见的四位整数。之后坐标数据就来了。每个坐标分量是8字节的双精度浮点数。点对象POINT就是2个double共16字节线串是点的个数加上各坐标点多边形则是环的个数每个环又有点数加各坐标点。递归嵌套处理。3.2 手算一个POINT的WKB字节流理论再绕不如直接算一个。一个点POINT (1 2)的WKB标准表示是二进制序列十六进制0101000000000000000000F03F0000000000000040分解解读一下。第一个字节01表示小端序。接着01000000按小端序读出来是整数1即Point类型。然后000000000000F03F是double类型的1.00000000000000040是double类型的2.0。这两个double截断前导零之后看起来就是F03F和0040。我用这段十六进制直接拼给数据库测试过插进去再取出来坐标没有任何偏差。理解为啥一个点要占21字节了吧比WKT的POINT (1 2)文本短得多空间数据量大时这种紧凑性是实打实的优势。3.3 WKB在实际系统里的使用数据库是WKB最大的消费场所。MySQL中的geometry字段本质上就是把WKB结构存进BLOB列PostGIS的geometry类型底层也类似同时对WKB做了内部索引优化。ORM框架做空间查询时很多就是用专门的驱动把数据库返回的WKB字节流解析成应用层对象。网络传输场景里WKB也在默默发挥价值。两个后端系统之间传递空间数据相比JSON文本动不动几十KBWKB用几百字节就能搞定。很多空间消息队列、空间数据中台内部传递几何体都默认走WKB只在边界出口才转成GeoJSON。真实项目追求性能优化时一个简单粗暴的思路就是把几何字段从JSON改为WKB二进制存储分析过我自己的一个项目数据平均单条几何体体积能压缩60%到75%序列化和反序列化时间也大幅下降。4. GeoJSON的Web时代封装GeoJSON是地图前端最亲切的格式没有之一。它的结构和JSON完全一致JavaScript原生就能解析这也使得它能在整个前端生态里畅行无阻。4.1 Feature和FeatureCollection的分层结构GeoJSON的最高层通常是一个根对象可以是Geometry对象、Feature对象或者FeatureCollection对象。实际项目里最常见的顶层是FeatureCollection它包含一个features数组每个feature又是一个Feature对象。Feature对象是GeoJSON的核心设计几何信息放在geometry字段里非空间属性放在properties字段里。属性可以是任意JSON对象比如道路名称、人口数量、传感器读数。这样空间数据和非空间数据天然分离不需要额外建表关联是GeoJSON最实用的设计之一。一个标准的FeatureCollection完整结构长这样用JSON展示{ type: FeatureCollection, features: [ { type: Feature, properties: { name: 示例地点, pop: 1000 }, geometry: { type: Point, coordinates: [116.3, 39.9] } } ] }这个结构把“在哪”geometry和“是什么”properties完美分开。我在实际的前后端联调中非常依赖这种结构前端拿到这个对象直接遍历features渲染属性值直接绑定弹窗展示不用再做数据重组。4.2 几何类型的坐标数组嵌套规则GeoJSON的几何类型和WKT几乎一一对应但坐标的组织方式完全遵循JSON的数组嵌套习惯其实就一条规律每增加一个维度方括号就往里多嵌一层。Point的坐标是两个数字组成的数组[116.3, 39.9]。LineString是一串坐标点组成的数组[[116.3, 39.9], [116.4, 39.9], [116.5, 39.9]]每个点是两个数字的位置。Polygon则又加一层[[[116.3, 39.9], [116.4, 39.9], [116.5, 39.9], [116.3, 39.9]]]最外层是环的数组。一个带洞的多边形第一个环是外环后面的环都是内环即[[外环坐标点], [内环1坐标点], [内环2坐标点]]。坐标数组看似简单但嵌套层数搞错是GeoJSON开发中极其常见的问题。LineString写成了三层括号前端渲染时直接报Invalid GeoJSONPolygon少了一层括号图形变形或者完全空白。排查这类问题建议先把单独某个几何体复制到在线GeoJSON解析器里看报错信息再定位具体是哪层括号出问题。我自己排查过很多次少数情况下整个FeatureCollection只要有一个坐标点格式错误就会整体解析失败而报错信息往往不够具体只能靠分段排除法。这个过程虽然费时间但也是理解这种嵌套格式最好的方式。4.3 坐标系和精度限制RFC 7946明确GeoJSON默认使用WGS84坐标系坐标顺序固定为经度在前、纬度在后即[longitude, latitude]。不要尝试往GeoJSON里塞投影坐标除非接收方明确约定支持。很多地图JavaScript库确实兼容投影坐标但去中心化的数据交换场景里坚持WGS84经纬度是稳妥的做法。还有一个精度问题经常被忽略JSON里的数字默认是双精度浮点数本身可以保留很高的精度但如果某个系统在生成GeoJSON前把坐标转成了单精度比如C里用了float而不是double那结果就是小数点后第五位开始明显漂移。肉眼看不出来但做地理围栏、路网匹配这类需要高精度的计算时会持续出问题。跨语言系统交换数据时务必确认各端的坐标数据类型都是双精度这是我跨团队协作时最常提醒别人的一件事。5. 三种格式互相转换的实操方案实际操作中格式转换是高频动作。不同场景有不同选型从数据库读WKB转成GeoJSON丢给前端从前端拿到的GeoJSON转成WKT写回数据库。下面按语言生态给出几个我验证过的转换方案。5.1 Python生态Shapely GeoPandasPython做GIS开发绕不开Shapely库它几乎成了Python空间操作的标准底层。Shapely里边WKT、WKB、GeoJSON之间的转换非常方便from shapely import wkt from shapely.geometry import mapping, shape from shapely import wkb # WKT - 几何对象 geom wkt.loads(POINT (116.3 39.9)) # 几何对象 - WKB wkb_bytes geom.wkb # WKB - 几何对象 geom2 wkb.loads(wkb_bytes) # 几何对象 - GeoJSON字典 geojson_dict mapping(geom2) # GeoJSON字典 - 几何对象 geom3 shape(geojson_dict)GeoPandas基于Shapely封装了一层表格型操作读写GeoJSON尤其方便。geopandas.read_file(data.geojson)直接读成DataFrame对于熟悉pandas的人来说几乎零学习成本。做批量转换时GeoPandas的矢量操作比手写循环快得多。5.2 JavaScript/TypeScript生态Terraformer与Turf前端生态里Terraformer是专门做格式转换的库。把WKT转GeoJSON这样就行const { WKT } require(terraformer); const wkt new WKT(); const geojson wkt.convert(POINT (116.3 39.9));Turf.js是前端空间分析的首选库它内部大量操作基于GeoJSON自身也提供了坐标和格式校验工具。我在前端项目里最常用的一条链路是后端返回WKB可能被编码为Base64字符串→ 前端解析成Geometry对象 → 转成GeoJSON → 渲染Leaflet或Mapbox图层。中间转换函数大概二十行调通一次就能复用到所有地图业务上。5.3 SQL里的隐式转换与显式处理数据库层面实现转换更直接。PostGIS函数非常完备-- WKT转GeoJSON SELECT ST_AsGeoJSON(ST_GeomFromText(POINT(116.3 39.9), 4326)); -- GeoJSON转WKT SELECT ST_AsText(ST_GeomFromGeoJSON({type:Point,coordinates:[116.3,39.9]})); -- WKB转WKT SELECT ST_AsText(ST_GeomFromWKB(0101000000...::bytea));MySQL从5.7版本开始也有对应的ST_GeomFromText、ST_AsGeoJSON等函数但功能覆盖度较PostGIS弱一些比如某些空间操作函数缺失或性能差异明显。选择数据库时如果不确定空间功能需求建议认真对比一下两者的空间函数文档。这个我在做某跨平台系统选型时专门研究过PostGIS在空间分析、空间索引成熟度上都强很多而MySQL胜在对已有业务的兼容和运维便利。5.4 转换过程中的精度与坐标顺序陷阱转换不只是换了个壳很多隐藏陷阱都在细节里。坐标顺序是最经典的坑。GeoJSON明确规定是经度在前纬度在后而WKT文本没有硬性规定坐标分量语义很多情况下按(x y)排列x对应投影坐标系的东向值、y对应北向值恰好相当于经纬度顺序。但这只是约定不是标准。文档里常见的POINT(39.9 116.3)就很难判断到底是纬度在前还是经度在前只能通过坐标系元数据或者数据来源推断。所以我经常提醒团队格式转换链路上保证坐标系元数据不被丢失比转换本身更重要。精度丢失是另一个隐蔽问题。WKB用双精度浮点数存储理论上能精确表达坐标数值GeoJSON底层也是双精度两者转换理论无损。但实际链路中如果中间经历过任何单精度存储比如某些代理层缓存用float32有效数字就会从15-17位降到7位左右损失在小数点后第五位开始体现。简单说就是原本精确到厘米的坐标可能变成精确到米甚至十米。对遥感影像、城市建模这类精细应用是不可接受的。6. 三种格式的选型判断与适用边界了解了三种格式的底细后真正的核心问题是什么时候用哪种格式。大多数项目其实三种都会用到关键在于把每种格式用在它最擅长的地方。6.1 数据库场景选型思路PostGIS、Oracle Spatial这些功能完备的空间数据库内部存储骨架基于WKB思想但对外交互层同时提供WKT和GeoJSON转换函数。应用层读取时一般转成GeoJSON供服务接口输出运维排查时直接输出WKT看文本最方便。MySQL/MariaDB的geometry字段就是WKB二进制接出时如果不加转换直接返回一个二进制buffer前端根本没法用必须在SQL里先做ST_AsGeoJSON或者后端解析。我做过一次性能对比数据量在十万行级别时数据库直接输出WKB再在程序里解析成GeoJSON比数据库直接输出GeoJSON要快约一倍同时带宽占用减少约三倍。但小项目不用纠结这个差距怎么方便怎么来。6.2 接口API场景选型思路面向外部系统的API几乎清一色选GeoJSON。理由很简单广泛的库生态、天然JSON兼容、容易调试、浏览器直接可视化。只要性能没有极端到毫秒级要求GeoJSON是WebGIS接口的最优解。有一种例外是接口双方都是空间数据底层服务比如数据中台内部的微服务间交换数据有的团队会为了极致性能改用WKB甚至Protobuf空间编码但换来的是调试难度和排障成本的上升。我对大多团队的建议是外部接口一律GeoJSON内部存储一律按数据库默认只有批量数据迁移和吞吐量成为瓶颈时才考虑上WKB优化。6.3 文件交换与离线数据场景离线数据文件和桌面GIS软件之间的交换场景Shapefile是最传统的老格式但这几年在逐渐被GeoJSON、GeoPackage取代。GeoJSON作为离线文件的最大优点是单一文件、结构自包含但是它的文件体积很大坐标精度如果控制不当会冗余。GeoPackage本质上是一个单体SQLite数据库内部存储空间数据体积控制好、支持空间索引但调试时要配套工具。我个人的习惯是简单场景直接吐GeoJSON文件复杂工程或者需要加空间索引的场景用GeoPackage。WKT则很少作为大规模交换格式使用那纯粹是给人读解的文本格式文件体积太大压缩率也没有优势。7. 实操中的常见疑难与避坑指南最后这部分是全文最有价值的沉淀。我在实际项目里踩过的、帮别人排查过的各种疑难杂症统一整理成速查表按问题症状列出常见原因和解决思路。故障现象可能原因排查思路前端地图渲染不出图形坐标数组嵌套层数不对或为空去工具站点校验GeoJSON结构合法性图形渲染出来南北偏移或颠倒坐标数组写错顺序[lat, lon]而不是[lon, lat]检查坐标组中数值的相对范围多边形填充区域异常变大环方向颠倒外环变内环确保外环逆时针内环顺时针多边形首尾不闭合坐标序列首尾点不一致多边形对象检查首尾点是否相同报错“无效的WKT”括号不匹配、坐标数量不足、类型大小写错误用数据库或在线解析器逐段提取检错WKB解析出来坐标完全乱掉字节序标记解释错误判断数据是大端还是小端生成调整解析端字节序配置空间距离计算结果离谱坐标系不同一个用经纬度一个用投影统一使用相同SRID再做计算数据库导入后坐标漂移几百米源数据类型是单精度或存储位置被截断检查字段类型、导出链路上的浮点类型定义前端拿到GeoJSON后属性丢失根对象是Geometry而不是Feature属性无处存放确保外层为FeatureCollection每个要素是FeatureZ坐标在转换后丢失目标格式或库不支持带Z的数据预检目标库类型有无三维支持能力这些坑没有哪个是冷门偏门每一个都在我实际处理数据时会反复遇到。印象很深的是某个凌晨上线前某个地图图层整个错位到海里了。最后定位就是后端接口返回的GeoJSON写反了经纬度顺序前端一时没察觉直接渲染上去。前端测试时拿到一个全黑的海洋背景过了五分钟才意识到一致性问题的严重性。类似这种基础错误一次不留神就有可能导致整个联调阶段加班到天亮。8. 自己动手从零构造一个GeoJSON导出脚本与其看一堆理论不如直接做一个能跑的小工具练手。下面这个Python脚本思路很简单从数据库拿WKT文本从项目本地的工具函数转换成GeoJSON字典再输出成文件或者封装成HTTP响应。这个脚本我以前直接拿来给某地图项目做数据导出用直接放在定时任务里到期自动跑一遍生成前端要的GeoJSON文件。import json from shapely import wkt from shapely.geometry import mapping def wkt_to_geojson(wkt_string, propertiesNone): geom wkt.loads(wkt_string) geojson_feature { type: Feature, properties: properties or {}, geometry: mapping(geom) } return geojson_feature def wkt_list_to_feature_collection(wkt_items): features [wkt_to_geojson(w, {id: idx}) for idx, w in enumerate(wkt_items)] return { type: FeatureCollection, features: features } # 模拟数据库查出来的WKT列表 demo_wkt_list [ POINT (116.3 39.9), LINESTRING (0 0, 1 1, 2 3), POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0)) ] result wkt_list_to_feature_collection(demo_wkt_list) with open(output.geojson, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse) print(转换完成共导出 {} 个要素.format(len(result[features])))执行完这个脚本同目录下会生成一个output.geojson文件可以直接拖到任意地图编辑器里查看渲染效果。要把脚本接进真实项目把demo_wkt_list换成数据库查询结果即可。这个思路本质上就是在系统边界把WKT统一转成GeoJSON前端拿到后零加工直接渲染是我在做前后端分离的地图功能时最喜欢的一种模式。9. 多维度经验总结先分别看三种格式在实际工程中给我的总体印象。WKT适合出现的地方是SQL脚本、数据文档、调试输出以及任何“需要人读懂”的场合。它体积不够紧凑性能也不是它的追求。正因为这一点它在自动化系统内部长期存活的时间往往不如WKB和GeoJSON长但却是每个GIS开发者最先应该掌握的“母语”。WKB适合出现的地方是数据库字段、消息队列、高吞吐量空间计算。它的字节结构紧凑到极致也因此没有给人留出可读的余地。所有工具在解析它的时候都必须先读懂字节序和类型码这两步没问题后面的坐标解析就顺理成章。GeoJSON适合出现的地方是Web API、前端渲染、地理数据文件交换。它借着JSON的东风火遍全网也继承了JSON的直观性和略显膨胀的体积。它已经是事实上的WebGIS标准这一点几乎没有疑问。我在实际项目中形成了一套固定的数据流转链路数据库内部用WKB系列存储服务端拼装结果时从数据库取出WKB或者直接吐WKT文本再在代码里转换面向API响应则统一生成GeoJSON。这套链路稳定跑了很久空间数据从未因为格式转换出过问题。很多时候格式本身没有优劣你只要给每一层数据选好它该用的表达方式再管好边界转换整个系统自然就顺了。一种格式的掌握并不难难的是理解它为什么存在、应该在哪个位置起作用以及面对混合生态时如何优雅地让它们协作。这篇梳理完希望你能少走一些我走过的弯路。
阅读完成 · 觉得有帮助?