Monday, December 17, 2007

Hacking Maps.app on the iPhone

The release of the new Google Maps API for Sky got me thinking. Google Maps, and Maps for Sky, doesn't really work very well inside Safari.app on the iPhone or the iPod touch. The maps in the browser aren't set up to trigger on the correct Javascript events, and as a result scrolling and zooming just doesn't work correctly. However there is a mobile Maps.app for the iPhone, which not only scrolls, pans, and zooms properly, but is also a whole lot faster than the browser based version.

So I didn't really want to play with the browser based version of Google Maps on my iPod, I wanted to play with the mobile version. So the obvious thing to do would be to hack the mobile version of Maps.app to support the Sky. It's just a different tile set, shouldn't be too hard?

There is nothing obvious in the plist files associated with the Maps application or the GMM.framework itself, but this is only really to be expected. So I pulled out my hex editor of choice, I recommend Hex Fiend by the way, to see what I could be done. Initially, and unfortunately somewhat optimistically, I thought that it should just be a case of tracking down the strings containing the URLs where Maps pulls tiles from the remote server, and changing these to point to the Sky tile server instead. But after poking around for a while inside the GMM.framework this didn't look like it was going to be too easy, at least without decompiling the code. However I did discover references to a SQL database which the application was using as a tile cache for downloaded tiles. Pulling this database off the phone an into SQLLite on my desktop let me examine the schema, which has three separate tables,

images(zoom int, x int, y int, flags int, length int, data blob);
version(version int);
index1 on images (zoom,x,y,flags);

The x, y and z were self explanatory. That's just the x and y position of the tile at zoom level z. The flags looks to be whether the tile is a map (flag is 2) or satellite (flag is 3) tile, the blob was presumably the tile itself and length was obviously the length of the binary blob in bytes. Probably...

The obvious strategy at this point was to download all the tiles, and yes, I mean all the tiles, from the Google Sky tile server, and poke them into the tile cache database in the appropriate places to fake out the Maps.app application into thinking it didn't have to go to the web to download those tiles after all.

Oddly enough the maps tiles served by Google Maps for Sky are JPG files, and the maps tiles in the cache database are PNG files. The JPG's also seem to be 256×256 pixels in size, while the tiles in the cache file are 128×128 pixels in size. So we had a bit of an issue here. I could either take the thousands of downloaded tiles, convert them to PNG, and split them up into smaller tiles, or I could just try throwing them into the cache and see what happened. Want to take a guess at which one I chose to do first?

Pulling the eatblob.c out of the SQLite tutorial, I hacked together a quick script to push all 5,461 tiles (zoom level 0 to zoom level 6) into the cache database. Copying the new enlarged database back onto my iPod, I restarted the iPod and fired up Maps.app but things didn't exactly go as planned. Predictably I didn't get the mapping between the x and y's of the Sky tiles and the Map tiles quite right. In fact I got my x and y's round the wrong way in one of my scripts. Second time was the charm though...


SMC in Google Maps on an Jailbroken iPod touch

Basically at this point I've got Google Maps for Sky working on my iPod touch under Firmware 1.1.2. This hack will presumably also work on the iPhone since my Maps.app was taken off a Jailbroken iPhone in the first place, after all my iPod touch didn't ship with the Maps application in the first place...

Despite all this there are still predictably some problems with the current implementation. First and foremost, the Sky map only occupies the upper left quarter of Earth map when zoomed out all the way out. The best explanation I can come up with for this is simply the fact I haven't yet cut my "big" tiles into quarters. If generate 4×(128×128) tiles out of each of my current 256×256 tiles at each of the current zoom levels then I reckon I'll fill the map.

Of course even with this solved there are two fundamental problems which means that this project is almost entirely an intellectual exercise. The current lack of zoom levels in the Sky data means than when you zoom in far enough you eventually drop back down onto the Earth. For instance the SMC is located somewhere over Marrakech, and if you zoom in far enough sub-Saharan Africa starts to poke through the star field. This could probably be lived with except for the fact that the only way to find the SMC in the first place is do a search for Marrakech, after all the search utility still thinks it's looking at the Earth's surface.

The other major problem is that the Maps.app application doesn't actually have the Maps API, the very thing I need so that I can manipulate the map at the very basic level of even adding a placemark. There are presumably hidden hooks into the application, and maybe those will appear in the Apple SDK due in February . But there still isn't any word on the licensing provisions for the SDK. Who knows if I can get access to it, especially if there is a issue surrounding the digital signatures rumoured to be needed for an "official" application. Finding the money needed, possibly a lot of money, for the SDK could be an interesting proposition. That's even if those hooks exist in the first place.

Anyway, the easiest thing for you to do if you want to replicate my hack is to download the hacked database file (29MB) I generated and copy it into /var/root/Library/Caches/MapTiles/. Make sure you make a copy of your existing MapTiles.sqlitedb database before dropping the new one ontop of it, you'll probably want to back out of the hack at some point. After copying the database file into the right place, simply reboot your iPhone or iPod touch. At that point it should all just work...

Source Code

If you're crazy enough to want to waste your time playing around with this I've attached the source code that will let you rebuild the MapTiles.sqlitedb database directly from the Sky tile servers. Hopefully the guys at Google won't be too cross, as I doubt the handful of people crazy enough to go off and duplicate this stunt will add any significant load to the tile servers...

download_tiles.plDownload all of the tiles from the server
eatblob.cProgram to push the tiles in the the SQLite DB
getdelim.c getdelim.h   Needed by eatblob.c
getline.c getline.h   Needed by eatblob.c
push_tiles.plScript to run eatblob.c in harness for all tiles.

Video

Of course if you don't want to get your hands dirty with the insides of the Maps application, you can always just watch the video...