Skip to content

Commit ec4df6c

Browse files
committedAug 5, 2017
Port points to path to new API
Improvements: - Maintain Z/M values - Keep original data type for group/order fields - Group field is optional - Added unit tests - Don't export text files for features by default
1 parent b4b3999 commit ec4df6c

File tree

8 files changed

+254
-81
lines changed

8 files changed

+254
-81
lines changed
 

‎python/plugins/processing/algs/help/qgis.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,9 @@ qgis:pointslayerfromtable: >
370370
The attributes table of the resulting layer will be the input table.
371371

372372
qgis:pointstopath:
373+
Converts a point layer to a line layer, by joining points in a defined order.
373374

375+
Points can be grouped by a field to output individual line features per group.
374376

375377
qgis:polarplot: >
376378
This algorithm generates a polar plot based on the value of an input vector layer.

‎python/plugins/processing/algs/qgis/PointsToPaths.py

Lines changed: 108 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -29,57 +29,58 @@
2929
import os
3030
from datetime import datetime
3131

32-
from qgis.PyQt.QtCore import QVariant
33-
from qgis.core import (QgsApplication,
34-
QgsFeature,
32+
from qgis.core import (QgsFeature,
3533
QgsFeatureSink,
3634
QgsFields,
3735
QgsField,
3836
QgsGeometry,
3937
QgsDistanceArea,
40-
QgsProject,
38+
QgsPointXY,
39+
QgsLineString,
4140
QgsWkbTypes,
42-
QgsProcessingUtils)
41+
QgsFeatureRequest,
42+
QgsProcessingParameterFeatureSource,
43+
QgsProcessingParameterField,
44+
QgsProcessingParameterString,
45+
QgsProcessing,
46+
QgsProcessingParameterFeatureSink,
47+
QgsProcessingParameterFolderDestination)
4348

4449
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
45-
from processing.core.parameters import ParameterVector
46-
from processing.core.parameters import ParameterTableField
47-
from processing.core.parameters import ParameterString
48-
from processing.core.outputs import OutputVector
49-
from processing.core.outputs import OutputDirectory
50-
from processing.tools import dataobjects
5150

5251

5352
class PointsToPaths(QgisAlgorithm):
5453

55-
VECTOR = 'VECTOR'
54+
INPUT = 'INPUT'
5655
GROUP_FIELD = 'GROUP_FIELD'
5756
ORDER_FIELD = 'ORDER_FIELD'
5857
DATE_FORMAT = 'DATE_FORMAT'
59-
#GAP_PERIOD = 'GAP_PERIOD'
60-
OUTPUT_LINES = 'OUTPUT_LINES'
61-
OUTPUT_TEXT = 'OUTPUT_TEXT'
58+
OUTPUT = 'OUTPUT'
59+
OUTPUT_TEXT_DIR = 'OUTPUT_TEXT_DIR'
6260

6361
def group(self):
6462
return self.tr('Vector creation tools')
6563

6664
def __init__(self):
6765
super().__init__()
6866

67+
def tags(self):
68+
return self.tr('join,points,lines,connect').split(',')
69+
6970
def initAlgorithm(self, config=None):
70-
self.addParameter(ParameterVector(self.VECTOR,
71-
self.tr('Input point layer'), [dataobjects.TYPE_VECTOR_POINT]))
72-
self.addParameter(ParameterTableField(self.GROUP_FIELD,
73-
self.tr('Group field'), self.VECTOR))
74-
self.addParameter(ParameterTableField(self.ORDER_FIELD,
75-
self.tr('Order field'), self.VECTOR))
76-
self.addParameter(ParameterString(self.DATE_FORMAT,
77-
self.tr('Date format (if order field is DateTime)'), '', optional=True))
78-
#self.addParameter(ParameterNumber(
79-
# self.GAP_PERIOD,
80-
# 'Gap period (if order field is DateTime)', 0, 60, 0))
81-
self.addOutput(OutputVector(self.OUTPUT_LINES, self.tr('Paths'), datatype=[dataobjects.TYPE_VECTOR_LINE]))
82-
self.addOutput(OutputDirectory(self.OUTPUT_TEXT, self.tr('Directory')))
71+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
72+
self.tr('Input point layer'), [QgsProcessing.TypeVectorPoint]))
73+
self.addParameter(QgsProcessingParameterField(self.ORDER_FIELD,
74+
self.tr('Order field'), parentLayerParameterName=self.INPUT))
75+
self.addParameter(QgsProcessingParameterField(self.GROUP_FIELD,
76+
self.tr('Group field'), parentLayerParameterName=self.INPUT, optional=True))
77+
self.addParameter(QgsProcessingParameterString(self.DATE_FORMAT,
78+
self.tr('Date format (if order field is DateTime)'), optional=True))
79+
80+
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Paths'), QgsProcessing.TypeVectorLine))
81+
output_dir_param = QgsProcessingParameterFolderDestination(self.OUTPUT_TEXT_DIR, self.tr('Directory for text output'), optional=True)
82+
output_dir_param.setCreateByDefault(False)
83+
self.addParameter(output_dir_param)
8384

8485
def name(self):
8586
return 'pointstopath'
@@ -88,29 +89,58 @@ def displayName(self):
8889
return self.tr('Points to path')
8990

9091
def processAlgorithm(self, parameters, context, feedback):
91-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context)
92-
groupField = self.getParameterValue(self.GROUP_FIELD)
93-
orderField = self.getParameterValue(self.ORDER_FIELD)
94-
dateFormat = str(self.getParameterValue(self.DATE_FORMAT))
95-
#gap = int(self.getParameterValue(self.GAP_PERIOD))
96-
dirName = self.getOutputValue(self.OUTPUT_TEXT)
92+
source = self.parameterAsSource(parameters, self.INPUT, context)
93+
group_field_name = self.parameterAsString(parameters, self.GROUP_FIELD, context)
94+
order_field_name = self.parameterAsString(parameters, self.ORDER_FIELD, context)
95+
date_format = self.parameterAsString(parameters, self.DATE_FORMAT, context)
96+
text_dir = self.parameterAsString(parameters, self.OUTPUT_TEXT_DIR, context)
97+
98+
group_field_index = source.fields().lookupField(group_field_name)
99+
order_field_index = source.fields().lookupField(order_field_name)
100+
101+
if group_field_index >= 0:
102+
group_field_def = source.fields().at(group_field_index)
103+
else:
104+
group_field_def = None
105+
order_field_def = source.fields().at(order_field_index)
97106

98107
fields = QgsFields()
99-
fields.append(QgsField('group', QVariant.String, '', 254, 0))
100-
fields.append(QgsField('begin', QVariant.String, '', 254, 0))
101-
fields.append(QgsField('end', QVariant.String, '', 254, 0))
102-
writer = self.getOutputFromName(self.OUTPUT_LINES).getVectorWriter(fields, QgsWkbTypes.LineString, layer.crs(),
103-
context)
108+
if group_field_def is not None:
109+
fields.append(group_field_def)
110+
begin_field = QgsField(order_field_def)
111+
begin_field.setName('begin')
112+
fields.append(begin_field)
113+
end_field = QgsField(order_field_def)
114+
end_field.setName('end')
115+
fields.append(end_field)
116+
117+
output_wkb = QgsWkbTypes.LineString
118+
if QgsWkbTypes.hasM(source.wkbType()):
119+
output_wkb = QgsWkbTypes.addM(output_wkb)
120+
if QgsWkbTypes.hasZ(source.wkbType()):
121+
output_wkb = QgsWkbTypes.addZ(output_wkb)
122+
123+
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
124+
fields, output_wkb, source.sourceCrs())
104125

105126
points = dict()
106-
features = QgsProcessingUtils.getFeatures(layer, context)
107-
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
127+
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([group_field_index, order_field_index]))
128+
total = 100.0 / source.featureCount() if source.featureCount() else 0
108129
for current, f in enumerate(features):
109-
point = f.geometry().asPoint()
110-
group = f[groupField]
111-
order = f[orderField]
112-
if dateFormat != '':
113-
order = datetime.strptime(str(order), dateFormat)
130+
if feedback.isCanceled():
131+
break
132+
133+
if not f.hasGeometry():
134+
continue
135+
136+
point = f.geometry().geometry().clone()
137+
if group_field_index >= 0:
138+
group = f.attributes()[group_field_index]
139+
else:
140+
group = 1
141+
order = f.attributes()[order_field_index]
142+
if date_format != '':
143+
order = datetime.strptime(str(order), date_format)
114144
if group in points:
115145
points[group].append((order, point))
116146
else:
@@ -121,46 +151,45 @@ def processAlgorithm(self, parameters, context, feedback):
121151
feedback.setProgress(0)
122152

123153
da = QgsDistanceArea()
124-
da.setSourceCrs(layer.sourceCrs())
154+
da.setSourceCrs(source.sourceCrs())
125155
da.setEllipsoid(context.project().ellipsoid())
126156

127157
current = 0
128158
total = 100.0 / len(points) if points else 1
129159
for group, vertices in list(points.items()):
160+
if feedback.isCanceled():
161+
break
162+
130163
vertices.sort()
131164
f = QgsFeature()
132-
f.initAttributes(len(fields))
133-
f.setFields(fields)
134-
f['group'] = group
135-
f['begin'] = vertices[0][0]
136-
f['end'] = vertices[-1][0]
137-
138-
fileName = os.path.join(dirName, '%s.txt' % group)
139-
140-
with open(fileName, 'w') as fl:
141-
fl.write('angle=Azimuth\n')
142-
fl.write('heading=Coordinate_System\n')
143-
fl.write('dist_units=Default\n')
144-
145-
line = []
146-
i = 0
147-
for node in vertices:
148-
line.append(node[1])
149-
150-
if i == 0:
151-
fl.write('startAt=%f;%f;90\n' % (node[1].x(), node[1].y()))
152-
fl.write('survey=Polygonal\n')
153-
fl.write('[data]\n')
154-
else:
155-
angle = line[i - 1].azimuth(line[i])
156-
distance = da.measureLine(line[i - 1], line[i])
157-
fl.write('%f;%f;90\n' % (angle, distance))
158-
159-
i += 1
160-
161-
f.setGeometry(QgsGeometry.fromPolyline(line))
162-
writer.addFeature(f, QgsFeatureSink.FastInsert)
165+
attributes = []
166+
if group_field_index >= 0:
167+
attributes.append(group)
168+
attributes.extend([vertices[0][0], vertices[-1][0]])
169+
f.setAttributes(attributes)
170+
line = [node[1] for node in vertices]
171+
172+
if text_dir:
173+
fileName = os.path.join(text_dir, '%s.txt' % group)
174+
175+
with open(fileName, 'w') as fl:
176+
fl.write('angle=Azimuth\n')
177+
fl.write('heading=Coordinate_System\n')
178+
fl.write('dist_units=Default\n')
179+
180+
for i in range(len(line)):
181+
if i == 0:
182+
fl.write('startAt=%f;%f;90\n' % (line[i].x(), line[i].y()))
183+
fl.write('survey=Polygonal\n')
184+
fl.write('[data]\n')
185+
else:
186+
angle = line[i - 1].azimuth(line[i])
187+
distance = da.measureLine(QgsPointXY(line[i - 1]), QgsPointXY(line[i]))
188+
fl.write('%f;%f;90\n' % (angle, distance))
189+
190+
f.setGeometry(QgsGeometry(QgsLineString(line)))
191+
sink.addFeature(f, QgsFeatureSink.FastInsert)
163192
current += 1
164193
feedback.setProgress(int(current * total))
165194

166-
del writer
195+
return {self.OUTPUT: dest_id}

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
from .PointsAlongGeometry import PointsAlongGeometry
9595
from .PointsInPolygon import PointsInPolygon
9696
from .PointsLayerFromTable import PointsLayerFromTable
97+
from .PointsToPaths import PointsToPaths
9798
from .PoleOfInaccessibility import PoleOfInaccessibility
9899
from .Polygonize import Polygonize
99100
from .PolygonsToLines import PolygonsToLines
@@ -152,7 +153,6 @@
152153
# from .PointsDisplacement import PointsDisplacement
153154
# from .PointsFromPolygons import PointsFromPolygons
154155
# from .PointsFromLines import PointsFromLines
155-
# from .PointsToPaths import PointsToPaths
156156
# from .SetVectorStyle import SetVectorStyle
157157
# from .SetRasterStyle import SetRasterStyle
158158
# from .SelectByAttributeSum import SelectByAttributeSum
@@ -194,7 +194,7 @@ def getAlgs(self):
194194
# StatisticsByCategories(),
195195
# RasterLayerStatistics(), PointsDisplacement(),
196196
# PointsFromPolygons(),
197-
# PointsFromLines(), PointsToPaths(),
197+
# PointsFromLines(),
198198
# SetVectorStyle(), SetRasterStyle(),
199199
# HypsometricCurves(),
200200
# FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(),
@@ -262,6 +262,7 @@ def getAlgs(self):
262262
PointsAlongGeometry(),
263263
PointsInPolygon(),
264264
PointsLayerFromTable(),
265+
PointsToPaths(),
265266
PoleOfInaccessibility(),
266267
Polygonize(),
267268
PolygonsToLines(),
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>points_to_path</Name>
4+
<ElementPath>points_to_path</ElementPath>
5+
<!--LINESTRING-->
6+
<GeometryType>2</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>1</FeatureCount>
10+
<ExtentXMin>0.00000</ExtentXMin>
11+
<ExtentXMax>8.00000</ExtentXMax>
12+
<ExtentYMin>-5.00000</ExtentYMin>
13+
<ExtentYMax>3.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>begin</Name>
17+
<ElementPath>begin</ElementPath>
18+
<Type>Integer</Type>
19+
</PropertyDefn>
20+
<PropertyDefn>
21+
<Name>end</Name>
22+
<ElementPath>end</ElementPath>
23+
<Type>Integer</Type>
24+
</PropertyDefn>
25+
</GMLFeatureClass>
26+
</GMLFeatureClassList>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:points_to_path fid="points_to_path.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>1,1 3,3 2,2 5,2 4,1 0,-5 8,-1 7,-1 0,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
<ogr:begin>1</ogr:begin>
18+
<ogr:end>9</ogr:end>
19+
</ogr:points_to_path>
20+
</gml:featureMember>
21+
</ogr:FeatureCollection>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>points_to_path_grouped</Name>
4+
<ElementPath>points_to_path_grouped</ElementPath>
5+
<!--LINESTRING-->
6+
<GeometryType>2</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>3</FeatureCount>
10+
<ExtentXMin>0.00000</ExtentXMin>
11+
<ExtentXMax>8.00000</ExtentXMax>
12+
<ExtentYMin>-5.00000</ExtentYMin>
13+
<ExtentYMax>3.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>id2</Name>
17+
<ElementPath>id2</ElementPath>
18+
<Type>Integer</Type>
19+
</PropertyDefn>
20+
<PropertyDefn>
21+
<Name>begin</Name>
22+
<ElementPath>begin</ElementPath>
23+
<Type>Integer</Type>
24+
</PropertyDefn>
25+
<PropertyDefn>
26+
<Name>end</Name>
27+
<ElementPath>end</ElementPath>
28+
<Type>Integer</Type>
29+
</PropertyDefn>
30+
</GMLFeatureClass>
31+
</GMLFeatureClassList>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
10+
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:points_to_path_grouped fid="points_to_path_grouped.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>1,1 5,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
<ogr:id2>2</ogr:id2>
18+
<ogr:begin>1</ogr:begin>
19+
<ogr:end>4</ogr:end>
20+
</ogr:points_to_path_grouped>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:points_to_path_grouped fid="points_to_path_grouped.1">
24+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,3 4,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
25+
<ogr:id2>1</ogr:id2>
26+
<ogr:begin>2</ogr:begin>
27+
<ogr:end>5</ogr:end>
28+
</ogr:points_to_path_grouped>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:points_to_path_grouped fid="points_to_path_grouped.2">
32+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,2 0,-5 8,-1 7,-1 0,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
33+
<ogr:id2>0</ogr:id2>
34+
<ogr:begin>3</ogr:begin>
35+
<ogr:end>9</ogr:end>
36+
</ogr:points_to_path_grouped>
37+
</gml:featureMember>
38+
</ogr:FeatureCollection>

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,31 @@ tests:
22322232
name: expected/hub_lines.gml
22332233
type: vector
22342234

2235+
- algorithm: qgis:pointstopath
2236+
name: Points to path (non grouped)
2237+
params:
2238+
INPUT:
2239+
name: points.gml
2240+
type: vector
2241+
ORDER_FIELD: id
2242+
results:
2243+
OUTPUT:
2244+
name: expected/points_to_path.gml
2245+
type: vector
2246+
2247+
- algorithm: qgis:pointstopath
2248+
name: Points to path (grouped)
2249+
params:
2250+
INPUT:
2251+
name: points.gml
2252+
type: vector
2253+
ORDER_FIELD: id
2254+
GROUP_FIELD: id2
2255+
results:
2256+
OUTPUT:
2257+
name: expected/points_to_path_grouped.gml
2258+
type: vector
2259+
22352260
# - algorithm: qgis:joinattributestable
22362261
# name: join the attribute table by common field
22372262
# params:

0 commit comments

Comments
 (0)
Please sign in to comment.