user3475132
user3475132

Reputation: 76

How to perform clustering on google map in iOS

How we can use clustering of GMS marker on google map in iOS.if anyone have code then please share.Thank you in advance

Upvotes: 0

Views: 916

Answers (1)

Eugene
Eugene

Reputation: 10045

Subclass a UIMapView. CAClusterAnnotation - clustered. CACatchAnnotation - singular.

.h

#import <MapKit/MapKit.h>

@interface CAMapView : MKMapView <MKMapViewDelegate>

@property (nonatomic) BOOL shouldCluster;

- (void)performClustering;

@end

.m

#import "CAMapView.h"
#import "CASpotClusterAnnotation.h"
#import "CACatchClusterAnnotation.h"
#include <math.h>

@interface CAMapView ()

@property (nonatomic) MKCoordinateSpan previousSpan;
@property (nonatomic) NSInteger previousCount;
@property (strong, nonatomic) NSMutableArray *allAnnotations;

@end

@implementation CAMapView

#pragma mark - Annotations
- (void)addAnnotation:(id<MKAnnotation>)annotation {
  [_allAnnotations addObject:annotation];
  [self performClustering];
}

- (void)removeAnnotation:(id<MKAnnotation>)annotation {
  [_allAnnotations removeObject:annotation];
  [self performClustering];
}

- (void)addAnnotations:(NSArray *)annotations {
  [_allAnnotations setArray:annotations];
  [self performClustering];
}

- (void)removeAnnotations:(NSArray *)annotations {
  [_allAnnotations removeAllObjects];
  [self performClustering];
}

- (CLLocationDistance)currentVisibleMapDistance {
  MKMapRect mRect = self.visibleMapRect;
  MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
  MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
  return MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
}

- (NSArray *)clustersWithAnnotations:(NSArray *)annotations {
  NSMutableArray *resultArray = [@[] mutableCopy];
  NSMutableArray *usedAnnotations = [@[] mutableCopy];

  BOOL hasIntersections = NO;

  for (id<MKAnnotation> annotation in annotations) {
    if ([usedAnnotations containsObject:annotation]) continue;
    [usedAnnotations addObject:annotation];

    NSMutableArray *clusterAnnotations = nil;
    CGRect activeRect = CGRectZero;
    CGPoint annotationPoint = [self convertCoordinate:annotation.coordinate
                                        toPointToView:self];
    if (![annotation isKindOfClass:[CAClusterAnnotation class]]) {
      clusterAnnotations = [NSMutableArray arrayWithArray:@[annotation]];
      activeRect = CGRectMake(annotationPoint.x,
                              annotationPoint.y,
                              20.0f,
                              20.0f);
    }
    else {
      CGSize size = [[CAClusterAnnotation fontAndSizeWithAnnotationsCount:[(CAClusterAnnotation *)annotation annotations].count][@"size"] CGSizeValue];
      activeRect = CGRectMake(annotationPoint.x,
                              annotationPoint.y,
                              size.width,
                              size.height);
    }

    for (id<MKAnnotation> anAnnotation in annotations) {
      if ([usedAnnotations containsObject:anAnnotation]) continue;
      CGPoint point = [self convertCoordinate:anAnnotation.coordinate
                                toPointToView:self];
      CGRect rect = CGRectZero;
      if ([anAnnotation isKindOfClass:[CAClusterAnnotation class]]) {
        CGSize size = [[CAClusterAnnotation fontAndSizeWithAnnotationsCount:[(CAClusterAnnotation *)anAnnotation annotations].count][@"size"] CGSizeValue];
        rect = CGRectMake(point.x,
                          point.y,
                          size.width,
                          size.height);
      }
      else {
        rect = CGRectMake(point.x,
                          point.y,
                          20.0f,
                          20.0f);
      }

      if (CGRectIntersectsRect(activeRect, rect)) {
        hasIntersections = YES;
        [usedAnnotations addObject:anAnnotation];
        if ([annotation isKindOfClass:[CAClusterAnnotation class]]) {
          if ([anAnnotation isKindOfClass:[CAClusterAnnotation class]]) {
            [((CAClusterAnnotation *)annotation).annotations addObjectsFromArray:[(CAClusterAnnotation *)anAnnotation annotations]];
          }
          else {
            [((CAClusterAnnotation *)annotation).annotations addObject:anAnnotation];
          }
        }
        else {
          if ([anAnnotation isKindOfClass:[CAClusterAnnotation class]]) {
            [clusterAnnotations addObjectsFromArray:[(CAClusterAnnotation *)anAnnotation annotations]];
          }
          else {
            [clusterAnnotations addObject:anAnnotation];
          }
        }
      }
    }

    if (clusterAnnotations) {
      if (clusterAnnotations.count == 1) {
        [resultArray addObject:[clusterAnnotations lastObject]];
      }
      else {
        if ([[annotations lastObject] isKindOfClass:[CACatch class]]) {
          CACatchClusterAnnotation *clusterAnnotation = [CACatchClusterAnnotation new];
          clusterAnnotation.annotations = clusterAnnotations;
          [resultArray addObject:clusterAnnotation];
        }
        else {
          CASpotClusterAnnotation *clusterAnnotation = [CASpotClusterAnnotation new];
          clusterAnnotation.annotations = clusterAnnotations;
          [resultArray addObject:clusterAnnotation];
        }
      }
    }
    else {
      [resultArray addObject:annotation];
    }
  }

  if (!hasIntersections)
    return resultArray;
  else
    return [self clustersWithAnnotations:resultArray];
}

- (void)performClustering {

  for (id<MKAnnotation> annotation in self.annotations) {
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
      _locationBtn.selected = (fabs([annotation coordinate].latitude - self.centerCoordinate.latitude) < .0001f
                               && fabs([annotation coordinate].longitude - self.centerCoordinate.longitude) < .0001f);
      break;
    }
  }

  if (fabs(_previousSpan.latitudeDelta) > .0001f
      && fabs(_previousSpan.longitudeDelta) > .0001f) {
    if (fabs(self.region.span.longitudeDelta - _previousSpan.longitudeDelta) < .00001f) {
      self.previousSpan = self.region.span;
      if (_previousCount && _allAnnotations.count) {
        return;
      }
    }
  }

  for (id<MKAnnotation> annotation in self.selectedAnnotations) {
    [self deselectAnnotation:annotation animated:NO];
  }

  self.previousSpan = self.region.span;
  self.previousCount = _allAnnotations.count;

  for (id<MKAnnotation> annotation in self.annotations) {
    if ([annotation isKindOfClass:[MKUserLocation class]]) continue;
    [super removeAnnotation:annotation];
  }

  if (!_shouldCluster) {
    if (_allAnnotations.count) {
      [super addAnnotations:_allAnnotations];
    }
    return;
  }

  if (!_allAnnotations.count) return;

  NSMutableArray *buffer = [@[] mutableCopy];

  [buffer addObjectsFromArray:[self clustersWithAnnotations:_allAnnotations]];

  [super addAnnotations:buffer];
}

@end

EDIT: CACatchClusterAnnotation and CASpotClusterAnnotation are both subclasses of CAClusterAnnotation, which you need to use, I used those two to distinguish the type of cluster annotation, in your case it's only one type. The code has a lot of architectural flaws, but I don't have any time to fix them, it should be sufficient for you to work with though.

CAClusterAnnotation.h

#import <Foundation/Foundation.h>

@interface CAClusterAnnotation : NSObject <MKAnnotation>

// <MKAnnotation> conformation properties
@property (copy, nonatomic) NSString *title;
@property (nonatomic) CLLocationCoordinate2D coordinate;

// Annotations setter that will update |coordinate|
@property (strong, nonatomic) NSMutableArray *annotations;

@end

CAClusterAnnotation.m

#import "CAClusterAnnotation.h"

@implementation CAClusterAnnotation

- (void)setAnnotations:(NSMutableArray *)annotations {
  _annotations = annotations;

  MKMapRect r = MKMapRectNull;
  for (NSUInteger i=0; i < _annotations.count; ++i) {
    MKMapPoint p = MKMapPointForCoordinate([_annotations[i] coordinate]);
    r = MKMapRectUnion(r, MKMapRectMake(p.x, p.y, 0, 0));
  }
  self.coordinate = MKCoordinateRegionForMapRect(r).center;
}

+ (NSDictionary *)fontAndSizeWithAnnotationsCount:(NSInteger)count {
  UIFont *font = [UIFont fontWithName:CAFontNameDefaultRegular size:12.0f];
  UIImage *img = [UIImage imageNamed:@"spotlist_map_other_spot_icon"];
  NSString *catchesNumberStr = (count > 999) ? @"999+" : [NSString stringWithFormat:@"%d", count];
  CGSize size = CGSizeMake([catchesNumberStr sizeWithFont:font].width + 6.0f, img.size.height);
  size.width = MAX(size.width, img.size.width);
  return @{@"font" : font, @"size" : [NSValue valueWithCGSize:size]};
}

@end

In the view controller that manages your MKMapView .m file, you need to implement at least two methods - one to render your annotations, another one to perform clustering whenever it's needed:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
  MKAnnotationView *annotationView = nil;

  if ([annotation isKindOfClass:[CACatch class]]) {
    NSString *catchIdentifier = @"catch";
    annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:catchIdentifier];
    if (!annotationView) {
      annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:catchIdentifier];
      annotationView.image = [UIImage imageNamed:@"icon"];
    }
  }
  else if ([annotation isKindOfClass:[CAClusterAnnotation class]]) {
    static NSString *catchIdentifier = @"catchCluster";
    annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:catchIdentifier];
    if (!annotationView) {
      annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:catchIdentifier];
    }

    NSInteger catchesCount = [(CACatchClusterAnnotation *)annotation annotations].count;
    NSDictionary *fontAndSize = [CAClusterAnnotation fontAndSizeWithAnnotationsCount:catchesCount];

    UIImage *img = [UIImage imageNamed:@"cluster_icon"];
    NSString *catchesNumberStr = (catchesCount > 999) ? @"999+" : [NSString stringWithFormat:@"%d", catchesCount];
    CGSize size = [fontAndSize[@"size"] CGSizeValue];

    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, MAX(size.width, img.size.width), size.height)];
    imgView.backgroundColor = [UIColor clearColor];
    imgView.opaque = NO;
    imgView.image = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f)];

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, imgView.frame.size.width, imgView.frame.size.height - 2.0f)];
    label.backgroundColor = [UIColor clearColor];
    label.font = fontAndSize[@"font"];
    label.textColor = [UIColor whiteColor];
    label.text = catchesNumberStr;
    label.textAlignment = NSTextAlignmentCenter;
    [imgView addSubview:label];

    UIGraphicsBeginImageContextWithOptions(imgView.bounds.size, imgView.opaque, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    annotationView.image = img;
  }
  else if ([annotation isKindOfClass:[MKUserLocation class]]) {
    static NSString *userIdentifier = @"user";
    annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:userIdentifier];
    if (!annotationView) {
      annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:userIdentifier];
      annotationView.canShowCallout = YES;
    }
    annotationView.image = [UIImage imageNamed:@"current_location_icon"];
  }

  return annotationView;
}

- (void)mapView:(CAMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
  [mapView performClustering];
}

Upvotes: 2

Related Questions