All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tsingjyujing.geo.element.GeoPointTimeSeries.scala Maven / Gradle / Ivy

There is a newer version: 2.8.9-2.11
Show newest version
package com.github.tsingjyujing.geo.element

import com.github.tsingjyujing.geo.basic.IGeoPoint
import com.github.tsingjyujing.geo.basic.timeseries.ITimeIndexSeq
import com.github.tsingjyujing.geo.element.immutable.{GeoLine, TimeElement}
import com.github.tsingjyujing.geo.util.GeoUtil
import com.github.tsingjyujing.geo.util.mathematical.SeqUtil

import scala.util.control.Breaks

/**
  * A TimeSeries with GeoPoint as value
  * @author [email protected]
  */
class GeoPointTimeSeries extends ITimeIndexSeq[TimeElement[IGeoPoint]] {

    /**
      * Query value by time
      * @param time time to query
      * @return
      */
    override def getValue(time: Double): TimeElement[IGeoPoint] = {
        val indexInfo = query(time)
        if (indexInfo._1 == (-1) && indexInfo._2 == (-1)) {
            throw new RuntimeException("Error while querying value")
        } else if (indexInfo._1 == (-1)) {
            apply(indexInfo._2)
        } else if (indexInfo._2 == (-1)) {
            apply(indexInfo._1)
        } else {
            val dt = apply(indexInfo._2).getTick - apply(indexInfo._1).getTick
            val tickRatio = (time - apply(indexInfo._1).getTick) / dt
            TimeElement[IGeoPoint](
                time,
                GeoUtil.interp(apply(indexInfo._1).getValue, apply(indexInfo._2).getValue, tickRatio)
            )
        }
    }

    /**
      * Get segmentationb by start and end time
      * @param startTime
      * @param endTime
      * @return
      */
    def sliceByTime(startTime: Double, endTime: Double): GeoPointTimeSeries = GeoPointTimeSeries(
        slice(query(startTime)._1, query(endTime)._2)
    )

    /**
      * Get indexes
      * @return
      */
    def indices: Range = data.indices

    /**
      * Get sum mileage of this route
      * @return
      */
    def mileage: Double = this.sliding(2).map(p2 => p2.head.getValue.geoTo(p2.last.getValue)).sum

    /**
      * Get speed by differential points
      * @param ratio scale ratio of the speed
      * @return
      */
    def getSpeed(ratio: Double = 1.0D): DoubleTimeSeries = DoubleTimeSeries(this.sliding(2).map(p2 => {
        val ds = p2.last.getValue.geoTo(p2.head.getValue)
        val dt = p2.last.getTick - p2.head.getTick
        new TimeElement[Double]((p2.head.getTick + p2.last.getTick) / 2.0, ds * ratio / dt)
    }))

    /**
      * Clean data by sliding data
      * @param cleanFunc
      * @return
      */
    private def cleanSliding(cleanFunc: (TimeElement[IGeoPoint], TimeElement[IGeoPoint]) => Boolean): GeoPointTimeSeries = GeoPointTimeSeries(sliding(2).filter(p2 => cleanFunc(p2.head, p2.last)).map(_.head))

    /**
      * Clean overspeed data
      * @param speedLimit max speed allowed to appear
      * @return
      */
    def cleanOverSpeed(speedLimit: Double): GeoPointTimeSeries = this.cleanSliding(
        (p1, p2) => {
            val ds = p1.getValue geoTo p2.getValue
            val dt = p2.getTick - p1.getTick
            (ds / dt) > speedLimit
        }
    )

    /**
      * ResultType: Iterable[TimeElement[IGeoPoint]]
      * StatusType: (lastValidPoint:TimeElement[IGeoPoint])
      *
      * @param marginDistance standard margin distance
      * @param maxTolerance do resample if d>maxTolerance*marginDistance
      * @return
      */
    def isometricallyResample(marginDistance: Double, maxTolerance: Double = 3.0): GeoPointTimeSeries = GeoPointTimeSeries(statusMachine[TimeElement[IGeoPoint], Iterable[TimeElement[IGeoPoint]]](
        (currentPoint, status) => {
            val d = currentPoint.value geoTo status.value
            if (d < marginDistance) {
                (Array.empty[TimeElement[IGeoPoint]], status)
            } else if (d <= marginDistance * maxTolerance) {
                (Array(currentPoint), currentPoint)
            } else {
                (GeoUtil.interp(status, currentPoint, math.ceil(d / marginDistance).toInt), currentPoint)
            }
        }, apply(0)
    ).flatten)


    /**
      * Sparse route
      * @param sparsityParam sparsity max distance to line
      * @param sparsitySearchParam how many points to search in range
      * @return
      */
    def toSparse(
                    sparsityParam: Double,
                    sparsitySearchParam: Int
                ): GeoPointTimeSeries
    = GeoPointTimeSeries(
        toSparseIndex(
            sparsityParam,
            sparsitySearchParam
        ).map(apply)
    )

    /**
      * Sparse route and get it's indexes
      * @param sparsityParam sparsity max distance to line
      * @param sparsitySearchParam how many points to search in range
      * @return
      */
    def toSparseIndex(
                         sparsityParam: Double,
                         sparsitySearchParam: Int
                     ): IndexedSeq[Int] = GeoPointTimeSeries.sparsifyGPSIndexed(
        this, sparsityParam, sparsitySearchParam
    )
}

/**
  * Apply and Utils
  */
object GeoPointTimeSeries {

    /**
      * Create TimeSeries by time-elements
      * @param data
      * @return
      */
    def apply(data: TraversableOnce[TimeElement[IGeoPoint]]): GeoPointTimeSeries = {
        val result = new GeoPointTimeSeries()
        result.appendAll(data)
        result
    }

    /**
      * Get sparse points'
      * @param gpsArray gps points
      * @param sparsityParam sparsity max distance to line
      * @param sparsitySearchParam how many points to search in range
      * @return
      */
    def sparsifyGPS(
                       gpsArray: GeoPointTimeSeries,
                       sparsityParam: Double,
                       sparsitySearchParam: Int
                   ): GeoPointTimeSeries = GeoPointTimeSeries(
        sparsifyGPSIndexed(
            gpsArray, sparsityParam, sparsitySearchParam
        ).map(
            i => gpsArray(i)
        )
    )

    /**
      * Get sparse points' index in seq
      * @param gpsArray gps points
      * @param sparsityParam sparsity max distance to line
      * @param sparsitySearchParam how many points to search in range
      * @return
      */
    def sparsifyGPSIndexed(
                              gpsArray: GeoPointTimeSeries,
                              sparsityParam: Double,
                              sparsitySearchParam: Int
                          ): IndexedSeq[Int] = {
        if (gpsArray.size < 10) return gpsArray.indices
        val returnList = new scala.collection.mutable.MutableList[Int]
        returnList += 0
        var nowIndex = 0
        val getDistance = (startIndex: Int, endIndex: Int) => {
            val line = GeoLine(
                gpsArray(startIndex).value,
                gpsArray(endIndex).value
            )
            gpsArray.slice(startIndex + 1, endIndex).map((point) => line.geoTo(point.value)).max
        }

        val loop = Breaks
        loop.breakable(
            while (true) {
                val indexFound = SeqUtil.searchInSorted(
                    (i) => getDistance(nowIndex, i),
                    sparsityParam,
                    nowIndex + 2,
                    math.min(
                        nowIndex + sparsitySearchParam,
                        gpsArray.size - 2
                    )
                )._1
                if (indexFound >= gpsArray.size - 4) {
                    returnList += gpsArray.size - 1
                    loop.break
                } else {
                    returnList += indexFound
                    nowIndex = indexFound
                }
            }
        )
        returnList.toIndexedSeq
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy