Reputation: 111
I have a big need to do an offline map for my app, as it is made mostly for Thailand, where internet connection is often hard to come by. I am using OpenStreetMap
right now for my MKTileOverlay
but am having issues implementing it for offline use. I have found a tutorial that says to subclass MKTileOverlay
. So, in my ViewController
where the map is I have:
- (void)viewWillAppear:(BOOL)animated {
CLLocationCoordinate2D coord = {.latitude = 15.8700320, .longitude = 100.9925410};
MKCoordinateSpan span = {.latitudeDelta = 3, .longitudeDelta = 3};
MKCoordinateRegion region = {coord, span};
[mapView setRegion:region];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Map";
NSString *template = @"http://tile.openstreetmap.org/{z}/{x}/{y}.png";
self.overlay = [[XXTileOverlay alloc] initWithURLTemplate:template];
self.overlay.canReplaceMapContent = YES;
[mapView addOverlay:self.overlay level:MKOverlayLevelAboveLabels];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay {
return [[MKTileOverlayRenderer alloc] initWithTileOverlay:overlay];
}
In my subclass of MKTileOverlay
, I have:
- (NSURL *)URLForTilePath:(MKTileOverlayPath)path {
return [NSURL URLWithString:[NSString stringWithFormat:@"http://tile.openstreetmap.org/{%ld}/{%ld}/{%ld}.png", (long)path.z, (long)path.x, (long)path.y]];
}
- (void)loadTileAtPath:(MKTileOverlayPath)path
result:(void (^)(NSData *data, NSError *error))result
{
if (!result) {
return;
}
NSData *cachedData = [self.cache objectForKey:[self URLForTilePath:path]];
if (cachedData) {
result(cachedData, nil);
} else {
NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
[NSURLConnection sendAsynchronousRequest:request queue:self.operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
result(data, connectionError);
}];
}
}
The issue is that NOTHING gets loaded at all, unless I comment out the code in the subclass. Where am I messing up?
Upvotes: 17
Views: 1974
Reputation: 29213
In our company, we chose to use MapBox for our offline mapping.
There, you can design and style your own maps using MapBox Studio, then export your maps (at a chosen range of zoom levels) into an external file. Ours is about 40Mb in size.
From there, you can use the MapBox iOS SDK to easily add it to your app.
(Disclaimer: no, I don't work for them ! We specifically chose them for the ability to define our own land/sea colors and styling, and the ability to have the map files included in our Xcode project, and usable offline.)
I do appreciate your exact question was how to get OpenStreetMap's own maps to be offline, but I hope this is useful to you.
Upvotes: 1
Reputation: 1094
It looks like you're not populating the cache. It's always going to be empty?
if (cachedData) {
result(cachedData, nil);
} else {
NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
[NSURLConnection sendAsynchronousRequest:request queue:self.operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// Instantiate a new cache if we don't already have one.
if (!self.cache) self.cache = [NSCache new];
// Add the data into the cache so it's there for next time.
if (data) {
[self.cache setObject:data forKey:[self URLForTilePath:path]];
}
result(data, connectionError);
}];
}
I think that'll solve your problem here. Remember that NSCache does not persist to disk (only to memory) so while it does survive the app being backgrounded, you'll need something more complex in the long run if you want total offline capability (probably Core Data).
Upvotes: 1