Skip to content

Commit 0d1c9a3

Browse files
committedDec 19, 2016
[processing] implement missed functionality from Raster terrain analysis
plugin
1 parent f9db068 commit 0d1c9a3

File tree

5 files changed

+556
-1
lines changed

5 files changed

+556
-1
lines changed
 

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@
167167
from .Ruggedness import Ruggedness
168168
from .Hillshade import Hillshade
169169
from .ReliefAuto import ReliefAuto
170+
from .Relief import Relief
170171
from .IdwInterpolationZValue import IdwInterpolationZValue
171172
from .IdwInterpolationAttribute import IdwInterpolationAttribute
172173
from .TinInterpolationZValue import TinInterpolationZValue
@@ -247,7 +248,7 @@ def __init__(self):
247248
OffsetLine(), PolygonCentroids(), Translate(),
248249
SingleSidedBuffer(), PointsAlongGeometry(),
249250
Aspect(), Slope(), Ruggedness(), Hillshade(),
250-
ReliefAuto(), ZonalStatisticsQgis(),
251+
ReliefAuto(), Relief(), ZonalStatisticsQgis(),
251252
IdwInterpolationZValue(), IdwInterpolationAttribute(),
252253
TinInterpolationZValue(), TinInterpolationAttribute(),
253254
RemoveNullGeometry(), ExtractByExpression(),
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
Relief.py
6+
---------------------
7+
Date : December 2016
8+
Copyright : (C) 2016 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__ = 'December 2016'
22+
__copyright__ = '(C) 2016, Alexander Bruy'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import os
29+
30+
from qgis.PyQt.QtGui import QIcon, QColor
31+
32+
from qgis.analysis import QgsRelief
33+
34+
from processing.core.GeoAlgorithm import GeoAlgorithm
35+
from processing.core.parameters import (Parameter,
36+
ParameterRaster,
37+
ParameterNumber,
38+
_splitParameterOptions)
39+
from processing.core.outputs import OutputRaster, OutputTable
40+
from processing.tools import raster
41+
42+
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
43+
44+
45+
class Relief(GeoAlgorithm):
46+
47+
INPUT_LAYER = 'INPUT_LAYER'
48+
Z_FACTOR = 'Z_FACTOR'
49+
COLORS = 'COLORS'
50+
OUTPUT_LAYER = 'OUTPUT_LAYER'
51+
FREQUENCY_DISTRIBUTION = 'FREQUENCY_DISTRIBUTION'
52+
53+
def getIcon(self):
54+
return QIcon(os.path.join(pluginPath, 'images', 'dem.png'))
55+
56+
def defineCharacteristics(self):
57+
self.name, self.i18n_name = self.trAlgorithm('Relief')
58+
self.group, self.i18n_group = self.trAlgorithm('Raster terrain analysis')
59+
60+
class ParameterReliefColors(Parameter):
61+
default_metadata = {
62+
'widget_wrapper': 'processing.algs.qgis.ui.ReliefColorsWidget.ReliefColorsWidgetWrapper'
63+
}
64+
65+
def __init__(self, name='', description='', parent=None):
66+
Parameter.__init__(self, name, description)
67+
self.parent = parent
68+
69+
def setValue(self, value):
70+
if value is None:
71+
return False
72+
73+
if isinstance(value, str):
74+
self.value = value
75+
else:
76+
self.value = ParameterReliefColors.colorsToString(value)
77+
return True
78+
79+
def getValueAsCommandLineParameter(self):
80+
return '"{}"'.format(self.value)
81+
82+
def getAsScriptCode(self):
83+
param_type = ''
84+
param_type += 'relief colors '
85+
return '##' + self.name + '=' + param_type
86+
87+
@classmethod
88+
def fromScriptCode(self, line):
89+
isOptional, name, definition = _splitParameterOptions(line)
90+
descName = _createDescriptiveName(name)
91+
parent = definition.lower().strip()[len('relief colors') + 1:]
92+
return ParameterReliefColors(name, description, parent)
93+
94+
@staticmethod
95+
def colorsToString(colors):
96+
s = ''
97+
for c in colors:
98+
s += '{:.2f}, {:.2f}, {:d}, {:d}, {:d};'.format(c[0],
99+
c[1],
100+
c[2],
101+
c[3],
102+
c[4])
103+
return s[:-1]
104+
105+
self.addParameter(ParameterRaster(self.INPUT_LAYER,
106+
self.tr('Elevation layer')))
107+
self.addParameter(ParameterNumber(self.Z_FACTOR,
108+
self.tr('Z factor'), 1.0, 999999.99, 1.0))
109+
self.addParameter(ParameterReliefColors(self.COLORS,
110+
self.tr('Relief colors'), self.INPUT_LAYER))
111+
self.addOutput(OutputRaster(self.OUTPUT_LAYER,
112+
self.tr('Relief')))
113+
self.addOutput(OutputTable(self.FREQUENCY_DISTRIBUTION,
114+
self.tr('Frequency distribution')))
115+
116+
def processAlgorithm(self, progress):
117+
inputFile = self.getParameterValue(self.INPUT_LAYER)
118+
zFactor = self.getParameterValue(self.Z_FACTOR)
119+
colors = self.getParameterValue(self.COLORS).split(';')
120+
outputFile = self.getOutputValue(self.OUTPUT_LAYER)
121+
frequencyDistribution = self.getOutputValue(self.FREQUENCY_DISTRIBUTION)
122+
123+
outputFormat = raster.formatShortNameFromFileName(outputFile)
124+
125+
reliefColors = []
126+
for c in colors:
127+
v = c.split(',')
128+
color = QgsRelief.ReliefColor(QColor(int(v[2]), int(v[3]), int(v[4])),
129+
float(v[0]),
130+
float(v[1]))
131+
reliefColors.append(color)
132+
133+
relief = QgsRelief(inputFile, outputFile, outputFormat)
134+
relief.setReliefColors(reliefColors)
135+
relief.setZFactor(zFactor)
136+
relief.exportFrequencyDistributionToCsv(frequencyDistribution)
137+
relief.processRaster(None)
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
ReliefColorsWidget.py
6+
---------------------
7+
Date : December 2016
8+
Copyright : (C) 2016 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__ = 'December 2016'
22+
__copyright__ = '(C) 2016, Alexander Bruy'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import os
29+
import codecs
30+
31+
from qgis.PyQt import uic
32+
from qgis.PyQt.QtCore import pyqtSlot, QDir
33+
from qgis.PyQt.QtGui import (QIcon,
34+
QBrush,
35+
QColor)
36+
from qgis.PyQt.QtWidgets import (QTreeWidgetItem,
37+
QFileDialog,
38+
QMessageBox,
39+
QInputDialog,
40+
QColorDialog
41+
)
42+
from qgis.PyQt.QtXml import QDomDocument
43+
44+
from qgis.core import QgsApplication, QgsMapLayer
45+
from qgis.analysis import QgsRelief
46+
47+
from processing.gui.wrappers import WidgetWrapper
48+
from processing.tools import system
49+
50+
pluginPath = os.path.dirname(__file__)
51+
WIDGET, BASE = uic.loadUiType(os.path.join(pluginPath, 'reliefcolorswidgetbase.ui'))
52+
53+
54+
class ReliefColorsWidget(BASE, WIDGET):
55+
56+
def __init__(self):
57+
super(ReliefColorsWidget, self).__init__(None)
58+
self.setupUi(self)
59+
60+
self.btnAdd.setIcon(QgsApplication.getThemeIcon('/symbologyAdd.svg'))
61+
self.btnRemove.setIcon(QgsApplication.getThemeIcon('/symbologyRemove.svg'))
62+
self.btnUp.setIcon(QgsApplication.getThemeIcon('/symbologyUp.svg'))
63+
self.btnDown.setIcon(QgsApplication.getThemeIcon('/symbologyDown.svg'))
64+
self.btnLoad.setIcon(QgsApplication.getThemeIcon('/mActionFileOpen.svg'))
65+
self.btnSave.setIcon(QgsApplication.getThemeIcon('/mActionFileSave.svg'))
66+
self.btnAuto.setIcon(QgsApplication.getThemeIcon('/mActionDraw.svg'))
67+
68+
self.layer = None
69+
70+
@pyqtSlot()
71+
def on_btnAdd_clicked(self):
72+
item = QTreeWidgetItem()
73+
item.setText(0, '0.00')
74+
item.setText(1, '0.00')
75+
item.setBackground(2, QBrush(QColor(127, 127, 127)))
76+
self.reliefClassTree.addTopLevelItem(item)
77+
78+
@pyqtSlot()
79+
def on_btnRemove_clicked(self):
80+
selectedItems = self.reliefClassTree.selectedItems()
81+
for item in selectedItems:
82+
self.reliefClassTree.invisibleRootItem().removeChild(item)
83+
item = None
84+
85+
@pyqtSlot()
86+
def on_btnDown_clicked(self):
87+
selectedItems = self.reliefClassTree.selectedItems()
88+
for item in selectedItems:
89+
currentIndex = self.reliefClassTree.indexOfTopLevelItem(item)
90+
if currentIndex < self.reliefClassTree.topLevelItemCount() - 1:
91+
self.reliefClassTree.takeTopLevelItem(currentIndex)
92+
self.reliefClassTree.insertTopLevelItem(currentIndex + 1, item)
93+
self.reliefClassTree.setCurrentItem(item)
94+
95+
@pyqtSlot()
96+
def on_btnUp_clicked(self):
97+
selectedItems = self.reliefClassTree.selectedItems()
98+
for item in selectedItems:
99+
currentIndex = self.reliefClassTree.indexOfTopLevelItem(item)
100+
if currentIndex > 0:
101+
self.reliefClassTree.takeTopLevelItem(currentIndex)
102+
self.reliefClassTree.insertTopLevelItem(currentIndex - 1, item)
103+
self.reliefClassTree.setCurrentItem(item)
104+
105+
@pyqtSlot()
106+
def on_btnLoad_clicked(self):
107+
fileName, _ = QFileDialog.getOpenFileName(None,
108+
self.tr('Import Colors and elevations from XML'),
109+
QDir.homePath(),
110+
self.tr('XML files (*.xml *.XML)'))
111+
if fileName == '':
112+
return
113+
114+
doc = QDomDocument()
115+
with codecs.open(fileName, 'r', encoding='utf-8') as f:
116+
content = f.read()
117+
118+
if not doc.setContent(content):
119+
QMessageBox.critical(None,
120+
self.tr('Error parsing XML'),
121+
self.tr('The XML file could not be loaded'))
122+
return
123+
124+
self.reliefClassTree.clear()
125+
reliefColorList = doc.elementsByTagName('ReliefColor')
126+
for i in range(reliefColorList.length()):
127+
elem = reliefColorList.at(i).toElement()
128+
item = QTreeWidgetItem()
129+
item.setText(0, elem.attribute('MinElevation'))
130+
item.setText(1, elem.attribute('MaxElevation'))
131+
item.setBackground(2, QBrush(QColor(int(elem.attribute('red')),
132+
int(elem.attribute('green')),
133+
int(elem.attribute('blue')))))
134+
self.reliefClassTree.addTopLevelItem(item)
135+
136+
@pyqtSlot()
137+
def on_btnSave_clicked(self):
138+
fileName, _ = QFileDialog.getSaveFileName(None,
139+
self.tr('Export Colors and elevations as XML'),
140+
QDir.homePath(),
141+
self.tr('XML files (*.xml *.XML)'))
142+
143+
if fileName == '':
144+
return
145+
146+
if not fileName.lower().endswith('.xml'):
147+
fileName += '.xml'
148+
149+
doc = QDomDocument()
150+
colorsElem = doc.createElement('ReliefColors')
151+
doc.appendChild(colorsElem)
152+
153+
colors = self.reliefColors()
154+
for c in colors:
155+
elem = doc.createElement('ReliefColor')
156+
elem.setAttribute('MinElevation', str(c.minElevation))
157+
elem.setAttribute('MaxElevation', str(c.maxElevation))
158+
elem.setAttribute('red', str(c.color.red()))
159+
elem.setAttribute('green', str(c.color.green()))
160+
elem.setAttribute('blue', str(c.color.blue()))
161+
colorsElem.appendChild(elem)
162+
163+
with codecs.open(fileName, 'w', encoding='utf-8') as f:
164+
f.write(doc.toString(2))
165+
166+
@pyqtSlot()
167+
def on_btnAuto_clicked(self):
168+
if self.layer is None:
169+
return
170+
171+
relief = QgsRelief(self.layer, system.getTempFilename(), 'GTiff')
172+
colors = relief.calculateOptimizedReliefClasses()
173+
self.populateColors(colors)
174+
175+
@pyqtSlot(QTreeWidgetItem, int)
176+
def on_reliefClassTree_itemDoubleClicked(self, item, column):
177+
if not item:
178+
return
179+
180+
if column == 0:
181+
d, ok = QInputDialog.getDouble(None,
182+
self.tr('Enter lower elevation class bound'),
183+
self.tr('Elevation'),
184+
float(item.text(0)),
185+
decimals=2)
186+
if ok:
187+
item.setText(0, str(d))
188+
elif column == 1:
189+
d, ok = QInputDialog.getDouble(None,
190+
self.tr('Enter upper elevation class bound'),
191+
self.tr('Elevation'),
192+
float(item.text(1)),
193+
decimals=2)
194+
if ok:
195+
item.setText(1, str(d))
196+
elif column == 2:
197+
c = QColorDialog.getColor(item.background(2).color(),
198+
None,
199+
self.tr('Select color for relief class'))
200+
if c.isValid():
201+
item.setBackground(2, QBrush(c))
202+
203+
def reliefColors(self):
204+
colors = []
205+
for i in range(self.reliefClassTree.topLevelItemCount()):
206+
item = self.reliefClassTree.topLevelItem(i)
207+
if item:
208+
c = QgsRelief.ReliefColor(item.background(2).color(),
209+
float(item.text(0)),
210+
float(item.text(1)))
211+
colors.append(c)
212+
return colors
213+
214+
def populateColors(self, colors):
215+
self.reliefClassTree.clear()
216+
for c in colors:
217+
item = QTreeWidgetItem()
218+
item.setText(0, str(c.minElevation))
219+
item.setText(1, str(c.maxElevation))
220+
item.setBackground(2, QBrush(c.color))
221+
self.reliefClassTree.addTopLevelItem(item)
222+
223+
def setLayer(self, layer):
224+
self.layer = layer
225+
226+
def setValue(self, value):
227+
self.reliefClassTree.clear()
228+
rows = value.split(';')
229+
for r in rows:
230+
v = r.split(',')
231+
item = QTreeWidgetItem()
232+
item.setText(0, v[0])
233+
item.setText(1, v[1])
234+
color = QColor(int(v[2]), int(v[3]), int(v[4]))
235+
item.setBackground(2, QBrush(color))
236+
self.reliefClassTree.addTopLevelItem(item)
237+
238+
def value(self):
239+
rColors = self.reliefColors()
240+
colors = ''
241+
for c in rColors:
242+
colors += '{:.2f}, {:.2f}, {:d}, {:d}, {:d};'.format(c.minElevation,
243+
c.maxElevation,
244+
c.color.red(),
245+
c.color.green(),
246+
c.color.blue())
247+
return colors[:-1]
248+
249+
250+
class ReliefColorsWidgetWrapper(WidgetWrapper):
251+
252+
def createWidget(self):
253+
return ReliefColorsWidget()
254+
255+
def postInitialize(self, wrappers):
256+
for wrapper in wrappers:
257+
if wrapper.param.name == self.param.parent:
258+
self.setLayer(wrapper.value())
259+
wrapper.widgetValueHasChanged.connect(self.parentValueChanged)
260+
break
261+
262+
def parentValueChanged(self, wrapper):
263+
self.setLayer(wrapper.value())
264+
265+
def setLayer(self, layer):
266+
if isinstance(layer, QgsMapLayer):
267+
layer = layer.source()
268+
self.widget.setLayer(layer)
269+
270+
def setValue(self, value):
271+
self.widget.setValue(value)
272+
273+
def value(self):
274+
return self.widget.value()
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>Form</class>
4+
<widget class="QWidget" name="Form">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>505</width>
10+
<height>209</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Form</string>
15+
</property>
16+
<layout class="QGridLayout" name="gridLayout">
17+
<item row="0" column="0">
18+
<widget class="QTreeWidget" name="reliefClassTree">
19+
<column>
20+
<property name="text">
21+
<string>Lower bound</string>
22+
</property>
23+
</column>
24+
<column>
25+
<property name="text">
26+
<string>Upper bound</string>
27+
</property>
28+
</column>
29+
<column>
30+
<property name="text">
31+
<string>Color</string>
32+
</property>
33+
</column>
34+
</widget>
35+
</item>
36+
<item row="0" column="1">
37+
<layout class="QVBoxLayout" name="verticalLayout">
38+
<item>
39+
<widget class="QToolButton" name="btnAdd">
40+
<property name="toolTip">
41+
<string>Add row</string>
42+
</property>
43+
<property name="text">
44+
<string>...</string>
45+
</property>
46+
</widget>
47+
</item>
48+
<item>
49+
<widget class="QToolButton" name="btnRemove">
50+
<property name="toolTip">
51+
<string>Remove row</string>
52+
</property>
53+
<property name="text">
54+
<string>...</string>
55+
</property>
56+
</widget>
57+
</item>
58+
<item>
59+
<widget class="QToolButton" name="btnUp">
60+
<property name="toolTip">
61+
<string>Move up</string>
62+
</property>
63+
<property name="text">
64+
<string>...</string>
65+
</property>
66+
</widget>
67+
</item>
68+
<item>
69+
<widget class="QToolButton" name="btnDown">
70+
<property name="toolTip">
71+
<string>Move down</string>
72+
</property>
73+
<property name="text">
74+
<string>...</string>
75+
</property>
76+
</widget>
77+
</item>
78+
<item>
79+
<spacer name="verticalSpacer">
80+
<property name="orientation">
81+
<enum>Qt::Vertical</enum>
82+
</property>
83+
<property name="sizeHint" stdset="0">
84+
<size>
85+
<width>20</width>
86+
<height>40</height>
87+
</size>
88+
</property>
89+
</spacer>
90+
</item>
91+
<item>
92+
<widget class="QToolButton" name="btnLoad">
93+
<property name="toolTip">
94+
<string>Load colors from file</string>
95+
</property>
96+
<property name="text">
97+
<string>...</string>
98+
</property>
99+
</widget>
100+
</item>
101+
<item>
102+
<widget class="QToolButton" name="btnSave">
103+
<property name="toolTip">
104+
<string>Save colors to file</string>
105+
</property>
106+
<property name="text">
107+
<string>...</string>
108+
</property>
109+
</widget>
110+
</item>
111+
<item>
112+
<widget class="QToolButton" name="btnAuto">
113+
<property name="toolTip">
114+
<string>Generate color table automatically</string>
115+
</property>
116+
<property name="text">
117+
<string>...</string>
118+
</property>
119+
</widget>
120+
</item>
121+
</layout>
122+
</item>
123+
</layout>
124+
</widget>
125+
<resources/>
126+
<connections/>
127+
</ui>

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,22 @@ tests:
11481148
hash: 7fe0e0174185fd743e23760f33615adf10f771b4275f320db6f7f4f8
11491149
type: rasterhash
11501150

1151+
- algorithm: qgis:relief
1152+
name: Relief (custom colors)
1153+
params:
1154+
COLORS: 85.00, 104.44, 7, 165, 144;104.44, 104.44, 12, 221, 162;104.44, 104.44,
1155+
33, 252, 183;104.44, 104.44, 247, 252, 152;104.44, 104.44, 252, 196, 8;104.44,
1156+
190.33, 252, 166, 15;190.33, 226.70, 175, 101, 15;226.70, 226.70, 255, 133,
1157+
92;226.70, 243.00, 204, 204, 204
1158+
INPUT_LAYER:
1159+
name: dem.tif
1160+
type: raster
1161+
Z_FACTOR: 1.0
1162+
results:
1163+
OUTPUT_LAYER:
1164+
hash: 6c79ec9b948c8e878aa490670e8a26a0b6efc5f9d162a0fff1042d80
1165+
type: rasterhash
1166+
11511167
# Case 1: Keep all fields
11521168
- algorithm: qgis:lineintersections
11531169
name: Line Intersection Keep All Fields from Both

0 commit comments

Comments
 (0)
Please sign in to comment.