Skip to content
Permalink
Browse files

[FEATURE][processing] Choice of units for non degree/unknown distances

When an algorithm has a distance parameter in meters/feet/etc (i.e.
non-geographic distances), show a combo box allowing choice of
unit type.

(We don't (and should never) expose this for distances in degrees --
it's up to users in this situation to choose a suitable local
projection and reproject their data to match. Refs: a recent
talk by @volaya)
  • Loading branch information
nyalldawson committed Jul 26, 2018
1 parent d1d6840 commit 2692de6ed084cb068bbefe00d6c87f0fd10e0c57
Showing with 73 additions and 2 deletions.
  1. +36 −2 python/plugins/processing/gui/NumberInputPanel.py
  2. +37 −0 python/plugins/processing/tests/GuiTest.py
@@ -32,7 +32,7 @@

from qgis.PyQt import uic
from qgis.PyQt.QtCore import pyqtSignal, QSize
from qgis.PyQt.QtWidgets import QDialog, QLabel
from qgis.PyQt.QtWidgets import QDialog, QLabel, QComboBox

from qgis.core import (QgsApplication,
QgsExpression,
@@ -268,22 +268,42 @@ def __init__(self, param):
super().__init__(param)

self.label = QLabel('')

self.units_combo = QComboBox()
self.base_units = QgsUnitTypes.DistanceUnknownUnit
for u in (QgsUnitTypes.DistanceMeters,
QgsUnitTypes.DistanceKilometers,
QgsUnitTypes.DistanceFeet,
QgsUnitTypes.DistanceMiles,
QgsUnitTypes.DistanceYards):
self.units_combo.addItem(QgsUnitTypes.toString(u), u)

label_margin = self.fontMetrics().width('X')
self.layout().insertSpacing(1, label_margin / 2)
self.layout().insertWidget(2, self.label)
self.layout().insertSpacing(3, label_margin / 2)
self.layout().insertWidget(3, self.units_combo)
self.layout().insertSpacing(4, label_margin / 2)
self.warning_label = QLabel()
icon = QgsApplication.getThemeIcon('mIconWarning.svg')
size = max(24, self.spnValue.height() * 0.5)
self.warning_label.setPixmap(icon.pixmap(icon.actualSize(QSize(size, size))))
self.warning_label.setToolTip(self.tr('Distance is in geographic degrees. Consider reprojecting to a projected local coordinate system for accurate results.'))
self.layout().insertWidget(4, self.warning_label)
self.layout().insertSpacing(5, label_margin)

self.setUnits(QgsUnitTypes.DistanceUnknownUnit)

def setUnits(self, units):
self.label.setText(QgsUnitTypes.toString(units))
if QgsUnitTypes.unitType(units) != QgsUnitTypes.Standard:
self.units_combo.hide()
self.label.show()
else:
self.units_combo.setCurrentIndex(self.units_combo.findData(units))
self.units_combo.show()
self.label.hide()
self.warning_label.setVisible(units == QgsUnitTypes.DistanceDegrees)
self.base_units = units

def setUnitParameterValue(self, value):
units = QgsUnitTypes.DistanceUnknownUnit
@@ -297,3 +317,17 @@ def setUnitParameterValue(self, value):
if crs.isValid():
units = crs.mapUnits()
self.setUnits(units)

def getValue(self):
val = super().getValue()
if isinstance(val, float) and self.units_combo.isVisible():
display_unit = self.units_combo.currentData()
return val * QgsUnitTypes.fromUnitToUnitFactor(display_unit, self.base_units)

return val

def setValue(self, value):
try:
self.spnValue.setValue(float(value))
except:
return
@@ -176,35 +176,72 @@ def testDistance(self):
widget.setUnitParameterValue('EPSG:3111')
self.assertEqual(widget.label.text(), 'meters')
self.assertFalse(widget.warning_label.isVisible())
self.assertTrue(widget.units_combo.isVisible())
self.assertFalse(widget.label.isVisible())
self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)

widget.setUnitParameterValue('EPSG:4326')
self.assertEqual(widget.label.text(), 'degrees')
self.assertTrue(widget.warning_label.isVisible())
self.assertFalse(widget.units_combo.isVisible())
self.assertTrue(widget.label.isVisible())

widget.setUnitParameterValue(QgsCoordinateReferenceSystem('EPSG:3111'))
self.assertEqual(widget.label.text(), 'meters')
self.assertFalse(widget.warning_label.isVisible())
self.assertTrue(widget.units_combo.isVisible())
self.assertFalse(widget.label.isVisible())
self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)

widget.setUnitParameterValue(QgsCoordinateReferenceSystem('EPSG:4326'))
self.assertEqual(widget.label.text(), 'degrees')
self.assertTrue(widget.warning_label.isVisible())
self.assertFalse(widget.units_combo.isVisible())
self.assertTrue(widget.label.isVisible())

# layer values
vl = QgsVectorLayer("Polygon?crs=epsg:3111&field=pk:int", "vl", "memory")
widget.setUnitParameterValue(vl)
self.assertEqual(widget.label.text(), 'meters')
self.assertFalse(widget.warning_label.isVisible())
self.assertTrue(widget.units_combo.isVisible())
self.assertFalse(widget.label.isVisible())
self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)

vl2 = QgsVectorLayer("Polygon?crs=epsg:4326&field=pk:int", "vl", "memory")
widget.setUnitParameterValue(vl2)
self.assertEqual(widget.label.text(), 'degrees')
self.assertTrue(widget.warning_label.isVisible())
self.assertFalse(widget.units_combo.isVisible())
self.assertTrue(widget.label.isVisible())

# unresolvable values
widget.setUnitParameterValue(vl.id())
self.assertEqual(widget.label.text(), '<unknown>')
self.assertFalse(widget.warning_label.isVisible())
self.assertFalse(widget.units_combo.isVisible())
self.assertTrue(widget.label.isVisible())

# resolvable text value
QgsProject.instance().addMapLayer(vl)
widget.setUnitParameterValue(vl.id())
self.assertEqual(widget.label.text(), 'meters')
self.assertFalse(widget.warning_label.isVisible())
self.assertTrue(widget.units_combo.isVisible())
self.assertFalse(widget.label.isVisible())
self.assertEqual(widget.units_combo.currentData(), QgsUnitTypes.DistanceMeters)

widget.setValue(5)
self.assertEqual(widget.getValue(), 5)
widget.units_combo.setCurrentIndex(widget.units_combo.findData(QgsUnitTypes.DistanceKilometers))
self.assertEqual(widget.getValue(), 5000)
widget.setValue(2)
self.assertEqual(widget.getValue(), 2000)

widget.setUnitParameterValue(vl.id())
self.assertEqual(widget.getValue(), 2)
widget.setValue(5)
self.assertEqual(widget.getValue(), 5)

widget.deleteLater()

0 comments on commit 2692de6

Please sign in to comment.
You can’t perform that action at this time.