Simplicity is the ultimate sophistication

Custom and Offline Maps Using Overlay Tiles

| Comments

New MapKit features

In WWDC 2013 Apple introduced many new MapKit features for iOS 7 and added this framework in OSX 10.9 (Mavericks). One of the major changes, which in my opinion didn’t get enough relevance in the developer community, has been the introduction of some base classes that allow full map customization and support for offline maps. In this article I’m going to describe the new MKTileOverlay class and present an example, for both iOS and OSX, that demonstrates the new capabilities.

Since the earlier iPhone OS versions there were many apps in the App Store that were supporting maps different from the ones provided by the operating system: consider for example Navigation apps, that required support for offline navigation (that is the possibility to see the map even without internet connection) or services that needed to show proprietary information (such as “Yellow Pages” apps) or technical information (e.g. when there was the requirement to show level curves for mountains or to represent the sea level).

There were several issues due to this limitation: first of all the overall mapping experience was completely different on each app and in most cases this experience were subpar if compared with the optimized OS maps performance (either with Google before iOS6 or Apple data since iOS6). From the point of view of the developer there was the problem of providing the right mapping code to support the map provider data: there weren’t a unique solution, but many: some were commercial and expensive, other were open source but with lack of support and finally there were a lot of web-browser based solutions whose performances were far from the native maps, other than difficult to integrate with Objective-C.

What we’re going to show in this article is how these things changed drastically and how it is easy now to integrate your own map content inside the common MapKit framework.

Map overlays

At the base of our discussion there is the concept of “map overlay”. This is not new in MapKit, but with iOS7 things changed. Overlays are regions of a map that can be overlayed over the base map, that is the part of the map representing the ground, the borders, the roads, and so on. Typically the usage of overlays is to emphasize some regions of the map having a common property: e.g. to highlight a specific country or to represent the several intensities of an earthquake that occurred in a certain area or finally to highlight a road path in a navigation app.

From the point of view of the developer, an overlay is any object that conforms to the MKOverlay protocol. This protocol defines the minimum properties and methods required to define an overlay: these are the approximate center coordinate and the bounding box the fully encloses the overlay:

@protocol MKOverlay <MKAnnotation>

// From MKAnnotation, for areas this should return the centroid of the area.
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

// boundingMapRect should be the smallest rectangle that completely contains the overlay.
// For overlays that span the 180th meridian, boundingMapRect should have either a negative MinX or a MaxX that is greater than MKMapSizeWorld.width.
@property (nonatomic, readonly) MKMapRect boundingMapRect;

These two properties allow MapKit to determine if a specific overlay is currently visible or not in the map so that the framework can take the actions needed to display this overlay. When an overlay object is added to the map using one of the MKMapView’s “addOverlay” family methods the control passes to the framework which, when determines that a specific overlay needs to be displayed, calls the map view delegate asking him to provide the graphical representation of the overlay. Before iOS7 Apple was providing a set of concrete MKOverlay compatible classes and they were associated to their corresponding MKOverlayView. E.g. to represent a circular overlay we could use the built-in MKCircle class and then provide, for rendering, the associated MKCircleView class, without the need to define our own object.

With iOS7 things changed: now the MKOverlayView has been replaced by the MKOverlayRenderer. Even if this change doesn’t require difficult refactoring to translate the code from pre-iOS7 to iOS7 (thanks to the fact that Apple did a 1:1 mapping of methods from the old class to the new class) conceptually the change is significant: now the graphical representation of the overlay is no more provided by a UIView subclass, which is typically considered a heavy class, but it is provided by an object, MKOverlayRenderer, which is much more lightweight and descends directly from NSObject. However the mapping between the old and new class is complete, so in the circle example we can see MKCircleView replaced by MKCircleRenderer.

Finally overlays are stacked on the map, so they are given a Z-index that provides the relative positions of overlays each other and with the fixed parts of the map. Before iOS7 you could stack these overlays and define their positions as in an array, with iOS7 two main stacks are defined in fact, and they are called “levels”: one level is the “above the roads level”, the other level is the “above the labels level” and overlays relative positions are referred to the stack they have been inserted. This is an important and useful distinction because now we can change how the overlay rendering interacts with the map by specifying if it lies above or below the labels.

Tile overlays

Whatever is the complexity and size of the overlay, we have seen them up to now as specific shapes. With the new MapKit provided with iOS 7 and OSX Mavericks, there is a new type of overlay called tiled overlay. You may consider it as a particular layer that covers the whole map: due to its large dimensions this overlay is tiled, that is it is partitioned in bitmap areas to reduce the memory required to show the data and make the overlay rendering efficient. The purpose of this concrete implementation of the MKOverlay protocol, called MKTileOverlay (together with its rendering counterpart given by the MKTileOverlayRenderer class), is to efficiently represent the whole set of tiles across the map plane and for different zoom levels. This last point is important: when you’re displaying a map using bitmap drawing (to be compared with vector drawing) you can get an efficient implementation only if the specific bitmap representing an area of the map has the right details suitable for the current zoom level. This means that if we show the full Europe map we don’t need to show roads whiles cities should be represented as points and only for the major ones; as soon as we zoom in a specific area then we cannot continue to represent the area by simply scaling the same tile, because it doesn’t contain the required information and also because we would see evident scaling effects. The solution to this is to divide the continuous allowed zoom range in discrete levels and for each level provide the required set of tiles that will show the details appropriate for that level. It is evident that if we keep the single bitmap tile size constant (e.g. 256 x 256 pixels) then for each zoom level we must increse the number of tiles by a factor of 4; you can see this in the picture below: the single european tile at zoom level 3, when zoomed to level 4 has been split in four new more detailed tiles having each the same size of the original tile.

URL templates

The tiled overlay class works efficiently as it does a lazy loading of the tiles: this means that a bitmap tile is looked for and loaded only when it needs to be displayed. In order to know the location of the tile, the developer must define in the tile overlay definition the so-called URL template. This is a string representing a template for the final URL that will be used to retrieve the tile: this template will contain some placeholders that will be replaced by effective values to get the final URL. Each tile can be characterized by 4 parameters: x and y for the tile indexes in the map plane, z for the zoom level and finally scale for the bitmap image resolution (scale factor). The corresponding placeholders for these parameters are: {x} {y} {z} {scale}. So as an example, the OpenStreetMap template URL will be{z}/{x}/{y}.png and then the tile with index X=547 Y=380 and zoom level Z=10, that fully encloses the city of Rome, will be represented by the URL: (see below the image taken from our OSX demo app).

Note that a URL template can be an http:// template to retrieve tiles from the internet, but it could also be a file:// template if we want to retrieve files from the disk: in this way we can save our tiles in the application bundle, or download and install a full tiles package for a certain city, and then display offline maps even if the device is not connected to the internet.

The mechanism that is used by the framework to translate a required tile coordinate (x;y;z;scale) to an effective bitmap is composed of several steps: this gives the developer the possibility to hook its own code to effectively customize the way the tiles are generated. This can be done by subclassing MKOverlayTile. Note that this is not required if setting the URL template is enough for you.

When the map framework needs a specific map tile, it calls the loadTileAtPath:result: of the MKOverlayTile class (or subclass):

- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData *tileData, NSError *error))result;
The first method argument is called path and is a MKTileOverlayPath structure which contains the tile coordinates:

typedef struct {
	NSInteger x;
	NSInteger y;
	NSInteger z;
	CGFloat contentScaleFactor; // The screen scale that the tile will be shown on. Either 1.0 or 2.0.
} MKTileOverlayPath;

The second method argument is a completion block that needs to be called when the tile data has been retrieved: this completion block will be called by passing the data and an error object. The MKTileOverlay default implementation will call the -URLForTilePath method to retrieve the URL and then it will use NSURLConnection to load the tile data asynchronously.

If we want to customize the tile loading behaviour we can easily subclass MKTileOverlay and redefine the loadTileAtPath:result: with our implementation of the loading mechanism. E.g. we can implement our own tiles caching mechanism (other than the one provided by the system via NSURLConnection) to return the cached data before triggering the network call; or we could watermark the default tile if we are shipping a freemium version of our offline map.

A more light way to hook into the tile loading mechanism is to redefine in our subclass the -URLForTilePath: method:

- (NSURL *)URLForTilePath:(MKTileOverlayPath)path;

The purpose of this method is to return the URL given the tile path. The default implementation is just to fill-out the URL template, as specified above. You need to redefine this method if the URL template mechanism is not sufficient for your needs. Typical cases are when you want to pass in the URL a sort of “device identifier” to validate the eligibility of that specific app to access the URL (e.g. if you provide a limit to the quantity of data that can be accessed by a user on a given time or if you want to charge for this data) or if you have multiple tile servers and you want to do a sort of “in-app” load balancing or regional-based API access (e.g. you have servers in multiple locations and based on the effective device location you want to access the closer server).

The tile renderer

As all overlays are associated to a renderer, also the tile overlay has its concrete renderer class: MKTileOverlayRenderer. Normally you don’t need to subclass this renderer so your map delegate’s -mapView:rendererForOverlay: method can simply instantiate the default tile overlay renderer initialized with your default or subclassed tile overlay instance. Possible applcations of a custom overlay renderer are when you need to further manipulate the bitmap image, e.g. adding a watermark or applying a filter, and this manipulation is independent from the tile source. In the demo code I defined a custom renderer to be used specifically for the Google map, whose effect is to add a sort of colored translucent mosaic on top of the map tiles. When you create your subclass usually you’re required to redefine the -drawMapRect:mapRect:zoomScale:inContext: method, which gives you a drawing context where you can add your own graphics; don’t forget to call super to pre-polutate the context if you have a tile to display (that tile has been loaded by the MKTileOverlay object associated to the renderer):

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context;

The demo code

You can get the demo code from Github. This code works on both iOS 7 and OSX 10.9 and its purpose is to present a map and give the user the possibility to switch between different tile sets: Apple (system), Google, OpenStreetMap and offline using bitmpas generated from a subset of OpenStreetMap tiles that have been bundled within the app. In all cases I added an extra overlay layer to show the tile grid with the x,y,z path associated to each grid. (Note: in OSX if you don’t code sign the app using your OSX Developer Program certificate, you will not be able to see the Apple tiles: the other three tile sets will be visible instead). You will see how you can fully take advantage of all features common to the MapKit (zoom, rotation, pan, custom overlays and also annotations which I didn’t include in the demo) and the only difference is in the tiles source and how they are rendered.

As you can see in the demo apps, there is a main view controller (iOS) and a main window controller (OSX). In both cases the main view contains an instance of MKMapView and a segmented control to switch between different visualizations. On the map I have instantiated two overlays. The first one is the grid overlay:

 // load grid tile overlay
 self.gridOverlay = [[GridTileOverlay alloc] init];
 [self.mapView addOverlay:self.gridOverlay level:MKOverlayLevelAboveLabels];

This is a tile overlay of subclass GridTileOverlay. It will not replace the map content (this means that is effectively overlayed on the map content) and its purpose is to draw, just above labels, the tiles grid.

The reloadOverlay method is called each time the overlay type selector is changed or when the view is loaded. It removes any existing tileOverlay and replaces it with a new one:

-(void)reloadTileOverlay {

	// remove existing map tile overlay
	if(self.tileOverlay) {
		[self.mapView removeOverlay:self.tileOverlay];

	// define overlay
	if(self.overlayType==CustomMapTileOverlayTypeApple) {
		// do nothing
		self.tileOverlay = nil;
	} else if(self.overlayType==CustomMapTileOverlayTypeOpenStreet || self.overlayType==CustomMapTileOverlayTypeGoogle) {
		// use online overlay
		NSString *urlTemplate = nil;
		if(self.overlayType==CustomMapTileOverlayTypeOpenStreet) {
			urlTemplate = @"{z}/{x}/{y}.png";
		} else {
			urlTemplate = @"{x}&y={y}&z={z}";
		self.tileOverlay = [[MKTileOverlay alloc] initWithURLTemplate:urlTemplate];
		[self.mapView insertOverlay:self.tileOverlay belowOverlay:self.gridOverlay];
	else if(self.overlayType==CustomMapTileOverlayTypeOffline) {
		NSString *baseURL = [[[NSBundle mainBundle] bundleURL] absoluteString];
		NSString *urlTemplate = [baseURL stringByAppendingString:@"/tiles/{z}/{x}/{y}.png"];
		self.tileOverlay = [[MKTileOverlay alloc] initWithURLTemplate:urlTemplate];
		[self.mapView insertOverlay:self.tileOverlay belowOverlay:self.gridOverlay];

In the Apple maps case no extra overlay is added of course: we just use the base map. When we select to view the Google and OpenStreetMap we will use a standard MKTileOverlay class with the appropriate URL template. In both cases the overlay will be added with the canReplaceMapContent property set to YES: this replaces the Apple base maps completely and will avoid that those data will be loaded. Note that we add the tileOverlay just below the gridOverlay. Finally the offline case still uses a base overlay class but with a file URL template: note that we create the path from a hierarchical directory structure built inside the bundle. In this case too the new tiles replace the base ones and are inserted below the grid.

Our controller, being a delegate of MKMapView, responds to the -mapView:rendererForOverlay:. This is required by every application that uses overlays as this is the point where the app effectively tells the system how to draw an overlay that is currently visible in the map. In our case we just check that the overlay is a tile overlay (this is a general case to consider that fact that we might have other types of overlays) and based on the selection we use the standard MKTileOverlayRenderer or a custom renderer WatermarkTileOverlayRenderer. The latter is used to apply a randomly colored semi-transparent effect on top of the tiles, getting as a result a vitreous mosaic effect.


The possibility to easily switch between different map types but keeping the same map navigation experience is one of the most revolutionary features introduced with iOS 7, other than the longly awaited introduction of native maps inside OSX. This provides the same map infrastructure whatever is the content. Obviously the generation of custom map content is another huge and highly specialized task that we cannot cover here: but for developers this is a great step forward.


  • Location and Maps Programming Guide from Apple Developer Library
  • WWDC 2013 session 304 video: What’s new in Map Kit from Apple WWDC 2013 videos
  • MBXMapKit GitHub project by Mapbox - A simple library to intergrate Mapbox maps on top of MapKit, one of the first applications of tiled overlays
  • TileMill the Mapbox software to create custom maps
  • MapTileOverlayTutorial the demo code for this article
  • The GDAL project one of the main references for custom maps creation. Here is a link to a compiled version of the GDAL OSX Framework
  • Maperitive another great tool (for Windows only) to create custom maps and prepare them for offline usage
  • Map servers a collection of tiled map servers from Neongeo wiki