在XML中表示地理坐标,初看只是把两个数字写进一对标签,但真正进入跨系统交换场景后,几何类型、坐标参考系、轴顺序和分隔规则都会成为解析歧义的来源。如果发送方写 <lat>39.908</lat> 和 <lng>116.397</lng>,接收方并不能从结构本身判断这是 WGS84 经纬度,也无法知道哪个值对应哪个轴。GML(Geography Markup Language)规范通过 OGC 标准把这些问题拆成几何元素和坐标容器两个层面,让空间数据在XML中具备可判读的结构。

XML中表达坐标的常见方式与局限
在单应用内部,最简单的方式是根据业务字段直接定义坐标标签。例如一个地点信息可以写成:
<place> <name>北京</name> <longitude>116.397</longitude> <latitude>39.908</latitude> </place>
这种结构可读性不错,但字段名完全是私有约定。其他系统拿到这份XML后,只能通过人工文档知道 <longitude> 表示经度、<latitude> 表示纬度,机器无法自动识别它们属于空间几何数据。另一个常见做法是把坐标放在属性中,例如 <poi lat="39.908" lng="116.397"/>。属性写法在单点场景下提取方便,但遇到折线、多边形或包含大量坐标点的轨迹数据时,属性值会变得非常冗长,而且无法扩展表达几何层级。
GIS数据通常不是孤立点,而是由点列组成的线、由线围成的面。这就需要XML既能表达坐标数值本身,也能表达这些数值如何组织成几何体。GML的做法是引入Point、LineString、Polygon等几何元素,并配合pos和posList两个坐标容器,使坐标表达和几何类型绑定在一起。接收方解析时先读几何元素,再根据元素规则读取坐标列表,整个过程不需要额外猜测字段含义。
GML规范中的点、线与面表示
GML 3.2版本中,一个基于WGS84坐标系的点通常写作:
<gml:Point xmlns:gml="http://www.opengis.net/gml/3.2"
srsName="urn:ogc:def:crs:EPSG::4326">
<gml:pos>116.397 39.908</gml:pos>
</gml:Point>
这里 <gml:Point> 是几何对象,<gml:pos> 内部存放空格分隔的坐标分量。命名空间 http://www.opengis.net/gml/3.2 必须正确声明,解析器才能识别gml前缀。srsName 属性指定坐标参考系统,使用 URN 形式指向 EPSG 4326。GML本身不规定坐标分量必须是经度在前还是纬度在前,这一点由srsName对应的坐标参考系统定义决定。实际项目中,许多Web API按经度、纬度顺序给出数据,而OGC的EPSG 4326轴顺序定义可能与常见习惯不一致,因此接口文档必须写清顺序。
折线结构使用 LineString 和 posList。posList 专门承载连续坐标序列,所有坐标分量统一用空格分隔,例如:
<gml:LineString xmlns:gml="http://www.opengis.net/gml/3.2"
srsName="urn:ogc:def:crs:EPSG::4326">
<gml:posList>116.397 39.908 116.405 39.915 116.410 39.920</gml:posList>
</gml:LineString>
这里的坐标序列表示三个点,依次为116.397 39.908、116.405 39.915和116.410 39.920。与pos只放一个坐标对不同,posList可以一次容纳大量坐标,这既适合线状地物,也适合后续将整条轨迹直接写入。解析规则上,一个二维坐标点由两个空格分隔的数值组成,整串坐标按顺序读取即可恢复点列。
多边形则由Polygon元素表示。普通无洞多边形通过exterior和LinearRing定义外边界,例如:
<gml:Polygon xmlns:gml="http://www.opengis.net/gml/3.2"
srsName="urn:ogc:def:crs:EPSG::4326">
<gml:exterior>
<gml:LinearRing>
<gml:posList>116.0 39.0 117.0 39.0 117.0 40.0 116.0 40.0 116.0 39.0</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
LinearRing要求首尾坐标一致,以形成闭合环。上例中首尾都是116.0 39.0,这是合法的闭合写法。对于带洞多边形,可以在exterior之后加入interior元素,interior内部同样使用LinearRing。这种层级结构让面状地物在XML中既保持清晰,又能直接对应GIS中的拓扑规则。
旧版coordinates元素与迁移差异
在GML 2.x时代,坐标通常写进coordinates元素。与posList不同,coordinates使用逗号分隔同一坐标点的分量,使用空格分隔不同坐标点。例如:
<gml:Point xmlns:gml="http://www.opengis.net/gml"> <gml:coordinates>116.397,39.908</gml:coordinates> </gml:Point>
这种写法的双分隔符规则在二维坐标下没有问题,但扩展到三维坐标或带维度扩展的数据时,逗号与空格混用会增加解析复杂度。GML 3.0以后,官方推荐使用pos和posList,coordinates则保留为兼容旧数据。如果系统仍然接收GML 2格式,可以继续解析coordinates,但新项目应尽量使用GML 3.x写法。
从coordinates迁移到posList并不只是替换元素名。迁移时要把每个坐标点内部的逗号改成空格,同时检查根元素或几何元素上是否显式声明了srsName。不少旧GML 2数据没有srsName,默认假设为WGS84,但GML 3更强调显式声明,缺失参考系会让坐标解释产生歧义。对于既有接口,建议在迁移前后做坐标值校验,尤其是高精度或投影坐标数据,不能只看是否能成功解析XML。
实际应用中的参考系、轴顺序与性能选择
srsName是GML坐标正确解释的关键。除了EPSG 4326,城市级GIS经常使用EPSG 3857或地方投影坐标系。GML允许srsName使用URN或URL形式,只要发送方和接收方共享同一套坐标参考系统定义即可。如果两者使用不同版本或不同来源的CRS元数据,同一组坐标也可能落在不同位置。
轴顺序问题是实际对接中最容易出错的点。WMS、WFS和部分桌面GIS中,EPSG 4326可能被实现为纬度在前、经度在后,而很多Web API返回的是经度在前、纬度在后。GML通过srsName把顺序交给CRS定义解释,因此不能默认所有实现都一致。解决办法是在交换协议中固定轴顺序,或者在应用层做显式转换,保证进入GML文档的坐标顺序与srsName声明一致。
大数据量场景下,坐标序列应优先使用posList一次性写入,而不是拆成很多个pos节点。这样可以减少XML节点数量,降低DOM解析内存占用,对包含上万个点的轨迹、海岸线或道路中心线数据有明显收益。同时,手工编辑时可以在posList内换行,但不要把坐标点拆到多个posList元素中,除非不同段坐标需要不同参考系或不同几何语义。命名空间和srsName的位置也建议放在几何根元素上,避免每个坐标节点重复声明。