Skip to content

Commit 37edb69

Browse files
authoredDec 7, 2016
Merge pull request #3843 from nyalldawson/oriented
Port minimum oriented bounding box to QgsGeometry
2 parents ed5a2bc + 1bdb35d commit 37edb69

File tree

11 files changed

+384
-74
lines changed

11 files changed

+384
-74
lines changed
 

‎python/core/geometry/qgsgeometry.sip

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,21 @@ class QgsGeometry
405405
*/
406406
QgsGeometry makeDifference( const QgsGeometry& other ) const;
407407

408-
/** Returns the bounding box of this feature*/
408+
/**
409+
* Returns the bounding box of the geometry.
410+
* @see orientedMinimumBoundingBox()
411+
*/
409412
QgsRectangle boundingBox() const;
410413

414+
/**
415+
* Returns the oriented minimum bounding box for the geometry, which is the smallest (by area)
416+
* rotated rectangle which fully encompasses the geometry. The area, angle (clockwise in degrees from North),
417+
* width and height of the rotated bounding box will also be returned.
418+
* @note added in QGIS 3.0
419+
* @see boundingBox()
420+
*/
421+
QgsGeometry orientedMinimumBoundingBox( double& area /Out/, double &angle /Out/, double& width /Out/, double& height /Out/ ) const;
422+
411423
/** Test for intersection with a rectangle (uses GEOS) */
412424
bool intersects( const QgsRectangle& r ) const;
413425

‎python/plugins/processing/algs/qgis/OrientedMinimumBoundingBox.py

Lines changed: 24 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
from math import degrees, atan2
2929
from qgis.PyQt.QtCore import QVariant
30-
from qgis.core import Qgis, QgsField, QgsPoint, QgsGeometry, QgsFeature, QgsWkbTypes
30+
from qgis.core import Qgis, QgsField, QgsFields, QgsPoint, QgsGeometry, QgsFeature, QgsWkbTypes, QgsFeatureRequest
3131
from processing.core.GeoAlgorithm import GeoAlgorithm
3232
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
3333
from processing.core.parameters import ParameterVector
@@ -62,13 +62,15 @@ def processAlgorithm(self, progress):
6262
if byFeature and layer.geometryType() == QgsWkbTypes.PointGeometry and layer.featureCount() <= 2:
6363
raise GeoAlgorithmExecutionException(self.tr("Can't calculate an OMBB for each point, it's a point. The number of points must be greater than 2"))
6464

65-
fields = [
66-
QgsField('AREA', QVariant.Double),
67-
QgsField('PERIMETER', QVariant.Double),
68-
QgsField('ANGLE', QVariant.Double),
69-
QgsField('WIDTH', QVariant.Double),
70-
QgsField('HEIGHT', QVariant.Double),
71-
]
65+
if byFeature:
66+
fields = layer.fields()
67+
else:
68+
fields = QgsFields()
69+
fields.append(QgsField('area', QVariant.Double))
70+
fields.append(QgsField('perimeter', QVariant.Double))
71+
fields.append(QgsField('angle', QVariant.Double))
72+
fields.append(QgsField('width', QVariant.Double))
73+
fields.append(QgsField('height', QVariant.Double))
7274

7375
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields,
7476
QgsWkbTypes.Polygon, layer.crs())
@@ -81,30 +83,27 @@ def processAlgorithm(self, progress):
8183
del writer
8284

8385
def layerOmmb(self, layer, writer, progress):
84-
current = 0
85-
86-
fit = layer.getFeatures()
87-
inFeat = QgsFeature()
88-
total = 100.0 / layer.featureCount()
86+
req = QgsFeatureRequest().setSubsetOfAttributes([])
87+
features = vector.features(layer, req)
88+
total = 100.0 / len(features)
8989
newgeometry = QgsGeometry()
9090
first = True
91-
while fit.nextFeature(inFeat):
91+
for current, inFeat in enumerate(features):
9292
if first:
9393
newgeometry = inFeat.geometry()
9494
first = False
9595
else:
9696
newgeometry = newgeometry.combine(inFeat.geometry())
97-
current += 1
9897
progress.setPercentage(int(current * total))
9998

100-
geometry, area, perim, angle, width, height = self.OMBBox(newgeometry)
99+
geometry, area, angle, width, height = newgeometry.orientedMinimumBoundingBox()
101100

102101
if geometry:
103102
outFeat = QgsFeature()
104103

105104
outFeat.setGeometry(geometry)
106105
outFeat.setAttributes([area,
107-
perim,
106+
width * 2 + height * 2,
108107
angle,
109108
width,
110109
height])
@@ -115,62 +114,17 @@ def featureOmbb(self, layer, writer, progress):
115114
total = 100.0 / len(features)
116115
outFeat = QgsFeature()
117116
for current, inFeat in enumerate(features):
118-
geometry, area, perim, angle, width, height = self.OMBBox(
119-
inFeat.geometry())
117+
geometry, area, angle, width, height = inFeat.geometry().orientedMinimumBoundingBox()
120118
if geometry:
121119
outFeat.setGeometry(geometry)
122-
outFeat.setAttributes([area,
123-
perim,
124-
angle,
125-
width,
126-
height])
120+
attrs = inFeat.attributes()
121+
attrs.extend([area,
122+
width * 2 + height * 2,
123+
angle,
124+
width,
125+
height])
126+
outFeat.setAttributes(attrs)
127127
writer.addFeature(outFeat)
128128
else:
129129
progress.setInfo(self.tr("Can't calculate an OMBB for feature {0}.").format(inFeat.id()))
130130
progress.setPercentage(int(current * total))
131-
132-
def GetAngleOfLineBetweenTwoPoints(self, p1, p2, angle_unit="degrees"):
133-
xDiff = p2.x() - p1.x()
134-
yDiff = p2.y() - p1.y()
135-
if angle_unit == "radians":
136-
return atan2(yDiff, xDiff)
137-
else:
138-
return degrees(atan2(yDiff, xDiff))
139-
140-
def OMBBox(self, geom):
141-
g = geom.convexHull()
142-
143-
if g.type() != QgsWkbTypes.PolygonGeometry:
144-
return None, None, None, None, None, None
145-
r = g.asPolygon()[0]
146-
147-
p0 = QgsPoint(r[0][0], r[0][1])
148-
149-
i = 0
150-
l = len(r)
151-
OMBBox = QgsGeometry()
152-
gBBox = g.boundingBox()
153-
OMBBox_area = gBBox.height() * gBBox.width()
154-
OMBBox_angle = 0
155-
OMBBox_width = 0
156-
OMBBox_heigth = 0
157-
OMBBox_perim = 0
158-
while i < l - 1:
159-
x = QgsGeometry(g)
160-
angle = self.GetAngleOfLineBetweenTwoPoints(r[i], r[i + 1])
161-
x.rotate(angle, p0)
162-
bbox = x.boundingBox()
163-
bb = QgsGeometry.fromWkt(bbox.asWktPolygon())
164-
bb.rotate(-angle, p0)
165-
166-
areabb = bb.area()
167-
if areabb <= OMBBox_area:
168-
OMBBox = QgsGeometry(bb)
169-
OMBBox_area = areabb
170-
OMBBox_angle = angle
171-
OMBBox_width = bbox.width()
172-
OMBBox_heigth = bbox.height()
173-
OMBBox_perim = 2 * OMBBox_width + 2 * OMBBox_heigth
174-
i += 1
175-
176-
return OMBBox, OMBBox_area, OMBBox_perim, OMBBox_angle, OMBBox_width, OMBBox_heigth
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>oriented_bbox</Name>
4+
<ElementPath>oriented_bbox</ElementPath>
5+
<!--POLYGON-->
6+
<GeometryType>3</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>6</FeatureCount>
10+
<ExtentXMin>-1.00000</ExtentXMin>
11+
<ExtentXMax>10.00000</ExtentXMax>
12+
<ExtentYMin>-3.00000</ExtentYMin>
13+
<ExtentYMax>6.00000</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>intval</Name>
17+
<ElementPath>intval</ElementPath>
18+
<Type>Integer</Type>
19+
</PropertyDefn>
20+
<PropertyDefn>
21+
<Name>floatval</Name>
22+
<ElementPath>floatval</ElementPath>
23+
<Type>Real</Type>
24+
</PropertyDefn>
25+
<PropertyDefn>
26+
<Name>name</Name>
27+
<ElementPath>name</ElementPath>
28+
<Type>String</Type>
29+
<Width>5</Width>
30+
</PropertyDefn>
31+
</GMLFeatureClass>
32+
</GMLFeatureClassList>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
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>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:oriented_bbox fid="polys.4">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3.8,2.2 6,1 6,-3 2.4,-1.0 3.8,2.2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
<ogr:intval>120</ogr:intval>
18+
<ogr:floatval>-100291.43213</ogr:floatval>
19+
</ogr:oriented_bbox>
20+
</gml:featureMember>
21+
<gml:featureMember>
22+
<ogr:oriented_bbox fid="polys.1">
23+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.4,5.0 6,4 5.2,3.8 4,4 5.4,5.0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
24+
<ogr:name>Aaaaa</ogr:name>
25+
<ogr:intval>-33</ogr:intval>
26+
<ogr:floatval>0</ogr:floatval>
27+
</ogr:oriented_bbox>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:oriented_bbox fid="polys.0">
31+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
32+
<ogr:name>aaaaa</ogr:name>
33+
<ogr:intval>33</ogr:intval>
34+
<ogr:floatval>44.12346</ogr:floatval>
35+
</ogr:oriented_bbox>
36+
</gml:featureMember>
37+
<gml:featureMember>
38+
<ogr:oriented_bbox fid="polys.3">
39+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6.8,1.8 10,1 9.6,-2.2 6.4,-3.0 7.2,-0.6 6.8,1.8</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>8.0,-0.6 7,-2 9,-2 9,0 8.0,-0.6</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
40+
<ogr:name>ASDF</ogr:name>
41+
<ogr:intval>0</ogr:intval>
42+
</ogr:oriented_bbox>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:oriented_bbox fid="polys.2">
46+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1.6,4.8 2,6 3,6 1.6,4.8</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
47+
<ogr:name>bbaaa</ogr:name>
48+
<ogr:floatval>0.123</ogr:floatval>
49+
</ogr:oriented_bbox>
50+
</gml:featureMember>
51+
<gml:featureMember>
52+
<ogr:oriented_bbox fid="polys.5">
53+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3.8,2.2 6,1 6,-3 2.4,-1.0 3.8,2.2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
54+
<ogr:name>elim</ogr:name>
55+
<ogr:intval>2</ogr:intval>
56+
<ogr:floatval>3.33</ogr:floatval>
57+
</ogr:oriented_bbox>
58+
</gml:featureMember>
59+
</ogr:FeatureCollection>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>oriented_bounds</Name>
4+
<ElementPath>oriented_bounds</ElementPath>
5+
<!--POLYGON-->
6+
<GeometryType>3</GeometryType>
7+
<SRSName>EPSG:4326</SRSName>
8+
<DatasetSpecificInfo>
9+
<FeatureCount>6</FeatureCount>
10+
<ExtentXMin>-1.00000</ExtentXMin>
11+
<ExtentXMax>10.04414</ExtentXMax>
12+
<ExtentYMin>-3.27034</ExtentYMin>
13+
<ExtentYMax>6.44118</ExtentYMax>
14+
</DatasetSpecificInfo>
15+
<PropertyDefn>
16+
<Name>intval</Name>
17+
<ElementPath>intval</ElementPath>
18+
<Type>Integer</Type>
19+
</PropertyDefn>
20+
<PropertyDefn>
21+
<Name>floatval</Name>
22+
<ElementPath>floatval</ElementPath>
23+
<Type>Real</Type>
24+
</PropertyDefn>
25+
<PropertyDefn>
26+
<Name>area</Name>
27+
<ElementPath>area</ElementPath>
28+
<Type>Real</Type>
29+
</PropertyDefn>
30+
<PropertyDefn>
31+
<Name>perimeter</Name>
32+
<ElementPath>perimeter</ElementPath>
33+
<Type>Real</Type>
34+
</PropertyDefn>
35+
<PropertyDefn>
36+
<Name>angle</Name>
37+
<ElementPath>angle</ElementPath>
38+
<Type>Real</Type>
39+
</PropertyDefn>
40+
<PropertyDefn>
41+
<Name>width</Name>
42+
<ElementPath>width</ElementPath>
43+
<Type>Real</Type>
44+
</PropertyDefn>
45+
<PropertyDefn>
46+
<Name>height</Name>
47+
<ElementPath>height</ElementPath>
48+
<Type>Real</Type>
49+
</PropertyDefn>
50+
<PropertyDefn>
51+
<Name>name</Name>
52+
<ElementPath>name</ElementPath>
53+
<Type>String</Type>
54+
<Width>5</Width>
55+
</PropertyDefn>
56+
</GMLFeatureClass>
57+
</GMLFeatureClassList>

0 commit comments

Comments
 (0)
Please sign in to comment.