Skip to content

Commit 274f61f

Browse files
authored
Merge pull request #4037 from wonder-sk/union-fixes
[processing] Union: fix output of features that do not instersect layer A
2 parents fe2b34d + 78396dc commit 274f61f

File tree

6 files changed

+198
-39
lines changed

6 files changed

+198
-39
lines changed

python/plugins/processing/algs/qgis/Union.py

+32-39
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ def processAlgorithm(self, progress):
123123
else:
124124
int_geom = QgsGeometry(int_geom)
125125

126+
# TODO: the result may have a different dimension (e.g. intersection of two polygons may result in a single point)
127+
# or the result may be a collection of geometries (e.g. intersection of two polygons results in three polygons and one linestring).
128+
# We need to filter out all acceptable geometries into a single (possibly multi-part) geometry - and we need
129+
# to do it consistently also in the code further below
130+
126131
if int_geom.wkbType() == QGis.WKBUnknown or QgsWKBTypes.flatType(int_geom.geometry().wkbType()) == QgsWKBTypes.GeometryCollection:
127132
# Intersection produced different geomety types
128133
temp_list = int_geom.asGeometryCollection()
@@ -160,6 +165,7 @@ def processAlgorithm(self, progress):
160165
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
161166
self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'))
162167

168+
# TODO: correctly handly different output geometry types (see todo above)
163169
if diff_geom.wkbType() == 0 or QgsWKBTypes.flatType(diff_geom.geometry().wkbType()) == QgsWKBTypes.GeometryCollection:
164170
temp_list = diff_geom.asGeometryCollection()
165171
for i in temp_list:
@@ -182,51 +188,38 @@ def processAlgorithm(self, progress):
182188
progress.setPercentage(nElement / float(nFeat) * 100)
183189
add = False
184190
geom = QgsGeometry(inFeatA.geometry())
185-
diff_geom = QgsGeometry(geom)
186191
atMap = [None] * length
187192
atMap.extend(inFeatA.attributes())
188193
intersects = indexB.intersects(geom.boundingBox())
194+
lstIntersectingA = []
189195

190-
if len(intersects) < 1:
191-
try:
192-
outFeat.setGeometry(geom)
193-
outFeat.setAttributes(atMap)
194-
writer.addFeature(outFeat)
195-
except:
196-
ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
197-
self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
198-
else:
199-
for id in intersects:
200-
request = QgsFeatureRequest().setFilterFid(id)
201-
inFeatB = vlayerA.getFeatures(request).next()
202-
atMapB = inFeatB.attributes()
203-
tmpGeom = QgsGeometry(inFeatB.geometry())
196+
for id in intersects:
197+
request = QgsFeatureRequest().setFilterFid(id)
198+
inFeatB = vlayerA.getFeatures(request).next()
199+
atMapB = inFeatB.attributes()
200+
tmpGeom = QgsGeometry(inFeatB.geometry())
204201

205-
if diff_geom.intersects(tmpGeom):
206-
add = True
207-
diff_geom = QgsGeometry(diff_geom.difference(tmpGeom))
208-
if diff_geom.isGeosEmpty() or not diff_geom.isGeosValid():
209-
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
210-
self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'))
211-
else:
212-
try:
213-
# Ihis only happends if the bounding box
214-
# intersects, but the geometry doesn't
215-
outFeat.setGeometry(diff_geom)
216-
outFeat.setAttributes(atMap)
217-
writer.addFeature(outFeat)
218-
except:
219-
ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
220-
self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
202+
if geom.intersects(tmpGeom):
203+
lstIntersectingA.append(tmpGeom)
221204

222-
if add:
223-
try:
224-
outFeat.setGeometry(diff_geom)
225-
outFeat.setAttributes(atMap)
226-
writer.addFeature(outFeat)
227-
except:
228-
ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
229-
self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
205+
if len(lstIntersectingA) == 0:
206+
res_geom = geom
207+
else:
208+
intA = QgsGeometry.unaryUnion(lstIntersectingA)
209+
res_geom = geom.difference(intA)
210+
if res_geom.isGeosEmpty() or not res_geom.isGeosValid():
211+
ProcessingLog.addToLog(ProcessingLog.LOG_ERROR,
212+
self.tr('GEOS geoprocessing error: One or more input features have invalid geometry.'))
213+
214+
# TODO: correctly handly different output geometry types (see todo above)
215+
216+
try:
217+
outFeat.setGeometry(res_geom)
218+
outFeat.setAttributes(atMap)
219+
writer.addFeature(outFeat)
220+
except:
221+
ProcessingLog.addToLog(ProcessingLog.LOG_INFO,
222+
self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'))
230223
nElement += 1
231224

232225
del writer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "FeatureCollection",
3+
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3857" } },
4+
"features": [
5+
{ "type": "Feature", "properties": { "id_a": "A1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 1.0, 3.0 ], [ 2.0, 3.0 ], [ 2.0, 10.0 ], [ 8.0, 10.0 ], [ 8.0, 11.0 ], [ 1.0, 11.0 ], [ 1.0, 3.0 ] ] ] } },
6+
{ "type": "Feature", "properties": { "id_a": "A4" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3.0, 3.0 ], [ 3.0, 4.0 ], [ 4.0, 4.0 ], [ 4.0, 3.0 ], [ 3.0, 3.0 ] ] ] } },
7+
{ "type": "Feature", "properties": { "id_a": "A2" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3.0, 5.0 ], [ 6.0, 5.0 ], [ 6.0, 6.0 ], [ 3.0, 6.0 ], [ 3.0, 5.0 ] ] ] } },
8+
{ "type": "Feature", "properties": { "id_a": "A3" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 5.0, 7.0 ], [ 9.0, 7.0 ], [ 9.0, 8.0 ], [ 5.0, 8.0 ], [ 5.0, 7.0 ] ] ] } }
9+
]
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "FeatureCollection",
3+
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3857" } },
4+
"features": [
5+
{ "type": "Feature", "properties": { "id_b": "B1" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 1.0, 1.0 ], [ 8.0, 1.0 ], [ 8.0, 9.0 ], [ 7.0, 9.0 ], [ 7.0, 2.0 ], [ 1.0, 2.0 ], [ 1.0, 1.0 ] ] ] } },
6+
{ "type": "Feature", "properties": { "id_b": "B4" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 5.0, 3.0 ], [ 6.0, 3.0 ], [ 6.0, 4.0 ], [ 5.0, 4.0 ], [ 5.0, 3.0 ] ] ] } },
7+
{ "type": "Feature", "properties": { "id_b": "B2" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 0.0, 5.0 ], [ 4.0, 5.0 ], [ 4.0, 6.0 ], [ 0.0, 6.0 ], [ 0.0, 5.0 ] ] ] } },
8+
{ "type": "Feature", "properties": { "id_b": "B3" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 3.0, 7.0 ], [ 6.0, 7.0 ], [ 6.0, 8.0 ], [ 3.0, 8.0 ], [ 3.0, 7.0 ] ] ] } }
9+
]
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ union1.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>1</gml:Y></gml:coord>
10+
<gml:coord><gml:X>9</gml:X><gml:Y>11</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:union1 fid="union1.0">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,6 2,5 1,5 1,6 2,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
<ogr:id_a>A1</ogr:id_a>
18+
<ogr:id_b>B2</ogr:id_b>
19+
</ogr:union1>
20+
</gml:featureMember>
21+
<gml:featureMember>
22+
<ogr:union1 fid="union1.1">
23+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:3857"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,5 2,3 1,3 1,5 2,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1,6 1,11 8,11 8,10 2,10 2,6 1,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
24+
<ogr:id_a>A1</ogr:id_a>
25+
</ogr:union1>
26+
</gml:featureMember>
27+
<gml:featureMember>
28+
<ogr:union1 fid="union1.2">
29+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,3 3,4 4,4 4,3 3,3</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
30+
<ogr:id_a>A4</ogr:id_a>
31+
</ogr:union1>
32+
</gml:featureMember>
33+
<gml:featureMember>
34+
<ogr:union1 fid="union1.3">
35+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4,5 3,5 3,6 4,6 4,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
36+
<ogr:id_a>A2</ogr:id_a>
37+
<ogr:id_b>B2</ogr:id_b>
38+
</ogr:union1>
39+
</gml:featureMember>
40+
<gml:featureMember>
41+
<ogr:union1 fid="union1.4">
42+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4,6 6,6 6,5 4,5 4,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
43+
<ogr:id_a>A2</ogr:id_a>
44+
</ogr:union1>
45+
</gml:featureMember>
46+
<gml:featureMember>
47+
<ogr:union1 fid="union1.5">
48+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>8,7 7,7 7,8 8,8 8,7</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
49+
<ogr:id_a>A3</ogr:id_a>
50+
<ogr:id_b>B1</ogr:id_b>
51+
</ogr:union1>
52+
</gml:featureMember>
53+
<gml:featureMember>
54+
<ogr:union1 fid="union1.6">
55+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,7 5,7 5,8 6,8 6,7</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
56+
<ogr:id_a>A3</ogr:id_a>
57+
<ogr:id_b>B3</ogr:id_b>
58+
</ogr:union1>
59+
</gml:featureMember>
60+
<gml:featureMember>
61+
<ogr:union1 fid="union1.7">
62+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:3857"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,7 6,7 6,8 7,8 7,7</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>8,8 9,8 9,7 8,7 8,8</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
63+
<ogr:id_a>A3</ogr:id_a>
64+
</ogr:union1>
65+
</gml:featureMember>
66+
<gml:featureMember>
67+
<ogr:union1 fid="union1.8">
68+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:3857"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>8,7 8,1 1,1 1,2 7,2 7,7 8,7</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,8 7,9 8,9 8,8 7,8</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
69+
<ogr:id_b>B1</ogr:id_b>
70+
</ogr:union1>
71+
</gml:featureMember>
72+
<gml:featureMember>
73+
<ogr:union1 fid="union1.9">
74+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,3 6,3 6,4 5,4 5,3</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
75+
<ogr:id_b>B4</ogr:id_b>
76+
</ogr:union1>
77+
</gml:featureMember>
78+
<gml:featureMember>
79+
<ogr:union1 fid="union1.10">
80+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:3857"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1,5 0,5 0,6 1,6 1,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,5 2,5 2,6 3,6 3,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
81+
<ogr:id_b>B2</ogr:id_b>
82+
</ogr:union1>
83+
</gml:featureMember>
84+
<gml:featureMember>
85+
<ogr:union1 fid="union1.11">
86+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:3857"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,7 3,7 3,8 5,8 5,7</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
87+
<ogr:id_b>B3</ogr:id_b>
88+
</ogr:union1>
89+
</gml:featureMember>
90+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="union1" type="ogr:union1_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="union1_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="id_a" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="255"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="id_b" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:string">
29+
<xs:maxLength value="255"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
</xs:sequence>
34+
</xs:extension>
35+
</xs:complexContent>
36+
</xs:complexType>
37+
</xs:schema>

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

+19
Original file line numberDiff line numberDiff line change
@@ -583,3 +583,22 @@ tests:
583583
OUTPUT_LAYER:
584584
name: expected/point_on_line.gml
585585
type: vector
586+
587+
- algorithm: qgis:union
588+
name: Test Union (basic)
589+
params:
590+
INPUT:
591+
name: custom/union1_a.geojson
592+
type: vector
593+
INPUT2:
594+
name: custom/union1_b.geojson
595+
type: vector
596+
results:
597+
OUTPUT:
598+
name: expected/union1.gml
599+
type: vector
600+
# TODO: may need improvements to comparison
601+
# - unordered comparison
602+
# (features could be written in different order and still being correct output)
603+
# - geometry equality comparison instead of WKT string comparison
604+
# (geometries could different order of coordinate but still being correct output)

0 commit comments

Comments
 (0)