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.
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: >
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
from datetime import datetime
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsApplication,
QgsFeature,
from qgis.core import (QgsFeature,
QgsFeatureSink,
QgsFields,
QgsField,
QgsGeometry,
QgsDistanceArea,
QgsProject,
QgsPointXY,
QgsLineString,
QgsWkbTypes,
QgsProcessingUtils)
QgsFeatureRequest,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterField,
QgsProcessingParameterString,
QgsProcessing,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterFolderDestination)
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):
VECTOR = 'VECTOR'
INPUT = 'INPUT'
GROUP_FIELD = 'GROUP_FIELD'
ORDER_FIELD = 'ORDER_FIELD'
DATE_FORMAT = 'DATE_FORMAT'
#GAP_PERIOD = 'GAP_PERIOD'
OUTPUT_LINES = 'OUTPUT_LINES'
OUTPUT_TEXT = 'OUTPUT_TEXT'
OUTPUT = 'OUTPUT'
OUTPUT_TEXT_DIR = 'OUTPUT_TEXT_DIR'
def group(self):
return self.tr('Vector creation tools')
@ -66,20 +64,23 @@ class PointsToPaths(QgisAlgorithm):
def __init__(self):
super().__init__()
def tags(self):
return self.tr('join,points,lines,connect').split(',')
def initAlgorithm(self, config=None):
self.addParameter(ParameterVector(self.VECTOR,
self.tr('Input point layer'), [dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(ParameterTableField(self.GROUP_FIELD,
self.tr('Group field'), self.VECTOR))
self.addParameter(ParameterTableField(self.ORDER_FIELD,
self.tr('Order field'), self.VECTOR))
self.addParameter(ParameterString(self.DATE_FORMAT,
self.tr('Date format (if order field is DateTime)'), '', optional=True))
#self.addParameter(ParameterNumber(
# self.GAP_PERIOD,
# 'Gap period (if order field is DateTime)', 0, 60, 0))
self.addOutput(OutputVector(self.OUTPUT_LINES, self.tr('Paths'), datatype=[dataobjects.TYPE_VECTOR_LINE]))
self.addOutput(OutputDirectory(self.OUTPUT_TEXT, self.tr('Directory')))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input point layer'), [QgsProcessing.TypeVectorPoint]))
self.addParameter(QgsProcessingParameterField(self.ORDER_FIELD,
self.tr('Order field'), parentLayerParameterName=self.INPUT))
self.addParameter(QgsProcessingParameterField(self.GROUP_FIELD,
self.tr('Group field'), parentLayerParameterName=self.INPUT, optional=True))
self.addParameter(QgsProcessingParameterString(self.DATE_FORMAT,
self.tr('Date format (if order field is DateTime)'), optional=True))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Paths'), QgsProcessing.TypeVectorLine))
output_dir_param = QgsProcessingParameterFolderDestination(self.OUTPUT_TEXT_DIR, self.tr('Directory for text output'), optional=True)
output_dir_param.setCreateByDefault(False)
self.addParameter(output_dir_param)
def name(self):
return 'pointstopath'
@ -88,29 +89,58 @@ class PointsToPaths(QgisAlgorithm):
return self.tr('Points to path')
def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context)
groupField = self.getParameterValue(self.GROUP_FIELD)
orderField = self.getParameterValue(self.ORDER_FIELD)
dateFormat = str(self.getParameterValue(self.DATE_FORMAT))
#gap = int(self.getParameterValue(self.GAP_PERIOD))
dirName = self.getOutputValue(self.OUTPUT_TEXT)
source = self.parameterAsSource(parameters, self.INPUT, context)
group_field_name = self.parameterAsString(parameters, self.GROUP_FIELD, context)
order_field_name = self.parameterAsString(parameters, self.ORDER_FIELD, context)
date_format = self.parameterAsString(parameters, self.DATE_FORMAT, context)
text_dir = self.parameterAsString(parameters, self.OUTPUT_TEXT_DIR, context)
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.append(QgsField('group', QVariant.String, '', 254, 0))
fields.append(QgsField('begin', QVariant.String, '', 254, 0))
fields.append(QgsField('end', QVariant.String, '', 254, 0))
writer = self.getOutputFromName(self.OUTPUT_LINES).getVectorWriter(fields, QgsWkbTypes.LineString, layer.crs(),
context)
if group_field_def is not None:
fields.append(group_field_def)
begin_field = QgsField(order_field_def)
begin_field.setName('begin')
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()
features = QgsProcessingUtils.getFeatures(layer, context)
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([group_field_index, order_field_index]))
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, f in enumerate(features):
point = f.geometry().asPoint()
group = f[groupField]
order = f[orderField]
if dateFormat != '':
order = datetime.strptime(str(order), dateFormat)
if feedback.isCanceled():
break
if not f.hasGeometry():
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:
points[group].append((order, point))
else:
@ -121,46 +151,45 @@ class PointsToPaths(QgisAlgorithm):
feedback.setProgress(0)
da = QgsDistanceArea()
da.setSourceCrs(layer.sourceCrs())
da.setSourceCrs(source.sourceCrs())
da.setEllipsoid(context.project().ellipsoid())
current = 0
total = 100.0 / len(points) if points else 1
for group, vertices in list(points.items()):
if feedback.isCanceled():
break
vertices.sort()
f = QgsFeature()
f.initAttributes(len(fields))
f.setFields(fields)
f['group'] = group
f['begin'] = vertices[0][0]
f['end'] = vertices[-1][0]
attributes = []
if group_field_index >= 0:
attributes.append(group)
attributes.extend([vertices[0][0], 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:
fl.write('angle=Azimuth\n')
fl.write('heading=Coordinate_System\n')
fl.write('dist_units=Default\n')
with open(fileName, 'w') as fl:
fl.write('angle=Azimuth\n')
fl.write('heading=Coordinate_System\n')
fl.write('dist_units=Default\n')
line = []
i = 0
for node in vertices:
line.append(node[1])
for i in range(len(line)):
if i == 0:
fl.write('startAt=%f;%f;90\n' % (line[i].x(), line[i].y()))
fl.write('survey=Polygonal\n')
fl.write('[data]\n')
else:
angle = line[i - 1].azimuth(line[i])
distance = da.measureLine(QgsPointXY(line[i - 1]), QgsPointXY(line[i]))
fl.write('%f;%f;90\n' % (angle, distance))
if i == 0:
fl.write('startAt=%f;%f;90\n' % (node[1].x(), node[1].y()))
fl.write('survey=Polygonal\n')
fl.write('[data]\n')
else:
angle = line[i - 1].azimuth(line[i])
distance = da.measureLine(line[i - 1], line[i])
fl.write('%f;%f;90\n' % (angle, distance))
i += 1
f.setGeometry(QgsGeometry.fromPolyline(line))
writer.addFeature(f, QgsFeatureSink.FastInsert)
f.setGeometry(QgsGeometry(QgsLineString(line)))
sink.addFeature(f, QgsFeatureSink.FastInsert)
current += 1
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 .PointsInPolygon import PointsInPolygon
from .PointsLayerFromTable import PointsLayerFromTable
from .PointsToPaths import PointsToPaths
from .PoleOfInaccessibility import PoleOfInaccessibility
from .Polygonize import Polygonize
from .PolygonsToLines import PolygonsToLines
@ -152,7 +153,6 @@ from .ZonalStatistics import ZonalStatistics
# from .PointsDisplacement import PointsDisplacement
# from .PointsFromPolygons import PointsFromPolygons
# from .PointsFromLines import PointsFromLines
# from .PointsToPaths import PointsToPaths
# from .SetVectorStyle import SetVectorStyle
# from .SetRasterStyle import SetRasterStyle
# from .SelectByAttributeSum import SelectByAttributeSum
@ -194,7 +194,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
# StatisticsByCategories(),
# RasterLayerStatistics(), PointsDisplacement(),
# PointsFromPolygons(),
# PointsFromLines(), PointsToPaths(),
# PointsFromLines(),
# SetVectorStyle(), SetRasterStyle(),
# HypsometricCurves(),
# FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(),
@ -262,6 +262,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
PointsAlongGeometry(),
PointsInPolygon(),
PointsLayerFromTable(),
PointsToPaths(),
PoleOfInaccessibility(),
Polygonize(),
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
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
# name: join the attribute table by common field
# params: