Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS MKMapView zoom to show all markers

I'm working with MKMapView and have plotted several points on the map. I have used the MKCoordinateRegion and MKCoordinateSpan to enable zooming etc around one of the points - but that's not what I want...

I'm trying to use something similar to the Javascript zoom to bounds function. so all my points should be visible to the user. (There will be around 10 points around the UK) I'd like to show them all, or if most of them were in the London area, zoom to there.

Is there a way to work this out programatically?

like image 342
Matt Facer Avatar asked Aug 08 '10 10:08

Matt Facer


4 Answers

Sure. You want to find the biggest and smallest latitude and longitude values among your annotations (which you can do by iterating over map.annotations), then set the map to show all of them.

// pad our map by 10% around the farthest annotations
#define MAP_PADDING 1.1

// we'll make sure that our minimum vertical span is about a kilometer
// there are ~111km to a degree of latitude. regionThatFits will take care of
// longitude, which is more complicated, anyway. 
#define MINIMUM_VISIBLE_LATITUDE 0.01

MKCoordinateRegion region;
region.center.latitude = (minLatitude + maxLatitude) / 2;
region.center.longitude = (minLongitude + maxLongitude) / 2;

region.span.latitudeDelta = (maxLatitude - minLatitude) * MAP_PADDING;

region.span.latitudeDelta = (region.span.latitudeDelta < MINIMUM_VISIBLE_LATITUDE)
    ? MINIMUM_VISIBLE_LATITUDE 
    : region.span.latitudeDelta;

region.span.longitudeDelta = (maxLongitude - minLongitude) * MAP_PADDING;

MKCoordinateRegion scaledRegion = [map regionThatFits:region];
[map setRegion:scaledRegion animated:YES];
like image 137
Seamus Campbell Avatar answered Nov 11 '22 22:11

Seamus Campbell


If you are only targeting iOS 7 or greater you can now use:

- (void)showAnnotations:(NSArray *)annotations 
               animated:(BOOL)animated
like image 27
Gerry Shaw Avatar answered Nov 11 '22 21:11

Gerry Shaw


Here is an improvement that takes into account the height of the annotation views that you are overlaying onto the map (such that the top of the annotation does not get cut off when its coordinate offset is at the bottom for example). Or to generalise further, allows you to specify padding in pixels as opposed to as a percentage. It requires a two stage pass whereby you find out the bounds for the annotations, then you further increase the bounds to take into account your map padding.

- (void) zoomToAnnotationsBounds:(NSArray *)annotations {

CLLocationDegrees minLatitude = DBL_MAX;
CLLocationDegrees maxLatitude = -DBL_MAX;
CLLocationDegrees minLongitude = DBL_MAX;
CLLocationDegrees maxLongitude = -DBL_MAX;

for (MyAnnotation *annotation in annotations) {
            double annotationLat = annotation.coordinate.latitude;
            double annotationLong = annotation.coordinate.longitude;
    minLatitude = fmin(annotationLat, minLatitude);
    maxLatitude = fmax(annotationLat, maxLatitude);
    minLongitude = fmin(annotationLong, minLongitude);
    maxLongitude = fmax(annotationLong, maxLongitude);
}

    // See function below
[self setMapRegionForMinLat:minLatitude minLong:minLongitude maxLat:maxLatitude maxLong:maxLongitude];

// If your markers were 40 in height and 20 in width, this would zoom the map to fit them perfectly. Note that there is a bug in mkmapview's set region which means it will snap the map to the nearest whole zoom level, so you will rarely get a perfect fit. But this will ensure a minimum padding.
UIEdgeInsets mapPadding = UIEdgeInsetsMake(40.0, 10.0, 0.0, 10.0);
CLLocationCoordinate2D relativeFromCoord = [self.mapView convertPoint:CGPointMake(0, 0) toCoordinateFromView:self.mapView];

// Calculate the additional lat/long required at the current zoom level to add the padding
CLLocationCoordinate2D topCoord = [self.mapView convertPoint:CGPointMake(0, mapPadding.top) toCoordinateFromView:self.mapView];
CLLocationCoordinate2D rightCoord = [self.mapView convertPoint:CGPointMake(0, mapPadding.right) toCoordinateFromView:self.mapView];
CLLocationCoordinate2D bottomCoord = [self.mapView convertPoint:CGPointMake(0, mapPadding.bottom) toCoordinateFromView:self.mapView];
CLLocationCoordinate2D leftCoord = [self.mapView convertPoint:CGPointMake(0, mapPadding.left) toCoordinateFromView:self.mapView];

double latitudeSpanToBeAddedToTop = relativeFromCoord.latitude - topCoord.latitude;
double longitudeSpanToBeAddedToRight = relativeFromCoord.latitude - rightCoord.latitude;
double latitudeSpanToBeAddedToBottom = relativeFromCoord.latitude - bottomCoord.latitude;
double longitudeSpanToBeAddedToLeft = relativeFromCoord.latitude - leftCoord.latitude;

maxLatitude = maxLatitude + latitudeSpanToBeAddedToTop;
minLatitude = minLatitude - latitudeSpanToBeAddedToBottom;

maxLongitude = maxLongitude + longitudeSpanToBeAddedToRight;
minLongitude = minLongitude - longitudeSpanToBeAddedToLeft;

[self setMapRegionForMinLat:minLatitude minLong:minLongitude maxLat:maxLatitude maxLong:maxLongitude];
}

-(void) setMapRegionForMinLat:(double)minLatitude minLong:(double)minLongitude maxLat:(double)maxLatitude maxLong:(double)maxLongitude {

    MKCoordinateRegion region;
    region.center.latitude = (minLatitude + maxLatitude) / 2;
    region.center.longitude = (minLongitude + maxLongitude) / 2;
    region.span.latitudeDelta = (maxLatitude - minLatitude);
    region.span.longitudeDelta = (maxLongitude - minLongitude);

    // MKMapView BUG: this snaps to the nearest whole zoom level, which is wrong- it doesn't respect the exact region you asked for. See http://stackoverflow.com/questions/1383296/why-mkmapview-region-is-different-than-requested
    [self.mapView setRegion:region animated:YES];
}
like image 14
Willster Avatar answered Nov 11 '22 21:11

Willster


It's an old question and I know you might not need any help. But I'm just putting it out there for anyone who is looking for a way to do this now as there's a new method in MKMapView as of iOS 7 that can be used. It is both clean and easy.

Declaration

SWIFT

func showAnnotations(_ annotations: [AnyObject]!,
            animated animated: Bool)

OBJECTIVE-C

- (void)showAnnotations:(NSArray *)annotations
               animated:(BOOL)animated

Parameters

annotations The annotations that you want to be visible in the map. animated YES if you want the map region change to be animated, or NO if you want the map to display the new region immediately without animations.

Discussion

Calling this method updates the value in the region property and potentially other properties to reflect the new map region.

like image 9
Matt Avatar answered Nov 11 '22 21:11

Matt