v0.5.3
v0.5.0
v0.4.0
v0.3.0
v0.2.0
v0.1.1
v0.1.0
The MOPRIM TMD SDK for iOS when installed and configured properly will gather data streams from the mobile device’s motion sensors to capture how its owner moves. Once the Moprim TMD service is started, the SDK automatically processes the data and synchronizes its results with our cloud. The final outputs of the TMD will be served through our cloud.
Pre-requisite: the MOPRIM TMD SDK requires that your project targets iOS 11 or later. The SDK has currently been tested on iOS 12, 13 and 14 devices.
Before you start to use the MOPRIM TMD SDK in your iOS application, you will need to add the SDK as a dependency. The MOPRIM SDK also requires that you include some other dependencies into your project in order to function. If you do not have the MOPRIM TMD SDK, you can contact us to get a quote and developer API key.
- AWSAPIGateway.xcframework
- AWSAuthCore.xcframework
- AWSCognitoIdentityProvider.xcframework
- AWSCognitoIdentityProviderASF.xcframework
- AWSCore.xcframework
- AWSMobileClientXCF.xcframework
- AWSS3.xcframework
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>The apps monitors the user's mobility and requires the user's location to do so</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>If the user is mobile, a significant location change will trigger the capture of the mobility data of the user</string>
<key>NSLocationUsageDescription</key>
<string>The apps monitors the user's mobility and requires the user's location to do so</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>The apps monitors the user's mobility and requires the user's location to do so</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>tmd.AccurateLocationPurpose</key>
<string>The app monitors your mobility and requires your precise location to do so</string>
</dict>
<key>NSMotionUsageDescription</key>
<string>The app collects accelerometer data to evaluate the modes of transports used by the iOS device user</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
</array>
The strings you associate with those keys will be presented to the user to ask for permission to access location and motion data. It is important that the user allows the app to access “Precise” location “Always and when in use” for the TMD to work correctly. It is also important that the user allows access to motion data.
Please note that starting from iOS 13, iOS first asks the user for “WhenInUse” authorization, and later, at a time that it deems appropriate, for “Always” authorization. The Transport Mode Detection will only work once the user allows the location to be accessed “always”.
Please note that starting from iOS 14, iOS allows the user to give access to only “Approximate” location. The Transport Mode Detection will only work once the user allows the “Precise” location to be accessed.
The SDK requires an API key and an API enpoint configuration. It is recommended to configure the TMD in your AppDelegate’s didFinishLaunchingWithOptions method with a call to TMD#initWithKey:withEndpoint:withLaunchOptions:. If for some reason you cannot initialize the TMD from the AppDelegate, be sure to call TMD#initWithKey:withEndpoint:withLaunchOptions: before you do any other operation with the SDK.
import MOPRIMTmdSdk
(...)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Configure the app to trigger Background Fetch events as regularly as possible.
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
// Declare your API Key and Endpoint:
let myKey = "MY_SDK_CONFIG_KEY"
let myEndpoint = "MY_SDK_CONFIG_END_POINT"
// Initialize the TMD:
TMD.initWithKey(myKey, withEndpoint: myEndpoint, withLaunchOptions: launchOptions).continueWith { (task) -> Any? in
if let error = task.error {
NSLog("Error while initializing the TMD SDK: %@", error.localizedDescription)
}
else {
// Get the app's installation id:
NSLog("Successfully initialized the TMD with id %@", task.result ?? "<nil>")
}
return task;
}
return true
}
Additionally, the TMD SDK’s backgroundFetch method needs to be called from your AppDelegate’s performFetchWithCompletionHandler method in order to send the recorded data from the phone to MOPRIM’s API when the app is in the background:
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Run our background operations
TMD.backgroundFetch().continueWith (block: { (task) -> Void in
let tmdFetchResult:UIBackgroundFetchResult = UIBackgroundFetchResult(rawValue: (task.result!.uintValue))!
// Call the completion handler with the UIBackgroundFetchResult returned by TMD.backgroundFetch(), or with your own background fetch result
completionHandler(tmdFetchResult)
})
}
To ensure that data is sent correctly to MOPRIM’s API, you should also call TMD#application:handleEventsForBackgroundURLSession:completionHandler: from your AppDelegate’s:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
TMD.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
It is also recommended that you make a call to the TMD’s applicationWillTerminate method from your AppDelegate’s applicationWillTerminate method in order to give a chance to the TMD to stop gracefully when the application is killed.
func applicationWillTerminate(_ application: UIApplication) {
TMD.applicationWillTerminate()
}
The TMD service can be started with:
TMD.start()
The TMD SDK will take care of asking user authorization to access location and motion data when it is first started. However, if you want to have control over when these permissions are asked to the user and what happens when they are denied, we recommend that you ask these permissions before starting the TMD for the first time.
In your code, you can do that with an instance of CLLocationManager:
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
func askLocationPermissions() {
self.locationManager.delegate = self
self.locationManager.requestAlwaysAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if #available(iOS 14.0, *) {
let preciseLocationAuthorized = (manager.accuracyAuthorization == .fullAccuracy)
if preciseLocationAuthorized == false {
manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "tmd.AccurateLocationPurpose")
// Note that this will only ask for TEMPORARY precise location.
// You should make sure to ask your user to keep the Precise Location turned on in the Settings.
}
} else {
// No need to ask for precise location before iOS 14
}
}
}
In your code, using an instance of CMMotionActivityManager to start activity updates will prompt the user to allow motion access, like that:
import CoreMotion
let motionActivityManager = CMMotionActivityManager()
func askMotionPermissions() {
if CMMotionActivityManager.isActivityAvailable() {
self.motionActivityManager.startActivityUpdates(to: OperationQueue.main) { (motion) in
print("received motion activity")
self.motionActivityManager.stopActivityUpdates()
}
}
}
You can stop the TMD service with:
TMD.stop()
Applications behave differently when they are in foreground and in background, and your app might get killed by the OS after being in the background for a while. If configured properly, your app can stay active in the background to some extent, and wake up to detect that the user started moving, and start the TMD.
In order to keep the TMD running in the background, the app must have access to location and motion data, and register to the “fetch” and “location” background modes, as mentionned in the Configure your project section.
You can register a delegate of type TMDDelegate to be notified when the TMD starts and stops, or when it could not start or had to stop because of an error.
TMD metadata contains information about the information that our cloud knows about a user:
More information about TMDCloudMetadata can be found in the appledoc.
In order to get the TMD Cloud metadata, please run the TMDCloudApi#fetchMetadata method.
TMDCloudApi.fetchMetadata().continueWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("fetchMetadata Error: %@", error.localizedDescription)
}
else if let metadata = task.result {
NSLog("fetchMetadata result: %@", metadata)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.
The TMD SDK uses TMDTask to handle operations with the TMDCloudAPI. More information can be found in the appledoc
TMD Activities contains information about the recognized means of transport of the mobile phone carrier:
More information about TMDActivity can be found in the appledoc.
In order to get the list of TMD activities, please run the TMDCloudApi#fetchData:minutesOffset: method or alike.
TMDCloudApi.fetchData(date, minutesOffset: 0).continueWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("fetchData Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("fetchData result: %@", data)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.
In the event the TMD recognized a wrong activity or the discovered activity is not precise enough (e.g., motorized/road/car instead of motorized/road/car/electric for an electric car), it is possible to manually change the label of this activity by using the TMDCloudApi#correctActivity:withLabel: method.
TMDCloudApi.correctActivity(activity, withLabel: newLabel).continueOnSuccessWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("correctActivity Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("correctActivity result: %@", data)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.
The iOS SDK is configured via the MOPRIM Cloud. Therefore, the number of TMD activities that can be recognized (e.g., motorized/road/car, non-motorized/bicycle) can change everytime the TMD is reconfigured with the latest models. Please refer to the list of current activities.
The TMD SDK enables you to annotate an activity with more information than the one provided by the SDK natively. You can set the metadata for this activity by using the TMDCloudApi#annotateActivity method.
TMDCloudApi.annotateActivity(activity, withMetadata: metadata).continueOnSuccessWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("annotateActivity Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("annotateActivity result: %@", data)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.
It is possible to update the label and add metadata at the same time by using the TMDCloudApi#updateActivity:withLabel:withMetadata: method.
TMD Trips are objects that group TMD Activities together as legs when the Moprim Cloud has determined that they are part of the same trip.
A TMDTrip contains the following:
More information about TMDTrip can be found in the appledoc.
Similarly to TMD Activities, it is possible to fetch TMD Trip data from the cloud or from the cache, with the following methods:
Fetching TMD Trips will also fetch their TMD Activities.
Here is an example of fetching trips for a date:
TMDCloudApi.fetchTrips(for: date, minutesOffset:0).continueWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("fetchTrips Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("fetchTrips result: %@", data)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.
Validating a trip will mark it and its activities (or legs) as validated. This can be done by calling the following method: TMDCloudApi#validateTrip:
TMDCloudApi.validateTrip(trip).continueOnSuccessWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("validateTrip Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("validateTrip result: %@", data)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.
A trip can be updated with some metadata, or a reason for the trip. This can be done by calling the following method: TMDCloudApi#updateTrip:withReason:withMetadata:
TMDCloudApi.updateTrip(trip, withReason: "errands", withMetadata: metadata).continueOnSuccessWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("updateTrip Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("updateTrip result: %@", data)
}
return nil;
}
}
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand.
TMD statistics contain information about a list of statistics for each type of activity and for a specific time period (weekend vs. weekdays and last X days vs. overall. For each value we provide the combined sum of CO2 emissions, the distance and duration of these activities for the user as well as for the community (daily average per user).
Check the appledoc for more information.
TMDCloudApi.fetchStatsForLast(nbOfDays).continueOnSuccessWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("fetchStats Error: %@", error.localizedDescription)
}
else if let data = task.result {
NSLog("fetchStats result: %@", data)
}
return nil;
}
}
Check out the appledoc appledoc for more information about TMDStats and TMDStatsValue.
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.
The TMD SDK has an option to enable or disable the upload of mobility data to our cloud using Mobile Data. When this option is disabled, uploads will only happen over Wifi.
This option is disabled by default. You should therefore call the following method (after initializing the TMD for example), if you want uploads to be done using Mobile Data as well:
TMD.setAllowUploadOnCellularNetwork(true)
Our system usually uploads the data automatically to our cloud periodically (when the OS triggers a background fetch, or when the service is started), but it is possible for the application developer to force the upload of data to our cloud.
The current modality is never synchronized with the cloud as it is not considered as completed.
To force the upload of data to our cloud, use the following method:
TMDCloudApi.uploadData().continueWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("uploadData Error: %@", error.localizedDescription)
}
else if let metadata = task.result {
NSLog("uploadData success: %@", metadata)
}
return nil;
}
}
TMDUploadMetadata contains information about timestamp and number of locations uploaded to the Cloud with the call as well as the information for TMD activities.
For more information about TMDUploadMetadata, check out the appledoc.
This method and its completion block execute in a background thread, and require the TMD to be initialized beforehand and an available internet connection.
You can also register a delegate of type TMDUploadDelegate to be notified when an upload starts or ends with the method:
TMD.setUploadDelegate(delegate)
Or at upload time with:
TMDCloudApi.uploadDataWithDelegate(delegate).continueWith { (task) -> Any? in
DispatchQueue.main.async {
// Execute your UI related code on the main thread
if let error = task.error {
NSLog("uploadData Error: %@", error.localizedDescription)
}
else if let metadata = task.result {
NSLog("uploadData success: %@", metadata)
}
return nil;
}
}
For more information about TMDUploadDelegate , check out the appledoc.
It is possible to allow the TMD SDK to automatically download new data after the Moprim Cloud has received and analyzed newly uploaded data. To do so, call this method before starting the TMD:
TMD.setAllowAutoFetch(true)
The auto-fetch is disabled by default. You can use TMD#isAutoFetchAllowed() to check its value.
It is possible to observe NSNotifications that will happen whenever TMDTrip or TMDActivity objects have been changed in the cache. The names of the NSNotifications and the keys that can be used with their corresponding userInfo dictionary can be accessed from TMDNotifications. For more information about TMDNotifications, check out the appledoc. Here is an example of registering to notifications to print in the console what has been changed in the cache:
func registerToNotifications(){
NotificationCenter.default.addObserver(self,
selector: #selector(self.didUpdateActivities(notification:)),
name: NSNotification.Name(rawValue: TMDNotifications.didUpdateActivitiesNotificationName()),
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.didUpdateTrips(notification:)),
name: NSNotification.Name(rawValue: TMDNotifications.didUpdateTripsNotificationName()),
object: nil)
}
@objc func didUpdateActivities(notification: Notification){
if let dict = notification.userInfo {
if let inserted = dict[TMDNotifications.insertedActivitiesKey()] as? [TMDActivity] {
NSLog("didUpdateActivities.inserted \(inserted.count) activities")
for activity in inserted {
NSLog("didUpdateActivities.inserted:\(activity.activityId) - \(activity.activity())")
}
}
if let updated = dict[TMDNotifications.updatedActivitiesKey()] as? [TMDActivity] {
NSLog("didUpdateActivities.updated \(updated.count) activities")
for activity in updated {
NSLog("didUpdateActivities.updated:\(activity.activityId) - \(activity.activity())")
}
}
if let deleted = dict[TMDNotifications.deletedActivitiesKey()] as? [TMDActivity] {
NSLog("didUpdateActivities.deleted \(deleted.count) activities")
for activity in deleted {
NSLog("didUpdateActivities.deleted:\(activity.activityId) - \(activity.activity())")
}
}
}
}
@objc func didUpdateTrips(notification: Notification){
if let dict = notification.userInfo {
if let inserted = dict[TMDNotifications.insertedTripsKey()] as? [TMDTripNotification] {
NSLog("didUpdateTrips.inserted \(inserted.count) trips")
for trip in inserted {
NSLog("didUpdateTrips.inserted:\(trip.tripId) - \(trip.timestampStart) - \(trip.timestampEnd)")
}
}
if let updated = dict[TMDNotifications.updatedTripsKey()] as? [TMDTripNotification] {
NSLog("didUpdateTrips.updated \(updated.count) trips")
for trip in updated {
NSLog("didUpdateTrips.updated:\(trip.tripId) - \(trip.timestampStart) - \(trip.timestampEnd)")
}
}
if let deleted = dict[TMDNotifications.deletedTripsKey()] as? [TMDTripNotification] {
NSLog("didUpdateTrips.deleted \(deleted.count) trips")
for trip in deleted {
NSLog("didUpdateTrips.deleted:\(trip.tripId) - \(trip.timestampStart) - \(trip.timestampEnd)")
}
}
}
}
The list of possible errors returned by any TMD method is available in the appledoc
We currently don’t provide a React Native module to use the TMD SDK from a React Native project. However, it is possible to make one by following the recommendations from the React Native documentation on how to make an iOS Native Module.
As a starting point, here is how you would start and stop the TMD from React Native:
// RCTTmdModule.h
#import <React/RCTBridgeModule.h>
@interface RCTTmdModule : NSObject <RCTBridgeModule>
@end
// RCTTmdModule.m
#import "RCTTmdModule.h"
#import <MOPRIMTmdSdk/MOPRIMTmdSdk.h>
@implementation RCTTmdModule
// Export a module named RCTTmdModule:
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(start)
{
[TMD start];
}
RCT_EXPORT_METHOD(stop)
{
[TMD stop];
}
@end
// NativeTmdModule.js
import { NativeModules } from 'react-native';
const { TmdModule } = NativeModules;
interface TmdInterface {
start(): void;
stop(): void;
}
export default TmdModule;
// App.js
import TmdModule from './NativeTmdModule';
const onPressTmdStart = () => {
TmdModule.start();
};
const onPressTmdStop = () => {
TmdModule.stop();
};
Version 0.6.0
Version 0.5.3
Version 0.5.2
Version 0.5.1
Version 0.5.0
Version 0.4.0
Version 0.3.0
Version 0.2.0
Version 0.1.1
Version 0.1.0
Contact us for getting a quote for the SDK.