6

I'm trying to zoom in on a map that focuses on all the pins that were associated that map. I have that information saved off in my map property.

I am starting with this, but it's not working yet:

    double maxLatitude = 0;
    double minLatitude = 0;

    double maxLongitude = 0;
    double minLongitude = 0;

    for (MKAnnotation *address in self.map.locations) {
        // Latitude
        if ([address.latitude doubleValue] > 0) {
            maxLatitude = MAX(maxLatitude, [address.latitude doubleValue]);
        }
        else {
            minLatitude = MAX(abs(minLatitude), abs([address.latitude doubleValue]));
        }

        // Longitude
        if ([address.longitude doubleValue] > 0) {
            maxLongitude = MAX(maxLongitude, [address.longitude doubleValue]);
        }
        else {
            minLongitude = MAX(abs(minLongitude), abs([address.longitude doubleValue]));
        }
    }
    double centerLatitude = (maxLatitude - abs(minLatitude)) / 2;
    centerLatitude *= [self calculateSignWithFirstValue:maxLatitude secondValue:minLatitude];

    double centerLongitude = (maxLongitude - abs(minLongitude)) / 2;
    centerLongitude *= [self calculateSignWithFirstValue:maxLongitude secondValue:minLongitude];

// Create some MKMapRect with the coordinates?

I don't think I understand the MKMapRect though since when I try to do something like this:

    CLLocationCoordinate2D theOrigin = CLLocationCoordinate2DMake(32, -117);
    MKMapRect mapRect;
    mapRect.origin = MKMapPointForCoordinate(theOrigin);
    mapRect.size = MKMapSizeMake(10, 10);

I get put over the ocean instead of San Diego. Not sure what's going on with a MKMapRect.

Crystal
  • 28,460
  • 62
  • 219
  • 393

4 Answers4

15
/** 
 * Return a region covering all the annotations in the given array.
 * @param annotations Array of objects conforming to the <MKAnnotation> protocol.
 */
+(MKCoordinateRegion) regionForAnnotations:(NSArray*) annotations 
{
    double minLat=90.0f, maxLat=-90.0f;
    double minLon=180.0f, maxLon=-180.0f;

    for (id<MKAnnotation> mka in annotations) {
        if ( mka.coordinate.latitude  < minLat ) minLat = mka.coordinate.latitude;
        if ( mka.coordinate.latitude  > maxLat ) maxLat = mka.coordinate.latitude;
        if ( mka.coordinate.longitude < minLon ) minLon = mka.coordinate.longitude;
        if ( mka.coordinate.longitude > maxLon ) maxLon = mka.coordinate.longitude;
    }

    CLLocationCoordinate2D center = CLLocationCoordinate2DMake((minLat+maxLat)/2.0, (minLon+maxLon)/2.0);
    MKCoordinateSpan span = MKCoordinateSpanMake(maxLat-minLat, maxLon-minLon);
    MKCoordinateRegion region = MKCoordinateRegionMake (center, span);

    return region;
}

// usage
MKCoordinateRegion region = [XXXX regionForAnnotations:self.mapView.annotations];
[self.mapView setRegion:region animated:YES];

MKMapView zooms to discrete intervals, meaning if you zoom over a random region, it will choose the nearest zoom interval. This may have to do with the tiles resolution, but AFAIK is undocumented.

Jano
  • 62,815
  • 21
  • 164
  • 192
  • this might be a stupid question, but why do you set the starting points to 360 and -360? – Crystal Sep 02 '12 at 04:33
  • actually--i think values of longitude should be ±180°, value of latitude ±90° – nielsbot Sep 02 '12 at 04:47
  • Thanks nielsbot. @Crystal I set ±360° for no other reason than being a big enough number to force a new value in the loop. This has also the effect of returning the whole map when there are no annotations. – Jano Sep 02 '12 at 12:12
  • +1 for the undocumented behaviour of 'setRegion:' and the fact that it zooms to discrete zoom intervals. Was going crazy trying to figure out this behaviour. – Marchy Sep 03 '13 at 18:18
  • Suggested improvement. Use the KVC Collection operators for max and min to simplify that for loop. For example: maxLat = [[annotations valueForKeyPath:@"@max.latitude"] doubleValue]; – Roderic Campbell Sep 05 '13 at 16:44
  • @RodericCampbell That would be considerably slower. See here http://stackoverflow.com/a/15931719/292145 . We don't have to optimize in the first place, but this has to be taken into account. – Klaas Jan 18 '14 at 13:57
3

Just to explain the second part of your question about creating an MKMapRect over San Diego and ending up in the ocean...

First, the coordinate 32, -117 is only "near" San Diego.
Actually, it is several kilometers south, in the Pacific Ocean a few km off the west coast of Mexico.

Also note that in an MKMapRect, the origin is the top-left corner of the rectangle (not the center) so the resulting rectangle doesn't completely include the region around the coordinate you are specifying.

The other real problem is that the span size is set to MKMapSizeMake(10, 10).
MKMapSize uses MKMapPoint units (not degrees, meters, miles, km, etc).
The distance in meters a map point equals varies by latitude.

At latitude 32, 10 map points corresponds to 1.261110 meters (which you can calculate with the MapKit function MKMetersPerMapPointAtLatitude using 10.0 * MKMetersPerMapPointAtLatitude(32)).

So the map rect being created is positioned off the west coast of Mexico and it is about 1.26 x 1.26 meters in size. Therefore, you see nothing but ocean (until you zoom out a lot).

Though you could use the function mentioned above to convert meters to map points and create an MKMapRect, it would be easier to use the MKCoordinateRegionMakeWithDistance function which takes a regular coordinate (latitude and longitude in degrees), and the desired width and height in meters so all the calculations are handled by the map view.

1

I've got a good feeling Jano's answer works perfectly as well, but here is another solution for the sake of variety. It's what I typically use to zoom into the given annotations:

-(void)zoomToFitMapAnnotations:(MKMapView *)mapView {
    if([mapView.annotations count] == 0)
        return;

    CLLocationCoordinate2D topLeftCoord;
    topLeftCoord.latitude = -90;
    topLeftCoord.longitude = 180;

    CLLocationCoordinate2D bottomRightCoord;
    bottomRightCoord.latitude = 90;
    bottomRightCoord.longitude = -180;

    for(MKPointAnnotation *annotation in mapView.annotations)
    {
        topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
        topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);

        bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
        bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
    }

    MKCoordinateRegion region;
    region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;
    region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; 

    region = [mapView regionThatFits:region];
    [mapView setRegion:region animated:YES];
}
justin
  • 5,811
  • 3
  • 29
  • 32
1

As of iOS 7 there's a much simpler way to do this:

mapView.showAnnotations(mapView.showAnnotations, animated: true)

Hope this helps.

pob21
  • 1,918
  • 2
  • 16
  • 17