[FEATURE][Processing] Minimal enclosing circle

This commit is contained in:
lbartoletti 2017-09-02 23:37:53 +02:00 committed by Nyall Dawson
parent f51244c98b
commit e30f7044c9
15 changed files with 595 additions and 3 deletions

View File

@ -112,6 +112,19 @@ class QgsCircle : QgsEllipse
:rtype: QgsCircle
%End
static QgsCircle minimalCircleFrom3Points( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon = 1E-8 );
%Docstring
Constructs the smallest circle from 3 points.
Z and m values are dropped for the center point.
The azimuth always takes the default value.
If the points are colinear an empty circle is returned.
\param pt1 First point.
\param pt2 Second point.
\param pt3 Third point.
\param epsilon Value used to compare point.
:rtype: QgsCircle
%End
virtual double area() const;
virtual double perimeter() const;
@ -159,6 +172,11 @@ Set the radius of the circle
:rtype: QgsCircularString
%End
bool contains( const QgsPoint &point, double epsilon = 1E-8 ) const;
%Docstring
Returns true if the circle contains the ``point``.
:rtype: bool
%End
virtual QgsRectangle boundingBox() const;

View File

@ -577,6 +577,16 @@ Returns true if WKB of the geometry is of WKBMulti* type
:rtype: QgsGeometry
%End
QgsGeometry minimalEnclosingCircle( QgsPointXY &center /Out/, double &radius /Out/, unsigned int segments = 36 ) const;
%Docstring
Returns the minimal enclosing circle for the geometry.
\param center Center of the minimal enclosing circle returneds
\param radius Radius of the minimal enclosing circle returned
.. seealso:: QgsEllipse.toPolygon()
.. versionadded:: 3.0
:rtype: QgsGeometry
%End
QgsGeometry orthogonalize( double tolerance = 1.0E-8, int maxIterations = 1000, double angleThreshold = 15.0 ) const;
%Docstring
Attempts to orthogonalize a line or polygon geometry by shifting vertices to make the geometries

View File

@ -354,6 +354,11 @@ qgis:orientedminimumboundingbox: >
As an alternative, the output layer can contain not just a single rectangle, but one for each input feature, representing the minimum rectangle that covers each of them.
qgis:minimalenclosingcircle: >
This algorithm takes a vector layer and generate a new one with the minimum enclosing circle that covers all the input features.
As an alternative, the output layer can contain not just a single circle, but one for each input feature, representing the minimum enclosing circle that covers each of them.
qgis:orthogonalize: >
This algorithm takes a line or polygon layer and attempts to orthogonalize all the geometries in the layer. This process shifts the nodes in the geometries to try to make every angle in the geometry either a right angle or a straight line.

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
MinimalEnclosingCircle.py
---------------------
Date : September 2017
Copyright : (C) 2017, Loïc BARTOLETTI
Email : lbartoletti at tuxfamily dot org
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Loïc BARTOLETTI'
__date__ = 'September 2017'
__copyright__ = '(C) 2017, Loïc BARTOLETTI'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField,
QgsFields,
QgsFeatureSink,
QgsGeometry,
QgsFeature,
QgsWkbTypes,
QgsFeatureRequest,
QgsProcessing,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterBoolean,
QgsProcessingParameterFeatureSink,
QgsProcessingException)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
class MinimalEnclosingCircle(QgisAlgorithm):
INPUT = 'INPUT'
BY_FEATURE = 'BY_FEATURE'
OUTPUT = 'OUTPUT'
def group(self):
return self.tr('Vector general')
def __init__(self):
super().__init__()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer'), [QgsProcessing.TypeVectorAnyGeometry]))
self.addParameter(QgsProcessingParameterBoolean(self.BY_FEATURE,
self.tr('Calculate bounds for each feature separately'), defaultValue=True))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Enclosing circles'), QgsProcessing.TypeVectorPolygon))
def name(self):
return 'minimalenclosingcircle'
def displayName(self):
return self.tr('Minimal enclosing circle')
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
by_feature = self.parameterAsBool(parameters, self.BY_FEATURE, context)
if not by_feature and QgsWkbTypes.geometryType(source.wkbType()) == QgsWkbTypes.PointGeometry and source.featureCount() <= 2:
raise QgsProcessingException(self.tr("Can't calculate a minimal enclosing circle for each point, it's a point. The number of points must be greater than 2"))
if by_feature:
fields = source.fields()
else:
fields = QgsFields()
fields.append(QgsField('center_x', QVariant.Double))
fields.append(QgsField('center_y', QVariant.Double))
fields.append(QgsField('radius', QVariant.Double))
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.Polygon, source.sourceCrs())
if by_feature:
self.featureMec(source, context, sink, feedback)
else:
self.layerMec(source, context, sink, feedback)
return {self.OUTPUT: dest_id}
def layerMec(self, source, context, sink, feedback):
req = QgsFeatureRequest().setSubsetOfAttributes([])
features = source.getFeatures(req)
total = 100.0 / source.featureCount() if source.featureCount() else 0
newgeometry = QgsGeometry()
first = True
geometries = []
for current, inFeat in enumerate(features):
if feedback.isCanceled():
break
if inFeat.hasGeometry():
geometries.append(inFeat.geometry())
feedback.setProgress(int(current * total))
newgeometry = QgsGeometry.unaryUnion(geometries)
geometry, center, radius = newgeometry.minimalEnclosingCircle()
if geometry:
outFeat = QgsFeature()
outFeat.setGeometry(geometry)
outFeat.setAttributes([center.x(),
center.y(),
radius])
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
def featureMec(self, source, context, sink, feedback):
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
outFeat = QgsFeature()
for current, inFeat in enumerate(features):
if feedback.isCanceled():
break
geometry, center, radius = inFeat.geometry().minimalEnclosingCircle()
if geometry:
outFeat.setGeometry(geometry)
attrs = inFeat.attributes()
attrs.extend([center.x(),
center.y(),
radius])
outFeat.setAttributes(attrs)
sink.addFeature(outFeat, QgsFeatureSink.FastInsert)
else:
feedback.pushInfo(self.tr("Can't calculate a minimal enclosing circle for feature {0}.").format(inFeat.id()))
feedback.setProgress(int(current * total))

View File

@ -100,6 +100,7 @@ from .LinesToPolygons import LinesToPolygons
from .MeanCoords import MeanCoords
from .Merge import Merge
from .MergeLines import MergeLines
from .MinimalEnclosingCircle import MinimalEnclosingCircle
from .NearestNeighbourAnalysis import NearestNeighbourAnalysis
from .OffsetLine import OffsetLine
from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox
@ -253,6 +254,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
MeanCoords(),
Merge(),
MergeLines(),
MinimalEnclosingCircle(),
NearestNeighbourAnalysis(),
OffsetLine(),
OrientedMinimumBoundingBox(),

View File

@ -0,0 +1,31 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>enclosing_circles_all</Name>
<ElementPath>enclosing_circles_all</ElementPath>
<!--POLYGON-->
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>1</FeatureCount>
<ExtentXMin>-1.50891</ExtentXMin>
<ExtentXMax>10.30638</ExtentXMax>
<ExtentYMin>-5.30638</ExtentYMin>
<ExtentYMax>6.50891</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>center_x</Name>
<ElementPath>center_x</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>center_y</Name>
<ElementPath>center_y</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>radius</Name>
<ElementPath>radius</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,22 @@
<?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.508909716010908</gml:X><gml:Y>-5.306378070441288</gml:Y></gml:coord>
<gml:coord><gml:X>10.30637807044129</gml:X><gml:Y>6.508909716010908</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:enclosing_circles_all fid="enclosing_circles_all.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4.39873417721519,6.50890971601091 5.42458577357907,6.4191593308691 6.41926738829339,6.15263519548031 7.35255612382824,5.71743551083061 8.19609447422128,5.12678359911643 8.92425195354681,4.39862611979089 9.51490386526099,3.55508776939786 9.95010354991069,2.62179903386301 10.2166276852995,1.62711741914869 10.3063780704413,0.601265822784808 10.2166276852995,-0.424585773579072 9.95010354991069,-1.41926738829339 9.51490386526099,-2.35255612382824 8.92425195354681,-3.19609447422128 8.19609447422127,-3.92425195354681 7.35255612382824,-4.514903865261 6.41926738829339,-4.95010354991069 5.42458577357907,-5.21662768529948 4.39873417721519,-5.30637807044129 3.37288258085131,-5.21662768529948 2.37820096613699,-4.95010354991069 1.44491223060214,-4.51490386526099 0.601373880209104,-3.92425195354681 -0.126783599116428,-3.19609447422127 -0.717435510830614,-2.35255612382824 -1.15263519548031,-1.41926738829339 -1.4191593308691,-0.42458577357907 -1.50890971601091,0.60126582278481 -1.4191593308691,1.62711741914869 -1.15263519548031,2.62179903386301 -0.717435510830614,3.55508776939786 -0.126783599116427,4.3986261197909 0.601373880209104,5.12678359911643 1.44491223060214,5.71743551083061 2.37820096613699,6.15263519548031 3.37288258085131,6.4191593308691 4.39873417721519,6.50890971601091</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:center_x>4.39873417721519</ogr:center_x>
<ogr:center_y>0.60126582278481</ogr:center_y>
<ogr:radius>5.9076438932261</ogr:radius>
</ogr:enclosing_circles_all>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,47 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>enclosing_circles_each</Name>
<ElementPath>enclosing_circles_each</ElementPath>
<!--POLYGON-->
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>6</FeatureCount>
<ExtentXMin>-1.81766</ExtentXMin>
<ExtentXMax>10.59982</ExtentXMax>
<ExtentYMin>-3.43247</ExtentYMin>
<ExtentYMax>6.32190</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>center_x</Name>
<ElementPath>center_x</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>center_y</Name>
<ElementPath>center_y</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>radius</Name>
<ElementPath>radius</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>5</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,77 @@
<?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.817664105611035</gml:X><gml:Y>-3.43247280352443</gml:Y></gml:coord>
<gml:coord><gml:X>10.59981974229994</gml:X><gml:Y>6.321903675995209</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:enclosing_circles_each fid="polys.4">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3.8,2.2 4.26819673362059,2.35151315326536 4.75559048978224,2.4194229717016 5.24737205583712,2.40166604983954 5.72859889775413,2.29878192276454 6.18464918145415,2.11389667261588 6.60166604983954,1.85262794416288 6.96697865638513,1.52291425551124 7.26948716239812,1.13477379024745 7.5,0.7 7.65151315326536,0.23180326637941 7.7194229717016,-0.255590489782239 7.70166604983954,-0.747372055837116 7.59878192276454,-1.22859889775413 7.41389667261588,-1.68464918145415 7.15262794416288,-2.10166604983954 6.82291425551124,-2.46697865638513 6.43477379024745,-2.76948716239812 6.0,-3.0 5.53180326637941,-3.15151315326536 5.04440951021776,-3.2194229717016 4.55262794416288,-3.20166604983954 4.07140110224587,-3.09878192276454 3.61535081854585,-2.91389667261588 3.19833395016046,-2.65262794416288 2.83302134361487,-2.32291425551124 2.53051283760188,-1.93477379024745 2.3,-1.5 2.14848684673464,-1.03180326637941 2.0805770282984,-0.544409510217762 2.09833395016046,-0.052627944162882 2.20121807723546,0.428598897754127 2.38610332738412,0.884649181454149 2.64737205583712,1.30166604983954 2.97708574448876,1.66697865638513 3.36522620975255,1.96948716239812 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:center_x>4.9</ogr:center_x>
<ogr:center_y>-0.4</ogr:center_y>
<ogr:radius>2.82311884269862</ogr:radius>
</ogr:enclosing_circles_each>
</gml:featureMember>
<gml:featureMember>
<ogr:enclosing_circles_each fid="polys.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.0,5.08319489631876 5.17420296559052,5.06795411167699 5.34311286222252,5.02269484128082 5.50159744815938,4.94879226515894 5.64484124945447,4.8484918756903 5.7684918756903,4.72484124945447 5.86879226515894,4.58159744815938 5.94269484128082,4.42311286222252 5.98795411167699,4.25420296559052 6.00319489631876,4.08 5.98795411167699,3.90579703440948 5.94269484128082,3.73688713777748 5.86879226515894,3.57840255184062 5.7684918756903,3.43515875054553 5.64484124945447,3.3115081243097 5.50159744815938,3.21120773484106 5.34311286222252,3.13730515871918 5.17420296559052,3.09204588832301 5.0,3.07680510368124 4.82579703440948,3.09204588832301 4.65688713777748,3.13730515871918 4.49840255184062,3.21120773484106 4.35515875054553,3.3115081243097 4.2315081243097,3.43515875054553 4.13120773484106,3.57840255184062 4.05730515871918,3.73688713777748 4.01204588832301,3.90579703440948 3.99680510368124,4.08 4.01204588832301,4.25420296559052 4.05730515871918,4.42311286222252 4.13120773484106,4.58159744815938 4.2315081243097,4.72484124945447 4.35515875054553,4.8484918756903 4.49840255184062,4.94879226515894 4.65688713777748,5.02269484128082 4.82579703440948,5.06795411167699 5.0,5.08319489631876</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:center_x>5</ogr:center_x>
<ogr:center_y>4.08</ogr:center_y>
<ogr:radius>1.00319489631876</ogr:radius>
</ogr:enclosing_circles_each>
</gml:featureMember>
<gml:featureMember>
<ogr:enclosing_circles_each fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3.0,3.0 3.31691186135828,2.62231915069056 3.56342552822315,2.19534495492048 3.73205080756888,1.73205080756888 3.81766410561104,1.24651366686488 3.81766410561104,0.753486333135122 3.73205080756888,0.267949192431123 3.56342552822315,-0.195344954920481 3.31691186135828,-0.622319150690556 3.0,-1.0 2.62231915069056,-1.31691186135828 2.19534495492048,-1.56342552822315 1.73205080756888,-1.73205080756888 1.24651366686488,-1.81766410561103 0.753486333135123,-1.81766410561103 0.267949192431122,-1.73205080756888 -0.195344954920479,-1.56342552822315 -0.622319150690555,-1.31691186135828 -1.0,-1.0 -1.31691186135828,-0.622319150690556 -1.56342552822315,-0.195344954920479 -1.73205080756888,0.267949192431122 -1.81766410561103,0.753486333135123 -1.81766410561103,1.24651366686488 -1.73205080756888,1.73205080756888 -1.56342552822316,2.19534495492048 -1.31691186135828,2.62231915069056 -1.0,3.0 -0.622319150690556,3.31691186135828 -0.195344954920481,3.56342552822315 0.267949192431124,3.73205080756888 0.753486333135123,3.81766410561104 1.24651366686488,3.81766410561104 1.73205080756888,3.73205080756888 2.19534495492048,3.56342552822316 2.62231915069056,3.31691186135828 3.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:center_x>1</ogr:center_x>
<ogr:center_y>1</ogr:center_y>
<ogr:radius>2.82842712474619</ogr:radius>
</ogr:enclosing_circles_each>
</gml:featureMember>
<gml:featureMember>
<ogr:enclosing_circles_each fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7.8734693877551,2.02022790556525 8.3468951585034,1.97880851760375 8.80593612677252,1.85580886086324 9.23664456502752,1.65496621767295 9.62593361532103,1.38238309011494 9.96197492684963,1.04634177858634 10.2345580544076,0.657052728292829 10.4354006975979,0.226344290037822 10.5584003543384,-0.232696678231291 10.5998197422999,-0.706122448979591 10.5584003543384,-1.17954821972789 10.4354006975979,-1.638589187997 10.2345580544076,-2.06929762625201 9.96197492684963,-2.45858667654552 9.62593361532103,-2.79462798807412 9.23664456502752,-3.06721111563213 8.80593612677252,-3.26805375882242 8.3468951585034,-3.39105341556293 7.8734693877551,-3.43247280352443 7.4000436170068,-3.39105341556293 6.94100264873769,-3.26805375882242 6.51029421048268,-3.06721111563213 6.12100516018918,-2.79462798807412 5.78496384866057,-2.45858667654552 5.51238072110256,-2.06929762625201 5.31153807791227,-1.63858918799701 5.18853842117176,-1.17954821972789 5.14711903321026,-0.70612244897959 5.18853842117176,-0.23269667823129 5.31153807791227,0.226344290037823 5.51238072110256,0.65705272829283 5.78496384866057,1.04634177858634 6.12100516018918,1.38238309011494 6.51029421048268,1.65496621767295 6.94100264873769,1.85580886086324 7.4000436170068,1.97880851760375 7.8734693877551,2.02022790556525</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>0</ogr:intval>
<ogr:name>ASDF</ogr:name>
<ogr:center_x>7.8734693877551</ogr:center_x>
<ogr:center_y>-0.706122448979591</ogr:center_y>
<ogr:radius>2.72635035454484</ogr:radius>
</ogr:enclosing_circles_each>
</gml:featureMember>
<gml:featureMember>
<ogr:enclosing_circles_each fid="polys.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,6 3.0935543337087,5.86933092744047 3.16299692054554,5.72440147214358 3.20621778264911,5.56961524227066 3.22190367599521,5.40967533909081 3.20957799265196,5.24944145562864 3.16961524227066,5.09378221735089 3.10322967279951,4.94742725144526 3.01243837617418,4.81482347949161 2.9,4.7 2.76933092744047,4.6064456662913 2.62440147214358,4.53700307945446 2.46961524227066,4.49378221735089 2.30967533909081,4.47809632400479 2.14944145562864,4.49042200734804 1.99378221735089,4.53038475772934 1.84742725144526,4.59677032720049 1.71482347949161,4.68756162382582 1.6,4.8 1.5064456662913,4.93066907255953 1.43700307945446,5.07559852785642 1.39378221735089,5.23038475772934 1.37809632400479,5.39032466090919 1.39042200734804,5.55055854437136 1.43038475772934,5.70621778264911 1.49677032720049,5.85257274855473 1.58756162382582,5.98517652050839 1.7,6.1 1.83066907255953,6.1935543337087 1.97559852785642,6.26299692054554 2.13038475772934,6.30621778264911 2.29032466090919,6.32190367599521 2.45055854437136,6.30957799265196 2.60621778264911,6.26961524227066 2.75257274855473,6.20322967279951 2.88517652050839,6.11243837617418 3,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:floatval>0.123</ogr:floatval>
<ogr:name>bbaaa</ogr:name>
<ogr:center_x>2.3</ogr:center_x>
<ogr:center_y>5.4</ogr:center_y>
<ogr:radius>0.921954445729289</ogr:radius>
</ogr:enclosing_circles_each>
</gml:featureMember>
<gml:featureMember>
<ogr:enclosing_circles_each fid="polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3.8,2.2 4.26819673362059,2.35151315326536 4.75559048978224,2.4194229717016 5.24737205583712,2.40166604983954 5.72859889775413,2.29878192276454 6.18464918145415,2.11389667261588 6.60166604983954,1.85262794416288 6.96697865638513,1.52291425551124 7.26948716239812,1.13477379024745 7.5,0.7 7.65151315326536,0.23180326637941 7.7194229717016,-0.255590489782239 7.70166604983954,-0.747372055837116 7.59878192276454,-1.22859889775413 7.41389667261588,-1.68464918145415 7.15262794416288,-2.10166604983954 6.82291425551124,-2.46697865638513 6.43477379024745,-2.76948716239812 6.0,-3.0 5.53180326637941,-3.15151315326536 5.04440951021776,-3.2194229717016 4.55262794416288,-3.20166604983954 4.07140110224587,-3.09878192276454 3.61535081854585,-2.91389667261588 3.19833395016046,-2.65262794416288 2.83302134361487,-2.32291425551124 2.53051283760188,-1.93477379024745 2.3,-1.5 2.14848684673464,-1.03180326637941 2.0805770282984,-0.544409510217762 2.09833395016046,-0.052627944162882 2.20121807723546,0.428598897754127 2.38610332738412,0.884649181454149 2.64737205583712,1.30166604983954 2.97708574448876,1.66697865638513 3.36522620975255,1.96948716239812 3.8,2.2</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:center_x>4.9</ogr:center_x>
<ogr:center_y>-0.4</ogr:center_y>
<ogr:radius>2.82311884269862</ogr:radius>
</ogr:enclosing_circles_each>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -3239,3 +3239,33 @@ tests:
OUTPUT:
name: expected/raster_extent.gml
type: vector
- algorithm: qgis:minimalenclosingcircle
name: Minimal enclosing circle each features
params:
BY_FEATURE: true
INPUT:
name: custom/oriented_bbox.gml
type: vector
results:
OUTPUT:
name: expected/enclosing_circles_each.gml
type: vector
compare:
geometry:
precision: 7
- algorithm: qgis:minimalenclosingcircle
name: Minimal enclosing circle all features
params:
BY_FEATURE: false
INPUT:
name: custom/oriented_bbox.gml
type: vector
results:
OUTPUT:
name: expected/enclosing_circles_all.gml
type: vector
compare:
geometry:
precision: 7

View File

@ -38,7 +38,7 @@ QgsCircle QgsCircle::from2Points( const QgsPoint &pt1, const QgsPoint &pt2 )
{
QgsPoint center = QgsGeometryUtils::midpoint( pt1, pt2 );
double azimuth = QgsGeometryUtils::lineAngle( pt1.x(), pt1.y(), pt2.x(), pt2.y() ) * 180.0 / M_PI;
double radius = pt1.distance( pt2 );
double radius = pt1.distance( pt2 ) / 2.0;
return QgsCircle( center, radius, azimuth );
}
@ -189,6 +189,22 @@ QgsCircle QgsCircle::from3Tangents( const QgsPoint &pt1_tg1, const QgsPoint &pt2
return QgsTriangle( p1, p2, p3 ).inscribedCircle();
}
QgsCircle QgsCircle::minimalCircleFrom3Points( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon )
{
double l1 = pt2.distance( pt3 );
double l2 = pt3.distance( pt1 );
double l3 = pt1.distance( pt2 );
if ( ( l1 * l1 ) - ( l2 * l2 + l3 * l3 ) >= epsilon )
return QgsCircle().from2Points( pt2, pt3 );
else if ( ( l2 * l2 ) - ( l1 * l1 + l3 * l3 ) >= epsilon )
return QgsCircle().from2Points( pt3, pt1 );
else if ( ( l3 * l3 ) - ( l1 * l1 + l2 * l2 ) >= epsilon )
return QgsCircle().from2Points( pt1, pt2 );
else
return QgsCircle().from3Points( pt1, pt2, pt3, epsilon );
}
QgsCircle QgsCircle::fromExtent( const QgsPoint &pt1, const QgsPoint &pt2 )
{
double delta_x = std::fabs( pt1.x() - pt2.x() );
@ -245,6 +261,11 @@ QgsCircularString *QgsCircle::toCircularString( bool oriented ) const
return circString.release();
}
bool QgsCircle::contains( const QgsPoint &point, double epsilon ) const
{
return ( mCenter.distance( point ) <= mSemiMajorAxis + epsilon );
}
QgsRectangle QgsCircle::boundingBox() const
{
return QgsRectangle( mCenter.x() - mSemiMajorAxis, mCenter.y() - mSemiMajorAxis, mCenter.x() + mSemiMajorAxis, mCenter.y() + mSemiMajorAxis );

View File

@ -119,6 +119,18 @@ class CORE_EXPORT QgsCircle : public QgsEllipse
*/
static QgsCircle fromExtent( const QgsPoint &pt1, const QgsPoint &pt2 );
/**
* Constructs the smallest circle from 3 points.
* Z and m values are dropped for the center point.
* The azimuth always takes the default value.
* If the points are colinear an empty circle is returned.
* \param pt1 First point.
* \param pt2 Second point.
* \param pt3 Third point.
* \param epsilon Value used to compare point.
*/
static QgsCircle minimalCircleFrom3Points( const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double epsilon = 1E-8 );
double area() const override;
double perimeter() const override;
@ -168,6 +180,8 @@ class CORE_EXPORT QgsCircle : public QgsEllipse
*/
QgsCircularString *toCircularString( bool oriented = false ) const;
//! Returns true if the circle contains the \a point.
bool contains( const QgsPoint &point, double epsilon = 1E-8 ) const;
QgsRectangle boundingBox() const override;

View File

@ -44,6 +44,7 @@ email : morb at ozemail dot com dot au
#include "qgspoint.h"
#include "qgspolygon.h"
#include "qgslinestring.h"
#include "qgscircle.h"
struct QgsGeometryPrivate
{
@ -1008,6 +1009,81 @@ QgsGeometry QgsGeometry::orientedMinimumBoundingBox( double &area, double &angle
return minBounds;
}
static QgsCircle __recMinimalEnclosingCircle( QgsMultiPoint points, QgsMultiPoint boundary )
{
auto l_boundary = boundary.length();
QgsCircle circ_mec;
if ( ( points.length() == 0 ) || ( l_boundary == 3 ) )
{
switch ( l_boundary )
{
case 0:
circ_mec = QgsCircle();
break;
case 1:
circ_mec = QgsCircle( QgsPoint( boundary.last() ), 0 );
boundary.pop_back();
break;
case 2:
{
QgsPointXY p1 = boundary.last();
boundary.pop_back();
QgsPointXY p2 = boundary.last();
boundary.pop_back();
circ_mec = QgsCircle().from2Points( QgsPoint( p1 ), QgsPoint( p2 ) );
}
break;
default:
QgsPoint p1( boundary.at( 0 ) );
QgsPoint p2( boundary.at( 1 ) );
QgsPoint p3( boundary.at( 2 ) );
circ_mec = QgsCircle().minimalCircleFrom3Points( p1, p2, p3 );
break;
}
return circ_mec;
}
else
{
QgsPointXY pxy = points.last();
points.pop_back();
circ_mec = __recMinimalEnclosingCircle( points, boundary );
QgsPoint p( pxy );
if ( !circ_mec.contains( p ) )
{
boundary.append( pxy );
circ_mec = __recMinimalEnclosingCircle( points, boundary );
}
}
return circ_mec;
}
QgsGeometry QgsGeometry::minimalEnclosingCircle( QgsPointXY &center, double &radius, unsigned int segments ) const
{
center = QgsPointXY( );
radius = 0;
if ( !d->geometry )
{
return QgsGeometry();
}
/* optimization */
QgsGeometry hull = convexHull();
if ( hull.isNull() )
return QgsGeometry();
QgsMultiPoint P = hull.convertToPoint( true ).asMultiPoint();
QgsMultiPoint R;
QgsCircle circ = __recMinimalEnclosingCircle( P, R );
center = QgsPointXY( circ.center() );
radius = circ.radius();
QgsGeometry geom;
geom.setGeometry( circ.toPolygon( segments ) );
return geom;
}
QgsGeometry QgsGeometry::orthogonalize( double tolerance, int maxIterations, double angleThreshold ) const
{
QgsInternalGeometryEngine engine( *this );

View File

@ -599,6 +599,15 @@ class CORE_EXPORT QgsGeometry
*/
QgsGeometry orientedMinimumBoundingBox( double &area SIP_OUT, double &angle SIP_OUT, double &width SIP_OUT, double &height SIP_OUT ) const;
/**
* Returns the minimal enclosing circle for the geometry.
* \param center Center of the minimal enclosing circle returneds
* \param radius Radius of the minimal enclosing circle returned
* \param segments Number of segments used to segment geometry. \see QgsEllipse::toPolygon()
* \since QGIS 3.0
*/
QgsGeometry minimalEnclosingCircle( QgsPointXY &center SIP_OUT, double &radius SIP_OUT, unsigned int segments = 36 ) const;
/**
* Attempts to orthogonalize a line or polygon geometry by shifting vertices to make the geometries
* angles either right angles or flat lines. This is an iterative algorithm which will loop until

View File

@ -128,6 +128,8 @@ class TestQgsGeometry : public QObject
void reshapeGeometryLineMerge();
void createCollectionOfType();
void minimalEnclosingCircle( );
private:
//! A helper method to do a render check to see if the geometry op is as expected
bool renderCheck( const QString &testName, const QString &comment = QLatin1String( QLatin1String( "" ) ), int mismatchCount = 0 );
@ -4161,8 +4163,8 @@ void TestQgsGeometry::circle()
//test "alt" constructors
// by2Points
QVERIFY( QgsCircle().from2Points( QgsPoint( -5, 0 ), QgsPoint( 5, 0 ) ) == QgsCircle( QgsPoint( 0, 0 ), 10, 90 ) );
QVERIFY( QgsCircle().from2Points( QgsPoint( 0, -5 ), QgsPoint( 0, 5 ) ) == QgsCircle( QgsPoint( 0, 0 ), 10, 0 ) );
QVERIFY( QgsCircle().from2Points( QgsPoint( -5, 0 ), QgsPoint( 5, 0 ) ) == QgsCircle( QgsPoint( 0, 0 ), 5, 90 ) );
QVERIFY( QgsCircle().from2Points( QgsPoint( 0, -5 ), QgsPoint( 0, 5 ) ) == QgsCircle( QgsPoint( 0, 0 ), 5, 0 ) );
// byExtent
QVERIFY( QgsCircle().fromExtent( QgsPoint( -5, -5 ), QgsPoint( 5, 5 ) ) == QgsCircle( QgsPoint( 0, 0 ), 5, 0 ) );
QVERIFY( QgsCircle().fromExtent( QgsPoint( -7.5, -2.5 ), QgsPoint( 2.5, 200.5 ) ) == QgsCircle() );
@ -4182,6 +4184,15 @@ void TestQgsGeometry::circle()
QgsCircle circ_tgt = QgsCircle().from3Tangents( QgsPoint( 0, 0 ), QgsPoint( 0, 1 ), QgsPoint( 2, 0 ), QgsPoint( 3, 0 ), QgsPoint( 5, 0 ), QgsPoint( 0, 5 ) );
QGSCOMPARENEARPOINT( circ_tgt.center(), QgsPoint( 1.4645, 1.4645 ), 0.0001 );
QGSCOMPARENEAR( circ_tgt.radius(), 1.4645, 0.0001 );
// minimalCircleFrom3points
QgsCircle minCircle3Points = QgsCircle().minimalCircleFrom3Points( QgsPoint( 0, 5 ), QgsPoint( 0, -5 ), QgsPoint( 1, 2 ) );
QGSCOMPARENEARPOINT( minCircle3Points.center(), QgsPoint( 0, 0 ), 0.0001 );
QGSCOMPARENEAR( minCircle3Points.radius(), 5.0, 0.0001 );
minCircle3Points = QgsCircle().minimalCircleFrom3Points( QgsPoint( 0, 5 ), QgsPoint( 5, 0 ), QgsPoint( -5, 0 ) );
QGSCOMPARENEARPOINT( minCircle3Points.center(), QgsPoint( 0, 0 ), 0.0001 );
QGSCOMPARENEAR( minCircle3Points.radius(), 5.0, 0.0001 );
// test quadrant
QVector<QgsPoint> quad = QgsCircle( QgsPoint( 0, 0 ), 5 ).northQuadrant();
@ -4286,6 +4297,15 @@ void TestQgsGeometry::circle()
QGSCOMPARENEAR( 314.1593, QgsCircle( QgsPoint( 0, 0 ), 10 ).area(), 0.0001 );
// perimeter
QGSCOMPARENEAR( 31.4159, QgsCircle( QgsPoint( 0, 0 ), 5 ).perimeter(), 0.0001 );
// contains
QgsPoint pc;
pc = QgsPoint( 1, 1 );
QVERIFY( QgsCircle( QgsPoint( 0, 0 ), 5 ).contains( pc ) );
pc = QgsPoint( 0, 5 );
QVERIFY( QgsCircle( QgsPoint( 0, 0 ), 5 ).contains( pc ) );
pc = QgsPoint( 6, 1 );
QVERIFY( !QgsCircle( QgsPoint( 0, 0 ), 5 ).contains( pc ) );
}
void TestQgsGeometry::regularPolygon()
@ -5577,5 +5597,73 @@ void TestQgsGeometry::createCollectionOfType()
QVERIFY( dynamic_cast< QgsMultiSurface *>( collect.get() ) );
}
void TestQgsGeometry::minimalEnclosingCircle()
{
QgsGeometry geomTest;
QgsGeometry result, resultTest;
QgsPointXY center;
double radius;
// empty
result = geomTest.minimalEnclosingCircle( center, radius );
QCOMPARE( center, QgsPointXY() );
QCOMPARE( radius, 0.0 );
QCOMPARE( result, QgsGeometry() );
// caase 1
geomTest = QgsGeometry::fromPoint( QgsPointXY( 5, 5 ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QCOMPARE( center, QgsPointXY( 5, 5 ) );
QCOMPARE( radius, 0.0 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
// case 2
geomTest = QgsGeometry::fromWkt( QString( "MULTIPOINT( 3 8, 7 4 )" ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QGSCOMPARENEARPOINT( center, QgsPointXY( 5, 6 ), 0.0001 );
QGSCOMPARENEAR( radius, sqrt( 2 ) * 2, 0.0001 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
geomTest = QgsGeometry::fromWkt( QString( "LINESTRING( 0 5, 2 2, 0 -5, -1 -1 )" ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QGSCOMPARENEARPOINT( center, QgsPointXY( 0, 0 ), 0.0001 );
QGSCOMPARENEAR( radius, 5, 0.0001 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
geomTest = QgsGeometry::fromWkt( QString( "MULTIPOINT( 0 5, 2 2, 0 -5, -1 -1 )" ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QGSCOMPARENEARPOINT( center, QgsPointXY( 0, 0 ), 0.0001 );
QGSCOMPARENEAR( radius, 5, 0.0001 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
geomTest = QgsGeometry::fromWkt( QString( "POLYGON(( 0 5, 2 2, 0 -5, -1 -1 ))" ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QGSCOMPARENEARPOINT( center, QgsPointXY( 0, 0 ), 0.0001 );
QGSCOMPARENEAR( radius, 5, 0.0001 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
geomTest = QgsGeometry::fromWkt( QString( "MULTIPOINT( 0 5, 0 -5, 0 0 )" ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QGSCOMPARENEARPOINT( center, QgsPointXY( 0, 0 ), 0.0001 );
QGSCOMPARENEAR( radius, 5, 0.0001 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
// case 3
geomTest = QgsGeometry::fromWkt( QString( "MULTIPOINT((0 0), (5 5), (0 -5), (0 5), (-5 0))" ) );
result = geomTest.minimalEnclosingCircle( center, radius );
QGSCOMPARENEARPOINT( center, QgsPointXY( 0.8333, 0.8333 ), 0.0001 );
QGSCOMPARENEAR( radius, 5.8926, 0.0001 );
resultTest.setGeometry( QgsCircle( QgsPoint( center ), radius ).toPolygon( 36 ) );
QCOMPARE( result, resultTest );
}
QGSTEST_MAIN( TestQgsGeometry )
#include "testqgsgeometry.moc"