news 2026/10/1 16:20:21

JSON 与 GeoJSON 区别:坐标顺序、几何规则与空间数据排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSON 与 GeoJSON 区别:坐标顺序、几何规则与空间数据排查

说到 JSON 和 GeoJSON,很多人第一反应是"这不就是一个东西吗,GeoJSON 不就是加了坐标的 JSON"。这话对了一半。JSON 是一套通用的数据交换语法,GeoJSON 是在这套语法上叠加了一层地理语义的约定。真正要命的地方在于:JSON 只要求你语法合法,而 GeoJSON 在语法之外还要求你遵守几何规则、坐标顺序、环方向、坐标基准这一堆隐性契约。语法不合法,解析器直接报错;语义不合法,解析器一声不吭地把数据放过去,然后你的点跑到南极洲附近、多边形变成一个蝴蝶结、地图上什么都没渲染出来。前者好排查,后者能耗掉你一整天。

这篇文章想聊的不是"JSON 是什么"这种入门科普,而是把 JSON 和 GeoJSON 放在一起拆开看:它们的共同点在哪,GeoJSON 额外加了哪些约束,这些约束在真实工程里会在什么地方咬你一口,以及遇到了该怎么一步步排查。适合已经在用地图、做数据可视化、写后端接口或者处理空间数据的人看,也适合刚学完 JSON 语法想往地理方向走的朋友。下面全部基于我在实际项目里踩过的坑和常用的处理链路来讲,能直接抄的代码和命令我都贴上。

1. 从那个坐标顺序被写反的下午说起

1.1 一个点飞到南极洲的真实经历

早期做数据可视化的时候,我把一批城市点位从数据库导出来,字段分别是lng和lat,然后随手拼成了[lat, lng]。渲染出来的时候地图是空的,我以为图层没加载上,翻控制台、翻网络请求,折腾了半天才发现前端库读的坐标顺序是[经度, 纬度],而我给的是反的。纬度取值范围是 -90 到 90,经度是 -180 到 180,我把一个纬度 39 的点放到了经度位置,前端把它当成东经 39 度,纬度位置放的是 116,超过了纬度上限,很多解析器不校验这个范围,直接算出一个天文数字的投影坐标,点就被扔到可见区域之外了。地图上看起来"什么都没画",实际上是画在了你看不到的地方。

这件事让我意识到一个关键区别:JSON parser 不关心你的数据是什么意思,GeoJSON 的消费端却很关心。同一份数据,用json.load读进来一切正常,用地图库渲染就出问题。问题不在 JSON 层,在 GeoJSON 的语义层。

1.2 GeoJSON 在语法层确实就是 JSON

先把最基础的事实钉死:GeoJSON 是一份完全合法的 JSON 文档。它没有引入任何新的语法元素——没有新类型、没有新括号、没有新转义规则。你把它丢给任意一个标准 JSON 解析器,只要 GeoJSON 写作规范,解析一定会成功。反过来说,任何一个能解析 JSON 的库,都不需要额外适配就能读 GeoJSON 的文本结构。

这也是它当年能迅速铺开的原因:不需要为它发明一套二进制格式或者类 XML 的方言,浏览器、Python、Node、Java、Go 里现成的 JSON 能力全部可以直接用。相比老一代的 Shapefile(.shp + .shx + .dbf 三件套,编码混乱,字段名限 10 个字符)、KML(XML 系,体积大)、GML(更啰嗦),GeoJSON 的"零门槛"优势非常明显,一个文本文件、拖进去就能用。

所以讨论两者的差异,层次要分清楚:

层次JSONGeoJSON
语法层定义完整完全继承 JSON,无新增语法
数据类型层6 种值类型复用同样 6 种类型
语义层无约定 type 取值、坐标含义、环方向、坐标基准
校验层语法校验即可语法校验 + 几何规则校验
生态层通用工具链通用工具链 + GIS 工具链

看这张表就明白了:绝大部分"GeoJSON 出错"的问题,都出在后三行,而不是第一行。

1.3 什么时候该用 GeoJSON,什么时候不该用

不是所有带坐标的数据都值得写成 GeoJSON。我一般的判断标准是这样的:

  • 数据需要在多种 GIS 软件和前端地图库之间流转,用 GeoJSON,兼容性最好。
  • 数据只是内部接口传参,字段结构还在频繁调整,用普通 JSON 更灵活,GeoJSON 的字段约定反而会限制你。
  • 数据量在几十万要素以上,考虑换 TopoJSON、FlatGeobuf 或者干脆用瓦片服务,GeoJSON 的体积在浏览器里会很吃紧。
  • 需要带大量业务属性做查询分析,导入 PostGIS 之类的空间数据库比在 GeoJSON 里硬扛更合适。

这个判断很重要,见过太多团队为了"看起来专业"把一堆简单配置塞成 GeoJSON,结果维护成本翻倍。工具是为场景服务的,别反过来。

2. JSON 本身的语法边界与解析报错的真实成因

2.1 六种值类型和三条容易忘的硬规则

JSON 的值类型只有六种:object、array、string、number、boolean、null。听起来简单,但有三条硬规则经常被违反:

  • 字符串必须用双引号,单引号不是合法 JSON。
  • 对象和数组的最后一个元素后面不能有逗号(trailing comma)。
  • 数字不支持NaN、Infinity、-Infinity,也不支持十六进制字面量。

这里有个非常隐蔽的坑:不同语言的标准库对规则的严格程度不一样。Python 的json.loads默认接受NaN和Infinity,这并不符合 JSON 规范。我遇到过 Python 服务端序列化出的数据里带了NaN,自己读回来一切正常,丢给浏览器JSON.parse直接抛Unexpected token N,排查了很久才发现是数值计算里出现了Infinity。

import json # Python 默认允许,但这不符合 JSON 规范 print(json.loads('{"v": NaN}')) # {'v': nan} # 想严格按规范来,加这个参数 try: json.loads('{"v": NaN}', parse_constant=lambda x: (_ for _ in ()).throw(ValueError(x))) except ValueError as e: print("非法常量:", e)

2.2 "unterminated string at position 8192" 这类报错怎么读

Unterminated string in JSON at position 8192 (line 1 column 8193)是个特别典型的错误。8192 这个数字非常有辨识度,它等于 8 × 1024,通常意味着你在某个缓冲区边界上把数据截断了。常见的成因有几个:HTTP 响应分块读取时没有拼完整、读取流的时候按固定长度切割、日志系统截断超长字段、某些网关对响应体做了长度限制。

这时候不要盯着 JSON 格式看,格式多半没问题,问题在于你拿到的文本本身就是残缺的。检查顺序我一般是这样:

  1. 打印出字符串的实际长度,和Content-Length对比。
  2. 看字符串结尾是不是停在半句中间(比如停在某个字段名或者半个转义序列里)。
  3. 检查是不是流式读取没有做完整的拼接。
  4. 用text[-100:]看尾部,如果尾部缺失闭合括号,基本可以确认是截断。

跟这个错误经常一起出现的还有Failed to deserialize the json body into the target type: input: missing field xxx。这类报错来自强类型语言的序列化框架(比如 Rust 的 serde、Java 的 Jackson 严格模式),意思是 JSON 本身解析成功了,但你声明的目标类型要求某个字段必须存在,而实际数据里没有。这属于结构契约不匹配,不是语法问题。解决办法要么给字段加默认值,要么让上游补齐字段,别去改解析器配置绕过去,否则数据缺陷会被吞掉,后面更难查。

2.3 别用肉眼读嵌套结构

这是我一直强调的一条:超过两层的嵌套 JSON,不要靠眼睛找问题。一份几千行的 JSON,你盯半小时也看不出哪个括号对不上。用工具:

  • 命令行快速看结构:jq '.'或者python -m json.tool data.json。
  • 编辑器插件做格式化和折叠,VS Code 自带格式化对 JSON 已经够用。
  • 真实数据里如果字段很多,用jq 'paths'快速列出所有路径,比逐个展开快得多。
# 校验并格式化,失败会直接告诉你出错的字节位置 python -m json.tool data.json > /dev/null # 提取所有键路径,快速摸清结构 jq -r 'paths | join(".")' data.json | sort -u # 只看 GeoJSON 里的 geometry 类型分布 jq -r '.features[].geometry.type' data.geojson | sort | uniq -c

提示:python -m json.tool报错时会指出行列号,配合编辑器的"跳转到行"功能,定位速度比任何手动查找都快。

2.4 数字精度:JSON 不区分整数和浮点

JSON 规范里数字就是一个 number,没有 int 和 float 的区分,但几乎所有解析器都会按双精度浮点(IEEE 754)来处理。这意味着大整数会丢精度。典型场景是数据库主键用了 18 位以上的长整型 ID,序列化成 JSON 再解析回来,末几位变了。处理方式是这类 ID 一律序列化成字符串,别用数字传。

这个特性在 GeoJSON 里更重要,因为坐标本身就是浮点。后面第 4 章会专门算一下精度截断对坐标距离的影响,这里的结论先记着:JSON 的数字层面对地理数据来说精度是够的,但要小心序列化环节的格式化行为。

3. GeoJSON 给 JSON 额外叠加的那些"私货"

3.1 type 字段:九种取值和两层对象模型

GeoJSON 的核心是把数据分成两类对象:几何对象和要素对象。

几何对象有七种:

  • Point:单个点,coordinates是[x, y]。
  • MultiPoint:点数组。
  • LineString:线,至少两个点。
  • MultiLineString:线数组。
  • Polygon:面,坐标是"环的数组",第一个环是外环,后面的是内环(洞)。
  • MultiPolygon:面数组,嵌套又深一层。
  • GeometryCollection:几何集合,用geometries字段而不是coordinates。

要素对象有两种:

  • Feature:必须是{"type":"Feature", "geometry": ..., "properties": ...},geometry可以是上面七种之一,也可以是null。
  • FeatureCollection:必须有features数组,每个元素是一个 Feature。
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 1, "properties": { "name": "示例点", "category": "poi" }, "geometry": { "type": "Point", "coordinates": [116.397, 39.908] } }, { "type": "Feature", "properties": { "name": "示例面" }, "geometry": { "type": "Polygon", "coordinates": [ [[116.0, 39.0], [116.2, 39.0], [116.2, 39.2], [116.0, 39.2], [116.0, 39.0]] ] } } ] }

这九种类型必须记住,因为它们直接决定了coordinates的嵌套深度:Point 是 1 层数组,LineString 是 2 层,Polygon 是 3 层,MultiPolygon 是 4 层。这是实际处理中最容易写错循环的地方。我常用的办法是按类型写一个深度映射表,处理前先查表,避免写一堆if type == ...的嵌套判断。

3.2 坐标必须是 [经度, 纬度],没有商量余地

现行规范里明确规定:坐标顺序是[longitude, latitude],也就是 x 在前、y 在后。这条规定和很多国内开发者的直觉相反,因为我们平时说"经纬度"是经度在前,但写代码时很多人习惯性写lat, lng,因为数据库字段名常常是latitude, longitude的顺序。

更麻烦的是历史遗留:早期的 GeoJSON 草案允许通过crs字段自定义坐标参考系,位置顺序也不那么明确。虽然现在规范已经明确取消了crs字段、统一到 WGS84 十进制度,但网上依然有大量老代码、老数据带着crs字段。碰到这种数据,处理方式是把crs忽略掉,然后确认坐标数值本身是不是 WGS84。如果原本是别的投影坐标(比如高斯克吕格、Web Mercator 的米制坐标),直接当经纬度用会得到完全错误的位置。

坐标顺序错误有个很好用的自检方法:看两个数值的分布范围。如果第一个数值大量落在 ±90 之外,那几乎可以确定是纬度放在了前面。写个统计脚本,一分钟就能判断:

import json def collect_xy(obj, out): if isinstance(obj, dict): if obj.get("type") in ("Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"): def walk(c): if isinstance(c, list) and c and isinstance(c[0], (int, float)): out.append(c) elif isinstance(c, list): for i in c: walk(i) walk(obj.get("coordinates")) for v in obj.values(): collect_xy(v, out) elif isinstance(obj, list): for v in obj: collect_xy(v, out) pairs = [] with open("data.geojson", encoding="utf-8") as f: collect_xy(json.load(f), pairs) first = [p[0] for p in pairs] second = [p[1] for p in pairs] print("第一个分量范围:", min(first), max(first)) print("第二个分量范围:", min(second), max(second))

3.3 多边形环的方向和闭合,规范里写得很细但很多人不读

Polygon 的coordinates是环的数组。规范里有两条硬要求:

  • 第一个环是外环,后续环是内环(洞)。
  • 外环按逆时针(右手法则)走,内环按顺时针走。
  • 每个环的首尾坐标必须相同,也就是环必须是闭合的。

真实情况是,绝大多数解析器和渲染库对环方向是宽容的——你给顺时针的外环,它照样渲染出来。但一旦要做空间分析,比如用 PostGIS 的ST_Area计算面积,方向错了会得到负值,或者用某些库做叠加分析时结果完全不对。环不闭合就更直接了,ST_IsValid会直接判为无效几何。

所以稳妥的做法是处理完数据后跑一次有效性检查。Python 侧用 shapely 最方便:

from shapely.geometry import shape import json with open("data.geojson", encoding="utf-8") as f: gj = json.load(f) for feat in gj["features"]: if not feat.get("geometry"): continue geom = shape(feat["geometry"]) if not geom.is_valid: print("无效几何:", feat.get("properties"), geom.is_valid_reason if hasattr(geom, "is_valid_reason") else "自相交或环未闭合") if not geom.is_simple: print("存在自相交:", feat.get("properties"))

3.4 properties、id、bbox:哪些必须写,哪些写了要负责

properties是挂业务属性的地方,规范要求它必须是 object 或 null。不能是数组、字符串、数字。这一条经常被违反,因为有人图省事写成"properties": ["a", "b"]或者"properties": "some text"。严格校验的库会直接拒收。

id是可选字段,可以是字符串或数字,用来标识要素。注意id应当唯一,但规范不强制。做数据合并的时候我会主动给每个要素生成稳定的 id,便于后续增量更新和去重。

bbox是包围盒,格式是[west, south, east, north],注意它的顺序和坐标一样是 x 在前。bbox可以出现在顶层、Feature 上、或者 Geometry 上。有了它,渲染和空间查询可以先做粗略的矩形过滤,省掉大量计算。规范允许你省略bbox,库会自己算,但如果数据要频繁被空间查询,建议预处理时算好写进去。

还有一点:GeoJSON 的顶层必须是一个对象,不能是数组。你不能返回[{...}, {...}]这种裸数组,必须是FeatureCollection。这个错误在 REST 接口里出现频率极高,因为开发者习惯了列表接口直接返数组。

4. 把 JSON 和 GeoJSON 摆在一起逐项对比

4.1 结构自由度与语义约束的取舍

这是两者最本质的差异。JSON 是纯结构格式,它只管括号配对、引号闭合、逗号位置,至于键名叫什么、值的含义是什么,完全不管。你可以写{"a": 1},也可以写{"经纬度": "北京"},JSON 层面都合法。

GeoJSON 把一部分结构固定下来了:type必须是指定枚举值,coordinates必须是数字嵌套数组,features必须是数组,properties必须是对象或 null。这些约束让不同工具之间可以互相理解,代价是灵活性。你想在顶层塞个metadata字段放数据版本、坐标系说明、生成时间,规范并不禁止,但严格校验的工具可能会警告;放在properties里又显得别扭。

我的处理方式是:顶层加自定义字段没问题,但别覆盖type、features、coordinates、geometry、properties这几个保留字。自定义字段加个前缀,比如x_meta,避免未来和规范新增字段撞车。

4.2 体积、精度和坐标顺序的三组实际数据

第一组是精度。GeoJSON 坐标是十进制度,1 度纬度对应的地面距离约为 111.32 公里。换算下来:

小数位数地面精度(纬度方向)
4 位约 11 米
5 位约 1.1 米
6 位约 0.11 米
7 位约 1.1 厘米

经度方向的精度还要乘以该纬度上的余弦值,在北纬 40 度附近,经度 1 度只有大约 85 公里。所以同一份数据里,经度方向的实际精度比纬度方向更高,截断到 6 位小数时,经度方向的误差大约 0.085 米。一般情况下,6 位小数对绝大多数业务场景都够了,7 位已经是厘米级,超过 7 位基本没有意义,纯属浪费体积。

我做过一次实测:把一份含 1.2 万个点的数据从 15 位小数截断到 6 位,文件从 4.8MB 降到 2.6MB,接近一半。渲染结果肉眼看不出差别。这就是精度控制的性价比。

第二组是体积对比。同样的几何数据,用 GeoJSON 和 TopoJSON 存,后者通常能小很多,因为 TopoJSON 把共享边界提取成 arc,相邻多边形复用同一段弧线的坐标,而 GeoJSON 每个多边形都完整存一遍边界。锯齿状边界多的行政区域数据,压缩比尤其明显。我的经验是典型行政区划数据能压到原体积的 20% 到 40%。不过 TopoJSON 不是 GeoJSON 的超集,前端库需要额外转换,这一点要权衡。

第三组是坐标顺序的验证成本。JSON 里数组顺序完全由你自己定义,前后端对齐就行;GeoJSON 的顺序是规范强制的,写错了不会有报错,只会在渲染时体现为"点不见了"。所以我会在 CI 里加一个范围检查:所有坐标的第一个分量必须在 ±180 内,第二个分量必须在 ±90 内,超了直接构建失败。

4.3 校验工具链的差异

JSON 的校验生态很成熟,JSON Schema 是通用方案,主流的语言都有对应实现。你可以定义一套 Schema 来约束业务字段类型、必填项、取值范围,然后集成到接口层做请求校验,非常顺畅。

GeoJSON 的校验要分两步走。第一步是语法校验,直接用 JSON 解析器;第二步是几何规则校验,需要专门的库或者自己写规则。常用的组合:

  • JavaScript:@turf/helpers配合@turf/invariant做几何断言,或者用geojson-validation做结构校验。
  • Python:jsonschema配 GeoJSON 的官方 Schema 文件,再加上shapely做几何有效性检查。
  • 命令行:ogrinfo可以读 GeoJSON 并输出几何类型和范围,快速确认解析是否正确。
# 快速看一份 GeoJSON 的概要:图层名、要素数、几何类型、范围、坐标系 ogrinfo -so -al data.geojson # 输出成其他格式,顺便验证能不能被 GDAL 正确读取 ogr2ogr -f FlatGeobuf out.fgb data.geojson

注意:单纯用JSON.parse通过的 GeoJSON,只说明语法没问题,不代表几何合法。做入库或者做空间分析之前,几何有效性检查这一步别省。

5. 一次完整的数据处理链路,从解析到上屏

5.1 读取阶段:编码、流式读取和异常兜底

GeoJSON 规范要求 UTF-8 编码。实际项目中我依然遇到过 GBK 文件、带 BOM 的文件。带 BOM 的 UTF-8 文件用某些解析器会在开头报错,因为 BOM 字符不是合法 JSON 起始符。处理方式很简单:

import json def load_geojson(path): with open(path, "rb") as f: raw = f.read() # 去掉 UTF-8 BOM if raw.startswith(b"\xef\xbb\xbf"): raw = raw[3:] return json.loads(raw.decode("utf-8"))

大文件的处理要换个思路。几十兆的 GeoJSON 直接json.load会把整个结构建在内存里,Python 里一个点要素解析成 dict 后的内存占用是文本体积的好几倍,几百兆的文件能把机器拖死。这时候用流式解析(ijson是 Python 里比较常用的选择),只在需要的时候构造几何对象,边读边处理边丢弃。

import ijson def iter_features(path): with open(path, "rb") as f: for feat in ijson.items(f, "features.item"): yield feat for feat in iter_features("big.geojson"): # 处理单个要素,处理完即释放 pass

Node 侧对应的是stream-json,Rust 侧有serde_json的StreamDeserializer,思路都一样:不一次性构建完整对象树。

5.2 几何阶段的常见操作和顺序

拿到数据之后通常要做几件事:坐标精度截断、几何简化、有效性修复、剔除空几何。顺序很重要,我一般按这个来:

  1. 剔除geometry为 null 或者坐标为空的要素,先减小数据量。
  2. 修复无效几何(自相交、环未闭合),因为简化操作对无效几何的行为不可预期。
  3. 做几何简化,减少顶点数。
  4. 截断坐标精度,减小文本体积。
  5. 重新计算bbox。

简化这一步在 Python 里用 shapely 的simplify很直接,容差按你要控制的精度来定。如果要求误差不超过 1 米,纬度方向 1 米约等于 0.000009 度,容差给这个量级。但要注意简化可能把多边形压成非法几何,所以简化后还要再检查一遍。

from shapely.geometry import shape, mapping def clean_geom(geom, tol=1e-5): if geom.is_empty: return None if not geom.is_valid: geom = geom.buffer(0) # 常见的修复手段 if geom.is_empty: return None simplified = geom.simplify(tol, preserve_topology=True) return simplified if not simplified.is_empty else None

preserve_topology=True这个参数很关键。不开启的话,简化算法可能让多边形自相交或者让相邻面之间产生缝隙,视觉上会出现细碎的黑边或者空洞。

5.3 入库阶段:PostGIS 和字段映射的坑

把 GeoJSON 导入空间数据库是高频操作。PostGIS 提供了直接的支持:

-- 从文本构造几何,SRID 明确指定为 4326 SELECT ST_GeomFromGeoJSON('{"type":"Point","coordinates":[116.397,39.908]}'); -- 建表时明确几何类型和 SRID,比用通用 GEOMETRY 类型性能和约束都更好 CREATE TABLE poi ( id bigserial PRIMARY KEY, name text, geom geometry(Point, 4326) );

这里有两个坑。第一,ST_GeomFromGeoJSON默认认为输入是 WGS84 经纬度,如果数据实际是投影坐标,必须先转换再入库,否则位置全错。第二,几何类型别用无参数的GEOMETRY,明确写Point、Polygon这类具体类型,数据库才能建对应的空间索引和约束,查询性能差异很大。

另外,如果 GeoJSON 里有三维坐标([x, y, z]),PostGIS 会识别为 Z 坐标。有些分析函数的 Z 处理行为和预期不同,导入前确认清理掉不需要的第三维,能省不少麻烦。

5.4 渲染阶段:前端库加载时的实际问题

前端两个主流路线:Canvas/SVG 系的 Leaflet,WebGL 系的 Mapbox GL、MapLibre、deck.gl。

Leaflet 加载 GeoJSON 非常直接:

fetch("/data.geojson") .then((r) => { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); }) .then((text) => { let data; try { data = JSON.parse(text); } catch (e) { console.error("JSON 解析失败:", e.message); console.error("文本长度:", text.length); console.error("末尾 120 字符:", JSON.stringify(text.slice(-120))); return; } L.geoJSON(data, { style: { color: "#2a6", weight: 1 }, onEachFeature: (feature, layer) => { if (feature.properties && feature.properties.name) { layer.bindPopup(feature.properties.name); } }, }).addTo(map); });

那段catch里的日志很值得保留。解析失败时把长度和末尾内容打出来,能立刻判断是截断还是格式问题,比在控制台里只看到一个SyntaxError有用得多。

WebGL 系加载时要注意的是数据量。几万个点用 GeoJSON source 还可以,几十万个点会明显卡顿。这时候的常规做法是转成矢量瓦片,让服务端按缩放级别切分,前端只加载当前视野内的部分。如果暂时不想上瓦片服务,deck.gl 的ScatterplotLayer用二进制数组喂数据,比 GeoJSON 对象数组快很多,代价是要自己把坐标转成类型化数组。

还有一点很容易忽略:渲染时坐标顺序是 [经度, 纬度],但很多绘制 API 的参数是 (lat, lng) 顺序。你在同一个文件里可能既调用 GeoJSON 相关接口又调用第三方 API,两者的习惯不一致,复制粘贴代码时最容易出错。我养成了一个习惯:变量名里带上单位,叫lngLatPair或者latLngPair,看到变量名就知道顺序,能挡掉一部分低级错误。

6. 处理坐标偏差和叠加错位时的排查顺序

底图的坐标基准和你的数据基准如果不一致,叠加出来的结果会整体偏移。这个问题的表现是"形状对得上,整体平移了几百米",和坐标顺序错误(点完全跑飞)有明显区别,靠这个特征就能快速区分。

排查我一般按这个顺序走,从成本最低的开始:

第一,确认数据本身的坐标基准。WGS84、Web Mercator(EPSG:3857)、其他投影坐标,这三类处理方式完全不同。Web Mercator 用的是米制坐标,数值通常在几百万量级,一眼就能看出来。如果数值在 ±180 和 ±90 内,那基本是经纬度。

第二,确认渲染底图用的是什么基准。不同地图服务商的底图数据基准可能不同,如果你的数据是 WGS84,底图是另一种基准,叠加时就会产生系统性偏移。这时候的处理方式是统一到同一基准再叠加,而不是靠肉眼微调偏移量。

第三,确认投影转换有没有做。Web Mercator 的转换公式是标准的,纬度方向用到了对数函数,自行实现时容易写错,建议直接用成熟库处理。

// 经纬度转 Web Mercator(EPSG:3857),常用在自定义着色器或者手写投影时 function lngLatToMercator(lng, lat) { const x = (lng * 20037508.34) / 180; let y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360)) / (Math.PI / 180); y = (y * 20037508.34) / 180; return [x, y]; }

第四,确认精度截断有没有造成可见偏差。这一条一般不会导致整体偏移,但如果数据被截断得过分(比如只剩 2 位小数),相邻要素之间会出现明显的对齐缝隙,看起来像是"错位",实际是精度问题。

这四步走下来,绝大多数偏移问题都能定位到具体环节。核心思路就是:别凭感觉调偏移量,一定要找到是哪一步的基准不一致。

7. 几句实在话

JSON 和 GeoJSON 的关系,说到底就是"通用语法"和"领域方言"的关系。写 GeoJSON 的时候,脑子里要同时跑两套检查:JSON 那一套检查有没有写错括号引号,GeoJSON 那一套检查坐标顺序、环方向、类型取值、坐标基准对不对。前者靠工具,后者靠习惯。

我自己的几条固定做法,都是被坑出来的:接口返回 GeoJSON 时,先在测试环境跑一遍坐标范围检查;导入数据库前,先跑几何有效性校验;数据落盘前,先做精度截断并重新算 bbox;前端解析失败时,一定把文本长度和尾部内容打出来。这几条加起来花不了多少时间,但省下来的排查时间非常可观。

最后提一个小技巧:如果一份 GeoJSON 怎么都渲染不出来,先别怀疑渲染代码。把文件拖进 QGIS 看一眼,QGIS 能正常显示说明数据没问题,问题在前端;QGIS 也不显示或者显示了但位置奇怪,问题在数据本身。这个分流动作能省掉一半的排查时间,我每次都用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 16:19:35

汽车电子从ECU到OTA:ADAS测试与故障注入实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 16:19:32

深度学习农作物病虫害识别实战:图像分类与迁移学习完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 16:18:49

从一颗芯片到机器人空间感知:速腾聚创“孔雀”SPAD-SoC量产落地解析

在2026柏林IFA展会上,搭载速腾聚创E2全固态激光雷达的未岚大陆Navimow H5 Pro智能割草机器人正式亮相。表面看,这是一款割草机器人新品发布;更深层看,这是速腾聚创自研“孔雀”SPAD-SoC完成量产交付后的首个公开落地项目。E2自WAIC首发之后,首个量产项目指向割草机器人,释…

作者头像 李华
网站建设 2026/10/1 16:18:29

逻辑严谨吗?8款AI写作辅助网站势力榜,毕业护航!

论文选题总找不到方向?文献综述翻来覆去写不出新意?格式排版反复修改仍不达标? 别担心!AI论文写作工具正在重新定义学术创作方式。本文将从内容逻辑性、资料整合力、格式规范性、查重通过率四个关键维度,深度测评8款热…

作者头像 李华
网站建设 2026/10/1 16:18:24

链表(3)

一、链表中环的入口结点给一个长度为n链表&#xff0c;若其中包含环&#xff0c;请找出该链表的环的入口结点&#xff0c;否则&#xff0c;返回null。数据范围&#xff1a; n≤10000&#xff0c;1<结点值<10000要求&#xff1a;空间复杂度 O(1)&#xff0c;时间复杂度 O(…

作者头像 李华