iOS : zoom in and zoom out effect on map - ios

I have created map as show on this link and its working perfectly.
But the problem is, It only zoom in one way (it get bigger only). How could I make it working on either way?
May be like we have on google map (plus - minus stick on the left side).

For ZoomIn
-(void)zoomIn
{
region.span.latitudeDelta = region.span.latitudeDelta/4 ;
region.span.longitudeDelta = region.span.longitudeDelta/4;
region.center.latitude = mapView.centerCoordinate.latitude ;
region.center.longitude = mapView.centerCoordinate.longitude ;
[mapView setRegion:region animated:YES];
}
For ZoomOut
-(void)zoomOut
{
region.span.latitudeDelta = region.span.latitudeDelta*4 ;
region.span.longitudeDelta = region.span.longitudeDelta*4;
region.center.latitude = mapView.centerCoordinate.latitude ;
region.center.longitude = mapView.centerCoordinate.longitude ;
[mapView setRegion:region animated:YES];
}
You use the delta values in this structure to indicate the desired
zoom level of the map, with smaller delta values corresponding to a
higher zoom level.
Please refer this link for more.

This is a revision of the answer by Midhun VP and Slavco Petkovski that avoids an NSInvalidArgumentException for an "Invalid Region" in zoomOut when setting the latitudeDelta too large. Zooming in doesn't seem to have any problems--it just doesn't zoom in any more once it hits the limit.
- (void)zoomIn {
MKCoordinateRegion region = [self.mapView region];
region.span.latitudeDelta = region.span.latitudeDelta/4;
region.span.longitudeDelta = region.span.longitudeDelta/4;
region.center.latitude = self.mapView.centerCoordinate.latitude;
region.center.longitude = self.mapView.centerCoordinate.longitude;
[self.mapView setRegion:region animated:YES];
//NSLog(#"zoomIn: center %lf, %lf; spanDelta %lf, %lf, upper left %lf, %lf; lower right %lf %lf", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2, region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2);
}
- (void)zoomOut {
MKCoordinateRegion region = [self.mapView region];
region.span.latitudeDelta = region.span.latitudeDelta*4;
region.span.longitudeDelta = region.span.longitudeDelta*4;
region.center.latitude = self.mapView.centerCoordinate.latitude;
region.center.longitude = self.mapView.centerCoordinate.longitude;
// The region upper latitude must not exceed 90.0 degrees, and the region lower latitude must not fall below -90.0.
double upperLatitude = region.center.latitude + region.span.latitudeDelta / 2.0;
double lowerLatitude = region.center.latitude - region.span.latitudeDelta / 2.0;
if ( upperLatitude > 90 || lowerLatitude < -90 ) {
region.center.latitude = 0.0;
double spanRatio = region.span.latitudeDelta / region.span.longitudeDelta;
region.span.latitudeDelta = 180;
region.span.longitudeDelta = 180 / spanRatio;
}
[self.mapView setRegion:region animated:YES];
//NSLog(#"zoomOut: center %lf, %lf; spanDelta %lf, %lf, upper left %lf, %lf; lower right %lf %lf", region.center.latitude, region.center.longitude, region.span.latitudeDelta, region.span.longitudeDelta, region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2, region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2);
}

Related

One unit test affects another

I'm defining two functions:
In mkgeometry_additions.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
MKMapRect MKMapRectForCoordinateRegion(MKCoordinateRegion region);
MKCoordinateRegion MKCoordinateRegionForSouthWestAndNorthEast(CLLocationCoordinate2D sw, CLLocationCoordinate2D ne);
In mkgeometry_additions.m (.m, even though they're c functions)
MKMapRect MKMapRectForCoordinateRegion(MKCoordinateRegion region)
{
CLLocationCoordinate2D center = region.center;
MKCoordinateSpan span = region.span;
CLLocationCoordinate2D topLeft =
CLLocationCoordinate2DMake(center.latitude - span.latitudeDelta / 2.0,
center.longitude - span.longitudeDelta / 2.0);
CLLocationCoordinate2D bottomRight =
CLLocationCoordinate2DMake(center.latitude - span.latitudeDelta / 2.0,
center.longitude - span.longitudeDelta / 2.0);
MKMapPoint mapPointTopLeft = MKMapPointForCoordinate(topLeft);
MKMapPoint mapPointBottomRight = MKMapPointForCoordinate(bottomRight);
double width = mapPointTopLeft.x - mapPointBottomRight.x;
double height = mapPointTopLeft.y - mapPointBottomRight.y;
return MKMapRectMake(mapPointTopLeft.x, mapPointTopLeft.y, width, height);
}
MKCoordinateRegion MKCoordinateRegionForSouthWestAndNorthEast(CLLocationCoordinate2D sw, CLLocationCoordinate2D ne)
{
MKCoordinateSpan span =
MKCoordinateSpanMake(ne.latitude - sw.latitude, ne.longitude - sw.longitude);
CLLocationCoordinate2D topLeft = CLLocationCoordinate2DMake(ne.latitude, sw.longitude);
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(topLeft.latitude + span.latitudeDelta * 0.5, topLeft.longitude + span.longitudeDelta * 0.5);
return MKCoordinateRegionMake(center, span);
}
That's pretty much the entirety of the two files.
In my unit test with XCTests:
- (void)testMKMapRectForCoordinateRegionAfterReverseConversion
{
CLLocationCoordinate2D fremont = CLLocationCoordinate2DMake(37.54827, -121.98857);
MKCoordinateRegion region = MKCoordinateRegionMake(fremont, MKCoordinateSpanMake(0.05, 0.05));
MKMapRect mapRect = MKMapRectForCoordinateRegion(region);
MKCoordinateRegion derivedRegion = MKCoordinateRegionForMapRect(mapRect);
double accuracy = 0.0001;
XCTAssertEqualWithAccuracy(region.center.latitude, derivedRegion.center.latitude,
accuracy, #"Latitude is equal");
XCTAssertEqualWithAccuracy(region.center.longitude, derivedRegion.center.longitude,
accuracy, #"Latitude is equal");
XCTAssertEqualWithAccuracy(region.span.latitudeDelta, derivedRegion.span.latitudeDelta,
accuracy, #"Latitude delta is equal");
XCTAssertEqualWithAccuracy(region.span.longitudeDelta, derivedRegion.span.longitudeDelta,
accuracy, #"Latitude delta is equal");
}
- (void)testMKCoordinateRegionForSouthWestAndNorthEast
{
CLLocationCoordinate2D taipeiWanHua = CLLocationCoordinate2DMake(25.02946, 121.49652);
CLLocationCoordinate2D taipeiSongShan = CLLocationCoordinate2DMake(25.06640, 121.56166);
/* When the following line is called, the first test fails */
MKCoordinateRegionForSouthWestAndNorthEast(taipeiWanHua, taipeiSongShan);
/* I have the rest of the lines in this test commented out */
}
Now, I have empty setup and teardown for this test class. When I run the test. The first test fails if the third line of the second test is in the test. A closer look shows that when it fails, it's that the values are still similar but falling outside of the given accuracy:
If I commented that third line of the second test out, the first test would pass. Why is that?
The problem is not with unit testing - for me it fails even if I comment out entirely the second test - it is with the MKMapRectForCoordinateRegion implementation.
I think the correct implementation is:
MKMapRect MKMapRectForCoordinateRegion(MKCoordinateRegion region)
{
CLLocationCoordinate2D center = region.center;
MKCoordinateSpan span = region.span;
CLLocationCoordinate2D topLeft =
CLLocationCoordinate2DMake(center.latitude + span.latitudeDelta / 2.0,
center.longitude - span.longitudeDelta / 2.0);
CLLocationCoordinate2D bottomRight =
CLLocationCoordinate2DMake(center.latitude - span.latitudeDelta / 2.0,
center.longitude + span.longitudeDelta / 2.0);
MKMapPoint mapPointTopLeft = MKMapPointForCoordinate(topLeft);
MKMapPoint mapPointBottomRight = MKMapPointForCoordinate(bottomRight);
double width = mapPointBottomRight.x - mapPointTopLeft.x;
double height = mapPointBottomRight.y - mapPointTopLeft.y;
MKMapRect ret = MKMapRectMake(mapPointTopLeft.x, mapPointTopLeft.y, width, height);
return ret;
}
check out this: Mercator Projection

current viewing coordinates on map for zooming tools

My final goal is to have buttons to zoom in and out on the map, but I am stuck in finding a way to keep the center coordinates the same as I zoom. How should I go about doing this?
You can do something like the following, which grab's the map's current region, leaves the center unchanged, but adjusts the span:
- (IBAction)zoomIn:(id)sender {
MKCoordinateRegion region = self.mapView.region;
region.span.latitudeDelta /= 2.0;
region.span.longitudeDelta /= 2.0;
[self.mapView setRegion:region animated:YES];
}
- (IBAction)zoomOut:(id)sender {
MKCoordinateRegion region = self.mapView.region;
region.span.latitudeDelta = MIN(region.span.latitudeDelta * 2.0, 180.0);
region.span.longitudeDelta = MIN(region.span.longitudeDelta * 2.0, 180.0);
[self.mapView setRegion:region animated:YES];
}

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.

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);

iPhone Map Region Weirdness

The attached image is what happens sometimes when I try to size a MKMapView to fit one or more placemarks. It's intermittent, but the display is always in exactly the same position.
Here is the code:
// loc1 is always non-null, and is equal to one of the annotation locations
CLLocationCoordinate2D topLeftCoord = loc1.coordinate;
CLLocationCoordinate2D bottomRightCoord = loc1.coordinate;
for(UserPlacemark* annotation in self.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;
double k = 0.01;
region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.25;
region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
region.span.latitudeDelta = k + fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.25; // Add a little extra space on the sides
region.span.longitudeDelta = k + fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.5; // Add a little extra space on the sides
// only zoom if region doesn't fit, or too small
CGRect newRect = [mapView convertRegion:region toRectToView:mapView];
double MIN_SIZE_PIXELS = 50.0;
double rectSizePixels = newRect.size.width+newRect.size.height;
if (!CGRectContainsRect(mapView.bounds, newRect) || rectSizePixels < MIN_SIZE_PIXELS)
{
region = [mapView regionThatFits:region];
[mapView setRegion:region animated:TRUE];
}
The problem in the above code is that a MKUserAnnotation instance is placed in the placemark list by the Map framework. This does not always correspond with the user's actual position, at least in the simulator.
It's better to avoid iterating over the annotations list and instead compute the min/max boundaries of your placemark using your objects directly.

Resources