SDK for iOS Developer's Guide

Using CLE2 Offline

You can perform search requests online to the server or offline to the local device. To enable offline search against local data, HERE SDK provides different ways for you to pre-fetch data.

Use offline mode as much as possible since it provides the following advantages:

  • Resilience to network instability.
  • More efficient use of network bandwidth. Instead of sending one request per object you can aggregate requests locally and transmit data in batches.
  • Savings in network bandwidth. Your app can cache and update data only near the user's current location or pre-download a layer only when a Wi-Fi network becomes available.
  • Potentially making the application more responsive and improving user experience and interface interactions since the data is already available locally on the device.
  • Create or modify geometries with HERE SDK and then store them locally effectively using HERE SDK as a data source and a storage of information.

The offline CLE2 feature is designed to be simple to use. Since all database synchronization and geospatial-related complexities are handled by the SDK, you can focus on other parts of app development.

Querying the Local Storage

To search using locally stored data, set the connectivity mode to NMACLE2ConnectivityModeOffline for any of your NMACLE2Request and perform the request:

// Create any request as normal
// In this example we use the proximity request:
NMACLE2ProximityRequest *proximityRequest;
proximityRequest = [[NMACLE2ProximityRequest alloc]
            initWithLayer:@"HERE_SITES"
                 center:[NMAGeoCoordinates geoCoordinatesWithLatitude:50.113905
                                      longitude:8.677608]
                 radius:500]; // 500 meters

// Set to offline mode:
proximityRequest.connectivityMode = NMACLE2ConnectivityModeOffline;

// Now perform the request
[proximityRequest startWithBlock:^(NMACLE2Request *request, NMACLE2Result *result, NSError *error) {
  if(!error) {
    NSLog(@"Geometries returned in result.geometriesArray came from the device local storage.");
  }
}];

You can configure the search request to hybrid or automatic mode indicating that if during an online request the connection drops or there is a network error, then the request automatically falls back to an offline operation. You can see whether the search was performed online or offline by checking the connectivity mode that was used to perform the search. This can be done by inspecting connectivityModeUsed property on the NMACLE2Result object.

// Create any request as normal;
// In this example we use the proximity request:
NMACLE2ProximityRequest *proximityRequest;
proximityRequest= [[NMACLE2ProximityRequest alloc]
          initWithLayer:@"HERE_SITES"
               center:[NMAGeoCoordinates geoCoordinatesWithLatitude:50.113905
                                    longitude:8.677608]
               radius:500]; // 500 meters

// Set to automatic mode
proximityRequest.connectivityMode = NMACLE2ConnectivityModeAutomatic;

// now calling a startWithBlock will try an online request, if it fails, the offline storage kicks in
[proximityRequest startWithBlock:^(NMACLE2Request *request, NMACLE2Result *result, NSError *error) {
  if(!error) {
    NSLog(@"Geometries returned in result.geometriesArray.");
    // to find out whether this response came from the local storage or was
    // obtained from the server (online request), check the requestMode property:
    NMACLE2ConnectivityMode connectivityModeUsed = result.connectivityModeUsed;
  }
  }];

Ways to Populate the Local Storage

By default offline features are disabled and the local storage contains no data. There are currently three ways to add geometries to make them available for offline search:

  1. Enable caching when performing one or more requests (for example, using NMACLE2ProximityRequest).
  2. Download one or more layers.
  3. Direct insertion of data into the local database.

After populating the database, you can query for the data in offline mode as usual by switching connectivity mode of the respective request to NMACLE2ConnectivityModeOffline.

NMACLE2DataManager and NMACLE2Task

The NMACLE2DataManager object is the central interaction point with the local storage. With it, it is possible to:

  • Download all geometries of a specific layer
  • Check how many geometries are currently stored in total, or in a specific layer
  • Delete geometries belonging to a specific layer
  • Purge the local storage by deleting all items
  • Create, update, or delete a local or remote geometry

All the operations relating to data management that NMACLE2DataManager exposes make use of an NMACLE2Task that represents a unit of work. Since all of the data management operations involve database access, network communication, or both, NMACLE2Task runs asynchronously. You can obtain an NMACLE2Task object from NMACLE2DataManager.

With NMACLE2Task you can:

  • Pass it to other parts of your code. NMACLE2Task is a self-contained unit of work.
  • Subscribe for results of the operation. Multiple subscribers are supported and they are called on the main thread.
  • Start execution of the task. Tasks are reusable. You can run them repeatedly multiple times, which makes retrying a failed operation very easy.
  • Cancel a running task.
  • Check if the task is started.
  • Check if the task has finished.
  • Wait for the task to finish.
  • Retrieve the status of a finished operation directly from the task (check for errors).
  • Retrieve the result of a successfully finished operation directly from the task.

Storing Data by Caching Search Results

When caching is enabled in an NMACLE2Request, any returned geometries are automatically stored locally. To activate it, set cacheEnabled property to YES before performing the request:

// Create any request as normal;
// In this example we use the proximity request:
NMACLE2ProximityRequest *proximityRequest;
proximityRequest = [[NMACLE2ProximityRequest alloc] initWithLayer:@"HERE_SITES"
     center:[NMAGeoCoordinates geoCoordinatesWithLatitude:50.113905
                          longitude:8.677608]
     radius:500]; // 500 meters

// activate caching:
proximityRequest.cachingEnabled = YES;

// Now perform the request
[proximityRequest startWithBlock:^(NMACLE2Request *request, NMACLE2Result *result, NSError *error) {
  if(!error) {
    NSLog(@"Geometries returned in result.geometriesArray are now stored in the local database.");
  }
}];

// now some geometries are in local storage. At a later point in time if we'd like to make an offline search,
// simply switch the requesMode to offline only in the request:
proximityRequest.connectivityMode = NMACLE2ConnectivityModeOffline;

// now calling a startWithBlock will operate completely offline:
[proximityRequest startWithBlock:^(NMACLE2Request *request, NMACLE2Result *result, NSError *error) {
  if(!error) {
    NSLog(@"Geometries returned in result.geometriesArray are now stored in the local database.");
  }
}];

Storing Data by Downloading Layers

The second option is to use NMACLE2DataManager to insert data to the local storage using downloadLayer method.

The following is an example of how to use downloadLayerTask method:


// Usage examples of NMACLE2DataManager

NMACLE2DataManager *dataManager = [NMACLE2DataManager sharedManager];
// 1 - Download a layer previously uploaded to the server
NMACLE2Task<NMACLE2OperationResult *> *task = [dataManager downloadLayerTask:@"LOCAL_SHOPS"];
[task subscribeWithBlock:^(NMACLE2OperationResult * _Nonnull result, NSError * _Nonnull error) {
  if (error) {
    NSLog(@"Unable to download layer. Error: %@", [error description]);
  } else {
    NSLog(@"Layers downloaded successfully.");
  }
}];
[task start];

// 2 - Print the total number of stored geometries (sum of all downloaded layers),
// plus any cached geometries (e.g., from a proximity request with cache enabled)
NSNumber *numberOfGeometries;
NSError *error;
numberOfGeometries = [dataManager numberOfStoredGeometriesinLayer:@"LOCAL_SHOPS" onError:&error];

NSLog(@"Total count: %lli", [numberOfGeometries longLongValue]);

// 3 - Delete all geometries from a specific layer
NMACLE2Task<NMACLE2OperationResult*> *deleteTask;
deleteTask = [[NMACLE2DataManager sharedManager] deleteLayersTask: @[@"LOCAL_SHOPS"]
                            fromStorage:NMACLE2StorageTypeLocal];
[deleteTask subscribeWithBlock:^(NMACLE2OperationResult * _Nonnull result, NSError * _Nonnull error) {
  if (error) {
     NSLog(@"Unable to delete layer. Error: %@", [error description]);
  } else {
    NSLog(@"Layers deleted successfully.");
  }
}];
[deleteTask start];

NMACLE2Task<NMACLE2OperationResult *> *purgeTask;
purgeTask = [[NMACLE2DataManager sharedManager] purgeLocalStorageTask];
[purgeTask subscribeWithBlock:^(NMACLE2OperationResult * _Nonnull result, NSError * _Nonnull error) {
  if (error) {
    NSLog(@"Failed to purge local storage: %@", [error description]);
  }
}];
[purgeTask start];

Storing Data by Inserting Geometries

You can generate location-based data and persist it locally, remotely, or both, by using the method geometryTask:onLayer:withGeometries:inStorage: from the NMACLE2DataManager class. This factory method returns an NMACLE2Task object that can be used to start, cancel, or to fetch results of operations at any given time.

  geometryTask:(NMACLE2Operation)operationType
     onLayer:(NSString*)layerId
withGeometries:(NSArray<NMACLE2Geometry *>*)geometriesArray
   inStorage:(NMACLE2StorageType)storage;
  • The first parameter in this method describes the operation type which can be one of the following:
    • NMACLE2OperationTypeCreate
    • NMACLE2OperationTypeUpdate
    • NMACLE2OperationTypeDelete
    Note that querying for geometries is accomplished through the respective NMACLE2Request specialized classes, so there is no "read" opreation here.
  • The second parameter is the layer the operation should be applied to.
  • The third parameter is a list with the geometries themselves.
  • The last parameter defines whether to operate on local (NMACLE2StorageTypeLocal) or remote storage (NMACLE2StorageTypeRemote) using HERE CLE2 server.
Note: While this section covers usage of this method for the local option, all operations (create, update, delete) can also be used to change remote layers.

The following is an example on how to create a geometry and store it locally:

NSMutableArray <NMACLE2Geometry *> *geometriesToAdd = [NSMutableArray new];
for (int i = 0; i < 100; ++i) {
   NMACLE2GeometryPoint *newPoint = [[NMACLE2GeometryPoint alloc] init];
   NSString *key = [NSString stringWithFormat:@"key_%i", i];
   NSString *valueString = @"hello";
   newPoint.attributes = [@{ key : @"value",
              @"HERE_SDK" : @"3.21",
              @"Some_numbers" : valueString,
              @"New_Feature" : @"Offline functionality \U0001F44C"} mutableCopy];
   newPoint.coordinates = [[NMAGeoCoordinates alloc] initWithLatitude: i % 180 longitude:i % 359];
   [geometriesToAdd addObject:newPoint];
}

NMACLE2Task<NMACLE2OperationResult*> *task;
task = [[NMACLE2DataManager sharedManager] geometryTask:NMACLE2OperationTypeCreate
                         onLayer:@"LOCAL_POINTS_TEST_LAYER"
                    withGeometries:geometriesToAdd
                       inStorage:NMACLE2StorageTypeLocal];
[task subscribeWithBlock:^(NMACLE2OperationResult * _Nonnull result, NSError * _Nonnull error) {
   if (error) {
     NSLog(@"error: %@", [error description]);
   } else {
     NSLog(@"Finished for layer %@", @"LOCAL_POINTS_TEST_LAYER");
   }
}];
[task start];

Uploading a Local Layer

It is possible to upload a locally stored layer to the server. Since this requires two operations (fetch from local storage and upload), it's a good candidate to run individual tasks in synchronous manner to avoid callback hell creeping in. Of course, this needs to be done on its own thread, for example using AsyncTask.

// first fetch all geometries
NMACLE2DataManager *dataManager = [NMACLE2DataManager sharedManager];
NSString *layerId = @"LAYER";
NMACLE2Task<NSMutableArray<NMACLE2Geometry *> *> *fetchTask;
fetchTask = [dataManager fetchLocalLayersTask:@[layerId]];
[fetchTask startWithBlock:^(NSMutableArray <NMACLE2Geometry *> *_Nonnull result, NSError *_Nonnull error) {
  if (error) {
    NSLog(@"Error fetching local geometries: %@", error.description);
    return;
  }
  // Success, upload all geometries from the selected local layer
  NMACLE2Task<NMACLE2OperationResult *> *uploadtask;
  uploadtask = [dataManager uploadLayerTask:layerId withGeometries:result];
  // start the upload
  [uploadtask startWithBlock:^(NMACLE2OperationResult *_Nonnull result, NSError *_Nonnull error) {
    if (error) {
      NSLog(@"Upload error: %@", error.description);
      return;
    }
    NSLog(@"Layer Upload Complete. %@ geometries uploaded.", result.affectedItemCount);
  }];
}];

Data Management Considerations

The following are a few tips to help with data management when using CLE2 in an offline context.

Local-only Geometries

All NMACLE2Geometry objects have the following properties:

  1. Geometry ID, accessible with geometryId
  2. Locality flag, accessible with isLocal

The geometry ID is unique to a layer. If a geometry object has just been created, its geometry ID is nil and the locality flag is NO.

The locality flag tells whether this geometry belongs to a local context only, meaning it was not retrieved or passed through the CLE2 server. A geometry with a true locality flag has a locally generated unique geometry ID. Otherwise, it contains a server-provided ID. This server-provided ID is not related to the locally generated IDs of geometries stored directly in the database created via geometryTask:onLayer:withGeometries:inStorage:.

Note: The functionality of locally storing geometries without passing through the server is provided so that you did not need to manage data persistence on these objects when a connection is not available.

For simplicity, when saving geometries directly to the local database, keep them using a separate layer name. If at a later desired point in time these geometries should be shared with the server, fetch all local geometries using fetchLocalLayersTask: method of NMACLE2DataManager and then upload them either using uploadLayerTask:withGeometries: or geometryTask:onLayer:withGeometries:inStorage: with a create operation (NMACLE2OperationTypeCreate). This avoids the requirement to check for isLocal property.

By using these concepts, you can move geometries to different layers, contexts, and use these tools to organize data.

Data Consistency

Use of uploadLayerTask:withGeometries: should be primarily restricted to administrative users because this method deletes all existing geometries in the server and recreates the layer with the provided ones. If the user does not have the latest information for this layer, data loss may occur, as it can overwrite another user's upload.

Therefore, for a scenario with continuous or concurrent geometry upload, use geometryTask:onLayer:withGeometries:inStorage: method with NMACLE2OperationTypeCreate or NMACLE2OperationTypeUpdate. Operating in an "append only" manner or only updating the existing geometries prevents data loss even if users are uploading geometries concurrently to the server.

Current Limitations

Currently individualized user account management for the CLE2 server is not available. For security reasons, take care to keep your app credentials well hidden. If your application requires a user account access feature, see Service Support to contact us for more details .

Note: Since geospatial queries are the focus of CLE2, HERE SDK does not support attribute searches in offline mode. You can filter the data using one of the geospatial queries (such as proximity) to narrow down the results to a small enough number that most applications do not suffer performance impact by iterating the geometry attributes key-value dictionary to filter results further.