[processing] Add algorithms to filter parts by length/area

These new algorithms "Remove parts by length" and
"Remove parts by area" filter out parts of geometries from
a vector layer, by checking their area or length vs
a minimum size parameter.

If the input geometry is a multipart geometry, then the parts
will be filtered by their individual sizes. If no parts match the
required minimum size, then the feature will be skipped and
omitted from the output layer.

If the input geometry is a singlepart geometry, then the feature
will be skipped if the geometry's size is below the required size
and omitted from the output layer.

Attributes are not modified.
This commit is contained in:
Nyall Dawson 2025-07-09 13:01:29 +10:00
parent 51df526a40
commit 4fe72dd84f
15 changed files with 848 additions and 0 deletions

View File

@ -0,0 +1,34 @@
<?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/ remove_line_parts_by_length.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::3857"><gml:lowerCorner>-170142.279029556 -368051.169570389</gml:lowerCorner><gml:upperCorner>1198122.33141201 455636.648094066</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:featureMember>
<ogr:remove_line_parts_by_length gml:id="remove_line_parts_by_length.0">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::3857"><gml:lowerCorner>-170142.279029556 -309175.468327608</gml:lowerCorner><gml:upperCorner>193303.008118985 455636.648094066</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:LineString srsName="urn:ogc:def:crs:EPSG::3857" gml:id="remove_line_parts_by_length.geom.0"><gml:posList>193303.008118985 455636.648094066 67700.5927073567 337822.202907407 40976.6745346706 33011.8416339877 -44539.8636179277 -303824.512095835 -170142.279029556 -309175.468327608</gml:posList></gml:LineString></ogr:geometryProperty>
<ogr:fid>line_3857.0</ogr:fid>
<ogr:val>5</ogr:val>
</ogr:remove_line_parts_by_length>
</ogr:featureMember>
<ogr:featureMember>
<ogr:remove_line_parts_by_length gml:id="remove_line_parts_by_length.1">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::3857"><gml:lowerCorner>67700.5927073567 -368051.169570389</gml:lowerCorner><gml:upperCorner>628902.874333781 254888.031682493</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:LineString srsName="urn:ogc:def:crs:EPSG::3857" gml:id="remove_line_parts_by_length.geom.1"><gml:posList>67700.5927073567 -368051.169570389 78390.1599764311 -23108.5844752597 300198.680809733 -47160.4899983067 628902.874333781 254888.031682493</gml:posList></gml:LineString></ogr:geometryProperty>
<ogr:fid>line_3857.2</ogr:fid>
<ogr:val>8</ogr:val>
</ogr:remove_line_parts_by_length>
</ogr:featureMember>
<ogr:featureMember>
<ogr:remove_line_parts_by_length gml:id="remove_line_parts_by_length.2">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::3857"><gml:lowerCorner>992348.161482323 -263698.89064191</gml:lowerCorner><gml:upperCorner>1198122.33141201 262911.813488384</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:LineString srsName="urn:ogc:def:crs:EPSG::3857" gml:id="remove_line_parts_by_length.geom.2"><gml:posList>992348.161482323 262911.813488384 1198122.33141201 -263698.89064191</gml:posList></gml:LineString></ogr:geometryProperty>
<ogr:fid>line_3857.4</ogr:fid>
<ogr:val>2</ogr:val>
</ogr:remove_line_parts_by_length>
</ogr:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,60 @@
<?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="remove_line_parts_by_length" type="ogr:remove_line_parts_by_length_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="remove_line_parts_by_length_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:CurvePropertyType" nillable="true" minOccurs="0" maxOccurs="1"/> <!-- restricted to LineString --><!-- srsName="urn:ogc:def:crs:EPSG::3857" -->
<xs:element name="fid" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="val" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -0,0 +1,22 @@
<?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/ remove_multiline_parts_by_length.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>0 2</gml:lowerCorner><gml:upperCorner>3 3</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:featureMember>
<ogr:remove_multiline_parts_by_length gml:id="remove_multiline_parts_by_length.0">
<ogr:fid>lines.3</ogr:fid>
</ogr:remove_multiline_parts_by_length>
</ogr:featureMember>
<ogr:featureMember>
<ogr:remove_multiline_parts_by_length gml:id="remove_multiline_parts_by_length.1">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>0 2</gml:lowerCorner><gml:upperCorner>3 3</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:MultiCurve srsName="urn:ogc:def:crs:EPSG::4326" gml:id="remove_multiline_parts_by_length.geom.1"><gml:curveMember><gml:LineString gml:id="remove_multiline_parts_by_length.geom.1.0"><gml:posList>0 2 2 2 2 3 3 3</gml:posList></gml:LineString></gml:curveMember></gml:MultiCurve></ogr:geometryProperty>
<ogr:fid>lines.4</ogr:fid>
</ogr:remove_multiline_parts_by_length>
</ogr:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,53 @@
<?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="remove_multiline_parts_by_length" type="ogr:remove_multiline_parts_by_length_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="remove_multiline_parts_by_length_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:MultiCurvePropertyType" nillable="true" minOccurs="0" maxOccurs="1"/> <!-- restricted to MultiLineString --><!-- srsName="urn:ogc:def:crs:EPSG::4326" -->
<xs:element name="fid" 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>

View File

@ -0,0 +1,28 @@
<?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/ remove_multipolygon_parts_by_area.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>-1 7</gml:lowerCorner><gml:upperCorner>3 8</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:featureMember>
<ogr:remove_multipolygon_parts_by_area gml:id="remove_multipolygon_parts_by_area.0">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::4326"><gml:lowerCorner>-1 7</gml:lowerCorner><gml:upperCorner>3 8</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:MultiSurface srsName="urn:ogc:def:crs:EPSG::4326" gml:id="remove_multipolygon_parts_by_area.geom.0"><gml:surfaceMember><gml:Polygon gml:id="remove_multipolygon_parts_by_area.geom.0.0"><gml:exterior><gml:LinearRing><gml:posList>-1 7 -1 8 3 8 3 7 -1 7</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></ogr:geometryProperty>
<ogr:fid>multipolys.1</ogr:fid>
<ogr:Bname xsi:nil="true"/>
<ogr:Bintval xsi:nil="true"/>
<ogr:Bfloatval xsi:nil="true"/>
</ogr:remove_multipolygon_parts_by_area>
</ogr:featureMember>
<ogr:featureMember>
<ogr:remove_multipolygon_parts_by_area gml:id="remove_multipolygon_parts_by_area.1">
<ogr:fid>multipolys.3</ogr:fid>
<ogr:Bname>Test</ogr:Bname>
<ogr:Bintval>3</ogr:Bintval>
<ogr:Bfloatval>0</ogr:Bfloatval>
</ogr:remove_multipolygon_parts_by_area>
</ogr:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,73 @@
<?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="remove_multipolygon_parts_by_area" type="ogr:remove_multipolygon_parts_by_area_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="remove_multipolygon_parts_by_area_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:MultiSurfacePropertyType" nillable="true" minOccurs="0" maxOccurs="1"/> <!-- restricted to MultiPolygon --><!-- srsName="urn:ogc:def:crs:EPSG::4326" -->
<xs:element name="fid" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Bname" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="4"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Bintval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Bfloatval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -0,0 +1,24 @@
<?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/ remove_polygon_parts_by_area.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::31256"><gml:lowerCorner>-5447984.1499324 -1696611.72536999</gml:lowerCorner><gml:upperCorner>-4968240.45365551 -812597.646423635</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:featureMember>
<ogr:remove_polygon_parts_by_area gml:id="remove_polygon_parts_by_area.0">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::31256"><gml:lowerCorner>-5082679.00301049 -1696611.72536999</gml:lowerCorner><gml:upperCorner>-4968240.45365551 -1536872.40598013</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:MultiSurface srsName="urn:ogc:def:crs:EPSG::31256" gml:id="remove_polygon_parts_by_area.geom.0"><gml:surfaceMember><gml:Polygon gml:id="remove_polygon_parts_by_area.geom.0.0"><gml:exterior><gml:LinearRing><gml:posList>-5046793.84388864 -1696611.72536999 -4998860.79192466 -1686316.7671871 -5005696.1429675 -1639845.87197112 -4968240.45365551 -1543713.48564309 -5022688.65205912 -1536872.40598013 -5060223.52298557 -1584812.75072525 -5082679.00301049 -1677567.90939181 -5046793.84388864 -1696611.72536999</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></ogr:geometryProperty>
<ogr:testattr>a</ogr:testattr>
</ogr:remove_polygon_parts_by_area>
</ogr:featureMember>
<ogr:featureMember>
<ogr:remove_polygon_parts_by_area gml:id="remove_polygon_parts_by_area.1">
<gml:boundedBy><gml:Envelope srsName="urn:ogc:def:crs:EPSG::31256"><gml:lowerCorner>-5447984.1499324 -1360816.09576364</gml:lowerCorner><gml:upperCorner>-5179666.180385 -812597.646423635</gml:upperCorner></gml:Envelope></gml:boundedBy>
<ogr:geometryProperty><gml:MultiSurface srsName="urn:ogc:def:crs:EPSG::31256" gml:id="remove_polygon_parts_by_area.geom.1"><gml:surfaceMember><gml:Polygon gml:id="remove_polygon_parts_by_area.geom.1.0"><gml:exterior><gml:LinearRing><gml:posList>-5340262.55811533 -1360816.09576364 -5179666.180385 -1343487.40838446 -5201113.62674451 -1263539.81063555 -5295898.32956886 -1122417.3150334 -5191026.76572497 -941120.6948349 -5278830.75393966 -826514.145092787 -5358635.00834848 -812597.646423635 -5379879.24143519 -1287359.99466885 -5447984.1499324 -1340734.64008444 -5340262.55811533 -1360816.09576364</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></ogr:geometryProperty>
<ogr:testattr>b</ogr:testattr>
</ogr:remove_polygon_parts_by_area>
</ogr:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,54 @@
<?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="remove_polygon_parts_by_area" type="ogr:remove_polygon_parts_by_area_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="remove_polygon_parts_by_area_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:MultiSurfacePropertyType" nillable="true" minOccurs="0" maxOccurs="1"/> <!-- restricted to MultiPolygon --><!-- srsName="urn:ogc:def:crs:EPSG::31256" -->
<xs:element name="testattr" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -373,3 +373,51 @@ tests:
OUTPUT:
name: expected/concave_hull_by_feature.gml
type: vector
- algorithm: native:removepartsbylength
name: Remove line parts by length
params:
INPUT:
name: custom/line_3857.gml|layername=line_3857
type: vector
MIN_LENGTH: 550000.0
results:
OUTPUT:
name: expected/remove_line_parts_by_length.gml
type: vector
- algorithm: native:removepartsbylength
name: Remove multiline parts by length
params:
INPUT:
name: multilines.gml|layername=multilines
type: vector
MIN_LENGTH: 3.0
results:
OUTPUT:
name: expected/remove_multiline_parts_by_length.gml
type: vector
- algorithm: native:removepartsbyarea
name: Remove polygon parts by area
params:
INPUT:
name: custom/polys_epsg31256.shp
type: vector
MIN_AREA: 9999000000.0
results:
OUTPUT:
name: expected/remove_polygon_parts_by_area.gml
type: vector
- algorithm: native:removepartsbyarea
name: Remove multipolygon parts by area
params:
INPUT:
name: multipolys.gml|layername=multipolys
type: vector
MIN_AREA: 4.0
results:
OUTPUT:
name: expected/remove_multipolygon_parts_by_area.gml
type: vector

View File

@ -236,6 +236,8 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmremoveduplicatevertices.cpp
processing/qgsalgorithmremoveholes.cpp
processing/qgsalgorithmremovenullgeometry.cpp
processing/qgsalgorithmremovepartsbyarea.cpp
processing/qgsalgorithmremovepartsbylength.cpp
processing/qgsalgorithmrenamelayer.cpp
processing/qgsalgorithmrenametablefield.cpp
processing/qgsalgorithmrepairshapefile.cpp

View File

@ -0,0 +1,161 @@
/***************************************************************************
qgsalgorithmremovepartsbyarea.cpp
---------------------
begin : July 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "qgsalgorithmremovepartsbyarea.h"
#include "qgsgeometrycollection.h"
#include "qgssurface.h"
///@cond PRIVATE
QString QgsRemovePartsByAreaAlgorithm::name() const
{
return QStringLiteral( "removepartsbyarea" );
}
QString QgsRemovePartsByAreaAlgorithm::displayName() const
{
return QObject::tr( "Remove parts by area" );
}
QStringList QgsRemovePartsByAreaAlgorithm::tags() const
{
return QObject::tr( "remove,delete,drop,filter,polygon,size" ).split( ',' );
}
QString QgsRemovePartsByAreaAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
}
QString QgsRemovePartsByAreaAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
}
QString QgsRemovePartsByAreaAlgorithm::outputName() const
{
return QObject::tr( "Cleaned" );
}
QList<int> QgsRemovePartsByAreaAlgorithm::inputLayerTypes() const
{
return QList<int>() << static_cast< int >( Qgis::ProcessingSourceType::VectorPolygon );
}
Qgis::ProcessingSourceType QgsRemovePartsByAreaAlgorithm::outputLayerType() const
{
return Qgis::ProcessingSourceType::VectorPolygon;
}
QString QgsRemovePartsByAreaAlgorithm::shortDescription() const
{
return QObject::tr( "Removes polygons which are smaller than a specified area." );
}
QString QgsRemovePartsByAreaAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm takes a polygon layer and removes polygons which are smaller than a specified area.\n\n"
"If the input geometry is a multipart geometry, then the parts will be filtered by their individual areas. If no parts match the "
"required minimum area, then the feature will be skipped and omitted from the output layer.\n\n"
"If the input geometry is a singlepart geometry, then the feature will be skipped if the geometry's "
"area is below the required size and omitted from the output layer.\n\n"
"The area will be calculated using Cartesian calculations in the source layer's coordinate reference system.\n\n"
"Attributes are not modified." );
}
QgsRemovePartsByAreaAlgorithm *QgsRemovePartsByAreaAlgorithm::createInstance() const
{
return new QgsRemovePartsByAreaAlgorithm();
}
Qgis::ProcessingFeatureSourceFlags QgsRemovePartsByAreaAlgorithm::sourceFlags() const
{
// skip geometry checks - this algorithm can be used to repair geometries
return Qgis::ProcessingFeatureSourceFlag::SkipGeometryValidityChecks;
}
void QgsRemovePartsByAreaAlgorithm::initParameters( const QVariantMap & )
{
std::unique_ptr< QgsProcessingParameterArea > minArea = std::make_unique< QgsProcessingParameterArea >( QStringLiteral( "MIN_AREA" ), QObject::tr( "Remove parts with area less than" ), 0.0, QStringLiteral( "INPUT" ), false, 0 );
minArea->setIsDynamic( true );
minArea->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "MIN_AREA" ), QObject::tr( "Remove parts with area less than" ), QgsPropertyDefinition::DoublePositive ) );
minArea->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
addParameter( minArea.release() );
}
bool QgsRemovePartsByAreaAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mMinArea = parameterAsDouble( parameters, QStringLiteral( "MIN_AREA" ), context );
mDynamicMinArea = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "MIN_AREA" ) );
if ( mDynamicMinArea )
mMinAreaProperty = parameters.value( QStringLiteral( "MIN_AREA" ) ).value< QgsProperty >();
return true;
}
QgsFeatureList QgsRemovePartsByAreaAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback * )
{
QgsFeature f = feature;
if ( f.hasGeometry() )
{
double minArea = mMinArea;
if ( mDynamicMinArea )
minArea = mMinAreaProperty.valueAsDouble( context.expressionContext(), minArea );
const QgsGeometry geometry = f.geometry();
QgsGeometry outputGeometry;
if ( const QgsGeometryCollection *inputCollection = qgsgeometry_cast< const QgsGeometryCollection * >( geometry.constGet() ) )
{
std::unique_ptr< QgsAbstractGeometry> filteredGeometry( geometry.constGet()->createEmptyWithSameType() );
QgsGeometryCollection *collection = qgsgeometry_cast< QgsGeometryCollection * >( filteredGeometry.get() );
const int size = inputCollection->numGeometries();
collection->reserve( size );
for ( int i = 0; i < size; ++i )
{
if ( const QgsSurface *surface = qgsgeometry_cast< const QgsSurface * >( inputCollection->geometryN( i ) ) )
{
if ( surface->area() >= minArea )
{
collection->addGeometry( surface->clone() );
}
}
}
if ( collection->numGeometries() == 0 )
{
// skip empty features
return {};
}
outputGeometry = QgsGeometry( std::move( filteredGeometry ) );
f.setGeometry( outputGeometry );
}
else if ( const QgsSurface *surface = qgsgeometry_cast< const QgsSurface * >( geometry.constGet() ) )
{
if ( surface->area() < minArea )
{
return {};
}
}
else
{
return {};
}
}
return { f };
}
///@endcond

View File

@ -0,0 +1,62 @@
/***************************************************************************
qgsalgorithmremovepartsbyarea.h
---------------------
begin : July 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSALGORITHMREMOVEPARTSBYAREA_H
#define QGSALGORITHMREMOVEPARTSBYAREA_H
#define SIP_NO_FILE
#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"
///@cond PRIVATE
/**
* Native remove parts by area algorithm.
*/
class QgsRemovePartsByAreaAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{
public:
QgsRemovePartsByAreaAlgorithm() = default;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortDescription() const override;
QString shortHelpString() const override;
QgsRemovePartsByAreaAlgorithm *createInstance() const override SIP_FACTORY;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
protected:
QString outputName() const override;
QList<int> inputLayerTypes() const override;
Qgis::ProcessingSourceType outputLayerType() const override;
Qgis::ProcessingFeatureSourceFlags sourceFlags() const override;
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
private:
double mMinArea = 0.0;
bool mDynamicMinArea = false;
QgsProperty mMinAreaProperty;
};
///@endcond PRIVATE
#endif // QGSALGORITHMREMOVEPARTSBYAREA_H

View File

@ -0,0 +1,161 @@
/***************************************************************************
qgsalgorithmremovepartsbylength.cpp
---------------------
begin : July 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "qgsalgorithmremovepartsbylength.h"
#include "qgsgeometrycollection.h"
#include "qgscurve.h"
///@cond PRIVATE
QString QgsRemovePartsByLengthAlgorithm::name() const
{
return QStringLiteral( "removepartsbylength" );
}
QString QgsRemovePartsByLengthAlgorithm::displayName() const
{
return QObject::tr( "Remove parts by length" );
}
QStringList QgsRemovePartsByLengthAlgorithm::tags() const
{
return QObject::tr( "remove,delete,drop,filter,lines,linestring,polyline,size" ).split( ',' );
}
QString QgsRemovePartsByLengthAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
}
QString QgsRemovePartsByLengthAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
}
QString QgsRemovePartsByLengthAlgorithm::outputName() const
{
return QObject::tr( "Cleaned" );
}
QList<int> QgsRemovePartsByLengthAlgorithm::inputLayerTypes() const
{
return QList<int>() << static_cast< int >( Qgis::ProcessingSourceType::VectorLine );
}
Qgis::ProcessingSourceType QgsRemovePartsByLengthAlgorithm::outputLayerType() const
{
return Qgis::ProcessingSourceType::VectorLine;
}
QString QgsRemovePartsByLengthAlgorithm::shortDescription() const
{
return QObject::tr( "Removes lines which are shorter than a specified length." );
}
QString QgsRemovePartsByLengthAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm takes a line layer and removes lines which are shorter than a specified length.\n\n"
"If the input geometry is a multipart geometry, then the parts will be filtered by their individual lengths. If no parts match the "
"required minimum length, then the feature will be skipped and omitted from the output layer.\n\n"
"If the input geometry is a singlepart geometry, then the feature will be skipped if the geometry's "
"length is below the required size and omitted from the output layer.\n\n"
"The length will be calculated using Cartesian calculations in the source layer's coordinate reference system.\n\n"
"Attributes are not modified." );
}
QgsRemovePartsByLengthAlgorithm *QgsRemovePartsByLengthAlgorithm::createInstance() const
{
return new QgsRemovePartsByLengthAlgorithm();
}
Qgis::ProcessingFeatureSourceFlags QgsRemovePartsByLengthAlgorithm::sourceFlags() const
{
// skip geometry checks - this algorithm can be used to repair geometries
return Qgis::ProcessingFeatureSourceFlag::SkipGeometryValidityChecks;
}
void QgsRemovePartsByLengthAlgorithm::initParameters( const QVariantMap & )
{
std::unique_ptr< QgsProcessingParameterDistance > minLength = std::make_unique< QgsProcessingParameterDistance >( QStringLiteral( "MIN_LENGTH" ), QObject::tr( "Remove parts with lengths less than" ), 0.0, QStringLiteral( "INPUT" ), false, 0 );
minLength->setIsDynamic( true );
minLength->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "MIN_LENGTH" ), QObject::tr( "Remove parts with length less than" ), QgsPropertyDefinition::DoublePositive ) );
minLength->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
addParameter( minLength.release() );
}
bool QgsRemovePartsByLengthAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mMinLength = parameterAsDouble( parameters, QStringLiteral( "MIN_LENGTH" ), context );
mDynamicMinLength = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "MIN_LENGTH" ) );
if ( mDynamicMinLength )
mMinLengthProperty = parameters.value( QStringLiteral( "MIN_LENGTH" ) ).value< QgsProperty >();
return true;
}
QgsFeatureList QgsRemovePartsByLengthAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback * )
{
QgsFeature f = feature;
if ( f.hasGeometry() )
{
double minLength = mMinLength;
if ( mDynamicMinLength )
minLength = mMinLengthProperty.valueAsDouble( context.expressionContext(), minLength );
const QgsGeometry geometry = f.geometry();
QgsGeometry outputGeometry;
if ( const QgsGeometryCollection *inputCollection = qgsgeometry_cast< const QgsGeometryCollection * >( geometry.constGet() ) )
{
std::unique_ptr< QgsAbstractGeometry> filteredGeometry( geometry.constGet()->createEmptyWithSameType() );
QgsGeometryCollection *collection = qgsgeometry_cast< QgsGeometryCollection * >( filteredGeometry.get() );
const int size = inputCollection->numGeometries();
collection->reserve( size );
for ( int i = 0; i < size; ++i )
{
if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( inputCollection->geometryN( i ) ) )
{
if ( curve->length() >= minLength )
{
collection->addGeometry( curve->clone() );
}
}
}
if ( collection->numGeometries() == 0 )
{
// skip empty features
return {};
}
outputGeometry = QgsGeometry( std::move( filteredGeometry ) );
f.setGeometry( outputGeometry );
}
else if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geometry.constGet() ) )
{
if ( curve->length() < minLength )
{
return {};
}
}
else
{
return {};
}
}
return { f };
}
///@endcond

View File

@ -0,0 +1,62 @@
/***************************************************************************
qgsalgorithmremovepartsbylength.h
---------------------
begin : July 2024
copyright : (C) 2024 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSALGORITHMREMOVEPARTSBYLENGTH_H
#define QGSALGORITHMREMOVEPARTSBYLENGTH_H
#define SIP_NO_FILE
#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"
///@cond PRIVATE
/**
* Native remove parts by length algorithm.
*/
class QgsRemovePartsByLengthAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{
public:
QgsRemovePartsByLengthAlgorithm() = default;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortDescription() const override;
QString shortHelpString() const override;
QgsRemovePartsByLengthAlgorithm *createInstance() const override SIP_FACTORY;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
protected:
QString outputName() const override;
QList<int> inputLayerTypes() const override;
Qgis::ProcessingSourceType outputLayerType() const override;
Qgis::ProcessingFeatureSourceFlags sourceFlags() const override;
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
private:
double mMinLength = 0.0;
bool mDynamicMinLength = false;
QgsProperty mMinLengthProperty;
};
///@endcond PRIVATE
#endif // QGSALGORITHMREMOVEPARTSBYLENGTH_H

View File

@ -219,6 +219,8 @@
#include "qgsalgorithmremoveduplicatevertices.h"
#include "qgsalgorithmremoveholes.h"
#include "qgsalgorithmremovenullgeometry.h"
#include "qgsalgorithmremovepartsbyarea.h"
#include "qgsalgorithmremovepartsbylength.h"
#include "qgsalgorithmrenamelayer.h"
#include "qgsalgorithmrenametablefield.h"
#include "qgsalgorithmrepairshapefile.h"
@ -570,6 +572,8 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsRemoveDuplicatesByAttributeAlgorithm() );
addAlgorithm( new QgsRemoveHolesAlgorithm() );
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
addAlgorithm( new QgsRemovePartsByAreaAlgorithm() );
addAlgorithm( new QgsRemovePartsByLengthAlgorithm() );
addAlgorithm( new QgsRenameLayerAlgorithm() );
addAlgorithm( new QgsRenameTableFieldAlgorithm() );
addAlgorithm( new QgsRepairShapefileAlgorithm() );