☰
GeoJSON省市边界数据处理:坐标纠偏、抽稀与离线渲染避坑
2026/11/5 6:03:28 网站建设 项目流程

简介:面向前端可视化与地图开发者的中国行政区划边界坐标数据包,以标准 GeoJSON 结构组织,可直接用于 ECharts、Leaflet、Mapbox 等地图组件的省市级轮廓绘制、区域着色与下钻交互,免去自行抓取与校对边界坐标的重复劳动。压缩包共 36 个文件,以 35 个 json 边界数据和 1 个 js 脚本为主,整体约 415KB,体积轻量,既可合并绘制全国视图,也能按省份拆分异步加载,便于控制首屏体积。数据覆盖全国各省、自治区、直辖市及港澳台地区,轮廓精度可支撑到区县一级的展示需求;每个要素均带有 id、name、cp 中心点与 childNum 等属性字段,geometry 以 Polygon 环形坐标序列表达真实轮廓,可直接映射为区域编码、悬浮提示与标签定位,叠加散点、飞线、热力图等图层即可完成区域数据看板。目前已有 2484 人学习下载,适合需要快速搭建地图原型、做区域数据展示或教学演示的开发者参考。

1. 省市边界 JSON 是离线出图的底牌

做数据看板的人大概都遇到过这种场面:业务方丢来一张带地区字段的明细表,要求“按省市边界把指标铺到地图上”,而你手上只有一个空的图表实例和一堆经纬度散点。这时候最省事的做法,不是去接在线瓦片服务、也不是临时找接口拉行政区划,而是手里常备一份省市边界坐标 JSON 文件——它把每个省级、地级行政单元的轮廓,用一串经纬度点完整描述出来,喂给图表库或 GIS 引擎就能直接出图,不依赖网络、不受接口限流影响。

这份资源真正的价值在于“离线可控”:数据不出内网、渲染不卡在别人的服务上、边界精度和体积可以自己调。适合三类人:做可视化大屏的前端、做区域统计分析的数据同学、以及需要在本地跑空间计算的算法工程师。下面按“结构怎么读 → 坐标怎么对 → 体积怎么压 → 哪儿容易翻车 → 怎么玩进阶”的顺序拆。

2. 拆开一份省市边界 JSON:Feature、环和坐标顺序

拿到文件别急着往图表库里塞,先搞清楚它的骨架。省市边界文件绝大多数遵循 GeoJSON 规范(RFC 7946),本质是一个大的 FeatureCollection,每个行政单元是一个 Feature,几何部分就是它的轮廓。读不懂这三层结构,后面所有偏移、缺块、渲染不全的问题都会变成玄学。

2.1 FeatureCollection 的骨架与常用属性字段

顶层固定三个键:type为FeatureCollection,features是数组,可能还有个crs或者自定义的元信息对象。真正有用的是features里每一项的properties,它决定了你能不能按层级筛选、能不能把数据和图形对上号。

常见的属性字段有这么几类:adcode是 6 位行政区划代码,是做关联的主键;name是单元名称;level标记层级,取值通常是 province / city / district 这类;parent指向上级代码,用来做多级联动;center或centroid是中心点坐标,打标签、放气泡用得上。不同来源的文件字段名会打架,有的叫adcode,有的叫code,有的把层级塞在properties.level里,有的干脆只给一个name。

第一步该做的是字段盘点,而不是直接渲染。我一般会写个小脚本,把所有 feature 的属性键求并集,看看哪个字段是全覆盖的、哪个字段有大面积缺失。一个只在省级 feature 上存在的字段,拿去给地级单元做筛选,结果必然是空图——这类问题在排查阶段看着莫名其妙,其实在盘点阶段一眼就能看出来。

还要注意adcode的类型。如果它是数字类型,前导的 0 会被吃掉,6 位代码变成 5 位,后续和业务表 join 直接对不上。稳妥的做法是统一按字符串处理,读进来就str()转一遍,别心存侥幸。

2.2 Polygon 与 MultiPolygon:外环、内环和飞地

几何部分是嵌套数组,层级差一层结果就完全不对。Polygon的结构是[ring][point][x, y]:最外层是环的列表,第一个环是外边界,后面的环都是内边界(也就是洞,比如某个市域里包着一块别的辖区)。MultiPolygon再多一层,是[polygon][ring][point][x, y],一个行政单元由多个互不相连的块组成。

为什么必须区分?因为沿海地区、有岛屿或者有飞地的单元,绝大多数是 MultiPolygon。如果你的代码只取coordinates[0],拿到的就是第一个多边形块,剩下的岛礁、飞地全部丢失。表现出来就是地图上某个区域“缺了一块”,而你翻遍数据也找不到缺失的那部分,因为它根本没被取出来。

坐标顺序也要盯死。GeoJSON 规范要求[经度, 纬度],但总有人按[纬度, 经度]写。判断方法很简单:经度范围大致在 -180 到 180,纬度在 -90 到 90,如果某个点的第一个值超过了 90,那基本就是写反了。渲染出来的图会整体跑到赤道附近或者干脆空白,这时候先怀疑顺序,别怀疑图表库。

环的闭合性同样是硬要求。合法的环至少 4 个点,且首尾坐标必须相同(闭合)。不闭合的环在部分渲染器里会被自动补上,在另一些引擎里直接判为非法几何被跳过。做数据体检时,把不闭合环的数量统计出来,是判断这份文件质量最直接的一个指标。

2.3 写个体检脚本,三分钟摸清文件底细

下面这段脚本不依赖任何 GIS 库,纯标准库把文件的关键指标全打出来,适合拿到新文件先跑一遍。

import json from collections import Counter with open("province_boundary.json", encoding="utf-8") as f: gj = json.load(f) assert gj.get("type") == "FeatureCollection", "顶层不是 FeatureCollection" print("要素总数:", len(gj["features"])) levels = Counter() geom_types = Counter() key_cover = Counter() bad_ring = 0 total_points = 0 for feat in gj["features"]: props = feat.get("properties") or {} for k in props: # 统计属性字段的覆盖情况 key_cover[k] += 1 levels[props.get("level")] += 1 geom = feat.get("geometry") if not geom: continue # 几何为空,通常是占位要素 geom_types[geom["type"]] += 1 # 统一成 MultiPolygon 的三层结构,省得后面到处判断 polys = geom["coordinates"] if geom["type"] == "MultiPolygon" else [geom["coordinates"]] for poly in polys: for ring in poly: total_points += len(ring) # 合法的环:至少 4 个点,且首尾坐标一致 if len(ring) < 4 or ring[0] != ring[-1]: bad_ring += 1 print("层级分布:", dict(levels)) print("几何类型:", dict(geom_types)) print("字段覆盖:", dict(key_cover)) print("不闭合环数:", bad_ring) print("坐标点总数:", total_points)

这段的逻辑是“先归一化再统计”:不管原始是 Polygon 还是 MultiPolygon,统一转成 MultiPolygon 的三层结构,后面所有统计都在同一套结构上跑,避免写两套分支。bad_ring统计的是不合法环,total_points是判断文件体积和渲染压力的直接依据——一个省级边界文件几十万个坐标点是常态,上百万点就要考虑抽稀了。

参数上没什么可调的,但要注意编码。如果文件是 GBK 存的,encoding="utf-8"会直接抛异常,这时候换成gbk再试;如果读出来中文是乱码但程序不报错,说明文件里混了转义写法,用json.load之后再输出一次ensure_ascii=False就能验证。

提示:体检脚本跑完,先看geom_types里有没有GeometryCollection。这类混合几何在部分轻量渲染器里支持很差,要么提前拆开,要么换渲染方案。

3. 坐标系对齐:省市边界偏移几百米是怎么来的

边界文件最常见的翻车现场,是图能出来、形状也对,但整体错位几百米,和底图上的路网、水系对不上。这不是数据画错了,而是坐标系不匹配。省市边界数据的坐标系通常有三种:WGS84 是原始 GPS 坐标,也是 GeoJSON 规范的默认;GCJ02 是在 WGS84 基础上做非线性偏移后的加密坐标;BD09 则是在 GCJ02 上再偏一次。三套坐标之间不做转换直接混用,偏移量在城市尺度上可以达到几百米,视觉上就是“明明该在河这边,画到了河那边”。

3.1 三套坐标系的差别与选型判断

判断一份文件用的哪套坐标,最粗暴也最有效的办法是抽样看地标。挑几个特征明显的位置——比如某段海岸线的拐角、某条大河的入海口、某个半岛的最南端——把它和已知底图比对。偏移是系统性的话,说明坐标系不匹配;偏移方向随位置变化,说明是投影问题而不是坐标系问题。

选型的逻辑其实取决于“最终渲染在哪”:如果底图用的是 WGS84 的影像或瓦片,边界也用 WGS84,最省事;如果底图是国内的加密坐标系底图,边界就得跟着转过去,否则永远对不齐。我一般倾向于把所有数据在服务端统一存成 WGS84,只在渲染前一步做转换,这样原始数据的语义不会丢,换底图的时候只改一个转换开关。

还有一种情况是数据本身标了坐标系但标的错的。文件里写着 WGS84,实际是加密坐标,这种情况只能靠比对发现,没有自动检测的银弹。所以拿到文件先做一次肉眼比对,是省事后患的必要步骤。

3.2 批量纠偏脚本与精度损失点

WGS84 转 GCJ02 的算法是公开的,核心是两个非线性扰动函数,配合椭球参数做偏移量换算。下面这段可以直接抄,先把单个点转好,再用递归把整个坐标树走一遍。

import math import json A = 6378245.0 # 克拉索夫斯基椭球长半轴 EE = 0.00669342162296594323 # 第一偏心率平方 def _dlat(x, y): ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * math.sqrt(abs(x)) ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(y * math.pi) + 40.0 * math.sin(y / 3.0 * math.pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(y / 12.0 * math.pi) + 320 * math.sin(y * math.pi / 30.0)) * 2.0 / 3.0 return ret def _dlon(x, y): ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * math.sqrt(abs(x)) ret += (20.0 * math.sin(6.0 * x * math.pi) + 20.0 * math.sin(2.0 * x * math.pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(x * math.pi) + 40.0 * math.sin(x / 3.0 * math.pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(x / 12.0 * math.pi) + 300.0 * math.sin(x / 30.0 * math.pi)) * 2.0 / 3.0 return ret def wgs84_to_gcj02(lon, lat): dlat = _dlat(lon - 105.0, lat - 35.0) dlon = _dlon(lon - 105.0, lat - 35.0) rad_lat = lat / 180.0 * math.pi magic = math.sin(rad_lat) magic = 1 - EE * magic * magic sqrt_magic = math.sqrt(magic) dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrt_magic) * math.pi) dlon = (dlon * 180.0) / (A / sqrt_magic * math.cos(rad_lat) * math.pi) return lon + dlon, lat + dlat def map_coords(coords, fn): # 递归到底层 [lon, lat] 数组后调用转换函数,其余层级原样展开 if isinstance(coords[0], (int, float)): return list(fn(coords[0], coords[1])) return [map_coords(c, fn) for c in coords] with open("province_boundary.json", encoding="utf-8") as f: gj = json.load(f) for feat in gj["features"]: geom = feat.get("geometry") if geom: geom["coordinates"] = map_coords(geom["coordinates"], wgs84_to_gcj02) with open("province_boundary_gcj02.json", "w", encoding="utf-8") as f: json.dump(gj, f, ensure_ascii=False) # 不转义中文,体积更小

这段的关键在map_coords:它不关心上层是 Polygon 还是 MultiPolygon,只判断“当前层是不是数字数组”,是就调用转换,不是就继续递归。这样两个几何类型共用一套代码,不会漏掉 MultiPolygon 里第二、第三个多边形块——这是手写循环最容易漏的地方。

参数上,A和EE是固定椭球参数,别乱改;lon - 105.0和lat - 35.0是扰动函数的输入偏移基准,也是固定的。真正要注意的是转换不可逆的精度损失:GCJ02 转回 WGS84 通常用迭代逼近,往返一次误差在米级。所以别在转换后的数据上反复来回转,转一次就定稿。

反向转换(GCJ02 → WGS84)可以用迭代法:先假设目标点等于源点,正算一次得到偏移量,减去偏移量后再正算,迭代两到三次即可收敛到亚米级。写的时候把迭代次数作为参数暴露出来,默认 3 次,够用。

3.3 转换对不对,用这三招验证

转完之后别急着上线,做三步验证。第一招,取一个已知坐标的参照点(比如某个明确的路口或建筑角点),转换前后的偏移量应该在合理范围内,且方向符合该区域的整体偏移趋势;如果偏移量是零,说明转换根本没生效。第二招,转换后的边界和加密坐标系底图叠加,看海岸线、行政界是否贴合,重点看河流拐弯和半岛尖端这种放大误差的位置。第三招,计算转换前后的包围盒差异,正常情况下整体平移量在几百米量级,如果包围盒尺寸明显变了,说明有坐标被算飞了——多半是递归函数在某个层级把嵌套结构当成数字处理了。

还有一种隐蔽的错误:转换时把[lon, lat]传成了[lat, lon]。因为转换函数内部只做数学运算,不会报错,结果就是整张图飞到一个完全不相干的位置。验证时盯着包围盒的经纬度范围,一眼就能发现。

4. 精度与体积的取舍:抽稀、量化与渲染提速

一份省级边界文件动辄几万到几十万个坐标点,地级、区县级叠加起来轻轻松松上百兆。直接塞给浏览器,首屏加载能卡到让人怀疑人生。抽稀和量化是必须做的两道工序,但两道工序都会改变几何形状,做过头就会出现“边界变形成锯齿”“小岛凭空消失”这类血泪经验。

4.1 Douglas-Peucker 抽稀的容差怎么定

抽稀的核心是 Douglas-Peucker 算法:保留对形状贡献大的点,丢掉近似共线的点。容差(tolerance)决定保留程度,单位是坐标系单位——在经纬度下就是“度”。

换算关系必须心里有数:纬度方向 1 度约 111 公里,所以容差 0.001 度约等于 111 米,0.0001 度约 11 米。经度方向要乘cos(lat),高纬度地区的实际距离会短一些。

from shapely.geometry import shape, mapping def simplify_geometry(geom_dict, tol=0.001): g = shape(geom_dict) # preserve_topology=True:抽稀后仍保证不产生自相交、不把面抽成线 s = g.simplify(tol, preserve_topology=True) return mapping(s) if not s.is_empty else None

preserve_topology=True这个参数别省。关掉它,速度能快一倍,但在多部件几何上很容易抽出自相交的无效环,渲染时直接被引擎拒绝。宁可慢一点,也别产出脏几何。

容差怎么定,看用途:全国尺度的大屏展示,0.01 度(约 1 公里)足够,体积能压到原来的十分之一;省市下钻到区县一级,0.001 度更稳;要做面积统计或者空间相交判断,就别抽稀,直接上原始精度。我一般会准备两套文件:一套精简版给前端渲染,一套原始版给后端算数,各司其职。

抽稀有个副作用:相邻两个行政单元的公共边界,如果各自独立抽稀,同一条线会抽出两组不同的点,交界处出现缝隙或重叠。解决办法是先把所有单元合成一个整体做抽稀,再拆回去——但拆回去需要拓扑重建,成本高。更实际的做法是对公共边界统一处理,或者干脆接受微小缝隙(视觉上通常看不出来),只在做严格空间计算时用原始数据。

4.2 坐标量化与文件压缩

量化就是把坐标的小数位截断。6 位小数约 0.11 米,5 位约 1.1 米,4 位约 11 米。对一个只用来展示的边界文件来说,4 到 5 位完全够,体积能减掉三成以上。

def quantize(coords, ndigits=5): if isinstance(coords[0], (int, float)): return [round(coords[0], ndigits), round(coords[1], ndigits)] return [quantize(c, ndigits) for c in coords] for feat in gj["features"]: if feat.get("geometry"): feat["geometry"]["coordinates"] = quantize(feat["geometry"]["coordinates"], 5)

量化的坑在于“舍入后环不再闭合”。首尾两个点如果舍入方向不一致,就会差最后一位,几何变非法。解决方法是量化完成后,强制把每个环的最后一个点赋值为第一个点的副本。这一步别偷懒,很多渲染器不会帮你兜底。

再往下压可以做两件事:一是去掉冗余属性字段,前端用不到的都删掉;二是把 JSON 序列化成紧凑格式(separators=(',', ':')),再开 gzip 或 brotli 传输。文本型 JSON 的压缩率很高,gzip 之后通常只剩原体积的两三成。如果还有优化空间,可以转成 TopoJSON,它把共享边界提取成弧段,相邻区域的公共边只存一份,全国数据能再省一半以上。

4.3 前端加载策略:bbox 预筛与分层渲染

文件压到再小,一次性渲染几万个点仍然会卡。加载策略上做两件事:按视野过滤、按层级分批。

// 粗筛:只保留与当前视野相交的要素,减少传给渲染器的数据量 function filterByViewport(features, bounds) { return features.filter((f) => { const b = featureBBox(f); // 每个要素预先算好的 [minX, minY, maxX, maxY] return !(b[2] < bounds.minX || b[0] > bounds.maxX || b[3] < bounds.minY || b[1] > bounds.maxY); }); }

featureBBox建议在构建阶段就预计算好,写进属性里,别在运行时遍历每个点求极值——那个开销比渲染本身还大。有了包围盒,视野外的要素一次数组过滤就排除了,缩放和平移时只处理相交的那部分。

分层渲染的思路是:初始只加载省级轮廓,用户下钻到某个区域时再异步加载该区域的地级、区县数据。这样首屏永远只渲染几十个要素,交互响应不会随数据量线性劣化。分层加载需要数据结构支持父子关系,adcode和parent字段这时候就派上用场了,提前按层级把文件拆成多个子文件,比运行时从一个大文件里筛要快得多。

5. 避坑与排查:省市边界文件最容易翻车的五处

做过几轮之后会发现,边界文件的问题高度集中在五类,每一类都有明确的现象特征和排查路径。下面按“现象 → 原因 → 解决”的形式记下来,下次遇到直接对号入座。

5.1 整体平移、和底图对不上

现象:图形完整、形状正确,但整体和底图错开一段距离,海岸线、河流的位置系统性偏移,偏移方向在同一区域内一致,跨区域时方向和幅度有变化。

原因:坐标系不匹配。最常见的组合是边界用 WGS84、底图用加密坐标,或者反过来。也可能是转换做了一半——只转了部分要素,或者只转了几何没转中心点坐标。

解决:先确认底图坐标系,再把边界统一转换过去。转换后抽样比对特征地标,确认偏移量级在预期范围。要特别注意属性里的中心点、标签点坐标也要跟着转,只转轮廓不转标签,会出现“面在这、文字在那”的诡异画面。

5.2 相邻单元之间的缝隙与重叠

现象:放大后能看到两个相邻区域之间有一条细白缝,或者反过来,一个区域压住了另一个区域的边缘,颜色叠出深色带。

原因:相邻单元的各条公共边界坐标不完全一致;或者做过抽稀、量化后,同一条线被处理成两组不同的点;还有一种情况是原始数据本身拓扑就不严,各单元独立采集。

解决:做展示的话,给边界描边加一点描边宽度,视觉上能把缝盖住,成本最低。要做严格空间计算,得做拓扑重建:把所有单元合并成一个整体,再重新切割。轻量一点的做法是给每个面做一个极小的缓冲区(buffer)扩张,让相邻面轻微重叠,但这会引入面积误差,只适合纯视觉用途。

5.3 飞地、岛屿和细小面丢失

现象:某个区域看着“缺了一块”,尤其沿海地区和有大江大河的区域;对比原始数据能确认那块确实存在,但渲染出来没有。

原因:代码只取了coordinates[0],把 MultiPolygon 里后面的块丢了;或者抽稀容差过大,面积很小的多边形被压缩成了退化线,被引擎丢弃;还有一种是量化位数太少,小块的所有点被舍入到同一个坐标,直接退化。

解决:几何遍历必须走完整递归,别写死索引。抽稀前先按面积排序,对小面积多边形单独降低容差或者跳过抽稀。量化位数至少保留 5 位,并且量化后重新校验每个环是否退化(面积是否为 0),退化的回退到未量化版本。

5.4 面积算出来离谱

现象:用几何直接算面积,结果和实际平方公里数差了一个数量级,或者不同纬度区域的面积比例明显不对。

原因:直接在经纬度坐标系上算面积。经纬度是球面角度单位,不是平面长度单位,直接套平面多边形面积公式,得到的是“平方度”,没有任何物理意义。而且越往高纬度,同样的经度跨度对应的实际距离越短,误差越大。

解决:投影到等积坐标系再算。常用的做法是投影到 Albers 等积圆锥投影,或者直接用测地线计算库算椭球面上的面积。两者结果会有微小差异,但量级一致。如果只是做相对大小排序,用投影法就够;要报告绝对面积,建议用测地线算法。

5.5 属性字段与编码问题

现象:按代码 join 数据,匹配率只有一半;或者页面上显示的区域名是乱码;或者筛选某一层级时整个结果为空。

原因:adcode是数字类型,前导 0 被吃掉,6 位变 5 位;文件编码是 GBK 但按 UTF-8 读,或者反过来;层级字段的取值不统一,有的写province,有的写省,有的写1。

解决:读入时统一把代码字段转成字符串并补零到 6 位;编码问题先用二进制方式读一次,看有没有 BOM 头,再决定用哪种编码;层级字段做一次值分布统计,把所有取值列出来,建立映射表统一。这三件事都在数据入库前做完,别留到查询时再处理。

6. 进阶玩法:按码裁剪边界 + 点在面内判断

基础用法是把整份文件塞进图表库,但真正的日常需求往往是“只取某一个区域”和“判断这个点属于哪个区域”。这两件事用 shapely 做都很轻,关键是别每次都全量遍历。

6.1 用 adcode 精确裁出单个单元

业务上经常要给某个区域单独出一张局部图,或者把该区域的边界导出来做专题图。直接按属性筛出 feature 就够了,但如果需要“只保留落在某个矩形范围内的部分”(比如只画中心城区),就要做几何裁剪。

from shapely.geometry import shape, box, mapping import json with open("city_boundary.json", encoding="utf-8") as f: gj = json.load(f) def pick_by_code(gj, code, clip_bbox=None): hits = [f for f in gj["features"] if str(f["properties"].get("adcode", "")).zfill(6) == str(code).zfill(6)] if not hits: return None g = shape(hits[0]["geometry"]) if clip_bbox: # clip_bbox = (minx, miny, maxx, maxy) g = g.intersection(box(*clip_bbox)) return mapping(g) if not g.is_empty else None sub = pick_by_code(gj, "000000", clip_bbox=(100.0, 20.0, 110.0, 30.0))

zfill(6)是为了兜住前导零丢失的问题,两侧都补一遍,避免一边是数字一边是字符串导致匹配失败。intersection处理的是“裁剪后可能变成多个碎片”的情况,返回的几何类型可能是 MultiPolygon,调用方别再假设它一定是 Polygon。

6.2 点归属判断与边界容差

判断一个经纬度点落在哪个行政单元里,最直接的做法是遍历所有面调contains,要素一多就慢得没法看。用空间索引把候选集压下来,再逐个精确判断,速度快一个量级。

from shapely.geometry import shape, Point from shapely.strtree import STRtree geoms = [shape(f["geometry"]) for f in gj["features"]] codes = [f["properties"]["adcode"] for f in gj["features"]] tree = STRtree(geoms) # 建一次索引,后续查询复用 def locate(lon, lat, tol=1e-6): p = Point(lon, lat) # 先放大一点点,把落在边界线上的点也纳入候选 idxs = tree.query(p.buffer(tol)) for i in idxs: if geoms[i].covers(p): # covers 包含边界,contains 不包含 return codes[i] return None

这里有个容易忽略的细节:contains对落在边界线上的点返回 False,而实际业务里点位恰好压在行政界线上的情况并不少,用covers更符合“归属判断”的语义。tol参数是给候选集查漏用的,buffer会让查询范围略微外扩,避免因为浮点误差漏掉相邻的候选面。

如果同一坐标命中多个结果(重叠区域或数据误差),别急着取第一个,按层级优先级排序,或者把命中列表全返回,交给业务层决定。我吃过这个亏:早期版本直接返回首个命中,结果一批压在边界上的点位归属随机漂移,排查了半天才发现是数据结构的问题,不是数据本身的问题。从那以后每次做点位归属,我都会强制把命中列表打日志,确认多命中比例在可接受范围再上线。

把边界文件按层级拆好、坐标系统一、体积压到位,剩下的事情就是把上面这几个函数包成服务,按需调用。文件本身不复杂,复杂的是坐标系、拓扑和精度这三件事叠在一起时的相互影响,先把这三块摸清楚,后面无论换什么渲染库都能快速接上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询