Skip to content

Commit 31c5e08

Browse files
authored
Merge pull request #28 from movingpandas/emeralds
Add Emeralds contributions
2 parents e4d2fb3 + 5192d8a commit 31c5e08

12 files changed

+969
-184
lines changed

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,34 @@
22

33
The Trajectools plugin adds trajectory analysis algorithms to the QGIS Processing toolbox.
44

5-
![Trajectools screenshot](screenshots/trajectools.PNG)
6-
![Trajectools clipping screenshot](screenshots/trajectools2.PNG)
75

86

9-
**Note: This plugin depends on MovingPandas!** You will need to install MovingPandas in your QGIS Python environment. I recommend installing both QGIS and MovingPandas from conda-forge:
7+
## Requirements
8+
9+
Trajectools requires [MovingPandas](https://github.com/movingpandas/movingpandas) (a Python library for movement data analysis) and optionally integrates [scikit-mobility](https://scikit-mobility.github.io/scikit-mobility/) and [gtfs_functions](https://github.com/Bondify/gtfs_functions).
10+
11+
The recommended way to install these dependencies is through conda/mamba:
1012

1113
```
1214
(base) conda create -n qgis -c conda-forge python=3.9
1315
(base) conda activate qgis
14-
(qgis) mamba install -c conda-forge qgis movingpandas
16+
(qgis) mamba install -c conda-forge qgis movingpandas scikit-mobility
17+
(qgis) pip install gtfs_functions
1518
```
1619

17-
More details: https://anitagraser.com/2023/01/21/pyqgis-jupyter-notebooks-on-windows-using-conda/
20+
(More details: https://anitagraser.com/2023/01/21/pyqgis-jupyter-notebooks-on-windows-using-conda/)
21+
22+
The Trajectools plugin can be installed directly in QGIS using the built-in Plugin Manager:
23+
24+
![image](https://github.com/emeralds-horizon/UC3-traveltime-analytics/assets/590385/9f6cdb53-f2b3-4f2f-82cf-923d3b61341f)
1825

26+
**Figure 1: QGIS Plugin Manager with Trajectools plugin installed.**
1927

28+
29+
30+
## Examples
31+
32+
The individual Trajectools algorithms are flexible and modular and can therefore be used on a wide array on input datasets, including, for example, the open [Microsoft Geolife dataset](http://research.microsoft.com/en-us/downloads/b16d359d-d164-469e-9fd4-daa38f2b2e13/) a [sample](https://github.com/emeralds-horizon/trajectools-qgis/tree/main/sample_data) of which is included in the plugin repo:
33+
34+
![Trajectools screenshot](screenshots/trajectools.PNG)
35+
![Trajectools clipping screenshot](screenshots/trajectools2.PNG)

icons/skmob.png

11.1 KB
Loading

qgis_processing/createTrajectoriesAlgorithm.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import sys
22

3-
from qgis.PyQt.QtCore import QCoreApplication
4-
53
sys.path.append("..")
64

75
from .trajectoriesAlgorithm import TrajectoryManipulationAlgorithm

qgis_processing/gtfsAlgorithm.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import os
2+
import sys
3+
4+
from qgis.PyQt.QtCore import QCoreApplication, QVariant
5+
from qgis.PyQt.QtGui import QIcon
6+
from qgis.core import (
7+
QgsProcessing,
8+
QgsProcessingAlgorithm,
9+
QgsProcessingParameterFeatureSink,
10+
QgsProcessingParameterFile,
11+
QgsProcessingParameterField,
12+
QgsProcessingUtils,
13+
QgsCoordinateReferenceSystem,
14+
QgsWkbTypes,
15+
QgsProcessingParameterString,
16+
QgsProcessingParameterBoolean,
17+
QgsField,
18+
QgsFields,
19+
QgsFeature,
20+
QgsGeometry,
21+
QgsFeatureSink,
22+
)
23+
24+
try:
25+
from gtfs_functions import Feed
26+
except ImportError as error:
27+
raise ImportError(
28+
"Missing optional dependencies. To use the GTFS algorithms please "
29+
"install gtfs_functions. For details see: "
30+
"https://github.com/Bondify/gtfs_functions."
31+
) from error
32+
33+
sys.path.append("..")
34+
35+
from .qgisUtils import tc_from_pt_layer, feature_from_gdf_row, df_from_pt_layer
36+
37+
pluginPath = os.path.dirname(__file__)
38+
39+
40+
41+
class GtfsShapesAlgorithm(QgsProcessingAlgorithm):
42+
INPUT = "INPUT"
43+
SPEED_OPTION = "SPEED"
44+
OUTPUT = "OUTPUT"
45+
46+
def __init__(self):
47+
super().__init__()
48+
49+
def name(self):
50+
return "gtfs_shapes"
51+
52+
def displayName(self):
53+
return self.tr("Extract shapes")
54+
55+
def group(self):
56+
return self.tr("GTFS")
57+
58+
def groupId(self):
59+
return "Gtfs"
60+
61+
def tr(self, text):
62+
return QCoreApplication.translate("trajectools", text)
63+
64+
def helpUrl(self):
65+
return "https://github.com/Bondify/gtfs_functions"
66+
67+
def shortHelpString(self):
68+
return self.tr(
69+
"<p>Extracts shapes from a GTFS ZIP file using "
70+
"gtfs_functions.Feed.shapes</p>"
71+
)
72+
73+
def createInstance(self):
74+
return type(self)()
75+
76+
def initAlgorithm(self, config=None):
77+
self.addParameter(
78+
QgsProcessingParameterFile(
79+
name=self.INPUT,
80+
description=self.tr("Input GTFS file"),
81+
)
82+
)
83+
self.addParameter(
84+
QgsProcessingParameterFeatureSink(
85+
name=self.OUTPUT,
86+
description=self.tr("GTFS shapes"),
87+
type=QgsProcessing.TypeVectorLine,
88+
)
89+
)
90+
91+
def processAlgorithm(self, parameters, context, feedback):
92+
gtfs_file = self.parameterAsFile(parameters, self.INPUT, context)
93+
(self.sink_shapes, self.dest_shapes) = self.parameterAsSink(
94+
parameters,
95+
self.OUTPUT,
96+
context,
97+
self.get_fields(),
98+
QgsWkbTypes.LineStringM,
99+
QgsCoordinateReferenceSystem("EPSG:4326"),
100+
)
101+
102+
feed = Feed(gtfs_file)
103+
segments = feed.shapes
104+
for _, shape in segments.iterrows():
105+
line = QgsGeometry.fromWkt(shape.geometry.wkt)
106+
f = QgsFeature()
107+
f.setGeometry(line)
108+
attrs = [
109+
shape.shape_id
110+
]
111+
f.setAttributes(attrs)
112+
self.sink_shapes.addFeature(f, QgsFeatureSink.FastInsert)
113+
114+
return {self.OUTPUT: self.dest_shapes}
115+
116+
def get_fields(self):
117+
fields = QgsFields()
118+
fields.append(QgsField("shape_id", QVariant.String))
119+
return fields
120+
121+
122+
123+
class GtfsSegmentsAlgorithm(QgsProcessingAlgorithm):
124+
INPUT = "INPUT"
125+
SPEED_OPTION = "SPEED"
126+
OUTPUT = "OUTPUT"
127+
128+
def __init__(self):
129+
super().__init__()
130+
131+
def name(self):
132+
return "gtfs_segments"
133+
134+
def displayName(self):
135+
return self.tr("Extract segments")
136+
137+
def group(self):
138+
return self.tr("GTFS")
139+
140+
def groupId(self):
141+
return "Gtfs"
142+
143+
def tr(self, text):
144+
return QCoreApplication.translate("trajectools", text)
145+
146+
def helpUrl(self):
147+
return "https://github.com/Bondify/gtfs_functions"
148+
149+
def shortHelpString(self):
150+
return self.tr(
151+
"<p>Extracts segments from a GTFS ZIP file using "
152+
"gtfs_functions.Feed.segments</p>"
153+
"<p>Optionally adds scheduled average speeds using"
154+
"gtfs_functions.Feed.avg_speeds</p>"
155+
)
156+
157+
def createInstance(self):
158+
return type(self)()
159+
160+
def initAlgorithm(self, config=None):
161+
self.addParameter(
162+
QgsProcessingParameterFile(
163+
name=self.INPUT,
164+
description=self.tr("Input GTFS file"),
165+
)
166+
)
167+
self.addParameter(
168+
QgsProcessingParameterBoolean(
169+
name=self.SPEED_OPTION,
170+
description=self.tr("Add scheduled speeds"),
171+
)
172+
)
173+
self.addParameter(
174+
QgsProcessingParameterFeatureSink(
175+
name=self.OUTPUT,
176+
description=self.tr("GTFS segments"),
177+
type=QgsProcessing.TypeVectorLine,
178+
)
179+
)
180+
181+
def processAlgorithm(self, parameters, context, feedback):
182+
gtfs_file = self.parameterAsFile(parameters, self.INPUT, context)
183+
add_avg_speed = self.parameterAsBool(parameters, self.SPEED_OPTION, context)
184+
(self.sink_segments, self.dest_segments) = self.parameterAsSink(
185+
parameters,
186+
self.OUTPUT,
187+
context,
188+
self.get_fields(add_avg_speed),
189+
QgsWkbTypes.LineStringM,
190+
QgsCoordinateReferenceSystem("EPSG:4326"),
191+
)
192+
193+
feed = Feed(gtfs_file)
194+
segments = self.get_segments(feed, add_avg_speed)
195+
for _, segment in segments.iterrows():
196+
line = QgsGeometry.fromWkt(segment.geometry.wkt)
197+
f = QgsFeature()
198+
f.setGeometry(line)
199+
attrs = [
200+
segment.shape_id,
201+
segment.route_id,
202+
segment.route_name,
203+
segment.direction_id,
204+
segment.stop_sequence,
205+
segment.segment_name,
206+
segment.start_stop_name,
207+
segment.end_stop_name,
208+
segment.segment_id,
209+
segment.start_stop_id,
210+
segment.end_stop_id,
211+
segment.distance_m,
212+
]
213+
if add_avg_speed:
214+
attrs = attrs + [
215+
segment.window,
216+
segment.speed_kmh,
217+
segment.avg_route_speed_kmh,
218+
segment.segment_max_speed_kmh,
219+
segment.runtime_sec,
220+
]
221+
f.setAttributes(attrs)
222+
self.sink_segments.addFeature(f, QgsFeatureSink.FastInsert)
223+
224+
return {self.OUTPUT: self.dest_segments}
225+
226+
def get_segments(self, feed, add_avg_speed=False):
227+
if add_avg_speed:
228+
segments = feed.avg_speeds
229+
else:
230+
segments = feed.segments
231+
return segments
232+
233+
def get_fields(self, add_avg_speed):
234+
fields = QgsFields()
235+
fields.append(QgsField("shape_id", QVariant.String))
236+
fields.append(QgsField("route_id", QVariant.String))
237+
fields.append(QgsField("route_name", QVariant.String))
238+
fields.append(QgsField("direction_id", QVariant.String))
239+
fields.append(QgsField("stop_sequence", QVariant.Int))
240+
fields.append(QgsField("segment_name", QVariant.String))
241+
fields.append(QgsField("start_stop_name", QVariant.String))
242+
fields.append(QgsField("end_stop_name", QVariant.String))
243+
fields.append(QgsField("segment_id", QVariant.String))
244+
fields.append(QgsField("start_stop_id", QVariant.String))
245+
fields.append(QgsField("end_stop_id", QVariant.String))
246+
fields.append(QgsField("distance_m", QVariant.Double))
247+
if add_avg_speed:
248+
fields.append(QgsField("window", QVariant.String))
249+
fields.append(QgsField("speed_kmh", QVariant.Double))
250+
fields.append(QgsField("avg_route_speed_kmh", QVariant.Double))
251+
fields.append(QgsField("segment_max_speed_kmh", QVariant.Double))
252+
fields.append(QgsField("runtime_sec", QVariant.Double))
253+
return fields

qgis_processing/icons/skmob.png

11.1 KB
Loading

0 commit comments

Comments
 (0)