Calculating tiles to display in a MapRect when “over-zoomed” beyond the overlay tile set

前端 未结 3 1131
予麋鹿
予麋鹿 2020-12-04 18:47

I am working on an app that uses MKOverlay views to layer my own custom maps on top of the Google base map. I have been using Apple\'s excellent TileMap sample code (from W

相关标签:
3条回答
  • 2020-12-04 19:22

    A bit late to the party, but... Under iOS 7.0 and greater, you can use the maximumZ property on MKTileOverlay. From the docs:

    If you use different overlay objects to represent different tiles at different zoom levels, use this property to specify the maximum zoom level supported by this overlay’s tiles. At zoom level 0, tiles cover the entire world map; at zoom level 1, tiles cover 1/4 of the world; at zoom level 2, tiles cover 1/16 of the world, and so on. The map never tries to load tiles for a zoom level greater than the value specified by this property.

    - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    
        if ([overlay isKindOfClass:[MKTileOverlay class]]) {
    
            MKTileOverlay *ovrly = (MKTileOverlay *)overlay;
            ovrly.maximumZ = 9;  // Set your maximum zoom level here
            MKTileOverlayRenderer *rndr = [[MKTileOverlayRenderer alloc] initWithTileOverlay:ovrly];
            return rndr;
    
        }
    
        return nil;
    }
    
    0 讨论(0)
  • 2020-12-04 19:25

    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
    0 讨论(0)
  • 2020-12-04 19:25

    This algorithm seems to produce a lot of map tiles outside of the MapRect. Adding the following inside the loop to skip tiles outside the boundaries helps a lot:

    if (! MKMapRectIntersectsRect(rect, tileMapRect))
       continue;
    
    0 讨论(0)
提交回复
热议问题