2010-12-11 61 views
10

Je travaille sur une application qui utilise les vues MKOverlay pour superposer mes propres cartes personnalisées en haut de la carte de base Google. J'ai utilisé l'excellent exemple de code TileMap d'Apple (de la WWDC 2010) comme guide. Mon problème - lorsque "overzoomed" à un niveau de détail plus profond que mon ensemble de tuiles générées, le code affiche rien parce qu'il n'y a pas de tuiles disponibles au niveau Z calculé.Calcul de mosaïques à afficher dans un MapRect en cas de "sur-zoom" au-delà de l'ensemble de mosaïques de superposition

Le comportement que je veux - lorsque l'application est "sur-agrandie", il ne faut que grossir le niveau le plus profond des tuiles. C'est une bonne expérience pour l'utilisateur de devenir plus flou - c'est une très mauvaise expérience de faire disparaître le recouvrement.

Voici le code qui retourne les tuiles à dessiner - J'ai besoin de comprendre comment modifier cela pour plafonner la profondeur Z sans casser la mise à l'échelle de la trame étant calculée pour la tuile de superposition. Des pensées???


- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale 
{ 
    NSInteger z = zoomScaleToZoomLevel(scale); 

    // PROBLEM: I need to find a way to cap z at my maximum tile directory depth. 

    // Number of tiles wide or high (but not wide * high) 
    NSInteger tilesAtZ = pow(2, z); 

    NSInteger minX = floor((MKMapRectGetMinX(rect) * scale)/TILE_SIZE); 
    NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale)/TILE_SIZE); 
    NSInteger minY = floor((MKMapRectGetMinY(rect) * scale)/TILE_SIZE); 
    NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale)/TILE_SIZE); 

    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 * TILE_SIZE)/scale, 
               (double)(y * TILE_SIZE)/scale, 
               TILE_SIZE/scale, 
               TILE_SIZE/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; 
} 

Pour votre information, voici la fonction d'aide zoomScaleToZoomLevel que quelqu'un a demandé sur:

// Convert an MKZoomScale to a zoom level where level 0 contains 4 256px square tiles, 
// which is the convention used by gdal2tiles.py. 
static NSInteger zoomScaleToZoomLevel(MKZoomScale scale) { 
    double numTilesAt1_0 = MKMapSizeWorld.width/TILE_SIZE; 
    NSInteger zoomLevelAt1_0 = log2(numTilesAt1_0); // add 1 because the convention skips a virtual level with 1 tile. 
    NSInteger zoomLevel = MAX(0, zoomLevelAt1_0 + floor(log2f(scale) + 0.5)); 
    return zoomLevel; 
} 

Répondre

12

Imaginez que la superposition est la couverture nuageuse - ou dans notre cas, la couverture du signal cellulaire. Il peut ne pas "sembler bien" tout en zoomant en profondeur, mais la superposition transmet toujours des informations essentielles à l'utilisateur.

J'ai résolu le problème en ajoutant un mode OverZoom pour améliorer l'exemple de code TileMap d'Apple.

Voici la nouvelle fonction tilesInMapRect dans 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; 
} 

Et voici la nouvelle drawMapRect dans 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]; 
    } 
} 

semble fonctionner grand maintenant. BTW - Je pense que le code de l'échantillon TileMap manque une [version d'image] et une fuite de mémoire. Notez où je l'ai ajouté dans le code ci-dessus.

J'espère que cela aide d'autres avec le même problème.

Cheers,

  • Chris
+2

Chris - vous êtes un sauveur. Je n'avais pas envie de créer une solution de contournement, alors j'ai fait une recherche, et votre réponse est apparue. J'ai copié la fonction 'zoomScaleToZoomLevel' et la constante' TILE_SIZE' dans le fichier TileOverlayView.m de TileOverlay.m afin de ne pas lancer une erreur, mais à part cela, cela fonctionne parfaitement. Merci un million! –

+0

commentaire supprimé, le code fonctionne bien – Craig

+1

J'utilise une manière différente de trouver mes tuiles comme décrit à https://github.com/mtigas/iOS-MapLayerDemo, mais l'idée est la même fondamentalement. J'ai du mal à porter le zoom sur. Quelqu'un a fait ça? –

2

Cet algorithme semble produire beaucoup de tuiles de carte en dehors de la MapRect. Ajouter ce qui suit à l'intérieur de la boucle de sauter des tuiles en dehors des limites aide beaucoup:

if (! MKMapRectIntersectsRect(rect, tileMapRect)) 
    continue; 
0

Un peu en retard à la fête, mais ... Sous iOS 7.0 et plus, vous pouvez utiliser la propriété maximumZ sur MKTileOverlay. De the docs:

Si vous utilisez des objets de recouvrement différents pour représenter les différentes tuiles à différents niveaux de zoom, utiliser cette propriété pour spécifier le niveau de zoom maximum supporté par les tuiles de cette superposition.Au niveau de zoom 0, les tuiles couvrent la carte du monde entière; au niveau de zoom 1, les carreaux couvrent 1/4 du monde; au niveau de zoom 2, les carreaux couvrent 1/16 du monde, et ainsi de suite. La carte ne cherche jamais à charger les mosaïques pour un niveau de zoom supérieur à la valeur spécifiée par cette propriété.

- (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; 
} 
+1

Cela n'empêche pas que la carte soit agrandie au-delà du minimum - elle n'affiche tout simplement pas la superposition, ce qui signifie qu'une carte sous-jacente apparaît à la place ou aucune carte si canReplaceMapContent est affiché – earnshavian