MKMapRectMake how to zoom out after setup - ios

I use MKMapRectMake to mark north east and south west to display a region. Here's how I do that:
routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
[self.mapView setVisibleMapRect:routeRect];
After I set up this display region, how can I zoom out the map a little? What is the best way to do this?
UPDATE
This is code that I use to get rect for setVisibleMapRect function:
for(Path* p in ar)
{
self.routeLine = nil;
self.routeLineView = nil;
// while we create the route points, we will also be calculating the bounding box of our route
// so we can easily zoom in on it.
MKMapPoint northEastPoint;
MKMapPoint southWestPoint;
// create a c array of points.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * ar.count);
for(int idx = 0; idx < ar.count; idx++)
{
Path *m_p = [ar objectAtIndex:idx];
[NSCharacterSet characterSetWithCharactersInString:#","]];
CLLocationDegrees latitude = m_p.Latitude;
CLLocationDegrees longitude = m_p.Longitude;
// create our coordinate and add it to the correct spot in the array
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);
MKMapPoint point = MKMapPointForCoordinate(coordinate);
// adjust the bounding box
// if it is the first point, just use them, since we have nothing to compare to yet.
if (idx == 0) {
northEastPoint = point;
southWestPoint = point;
}
else
{
if (point.x > northEastPoint.x)
northEastPoint.x = point.x;
if(point.y > northEastPoint.y)
northEastPoint.y = point.y;
if (point.x < southWestPoint.x)
southWestPoint.x = point.x;
if (point.y < southWestPoint.y)
southWestPoint.y = point.y;
}
pointArr[idx] = point;
}
// create the polyline based on the array of points.
self.routeLine = [MKPolyline polylineWithPoints:pointArr count:ar.count];
_routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);
// clear the memory allocated earlier for the points
free(pointArr);
[self.mapView removeOverlays: self.mapView.overlays];
// add the overlay to the map
if (nil != self.routeLine) {
[self.mapView addOverlay:self.routeLine];
}
// zoom in on the route.
[self zoomInOnRoute];
}

Try this: (Edit)
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta = 0.01;
span.longitudeDelta = 0.01;
CLLocationCoordinate2D zoomLocation = newLocation.coordinate;
region.center = zoomLocation;
region.span = span;
region = [mapViewObject regionThatFits:region];
[mapViewObject setRegion:region animated:NO];

You can use this custom function to center the Map around two points
- (void)centerMapAroundSourceAndDestination
{
MKMapRect rect = MKMapRectNull;
MKMapPoint sourcePoint = MKMapPointForCoordinate(southWestPoint);
rect = MKMapRectUnion(rect, MKMapRectMake(sourcePoint.x, sourcePoint.y, 0, 0));
MKMapPoint destinationPoint = MKMapPointForCoordinate(_northEastPoint);
rect= MKMapRectUnion(rect, MKMapRectMake(destinationPoint.x, destinationPoint.y, 0, 0));
MKCoordinateRegion region = MKCoordinateRegionForMapRect(rect);
[_mapView setRegion:region animated:YES];
}

So in that case you need to find Centroid of a polygon and then pass that centroid values to this method so it would zoom to center of polygon i.e Centroid.
- (void)zoomMapView:(MKMapView *)mapview withLatitude:(Float32 )latitude andLongitude:(Float32 )longitude {
MKCoordinateRegion region;
region.span.latitudeDelta =0.005; //Change values to zoom. lower the value to zoom in and vice-versa
region.span.longitudeDelta = 0.009;//Change values to zoom. lower the value to zoom in and vice-versa
CLLocationCoordinate2D location;
location.latitude = latitude; // Add your Current Latitude here.
location.longitude = longitude; // Add your Current Longitude here.
region.center = location;
[mapview setRegion:region];
}
To use this method you need to pass three thing mapView, latitude and longitude i.e Position where to zoom.

how can I zoom out the map a little?
Unfortunatley MkMapView setRegion behaves so strange that this does not work on iPhone. (ios 6.1.3)
It works on iPad (ios 6.1.3)
setRegion and setVisibleMapRect
both changes the zoom factor only by steps of two. So you cannot programmatically zoom out by e.g 10%. Although Apple maps are vector based, they still snap the next higher zoom level that (would) fit the map tile pixels 1:1. Maybe to be compatible to map satellite display mode, which uses prerendered bitmaps.
Although bot methods should only correct the aspect ratio if you provided one of the lat/lon spans not perfectly, they additonally snap to said zoom levels.
Try it out: display map, then on button press: get the current region, make the longitude span 10% bigger (factor 1.1), set the region, then read it back, you will see on iphone simu and on iphone4 device the longitude span is now double the size, instead of factor 1.1.
Till today there exists no good solution.
Shame on you Apple.

Related

Update region to be around Annotations

I have a mapView with some annotations and I'd like to center the map around the annotations. I have the following code:
- (void)updateRegion {
self.needUpdateRegion = NO;
CGRect boundingRect;
BOOL started = NO;
for (id <MKAnnotation> annotation in self.mapView.annotations){
CGRect annotationRect = CGRectMake(annotation.coordinate.longitude, annotation.coordinate.latitude, 0, 0);
if (!started) {
started = YES;
boundingRect = annotationRect;
} else {
boundingRect = CGRectUnion(boundingRect, annotationRect);
}
} if (started) {
boundingRect = CGRectInset(boundingRect, -0.2, -0.2);
if ((boundingRect.size.width >20) && (boundingRect.size.height >20)) {
MKCoordinateRegion region;
region.center.latitude = boundingRect.origin.x + boundingRect.size.width /2;
region.center.longitude = boundingRect.origin.y + boundingRect.size.height / 2;
region.span.latitudeDelta = boundingRect.size.width;
region.span.longitudeDelta = boundingRect.size.height;
[self.mapView setRegion:region animated:YES];
}
}
}
it gets executed in viewDidAppear to make the "sliding effect":
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.needUpdateRegion) [self updateRegion];
}
When I run the app it doesn't do anything and just shows the US.
The annotations are shown (in Europe).
Assuming that updateRegion gets called in the first place (make sure needUpdateRegion is initialized to YES), there are two main problems with the updateRegion method:
It only calls setRegion if the resulting bounding map rect's width and height are 20. Since you are doing the calculations using latitude and longitude degrees, this means setRegion will only get called if the resulting bounding map rect is more than 20 degrees latitude/longitude wide/high. It's not clear if this is what you intended.
The region properties are being set backwards. In the calculation of the bounding map rect, the x values are set to the longitude and the y values are set to the latitude. But when setting region.center.latitude, it is using boundingRect.origin.x instead of boundingRect.origin.y. This applies to the other properties as well so the code there should be:
region.center.longitude = boundingRect.origin.x + boundingRect.size.width /2;
region.center.latitude = boundingRect.origin.y + boundingRect.size.height / 2;
region.span.longitudeDelta = boundingRect.size.width;
region.span.latitudeDelta = boundingRect.size.height;
Note that iOS 7 provides a new convenient method showAnnotations:animated: to automatically show the annotations so you don't have to calculate the region yourself.
So in updateRegion you could do the following:
- (void)updateRegion {
self.needUpdateRegion = NO;
//if showAnnotations:animated: is available, use it...
if ([mapView respondsToSelector:#selector(showAnnotations:animated:)])
{
[self.mapView showAnnotations:mapView.annotations animated:YES];
return;
}
//calculate region manually...
}

How to setRegion with google maps sdk for iOS?

How to setRegion with google maps sdk for iOS?
I want set zoom for location and radius of markers.
UPDATE:
The original answer below is obsolete as of version 1.2 of the SDK - you can now use the fitBounds: method of the GMSCameraUpdate class:
https://developers.google.com/maps/documentation/ios/reference/interface_g_m_s_camera_update
Original answer:
The MKMapPoint type in MapKit defines a 2D projection of a map. Although the actual values of the projection are meant to be opaque, they turn out to be equivalent to pixels at zoom level 20. This can be used to convert lat/lon values to pixels, and therefore a scale, and therefore a zoom level.
Start by defining two locations which specify the bounds of the region you want to display. These could be opposite corners of the bounding box, or just two locations, for example:
CLLocationCoordinate2D location1 =
CLLocationCoordinate2DMake(-33.8683, 151.2086); // Sydney
CLLocationCoordinate2D location2 =
CLLocationCoordinate2DMake(-31.9554, 115.8585); // Perth
If you have more than two points that you want to include, you could calculate the bounds of them yourself. This can also be done using GMSCoordinateBounds, for example:
GMSCoordinateBounds* bounds =
[[GMSCoordinateBounds alloc]
initWithCoordinate: CLLocationCoordinate2DMake(-33.8683, 151.2086) // Sydney
andCoordinate: CLLocationCoordinate2DMake(-31.9554, 115.8585)]; // Perth
bounds = [bounds including:
CLLocationCoordinate2DMake(-12.4667, 130.8333)]; // Darwin
CLLocationCoordinate2D location1 = bounds.southWest;
CLLocationCoordinate2D location2 = bounds.northEast;
Next, you need to get the size of the map view in points. You could use this:
float mapViewWidth = _mapView.frame.size.width;
float mapViewHeight = _mapView.frame.size.height;
But that will only work if you've already created the map view. Also, if you're using the sample code in the getting started guide, the frame is set to CGRectZero, as the actual size will be set later to fill the screen. In these cases if you're creating a full-screen map then you might want something like this:
float mapViewWidth = [UIScreen mainScreen].applicationFrame.size.width;
float mapViewHeight = [UIScreen mainScreen].applicationFrame.size.height;
Otherwise, use the size which you're creating your map view with.
Now you have the info necessary to calculate the camera position:
MKMapPoint point1 = MKMapPointForCoordinate(location1);
MKMapPoint point2 = MKMapPointForCoordinate(location2);
MKMapPoint centrePoint = MKMapPointMake(
(point1.x + point2.x) / 2,
(point1.y + point2.y) / 2);
CLLocationCoordinate2D centreLocation = MKCoordinateForMapPoint(centrePoint);
double mapScaleWidth = mapViewWidth / fabs(point2.x - point1.x);
double mapScaleHeight = mapViewHeight / fabs(point2.y - point1.y);
double mapScale = MIN(mapScaleWidth, mapScaleHeight);
double zoomLevel = 20 + log2(mapScale);
GMSCameraPosition *camera = [GMSCameraPosition
cameraWithLatitude: centreLocation.latitude
longitude: centreLocation.longitude
zoom: zoomLevel];
You can then initialize the map view with this camera, or set the map view to this camera.
For this code to compile, you will need to add the MapKit framework to your project, and then also import it:
#import <MapKit/MapKit.h>
Note that this code doesn't handle wrap-around if your coordinates span across the date line. For example if you tried using this code with Tokyo and Hawaii, instead of displaying an area of the Pacific, it will try to display almost the entire world. In portrait mode it's not possible to zoom out far enough to see Hawaii on the left and Tokyo on the right, and so the map ends up centred on Africa with neither location visible. You could modify the above code to handle the wrap-around at the date line if you wanted to.
UPDATE
All issues were fixed in the latest version of Google maps (1.5). Standard method [mapView_ animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds]]; can noow be used instead of the code below
ORIGINAL ANSWER
[GMSCameraUpdate fitBounds] does not give accurate results in my version of the SDK (1.2.0). I am using the code below instead of it. The formulae are taken from the Mercator Projection. The world is latitudonally bounded at 85 degrees as per Google Documentation.
#import <stdlib.h>
-(void) animateBoundsNorth:(CGFloat)north West:(CGFloat)west South:(CGFloat)south East:(CGFloat)east Padding:(int)padding {
CGFloat northRad = north * M_PI / 180.0;
CGFloat northProj = logf(tanf(M_PI_4 + northRad/2.0));
CGFloat southRad = south * M_PI / 180.0;
CGFloat southProj = logf(tanf(M_PI_4 + southRad/2.0));
CGFloat topRad = 85.0 * M_PI / 180.0;
CGFloat topProj = logf(tanf(M_PI_4 + topRad/2.0));
CGFloat zoomLat = log2f((mapView_.bounds.size.height - padding * 2) * 2 * topProj /(northProj - southProj)) - 8;
CGFloat zoomLon = log2f((mapView_.bounds.size.width - padding * 2) * 360/(east - west)) - 8;
GMSCameraUpdate *update = [GMSCameraUpdate setTarget:CLLocationCoordinate2DMake((north+south)/2.0, (west+east)/2.0) zoom:MIN(zoomLat, zoomLon)];
[mapView_ animateWithCameraUpdate:update];
}
Saxun Druce's answer is really good. But in addition, if you want to calculate a radius from any location you can do that with the following code:
CLLocationCoordinate2D center = CLLocationCoordinate2DMake([currentLocationLat doubleValue],[currentLocationLong doubleValue]);
float radius = 25*1000; //radius in meters (25km)
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, radius*2, radius*2);
CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta/2, region.center.longitude - region.span.longitudeDelta/2);
CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta/2, region.center.longitude + region.span.longitudeDelta/2);
GMSCoordinateBounds* bounds = [[GMSCoordinateBounds alloc]
initWithCoordinate: northEast
andCoordinate: southWest];
If you have latitude and longitude of 'far-left' and 'near-right' corners of the google map ,you can display the data in swift using below code
var region:GMSVisibleRegion = GMSVisibleRegion()
region.nearLeft = CLLocationCoordinate2DMake(nearleflat, nearleftlong)
region.farRight = CLLocationCoordinate2DMake(fareastlat,fareastlong)
let bounds = GMSCoordinateBounds(coordinate: region.nearLeft,coordinate: region.farRight)
let camera = googleMapView.cameraForBounds(bounds, insets:UIEdgeInsetsZero)
googleMapView.camera = camera;
This link may also be helpful for related things.
I currently using this method.
self.markers is a dictionary with markers stored by a location ID, self.currentLocation is a CLLocation2D, and self.mapView is a GMSMapView.
The maths here is a check on whether to match the sizes on the width or the height, and then a calculation of the zoom based on the fact that x1 / pow(2, zoom1) = x2 / pow(2, zoom2)", leading to zoom2 = log2(x2 * pow(2, self.mapView.camera.zoom) / x1).
- (void)fitMarkers
{
if (2 > self.markers.count)
{
[self.mapView animateToCameraPosition:[GMSCameraPosition cameraWithTarget:self.currentLocation.coordinate zoom:kZoom]];
return;
}
NSArray* markers = self.markers.allValues;
GMSCoordinateBounds* markerBounds = [[GMSCoordinateBounds alloc] initWithCoordinate:((id<GMSMarker>)markers[0]).position andCoordinate:((id<GMSMarker>)markers[1]).position];
for (id<GMSMarker> marker in markers)
{
markerBounds = [markerBounds including:marker.position];
}
// get marker bounds in points
CGPoint markerBoundsTopLeft = [self.mapView.projection pointForCoordinate:CLLocationCoordinate2DMake(markerBounds.northEast.latitude, markerBounds.southWest.longitude)];
CGPoint markerBoundsBottomRight = [self.mapView.projection pointForCoordinate:CLLocationCoordinate2DMake(markerBounds.southWest.latitude, markerBounds.northEast.longitude)];
// get user location in points
CGPoint currentLocation = [self.mapView.projection pointForCoordinate:self.currentLocation.coordinate];
CGPoint markerBoundsCurrentLocationMaxDelta = CGPointMake(MAX(fabs(currentLocation.x - markerBoundsTopLeft.x), fabs(currentLocation.x - markerBoundsBottomRight.x)), MAX(fabs(currentLocation.y - markerBoundsTopLeft.y), fabs(currentLocation.y - markerBoundsBottomRight.y)));
// the marker bounds centered on self.currentLocation
CGSize centeredMarkerBoundsSize = CGSizeMake(2.0 * markerBoundsCurrentLocationMaxDelta.x, 2.0 * markerBoundsCurrentLocationMaxDelta.y);
// inset the view bounds to fit markers
CGSize insetViewBoundsSize = CGSizeMake(self.mapView.bounds.size.width - kMarkerSize / 2.0 - kMarkerMargin, self.mapView.bounds.size.height - kMarkerSize / 2.0 - kMarkerSize);
CGFloat x1;
CGFloat x2;
// decide which axis to calculate the zoom level with by comparing the width/height ratios
if (centeredMarkerBoundsSize.width / centeredMarkerBoundsSize.height > insetViewBoundsSize.width / insetViewBoundsSize.height)
{
x1 = centeredMarkerBoundsSize.width;
x2 = insetViewBoundsSize.width;
}
else
{
x1 = centeredMarkerBoundsSize.height;
x2 = insetViewBoundsSize.height;
}
CGFloat zoom = log2(x2 * pow(2, self.mapView.camera.zoom) / x1);
GMSCameraPosition* camera = [GMSCameraPosition cameraWithTarget:self.currentLocation.coordinate zoom:zoom];
[self.mapView animateToCameraPosition:camera];
}
As per new release of GoogleMaps iOS sdk 1.9.2,
We can set up using Camera's position, zoom level, viewingAngle.
GMSCameraPosition* camera = [GMSCameraPosition cameraWithLatitude:28.6100
longitude:77.2300
zoom:14.0
bearing:0
viewingAngle:0.00];
self.mapView = [GMSMapView mapWithFrame:CGRectMake(0, 45, self.view.frame.size.width, self.view.frame.size.height - 45) camera:camera];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
mapView.delegate = self;
mapView.myLocationEnabled = YES;
mapView.mapType = kGMSTypeTerrain;
mapView.settings.compassButton = YES;
mapView.settings.myLocationButton = YES;
[self.mapView setMinZoom:10 maxZoom:18];
GMSMarker* marker = [[GMSMarker alloc] init];
marker.position = CLLocationCoordinate2DMake(28.6100, 77.2300);
marker.title = #"New Delhi";
marker.snippet = #"Capital Of India";
marker.map = self.mapView;
marker.appearAnimation = kGMSMarkerAnimationPop;
marker.icon = [GMSMarker markerImageWithColor:[UIColor grayColor]];
[self.view addSubview:self.mapView];
For Further reference see this PDF document.
you can also set minimum and maximum Zoom level as per your need:
[self.mapView setMinZoom:10 maxZoom:30];
Hope this solves the problem.
I searched through the header files of the framework and only found the interface that could be used for the following code which could be a start. The problem here is that i can not find any imports of GMSCoordinateBounds in one of the other headers so i can not find a way to display this region in GMSMapView.
GMSVisibleRegion region;
region.farLeft = CLLocationCoordinate2DMake(farLeftLatitude, farLeftlongitude);
region.farRight = CLLocationCoordinate2DMake(farRightlatitude, farRightlongitude);
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion:region];
As of June 2014, this answer is the simplest way to iterate over a given array of markers and then set bounds accordingly.
// I have found this method that worked for me
func setMapZoomToRadius(lat:Double, lng:Double, var mile:Double)
{
let center = CLLocationCoordinate2DMake(lat, lng)
let radius: Double = (mile ) * 621.371
let region = MKCoordinateRegionMakeWithDistance(center, radius * 2.0, radius * 2.0)
let northEast = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta, region.center.longitude - region.span.longitudeDelta)
let southWest = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta, region.center.longitude + region.span.longitudeDelta)
print("\(region.center.longitude) \(region.span.longitudeDelta)")
let bounds = GMSCoordinateBounds(coordinate: southWest, coordinate: northEast)
let camera = googleMapView.cameraForBounds(bounds, insets:UIEdgeInsetsZero)
googleMapView.camera = camera;
}

Zoom MKMapView to best fit distance zoom level based on list of locations

I have a center location I want my MKMapView to zoom to and also a list of locations (latitude/longitude). I would like to set the zoom level (distance) based on this list of locations so that as many of these locations are visible on the MKMapView at the same time, but with a minimum zoom level (or max distance) so that my MKMapView does not zoom out to much.
So lets say I have a list of 10 locations but one of those locations is so far away from my center location that if I show that one location on the MKMapView it will be zoomed out to much, how do I calculate the distance parameters for MKCoordinateRegionMakeWithDistance?
I hope I explained my problem well enough, I have difficulties explaining it I think.
Thank you
Søren
I got it working with the following solution.
I use the Great-circle distance formula to calculate the distance between my center point and all the found lat/long points, and if this distance is greater than my minimum distance and my maximum distance and larger than my "last found distance" I set this distance to be my zoom distance. It works like a charm and was actually quite simple.
Here is my distance calculation code:
-(double)distanceBetweenLocations:(CLLocationCoordinate2D)c1 :(CLLocationCoordinate2D)c2 {
int r = 6371 * 1000;//meters
double dLat = (c2.latitude - c1.latitude) * M_PI / 180;
double dLon = (c2.longitude - c1.longitude) * M_PI / 180;
double lat1 = c1.latitude * M_PI / 180;
double lat2 = c2.latitude * M_PI / 180;
double a = sin(dLat / 2) * sin(dLat / 2) + sin(dLon / 2) * sin(dLon / 2) * cos(lat1) * cos(lat2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
double d = r * c;
return d;
}
And here is my code that zooms to my center point with the calculated distance:
-(void)zoomToLocation {
double MAX_DISTANCE = 10000.0;
double MIN_DISTANCE = 600.0;
double zoomDistance = MIN_DISTANCE;
CLLocationCoordinate2D center;
center.latitude = self.searchLocationLatitude;
center.longitude = self.searchLocationLongitude;
BOOL treaterVisible = NO;
for (Treater *treater in treaters) {
CLLocationCoordinate2D c2;
c2.latitude = treater.latitude;
c2.longitude = treater.longitude;
double distance = [self distanceBetweenLocations:center :c2];
if(distance > zoomDistance && distance < MAX_DISTANCE) {
zoomDistance = distance;
treaterVisible = YES;
}
}
if(!treaterVisible) {
zoomDistance = MAX_DISTANCE;
}
CLLocationCoordinate2D location;
location.latitude = self.searchLocationLatitude;
location.longitude = self.searchLocationLongitude;
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(location, zoomDistance, zoomDistance);
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:region];
[self.mapView setRegion:adjustedRegion];
}
Just if anybody should need something similar.
Best regards
Søren
Edit didAddAnnotationViews method. I hope this is what you are looking for,
-(void) mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
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);
if (MKMapRectIsNull(zoomRect)) {
zoomRect = pointRect;
} else {
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
}
[mapView setVisibleMapRect:zoomRect animated:YES];
}
DDAnnotation is custom class for display pin(PlaceMark)
-(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(DDAnnotation* 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);
}
NSLog(#"A%f, B%f, C%f, D%f,", topLeftCoord.latitude, topLeftCoord.longitude, bottomRightCoord.latitude, bottomRightCoord.longitude);
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];
}
Some days ago i have read a solution to your problem in a book, I think it is Preparata Shamos, Computational Geometry.
Unfortunatley the solution is komplex: it is the question: find the best/max subset of points that are visible withing a given radius.
if your point count is not to high, less than ten thousands, i would do it as follows
0) add all point to current selected subset.
1) iterate through all points insubset, calcualte gravity point, calc all distances to gravity point, does it fit the max zoom level desired distance? yes the ready.
2) No? the remove the point from subset, with highest distance, and do step 1 again.
Do that till all points are within the desired distance from the current subset center.
The gravity point is that point in the subset with average x (or longitude) and average y ( or latitude) coordinate.
(If you accept, please dont upvote)
For this we need to find the minimum and maximum latitude and longitude, after that we need make the region with average sum of these latitude and longitude. then assign the region to the mapview and fit the region on it. thats it.

MKMapView - how to calculate square miles currently in view

To use various APIs that accept northEast and southWest coordinates to use as the "viewport", some have a maximum square miles limit for search.
I would like to calculate the square miles shown at the current MKMapView zoom level.
Here is the solution:
- (float)numSquareMiles
{
CLLocationCoordinate2D northEast, southWest;
northEast = [mapView convertPoint:CGPointMake(mapView.frame.size.width, 0) toCoordinateFromView:mapView];
southWest = [mapView convertPoint:CGPointMake(0, mapView.frame.size.height) toCoordinateFromView:mapView];
float distanceLatInDegrees = northEast.latitude - southWest.latitude;
float numLatMiles = 69.172 * distanceLatInDegrees;
float distanceLonInDegrees = northEast.longitude - southWest.longitude;
float numLonMiles = 69.172 * distanceLonInDegrees;
float numSquareMiles = numLatMiles*numLonMiles;
return numSquareMiles;
}

What's the best way to zoom out and fit all annotations in MapKit

Quick background of what I have going on. UIMapView loads and shows a single annotation (Never more than one). On the menu bar, there is a Locate Me button, on tap the userLocation is found and displayed as a second annotation. I then I have MapView zoom out to fit both those annotations in range but I am unable to find a suitable way of doing so. Depending on where the first annotation is placed in relation to the user location, sometimes it doesn't zoom out enough.
For example, if the annotation is North West of the User, it zooms out fine. But if the annotation is South East of the User, it only zooms out enough that they are just getting cut off and you have to manually zoom out a bit more. Here's the code I am using, variation of some others I have found on SO.
CLLocation *whereIAm = mapView.userLocation.location;
float lat = whereIAm.coordinate.latitude;
float lon = whereIAm.coordinate.longitude;
CLLocationCoordinate2D southWest = {[currentLatitude floatValue], [currentLongitude floatValue]};
CLLocationCoordinate2D northEast = southWest;
southWest.latitude = MIN(southWest.latitude, lat);
southWest.longitude = MIN(southWest.longitude, lon);
northEast.latitude = MAX(northEast.latitude, lat);
northEast.longitude = MAX(northEast.longitude, lon);
CLLocation *locSouthWest = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
CLLocation *locNorthEast = [[CLLocation alloc] initWithLatitude:northEast.latitude longitude:northEast.longitude];
CLLocationDistance meters = [locSouthWest distanceFromLocation:locNorthEast];
MKCoordinateRegion region;
region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0;
region.center.longitude = (southWest.longitude + northEast.longitude) / 2.0;
region.span.latitudeDelta = meters / 111319.5
region.span.longitudeDelta = 7.0;
MKCoordinateRegion savedRegion = [mapView regionThatFits:region];
[mapView setRegion:savedRegion animated:YES];
[locSouthWest release];
[locNorthEast release];
Is there a better way built into MapKit to just zoom out so that both annotations have, lets say half an inch between them at the outer frame? I don't really care if the user has to zoom in after, I just want it to zoom out properly.
-(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(MapAnnotation* 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 = [mapView regionThatFits:region];
[mapView setRegion:region animated:YES];
}
Taken from: http://codisllc.com/blog/zoom-mkmapview-to-fit-annotations/
(Use it all the time.)
In iOS7 there's a method that does just that. Just call:
[yourMapView showAnnotations:yourAnnotationsArray animated:YES];
Those who do the monotouch c# coding
BasicMapAnnotation is inherit class from MKAnnotation
private void GetRegion(MKMapView mapView)
{
var userWasVisible = mapView.ShowsUserLocation;
mapView.ShowsUserLocation = false; // ignoring the blue blip
// start with the widest possible viewport
var tl = new CLLocationCoordinate2D(-90, 180); // top left
var br = new CLLocationCoordinate2D(90, -180); // bottom right
foreach (var an in mapView.Annotations)
{
// narrow the viewport bit-by-bit
CLLocationCoordinate2D coordinate = ((BasicMapAnnotation) an).Coordinate;
tl.Longitude = Math.Min(tl.Longitude, coordinate.Longitude);
tl.Latitude = Math.Max(tl.Latitude, coordinate.Latitude);
br.Longitude = Math.Max(br.Longitude, coordinate.Longitude);
br.Latitude = Math.Min(br.Latitude, coordinate.Latitude);
}
var center = new CLLocationCoordinate2D
{
// divide the range by two to get the center
Latitude = tl.Latitude - (tl.Latitude - br.Latitude)*0.5,Longitude = tl.Longitude + (br.Longitude - tl.Longitude)*0.5
};
var span = new MKCoordinateSpan
{
// calculate the span, with 20% margin so pins aren’t on the edge
LatitudeDelta = Math.Abs(tl.Latitude - br.Latitude)*1.2,LongitudeDelta = Math.Abs(br.Longitude - tl.Longitude)*1.2
};
var region = new MKCoordinateRegion {Center = center, Span = span};
region = mapView.RegionThatFits(region); // adjusts zoom level too
mapView.SetRegion(region, true); // animated transition
mapView.ShowsUserLocation =
userWasVisible;
}
You can use this code to show all annotations
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];
if you want to include user location just replace two lines below with the first line of above code
MKMapPoint annotationPoint = MKMapPointForCoordinate(mapView.userLocation.coordinate);
MKMapRect zoomRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);

Resources