From 47d2c55b7539a59d15fab6362de8465009ad9265 Mon Sep 17 00:00:00 2001 From: Braeburn Date: Sun, 1 Nov 2015 16:18:28 -0500 Subject: [PATCH] Updated to support VE file format. --- FMDatabase.m | 5 +- FMDatabaseAdditions.m | 12 +- map2sqlite.m | 223 ++++++++++++++++++++++----- map2sqlite.xcodeproj/project.pbxproj | 26 ++-- 4 files changed, 206 insertions(+), 60 deletions(-) diff --git a/FMDatabase.m b/FMDatabase.m index 7c8eb88..4d952c7 100644 --- a/FMDatabase.m +++ b/FMDatabase.m @@ -702,8 +702,9 @@ - (void)setUseCount:(long)value { } } -- (NSString*) description { - return [NSString stringWithFormat:@"%@ %d hit(s) for query %@", [super description], useCount, query]; +- (NSString*) description +{ + return [NSString stringWithFormat:@"%@ %ld hit(s) for query %@", [super description], useCount, query]; } diff --git a/FMDatabaseAdditions.m b/FMDatabaseAdditions.m index 1b357f9..0f8e868 100644 --- a/FMDatabaseAdditions.m +++ b/FMDatabaseAdditions.m @@ -58,17 +58,17 @@ - (NSData*)dataForQuery:(NSString*)query, ...; { //this technique is being implemented as described by Matt Gallagher at //http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html -- (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments { - +- (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments +{ id returnObject; //also need make sure that everything in arguments is an Obj-C object //or else argList will be the wrong size NSUInteger argumentsCount = [arguments count]; - char *argList = (char *)malloc(sizeof(id *) * argumentsCount); + va_list* argList = malloc(sizeof(id *) * argumentsCount); [arguments getObjects:(id *)argList]; - returnObject = [self executeQuery:sql arguments:argList]; + returnObject = [self executeQuery:sql arguments:*argList]; free(argList); @@ -82,10 +82,10 @@ - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments //also need make sure that everything in arguments is an Obj-C object //or else argList will be the wrong size NSUInteger argumentsCount = [arguments count]; - char *argList = (char *)malloc(sizeof(id *) * argumentsCount); + va_list* argList = malloc(sizeof(id *) * argumentsCount); [arguments getObjects:(id *)argList]; - returnBool = [self executeUpdate:sql arguments:argList]; + returnBool = [self executeUpdate:sql arguments:*argList]; free(argList); diff --git a/map2sqlite.m b/map2sqlite.m index 7fcfffc..f229cd1 100644 --- a/map2sqlite.m +++ b/map2sqlite.m @@ -99,7 +99,7 @@ #define NSStringFromRect(r) ([NSString stringWithFormat:@"{x=%1.1f,y=%1.1f,w=%1.1f,h=%1.1f}", (r).origin.x, (r).origin.y, (r).size.width, (r).size.height]) // version of this program -#define kVersion @"1.0" +#define kVersion @"2.0" // mandatory preference keys #define kMinZoomKey @"map.minZoom" @@ -126,6 +126,49 @@ * Helper functions */ + +/* +/// Converts a QuadKey into tile XY coordinates. +/// +/// QuadKey of the tile. +/// Output parameter receiving the tile X coordinate. +/// Output parameter receiving the tile Y coordinate. +/// Output parameter receiving the level of detail. + */ +void QuadKeyToTileXY(const char* quadKey, int* tileX, int* tileY, int* levelOfDetail) +{ + *levelOfDetail = strlen(quadKey); + *tileX = 0; + *tileY = 0; + + for(int i = *levelOfDetail; i > 0; i--) + { + int mask = 1 << (i - 1); + switch (quadKey[*levelOfDetail - i]) + { + case '0': + break; + + case '1': + *tileX |= mask; + break; + + case '2': + *tileY |= mask; + break; + + case '3': + *tileX |= mask; + *tileY |= mask; + break; + + default: + NSLog(@"Invalid QuadKey digit sequence."); + } + } +} + + /* * Calculates the top left coordinate of a tile. * (assumes OpenStreetmap tiles) @@ -141,21 +184,26 @@ CGPoint pointForTile(int row, int col, int zoom) { /* * Prints usage information. */ -void printUsage() { - NSLog(@"Usage: map2sqlite -db [-mapdir ]"); +void printUsage() +{ + printf(" Reads OSM or VE format tiles into an SQLite database file.\n"); + printf(" Usage: map2sqlite -db [-mapdir ]\n"); } /* * Converts a hexadecimal string into an integer value. */ -NSUInteger scanHexInt(NSString* s) { - NSUInteger value; +NSInteger scanHexInt(NSString* s) { + unsigned int value; NSScanner* scanner = [NSScanner scannerWithString:s]; [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"LRC"]]; - if ([scanner scanHexInt:&value]) { - return value; - } else { + if ([scanner scanHexInt:&value]) + { + return (NSInteger)value; + } + else + { NSLog(@"not a hex int %@", s); return -1; } @@ -186,13 +234,24 @@ uint64_t RMTileKey(int tileZoom, int tileX, int tileY) /* * Creates an empty sqlite database */ -FMDatabase* createDB(NSString* dbFile) { +FMDatabase* createDB(NSString* dbFile) +{ // delete the old db file - [[NSFileManager defaultManager] removeFileAtPath:dbFile handler:nil]; + if(dbFile == nil || (dbFile.length < 1)) + { + return nil; + } + + BOOL isDir; + if([[NSFileManager defaultManager] fileExistsAtPath:dbFile isDirectory:&isDir] && !isDir) + { + [[NSFileManager defaultManager] removeItemAtPath:dbFile error:nil]; + } FMDatabase* db = [FMDatabase databaseWithPath:dbFile]; NSLog(@"Creating %@", dbFile); - if (![db open]) { + if (![db open]) + { NSLog(@"Could not create %@.", dbFile); return nil; } @@ -252,7 +311,8 @@ void createPrefs(FMDatabase* db) { * Creates the "tiles" table and imports a given directory structure * into the table. */ -void createMapDB(FMDatabase* db, NSString* mapDir) { +void createMapDB(FMDatabase* db, NSString* mapDir) +{ NSFileManager* fileManager = [NSFileManager defaultManager]; // import the tiles @@ -274,17 +334,34 @@ void createMapDB(FMDatabase* db, NSString* mapDir) { int minZoom = INT_MAX; int maxZoom = INT_MIN; NSLog(@"Importing map tiles at %@", mapDir); - for (NSString* f in [fileManager subpathsAtPath:mapDir]) { - if ([[[f pathExtension] lowercaseString] isEqualToString:@"png"]) { + + NSArray* paths = [fileManager subpathsAtPath:mapDir]; + + for (NSString* f in paths) + { + if ([[[f pathExtension] lowercaseString] isEqualToString:@"png"]) + { NSArray* comp = [f componentsSeparatedByString:@"/"]; - NSUInteger zoom, row, col; + int zoom, row, col; // openstreetmap or ArcGis tiles? - if ([[comp objectAtIndex:0] characterAtIndex:0] == 'L') { + if ([[comp objectAtIndex:0] characterAtIndex:0] == 'L') + { zoom = [[[comp objectAtIndex:0] substringFromIndex:1] intValue]; row = scanHexInt([comp objectAtIndex:1]); col = scanHexInt([[comp objectAtIndex:2] stringByDeletingPathExtension]); - } else { + } + else if(comp.count == 1) // + { + NSString* tileName = [comp objectAtIndex:0]; + NSRange r = {0, tileName.length - 4}; + tileName = [tileName substringWithRange:r]; + const char* tile = [tileName UTF8String]; + + QuadKeyToTileXY(tile, &row, &col, &zoom); + } + else + { zoom = [[comp objectAtIndex:0] intValue]; col = [[comp objectAtIndex:1] intValue]; row = [[[comp objectAtIndex:2] stringByDeletingPathExtension] intValue]; @@ -295,7 +372,8 @@ void createMapDB(FMDatabase* db, NSString* mapDir) { maxZoom = fmax(maxZoom, zoom); NSData* image = [[NSData alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", mapDir, f]]; - if (image) { + if (image) + { executeUpdate(db, @"insert into tiles (tilekey, zoom, row, col, image) values (?, ?, ?, ?, ?)", [NSNumber numberWithLongLong:RMTileKey(zoom, col, row)], [NSNumber numberWithInt:zoom], @@ -303,7 +381,9 @@ void createMapDB(FMDatabase* db, NSString* mapDir) { [NSNumber numberWithInt:col], image); [image release]; - } else { + } + else + { NSLog(@"Could not read %@", f); } } @@ -342,8 +422,6 @@ void createMapDB(FMDatabase* db, NSString* mapDir) { * Displays some statistics about the tiles in the imported database. */ void showMapDBStats(FMDatabase* db, NSString* dbFile, NSString* mapDir) { - NSFileManager* fileManager = [NSFileManager defaultManager]; - // print some map statistics // print some map statistics FMResultSet* rs = executeQuery(db, @"select count(*) count, min(zoom) min_zoom, max(zoom) max_zoom from tiles"); @@ -351,7 +429,10 @@ void showMapDBStats(FMDatabase* db, NSString* dbFile, NSString* mapDir) { int count = [rs intForColumn:@"count"]; int minZoom = [rs intForColumn:@"min_zoom"]; int maxZoom = [rs intForColumn:@"max_zoom"]; - unsigned long long fileSize = [[[fileManager fileAttributesAtPath:dbFile traverseLink:YES] objectForKey:NSFileSize] unsignedLongLongValue]; + + NSError *error; + NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:dbFile error:&error]; + unsigned long long fileSize = [[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; NSLog(@"\n"); NSLog(@"Map statistics"); @@ -389,49 +470,106 @@ void showMapDBStats(FMDatabase* db, NSString* dbFile, NSString* mapDir) { [rs close]; } + /* * main method */ -int main (int argc, const char * argv[]) { +int main (int argc, const char * argv[]) +{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSFileManager* fileManager = [NSFileManager defaultManager]; + char buffer[128]; // print the version - NSLog(@"map2sqlite %@\n", kVersion); - + printf("map2sqlite %s\n", [kVersion UTF8String]); + printUsage(); + // get the command line args NSUserDefaults *args = [NSUserDefaults standardUserDefaults]; // command line args NSString* dbFile = [args stringForKey:@"db"]; NSString* mapDir = [args stringForKey:@"mapdir"]; - - // check command line args - if (dbFile == nil) { - printUsage(); - [pool release]; - return 1; - } + NSString* currentDir = [fileManager currentDirectoryPath]; + printf("> %s\n", [currentDir UTF8String]); + // check that the map directory exists - if (mapDir != nil) { - BOOL isDir; - if (![fileManager fileExistsAtPath:mapDir isDirectory:&isDir] && isDir) { - NSLog(@"Map directory does not exist: %@", mapDir); + BOOL isDir; + + if(mapDir == nil) + { + size_t length; + printf("Enter map directory > "); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + { [pool release]; return 1; } + + length = strlen(buffer); + if (buffer[length - 1] == '\n') // remove the traling '\n' add by fgets + buffer[length - 1] = '\0'; + mapDir = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding]; + } + + if(![fileManager fileExistsAtPath:mapDir isDirectory:&isDir] || !isDir) + { + NSLog(@"Map directory does not exist: %@", mapDir); + [pool release]; + return 3; + } + + // check command line args + if(dbFile == nil) + { + size_t length; + printf("Enter DB file name > "); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + return -1; // error or end of file sent to the terminal + length = strlen(buffer); + if (buffer[length - 1] == '\n') // remove the traling '\n' add by fgets + buffer[length - 1] = '\0'; + + dbFile = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding]; } - // delete the old db - [fileManager removeFileAtPath:dbFile handler:nil]; + if((dbFile == nil) || (dbFile.length < 1)) + { + dbFile = [mapDir lastPathComponent]; + } + + NSString* ext = [dbFile pathExtension]; + + if((ext == nil) || (ext.length < 1)) + { + dbFile = [dbFile stringByAppendingString:@".db"]; + } + + // delete any old db + if([fileManager fileExistsAtPath:dbFile isDirectory:&isDir]) + { + if(isDir) + { + NSLog(@"Error db file name is a directory: %@", dbFile); + [pool release]; + return 4; + } + else + { + NSURL *fileURL = [NSURL fileURLWithPath:dbFile]; + [fileManager removeItemAtURL:fileURL error:nil]; + } + } // create the db FMDatabase* db = createDB(dbFile); - if (db == nil) { - NSLog(@"Error creating database %@", dbFile); + + if(db == nil) + { + NSLog(@"Error creating database: %@", dbFile); [pool release]; - return 1; + return 3; } // cache the statements as we're using them a lot @@ -441,7 +579,8 @@ int main (int argc, const char * argv[]) { createPrefs(db); // import the map - if (mapDir != nil) { + if (mapDir != nil) + { createMapDB(db, mapDir); showMapDBStats(db, dbFile, mapDir); } diff --git a/map2sqlite.xcodeproj/project.pbxproj b/map2sqlite.xcodeproj/project.pbxproj index 57d57d9..2096546 100644 --- a/map2sqlite.xcodeproj/project.pbxproj +++ b/map2sqlite.xcodeproj/project.pbxproj @@ -3,14 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 45; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ - 0105B4220FDB9CF8005F4C4D /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0105B4210FDB9CF8005F4C4D /* libsqlite3.0.dylib */; }; 0105B42C0FDB9D15005F4C4D /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 0105B4270FDB9D15005F4C4D /* FMDatabase.m */; }; 0105B42D0FDB9D15005F4C4D /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0105B4290FDB9D15005F4C4D /* FMDatabaseAdditions.m */; }; 0105B42E0FDB9D15005F4C4D /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 0105B42B0FDB9D15005F4C4D /* FMResultSet.m */; }; + 560B31211BE69F5D002582BE /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 560B31201BE69F5D002582BE /* libsqlite3.0.tbd */; }; 8DD76F9A0486AA7600D96B5E /* map2sqlite.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* map2sqlite.m */; settings = {ATTRIBUTES = (); }; }; 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; /* End PBXBuildFile section */ @@ -28,7 +28,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0105B4210FDB9CF8005F4C4D /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; }; 0105B4260FDB9D15005F4C4D /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = ""; }; 0105B4270FDB9D15005F4C4D /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = ""; }; 0105B4280FDB9D15005F4C4D /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseAdditions.h; sourceTree = ""; }; @@ -38,6 +37,7 @@ 08FB7796FE84155DC02AAC07 /* map2sqlite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = map2sqlite.m; sourceTree = ""; }; 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 32A70AAB03705E1F00C91783 /* map2sqlite_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = map2sqlite_Prefix.pch; sourceTree = ""; }; + 560B31201BE69F5D002582BE /* libsqlite3.0.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; 8DD76FA10486AA7600D96B5E /* map2sqlite */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = map2sqlite; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -46,8 +46,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 560B31211BE69F5D002582BE /* libsqlite3.0.tbd in Frameworks */, 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, - 0105B4220FDB9CF8005F4C4D /* libsqlite3.0.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -91,7 +91,7 @@ 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { isa = PBXGroup; children = ( - 0105B4210FDB9CF8005F4C4D /* libsqlite3.0.dylib */, + 560B31201BE69F5D002582BE /* libsqlite3.0.tbd */, 08FB779EFE84155DC02AAC07 /* Foundation.framework */, ); name = "External Frameworks and Libraries"; @@ -138,9 +138,16 @@ /* Begin PBXProject section */ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; + attributes = { + LastUpgradeCheck = 0700; + }; buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "map2sqlite" */; - compatibilityVersion = "Xcode 3.1"; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; hasScannedForEncodings = 1; + knownRegions = ( + en, + ); mainGroup = 08FB7794FE84155DC02AAC07 /* map2sqlite */; projectDirPath = ""; projectRoot = ""; @@ -197,27 +204,26 @@ 1DEB927908733DD40010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; - SDKROOT = macosx10.5; + SDKROOT = macosx; }; name = Debug; }; 1DEB927A08733DD40010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; GCC_C_LANGUAGE_STANDARD = c99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; - SDKROOT = macosx10.5; + SDKROOT = macosx; }; name = Release; };