轨迹驻留点检测

1. 定义

驻留点:驻留点(Stay Point)是车辆在行驶过程中的停留信息,是由一系列连续的GPS点组成(特殊的轨迹段),GPS在空间上非常接近,但是时间跨度很大。通俗地讲,如果一个车辆在一个小的空间范围内持续出现了很长时间,则认为车辆在该范围内驻留了。
驻留点检测:从一条轨迹中检测出所有的驻留点所在的位置称为驻留点检测。

图1 轨迹驻留点示意图[1]

图1表示了一条轨迹中的驻留点信息,从p_5开始,后续的点p_6p_7p_8p_5的空间距离都比较小,且p_5p_8的时间跨度很大时,{p_5,...,p_8}便被认为是一个驻留点。驻留点包含了丰富的时空语义信息,例如:驻留点比较多的地方可能是一个商场(图1B))、公园(图1C))等。

2. 算法描述

从定义中可以得到驻留点的两个特征:空间范围小,时间跨度大。驻留点检测算法的关键就在如何定义这个小和大。

我们称驻留点的起始GPS点称为锚点(Anchor),下面从空间和时间两个维度对驻留点检测算法进行阐述。

2.1 空间范围检测:

在空间上,从锚点开始,找到锚点之后连续的n个与锚点距离小于最大驻留距离(Maximum Stay Distance)\delta的GPS点。用公式表示为:\forall{i} {(dist(GPS_{anchor},GPS_i) \le{\delta})},其中 {({1}\le{i}\le{n}) },并且
满足dist(GPS_{anchor},GPS_{n+1})> \delta

如图2所示,当\delta取5,并以p_5作为锚点anchor进行驻留点检测时,dist(p_5,p_6),dist(p_5,p_7),dist(p_5,p_8)都小于5,而dist(p_5,p_9)=7大于5。则本次空间范围检测中找到了3个与锚点p_5距离小于\delta的GPS点{p_6,p_7,p_8}。

图2 驻留点检测示意图

2.2 时间跨度检测:

对于锚点anchor和其后检测到的n个连续的距离小于\delta的GPS点,如果GPS_n.time-anchor.time>\beta,则认为车辆驻留的时间足够大,可以将anchor及其后面的连续n个GPS点识别成一个驻留点(共n+1个GPS),其中\beta是用户设置的最小驻留时间(Minimum Stay Time)。

在图2中,如果p_8.time-p_5.time>\beta,则{p_5,p_6,p_7,p_8}构成了一个驻留点,否则,不认为这是一个驻留点。因为虽然这4个GPS点的空间距离很小,但是时间跨度也很小,有可能是车辆在缓慢移动(比如在等待红绿灯)。

3. 算法分类

上文中描述了给定一个锚点anchor,根据空间范围和时间跨度检测驻留点的方法。如果检测到一个驻留点,那么下一个锚点anchor应该怎么选呢,这就引出了两种驻留点检测算法的划分。

3.1 基础的驻留点检测[文献1]

如果当前锚点anchor及其后面的n个GPS点组成一个驻留点,则下一次检查的锚点取GPS_{n+1};否则下一次检测的锚点为GPS_{anchor+1},即当前锚点的下一个GPS点。

这种检测方法得到的驻留点可能存在驻留点相邻的情况,比如检测到sp_1 = {p_i,...,p_j}和sp_2 = {p_{j+1},...,p_k}这两个相邻的驻留点。

3.2 基于密度合并的驻留点检测[文献2]

无论当前锚点anchor是否检测到驻留点,下一次检测锚点都取anchor的下一个GPS点GPS_{anchor+1}

这种检测方法首先检测出所有的驻留点,比如sp_1 = {p_i,...p_j},sp_2 = {p_{j-1},...p_k}和sp_3 = {p_{k+5},...p_t}这3个驻留点;然后将相互重叠的驻留点合并成一个最终的驻留点,比如将sp_1,sp_2进行合并,最终得到驻留点sp_{1+2}={p_1,...,p_j,...p_k}和sp_3

4. 代码实现

完整的Scala代码请查看我的GitHub

4.1 驻留点检测父类

代码实现的主要逻辑是利用calStayPointBounds抽象方法计算出驻留点在轨迹GPS点序列中的位置,然后在detect方法中将这些GPS点摘取出来,组成驻留点(StayPoint)。

abstract class AbstractStayPointDetector {
  def detect(trajectory: Trajectory): Array[StayPoint] = {
    val gpsCoordinates = trajectory.getCoordinatesGPS
    val stayPointBounds = calStayPointBounds(gpsCoordinates)

    for ((startIndex, endIndex) <- stayPointBounds) yield {
      val stayPointCoordinates = gpsCoordinates.slice(startIndex, endIndex + 1)
      new StayPoint(stayPointCoordinates.toList.asJava, trajectory.oid)
    }
  }

  /**
   * Find all the stay points' bound in trajectory's coordinates
   *
   * @param gpsCoordinates trajectory's gps coordinates containing longitude latitude and time
   * @return bound of each stay point, consisting of start index and end index(include)
   */
   def calStayPointBounds(gpsCoordinates: Array[CoordinateGPS]): Array[(Int, Int)]

  /**
   * Calculate the position of gps coordinate whose distance
   * to anchor firstly exceeds the max stay point distance threshold
   *
   * @param gpsCoordinates gps coordinates of trajectory
   * @param anchorIndex    anchor position in gpsCoordinates
   * @param maxDistInM     max stay point distance in unit meter
   * @return the position of first coordinate whose distance from anchor is further than maxDistInM
   */
  protected def getFirstExceedIndex(gpsCoordinates: Array[CoordinateGPS],
                                    anchorIndex: Int, maxDistInM: Double): Int = {

    val anchor = gpsCoordinates(anchorIndex)
    var currIndex = anchorIndex + 1
    while (currIndex < gpsCoordinates.length) {
      val distInM = DistanceCalculator.distInMeter(anchor, gpsCoordinates(currIndex))
      if (distInM > maxDistInM) {
        return currIndex
      }
      currIndex += 1
    }
    currIndex
  }

  /**
   * Judge whether start to end time interval of stay point exceeds the specific threshold
   *
   * @param startTime        time of stay point's start coordinate
   * @param endTime          time of stay point's end coordinate
   * @param minStayTimeInSec minimum stay point time interval in unit second
   * @return whether time interval from start to end coordinate exceeds the threshold
   */
  protected def isExceedMinTimeThreshold(startTime: Timestamp, endTime: Timestamp,
                                         minStayTimeInSec: Double): Boolean = {
    val timeIntervalInSec = endTime.toInstant.getEpochSecond - startTime.toInstant.getEpochSecond
    timeIntervalInSec > minStayTimeInSec
  }
}
4.2 基础的驻留点检测

实现父类中的calStayPointBounds方法,检测出的驻留点可能相邻。

class ClassicStayPointDetector(maxStayDistInMeter: Double, minStayTimeInSecond: Double) extends AbstractStayPointDetector {

  /**
   * Find all the stay points' bound in trajectory's coordinates
   *
   * @param gpsCoordinates trajectory's gps coordinates containing longitude latitude and time
   * @return bound of each stay point, consisting of start index and end index(include)
   */
  override def calStayPointBounds(gpsCoordinates: Array[CoordinateGPS]): Array[(Int, Int)] = {
    val stayPointBounds = new ArrayBuffer[(Int, Int)]()

    var currIndex = 0
    while (currIndex < gpsCoordinates.length) {
      val endIndex = super.getFirstExceedIndex(gpsCoordinates, currIndex, maxStayDistInMeter) - 1

      val anchorTime = gpsCoordinates(currIndex).time
      val endTime = gpsCoordinates(endIndex).time
      val isExceedTime = super.isExceedMinTimeThreshold(anchorTime, endTime, minStayTimeInSecond)
      if (isExceedTime) {
        stayPointBounds += ((currIndex, endIndex))
        currIndex = endIndex + 1
      } else {
        currIndex += 1
      }
    }

    stayPointBounds.toArray
  }
}
4.3 基于密度合并的驻留点检测

实现父类的calStayPointBounds方法,检测出基于密度合并后的驻留点

class DensityStayPointDetector(maxStayDistInMeter: Double, minStayTimeInSecond: Double) extends AbstractStayPointDetector {
  /**
   * Find all the stay points' bound in trajectory's coordinates
   *
   * @param gpsCoordinates trajectory's gps coordinates containing longitude latitude and time
   * @return bound of each stay point, consisting of start index and end index(include)
   */
  override def calStayPointBounds(gpsCoordinates: Array[CoordinateGPS]): Array[(Int, Int)] = {
    val stayPointBounds = new ArrayBuffer[(Int, Int)]()

    var startIndex: Option[Int] = None
    var endIndex: Option[Int] = None
    for (currIndex <- gpsCoordinates.indices) {
      val tempEndIndex = super.getFirstExceedIndex(gpsCoordinates, currIndex, maxStayDistInMeter) - 1

      val anchorTime = gpsCoordinates(currIndex).time
      val endTime = gpsCoordinates(tempEndIndex).time
      val isExceedTime = super.isExceedMinTimeThreshold(anchorTime, endTime, minStayTimeInSecond)
      if (isExceedTime) {
        //detected stay point
        if (startIndex.isEmpty) {
          startIndex = Some(currIndex)
        }
        if (endIndex.isEmpty || endIndex.get < tempEndIndex) {
          endIndex = Some(tempEndIndex)
        }
      }

      if (endIndex.isDefined && endIndex.get == currIndex) {
        stayPointBounds += ((startIndex.get, endIndex.get))
        startIndex = None
        endIndex = None
      }
    }

    stayPointBounds.toArray
  }
}

5. 总结

驻留点检测是指检测轨迹GPS点序列中表示车辆驻留信息的GPS子序列,轨迹的驻留信息会在很多轨迹分析挖掘算法中得到广泛应用。

参考文献

[1] Zheng Y. Trajectory data mining: an overview[J]. ACM Transactions on Intelligent Systems and Technology (TIST), 2015, 6(3): 1-41.
[2] Yuan J, Zheng Y, Zhang L, et al. Where to find my next passenger[C]//Proceedings of the 13th international conference on Ubiquitous computing. 2011: 109-118.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,888评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,677评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,386评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,726评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,729评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,337评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,902评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,807评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,349评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,439评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,567评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,242评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,933评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,420评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,531评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,995评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,585评论 2 359

推荐阅读更多精彩内容