Skip to content

Commit 442bdfc

Browse files
lbartolettislarosa
authored andcommittedSep 12, 2015
Oriented Minimum Bounding Box - processing version as requested #2116
Inverse field order for PERIMETER and ANGLE
1 parent 95e9ade commit 442bdfc

File tree

2 files changed

+185
-2
lines changed

2 files changed

+185
-2
lines changed
 
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
OrientedMinimumBoundingBox.py
6+
---------------------
7+
Date : June 2015
8+
Copyright : (C) 2015, Loïc BARTOLETTI
9+
Email : coder at tuxfamily dot org
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Loïc BARTOLETTI'
21+
__date__ = 'June 2015'
22+
__copyright__ = '(C) 2015, Loïc BARTOLETTI'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
from PyQt4.QtCore import QVariant
29+
from qgis.core import QGis, QgsField, QgsPoint, QgsGeometry, QgsFeature
30+
from processing.core.GeoAlgorithm import GeoAlgorithm
31+
from processing.core.parameters import ParameterVector
32+
from processing.core.parameters import ParameterBoolean
33+
from processing.core.outputs import OutputVector
34+
from processing.tools import dataobjects, vector
35+
from math import *
36+
37+
38+
39+
class OrientedMinimumBoundingBox(GeoAlgorithm):
40+
41+
INPUT_LAYER = 'INPUT_LAYER'
42+
BY_FEATURE = 'BY_FEATURE'
43+
44+
OUTPUT = 'OUTPUT'
45+
46+
def defineCharacteristics(self):
47+
self.name = 'Oriented Minimum Bounding Box'
48+
self.group = 'Vector general tools'
49+
50+
self.addParameter(ParameterVector(self.INPUT_LAYER,
51+
self.tr('Input layer'), [ParameterVector.VECTOR_TYPE_ANY]))
52+
self.addParameter(ParameterBoolean(self.BY_FEATURE,
53+
self.tr('Calculate OMBB for each feature separately'), True))
54+
55+
self.addOutput(OutputVector(self.OUTPUT, self.tr('Oriented_MBBox')))
56+
57+
def processAlgorithm(self, progress):
58+
layer = dataobjects.getObjectFromUri(
59+
self.getParameterValue(self.INPUT_LAYER))
60+
byFeature = self.getParameterValue(self.BY_FEATURE)
61+
62+
if byFeature and layer.geometryType() == 0:
63+
progress.setInfo(self.tr("Can't calculate an OMBB for each point, it's a point."))
64+
byFeature = False
65+
66+
fields = [
67+
QgsField('AREA', QVariant.Double),
68+
QgsField('PERIMETER', QVariant.Double),
69+
QgsField('ANGLE', QVariant.Double),
70+
QgsField('WIDTH', QVariant.Double),
71+
QgsField('HEIGHT', QVariant.Double),
72+
]
73+
74+
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields,
75+
QGis.WKBPolygon, layer.crs())
76+
77+
if byFeature:
78+
self.featureOmbb(layer, writer, progress)
79+
else:
80+
self.layerOmmb(layer, writer, progress)
81+
82+
del writer
83+
84+
def layerOmmb(self, layer, writer, progress):
85+
current = 0
86+
vprovider = layer.dataProvider()
87+
88+
fit = vprovider.getFeatures()
89+
inFeat = QgsFeature()
90+
total = 100.0 / float(vprovider.featureCount())
91+
newgeometry = QgsGeometry()
92+
first = True
93+
while fit.nextFeature(inFeat):
94+
if first:
95+
newgeometry = QgsGeometry(inFeat.geometry())
96+
first = False
97+
else:
98+
newgeometry = newgeometry.combine(inFeat.geometry())
99+
current += 1
100+
progress.setPercentage(int(current * total))
101+
102+
geometry, area, perim, angle, width, height = self.OMBBox(newgeometry)
103+
104+
if geometry:
105+
outFeat = QgsFeature()
106+
107+
outFeat.setGeometry(geometry)
108+
outFeat.setAttributes([ area,
109+
perim,
110+
angle,
111+
width,
112+
height])
113+
writer.addFeature(outFeat)
114+
115+
def featureOmbb(self, layer, writer, progress):
116+
current = 0
117+
features = vector.features(layer)
118+
total = 100.0 / float(len(features))
119+
outFeat = QgsFeature()
120+
inFeat = QgsFeature()
121+
for inFeat in features:
122+
geometry, area, perim, angle, width, height = self.OMBBox(
123+
inFeat.geometry())
124+
if geometry:
125+
outFeat.setGeometry(geometry)
126+
outFeat.setAttributes([ area,
127+
perim,
128+
angle,
129+
width,
130+
height])
131+
writer.addFeature(outFeat)
132+
else:
133+
progress.setInfo(self.tr("Can't calculate an OMBB for features n°") + str(inFeat.id()) + ".")
134+
current += 1
135+
progress.setPercentage(int(current * total))
136+
137+
def GetAngleOfLineBetweenTwoPoints(self, p1, p2, angle_unit="degrees"):
138+
xDiff = p2.x() - p1.x()
139+
yDiff = p2.y() - p1.y()
140+
if angle_unit == "radians":
141+
return atan2(yDiff, xDiff)
142+
else:
143+
return degrees(atan2(yDiff, xDiff))
144+
145+
def OMBBox(self, geom):
146+
147+
g = geom.convexHull()
148+
149+
if g.type() != 2:
150+
return None, None, None, None, None, None
151+
r = g.asPolygon()[0]
152+
153+
p0 = QgsPoint(r[0][0], r[0][1])
154+
155+
i = 0
156+
l = len(r)
157+
OMBBox = QgsGeometry()
158+
gBBox = g.boundingBox()
159+
OMBBox_area = gBBox.height() * gBBox.width()
160+
OMBBox_angle = 0
161+
OMBBox_width = 0
162+
OMBBox_heigth = 0
163+
OMBBox_perim = 0
164+
while i < l - 1:
165+
x = QgsGeometry(g)
166+
angle = self.GetAngleOfLineBetweenTwoPoints(r[i], r[i + 1])
167+
x.rotate(angle, p0)
168+
bbox = x.boundingBox()
169+
bb = QgsGeometry.fromWkt(bbox.asWktPolygon())
170+
bb.rotate(-angle, p0)
171+
172+
a = bb.area()
173+
if a <= OMBBox_area:
174+
OMBBox = QgsGeometry(bb)
175+
OMBBox_area = a
176+
OMBBox_angle = angle
177+
OMBBox_width = bbox.width()
178+
OMBBox_heigth = bbox.height()
179+
OMBBox_perim = 2 * OMBBox_width + 2 * OMBBox_heigth
180+
i += 1
181+
182+
return OMBBox, OMBBox_area, OMBBox_perim, OMBBox_angle, OMBBox_width, OMBBox_heigth

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
from FieldsMapper import FieldsMapper
131131
from Datasources2Vrt import Datasources2Vrt
132132
from CheckValidity import CheckValidity
133+
from OrientedMinimumBoundingBox import OrientedMinimumBoundingBox
133134

134135
pluginPath = os.path.normpath(os.path.join(
135136
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -177,8 +178,8 @@ def __init__(self):
177178
SetVectorStyle(), SetRasterStyle(),
178179
SelectByExpression(), HypsometricCurves(),
179180
SplitLinesWithLines(), CreateConstantRaster(),
180-
FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(),
181-
CheckValidity()
181+
FieldsMapper(),SelectByAttributeSum(), Datasources2Vrt(),
182+
CheckValidity(), OrientedMinimumBoundingBox()
182183
]
183184

184185
if hasMatplotlib:

0 commit comments

Comments
 (0)
Please sign in to comment.