views:

863

answers:

5

I'm plotting over 500 points on a map using mapkit. Zooming is a little jittery compared to the native google map app. I've discovered what is causing the slowness. I'm adding custom annotations so that I can later add different pin colors and buttons for detail views:

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(AddressNote *) annotation {
    MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"currentlocation"];
    annView.pinColor = MKPinAnnotationColorGreen;
    annView.animatesDrop=TRUE;
    annView.canShowCallout = YES;
    annView.calloutOffset = CGPointMake(-5, 5);
    return annView;
}

If I comment out the above code, everything works fine. Very smooth with zooming in/out. Should I be adding annotations differently to boost performance?

A: 

It sounds to me that adding 500 map points to a section of a map the size of the iPhone screen makes no sense. There is no way you can visualize that or even click on the right annotation.

Can't you look at the zoom level and center of the map and then decide to only add a subset of annotations to the map?

I think I've seen other apps do this too.

St3fan
Can you explain how you would ever be able to accomplish that type of lazy loading?
MKMapView delivers messages to its delegate whenever the map scrolls. Every time that happens, store the latitude and longitude deltas of the view so you know the sale of the map.Before you store the value, check to see if the map scale has grown or shrunk enough to need to adjust the number of annotations.If so, just do a level of detail calculation. Run through the list of annotations and combine those that are within a certain distance of each other into one new annotation representing all of them. If the user zooms back in, swap it for the original annotations.
Victorb
+1  A: 

I think St3fan is correct. In theory you can just keep adding annotations to the map and let the view handle display of them, but the reality is that it's better if you keep the count down a bit, 500 on screen at once would be way too many to see anyway.

However there is one more step to try - make sure all of the annotation views have opaque set to YES like so:

annView.opaque = YES; 
Kendall Helmstetter Gelner
You misunderstand the question. There aren't 500 pins in the viewing area. There are only a few. If you zoom out far enough, you'll eventually see all 500. annView.opaque = YES didn't help.
Then you need to control how many the map knows about at once, after you are zoomed in to where some are not visible you need to remove them. That's why I was talking about how technically it's built to let you do what you are doing - simply adding many annotations where some are offscreen - but for performance the reality is that you can't have that many annotations on a map and have it perform well, so you have to manage to some extent what annotations the map really knows about.
Kendall Helmstetter Gelner
Ok, Yes - I agree. But how can I manage them so I know which ones to load and which not to load? All of my points are in an array. There is anything tied to them that says these points are in the viewing area, load their annotations. These aren't, remove their annotations.
You have to devise something that goes through the array and adds elements that are in the map viewing rectangle, removing any that are not... I think after a resize you may need to clear all elements and re-add just the ones that would be visible.If you have a lot of points another option is putting together a spatial database (there is a spatial version of sqllite), but that is a lot of work.
Kendall Helmstetter Gelner
Basically, it is a point of polygon issue. When you say map viewing rectangle, you mean the four lat/long points that make up the corners of that rectangle? How do I get those?
Yes, the lat/long rectangle - the MKMapView has a property like "visibleRegion" that gives you back those sets of coordinates.
Kendall Helmstetter Gelner
annotationVisibleRect gives back a CGRect with x=8032,y=7986 and width=320,height=411. Then when I do [self.mapView convertCoordinate:pin.coordinate toPointToView:self.mapView], the resulting CGPoint is x=-245656.241,y=104360.555. Other points are similar. No point will ever show on the map. Do you have any idea what I did wrong?
I looked at the API, the property I used was "region" where you can get the lat//long pairs for the corners of the visible map, and compare them with lat/long values for your annotations. I never used the pointToView stuff as I never trust that stuff (though obviously it should work). From the coordinates you gave it does seem like your annotations are outside the visible map though...
Kendall Helmstetter Gelner
Can you provide a code example of how you are doing it? I'm not able to get anything from it that makes any sense.
+2  A: 

500 annotations is probably too many, but not so many that the performance suffers when you are only viewing some of them. However, you should be using dequeueing with your annotations to improve performance

- (MKAnnotationView *)mapView:(MKMapView *)mapView
            viewForAnnotation:(id <MKAnnotation>)annotation
{
    MKPinAnnotationView *view = nil;
    if (annotation != mapView.userLocation) {
        view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"identifier"];
        if (nil == view) {
            view = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"identifier"] autorelease];
        }
        [view setPinColor:MKPinAnnotationColorGreen];
        [view setCanShowCallout:YES];
        [view setAnimatesDrop:NO];
    }
    else {
        // Do something with the user location view
    }
    return view;
}

Also, with 500 pins, the drop animation takes a long time to finish. You should turn that off with [view setAnimatesDrop:NO]

nevan
Thanks but dequeueing doesn't help performance any. I'm aware that the large number of annotations are causing the performance hit. I can decrease to 100 and get good performance. When the map viewing area changes, I need a way to load annotations for points that are coming into view. Need lazy loading but mapkit isn't friendly for that type of thing.
Dequeueing does help performance, why else would you use it? "The reuse of annotation views provides significant performance improvements during scrolling by avoiding the creation of new view objects during this time critical operation." http://developer.apple.com/iphone/library/documentation/MapKit/Reference/MKAnnotationView_Class/Reference/Reference.html
nevan
Looks great on paper.
+1  A: 

I wrote an iPhone app for a client and we included a store locator for one of their store brands. It includes 501 locations and while the animation can be a bit jerky when you are zoomed out to view the entire United States, it's perfectly fine zoomed in to the state level, where only a handful of pins are visible.

The keys:

  • Zoom the user into their current location before you add your annotations.
  • Reuse annotation views using dequeueReusableAnnotationViewWithIdentifier:.

To achieve the first point, you need to turn on location updates and set a flag when you receive the first one. Animate the map to that location using a region span that makes sense for your app, then in mapView:regionDidChangeAnimated:, check to see if you still need to add your annotations and that the current location has been updated before you call addAnnotation:.

If you can't or don't want to zoom in to the user's location, can you filter the annotations shown at the highest level and only add additional annotations as the user zooms in?

By the way, I believe you have a memory leak in your code as posted above. Even if you don't want to use the dequeuing mechanism, the view you return from mapView:viewForAnnotation: should be autoreleased.

Steve Madsen
A: 

If you are not removing annotations not seen by user from view, that is one thing to do by means of MKMapViewDelegate. If performance is degrading when user zoom-out to country level you may want to present aggregated info on zoom levels > some const value, e.g. instead of 30 annotations within 10 square miles show single annotation like [30 something].

Igor Romanov