简体   繁体   English

如何从 cgpoints 数组中获取中心点?

[英]How to get a centerpoint from an array of cgpoints?

I have an array of CGPoints:我有一个 CGPoints 数组:

let points = [(1234.0, 1053.0), (1241.0, 1111.0), (1152.0, 1043.0)]

what I'm trying to do is find the center of the CGPoints.我要做的是找到 CGPoints 的中心。 So I can place an object at the center of all the points.所以我可以在所有点的中心放置一个 object。 If this was an array of lets say integers I would reduce the array like this:如果这是一个让我们说整数的数组,我会像这样减少数组:

points.reduce(0, +)

and then divide by the total array count to get the average.然后除以总数组计数以获得平均值。 But since its CGPoints this does not work.但由于它的 CGPoints 这不起作用。 Any ideas on how to achieve this?关于如何实现这一目标的任何想法?

Farhad has answered the question about how to average all of these data points. Farhad 已经回答了如何平均所有这些数据点的问题。 (+1) (+1)

But is that really what you want?但这真的是你想要的吗? Consider:考虑:

这个图片

This convex shape is defined by the blue points, but the mean of all of those points is the red point.这个凸形由蓝点定义,但所有这些点的平均值是红点。 But that's not really in the center of the shape.但这并不是真正在形状的中心。 It is skewed up because there are five data points near the top, and only two down below.它是向上倾斜的,因为顶部附近有五个数据点,而下方只有两个。 This convex shape illustrates the problem well, but it doesn't need to be convex to manifest this issue.这个凸的形状很好地说明了这个问题,但它不需要是凸的来表现这个问题。 Any situation where the data points are not relatively evenly distributed can manifest this behavior.任何数据点分布不均匀的情况都可能表现出这种行为。

The green point is the centroid of the polygon.绿点是多边形的质心 You can see that it falls below the center of the bounding box (the crosshairs in the above image), like you'd expect.您可以看到它低于边界框的中心(上图中的十字准线),正如您所期望的那样。 For simple shapes, that might be a better place to place your label.对于简单的形状,这可能是放置 label 的更好位置。 That can be calculated as follows:这可以计算如下:

extension Array where Element == CGPoint {
    /// Calculate signed area.
    ///
    /// See https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
    ///
    /// - Returns: The signed area

    func signedArea() -> CGFloat {
        if isEmpty { return .zero }

        var sum: CGFloat = 0
        for (index, point) in enumerated() {
            let nextPoint: CGPoint
            if index < count-1 {
                nextPoint = self[index+1]
            } else {
                nextPoint = self[0]
            }

            sum += point.x * nextPoint.y - nextPoint.x * point.y
        }

        return sum / 2
    }

    /// Calculate centroid
    ///
    /// See https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
    ///
    /// - Note: If the area of the polygon is zero (e.g. the points are collinear), this returns `nil`.
    ///
    /// - Parameter points: Unclosed points of polygon.
    /// - Returns: Centroid point.

    func centroid() -> CGPoint? {
        if isEmpty { return nil }

        let area = signedArea()
        if area == 0 { return nil }

        var sumPoint: CGPoint = .zero

        for (index, point) in enumerated() {
            let nextPoint: CGPoint
            if index < count-1 {
                nextPoint = self[index+1]
            } else {
                nextPoint = self[0]
            }

            let factor = point.x * nextPoint.y - nextPoint.x * point.y
            sumPoint.x += (point.x + nextPoint.x) * factor
            sumPoint.y += (point.y + nextPoint.y) * factor
        }

        return sumPoint / 6 / area
    }

    func mean() -> CGPoint? {
        if isEmpty { return nil }

        return reduce(.zero, +) / 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)
    }
}

And you'd calculate the centroid like so:你会像这样计算质心:

let points = [(1234.0, 1053.0), (1241.0, 1111.0), (1152.0, 1043.0)]
    .map(CGPoint.init)

guard let point = points.centroid() else { return }

FWIW, with complex, concave shapes, even the centroid is not optimal. FWIW 具有复杂的凹面形状,即使质心也不是最佳的。 See What is the fastest way to find the "visual" center of an irregularly shaped polygon?请参阅找到不规则形状多边形的“视觉”中心的最快方法是什么?

you can overload operations in swift like:您可以重载 swift 中的操作,例如:

func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
    CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
func /(lhs: CGPoint, rhs: CGFloat) -> CGPoint {
    CGPoint(x: lhs.x / rhs, y: lhs.y / rhs)
}


let points = [(1234.0, 1053.0), (1241.0, 1111.0), (1152.0, 1043.0)].map({CGPoint(x: $0.0, y: $0.1)})

let m = points.reduce(.zero, +) / CGFloat(points.count)
print(m)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM