Slight zoom on MKCoordinateRegion? - iphone

I am zooming an MKMapView to fit the bounding region of a collection of pins, however when the pins are displayed I have noticed that the zoom could ideally do with being a little tighter. My proposed solution to this was to make the region deltas slightly smaller:
// SMALL ZOOM
region.span.latitudeDelta = (upper.latitude - lower.latitude) * 0.9;
region.span.longitudeDelta = (upper.longitude - lower.longitude) * 0.9;
However I have noticed that fine adjustments don't seem to translate to a small zoom increase, is there some form of snapping on the zoom? Really small values work, as do really big ones, but just adjusting the region size by a few percent does not seem to work with the view nearly always jumping/zooming in to far and clipping my pins.
EDIT:
Quick tests showing the results of different scaling factors on the region:
// SCALE FACTOR
// V
region.span.latitudeDelta = (upper.latitude - lower.latitude) * 0.9;
region.span.longitudeDelta = (upper.longitude - lower.longitude) * 0.9;
Here are the results:
x0.5 region too small, some annotations off screen
x0.6 Same as using 1.0
x0.7 Same as using 1.0
x0.8 Same as using 1.0
x0.9 Same as using 1.0
x1.0 Original fit
x1.1 region too big, annotations too small on screen
My point is that very small adjustments (e.g. 0.6 to 0.9) don't seem to make any difference.

Smaller adjustments will never make the mapview zoom level change. When you pass a region, the mapview decides on what zoom level to use for the best fit. It will never use an "in-between" level. The reason is that the "in-between" zoom levels look fuzzy. You give it the region you want to show and it makes a zoom level that includes that whole region. Giving your desired region to regionThatFits: should return the level that it uses.
If you're zooming the map with a pinch, you can get to a level between two zoom levels, but if you double-tap (to zoom in) or do a 2-finger tap (to zoom out) you will only see the "standard" zoom levels.
I'm talking about zoom levels here, but really they don't exist in iOS in the same way they exist in Google Maps. Only regions exist as far as setting the map to a certain level.
With your problem of getting the best fit for pins, I found that something changed in iOS 4, and the code I'd used to fit pins suddenly gave too much space. I divided the deltas by 3 and it worked again. You might want to wrap this in a conditional to target only iOS 4.
region.span.longitudeDelta = (maxCoord.longitude - minCoord.longitude) / 3.0;
region.span.latitudeDelta = (maxCoord.latitude - minCoord.latitude) / 3.0;
Looking at your code, you use * 0.9 to get the exact same thing.
One of the strange things I found was that the value returned by regionThatFits: wasn't always the region that the mapview ended up setting. It might be a bug, but it's been there since iOS 4.0. You can test this yourself by logging the MKCoordinateRegion from regionThatFits: and comparing it to the mapview's region after zooming. I seem to remember it coming up on the Apple Developer Forums.

I have found this method to be extremely useful. All you need to do is call it and pass your MKMapView as the argument, and it will determine the best zoom level to fit all of your annotations. The "tightness" can be adjusted by modifying the constant multiplier on the commented lines (currently 1.1).
What's the best way to zoom out and fit all annotations in MapKit
- (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];
}

Related

Different map zoom on iPhone 4 and iPhone 5

I am using MapKit for my project and so far it has been very good. Here is a chunk of code I use for displaying and centering the map.
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 = 0.0;
[self.mapView setRegion:region animated:YES];
The problem is, that it works differently on iPhone 4 and iPhone 5.
Here is iPhone 4 (same results for iOS5 and iOS6):
and here is iPhone 5 (using the same coordinates):
Anybody experiencing the same?
MapKit has fixed zoom levels. Setting the map's region ensures that the region will be visible in the map, but does not set the exact zoom. This has several benefits, the primary one being you can't create a map that scales latitude and longitude disproportionately (leading to a confusing and/or misleading map). The frames of the maps are different sizes, so your selected region can display at different zoom levels on each device.
As an experiment, try setting the frame of the map view to the same size on both devices. Then, if you absolutely need the maps to display at the same scale you could do some math to compute the appropriate region based on the frame of the map.
These are the lines of code that are causing the difference:
region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0;
region.center.longitude = (southWest.longitude + northEast.longitude) / 2.0;
Which are bound to create a difference since the screen sizes vary.
What you can do here to center the map is to use mapView.centerCoordinate

Indifferent behaviour of zoom level in IOS 6 mapview

I have been using the following code to zoom the mapview, so that all the annotations will be displayed in the view at the same time at optimum zoom level. But in IOS 6, there seems to be some problem with the zoom level. Testing a few cases, I found that
1. If the contacts are in US, when the map is loaded it is zooming else where.
2. The zoom level seems to be correct in the UK area(as far as I have tested).
3. When I include contacts from both UK and US, the UK contacts gets rightly zoomed, but the US contacts are out of the view. But a slight swipe will ensure that all contacts fit into the view and are zoomed properly.
-(void)zoomToFitMapAnnotations:(MKMapView*)mapViews insideArray:(NSArray*)anAnnotationArray
{
if([mapViews.annotations count] == 0) return;
CLLocationCoordinate2D topLeftCoord;
topLeftCoord.latitude = -90;
topLeftCoord.longitude = 180;
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for(MKPointAnnotation* annotation in anAnnotationArray)
{
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 = [mapViews regionThatFits:region];
[mapView setRegion:region animated:YES];
}
After loading the mapview with annotations, in random cases I get the following log
<GEOTileSource: 0x108f6470>: Error downloading tiles Server Error: Error Domain=GEOErrorDomain Code=-204 "The operation couldn’t be completed. (GEOErrorDomain error -204.)" UserInfo=0x18a5b9c0 {UnderlyingErrors=(
"Error Domain=GEOErrorDomain Code=-204 \"The operation couldn\U2019t be completed. (GEOErrorDomain error -204.)\""
)}
What could be the reason for this and how can I correct it? Couldn't find anything helpful after googling.
I am not able to identify any particular pattern for this zooming inconsistency. The above code is working fine in previous IOS versions.
I almost offered a bounty on your question but then I noticed, that with the new maps in iOS6 you just cannot zoom out to see the whole world (try it yourself in the maps app). There is a maximum zoom level which you can figure out by commenting out your for loop and logging this:
NSLog(#"region.span.latitudeDelta = %f", region.span.latitudeDelta);
NSLog(#"longitudeDelta = %f", region.span.longitudeDelta);
[map setRegion:region animated:NO];
NSLog(#"region.span.latitudeDelta = %f", map.region.span.latitudeDelta);
NSLog(#"longitudeDelta = %f", map.region.span.longitudeDelta);
The output looks like this:
region.span.latitudeDelta = 179.283409
longitudeDelta = 360.000000
region.span.latitudeDelta = 76.269114
longitudeDelta = 98.437499

Zooming the mapview

I am doing an app based on mapview. I am getting the location(place) and converting it to co-ordinates. Using this, I am zooming into the exact location of the place specified.
But the problem I am facing is that,
If I am specifying just the street name or the city name or the state name or the country name the zoom level is always the same. (The zoomlevel is always the same as that of the street.)
If I am just specifying just the country name, the zoom level should be of the country level and not zooming right into any random street of the country.
Here is the code I have used in zooming.
-(void)zoomToFitMapAnnotations:(MKMapView*)mapViews
{
if([mapViews.annotations count] == 0)
return;
CLLocationCoordinate2D topLeftCoordinate;
topLeftCoordinate.latitude = -90;
topLeftCoordinate.longitude = 180;
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for(SJAddressAnnotation* annotation in mapViews.annotations)
{
topLeftCoordinate.longitude = fmin(topLeftCoordinate.longitude, annotation.coordinate.longitude);
topLeftCoordinate.latitude = fmax(topLeftCoordinate.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 = topLeftCoordinate.latitude - (topLeftCoordinate.latitude - bottomRightCoord.latitude) * 0.5;
region.center.longitude = topLeftCoordinate.longitude + (bottomRightCoord.longitude - topLeftCoordinate.longitude) * 0.5;
region.span.latitudeDelta = fabs(topLeftCoordinate.latitude - bottomRightCoord.latitude) * 1.1; // Add a little extra space on the sides
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoordinate.longitude) * 1.1; // Add a little extra space on the sides
region = [mapView regionThatFits:region];
[mapView setRegion:region animated:YES];
}
I have tried changing the span level unsuccessfully. Needs help in this case.
As I said earlier there is problem with one annotation. If you are retrieving the places from Google search then with each place they give approximate level of zoom(depending upon whether it is country/city). you can try by typing http://maps.google.com/maps/geo?q=asia&output=json in your browser. you can try different entries by replacing asia with china or newyork etc. Look at the Accuracy attribute of response in browser. It will be 0 for continents, 1 for countries etc.
If you have created annotations by yourself then you can attach a parameter to it which will relate to zoomlevel.
#interface AddressAnnotation : NSObject<MKAnnotation> {
double zoomLevel;
}
#property(readwrite,nonatomic) double zoomLevel ;
#end
#implementation AddressAnnotation
#synthesize zoomLevel;
-(void)setZoomLevel (double) parameter
{
self.zoomLevel = parameter;
}
#end
and finally assuming annot is your annotation then
[annot setZoomLevel: .1]; //instead of .1 you can set different values
when you are displaying this annotation set region center as annotation coordinate and set span as annotation.zoomLevel.
MKCoordinateRegion region;
region.center.latitude = annotation.coordinate.latitude;
region.center.longitude = annotation.coordinate.longitude;
region.span.latitudeDelta = annotation.zoomLevel;
region.span.longitudeDelta = annotation.zoomLevel;

Match the zoom/bounds of a MapKit map with the zoom/bounds of a RouteMe map

EDIT: I believe my issue is that this code works for integer zoom levels, but I would like it to work for float zoom levels.
I have an iOS app in which the user can switch between a RouteMe-based map and a MapKit-based map.
When they switch sources, I would like to be able to show the exact same area in one as in the other. However, I can't figure out how to make them match because RouteMe and MapKit use different data structures to describe the map bounds.
Here is some code that gets it to be somewhat close, but it's not exact. This code comes from: http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/
I'm not sure whether this code should be fixed, or possibly I am overlooking a much easier solution. The code executes starting with the last method listed:
#define MERCATOR_OFFSET 268435456
#define MERCATOR_RADIUS 85445659.44705395
#pragma mark -
#pragma mark Map conversion methods
- (double)longitudeToPixelSpaceX:(double)longitude {
return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0);
}
- (double)latitudeToPixelSpaceY:(double)latitude {
return round(MERCATOR_OFFSET - MERCATOR_RADIUS * logf((1 + sinf(latitude * M_PI / 180.0)) / (1 - sinf(latitude * M_PI / 180.0))) / 2.0);
}
- (double)pixelSpaceXToLongitude:(double)pixelX {
return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / M_PI;
}
- (double)pixelSpaceYToLatitude:(double)pixelY {
return (M_PI / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / M_PI;
}
- (MKCoordinateSpan)coordinateSpanWithMapView:(MKMapView *)mapView
centerCoordinate:(CLLocationCoordinate2D)centerCoordinate
andZoomLevel:(NSInteger)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;
}
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
zoomLevel:(NSUInteger)zoomLevel
animated:(BOOL)animated {
// use the zoom level to compute the region
MKCoordinateSpan span = [self coordinateSpanWithMapView:self
centerCoordinate:centerCoordinate
andZoomLevel:zoomLevel];
MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
// set the region like normal
[self setRegion:region animated:animated];
}
Unfortunately this is a limitation of the Google Maps API, which only accepts integer values when setting the map's zoom level: Apple's MapKit code is calling the underlying Google Maps APIs when you set a MKMapView's displayed area, and the result – no matter which MapKit method you use to set the area – is a map that's zoomed out to the nearest integer zoom level.
Troy Brant's code takes you full circle, and puts a layer above the MapKit APIs that allows you to set the zoom level directly… but ultimately you don't have precise control over the area displayed by an MKMapView, unless the zoom level of your desired map happens to be an integer.
Several variations on this question have appeared on Stack Overflow (e.g., MKMapView setRegion "snaps" to predefined zoom levels? and MKMapView show incorrectly saved region), but so far no one has come up with a programmatic way to make a map with a non-integer zoom level, and I suspect it'd take cooperation between Google and Apple to ever make it happen.

Continuous zooming with MKMapView?

I am working with a MKMapView thats visible area (zoom level) is set with a slider.
I set the visible area with a MKCoordinateRegion and setRegion:animated:.
The problem is that I don't seem to be able to get extremely precise control over the visible area. It's as if the latitude\longitude deltas snap to the default ~21 zoom levels provided by Google.
What I really want is similar to the behavior of the Map.app when pinching to zoom. It scales the view until the threshold for a new zoom level is reached, and then it renders the new map level.
Is there a simple way to access\emulate this behavior? How does it work?
Code I'm using:
MKCoordinateRegion region;
MKCoordinateSpan span;
CLLocationCoordinate2D center = {45.475969,-73.64095};
region.center = center;
span.latitudeDelta = 0.01;
span.longitudeDelta = 0.01;
region.span = span;
[mapView setRegion:region animated:TRUE];
If I use a delta of 0.01 or 0.013, I get the exact same map.
The simple answer is that MapKit does not allow you to programatically set a zoom level other than the default 21 levels supplied by google. The region span you set will always "snap" to the closest one.
Are you using setRegion:animated: ?
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html#//apple_ref/occ/instm/MKMapView/setRegion:animated:
You can set mapview's visibleMapRect to achieve continuous zooming.
Following code shows how to zoom in 1 level (it's a subclass of a MKMapView so self is an instance of MKMapView).
MKMapRect visibleRect = self.visibleMapRect;
MKMapSize size = visibleRect.size;
CGFloat aspectRatio = size.width / size.height;
CGPoint center = CGPointMake(visibleRect.origin.x + size.width/2, visibleRect.origin.y + size.height/2);
CGFloat zoomedHeight = size.height / 2; // By divide by 2, will zoom in exact 1 level
CGFloat zoomedWidth = zoomedHeight * aspectRatio;
visibleRect = MKMapRectMake(center.x - zoomedWidth/2, center.y - zoomedHeight/2, zoomedWidth, zoomedHeight);
self.visibleMapRect = visibleRect;