Skip to content
Permalink
Browse files

[needs-docs][processing] Port SAGA raster surface volume to native QG…

…IS alg

The SAGA version of this algorithm is of limited use in QGIS, because the
volume calculated is embedded only in the SAGA terminal output. This prevents
it being saved to a file, or reused within a model as an input to a later
model step.

It's also very user-unfriendly, because users must know to manually scan
the algorithm log to find the SAGA output.

Given that the maths here is trivial, this commit ports the algorithm across
to be a native QGIS c++ algorithm. The algorithm duplicates the SAGA alg
1:1, but outputs the volume (and area) to either a HTML report, or a vector
table. Additionally, the outputs are exported as numeric outputs from the
algorithm, allowing them to be re-used within models.

(It's also considerably faster, because it avoids the forced conversion
to SAGA raster format)

Fixes #8607 (properly, even though that report is closed)
  • Loading branch information
nyalldawson committed Jan 21, 2019
1 parent e3fda18 commit 195d98f43a7d290015487b5bd2d4211cd38dfe9c
Showing with 1,114 additions and 0 deletions.
  1. BIN python/plugins/processing/tests/testdata/custom/dem_crs.tif
  2. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_above.gml
  3. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_above.html
  4. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_above.xsd
  5. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_above_crs.gml
  6. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_above_crs.html
  7. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_above_crs.xsd
  8. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_add.gml
  9. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_add.html
  10. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_add.xsd
  11. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_add_crs.gml
  12. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_add_crs.html
  13. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_add_crs.xsd
  14. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_below.gml
  15. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_below.html
  16. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_below.xsd
  17. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_below_crs.gml
  18. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_below_crs.html
  19. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_below_crs.xsd
  20. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_gaps.gml
  21. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_gaps.xsd
  22. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_subtract.gml
  23. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_subtract.html
  24. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_subtract.xsd
  25. +16 −0 python/plugins/processing/tests/testdata/expected/surface_vol_subtract_crs.gml
  26. +6 −0 python/plugins/processing/tests/testdata/expected/surface_vol_subtract_crs.html
  27. +45 −0 python/plugins/processing/tests/testdata/expected/surface_vol_subtract_crs.xsd
  28. +182 −0 python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
  29. +1 −0 src/analysis/CMakeLists.txt
  30. +250 −0 src/analysis/processing/qgsalgorithmrastersurfacevolume.cpp
  31. +82 −0 src/analysis/processing/qgsalgorithmrastersurfacevolume.h
  32. +2 −0 src/analysis/processing/qgsnativealgorithms.cpp
Binary file not shown.
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ surface_vol_above.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

<gml:featureMember>
<ogr:surface_vol_above fid="surface_vol_above.0">
<ogr:volume>0.06480527</ogr:volume>
<ogr:deg2>0.00095901</ogr:deg2>
<ogr:pixel_count>95901</ogr:pixel_count>
</ogr:surface_vol_above>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,6 @@
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/></head><body>
<p>Analyzed file: /home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/dem.tif (band 1)</p>
<p>Volume: 0.0648052733680633</p>
<p>Pixel count: 95901</p>
<p>Area: 0.0009590099999998638 deg2</p>
</body></html>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="surface_vol_above" type="ogr:surface_vol_above_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="surface_vol_above_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="volume" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="deg2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="pixel_count" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:long">
<xs:totalDigits value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ surface_vol_above_crs.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

<gml:featureMember>
<ogr:surface_vol_above_crs fid="surface_vol_above_crs.0">
<ogr:volume>413784918.00990051</ogr:volume>
<ogr:m2>11497732.58555590</ogr:m2>
<ogr:pixel_count>64692</ogr:pixel_count>
</ogr:surface_vol_above_crs>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,6 @@
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/></head><body>
<p>Analyzed file: /home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/dem_crs.tif (band 1)</p>
<p>Volume: 413784918.0099005</p>
<p>Pixel count: 64692</p>
<p>Area: 11497732.5855559 m2</p>
</body></html>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="surface_vol_above_crs" type="ogr:surface_vol_above_crs_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="surface_vol_above_crs_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="volume" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="m2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="pixel_count" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:long">
<xs:totalDigits value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ surface_vol_add.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

<gml:featureMember>
<ogr:surface_vol_add fid="surface_vol_add.0">
<ogr:volume>0.06802753</ogr:volume>
<ogr:deg2>0.00130550</ogr:deg2>
<ogr:pixel_count>130550</ogr:pixel_count>
</ogr:surface_vol_add>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,6 @@
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/></head><body>
<p>Analyzed file: /home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/dem.tif (band 1)</p>
<p>Volume: 0.06802752691184032</p>
<p>Pixel count: 130550</p>
<p>Area: 0.001305499999999814 deg2</p>
</body></html>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="surface_vol_add" type="ogr:surface_vol_add_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="surface_vol_add_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="volume" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="deg2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="pixel_count" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:long">
<xs:totalDigits value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ surface_vol_add_crs.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

<gml:featureMember>
<ogr:surface_vol_add_crs fid="surface_vol_add_crs.0">
<ogr:volume>893436802.88886535</ogr:volume>
<ogr:m2>23202698.77333090</ogr:m2>
<ogr:pixel_count>130550</ogr:pixel_count>
</ogr:surface_vol_add_crs>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,6 @@
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/></head><body>
<p>Analyzed file: /home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/dem_crs.tif (band 1)</p>
<p>Volume: 893436802.8888654</p>
<p>Pixel count: 130550</p>
<p>Area: 23202698.7733309 m2</p>
</body></html>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="surface_vol_add_crs" type="ogr:surface_vol_add_crs_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="surface_vol_add_crs_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="volume" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="m2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="pixel_count" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:long">
<xs:totalDigits value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ surface_vol_below.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

<gml:featureMember>
<ogr:surface_vol_below fid="surface_vol_below.0">
<ogr:volume>-0.00322225</ogr:volume>
<ogr:deg2>0.00034600</ogr:deg2>
<ogr:pixel_count>34600</ogr:pixel_count>
</ogr:surface_vol_below>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,6 @@
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/></head><body>
<p>Analyzed file: /home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/dem.tif (band 1)</p>
<p>Volume: -0.003222253543777008</p>
<p>Pixel count: 34600</p>
<p>Area: 0.0003459999999999508 deg2</p>
</body></html>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="surface_vol_below" type="ogr:surface_vol_below_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="surface_vol_below_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="volume" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="deg2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="21"/>
<xs:fractionDigits value="8"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="pixel_count" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:long">
<xs:totalDigits value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ surface_vol_below_crs.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

<gml:featureMember>
<ogr:surface_vol_below_crs fid="surface_vol_below_crs.0">
<ogr:volume>-479651884.87896484</ogr:volume>
<ogr:m2>11669775.57607742</ogr:m2>
<ogr:pixel_count>65660</ogr:pixel_count>
</ogr:surface_vol_below_crs>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,6 @@
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"/></head><body>
<p>Analyzed file: /home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/dem_crs.tif (band 1)</p>
<p>Volume: -479651884.8789648</p>
<p>Pixel count: 65660</p>
<p>Area: 11669775.57607742 m2</p>
</body></html>

0 comments on commit 195d98f

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