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:
- Enable caching when performing one or more requests (for example, using
NMACLE2ProximityRequest
). - Download one or more layers.
- 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
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.
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:
- Geometry ID, accessible with
geometryId
- 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:
.
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 .