Skip to content

Commit 394ecff

Browse files
committed
Add IntersectWithPolygonLayerAlgorithm
1 parent 16bc460 commit 394ecff

File tree

3 files changed

+144
-25
lines changed

3 files changed

+144
-25
lines changed

qgis_processing/overlayAlgorithm.py

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import shapely.wkt
44
from shapely.geometry import Polygon
55

6-
from qgis.PyQt.QtCore import QCoreApplication
6+
from qgis.PyQt.QtCore import QVariant
77
from qgis.core import (
88
QgsProcessingParameterExtent,
99
QgsProcessingParameterVectorLayer,
10+
QgsWkbTypes,
11+
QgsField,
1012
)
1113

1214
sys.path.append("..")
@@ -68,8 +70,8 @@ def processTc(self, tc, parameters, context):
6870
self.traj_to_sink(traj)
6971

7072

71-
class ClipTrajectoriesByPolygonLayer(OverlayTrajectoriesAlgorithm):
72-
CLIP_LAYER = "CLIP_LAYER"
73+
class ClipTrajectoriesByPolygonLayerAlgorithm(OverlayTrajectoriesAlgorithm):
74+
OVERLAY_LAYER = "OVERLAY_LAYER"
7375

7476
def __init__(self):
7577
super().__init__()
@@ -78,7 +80,7 @@ def initAlgorithm(self, config=None):
7880
super().initAlgorithm(config)
7981
self.addParameter(
8082
QgsProcessingParameterVectorLayer(
81-
name=self.CLIP_LAYER,
83+
name=self.OVERLAY_LAYER,
8284
description=self.tr("Overlay layer"),
8385
optional=False,
8486
)
@@ -105,10 +107,105 @@ def helpUrl(self):
105107
return "https://movingpandas.org/units"
106108

107109
def processTc(self, tc, parameters, context):
108-
vlayer = self.parameterAsVectorLayer(parameters, self.CLIP_LAYER, context)
110+
vlayer = self.parameterAsVectorLayer(parameters, self.OVERLAY_LAYER, context)
109111
for feature in vlayer.getFeatures():
110-
extent = shapely.wkt.loads(feature.geometry().asWkt())
111-
clipped = tc.clip(extent)
112+
shapely_feature = shapely.wkt.loads(feature.geometry().asWkt())
113+
clipped = tc.clip(shapely_feature)
112114
self.tc_to_sink(clipped)
113115
for traj in clipped:
114116
self.traj_to_sink(traj)
117+
118+
119+
class IntersectWithPolygonLayerAlgorithm(OverlayTrajectoriesAlgorithm):
120+
OVERLAY_LAYER = "OVERLAY_LAYER"
121+
122+
def __init__(self):
123+
super().__init__()
124+
125+
def initAlgorithm(self, config=None):
126+
super().initAlgorithm(config)
127+
self.addParameter(
128+
QgsProcessingParameterVectorLayer(
129+
name=self.OVERLAY_LAYER,
130+
description=self.tr("Overlay layer"),
131+
optional=False,
132+
)
133+
)
134+
135+
def name(self):
136+
return "intersect_traj_vector"
137+
138+
def displayName(self):
139+
return self.tr("Intersect trajectories with polygon layer")
140+
141+
def shortHelpString(self):
142+
return self.tr(
143+
"<p>Creates a trajectory point layers with speed and direction information "
144+
"as well as a trajectory line layer which ihntersects the specified vector layer.</p>"
145+
"<p><b>Speed</b> is calculated based on the input layer CRS information and "
146+
"converted to the desired speed units. For more info on the supported units, "
147+
"see https://movingpandas.org/units</p>"
148+
"<p><b>Direction</b> is calculated between consecutive locations. Direction "
149+
"values are in degrees, starting North turning clockwise.</p>"
150+
)
151+
152+
def helpUrl(self):
153+
return "https://movingpandas.org/units"
154+
155+
def setup_pt_sink(self, parameters, context, tc, crs):
156+
self.fields_pts = self.get_pt_fields(
157+
[
158+
QgsField(tc.get_speed_col(), QVariant.Double),
159+
QgsField(tc.get_direction_col(), QVariant.Double),
160+
],
161+
)
162+
163+
vlayer = self.parameterAsVectorLayer(parameters, self.OVERLAY_LAYER, context)
164+
for field in vlayer.fields():
165+
field.setName(f'intersecting_{field.name()}')
166+
self.fields_pts.append(field)
167+
168+
(self.sink_pts, self.dest_pts) = self.parameterAsSink(
169+
parameters,
170+
self.OUTPUT_PTS,
171+
context,
172+
self.fields_pts,
173+
QgsWkbTypes.Point,
174+
crs,
175+
)
176+
177+
def setup_traj_sink(self, parameters, context, crs):
178+
self.fields_trajs = self.get_traj_fields()
179+
180+
vlayer = self.parameterAsVectorLayer(parameters, self.OVERLAY_LAYER, context)
181+
for field in vlayer.fields():
182+
field.setName(f'intersecting_{field.name()}')
183+
self.fields_trajs.append(field)
184+
185+
(self.sink_trajs, self.dest_trajs) = self.parameterAsSink(
186+
parameters,
187+
self.OUTPUT_TRAJS,
188+
context,
189+
self.fields_trajs,
190+
QgsWkbTypes.LineStringM,
191+
crs,
192+
)
193+
194+
def processTc(self, tc, parameters, context):
195+
vlayer = self.parameterAsVectorLayer(parameters, self.OVERLAY_LAYER, context)
196+
layer_fields = vlayer.fields()
197+
field_names = [field.name() for field in layer_fields]
198+
field_names_to_add = [f'intersecting_{name}' for name in field_names]
199+
200+
for feature in vlayer.getFeatures():
201+
attrs = feature.attributes()
202+
203+
shapely_feature = {
204+
"geometry": shapely.wkt.loads(feature.geometry().asWkt()),
205+
"properties": dict(zip(field_names, attrs)),
206+
}
207+
intersecting = tc.intersection(shapely_feature)
208+
209+
self.tc_to_sink(intersecting, field_names_to_add=field_names_to_add)
210+
for traj in intersecting:
211+
self.traj_to_sink(traj, attr_first_to_add=field_names_to_add)

qgis_processing/trajectoolsProvider.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
)
1717
from .overlayAlgorithm import (
1818
ClipTrajectoriesByExtentAlgorithm,
19-
ClipTrajectoriesByPolygonLayer,
19+
ClipTrajectoriesByPolygonLayerAlgorithm,
20+
IntersectWithPolygonLayerAlgorithm,
2021
)
2122
from .extractPtsAlgorithm import ExtractODPtsAlgorithm, ExtractStopsAlgorithm
2223
from .privacyAttackAlgorithm import HomeWorkAttack
@@ -59,7 +60,8 @@ def getAlgs(self):
5960
TemporalSplitterAlgorithm(),
6061
StopSplitterAlgorithm(),
6162
ClipTrajectoriesByExtentAlgorithm(),
62-
ClipTrajectoriesByPolygonLayer(),
63+
ClipTrajectoriesByPolygonLayerAlgorithm(),
64+
IntersectWithPolygonLayerAlgorithm(),
6365
ExtractODPtsAlgorithm(),
6466
ExtractStopsAlgorithm(),
6567
]

qgis_processing/trajectoriesAlgorithm.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,26 @@ def initAlgorithm(self, config=None):
186186
def processAlgorithm(self, parameters, context, feedback):
187187
tc, crs = self.create_tc(parameters, context)
188188

189+
self.setup_pt_sink(parameters, context, tc, crs)
190+
191+
self.setup_traj_sink(parameters, context, crs)
192+
193+
self.processTc(tc, parameters, context)
194+
195+
return {self.OUTPUT_PTS: self.dest_pts, self.OUTPUT_TRAJS: self.dest_trajs}
196+
197+
def setup_traj_sink(self, parameters, context, crs):
198+
self.fields_trajs = self.get_traj_fields()
199+
(self.sink_trajs, self.dest_trajs) = self.parameterAsSink(
200+
parameters,
201+
self.OUTPUT_TRAJS,
202+
context,
203+
self.fields_trajs,
204+
QgsWkbTypes.LineStringM,
205+
crs,
206+
)
207+
208+
def setup_pt_sink(self, parameters, context, tc, crs):
189209
self.fields_pts = self.get_pt_fields(
190210
[
191211
QgsField(tc.get_speed_col(), QVariant.Double),
@@ -201,20 +221,6 @@ def processAlgorithm(self, parameters, context, feedback):
201221
crs,
202222
)
203223

204-
self.fields_trajs = self.get_traj_fields()
205-
(self.sink_trajs, self.dest_trajs) = self.parameterAsSink(
206-
parameters,
207-
self.OUTPUT_TRAJS,
208-
context,
209-
self.fields_trajs,
210-
QgsWkbTypes.LineStringM,
211-
crs,
212-
)
213-
214-
self.processTc(tc, parameters, context)
215-
216-
return {self.OUTPUT_PTS: self.dest_pts, self.OUTPUT_TRAJS: self.dest_trajs}
217-
218224
def processTc(self, tc, parameters, context):
219225
pass # needs to be implemented by each splitter
220226

@@ -241,7 +247,7 @@ def get_traj_fields(self, fields_to_add=[]):
241247
fields.append(field)
242248
return fields
243249

244-
def traj_to_sink(self, traj, attr_mean_to_add=[]):
250+
def traj_to_sink(self, traj, attr_mean_to_add=[], attr_first_to_add=[]):
245251
line = QgsGeometry.fromWkt(traj.to_linestringm_wkt())
246252
f = QgsFeature()
247253
f.setGeometry(line)
@@ -253,16 +259,30 @@ def traj_to_sink(self, traj, attr_mean_to_add=[]):
253259
attrs = [traj.id, start_time, end_time, duration, length, speed]
254260
for a in attr_mean_to_add:
255261
attrs.append(float(traj.df[a].mean()))
262+
for a in attr_first_to_add:
263+
try:
264+
attrs.append(float(traj.df[a].iloc[0]))
265+
continue
266+
except:
267+
pass
268+
try:
269+
attrs.append(int(traj.df[a].iloc[0]))
270+
continue
271+
except:
272+
pass
273+
attrs.append(traj.df[a].iloc[0])
256274
f.setAttributes(attrs)
257275
self.sink_trajs.addFeature(f, QgsFeatureSink.FastInsert)
258276

259-
def tc_to_sink(self, tc):
277+
def tc_to_sink(self, tc, field_names_to_add=[]):
260278
try:
261279
gdf = tc.to_point_gdf()
262280
except ValueError: # when the tc is empty
263281
return
264282
gdf[self.timestamp_field] = gdf.index.astype(str)
265283
names = [field.name() for field in self.fields_pts]
284+
for field_name in field_names_to_add:
285+
names.append(field_name)
266286
names.append("geometry")
267287
gdf = gdf[names]
268288

0 commit comments

Comments
 (0)