11

I'm trying to zoom to fit annotations on a map while locking the center and providing some insets.

- (void)fitAnnotations:(NSArray *)annotations edgeInsets:(UIEdgeInsets)insets
{
    CLLocationCoordinate2D originalCenter = self.centerCoordinate;
    MKMapRect mapRect = MKMapRectNull;

    for (id<MKAnnotation> annotation in annotations) {
        MKMapPoint p = MKMapPointForCoordinate([annotation coordinate]);
        mapRect = MKMapRectUnion(mapRect, MKMapRectMake(p.x, p.y, 0, 0));
    }

    mapRect = [self mapRectThatFits:mapRect edgePadding:insets];
    MKCoordinateRegion mapRegion = MKCoordinateRegionForMapRect(mapRect);

    // we now try to go back to the original center, while increasing the span by neccessary amount
    MKCoordinateSpan centeringDelta = MKCoordinateSpanMake(fabs(mapRegion.center.latitude - originalCenter.latitude), fabs(mapRegion.center.longitude - originalCenter.longitude));
    mapRegion.center = originalCenter;
    mapRegion.span.latitudeDelta += centeringDelta.latitudeDelta * 2.0;
    mapRegion.span.longitudeDelta += centeringDelta.longitudeDelta * 2.0;
    mapRegion = [self regionThatFits:mapRegion];
    [self setRegion:mapRegion animated:YES];
}

The first part of the code here works as expected: it zooms to fit while respecting the insets. However, it shifts the center.

I try to re-adjust the center after that, but it fails. I'm not sure if my math on the re-centering is correct.

Snowman
  • 31,411
  • 46
  • 180
  • 303
  • Have you tried using `MKCoordinateRegionMake` to create the new map region? That will allow you to pass in the `centerCoordinate` and a `span` – rmp Apr 29 '15 at 16:05
  • @rmp Right but then when you shift the center, some of the annotations you made sure fit are now out of bounds. – Snowman Apr 29 '15 at 16:06
  • you should be able to pass in the original center and not shift it – rmp Apr 29 '15 at 16:07
  • If there is an annotation on (0,0) and I shift the center by (0, -5), then that top annotation will now be at (0, -5). This is what I'm trying to avoid. – Snowman Apr 29 '15 at 18:14
  • if you pass your newly calculated `span` with the `centerCoordinate` the `MKCoordinateRegionMake` should take care of this – rmp Apr 29 '15 at 18:22
  • I'm not sure if we're on the same page. If you have the time please articulate with an answer. – Snowman Apr 29 '15 at 18:28
  • Your question is similar to [this one](http://stackoverflow.com/questions/26416587/fitting-annotations-on-a-mkmapview-while-keeping-user-position-centered) and I think the solution is the same (except you use originalCenter instead of the user location). –  Apr 29 '15 at 19:43
  • @Anna yes but without the insets, which is an important point – Snowman Apr 29 '15 at 20:07
  • MKMapView's setRegion rarely sets the exact region you specify. It's very annoying - I filed a bug on it years ago. If I remember correctly, the best you can do is to round the rectangle size to the nearest zoom multiple. – EricS May 06 '15 at 00:33

4 Answers4

1

The first part of your code that calculates the bounding map rect that fits the annotations is OK.

Only the adjustment of that "minimal" map rect so that the "locked" center is actually in the center needs to be corrected.

The main problem, I believe, is that the code in the question is adjusting the span to both re-center the region and account for the insets after calling mapRectThatFits: (which will itself already give a slightly modified version of the rect you actually requested).

Instead, your code should only calculate the actual, minimal rect it wants and then finally call setVisibleMapRect:edgePadding:animated: and let the map view figure out both the "rect that fits" and the insets.

Please try the following:

- (void)fitAnnotations:(NSArray *)annotations edgeInsets:(UIEdgeInsets)insets
{
    MKMapPoint centerMapPoint = MKMapPointForCoordinate(originalCenter);

    //--- First create minimal bounding map rect to tightly fit annotations...
    MKMapRect minimalMapRect = MKMapRectNull;

    for (id<MKAnnotation> annotation in annotations) {
        MKMapPoint annMapPoint = MKMapPointForCoordinate(annotation.coordinate);
        minimalMapRect = MKMapRectUnion(minimalMapRect, MKMapRectMake(annMapPoint.x, annMapPoint.y, 0, 0));
    }


    //--- Now create adjusted map rect so center coordinate is in the center...

    //distance of desired center from minimal left edge...
    double centerXOffset = centerMapPoint.x - minimalMapRect.origin.x;

    //raw amount width needs to be adjusted to get center in center...
    //negative/positive indicates whether center is in left/right half
    double widthOffset = 2.0 * centerXOffset - minimalMapRect.size.width;

    //add absolute value of raw width offset to minimal width...
    double adjustedWidth = minimalMapRect.size.width + fabs(widthOffset);

    //distance of desired center from minimal top edge...
    double centerYOffset = centerMapPoint.y - minimalMapRect.origin.y;

    //raw amount height needs to be adjusted to get center in center...
    //negative/positive indicates whether center is in top/bottom half
    double heightOffset = 2.0 * centerYOffset - minimalMapRect.size.height;

    //add absolute value of raw height offset to minimal height...
    double adjustedHeight = minimalMapRect.size.height + fabs(heightOffset);

    //adjust origin if necessary (if center is in top/left half)...
    MKMapPoint adjustedOrigin = minimalMapRect.origin;
    if ((centerXOffset / minimalMapRect.size.width) < 0.5)
    {
        adjustedOrigin.x = adjustedOrigin.x + widthOffset;
    }
    if ((centerYOffset / minimalMapRect.size.height) < 0.5)
    {
        adjustedOrigin.y = adjustedOrigin.y + heightOffset;
    }

    //create adjusted MKMapRect...
    MKMapRect adjustedMapRect = MKMapRectMake(adjustedOrigin.x, adjustedOrigin.y, adjustedWidth, adjustedHeight);


    //--- Apply the adjusted map rect with insets to map view...
    [mapView setVisibleMapRect:adjustedMapRect edgePadding:insets animated:YES];
}
  • The issue however is that `setVisibleMapRect:edgePadding` using insets will change the original center so that the rect is shifted by those insets. I tried this code and every time I call it, the center changes, however, all the annotations do fit on the screen. The center moves north, while the annotations move south, so the map zooms out, but both are still visible. What should happen is that the center stays fixed and the map zooms out/in to accommodate fitting annotations. – Snowman May 06 '15 at 14:18
0

Try something like this, where you use your calculated mapRect to create the new region with your originalCenter via the MKCoordinateRegionMake method

MKCoordinateRegion mapRegion = MKCoordinateRegionForMapRect(mapRect);
mapRegion = MKCoordinateRegionMake(originalCenter, mapRegion.span);
mapView.region = mapRegion;
rmp
  • 3,503
  • 1
  • 17
  • 26
  • This doesn't work. The rect is computed to fit all the annotations, right to the edge. When you realign the center to a different coordinate by delta, that means you're shifting the entire rect by that delta, which means it might exclude the fitted annotations. – Snowman Apr 29 '15 at 20:12
  • I believe you are calculating mapRect incorrectly, You need to get the distance of the furthest point from the center and use that to set your span. – rmp Apr 29 '15 at 20:35
0

Try this.

MKMapPoint center = MKMapPointForCoordinate(self.mapView.centerCoordinate);

double maxX = 0;
double maxY = 0;
for (MKPointAnnotation *a in self.mapView.annotations)
{
    MKMapPoint p = MKMapPointForCoordinate(a.coordinate);
    double deltaX = fabs(center.x - p.x);
    double deltaY = fabs(center.y - p.y);
    maxX = MAX(maxX, deltaX);
    maxY = MAX(maxY, deltaY);
}


MKMapRect rect = MKMapRectMake(center.x - maxX, center.y - maxY, maxX * 2, maxY * 2);
rect = [self.mapView mapRectThatFits:rect edgePadding:UIEdgeInsetsMake(20, 20, 20, 20)];

[self.mapView setVisibleMapRect:rect animated:1];
PowHu
  • 2,159
  • 16
  • 17
  • Anytime you do `mapRectThatFits:rect edgePadding:`, the center is disregarded. This won't work. – Snowman May 12 '15 at 16:54
0

@moby I am thinking of a different approach. How about taking the maps centre location as you already did. Now calculate distance to each annotation from this centre coordinate till you find the longest annotation (say 'requiredDistance' ). Get a new map rect with all your annotations plotted with same centre using below code:

    MKCircle *circleLine = [MKCircle circleWithCenterCoordinate:self.centerCoordinate radius:requiredDistance];
    [self.mapView setVisibleMapRect:circleLine.boundingMapRect];

Coming to your insets what ever insets you wanted to apply should be applied to your 'requiredDistace' variable in such a way that your 'requiredDistance' variable has a value always greater than or equal to the distance between your centre coordinate and your longest annotation to make sure all the annotations are always visible.

Rajesh
  • 850
  • 9
  • 18
  • How would you apply the insets to requiredDistance? That's really what we're trying to figure out here. – Snowman May 12 '15 at 16:53