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 我加了 jointTypeCap。为什么?因为默认的折线拐角是直的,看起来像锯齿。我在项目里第一次画轨迹时,用户反馈说「这线怎么有棱有角的?」——嗯,加上圆角连接和圆头端点,视觉上舒服多了。

路径点连接:别小看这个环节

路径点连接,就是把 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个,肉眼几乎看不出区别。

知识体系总览

下面这张图,帮你理清路径绘制的核心逻辑:

路径绘制核心知识体系 Polyline vs Polygon 路径点连接 实时轨迹优化 折线:轨迹、路线 多边形:区域、围栏 样式:颜色、宽度、圆角 距离阈值过滤 断点处理:虚线/分段 数据清洗:去噪、平滑 复用 Polyline 对象 点降采样 / 抽稀 Douglas-Peucker 算法 核心目标:流畅、准确、省电

性能对比:不同方案的表现

我做过一个简单的性能测试,数据如下:

方案 100个点 500个点 1000个点 帧率(1000点)
每次新建 Polyline 12ms 58ms 115ms 15fps
复用 Polyline 对象 3ms 8ms 14ms 55fps
复用 + 抽稀 2ms 4ms 6ms 60fps

看到没?复用对象加上抽稀,性能差距是数量级的。我后来在项目里一直用第三种方案,用户再也没抱怨过卡顿。

总结一下:路径绘制不难,但要做好细节。选对类型(Polyline/Polygon),处理好点连接,优化实时性能。这三步走完,你的轨迹绘制功能就稳了。

公众号:蓝海资料掘金营,微信deep3321