Monday, January 19, 2015

How To Efficiently Display Annotation Pins on the Same Coordinate on iOS Maps

Overview :

This tutorial will demonstrate how to handle and display Same Coordinate of points of data on an iOS map in a way people understand and enjoy. We are going to make an iOS app which Shopping Center with 100 km areas, each with a coordinate, a name and a phone number and Profile Image. This app will update the map as the user pans and zooms, allowing the user to freely explore the data.

Framework : MKMapKit

Steps :
  1. Open Xcode 6.
  2. Create new project.
  3. Select: iOS, Application, Single View Application.
  4. Give the project a proper name. In the example, I’ve used the following:
    • Product Name: MapAnnotationCircle
    • Organization Name: XXXX
    • Class Prefix: XXX
  5. To keep stuff simple, I’ve selected iPad as target device.
    • Click Next.
  6. Now in project navigator you will find “Main.Storyboard”.
  7. Drag and drop MKMapView and add MKMapKit framework.
  8. Just Control-drag your map to the MapViewController.h file and create an IBOutlet. Let’s name our outlet iMapView.
  9. Import in your header file.
  10. First we group the annotations by coordinate.

for (int i=0; [latitude_MutableArray count]>i; i++)
NSString *latStr=[latitude_MutableArray objectAtIndex:i];
NSString *longStr=[longtitude_MutableArray objectAtIndex:i];
double lat=[latStr doubleValue];
double lan=[longStr doubleValue];
customAnnotation = [[BasicMapAnnotation alloc] initWithLatitude:lat andLongitude:lan];
customAnnotation.title =[centerName_MutableArray objectAtIndex:i];
[pinPointAnnotations addObject:customAnnotation];
[AnnotationCoordinateUtility mutateCoordinatesOfClashingAnnotations:pinPointAnnotations];
[self.iMapView addAnnotations:pinPointAnnotations];

11. The value of a entry in the dictionary is a NSArray of annotations at that coordinate. You can see this only matches on exactly equal coordinates, but it would be relatively straightforward to group on coordinates that were close by calculating the distance between them. Next we enumerate the NSArray looking for locations that have more than one annotation. When we find one, we reposition the annotations.

+ (void)mutateCoordinatesOfClashingAnnotations:(NSArray *)annotations {
NSDictionary *coordinateValuesToAnnotations = [self groupAnnotationsByLocationValue:annotations]; for (NSValue *coordinateValue in coordinateValuesToAnnotations.allKeys) {
NSMutableArray *outletsAtLocation = coordinateValuesToAnnotations[coordinateValue];
if (outletsAtLocation.count > 1) {
CLLocationCoordinate2D coordinate;
[coordinateValue getValue:&coordinate];
[self repositionAnnotations:outletsAtLocation toAvoidClashAtCoordination:coordinate];
+ (NSDictionary *)groupAnnotationsByLocationValue:(NSArray *)annotations {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (id pin in annotations) {
CLLocationCoordinate2D coordinate = pin.coordinate;
NSValue *coordinateValue = [NSValue valueWithBytes:&coordinate objCType:@encode(CLLocationCoordinate2D)];
NSMutableArray *annotationsAtLocation = result[coordinateValue];
if (!annotationsAtLocation) {
annotationsAtLocation = [NSMutableArray array];
result[coordinateValue] = annotationsAtLocation;
[annotationsAtLocation addObject:pin];
return result;
+ (void)repositionAnnotations:(NSMutableArray *)annotations toAvoidClashAtCoordination:(CLLocationCoordinate2D)coordinate {
double distance = 3 * annotations.count / 2.0;
double radiansBetweenAnnotations = (M_PI * 2) / annotations.count;
for (int i = 0; i < annotations.count; i++) {
double heading = radiansBetweenAnnotations * i;
CLLocationCoordinate2D newCoordinate = [self calculateCoordinateFrom:coordinate onBearing:heading atDistance:distance];
id annotation = annotations[i];
annotation.coordinate = newCoordinate;

12. The pins are arranged in a circle around the contested point by dividing the circle by the number of contesting annotations. You can see that the distance from the contested coordinate to the new coordinate is a function of the number of annotations contesting – if there are few pins contesting the coordinate, then we have space to place the pins close to the coordinate.

Finally, the new coordinate is calculated using an implementation of the function from this excellent resource: Destination point given distance and bearing from start point

+ (CLLocationCoordinate2D)calculateCoordinateFrom:(CLLocationCoordinate2D)coordinate onBearing:(double)bearingInRadians atDistance:(double)distanceInMetres {
double coordinateLatitudeInRadians = coordinate.latitude * M_PI / 180;
double coordinateLongitudeInRadians = coordinate.longitude * M_PI / 180;
double distanceComparedToEarth = distanceInMetres / 6378100;
double resultLatitudeInRadians = asin(sin(coordinateLatitudeInRadians) * cos(distanceComparedToEarth) + cos(coordinateLatitudeInRadians) * sin(distanceComparedToEarth) *
double resultLongitudeInRadians = coordinateLongitudeInRadians + atan2(sin(bearingInRadians) * sin(distanceComparedToEarth) * cos(coordinateLatitudeInRadians),
cos(distanceComparedToEarth) - sin(coordinateLatitudeInRadians) * sin(resultLatitudeInRadians));
CLLocationCoordinate2D result;
result.latitude = resultLatitudeInRadians * 180 / M_PI;
result.longitude = resultLongitudeInRadians * 180 / M_PI;
return result;

Output :

1. Pins with inside the rectangle

2. After Zooming

Bharathimohan K is a Software Engineer at Span Technology Services

No comments:

Post a Comment