How can I center a UIMapView around an array of MKAnnotation? - iphone

How can I center a UIMapView around an array of MKAnnotation?

First find the minimum and maximum latitude and longitude from your array of annotations. Then you can set the region:
MKCoordinateRegion extents;
extents.center.latitude = (maxLat + minLat)/2.0;
extents.center.longitude = (maxLong + minLong)/2.0;
extents.span.latitudeDelta = (maxLat - minLat);
extents.span.longitudeDelta = (maxLong - minLong);
MKCoordinateRegion fittedRegion = [mapView regionThatFits:extents];
[mapView setRegion:fittedRegion animated:YES];

Related

Calculating a MKMapRect that contains all pins

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.
/**
* 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.
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.
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];
}
As of iOS 7 there's a much simpler way to do this:
mapView.showAnnotations(mapView.showAnnotations, animated: true)
Hope this helps.

How to set the map to a particular location on map load

I am using a mapkit I found on Github to plot directions, but the problem is when the mapview loads it shows US at startup and then moves to the location where route is plotted. Is there any way to show Australia instead at the startup?
Thanks
I'm using this to initialize the Map View, and it works perfectly for me :
CLLocationCoordinate2D coord = {.latitude = XX.XXX, .longitude = X.XXX};
MKCoordinateSpan span = {.latitudeDelta = X.XX, .longitudeDelta = X.XX};
MKCoordinateRegion region = {coord, span};
Then, use the setRegion method for your MKMapView
Is it that you're looking for?
I do the following:
CLLocationCoordinate2D maxCoord = {-90.0f, -180.0f};
CLLocationCoordinate2D minCoord = {90.0f, 180.0f};
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center.longitude = (minCoord.longitude + maxCoord.longitude) / 2.0;
region.center.latitude = (minCoord.latitude + maxCoord.latitude) / 2.0;
region.span.longitudeDelta = (maxCoord.longitude - minCoord.longitude);
region.span.latitudeDelta = (maxCoord.latitude - minCoord.latitude);
[self.mainMapView setRegion:region animated:YES];
You can adjust the maxCoord and minCoord inputs to the values you'd like for Australia.

How to find the radius of visible MKMapView visible screen area?

I want to know the radius of visible area in iphone screen, as I will zoomout and zoom in the visible area will change, so I want to know the radius of that particular area, how can I do it?
Its not radius what is required.
You need to use the region parameter from mapView.
Check out apple docs, it is pretty much clear from those.
Go thru this tutorial. It will help you a lot
icode blog mapkit demo
specifically you need to set something like this..
MKCoordinateSpan span = [self coordinateSpanWithMapView:self centerCoordinate:centerCoordinate andZoomLevel:zoomLevel];
MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
[self setRegion:region animated:animated];
where span can be calculated as
- (MKCoordinateSpan)coordinateSpanWithMapView:(MKMapView *)mapView
centerCoordinate:(CLLocationCoordinate2D)centerCoordinate
andZoomLevel:(NSUInteger)zoomLevel
{
// convert center coordiate to pixel space
double centerPixelX = [self longitudeToPixelSpaceX:centerCoordinate.longitude];
double centerPixelY = [self latitudeToPixelSpaceY:centerCoordinate.latitude];
// determine the scale value from the zoom level
NSInteger zoomExponent = 20 - zoomLevel;
double zoomScale = pow(2, zoomExponent);
// scale the map’s size in pixel space
CGSize mapSizeInPixels = mapView.bounds.size;
double scaledMapWidth = mapSizeInPixels.width * zoomScale;
double scaledMapHeight = mapSizeInPixels.height * zoomScale;
// figure out the position of the top-left pixel
double topLeftPixelX = centerPixelX - (scaledMapWidth / 2);
double topLeftPixelY = centerPixelY - (scaledMapHeight / 2);
// find delta between left and right longitudes
CLLocationDegrees minLng = [self pixelSpaceXToLongitude:topLeftPixelX];
CLLocationDegrees maxLng = [self pixelSpaceXToLongitude:topLeftPixelX + scaledMapWidth];
CLLocationDegrees longitudeDelta = maxLng - minLng;
// find delta between top and bottom latitudes
CLLocationDegrees minLat = [self pixelSpaceYToLatitude:topLeftPixelY];
CLLocationDegrees maxLat = [self pixelSpaceYToLatitude:topLeftPixelY + scaledMapHeight];
CLLocationDegrees latitudeDelta = -1 * (maxLat - minLat);
// create and return the lat/lng span
MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
return span;
}
Cheers :)
I might be misunderstanding the question, but isn't it as simple as:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
CGFloat latD = mapView.region.span.latitudeDelta;
CGFloat lngD = mapView.region.span.longitudeDelta;
NSLog(#"This is the latitude delta of the visible map: %f", latD);
NSLog(#"This is the longitude delta of the visible map: %f", lngD);
}

How do can I add 2 miles to viewRegion.span.latitudeDelta?

How do can I add 2 miles to viewRegion.span.latitudeDelta ? How do can I calculate kTwoMiles?
MKCoordinateRegion viewRegion;
viewRegion.center = coordinateCentre;
viewRegion.span.latitudeDelta = latitudeMax - latitudeMin + kTwoMiles;
viewRegion.span.longitudeDelta = longitudeMax - longitudeMin + kTwoMiles;
MKCoordinateRegion adjustedRegion = [mapView regionThatFits:viewRegion];
[mapView setRegion:adjustedRegion animated:YES];
Basically this is relatively complicated because every degree has a different delta.
At Earth's equator you will have 111 km per degree and at the poles 0 km. (http://en.wikipedia.org/wiki/Longitude#Degree_length)
I decided to add 2 miles only in the latitudeDelta. Probably regionThatFits will resize longitudeDelta.
double kTwoMiles = (double)2/69;
MKCoordinateRegion viewRegion;
viewRegion.center = coordinateCentre;
viewRegion.span.latitudeDelta = latitudeMax - latitudeMin + kTwoMiles;
viewRegion.span.longitudeDelta = longitudeMax - longitudeMin;
MKCoordinateRegion adjustedRegion = [mapView regionThatFits:viewRegion];
[mapView setRegion:adjustedRegion animated:YES];`enter code here`

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?
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];
If you are only targeting iOS 7 or greater you can now use:
- (void)showAnnotations:(NSArray *)annotations
animated:(BOOL)animated
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];
}
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.
Modified Answer with all Perfect Working Code.
//Zooming the ploted Area
- (void)zoomToAnnotationsBounds:(NSArray *)latLongArray {
__block CLLocationDegrees minLatitude = DBL_MAX;
__block CLLocationDegrees maxLatitude = -DBL_MAX;
__block CLLocationDegrees minLongitude = DBL_MAX;
__block CLLocationDegrees maxLongitude = -DBL_MAX;
[latLongArray enumerateObjectsUsingBlock:^(NSString *latLongObj, NSUInteger latLongIdx, BOOL *stop) {
latLongObj = [latLongArray objectAtIndex:latLongIdx];
NSArray *latLongPoint = [latLongObj componentsSeparatedByString:#","];
double annotationLat = [[latLongPoint objectAtIndex:0] doubleValue];
double annotationLong = [[latLongPoint objectAtIndex:1] doubleValue];
minLatitude = fmin(annotationLat, minLatitude);
maxLatitude = fmax(annotationLat, maxLatitude);
minLongitude = fmin(annotationLong, minLongitude);
maxLongitude = fmax(annotationLong, maxLongitude);
}];
[self setMapRegionForMinLat:minLatitude minLong:minLongitude maxLat:maxLatitude maxLong:maxLongitude];
}
-(void) setMapRegionForMinLat:(double)minLatitude minLong:(double)minLongitude maxLat:(double)maxLatitude maxLong:(double)maxLongitude {
// pad our map by 10% around the farthest annotations
// 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.
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 = [regionsMapView regionThatFits:region];
[regionsMapView setRegion:scaledRegion animated:YES];
}