MKMap not requesting more than 3 longitudinal tiles from OverlayRenderer - ios

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

Related

Yet another MKMapView zoom level and Offline maps

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.

Adding overlapping annotations to map

I have an array of coordinates and I already know how to add it on a map as annotations.
What I'd like to do now is the following:
each annotation should be a red circle (no pins) that represents a fixed radius of 1 Km around the coordinates. That means that if I zoom in or out the map, the circle should adjusts itself to always represent a 1 Km radius;
if two or more circles overlaps, their color intensity should increase. For example, three or four overlapping circles will produce a solid red circle.
That's all. I have no idea where to start with this, so any help will be greatly appreciated.
For starter you can use below code but you will have to tweak it little to make it of your use:
in .h file confirm to MKMapViewDelegate
#interface MapViewController : UIViewController <MKMapViewDelegate>
Then,
in "viewDidLoad"
CLLocationCoordinate2D center = {X cordinate, Y cordinate};
//--> Add overlay
MKCircle *mCircle = [MKCircle circleWithCenterCoordinate:center radius:1000]; //set radius as per your need
[self.mapView addOverlay:mCircle];
Then,
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
MKCircleView *cirView = [[MKCircleView alloc] initWithOverlay:overlay];
[cirView setFillColor:[UIColor redColor]];
[cirView setStrokeColor:[UIColor blackColor]];
[cirView setAlpha:0.3f];
return cirView;
}
I think this should get you started.

MKMapView Region

I have a question regarding setting the region on my MKMapView.
I need to set the mapview to display a specific region when my view first loads.
The north east and south west latitude and longitude of this region is:
North East Coordinate Lat:59.623724 Long:2.911587
South West Coordinate Lat:49.004833 Long:-11.361825
Further to this, I would like to 'lock' the mapview to this region. Ideally the lock will be transparent, i.e: the coordinates above represent the maximum extent of the MKMapView. However if it is simply a case of checking the northeast and southwest coordinates within
- (void)mapView:(MKMapView *)aMapView regionDidChangeAnimated:(BOOL)animated
and resetting the view if they exceed my maximum range, that would be acceptable to me also.
Many thanks for any pointers on this matter.
EDIT:
Regarding the first part of my question, I have figured out I can set the initial region on the MKMapView using the following code:
CLLocationCoordinate2D neCoord;
neCoord.latitude = 59.787643;
neCoord.longitude = 3.025857;
CLLocationCoordinate2D swCoord;
swCoord.latitude = 49.394171;
swCoord.longitude = -11.036642;
MKCoordinateRegion region;
region.center.latitude = neCoord.latitude - (neCoord.latitude - swCoord.latitude) * 0.5;
region.center.longitude = neCoord.longitude + (swCoord.longitude - neCoord.longitude) * 0.5;
region.span.latitudeDelta = fabs(neCoord.latitude - swCoord.latitude); // Add a little extra space on the sides
region.span.longitudeDelta = fabs(swCoord.longitude - neCoord.longitude); // Add a little extra space on the sides
region = [self.mapView regionThatFits:region];
[self.mapView setRegion:region animated:YES];
First, you'll need to make sure you set the region on the map view after the view has been displayed. If you set it before the map has loaded, it probably won't center on that region. Once you've done that, just set self.mapView.zoomEnabled = NO; and self.mapView.scrollEnabled = NO; and it will prevent the user from moving the map around.
If you want to lock the maximum bounds the user can view but still allow scrolling and zooming, you will have to use -mapView:regionDidChangeAnimated: and 'bump' the user back inside your bounds if they leave it. Note that the user experience for this will probably suck - they'll pan around, let go, and then the map will suddenly move back to the region you defined. You could try using -mapView:regionWillChangeAnimated: and modify the map region if they left your boundaries, that could be a little less jarring.

New foursquare venue detail map

I really love the way foursquare designed venue detail view. Especially the map with venue location in the "header" of view ... How was it done? Details are obviously some uiscrollview (maybe uitableview?) and behind it (in the header) there is a map so when you scroll up the map is beeing uncovered as the scroll view bounces... does anyone has an idea how to do this?
Here's the way I manage to reproduce it:-
You need a UIViewController with a UIScrollView as its view. Then, the content of the UIView you add to your scrollview should look like this :-
- The frame of the MKMapView have a negative y position. In this case, we can only see 100pts of the maps in the default state (before dragging).
- You need to disable zooming and scrolling on your MKMapView instance.
Then, the trick is to move down the centerCoordinate of the MKMapView when you drag down, and adjust its center position.
For that, we compute how much 1point represent as a delta latitude so that we know how much the center coordinate of the map should be moved when being dragged of x points on the screen :-
- (void)viewDidLoad {
[super viewDidLoad];
UIScrollView* scrollView = (UIScrollView*)self.view;
[scrollView addSubview:contentView];
scrollView.contentSize = contentView.frame.size;
scrollView.delegate = self;
center = CLLocationCoordinate2DMake(43.6010, 7.0774);
mapView.region = MKCoordinateRegionMakeWithDistance(center, 1000, 1000);
mapView.centerCoordinate = center;
//We compute how much latitude represent 1point.
//so that we know how much the center coordinate of the map should be moved
//when being dragged.
CLLocationCoordinate2D referencePosition = [mapView convertPoint:CGPointMake(0, 0) toCoordinateFromView:mapView];
CLLocationCoordinate2D referencePosition2 = [mapView convertPoint:CGPointMake(0, 100) toCoordinateFromView:mapView];
deltaLatFor1px = (referencePosition2.latitude - referencePosition.latitude)/100;
}
Once those properties are initialized, we need to implement the behavior of the UIScrollViewDelegate. When we drag, we convert the move expressed in points to a latitude. And then, we move the center of the map using the half of this value.
- (void)scrollViewDidScroll:(UIScrollView *)theScrollView {
CGFloat y = theScrollView.contentOffset.y;
// did we drag ?
if (y<0) {
//we moved y pixels down, how much latitude is that ?
double deltaLat = y*deltaLatFor1px;
//Move the center coordinate accordingly
CLLocationCoordinate2D newCenter = CLLocationCoordinate2DMake(center.latitude-deltaLat/2, center.longitude);
mapView.centerCoordinate = newCenter;
}
}
You get the same behavior as the foursquare app (but better: in the foursquare app, the maps recenter tends to jump, here, changing the center is done smoothly).
The example above is nice. If you need more help, I think they're using something very similar to RBParallaxTableViewController. https://github.com/Rheeseyb/RBParallaxTableViewController
It's essentially the same effect that Path uses for its header photo.
Yonel's answer is nice, but I found a problem as I have a pin at the center of the map. Because the negative Y, the point is hidden under my UINavigationBar.
Then, I didn't set the Negative Y, and I correct my mapView.frame according the scroll offset.
My mapView is 320 x 160
_mapView.frame = CGRectMake(0, 160, 320, -160+y);
Hope this helps someone.

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.

Resources