123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- """
- /***************************************************************************
- Climb
- begin : 2019-05-15
- copyright : (C) 2019 by Håvard Tveite
- email : havard.tveite@nmbu.no
- ***************************************************************************/
- /***************************************************************************
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 of the License, or *
- * (at your option) any later version. *
- * *
- ***************************************************************************/
- """
- __author__ = 'Håvard Tveite'
- __date__ = '2019-03-01'
- __copyright__ = '(C) 2019 by Håvard Tveite'
- import os
- import math
- from qgis.PyQt.QtGui import QIcon
- from qgis.PyQt.QtCore import QVariant
- from qgis.core import (QgsProcessing,
- QgsFeatureSink,
- QgsProcessingAlgorithm,
- QgsProcessingParameterFeatureSource,
- QgsProcessingParameterFeatureSink,
- QgsProcessingOutputNumber,
- QgsProcessingException,
- QgsProcessingUtils,
- QgsWkbTypes,
- QgsFields,
- QgsField)
- from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
- class Climb(QgisAlgorithm):
- INPUT = 'INPUT'
- OUTPUT = 'OUTPUT'
- TOTALCLIMB = 'TOTALCLIMB'
- TOTALDESCENT = 'TOTALDESCENT'
- MINELEVATION = 'MINELEVATION'
- MAXELEVATION = 'MAXELEVATION'
- CLIMBATTRIBUTE = 'climb'
- DESCENTATTRIBUTE = 'descent'
- MINELEVATTRIBUTE = 'minelev'
- MAXELEVATTRIBUTE = 'maxelev'
- def name(self):
- return 'climbalongline'
- def displayName(self):
- return self.tr('Climb along line')
- def group(self):
- return self.tr('Vector analysis')
- def groupId(self):
- return 'vectoranalysis'
- def __init__(self):
- super().__init__()
- def initAlgorithm(self, config=None):
- self.addParameter(
- QgsProcessingParameterFeatureSource(
- self.INPUT,
- self.tr('Line layer'),
- [QgsProcessing.TypeVectorLine]
- )
- )
- self.addParameter(
- QgsProcessingParameterFeatureSink(
- self.OUTPUT,
- self.tr('Climb layer')
- )
- )
- self.addOutput(
- QgsProcessingOutputNumber(
- self.TOTALCLIMB,
- self.tr('Total climb')
- )
- )
- self.addOutput(
- QgsProcessingOutputNumber(
- self.TOTALDESCENT,
- self.tr('Total descent')
- )
- )
- self.addOutput(
- QgsProcessingOutputNumber(
- self.MINELEVATION,
- self.tr('Minimum elevation')
- )
- )
- self.addOutput(
- QgsProcessingOutputNumber(
- self.MAXELEVATION,
- self.tr('Maximum elevation')
- )
- )
- def processAlgorithm(self, parameters, context, feedback):
- source = self.parameterAsSource(
- parameters,
- self.INPUT,
- context
- )
- fcount = source.featureCount()
- source_fields = source.fields()
- hasZ = QgsWkbTypes.hasZ(source.wkbType())
- if not hasZ:
- raise QgsProcessingException(self.tr('The layer does not have Z values. If you have a DEM, use the Drape algorithm to extract Z values.'))
- thefields = QgsFields()
- climbindex = -1
- descentindex = -1
- minelevindex = -1
- maxelevindex = -1
- fieldnumber = 0
- # Create new fields for climb and descent
- thefields.append(QgsField(self.CLIMBATTRIBUTE, QVariant.Double))
- thefields.append(QgsField(self.DESCENTATTRIBUTE, QVariant.Double))
- thefields.append(QgsField(self.MINELEVATTRIBUTE, QVariant.Double))
- thefields.append(QgsField(self.MAXELEVATTRIBUTE, QVariant.Double))
- # combine all the vector fields
- out_fields = QgsProcessingUtils.combineFields(thefields, source_fields)
- layerwithz = source
- (sink, dest_id) = self.parameterAsSink(parameters,
- self.OUTPUT,
- context,
- out_fields,
- layerwithz.wkbType(),
- source.sourceCrs())
- # get features from source (with z values)
- features = layerwithz.getFeatures()
- totalclimb = 0
- totaldescent = 0
- minelevation = float('Infinity')
- maxelevation = float('-Infinity')
- no_z_nodes = []
- no_geometry = []
- for current, feature in enumerate(features):
- if feedback.isCanceled():
- break
- climb = 0
- descent = 0
- minelev = float('Infinity')
- maxelev = float('-Infinity')
- # In case of multigeometries we need to do the parts
- parts = feature.geometry().constParts()
- if not feature.hasGeometry():
- no_geometry.append(self.tr(
- 'Feature: {feature_id}'.format(
- feature_id=feature.id())
- )
- )
- for partnumber, part in enumerate(parts):
- # Calculate the climb
- first = True
- zval = 0
- for idx, v in enumerate(part.vertices()):
- zval = v.z()
- if math.isnan(zval):
- no_z_nodes.append(self.tr(
- 'Feature: {feature_id}, part: {part_id}, point: {point_id}'.format(
- feature_id=feature.id(),
- part_id=partnumber,
- point_id=idx)
- )
- )
- continue
- if first:
- prevz = zval
- minelev = zval
- maxelev = zval
- first = False
- else:
- diff = zval - prevz
- if diff > 0:
- climb += diff
- else:
- descent -= diff
- minelev = min(minelev, zval)
- maxelev = max(maxelev, zval)
- prevz = zval
- totalclimb += climb
- totaldescent += descent
- # Set the attribute values
- # Append the attributes to the end of the existing ones
- attrs = [
- climb,
- descent,
- minelev,
- maxelev,
- *feature.attributes()
- ]
- # Set the final attribute list
- feature.setAttributes(attrs)
- # Add a feature to the sink
- sink.addFeature(feature, QgsFeatureSink.FastInsert)
- minelevation = min(minelevation, minelev)
- maxelevation = max(maxelevation, maxelev)
- # Update the progress bar
- if fcount > 0:
- feedback.setProgress(int(100 * current / fcount))
- feedback.pushInfo(self.tr(
- 'The following features do not have geometry: {no_geometry_report}'.format(
- no_geometry_report=(', '.join(no_geometry)))
- )
- )
- feedback.pushInfo(self.tr(
- 'The following points do not have Z values: {no_z_report}'.format(
- no_z_report=(', '.join(no_z_nodes)))
- )
- )
- # Return the results
- return {self.OUTPUT: dest_id, self.TOTALCLIMB: totalclimb,
- self.TOTALDESCENT: totaldescent,
- self.MINELEVATION: minelevation,
- self.MAXELEVATION: maxelevation}
|