地图服务是位置类应用的核心基础设施。本文详解从地图瓦片加载、坐标系转换、渲染管线到定位与路径规划的完整链路,并梳理核心数据结构与源码模块位置。

flowchart LR
 A[地图初始化
MapComponent] --> B[瓦片加载
多级缩放金字塔]
    B --> C[坐标转换
经纬度 屏幕像素]
 C --> D[渲染管线
图层叠加绘制]
    D --> E[交互与业务
定位/POI/导航]
    B -.瓦片缓存.-> E2[(三级缓存体系)]
 E -.定位数据.-> F[(GNSS/WiFi/基站)] 

一、概述

地图服务(Map Kit)为应用提供地图显示、定位、搜索、导航等能力。它的目标很简单:把真实的地理世界映射到一块屏幕上,并支持缩放、平移、旋转等交互。你可以把地图引擎看成”地理数据的渲染器 + 位置服务的调度器”。

整个流程围绕一个核心组件展开——地图组件(MapComponent。应用通过它加载地图,引擎在后台完成瓦片调度、坐标变换、图层合成,最终呈现一个可交互的地图世界。

二、整体阶段划分

地图服务分为 5 个阶段,按顺序执行:

阶段

名称

作用

1

地图初始化(Init

创建地图组件,配置参数

2

瓦片加载(TileLoad

按视野范围请求地图瓦片

3

坐标转换(Projection

经纬度与屏幕坐标互转

4

图层渲染(Render

多图层叠加绘制

5

交互与业务(Interaction

定位、POI 搜索、路径规划

下面逐个展开。

三、地图初始化阶段(Init

应用通过 MapComponent 创建地图实例:

import { MapComponent, mapCommon, map } from '@kit.MapKit';

@Entry
@Component
struct MapPage {
  private mapOption: mapCommon.MapOptions = {
    position: {
      target: { latitude: 39.9042, longitude: 116.4074 },  // 北京
      zoom: 12
    }
  };
  private controller?: map.MapController;

  build() {
    MapComponent({ mapOptions: this.mapOption })
      .onMapLoaded((controller) => {
        this.controller = controller;
      })
      .width('100%')
      .height('100%')
  }
}

初始化项

说明

position

初始中心点经纬度与缩放级别

mapType

地图类型(标准/卫星/深色)

controls

控件配置(指南针、比例尺、缩放按钮)

gesture

手势开关(旋转、倾斜、缩放)

controller

地图控制器,后续交互通过它发起

3.1 地图类型

类型

说明

STANDARD

标准地图(道路、建筑、POI 标注)

SATELLITE

卫星影像图

DARK

深色模式地图

TERRAIN

地形图(等高线、山体阴影)

3.2 核心数据结构

// 经纬度坐标
interface LatLng {
  latitude: number;    // 纬度 [-90, 90]
  longitude: number;   // 经度 [-180, 180]
}

// 视野范围
interface CameraPosition {
  target: LatLng;      // 中心点
  zoom: number;        // 缩放级别 [3, 20]
  tilt: number;        // 倾斜角度 [0, 90]
  bearing: number;     // 旋转角度 [0, 360]
}

// 可视区域
interface VisibleRegion {
  nearLeft: LatLng;    // 左下角
  nearRight: LatLng;   // 右下角
  farLeft: LatLng;     // 左上角
  farRight: LatLng;    // 右上角
  bounds: LatLngBounds; // 包围盒
}

四、瓦片加载阶段(TileLoad

地图数据以”瓦片金字塔”形式组织,按需加载:

4.1 瓦片金字塔模型

Level 0:  1 张瓦片 (全世界)
Level 1:  2×2 = 4 张瓦片
Level 2:  4×4 = 16 张瓦片
Level 3:  8×8 = 64 张瓦片
  ...
Level n:  2^n × 2^n 张瓦片
  ...
Level 18: 262144 × 262144 张瓦片(街道级细节)

缩放级别

瓦片数

对应实景尺度

3

8×8

大洲级(全国概览)

8

256×256

城市群级

12

4096×4096

城市级(街道可见)

16

65536×65536

街区级(建筑轮廓)

18

262144×262144

建筑级(门牌细节)

  • 每张瓦片固定 256 × 256 像素PNG/WebP 格式
  • 瓦片 URL 遵循 /{z}/{x}/{y} 规范,z 为层级、x/y 为行列号
  • 屏幕通常显示 12~20 张瓦片(含预加载边缘)

4.2 瓦片加载流程

sequenceDiagram
 participant V as 视野变化
 participant T as TileScheduler
 participant C as 三级缓存
 participant N as 瓦片服务器

 V->>T: 1. 相机移动 (新视野范围)
 T->>T: 2. 计算可视瓦片 x/y/z 列表
 T->>C: 3. 逐张查找缓存
 C-->>T: 4. 内存命中 直接返回
 T->>C: 5. 未命中 查磁盘缓存
 C-->>T: 6. 磁盘命中 解码进内存
 T->>N: 7. 未命中 网络请求瓦片
 N-->>T: 8. 返回瓦片数据
 T->>C: 9. 写入内存 + 磁盘缓存
 T->>T: 10. 通知渲染层重绘

4.3 三级缓存体系

缓存层级

容量

命中耗时

说明

内存缓存

 200

<1ms

LRU 淘汰,直接存解码后位图

磁盘缓存

 500MB

5~20ms

按瓦片文件存储

网络

无限

50~500ms

按优先级并发请求,超时降级

4.4 瓦片调度策略

策略

说明

视野优先

当前可视瓦片优先级最高

预加载边缘

屏幕外 1~2 圈瓦片预加载,滑动更流畅

层级预取

相邻缩放级别的瓦片预取,缩放不白屏

并发限制

同时最多 6~8 个网络请求

请求合并

相同瓦片的多次请求合并为一个

失败重试

超时重试 2 次,仍失败显示占位灰块

五、坐标转换阶段(Projection

地图引擎的核心数学问题:球面经纬度 平面像素坐标的互转。

5.1 Web 墨卡托投影

主流地图采用 Web 墨卡托(EPSG:3857)投影,把球面”摊平”成正方形:

经纬度 像素坐标:
pixelX = (longitude + 180) / 360 × worldWidth
pixelY = (1 - ln(tan(lat) + sec(lat)) / π) / 2 × worldHeight

其中 worldWidth = worldHeight = 256 × 2^zoom

// 经纬度转瓦片坐标
function lngLatToTile(lng: number, lat: number, zoom: number) {
  let n = Math.pow(2, zoom);
  let x = Math.floor((lng + 180) / 360 * n);
  let latRad = lat * Math.PI / 180;
  let y = Math.floor((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n);
  return { x, y };
}

// 瓦片坐标转经纬度
function tileToLngLat(x: number, y: number, zoom: number) {
  let n = Math.pow(2, zoom);
  let lng = x / n * 360 - 180;
  let latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n)));
  let lat = latRad * 180 / Math.PI;
  return { lat, lng };
}

5.2 三层坐标体系

坐标体系

说明

用途

经纬度(WGS84/GCJ02

球面坐标,单位度

数据存储、接口输入输出

世界像素坐标

投影后平面坐标,单位像素

视野计算、瓦片定位

屏幕坐标

相对屏幕的坐标,单位像素

触摸交互、覆盖物绘制

5.3 坐标系注意事项

坐标系

说明

WGS84

国际标准(GPS 原始坐标)

GCJ02

国测局加密坐标(中国境内地图强制)

BD09

百度坐标系

在中国境内,地图展示必须使用 GCJ02 坐标系,WGS84 直接叠加会产生数百米偏移。地图服务在内部完成 WGS84 → GCJ02 的纠偏转换。

六、图层渲染阶段(Render

地图采用多图层叠加的渲染架构:

flowchart TB
 A[渲染管线] --> B[底图层
瓦片网格]
    B --> C[卫星图层
影像瓦片]
 A --> D[路况图层
动态路况线]
    A --> E[覆盖物层
Marker/Polyline/Polygon]
 A --> F[UI 控件层
指南针/比例尺]
    B --> G[合成输出
GPU 纹理混合]
 C --> G
    D --> G
 E --> G
    F --> G

6.1 图层体系

图层

内容

更新时机

底图层

瓦片网格

瓦片加载完成

卫星层

影像瓦片

瓦片加载完成

路况层

实时路况(红黄绿)

 1~5 分钟

覆盖物层

Marker、折线、多边形、圆

业务操作

UI 控件层

指南针、比例尺、Logo

视野变化

6.2 覆盖物类型

// 添加标记点
let marker = this.controller.addMarker({
  position: { latitude: 39.9042, longitude: 116.4074 },
  title: "天安门",
  snippet: "北京市东城区",
  icon: markerIcon,
  clickable: true
});

// 添加折线(路径)
let polyline = this.controller.addPolyline({
  points: [
    { latitude: 39.90, longitude: 116.40 },
    { latitude: 39.95, longitude: 116.45 }
  ],
  width: 8,
  color: 0xFF4A90D9,
  dottedLine: false
});

// 添加多边形(区域)
let polygon = this.controller.addPolygon({
  points: [...],
  fillColor: 0x334A90D9,
  strokeColor: 0xFF4A90D9,
  strokeWidth: 2
});

覆盖物

说明

典型用途

Marker

单点标记,支持图标+信息窗

POI 标注、我的位置

Polyline

折线,支持宽度/颜色/虚线

路径规划结果

Polygon

多边形,支持填充与描边

电子围栏、区域高亮

Circle

圆形区域

周边搜索范围

Ground

图片覆盖层

活动区域示意图

6.3 渲染性能优化

优化策略

说明

视野剔除

只渲染可视范围内的覆盖物

聚合(Cluster

密集 Marker 自动聚合为一个带数字的气泡

位图复用

相同图标复用纹理,减少显存

增量绘制

视野平移时只绘制新增瓦片

GPU 合成

图层用 GPU 纹理混合,CPU 只做数据准备

离屏缓存

静态图层缓存为位图,避免重复绘制

七、交互与业务阶段(Interaction

7.1 地图手势交互

手势

行为

对应 Camera 变化

单指拖动

平移地图

target 变化

双指捏合

缩放地图

zoom ±1

双指旋转

旋转地图

bearing 0~360°

双指上下滑

倾斜地图

tilt 0~90°

双击

放大一级

zoom +1

双指双击

缩小一级

zoom -1

7.2 定位能力

定位服务融合多源数据提供位置:

flowchart LR
 A[GNSS 卫星定位
GPS/北斗/GLONASS] --> D[融合定位引擎]
    B[WiFi 定位
热点指纹库] --> D
 C[基站定位
Cell ID] --> D
    D --> E[输出最优位置]
 E --> F[地图蓝点展示]

定位方式

精度

耗时

耗电

适用场景

GNSS

3~10

5~30 秒(首次)

户外导航

WiFi

20~50

1~2

室内城区

基站

100~2000

<1

极低

粗略定位

// 获取当前位置并移动相机
let location = await geoLocationManager.getCurrentLocation({
  priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
  scenario: geoLocationManager.LocationRequestScenario.NAVIGATION
});
this.controller.animateCamera({
  target: {
    latitude: location.latitude,
    longitude: location.longitude
  },
  zoom: 16
}, 500);  // 500ms 动画

7.3 POI 搜索

用户输入"咖啡"
 
 
输入框联想(拼音/纠错/历史)
 
 
构造搜索请求(关键词 + 中心点 + 半径/城市)
 
 
服务端检索(名称/分类/地址匹配)
 
 
排序(距离/权重/热度)
 
 
返回 POI 列表(名称/地址/坐标/评分)
 
 
地图打点展示 + 点击查看详情

搜索类型

说明

关键词搜索

按名称/分类模糊检索

周边搜索

以某点为圆心,指定半径内检索

分类搜索

按类目(餐饮/酒店/加油站)检索

地点详情

POI 的营业时间、评分、电话等

7.4 路径规划与导航

sequenceDiagram
 participant U as 用户
 participant M as 地图引擎
 participant S as 路线服务

 U->>M: 1. 选择起终点
 M->>S: 2. 请求路线(origin, dest, mode)
 S->>S: 3. 路网检索 + 代价计算
 S-->>M: 4. 返回多条候选路线
 M->>M: 5. 绘制路线 Polyline + 标注
 U->>M: 6. 选择路线, 开始导航
 M->>S: 7. 持续请求引导指令(tbt)
 S-->>M: 8. 前方200米右转...
 M->>U: 9. 语音播报 + 界面高亮

规划模式

说明

驾车

支持躲避拥堵/收费/高速偏好

步行

人行道、过街天桥路径

骑行

非机动车道路径

公交

换乘方案(地铁+公交)

导航的核心是 TBTTurn-By-Turn)引导:引擎根据实时定位与路线的匹配结果,在恰当距离(500m/200m/50m)触发转向指令,配合语音播报。

八、地图的生命周期

应用启动
 
 
创建 MapComponent → 初始化渲染引擎
 
 
首次瓦片加载 首屏地图呈现
 
 
用户交互(缩放/平移/旋转) 增量瓦片调度
 
 
业务操作(定位/搜索/导航)
 
 
页面隐藏 暂停渲染, 释放部分资源
 
 
页面恢复 恢复渲染
 
 
页面销毁 清理内存缓存, 磁盘缓存保留

特性

说明

离线地图

支持下载城市级离线包,无网可用

缓存复用

磁盘缓存跨会话保留,二次进入秒开

资源分级释放

隐藏时降帧,销毁时才全量释放

引擎单例

多页面共享同一渲染引擎实例

九、关键代码模块位置

模块层级(OpenHarmony / HMS 生态路径):

模块

路径

说明

地图组件

@kit.MapKit (MapComponent)

地图 UI 组件入口

地图控制

@kit.MapKit (map.MapController)

相机、手势、覆盖物控制

定位服务

@kit.LocationKit (geoLocationManager)

定位能力

地理编码

@kit.LocationKit (geoCodingManager)

地址 坐标转换

瓦片引擎

SDK 内部(渲染闭源模块)

瓦片调度与 GPU 合成

瓦片缓存

应用沙箱 cache/map_tile/

磁盘瓦片缓存目录

覆盖物管理

@kit.MapKit (Marker/Polyline/Polygon)

覆盖物对象模型

权限声明

module.json5 (ohos.permission.LOCATION)

定位权限

9.1 权限配置

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": { "abilities": ["MapAbility"], "when": "inuse" }
      },
      {
        "name": "ohos.permission.LOCATION_IN_BACKGROUND",
        "reason": "$string:bg_location_reason",
        "usedScene": { "abilities": ["MapAbility"], "when": "always" }
      }
    ]
  }
}

十、一张图总结

flowchart TB
 A[MapComponent 初始化] --> B[相机参数
target/zoom/tilt/bearing]
    B --> C[瓦片调度器
计算可视 x/y/z]
 C --> D{三级缓存}
    D -->|内存| E[直接返回位图]
 D -->|磁盘| F[解码进内存]
    D -->|网络| G[请求瓦片服务器]
 G --> H[写入双级缓存]
    E --> I[图层渲染管线]
 F --> I
    H --> I
 I --> J[底图+卫星+路况+覆盖物 GPU合成]
    J --> K[用户手势交互]
 K --> L[定位/POI搜索/路径规划]
    C -.LRU淘汰.-> M[(缓存上限)]
 L -.TBT引导.-> N[(导航服务)]

十一、延伸话题

如果你感兴趣,可以进一步聊这几个方向:

  • 瓦片预取算法 —— 基于用户滑动速度和方向的预测性预取,如何进一步消灭白块
  • 矢量地图 vs 栅格地图 —— 矢量瓦片(如 Mapbox Vector Tile)如何实现无损缩放与客户端样式定制
  • 离线导航包设计 —— 路网数据的分层裁剪与增量更新,如何在数百 MB 内装下一座城市的完整导航数据
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐