LatLonBox to CLLocationCoordinate2D - ios

I try to convert LatLonBox coordinate to CLLocationCoordinate2D. But I think somewhere is a mistake because die Image doesn't fit..
<LatLonBox>
<north>2.254403</north>
<south>-55.256903</south>
<east>161.994477</east>
<west>98.003023</west>
<rotation>0</rotation>
</LatLonBox>
Here my function to convert the LatLonBox coordinates.
** --- MapOverlay : NSObject --- **
-(id)initWithNorth:(double)north south:(double)south west:(double)west east:(double)east imageStr:(NSString*)imageStr{
self = [super init];
if (self) {
imageString = imageStr;
CLLocationCoordinate2D lowerLeftCoordinate = CLLocationCoordinate2DMake(south, west); // south, west
CLLocationCoordinate2D upperRightCoordinate = CLLocationCoordinate2DMake(north, east); //north,east
baseCoordinate = lowerLeftCoordinate;
MKMapPoint lowerLeft = MKMapPointForCoordinate(lowerLeftCoordinate);
MKMapPoint upperRight = MKMapPointForCoordinate(upperRightCoordinate);
mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);
}
return self;
}
** --- MapOverlayView : MKOverlayView --- **
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)ctx
{
CGImageRef imageReference = image.CGImage;
//Loading and setting the image
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
// We need to flip and reposition the image here
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextTranslateCTM(ctx, 0.0, -theRect.size.height);
//drawing the image to the context
CGContextDrawImage(ctx, theRect, imageReference);
}
** --- My Map --- **
//Adding the overlay to the map
MapOverlay * mapOverlay = [[MapOverlay alloc] initWithNorth:2.254403
south:-55.256903
west:161.994477
east:98.003023
imageStr:#"EarthquakeHazard.png"];
[self addOverlay:mapOverlay];
The thing is, if I change the north value. (-2.2544..) The image move down. Then it should not move up to??
Well, the functionality works fine and correct. My .kml file had wrong values...
Thanks for your help!!!

mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);
should probably be
mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, upperRight.y - lowerLeft.y);

Related

How to prevent rotation of all UIImage overlays with MapKit

I am placing overlays with MapOverlayRenderer. I first use a UIImage in a view over the map and can rotate it in its view and then convert the points (define boundingMapRect, coordinate, etc.) and then place it (Renderer/addOverlay) onto the map with the correct inserted image (CGContextDrawImage) and correct rotation. Each time that I create a new overlay [UIImage in view, rotate, convert points, addOverlay) it works just fine. But, on each successive overlay added to the Map, the code also rotates all of the other overlays already on the map. It seems as though my rotation code (below) is being applied to all of the overlays.
Should I be making an array which includes boundingMapRect and coordinates for each overlay - if so, should these be in viewcontroller?
Should I be naming each overlay [overlay.title? - how do I set it and call it?]
Appreciate some guidance - thanks.
MapOverlayView.h
#interface MapOverlayView ()
#property (nonatomic, strong) UIImage *overlayImage;
#end
#implementation MapOverlayView
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:
(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:
(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
IonQuestSingleton *tmp = [IonQuestSingleton sharedSingleton];
float new_angle = tmp.image_angle;
NSLog(#"%s%f","New angle is ",new_angle);
{
if (new_angle<90) {
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
NSLog(#"%s%f","New angle 90 is ",new_angle);//tick
}
else if (new_angle >90 && new_angle <=180)
{
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, 0.0);
CGContextDrawImage(context, theRect, imageReference);
NSLog(#"%s%f","New angle 180 is ",new_angle);//tick
}
else if (new_angle >180 && new_angle <=270)
{
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -theRect.size.width, 0.0);
CGContextDrawImage(context, theRect, imageReference);
NSLog(#"%s%f","New angle 270 is ",new_angle);//tick
}
else if (new_angle >270) {
CGContextRotateCTM(context,new_angle*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, -theRect.size.width,
-theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);//tick
NSLog(#"%s%f","New angle 360 is ",new_angle);
}
}
}
#end
MapOverlay.h
#import "MapOverlay.h"
#import "IonQuestSingleton.h"
#implementation MapOverlay
#define debug 1
#synthesize boundingMapRect;
#synthesize coordinate;
-(CLLocationCoordinate2D)coordinate {
//Image center point
IonQuestSingleton *tmp = [IonQuestSingleton sharedSingleton];
return CLLocationCoordinate2DMake(tmp.image_ctr_lat,
tmp.image_ctr_long);
}
- (instancetype)init
{
self = [super init];
if (self) {
IonQuestSingleton *tmp = [IonQuestSingleton sharedSingleton];
MKMapPoint upperLeft =
MKMapPointForCoordinate(CLLocationCoordinate2DMake(tmp.image_tl_lat,
tmp.image_tl_long));
MKMapPoint boundsLeft = ```MKMapPointForCoordinate(CLLocationCoordinate2DMake(tmp.image_tl_bounds_lat, tmp.image_tl_bounds_long));
boundingMapRect = MKMapRectMake(upperLeft.x, upperLeft.y,
fabs(upperLeft.x - boundsLeft.x), fabs(upperLeft.x - boundsLeft.x));
}
return self;
}
#end
Viewcontroller.h
-(void) loadOverlay
{
MapOverlay *overlay = [[MapOverlay alloc] init];
[self.mapView addOverlay:overlay level:MKOverlayLevelAboveLabels];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:
(id<MKOverlay>)overlay {
NSString *mapNameFromMVCPos = [NSString
stringWithFormat:#"%#%s",self.mapNameFromMVC,"Pos"];//not working
{
if ([overlay isKindOfClass:[MapOverlay class]]) {
UIImage *magicMountainImage = [UIImage
imageNamed:mapNameFromMVCPos];
MKOverlayRenderer *overlayView = [[MapOverlayView alloc]
initWithOverlay:overlay overlayImage:magicMountainImage];
return overlayView;
} etc [evaluate other overlaytypes]

Draw Flag as Overlay on MKMapView

I want to draw a country's flag on top of country boundary. I am using the coordinate data from here
The way I am adding overlay is
[mapView addOverlays:countryName];
Then rendering it as
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *renderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
renderer.fillColor = [ColorUtil colorFromHexString:#"#EE5A43" withAlpha:0.6];
renderer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:1.0];
renderer.lineWidth = 2;
return renderer;
}
return nil;
}
Now my idea was to draw the overlay with the flag. So I subclasses MKPolygonRenderer and added
#implementation MyMKOverlayRenderer
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
}
#end
Now check the two attached images. I was expecting the flag to be inside the boundary of the polygon. But looks like it is not happening. So may be my approach is wrong. Can some expert help me in correct direction.
I solved this problem. The key was to create a UIBezierPath and clip the overlay image.
I have posted the entire solution here
Now it looks like

face detection and image placing on it

i am trying to detect face in UIImageview and place image on mouth.
i have tried this method, but i can't transform CoreImage Coordination system to UIkit coordination system. here is my code:
code updated but still not functioning, just rotating view
#interface ProcessImageViewController ()
#end
#implementation ProcessImageViewController
#synthesize receivedImageData;
#synthesize renderImageView;
#synthesize viewToRender;
#synthesize preview;
#synthesize pancontrol;
#synthesize pinchcontrol;
#synthesize rotatecontrol;
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (void)viewDidLoad
{
[super viewDidLoad];
renderImageView.image = receivedImageData;
renderImageView.contentMode = UIViewContentModeScaleToFill;
}
-(void)tryAddCliparts
{
NSLog(#"button clicked");
[self performSelectorInBackground:#selector(markFaces:) withObject:renderImageView];
}
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGFloat firstX = recognizer.view.center.x;
CGFloat firstY = recognizer.view.center.y;
CGPoint translationPoint = [recognizer translationInView:self.view];
CGPoint translatedPoint = CGPointMake(firstX + translationPoint.x, firstY+ translationPoint.y);
CGFloat viewW = renderImageView.frame.size.width;
CGFloat viewH = renderImageView.frame.size.height;
if (translatedPoint.x<0 || translatedPoint.x>viewW)
translatedPoint.x = renderImageView.frame.origin.x;
if (translatedPoint.y<0|| translatedPoint.y>viewH)
translatedPoint.y = renderImageView.frame.origin.y;
recognizer.view.center = CGPointMake(translatedPoint.x, translatedPoint.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer {
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
recognizer.scale = 1;
}
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer {
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
recognizer.rotation = 0;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
-(void)markFaces:(UIImageView *)facePicture
{
NSLog(#"face detection started");
// draw a ci image from view
CIImage *image = [CIImage imageWithCGImage:facePicture.image.CGImage];
// Create face detector with high accuracy
CIDetector* detector = [CIDetector detectorOfType:CIDetectorTypeFace
context:nil options:[NSDictionary dictionaryWithObject:CIDetectorAccuracyHigh forKey:CIDetectorAccuracy]];
CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
transform = CGAffineTransformTranslate(transform,
0,-facePicture.bounds.size.height);
// Get features from the image
NSArray* features = [detector featuresInImage:image];
for(CIFaceFeature* faceFeature in features) {
// Transform CoreImage coordinates to UIKit
CGRect faceRect = CGRectApplyAffineTransform(faceFeature.bounds, transform);
UIImage *mustache = [UIImage imageNamed:#"mustacheok.png"];
UIImageView *mustacheview = [[UIImageView alloc] initWithImage:mustache];
mustacheview.contentMode = UIViewContentModeScaleAspectFill;
[mustacheview.layer setBorderColor:[[UIColor whiteColor] CGColor]];
[mustacheview.layer setBorderWidth:3];
[mustacheview addGestureRecognizer:pancontrol];
[mustacheview addGestureRecognizer:pinchcontrol];
[mustacheview addGestureRecognizer:rotatecontrol];
mustacheview.userInteractionEnabled=YES;
CGPoint mouthPos = CGPointApplyAffineTransform(faceFeature.mouthPosition, transform);
[mustacheview setFrame:CGRectMake(mouthPos.x, mouthPos.y, mustacheview.frame.size.width, mustacheview.frame.size.height)];
[viewToRender addSubview:mustacheview];
[viewToRender bringSubviewToFront:mustacheview];
}
}
#end
CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
transform = CGAffineTransformTranslate(transform,
0,-facePicture.bounds.size.height);
for (CIFaceFeature *faceFeature in features) {
// Transform CoreImage coordinates to UIKit
CGRect faceRect = CGRectApplyAffineTransform(faceFeature.bounds, transform);
if (faceFeature.hasMouthPosition) {
// Transform CoreImage coordinates to UIKit
CGPoint mouthPos = CGPointApplyAffineTransform(faceFeature.mouthPosition, transform);
}
}
the only thing I see wrong on your code is this:
[mustacheview setFrame:CGRectMake(mouthPos.x, mouthPos.y, mustacheview.frame.size.width, mustacheview.frame.size.height)];
you should use:
[mustacheview setCenter:mouthPos];
because the detector returns the mouth center point.
CoreImage uses the same coordinate system as CoreGraphics, a bottom left coordinate system, as opposed to the top left coordinate system of UIKit.
So you basically have to flip along the Y-axis (multiply with -1 and offset the height of the screen)
CGAffineTransformation flipVertical =
CGAffineTransformMake(1, 0, 0, -1, 0, self.bounds.size.height);

How to overlay a circle on an iOS map

I've got a radius and a location.
This is how I'm trying to get the bounding rectangle of the circle.
- (MKMapRect)boundingMapRect{
CLLocationCoordinate2D tmp;
MKCoordinateSpan radiusSpan = MKCoordinateRegionMakeWithDistance(self.coordinate, 0, self.radius).span;
tmp.latitude = self.coordinate.latitude - radiusSpan.longitudeDelta;
tmp.longitude = self.coordinate.longitude - radiusSpanSpan.longitudeDelta;
MKMapPoint upperLeft = MKMapPointForCoordinate(tmp);
MKMapRect bounds = MKMapRectMake(upperLeft.x, upperLeft.y, self.radius * 2, self.radius * 2);
return bounds;
}
MKMapRectMake(...) seems to want width and height measured in Map points. How do I convert the radius to that?
In the end I'm rendering it like this:
MKMapRect theMapRect = [self.overlay boundingMapRect];
CGRect theRect = [self rectForMapRect:theMapRect];
CGContextAddEllipseInRect(ctx, theRect);
CGContextFillPath(ctx);
The radius doesn't seem to equal meters on the map in the end and also the distance doesn't seem to be measured correctly. How to do it right?
I would be really thankful for every hint.
You should be using MKCircle instead. Do something like:
CLLocationDistance fenceDistance = 300;
CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(yourLocation.latitude, yourLocation.longitude);
MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
[yourMapView addOverlay: circle];
And adopt the MKMapViewDelegate Method below and do something like this:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
MKCircleView *circleView = [[[MKCircleView alloc] initWithCircle:(MKCircle *)overlay] autorelease];
circleView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.9];
return circleView;
}
For iOS7 :
Same as tdevoy in viewDidLoad :
CLLocationDistance fenceDistance = 300;
CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(yourLocation.latitude, yourLocation.longitude);
MKCircle *circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
[yourMapView addOverlay: circle];
But the delegate method (because mapView:viewForOverlay: is deprecated) :
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay
{
MKCircleRenderer *circleR = [[MKCircleRenderer alloc] initWithCircle:(MKCircle *)overlay];
circleR.fillColor = [UIColor greenColor];
return circleR;
}

Zooming MKMapView to fit annotation pins?

I am using MKMapView and have added a number of annotation pins to the map about a 5-10 kilometre area. When I run the application my map starts zoomed out to show the whole world, what is the best way to zoom the map so the pins fit the view?
EDIT:
My initial thinking would be to use MKCoordinateRegionMake and calculate the coordinate centre, longitudeDelta and latitudeDelta from my annotations. I am pretty sure this will work, but I just wanted to check I was not missing anything obvious.
Code added, BTW: FGLocation is an class that conforms to MKAnnotation, locationFake is an NSMutableArray of these objects. Comments are always welcome ....
- (MKCoordinateRegion)regionFromLocations {
CLLocationCoordinate2D upper = [[locationFake objectAtIndex:0] coordinate];
CLLocationCoordinate2D lower = [[locationFake objectAtIndex:0] coordinate];
// FIND LIMITS
for(FGLocation *eachLocation in locationFake) {
if([eachLocation coordinate].latitude > upper.latitude) upper.latitude = [eachLocation coordinate].latitude;
if([eachLocation coordinate].latitude < lower.latitude) lower.latitude = [eachLocation coordinate].latitude;
if([eachLocation coordinate].longitude > upper.longitude) upper.longitude = [eachLocation coordinate].longitude;
if([eachLocation coordinate].longitude < lower.longitude) lower.longitude = [eachLocation coordinate].longitude;
}
// FIND REGION
MKCoordinateSpan locationSpan;
locationSpan.latitudeDelta = upper.latitude - lower.latitude;
locationSpan.longitudeDelta = upper.longitude - lower.longitude;
CLLocationCoordinate2D locationCenter;
locationCenter.latitude = (upper.latitude + lower.latitude) / 2;
locationCenter.longitude = (upper.longitude + lower.longitude) / 2;
MKCoordinateRegion region = MKCoordinateRegionMake(locationCenter, locationSpan);
return region;
}
This is the one I found here that worked for me:
(EDIT: I have updated the solution using #Micah's suggestion to increase the pointRect by 0.1 to ensure the rect doesn't end up being infinitesimally small!)
MKMapRect zoomRect = MKMapRectNull;
for (id <MKAnnotation> annotation in mapView.annotations)
{
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
[mapView setVisibleMapRect:zoomRect animated:YES];
You could also update this to include the userLocation pin by replacing the first line with:
MKMapPoint annotationPoint = MKMapPointForCoordinate(mapView.userLocation.coordinate);
MKMapRect zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
You've got it right.
Find your maximum and minimum latitudes and longitudes, apply some simple arithmetic, and use MKCoordinateRegionMake.
For iOS 7 and above, use showAnnotations:animated:, from MKMapView.h:
// Position the map such that the provided array of annotations are all visible to the fullest extent possible.
- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);
Apple has added a new method for IOS 7 to simplify life a bit.
[mapView showAnnotations:yourAnnotationArray animated:YES];
You can easily pull from an array stored in the map view:
yourAnnotationArray = mapView.annotations;
and quickly adjust the camera too!
mapView.camera.altitude *= 1.4;
this won't work unless the user has iOS 7+ or OS X 10.9+ installed. check out custom animation here
I use this this code and works fine for me:
-(void)zoomToFitMapAnnotations:(MKMapView*)aMapView
{
if([aMapView.annotations count] == 0)
return;
CLLocationCoordinate2D topLeftCoord;
topLeftCoord.latitude = -90;
topLeftCoord.longitude = 180;
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for(MapViewAnnotation *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; // Add a little extra space on the sides
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; // Add a little extra space on the sides
region = [aMapView regionThatFits:region];
[mapView setRegion:region animated:YES];
}
In Swift use
mapView.showAnnotations(annotationArray, animated: true)
In Objective c
[mapView showAnnotations:annotationArray animated:YES];
I created an extension to show all the annotations using some code from here and there in swift. This will not show all annotations if they can't be shown even at maximum zoom level.
import MapKit
extension MKMapView {
func fitAllAnnotations() {
var zoomRect = MKMapRectNull;
for annotation in annotations {
let annotationPoint = MKMapPointForCoordinate(annotation.coordinate)
let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50), animated: true)
}
}
I have converted the answer by Rafael Moreira. The credit goes to him.
For those of you looking for the Swift version, here is the code:
func zoomToFitMapAnnotations(aMapView: MKMapView) {
guard aMapView.annotations.count > 0 else {
return
}
var topLeftCoord: CLLocationCoordinate2D = CLLocationCoordinate2D()
topLeftCoord.latitude = -90
topLeftCoord.longitude = 180
var bottomRightCoord: CLLocationCoordinate2D = CLLocationCoordinate2D()
bottomRightCoord.latitude = 90
bottomRightCoord.longitude = -180
for annotation: MKAnnotation in myMap.annotations as! [MKAnnotation]{
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)
}
var region: MKCoordinateRegion = MKCoordinateRegion()
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.4
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.4
region = aMapView.regionThatFits(region)
myMap.setRegion(region, animated: true)
}
Swift 3 This is de correct way for fit all annotations in map.
func zoomMapaFitAnnotations() {
var zoomRect = MKMapRectNull
for annotation in mapview.annotations {
let annotationPoint = MKMapPointForCoordinate(annotation.coordinate)
let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0)
if (MKMapRectIsNull(zoomRect)) {
zoomRect = pointRect
} else {
zoomRect = MKMapRectUnion(zoomRect, pointRect)
}
}
self.mapview.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(50, 50, 50, 50), animated: true)
}
#jowie's solution works great. One catch, if a map has only one annotation you'll end up with a fully zoomed out map. I added 0.1 to the rect make size to make sure setVisibleMapRect has something to zoom to.
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
If your are looking for iOS 8 and above, the simplest way to do it is to set the var layoutMargins: UIEdgeInsets { get set } of your map view before calling func showAnnotations(annotations: [MKAnnotation], animated: Bool)
For instance (Swift 2.1):
#IBOutlet weak var map: MKMapView! {
didSet {
map.delegate = self
map.mapType = .Standard
map.pitchEnabled = false
map.rotateEnabled = false
map.scrollEnabled = true
map.zoomEnabled = true
}
}
// call 'updateView()' when viewWillAppear or whenever you set the map annotations
func updateView() {
map.layoutMargins = UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25)
map.showAnnotations(map.annotations, animated: true)
}
Added this If loop within the for loop to exclude the users location pin from this method (required in my case, and maybe others)
if (![annotation isKindOfClass:[MKUserLocation class]] ) {
//Code Here...
}
For iOS 7 and above (Referring MKMapView.h) :
// Position the map such that the provided array of annotations are all visible to the fullest extent possible.
- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);
remark from – Abhishek Bedi
You just call:
[yourMapView showAnnotations:#[yourAnnotation] animated:YES];
var zoomRect: MKMapRect = MKMapRect.null
for annotation in mapView.annotations {
let annotationPoint = MKMapPoint(annotation.coordinate)
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.1, height: 0.1)
zoomRect = zoomRect.union(pointRect)
}
mapView.setVisibleMapRect(zoomRect, animated: true)
// Edited for swift 5
In Swift
var zoomRect = MKMapRectNull;
for i in 0..<self.map.annotations.count {
let annotation: MKAnnotation = self.map.annotations[i]
let annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
self.map.setVisibleMapRect(zoomRect, animated: true)
Thanks to jowie I've updated my old category to more elegant solution. Sharing complete, almost copy&paste ready solution
MKMapView+AnnotationsRegion.h
#import <MapKit/MapKit.h>
#interface MKMapView (AnnotationsRegion)
-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated;
-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding;
-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated;
-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding;
#end
MKMapView+AnnotationsRegion.m
#import "MKMapView+AnnotationsRegion.h"
#implementation MKMapView (AnnotationsRegion)
-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated{
[self updateRegionForCurrentAnnotationsAnimated:animated edgePadding:UIEdgeInsetsZero];
}
-(void)updateRegionForCurrentAnnotationsAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding{
[self updateRegionForAnnotations:self.annotations animated:animated edgePadding:edgePadding];
}
-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated{
[self updateRegionForAnnotations:annotations animated:animated edgePadding:UIEdgeInsetsZero];
}
-(void)updateRegionForAnnotations:(NSArray *)annotations animated:(BOOL)animated edgePadding:(UIEdgeInsets)edgePadding{
MKMapRect zoomRect = MKMapRectNull;
for(id<MKAnnotation> annotation in annotations){
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
[self setVisibleMapRect:zoomRect edgePadding:edgePadding animated:animated];
}
#end
Hope it helps someone and thanks again jowie!
- (void)zoomMapViewToFitAnnotationsWithExtraZoomToAdjust:(double)extraZoom
{
if ([self.annotations count] == 0) return;
int i = 0;
MKMapPoint points[[self.annotations count]];
for (id<MKAnnotation> annotation in [self annotations])
{
points[i++] = MKMapPointForCoordinate(annotation.coordinate);
}
MKPolygon *poly = [MKPolygon polygonWithPoints:points count:i];
MKCoordinateRegion r = MKCoordinateRegionForMapRect([poly boundingMapRect]);
r.span.latitudeDelta += extraZoom;
r.span.longitudeDelta += extraZoom;
[self setRegion: r animated:YES];
}
As Abhishek Bedi points out in a comment, For iOS7 forward the best way to do this is:
//from API docs:
//- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated NS_AVAILABLE(10_9, 7_0);
[self.mapView showAnnotations:self.mapView.annotations animated:YES];
For my personal project (prior to iOS7) I simply added a category on the MKMapView class to encapsulate the "visible area" functionality for a very common operation: setting it to be able to see all the currently-loaded annotations on the MKMapView instance (this includes as many pins as you might have placed, as well as the user's location). the result was this:
.h file
#import <MapKit/MapKit.h>
#interface MKMapView (Extensions)
-(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated;
-(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated;
#end
.m file
#import "MKMapView+Extensions.h"
#implementation MKMapView (Extensions)
/**
* Changes the currently visible portion of the map to a region that best fits all the currently loadded annotations on the map, and it optionally animates the change.
*
* #param animated is the change should be perfomed with an animation.
*/
-(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated
{
MKMapView * mapView = self;
NSArray * annotations = mapView.annotations;
[self ij_setVisibleRectToFitAnnotations:annotations animated:animated];
}
/**
* Changes the currently visible portion of the map to a region that best fits the provided annotations array, and it optionally animates the change.
All elements from the array must conform to the <MKAnnotation> protocol in order to fetch the coordinates to compute the visible region of the map.
*
* #param annotations an array of elements conforming to the <MKAnnotation> protocol, holding the locations for which the visible portion of the map will be set.
* #param animated wether or not the change should be perfomed with an animation.
*/
-(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated
{
MKMapView * mapView = self;
MKMapRect r = MKMapRectNull;
for (id<MKAnnotation> a in annotations) {
ZAssert([a conformsToProtocol:#protocol(MKAnnotation)], #"ERROR: All elements of the array MUST conform to the MKAnnotation protocol. Element (%#) did not fulfill this requirement", a);
MKMapPoint p = MKMapPointForCoordinate(a.coordinate);
//MKMapRectUnion performs the union between 2 rects, returning a bigger rect containing both (or just one if the other is null). here we do it for rects without a size (points)
r = MKMapRectUnion(r, MKMapRectMake(p.x, p.y, 0, 0));
}
[mapView setVisibleMapRect:r animated:animated];
}
#end
As you can see, I've added 2 methods so far: one for setting the visible region of the map to the one that fits all currently-loaded annotations on the MKMapView instance, and another method to set it to any array of objects.
So to set the mapView's visible region the code would then be as simple as:
//the mapView instance
[self.mapView ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:animated];
I hope it helps =)
All the answers on this page assume that the map occupies the full screen. I actually have a HUD display (ie buttons scattered at the top and bottom) that give information ontop of the map.. and so the algorithms on the page will display the pins all right, but some of them will appear under the HUD display buttons.
My solution zooms the map in to display the annotations in a subset of the screen and works for different screen sizes (ie 3.5" vs 4.0" etc):
// create a UIView placeholder and throw it on top of the original mapview
// position the UIView to fit the maximum area not hidden by the HUD display buttons
// add an *other* mapview in that uiview,
// get the MKCoordinateRegion that fits the pins from that fake mapview
// kill the fake mapview and set the region of the original map
// to that MKCoordinateRegion.
Here is what I did in code (note: i use NSConstraints with some helper methods to make my code work in different screen sizes.. while the code is quite readable.. my answer here explains it better.. it's basically the same workflow:)
// position smallerMap to fit available space
// don't store this map, it will slow down things if we keep it hidden or even in memory
[#[_smallerMapPlaceholder] mapObjectsApplyingBlock:^(UIView *view) {
[view removeFromSuperview];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[view setHidden:NO];
[self.view addSubview:view];
}];
NSDictionary *buttonBindingDict = #{ #"mapPlaceholder": _smallerMapPlaceholder};
NSArray *constraints = [#[#"V:|-225-[mapPlaceholder(>=50)]-176-|",
#"|-40-[mapPlaceholder(<=240)]-40-|"
] mapObjectsUsingBlock:^id(NSString *formatString, NSUInteger idx){
return [NSLayoutConstraint constraintsWithVisualFormat:formatString options:0 metrics:nil views:buttonBindingDict];
}];
[self.view addConstraints:[constraints flattenArray]];
[self.view layoutIfNeeded];
MKMapView *smallerMap = [[MKMapView alloc] initWithFrame:self.smallerMapPlaceholder.frame];
[_smallerMapPlaceholder addSubview:smallerMap];
MKCoordinateRegion regionThatFits = [smallerMap getRegionThatFits:self.mapView.annotations];
[smallerMap removeFromSuperview];
smallerMap = nil;
[_smallerMapPlaceholder setHidden:YES];
[self.mapView setRegion:regionThatFits animated:YES];
here is the code that gets region that fits:
- (MKCoordinateRegion)getRegionThatFits:(NSArray *)routes {
MKCoordinateRegion region;
CLLocationDegrees maxLat = -90.0;
CLLocationDegrees maxLon = -180.0;
CLLocationDegrees minLat = 90.0;
CLLocationDegrees minLon = 180.0;
for(int idx = 0; idx < routes.count; idx++)
{
CLLocation* currentLocation = [routes objectAtIndex:idx];
if(currentLocation.coordinate.latitude > maxLat)
maxLat = currentLocation.coordinate.latitude;
if(currentLocation.coordinate.latitude < minLat)
minLat = currentLocation.coordinate.latitude;
if(currentLocation.coordinate.longitude > maxLon)
maxLon = currentLocation.coordinate.longitude;
if(currentLocation.coordinate.longitude < minLon)
minLon = currentLocation.coordinate.longitude;
}
region.center.latitude = (maxLat + minLat) / 2.0;
region.center.longitude = (maxLon + minLon) / 2.0;
region.span.latitudeDelta = 0.01;
region.span.longitudeDelta = 0.01;
region.span.latitudeDelta = ((maxLat - minLat)<0.0)?100.0:(maxLat - minLat);
region.span.longitudeDelta = ((maxLon - minLon)<0.0)?100.0:(maxLon - minLon);
MKCoordinateRegion regionThatFits = [self regionThatFits:region];
return regionThatFits;
}
I've made a little modification of Rafael's code for MKMapView Category.
- (void)zoomToFitMapAnnotations {
if ([self.annotations count] == 0)
return;
CLLocationCoordinate2D topLeftCoord;
topLeftCoord.latitude = -90;
topLeftCoord.longitude = 180;
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for (id <MKAnnotation> annotation in self.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; // Add a little extra space on the sides
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; // Add a little extra space on the sides
[self setRegion:[self regionThatFits:region] animated:YES];
}
Based on answers above you can use universal method to zoom map to fit all annotations and overlays at the same time.
-(MKMapRect)getZoomingRectOnMap:(MKMapView*)map toFitAllOverlays:(BOOL)overlays andAnnotations:(BOOL)annotations includeUserLocation:(BOOL)userLocation {
if (!map) {
return MKMapRectNull;
}
NSMutableArray* overlaysAndAnnotationsCoordinateArray = [[NSMutableArray alloc]init];
if (overlays) {
for (id <MKOverlay> overlay in map.overlays) {
MKMapPoint overlayPoint = MKMapPointForCoordinate(overlay.coordinate);
NSArray* coordinate = #[[NSNumber numberWithDouble:overlayPoint.x], [NSNumber numberWithDouble:overlayPoint.y]];
[overlaysAndAnnotationsCoordinateArray addObject:coordinate];
}
}
if (annotations) {
for (id <MKAnnotation> annotation in map.annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
NSArray* coordinate = #[[NSNumber numberWithDouble:annotationPoint.x], [NSNumber numberWithDouble:annotationPoint.y]];
[overlaysAndAnnotationsCoordinateArray addObject:coordinate];
}
}
MKMapRect zoomRect = MKMapRectNull;
if (userLocation) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(map.userLocation.coordinate);
zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
}
for (NSArray* coordinate in overlaysAndAnnotationsCoordinateArray) {
MKMapRect pointRect = MKMapRectMake([coordinate[0] doubleValue], [coordinate[1] doubleValue], 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
return zoomRect;
}
And then:
MKMapRect mapRect = [self getZoomingRectOnMap:mapView toFitAllOverlays:YES andAnnotations:YES includeUserLocation:NO];
[mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0) animated:YES];
Swift 5+ in 2021
// Position the map such that the provided array of annotations are all visible to the fullest extent possible.
#available(iOS 7.0, *)
open func showAnnotations(_ annotations: [MKAnnotation], animated: Bool)
so you can just:
mapView.showAnnotations(mapView.annotations, animated: true)
Just sharing my observations on this:
If you are using xCode > 6 with "inferred" sizes for the screens (see "simulated metrics" on the file inspector) in storyboard, calling
- (void)showAnnotations:(NSArray *)annotations
animated:(BOOL)animated
in viewDidLoad will result in a too large zoom level on iPhones with 4 inches because the layout for the map is still on the size of the wider screens from the storyboard.
You can move your call to showAnnotations... to viewDidAppear. Then the size of the map has already been adjusted to the smaller screen of an iPhone 4.
Or alternatively change the value "inferred" in the file inspector under "simulated metrics" to iphone 4-inch.
You can select which shapes you want to show along with the Annotations.
extension MKMapView {
func setVisibleMapRectToFitAllAnnotations(animated: Bool = true,
shouldIncludeUserAccuracyRange: Bool = true,
shouldIncludeOverlays: Bool = true,
edgePadding: UIEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)) {
var mapOverlays = overlays
if shouldIncludeUserAccuracyRange, let userLocation = userLocation.location {
let userAccuracyRangeCircle = MKCircle(center: userLocation.coordinate, radius: userLocation.horizontalAccuracy)
mapOverlays.append(MKOverlayRenderer(overlay: userAccuracyRangeCircle).overlay)
}
if shouldIncludeOverlays {
let annotations = self.annotations.filter { !($0 is MKUserLocation) }
annotations.forEach { annotation in
let cirlce = MKCircle(center: annotation.coordinate, radius: 1)
mapOverlays.append(cirlce)
}
}
let zoomRect = MKMapRect(bounding: mapOverlays)
setVisibleMapRect(zoomRect, edgePadding: edgePadding, animated: animated)
}
}
extension MKMapRect {
init(bounding overlays: [MKOverlay]) {
self = .null
overlays.forEach { overlay in
let rect: MKMapRect = overlay.boundingMapRect
self = self.union(rect)
}
}
}
#"I'm not sure if this is because of some other factors in my implementation, but I find that showAnnotations doesn't do as close a zoom/fit of the annotations as the manual implementation does, so I've stuck with the manual one. – Ted Avery Apr 17 at 0:35"
I had the same problem, but then I tried doing showAnnotations twice (like below), and for some reason, it worked.
[mapView showAnnotations:yourAnnotationArray animated:YES];
[mapView showAnnotations:yourAnnotationArray animated:YES];
An iOS 7 compatible way is to use the following. First call showAnnotation in order to get a rectangle including all annotations. Afterwards create and UIEdgeInset with an top inset of the pin height. Thus you ensure to show the whole pin on the map.
[self.mapView showAnnotations:self.mapView.annotations animated:YES];
MKMapRect rect = [self.mapView visibleMapRect];
UIEdgeInsets insets = UIEdgeInsetsMake(pinHeight, 0, 0, 0);
[self.mapView setVisibleMapRect:rect edgePadding:insets animated:YES];
Put this in to your code accordingly:
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
{
id<MKAnnotation> mp = [annotationView annotation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate] ,250,250);
[mv setRegion:region animated:YES];
}

Resources