地图数据转换与导航投影深挖
地图数据转换与导航投影深挖
为巽1. 离线转换为什么做成 mapbin
完整广东 GeoJSON 不能在设备上解析。tools/geojson_to_c_array_gui.py 采用两遍流式扫描:第一遍建立边界和网格统计,第二遍写入 SQLite 临时几何,再输出 map.idx/map.dat。最终文件先写 .tmp,完成后替换正式文件,避免断电留下半包地图。
设备侧只解析定长二进制,避免 JSON token、字符串和浮点对象造成的 RAM 峰值。所有字段按 little-endian 逐字段读取,不能直接 fread() 到 C struct,因为编译器 padding 会破坏协议兼容。
2. 坐标与网格
mapbin 使用局部等距圆柱投影:
1 | x_m = radians(lon - min_lon) * 6378137 * cos(radians(projection_lat)) |
默认网格边长 2000 m。grid_x=floor(x_m/cell_size_m),grid_y=floor(y_m/cell_size_m),索引位置为 64 + (grid_y*grid_cols+grid_x)*8。网格内点是 uint16,单位 0.1 m;设备解析后把它加上网格原点,形成 map_store_point_t{x_dm,y_dm}。
3. map.idx / map.dat 校验
map.idx 头 64 字节 magic=OMAP,包含版本、边界、参考纬度、网格尺寸;每项 8 字节 offset/length。map.dat 头 16 字节 magic=ODAT。每个网格块含 32 字节块头、20 字节折线头、16 字节三角形和 4 字节点。
map_store.c 校验文件长度、版本、header size、网格边界、offset+length 是否越界,并验证折线表/三角形表/点表的相对偏移。单个网格损坏时整次加载失败;若已有旧地图,旧快照继续显示,错误只写入 state/error。
4. 3x3 加载与快照交换
map_store_request_wgs84() 只写请求并创建 map_store task。任务挂载 SD、读取索引头,计算中心网格,然后读取中心周围 9 个 cell。所有临时 cell buffer 和最终 polyline/triangle/point 数组都在 PSRAM。
新数据写入局部 loaded context,完成后在临界区把 s_store 一次性替换;旧 context 在退出临界区后释放。这样 UI 不会看到“折线数组已换、点数组尚未换”的中间态。generation 每次成功发布递增。
建筑三角形只在 LOD bit 2 且视野不超过 1500 m 时绘制;道路、水域和绿地按当前 LOD 过滤,减少不可见遍历。
5. GCJ-02 到 WGS84 的实现
路线和 PHONE 数据来自微信/腾讯,固定是 GCJ-02;SD OSM 地图按 WGS84。map_route.c 先调用 route_gcj02_to_wgs84(),内部以 WGS84 候选点正向计算 route_wgs84_to_gcj02(),迭代 2 次用误差修正:
1 | wgs_lat += gcj_lat - test_lat |
中国境外直接返回原坐标。结果重新量化为 1e-7 度整数。转换只在路线准备 task 或一次定位投影接口中执行,绝不在 LVGL draw callback 逐点做。
6. 路线预处理
map_route_prepare() 复制 BLE LOADED 点列,然后为每点分配:WGS 输入、地图世界点、累计距离数组。相邻点距离用世界坐标的欧氏距离,得到 cumulative_m[];同时计算路线包围盒和中心。
V2 带高程时一次性构造 altitude_cm[]、ascent_cm[]、descent_cm[]。高程差为正累加 ascent,为负累加 descent;25 m 窗口内用前后高程差计算 gradient_x100。这些数组位于 PSRAM,导航刷新只做索引和插值。
7. 位置投影算法
map_route_project_world() 遍历每一条路线线段,把当前位置投影到线段参数 t,选择距离平方最小的段。返回:投影点、progress_m、off_route_m、段航向、总距离和下一明显转向。
map_route_project_gcj02() 只是“转换坐标 + world 投影”的组合;map_route_project_progress() 根据累计距离二分/顺序找到段,再对端点做线性插值,供 DEMO 沿路线推进。
下一转向从当前段之后寻找,计算相邻段航向差;页面使用 25° 阈值分类左/直/右。路线回环不会直接把最后一段当终点,PHONE 到达还要求剩余路线、真实距终点和连续定位次数同时满足。
8. 导航页面如何避免抖动
page_map_sd_route.c 复制地图和路线到自己的 PSRAM 缓存。导航状态 timer=500 ms,导航定位刷新=20 ms,但只有运动快照 version 变化才重算。航向使用 28 m look-ahead、死区和最大步进限制,再用 0.10 平滑因子更新 sin/cos,避免手机航向瞬跳造成整图旋转抖动。
屏幕范围固定 466x466,地图视野预览最小 600 m、导航默认 430 m,当前位置锚在 y=360。地图绘制层单独 invalidate,拖动累计 4 px 才提交平移,不做整页刷新。
9. 面试追问与回答
为什么不用 Web Mercator? 当前数据量和显示范围是广东局部,局部等距投影在 2 km 网格内足够,公式简单、设备端无需瓦片金字塔。若扩展全球地图,应更换投影并升级 mapbin 版本。
为什么底图只加载 3x3? 单 cell 上限 1 MiB,3x3 能覆盖当前视野和拖动缓冲;沿整条长路线一次性加载会造成无上限 SD IO 和 PSRAM 占用。跨网格后异步换中心。
地图加载失败会不会黑屏? 不会。无旧图时发布 ERROR,页面显示错误并可返回;有旧图时保留旧几何,只更新 error/generation。
1 |



