从割草机项目聊地图业务中的边界处理:Android 开发必须考虑的 9 个问题

引言

做地图业务时,很多人一开始会觉得很简单:

地图 = Marker + Polyline + Polygon

Marker 表示设备位置,Polyline 表示运动轨迹,Polygon 表示工作区域。

但真正落到业务里,尤其是割草机、机器人、无人车、巡检设备这类场景时,会发现地图业务远不只是"画个点、画条线"。

比如割草机场景中,用户在 App 里画出一块草坪区域,设备按照规划路径自动割草。看起来只是地图展示问题,但实际会涉及:

复制代码
定位
轨迹
边界
禁区
越界
漂移
断点
补发
同步
任务恢复

这些问题如果处理不好,就会出现:

  • 地图上设备位置突然跳变
  • 轨迹线穿过禁区
  • 设备明明没出界却提示越界
  • 断网后轨迹丢失
  • 边界修改了但设备没生效
  • 坐标系不一致导致整体偏移

所以,地图业务里的边界处理,并不是一个简单的 UI 问题,而是一整套工程设计问题。

本文结合割草机场景,总结地图业务中常见的 9 类边界处理问题。


一、工作区域边界

最基础的边界,就是用户在 App 中绘制的工作区域。

比如割草机场景中,用户需要在地图上画出草坪范围:

复制代码
Work Area

设备只能在这个区域内作业。

Android 侧通常需要负责:

复制代码
1. 绘制工作区域 Polygon
2. 展示区域边界
3. 显示设备当前位置
4. 展示设备是否接近边界
5. 展示设备是否疑似越界

最简单的判断是:

复制代码
当前位置是否在多边形内

但实际项目中不能只靠一个点来判断。

因为 GPS / RTK 都可能存在误差。如果某一个定位点瞬间飘到边界外,不能马上认定设备越界。

更合理的判断应该结合:

复制代码
1. 是否连续多个点越界
2. 越界距离是多少
3. 持续时间多久
4. 当前定位精度如何
5. RTK 状态是否正常

例如:

复制代码
单点轻微越界:可能是定位漂移
连续越界:进入告警状态
持续越界且距离较大:判定为越界异常

从 Android 侧看,重点不是控制设备怎么转向,而是把边界状态清晰展示出来:

复制代码
正常
接近边界
疑似越界
已越界
设备已暂停

这样用户才能理解当前设备到底发生了什么。


二、禁区边界

割草机并不是只需要一个外部工作区域。

实际草坪中可能会有很多不能进入的区域,例如:

复制代码
花坛
水池
树木
儿童活动区
斜坡危险区
宠物活动区

这些区域可以理解为:

复制代码
Forbidden Area
No-Go Zone

也就是禁区。

在 App 中,禁区通常也是一个或多个 Polygon。

Android 侧需要处理:

复制代码
1. 禁区绘制
2. 禁区编辑
3. 禁区删除
4. 禁区与工作区关系校验
5. 禁区同步给设备
6. 设备靠近禁区时的告警展示

这里有一个很重要的点:

禁区必须在工作区域内部。

否则会出现逻辑混乱。

例如用户画了一个禁区,但这个禁区部分超出了工作区域。这时候 App 应该在提交前校验,而不是把错误数据直接丢给设备端。

常见校验包括:

复制代码
禁区是否完全位于工作区内
禁区是否和其他禁区重叠
禁区面积是否过小
禁区边界是否自相交

从业务上看,工作区决定"哪里可以作业",禁区决定"工作区内哪里不能作业"。

这两个边界要一起考虑。


三、拐角轨迹断点处理

这是地图业务里很容易被忽略的问题。

假设设备真实运动轨迹是:

复制代码
A -> B -> C

其中 B 是一个拐角点。

但是由于网络信号差,B 点没有及时上报给 App。App 只收到:

复制代码
A -> C

如果 App 直接把 A 和 C 连成一条实线:

复制代码
A -------- C

看起来就像设备从 A 直线走到了 C。

但真实情况可能是:

复制代码
A -> B -> C

如果 A 和 C 之间直接连线,可能会出现几个问题:

复制代码
1. 轨迹线穿过禁区
2. 轨迹线穿过边界外
3. 轨迹线穿过障碍物
4. 用户误以为设备真的这样走了

所以,轨迹绘制不能只按"收到的点"简单连线。

每个轨迹点最好带上:

复制代码
taskId
seq
timestamp
lat
lng

其中:

复制代码
seq:表示轨迹顺序
timestamp:表示设备采集时间

如果发现两个点之间的 seq 不连续,或者时间间隔过大,就说明中间可能存在轨迹断点。

这时候 App 不应该直接画实线,而应该:

复制代码
1. 保留断点
2. 使用虚线表示未知轨迹
3. 等待设备补发中间点
4. 补发后重新合并轨迹

也就是说:

地图展示层不能把"没收到点"误画成"设备真的这样走了"。

这一点在边界和禁区附近尤其重要。


四、定位漂移边界

RTK 和 GPS 都不是绝对稳定的。

即使设备没有真正出界,定位点也可能瞬间飘到边界外。

典型情况:

复制代码
设备实际仍在工作区内
地图上的点突然跳到边界外
下一秒又回来了

如果 App 看到一个点越界就直接提示:

复制代码
设备越界

用户体验会非常差。

更严重的是,如果设备端也这样处理,可能导致误停机、误告警。

所以边界判断要考虑定位漂移。

比较合理的策略是:

复制代码
单点越界:标记为疑似漂移
连续越界:进入预警状态
持续越界:判定为越界

同时要结合定位状态:

复制代码
RTK FIX:可信度高
RTK FLOAT:可信度下降
RTK LOST:不适合做强判断
GPS 精度差:降低点位可信度

Android 侧可以做展示兜底:

复制代码
1. 异常点不直接加入真实轨迹
2. Marker 保留在最后有效位置
3. UI 提示"定位信号异常"
4. 等待后续连续点确认

这就避免了一个漂移点污染整条轨迹。


五、贴边割草处理

割草机有一个特殊场景:它需要尽可能贴边割草。

如果离边界太远,边缘草坪就会漏割。

但是如果太靠近边界,又可能有安全风险。

所以不能简单判断:

复制代码
设备中心点接近边界 = 危险

还要考虑:

复制代码
设备宽度
刀盘半径
定位误差
安全距离
贴边补偿距离

例如,设备中心点距离边界 30cm,可能是正常贴边;但如果设备中心点已经越出边界 50cm,那就可能是异常。

实际设计中可以引入边界缓冲区:

复制代码
安全区
预警区
危险区

比如:

复制代码
距离边界 > 50cm:正常
距离边界 20cm ~ 50cm:接近边界
距离边界 < 20cm:贴边作业
越出边界一定距离:疑似越界
持续越界:异常

当然,真正的控制策略一般在设备端完成。

Android 侧主要负责:

复制代码
1. 展示边界距离状态
2. 显示接近边界提醒
3. 显示设备越界告警
4. 给用户提供暂停、回充、重新规划等操作

从 App 角度,重点是让用户看懂:

复制代码
设备是在正常贴边,还是已经异常越界。

六、已割区域与补割区域

割草机不是普通的定位设备,它是覆盖型作业设备。

它关心的不只是"走到哪里",还关心:

复制代码
哪里已经割了
哪里还没割
哪里需要补割
哪里重复割了

这就涉及任务边界。

尤其在边界附近,最容易出现:

复制代码
漏割
重复割
轨迹断点
补割区域

例如设备在边界附近断网,App 没有收到中间轨迹点。

这时候 App 地图上可能看起来少了一段轨迹,但设备实际已经割过了。

所以任务系统不能只依赖 App 实时收到的轨迹,而应该有设备端或服务端的任务进度记录。

更完整的设计是:

复制代码
设备端记录实际作业轨迹
网络异常时本地缓存
网络恢复后补发轨迹
服务端合并轨迹
App 刷新已割区域

对于 Android 侧来说,需要展示:

复制代码
已割区域
未割区域
补割区域
任务完成度
异常中断区域

任务恢复后,也不能只是继续沿原轨迹走,而应该判断:

复制代码
是否有漏割区域
是否需要补割
是否需要重新规划局部路径

这也是割草机地图业务和普通地图轨迹展示最大的区别之一。


七、用户绘制边界合法性

很多边界问题,不是设备造成的,而是用户在 App 上画边界时就已经埋下了问题。

比如用户可能画出这样的区域:

复制代码
点数不足
多边形未闭合
多边形自相交
区域面积过小
边界穿过禁区
禁区不在工作区内
多个禁区互相重叠

如果 App 不做校验,直接提交给设备端,后面就会出现各种异常。

所以 Android 侧在用户提交边界前,至少要做基础合法性校验。

常见规则:

复制代码
1. 工作区至少 3 个点
2. 多边形需要闭合
3. 多边形不能自相交
4. 面积不能太小
5. 禁区必须在工作区内部
6. 禁区之间不能非法重叠
7. 边界点不能过于密集或过于稀疏

例如用户拖动边界点时,App 应该实时判断当前 Polygon 是否仍然合法。

如果不合法,可以:

复制代码
禁用保存按钮
高亮错误边
提示错误原因
引导用户重新调整

这样可以把问题挡在 App 层,减少设备端处理复杂度。


八、边界数据同步一致性

边界不是只画在 App 上给用户看的。

真正生效的是设备端使用的边界数据。

所以这里有一个很关键的问题:

App 显示的边界,服务端保存的边界,设备实际使用的边界,是否一致?

如果 App 修改了边界,但设备没有收到,用户却以为已经生效,这就是严重问题。

因此边界数据同步要有版本概念。

可以设计类似结构:

复制代码
data class Boundary(
    val boundaryId: String,
    val version: Int,
    val workArea: List<LatLng>,
    val forbiddenAreas: List<List<LatLng>>,
    val updateTime: Long,
    val checksum: String
)

同步流程可以是:

复制代码
App 修改边界
    ↓
提交到服务端
    ↓
服务端保存新版本
    ↓
下发给设备
    ↓
设备保存并返回 ACK
    ↓
App 显示边界已生效

如果设备离线,则不能直接显示"已生效"。

应该显示:

复制代码
已保存,等待设备同步

或者:

复制代码
设备离线,边界将在设备上线后同步

同时,设备端返回的 ACK 也应该包含:

复制代码
boundaryId
version
result
errorCode

这样 App 才能判断设备当前使用的是不是最新边界。

一句话:

边界数据属于强一致性要求较高的数据,不能只保存到 App 本地就认为已经生效。


九、地图坐标系边界

这是 Android 地图项目中非常经典的问题。

很多设备上报的是标准 GPS 坐标:

复制代码
WGS84

但国内高德地图使用的是:

复制代码
GCJ02

如果不做坐标转换,会出现:

复制代码
设备位置偏移
轨迹线偏移
工作区边界偏移
禁区偏移
越界判断错误

比如设备实际上在工作区域内,但因为坐标系不一致,地图上显示它在区域外。

这时候再去做边界判断,结果一定是错的。

所以地图业务中必须先统一坐标系。

常见原则:

复制代码
1. 地图展示用地图厂商坐标系
2. 设备原始数据保留原始坐标
3. 业务判断前明确坐标系
4. App、服务端、设备端约定统一转换规则

如果项目中同时支持高德、Google、百度地图,那么坐标系问题更要抽象出来。

比如可以设计一个地图坐标转换层:

复制代码
interface CoordinateConverter {

    fun toMapCoordinate(point: LatLng): LatLng

    fun toDeviceCoordinate(point: LatLng): LatLng
}

这样后续切换地图厂商时,不至于所有业务代码都散落着坐标转换逻辑。

坐标系不统一,后面的边界判断、轨迹绘制、禁区判断都会出问题。


十、轨迹补发和边界处理的关系

这里再补充一个容易混淆的问题。

假设割草机运动到拐角处,网络信号变差,设备采集到了点位,但 MQTT 没有发出去。

这时候手机 App 收不到中间点。

很多人会把这个叫"轨迹丢失"或者"边界处理",但严格来说,它首先是一个:

复制代码
网络中断
消息可靠性
轨迹缓存
断点补发
时序一致性

的问题。

MQTT 在完全无网络时,不可能把点位发出去。

所以正确设计应该是:

复制代码
设备采集点位
    ↓
先写入本地缓存
    ↓
尝试上报
    ↓
失败则保留
    ↓
网络恢复后补发

点位数据中必须有:

复制代码
taskId
seq
timestamp
lat
lng

其中:

复制代码
seq 解决顺序问题
timestamp 解决采集时间问题

App 或服务端收到补发点后,不能按"收到顺序"画轨迹,而应该按:

复制代码
seq 或 timestamp

重新排序、去重、合并。

如果中间点补齐了,可以画实线。

如果中间点仍然缺失,就应该用:

复制代码
断点
虚线
灰线

表示未知轨迹。

这和边界处理有关系,但不是同一个问题。

它的核心是:

不要把网络原因造成的点位缺失,错误展示成设备真实运动轨迹。


总结

地图业务里的边界处理,远不只是判断一个点有没有出界。

在割草机、机器人、无人车等场景中,边界处理至少包括 9 个核心问题:

复制代码
1. 工作区域边界
2. 禁区边界
3. 拐角轨迹断点处理
4. 定位漂移边界
5. 贴边割草处理
6. 已割区域与补割区域
7. 用户绘制边界合法性
8. 边界数据同步一致性
9. 地图坐标系边界

从 Android 开发角度看,这些问题并不是让 App 去实现完整的运动控制算法,而是要做好:

复制代码
地图绘制
状态展示
异常提示
轨迹缓存
断点补齐
边界编辑
数据校验
坐标转换
设备同步

一句话总结:

地图业务的核心,不只是 Marker 和 Polyline,而是如何保证设备位置、轨迹、边界、任务状态和用户认知始终一致。

这也是地图类 IoT 项目真正复杂的地方。