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
This commit is contained in:
Nyall Dawson 2017-08-03 23:26:39 +10:00
parent b4b39996d2
commit ec4df6c019
8 changed files with 250 additions and 77 deletions

View File

@ -370,7 +370,9 @@ qgis:pointslayerfromtable: >
The attributes table of the resulting layer will be the input table. The attributes table of the resulting layer will be the input table.
qgis:pointstopath: qgis:pointstopath:
Converts a point layer to a line layer, by joining points in a defined order.
Points can be grouped by a field to output individual line features per group.
qgis:polarplot: > qgis:polarplot: >
This algorithm generates a polar plot based on the value of an input vector layer. This algorithm generates a polar plot based on the value of an input vector layer.

View File

@ -29,36 +29,34 @@ __revision__ = '$Format:%H$'
import os import os
from datetime import datetime from datetime import datetime
from qgis.PyQt.QtCore import QVariant from qgis.core import (QgsFeature,
from qgis.core import (QgsApplication,
QgsFeature,
QgsFeatureSink, QgsFeatureSink,
QgsFields, QgsFields,
QgsField, QgsField,
QgsGeometry, QgsGeometry,
QgsDistanceArea, QgsDistanceArea,
QgsProject, QgsPointXY,
QgsLineString,
QgsWkbTypes, QgsWkbTypes,
QgsProcessingUtils) QgsFeatureRequest,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterString,
QgsProcessing,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterFolderDestination)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterTableField
from processing.core.parameters import ParameterString
from processing.core.outputs import OutputVector
from processing.core.outputs import OutputDirectory
from processing.tools import dataobjects
class PointsToPaths(QgisAlgorithm): class PointsToPaths(QgisAlgorithm):
VECTOR = 'VECTOR' INPUT = 'INPUT'
GROUP_FIELD = 'GROUP_FIELD' GROUP_FIELD = 'GROUP_FIELD'
ORDER_FIELD = 'ORDER_FIELD' ORDER_FIELD = 'ORDER_FIELD'
DATE_FORMAT = 'DATE_FORMAT' DATE_FORMAT = 'DATE_FORMAT'
#GAP_PERIOD = 'GAP_PERIOD' OUTPUT = 'OUTPUT'
OUTPUT_LINES = 'OUTPUT_LINES' OUTPUT_TEXT_DIR = 'OUTPUT_TEXT_DIR'
OUTPUT_TEXT = 'OUTPUT_TEXT'
def group(self): def group(self):
return self.tr('Vector creation tools') return self.tr('Vector creation tools')
@ -66,20 +64,23 @@ class PointsToPaths(QgisAlgorithm):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def tags(self):
return self.tr('join,points,lines,connect').split(',')
def initAlgorithm(self, config=None): def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(self.VECTOR, self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input point layer'), [dataobjects.TYPE_VECTOR_POINT])) self.tr('Input point layer'), [QgsProcessing.TypeVectorPoint]))
self.addParameter(ParameterTableField(self.GROUP_FIELD, self.addParameter(QgsProcessingParameterField(self.ORDER_FIELD,
self.tr('Group field'), self.VECTOR)) self.tr('Order field'), parentLayerParameterName=self.INPUT))
self.addParameter(ParameterTableField(self.ORDER_FIELD, self.addParameter(QgsProcessingParameterField(self.GROUP_FIELD,
self.tr('Order field'), self.VECTOR)) self.tr('Group field'), parentLayerParameterName=self.INPUT, optional=True))
self.addParameter(ParameterString(self.DATE_FORMAT, self.addParameter(QgsProcessingParameterString(self.DATE_FORMAT,
self.tr('Date format (if order field is DateTime)'), '', optional=True)) self.tr('Date format (if order field is DateTime)'), optional=True))
#self.addParameter(ParameterNumber(
# self.GAP_PERIOD, self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Paths'), QgsProcessing.TypeVectorLine))
# 'Gap period (if order field is DateTime)', 0, 60, 0)) output_dir_param = QgsProcessingParameterFolderDestination(self.OUTPUT_TEXT_DIR, self.tr('Directory for text output'), optional=True)
self.addOutput(OutputVector(self.OUTPUT_LINES, self.tr('Paths'), datatype=[dataobjects.TYPE_VECTOR_LINE])) output_dir_param.setCreateByDefault(False)
self.addOutput(OutputDirectory(self.OUTPUT_TEXT, self.tr('Directory'))) self.addParameter(output_dir_param)
def name(self): def name(self):
return 'pointstopath' return 'pointstopath'
@ -88,29 +89,58 @@ class PointsToPaths(QgisAlgorithm):
return self.tr('Points to path') return self.tr('Points to path')
def processAlgorithm(self, parameters, context, feedback): def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) source = self.parameterAsSource(parameters, self.INPUT, context)
groupField = self.getParameterValue(self.GROUP_FIELD) group_field_name = self.parameterAsString(parameters, self.GROUP_FIELD, context)
orderField = self.getParameterValue(self.ORDER_FIELD) order_field_name = self.parameterAsString(parameters, self.ORDER_FIELD, context)
dateFormat = str(self.getParameterValue(self.DATE_FORMAT)) date_format = self.parameterAsString(parameters, self.DATE_FORMAT, context)
#gap = int(self.getParameterValue(self.GAP_PERIOD)) text_dir = self.parameterAsString(parameters, self.OUTPUT_TEXT_DIR, context)
dirName = self.getOutputValue(self.OUTPUT_TEXT)
group_field_index = source.fields().lookupField(group_field_name)
order_field_index = source.fields().lookupField(order_field_name)
if group_field_index >= 0:
group_field_def = source.fields().at(group_field_index)
else:
group_field_def = None
order_field_def = source.fields().at(order_field_index)
fields = QgsFields() fields = QgsFields()
fields.append(QgsField('group', QVariant.String, '', 254, 0)) if group_field_def is not None:
fields.append(QgsField('begin', QVariant.String, '', 254, 0)) fields.append(group_field_def)
fields.append(QgsField('end', QVariant.String, '', 254, 0)) begin_field = QgsField(order_field_def)
writer = self.getOutputFromName(self.OUTPUT_LINES).getVectorWriter(fields, QgsWkbTypes.LineString, layer.crs(), begin_field.setName('begin')
context) fields.append(begin_field)
end_field = QgsField(order_field_def)
end_field.setName('end')
fields.append(end_field)
output_wkb = QgsWkbTypes.LineString
if QgsWkbTypes.hasM(source.wkbType()):
output_wkb = QgsWkbTypes.addM(output_wkb)
if QgsWkbTypes.hasZ(source.wkbType()):
output_wkb = QgsWkbTypes.addZ(output_wkb)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, output_wkb, source.sourceCrs())
points = dict() points = dict()
features = QgsProcessingUtils.getFeatures(layer, context) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([group_field_index, order_field_index]))
total = 100.0 / layer.featureCount() if layer.featureCount() else 0 total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, f in enumerate(features): for current, f in enumerate(features):
point = f.geometry().asPoint() if feedback.isCanceled():
group = f[groupField] break
order = f[orderField]
if dateFormat != '': if not f.hasGeometry():
order = datetime.strptime(str(order), dateFormat) continue
point = f.geometry().geometry().clone()
if group_field_index >= 0:
group = f.attributes()[group_field_index]
else:
group = 1
order = f.attributes()[order_field_index]
if date_format != '':
order = datetime.strptime(str(order), date_format)
if group in points: if group in points:
points[group].append((order, point)) points[group].append((order, point))
else: else:
@ -121,46 +151,45 @@ class PointsToPaths(QgisAlgorithm):
feedback.setProgress(0) feedback.setProgress(0)
da = QgsDistanceArea() da = QgsDistanceArea()
da.setSourceCrs(layer.sourceCrs()) da.setSourceCrs(source.sourceCrs())
da.setEllipsoid(context.project().ellipsoid()) da.setEllipsoid(context.project().ellipsoid())
current = 0 current = 0
total = 100.0 / len(points) if points else 1 total = 100.0 / len(points) if points else 1
for group, vertices in list(points.items()): for group, vertices in list(points.items()):
if feedback.isCanceled():
break
vertices.sort() vertices.sort()
f = QgsFeature() f = QgsFeature()
f.initAttributes(len(fields)) attributes = []
f.setFields(fields) if group_field_index >= 0:
f['group'] = group attributes.append(group)
f['begin'] = vertices[0][0] attributes.extend([vertices[0][0], vertices[-1][0]])
f['end'] = vertices[-1][0] f.setAttributes(attributes)
line = [node[1] for node in vertices]
fileName = os.path.join(dirName, '%s.txt' % group) if text_dir:
fileName = os.path.join(text_dir, '%s.txt' % group)
with open(fileName, 'w') as fl: with open(fileName, 'w') as fl:
fl.write('angle=Azimuth\n') fl.write('angle=Azimuth\n')
fl.write('heading=Coordinate_System\n') fl.write('heading=Coordinate_System\n')
fl.write('dist_units=Default\n') fl.write('dist_units=Default\n')
line = [] for i in range(len(line)):
i = 0
for node in vertices:
line.append(node[1])
if i == 0: if i == 0:
fl.write('startAt=%f;%f;90\n' % (node[1].x(), node[1].y())) fl.write('startAt=%f;%f;90\n' % (line[i].x(), line[i].y()))
fl.write('survey=Polygonal\n') fl.write('survey=Polygonal\n')
fl.write('[data]\n') fl.write('[data]\n')
else: else:
angle = line[i - 1].azimuth(line[i]) angle = line[i - 1].azimuth(line[i])
distance = da.measureLine(line[i - 1], line[i]) distance = da.measureLine(QgsPointXY(line[i - 1]), QgsPointXY(line[i]))
fl.write('%f;%f;90\n' % (angle, distance)) fl.write('%f;%f;90\n' % (angle, distance))
i += 1 f.setGeometry(QgsGeometry(QgsLineString(line)))
sink.addFeature(f, QgsFeatureSink.FastInsert)
f.setGeometry(QgsGeometry.fromPolyline(line))
writer.addFeature(f, QgsFeatureSink.FastInsert)
current += 1 current += 1
feedback.setProgress(int(current * total)) feedback.setProgress(int(current * total))
del writer return {self.OUTPUT: dest_id}

View File

@ -94,6 +94,7 @@ from .PointOnSurface import PointOnSurface
from .PointsAlongGeometry import PointsAlongGeometry from .PointsAlongGeometry import PointsAlongGeometry
from .PointsInPolygon import PointsInPolygon from .PointsInPolygon import PointsInPolygon
from .PointsLayerFromTable import PointsLayerFromTable from .PointsLayerFromTable import PointsLayerFromTable
from .PointsToPaths import PointsToPaths
from .PoleOfInaccessibility import PoleOfInaccessibility from .PoleOfInaccessibility import PoleOfInaccessibility
from .Polygonize import Polygonize from .Polygonize import Polygonize
from .PolygonsToLines import PolygonsToLines from .PolygonsToLines import PolygonsToLines
@ -152,7 +153,6 @@ from .ZonalStatistics import ZonalStatistics
# from .PointsDisplacement import PointsDisplacement # from .PointsDisplacement import PointsDisplacement
# from .PointsFromPolygons import PointsFromPolygons # from .PointsFromPolygons import PointsFromPolygons
# from .PointsFromLines import PointsFromLines # from .PointsFromLines import PointsFromLines
# from .PointsToPaths import PointsToPaths
# from .SetVectorStyle import SetVectorStyle # from .SetVectorStyle import SetVectorStyle
# from .SetRasterStyle import SetRasterStyle # from .SetRasterStyle import SetRasterStyle
# from .SelectByAttributeSum import SelectByAttributeSum # from .SelectByAttributeSum import SelectByAttributeSum
@ -194,7 +194,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
# StatisticsByCategories(), # StatisticsByCategories(),
# RasterLayerStatistics(), PointsDisplacement(), # RasterLayerStatistics(), PointsDisplacement(),
# PointsFromPolygons(), # PointsFromPolygons(),
# PointsFromLines(), PointsToPaths(), # PointsFromLines(),
# SetVectorStyle(), SetRasterStyle(), # SetVectorStyle(), SetRasterStyle(),
# HypsometricCurves(), # HypsometricCurves(),
# FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(), # FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(),
@ -262,6 +262,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
PointsAlongGeometry(), PointsAlongGeometry(),
PointsInPolygon(), PointsInPolygon(),
PointsLayerFromTable(), PointsLayerFromTable(),
PointsToPaths(),
PoleOfInaccessibility(), PoleOfInaccessibility(),
Polygonize(), Polygonize(),
PolygonsToLines(), PolygonsToLines(),

View File

@ -0,0 +1,26 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>points_to_path</Name>
<ElementPath>points_to_path</ElementPath>
<!--LINESTRING-->
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>1</FeatureCount>
<ExtentXMin>0.00000</ExtentXMin>
<ExtentXMax>8.00000</ExtentXMax>
<ExtentYMin>-5.00000</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>begin</Name>
<ElementPath>begin</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>end</Name>
<ElementPath>end</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:points_to_path fid="points_to_path.0">
<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>
<ogr:begin>1</ogr:begin>
<ogr:end>9</ogr:end>
</ogr:points_to_path>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,31 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>points_to_path_grouped</Name>
<ElementPath>points_to_path_grouped</ElementPath>
<!--LINESTRING-->
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>3</FeatureCount>
<ExtentXMin>0.00000</ExtentXMin>
<ExtentXMax>8.00000</ExtentXMax>
<ExtentYMin>-5.00000</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>id2</Name>
<ElementPath>id2</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>begin</Name>
<ElementPath>begin</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>end</Name>
<ElementPath>end</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:points_to_path_grouped fid="points_to_path_grouped.0">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>1,1 5,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id2>2</ogr:id2>
<ogr:begin>1</ogr:begin>
<ogr:end>4</ogr:end>
</ogr:points_to_path_grouped>
</gml:featureMember>
<gml:featureMember>
<ogr:points_to_path_grouped fid="points_to_path_grouped.1">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,3 4,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
<ogr:id2>1</ogr:id2>
<ogr:begin>2</ogr:begin>
<ogr:end>5</ogr:end>
</ogr:points_to_path_grouped>
</gml:featureMember>
<gml:featureMember>
<ogr:points_to_path_grouped fid="points_to_path_grouped.2">
<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>
<ogr:id2>0</ogr:id2>
<ogr:begin>3</ogr:begin>
<ogr:end>9</ogr:end>
</ogr:points_to_path_grouped>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -2232,6 +2232,31 @@ tests:
name: expected/hub_lines.gml name: expected/hub_lines.gml
type: vector type: vector
- algorithm: qgis:pointstopath
name: Points to path (non grouped)
params:
INPUT:
name: points.gml
type: vector
ORDER_FIELD: id
results:
OUTPUT:
name: expected/points_to_path.gml
type: vector
- algorithm: qgis:pointstopath
name: Points to path (grouped)
params:
INPUT:
name: points.gml
type: vector
ORDER_FIELD: id
GROUP_FIELD: id2
results:
OUTPUT:
name: expected/points_to_path_grouped.gml
type: vector
# - algorithm: qgis:joinattributestable # - algorithm: qgis:joinattributestable
# name: join the attribute table by common field # name: join the attribute table by common field
# params: # params: