【问题标题】:Finding the center of a CGPath找到 CGPath 的中心
【发布时间】:2011-08-25 06:37:00
【问题描述】:

我有一个任意的CGPath,我想找到它的地理中心。我可以使用CGPathGetPathBoundingBox 获取路径边界框,然后找到该框的中心。但是有没有更好的方法来找到路径的中心?

更新给那些喜欢看代码的人:这里是使用 Adam 在答案中建议的平均点法的代码(不要错过下面答案中更好的技术)...

    BOOL moved = NO; // the first coord should be a move, the rest add lines
    CGPoint total = CGPointZero;
    for (NSDictionary *coord in [polygon objectForKey:@"coordinates"]) {
        CGPoint point = CGPointMake([(NSNumber *)[coord objectForKey:@"x"] floatValue], 
                                    [(NSNumber *)[coord objectForKey:@"y"] floatValue]);
        if (moved) {
            CGContextAddLineToPoint(context, point.x, point.y);
            // calculate totals of x and y to help find the center later
            // skip the first "move" point since it is repeated at the end in this data
            total.x = total.x + point.x;
            total.y = total.y + point.y;
        } else {
            CGContextMoveToPoint(context, point.x, point.y);
            moved = YES; // we only move once, then we add lines
        }
    }

    // the center is the average of the total points
    CGPoint center = CGPointMake(total.x / ([[polygon objectForKey:@"coordinates"] count]-1), total.y / ([[polygon objectForKey:@"coordinates"] count]-1));

如果你有更好的想法,请分享!

【问题讨论】:

    标签: iphone ios cocoa-touch cgpath


    【解决方案1】:

    该技术有效,但您在问题中输入的代码无效。 AFAICS,仅适用于您只做直线多边形的少数情况,并且您有一个点列表,并且您还没有制作 CGPath 对象还没有。

    我需要为任意 CGPath 对象执行此操作。使用 Adam(其他 Adam)的建议和 Apple 的 CGPathApply,我想出了这个,效果很好:

    {
                float dataArray[3] = { 0, 0, 0 };
                CGPathApply( (CGPathRef) YOUR_PATH, dataArray, pathApplierSumCoordinatesOfAllPoints);
    
                float averageX = dataArray[0] / dataArray[2];
                float averageY = dataArray[1]  / dataArray[2];
                CGPoint centerOfPath = CGPointMake(averageX, averageY);
    }
    
    static void pathApplierSumCoordinatesOfAllPoints(void* info, const CGPathElement* element)
    {
        float* dataArray = (float*) info;
        float xTotal = dataArray[0];
        float yTotal = dataArray[1];
        float numPoints = dataArray[2];
    
    
        switch (element->type)
        {
            case kCGPathElementMoveToPoint:
            {
                /** for a move to, add the single target point only */
    
                CGPoint p = element->points[0];
                xTotal += p.x;
                yTotal += p.y;
                numPoints += 1.0;
    
            }
                break;
            case kCGPathElementAddLineToPoint:
            {
                /** for a line to, add the single target point only */
    
                CGPoint p = element->points[0];
                xTotal += p.x;
                yTotal += p.y;
                numPoints += 1.0;
    
            }
                break;
            case kCGPathElementAddQuadCurveToPoint:
                for( int i=0; i<2; i++ ) // note: quad has TWO not THREE
                {
                    /** for a curve, we add all ppints, including the control poitns */
                    CGPoint p = element->points[i];
                    xTotal += p.x;
                    yTotal += p.y;
                    numPoints += 1.0;
                }
                break;
            case kCGPathElementAddCurveToPoint:         
                for( int i=0; i<3; i++ ) // note: cubic has THREE not TWO
                {
                    /** for a curve, we add all ppints, including the control poitns */
                    CGPoint p = element->points[i];
                    xTotal += p.x;
                    yTotal += p.y;
                    numPoints += 1.0;
                }
                break;
            case kCGPathElementCloseSubpath:
                /** for a close path, do nothing */
                break;
        }
    
        //NSLog(@"new x=%2.2f, new y=%2.2f, new num=%2.2f", xTotal, yTotal, numPoints);
        dataArray[0] = xTotal;
        dataArray[1] = yTotal;
        dataArray[2] = numPoints;
    }
    

    【讨论】:

    • 亚当,谢谢你再看一眼。是的,我只处理拥有所有坐标的直线多边形。您的解决方案更普遍有用。
    • 这太棒了!节省了我几个小时的头撞。
    • 很好,但有严重错误,kCGPathElementAddCurveToPoint 元素只有 3 个点,而不是 4 个点。
    • 好点,已修复。我一定一直在想他们所做的“三次曲线有 4 个点”——苹果当然只需要 3 个,因为他们把你当前的位置作为第 1 点。在 4 年内,似乎没有其他人注意到它——三次曲线似乎在 CGPath 中很少见!
    【解决方案2】:

    对我来说,路径中所有点的简单平均值对于我正在处理的某些多边形来说是不够的。

    我使用该区域实现了它(参见维基百科,Centroid of polygonPaul Bourke's page)。它可能不是最有效的实现,但它对我有用。

    请注意,它仅适用于封闭的、不相交的多边形。假设顶点按照它们沿多边形周长出现的顺序编号,并且假设最后一个点与第一个点相同。

    CGPoint GetCenterPointOfCGPath (CGPathRef aPath)
    {
        // Convert path to an array
        NSMutableArray* a = [NSMutableArray new];
        CGPathApply(aPath, (__bridge void *)(a), convertToListOfPoints);
        return centroid(a);
    }
    
    static void convertToListOfPoints(void* info, const CGPathElement* element)
    {
        NSMutableArray* a = (__bridge NSMutableArray*) info;
    
        switch (element->type)
        {
            case kCGPathElementMoveToPoint:
            {
                [a addObject:[NSValue valueWithCGPoint:element->points[0]]];
            }
            break;
            case kCGPathElementAddLineToPoint:
            {
                [a addObject:[NSValue valueWithCGPoint:element->points[0]]];
            }
            break;
            case kCGPathElementAddQuadCurveToPoint:
            {
                for (int i=0; i<2; i++)
                    [a addObject:[NSValue valueWithCGPoint:element->points[i]]];
            }
            break;
            case kCGPathElementAddCurveToPoint:
            {
                for (int i=0; i<3; i++)
                    [a addObject:[NSValue valueWithCGPoint:element->points[i]]];
            }
            break;
            case kCGPathElementCloseSubpath:
            break;
        }
    }
    
    double polygonArea(NSMutableArray* points) {
        int i,j;
        double area = 0;
        int N = [points count];
    
        for (i=0;i<N;i++) {
            j = (i + 1) % N;
            CGPoint pi =  [(NSValue*)[points objectAtIndex:i] CGPointValue];
            CGPoint pj =  [(NSValue*)[points objectAtIndex:j] CGPointValue];
            area += pi.x * pj.y;
            area -= pi.y * pj.x;
        }
    
        area /= 2;
        return area;
    }
    
    CGPoint centroid(NSMutableArray* points) {
        double cx = 0, cy = 0;
        double area = polygonArea(points);
    
        int i, j, n = [points count];
    
        double factor = 0;
        for (i = 0; i < n; i++) {
            j = (i + 1) % n;
            CGPoint pi =  [(NSValue*)[points objectAtIndex:i] CGPointValue];
            CGPoint pj =  [(NSValue*)[points objectAtIndex:j] CGPointValue];
            factor = (pi.x * pj.y - pj.x * pi.y);
            cx += (pi.x + pj.x) * factor;
            cy += (pi.y + pj.y) * factor;
        }
    
        cx *= 1 / (6.0f * area);
        cy *= 1 / (6.0f * area);
    
        return CGPointMake(cx, cy);
    }
    

    【讨论】:

    • 我复制并粘贴了这段代码,它似乎返回了错误的质心
    • @etayluz 你能更具体一点吗?您确定您使用的是封闭多边形吗?另请注意,上面的代码并不是 CGPath 中所有点的简单平均值,正如我在答案中所指出的那样。
    • 这里有个bug,kCGPathElementAddCurveToPoint有3分,不是4分。
    • 谢谢@yonilevy - 这确实是一个错字。
    【解决方案3】:

    路径中所有点的所有 x 和所有 y 的简单平均值是否给出了您想要的点?为 x 计算一个值,为 y 计算一个值。我做了一个速写,这个方法给出了一个可信的答案。

    wikipedia, finding the centroid of a finite set of points.

    如果不是,您可能需要先找到该区域 - 请参阅 Paul Bourke's page.

    【讨论】:

    • 非常好!获得平均分比边界中心要好得多,而且很容易实现。谢谢!
    【解决方案4】:

    更新Adam's对swift4版本的回答:

    extension CGPath {
        func findCenter() -> CGPoint {
            class Context {
                var sumX: CGFloat = 0
                var sumY: CGFloat = 0
                var points = 0
            }
    
            var context = Context()
    
            apply(info: &context) { (context, element) in
                guard let context = context?.assumingMemoryBound(to: Context.self).pointee else {
                    return
                }
                switch element.pointee.type {
                case .moveToPoint, .addLineToPoint:
                    let point = element.pointee.points[0]
                    context.sumX += point.x
                    context.sumY += point.y
                    context.points += 1
                case .addQuadCurveToPoint:
                    let controlPoint = element.pointee.points[0]
                    let point = element.pointee.points[1]
                    context.sumX += point.x + controlPoint.x
                    context.sumY += point.y + controlPoint.y
                    context.points += 2
                case .addCurveToPoint:
                    let controlPoint1 = element.pointee.points[0]
                    let controlPoint2 = element.pointee.points[1]
                    let point = element.pointee.points[2]
                    context.sumX += point.x + controlPoint1.x + controlPoint2.x
                    context.sumY += point.y + controlPoint1.y + controlPoint2.y
                    context.points += 3
                case .closeSubpath:
                    break
                }
            }
    
            return CGPoint(x: context.sumX / CGFloat(context.points),
                    y: context.sumY / CGFloat(context.points))
        }
    }
    

    但要小心,CGPath 可能有额外的移动命令,因为点数会破坏这个逻辑

    【讨论】:

      【解决方案5】:

      这是质心,来拿吧:

      -(CLLocationCoordinate2D)getCentroidFor:(GMSMutablePath *)rect
      {
        CLLocationCoordinate2D coord = [rect coordinateAtIndex:0];
        double minX = coord.longitude;
        double maxX = coord.longitude;
        double minY = coord.latitude;
        double maxY = coord.latitude;
        for (int i = 1; i < rect.count; i++)
        {
          CLLocationCoordinate2D coord = [rect coordinateAtIndex:i];
          if (minX > coord.longitude)
            minX = coord.longitude;
          if (maxX < coord.longitude)
            maxX = coord.longitude;
          if (minY > coord.latitude)
            minY = coord.latitude;
          if (maxY < coord.latitude)
            maxY = coord.latitude;
        }
      
        CLLocationDegrees centerX = minX + ((maxX - minX) / 2);
        CLLocationDegrees centerY = minY + ((maxY - minY) / 2); 
          return CLLocationCoordinate2DMake(centerY, centerX);
      }
      

      【讨论】:

      • 请再次阅读OP的问题。此答案不相关或无用。
      • swift 4 版本?谢谢
      【解决方案6】:

      找到 CGPath 的边界框并取其中心。

      CGRect boundingBox = CGPathGetBoundingBox(my_path); my_center_point = ccp(boundingBox.origin.x+boundingBox.size.width/2, boundingBox.origin.y+boundingBox.size.height/2);

      【讨论】:

      • 正如我在问题陈述中所说,在这种情况下,找到边界框的中心并不能提供一个好的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-22
      • 2011-02-03
      • 1970-01-01
      • 1970-01-01
      • 2012-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多