"""Class to manage segmentation of GPS tracks"""
import sys
import math
import progressbar
import numpy as np
from tracklib.core.Obs import Obs
from tracklib.core.ObsCoords import ENUCoords
from tracklib.algo.Geometrics import Circle, minCircle
import tracklib.core.Utils as utils
from tracklib.algo.Interpolation import ALGO_LINEAR, MODE_SPATIAL
import tracklib.core.Operator as Operator
# --------------------------------------------------------------------------
# Circular import (not satisfying solution)
# --------------------------------------------------------------------------
from tracklib.core.Track import Track
from tracklib.core.TrackCollection import TrackCollection
MODE_COMPARAISON_AND = 1
MODE_COMPARAISON_OR = 2
MODE_STOPS_LOCAL = 0
MODE_STOPS_GLOBAL = 1
MODE_STOPS_RTK = 2
MODE_STOPS_ACC = 3
MODE_SPLIT_RETURN_EXHAUSTIVE = 0
MODE_SPLIT_RETURN_FAST = 1
MODE_SEGMENTATION_MINIMIZE = 0
MODE_SEGMENTATION_MAXIMIZE = 1
# -------------------------------------------------------------------------
# Segmentation and Split track
# -------------------------------------------------------------------------
[docs]def segmentation(track, afs_input, af_output,
thresholds_max, mode_comparaison=MODE_COMPARAISON_AND):
"""
Method to divide a track into multiple according to analytical feaures value.
Creates an AF with 0 if change of division, 1 otherwise
"""
# Gestion cas un seul AF
if not isinstance(afs_input, list):
afs_input = [afs_input]
if not isinstance(thresholds_max, list):
thresholds_max = [thresholds_max]
track.createAnalyticalFeature(af_output)
for i in range(track.size()):
# On cumule les comparaisons pour chaque af_input
comp = True
for index, af_input in enumerate(afs_input):
current_value = track.getObsAnalyticalFeature(af_input, i)
# on compare uniquement si on peut
if not utils.isnan(current_value):
seuil_max = sys.float_info.max
if thresholds_max != None and len(thresholds_max) >= index:
seuil_max = thresholds_max[index]
if mode_comparaison == MODE_COMPARAISON_AND:
comp = comp and (current_value <= seuil_max)
else:
comp = comp or (current_value <= seuil_max)
# On clot l'intervalle, on le marque a 1
if not comp:
track.setObsAnalyticalFeature(af_output, i, 1)
else:
track.setObsAnalyticalFeature(af_output, i, 0)
[docs]def split(track, source) -> TrackCollection:
"""Splits track according to :
- af name (considered as a marker) if `source` is a string
- list of index if `source` is a list
:return: No track if no segmentation, otherwise a TrackCollection object
"""
NEW_TRACES = TrackCollection()
# --------------------------------------------
# Split from analytical feature name
# --------------------------------------------
if isinstance(source, str):
count = 0 # Initialisation du compteur des étapes
begin = 0 # indice du premier point de l'étape
for i in range(track.size()):
if track.getObsAnalyticalFeature(source, i) == 1: # Nouvelle trajectoire
# L'identifiant de la trace subdivisée est obtenue par concaténation
# de l'identifiant de la trace initiale et du compteur
new_id = str(track.uid) + "." + str(count)
# La liste de points correspondant à l'intervalle de subdivision est créée
new_traj = track.extract(begin, i)
new_traj.setUid(new_id)
NEW_TRACES.addTrack(new_traj)
count += 1
begin = i + 1
# Si tous les points sont dans la même classe, la liste d'étapes reste vide
# sinon, on clôt la derniere étape et on l'ajoute à la liste
if begin != 0:
new_id = str(track.uid) + "." + str(count)
new_traj = track.extract(begin, track.size() - 1)
new_traj.setUid(new_id)
NEW_TRACES.addTrack(new_traj)
# --------------------------------------------
# Split from list of indices
# --------------------------------------------
if isinstance(source, list):
for i in range(len(source) - 1):
NEW_TRACES.addTrack(track.extract(source[i], source[i + 1]))
return NEW_TRACES
# -------------------------------------------------------------------
#
# -------------------------------------------------------------------
[docs]def findStops(track: Track, spatial, temporal, mode, verbose=True) -> Track:
'''
Function to find stop positions from a track
Parameters
----------
track : Track
spatial : float
temporal : float
mode : MODE_STOPS_LOCAL, MODE_STOPS_GLOBAL, MODE_STOPS_RTK, MODE_STOPS_ACC
verbose : bool, optional
The default is True.
Returns
-------
TYPE
DESCRIPTION.
'''
if mode == MODE_STOPS_LOCAL:
return findStopsLocal(track, spatial, temporal)
if mode == MODE_STOPS_GLOBAL:
return findStopsGlobal(track, spatial, temporal, verbose)
if mode == MODE_STOPS_RTK:
return findStopsGlobalForRTK(track, spatial, temporal, verbose)
if mode == MODE_STOPS_ACC:
pass
# -----------------------------------------------------------------------------
# Function to find stop positions from a track
# Inputs:
# - duration: minimal stop duration (in seconds)
# - speed: maximal speed during stop (in ground units / sec)
# Output: a track with centroids (and first time of stop sequence)
# For classical standard GPS track set 1 m/s for 10 sec)
# -----------------------------------------------------------------------------
[docs]def findStopsLocal(track, speed=1, duration=10):
track = track.copy()
stops = Track()
segmentation(track, "speed", "#mark", speed)
track.operate(Operator.Operator.DIFFERENTIATOR, "#mark")
track.operate(Operator.Operator.RECTIFIER, "#mark")
TRACES = split(track, "#mark")
TMP_MEAN_X = []
TMP_MEAN_Y = []
TMP_MEAN_Z = []
TMP_STD_X = []
TMP_STD_Y = []
TMP_STD_Z = []
TMP_DURATION = []
TMP_NBPOINTS = []
TMP_SIGMA_X = []
TMP_SIGMA_Y = []
TMP_SIGMA_Z = []
for i in range(0, len(TRACES), 2):
if TRACES[i].duration() < duration:
continue
stops.addObs(
Obs(
TRACES[i].getCentroid().copy(), TRACES[i].getFirstObs().timestamp.copy()
)
)
TMP_SIGMA_X.append(TRACES[i].operate(Operator.Operator.AVERAGER, "x"))
TMP_SIGMA_Y.append(TRACES[i].operate(Operator.Operator.AVERAGER, "y"))
TMP_SIGMA_Z.append(TRACES[i].operate(Operator.Operator.AVERAGER, "z"))
TMP_SIGMA_X.append(TRACES[i].operate(Operator.Operator.STDDEV, "x"))
TMP_SIGMA_Y.append(TRACES[i].operate(Operator.Operator.STDDEV, "y"))
TMP_SIGMA_Z.append(TRACES[i].operate(Operator.Operator.STDDEV, "z"))
TMP_NBPOINTS.append(TRACES[i].size())
TMP_DURATION.append(TRACES[i].duration())
if stops.size() == 0:
return stops
# stops.createAnalyticalFeature("radius", TMP_RADIUS)
stops.createAnalyticalFeature("mean_x", TMP_MEAN_X)
stops.createAnalyticalFeature("mean_y", TMP_MEAN_Y)
stops.createAnalyticalFeature("mean_z", TMP_MEAN_Z)
stops.createAnalyticalFeature("sigma_x", TMP_STD_X)
stops.createAnalyticalFeature("sigma_y", TMP_STD_Y)
stops.createAnalyticalFeature("sigma_z", TMP_STD_Z)
stops.createAnalyticalFeature("duration", TMP_DURATION)
stops.createAnalyticalFeature("nb_points", TMP_NBPOINTS)
stops.operate(Operator.Operator.QUAD_ADDER, "sigma_x", "sigma_y", "rmse")
return stops
# ----------------------------------------------------------------
# Fonctions utilitaires
# ----------------------------------------------------------------
[docs]def backtracking(B, i, j):
if (B[i, j] < 0) or (abs(i - j) <= 1):
return [i]
else:
id = (int)(B[i, j])
return backtracking(B, i, id) + backtracking(B, id, j)
[docs]def backward(B):
n = B.shape[0]
return backtracking(B, 0, n - 1) + [n - 1]
[docs]def plotStops(stops, x="x", y="y", r="radius", rf=1, sym="r-"):
for i in range(len(stops)):
Circle(ENUCoords(stops[x][i], stops[y][i]), rf * stops[r][i]).plot(sym)
'''
def removeStops(track, stops=None):
if stops is None:
stops = extractStopsBis(track)
output = track.extract(0, stops["id_ini"][0])
for i in range(len(stops) - 1):
output = output + track.extract(stops["id_end"][i], stops["id_ini"][i + 1])
output = output + track.extract(stops["id_end"][-1], track.size() - 1)
return output
'''
[docs]def findStopsGlobal(track, diameter=20, duration=60, downsampling=1, verbose=True):
'''
Find stop points in a track based on two parameters:
Maximal size of a stop (as the diameter of enclosing circle,
in ground units) and minimal time duration (in seconds).
Use downsampling parameter > 1 to speed up the process
Parameters
----------
track : Track
track to detect stop points
diameter : float, optional
Maximal size of a stop (as the diameter of enclosing circle,
in ground units). The default is 20.
duration : float, optional
minimal time duration (in seconds). The default is 60.
downsampling : float, optional
to speed up the process, value need to be > 1. The default is 1.
verbose : boolean, optional
verbose or not. The default is True.
Returns
-------
stops : Track
stops points constitute a new Track.
'''
# If down-sampling is required
if downsampling > 1:
track = track.copy()
track **= track.size() / downsampling
# ---------------------------------------------------------------------------
# Computes cost matrix as :
# Cij = 0 if size of enclosing circle of pi, pi+1, ... pj-1 is > diameter
# Cij = 0 if time duration between pi and pj-1 is < duration
# Cij = (j-i)**2 = square of the number of points of segment otherwise
# ---------------------------------------------------------------------------
C = np.zeros((track.size(), track.size()))
RANGE = range(track.size() - 2)
if verbose:
print("Minimal enclosing circles computation:")
RANGE = progressbar.progressbar(RANGE)
for i in RANGE:
for j in range(i + 1, track.size() - 1):
if track[i].distance2DTo(track[j - 1]) > diameter:
C[i, j] = 0
break
if track[j - 1].timestamp - track[i].timestamp <= duration:
C[i, j] = 0
continue
cercle = minCircle(track.extract(i, j - 1))
if cercle != None:
C[i, j] = 2 * cercle.radius
C[i, j] = (C[i, j] < diameter) * (j - i) ** 2
else:
# TODO : à valider
C[i, j] = 0
continue
C = C + np.transpose(C)
# ---------------------------------------------------------------------------
# Computes optimal partition with dynamic programing
# ---------------------------------------------------------------------------
segmentation = optimalPartition(C, MODE_SEGMENTATION_MAXIMIZE, verbose)
stops = Track()
TMP_RADIUS = []
TMP_MEAN_X = []
TMP_MEAN_Y = []
TMP_MEAN_Z = []
TMP_IDSTART = []
TMP_IDEND = []
TMP_STD_X = []
TMP_STD_Y = []
TMP_STD_Z = []
TMP_DURATION = []
TMP_NBPOINTS = []
for i in range(len(segmentation) - 1):
portion = track.extract(segmentation[i], segmentation[i + 1] - 1)
C = minCircle(portion)
if C != None:
if (C.radius > diameter / 2) or (portion.duration() < duration):
continue
stops.addObs(Obs(C.center, portion.getFirstObs().timestamp))
TMP_RADIUS.append(C.radius)
TMP_MEAN_X.append(portion.operate(Operator.Operator.AVERAGER, "x"))
TMP_MEAN_Y.append(portion.operate(Operator.Operator.AVERAGER, "y"))
TMP_MEAN_Z.append(portion.operate(Operator.Operator.AVERAGER, "z"))
TMP_STD_X.append(portion.operate(Operator.Operator.STDDEV, "x"))
TMP_STD_Y.append(portion.operate(Operator.Operator.STDDEV, "y"))
TMP_STD_Z.append(portion.operate(Operator.Operator.STDDEV, "z"))
TMP_IDSTART.append(segmentation[i] * downsampling)
TMP_IDEND.append((segmentation[i + 1] - 1) * downsampling)
TMP_NBPOINTS.append(segmentation[i + 1] - segmentation[i])
TMP_DURATION.append(portion.duration())
if stops.size() == 0:
return stops
stops.createAnalyticalFeature("radius", TMP_RADIUS)
stops.createAnalyticalFeature("mean_x", TMP_MEAN_X)
stops.createAnalyticalFeature("mean_y", TMP_MEAN_Y)
stops.createAnalyticalFeature("mean_z", TMP_MEAN_Z)
stops.createAnalyticalFeature("id_ini", TMP_IDSTART)
stops.createAnalyticalFeature("id_end", TMP_IDEND)
stops.createAnalyticalFeature("sigma_x", TMP_STD_X)
stops.createAnalyticalFeature("sigma_y", TMP_STD_Y)
stops.createAnalyticalFeature("sigma_z", TMP_STD_Z)
stops.createAnalyticalFeature("duration", TMP_DURATION)
stops.createAnalyticalFeature("nb_points", TMP_NBPOINTS)
stops.operate(Operator.Operator.QUAD_ADDER, "sigma_x", "sigma_y", "rmse")
stops.base = track.base
return stops
[docs]def findStopsGlobalForRTK(
track, std_max=2e-2, duration=5, downsampling=1, verbose=True
):
"""Find stop points in a track based on maximal size of a stop and minimal
time duration
Two parameters:
- Maximal size of a stop (as the standard deviation per axis, in ground units)
- Minimal time duration (in seconds)
Use downsampling parameter > 1 to speed up the process.
Default is set for precise RTK GNSS survey (2 cm for 5 sec)
"""
# If down-sampling is required
if downsampling > 1:
track = track.copy()
track **= track.size() / downsampling
# ---------------------------------------------------------------------------
# Computes cost matrix as :
# Cij = 0 if sqrt(0.33*(std_x^2 + std_y^2 + std_Z^2)) > std_max
# Cij = 0 if time duration between pi and p-1 is < duration
# Cij = (j-i)**2 = square of the number of points of segment otherwise
# ---------------------------------------------------------------------------
C = np.zeros((track.size(), track.size()))
RANGE = range(track.size() - 2)
if verbose:
print("Minimal enclosing circles computation:")
RANGE = progressbar.progressbar(RANGE)
for i in RANGE:
for j in range(i + 1, track.size() - 1):
if track[i].distanceTo(track[j - 1]) > 3 * std_max:
C[i, j] = 0
break
if track[j - 1].timestamp - track[i].timestamp <= duration:
C[i, j] = 0
continue
portion = track.extract(i, j - 1)
varx = portion.operate(Operator.Operator.VARIANCE, "x")
vary = portion.operate(Operator.Operator.VARIANCE, "y")
varz = portion.operate(Operator.Operator.VARIANCE, "z")
C[i, j] = math.sqrt(varx + vary + varz)
C[i, j] = (C[i, j] < std_max) * (j - i) ** 2
C = C + np.transpose(C)
# ---------------------------------------------------------------------------
# Computes optimal partition with dynamic programing
# ---------------------------------------------------------------------------
segmentation = optimalPartition(C, MODE_SEGMENTATION_MAXIMIZE, verbose)
stops = Track()
TMP_RADIUS = []
TMP_MEAN_X = []
TMP_MEAN_Y = []
TMP_MEAN_Z = []
TMP_IDSTART = []
TMP_IDEND = []
TMP_STD_X = []
TMP_STD_Y = []
TMP_STD_Z = []
TMP_DURATION = []
TMP_NBPOINTS = []
for i in range(len(segmentation) - 1):
portion = track.extract(segmentation[i], segmentation[i + 1] - 1)
radius = C[segmentation[i], segmentation[i + 1]]
if radius == 0:
continue
xm = portion.operate(Operator.Operator.AVERAGER, "x")
ym = portion.operate(Operator.Operator.AVERAGER, "y")
zm = portion.operate(Operator.Operator.AVERAGER, "z")
xv = portion.operate(Operator.Operator.VARIANCE, "x")
yv = portion.operate(Operator.Operator.VARIANCE, "y")
zv = portion.operate(Operator.Operator.VARIANCE, "z")
pt = portion[0].position.copy()
pt.setX(xm)
pt.setY(ym)
pt.setZ(zm)
stops.addObs(Obs(pt, portion[0].timestamp))
TMP_RADIUS.append(math.sqrt(xv + yv + zv))
TMP_MEAN_X.append(xm)
TMP_MEAN_Y.append(ym)
TMP_MEAN_Z.append(zm)
TMP_STD_X.append(xv ** 0.5)
TMP_STD_Y.append(yv ** 0.5)
TMP_STD_Z.append(zv ** 0.5)
TMP_IDSTART.append(segmentation[i] * downsampling)
TMP_IDEND.append((segmentation[i + 1] - 1) * downsampling)
TMP_NBPOINTS.append(segmentation[i + 1] - segmentation[i])
TMP_DURATION.append(portion.duration())
if stops.size() == 0:
return stops
stops.createAnalyticalFeature("radius", TMP_RADIUS)
stops.createAnalyticalFeature("mean_x", TMP_MEAN_X)
stops.createAnalyticalFeature("mean_y", TMP_MEAN_Y)
stops.createAnalyticalFeature("mean_z", TMP_MEAN_Z)
stops.createAnalyticalFeature("id_ini", TMP_IDSTART)
stops.createAnalyticalFeature("id_end", TMP_IDEND)
stops.createAnalyticalFeature("sigma_x", TMP_STD_X)
stops.createAnalyticalFeature("sigma_y", TMP_STD_Y)
stops.createAnalyticalFeature("sigma_z", TMP_STD_Z)
stops.createAnalyticalFeature("duration", TMP_DURATION)
stops.createAnalyticalFeature("nb_points", TMP_NBPOINTS)
stops.operate(Operator.Operator.QUAD_ADDER, "sigma_x", "sigma_y", "rmse")
stops.base = track.base
return stops
[docs]def splitReturnTrip(track, mode):
if mode == MODE_SPLIT_RETURN_EXHAUSTIVE:
return splitReturnTripExhaustive(track)
if mode == MODE_SPLIT_RETURN_FAST:
return splitReturnTripFast(track)
[docs]def splitReturnTripExhaustive(track):
"""Split track when there is a return trip to keep only the first part"""
min_val = 1e300
argmin = 0
AVG = Operator.Operator.AVERAGER
for return_point in progressbar.progressbar(range(1, track.size() - 1)):
T1 = track.extract(0, return_point)
T2 = track.extract(return_point, track.size() - 1)
avg = (T1 - T2).operate(AVG, "diff") + (T2 - T1).operate(AVG, "diff")
if avg < min_val:
min_val = avg
argmin = return_point
first_part = track.extract(0, argmin - 1)
second_part = track.extract(argmin, track.size() - 1)
TRACKS = TrackCollection()
TRACKS.addTrack(first_part)
TRACKS.addTrack(second_part)
return TRACKS
[docs]def splitReturnTripFast(track, side_effect=0.1, sampling=1):
"""Split track when there is a return trip to keep only the first part.
Second version with Fast Fourier Transform"""
track = track.copy()
track.toENUCoords(track.getFirstObs().position)
track_test = track.copy()
track_test.resample(
(track_test.length() / track_test.size()) / sampling, ALGO_LINEAR, MODE_SPATIAL
)
H = np.fft.fft(track_test.getY())
G = np.fft.fft(track_test.getY()[::-1])
temp = np.flip(np.abs(np.fft.ifft(H * np.conj(G))))
id = np.argmax(
temp[int(side_effect * len(temp)) : int((1 - side_effect) * len(temp))]
)
pt = track_test[id].position
dmin = 1e300
argmin = 0
for i in range(track.size()):
d = track[i].position.distance2DTo(pt)
if d < dmin:
dmin = d
argmin = i
first_part = track.extract(0, argmin - 1)
second_part = track.extract(argmin, track.size() - 1)
TRACKS = TrackCollection()
TRACKS.addTrack(first_part)
TRACKS.addTrack(second_part)
return TRACKS
# -------------------------------------------------------------------------
# Generic method to segment a track with dynamic programming
# -------------------------------------------------------------------------
# Inputs:
# - track: A track to segment (potentially with analytical features)
# - cost: a three or four-valued function taking as input a track, two
# integers i < j and an optional global parameter, and returning
# the cost of a segment from i to j (both included) in track
# Note that cost function may as well be considered as a reward
# function, simply by setting mode to MODE_SEGMENTATION_MAXIMIZE
# - mode: a parameter inidcating wether cost function must be minnimized
# (MODE_SEGMENTATION_MINIMIZE) or maximized (MODE_SEGMENTATION_MAXIMIZE)
# - verbose: parameter to enable progress bar and console displays
# Output:
# - A optimal segmentation, represented as a list of indices in track
# -------------------------------------------------------------------------
[docs]def optimalPartition(cost_matrix, mode=MODE_SEGMENTATION_MINIMIZE, verbose=True):
N = cost_matrix.shape[0] - 1
D = np.zeros((N, N))
M = np.zeros((N, N))
for i in range(N):
for j in range(i, N):
D[i, j] = cost_matrix[i, j]
M[i, j] = -1
# ---------------------------------------------------------------------------
# Computes optimal partition with dynamic programing
# ---------------------------------------------------------------------------
RANGE = range(2, N)
if verbose:
print("Optimal split search:")
RANGE = progressbar.progressbar(RANGE)
for diag in RANGE:
for i in range(0, N - diag):
j = i + diag
for k in range(i + 1, j):
val = D[i, k] + D[k, j]
if val < D[i, j] and MODE_SEGMENTATION_MINIMIZE:
D[i, j] = val
M[i, j] = k
if val > D[i, j] and MODE_SEGMENTATION_MAXIMIZE:
D[i, j] = val
M[i, j] = k
# ---------------------------------------------------------------------------
# Backward phase to form optimal split
# ---------------------------------------------------------------------------
return backward(M)
[docs]def optimalSegmentation(
track, cost, glob_param=None, mode=MODE_SEGMENTATION_MINIMIZE, verbose=True
):
# ---------------------------------------------------------------------------
# Computes cost matrix as :
# Cij = width of MBR of point set pi, pi+1, ... pj-1
# ---------------------------------------------------------------------------
C = np.zeros((track.size(), track.size()))
RANGE = range(track.size() - 2)
if verbose:
print("Cost matrix computation:")
RANGE = progressbar.progressbar(RANGE)
for i in RANGE:
for j in range(i, track.size() - 1):
if glob_param is None:
C[i, j] = cost(track, i, j - 1)
else:
C[i, j] = cost(track, i, j - 1, glob_param)
C = C + np.transpose(C)
return optimalPartition(C, mode, verbose)
# -------------------------------------------------------------------------
# Method to split a track between two mark points
# -------------------------------------------------------------------------
# Inputs:
# - track : a track to segment
# - pt1 : first point 'departure'
# - pt2 : second point 'return'
# - radius : threshold distance (in ground units) around pt1 and pt2
# - nb_min_pts : minima number of points to form a track
# If pt2 is not provided, it is set automatically equal to pt1
# Output:
# - a track collection containing segmented tracks
# -------------------------------------------------------------------------
[docs]def splitAR(track, pt1, pt2=None, radius=10, nb_min_pts=10, verbose=True):
if pt2 is None:
pt2 = pt1
tracks = TrackCollection()
subtrack = Track()
k = -1
while k < len(track) - 1:
k = k + 1
if (
min(
track[k].position.distance2DTo(pt1), track[k].position.distance2DTo(pt2)
)
< radius
):
if len(subtrack) > nb_min_pts:
tracks.addTrack(subtrack)
if verbose:
print(
"Add sub-track: ",
subtrack[0].timestamp,
subtrack[-1].timestamp,
"[" + str(len(tracks)) + "]",
)
subtrack = Track()
subtrack.addObs(track[k].copy())
if len(subtrack) > nb_min_pts:
tracks.addTrack(subtrack)
if verbose:
print(
"Add sub-track: ",
subtrack[0].timestamp,
subtrack[-1].timestamp,
"[" + str(len(tracks)) + "]",
)
return tracks
[docs]def stdbscan(track, eps1, eps2, minPts, deltaT):
'''
Birant, D., & Kut, A. (2007). ST-DBSCAN: An algorithm for clustering
spatial–temporal data. Data & Knowledge Engineering, 60(1), 208-221.
Parameters
----------
track : TYPE
DESCRIPTION.
eps1 : TYPE
DESCRIPTION.
eps2 : TYPE
DESCRIPTION.
minPts : TYPE
DESCRIPTION.
deltaT : float
threshold value to be included in a cluster.
Returns
-------
Cluster in AF
'''
stack = []
cluster_label = 0
track.createAnalyticalFeature('stdbscan', -1)
track.createAnalyticalFeature('noise', -1)
for i, obs in enumerate(track):
nocluster = track.getObsAnalyticalFeature('stdbscan', i)
if nocluster == -1:
neighbors_index = retrieveNeighbors(track, i, eps1, eps2)
#print (len(neighbors_index))
if len(neighbors_index) < minPts:
track.setObsAnalyticalFeature('noise', i, 1)
else:
cluster_label += 1
for k in neighbors_index:
track.setObsAnalyticalFeature('stdbscan', k, cluster_label)
for idx in neighbors_index:
stack.append(idx)
while len(stack) > 0:
io = stack[0]
stack.remove(io)
neighbors_index2 = retrieveNeighbors(track, io, eps1, eps2)
if len(neighbors_index2) >= minPts:
for k in neighbors_index2:
nonoise = track.getObsAnalyticalFeature('noise', k)
nocluster = track.getObsAnalyticalFeature('stdbscan', k)
if nonoise > -1 or nocluster == -1:
track.setObsAnalyticalFeature('stdbscan', k, cluster_label)
[docs]def retrieveNeighbors(track, j, eps1, eps2):
neighbors_index = []
for i in range (track.size()):
if i == j:
continue
dd = track.getObs(j).distanceTo(track.getObs(i))
dt = abs(track.getObs(j).timestamp.toAbsTime() - track.getObs(i).timestamp.toAbsTime())
if dd <= eps1 and dt <= eps2:
neighbors_index.append(i)
return neighbors_index
#def standardizing(neigh_ipm, ipm_cluster):
# return (neigh_ipm - ipm_cluster.mean()) / ipm_cluster.std(ddof=0)
# =============================================================================
# =============================================================================
from tracklib.algo.Analytics import speed, acceleration
[docs]def stop_point_with_acceleration_criteria(track, i):
"""
This algorithm detect stop point.
A point is a stop when speed is null and acceleration is negative.
"""
if i == 0:
return 0
stop_point = 0
v = speed(track, i)
acc = acceleration(track, i)
# Si un point d'indice [i] affiche une vitesse nulle suivant une deccelération,
# on cherche le prochain point d'accélération
if abs(v) < 3 and acc < 0:
# Initialisation d'un compteur sur i
j = i
# Tant qu'aucun des points suivants n'accélère, on ne marque pas le point d'arrêt
while j <= track.size() - 2 and acceleration(track, j) <= 0:
j += 1
# Si on trouve un point d'accélération, on donne la valeur 1
# au paramètre du point d'indice [i]
if acceleration(track, j) > 0:
stop_point = 1
return stop_point
VAL_AF_TIME_WINDOW_STOP = 1
VAL_AF_TIME_WINDOW_MOVE = 0
VAL_AF_TIME_WINDOW_NONE = -1
[docs]def stop_point_with_time_window_criteria(trace, i):
"""This algorithm of stop detection is based on geographical moving distance in time windows.
The AF has value:
- stop (*1*)
- not stop (*0*)
- not yet examined (*-1*)
"""
name_af = "stop_point_with_time_window_criteria"
N = trace.size()
val = trace.getObsAnalyticalFeature(name_af, i)
if val > -1:
return val
if i == N - 1:
return trace.getObsAnalyticalFeature(name_af, N - 2)
T = 15 # fenetre de 45s
D = 30 # 30 metres
S = trace.getAnalyticalFeature("abs_curv")
j = i + 1
ispause = False
tj = trace.getObs(j).timestamp
ti = trace.getObs(i).timestamp
# On cherche la fin de la fenetre
while (tj - ti) <= T:
j = j + 1
if j == N - 1:
break
tj = trace.getObs(j).timestamp
#print(S[j] - S[i])
# On agrandit la fenetre
while (tj - ti) >= T and (S[j] - S[i]) <= D:
ispause = True
# print ('pause ' + str(i) + ',' + str(j))
j = j + 1
if j == N - 1:
break
tj = trace.getObs(j).timestamp
retour = VAL_AF_TIME_WINDOW_MOVE
if ispause:
# PAUSES.append([i, j-1])
#print ('pause de ' + str(i) + ',' + str(j-1))
retour = VAL_AF_TIME_WINDOW_STOP
for k in range(i, j - 1):
trace.setObsAnalyticalFeature(name_af, k, VAL_AF_TIME_WINDOW_STOP)
else:
trace.setObsAnalyticalFeature(name_af, i, VAL_AF_TIME_WINDOW_MOVE)
return retour