本文概述
全球移动电话的使用正在不断增加。截至2013年, 大约73%的互联网用户通过移动设备消费了内容, 到2017年, 这一比例预计将接近90%。
当然, 移动革命有很多原因。但最重要的一点是, 由于当今几乎所有智能手机都配备了位置传感器, 运动传感器, 蓝牙和wifi, 因此移动应用程序通常可以访问更丰富的上下文。通过利用他们的数据, 应用程序可以实现”上下文感知”, 从而极大地提高其功能和价值, 并真正使它们在应用程序商店中脱颖而出。
在本教程中, 我们将通过一个复杂的事件处理示例探索创建上下文相关的应用程序。我们将使用一个非常简单的示例:一个燃油价格应用程序, 它可以找到你所在地区的最佳燃油价格。
情境感知应用
马克·威瑟(Mark Weiser)和约翰·西里·布朗(John Seely Brown)在设计冷静技术时, 将冷静技术描述为”能够提供信息但不需要我们关注或关注的技术。”
上下文感知的移动应用程序与此概念高度一致, 并且是沿着这条道路迈出的重要而宝贵的一步。他们使用从其传感器收集的上下文信息来主动为用户提供有价值的信息, 并且这样做使用户方面的工作量最小。马克·韦瑟(Mark Weiser)和约翰·西里·布朗(John Seely Brown)无疑会为这项技术进步表示赞赏。
上下文感知是应用程序可以根据其有权访问的上下文数据进行感知和响应的想法。这样的应用程序利用在移动设备上可用的丰富传感器数据在适当的上下文中向用户提供准确和相关的信息。通过它在设备使用过程中观察到的趋势, 和/或通过用户提供的反馈, 这样的应用实际上可以随着时间的推移”学习”, 从而变得”更智能”和更有用。
复杂事件处理
复杂事件处理(CEP)是事件处理的一种形式, 它对多个事件进行了更复杂的分析(即随着时间的推移, 来自不同的来源等等), 集成并分析了它们的内容以推断出更有意义的信息和模式。
在移动应用程序中, CEP可以应用于从移动设备的传感器以及该应用程序可以访问的外部数据源生成的事件。
我们的燃油价格应用程序的主要功能
出于我们复杂事件处理教程的目的, 我们假设燃油价格应用程序的功能仅限于以下各项:
- 自动检测与用户在地理位置上相关的位置(例如, 用户的住所和工作地点)
- 自动识别用户住所和工作地点合理距离内的加油站
- 自动通知用户家附近和工作地点附近的最佳燃油价格
好的, 让我们开始吧。
检测用户的家庭和工作地点
让我们从自动检测用户住所和工作地点的逻辑开始。为了简化我们复杂的事件处理示例的工作, 我们假设用户的工作时间表相当正常。因此, 我们可以假设用户通常在凌晨2点至3点之间在家里, 并且通常在下午2点至3点之间在他们的办公室里。
基于这些假设, 我们定义了两个CEP规则, 并从用户的智能手机收集位置和时间数据:
- 家庭位置规则
- 每周2到3 AM之间收集位置数据
- 聚类位置数据以获得大概的家庭住址
- 工作地点规则
- 收集工作日下午2点至3点之间的位置数据
- 聚类位置数据以获得大概的工作位置
下面描述了用于检测位置的高级算法。
让我们假设以下简单的JSON数据结构用于位置数据:
{
"uid": "some unique identifier for device/user", "location": [longitude, latitude]
"time": "time in user's timezone"
}
注意:始终使传感器数据不可变(或值类型)是一个好习惯, 以便CEP工作流程中的不同模块可以安全地使用它。
实例
我们将使用可组合的模块模式实现算法, 其中每个模块仅执行一个任务, 并在任务完成时调用下一个。这符合Unix模块化原则。
具体来说, 每个模块都是一个接受配置对象的函数和一个将数据传递给下一个模块的下一个函数。因此, 每个模块返回可以接受传感器数据的功能。这是模块的基本签名:
// nominal structure of each composable module
function someModule(config, next) {
// do initialization if required
return function(data) {
// do runtime processing, handle errors, etc.
var nextData = SomeFunction(data);
// optionally call next with nextData
next(nextData);
}
}
要实施我们的算法来推断用户的住所和工作地点, 我们需要以下模块:
- 时间过滤模块
- 蓄能器模块
- 集群模块
以下各小节将更详细地描述这些模块中的每一个。
时间过滤模块
我们的时间过滤器是一个简单的函数, 它将位置数据事件作为输入, 并且仅在事件发生在感兴趣的时间段内时才将数据传递到下一个模块。因此, 此模块的配置数据由感兴趣的时间片的开始和结束时间组成。 (该模块的更高级版本可以基于多个时间片进行过滤。)
这是时间过滤器模块的伪代码实现:
function timeFilter(config, next) {
function isWithin(timeval) {
// implementation to compare config.start <= timeval <= config.end
// return true if within time slice, false otherwise
}
return function (data) {
if(isWithin(data.time)) {
next(data);
}
};
}
蓄能器模块
累加器的职责只是收集位置数据, 然后将其传递到下一个模块。此功能维护一个内部固定大小的存储桶以存储数据。遇到的每个新位置都会添加到存储桶中, 直到存储桶已满。然后, 存储桶中的累积位置数据将作为数组发送到下一个模块。
支持两种类型的蓄能桶。在将数据转发到下一阶段之后, 存储桶类型会影响对存储桶内容的处理方式, 如下所示:
-
滚动窗口存储桶(类型=’滚动’):转发数据后, 清空整个存储桶并重新开始(将存储桶大小减小为0)
-
正在运行的窗口类型(类型=”正在运行”):转发数据后, 仅丢弃存储桶中最早的数据元素(将存储桶大小减小1)
这是累加器模块的基本实现:
function accumulate(config, next) {
var bucket = [];
return function (data) {
bucket.unshift(data);
if(bucket.length >= config.size) {
var newSize = (config.type === 'tumbling' ? 0 : bucket.length - 1);
next(bucket.slice(0));
bucket.length = newSize;
}
};
}
集群模块
当然, 在坐标几何中有许多复杂的技术可以将2D数据聚类。这是群集位置数据的一种简单方法:
- 在一组位置中为每个位置找到邻居
- 如果某些邻居属于现有群集, 则使用群集扩展邻居
- 如果邻居集中的位置超过阈值, 则将邻居添加为新集群
这是此聚类算法的实现(使用Lo-Dash):
var _ = require('lodash');
function createClusters(location_data, radius) {
var clusters = [];
var min_points = 5; // Minimum cluster size
function neighborOf(this_location, all_locations) {
return _.filter(all_locations, function(neighbor) {
var distance = distance(this_point.location, neighbor.location);
// maximum allowed distance between neighbors is 500 meters.
return distance && (500 > distance);
}
}
_.each(location_data, function (loc_point) {
// Find neighbors of loc_point
var neighbors = neighborOf(loc_point, location_data, radius);
_.each(clusters, function (cluster, index) {
// Check whether some of the neighbors belong to cluster.
if(_.intersection(cluster, neighbors).length){
// Expand neighbors
neighbors = _.union(cluster, neighbors);
// Remove existing cluster. We will add updated cluster later.
clusters[index] = void 0;
}
});
if(neighbors.length >= min_points){
// Add new cluster.
clusters.unshift(neighbors);
}
});
return _.filter(clusters, function(cluster){ return cluster !== void 0; });
}
上面的代码假定存在distance()函数, 该函数计算两个地理位置之间的距离(以米为单位)。它接受[经度, 纬度]形式的两个位置点, 并返回它们之间的距离。这是此功能的示例实现:
function distance(point1, point2) {
var EARTH_RADIUS = 6371000;
var lng1 = point1[0] * Math.PI / 180;
var lat1 = point1[1] * Math.PI / 180;
var lng2 = point2[0] * Math.PI / 180;
var lat2 = point2[1] * Math.PI / 180;
var dLat = lat2 - lat1;
var dLon = lng2 - lng1;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var arc = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var distance = EARTH_RADIUS * arc;
return distance;
}
定义并实现我们的集群算法(在前面显示的createClusters()函数中), 我们可以将其用作集群模块的基础:
function clusterize(config, next) {
return function(data) {
var clusters = createClusters(data, config.radius);
next(clusters);
};
}
一起拉
现在已定义了所有必需的组件功能, 因此我们准备编写家庭/工作位置规则。
例如, 以下是归属位置规则的可能实现:
var CLUSTER_RADIUS = 150; // use cluster radius of 150 meters
var BUCKET_SIZE = 500; // collect 500 location points
var BUCKET_TYPE = 'tumbling'; // use a tumbling bucket in our accumulator
var home_cluster = clusterize({radius: CLUSTER_RADIUS}, function(clusters) {
// Save clusters in db
});
var home_accumulator = accumulate({size: BUCKET_SIZE, type: BUCKET_TYPE}, home_cluster);
var home_rule = timeFilter({start: "2AM", end: "3AM"}, home_accumulator);
现在, 无论何时从智能手机接收到位置数据(通过websocket, TCP, HTTP), 我们都会将此数据转发到home_rule函数, 该函数依次检测用户房屋的集群。
然后, 假定用户的”家庭位置”是家庭位置群集的中心。
注意:虽然这可能并不完全精确, 但对于我们的简单示例而言已经足够了, 尤其是因为该应用程序的目标只是在任何情况下都只是知道用户房屋周围的区域, 而不是知道用户的确切房屋位置。
这是一个简单的示例函数, 它通过平均集群集中所有点的纬度和经度来计算集群中一组点的”中心”:
function getCentre(cluster_data) {
var len = cluster_data.length;
var sum = _.reduce(cluster_data, function(memo, cluster_point){
memo[0] += cluster_point[0];
memo[1] += cluster_point[1];
return memo;
}, [0, 0]);
return [sum[0] / len, sum[1] / len];
}
可以采用类似的方法来推导工作位置, 唯一的区别是它将使用2到3 PM之间的时间过滤器(与2到3 AM相反)。
因此, 我们的加油应用程序能够自动检测用户的工作和家庭位置, 而无需用户干预。最好的上下文感知计算!
寻找附近的加油站
建立上下文感知的艰苦工作现在已经完成, 但是我们仍然需要另外一条规则来识别要监视的加油站价格(即, 哪个加油站离用户的家或工作地点足够近以至于不相关)。该规则需要访问燃料应用支持的所有区域的所有加油站位置。规则如下:
- 加油站规则
- 查找每个家庭和工作地点最近的加油站
可以使用前面显示的距离功能作为位置过滤器轻松地实现此功能, 以将其应用于应用程序已知的所有加油站。
监控燃油价格
一旦加油站应用程序为用户获取了首选(即附近)加油站的列表, 它就可以轻松地在这些加油站寻找最佳燃油价格。当这些加油站之一具有特殊价格或优惠时, 它也可以通知用户, 尤其是当检测到用户在这些加油站附近时。
总结
在这个复杂的事件处理教程中, 我们几乎没有涉及到上下文感知计算的表面。
在我们的简单示例中, 我们将位置上下文添加到了本来简单的燃油价格报告应用程序中, 并使它变得更智能。该应用程序现在在每个设备上的行为都不同, 随着时间的流逝, 它们会检测到位置模式, 以自动提高其提供给用户的信息的价值。
当然, 可以添加更多的逻辑和传感器数据, 以提高上下文感知应用程序的准确性和实用性。例如, 一个聪明的移动开发人员可以利用社交网络数据, 天气数据, POS终端交易数据等来为我们的应用程序添加更多的上下文感知, 并使其更加可行和可销售。
使用上下文感知计算, 可能性是无限的。越来越多的智能应用程序将继续出现在应用商店中, 这些应用程序利用这项强大的技术来简化我们的生活。
评论前必须登录!
注册