Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Created May 19, 2020 20:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertmryan/08bfe4445b1e05507014db3af3e6a997 to your computer and use it in GitHub Desktop.
Save robertmryan/08bfe4445b1e05507014db3af3e6a997 to your computer and use it in GitHub Desktop.
//
// CGPoint+Centroid.swift
//
// Created by Robert Ryan on 5/19/20.
// Copyright © 2020 Robert Ryan. All rights reserved.
//
// See https://stackoverflow.com/a/61884774/1271826
import Foundation
extension Sequence where Element == CGPoint {
/// Calculate centroid
///
/// See https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
///
/// - Note: If the area of the polygon is zero, this returns `nil`.
///
/// - Parameter points: Unclosed points of polygon.
/// - Returns: Centroid point.
func centroid() -> CGPoint? {
var iterator = makeIterator()
guard let firstPoint = iterator.next() else { return nil }
var area: CGFloat = 0
var sumPoint: CGPoint = .zero
var lastPoint = firstPoint
var nextPoint: CGPoint?
repeat {
nextPoint = iterator.next()
let point = nextPoint ?? firstPoint
area += lastPoint.x * point.y - point.x * lastPoint.y
let factor = lastPoint.x * point.y - point.x * lastPoint.y
sumPoint.x += (lastPoint.x + point.x) * factor
sumPoint.y += (lastPoint.y + point.y) * factor
lastPoint = point
} while nextPoint != nil
area /= 2
if area == 0 { return nil }
return sumPoint / 6 / area
}
func mean() -> CGPoint? {
var sum: CGPoint = .zero
var count = 0
for point in self {
count += 1
sum = sum + point
}
return count == 0 ? nil : sum / CGFloat(count)
}
}
extension CGPoint {
static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
CGPoint(x: lhs.x / rhs, y: lhs.y / rhs)
}
static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
}
}
@robertmryan
Copy link
Author

This is a Sequence based rendition of https://stackoverflow.com/a/61884774/1271826, which makes it a little more flexible (e.g. it can be used with slices). I’m also calculating the signed area and the centroid in a single loop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment