Контекстно-зависимые приложения и сложная архитектура обработки событий
Опубликовано: 2022-03-11Использование мобильных телефонов во всем мире постоянно растет. По состоянию на 2013 год около 73% пользователей Интернета потребляли контент через мобильные устройства, и ожидается, что к 2017 году этот процент достигнет почти 90%.
Есть, конечно, много причин для мобильной революции. Но одним из наиболее важных является то, что мобильные приложения обычно получают доступ к более богатому контексту, поскольку сегодня почти все смартфоны оснащены датчиками местоположения, датчиками движения, Bluetooth и Wi-Fi. Используя свои данные, приложения могут достичь «контекстной осведомленности», что может значительно увеличить их возможности и ценность и действительно выделить их в магазинах приложений.
В этом руководстве мы рассмотрим создание контекстно-зависимых приложений на примере обработки сложных событий. Мы будем использовать довольно простой пример: приложение для определения цен на топливо, которое находит лучшие цены на топливо в вашем районе.
Контекстно-зависимые приложения
В книге «Проектирование технологии спокойствия» Марк Вайзер и Джон Сили Браун описывают спокойную технологию как «то, что информирует, но не требует нашего сосредоточения или внимания».
Контекстно-зависимые мобильные приложения полностью соответствуют этому понятию и являются важным и ценным шагом на этом пути. Они используют контекстную информацию, полученную от их сенсоров, чтобы активно предоставлять пользователю ценную информацию, и они делают это с минимальными усилиями со стороны пользователя. Марк Вайзер и Джон Сили Браун, несомненно, приветствовали бы этот технологический прогресс.
Контекстная осведомленность — это идея, согласно которой приложение может воспринимать и реагировать на основе контекстных данных, к которым у него есть доступ. Такое приложение использует обширные данные датчиков, доступные на мобильном устройстве, чтобы предоставить пользователю точную и актуальную информацию в соответствующем контексте. Благодаря тенденциям, которые оно отслеживает в ходе использования устройства, и/или благодаря обратной связи, предоставляемой пользователем, такое приложение может фактически «учиться» с течением времени, тем самым становясь «умнее» и более полезным.
Комплексная обработка событий
Сложная обработка событий (CEP) — это форма обработки событий, которая использует более сложный анализ нескольких событий (т. е. во времени, из разных источников и т. д.), интегрируя и анализируя их содержимое для получения более значимой информации и закономерностей.
В мобильном приложении CEP может применяться к событиям, генерируемым датчиками мобильного устройства, а также внешними источниками данных, к которым у приложения есть доступ.
Ключевые особенности нашего приложения для цен на топливо
Для целей нашего руководства по обработке сложных событий давайте предположим, что функции нашего приложения цен на топливо ограничены следующим:
- автоматическое обнаружение местоположений, которые имеют географическое значение для пользователя (например, домашнее местоположение пользователя и рабочее местоположение пользователя)
- автоматическое определение заправочных станций на разумном расстоянии от дома и работы пользователя
- автоматическое уведомление пользователя о лучших ценах на топливо рядом с домом и работой
Хорошо, давайте начнем.
Определение домашнего и рабочего местоположения пользователя
Начнем с логики автоматического определения домашнего и рабочего местоположения пользователя. Для простоты нашего сложного примера обработки событий мы предположим, что у пользователя довольно обычный график работы. Таким образом, мы можем предположить, что пользователь обычно будет дома между 2 и 3 часами ночи и, как правило, будет в своем офисе между 14 и 15 часами.
Основываясь на этих предположениях, мы определяем два правила CEP и собираем данные о местоположении и времени со смартфона пользователя:
- Правило домашнего местоположения
- собирать данные о местоположении между 2 и 3 часами ночи в течение недели
- кластеризовать данные о местоположении, чтобы получить приблизительный домашний адрес
- Правило рабочего места
- собирать данные о местоположении между 14:00 и 15:00 в будние дни
- кластеризовать данные о местоположении, чтобы получить приблизительное место работы
Алгоритм высокого уровня для определения местоположения изображен ниже.
Давайте предположим следующую простую структуру данных JSON для данных о местоположении:
{ "uid": "some unique identifier for device/user", "location": [longitude, latitude] "time": "time in user's timezone" }
Примечание. Рекомендуется всегда делать данные датчика неизменяемыми (или типом значения), чтобы их можно было безопасно использовать в различных модулях рабочего процесса CEP.
Реализация
Мы реализуем наш алгоритм с использованием шаблона компонуемого модуля , при котором каждый модуль выполняет только одну задачу и вызывает следующую, когда задача завершена. Это соответствует философии Unix Rule of Modularity.
В частности, каждый модуль представляет собой функцию, которая принимает объект 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-данных. Вот один простой способ кластеризации данных о местоположении:
- найти соседей для каждой локации в наборе локаций
- если некоторые из соседей принадлежат существующему кластеру, то расширяем соседей с помощью cluster
- если местоположения в наборе соседей больше порогового значения, добавить соседей в качестве нового кластера
Вот реализация этого алгоритма кластеризации (с использованием 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);
Теперь всякий раз, когда со смартфона поступают данные о местоположении (через веб-сокет, 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 часов ночи).
Таким образом, наше топливное приложение может автоматически определять рабочее и домашнее местоположение пользователя, не требуя вмешательства пользователя. Это контекстно-зависимые вычисления в лучшем виде!
Поиск ближайших заправочных станций
Тяжелая работа по установлению контекстной осведомленности уже проделана, но нам все еще нужно еще одно правило, чтобы определить, какие цены на заправочных станциях следует отслеживать (т. е. какие заправочные станции находятся достаточно близко к дому или месту работы пользователя, чтобы быть релевантными). Для этого правила требуется доступ ко всем местоположениям заправочных станций для всех регионов, поддерживаемых топливным приложением. Правило следующее:
- Правило АЗС
- найти ближайшие заправочные станции для каждого дома и места работы
Это можно легко реализовать с помощью показанной ранее функции расстояния в качестве фильтра местоположения для применения ко всем заправочным станциям, известным приложению.
Мониторинг цен на топливо
Как только топливное приложение получает список предпочтительных (т. е. ближайших) заправочных станций для пользователя, оно может легко отслеживать лучшие цены на топливо на этих станциях. Он также может уведомлять пользователя, когда на одной из этих заправочных станций есть специальные цены или предложения, особенно когда обнаруживается, что пользователь находится рядом с этими заправочными станциями.
Заключение
В этом руководстве по обработке сложных событий мы едва коснулись контекстно-зависимых вычислений.
В нашем простом примере мы добавили контекст местоположения в простое приложение для составления отчетов о ценах на топливо и сделали его умнее. Приложение теперь ведет себя по-разному на каждом устройстве и со временем определяет шаблоны местоположения, чтобы автоматически повышать ценность информации, которую оно предоставляет своим пользователям.
Конечно, можно добавить гораздо больше логических данных и данных датчиков, чтобы повысить точность и полезность нашего контекстно-зависимого приложения. Умный разработчик мобильных приложений мог бы, например, использовать данные социальных сетей, данные о погоде, данные о транзакциях POS-терминалов и т. д., чтобы сделать наше приложение еще более контекстно-зависимым и сделать его более жизнеспособным и востребованным на рынке.
Возможности контекстно-зависимых вычислений безграничны. В магазинах приложений будет появляться все больше и больше интеллектуальных приложений, которые используют эту мощную технологию, чтобы сделать нашу жизнь проще.