mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
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:
parent
b4b39996d2
commit
ec4df6c019
@ -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,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}
|
||||
|
@ -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(),
|
||||
|
26
python/plugins/processing/tests/testdata/expected/points_to_path.gfs
vendored
Normal file
26
python/plugins/processing/tests/testdata/expected/points_to_path.gfs
vendored
Normal 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>
|
21
python/plugins/processing/tests/testdata/expected/points_to_path.gml
vendored
Normal file
21
python/plugins/processing/tests/testdata/expected/points_to_path.gml
vendored
Normal 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>
|
31
python/plugins/processing/tests/testdata/expected/points_to_path_grouped.gfs
vendored
Normal file
31
python/plugins/processing/tests/testdata/expected/points_to_path_grouped.gfs
vendored
Normal 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>
|
38
python/plugins/processing/tests/testdata/expected/points_to_path_grouped.gml
vendored
Normal file
38
python/plugins/processing/tests/testdata/expected/points_to_path_grouped.gml
vendored
Normal 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>
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user