Offline maps, routing and geocoding in the CARTO Mobile SDK require that you pre-download map data. For this we provide SDK internal API called PackageManager - this manages offline data packages, which are downloaded from CARTO server and used by the SDK for specific features.
So for offline mapping (geocoding or routing) you need to
The packages that are once downloaded are kept persistently on the device and can be updated or removed through the PackageManager as the app requires.
When creating PackageManager, you specify package contents by setting source
parameter value as following:
carto.streets
map data packages (vector tiles) for visual map layer.routing:nutiteq.osm.car
- routing data packages for OSRM routing engine and car profilerouting:carto.streets
- routing data packages for Valhalla routing engine (deprecated in SDK 4.2)geocoding:carto.streets
- geocoding/address search data packagesAll these sources are based on OpenStreetMaps map data.
App has to decide to which folder the map files are stored. Typically you use standard file locations there, depending on platform. Note that each package source ID should be stored in a different folder, e.g. mappackages
, routingpackages
, geocodingpackages
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Create PackageManager instance for dealing with offline packages
File packageFolder = new File(getApplicationContext().getExternalFilesDir(null), "foldername");
if (!(packageFolder.mkdirs() || packageFolder.isDirectory())) {
Log.e(Const.LOG_TAG, "Could not create package folder!");
}
try {
packageManager = new CartoPackageManager("<your-package-source>", packageFolder.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
10
11
12
// Create PackageManager instance for dealing with offline packages
var packageFolder = new File(GetExternalFilesDir(null), "foldername");
if (!(packageFolder.Mkdirs() || packageFolder.IsDirectory))
{
Log.Fatal("Could not create package folder!");
}
packageManager = new CartoPackageManager("<your-package-source>", packageFolder);
1
2
3
4
5
6
7
8
9
10
11
// Define PackageManger to download offline packages
// Create folder for package manager. Package manager needs persistent writable folder.
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask,YES);
NSString* appSupportDir = [paths objectAtIndex: 0];
NSString* packagesDir = [appSupportDir stringByAppendingString:@"/foldername"];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:packagesDir withIntermediateDirectories:YES attributes:nil error:&error];
packageManager = [[NTCartoPackageManager alloc] initWithSource:@"<your-package-source>" dataFolder:packagesDir];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Define PackageManger to download offline packages
// Create folder for package manager. Package manager needs persistent writable folder.
let packageFolder = NTAssetUtils.calculateWritablePath("foldername")
do {
try FileManager.default.createDirectory(atPath: packageFolder!, withIntermediateDirectories: false, attributes: nil)
} catch let error as NSError {
print(error.localizedDescription);
}
// Create PackageManager instance for dealing with offline packages
var packageManager = NTCartoPackageManager(source: "<your-package-source>", dataFolder: packageFolder)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Create PackageManager instance for dealing with offline packages
val packageFolder = File(applicationContext.getExternalFilesDir(null), "foldername")
if (!(packageFolder.mkdirs() || packageFolder.isDirectory())) {
println("Could not create package folder!")
}
var packageManager = try {
CartoPackageManager("<your-package-source>", packageFolder.absolutePath)
} catch (e: IOException) {
null
}
Package downloads for country packages cannot be started immediately, as the Mobile SDK needs to get latest definition of packages from CARTO online service. Once this list is received, PackageManagerListener’s onPackageListUpdated()
is called.
Therefore you should write your own PackageManagerListener
, and start package download using the onPackageListUpdated
method, which ensures that the package metadata is downloaded.
You see that onPackageListUpdated()
callback starts immediately download of some packages, as we are quite impatient here. Real download starts when initialization is done and PackageManager is started.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MyPackageManagerListener extends PackageManagerListener {
PackageManager packageManager;
public MyPackageManagerListener(PackageManager manager) {
packageManager = manager;
}
@Override
public void onPackageListUpdated() {
Log.d(Const.LOG_TAG, "Package list updated");
// Start download of package of Estonia. You can call several package downloads here
// see list of available ID-s: https://github.com/CartoDB/mobile-sdk/wiki/List-of-Offline-map-packages
packageManager.startPackageDownload("EE");
packageManager.startPackageDownload("LV");
}
@Override
public void onPackageListFailed() {
Log.e(Const.LOG_TAG, "Package list update failed. Network connection issues ? ");
}
@Override
public void onPackageStatusChanged(String id, int version, PackageStatus status)
{
// you can monitor download process %
}
@Override
public void onPackageCancelled(String id, int version) {
}
@Override
public void onPackageUpdated(String id, int version) {
Log.d(Const.LOG_TAG, "Offline package updated: " + id);
}
@Override
public void onPackageFailed(String id, int version, PackageErrorType errorType) {
Log.e(Const.LOG_TAG, "Offline package update failed: " + id);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class MyPackageManagerListener : PackageManagerListener
{
PackageManager packageManager;
public MyPackageManagerListener(PackageManager manager)
{
packageManager = manager;
}
public override void OnPackageListUpdated()
{
Log.Debug("Package list updated");
// We have packages for all country/regions
// see list of available ID-s: https://github.com/CartoDB/mobile-sdk/wiki/List-of-Offline-map-packages
packageManager.StartPackageDownload("EE");
packageManager.StartPackageDownload("LV");
}
public override void OnPackageListFailed()
{
Log.Error("Package list update failed");
}
public override void OnPackageStatusChanged(String id, int version, PackageStatus status)
{
// here you can get progress of download
}
public override void OnPackageCancelled(String id, int version)
{
}
public override void OnPackageUpdated(String id, int version)
{
Log.Debug("Offline package updated: " + id);
}
public override void OnPackageFailed(String id, int version, PackageErrorType errorType)
{
Log.Error("Offline package download failed: " + id);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@interface MyPackageManagerListener : NTPackageManagerListener
@property NTPackageManager* _packageManager;
- (void)setPackageManager:(NTPackageManager*)manager;
@end
@implementation MyPackageManagerListener
- (void)onPackageListUpdated
{
NSLog(@"onPackageListUpdated");
// We have packages for all country/regions
// see list of available ID-s: https://github.com/CartoDB/mobile-sdk/wiki/List-of-Offline-map-packages
[self._packageManager startPackageDownload: @"EE"];
[self._packageManager startPackageDownload: @"LV"];
}
- (void)onPackageListFailed
{
NSLog(@"onPackageListFailed");
}
- (void)onPackageUpdated:(NSString*)packageId version:(int)version
{
}
- (void)onPackageCancelled:(NSString*)packageId version:(int)version
{
}
- (void)onPackageFailed:(NSString*)packageId version:(int)version errorType:(enum NTPackageErrorType)errorType
{
NSLog(@"onPackageFailed");
}
- (void)onPackageStatusChanged:(NSString*)packageId version:(int)version status:(NTPackageStatus*)status
{
// here you can get progress of download
NSLog(@"onPackageStatusChanged progress: %f", [status getProgress]);
}
- (void)setPackageManager:(NTPackageManager*)manager
{
self._packageManager = manager;
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MyPackageManagerListener : NTPackageManagerListener {
var packageManager: NTPackageManager?
convenience init(manager: NTCartoPackageManager) {
self.init()
self.packageManager = manager
}
public override func onPackageListUpdated() {
// Start download of package of Estonia and Latvia
// see list of available ID-s: https://github.com/CartoDB/mobile-sdk/wiki/List-of-Offline-map-packages
self.packageManager?.startPackageDownload("EE");
self.packageManager?.startPackageDownload("LV");
}
public override func onPackageListFailed() {
}
public override func onPackageStatusChanged(_ arg1: String!, version: Int32, status: NTPackageStatus!) {
// Here you can monitor download process %
}
public override func onPackageUpdated(_ arg1: String!, version: Int32) {
}
public override func onPackageCancelled(_ arg1: String!, version: Int32) {
}
public override func onPackageFailed(_ arg1: String!, version: Int32, errorType: NTPackageErrorType) {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class MyPackageManagerListener(val packageManager: CartoPackageManager) : PackageManagerListener() {
var packageManager: PackageManager? = null
constructor(val manager: PackageManager?) {
packageManager = manager;
}
override fun onPackageListUpdated() {
// Start download of package of Estonia and Latvia
// see list of available ID-s: https://github.com/CartoDB/mobile-sdk/wiki/List-of-Offline-map-packages
packageManager.startPackageDownload("EE");
packageManager.startPackageDownload("LV");
}
override fun onPackageListFailed() {
}
override fun onPackageStatusChanged(id: String?, version: Int, status: PackageStatus?) {
// Here you can monitor download process %
}
override fun onPackageUpdated(id: String?, version: Int) {
}
override fun onPackageCancelled(id: String?, version: Int) {
}
override fun onPackageFailed(id: String?, version: Int, errorType: PackageErrorType?) {
}
}
To link PackageManagerListener with PackageManager, apply the following code.
1
2
3
4
5
6
7
8
// 1. Set listener, and start PackageManager
packageManager.setPackageManagerListener(new MyPackageManagerListener(packageManager));
packageManager.start();
// 2. Fetch list of available packages from server. Note that this is asynchronous operation and listener will be notified via onPackageListUpdated when this succeeds.
packageManager.startPackageListDownload();
1
2
3
4
5
6
7
8
9
// 1. Create and set listener, and start PackageManager
packageManager.PackageManagerListener = new MyPackageManagerListener(packageManager);
packageManager.Start();
// 2. Fetch list of available packages from server.
// Note that this is asynchronous operation and the listener will be notified via OnPackageListUpdated when this succeeds.
packageManager.StartPackageListDownload();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NTCartoPackageManager* packageManager = [[NTCartoPackageManager alloc] initWithSource:@"<your-package-source>" dataFolder:packagesDir];
// 1. Create PackageManagerListener with your listener class
MyPackageManagerListener* _packageManagerListener = [[MyPackageManagerListener alloc] init];
[_packageManagerListener setPackageManager: packageManager];
// Attach package manager listener
[packageManager setPackageManagerListener:_packageManagerListener];
// Start PackageManager
[packageManager start];
// 2. Start download of packageList. When download is done, then the listener's onPackageListUpdated() is called
[packageManager startPackageListDownload];
1
2
3
4
5
6
7
8
9
10
11
12
13
// Create PackageManager instance for dealing with offline packages
var packageManager = NTCartoPackageManager(source: "<your-package-source>", dataFolder: packageFolder)
// 1. Set listener, and start PackageManager
packageManager?.setPackageManagerListener(MyPackageManagerListener(manager: packageManager!))
packageManager?.start()
// 2. Fetch list of available packages from server.
// Note that this is asynchronous operation
// and listener will be notified via onPackageListUpdated when this succeeds.
packageManager?.startPackageListDownload()
1
2
3
4
5
6
7
8
9
10
// 1. Set listener, and start PackageManager
packageManager?.packageManagerListener = MyPackageManagerListener(packageManager)
packageManager.start()
// 2. Fetch list of available packages from server.
// Note that this is asynchronous operation
// and listener will be notified via onPackageListUpdated when this succeeds.
packageManager.startPackageListDownload()
To have offline map data the PackageManager has to download some maps. Thre are two types of offline downloads: code sample above already includes downloading for country ID-s, and you can download also offline maps for any custom areas e.g. cities.
During any package download PackageManagerListener gets onPackageStatusChanged()
call back events, this can be used to display progress bar UI.
Se list of available ID-s: https://github.com/CartoDB/mobile-sdk/wiki/List-of-Offline-map-packages
Start download of e.g. Estonia with this PackageManager use method: .startPackageDownload("EE");
. Note that you must be sure that the PackageList is downloaded at least once: this is started with .startPackageListDownload()
and confirmed in onPackageListUpdated()
callback.
CARTO Mobile SDK allows for the download of custom areas, called bounding boxes. It can be a city or national park for example. Note that there is size limit of about 50x50 km for the allowed areas here. For bigger areas you need to use several downloads, or country package.
A bounding box is constructed as bbox(west longitude,south latitude,east longitude,north latitude
, so the bounding box of Berlin would be: bbox(13.2285,52.4698,13.5046,52.57477)
. There is no separate method for bounding box download start, just use same as string instead of instead of a country or county code, as package ID. So Berlin download would start with .startPackageDownload("bbox(13.2285,52.4698,13.5046,52.57477)");
Note that if you only download bounding box areas in your app, then PackageListDownload is not needed.
You should add CartoOfflineVectorTileLayer
to the MapView, using PackageManager and map style for offline map layer as constructor parameters.
Warning - until map is downloaded, then this layer will have no map. So you may want to add another online tiled layer with same style, which will be replaced once offline map is downloaded
1
2
3
CartoOfflineVectorTileLayer layer = new CartoOfflineVectorTileLayer(cartoPackageManager, CartoBaseMapStyle.CARTO_BASEMAP_STYLE_VOYAGER);
mapView.getLayers().add(layer);
Offline Routing is covered in our Offline Routing document
Offline geocoding is covered in our Offline Geocoding document
There is no special event or method to check package updates, so updates can be checked and controlled by application using following logic. You can call this logic as soon as you feel appropriate. Different packages can be updated in different point of time, and with different frequency.
During re-download of same package application shows old map until download is complete. So the update can run in background safely.