iphoneioscocoa-touchcgpath

Finding the center of a CGPath


I have an arbitrary CGPath and I'd like to find it's geographic center. I can get the path bounding box with CGPathGetPathBoundingBox and then find the center of that box. But is there a better way to find the center of a path?

Update for those who like to see code: here is code for using the average-of-points method suggested by Adam in the answers (don't miss the even better technique in the answers below)...

    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));

If you have a better idea, please share!


Solution

  • The technique works, but the code you put in the question doesn't. AFAICS, that only works for the few situations where you are doing straight-line polygons ONLY, and you have a list of points, and you haven't made the CGPath object yet.

    I needed to do it for arbitrary CGPath objects. Using Adam's (other Adam) suggestion, and Apple's CGPathApply, I came up with this, which works very well:

    {
                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;
    }