I have a Core Data entity called Line. Each line contains an instance of a VerticePoint which contains an x and y property. These x and y vertices form simple 2D polygons.
What I want to do is sort an array of these Line objects which is in random order so that the origin of the shape, the bottom left point, is always the first element in the array, then followed by the remaining vertices wound in counter-clockwise direction from the origin.
So say the points in my original array are (x y axis is centred at 0,0) :
x = 20, y = 20
x = 20 , y= 10
x = 10, y=10
x = 10, y =20
x = 15, y = 10
I want to sort them like so :
x = 10, y=10
x = 15, y = 10
x = 20 , y= 10
x = 20, y = 20
x = 10, y =20
Many thanks
You can use
- (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors
of NSArray.
You can use more then one descriptor. Just initialize two descriptors one with x, one with y property.
Here's a proposal for a precise specification:
- Assume a first quadrant coordinate system (with the y axis pointing up).
- Find the center of the axis aligned bounding box of all points.
- Sort the points by the angle of a vector from the center to the point. To calculate the angle consider a vector pointing south west to be at 0° with angles ascending in counter-clockwise direction.
And here's a solution:
NSArray *points = @[
[NSValue valueWithCGPoint:(CGPoint){20, 20}],
[NSValue valueWithCGPoint:(CGPoint){20, 10}],
[NSValue valueWithCGPoint:(CGPoint){10, 10}],
[NSValue valueWithCGPoint:(CGPoint){10, 20}],
[NSValue valueWithCGPoint:(CGPoint){15, 10}],
];
CGPoint min = [points[0] CGPointValue];
CGPoint max = min;
for (NSValue *value in points) {
CGPoint point = [value CGPointValue];
min.x = fminf(point.x, min.x);
min.y = fminf(point.y, min.y);
max.x = fmaxf(point.x, max.x);
max.y = fmaxf(point.y, max.y);
}
CGPoint center = {
0.5f * (min.x + max.x),
0.5f * (min.y + max.y),
};
NSLog(@"center: %@", NSStringFromCGPoint(center));
NSNumber *(^angleFromPoint)(id) = ^(NSValue *value){
CGPoint point = [value CGPointValue];
CGFloat theta = atan2f(point.y - center.y, point.x - center.x);
CGFloat angle = fmodf(M_PI - M_PI_4 + theta, 2 * M_PI);
return @(angle);
};
NSArray *sortedPoints = [points sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
return [angleFromPoint(a) compare:angleFromPoint(b)];
}];
NSLog(@"sorted points: %@", sortedPoints);
You should implement method for your VerticePoint object which do the comparison, something like that:
- (NSComparisonResult)compare:(VerticePoint *)vpoint
{
if (self.x > vpiont.x)
return NSOrderedAscending;
else if (self.x < vpiont.x)
return NSOrderedDescending;
else if (self.y > vpiont.y)
return NSOrderedAscending;
else if (self.y < vpiont.y)
return NSOrderedDescending;
else
return NSOrderedSame;
}
And after that if you have your array with VerticePoint object you call:
NSArray *sortedArray = [yourArray sortedArrayUsingSelector:@selector(compare:)];
Hope this help.
//EXTENDED
If you don't want to create subclass of NSManagedObject you can use NSSortDescriptor:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"ENTITYNAME" inManagedObjectContext:context]];
NSSortDescriptor *sortDescriptorX = [NSSortDescriptor sortDescriptorWithKey:@"yourObjecy.x" ascending:YES];
NSSortDescriptor *sortDescriptorY = [NSSortDescriptor sortDescriptorWithKey:@"yourObjecy.y" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObjects:sortDescriptorX, sortDescriptorY, nil]];
NSArray *sortedResults = [context executeFetchRequest:request error:nil];
// EXTENDED
Or the easiest solution is
NSArray *returnedVertices = [verticesPassed sortedArrayUsingComparator:^(id obj1, id obj2) {
//Cast to your object:
VerticePoint *p1 = (VerticePoint*)obj1;
VerticePoint *p2 = (VerticePoint*)obj2;
if (p1.x > p2.x)
return NSOrderedAscending;
else if (p1.x < p2.x)
return NSOrderedDescending;
else if (p1.y > p2.y)
return NSOrderedAscending;
else if (p1.y < p2.y)
return NSOrderedDescending;
else
return NSOrderedSame;
}
];