Calculating tiles to display in a MapRect when over-zoomed beyond the overlay tile set
Imagine that the overlay is cloud cover - or in our case, cellular signal coverage. It might not "look good" while zoomed in deep, but the overlay is still conveying essential information to the user.
I've worked around the problem by adding an OverZoom mode to enhance Apple's TileMap sample code.
Here is the new tilesInMapRect function in TileOverlay.m:
- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale
{
NSInteger z = zoomScaleToZoomLevel(scale);
// OverZoom Mode - Detect when we are zoomed beyond the tile set.
NSInteger overZoom = 1;
NSInteger zoomCap = MAX_ZOOM; // A constant set to the max tile set depth.
if (z > zoomCap) {
// overZoom progression: 1, 2, 4, 8, etc...
overZoom = pow(2, (z - zoomCap));
z = zoomCap;
}
// When we are zoomed in beyond the tile set, use the tiles
// from the maximum z-depth, but render them larger.
NSInteger adjustedTileSize = overZoom * TILE_SIZE;
// Number of tiles wide or high (but not wide * high)
NSInteger tilesAtZ = pow(2, z);
NSInteger minX = floor((MKMapRectGetMinX(rect) * scale) / adjustedTileSize);
NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale) / adjustedTileSize);
NSInteger minY = floor((MKMapRectGetMinY(rect) * scale) / adjustedTileSize);
NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale) / adjustedTileSize);
NSMutableArray *tiles = nil;
for (NSInteger x = minX; x <= maxX; x++) {
for (NSInteger y = minY; y <= maxY; y++) {
// As in initWithTilePath, need to flip y index to match the gdal2tiles.py convention.
NSInteger flippedY = abs(y + 1 - tilesAtZ);
NSString *tileKey = [[NSString alloc] initWithFormat:@"%d/%d/%d", z, x, flippedY];
if ([tilePaths containsObject:tileKey]) {
if (!tiles) {
tiles = [NSMutableArray array];
}
MKMapRect frame = MKMapRectMake((double)(x * adjustedTileSize) / scale,
(double)(y * adjustedTileSize) / scale,
adjustedTileSize / scale,
adjustedTileSize / scale);
NSString *path = [[NSString alloc] initWithFormat:@"%@/%@.png", tileBase, tileKey];
ImageTile *tile = [[ImageTile alloc] initWithFrame:frame path:path];
[path release];
[tiles addObject:tile];
[tile release];
}
[tileKey release];
}
}
return tiles;
}
And here is the new drawMapRect in TileOverlayView.m:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
// OverZoom Mode - Detect when we are zoomed beyond the tile set.
NSInteger z = zoomScaleToZoomLevel(zoomScale);
NSInteger overZoom = 1;
NSInteger zoomCap = MAX_ZOOM;
if (z > zoomCap) {
// overZoom progression: 1, 2, 4, 8, etc...
overZoom = pow(2, (z - zoomCap));
}
TileOverlay *tileOverlay = (TileOverlay *)self.overlay;
// Get the list of tile images from the model object for this mapRect. The
// list may be 1 or more images (but not 0 because canDrawMapRect would have
// returned NO in that case).
NSArray *tilesInRect = [tileOverlay tilesInMapRect:mapRect zoomScale:zoomScale];
CGContextSetAlpha(context, tileAlpha);
for (ImageTile *tile in tilesInRect) {
// For each image tile, draw it in its corresponding MKMapRect frame
CGRect rect = [self rectForMapRect:tile.frame];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:tile.imagePath];
CGContextSaveGState(context);
CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
// OverZoom mode - 1 when using tiles as is, 2, 4, 8 etc when overzoomed.
CGContextScaleCTM(context, overZoom/zoomScale, overZoom/zoomScale);
CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), [image CGImage]);
CGContextRestoreGState(context);
// Added release here because "Analyze" was reporting a potential leak. Bug in Apple's sample code?
[image release];
}
}
Seems to be working great now.
BTW - I think the TileMap sample code is missing an [image release] and was leaking memory. Note where I added it in the code above.
I hope that this helps some others with the same problem.
Cheers,
- Chris
Preventing overlays from disappearing when zoomed - MKMapView & MKOverlay
I modified Apple's TileMap sample code by adding an "OverZoom" mode. I have posted more details and my code as an answer to this question.
I hope I can help out anyone else who stumbles across this problem.
MKMapView tile-based overlay
I'm working on a game where I need to overlay objects on the map and have them scroll and zoom with the map.
Using annotation views I've been able to solve the first problem and partially solve the second problem. Annotations automatically move with the map. For scaling them, I use the mapView:regionDidChangeAnimated:
delegate method to resize my annotations after a zoom event. The problem is that the annotations don't rescale until after the zoom gesture is complete.
I can think of two approaches other than filing a bug with Apple requesting that they provide an API for map overlays:
Put a (mostly invisible) view over the top of the MKMapView that intercepts zoom and scroll events, handles them, and passes them on to the map view.
Customize the open-source RouteMe library with tiles from Open Street Map or CloudMade (the former is slow, the latter costs money). But it's fully open source so you should be able to do overlays to your heart's content. You could also run your own tile server that does the tile overlays on the server.
Loading MKMapKit overlay tiles from server
You could start with the Open Street Map iOS page, which links to some libraries.
Route Me is an open-source replacement for a MapView. Not quite what you're looking for, but you might be able to grab the network code and use it.
Let's Do It World has some sample code, with instructions here.
Related Topics
Uipopovercontroller Dealloc Reached While Popover Is Still Visible
How to Get Selected Value from Uipickerview
How to Add a Toolbar to the Bottom of a Uitableviewcontroller in Storyboards
Get Selected Index of Uitableview
iOS Steps to Create Custom Uitableviewcell with Xib File
Drop Cap with Nsattributedstring
How to Cache Videos? iOS - Swift
Swift Failed with Exit Code 1 While Compiling in Xcode - Possibly Related to Bridging-Headers
Removing Badge from iOS App Icon
Avplayer Stops Playing Video After Buffering
How to Create a Release Build in Xcode
"Initialize" Class Method for Classes in Swift
Uicollectionview Scrolling Choppy When Loading Cells
Removing from Array During Enumeration in Swift
How to Get Uiviewcontroller of a Uiview's Superview in iOS
Making Video from Uiimage Array with Different Transition Animations