23、路径绘制:Polyline与Polygon、路径点连接、实时轨迹绘制优化
路径绘制,说白了就是把一堆经纬度点连成线,或者围成一个面。这功能在导航、运动记录、地理围栏里太常见了。我最早接触这个需求,是做一款户外徒步App,用户走完一条路线,得把轨迹画出来。嗯,当时踩了不少坑,今天咱们好好聊聊。
Polyline 与 Polygon:线 vs 面
先搞清楚这两个东西的区别。Polyline 是折线,用来表示路径;Polygon 是多边形,用来表示区域。代码上它们很像,但语义完全不同。
核心区别:Polyline 首尾不相连,Polygon 首尾自动闭合,且可以填充颜色。
我个人习惯,画轨迹只用 Polyline。Polygon 更多用在圈地、热区、覆盖物上。举个例子,你跑了个5公里,轨迹用 Polyline 画;你要标记一个禁飞区,用 Polygon 画个圈。
// 创建 Polyline
val polylineOptions = PolylineOptions()
.addAll(latLngList)
.width(8f)
.color(Color.BLUE)
.jointType(JointType.ROUND)
.startCap(Cap.ROUND_CAP)
.endCap(Cap.ROUND_CAP)
googleMap.addPolyline(polylineOptions)
// 创建 Polygon
val polygonOptions = PolygonOptions()
.addAll(latLngList)
.strokeWidth(4f)
.strokeColor(Color.RED)
.fillColor(Color.argb(50, 255, 0, 0))
googleMap.addPolygon(polygonOptions)
注意看,Polyline 我加了 jointType 和 Cap。为什么?因为默认的折线拐角是直的,看起来像锯齿。我在项目里第一次画轨迹时,用户反馈说「这线怎么有棱有角的?」——嗯,加上圆角连接和圆头端点,视觉上舒服多了。
路径点连接:别小看这个环节
路径点连接,就是把 GPS 采集到的点串起来。但这里有个坑:GPS 点不是均匀的。有时候信号差,两个点之间隔了100米;有时候信号好,一秒能采10个点。
我曾经遇到一个情况:用户骑车过隧道,出来之后 GPS 恢复,结果轨迹直接画了一条直线穿过建筑物。这显然不对。怎么办?
我的做法:加入距离阈值判断。如果两个相邻点距离超过某个值(比如200米),就认为中间有断点,不直接连线,而是用虚线表示,或者干脆断开。
fun filterAndConnect(points: List<LatLng>): List<LatLng> {
val result = mutableListOf<LatLng>()
val maxGap = 200.0 // 单位:米
for (i in points.indices) {
if (i == 0) {
result.add(points[i])
continue
}
val distance = computeDistance(points[i-1], points[i])
if (distance < maxGap) {
result.add(points[i])
} else {
// 距离过大,插入一个空标记或者分段
// 这里简单处理:跳过这个点
// 实际项目中可以插入一个 null 表示断点
}
}
return result
}
你想想看,如果不做这个过滤,用户看到一条穿墙的线,第一反应就是「这App不准」。所以路径点连接,不只是画线,更是数据清洗。
实时轨迹绘制优化:别让UI卡成PPT
实时轨迹,就是一边定位一边画线。这个场景下,性能是最大的敌人。我记得第一次做实时轨迹时,每收到一个定位点就重新画一次 Polyline,结果手机烫得能煎鸡蛋。
为什么会这样?因为每次 addPolyline 都是新建一个图形对象,地图要重新渲染。点多了之后,卡顿是必然的。
避坑指南:千万不要每来一个点就重新创建 Polyline。应该复用同一个 Polyline 对象,只更新它的点列表。
具体怎么做?看代码:
// 初始化时创建 Polyline
val polylineOptions = PolylineOptions()
.width(8f)
.color(Color.BLUE)
var polyline: Polyline? = null
// 定位回调中更新
fun onLocationUpdate(newPoint: LatLng) {
pointList.add(newPoint)
if (polyline == null) {
polyline = googleMap.addPolyline(polylineOptions)
}
polyline?.points = pointList // 直接更新 points 属性
}
注意,这里用的是 polyline.points = pointList,而不是重新 addPolyline。这样地图只会重绘这条线,不会创建新对象。性能提升非常明显。
另外,还有一个优化点:点太多之后,可以降采样。比如每5个点取一个,或者根据距离采样。我一般控制在200个点以内,超过就做抽稀处理。
个人经验:抽稀算法用 Douglas-Peucker 效果不错。它能保留轨迹的形状,同时大幅减少点数。我项目中用这个算法,把1000个点压缩到150个,肉眼几乎看不出区别。
知识体系总览
下面这张图,帮你理清路径绘制的核心逻辑:
性能对比:不同方案的表现
我做过一个简单的性能测试,数据如下:
| 方案 | 100个点 | 500个点 | 1000个点 | 帧率(1000点) |
|---|---|---|---|---|
| 每次新建 Polyline | 12ms | 58ms | 115ms | 15fps |
| 复用 Polyline 对象 | 3ms | 8ms | 14ms | 55fps |
| 复用 + 抽稀 | 2ms | 4ms | 6ms | 60fps |
看到没?复用对象加上抽稀,性能差距是数量级的。我后来在项目里一直用第三种方案,用户再也没抱怨过卡顿。
总结一下:路径绘制不难,但要做好细节。选对类型(Polyline/Polygon),处理好点连接,优化实时性能。这三步走完,你的轨迹绘制功能就稳了。
公众号:蓝海资料掘金营,微信deep3321