|
1 | 1 | # -*- coding: utf-8 -*-
|
2 |
| - |
3 |
| -""" |
4 |
| -*************************************************************************** |
5 |
| - addMetersPerSecAlgorithm.py |
6 |
| - --------------------- |
7 |
| - Date : December 2018 |
8 |
| - Copyright : (C) 2018 by Anita Graser |
9 |
| - Email : anitagraser@gmx.at |
10 |
| -*************************************************************************** |
11 |
| -* * |
12 |
| -* This program is free software; you can redistribute it and/or modify * |
13 |
| -* it under the terms of the GNU General Public License as published by * |
14 |
| -* the Free Software Foundation; either version 2 of the License, or * |
15 |
| -* (at your option) any later version. * |
16 |
| -* * |
17 |
| -*************************************************************************** |
18 |
| -""" |
19 |
| - |
20 | 2 | import os
|
21 | 3 | import sys
|
22 | 4 |
|
|
37 | 19 | QgsProcessingParameterBoolean,
|
38 | 20 | QgsProcessingParameterFeatureSink,
|
39 | 21 | QgsProcessingParameterEnum,
|
40 |
| - QgsWkbTypes |
| 22 | + QgsWkbTypes, |
| 23 | + QgsProcessingUtils |
41 | 24 | )
|
42 | 25 |
|
43 | 26 | sys.path.append("..")
|
|
47 | 30 | pluginPath = os.path.dirname(__file__)
|
48 | 31 |
|
49 | 32 |
|
50 |
| -class AddMetersPerSecAlgorithm(QgsProcessingAlgorithm): |
| 33 | +class CreateTrajectoriesAlgorithm(QgsProcessingAlgorithm): |
51 | 34 | # script parameters
|
52 | 35 | INPUT = 'INPUT'
|
53 | 36 | TRAJ_ID_FIELD = 'OBJECT_ID_FIELD'
|
54 | 37 | TIMESTAMP_FIELD = 'TIMESTAMP_FIELD'
|
55 | 38 | TIMESTAMP_FORMAT = 'TIMESTAMP_FORMAT'
|
56 |
| - OUTPUT = 'OUTPUT' |
| 39 | + OUTPUT_PTS = 'OUTPUT_PTS' |
| 40 | + OUTPUT_SEGS = 'OUTPUT_SEGS' |
| 41 | + OUTPUT_TRAJS = 'OUTPUT_TRAJS' |
| 42 | + SPEED_UNIT = 'SPEED_UNIT' |
57 | 43 |
|
58 | 44 | def __init__(self):
|
59 | 45 | super().__init__()
|
60 | 46 |
|
61 | 47 | def name(self):
|
62 |
| - return "add_meters_per_sec" |
| 48 | + return "create_trajectory" |
63 | 49 |
|
64 | 50 | def icon(self):
|
65 | 51 | return QIcon(os.path.join(pluginPath, "icons", "icon.png"))
|
66 | 52 |
|
67 | 53 | def tr(self, text):
|
68 |
| - return QCoreApplication.translate("add_meters_per_sec", text) |
| 54 | + return QCoreApplication.translate("create_trajectory", text) |
69 | 55 |
|
70 | 56 | def displayName(self):
|
71 |
| - return self.tr("Add speed (m/s) to points") |
| 57 | + return self.tr("Create trajectories") |
72 | 58 |
|
73 |
| - def group(self): |
74 |
| - return self.tr("Basic") |
| 59 | + #def group(self): |
| 60 | + # return self.tr("Basic") |
75 | 61 |
|
76 |
| - def groupId(self): |
77 |
| - return "TrajectoryBasic" |
| 62 | + #def groupId(self): |
| 63 | + # return "TrajectoryBasic" |
78 | 64 |
|
79 | 65 | def shortHelpString(self):
|
80 | 66 | return self.tr(
|
81 |
| - "<p>If the input layer CRS is EPSG:4326, distances are computed using spherical " + |
82 |
| - "equations. If the layer CRS unit miles, the resulting speed values will be " + |
83 |
| - "miles per second but the attribute name is currently hard-coded to meters_per_sec.</p>") |
| 67 | + "<p><b>Speed</b> is calculated based on the input layer CRS information and " + |
| 68 | + "converted to the desired speed units. For more info on the supported units, " + |
| 69 | + "see https://movingpandas.org/units </p>" + |
| 70 | + "<p><b>Direction</b> is calculated between consecutive locations. Direction " + |
| 71 | + "values are in degrees, starting North turning clockwise.</p>" |
| 72 | + ) |
84 | 73 |
|
85 | 74 | def helpUrl(self):
|
86 |
| - return "https://github.com/anitagraser/processing-trajectory" |
| 75 | + return "https://movingpandas.org/units" |
87 | 76 |
|
88 | 77 | def createInstance(self):
|
89 | 78 | return type(self)()
|
@@ -116,37 +105,71 @@ def initAlgorithm(self, config=None):
|
116 | 105 | description=self.tr("Timestamp format"),
|
117 | 106 | defaultValue="%Y-%m-%d %H:%M:%S+00",
|
118 | 107 | optional=False))
|
| 108 | + self.addParameter(QgsProcessingParameterString( |
| 109 | + name=self.SPEED_UNIT, |
| 110 | + description=self.tr("Speed units (e.g. km/h, m/s)"), |
| 111 | + defaultValue="km/h", |
| 112 | + optional=False)) |
119 | 113 | # output layer
|
120 | 114 | self.addParameter(QgsProcessingParameterFeatureSink(
|
121 |
| - name=self.OUTPUT, |
122 |
| - description=self.tr("Trajectories"), |
| 115 | + name=self.OUTPUT_PTS, |
| 116 | + description=self.tr("Trajectory points"), |
123 | 117 | type=QgsProcessing.TypeVectorPoint))
|
124 |
| - |
125 |
| - |
| 118 | + self.addParameter(QgsProcessingParameterFeatureSink( |
| 119 | + name=self.OUTPUT_TRAJS, |
| 120 | + description=self.tr("Trajectories"), |
| 121 | + type=QgsProcessing.TypeVectorLine)) |
126 | 122 |
|
127 | 123 | def processAlgorithm(self, parameters, context, feedback):
|
128 | 124 | input_layer = self.parameterAsSource(parameters, self.INPUT, context)
|
129 | 125 | traj_id_field = self.parameterAsFields(parameters, self.TRAJ_ID_FIELD, context)[0]
|
130 | 126 | timestamp_field = self.parameterAsFields(parameters, self.TIMESTAMP_FIELD, context)[0]
|
131 | 127 | timestamp_format = self.parameterAsString(parameters, self.TIMESTAMP_FORMAT, context)
|
| 128 | + speed_units = self.parameterAsString(parameters, self.SPEED_UNIT, context).split("/") |
132 | 129 |
|
133 | 130 | tc = tc_from_pt_layer(input_layer, timestamp_field, traj_id_field, timestamp_format)
|
134 |
| - tc.add_speed(units=("m","s")) |
| 131 | + tc.add_speed(units=tuple(speed_units)) |
| 132 | + tc.add_direction() |
| 133 | + |
| 134 | + self.dest_pts = self.create_points(parameters, context, input_layer, timestamp_field, tc) |
| 135 | + self.dest_trajs = self.create_trajectories(parameters, context, input_layer, traj_id_field, tc) |
| 136 | + |
| 137 | + return {self.OUTPUT_PTS: self.dest_pts, self.OUTPUT_TRAJS: self.dest_trajs} |
| 138 | + |
| 139 | + def postProcessAlgorithm(self, context, feedback): |
| 140 | + processed_layer = QgsProcessingUtils.mapLayerFromString(self.dest_pts, context) |
| 141 | + processed_layer.loadNamedStyle(os.path.join(pluginPath, "styles", "pts.qml")) |
| 142 | + return {self.OUTPUT_PTS: self.dest_pts, self.OUTPUT_TRAJS: self.dest_trajs} |
135 | 143 |
|
| 144 | + def create_points(self, parameters, context, input_layer, timestamp_field, tc): |
136 | 145 | output_fields = input_layer.fields()
|
| 146 | + self.drop_fid_field(output_fields) |
137 | 147 | output_fields.append(QgsField(tc.get_speed_col(), QVariant.Double))
|
| 148 | + output_fields.append(QgsField(tc.get_direction_col(), QVariant.Double)) |
| 149 | + |
| 150 | + (sink, dest) = self.parameterAsSink( |
| 151 | + parameters, self.OUTPUT_PTS, context, output_fields, QgsWkbTypes.Point, input_layer.sourceCrs()) |
| 152 | + |
| 153 | + tc_to_sink(tc, sink, output_fields, timestamp_field) |
| 154 | + return dest |
| 155 | + |
| 156 | + def drop_fid_field(self, output_fields): |
138 | 157 | i = output_fields.indexFromName("fid")
|
139 | 158 | if i >= 0:
|
140 | 159 | output_fields.remove(i)
|
141 |
| - |
142 |
| - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, |
143 |
| - output_fields, |
144 |
| - QgsWkbTypes.Point, |
145 |
| - input_layer.sourceCrs()) |
146 | 160 |
|
147 |
| - tc_to_sink(tc, sink, output_fields, timestamp_field) |
148 |
| - |
149 |
| - |
150 |
| - # default return type for function |
151 |
| - return {self.OUTPUT: dest_id} |
| 161 | + def create_trajectories(self, parameters, context, input_layer, traj_id_field, tc): |
| 162 | + output_fields_lines = QgsFields() |
| 163 | + output_fields_lines.append(QgsField(traj_id_field, QVariant.String)) |
| 164 | + |
| 165 | + (sink, dest) = self.parameterAsSink( |
| 166 | + parameters, self.OUTPUT_TRAJS, context, output_fields_lines, QgsWkbTypes.LineStringM, input_layer.sourceCrs()) |
| 167 | + |
| 168 | + for traj in tc.trajectories: |
| 169 | + line = QgsGeometry.fromWkt(traj.to_linestringm_wkt()) |
| 170 | + f = QgsFeature() |
| 171 | + f.setGeometry(line) |
| 172 | + f.setAttributes([traj.id]) |
| 173 | + sink.addFeature(f, QgsFeatureSink.FastInsert) |
| 174 | + return dest |
152 | 175 |
|
0 commit comments