3

I'm trying to update my tile map code to use iOS 7's MKTileOverlay and MKTileOverlayRenderer, and I could use some pointers for making things work better.

First, here is the iOS6 code: AppleTileOverlay.m and TileOverlayView.m. This still works quite well in iOS 7 when when I replace TileOverlayView with a class that is identical in all ways except that it's a subclass of MKOverlayRenderer instead of MKOverlayView.

The new piece I'm testing is a subclass of MKTileOverlay with the only method being:

-(NSURL *)URLForTilePath:(MKTileOverlayPath)path {
    NSString *tileKey = [[NSString alloc] initWithFormat:@"%d%d%d", path.x, path.y, path.z];
    NSString *tilePath = [[NSBundle mainBundle] pathForResource:tileKey ofType:nil inDirectory:@"TileFolder"];

    NSURL *url;
    if (tilePath) {
        url = [NSURL fileURLWithPath:tilePath];
    }

    return url;
}

The map tiles load fine most of the time, but the log fills up with messages like this:

Error loading URL (null): Error Domain=NSURLErrorDomain Code=-1000 "bad URL" UserInfo=0x1b3e19e0 {NSUnderlyingError=0x1894d470 "bad URL", NSLocalizedDescription=bad URL}

from the method returning nil for the URL.

So the question is: Can I avoid those error messages, or should I just stick with the older overlay class I have?

RL2000
  • 913
  • 10
  • 20
  • Nothing. I just went back to using an older tile overlay based on https://github.com/mtigas/iOS-MapLayerDemo I don't know if the newer version is more efficient or better for any other reasons at this point. – RL2000 Nov 28 '13 at 02:15
  • 2
    BY the way I think there is an issue with your file naming system. The tiles x=1 y=11 z=5 and x=11 y=1 z=5 would both be named 1115.png, as would x=1, y=1 z=15. Perhaps your tiles never coincide with coordinates that can be crossed over, but if someone puled the map view to an unexpected location they may get strange tiles floating in the wrong place. You might be better off putting an underscore between each parameter such as 1_11_5.png to avoid any misinterpretation. – Craig Jan 05 '14 at 18:19
  • Very good point! I haven't seen problems with this so far because my maps are only used in small areas anyway, but that would be a very simple fix. Good call! – RL2000 Jan 18 '14 at 13:08

4 Answers4

1

I think with Swift 2.0, URLForFilePath(...) cannot return nil as it is not an Optional.

I managed to solve this problem using the MKTileOverlay subclass to check for valid tile path as above and loading a 'dummy' transparent tile if a tile image was not available.

override func URLForTilePath(path: MKTileOverlayPath) -> NSURL {

        let tileKey = String(format:"%d/%d/%d",path.z,path.x,path.y)

        let tilePath = NSBundle.mainBundle().pathForResource(tileKey, ofType: "png", inDirectory: "Maps/Map1880")

        let blankTilePath = NSBundle.mainBundle().pathForResource("blank", ofType: "png", inDirectory: "Maps")

        var url: NSURL

        if ((tilePath) != nil)
        {
            url =  NSURL.fileURLWithPath(tilePath!)
        } else {
            url = NSURL.fileURLWithPath(blankTilePath!)
        }

        return url;
    }

This is not very elegant as it loads the blank tile for every tile that is not part of the overlay.

However, there is a better solution, thanks to User: junkpile on Apple Developer Forum, the problem with attempting to load non-existent overlay tiles is that boundingMapRect is set by default to MKMapRectWorld, i.e. the whole world.

To restrict this to the required overlay region, subclass MKTileOverlay.

Here's an example:

import MapKit
class CustomTileOverlay : MKTileOverlay {

        override var boundingMapRect: MKMapRect {
            get {
               //North-East Corner of region
                let lat1 = 53.46075
                let long1 = -1.92618
               //South-West Corner of region
                let lat2 = 53.43018
                let long2 = -1.992885

                //Convert to Coordinates
                let coord1 = CLLocationCoordinate2DMake(lat1,long1)
                let coord2 = CLLocationCoordinate2DMake(lat2,long2)

                //Convert to map points
                let p1 = MKMapPointForCoordinate (coord1);
                let p2 = MKMapPointForCoordinate (coord2);

                //Return the MKMapRect
               return MKMapRectMake(fmin(p1.x,p2.x), fmin(p1.y,p2.y), fabs(p1.x-p2.x), fabs(p1.y-p2.y)); 
            }
        }
    }
Eric
  • 121
  • 1
  • 5
0

My guess is you are somehow always trying to set url despite not having a true, valid tilePath. Add some debugging and see.

incanus
  • 5,100
  • 1
  • 13
  • 20
0

Does this happen when you are looking at areas of the map that you don't have tiles for?

Because you check if you have the file and then don't set url if the file doesn't exist, you return nil. You should return a valid NSURL to a transparent image instead.

Craig
  • 8,093
  • 8
  • 42
  • 74
  • Yep, it's only for URLs where the file doesn't exist. The other overlay class I have checks for that. I guess it's probably best to keep the old one in this case. – RL2000 Oct 18 '13 at 01:11
  • I've updated my solution to the one I am using in my app. It works pretty well for me, no more error messages, but I haven't tested it for speed other than a visual comparison. – Craig Jan 05 '14 at 18:22
  • Almost forgot to respond to this, Craig. Thanks for the idea. I'll see about testing this at some point. For now, what I've already got seems to work well enough, so I'm hesitant to change anything ;) – RL2000 Jan 18 '14 at 13:07
  • Oh come on, what could possibly go wrong? You've got 4 more months until hiking season really kicks in ;) – Craig Jan 18 '14 at 22:30
0

You can avoid it by creating a custom MKTileOverlay subclass like the following:

@interface MyTileOverlay : MKTileOverlay
@end

@implementation MyTileOverlay

- (NSURL *)URLForTilePath:(MKTileOverlayPath)path 
{
    NSString *tileKey = [[NSString alloc] initWithFormat:@"%d%d%d", path.x, path.y, path.z];
    NSString *tilePath = [[NSBundle mainBundle] pathForResource:tileKey ofType:nil inDirectory:@"TileFolder"];

    NSURL *url;
    if (tilePath) 
    {
        url = [NSURL fileURLWithPath:tilePath];
    }

    return url;
}

- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData *, NSError *))result
{
    NSURL *url = [self URLForTilePath:path];
    if (url)
    {
        [super loadTileAtPath:path result:result];
    }
}

@end

The idea is to not trigger the tile loading if you are sure the tile does not exists.

nverinaud
  • 1,270
  • 14
  • 25