Finding the center of a CGPath

uibezierpath
cgpath transform
cgpath circle
cgpath tutorial
addquadcurve
swift draw shapes
addarc swift
spritekit bezier path

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!

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

Core Graphics, Part 4: A Path! A Path!, Depending on how you're calculating points, accumulated floating point round off let squarePath = CGPath(rect: rect1, transform: nil) let ovalpath Arc – Give it the center of the circle, its radius, and the starting and ending  Centering a CGPath in Swift Recently I was trying to work out how to center a CGPath in Swift, and Google was failing me. I found a pretty quick and easy way to do it so I thought I’d drop it here for others to find.

For me, the simple average of all points in the path did not suffice for some of the polygons I was dealing with.

I implemented it using the area (see wikipedia, Centroid of polygon and Paul Bourke's page). It might not be the most efficient implementation but it works for me.

Note that it only works for closed, non-intersecting polygons. The vertices are assumed to be numbered in order of their occurrence along the polygon's perimeter, and the last point is assumed to be the same as the first point.

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

CGMutablePath, class CGMutablePath : CGPath func addArc(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool, transform:  Arc – Give it the center of the circle, its radius, and the starting and ending angles (in radians) of the arc segment you want. The section of the circle between the start and ending angles (going clockwise or counter-clockwise) is what will be drawn.

Does the simple average of all x and all y for the points in the path give the point you want? Calculate one value for x and one for y. I made a quick sketch and this method gave a believable answer.

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

If not you may need to first find the area - see Paul Bourke's page.

calculate points on UIBezierPath – MachineThinks, cgPath. shapeLayer.fillColor = UIColor.clear.cgColor. shapeLayer.strokeColor It will be useful to calculate the intermidiate point on curve. easiest way to find the center of the circle. draw a square (square has four equal sides and four right angles) inside the circle with the corners exactly on the circular line then draw a straight line from one corner of the square to its opposite corner do it also on the other two corners left, the center of the circle will be the intersection point of the two straight lines drawn from the

Updated Adam's answer to swift4 version:

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

But be careful, CGPath may have extra move commands that will break this logic because of point count

Thinking like a Bézier path, Orange dots are the center of the circles that define the rounded corners. or their counterparts for CGPath's: Unless you are calculating the exact point for a given t there is no need to do these calculations yourself but having seen them can  Finding Center, Marion, Ohio. 1.4K likes. Finding Center specializes in hot yoga. Classes ranging from Vinyasa Flow to Yin Yoga. Our retail space includes handmade jewelry, leisure apparel, and

Here is the centroid, come get it:

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

Core Graphics Tutorial: Arcs and Paths, And now that you know c and d, you can calculate the radius with the arcHeight​: CGFloat, startAngle: Angle, endAngle: Angle ) -> CGPath  This work provides the foundation for creating a CGPath based on ID2D1PathGeometry and GeometrySink. A simple implementation for adding a simple line, moving the current location to a new point and retrieving some basic information from the Geometry sink has been added. Isolated unit tests have also been added to ensure

UIBezierPath, addArcWithCenter(CGPoint(x: 2, y: 2), // center point of circle radius: 2, // this will make it CGPath // apply other properties related to the path shapeLayer. 15, 5, 17, 3, 10, 20] // 1. find center of draw rect let center: CGPoint = CGPoint(x: rect. * Switch all internal interfaces to HR returns. * Implement the CGContext non-drawing path functions in terms of CGPath. Curiously, CGContext path queries and CopyPath return untransformed units. Closes #1238. * Finish Simple line and status CGPath APIs. * Finish Simple line and status CGPath APIs. Add additional tests to cover these scenarios.

How to use UIBezierPath and CGPath in SwiftUI, If you have existing paths made using UIBezierPath or CGPath it's trivial to convert them for use in SwiftUI because the Path struct has an  4.0 out of 5 stars Finding the Center by V.S. Naipaul Reviewed in the United States on February 27, 2002 It is a rear book of two essays that dealt with the author's own personal experiences in two entirely different worlds.

Advanced UIView shadow effects using shadowPath – Hacking with , Making UIKit calculate the shadow path dynamically is significantly but normally I just create a UIBezierPath then use its cgPath property. Similarly, with the GraphView selected, Control-drag from the center slightly up (still within the view), and choose Height from the popup menu. Control-drag left from inside the view to outside the view and choose Center Horizontally in Container. Control-drag up from inside the view to outside the view, and choose Center Vertically in Container.

Comments
  • Adam, thanks for giving this another look. Yes, I was dealing with only straight line polygons where I had all the coordinates. Your solution is more generally useful.
  • This is great! Saved me hours of head banging.
  • Its great, but has serious error, kCGPathElementAddCurveToPoint element has only 3 points, not 4 points.
  • Good point, fixed. I must have been thinking of "cubic curves have 4 points" which they do - Apple only takes 3 of course because they take your current position as point 1. In 4 years, no-one else seems to have noticed it - cubic curves seem to be rare in CGPath!
  • I copied and pasted this code and it seems to return the wrong centroid
  • @etayluz Could you be a bit more specific? Are you sure that you are using a closed polygon? Also note that the above code is not a simple average of all points in the CGPath as I also indicated in the answer.
  • There's a bug here, kCGPathElementAddCurveToPoint has 3 points, not 4.
  • Thanks @yonilevy - that was indeed a typo.
  • Very nice! Getting the average point was much better than the center of the bounds and very easy to pull off. Thanks!
  • Please read the OP's question again. This answer is not relevant or useful.
  • swift 4 version? Thanks
  • As I said in the statement of the problem, finding the center of the bounding box does NOT provide a good solution in this case.