轨迹追踪

简介

鹰眼iOS SDK可以根据开发者自定义的定位周期和上传周期,自动地采集设备的位置信息,回传到鹰眼服务端,形成连续的轨迹。开发者可以为每个轨迹点添加附加信息,也可以对定位选项、采集周期与上传周期、缓存容量等进行自定义设置。SDK支持后台运行,断网时自动缓存,网络恢复后自动重连上传。SDK与服务端采用TCP长连接与HTTPS通信,所有数据经过压缩和加密,保障数据安全,省电省流量。


轨迹追踪是鹰眼SDK中最基础、最核心的功能。通过类BTKAction中相应的接口,控制轨迹服务与轨迹采集的开启和停止,操作结果通过BTKTraceDelegate协议中相应的方法回调给开发者。下面首先对涉及到的基本概念进行介绍,之后会对各功能进行阐述。

基础概念

轨迹服务与轨迹采集


轨迹服务的含义:轨迹服务负责和服务端建立并保持长连接,接收服务端的推送消息,并将客户端的轨迹数据尽快地上传到服务端。


轨迹采集的含义:SDK通过访问iOS系统定位服务获取当前设备的位置信息。


服务与采集的关系:与采集相比,服务是一个更广的概念。虽然采集的开启与结束和服务的开启与结束是独立控制的,但他们的调用顺序也有一定的要求:调用startGather和stopGather之前必须先调用startService,并且调用stopService时,SDK会自动stopGather。一个典型的使用场景是:

startService -> startGather -> stopGather -> startGather -> stopGather -> … -> stopService 也就是开启服务之后,在需要采集轨迹的时候就调用startGather,不需要采集轨迹的时候就调用stopGather,最后停止服务。


采集周期与上传周期

SDK以采集周期为间隔,周期性地采集当前设备的位置信息;以上传周期为间隔,周期性地将若干轨迹点上传至鹰眼服务端。


后台保活

保活指的是当SDK在后台运行时不被系统杀死。如果选择不保活,当SDK只是开启了服务而没有开启采集时,由于没有访问定位服务,在后台运行时,可能会被系统杀死;如果选择保活,开启服务之后,即使没有开启采集,SDK也会访问系统的定位服务,使其在后台运行时不被系统杀死。有3点需要注意的是:

保活时由于会访问系统定位服务,因此在系统的状态栏上会显示定位图标,但此时SDK并不会采集设备的位置。只要没有开启采集,当前设备的位置就不会被上传到鹰眼服务端。 保活目的下访问系统定位服务的定位精度较低、距离阈值较大,因此几乎不会额外增加耗电量。 由于iOS优先保证前台APP的资源,保活只是减小后台运行被杀的概率,不保证系统资源紧张时,SDK在后台运行一定不会被杀掉。

服务初始化

通过BTKAction类的 –(BOOL)initInfo:(BTKServiceOption *)option; 方法设置SDK运行所需要的基础信息。包括ak、mcode、轨迹服务的ID以及是否需要保活。调用SDK的任何接口之前,必须先调用此方法,否则SDK的各功能无法正常运行。


以下代码片段表示,设置SDK运行时需要的基础信息。

BTKServiceOption *sop = [[BTKServiceOption alloc] initWithAK:@"asdf1234asdf1234" mcode:@"com.yingyan.sdk" serviceID:100000 keepAlive:false];
[[BTKAction sharedInstance] initInfo:sop];

轨迹服务控制

轨迹服务的控制分为开启轨迹服务(startService)和停止轨迹服务(stopService)两个接口。


开启服务

通过BTKAction类的 -(void)startService:(BTKStartServiceOption *)option delegate:(id <BTKTraceDelegate>)delegate; 方法开启轨迹服务,操作结果通过 BTKTraceDelegate 协议的 -(void)onStartService:(BTKServiceErrorCode) error; 方法回调给开发者。 开启服务时需要指定BTKStartServiceOption类型的配置信息,目前BTKStartServiceOption 类中只有entityName一个属性。该entityName在SDK的运行过程中有举足轻重的作用:


开启轨迹服务之后,SDK会以此entityName的身份登录到服务端; - 在轨迹服务运行期间,收到的地理围栏报警信息都来自于被监控对象为此entityName的地理围栏; - 在轨迹服务运行期间,采集到的轨迹数据也隶属于此entityName名下; - 开启服务之后,如果有之前遗留的缓存数据,也会将其全部上传至服务端(所有entity的缓存数据都会上传)。


以下代码片段表示,开启轨迹服务:

// 设置开启轨迹服务时的服务选项,指定本次服务以“entityA”的名义开启
BTKStartServiceOption *op = [[BTKStartServiceOption alloc] initWithEntityName:@"entityA"];
// 开启服务
[[BTKAction sharedInstance] startService:op delegate:self];


停止服务

停止轨迹服务时,SDK会和服务端断开连接。在此之前,如果当前登录的entity有断网缓存的数据,则将其上传至服务端。以上操作均要求网络畅通,否则只是停止轨迹服务,缓存数据会由SDK持久化保存,待下次开启轨迹服务后,网络畅通的情况下,由SDK自动继续上传。

以下代码片段表示,停止轨迹服务:

[[BTKAction sharedInstance] stopService:self];

轨迹采集控制

轨迹采集的控制分为开启采集(startGather)和结束采集(stopGather)两个接口。开启采集和结束采集都必须在开启轨迹服务之后才可以调用。开启采集之后,SDK会在每个采集周期获取当前设备的位置信息,停止采集之后,SDK 不再获取当前设备的位置信息。


开始采集

通过BTKAction 类的 -(void)startGather:(id <BTKTraceDelegate>)delegate; 方法开始采集轨迹,操作结果通过 BTKTraceDelegate 协议的-(void)onStartGather:(BTKGatherErrorCode) error; 方法回调给开发者。

以下代码片段表示,开始采集。

[[BTKAction sharedInstance] startGather:self];


停止采集

通过BTKAction 类的 -(void)stopGather:(id <BTKTraceDelegate>)delegate; 方法停止采集轨迹,操作结果通过 BTKTraceDelegate 协议的-(void)onStopGather:(BTKGatherErrorCode) error; 方法回调给开发者。


以下代码片段表示,停止采集。

[[BTKAction sharedInstance] stopGather:self];

自定义字段上传

每个轨迹点都可能有一些和具体业务相关的数据,鹰眼iOS SDK支持开发者将这些数据作为轨迹点的自定义数据,“附加”到这个轨迹点上。SDK在每个采集周期会回调 BTKTraceDelegate 协议中的 -(NSDictionary *)onGetCustomData; 方法,将其返回值作为当前采集周期采集的轨迹点的自定义字段的值。SDK会解析此方法返回的字典,将key认为是自定义字段的名称,将value认为是自定义字段的值。需要注意的是,字典中的key必须是已经存在的 track属性字段 。开发者可以通过鹰眼轨迹管理台,设置某个service的 track属性字段。下面我们通过一个例子,来进一步说明如何上传轨迹点的自定义字段值:


比如当前在开发一款跑步类的APP,可能需要记录每个轨迹点所对应的心率信息,那么可以通过以下几个步骤实现这一点。


① 通过鹰眼轨迹管理台,为当前service添加一个 track属性字段,其 字段名称 为 ‘heart_rate’ ,字段描述 为 “心率”,字段类型 为 int。


② 通过心率带或带有心率检测功能的外部设备获取心率,并通过其相应的接口将心率数据传入APP。 定义一个字典类型的变量,包含一个名为heart_rate的键,并设置其初始值。假设为 additional_data[‘heart_rate’] = 60 。


③ 每当APP获取到最新的心率数据时,更新这个字典中heart_rate的值为最新的心率数据。

-(NSDictionary *)onGetCustomData; 方法的实现非常简单,只需要return这个字典即可。


通过以上步骤,SDK在每个采集周期回调这个方法时,就获取到了最新的心率数据,并将此心率数据“附加”在了当前的轨迹点上。


无论是通过 queryEntityList方法查询实时位置,还是通过getTrackHistory方法查询历史轨迹,这些心率数据都会作为轨迹的“附加”数据呈现出来。


补充说明一点,每个采集周期对应轨迹点的速度、方向、海拔高度、定位精度的值,作为轨迹数据的一部分,SDK会自动采集上传,不需要额外实现此方法去指定。


以下代码示例表示,当前开启轨迹服务时指定的serviceID下,已经在轨迹管理台中添加了名为“intTest”、“doubleTest”、“stringTest” 这3个track属性字段,在每个采集周期采集的轨迹点,都将这3个字段赋予一个随机值,“附加”在该轨迹点上。

-(NSDictionary *)onGetCustomData {
    NSMutableDictionary *customData = [NSMutableDictionary dictionaryWithCapacity:3];
    NSArray *intPoll = @[@5000, @7000, @9000];
    NSArray *doublePoll = @[@3.5, @4.6, @5.7];
    NSArray *stringPoll = @[@"aaa", @"bbb", @"ccc"];
    int randomNum = arc4random() % 3;
    // intTest doubleTest stringTest这3个自定义字段需要在轨迹管理台提前设置才有效
    customData[@"intTest"] = intPoll[randomNum];
    customData[@"doubleTest"] = doublePoll[randomNum];
    customData[@"stringTest"] = stringPoll[randomNum];
    return [NSDictionary dictionaryWithDictionary:customData];
}

消息推送回调

在开启轨迹服务之后,停止轨迹服务之前,鹰眼iOS SDK在运行期间内会随时通过 BTKTraceDelegate 协议的 -(void)onGetPushMessage:(BTKPushMessage *)message; 回调方法,向开发者推送消息。推送的消息为 BTKPushMessage 类型,其中type 属性代表推送消息的类型,content 属性代表推送消息的内容。


消息分类

目前鹰眼iOS SDK只会推送服务端地理围栏报警和客户端地理围栏报警2种类型的消息,对应的 type 属性的值分别为 0x03 和 0x04。


消息内容

对于服务端地理围栏报警和客户端地理围栏报警这2种消息类型对应的消息内容,按照 BTKPushMessageFenceAlarmContent 类来解析。


以下代码示例展示了,如何解析消息推送:

- (void)onGetPushMessage:(BTKPushMessage *)message {
    NSLog(@"收到推送消息,消息类型: %@", @(message.type));
    if (message.type == 0x03) {
        BTKPushMessageFenceAlarmContent *content = (BTKPushMessageFenceAlarmContent *)message.content;
        if (content.actionType == BTK_FENCE_MONITORED_OBJECT_ACTION_TYPE_ENTER) {
            NSLog(@"被监控对象 %@ 进入 服务端地理围栏 %@ ", content.monitoredObject, content.fenceName);
        } else if (content.actionType == BTK_FENCE_MONITORED_OBJECT_ACTION_TYPE_EXIT) {
            NSLog(@"被监控对象 %@ 离开 服务端地理围栏 %@ ", content.monitoredObject, content.fenceName);
        }
    } else if (message.type == 0x04) {
        BTKPushMessageFenceAlarmContent *content = (BTKPushMessageFenceAlarmContent *)message.content;
        if (content.actionType == BTK_FENCE_MONITORED_OBJECT_ACTION_TYPE_ENTER) {
            NSLog(@"被监控对象 %@ 进入 客户端地理围栏 %@ ", content.monitoredObject, content.fenceName);
        } else if (content.actionType == BTK_FENCE_MONITORED_OBJECT_ACTION_TYPE_EXIT) {
            NSLog(@"被监控对象 %@ 离开 客户端地理围栏 %@ ", content.monitoredObject, content.fenceName);
        }
    }
}

偏好设置

鹰眼iOS SDK支持开发者对SDK的某些行为偏好进行自定义设置。主要分为定位选项设置、采集周期与上传周期设置、缓存容量设置3个方面。


定位选项设置

鹰眼iOS SDK 使用iOS系统定位服务(CoreLocation),开发者可以通过 BTKAction 类中

-(void)setLocationAttributeWithActivityType:(CLActivityType)activityType 
                            desiredAccuracy:(CLLocationAccuracy)desiredAccuracy 
                            distanceFilter:(CLLocationDistance)distanceFilter;

方法设置CoreLocation中定位的活动类型、定位精度以及触发定位的距离阈值。该方法中的3个参数,将会透传给CoreLocation的LocationManager,3个参数的值请参考苹果官方文档中的用法,根据开发者自己的使用场景进行设置。


采集周期与上传周期设置

鹰眼iOS SDK默认的采集周期为5秒,上传周期为30秒,即SDK会每隔5秒采集一次当前设备的位置信息,每隔30秒将该周期内的若干个位置信息打包、压缩、加密后传输至鹰眼服务端。

SDK支持开发者通过 BTKAction 类中的 -(void)changeGatherAndPackIntervals:(NSUInteger)gatherInterval packInterval:(NSUInteger)packInterval delegate:(id <BTKTraceDelegate>)delegate; 方法自定义采集周期和上传周期。需要注意的是,该值一经设置,永久有效。即使是重新开启轨迹服务,也会按照上一次设置过的采集周期和上传周期进行采集和上传,SDK默认的采集周期和上传周期不再有效。如果想更改周期,再次调用此方法即可。该方法在开启轨迹服务之前和之后都可以调用。

采集周期一定是准确的,而上传周期只在网络畅通的时候才有意义。网络畅通的情况下,按照5秒采集30秒上传的默认值,查询历史轨迹时会发现,各轨迹点的loctime间隔为采集周期,而每6个连续的轨迹点的create_time是相同的,代表这6个点是同时上传至服务端的。而当断网时,SDK会自动缓存轨迹,等网络恢复后再批量上传至服务端。这种情况下,当查询历史轨迹时会发现,各轨迹点的loctime间隔仍然为采集周期,但create_time则没有什么规律了。但SDK会保证同一个entity终端的轨迹点,loctime较晚的点不会在loctime较早的点之前上传,以确保正确触发服务端地理围栏报警。

以下代码片段表示,将SDK默认的5秒采集一次位置信息、30秒上传一次位置信息,改为自定义的2秒采集一次位置信息、10秒上传一次位置信息。直到下次调用此方法,重新自定义采集周期和上传周期,SDK将一直使用本次自定义的值,即使停止轨迹服务后,重新开启轨迹服务也不受影响。

[[BTKAction sharedInstance] changeGatherAndPackIntervals:2 packInterval:10 delegate:self];


缓存容量设置

鹰眼iOS SDK支持开发者自定义SDK缓存所占磁盘空间的最大值。开发者可以通过 BTKAction 类的 -(void)setCacheMaxSize:(NSUInteger)size delegate:(id <BTKTraceDelegate>)delegate; 方法进行设置。关于此方法的行为有以下几点说明:

如果设置了该阈值,当SDK缓存的数据占用磁盘超过该阈值时,将删除最早的缓存轨迹,直到满足该条件。 SDK默认情况下缓存占用空间没有限制。如果对于缓存占用空间没有非常强烈的要求,建议不要调用此方法。否则将会导致缓存轨迹数据被丢弃等情况,且数据无法找回。 阈值一经设置,永久有效。如果想取消限制,只有再次设置一个非常大的值才行。 以下代码片段表示,设置客户端缓存占用磁盘空间的最大值为60MB,缓存轨迹数据占用超过60MB时将清空最早的缓存轨迹,直到占用空间满足要求。

[[BTKAction sharedInstance] setCacheMaxSize:60 delegate:self];

自行上传轨迹点

鹰眼iOS SDK除了可以自动采集并上传轨迹信息外,还支持开发者通过调用接口主动上传轨迹点。比如在两个采集周期之间,上传某个轨迹点作为补充;或者上传非当前登陆的entity的其他终端的轨迹点等使用场景。自定义轨迹点有如下几点说明:


自行上传的轨迹点除了可以上传位置坐标以及速度、方向、海拔高度、定位精度等系统预定义的track属性字段外,同样支持开发者自定义的track属性字段的上传,类似通过 BTKTraceDelegate 协议中的 -(NSDictionary *)onGetCustomData; 方法设置每个轨迹点的附加数据。


自行上传的轨迹点和 鹰眼SDK主动采集上传的轨迹点存在以下异同:

① 自行上传的轨迹点上传至服务端之后,和SDK自动采集上传的点没有区别。都会触发服务端地理围栏的计算、查询实时位置和轨迹时,也会返回。但自行上传的轨迹点的上传不会参与客户端地理围栏的计算,也就不会触发客户端地理围栏报警。

② 自行上传的轨迹点的上传依赖网络连通,仅在网络连通时可上传成功,鹰眼SDK 也不会缓存上传失败的轨迹点。


上传某个entity的单个轨迹点

通过 -(void)addCustomPointWith:(BTKAddCustomTrackPointRequest *)request delegate:(id<BTKTrackDelegate>)delegate; 方法,上传单个的自定义轨迹点;

以下代码片段表示,100000这个service下,已经通过轨迹管理台创建了3个叫做”someIntColumn”、 “someDoubleColumn”、 “someStringColumn” 的自定义track属性。名称为 “entityA” 的终端,在10秒钟之前,出现在了东经111.123456度,北纬44.654321度的位置,当时它的 “someIntColumn”属性的值为50,someDoubleColumn属性的值为44.55,someStringColumn属性的值为“aaa”,且方向为北偏东11度,海拔高度58米,定位精度为3米,速度为68米/秒。

// 设置轨迹点的坐标
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(44.654321, 111.123456);
// 设置轨迹点的定位时间戳
NSUInteger timestamp = [[NSDate date] timeIntervalSince1970] - 10;
// 设置轨迹点的自定义track属性字段的值
NSMutableDictionary *customData = [NSMutableDictionary dictionaryWithCapacity:3];
customData[@"someIntColumn"] = @50;
customData[@"someDoubleColumn"] = @44.55;
customData[@"someStringColumn"] = @"aaa";
// 构造自定义轨迹点
BTKCustomTrackPoint *point = [[BTKCustomTrackPoint alloc] initWithCoordinate:coordinate coordType:BTK_COORDTYPE_BD09LL loctime:timestamp direction:11 height:58 radius:3 speed:68 customData:customData entityName:@"entityA"];
// 使用自定义轨迹点构造请求对象
BTKAddCustomTrackPointRequest *request = [[BTKAddCustomTrackPointRequest alloc] initWithCustomTrackPoint:point serviceID:100000 tag:444];
// 发起上传请求
[[BTKTrackAction sharedInstance] addCustomPointWith:request delegate:self];


批量上传多个entity的多个轨迹点

通过 -(void)batchAddCustomPointWith:(BTKBatchAddCustomTrackPointRequest *)request delegate:(id<BTKTrackDelegate>)delegate; 方法,批量上传若干个开发者自定义轨迹点,这些轨迹点可以属于不同的终端实体。

以下代码片段表示,一次性上传2个自定义轨迹点,分别属于entityA和entityB,每个轨迹点的设置与上面上传单个轨迹点时相同。


// 第一个轨迹点point1
CLLocationCoordinate2D coordinate1 = CLLocationCoordinate2DMake(44.44, 111.11);
NSUInteger timestamp1 = [[NSDate date] timeIntervalSince1970] - 10;
NSMutableDictionary *customData1 = [NSMutableDictionary dictionaryWithCapacity:3];
customData1[@"someIntColumn"] = @50;
customData1[@"someDoubleColumn"] = @44.55;
customData1[@"someStringColumn"] = @"aaa";
BTKCustomTrackPoint *point1 = [[BTKCustomTrackPoint alloc] initWithCoordinate:coordinate1 coordType:BTK_COORDTYPE_BD09LL loctime:timestamp1 direction:11 height:11 radius:11 speed:11 customData:customData1 entityName:@"entityA"];
// 第二个轨迹点point2
CLLocationCoordinate2D coordinate2 = CLLocationCoordinate2DMake(55.55, 122.22);
NSUInteger timestamp2 = [[NSDate date] timeIntervalSince1970] - 20;
NSMutableDictionary *customData2 = [NSMutableDictionary dictionaryWithCapacity:3];
customData2[@"someIntColumn"] = @50;
customData2[@"someDoubleColumn"] = @44.55;
customData2[@"someStringColumn"] = @"aaa";
BTKCustomTrackPoint *point2 = [[BTKCustomTrackPoint alloc] initWithCoordinate:coordinate2 coordType:BTK_COORDTYPE_BD09LL loctime:timestamp2 direction:22 height:22 radius:22 speed:22 customData:customData2 entityName:@"entityB"];
// 构造需要上传的多个轨迹点
NSMutableArray *points = [NSMutableArray arrayWithCapacity:2];
[points addObject:point1];
[points addObject:point2];
// 构造请求对象
BTKBatchAddCustomTrackPointRequest *request = [[BTKBatchAddCustomTrackPointRequest alloc] initWithCustomTrackPoints:points serviceID:100000 tag:444];
// 发起请求
[[BTKTrackAction sharedInstance] batchAddCustomPointWith:request delegate:self];