Skip to content
Permalink
Browse files

[FEATURE][Processing] Minimal enclosing circle

  • Loading branch information
lbartoletti authored and nyalldawson committed Sep 2, 2017
1 parent f51244c commit e30f7044c9af22096dda8722d188d452228d420c
@@ -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;

@@ -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
@@ -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.

@@ -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))
@@ -100,6 +100,7 @@
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 @@ def getAlgs(self):
MeanCoords(),
Merge(),
MergeLines(),
MinimalEnclosingCircle(),
NearestNeighbourAnalysis(),
OffsetLine(),
OrientedMinimumBoundingBox(),
@@ -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>
@@ -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>
@@ -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>

0 comments on commit e30f704

Please sign in to comment.
You can’t perform that action at this time.