1# -*- coding: utf-8 -*- 2 3""" 4*************************************************************************** 5 PointsToPaths.py 6 --------------------- 7 Date : April 2014 8 Copyright : (C) 2014 by Alexander Bruy 9 Email : alexander dot bruy at gmail dot com 10*************************************************************************** 11* * 12* This program is free software; you can redistribute it and/or modify * 13* it under the terms of the GNU General Public License as published by * 14* the Free Software Foundation; either version 2 of the License, or * 15* (at your option) any later version. * 16* * 17*************************************************************************** 18""" 19 20__author__ = 'Alexander Bruy' 21__date__ = 'April 2014' 22__copyright__ = '(C) 2014, Alexander Bruy' 23 24import os 25from datetime import datetime 26 27from qgis.core import (QgsFeature, 28 QgsFeatureSink, 29 QgsFields, 30 QgsField, 31 QgsGeometry, 32 QgsDistanceArea, 33 QgsPointXY, 34 QgsLineString, 35 QgsWkbTypes, 36 QgsFeatureRequest, 37 QgsProcessingException, 38 QgsProcessingParameterBoolean, 39 QgsProcessingParameterFeatureSource, 40 QgsProcessingParameterField, 41 QgsProcessingParameterString, 42 QgsProcessingFeatureSource, 43 QgsProcessing, 44 QgsProcessingParameterFeatureSink, 45 QgsProcessingParameterFolderDestination) 46 47from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm 48 49 50class PointsToPaths(QgisAlgorithm): 51 INPUT = 'INPUT' 52 CLOSE_PATH = 'CLOSE_PATH' 53 GROUP_FIELD = 'GROUP_FIELD' 54 ORDER_FIELD = 'ORDER_FIELD' 55 DATE_FORMAT = 'DATE_FORMAT' 56 OUTPUT = 'OUTPUT' 57 OUTPUT_TEXT_DIR = 'OUTPUT_TEXT_DIR' 58 59 def group(self): 60 return self.tr('Vector creation') 61 62 def groupId(self): 63 return 'vectorcreation' 64 65 def __init__(self): 66 super().__init__() 67 68 def tags(self): 69 return self.tr('join,points,lines,connect').split(',') 70 71 def initAlgorithm(self, config=None): 72 self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, 73 self.tr('Input point layer'), [QgsProcessing.TypeVectorPoint])) 74 self.addParameter(QgsProcessingParameterBoolean(self.CLOSE_PATH, 75 self.tr('Close path'), defaultValue=False)) 76 self.addParameter(QgsProcessingParameterField(self.ORDER_FIELD, 77 self.tr('Order field'), parentLayerParameterName=self.INPUT)) 78 self.addParameter(QgsProcessingParameterField(self.GROUP_FIELD, 79 self.tr('Group field'), parentLayerParameterName=self.INPUT, optional=True)) 80 self.addParameter(QgsProcessingParameterString(self.DATE_FORMAT, 81 self.tr('Date format (if order field is DateTime)'), optional=True)) 82 83 self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Paths'), QgsProcessing.TypeVectorLine)) 84 output_dir_param = QgsProcessingParameterFolderDestination(self.OUTPUT_TEXT_DIR, self.tr('Directory for text output'), optional=True) 85 output_dir_param.setCreateByDefault(False) 86 self.addParameter(output_dir_param) 87 88 def name(self): 89 return 'pointstopath' 90 91 def displayName(self): 92 return self.tr('Points to path') 93 94 def processAlgorithm(self, parameters, context, feedback): 95 source = self.parameterAsSource(parameters, self.INPUT, context) 96 if source is None: 97 raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) 98 99 close_path = self.parameterAsBool(parameters, self.CLOSE_PATH, context) 100 group_field_name = self.parameterAsString(parameters, self.GROUP_FIELD, context) 101 order_field_name = self.parameterAsString(parameters, self.ORDER_FIELD, context) 102 date_format = self.parameterAsString(parameters, self.DATE_FORMAT, context) 103 text_dir = self.parameterAsString(parameters, self.OUTPUT_TEXT_DIR, context) 104 105 group_field_index = source.fields().lookupField(group_field_name) 106 order_field_index = source.fields().lookupField(order_field_name) 107 108 if group_field_index >= 0: 109 group_field_def = source.fields().at(group_field_index) 110 else: 111 group_field_def = None 112 order_field_def = source.fields().at(order_field_index) 113 114 fields = QgsFields() 115 if group_field_def is not None: 116 fields.append(group_field_def) 117 begin_field = QgsField(order_field_def) 118 begin_field.setName('begin') 119 fields.append(begin_field) 120 end_field = QgsField(order_field_def) 121 end_field.setName('end') 122 fields.append(end_field) 123 124 output_wkb = QgsWkbTypes.LineString 125 if QgsWkbTypes.hasM(source.wkbType()): 126 output_wkb = QgsWkbTypes.addM(output_wkb) 127 if QgsWkbTypes.hasZ(source.wkbType()): 128 output_wkb = QgsWkbTypes.addZ(output_wkb) 129 130 (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, 131 fields, output_wkb, source.sourceCrs()) 132 if sink is None: 133 raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) 134 135 if text_dir and not(os.path.exists(text_dir)): 136 raise QgsProcessingException(self.tr("The text output directory does not exist")) 137 138 points = dict() 139 features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([group_field_index, order_field_index]), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) 140 total = 100.0 / source.featureCount() if source.featureCount() else 0 141 for current, f in enumerate(features): 142 if feedback.isCanceled(): 143 break 144 145 if not f.hasGeometry(): 146 continue 147 148 point = f.geometry().constGet().clone() 149 if group_field_index >= 0: 150 group = f[group_field_index] 151 else: 152 group = 1 153 order = f[order_field_index] 154 if date_format != '': 155 order = datetime.strptime(str(order), date_format) 156 if group in points: 157 points[group].append((order, point)) 158 else: 159 points[group] = [(order, point)] 160 161 feedback.setProgress(int(current * total)) 162 163 feedback.setProgress(0) 164 165 da = QgsDistanceArea() 166 da.setSourceCrs(source.sourceCrs(), context.transformContext()) 167 da.setEllipsoid(context.ellipsoid()) 168 169 current = 0 170 total = 100.0 / len(points) if points else 1 171 for group, vertices in points.items(): 172 if feedback.isCanceled(): 173 break 174 175 vertices.sort(key=lambda x: (x[0] is None, x[0])) 176 f = QgsFeature() 177 attributes = [] 178 if group_field_index >= 0: 179 attributes.append(group) 180 attributes.extend([vertices[0][0], vertices[-1][0]]) 181 f.setAttributes(attributes) 182 line = [node[1] for node in vertices] 183 184 if close_path is True: 185 if line[0] != line[-1]: 186 line.append(line[0]) 187 188 if text_dir: 189 fileName = os.path.join(text_dir, '%s.txt' % group) 190 191 with open(fileName, 'w') as fl: 192 fl.write('angle=Azimuth\n') 193 fl.write('heading=Coordinate_System\n') 194 fl.write('dist_units=Default\n') 195 196 for i in range(len(line)): 197 if i == 0: 198 fl.write('startAt=%f;%f;90\n' % (line[i].x(), line[i].y())) 199 fl.write('survey=Polygonal\n') 200 fl.write('[data]\n') 201 else: 202 angle = line[i - 1].azimuth(line[i]) 203 distance = da.measureLine(QgsPointXY(line[i - 1]), QgsPointXY(line[i])) 204 fl.write('%f;%f;90\n' % (angle, distance)) 205 206 f.setGeometry(QgsGeometry(QgsLineString(line))) 207 sink.addFeature(f, QgsFeatureSink.FastInsert) 208 current += 1 209 feedback.setProgress(int(current * total)) 210 211 return {self.OUTPUT: dest_id} 212