|
7 | 7 | from qgis.core import (
|
8 | 8 | QgsProcessingParameterExtent,
|
9 | 9 | QgsProcessingParameterVectorLayer,
|
| 10 | + QgsProcessingParameterNumber, |
| 11 | + QgsProcessingParameterString, |
10 | 12 | QgsProcessingParameterFeatureSink,
|
11 | 13 | QgsProcessing,
|
12 | 14 | QgsWkbTypes,
|
13 | 15 | QgsField,
|
14 | 16 | QgsFeatureSink,
|
| 17 | + QgsFields, |
15 | 18 | )
|
16 | 19 | from qgis.core import QgsMessageLog, Qgis
|
17 | 20 |
|
| 21 | +import pandas as pd |
| 22 | +from movingpandas import TrajectoryStopDetector |
| 23 | + |
18 | 24 | sys.path.append("..")
|
19 | 25 |
|
20 | 26 | from .trajectoriesAlgorithm import TrajectoriesAlgorithm
|
@@ -114,3 +120,99 @@ def processTc(self, tc, parameters, context):
|
114 | 120 | for _, row in gdf.iterrows():
|
115 | 121 | f = feature_from_gdf_row(row)
|
116 | 122 | 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) |
0 commit comments