我有一个任意的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));
如果您有更好的想法,请分享!
该技术可行,但您在问题中提供的代码却没有。 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;
}
对我来说,路径中所有点的简单平均值不足以满足我正在处理的一些多边形。
我使用该区域实现了它(参见维基百科,Centroid of polygon和Paul 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);
}
路径中的点的所有x和所有y的简单平均值是否给出了您想要的点?计算x的一个值和y的一个值。我做了一个快速草图,这个方法给出了一个可信的答案。
见wikipedia, finding the centroid of a finite set of points.
如果没有,您可能需要先找到该区域 - 请参阅Paul Bourke's page.
更新了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可能有额外的移动命令,因为点数会破坏这个逻辑
这是质心,来吧:
-(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);
}
找到CGPath的边界框并取其中心。
CGRect boundingBox = CGPathGetBoundingBox(my_path); my_center_point = ccp(boundingBox.origin.x + boundingBox.size.width / 2,boundingBox.origin.y + boundingBox.size.height / 2);