Skip to content

Commit

Permalink
[processing] Add algorithm to extract min/max pixel from raster
Browse files Browse the repository at this point in the history
This algorithm extracts extrema (minimum and maximum) values
from a given band of the raster layer.

The output is a vector layer containing point features for
the selected extrema, at the center of the associated pixel.

If multiple pixels in the raster share the minimum or maximum
value, then only one of these pixels will be included in the output.

The algorithm uses raster iterator to remain efficient on huge
rasters, and does not require reading the entire raster to
memory
  • Loading branch information
nyalldawson committed Nov 5, 2024
1 parent 487ade9 commit aee6882
Show file tree
Hide file tree
Showing 11 changed files with 603 additions and 0 deletions.
18 changes: 18 additions & 0 deletions python/plugins/processing/tests/testdata/expected/raster_max.gml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
gml:id="aFeatureCollection"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ raster_max.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml/3.2">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.7964514376 18.6964479442</gml:lowerCorner><gml:upperCorner>45.7964514376 18.6964479442</gml:upperCorner></gml:Envelope></gml:boundedBy>

<ogr:featureMember>
<ogr:raster_max gml:id="raster_max.0">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.7964514376 18.6964479442</gml:lowerCorner><gml:upperCorner>45.7964514376 18.6964479442</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326" gml:id="raster_max.geom.0"><gml:pos>45.7964514376 18.6964479442</gml:pos></gml:Point></ogr:geometryProperty>
<ogr:value>243.00000000</ogr:value>
<ogr:extremum>maximum</ogr:extremum>
</ogr:raster_max>
</ogr:featureMember>
</ogr:FeatureCollection>
61 changes: 61 additions & 0 deletions python/plugins/processing/tests/testdata/expected/raster_max.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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/3.2"
xmlns:gmlsf="http://www.opengis.net/gmlsf/2.0"
elementFormDefault="qualified"
version="1.0">
<xs:annotation>
<xs:appinfo source="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd">
<gmlsf:ComplianceLevel>0</gmlsf:ComplianceLevel>
</xs:appinfo>
</xs:annotation>
<xs:import namespace="http://www.opengis.net/gml/3.2" schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd"/>
<xs:import namespace="http://www.opengis.net/gmlsf/2.0" schemaLocation="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="featureMember">
<xs:complexType>
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureMemberType">
<xs:sequence>
<xs:element ref="gml:AbstractFeature"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="raster_max" type="ogr:raster_max_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="raster_max_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/><!-- srsName="urn:ogc:def:crs:EPSG::4326" -->
<xs:element name="value" 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="extremum" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
18 changes: 18 additions & 0 deletions python/plugins/processing/tests/testdata/expected/raster_min.gml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
gml:id="aFeatureCollection"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ raster_min.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml/3.2">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.8027514376 18.6786479442</gml:lowerCorner><gml:upperCorner>45.8027514376 18.6786479442</gml:upperCorner></gml:Envelope></gml:boundedBy>

<ogr:featureMember>
<ogr:raster_min gml:id="raster_min.0">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.8027514376 18.6786479442</gml:lowerCorner><gml:upperCorner>45.8027514376 18.6786479442</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326" gml:id="raster_min.geom.0"><gml:pos>45.8027514376 18.6786479442</gml:pos></gml:Point></ogr:geometryProperty>
<ogr:value>85.00000000</ogr:value>
<ogr:extremum>minimum</ogr:extremum>
</ogr:raster_min>
</ogr:featureMember>
</ogr:FeatureCollection>
61 changes: 61 additions & 0 deletions python/plugins/processing/tests/testdata/expected/raster_min.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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/3.2"
xmlns:gmlsf="http://www.opengis.net/gmlsf/2.0"
elementFormDefault="qualified"
version="1.0">
<xs:annotation>
<xs:appinfo source="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd">
<gmlsf:ComplianceLevel>0</gmlsf:ComplianceLevel>
</xs:appinfo>
</xs:annotation>
<xs:import namespace="http://www.opengis.net/gml/3.2" schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd"/>
<xs:import namespace="http://www.opengis.net/gmlsf/2.0" schemaLocation="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="featureMember">
<xs:complexType>
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureMemberType">
<xs:sequence>
<xs:element ref="gml:AbstractFeature"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="raster_min" type="ogr:raster_min_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="raster_min_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/><!-- srsName="urn:ogc:def:crs:EPSG::4326" -->
<xs:element name="value" 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="extremum" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
gml:id="aFeatureCollection"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ raster_min_max.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml/3.2">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.7964514376 18.6786479442</gml:lowerCorner><gml:upperCorner>45.8027514376 18.6964479442</gml:upperCorner></gml:Envelope></gml:boundedBy>

<ogr:featureMember>
<ogr:raster_min_max gml:id="raster_min_max.0">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.8027514376 18.6786479442</gml:lowerCorner><gml:upperCorner>45.8027514376 18.6786479442</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326" gml:id="raster_min_max.geom.0"><gml:pos>45.8027514376 18.6786479442</gml:pos></gml:Point></ogr:geometryProperty>
<ogr:value>85.00000000</ogr:value>
<ogr:extremum>minimum</ogr:extremum>
</ogr:raster_min_max>
</ogr:featureMember>
<ogr:featureMember>
<ogr:raster_min_max gml:id="raster_min_max.1">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>45.7964514376 18.6964479442</gml:lowerCorner><gml:upperCorner>45.7964514376 18.6964479442</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326" gml:id="raster_min_max.geom.1"><gml:pos>45.7964514376 18.6964479442</gml:pos></gml:Point></ogr:geometryProperty>
<ogr:value>243.00000000</ogr:value>
<ogr:extremum>maximum</ogr:extremum>
</ogr:raster_min_max>
</ogr:featureMember>
</ogr:FeatureCollection>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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/3.2"
xmlns:gmlsf="http://www.opengis.net/gmlsf/2.0"
elementFormDefault="qualified"
version="1.0">
<xs:annotation>
<xs:appinfo source="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd">
<gmlsf:ComplianceLevel>0</gmlsf:ComplianceLevel>
</xs:appinfo>
</xs:annotation>
<xs:import namespace="http://www.opengis.net/gml/3.2" schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd"/>
<xs:import namespace="http://www.opengis.net/gmlsf/2.0" schemaLocation="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="featureMember">
<xs:complexType>
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureMemberType">
<xs:sequence>
<xs:element ref="gml:AbstractFeature"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="raster_min_max" type="ogr:raster_min_max_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="raster_min_max_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/><!-- srsName="urn:ogc:def:crs:EPSG::4326" -->
<xs:element name="value" 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="extremum" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,41 @@ tests:
name: expected/create_grid_negative_overlay.gml
type: vector

- algorithm: native:rasterminmax
name: Raster min max
params:
BAND: 1
EXTRACT: 0
INPUT:
name: dem.tif
type: raster
results:
OUTPUT:
name: expected/raster_min_max.gml
type: vector

- algorithm: native:rasterminmax
name: Raster min
params:
BAND: 1
EXTRACT: 1
INPUT:
name: dem.tif
type: raster
results:
OUTPUT:
name: expected/raster_min.gml
type: vector

- algorithm: native:rasterminmax
name: Raster max
params:
BAND: 1
EXTRACT: 2
INPUT:
name: dem.tif
type: raster
results:
OUTPUT:
name: expected/raster_max.gml
type: vector
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmrasterlayerproperties.cpp
processing/qgsalgorithmrasterlayeruniquevalues.cpp
processing/qgsalgorithmrasterlogicalop.cpp
processing/qgsalgorithmrasterminmax.cpp
processing/qgsalgorithmrasterize.cpp
processing/qgsalgorithmrastersampling.cpp
processing/qgsalgorithmrasterstackposition.cpp
Expand Down
Loading

0 comments on commit aee6882

Please sign in to comment.