RMMBTilesTileSource.m 9.08 KB
//
//  RMMBTilesTileSource.m
//
//  Created by Justin R. Miller on 6/18/10.
//  Copyright 2010, Code Sorcery Workshop, LLC and Development Seed, Inc.
//  All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are met:
//  
//      * Redistributions of source code must retain the above copyright
//        notice, this list of conditions and the following disclaimer.
//  
//      * Redistributions in binary form must reproduce the above copyright
//        notice, this list of conditions and the following disclaimer in the
//        documentation and/or other materials provided with the distribution.
//  
//      * Neither the names of Code Sorcery Workshop, LLC or Development Seed,
//        Inc., nor the names of its contributors may be used to endorse or
//        promote products derived from this software without specific prior
//        written permission.
//  
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
//  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
//  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#import "RMMBTilesTileSource.h"
#import "RMTileImage.h"
#import "RMProjection.h"
#import "RMFractalTileProjection.h"

#import "FMDatabase.h"
#import "FMDatabaseQueue.h"

@implementation RMMBTilesTileSource

- (id)initWithTileSetURL:(NSURL *)tileSetURL
{
	if ( ! [super init])
		return nil;
	
	tileProjection = [[RMFractalTileProjection alloc] initFromProjection:[self projection] 
                                                          tileSideLength:kMBTilesDefaultTileSize 
                                                                 maxZoom:kMBTilesDefaultMaxTileZoom 
                                                                 minZoom:kMBTilesDefaultMinTileZoom];

    queue = [[FMDatabaseQueue databaseQueueWithPath:[tileSetURL relativePath]] retain];
    
    if ( ! queue)
        return nil;
    
    [[queue database] setShouldCacheStatements:YES];
    
	return self;
}

- (void)cancelAllDownloads
{
    // no-op
}

- (void)dealloc
{
	[tileProjection release];
    
    [queue release];
    
	[super dealloc];
}

- (int)tileSideLength
{
	return tileProjection.tileSideLength;
}

- (void)setTileSideLength:(NSUInteger)aTileSideLength
{
	[tileProjection setTileSideLength:aTileSideLength];
}

- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache
{
    NSAssert4(((tile.zoom >= self.minZoom) && (tile.zoom <= self.maxZoom)),
			  @"%@ tried to retrieve tile with zoomLevel %d, outside source's defined range %f to %f", 
			  self, tile.zoom, self.minZoom, self.maxZoom);

    NSInteger zoom = tile.zoom;
    NSInteger x    = tile.x;
    NSInteger y    = pow(2, zoom) - tile.y - 1;
    
    __block UIImage *image;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select tile_data from tiles where zoom_level = ? and tile_column = ? and tile_row = ?", 
                                   [NSNumber numberWithShort:zoom], 
                                   [NSNumber numberWithUnsignedInt:x], 
                                   [NSNumber numberWithUnsignedInt:y]];

        if ([db hadError])
            image = [RMTileImage errorTile];
        
        [results next];
        
        NSData *data = [results dataForColumn:@"tile_data"];
        
        if ( ! data)
            image = [RMTileImage errorTile];
        
        else
            image = [UIImage imageWithData:data];
        
        [results close];
    }];

    return image;
}

- (NSString *)tileURL:(RMTile)tile
{
    return nil;
}

- (NSString *)tileFile:(RMTile)tile
{
    return nil;
}

- (NSString *)tilePath
{
    return nil;
}

- (RMFractalTileProjection *)mercatorToTileProjection
{
	return [[tileProjection retain] autorelease];
}

- (RMProjection *)projection
{
	return [RMProjection googleProjection];
}

- (float)minZoom
{
    __block double minZoom;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select min(zoom_level) from tiles"];
        
        if ([db hadError])
            minZoom = kMBTilesDefaultMinTileZoom;
        
        [results next];
        
        minZoom = [results doubleForColumnIndex:0];
        
        [results close];
    }];
    
    return minZoom;
}

- (float)maxZoom
{
    __block double maxZoom;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select max(zoom_level) from tiles"];
        
        if ([db hadError])
            maxZoom = kMBTilesDefaultMaxTileZoom;
        
        [results next];
        
        maxZoom = [results doubleForColumnIndex:0];
        
        [results close];
    }];
    
    return maxZoom;
}

- (void)setMinZoom:(NSUInteger)aMinZoom
{
    [tileProjection setMinZoom:aMinZoom];
}

- (void)setMaxZoom:(NSUInteger)aMaxZoom
{
    [tileProjection setMaxZoom:aMaxZoom];
}

- (RMSphericalTrapezium)latitudeLongitudeBoundingBox
{
    __block RMSphericalTrapezium bounds = kMBTilesDefaultLatLonBoundingBox;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select value from metadata where name = 'bounds'"];
        
        [results next];
        
        NSString *boundsString = [results stringForColumnIndex:0];
        
        [results close];
        
        if (boundsString)
        {
            NSArray *parts = [boundsString componentsSeparatedByString:@","];
            
            if ([parts count] == 4)
            {
                bounds.southWest.longitude = [[parts objectAtIndex:0] doubleValue];
                bounds.southWest.latitude  = [[parts objectAtIndex:1] doubleValue];
                bounds.northEast.longitude = [[parts objectAtIndex:2] doubleValue];
                bounds.northEast.latitude  = [[parts objectAtIndex:3] doubleValue];
            }
        }
        
    }];
    
    return bounds;
}

- (BOOL)coversFullWorld
{
    RMSphericalTrapezium ownBounds     = [self latitudeLongitudeBoundingBox];
    RMSphericalTrapezium defaultBounds = kMBTilesDefaultLatLonBoundingBox;
    
    if (ownBounds.southWest.longitude <= defaultBounds.southWest.longitude + 10 && 
        ownBounds.northEast.longitude >= defaultBounds.northEast.longitude - 10)
        return YES;
    
    return NO;
}

- (NSString *)legend
{
    __block NSString *legend;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select value from metadata where name = 'legend'"];

        if ([db hadError])
            legend = nil;

        [results next];

        legend = [results stringForColumn:@"value"];

        [results close];
    }];
    
    return legend;
}

- (void)didReceiveMemoryWarning
{
    NSLog(@"*** didReceiveMemoryWarning in %@", [self class]);
}

- (NSString *)uniqueTilecacheKey
{
    return [NSString stringWithFormat:@"MBTiles%@", [queue.path lastPathComponent]];
}

- (NSString *)shortName
{
    __block NSString *shortName;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select value from metadata where name = 'name'"];
        
        if ([db hadError])
            shortName = nil;
        
        [results next];
        
        shortName = [results stringForColumnIndex:0];
        
        [results close];
    }];
    
    return shortName;
}

- (NSString *)longDescription
{
    __block NSString *description;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select value from metadata where name = 'description'"];
        
        if ([db hadError])
            description = nil;
        
        [results next];
        
        description = [results stringForColumnIndex:0];
        
        [results close];
    }];
    
    return [NSString stringWithFormat:@"%@ - %@", [self shortName], description];
}

- (NSString *)shortAttribution
{
    __block NSString *attribution;
    
    [queue inDatabase:^(FMDatabase *db)
    {
        FMResultSet *results = [db executeQuery:@"select value from metadata where name = 'attribution'"];

        if ([db hadError])
            attribution = @"Unknown MBTiles attribution";

        [results next];

        attribution = [results stringForColumnIndex:0];

        [results close];
    }];
    
    return attribution;
}

- (NSString *)longAttribution
{
    return [NSString stringWithFormat:@"%@ - %@", [self shortName], [self shortAttribution]];
}

- (void)removeAllCachedImages
{
    NSLog(@"*** removeAllCachedImages in %@", [self class]);
}

@end