Tracking and notifying geolocation status with Ionic

This post is more than 2 years old.

On Twitter, Snehal reached out to me with an interesting question. Given a location X, he wanted to track a user's location and know when they were within a certain distance to X. By itself, that's not really a difficult task. You need to:

  • Track the user's location - which is easy with geolocation and an interval.
  • Get the distance from the user's location to your target, which is also easy. (Ok, I lie. It's bat-shit crazy math but you can copy and paste a solution so let's call it easy.)
  • Tell the user when they are close - again easy.

What wasn't particularly easy for me to wrap my head around how to build this within Ionic, or specifically, within Angular. As I've said on multiple occasions now, I can write Angular, but I'm still struggling with how best to organize and coordinate various different aspects of my application. In this case, I was particularly confused by how I'd handle the interval process. I also needed something that would run all the time, not just for a particular view/controller.

I was stuck - but then I figured - if I know I'm probably going to do this wrong, let me just take a stab at it and let my smarter readers tell me what I did wrong. ;)

I began by creating a new Ionic application. I let it use the default template, Tabs, so I'd have a "real" app with multiple views in it. I then created a new service in services.js called GeoAlert. GeoAlert would have a simple API:

  • begin: This would initiate tracking and would be passed a target location and a callback to fire when the user is "close enough". I ended up hard coding what "close enough" was, but that could have been an argument as well. Ditto for how often it checked the location.
  • end: This simply stops the tracking.
  • setTarget: A method I built and abandoned, but I thought it made sense so I kept it in. This lets you change the target.

Here is my service:

.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;
     }
   };
   
})

So a few notes about this. Since Geolocation is async, I used a variable, processing, to detect when it was active so that my heartbeat function (hb) wouldn't request it again. I could have switched from setInterval to setTimeout as well. I'd simply call the setTimeout in the success function of the geolocation request. I'm not necessarily sure that makes a big deal, but it is something I'd possibly change in the future. As mentioned, the "how far away" logic is something I just cut and paste from a good StackOverflow answer. As I said above, both the interval and minimum distance for a match (10 kilometers) are hard coded, but could easily be arguments instead.

At this point, I've got a service that will basically run every N seconds and determine when I'm within X kilometers of a target. How do I use it?

I decided to inject the service in the run method of my main Angular module. I don't know why it felt weird to work there, but it did. Normally I use services in my controllers, but obviously you can use them here too. Heck, Ionic does this with the $ionicPlatform service. As for how to let the user know something happened, I had a few choices. I could have used an Ionic Modal or Popup, but they felt wrong to me. I don't have a good reason for why they felt wrong, but I decided to use the Dialogs plugin. Obviously that's a personal choice. My thinking was that the dialog would let the user know they are close to some target and give them the choice to view information about that target. For my demo though I just record what button was clicked and leave the implementation of that to the reader. Here's the code.

.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']
      );
      
    });
    
  });
})

Pretty simple I think. As a quick note, the button index passed to onConfirm is 1-based, which is good, but don't forget. For testing, I fired up my iOS Simulator. What you may not know is that it lets you change your location. This can be found under the Debug menu.

Screen Shot 2015-05-18 at 8.35.31 AM

Notice it has a "Custom Location" item. You can select this, enter a location, and it will remember it. I entered my values (which are the same as the static values in the code) and in the next iteration of the heart beat, it matched:

iOS Simulator Screen Shot May 18, 2015, 8.37.44 AM

If you want to play with this, check out the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/geoalert.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by JerryBels posted on 5/18/2015 at 4:58 PM

Hey, thanks for sharing,

have you tried using watchPosition instead of getCurrentPosition ? It looks to me more accurate to use in that case, no ?

Comment 2 (In reply to #1) by Raymond Camden posted on 5/18/2015 at 5:13 PM

Ah, interesting. So no, afaik, getCurrentPosition isn't more accurate. That's controlled by the options you can pass to either API call (enableHighAccuracy if I remember right), but using the watch call may mean less code as you aren't using the setInterval.

Comment 3 by Don posted on 7/1/2015 at 2:21 AM

Bonus points: Get this to work when your app is in the background...

Comment 4 by Daniel Saidi posted on 9/23/2015 at 10:04 AM

Great write up, thanks!

Comment 5 (In reply to #3) by Sumeet Darade posted on 10/7/2015 at 3:28 PM

does this really work when app is not active? please confirm .... ! if yes..... this article and you made my day......

Comment 6 (In reply to #5) by Raymond Camden posted on 10/7/2015 at 4:02 PM

I know there is a plugin to do stuff in the background. I'd look there first.

Comment 7 (In reply to #6) by Sumeet Darade posted on 10/12/2015 at 4:49 AM

ya... i came accross that one ..!! but never got any exampe/tut related to that one !! Since i am a newbie to angular... those docs are pretty much confusing for me ! i have too may doubts on that ! do you know any link to example/tut ? that would help me really !! thanks for your response

:)

Comment 8 (In reply to #0) by Raymond Camden posted on 1/29/2016 at 1:29 PM

You would modify the code to take a list of targets (as an array), and just check the current position against every item in the list.

Comment 9 by Diego Herrera posted on 2/24/2016 at 1:06 PM

Hello Raymond, Great work, thanks for the code, I have a question about the spend of mobile data in this app? is really viable use this app with mobile data?
Regards
Thanks
(Sorry for my english)

Comment 10 (In reply to #9) by Raymond Camden posted on 2/24/2016 at 3:00 PM

Is it viable depends on a lot of things. :) Where do most of your app users live and what is their network connectivity like? How important is this feature? Etc. You can also tweak how often it pings.

Comment 11 (In reply to #10) by Diego Herrera posted on 2/24/2016 at 3:22 PM

Thanks for the response Raymond, this feature is very important in the app, the users use network connectivity 4G mobile data. I need that users in my app can receive notifications when they are near to different sites in the street, accordingly, the app need execute the script all day

Comment 12 (In reply to #11) by Raymond Camden posted on 2/24/2016 at 3:24 PM

Remember you can use the Connection plugin to see if they are connected and estimate how well they are connected.

Comment 13 (In reply to #7) by JosePaez posted on 3/12/2016 at 6:31 PM

Hi, could you share what is that plugin you found? thanks!

Comment 14 by jonsey posted on 3/17/2016 at 8:25 AM

Hi,there is a method to detect the gps status activation?Like a listener?Thanks :)

Comment 15 (In reply to #14) by Raymond Camden posted on 3/17/2016 at 1:07 PM

You mean being enabled/disabled?

Comment 16 (In reply to #15) by jonsey posted on 3/17/2016 at 1:15 PM

yes...i want to disable some buttons if the status of gps is enabled or disabled on the iphone/android phone.So i suppouse i need to have a listener/watcher enabled..

Comment 17 (In reply to #16) by Raymond Camden posted on 3/17/2016 at 3:02 PM

So I googled for 'cordova gps enabled' and this first result looks good to me: http://stackoverflow.com/qu...

Comment 18 (In reply to #17) by jonsey posted on 3/17/2016 at 3:26 PM

thanks for the link, but i don't want to call a method to have an answer, i want a listener always "listening" for a change status of gps (the setting switch i mean!) .I don't want to make a polling to check periodically if is enabled or not... :/

Comment 19 (In reply to #18) by Raymond Camden posted on 3/17/2016 at 3:28 PM

Then you would need a plugin that check device settings. I'd try searching for that. I'm not aware of one, but it may exist.

Comment 20 (In reply to #19) by jonsey posted on 3/17/2016 at 4:02 PM

thank you sir :)

Comment 21 (In reply to #20) by jonsey posted on 3/22/2016 at 3:08 PM

at the end i decide to check if the gps get an error on .error function, and callback this to disable the action. :)

Comment 22 (In reply to #0) by Matthew Pill posted on 4/15/2016 at 12:34 PM

Hi Dhameer, did you manage to fins a solution for this? The function is running fine for me with one target, but having issues getting it to loop through a list of targets!

Comment 23 (In reply to #0) by Raymond Camden posted on 4/15/2016 at 1:11 PM

I'll try to make a demo of this today since multiple people want it.

Comment 24 (In reply to #0) by Raymond Camden posted on 4/15/2016 at 5:01 PM

Updated demo: https://github.com/cfjedima...

Ok, so the changes aren't too big, but it is important you understand them. The Service I built used to take 3 args: lat, long, and a callback. Now it takes an array and a callback, where the array is an array of targets. What a "target" means is a bit loose. In my demo, it is: lat, long, label. The label will be important in a bit.

I updated app.js to use an array:

var targetList = [];
//first target (Louisiana)
targetList.push({lat:30.224090, long: -92.019843, label:"Lafayette"});
//second target (Paris)
targetList.push({lat: 48.87146,long: 2.355, label:"Paris"});

This gets passed to the service:

GeoAlert.begin(targetList, function(matchedTargets) {

Notice my callback is expecting an argument, matchedTargets, that represents an array of 'close' targets. (It is possible you are close to 2+). I could have fired the callback for each close target, but this seemed to make more sense.

How you *use* this result is a bit undefined. You could print the labels so the user knows. Or you could include other data in the target like some ID value or something else.

But the meat of the work is done.

If this helps, please visit my Amazon Wishlist. :)

Comment 25 (In reply to #0) by Raymond Camden posted on 4/16/2016 at 1:26 PM

Thanks - would love some BBQ down there. :)

Comment 26 (In reply to #2) by sabareeshk posted on 5/24/2016 at 11:15 AM

i used watchPosition instead of getCurrentPosition..

Comment 27 (In reply to #13) by andpr posted on 6/3/2016 at 10:03 PM
Comment 28 by Gautam Menariya posted on 9/8/2016 at 8:22 PM

I have a project with following requirements

A. SHOULD ASSIGN A ROUTE FOR EMPLOYEE
B. SHOULD TRACK IF EMPLOYEE IS FOLLOWING THE SAME ROUTE AS SPECIFIED ELSE AN ALERT will be send to the admin

I can assign route to a employee. And if app is open, then i can also check that user follow same path. But if i close the app, i can do nothing.

So i want to know that after closing my app, is there any way that we can track same? because push notifications works after app is closed.

Comment 29 (In reply to #28) by Raymond Camden posted on 9/8/2016 at 9:37 PM

There is a plugin for background work, but I'm not sure if GPS works with it. As it drains the battery, I'd be surprised if it did.

Comment 30 by Caio Ramos posted on 10/11/2016 at 4:35 PM

Hi Raymond! hugs from Brasil!
I'm a very fan of ur work!
I want to know how i get the cellphone direction in relative of gps, i found in geolation plugin that i can get heading that is a direction of travel, but i just want take direction when i stop.
I try to do a app like planet finder but for find radio antennas that have static positions.

Nice work and thanks.

Srry for my english

Comment 31 (In reply to #30) by Raymond Camden posted on 10/11/2016 at 8:02 PM

Hmm - do you mean - you only want to check the location when the user isn't moving? Not sure you can do that. You can compare the current location to a previous one, and if it is the same, or very close, assume they are 'still', but your getting the location anyway.

Comment 32 (In reply to #31) by Vlad posted on 10/31/2016 at 8:44 AM

Hi Raymond,

I think what Caio Ramos what's to know is: When I find the location of a user, is it possible to know which direction they are facing? It seems he has found a plugin which will tell him which direction the user is travelling in, but if they are staying still (and hence not "travelling") this plugin will not work. Presumably the plugin takes two locations in quick succession to determine the direction of travel, which would explain why it won't work if the user is standing still.

I hope this helps clarify things,

Vlad

Comment 33 (In reply to #32) by Raymond Camden posted on 10/31/2016 at 1:11 PM

The Geolocation result includes heading - not just location. Did yall try that?

Comment 34 by Nabil posted on 11/25/2016 at 3:23 PM

Raymond, can i use it for Unity? IOs and Android?

Comment 35 (In reply to #34) by Raymond Camden posted on 11/25/2016 at 3:36 PM

This code is for a hybrid mobile app built with Ionic - it is not for Unity. I've never used Unity so I can't comment on it at all.

Comment 36 by SRAVANREDDY SUNKAM posted on 1/9/2017 at 4:42 PM

hi raymond will you please give tutorial and sample project on ionic user tracking application please....

Comment 37 (In reply to #36) by Raymond Camden posted on 1/9/2017 at 4:43 PM

Sorry what?

Comment 38 by Shivang Pokar posted on 1/18/2017 at 8:10 AM

it this work on background app in ios and android ?

Comment 39 (In reply to #38) by Raymond Camden posted on 1/18/2017 at 4:12 PM

There is a plugin that supports doing stuff in the background. You may need to use that.

Comment 40 (In reply to #39) by Shivang Pokar posted on 1/30/2017 at 10:50 AM

there have no any proper plugin for background app can you suggest me background mode plugin

Comment 41 (In reply to #40) by Raymond Camden posted on 1/30/2017 at 12:27 PM

I'm not sure what you are saying here.

Comment 42 (In reply to #41) by Shivang Pokar posted on 1/30/2017 at 12:30 PM

can you suggest me a background mode plugin for ios in ionic

Comment 43 (In reply to #42) by Raymond Camden posted on 1/30/2017 at 12:39 PM

While I'm aware of a background plugin, I haven't had a chance to use it myself.

Comment 44 (In reply to #43) by Shivang Pokar posted on 1/30/2017 at 12:44 PM

Thank you for helping me out about this query.!

Comment 45 by Alisha K posted on 5/22/2017 at 6:26 PM

Hello! I am working on the same app. For now.. Can you help me send the full access to the app, you've made? I mean I wanna use the same code and infuse it into another app base made by me. How to go about.?

Comment 46 (In reply to #45) by Raymond Camden posted on 5/22/2017 at 7:45 PM

I shared the GitHub URL - you can access the full code there.

Comment 47 (In reply to #46) by Alisha K posted on 5/22/2017 at 7:49 PM

Alright! Can this be used in ionic 2?

Comment 48 (In reply to #47) by Raymond Camden posted on 5/22/2017 at 7:57 PM

You would need to modify it. You couldn't just copy and paste.

Comment 49 (In reply to #48) by Alisha K posted on 5/22/2017 at 8:35 PM

If the source code is directly copied from Github to ionic, Will I get an fully functional app sir?

Comment 50 (In reply to #49) by Raymond Camden posted on 5/22/2017 at 9:06 PM

No - you would need to generate an Ionic 1 app and then copy in my www folder and any plugins I mentioned above.

Comment 51 by Mauro posted on 6/5/2017 at 12:45 PM

Hi, is there some updates for Ionic 3 ?

Comment 52 (In reply to #51) by Raymond Camden posted on 6/5/2017 at 12:55 PM

Can you be more specific?

Comment 53 (In reply to #52) by Mauro posted on 6/6/2017 at 12:41 PM

Hi Raymond, reading below on thread i seen that this your code need modifications for Ionic 3, is it true ?

Comment 54 (In reply to #53) by Raymond Camden posted on 6/6/2017 at 1:25 PM

This code was for Ionic 1, yes. It's over two years old. The general principles would still work of course.

Comment 55 by yushin kalambai posted on 7/6/2017 at 4:50 PM

hey raymond, i wanted to ask if you could help in providing the code for looping through co-ordinates in a database, unlike the hard code target you put up?

Comment 56 (In reply to #55) by Raymond Camden posted on 7/6/2017 at 5:23 PM

No, but in general the process would be:

setup the db and data (very dependent on what db system you have, NoSQL or not)

setup an app server to sit between the data and the web - something like Node, PHP, ColdFusion, etc, that can read the data and export JSON.

On the front end - use jQuery or regular old JS and just make an XHR request to it.

Comment 57 by snehalp77 posted on 8/9/2017 at 10:08 PM

Hi Raymond cannot thank you more for putting down this blog on my query, have one question though, how close can the tracking be done in meters using the same plugin? i read there is a enableHighaccuracy parameter, but unable to find out the accuracy in meters, can it track down to say upto 5 or 10 meters?

Comment 58 (In reply to #57) by Raymond Camden posted on 8/9/2017 at 10:30 PM

I don't believe you can get that precise with commercial GPS - that level is just for military as far as I know.

Comment 59 (In reply to #58) by snehalp77 posted on 8/10/2017 at 5:09 PM

Thanks, any guess then how close can one get then ?200 meters?500 meters?

Comment 60 (In reply to #59) by Raymond Camden posted on 8/10/2017 at 5:23 PM

Not sure - best I can suggest is Googling.

Comment 61 by Gurjit posted on 8/27/2017 at 4:32 PM

As we are using here, getCurrentGeolocation method which does not pic the exact lat,lng always I meant to say result with low accuracy e.g I am under fence of target but the picked lat lang are not accurate. How over come this problem? Already using enableHighAccuracy true but still fails some time and shows location far from target even when my device was on target position. Please suggest. Thanks

Comment 62 (In reply to #61) by Raymond Camden posted on 8/28/2017 at 1:06 PM

Not sure what to suggest. Honestly I don't think it is meant for *super* precise locations.

Comment 63 by Bikash Mahata posted on 4/9/2018 at 2:04 AM

Hey. Nice to read this. Does it possible to start tacking in between a specifed time frame. I.e i want to start it in between 9am to 6 pm.

Comment 64 (In reply to #63) by Raymond Camden posted on 4/9/2018 at 1:19 PM

Well one simple way would be to make the hb function simply do nothing if the time isn't right.