Skip to content

Commit c7382ad

Browse files
committed
Add ExtractStopsAlgorithm
1 parent ca32129 commit c7382ad

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

qgis_processing/extractPtsAlgorithm.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
from qgis.core import (
88
QgsProcessingParameterExtent,
99
QgsProcessingParameterVectorLayer,
10+
QgsProcessingParameterNumber,
11+
QgsProcessingParameterString,
1012
QgsProcessingParameterFeatureSink,
1113
QgsProcessing,
1214
QgsWkbTypes,
1315
QgsField,
1416
QgsFeatureSink,
17+
QgsFields,
1518
)
1619
from qgis.core import QgsMessageLog, Qgis
1720

21+
import pandas as pd
22+
from movingpandas import TrajectoryStopDetector
23+
1824
sys.path.append("..")
1925

2026
from .trajectoriesAlgorithm import TrajectoriesAlgorithm
@@ -114,3 +120,99 @@ def processTc(self, tc, parameters, context):
114120
for _, row in gdf.iterrows():
115121
f = feature_from_gdf_row(row)
116122
self.sink_dest.addFeature(f, QgsFeatureSink.FastInsert)
123+
124+
125+
class ExtractStopsAlgorithm(TrajectoriesAlgorithm):
126+
MAX_DIAMETER = "MAX_DIAMETER"
127+
MIN_DURATION = "MIN_DURATION"
128+
STOP_PTS = "STOP_PTS"
129+
130+
def __init__(self):
131+
super().__init__()
132+
133+
def initAlgorithm(self, config=None):
134+
super().initAlgorithm(config)
135+
self.addParameter(
136+
QgsProcessingParameterNumber(
137+
name=self.MAX_DIAMETER,
138+
description=self.tr("Max stop diameter (meters)"),
139+
defaultValue=30,
140+
optional=False,
141+
)
142+
)
143+
self.addParameter(
144+
QgsProcessingParameterString(
145+
name=self.MIN_DURATION,
146+
description=self.tr(
147+
"Min stop duration (timedelta, e.g. 1 hours, 15 minutes)"
148+
),
149+
defaultValue="2 minutes",
150+
optional=False,
151+
)
152+
)
153+
self.addParameter(
154+
QgsProcessingParameterFeatureSink(
155+
name=self.STOP_PTS,
156+
description=self.tr("Stop points"),
157+
type=QgsProcessing.TypeVectorPoint,
158+
)
159+
)
160+
161+
def group(self):
162+
return self.tr("Basic")
163+
164+
def groupId(self):
165+
return "TrajectoryBasic"
166+
167+
def name(self):
168+
return "extract_stop_pts"
169+
170+
def displayName(self):
171+
return self.tr("Extract stop points")
172+
173+
def shortHelpString(self):
174+
return self.tr("<p>Extracts stop points from trajectories.</p>")
175+
176+
def processAlgorithm(self, parameters, context, feedback):
177+
tc, crs = self.create_tc(parameters, context)
178+
179+
self.fields_pts = QgsFields()
180+
self.fields_pts.append(QgsField("stop_id", QVariant.String))
181+
self.fields_pts.append(QgsField("start_time", QVariant.String)) # .DateTime))
182+
self.fields_pts.append(QgsField("end_time", QVariant.String)) # .DateTime))
183+
self.fields_pts.append(QgsField("traj_id", QVariant.String))
184+
self.fields_pts.append(QgsField("duration_s", QVariant.Double))
185+
186+
(self.sink, self.stop_pts) = self.parameterAsSink(
187+
parameters,
188+
self.STOP_PTS,
189+
context,
190+
self.fields_pts,
191+
QgsWkbTypes.Point,
192+
crs,
193+
)
194+
195+
self.processTc(tc, parameters, context)
196+
197+
return {self.STOP_PTS: self.stop_pts}
198+
199+
def processTc(self, tc, parameters, context):
200+
max_diameter = self.parameterAsDouble(parameters, self.MAX_DIAMETER, context)
201+
min_duration = self.parameterAsString(parameters, self.MIN_DURATION, context)
202+
min_duration = pd.Timedelta(min_duration).to_pytimedelta()
203+
204+
gdf = TrajectoryStopDetector(tc).get_stop_points(
205+
max_diameter=max_diameter, min_duration=min_duration
206+
)
207+
gdf = gdf.convert_dtypes()
208+
gdf["stop_id"] = gdf.index.astype(str)
209+
gdf["start_time"] = gdf["start_time"].astype(str)
210+
gdf["end_time"] = gdf["end_time"].astype(str)
211+
212+
names = [field.name() for field in self.fields_pts]
213+
names.append("geometry")
214+
gdf = gdf[names]
215+
216+
for _, row in gdf.iterrows():
217+
f = feature_from_gdf_row(row)
218+
self.sink.addFeature(f, QgsFeatureSink.FastInsert)

qgis_processing/splitTrajectoriesAlgorithm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def initAlgorithm(self, config=None):
139139
QgsProcessingParameterNumber(
140140
name=self.MAX_DIAMETER,
141141
description=self.tr("Max stop diameter (meters)"),
142-
defaultValue=15,
142+
defaultValue=30,
143143
optional=False,
144144
)
145145
)
@@ -149,7 +149,7 @@ def initAlgorithm(self, config=None):
149149
description=self.tr(
150150
"Min stop duration (timedelta, e.g. 1 hours, 15 minutes)"
151151
),
152-
defaultValue="15 minutes",
152+
defaultValue="2 minutes",
153153
optional=False,
154154
)
155155
)

qgis_processing/trajectoolsProvider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
ClipTrajectoriesByExtentAlgorithm,
1919
ClipTrajectoriesByPolygonLayer,
2020
)
21-
from .extractPtsAlgorithm import ExtractODPtsAlgorithm
21+
from .extractPtsAlgorithm import ExtractODPtsAlgorithm, ExtractStopsAlgorithm
2222

2323
pluginPath = os.path.dirname(__file__)
2424

@@ -59,6 +59,7 @@ def getAlgs(self):
5959
ClipTrajectoriesByExtentAlgorithm(),
6060
ClipTrajectoriesByPolygonLayer(),
6161
ExtractODPtsAlgorithm(),
62+
ExtractStopsAlgorithm(),
6263
]
6364
return algs
6465

0 commit comments

Comments
 (0)