mirror of
https://github.com/qgis/QGIS.git
synced 2025-05-05 00:03:20 -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;
|
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;
|
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) */
|
/** Test for intersection with a rectangle (uses GEOS) */
|
||||||
bool intersects( const QgsRectangle& r ) const;
|
bool intersects( const QgsRectangle& r ) const;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ __revision__ = '$Format:%H$'
|
|||||||
|
|
||||||
from math import degrees, atan2
|
from math import degrees, atan2
|
||||||
from qgis.PyQt.QtCore import QVariant
|
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.GeoAlgorithm import GeoAlgorithm
|
||||||
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
|
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
|
||||||
from processing.core.parameters import ParameterVector
|
from processing.core.parameters import ParameterVector
|
||||||
@ -62,13 +62,15 @@ class OrientedMinimumBoundingBox(GeoAlgorithm):
|
|||||||
if byFeature and layer.geometryType() == QgsWkbTypes.PointGeometry and layer.featureCount() <= 2:
|
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"))
|
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 = [
|
if byFeature:
|
||||||
QgsField('AREA', QVariant.Double),
|
fields = layer.fields()
|
||||||
QgsField('PERIMETER', QVariant.Double),
|
else:
|
||||||
QgsField('ANGLE', QVariant.Double),
|
fields = QgsFields()
|
||||||
QgsField('WIDTH', QVariant.Double),
|
fields.append(QgsField('area', QVariant.Double))
|
||||||
QgsField('HEIGHT', 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,
|
writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields,
|
||||||
QgsWkbTypes.Polygon, layer.crs())
|
QgsWkbTypes.Polygon, layer.crs())
|
||||||
@ -81,30 +83,27 @@ class OrientedMinimumBoundingBox(GeoAlgorithm):
|
|||||||
del writer
|
del writer
|
||||||
|
|
||||||
def layerOmmb(self, layer, writer, progress):
|
def layerOmmb(self, layer, writer, progress):
|
||||||
current = 0
|
req = QgsFeatureRequest().setSubsetOfAttributes([])
|
||||||
|
features = vector.features(layer, req)
|
||||||
fit = layer.getFeatures()
|
total = 100.0 / len(features)
|
||||||
inFeat = QgsFeature()
|
|
||||||
total = 100.0 / layer.featureCount()
|
|
||||||
newgeometry = QgsGeometry()
|
newgeometry = QgsGeometry()
|
||||||
first = True
|
first = True
|
||||||
while fit.nextFeature(inFeat):
|
for current, inFeat in enumerate(features):
|
||||||
if first:
|
if first:
|
||||||
newgeometry = inFeat.geometry()
|
newgeometry = inFeat.geometry()
|
||||||
first = False
|
first = False
|
||||||
else:
|
else:
|
||||||
newgeometry = newgeometry.combine(inFeat.geometry())
|
newgeometry = newgeometry.combine(inFeat.geometry())
|
||||||
current += 1
|
|
||||||
progress.setPercentage(int(current * total))
|
progress.setPercentage(int(current * total))
|
||||||
|
|
||||||
geometry, area, perim, angle, width, height = self.OMBBox(newgeometry)
|
geometry, area, angle, width, height = newgeometry.orientedMinimumBoundingBox()
|
||||||
|
|
||||||
if geometry:
|
if geometry:
|
||||||
outFeat = QgsFeature()
|
outFeat = QgsFeature()
|
||||||
|
|
||||||
outFeat.setGeometry(geometry)
|
outFeat.setGeometry(geometry)
|
||||||
outFeat.setAttributes([area,
|
outFeat.setAttributes([area,
|
||||||
perim,
|
width * 2 + height * 2,
|
||||||
angle,
|
angle,
|
||||||
width,
|
width,
|
||||||
height])
|
height])
|
||||||
@ -115,62 +114,17 @@ class OrientedMinimumBoundingBox(GeoAlgorithm):
|
|||||||
total = 100.0 / len(features)
|
total = 100.0 / len(features)
|
||||||
outFeat = QgsFeature()
|
outFeat = QgsFeature()
|
||||||
for current, inFeat in enumerate(features):
|
for current, inFeat in enumerate(features):
|
||||||
geometry, area, perim, angle, width, height = self.OMBBox(
|
geometry, area, angle, width, height = inFeat.geometry().orientedMinimumBoundingBox()
|
||||||
inFeat.geometry())
|
|
||||||
if geometry:
|
if geometry:
|
||||||
outFeat.setGeometry(geometry)
|
outFeat.setGeometry(geometry)
|
||||||
outFeat.setAttributes([area,
|
attrs = inFeat.attributes()
|
||||||
perim,
|
attrs.extend([area,
|
||||||
angle,
|
width * 2 + height * 2,
|
||||||
width,
|
angle,
|
||||||
height])
|
width,
|
||||||
|
height])
|
||||||
|
outFeat.setAttributes(attrs)
|
||||||
writer.addFeature(outFeat)
|
writer.addFeature(outFeat)
|
||||||
else:
|
else:
|
||||||
progress.setInfo(self.tr("Can't calculate an OMBB for feature {0}.").format(inFeat.id()))
|
progress.setInfo(self.tr("Can't calculate an OMBB for feature {0}.").format(inFeat.id()))
|
||||||
progress.setPercentage(int(current * total))
|
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:
|
OUTPUT:
|
||||||
hash: fe6e018be13c5a3c17f3f4d0f0dc7686c628cb440b74c4642aa0c939
|
hash: fe6e018be13c5a3c17f3f4d0f0dc7686c628cb440b74c4642aa0c939
|
||||||
type: rasterhash
|
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()):
|
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:
|
try:
|
||||||
cmp = compare['fields'][field_expected.name()]
|
cmp = compare['fields'][field_expected.name()]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -116,6 +114,9 @@ class TestCase(_TestCase):
|
|||||||
if 'skip' in cmp:
|
if 'skip' in cmp:
|
||||||
continue
|
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
|
# Cast field to a given type
|
||||||
if 'cast' in cmp:
|
if 'cast' in cmp:
|
||||||
if cmp['cast'] == 'int':
|
if cmp['cast'] == 'int':
|
||||||
|
@ -862,6 +862,65 @@ QgsRectangle QgsGeometry::boundingBox() const
|
|||||||
return QgsRectangle();
|
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
|
bool QgsGeometry::intersects( const QgsRectangle& r ) const
|
||||||
{
|
{
|
||||||
QgsGeometry g = fromRect( r );
|
QgsGeometry g = fromRect( r );
|
||||||
|
@ -458,9 +458,21 @@ class CORE_EXPORT QgsGeometry
|
|||||||
*/
|
*/
|
||||||
QgsGeometry makeDifference( const QgsGeometry& other ) const;
|
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;
|
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)
|
//! Test for intersection with a rectangle (uses GEOS)
|
||||||
bool intersects( const QgsRectangle& r ) const;
|
bool intersects( const QgsRectangle& r ) const;
|
||||||
|
|
||||||
|
@ -3645,6 +3645,29 @@ class TestQgsGeometry(unittest.TestCase):
|
|||||||
self.assertTrue(compareWkt(result, exp, 0.00001),
|
self.assertTrue(compareWkt(result, exp, 0.00001),
|
||||||
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
|
"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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user