mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Merge pull request #3843 from nyalldawson/oriented
Port minimum oriented bounding box to QgsGeometry
This commit is contained in:
commit
37edb69c21
@ -405,9 +405,21 @@ class QgsGeometry
|
||||
*/
|
||||
QgsGeometry makeDifference( const QgsGeometry& other ) const;
|
||||
|
||||
/** Returns the bounding box of this feature*/
|
||||
/**
|
||||
* Returns the bounding box of the geometry.
|
||||
* @see orientedMinimumBoundingBox()
|
||||
*/
|
||||
QgsRectangle boundingBox() const;
|
||||
|
||||
/**
|
||||
* Returns the oriented minimum bounding box for the geometry, which is the smallest (by area)
|
||||
* rotated rectangle which fully encompasses the geometry. The area, angle (clockwise in degrees from North),
|
||||
* width and height of the rotated bounding box will also be returned.
|
||||
* @note added in QGIS 3.0
|
||||
* @see boundingBox()
|
||||
*/
|
||||
QgsGeometry orientedMinimumBoundingBox( double& area /Out/, double &angle /Out/, double& width /Out/, double& height /Out/ ) const;
|
||||
|
||||
/** Test for intersection with a rectangle (uses GEOS) */
|
||||
bool intersects( const QgsRectangle& r ) const;
|
||||
|
||||
|
@ -27,7 +27,7 @@ __revision__ = '$Format:%H$'
|
||||
|
||||
from math import degrees, atan2
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.core import Qgis, QgsField, QgsPoint, QgsGeometry, QgsFeature, QgsWkbTypes
|
||||
from qgis.core import Qgis, QgsField, QgsFields, QgsPoint, QgsGeometry, QgsFeature, QgsWkbTypes, QgsFeatureRequest
|
||||
from processing.core.GeoAlgorithm import GeoAlgorithm
|
||||
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
|
||||
from processing.core.parameters import ParameterVector
|
||||
@ -62,13 +62,15 @@ class OrientedMinimumBoundingBox(GeoAlgorithm):
|
||||
if byFeature and layer.geometryType() == QgsWkbTypes.PointGeometry and layer.featureCount() <= 2:
|
||||
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"))
|
||||
|
||||
fields = [
|
||||
QgsField('AREA', QVariant.Double),
|
||||
QgsField('PERIMETER', QVariant.Double),
|
||||
QgsField('ANGLE', QVariant.Double),
|
||||
QgsField('WIDTH', QVariant.Double),
|
||||
QgsField('HEIGHT', QVariant.Double),
|
||||
]
|
||||
if byFeature:
|
||||
fields = layer.fields()
|
||||
else:
|
||||
fields = QgsFields()
|
||||
fields.append(QgsField('area', QVariant.Double))
|
||||
fields.append(QgsField('perimeter', QVariant.Double))
|
||||
fields.append(QgsField('angle', QVariant.Double))
|
||||
fields.append(QgsField('width', QVariant.Double))
|
||||
fields.append(QgsField('height', QVariant.Double))
|
||||
|
||||
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields,
|
||||
QgsWkbTypes.Polygon, layer.crs())
|
||||
@ -81,30 +83,27 @@ class OrientedMinimumBoundingBox(GeoAlgorithm):
|
||||
del writer
|
||||
|
||||
def layerOmmb(self, layer, writer, progress):
|
||||
current = 0
|
||||
|
||||
fit = layer.getFeatures()
|
||||
inFeat = QgsFeature()
|
||||
total = 100.0 / layer.featureCount()
|
||||
req = QgsFeatureRequest().setSubsetOfAttributes([])
|
||||
features = vector.features(layer, req)
|
||||
total = 100.0 / len(features)
|
||||
newgeometry = QgsGeometry()
|
||||
first = True
|
||||
while fit.nextFeature(inFeat):
|
||||
for current, inFeat in enumerate(features):
|
||||
if first:
|
||||
newgeometry = inFeat.geometry()
|
||||
first = False
|
||||
else:
|
||||
newgeometry = newgeometry.combine(inFeat.geometry())
|
||||
current += 1
|
||||
progress.setPercentage(int(current * total))
|
||||
|
||||
geometry, area, perim, angle, width, height = self.OMBBox(newgeometry)
|
||||
geometry, area, angle, width, height = newgeometry.orientedMinimumBoundingBox()
|
||||
|
||||
if geometry:
|
||||
outFeat = QgsFeature()
|
||||
|
||||
outFeat.setGeometry(geometry)
|
||||
outFeat.setAttributes([area,
|
||||
perim,
|
||||
width * 2 + height * 2,
|
||||
angle,
|
||||
width,
|
||||
height])
|
||||
@ -115,62 +114,17 @@ class OrientedMinimumBoundingBox(GeoAlgorithm):
|
||||
total = 100.0 / len(features)
|
||||
outFeat = QgsFeature()
|
||||
for current, inFeat in enumerate(features):
|
||||
geometry, area, perim, angle, width, height = self.OMBBox(
|
||||
inFeat.geometry())
|
||||
geometry, area, angle, width, height = inFeat.geometry().orientedMinimumBoundingBox()
|
||||
if geometry:
|
||||
outFeat.setGeometry(geometry)
|
||||
outFeat.setAttributes([area,
|
||||
perim,
|
||||
angle,
|
||||
width,
|
||||
height])
|
||||
attrs = inFeat.attributes()
|
||||
attrs.extend([area,
|
||||
width * 2 + height * 2,
|
||||
angle,
|
||||
width,
|
||||
height])
|
||||
outFeat.setAttributes(attrs)
|
||||
writer.addFeature(outFeat)
|
||||
else:
|
||||
progress.setInfo(self.tr("Can't calculate an OMBB for feature {0}.").format(inFeat.id()))
|
||||
progress.setPercentage(int(current * total))
|
||||
|
||||
def GetAngleOfLineBetweenTwoPoints(self, p1, p2, angle_unit="degrees"):
|
||||
xDiff = p2.x() - p1.x()
|
||||
yDiff = p2.y() - p1.y()
|
||||
if angle_unit == "radians":
|
||||
return atan2(yDiff, xDiff)
|
||||
else:
|
||||
return degrees(atan2(yDiff, xDiff))
|
||||
|
||||
def OMBBox(self, geom):
|
||||
g = geom.convexHull()
|
||||
|
||||
if g.type() != QgsWkbTypes.PolygonGeometry:
|
||||
return None, None, None, None, None, None
|
||||
r = g.asPolygon()[0]
|
||||
|
||||
p0 = QgsPoint(r[0][0], r[0][1])
|
||||
|
||||
i = 0
|
||||
l = len(r)
|
||||
OMBBox = QgsGeometry()
|
||||
gBBox = g.boundingBox()
|
||||
OMBBox_area = gBBox.height() * gBBox.width()
|
||||
OMBBox_angle = 0
|
||||
OMBBox_width = 0
|
||||
OMBBox_heigth = 0
|
||||
OMBBox_perim = 0
|
||||
while i < l - 1:
|
||||
x = QgsGeometry(g)
|
||||
angle = self.GetAngleOfLineBetweenTwoPoints(r[i], r[i + 1])
|
||||
x.rotate(angle, p0)
|
||||
bbox = x.boundingBox()
|
||||
bb = QgsGeometry.fromWkt(bbox.asWktPolygon())
|
||||
bb.rotate(-angle, p0)
|
||||
|
||||
areabb = bb.area()
|
||||
if areabb <= OMBBox_area:
|
||||
OMBBox = QgsGeometry(bb)
|
||||
OMBBox_area = areabb
|
||||
OMBBox_angle = angle
|
||||
OMBBox_width = bbox.width()
|
||||
OMBBox_heigth = bbox.height()
|
||||
OMBBox_perim = 2 * OMBBox_width + 2 * OMBBox_heigth
|
||||
i += 1
|
||||
|
||||
return OMBBox, OMBBox_area, OMBBox_perim, OMBBox_angle, OMBBox_width, OMBBox_heigth
|
||||
|
32
python/plugins/processing/tests/testdata/custom/oriented_bbox.gfs
vendored
Normal file
32
python/plugins/processing/tests/testdata/custom/oriented_bbox.gfs
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<GMLFeatureClassList>
|
||||
<GMLFeatureClass>
|
||||
<Name>oriented_bbox</Name>
|
||||
<ElementPath>oriented_bbox</ElementPath>
|
||||
<!--POLYGON-->
|
||||
<GeometryType>3</GeometryType>
|
||||
<SRSName>EPSG:4326</SRSName>
|
||||
<DatasetSpecificInfo>
|
||||
<FeatureCount>6</FeatureCount>
|
||||
<ExtentXMin>-1.00000</ExtentXMin>
|
||||
<ExtentXMax>10.00000</ExtentXMax>
|
||||
<ExtentYMin>-3.00000</ExtentYMin>
|
||||
<ExtentYMax>6.00000</ExtentYMax>
|
||||
</DatasetSpecificInfo>
|
||||
<PropertyDefn>
|
||||
<Name>intval</Name>
|
||||
<ElementPath>intval</ElementPath>
|
||||
<Type>Integer</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>floatval</Name>
|
||||
<ElementPath>floatval</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>name</Name>
|
||||
<ElementPath>name</ElementPath>
|
||||
<Type>String</Type>
|
||||
<Width>5</Width>
|
||||
</PropertyDefn>
|
||||
</GMLFeatureClass>
|
||||
</GMLFeatureClassList>
|
59
python/plugins/processing/tests/testdata/custom/oriented_bbox.gml
vendored
Normal file
59
python/plugins/processing/tests/testdata/custom/oriented_bbox.gml
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ogr:FeatureCollection
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation=""
|
||||
xmlns:ogr="http://ogr.maptools.org/"
|
||||
xmlns:gml="http://www.opengis.net/gml">
|
||||
<gml:boundedBy>
|
||||
<gml:Box>
|
||||
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
|
||||
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
|
||||
</gml:Box>
|
||||
</gml:boundedBy>
|
||||
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bbox fid="polys.4">
|
||||
<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>
|
||||
<ogr:intval>120</ogr:intval>
|
||||
<ogr:floatval>-100291.43213</ogr:floatval>
|
||||
</ogr:oriented_bbox>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bbox fid="polys.1">
|
||||
<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>
|
||||
<ogr:name>Aaaaa</ogr:name>
|
||||
<ogr:intval>-33</ogr:intval>
|
||||
<ogr:floatval>0</ogr:floatval>
|
||||
</ogr:oriented_bbox>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bbox fid="polys.0">
|
||||
<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>
|
||||
<ogr:name>aaaaa</ogr:name>
|
||||
<ogr:intval>33</ogr:intval>
|
||||
<ogr:floatval>44.12346</ogr:floatval>
|
||||
</ogr:oriented_bbox>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bbox fid="polys.3">
|
||||
<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>
|
||||
<ogr:name>ASDF</ogr:name>
|
||||
<ogr:intval>0</ogr:intval>
|
||||
</ogr:oriented_bbox>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bbox fid="polys.2">
|
||||
<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>
|
||||
<ogr:name>bbaaa</ogr:name>
|
||||
<ogr:floatval>0.123</ogr:floatval>
|
||||
</ogr:oriented_bbox>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bbox fid="polys.5">
|
||||
<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>
|
||||
<ogr:name>elim</ogr:name>
|
||||
<ogr:intval>2</ogr:intval>
|
||||
<ogr:floatval>3.33</ogr:floatval>
|
||||
</ogr:oriented_bbox>
|
||||
</gml:featureMember>
|
||||
</ogr:FeatureCollection>
|
57
python/plugins/processing/tests/testdata/expected/oriented_bounds.gfs
vendored
Normal file
57
python/plugins/processing/tests/testdata/expected/oriented_bounds.gfs
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<GMLFeatureClassList>
|
||||
<GMLFeatureClass>
|
||||
<Name>oriented_bounds</Name>
|
||||
<ElementPath>oriented_bounds</ElementPath>
|
||||
<!--POLYGON-->
|
||||
<GeometryType>3</GeometryType>
|
||||
<SRSName>EPSG:4326</SRSName>
|
||||
<DatasetSpecificInfo>
|
||||
<FeatureCount>6</FeatureCount>
|
||||
<ExtentXMin>-1.00000</ExtentXMin>
|
||||
<ExtentXMax>10.04414</ExtentXMax>
|
||||
<ExtentYMin>-3.27034</ExtentYMin>
|
||||
<ExtentYMax>6.44118</ExtentYMax>
|
||||
</DatasetSpecificInfo>
|
||||
<PropertyDefn>
|
||||
<Name>intval</Name>
|
||||
<ElementPath>intval</ElementPath>
|
||||
<Type>Integer</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>floatval</Name>
|
||||
<ElementPath>floatval</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>area</Name>
|
||||
<ElementPath>area</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>perimeter</Name>
|
||||
<ElementPath>perimeter</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>angle</Name>
|
||||
<ElementPath>angle</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>width</Name>
|
||||
<ElementPath>width</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>height</Name>
|
||||
<ElementPath>height</ElementPath>
|
||||
<Type>Real</Type>
|
||||
</PropertyDefn>
|
||||
<PropertyDefn>
|
||||
<Name>name</Name>
|
||||
<ElementPath>name</ElementPath>
|
||||
<Type>String</Type>
|
||||
<Width>5</Width>
|
||||
</PropertyDefn>
|
||||
</GMLFeatureClass>
|
||||
</GMLFeatureClassList>
|
89
python/plugins/processing/tests/testdata/expected/oriented_bounds.gml
vendored
Normal file
89
python/plugins/processing/tests/testdata/expected/oriented_bounds.gml
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ogr:FeatureCollection
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation=""
|
||||
xmlns:ogr="http://ogr.maptools.org/"
|
||||
xmlns:gml="http://www.opengis.net/gml">
|
||||
<gml:boundedBy>
|
||||
<gml:Box>
|
||||
<gml:coord><gml:X>-1</gml:X><gml:Y>-3.270344827586206</gml:Y></gml:coord>
|
||||
<gml:coord><gml:X>10.04413793103448</gml:X><gml:Y>6.441176470588236</gml:Y></gml:coord>
|
||||
</gml:Box>
|
||||
</gml:boundedBy>
|
||||
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bounds fid="polys.4">
|
||||
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,-3 7.69811320754717,0.056603773584906 3.80943396226415,2.21698113207547 2.11132075471698,-0.839622641509436 6,-3</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
|
||||
<ogr:intval>120</ogr:intval>
|
||||
<ogr:floatval>-100291.43213</ogr:floatval>
|
||||
<ogr:area>15.5547169811321</ogr:area>
|
||||
<ogr:perimeter>15.8902367081648</ogr:perimeter>
|
||||
<ogr:angle>119.054604099077</ogr:angle>
|
||||
<ogr:width>3.49662910448615</ogr:width>
|
||||
<ogr:height>4.44848924959627</ogr:height>
|
||||
</ogr:oriented_bounds>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bounds fid="polys.1">
|
||||
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4.11764705882353,3.52941176470588 6.0,4.0 5.72941176470588,5.08235294117647 3.84705882352941,4.61176470588235 4.11764705882353,3.52941176470588</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
|
||||
<ogr:intval>-33</ogr:intval>
|
||||
<ogr:floatval>0</ogr:floatval>
|
||||
<ogr:name>Aaaaa</ogr:name>
|
||||
<ogr:area>2.16470588235294</ogr:area>
|
||||
<ogr:perimeter>6.11189775091559</ogr:perimeter>
|
||||
<ogr:angle>165.963756532073</ogr:angle>
|
||||
<ogr:width>1.94028500029066</ogr:width>
|
||||
<ogr:height>1.11566387516713</ogr:height>
|
||||
</ogr:oriented_bounds>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bounds fid="polys.0">
|
||||
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1.0,3.0 -1.0,-1.0 3.0,-1.0 3.0,3.0 -1.0,3.0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
|
||||
<ogr:intval>33</ogr:intval>
|
||||
<ogr:floatval>44.12346</ogr:floatval>
|
||||
<ogr:name>aaaaa</ogr:name>
|
||||
<ogr:area>16</ogr:area>
|
||||
<ogr:perimeter>16</ogr:perimeter>
|
||||
<ogr:angle>90</ogr:angle>
|
||||
<ogr:width>4</ogr:width>
|
||||
<ogr:height>4</ogr:height>
|
||||
</ogr:oriented_bounds>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bounds fid="polys.3">
|
||||
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6.4,-3.0 9.64413793103449,-3.27034482758621 10.0441379310345,1.52965517241379 6.8,1.8 6.4,-3.0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
|
||||
<ogr:intval>0</ogr:intval>
|
||||
<ogr:name>ASDF</ogr:name>
|
||||
<ogr:area>15.68</ogr:area>
|
||||
<ogr:perimeter>16.1440412835671</ogr:perimeter>
|
||||
<ogr:angle>4.76364169072617</ogr:angle>
|
||||
<ogr:width>3.25538281026661</ogr:width>
|
||||
<ogr:height>4.81663783151692</ogr:height>
|
||||
</ogr:oriented_bounds>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bounds fid="polys.2">
|
||||
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1.36470588235294,4.94117647058824 2.1,4.5 3.0,6.0 2.26470588235294,6.44117647058824 1.36470588235294,4.94117647058824</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
|
||||
<ogr:floatval>0.123</ogr:floatval>
|
||||
<ogr:name>bbaaa</ogr:name>
|
||||
<ogr:area>1.5</ogr:area>
|
||||
<ogr:perimeter>5.21355698833227</ogr:perimeter>
|
||||
<ogr:angle>30.9637565320735</ogr:angle>
|
||||
<ogr:width>0.857492925712544</ogr:width>
|
||||
<ogr:height>1.74928556845359</ogr:height>
|
||||
</ogr:oriented_bounds>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<ogr:oriented_bounds fid="polys.5">
|
||||
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,-3 7.69811320754717,0.056603773584906 3.80943396226415,2.21698113207547 2.11132075471698,-0.839622641509436 6,-3</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
|
||||
<ogr:intval>2</ogr:intval>
|
||||
<ogr:floatval>3.33</ogr:floatval>
|
||||
<ogr:name>elim</ogr:name>
|
||||
<ogr:area>15.5547169811321</ogr:area>
|
||||
<ogr:perimeter>15.8902367081648</ogr:perimeter>
|
||||
<ogr:angle>119.054604099077</ogr:angle>
|
||||
<ogr:width>3.49662910448615</ogr:width>
|
||||
<ogr:height>4.44848924959627</ogr:height>
|
||||
</ogr:oriented_bounds>
|
||||
</gml:featureMember>
|
||||
</ogr:FeatureCollection>
|
@ -1854,3 +1854,15 @@ tests:
|
||||
OUTPUT:
|
||||
hash: fe6e018be13c5a3c17f3f4d0f0dc7686c628cb440b74c4642aa0c939
|
||||
type: rasterhash
|
||||
|
||||
- algorithm: qgis:orientedminimumboundingbox
|
||||
name: Oriented minimum bounding box polys
|
||||
params:
|
||||
BY_FEATURE: true
|
||||
INPUT_LAYER:
|
||||
name: custom/oriented_bbox.gml
|
||||
type: vector
|
||||
results:
|
||||
OUTPUT:
|
||||
name: expected/oriented_bounds.gml
|
||||
type: vector
|
||||
|
@ -102,8 +102,6 @@ class TestCase(_TestCase):
|
||||
)
|
||||
|
||||
for attr_expected, field_expected in zip(feats[0].attributes(), layer_expected.fields().toList()):
|
||||
attr_result = feats[1][field_expected.name()]
|
||||
field_result = [fld for fld in layer_expected.fields().toList() if fld.name() == field_expected.name()][0]
|
||||
try:
|
||||
cmp = compare['fields'][field_expected.name()]
|
||||
except KeyError:
|
||||
@ -116,6 +114,9 @@ class TestCase(_TestCase):
|
||||
if 'skip' in cmp:
|
||||
continue
|
||||
|
||||
attr_result = feats[1][field_expected.name()]
|
||||
field_result = [fld for fld in layer_expected.fields().toList() if fld.name() == field_expected.name()][0]
|
||||
|
||||
# Cast field to a given type
|
||||
if 'cast' in cmp:
|
||||
if cmp['cast'] == 'int':
|
||||
|
@ -862,6 +862,65 @@ QgsRectangle QgsGeometry::boundingBox() const
|
||||
return QgsRectangle();
|
||||
}
|
||||
|
||||
QgsGeometry QgsGeometry::orientedMinimumBoundingBox( double& area, double &angle, double& width, double& height ) const
|
||||
{
|
||||
QgsRectangle minRect;
|
||||
area = DBL_MAX;
|
||||
angle = 0;
|
||||
width = DBL_MAX;
|
||||
height = DBL_MAX;
|
||||
|
||||
if ( !d->geometry || d->geometry->nCoordinates() < 2 )
|
||||
return QgsGeometry();
|
||||
|
||||
QgsGeometry hull = convexHull();
|
||||
if ( hull.isEmpty() )
|
||||
return QgsGeometry();
|
||||
|
||||
QgsVertexId vertexId;
|
||||
QgsPointV2 pt0;
|
||||
QgsPointV2 pt1;
|
||||
QgsPointV2 pt2;
|
||||
// get first point
|
||||
hull.geometry()->nextVertex( vertexId, pt0 );
|
||||
pt1 = pt0;
|
||||
double prevAngle = 0.0;
|
||||
while ( hull.geometry()->nextVertex( vertexId, pt2 ) )
|
||||
{
|
||||
double currentAngle = QgsGeometryUtils::lineAngle( pt1.x(), pt1.y(), pt2.x(), pt2.y() );
|
||||
double rotateAngle = 180.0 / M_PI * ( currentAngle - prevAngle );
|
||||
prevAngle = currentAngle;
|
||||
|
||||
QTransform t = QTransform::fromTranslate( pt0.x(), pt0.y() );
|
||||
t.rotate( rotateAngle );
|
||||
t.translate( -pt0.x(), -pt0.y() );
|
||||
|
||||
hull.geometry()->transform( t );
|
||||
|
||||
QgsRectangle bounds = hull.geometry()->boundingBox();
|
||||
double currentArea = bounds.width() * bounds.height();
|
||||
if ( currentArea < area )
|
||||
{
|
||||
minRect = bounds;
|
||||
area = currentArea;
|
||||
angle = 180.0 / M_PI * currentAngle;
|
||||
width = bounds.width();
|
||||
height = bounds.height();
|
||||
}
|
||||
|
||||
pt2 = pt1;
|
||||
}
|
||||
|
||||
QgsGeometry minBounds = QgsGeometry::fromRect( minRect );
|
||||
minBounds.rotate( angle, QgsPoint( pt0.x(), pt0.y() ) );
|
||||
|
||||
// constrain angle to 0 - 180
|
||||
if ( angle > 180.0 )
|
||||
angle = fmod( angle, 180.0 );
|
||||
|
||||
return minBounds;
|
||||
}
|
||||
|
||||
bool QgsGeometry::intersects( const QgsRectangle& r ) const
|
||||
{
|
||||
QgsGeometry g = fromRect( r );
|
||||
|
@ -458,9 +458,21 @@ class CORE_EXPORT QgsGeometry
|
||||
*/
|
||||
QgsGeometry makeDifference( const QgsGeometry& other ) const;
|
||||
|
||||
//! Returns the bounding box of this feature
|
||||
/**
|
||||
* Returns the bounding box of the geometry.
|
||||
* @see orientedMinimumBoundingBox()
|
||||
*/
|
||||
QgsRectangle boundingBox() const;
|
||||
|
||||
/**
|
||||
* Returns the oriented minimum bounding box for the geometry, which is the smallest (by area)
|
||||
* rotated rectangle which fully encompasses the geometry. The area, angle (clockwise in degrees from North),
|
||||
* width and height of the rotated bounding box will also be returned.
|
||||
* @note added in QGIS 3.0
|
||||
* @see boundingBox()
|
||||
*/
|
||||
QgsGeometry orientedMinimumBoundingBox( double& area, double &angle, double& width, double& height ) const;
|
||||
|
||||
//! Test for intersection with a rectangle (uses GEOS)
|
||||
bool intersects( const QgsRectangle& r ) const;
|
||||
|
||||
|
@ -3645,6 +3645,29 @@ class TestQgsGeometry(unittest.TestCase):
|
||||
self.assertTrue(compareWkt(result, exp, 0.00001),
|
||||
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
|
||||
|
||||
def testMinimumOrientedBoundingBox(self):
|
||||
empty = QgsGeometry()
|
||||
bbox, area, angle, width, height = empty.orientedMinimumBoundingBox()
|
||||
self.assertFalse(bbox)
|
||||
|
||||
# not a useful geometry
|
||||
point = QgsGeometry.fromWkt('Point(1 2)')
|
||||
bbox, area, angle, width, height = point.orientedMinimumBoundingBox()
|
||||
self.assertFalse(bbox)
|
||||
|
||||
# polygon
|
||||
polygon = QgsGeometry.fromWkt('Polygon((-0.1 -1.3, 2.1 1, 3 2.8, 6.7 0.2, 3 -1.8, 0.3 -2.7, -0.1 -1.3))')
|
||||
bbox, area, angle, width, height = polygon.orientedMinimumBoundingBox()
|
||||
exp = 'Polygon ((-0.94905660 -1.571698, 2.3817055 -4.580453, 6.7000000 0.1999999, 3.36923 3.208754, -0.949056 -1.57169))'
|
||||
|
||||
result = bbox.exportToWkt()
|
||||
self.assertTrue(compareWkt(result, exp, 0.00001),
|
||||
"Oriented MBBR: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
|
||||
self.assertAlmostEqual(area, 28.9152, places=3)
|
||||
self.assertAlmostEqual(angle, 42.0922, places=3)
|
||||
self.assertAlmostEqual(width, 4.4884, places=3)
|
||||
self.assertAlmostEqual(height, 6.4420, places=3)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user