mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-30 00:07:09 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			250 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| """
 | |
| /***************************************************************************
 | |
|  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}
 |