Skip to content
Permalink
Browse files

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
  • Loading branch information
nyalldawson committed Aug 5, 2017
1 parent b4b3999 commit ec4df6c019efddc05e9555c43d58a155b811d078
@@ -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.
@@ -29,57 +29,58 @@
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')

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 @@ def displayName(self):
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 @@ def processAlgorithm(self, parameters, context, feedback):
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]

fileName = os.path.join(dirName, '%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')

line = []
i = 0
for node in vertices:
line.append(node[1])

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)
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]

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')

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))

f.setGeometry(QgsGeometry(QgsLineString(line)))
sink.addFeature(f, QgsFeatureSink.FastInsert)
current += 1
feedback.setProgress(int(current * total))

del writer
return {self.OUTPUT: dest_id}
@@ -94,6 +94,7 @@
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 .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 @@ def getAlgs(self):
# StatisticsByCategories(),
# RasterLayerStatistics(), PointsDisplacement(),
# PointsFromPolygons(),
# PointsFromLines(), PointsToPaths(),
# PointsFromLines(),
# SetVectorStyle(), SetRasterStyle(),
# HypsometricCurves(),
# FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(),
@@ -262,6 +262,7 @@ def getAlgs(self):
PointsAlongGeometry(),
PointsInPolygon(),
PointsLayerFromTable(),
PointsToPaths(),
PoleOfInaccessibility(),
Polygonize(),
PolygonsToLines(),
@@ -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>
@@ -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>
@@ -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>

0 comments on commit ec4df6c

Please sign in to comment.
You can’t perform that action at this time.