轨迹展示与展示

背景介绍

基于PM2.5的健康路径规划项目,需要轨迹记录和轨迹展示功能。帮助用户记录出行轨迹,并提供分析PM2.5的吸入剂量。
图 0

图 1

主要涉及功能如下

  • 实时采集用户轨迹并可视化在主页面地图中,点击开始按钮,采集开始,点击结束按钮采集结束,采集过程中支持暂停/继续
  • 轨迹展示功能,点击轨迹在地图中生成轨迹图

亮点

  • 定期推送功能
  • 轨迹展示的灵活统计功能

设计思路

轨迹采集

两种策略:一种是暂存起来,定量推动到服务器。另外基于websocket实时推送

第一种策略实时性差,但是如果只是推送到服务器,不需要共享到其他用户,实时性不重要前端可视化也不依赖于服务器的数据。第二种策略的实时性强,但是要保持持久连接较耗时。因此选择第一种。但是本文会给出两者的实现方法。

定量推送

主逻辑

  • 定量推送数据:当 localStorage 中的数据量达到设定的阈值就推送到服务器。

  • 因为网络异常推送失败或其他原因,localStorage 数据不断增加,导致容量接近上限时,上限是容量的2/3,将数据转移到 IndexedDB,继续保持数据采集。

  • 顺序问题:当恢复网络时,必须优先推送 IndexedDB 中的数据,随后再推送 localStorage 中的数据,确保数据按时间顺序上传。

  • 检测网络状态:navigator.onLine用于判断当前网络状态是否在线。onlineoffline 事件:分别在网络恢复和网络断开时触发。online 事件触发后立即尝试发送缓存数据,确保及时上传未发送的数据。

  • 为保证上传顺序使用任务队列和互斥锁,避免上传任务的冲突。

采用互斥锁

在上传过程中引入一种互斥锁的机制,确保在上传过程中不会重复访问同一份数据。这样在上传过程中,即使有新的数据加入到 localStorage,也不会与当前正在上传的数据发生冲突。避免了多个上传任务可能同时进行,导致并发冲突,比如数据重复上传、数据上传顺序错乱等问题

如何实现:在 JavaScript 的单线程环境中,虽然没有直接的互斥锁机制,但我们可以通过标志位(flag)的方式实现互斥锁的效果。

在我们的实现中,isUploading 就相当于一个互斥锁,它用来标记当前是否有上传任务正在进行:

1
2
let isUploading = false; // 标志是否正在上传数据
锁定(加锁):当开始执行上传任务时,我们将 isUploading 设为 true,这就相当于上了锁,表示系统正在上传数据。
1
2
isUploading = true; // 加锁,表示系统正在执行上传任务
解锁:上传任务完成后,记得将 isUploading 设为 false,解锁表示上传任务完成,允许下一次上传开始。
1
2
3

isUploading = false; // 解锁,表示上传任务完成
在上传任务的开始处,我们通过判断 isUploading 来决定是否执行上传任务。如果 isUploading 为 true,则跳过当前任务,以避免多个上传任务同时进行:
1
if (isUploading) return; // 如果已加锁(正在上传),直接返回,不执行新的上传任务

实现

    1. 数据采集与存储
    • 使用 navigator.geolocation 进行轨迹点的实时采集。每次采集到一个点后,立即将其存入 localStorage 中。
    • localStorage 达到容量的 2/3 时,将数据转移到 IndexedDB 中以防止 localStorage 的存储容量不足。
    • 轨迹点数据的采集频率不依赖于网络状态,数据始终保持持续采集。
    1. 任务队列与上传机制
    • 使用任务队列来管理数据上传过程,确保即使有多个任务并发执行时,上传操作不会发生冲突。
    • 每次采集到的数据会在 localStorage 中暂存。当数据量达到一定的阈值(如 500 个点),或者网络恢复时,系统会尝试将数据上传到服务器。
    • 如果当前已有正在执行的上传任务,则新的上传任务会被添加到任务队列中,等待前一个任务完成后依次执行。这样可以避免同时多个上传任务导致的冲突。
    1. 网络状态检测与数据传输
    • 系统实时监控网络状态,当网络断开时,数据会继续存储到 localStorageIndexedDB 中,而不会尝试上传。
    • 当网络恢复时,会优先处理任务队列中的上传任务,将缓存中的数据(优先从 IndexedDB,然后是 localStorage)上传至服务器。
    1. 上传过程与冲突管理
    • 通过任务队列机制来防止并发上传冲突:当一个上传任务正在进行时,新的上传任务不会立即执行,而是加入队列等待当前任务完成。
    • 在上传成功后,系统会清理已上传的数据(从 localStorage 或 IndexedDB 中删除),并继续处理下一个上传任务,直到所有缓存的数据都上传完成。
    1. 数据传输与顺序维护
    • 当网络恢复时,系统首先检查 IndexedDB 中的数据。由于 IndexedDB 保存的是 localStorage 中超过容量的数据,因此 IndexedDB 中的数据应优先上传。
    • IndexedDB 的数据上传完成后,系统会继续处理 localStorage 中的数据,保证传输顺序的正确性。

实时推送

主逻辑

  • 获取到一个点数据就备份到localStorage中,然后推送到服务端,同送成功将备份删除。

  • 遇到websocket断开的的情况,将数据保存如localStorage,链接回复,将localStorage的数据推送到服务端,推送成功就删除localStorage中的数据。

推送顺序问题

点的顺序对轨迹来说是重要的,因此需要确保数据库中存储的点的数据是正确的,每一个点数据有时间戳,可以在后端进行排序。也可以在前端控制推送顺序,使用双队列。从复杂度上说前端复杂度较小,因此选择前端处理的方式。

实现

    1. 数据采集与存储
    • 使用 navigator.geolocation 进行轨迹点的实时采集,每次采集到一个点后,立即将其存入实时队列,并将该数据缓存到 localStorage 中。
    • 实时队列用于存储采集到的新数据,实时发送给服务端;localStorage 则作为本地缓存,防止断网或其他异常情况下数据丢失。
    1. WebSocket 连接与重连机制
    • WebSocket 建立后,若连接成功,会开始发送实时数据,并检查是否有缓存的轨迹数据(localStorage 中)。
    • WebSocket 连接断开,会进入重连模式,在重连成功后立即尝试将缓存中的数据发送到服务器。
    1. 双队列管理
      实时队列:存储正在采集的数据,WebSocket 连接正常时,直接发送数据给服务端。
    • 缓存队列(localStorage):当 WebSocket 连接断开时,实时队列中的数据存储到 localStorage 中;当连接恢复时,发送缓存队列的数据。
    1. 数据传输与顺序维护
    • 当网络恢复时,先将 localStorage 中的数据传输到服务端,随后再发送实时队列中的新数据,确保传输顺序不乱。
    1. localStorage 清空与缓存清理
    • WebSocket 连接恢复后,成功发送缓存队列的数据后,立即从localStorage 中删除已成功推送的数据。
    • 避免重复发送数据,确保只发送最新数据。

历史轨迹展示

可以根据年、月、周、日来统计和显示轨迹,对后端传来的数据进行处理统计,统计原则根据用户的设置决定,生成用于渲染的数据。点击每一条数据在地图上生成轨迹。

亮点

轨迹数据的层次化管理,需要对后端返回的扁平数据进行处理,将数据转化为多层次的树状结构。这样可以让前端在展示时按年、月、周、日等不同粒度进行分组。

图 2

图 3

图 4

图 5

图 6