hulab / ClusterKit

An iOS map clustering framework targeting MapKit, Google Maps and Mapbox.
MIT License
512 stars 86 forks source link

Single annotation exists in multiple clusters? #43

Open solomon-gumball opened 6 years ago

solomon-gumball commented 6 years ago

I've been attempting to integrate ClusterKit into our app but it seems at certain zoom levels a single annotation may exist in multiple clusters which looks strange. Here is a gif illustrating the issue:

clusteringissue

Here you can see that there are just 3 annotations, but when I zoom out to a certain level it goes from clusters of 2 and 1 to clusters of 2 and 2 and then when zooming out more it goes to one cluster of 3. There are actually only 3 annotations so it seems they are mistakenly sharing an annotation at a specific zoom level.

The code I've added so far is pretty straightforward and follows your example quite closely.

I add the algorithm with a custom cell size:

    CKNonHierarchicalDistanceBasedAlgorithm *algorithm = [CKNonHierarchicalDistanceBasedAlgorithm new];
    algorithm.cellSize = 200;
    _mapView.clusterManager.algorithm = algorithm;
    _mapView.clusterManager.marginFactor = 1;

Creating MKAnnotationViews and setting the annotation:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
    if ([annotation isKindOfClass:[MKUserLocation class]]) {
        return nil;
    }

    MKAnnotationView *mapAnnotationView = nil;
    mapAnnotationView = (GWTicketsGroupedByLocationPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:pinAllTicketsAnnotationIdentifier];
    if ([annotation isKindOfClass:CKCluster.class]) {
        return [[GroupedGigAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pinAllTicketsAnnotationIdentifier];
    }

    [mapAnnotationView setAnnotation:annotation];

    return mapAnnotationView;
}

and updating the clusters when region changes:

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    [_mapView.clusterManager updateClustersIfNeeded];
}

Is there something wrong with the distance based algorithm or am I doing something incorrectly?

vijayradke commented 6 years ago

Any resolution of the issue?

solomon-gumball commented 6 years ago

@vijayradke Yea. Used a different library lol

vijayradke commented 6 years ago

May I know which one you used?

solomon-gumball commented 6 years ago

I used CCHMapClusterController. It doesn't have the nice pin location animations but it works and it's easy to use.

ZalyalovIldar commented 6 years ago

Just fix this with next line of codes:

Add open orderedSet to CKCluster.h:

- (NSMutableOrderedSet<id<MKAnnotation>> *) orderedSet;

In realisation CKCluster.m just return _annotations:

- (NSMutableOrderedSet<id<MKAnnotation>> *) orderedSet {
    return _annotations;
}

And inside CKClusterManager.m update private method:

- (void)updateMapRect:(MKMapRect)visibleMapRect animated:(BOOL)animated {
    if (!self.tree || MKMapRectIsNull(visibleMapRect) || MKMapRectIsEmpty(visibleMapRect)) {
        return;
    }

    MKMapRect clusterMapRect = MKMapRectWorld;
    if (self.marginFactor != kCKMarginFactorWorld) {
        clusterMapRect = MKMapRectInset(visibleMapRect,
                                        -self.marginFactor * visibleMapRect.size.width,
                                        -self.marginFactor * visibleMapRect.size.height);
    }

    double zoom = self.map.zoom;
    CKClusterAlgorithm *algorithm = (zoom < self.maxZoomLevel)? self.algorithm : [CKClusterAlgorithm new];
    NSArray *clusters = [algorithm clustersInRect:clusterMapRect zoom:zoom tree:self.tree];

   //------HERE the moment that delete all duplicates from clusters 
    for (CKCluster* cluster in clusters) {
        for (CKCluster* secondCluster in clusters) {

            if (![secondCluster isEqualToCluster:cluster] && [secondCluster intersectsCluster:cluster]) {

                NSMutableSet<id<MKAnnotation>> *secondSet = secondCluster.orderedSet.set.mutableCopy;

                [cluster.orderedSet minusSet:secondSet];
            }
        }
    }
    //---------------------------------------
    NSMutableSet *newClusters = [NSMutableSet setWithArray:clusters];
    NSMutableSet *oldClusters = [NSMutableSet setWithSet:_clusters];

    [oldClusters minusSet:newClusters];
    [newClusters minusSet:_clusters];

    if (visibleMapRect.size.width > _visibleMapRect.size.width) {
        [self collapse:oldClusters.allObjects to:newClusters.allObjects in:visibleMapRect];

    } else if (visibleMapRect.size.width < _visibleMapRect.size.width) {
        [self expand:newClusters.allObjects from:oldClusters.allObjects in:visibleMapRect];

    } else {
        [self.map addClusters:newClusters.allObjects];
        [self.map removeClusters:oldClusters.allObjects];
    }

    [_clusters minusSet:oldClusters];
    [_clusters unionSet:newClusters];

    _visibleMapRect = visibleMapRect;
}

Not optimised but although work perfectly.