Използването на мобилни телефони по целия свят непрекъснато се увеличава. Към 2013 г. около 73% от потребителите на интернет консумират съдържание чрез мобилно устройство и се очаква този процент да достигне близо до 90% до 2017г.
Има, разбира се, много причини за мобилната революция. Но едно от най-значимите е, че мобилните приложения обикновено получават достъп до по-богат контекст, тъй като почти всички смартфони днес са оборудвани със сензори за местоположение, сензори за движение, bluetooth и wifi. Използвайки техните данни, приложенията могат да постигнат „осъзнаване на контекста“, което може драстично да увеличи техните възможности и стойност и наистина може да ги накара да се откроят в магазините за приложения.
В този урок ще изследваме създаването на контекстно ориентирани приложения чрез сложен пример за обработка на събития. Ще използваме доста прост пример: приложение за цени на горивата, което намира най-добрите цени на горивата във вашия район.
В Проектиране на спокойна технология , Марк Вайзер и Джон Сийли Браун описва спокойна технология като „това, което информира, но не изисква нашия фокус или внимание.“
Контекстните мобилни приложения са напълно съвместими с тази идея и са важна и ценна стъпка по този път. Те използват контекстна информация, извлечена от техните сензори, за да предоставят активно на потребителя ценна информация и го правят с минимални усилия от страна на потребителя. Марк Вайзер и Джон Сийли Браун несъмнено биха аплодирали този технологичен напредък.
Контекстното осъзнаване е идеята, че приложението може да усети и реагира въз основа на контекстните данни, до които има достъп. Такова приложение използва богати данни от сензори, които са достъпни на мобилно устройство, за да предоставят точна и подходяща информация на потребителя в подходящия контекст. Чрез тенденции, които то наблюдава по време на използването на устройството, и / или чрез обратна връзка, предоставена от потребителя, такова приложение всъщност може да се „учи“ с времето, като по този начин става „по-умно“ и по-полезно.
Сложна обработка на събития (CEP) е форма на обработка на събития, която използва по-сложни анализи на множество събития (т.е. с течение на времето, от различни източници и т.н.), интегрирайки и анализирайки тяхното съдържание, за да изведе по-значима информация и модели.
В мобилно приложение CEP може да се прилага към събития, генерирани от сензорите на мобилното устройство, както и външни източници на данни, до които приложението има достъп.
За целите на нашия сложен урок за обработка на събития, нека приемем, че характеристиките на нашето приложение за цени на горивата са ограничени до следното:
Добре, нека започнем.
Нека започнем с логиката за автоматично откриване на домашното и работното местоположение на потребителя. За да улесним нещата за нашия сложен пример за обработка на събития, ще приемем, че потребителят има доста нормален работен график. Следователно можем да предположим, че потребителят обикновено е вкъщи между 2 и 3 ч. И обикновено е в офиса си между 14 и 15 ч.
Въз основа на тези предположения ние дефинираме две правила на CEP и събираме данни за местоположението и времето от смартфона на потребителя:
Алгоритъмът на високо ниво за откриване на местоположения е изобразен по-долу.
Нека приемем следната проста структура на JSON данни за данни за местоположението:
{ 'uid': 'some unique identifier for device/user', 'location': [longitude, latitude] 'time': 'time in user's timezone' }
Забележка: Винаги е добра практика да направите данните от сензора неизменни (или тип стойност), така че те да могат безопасно да се използват от различни модули в работния процес на CEP.
Ще внедрим нашия алгоритъм, използвайки a сглобяем модулен шаблон , при което всеки модул изпълнява само една задача и извиква следващата, когато задачата е завършена. Това отговаря на Unix Правило за модулност философия.
По-конкретно, всеки модул е функция, която приема config
обект и next
функция, която се извиква за предаване на данните към следващия модул. Съответно, всеки модул връща функция, която може да приема данни от сензора. Ето основния подпис на модул:
// 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); } }
За да приложим нашия алгоритъм за извеждане на местоположението на дома и работата на потребителя, ще са ни необходими следните модули:
Всеки от тези модули е описан по-подробно в следващите подраздели.
Нашият филтър за време е проста функция, която приема събития с данни за местоположението като вход и предава данни само на next
модул, ако събитието се е случило в рамките на интересуващия отрязък от време. config
данните за този модул следователно се състоят от началното и крайното време на интересуващия отрязък от време. (По-сложната версия на модула може да филтрира въз основа на множество времеви срезове.)
Ето изпълнение на псевдокод на модула за филтриране на времето:
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); } }; }
Отговорността на акумулатора е просто да събира данни за местоположението, които след това да се предават на next
модул. Тази функция поддържа вътрешен сегмент с фиксиран размер за съхраняване на данни. Всяко ново срещано местоположение се добавя към кофата, докато кофата се напълни. След това натрупаните данни за местоположението в кофата се изпращат до следващия модул като масив.
Поддържат се два типа кофи за акумулатори. Типът кофа влияе върху това, което се прави със съдържанието на кофата, след като данните се препратят към следващата фаза, както следва:
Преместване на кофата на прозореца (type = 'tumbling'
): след препращане на данни, изпразва цялата кофа и започва ново (намален размер на кофата обратно на 0)
Тип на работещ прозорец (type = 'running'
): след препращане на данни само изхвърля най-стария елемент от данни в групата (намалява размера на групата с 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()
функция, която изчислява разстоянието (в метри) между две географски местоположения. Приема две точки за местоположение под формата на [longitude, latitude]
и връща разстоянието между тях. Ето примерна реализация на такава функция:
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]; }
Подобен подход може да се използва за изчисляване на местоположението на работата, с единствената разлика, че той ще използва времеви филтър между 14 и 15 ч. (За разлика от 2 и 3 ч.).
По този начин нашето приложение за гориво е в състояние да автоматично откриване на местоположението на работата и дома на потребителя, без да се изисква намеса на потребителя. Това е контекстуално изчислително изчисление в най-доброто!
Усилената работа за установяване на осведоменост за контекста вече е свършена, но все още се нуждаем от още едно правило, за да определим кои цени на бензиностанциите да наблюдаваме (т.е. кои бензиностанции са достатъчно близо до дома или работното място на потребителя, за да бъдат подходящи) Това правило се нуждае от достъп до всички местоположения на бензиностанциите за всички региони, поддържани от приложението за гориво. Правилото е следното:
Това може лесно да бъде приложено с помощта на функцията за разстояние, показана по-рано като филтър за местоположение, за да се приложи към всички бензиностанции, известни на приложението.
След като приложението за гориво получи списък с предпочитани (т.е. близки) бензиностанции за потребителя, то може лесно да следи за най-добрите цени на горивата в тези станции. Той може също така да уведомява потребителя, когато една от тези бензиностанции има специални цени или оферти, особено когато потребителят е открит, че е близо до тези бензиностанции.
В този сложен урок за обработка на събития ние наистина едва надраскахме повърхността на контекстно-ориентираните изчисления.
В нашия прост пример добавихме контекст на местоположението към иначе просто приложение за отчитане на цените на горивата и го направихме по-интелигентен. Сега приложението се държи по различен начин на всяко устройство и с течение на времето открива моделите на местоположението, за да подобри автоматично стойността на информацията, която предоставя на своите потребители.
Със сигурност могат да се добавят много повече логика и данни от сензора, за да се увеличи точността и полезността на нашето контекстно ориентирано приложение. A умен мобилен разработчик например може да използва данни от социалната мрежа, метеорологични данни, данни за транзакции на POS терминали и т.н., за да добави още повече контекстна осведоменост към нашето приложение и направете го по-жизнеспособен и търгуем .
С изчислителния контекст възможностите са безкрайни. Все повече интелигентни приложения ще продължат да се появяват в магазините за приложения, които използват тази мощна технология, за да направят живота ни по-опростен.