Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add RMMultiMBTilesTileSource #123

Open
wants to merge 2 commits into from

1 participant

@mtoon

On branch master
Changes to be committed:
new file: RMMultiMBTilesTileSource.h
new file: RMMultiMBTilesTileSource.m

Allows multiple mbtiles datasources to be merged into a single tilesource.

Malcolm Toon and others added some commits
Malcolm Toon Committer: Malcolm Toon <mtoon@Malcolm-Toons-MacBook-Pro.local>
 On branch master
 Changes to be committed:
	new file:   RMMultiMBTilesTileSource.h
	new file:   RMMultiMBTilesTileSource.m

Allows multiple mbtiles datasources to be merged into a single tilesource.
403949d
@mtoon mtoon modified: RMMultiMBTilesTileSource.m 5b8b3d4
@mtoon

I updated some memory leaks in the tile merge as well as updated the array to support full paths of the mbtiles files. This will allow the library to use files that are not originally bundled in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 27, 2011
  1. Committer: Malcolm Toon <mtoon@Malcolm-Toons-MacBook-Pro.local>

    Malcolm Toon authored
     On branch master
     Changes to be committed:
    	new file:   RMMultiMBTilesTileSource.h
    	new file:   RMMultiMBTilesTileSource.m
    
    Allows multiple mbtiles datasources to be merged into a single tilesource.
Commits on May 31, 2011
  1. @mtoon
This page is out of date. Refresh to see the latest.
View
70 MapView/Map/RMMultiMBTilesTileSource.h
@@ -0,0 +1,70 @@
+//
+// RMMultiMBTilesTileSource.h
+//
+//
+// Created by Malcolm Toon on 5/21/11.
+// Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "RMTileSource.h"
+
+@class RMFractalTileProjection;
+@class FMDatabase;
+
+#define kMBTilesDefaultTileSize 256
+#define kMBTilesDefaultMinTileZoom 0
+#define kMBTilesDefaultMaxTileZoom 18
+#define kMBTilesDefaultLatLonBoundingBox ((RMSphericalTrapezium){ .northeast = { .latitude = 90, .longitude = 180 }, \
+.southwest = { .latitude = -90, .longitude = -180 } })
+
+
+@interface RMMultiMBTilesTileSource : NSObject <RMTileSource> {
+ RMFractalTileProjection *tileProjection;
+ NSMutableDictionary *dbDictionary;
+ float _minZoom;
+ float _maxZoom;
+ NSString *_cacheName;
+ NSMutableArray *databaseNames;
+
+ NSString *shortName;
+ NSString *longDescription;
+ NSString *shortAttribution;
+ NSString *longAttribution;
+ bool hasBaseLayer;
+ NSString *baseLayerName;
+}
+
+@property (copy, nonatomic) NSString *baseLayerName;
+@property (copy, nonatomic) NSString *shortName;
+@property (copy, nonatomic) NSString *longDescription;
+@property (copy, nonatomic) NSString *shortAttribution;
+@property (copy, nonatomic) NSString *longAttribution;
+
+
+
+-(id)initWithArray:(NSArray *)fileArray cacheName:(NSString *)cacheName;
+-(void)closeAllDatabases;
+-(void)addDatabase:(NSString *)databaseFilename;
+-(RMTileImage *)getTileImageFrom:(FMDatabase *)db atZoom:(int)zoom atCol:(int)col atRow:(int)row aTile:(RMTile)tile;
+-(void)setBaseLayer:(NSString *)databaseFilename;
+-(void)setReverseTiles:(NSString *)databaseFilename;
+
+- (int)tileSideLength;
+- (void)setTileSideLength:(NSUInteger)aTileSideLength;
+- (RMTileImage *)tileImage:(RMTile)tile;
+- (NSString *)tileURL:(RMTile)tile;
+- (NSString *)tileFile:(RMTile)tile;
+- (NSString *)tilePath;
+- (id <RMMercatorToTileProjection>)mercatorToTileProjection;
+- (RMProjection *)projection;
+- (float)minZoom;
+- (float)maxZoom;
+- (void)setMinZoom:(NSUInteger)aMinZoom;
+- (void)setMaxZoom:(NSUInteger)aMaxZoom;
+- (RMSphericalTrapezium)latitudeLongitudeBoundingBox;
+- (void)didReceiveMemoryWarning;
+- (NSString *)uniqueTilecacheKey;
+- (void)removeAllCachedImages;
+
+@end
View
407 MapView/Map/RMMultiMBTilesTileSource.m
@@ -0,0 +1,407 @@
+//
+// RMMultiMBTilesTileSource.m
+//
+//
+// Created by Malcolm Toon on 5/21/11.
+// Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#import "RMMultiMBTilesTileSource.h"
+#import "RMTileImage.h"
+#import "RMProjection.h"
+#import "RMFractalTileProjection.h"
+#import "FMDatabase.h"
+
+
+@implementation RMMultiMBTilesTileSource
+
+@synthesize shortName, longDescription, shortAttribution, longAttribution;
+@synthesize baseLayerName;
+
+-(void)closeAllDatabases {
+
+ int c = [databaseNames count];
+ for (int i=0; i < c; i++) {
+ NSString *dbName = [databaseNames objectAtIndex:i];
+ NSDictionary *d = [dbDictionary objectForKey:dbName];
+ FMDatabase *db = [d objectForKey:@"database"];
+ [db close];
+ }
+
+}
+
+-(void)addDatabase:(NSString *)databaseFilename {
+ /*
+ DB Format:
+
+ databaseFilename
+ db: FMDatabase object
+ reverse-tiles: T/F
+ 1: Zoom Level (as NSNumber) (NSMutableDictionary)
+ mincol: NSNumber (int)
+ maxcol: NSNumber (int)
+ minrow: NSNumber (int)
+ maxrow: NSNumber (int)
+ .
+ .
+ 12
+
+ */
+
+// NSURL *tilesURL = [NSURL fileURLWithPath:[[NSBundle mainBundle]
+// pathForResource:databaseFilename
+// ofType:@"mbtiles"]];
+
+ NSURL *tilesURL = [NSURL fileURLWithPath:databaseFilename];
+
+ FMDatabase *db = [[FMDatabase databaseWithPath:[tilesURL relativePath]] retain];
+
+ if (![db open]) return;
+
+ NSMutableDictionary *dbEntry = [[NSMutableDictionary alloc] init];
+ [dbEntry setObject:@"F" forKey:@"base-layer"];
+ [dbEntry setObject:@"F" forKey:@"reverse-tiles"];
+ [dbEntry setObject:db forKey:@"database"];
+
+ [dbDictionary setObject:dbEntry forKey:databaseFilename];
+ [databaseNames addObject:databaseFilename];
+
+ // Get all the zoom levels and min/max for each
+ FMResultSet *results = [db executeQuery:@"select distinct zoom_level from tiles"];
+
+// if ([db hadError])
+// return [RMTileImage dummyTile:tile];
+
+ while ([results next]) {
+ int zl = [results intForColumn:@"zoom_level"];
+ if (zl < _minZoom) {
+ _minZoom = zl;
+ }
+ if (zl > _maxZoom) {
+ _maxZoom = zl;
+ }
+
+ FMResultSet *minMaxResults = [db executeQuery:@"select max(tile_column) as maxcol, max(tile_row) as maxrow, min(tile_column) as mincol, min(tile_row) as minrow from tiles where zoom_level = ?", [NSNumber numberWithInt:zl]];
+ if ([minMaxResults next]) {
+ //
+ int minCol = [minMaxResults intForColumn:@"mincol"];
+ int maxCol = [minMaxResults intForColumn:@"maxcol"];
+ int minRow = [minMaxResults intForColumn:@"minrow"];
+ int maxRow = [minMaxResults intForColumn:@"maxrow"];
+
+ // ZoomLevel Dictionary
+ NSMutableDictionary *zoomDictionary = [[NSMutableDictionary alloc] init];
+ [zoomDictionary setObject:[NSNumber numberWithInt:minCol] forKey:@"minCol"];
+ [zoomDictionary setObject:[NSNumber numberWithInt:maxCol] forKey:@"maxCol"];
+ [zoomDictionary setObject:[NSNumber numberWithInt:minRow] forKey:@"minRow"];
+ [zoomDictionary setObject:[NSNumber numberWithInt:maxRow] forKey:@"maxRow"];
+
+
+ [dbEntry setObject:zoomDictionary forKey:[NSNumber numberWithInt:zl]];
+
+ NSLog(@"Adding Zoom Level for %@, %i, %i, %i, %i, %i", databaseFilename, zl, minCol, maxCol, minRow, maxRow);
+ }
+ [minMaxResults close];
+ }
+
+ [results close];
+
+
+}
+
+-(NSMutableDictionary *)dictionaryForFile:(NSString *)aFilename {
+ NSMutableDictionary *d = [dbDictionary objectForKey:aFilename];
+ return d;
+}
+
+-(void)setBaseLayer:(NSString *)databaseFilename {
+ // Find the db entry with the datbase filename and set it's "base-layer" to "T"
+ NSMutableDictionary *d = [self dictionaryForFile:databaseFilename];
+ if (!d) {
+ return;
+ }
+ hasBaseLayer = YES;
+ self.baseLayerName = databaseFilename;
+ [d setObject:@"T" forKey:@"base-layer"];
+}
+
+-(void)setReverseTiles:(NSString *)databaseFilename {
+ // This will reverse how the tiles are loaded
+ // Find the db entry with the datbase filename and set it's "base-layer" to "T"
+ NSMutableDictionary *d = [self dictionaryForFile:databaseFilename];
+ if (!d) {
+ return;
+ }
+
+ [d setObject:@"T" forKey:@"reverse-tiles"];
+
+}
+
+
+
+-(id)initWithArray:(NSArray *)fileArray cacheName:(NSString *)cacheName {
+ if ( ! [super init])
+ return nil;
+
+ hasBaseLayer = NO;
+ _minZoom = 100000;
+ _maxZoom = 0;
+ _cacheName = [cacheName copy];
+
+ tileProjection = [[RMFractalTileProjection alloc] initFromProjection:[self projection]
+ tileSideLength:kMBTilesDefaultTileSize
+ maxZoom:kMBTilesDefaultMaxTileZoom
+ minZoom:kMBTilesDefaultMinTileZoom];
+
+ // Load all of the databases
+ databaseNames = [[NSMutableArray alloc] init];
+
+ dbDictionary = [[NSMutableDictionary alloc] init];
+
+ for (int i=0; i < [fileArray count]; i++) {
+ NSString *s = [fileArray objectAtIndex:i];
+ [self addDatabase:s];
+ }
+
+
+ return self;
+
+}
+
+
+- (void)dealloc
+{
+ [tileProjection release];
+ [self closeAllDatabases];
+ [dbDictionary release];
+ [databaseNames release];
+ [super dealloc];
+}
+
+- (int)tileSideLength {
+ return tileProjection.tileSideLength;
+}
+
+- (void)setTileSideLength:(NSUInteger)aTileSideLength {
+ [tileProjection setTileSideLength:aTileSideLength];
+}
+
+-(NSData *)getTileDataFrom:(FMDatabase *)db atZoom:(int)zoom atCol:(int)col atRow:(int)row {
+ FMResultSet *singleImageResult = [db executeQuery:@"select tile_data from tiles where zoom_level = ? and tile_column = ? and tile_row = ?",
+ [NSNumber numberWithFloat:zoom],
+ [NSNumber numberWithFloat:col],
+ [NSNumber numberWithFloat:row]];
+ if ([db hadError]) {
+ return nil;
+ } else {
+ if ([singleImageResult next]) {
+ NSData *data = [singleImageResult dataForColumn:@"tile_data"];
+ [singleImageResult close];
+ return data;
+ } else {
+ return nil;
+ }
+ }
+}
+
+-(RMTileImage *)getTileImageFrom:(FMDatabase *)db atZoom:(int)zoom atCol:(int)col atRow:(int)row aTile:(RMTile)tile {
+ RMTileImage *image = nil;
+
+ NSData *data = [self getTileDataFrom:db atZoom:zoom atCol:col atRow:row];
+ if (!data) {
+ image = [RMTileImage dummyTile:tile];
+ } else {
+ image = [RMTileImage imageForTile:tile withData:data];
+ }
+
+ return image;
+}
+
+- (RMTileImage *)tileImage:(RMTile)tile {
+ NSInteger zoom = tile.zoom;
+ NSInteger x = tile.x;
+ NSInteger y = pow(2, zoom) - tile.y - 1;
+
+ RMTileImage *image = nil;
+
+
+ // Get all of the various databases that can match this
+ NSMutableArray *matchingDBArray = [[NSMutableArray alloc] init];
+ @try {
+ int c = [databaseNames count];
+ for (int i=0; i < c; i++) {
+ NSString *dbName = [databaseNames objectAtIndex:i];
+ NSDictionary *d = [dbDictionary objectForKey:dbName];
+ NSDictionary *zoomD = [d objectForKey:[NSNumber numberWithInt:zoom]];
+ if (zoomD) {
+ NSNumber *minColn = [zoomD objectForKey:@"minCol"];
+ NSNumber *maxColn = [zoomD objectForKey:@"maxCol"];
+ NSNumber *minRown = [zoomD objectForKey:@"minRow"];
+ NSNumber *maxRown = [zoomD objectForKey:@"maxRow"];
+
+ int minCol = [minColn intValue];
+ int maxCol = [maxColn intValue];
+ int minRow = [minRown intValue];
+ int maxRow = [maxRown intValue];
+
+ if ([[d objectForKey:@"reverse-tiles"] isEqualToString:@"T"]) {
+ y = tile.y;
+ } else {
+ y = pow(2, zoom) - tile.y - 1;
+ }
+
+ if ((x >= minCol) && (x <= maxCol) && (y >= minRow) && (y <= maxRow)) {
+ // NSLog(@"Found match: %@ for z: %i x: %i y: %i", dbName, zoom, x, y);
+ // Check to see if it's a base layer and if so, insert at the beginning
+ if ([[d objectForKey:@"base-layer"] isEqualToString:@"T"]) {
+ // NSLog(@"Current layer is baselayer... inserting instead of adding");
+ [matchingDBArray insertObject:d atIndex:0];
+ } else {
+ [matchingDBArray addObject:d];
+ }
+ }
+ }
+ }
+
+ // Get all of the images
+ if ([matchingDBArray count] == 0) {
+ image = [RMTileImage dummyTile:tile];
+ } else if ([matchingDBArray count] == 1) {
+ // Optimized for the single use case
+ NSDictionary *singleImageDict = [matchingDBArray objectAtIndex:0];
+ FMDatabase *singleImageDB = [singleImageDict objectForKey:@"database"];
+ if ([[singleImageDict objectForKey:@"reverse-tiles"] isEqualToString:@"T"]) {
+ y = tile.y;
+ } else {
+ y = pow(2, zoom) - tile.y - 1;
+ }
+ image = [self getTileImageFrom:singleImageDB atZoom:zoom atCol:x atRow:y aTile:tile];
+ } else {
+ // Iterates the list and creates a combined tile
+ // NSMutableArray *tileImageArray = [[NSMutableArray alloc] init];
+
+ // Create te
+ CGRect area = CGRectMake(0,0,256,256);
+ UIGraphicsBeginImageContext(area.size);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGContextRetain(context);
+
+ // mirroring context
+ CGContextTranslateCTM(context, 0.0, area.size.height);
+ CGContextScaleCTM(context, 1.0, -1.0);
+
+ for (int j=0; j < [matchingDBArray count]; j++) {
+ NSDictionary *d = [matchingDBArray objectAtIndex:j];
+ FMDatabase *db = [d objectForKey:@"database"];
+
+ if ([[d objectForKey:@"reverse-tiles"] isEqualToString:@"T"]) {
+ y = tile.y;
+ } else {
+ y = pow(2, zoom) - tile.y - 1;
+ }
+
+ NSData *imageData = [self getTileDataFrom:db atZoom:zoom atCol:x atRow:y];
+ UIImage *tempImage = [[UIImage alloc] initWithData:imageData];
+ // [imageData release];
+
+ CGContextBeginTransparencyLayer(context, nil);
+
+ if (hasBaseLayer) {
+ if ([[d objectForKey:@"base-layer"] isEqualToString:@"T"]) {
+ CGContextSetAlpha( context, 1.0 );
+ } else {
+ CGContextSetAlpha( context, 1.0 );
+ }
+ } else {
+ CGContextSetAlpha( context, 1.0 );
+ }
+
+ CGContextDrawImage(context, area, tempImage.CGImage);
+ [tempImage release];
+ CGContextEndTransparencyLayer(context);
+ }
+
+ // get created image
+ UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
+ CGContextRelease(context);
+ UIGraphicsEndImageContext();
+ NSData *finalData = UIImagePNGRepresentation(finalImage);
+
+ image = [RMTileImage imageForTile:tile withData:finalData];
+ }
+// @catch (NSException * e) {
+// <#handler#>
+// }
+ }
+ @finally {
+ [matchingDBArray release];
+ }
+
+ return image;
+}
+
+- (NSString *)tileURL:(RMTile)tile
+{
+ return nil;
+}
+
+- (NSString *)tileFile:(RMTile)tile
+{
+ return nil;
+}
+
+- (NSString *)tilePath
+{
+ return nil;
+}
+
+- (id <RMMercatorToTileProjection>)mercatorToTileProjection
+{
+ return [[tileProjection retain] autorelease];
+}
+
+- (RMProjection *)projection
+{
+ return [RMProjection googleProjection];
+}
+
+- (float)minZoom
+{
+ return _minZoom;
+}
+
+- (float)maxZoom
+{
+ return _maxZoom;
+}
+
+- (void)setMinZoom:(NSUInteger)aMinZoom
+{
+ [tileProjection setMinZoom:aMinZoom];
+}
+
+- (void)setMaxZoom:(NSUInteger)aMaxZoom
+{
+ [tileProjection setMaxZoom:aMaxZoom];
+}
+
+- (RMSphericalTrapezium)latitudeLongitudeBoundingBox
+{
+ return kMBTilesDefaultLatLonBoundingBox;
+}
+
+- (void)didReceiveMemoryWarning
+{
+ NSLog(@"*** didReceiveMemoryWarning in %@", [self class]);
+}
+
+- (NSString *)uniqueTilecacheKey {
+ return _cacheName;
+}
+
+- (void)removeAllCachedImages
+{
+ NSLog(@"*** removeAllCachedImages in %@", [self class]);
+}
+
+@end
Something went wrong with that request. Please try again.