Yet another MKMapView zoom level and Offline maps - ios

I've been working with MKMapView in iOS 7 trying to set and get programatically the zoom level in order to download and reuse map tiles when I were offline.
As I can't download the whole map into my phone, I download just several tiles in an appropriated zoom level and, afterwards I fix that zoom level and use the tiles thought MKTileOverlay and MKTileOverlayRenderer.
I tried to use Troybrant's algorithm (http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview) but it didn't worked well for me. It failed to establish the zoom level back properly.
So I created one of my own that works fine.
Some explanations about my own method:
At the maximum map zoom level (20), you would see every map point at 1:1 scale. The whole map would have 256*2^20 points.
In retina displays there is a 2.0 scale factor between map points and pixels.
Apple maps can vary zoom level from 3 to 19 (min & max)
Then there is a simple inverse rule:
At maximum zoom level our view will show as much points as pixels it has (with the point-pixel scale factor of 2.0 for retina displays)
If zoom level is decreased the amount of map points shown should increase (inverse rule)
With that information the idea is to set the MKMapView's visibleRect property:
visibleRect width points = 2.0 * mapView.bounds.size.width * 2^(20-zoom)
Using that formula I've been able to centre my maps and apply to them zoom levels properly.
As Troybrant did, I created a category with the following methods:
#interface MKMapView (ZoomLevel)
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
zoomLevel:(NSUInteger)zoomLevel
animated:(BOOL)animated;
#end
#implementation MKMapView (ZoomLevel)
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
zoomLevel:(NSUInteger)zoomLevel
animated:(BOOL)animated
{
MKMapPoint centrePoint = MKMapPointForCoordinate(centreCoord);
CGFloat rectWidth = 2.0 * self.bounds.size.width * pow(2, 20-zoomLevel);
CGFloat rectHeight = 2.0 * self.bounds.size.height * pow(2, 20-zoomLevel);
MKMapRect visibleRect = MKMapRectMake(centrePoint.x-rectWidth/2, centrePoint.y-rectHeight/2, rectWidth, rectHeight);
[self setVisibleMapRect:visibleRect animated:animated];
}
#end
I hope this code can help you all.

Related

MKMap not requesting more than 3 longitudinal tiles from OverlayRenderer

I used apples breadcrumb sample, adapted it and got to a weird effect in my code.
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
is called with as many tiles as necessary to cover north to south, but it never requests more than 3 tiles east to west. So it never covers wide overlays.
Everything inside the tiles is drawn correctly etc. its the map that is simply not calling any more requests even with
- (BOOL)intersectsMapRect:(MKMapRect)mapRect {
return YES;
}
Center coordinate is smack in the middle of the bounds.
// init of CrumbPath : NSObject <MKOverlay>
upper = CLLocationCoordinate2DMake(49.0, 10.0);
lower = CLLocationCoordinate2DMake(48.0, 5.0);
_coordinate = CLLocationCoordinate2DMake(48.5, 7.5);
MKMapPoint upperLeft = MKMapPointForCoordinate(upper);
MKMapPoint lowerRight = MKMapPointForCoordinate(lower);
_boundingMapRect = MKMapRectMake(upperLeft.x,
upperLeft.y,
lowerRight.x - upperLeft.x,
lowerRight.y - upperLeft.y);
screenshot at http://imgur.com/lc5KpTT
MapKit can NOT handle MKMapRect with negative sizes. All calculations from and to CGRects and drawing WORK but the map itself will not request the right MapRects if the size is negative as it sees them as size 0.
So with 'abs' it will work
_boundingMapRect = MKMapRectMake(upperLeft.x,
upperLeft.y,
fabs(lowerRight.x - upperLeft.x),
fabs(lowerRight.y - upperLeft.y));

How to get notified when map zoom or scroll animation has finished?

I'm very new to iOS programming, and apologize fi this question sounds rather trivial. But after searching the web for hours, I've given up.
I need to get current zoom level of the map in my iOS app, so that when I change current view, I can retain zoom level. Using this code, I can get and set zoom levels. I implemented the mapView:regionDidChangeAnimated: method of MKMapViewDelegate protocol. But this method is called multiple times during initial "zoom in" animation of the map, and if during this period, I need to update the map, I might have wrong zoom level. I certainly don't want to turn off map animations. So, I was looking for a way to determine if map is currently being animated or stationary, before reading and storing zoom level.
I'm using MKMapView.
You can try by below code
#define MERCATOR_RADIUS 85445659.44705395
#define MAX_GOOGLE_LEVELS 20
#interface MKMapView (ZoomLevel)
- (double)getZoomLevel;
#end
#implementation MKMapView (ZoomLevel)
- (double)getZoomLevel
{
CLLocationDegrees longitudeDelta = self.region.span.longitudeDelta;
CGFloat mapWidthInPixels = self.bounds.size.width;
double zoomScale = longitudeDelta * MERCATOR_RADIUS * M_PI / (180.0 * mapWidthInPixels);
double zoomer = MAX_GOOGLE_LEVELS - log2( zoomScale );
if ( zoomer < 0 ) zoomer = 0;
// zoomer = round(zoomer);
return zoomer;
}
#end

Can anyone explain these inaccuracies with MKMapView::mapRectThatFits

I am getting strange inaccuracies when using MKMapView::mapRectThatFits. If I pass in an MKMapRect that is wider than it is higher I would expect mapRectThatFits to return a new MapRect with the same horizontal span, but with increased vertical span to fit it into the mapView, however I am noticing MKMapView adding to the horizontal span as well as the vertical.
This inaccuracy seems to increase as the coordinate region increases in dimensions. At a few hundred meters the accuracy is negligible, but at a 1.5 kilometers, the difference is 0.0006 which is significant.
I have tried using mapRectThatFits:edgePadding with edge padding set to zero and regionThatFits but both result in the same inaccuracies.
MKCoordinateRegion combinedRegion = self.models.locationModelsCoordinator.coordinateRegion;
NSLog(#"Before %f", combinedRegion.center.longitude - (combinedRegion.span.longitudeDelta * 0.5)); // Logs -0.103473
MKMapRect combinedRect = [ELMapKitUtils mapRectForCoordinateRegion:combinedRegion];
// If I convert combinedRect back to an MKCoordinateRegion here I can verify it is unaltered, so there is no issue with my conversion code.
MKMapRect focusRect = [self.mapView mapRectThatFits:combinedRect];
MKCoordinateRegion regionFittedToMapView = MKCoordinateRegionForMapRect(focusRect);
NSLog(#"After %f", regionFittedToMapView.center.longitude - (regionFittedToMapView.span.longitudeDelta * 0.5)); // Logs -0.104107
It is because you have bitmap tiles. MapKit today adapts to the full size of the bitmap tiles, showing horizontally wider area because of that. If MapKit did use vector based tiles, which it might do in a future version, as demonstrated yesterday during the Keynote, it wouldn't be limited by that bitmap tiles issue.
There is no workaround today with MapKit. I didn't look at bing Maps or Routeme. You should.

Zooming out with RMMapView constrained to coordinates

I'm trying to setup an offline map in an iPhone app, but the result is not very good.
I'm using the route-me framework, I've got an offline file .db (created by tiles2sqlite) and the map view is constrained with coordinates (using setConstraintsSW:NE:).
My problem is appearing when zooming out (pinch gesture), this error message "Zooming will move map out of bounds: no zoom" is always present and it's very difficult to zoom out when you are not near the real center of the map.
Is there a solution to have the same result as in Offmaps (iOS app) where the map has a nice scrollview behavior?
Cheers.
Cyril
I had to edit RMMapView.m source code to make a quick fix. Find - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center animated:(BOOL)animated method (near line 300). It has constrain logic, I turned it off:
- (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center animated:(BOOL)animated
{
if ( _constrainMovement && false ) // turn constraint checks off
{
//check that bounds after zoom don't exceed map constraints
//the logic is copued from the method zoomByFactor,
float _zoomFactor = [self.contents adjustZoomForBoundingMask:zoomFactor];
float zoomDelta = log2f(_zoomFactor);
...
}
...
}
Now map is zoomed smoothly, but this fix could have side effects.
Instead you can use of setting setConstraintsSW:NE: we can set RMMapview as,
RMDBMapSource *mapSrc = [[[RMDBMapSource alloc] initWithPath:#"mapDB.sqlite"] autorelease];
[[RMMapContents alloc] initWithView:mapView tilesource:mapSrc centerLatLon:mapSrc.centerOfCoverage zoomLevel:17 maxZoomLevel:18 minZoomLevel:15 backgroundImage:image screenScale:10 ];
This will enable your zooming acording to your set parameter

How do I move the image based on data it receives a variable with IOS?

I'm starting to program in IOS with Xcode, my idea is to carry a watch with hands that mark the altitude according to the GPS data received from the Iphone. I have the variable that returns me the height in meters, how do I do that to interpret it clockwise?
Set the view.transform property based on the distance.
You'll need to convert the distance into radians. Here's some code
#define METERS_IN_A_CIRCLE 100.0f // change this to get a different scale
CGFloat distance = 40.0f; // the distance in meters - set this to your variable value
CGFloat angle = (M_PI * 2.0f) / METERS_IN_A_CIRCLE * distance;
view.transform = CGAffineTransformMakeRotation(angle);
If it's turning the wrong way, use -angle in the transform instead. If it's off by 1/4 of a circle, subtract M_PI_2 from the angle.
For your watch hand to look right, the centre of the hand image will need to be in the centre of the imageView, so you may need to leave a lot of empty white-space in the image, but don't worry it won't affect performance significantly.

Resources