Offline Cache of Map
Asked Answered
A

2

17

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?

Arella answered 9/8, 2016 at 20:8 Comment(10)
Where are you calling loadTileAtPath method?Sacculus
I'm not calling it, because I am not sure HOW to call it since it calls for a path and I don't know how to get that. @SacculusArella
@Sacculus How would I pass the path for where the map initially loads to it? With the normal initWithUrlTemplate it just happens, but I just don't have a clue with this set up.Arella
How do you create the MKTileOverlayPath to pass to the method?Arella
Right after [mapView addOverlay:self.overlay level:MKOverlayLevelAboveLabels];, you need to call [self.overlay loadTitlePath:"Your path" result: "Your completion block"]Sacculus
@Sacculus ok, but what is path? In the subclass it is listed as MKTileOverlayPath and gives me errors for incompatible integers on everything I try.Arella
path is what will passed as a parameter to loadTitlePath which is of MKTileOverlayPath, that only you can tell what it is?Sacculus
That's my point. I've never made that before. This was from a tutorial that needs more information.Arella
Are you making an android based app?Dasha
@Dasha iOS, hence the iOS tag, the Obj-C codeArella
E
1

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.

Erund answered 18/8, 2016 at 10:3 Comment(0)
T
1

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

Tenuous answered 18/8, 2016 at 9:14 Comment(0)
E
1

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.

Erund answered 18/8, 2016 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.