Статьи

Отслеживание и уведомление о статусе геолокации с помощью Ionic

В Твиттере Снехал обратился ко мне с интересным вопросом. Учитывая местоположение X, он хотел отслеживать местоположение пользователя и знать, когда он находился на определенном расстоянии от X. Само по себе это не очень сложная задача. Тебе нужно:

  • Отслеживайте местоположение пользователя — что легко с геолокации и интервалом.
  • Получить расстояние от местоположения пользователя до вашей цели, что также легко. (Хорошо, я лгу. Это сумасшедшая математика, но вы можете скопировать и вставить решение, так что давайте назовем это легко.)
  • Скажите пользователю, когда они рядом — опять легко.

Что  было не  особенно легко для меня, чтобы обернуть голову, как построить это в Ionic, или, в частности, в Angular. Как я уже неоднократно говорил, я могу писать на Angular, но я все еще борюсь с тем, как лучше организовать и скоординировать различные аспекты моего приложения. В этом случае меня особенно смутило то, как я буду обрабатывать интервальный процесс. Мне также нужно что-то, что будет работать  все  время, а не только для определенного представления / контроллера.

Я застрял — но потом подумал — если я знаю, что, возможно, я сделаю это неправильно, позвольте мне сделать удар, и пусть мои умные читатели скажут мне, что я сделал неправильно.

Я начал с создания нового приложения Ionic. Я позволил ему использовать шаблон по умолчанию Tabs, поэтому у меня было бы «настоящее» приложение с несколькими представлениями. Затем я создал новый сервис в services.js под названием GeoAlert. GeoAlert будет иметь простой API:

  • begin: Это инициировало бы отслеживание и передавало бы целевое местоположение и обратный вызов, чтобы выстрелить, когда пользователь «достаточно близко». В итоге я жестко запрограммировал, что такое «достаточно близко», но это тоже могло быть аргументом. То же самое, как часто он проверял местоположение.
  • конец: это просто останавливает отслеживание.
  • setTarget: метод, который я создал и отказался, но я подумал, что он имеет смысл, поэтому я оставил его. Это позволяет вам изменить цель.

Вот мой сервис:

.factory('GeoAlert', function() {
   console.log('GeoAlert service instantiated');
   var interval;
   var duration = 6000;
   var long, lat;
   var processing = false;
   var callback;
   var minDistance = 10;
    
   // Credit: http://stackoverflow.com/a/27943/52160   
   function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
    var R = 6371; // Radius of the earth in km
    var dLat = deg2rad(lat2-lat1);  // deg2rad below
    var dLon = deg2rad(lon2-lon1); 
    var a = 
      Math.sin(dLat/2) * Math.sin(dLat/2) +
      Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
      Math.sin(dLon/2) * Math.sin(dLon/2)
      ; 
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
    var d = R * c; // Distance in km
    return d;
   }
  
   function deg2rad(deg) {
    return deg * (Math.PI/180)
   }
   
   function hb() {
      console.log('hb running');
      if(processing) return;
      processing = true;
      navigator.geolocation.getCurrentPosition(function(position) {
        processing = false;
        console.log(lat, long);
        console.log(position.coords.latitude, position.coords.longitude);
        var dist = getDistanceFromLatLonInKm(lat, long, position.coords.latitude, position.coords.longitude);
        console.log("dist in km is "+dist);
        if(dist <= minDistance) callback();
      });
   }
   
   return {
     begin:function(lt,lg,cb) {
       long = lg;
       lat = lt;
       callback = cb;
       interval = window.setInterval(hb, duration);
       hb();
     }, 
     end: function() {
       window.clearInterval(interval);
     },
     setTarget: function(lg,lt) {
       long = lg;
       lat = lt;
     }
   };
   
})

Итак, несколько замечаний по этому поводу. Поскольку Geolocation является асинхронным, я использовал переменную processing, чтобы определить, когда она была активна, чтобы моя функция сердцебиения (hb) не запрашивала ее снова. Я мог бы также переключиться с setInterval на setTimeout. Я бы просто вызвал setTimeout в функции успеха запроса геолокации. Я не обязательно уверен, что это имеет большое значение, но это то, что я мог бы изменить в будущем. Как уже упоминалось, логику «как далеко» я просто вырезал и вставил из хорошего  ответа StackOverflow . Как я уже говорил выше, интервал и минимальное расстояние для матча (10 километров) жестко заданы, но вместо этого они могут быть аргументами.

На данный момент у меня есть служба, которая в основном будет запускаться каждые N секунд и определять, когда я нахожусь в пределах X километров от цели. Как мне это использовать?

Я решил ввести сервис по  run методу моего основного модуля Angular. Я не знаю, почему было странно работать там, но это было так. Обычно я использую сервисы в своих контроллерах, но, очевидно, вы можете использовать их и здесь. Черт, Ионик делает это с помощью сервиса $ ionicPlatform. Что касается того, как сообщить пользователю, что что-то произошло, у меня было несколько вариантов. Я мог бы использовать  Ionic Modal  или  Popup, но они чувствовали себя неправильно со мной. У меня нет веских причин, почему они чувствовали себя неправильно, но я решил использовать плагин Dialogs. Очевидно, это личный выбор. Я думал, что диалог позволит пользователю узнать, что он близок к некоторой цели, и даст ему возможность просмотреть информацию об этой цели. Для моей демонстрации я просто записываю, какая кнопка была нажата, и оставляю ее реализацию читателю. Вот код

.run(function($ionicPlatform, GeoAlert) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleLightContent();
    }
    
    //Begin the service
    //hard coded 'target'
    var lat = 30.224090;
    var long = -92.019843;
    function onConfirm(idx) {
      console.log('button '+idx+' pressed');
    }
    
    GeoAlert.begin(lat,long, function() {
      console.log('TARGET');
      GeoAlert.end();
      navigator.notification.confirm(
        'You are near a target!',
        onConfirm,
        'Target!',
        ['Cancel','View']
      );
      
    });
    
  });
})

Довольно просто, я думаю. В качестве краткого примечания, индекс кнопки, передаваемый onConfirm, основан на 1, что хорошо, но не забывайте. Для тестирования я запустил свой iOS Simulator. То, что вы можете не знать, это то, что он позволяет вам изменить свое местоположение Это можно найти в меню «Отладка».

Снимок экрана 2015-05-18 в 8.35.31 утра

Обратите внимание, что у него есть пункт «Пользовательское местоположение». Вы можете выбрать это, ввести местоположение, и оно запомнит это. Я ввел свои значения (которые совпадают со статическими значениями в коде), и на следующей итерации сердечного ритма это совпадало:

Снимок экрана iOS-симулятора 18 мая 2015 г., 8.37.44

Если вы хотите поиграть с этим, ознакомьтесь с полным исходным кодом здесь:  https://github.com/cfjedimaster/Cordova-Examples/tree/master/geoalert .