Skip to content

Commit

Permalink
[feature[ Processing Climb along lines algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
ghtmtt committed May 15, 2019
1 parent 2cc8e11 commit 4ca3935
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 0 deletions.
8 changes: 8 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -53,6 +53,14 @@ qgis:checkvalidity: >

By default the algorithm uses the strict OGC definition of polygon validity, where a polygon is marked as invalid if a self-intersecting ring causes an interior hole. If the "Ignore ring self intersections" option is checked, then this rule will be ignored and a more lenient validity check will be performed.

qgis:climbalongline: >
This algorithm calculates the total climb and descent along line geometries.

It works only if the line layer already has Z values on the vertex. If it does not, consider using the Drape (set z-value from
raster) algorithm.

The output layer is a copy of the input layer with some additional fields that represent the total climb, total descent, the minimum elevation and the maximum elevation.

qgis:clip: >
This algorithm clips a vector layer using the polygons of an additional polygons layer. Only the parts of the features in the input layer that falls within the polygons of the clipping layer will be added to the resulting layer.

Expand Down
243 changes: 243 additions & 0 deletions python/plugins/processing/algs/qgis/Climb.py
@@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-

"""
/***************************************************************************
Climb
A QGIS plugin
-------------------
begin : 2019-03-01
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'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'

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,
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('Input (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()

hasZ = QgsWkbTypes.hasZ(source.wkbType())

if not hasZ:
feedback.reportError(self.tr('The layer has not Z values. Please use the Drape algorithm with a DEM layer to extact the Z value.'))
return

thefields = QgsFields()
climbindex = -1
descentindex = -1
fieldnumber = 0

# Skip fields with names that are equal to the generated ones
for field in source.fields():
if str(field.name()) == str(self.CLIMBATTRIBUTE):
feedback.pushInfo("Warning: existing " +
str(self.CLIMBATTRIBUTE) +
" attribute found and removed")
climbindex = fieldnumber
elif str(field.name()) == str(self.DESCENTATTRIBUTE):
feedback.pushInfo("Warning: existing " +
str(self.DESCENTATTRIBUTE) +
" attribute found and removed")
descentindex = fieldnumber
else:
thefields.append(field)
fieldnumber = fieldnumber + 1

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

layerwithz = source

(sink, dest_id) = self.parameterAsSink(parameters,
self.OUTPUT,
context, thefields,
layerwithz.wkbType(),
source.sourceCrs())

# get features from source (with z values)
features = layerwithz.getFeatures()
totalclimb = 0
totaldescent = 0
minelevation = float('Infinity')
maxelevation = float('-Infinity')

for current, feature in enumerate(features):
# Stop the algorithm if cancelled
if feedback.isCanceled():
break
climb = 0
descent = 0
minelev = float('Infinity')
maxelev = float('-Infinity')
# In case of multigeometries we need to do the parts
for part in feature.geometry().constParts():
# Calculate the climb
first = True
zval = 0
for v in part.vertices():
zval = v.z()
if first:
prevz = zval
minelev = zval
maxelev = zval
first = False
else:
diff = zval - prevz
if diff > 0:
climb = climb + diff
else:
descent = descent - diff
if minelev > zval:
minelev = zval
if maxelev < zval:
maxelev = zval
prevz = zval
totalclimb = totalclimb + climb
totaldescent = totaldescent + descent
# Set the attribute values
attrs = feature.attributes()
outattrs = []
attrindex = 0
for attr in attrs:
# Skip attributes from the input layer that had names
# that were equal to the generated ones
if not (attrindex == climbindex or
attrindex == descentindex):
outattrs.append(attr)
attrindex = attrindex + 1
# feature.setAttributes(outattrs + [climb, descent])
feature.setAttributes(outattrs +
[climb, descent, minelev, maxelev])
# Add a feature to the sink
sink.addFeature(feature, QgsFeatureSink.FastInsert)
if minelevation > minelev:
minelevation = minelev
if maxelevation < maxelev:
maxelevation = maxelev
# Update the progress bar
if fcount > 0:
feedback.setProgress(int(100 * current / fcount))
# Return the results
return {self.OUTPUT: dest_id, self.TOTALCLIMB: totalclimb,
self.TOTALDESCENT: totaldescent,
self.MINELEVATION: minelevation,
self.MAXELEVATION: maxelevation}
2 changes: 2 additions & 0 deletions python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py
Expand Up @@ -52,6 +52,7 @@
from .Aspect import Aspect
from .BasicStatistics import BasicStatisticsForField
from .CheckValidity import CheckValidity
from .Climb import Climb
from .ConcaveHull import ConcaveHull
from .CreateAttributeIndex import CreateAttributeIndex
from .CreateConstantRaster import CreateConstantRaster
Expand Down Expand Up @@ -163,6 +164,7 @@ def getAlgs(self):
Aspect(),
BasicStatisticsForField(),
CheckValidity(),
Climb(),
ConcaveHull(),
CreateAttributeIndex(),
CreateConstantRaster(),
Expand Down
@@ -0,0 +1 @@
UTF-8
Binary file not shown.
@@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
@@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
@@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]
Binary file not shown.
Binary file not shown.
11 changes: 11 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -1827,4 +1827,15 @@ tests:
name: expected/polygon_from_extent_rounded_2.gml
type: vector

- algorithm: qgis:climbalongline
name: Climb line layer with DTM
params:
INPUT:
name: custom/climb_line.shp
type: vector
results:
OUTPUT:
name: expected/climb_layer_result.shp
type: vector

# See ../README.md for a description of the file format

0 comments on commit 4ca3935

Please sign in to comment.