mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-31 00:06:02 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			552 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			552 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| """
 | |
| /***************************************************************************
 | |
|     KNearestConcaveHull.py
 | |
|     ----------------------
 | |
|     Date                 : November 2014
 | |
|     Copyright            : (C) 2014 by Detlev Neumann
 | |
|                            Dr. Neumann Consulting - Geospatial Services
 | |
|     Email                : dneumann@geospatial-services.de
 | |
|  ***************************************************************************/
 | |
| 
 | |
| /***************************************************************************
 | |
|  *                                                                         *
 | |
|  *   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__ = 'Detlev Neumann'
 | |
| __date__ = 'November 2014'
 | |
| __copyright__ = '(C) 2014, Detlev Neumann'
 | |
| 
 | |
| # This will get replaced with a git SHA1 when you do a git archive
 | |
| 
 | |
| __revision__ = '$Format:%H$'
 | |
| 
 | |
| import os.path
 | |
| import math
 | |
| 
 | |
| from qgis.PyQt.QtGui import QIcon
 | |
| from qgis.PyQt.QtCore import QVariant
 | |
| 
 | |
| from qgis.core import (QgsApplication,
 | |
|                        QgsExpression,
 | |
|                        QgsFeature,
 | |
|                        QgsFeatureRequest,
 | |
|                        QgsFeatureSink,
 | |
|                        QgsField,
 | |
|                        QgsFields,
 | |
|                        QgsGeometry,
 | |
|                        QgsProcessing,
 | |
|                        QgsProcessingException,
 | |
|                        QgsProcessingParameterFeatureSink,
 | |
|                        QgsProcessingParameterFeatureSource,
 | |
|                        QgsProcessingParameterField,
 | |
|                        QgsProcessingParameterNumber,
 | |
|                        QgsPoint,
 | |
|                        QgsPointXY,
 | |
|                        QgsWkbTypes)
 | |
| from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
 | |
| 
 | |
| 
 | |
| class KNearestConcaveHull(QgisAlgorithm):
 | |
|     KNEIGHBORS = 'KNEIGHBORS'
 | |
|     INPUT = 'INPUT'
 | |
|     OUTPUT = 'OUTPUT'
 | |
|     FIELD = 'FIELD'
 | |
| 
 | |
|     def name(self):
 | |
|         return 'knearestconcavehull'
 | |
| 
 | |
|     def displayName(self):
 | |
|         return self.tr('Concave hull (k-nearest neighbor)')
 | |
| 
 | |
|     def shortDescription(self):
 | |
|         return self.tr('Creates a concave hull using the k-nearest neighbor algorithm.')
 | |
| 
 | |
|     def icon(self):
 | |
|         return QgsApplication.getThemeIcon("/algorithms/mAlgorithmConcaveHull.svg")
 | |
| 
 | |
|     def svgIconPath(self):
 | |
|         return QgsApplication.iconPath("/algorithms/mAlgorithmConcaveHull.svg")
 | |
| 
 | |
|     def group(self):
 | |
|         return self.tr('Vector geometry')
 | |
| 
 | |
|     def groupId(self):
 | |
|         return 'vectorgeometry'
 | |
| 
 | |
|     def __init__(self):
 | |
|         super().__init__()
 | |
| 
 | |
|     def initAlgorithm(self, config=None):
 | |
|         self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
 | |
|                                                               self.tr('Input layer')))
 | |
|         self.addParameter(QgsProcessingParameterNumber(self.KNEIGHBORS,
 | |
|                                                        self.tr('Number of neighboring points to consider (a lower number is more concave, a higher number is smoother)'),
 | |
|                                                        QgsProcessingParameterNumber.Integer,
 | |
|                                                        defaultValue=3, minValue=3))
 | |
|         self.addParameter(QgsProcessingParameterField(self.FIELD,
 | |
|                                                       self.tr('Field (set if creating concave hulls by class)'),
 | |
|                                                       parentLayerParameterName=self.INPUT, optional=True))
 | |
|         self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Concave hull'),
 | |
|                                                             QgsProcessing.TypeVectorPolygon))
 | |
| 
 | |
|     def processAlgorithm(self, parameters, context, feedback):
 | |
|         # Get variables from dialog
 | |
|         source = self.parameterAsSource(parameters, self.INPUT, context)
 | |
|         if source is None:
 | |
|             raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
 | |
| 
 | |
|         field_name = self.parameterAsString(parameters, self.FIELD, context)
 | |
|         kneighbors = self.parameterAsInt(parameters, self.KNEIGHBORS, context)
 | |
|         use_field = bool(field_name)
 | |
| 
 | |
|         field_index = -1
 | |
| 
 | |
|         fields = QgsFields()
 | |
|         fields.append(QgsField('id', QVariant.Int, '', 20))
 | |
| 
 | |
|         current = 0
 | |
| 
 | |
|         # Get properties of the field the grouping is based on
 | |
|         if use_field:
 | |
|             field_index = source.fields().lookupField(field_name)
 | |
|             if field_index >= 0:
 | |
|                 fields.append(source.fields()[field_index]) # Add a field with the name of the grouping field
 | |
| 
 | |
|                 # Initialize writer
 | |
|                 (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
 | |
|                                                        fields, QgsWkbTypes.Polygon, source.sourceCrs())
 | |
|                 if sink is None:
 | |
|                     raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
 | |
| 
 | |
|                 success = False
 | |
|                 fid = 0
 | |
| 
 | |
|                 # Get unique values of grouping field
 | |
|                 unique_values = source.uniqueValues(field_index)
 | |
|                 total = 100.0 / float(source.featureCount() * len(unique_values))
 | |
| 
 | |
|                 for unique in unique_values:
 | |
|                     points = []
 | |
|                     filter = QgsExpression.createFieldEqualityExpression(field_name, unique)
 | |
|                     request = QgsFeatureRequest().setFilterExpression(filter)
 | |
|                     request.setSubsetOfAttributes([])
 | |
|                     # Get features with the grouping attribute equal to the current grouping value
 | |
|                     features = source.getFeatures(request)
 | |
|                     for in_feature in features:
 | |
|                         if feedback.isCanceled():
 | |
|                             break
 | |
|                         # Add points or vertices of more complex geometry
 | |
|                         points.extend(extract_points(in_feature.geometry()))
 | |
|                         current += 1
 | |
|                         feedback.setProgress(int(current * total))
 | |
|                     # A minimum of 3 points is necessary to proceed
 | |
|                     if len(points) >= 3:
 | |
|                         out_feature = QgsFeature()
 | |
|                         the_hull = concave_hull(points, kneighbors)
 | |
|                         if the_hull:
 | |
|                             vertex = [QgsPointXY(point[0], point[1]) for point in the_hull]
 | |
|                             poly = QgsGeometry().fromPolygonXY([vertex])
 | |
| 
 | |
|                             out_feature.setGeometry(poly)
 | |
|                             # Give the polygon the same attribute as the point grouping attribute
 | |
|                             out_feature.setAttributes([fid, unique])
 | |
|                             sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
 | |
|                             success = True  # at least one polygon created
 | |
|                     fid += 1
 | |
|                 if not success:
 | |
|                     raise QgsProcessingException('No hulls could be created. Most likely there were not at least three unique points in any of the groups.')
 | |
|             else:
 | |
|                 # Field parameter provided but can't read from it
 | |
|                 raise QgsProcessingException('Unable to find grouping field')
 | |
| 
 | |
|         else:
 | |
|             # Not grouped by field
 | |
|             # Initialize writer
 | |
|             (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
 | |
|                                                    fields, QgsWkbTypes.Polygon, source.sourceCrs())
 | |
|             if sink is None:
 | |
|                 raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
 | |
| 
 | |
|             points = []
 | |
|             request = QgsFeatureRequest()
 | |
|             request.setSubsetOfAttributes([])
 | |
|             features = source.getFeatures(request) # Get all features
 | |
|             total = 100.0 / source.featureCount() if source.featureCount() else 0
 | |
|             for in_feature in features:
 | |
|                 if feedback.isCanceled():
 | |
|                     break
 | |
|                 # Add points or vertices of more complex geometry
 | |
|                 points.extend(extract_points(in_feature.geometry()))
 | |
|                 current += 1
 | |
|                 feedback.setProgress(int(current * total))
 | |
| 
 | |
|             # A minimum of 3 points is necessary to proceed
 | |
|             if len(points) >= 3:
 | |
|                 out_feature = QgsFeature()
 | |
|                 the_hull = concave_hull(points, kneighbors)
 | |
|                 if the_hull:
 | |
|                     vertex = [QgsPointXY(point[0], point[1]) for point in the_hull]
 | |
|                     poly = QgsGeometry().fromPolygonXY([vertex])
 | |
| 
 | |
|                     out_feature.setGeometry(poly)
 | |
|                     out_feature.setAttributes([0])
 | |
|                     sink.addFeature(out_feature, QgsFeatureSink.FastInsert)
 | |
|                 else:
 | |
|                     # the_hull returns None only when there are less than three points after cleaning
 | |
|                     raise QgsProcessingException('At least three unique points are required to create a concave hull.')
 | |
|             else:
 | |
|                 raise QgsProcessingException('At least three points are required to create a concave hull.')
 | |
| 
 | |
|         return {self.OUTPUT: dest_id}
 | |
| 
 | |
| 
 | |
| def clean_list(list_of_points):
 | |
|     """
 | |
|     Deletes duplicate points in list_of_points
 | |
|     """
 | |
|     return list(set(list_of_points))
 | |
| 
 | |
| 
 | |
| def find_min_y_point(list_of_points):
 | |
|     """
 | |
|     Returns that point of *list_of_points* having minimal y-coordinate
 | |
| 
 | |
|     :param list_of_points: list of tuples
 | |
|     :return: tuple (x, y)
 | |
|     """
 | |
|     min_y_pt = list_of_points[0]
 | |
|     for point in list_of_points[1:]:
 | |
|         if point[1] < min_y_pt[1] or (point[1] == min_y_pt[1] and point[0] < min_y_pt[0]):
 | |
|             min_y_pt = point
 | |
|     return min_y_pt
 | |
| 
 | |
| 
 | |
| def add_point(vector, element):
 | |
|     """
 | |
|     Returns vector with the given element append to the right
 | |
|     """
 | |
|     vector.append(element)
 | |
|     return vector
 | |
| 
 | |
| 
 | |
| def remove_point(vector, element):
 | |
|     """
 | |
|     Returns a copy of vector without the given element
 | |
|     """
 | |
|     vector.pop(vector.index(element))
 | |
|     return vector
 | |
| 
 | |
| 
 | |
| def euclidian_distance(point1, point2):
 | |
|     """
 | |
|     Returns the euclidian distance of the 2 given points.
 | |
| 
 | |
|     :param point1: tuple (x, y)
 | |
|     :param point2: tuple (x, y)
 | |
|     :return: float
 | |
|     """
 | |
|     return math.sqrt(math.pow(point1[0] - point2[0], 2) + math.pow(point1[1] - point2[1], 2))
 | |
| 
 | |
| 
 | |
| def nearest_points(list_of_points, point, k):
 | |
|     """
 | |
|     Returns a list of the indices of the k closest neighbors from list_of_points to the specified point. The measure
 | |
|     of proximity is the Euclidean distance. Internally, k becomes the minimum between the given value for k and the
 | |
|     number of points in list_of_points
 | |
| 
 | |
|     :param list_of_points: list of tuples
 | |
|     :param point: tuple (x, y)
 | |
|     :param k: integer
 | |
|     :return: list of k tuples
 | |
|     """
 | |
|     # build a list of tuples of distances between point *point* and every point in *list_of_points*, and
 | |
|     # their respective index of list *list_of_distances*
 | |
|     list_of_distances = []
 | |
|     for index in range(len(list_of_points)):
 | |
|         list_of_distances.append((euclidian_distance(list_of_points[index], point), index))
 | |
| 
 | |
|     # sort distances in ascending order
 | |
|     list_of_distances.sort()
 | |
| 
 | |
|     # get the k nearest neighbors of point
 | |
|     nearest_list = []
 | |
|     for index in range(min(k, len(list_of_points))):
 | |
|         nearest_list.append((list_of_points[list_of_distances[index][1]]))
 | |
|     return nearest_list
 | |
| 
 | |
| 
 | |
| def angle(from_point, to_point):
 | |
|     """
 | |
|     Returns the angle of the directed line segment, going from *from_point* to *to_point*, in radians. The angle is
 | |
|     positive for segments with upward direction (north), otherwise negative (south). Values ranges from 0 at the
 | |
|     right (east) to pi at the left side (west).
 | |
| 
 | |
|     :param from_point: tuple (x, y)
 | |
|     :param to_point: tuple (x, y)
 | |
|     :return: float
 | |
|     """
 | |
|     return math.atan2(to_point[1] - from_point[1], to_point[0] - from_point[0])
 | |
| 
 | |
| 
 | |
| def angle_difference(angle1, angle2):
 | |
|     """
 | |
|     Calculates the difference between the given angles in clockwise direction as radians.
 | |
| 
 | |
|     :param angle1: float
 | |
|     :param angle2: float
 | |
|     :return: float; between 0 and 2*Pi
 | |
|     """
 | |
|     if (angle1 > 0 and angle2 >= 0) and angle1 > angle2:
 | |
|         return abs(angle1 - angle2)
 | |
|     elif (angle1 >= 0 and angle2 > 0) and angle1 < angle2:
 | |
|         return 2 * math.pi + angle1 - angle2
 | |
|     elif (angle1 < 0 and angle2 <= 0) and angle1 < angle2:
 | |
|         return 2 * math.pi + angle1 + abs(angle2)
 | |
|     elif (angle1 <= 0 and angle2 < 0) and angle1 > angle2:
 | |
|         return abs(angle1 - angle2)
 | |
|     elif angle1 <= 0 < angle2:
 | |
|         return 2 * math.pi + angle1 - angle2
 | |
|     elif angle1 >= 0 >= angle2:
 | |
|         return angle1 + abs(angle2)
 | |
|     else:
 | |
|         return 0
 | |
| 
 | |
| 
 | |
| def intersect(line1, line2):
 | |
|     """
 | |
|     Returns True if the two given line segments intersect each other, and False otherwise.
 | |
| 
 | |
|     :param line1: 2-tuple of tuple (x, y)
 | |
|     :param line2: 2-tuple of tuple (x, y)
 | |
|     :return: boolean
 | |
|     """
 | |
|     a1 = line1[1][1] - line1[0][1]
 | |
|     b1 = line1[0][0] - line1[1][0]
 | |
|     c1 = a1 * line1[0][0] + b1 * line1[0][1]
 | |
|     a2 = line2[1][1] - line2[0][1]
 | |
|     b2 = line2[0][0] - line2[1][0]
 | |
|     c2 = a2 * line2[0][0] + b2 * line2[0][1]
 | |
|     tmp = (a1 * b2 - a2 * b1)
 | |
|     if tmp == 0:
 | |
|         return False
 | |
|     sx = (c1 * b2 - c2 * b1) / tmp
 | |
|     if (sx > line1[0][0] and sx > line1[1][0]) or (sx > line2[0][0] and sx > line2[1][0]) or\
 | |
|             (sx < line1[0][0] and sx < line1[1][0]) or (sx < line2[0][0] and sx < line2[1][0]):
 | |
|         return False
 | |
|     sy = (a1 * c2 - a2 * c1) / tmp
 | |
|     if (sy > line1[0][1] and sy > line1[1][1]) or (sy > line2[0][1] and sy > line2[1][1]) or\
 | |
|             (sy < line1[0][1] and sy < line1[1][1]) or (sy < line2[0][1] and sy < line2[1][1]):
 | |
|         return False
 | |
|     return True
 | |
| 
 | |
| 
 | |
| def point_in_polygon_q(point, list_of_points):
 | |
|     """
 | |
|     Return True if given point *point* is laying in the polygon described by the vertices *list_of_points*,
 | |
|     otherwise False
 | |
| 
 | |
|     Based on the "Ray Casting Method" described by Joel Lawhead in this blog article:
 | |
|     http://geospatialpython.com/2011/01/point-in-polygon.html
 | |
| 
 | |
|     """
 | |
|     x = point[0]
 | |
|     y = point[1]
 | |
|     poly = [(pt[0], pt[1]) for pt in list_of_points]
 | |
|     n = len(poly)
 | |
|     inside = False
 | |
| 
 | |
|     p1x, p1y = poly[0]
 | |
|     for i in range(n + 1):
 | |
|         p2x, p2y = poly[i % n]
 | |
|         if y > min(p1y, p2y):
 | |
|             if y <= max(p1y, p2y):
 | |
|                 if x <= max(p1x, p2x):
 | |
|                     if p1y != p2y:
 | |
|                         xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
 | |
|                     if p1x == p2x or x <= xints:
 | |
|                         inside = not inside
 | |
|         p1x, p1y = p2x, p2y
 | |
| 
 | |
|     return inside
 | |
| 
 | |
| 
 | |
| def extract_points(geom):
 | |
|     """
 | |
|     Generate list of QgsPoints from QgsGeometry *geom* ( can be point, line, or polygon )
 | |
|     Code taken from fTools plugin
 | |
| 
 | |
|     :param geom: an arbitrary geometry feature
 | |
|     :return: list of points
 | |
|     """
 | |
|     multi_geom = QgsGeometry()
 | |
|     temp_geom = []
 | |
|     # point geometry
 | |
|     if geom.type() == 0:
 | |
|         if geom.isMultipart():
 | |
|             temp_geom = geom.asMultiPoint()
 | |
|         else:
 | |
|             temp_geom.append(geom.asPoint())
 | |
|     # line geometry
 | |
|     if geom.type() == 1:
 | |
|         # if multipart feature explode to single part
 | |
|         if geom.isMultipart():
 | |
|             multi_geom = geom.asMultiPolyline()
 | |
|             for i in multi_geom:
 | |
|                 temp_geom.extend(i)
 | |
|         else:
 | |
|             temp_geom = geom.asPolyline()
 | |
|     # polygon geometry
 | |
|     elif geom.type() == 2:
 | |
|         # if multipart feature explode to single part
 | |
|         if geom.isMultipart():
 | |
|             multi_geom = geom.asMultiPolygon()
 | |
|             # now single part polygons
 | |
|             for i in multi_geom:
 | |
|                 # explode to line segments
 | |
|                 for j in i:
 | |
|                     temp_geom.extend(j)
 | |
|         else:
 | |
|             multi_geom = geom.asPolygon()
 | |
|             # explode to line segments
 | |
|             for i in multi_geom:
 | |
|                 temp_geom.extend(i)
 | |
|     return temp_geom
 | |
| 
 | |
| 
 | |
| def sort_by_angle(list_of_points, last_point, last_angle):
 | |
|     """
 | |
|     returns the points in list_of_points in descending order of angle to the last segment of the envelope, measured
 | |
|     in a clockwise direction. Thus, the rightmost of the neighboring points is always selected. The first point of
 | |
|     this list will be the next point of the envelope.
 | |
|     """
 | |
|     def getkey(item):
 | |
|         return angle_difference(last_angle, angle(last_point, item))
 | |
| 
 | |
|     vertex_list = sorted(list_of_points, key=getkey, reverse=True)
 | |
|     return vertex_list
 | |
| 
 | |
| 
 | |
| def concave_hull(points_list, k):
 | |
|     """
 | |
|     Calculates a valid concave hull polygon containing all given points. The algorithm searches for that
 | |
|     point in the neighborhood of k nearest neighbors which maximizes the rotation angle in clockwise direction
 | |
|     without intersecting any previous line segments.
 | |
| 
 | |
|     This is an implementation of the algorithm described by Adriano Moreira and Maribel Yasmina Santos:
 | |
|     CONCAVE HULL: A neighborhood_k-NEAREST NEIGHBORS APPROACH FOR THE COMPUTATION OF THE REGION OCCUPIED BY A SET OF POINTS.
 | |
|     GRAPP 2007 - International Conference on Computer Graphics Theory and Applications; pp 61-68.
 | |
| 
 | |
|     :param points_list: list of tuples (x, y)
 | |
|     :param k: integer
 | |
|     :return: list of tuples (x, y)
 | |
|     """
 | |
|     # return an empty list if not enough points are given
 | |
|     if k > len(points_list):
 | |
|         k = len(points_list)
 | |
| 
 | |
|     # the number of nearest neighbors k must be greater than or equal to 3
 | |
|     kk = max(k, 3)
 | |
| 
 | |
|     # delete duplicate points
 | |
|     point_set = clean_list(points_list)
 | |
| 
 | |
|     # if point_set has less then 3 points no polygon can be created and an empty list will be returned
 | |
|     if len(point_set) < 3:
 | |
|         return None
 | |
| 
 | |
|     # if point_set has 3 points then these are already vertices of the hull. Append the first point to
 | |
|     # close the hull polygon
 | |
|     if len(point_set) == 3:
 | |
|         return add_point(point_set, point_set[0])
 | |
| 
 | |
|     # make sure that k neighbors can be found
 | |
|     kk = min(kk, len(point_set))
 | |
| 
 | |
|     # start with the point having the smallest y-coordinate (most southern point)
 | |
|     first_point = find_min_y_point(point_set)
 | |
| 
 | |
|     # add this points as the first vertex of the hull
 | |
|     hull = [first_point]
 | |
| 
 | |
|     # make the first vertex of the hull to the current point
 | |
|     current_point = first_point
 | |
| 
 | |
|     # remove the point from the point_set, to prevent him being among the nearest points
 | |
|     point_set = remove_point(point_set, first_point)
 | |
|     previous_angle = math.pi
 | |
| 
 | |
|     # step counts the number of segments
 | |
|     step = 2
 | |
| 
 | |
|     # as long as point_set is not empty or search is returning to the starting point
 | |
|     while (current_point != first_point) or (step == 2) and (len(point_set) > 0):
 | |
| 
 | |
|         # after 3 iterations add the first point to point_set again, otherwise a hull cannot be closed
 | |
|         if step == 5:
 | |
|             point_set = add_point(point_set, first_point)
 | |
| 
 | |
|         # search the k nearest neighbors of the current point
 | |
|         k_nearest_points = nearest_points(point_set, current_point, kk)
 | |
| 
 | |
|         # sort the candidates (neighbors) in descending order of right-hand turn. This way the algorithm progresses
 | |
|         # in clockwise direction through as many points as possible
 | |
|         c_points = sort_by_angle(k_nearest_points, current_point, previous_angle)
 | |
| 
 | |
|         its = True
 | |
|         i = -1
 | |
| 
 | |
|         # search for the nearest point to which the connecting line does not intersect any existing segment
 | |
|         while its is True and (i < len(c_points) - 1):
 | |
|             i += 1
 | |
|             if c_points[i] == first_point:
 | |
|                 last_point = 1
 | |
|             else:
 | |
|                 last_point = 0
 | |
|             j = 2
 | |
|             its = False
 | |
| 
 | |
|             while its is False and (j < len(hull) - last_point):
 | |
|                 its = intersect((hull[step - 2], c_points[i]), (hull[step - 2 - j], hull[step - 1 - j]))
 | |
|                 j += 1
 | |
| 
 | |
|         # there is no candidate to which the connecting line does not intersect any existing segment, so the
 | |
|         # for the next candidate fails. The algorithm starts again with an increased number of neighbors
 | |
|         if its is True:
 | |
|             return concave_hull(points_list, kk + 1)
 | |
| 
 | |
|         # the first point which complies with the requirements is added to the hull and gets the current point
 | |
|         current_point = c_points[i]
 | |
|         hull = add_point(hull, current_point)
 | |
| 
 | |
|         # calculate the angle between the last vertex and his precursor, that is the last segment of the hull
 | |
|         # in reversed direction
 | |
|         previous_angle = angle(hull[step - 1], hull[step - 2])
 | |
| 
 | |
|         # remove current_point from point_set
 | |
|         point_set = remove_point(point_set, current_point)
 | |
| 
 | |
|         # increment counter
 | |
|         step += 1
 | |
| 
 | |
|     all_inside = True
 | |
|     i = len(point_set) - 1
 | |
| 
 | |
|     # check if all points are within the created polygon
 | |
|     while (all_inside is True) and (i >= 0):
 | |
|         all_inside = point_in_polygon_q(point_set[i], hull)
 | |
|         i -= 1
 | |
| 
 | |
|     # since at least one point is out of the computed polygon, try again with a higher number of neighbors
 | |
|     if all_inside is False:
 | |
|         return concave_hull(points_list, kk + 1)
 | |
| 
 | |
|     # a valid hull has been constructed
 | |
|     return hull
 |