Recently, I had to implement an offline mapping solution for an iOS application. Here's a walkthrough of how to do it.
I generated a tile database using TileMill. I used the Route-Me iOS library which provides a map view that supports offline tile sources.
Actually, generation of the tile database was the more time consuming part of the task, especially since I had not worked with maps before. Here's a run down of what it took. TileMill will help you style maps.
Getting started, I installed the application and tried to use built-in features to style the map. I quickly realized I would need to import some data sources to get the map I wanted.
After more research, I found this project called OSM Bright by the company that produces TileMill, MapBox. I used the OSM Bright Mac OS X Quickstart to set everything up. The script that follows runs through most of the steps required in the Quickstart.
First, we'll setup Postgres and the PostGIS extension.
brew install postgresql
# libpng is a dependency of gdal, which is a dependency of postgis
brew install libpng && brew link -f libpng
brew install postgis
# initialize database
initdb /usr/local/var/postgres -E utf8
createdb `whoami` # creates a db for your username. For some reason initdb didn't do this for me and I was getting login issues without this existing.
# make sure `which psql` = /usr/local/bin/psql
pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start
psql -c "create database osm;"
psql -d osm -c "create extension postgis;"
Now, we'll setup the osm2pgsql
tool which is used to load Open Street Map data into a Postgres database.
# although there is a homebrew formula available, it did not seem to work on Mountain Lion
# brew install --HEAD osm2pgsql
wget http://dbsgeo.com/downloads/osm2pgsql/snow/intel/r26782.dmg
hdiutil attach r26782.dmg
open /Volumes/osm2pgsql-r26782M/osm2pgsql-r26782M.pkg
# Go through the installation instructions
We're done setting up our database that will store Open Street Map data. Now, let's download the data we'll need. It's OSM data for the Philadelpha Metro area in PBF format.
# download relevant data from http://metro.teczno.com/#philadelphia
wget http://osm-metro-extracts.s3.amazonaws.com/philadelphia.osm.pbf
And then we import it into the database.
osm2pgsql -c -G -d osm -S /usr/local/share/osm2pgsql/default.style milan.osm.pbf
Now, we can use the OSM Bright project to create a new TileMill project.
git clone git://github.com/mapbox/osm-bright.git
cd osm-bright
vim configure.py # edit and add your postgres settings
./make.py
This will create a new project in your TileMill projects directory, which is probably ~/Documents/MapBox/project/. The directory name will be whatever was specified in configure.py
.
Open TileMill and select your project. If you didn't change the name in configure.py
, it will be called OSM Bright.
- Edit the Project Settings by clicking the wrench icon at the top right. Zoom in on Philadelphia and change the
Center
location andBounds
using the interface. - Click save.
- Click the Export Menu and select
MBTiles
. Select a zoom range. The higher the range you use, the larger your database will be. I though ranges between 13 and 18 worked best for my purposes. Save thembtiles
file with a name likePhiladelphia.mbtiles
. - The export will be queued and put in
~/Documents/MapBox/export/
when complete.
Once you have created a new iOS application, we will setup the Route-Me MapView as a subproject. Below, the Header Search Paths and Link Binaries are the most important steps.
- Clone the Route-Me repo into a vendor directory inside your project. (This is the convention I used, and I'm not sure if it is the best)
git clone https://github.com/route-me/route-me vendor/route-me
- Add the MapView.xcodeproj file into your project
- In your Project Settings, click the application target > Build Settings > Enter "Header" into the Search Box, and add
vendor/route-me/MapView/
to the *Header Search Paths key - Build Settings > Under Other Linker Flags > Add "-all_load -ObjC"
- Go to Build Phases > Target Dependencies > Click +, Choose MapView > MapView
- Go to Build Phases > Link Binaries > Click +, Choose
libMapView.a
,libsqlite3.dylib
,CoreGraphics.framework
,CoreLocation.framework
, andQuartzCore.framework
- Add the
Philadelphia.mbtiles
file you created before to the project.
-
Go to your storyboard or nib and add a subview to your primary view. Give it the class
RMMapView
-
Add outlets to your UIViewController for this map view
// In your header file #import <UIKit/UIKit.h> @class RMMapView;
@interface MyViewController : UIViewController { IBOutlet RMMapView *mapView; }
@property (nonatomic, strong) IBOutlet RMMapView *mapView; @end
// In your implementation file
#import "RMMapView.h"
// ...
@implementation MyViewController @synthesize mapView;
// ...
-
Set the map's center point, default zoom level, and min and max zoom. The min zoom should match the one you specified when you exported the
mbtiles
file from TileMill.// In your implementation file
- (void)viewDidLoad { // ... mapView.contents.minZoom = 15.f; mapView.contents.maxZoom = 17.f; mapView.contents.zoom = 16.5; [mapView.contents moveToLatLong: CLLocationCoordinate2DMake(39.949721,-75.150261)]; // ... }
-
Run the app and you should see everything load. The map is loading live from http://openstreetmaps.org.
-
Set the tile source to use an offline source instead.
// In your implementation file #import "RMMBTilesTileSource.h"
-
(void)viewDidLoad { // ...
mapView.contents.minZoom = 15.f; mapView.contents.maxZoom = 17.f; mapView.contents.zoom = 16.5;
// the tile source MUST be set after min and max zoom NSURL *tileSetURL = [[NSBundle mainBundle] URLForResource:@"Philadelphia.mbtiles" withExtension:@"mbtiles"]; mapView.contents.tileSource = [[RMMBTilesTileSource alloc] initWithTileSetURL: tileSetURL];
[mapView.contents moveToLatLong: CLLocationCoordinate2DMake(39.949721,-75.150261)]; // ... }
-