Offline

If your user base spends most of their time off the grid, use the Mapbox Maps SDK's offline features to download and store pre-selected regions for use when there is a loss of connectivity. The maps they download will be fully functional and include the styles, tiles, and other resources you specify.

An offline map requires two main components:

  1. Style data, which is organized into style packs. Use the Offline Manager API to manage style packs.
  2. Map data, which is organized into preassembled tile packs. Use the tile store to manage tile packs.

These are a few key terms and concepts you may need to reference when working with offline maps:

  • Offline Manager: The Offline Manager API provides a configuration interface and entrypoint for offline map functionality. It is used to manage style packs and to produce tileset descriptors that can be used with a tile store.
  • Tileset descriptor: A tileset descriptor contains metadata about the tilesets, zoom ranges, and pixel ratio that cached tile packs should include. You can create tileset descriptors with the Offline Manager. Each time you create a tile pack, you will provide both a tileset descriptor and a tile region before tile data can be fetched for offline use.
  • Tile region: A tile region is a geographic area for which offline data is needed. Tile regions are used by tile stores to fetch and manage tile packs necessary to provide offline support in that area. To create a tile region, a developer must specify a geometry describing each region's boundaries, source tileset(s), and zoom range(s).
  • Tile pack: Tile packs are binary files made up of a parent tile and its descendant tiles covering a specific range of zoom levels and over a predefined geographic area. Because they have less per-tile and per-connection overhead, they are more efficient to transfer over the network and store on the file system than individual tiles. Tile packs only store tiles. The Offline Manager API handles metadata and descriptors related to tilesets.
  • Tile Store: The tile store manages retrieving and organizing tile packs. By default, the Maps SDK only uses the tile store for tile regions. The tile store does not share tiles with the disk cache (see note on disk cache below). Although the Offline Manager handles creating tileset descriptors, it is the job of the tile store to manage tile regions. You should retain the TileStore instance by keeping a reference to it while using the SDK. This prevents unnecessary re-initialization and possible loss of options you provided earlier. The user is responsible for providing proper options, including an access token, when creating a tile store with a `TileStore.setOptionForKey() API.
  • Style pack: A style pack contains a map style and the non-tile resources it depends upon, including loaded sources, fonts, and sprites. Collectively, these resources are used to transform map data stored in tiles into a rendered map with a particular look and feel. Style packs are identified by their style URL. Style packs are stored in the disk cache that is employed during normal online map use, but their resources are not subject to the processes that typically remove objects from the cache to control its size.
Disk cache

When a user loads and interacts with a map on their device, any visible tiles and style resources (style JSON, fonts, sprites, etc.) are placed in the device's disk cache, which is located in the maps data directory defined in the ResourceOptions.dataPathURL. The disk cache observes when these resources are used and makes intelligent guesses about which resources may be needed again. When a user revisits a place on the map, it will load more quickly if the associated resources are still present in the disk cache, because the device will not have to request those resources from the server to render the map. Resources that do not belong to a style pack are not permanently stored in the disk cache. There are no guarantees about how long these resources will be cached, but the least-recently used resources are normally removed to make room for newer resources.

Limits

The cumulative number of unique maps tile packs used in the offline regions cannot be greater than 750. The SDK will not load tile regions if it would lead to exceeding the tile pack limit.

Our terms of service do not allow developers or end users to redistribute offline maps downloaded from Mapbox servers. Users of the SDK must retrieve Mapbox data intended for offline use from Mapbox servers--the data may not be preloaded, bundled or otherwise redistributed.

Specifying the style and areas that will be used offline

To render a complete map in an offline environment, the device needs to fetch two main components: a style and the required map tiles, each of which needs to be configured ahead of time.

Define a style pack

To render a map offline, the SDK must be able to fetch the non-tile resources that the map requires. This includes loaded sources, fonts, styles and sprites. The SDK calculates which resources it needs based on the StylePackLoadOptions you specify and pass to the Offline Manager API.

let options = StylePackLoadOptions(glyphsRasterizationMode: .ideographsRasterizedLocally,
                                   metadata: ["my-key": "my-value"],
                                   acceptExpired: false)

Define a tileset descriptor and tile region

The tileset descriptor associates tile data (stored in a tile pack) with a given style (stored in a style pack). Use the tileset descriptor to define a tileset's zoom ranges and pixel ratio.

The tile region is a geographic region and its metadata. It is used to calculate which tile packs will be necessary to provide offline functionality in that region.

// When providing a `TileStore` to `ResourceOptions`, you must ensure that
// the `TileStore` is correctly initialized using `setOptionForKey(_:value:)`.
// This includes providing an access token, if you are not using a default
// from the application's Info.plist
tileStore.setOptionForKey(TileStoreOptions.mapboxAccessToken, value: accessToken as Any)

let offlineManager = OfflineManager(resourceOptions: ResourceOptions(accessToken: accessToken,
                                                                     tileStore: tileStore))

// 1. Create the tile set descriptor
let options = TilesetDescriptorOptions(styleURI: .outdoors, zoomRange: 0...16)
let tilesetDescriptor = offlineManager.createTilesetDescriptor(for: options)

// 2. Create the TileRegionLoadOptions
let tileRegionLoadOptions = TileRegionLoadOptions(
    geometry: Geometry(coordinate: tokyoCoord),
    descriptors: [tilesetDescriptor],
    acceptExpired: true)

Metadata

You may provide arbitrary metadata for both style packs and tile regions. You may want to provide metadata to distinguish the various regions your users download from one another or for other record-keeping requirements within your app. This data will be associated with the style packs and regions that the system fetches and stores locally. While optional, providing a metadata object with at least a region name is encouraged. In addition to a region name, you can store any arbitrary serialisable information you'd like as long as it can be wrapped into a JSON object. For example, you might wish to store the record ID of a travel itinerary that a user has created in another part of your app, and for which the offline region is being retrieved, to link the two together. The contents of the metadata you specify will only be available inside your app and will not affect your tile region download.

Add metadata to the style pack with StylePackLoadOptions.

let metadata = ["my-key": "my-style-pack-value"]
let options = StylePackLoadOptions(glyphsRasterizationMode: .ideographsRasterizedLocally,
                                   metadata: metadata)

Add metadata to the tile region with TileRegionLoadOptions.

let metadata = [
    "name": "my-region",
    "my-other-key": "my-other-tile-region-value"]
let tileRegionLoadOptions = TileRegionLoadOptions(
    geometry: Geometry(coordinate: tokyoCoord),
    descriptors: [],
    metadata: metadata,
    acceptExpired: true)

Working with downloads

Once the above steps are complete, the system has the information necessary to know what to resources must be downloaded. The next step is to start downloads for each of your style packs and tile packs.

Download a style pack

Now that the StylePackLoadOptions has been created, you can use the OfflineManager to download a style pack asynchronously by calling loadStylePack. loadStylePack returns a Cancelable object, allowing you to abort the download by calling cancel().

To initialize the download, pass in the StylePackLoadOptions you created, as shown in both the Define a style pack and Metadata sections.

This will provide you with two callbacks:

  1. StylePackLoadProgressCallback tracks the progress of the download.
  2. StylePackCallback tracks the completion of the download or any errors that occur. It returns a StylePack object when the download completes successfully, which you can use to check the downloaded style package, including its expiration date.
// These closures do not get called from the main thread. Depending on
// the use case, you may need to use `DispatchQueue.main.async`, for
// example to update your UI.
let stylePackCancelable = offlineManager.loadStylePack(for: .outdoors,
                                                       loadOptions: stylePackLoadOptions) { _ in
    //
    // Handle progress here
    //
} completion: { result in
    //
    // Handle StylePack result
    //
    switch result {
    case let .success(stylePack):
        // Style pack download finishes successfully
        print("Process \(stylePack)")

    case let .failure(error):
        // Handle error occurred during the style pack download
        if case StylePackError.canceled = error {
            handleCancelation()
        } else {
            handleFailure()
        }
    }
}

// Cancel the download if needed
stylePackCancelable.cancel()

Download a tile region

In the previous step, you used the Offline Manager API to download a style pack. You will use the tile store in a similar way to download any tile packs required to render your map offline.

To create an asynchronous tile region download, call the tile store's loadTileRegion method and pass in the TileRegionLoadOptions you created, as shown in the Define a tileset descriptor and tile region and Metadata sections. loadTileRegion will return a Cancelable object, which may be used to abort the download.

loadTileRegion will provide you with two callbacks:

  1. TileRegionLoadProgressCallback tracks the download progress.
  2. TileRegionCallback tracks the completion of the download or any errors that occur. It provides a TileRegion object when the download completes successfully, which you can use to check the downloaded tile region, including its expiration date.
let tileRegionId = "my-tile-region-id"

// Load the tile region
let tileRegionLoadOptions = TileRegionLoadOptions(
    geometry: Geometry(coordinate: tokyoCoord),
    descriptors: [tilesetDescriptor],
    acceptExpired: true)!

let tileRegionCancelable = tileStore.loadTileRegion(
    forId: tileRegionId,
    loadOptions: tileRegionLoadOptions) { _ in
    //
    // Handle progress here
    //
} completion: { result in
    //
    // Handle TileRegion result
    //
    switch result {
    case let .success(tileRegion):
        // Tile region download finishes successfully
        print("Process \(tileRegion)")

    case let .failure(error):
        // Handle error occurred during the tile region download
        if case TileRegionError.canceled = error {
            handleCancelation()
        } else {
            handleFailure(error)
        }
    }
}

// Cancel the download if needed
tileRegionCancelable.cancel()

Manage offline data

Once a download has completed, you may want to give your users the option to view and manage their downloads in one place. The Offline Manager API and tile store each provide methods for listing and managing offline data.

List offline style packs and regions

The OfflineManager offers a allStylePacks(completion:) method which returns an Result type, encapsulating the array of style packs available or an error.

// Get a list of style packs that are currently available.
offlineManager.allStylePacks { result in
    switch result {
    case let .success(stylePacks):
        handleStylePacks(stylePacks)

    case let .failure(error) where error is StylePackError:
        handleStylePackError(error)

    case .failure(_):
        handleFailure()
    }
}

Similarly, the TileStore offers a allTileRegions(completion:) method which returns a Result type, encapsulating the list of tile regions available or an error.

// Get a list of tile regions that are currently available.
tileStore.allTileRegions { result in
    switch result {
    case let .success(tileRegions):
        handleTileRegions(tileRegions)

    case let .failure(error) where error is TileRegionError:
        handleTileRegionError(error)

    case .failure(_):
        handleFailure()
    }
}

Update a style pack

To maintain the integrity of a style package, the map style that belongs to an offline pack will not be automatically updated if a user returns to an area with network services and views the map while connected. This includes loaded sources, glyphs, and sprites.

You can manually refresh style packs using OfflineManager.loadStylePack, passing it the same StyleURI and an empty StylePackLoadOptions (note that if the acceptExpired flag is set the existing outdated resources will not be refreshed). During the refresh, any missing resources will be loaded and expired resources will be updated.

Update a region

Downloaded tile regions can be refreshed using the same TileStore.loadTileRegion call you made when you initialized your download. Provide the same tile region ID (tileRegionId in the download initialization example above) and an empty TileRegionLoadOptions (note that if the acceptExpired flag is set the existing outdated resources will not be refreshed). During the refresh, any missing resources will be loaded and any expired resources will be updated.

Normally, the map does not load new tile packs from the network, unless ResourceOptions.tileStoreUsageMode is set to .readAndUpdate. If ResourceOptions.tileStoreUsageMode is set to .readAndUpdate, the map will replace the outdated visible tile packs with fresh ones for all the tile regions that refer to these tile packs.

Delete style packs and regions

To remove offline data from the database, you must first receive the list of tile regions as explained in the List offline styles packs and regions section. Once this list has been retrieved it may be used to select the style pack and/or tile regions to be deleted.

Delete a style pack using the OfflineManager.removeStylePack API and the style's URI.

offlineManager.removeStylePack(for: .outdoors)

Note this may not immediately delete the downloaded style pack. Instead, it will mark the resources as not being a part of an offline style pack and they will be removed from the disk cache during its normal cleanup process.

You can delete a tile region with TileStore.removeTileRegion.

tileStore.removeTileRegion(forId: "my-tile-region-id")

Note that this may not immediately delete the downloaded tile packs. Instead, it will mark the tileset as not being a part of an offline tile region and they will be removed from the disk cache during its normal cleanup process.

You can fully remove tiles that have been downloaded by setting the disk quota to zero. This will make sure tile regions are fully removed.

tileStore.setOptionForKey(TileStoreOptions.diskQuota, value: 0)

Understanding and configuring offline behavior

Understanding the default behavior and customization options of the Maps SDK's offline functionality can allow you to optimize your app's offline functionality and manage it more flexibly.

Each Map instance stores files for both the disk cache and tile store in a default location (normally, inside the application cache folder). To override the default, you can provide a custom data path for the disk cache and a tile store instance. There is only ever one tile store for each unique file path.

The Offline Manager can be configured using ResourceOptions. You can completely disable the tile store by setting ResourceOptions.tileStoreUsageMode to .disabled. In this case, a tile store will not be created and tile region functionality will not be available.

By default, a tile store is read-only and the Maps SDK only interacts with it to check for tiles it needs. If the necessary tile is not in the tile store, the SDK will request the individual tile and store it in the disk cache instead of the tile store. By default, the SDK will not fetch tile packs in scenarios like this, where a tile is not available locally and must be retrieved.

You can enable implicit tile pack loading by setting the ResourceOptions.tileStoreUsageMode resource option. When set to .readAndUpdate, the map engine will use the tile store for loading tile packs rather than loading tiles individually. Only tile packs will be used. This option can be useful if the map trajectory is predefined and the user cannot pan freely (e.g. navigation use cases). In these circumstances, it is more efficient to use tile packs for all map data loading instead of allowing the map to request arbitrary tiles.

Tile store disk quota

Like the disk cache, the maximum size of a tile store may be configured. When this quota is reached, the least-recently used tile packs that aren't shared by another tile region will be removed to make room for newer tile packs.

If the tile store is only used for tile regions and there is no need to keep tile packs bound to those regions, you can set the tile store disk quota to zero, forcing disk clean up after a tile region is deleted.

Tile count granularity

The tile store loads and stores tiles in tile packs. Each tile pack has a predefined zoom range and contains all child tiles within that range. The currently used tile pack zoom ranges are:

  • Global coverage: 0 - 5
  • Regional information: 6 - 10
  • Local information: 11 - 14
  • Streets detail: 15 - 16

When specifying minimum and maximum zoom levels as part of the TilesetDescriptorOptions, the actual zoom levels of the retrieved data will be determined by these ranges. For example, if you set minZoom to 8 and maxZoom to 15, the downloaded tiles will include zoom levels ranging from 6 to 16.

Supported tiled sources

Tile regions support only the tiled sources whose tile endpoint URLs correspond to the Mapbox Vector/Raster tile v4 URL schema. Your tiled source probably uses the v4 URL schema unless it is more than a few years old.