"""
Stop point detection operations.
create_or_update_move_stop_by_dist_time,
create_or_update_move_and_stop_by_radius
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from pymove.preprocessing.segmentation import by_max_dist
from pymove.utils.constants import (
DIST_TO_PREV,
MOVE,
SEGMENT_STOP,
SITUATION,
STOP,
TIME_TO_PREV,
TRAJ_ID,
)
from pymove.utils.log import logger, timer_decorator
if TYPE_CHECKING:
from pymove.core.dask import DaskMoveDataFrame
from pymove.core.pandas import PandasMoveDataFrame
[docs]@timer_decorator
def create_or_update_move_stop_by_dist_time(
move_data: 'PandasMoveDataFrame' | 'DaskMoveDataFrame',
dist_radius: float = 30,
time_radius: float = 900,
label_id: str = TRAJ_ID,
new_label: str = SEGMENT_STOP,
inplace: bool = False
) -> 'PandasMoveDataFrame' | 'DaskMoveDataFrame' | None:
"""
Determines the stops and moves points of the dataframe.
If these points already exist, they will be updated.
Parameters
----------
move_data : dataframe
The input trajectory data
dist_radius : float, optional
The first step in this function is segmenting the trajectory
The segments are used to find the stop points
The dist_radius defines the distance used in the segmentation,
by default 30
time_radius : float, optional
The time_radius used to determine if a segment is a stop
If the user stayed in the segment for a time
greater than time_radius, than the segment is a stop,
by default 900
label_id : str, optional
Indicates the label of the id column in the user dataframe, by default TRAJ_ID
new_label : float, optional
Is the name of the column to indicates if a point is a stop of a move,
by default SEGMENT_STOP
inplace : bool, optional
if set to true the original dataframe will be altered to
contain the result of the filtering, otherwise a copy will be returned,
by default False
Returns
-------
DataFrame
DataFrame with 2 aditional features: segment_stop and stop.
segment_stop indicates the trajectory segment to which the point belongs
stop indicates if the point represents a stop.
"""
if not inplace:
move_data = move_data.copy()
by_max_dist(
move_data,
label_id=label_id,
max_dist_between_adj_points=dist_radius,
label_new_tid=new_label,
inplace=True
)
move_data.generate_dist_time_speed_features(
label_id=new_label
)
logger.debug('Create or update stop as True or False')
logger.debug(
'...Creating stop features as True or False using %s to time in seconds'
% time_radius
)
move_data[STOP] = False
move_dataagg_tid = (
move_data.groupby(by=new_label)
.agg({TIME_TO_PREV: 'sum'})
.query(f'{TIME_TO_PREV} > {time_radius}')
.index
)
idx = move_data[
move_data[new_label].isin(move_dataagg_tid)
].index
move_data.at[idx, STOP] = True
logger.debug(move_data[STOP].value_counts())
if not inplace:
return move_data
[docs]@timer_decorator
def create_or_update_move_and_stop_by_radius(
move_data: 'PandasMoveDataFrame' | 'DaskMoveDataFrame',
radius: float = 0,
target_label: str = DIST_TO_PREV,
new_label: str = SITUATION,
inplace: bool = False,
) -> 'PandasMoveDataFrame' | 'DaskMoveDataFrame' | None:
"""
Finds the stops and moves points of the dataframe.
If these points already exist, they will be updated.
Parameters
----------
move_data : dataframe
The input trajectory data
radius : float, optional
The radius value is used to determine if a segment is a stop.
If the value of the point in target_label is
greater than radius, the segment is a stop, otherwise it's a move,
by default 0
target_label : String, optional
The feature used to calculate the stay points, by default DIST_TO_PREV
new_label : String, optional
Is the name of the column to indicates if a point is a stop of a move,
by default SITUATION
inplace : bool, optional
if set to true the original dataframe will be altered to
contain the result of the filtering, otherwise a copy will be returned,
by default False
Returns
-------
DataFrame
dataframe with 2 aditional features: segment_stop and new_label.
segment_stop indicates the trajectory segment to which the point belongs
new_label indicates if the point represents a stop or moving point.
"""
logger.debug('\nCreating or updating features MOVE and STOPS...\n')
if not inplace:
move_data = move_data.copy()
if DIST_TO_PREV not in move_data:
move_data.generate_dist_features()
conditions = (
(move_data[target_label] > radius),
(move_data[target_label] <= radius),
)
choices = [MOVE, STOP]
move_data[new_label] = np.select(conditions, choices, np.nan)
logger.debug(
'\n....There are %s stops to this parameters\n'
% (move_data[move_data[new_label] == STOP].shape[0])
)
if not inplace:
return move_data