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.
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:
If you want to play with this, check out the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/geoalert.
Archived Comments
Hey, thanks for sharing,
have you tried using watchPosition instead of getCurrentPosition ? It looks to me more accurate to use in that case, no ?
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.
Bonus points: Get this to work when your app is in the background...
Great write up, thanks!
does this really work when app is not active? please confirm .... ! if yes..... this article and you made my day......
I know there is a plugin to do stuff in the background. I'd look there first.
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
:)
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.
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)
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.
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
Remember you can use the Connection plugin to see if they are connected and estimate how well they are connected.
Hi, could you share what is that plugin you found? thanks!
Hi,there is a method to detect the gps status activation?Like a listener?Thanks :)
You mean being enabled/disabled?
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..
So I googled for 'cordova gps enabled' and this first result looks good to me: http://stackoverflow.com/qu...
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... :/
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.
thank you sir :)
at the end i decide to check if the gps get an error on .error function, and callback this to disable the action. :)
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!
I'll try to make a demo of this today since multiple people want it.
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. :)
Thanks - would love some BBQ down there. :)
i used watchPosition instead of getCurrentPosition..
It may be this one:
https://github.com/cowbell/...
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.
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.
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
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.
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
The Geolocation result includes heading - not just location. Did yall try that?
Raymond, can i use it for Unity? IOs and Android?
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.
hi raymond will you please give tutorial and sample project on ionic user tracking application please....
Sorry what?
it this work on background app in ios and android ?
There is a plugin that supports doing stuff in the background. You may need to use that.
there have no any proper plugin for background app can you suggest me background mode plugin
I'm not sure what you are saying here.
can you suggest me a background mode plugin for ios in ionic
While I'm aware of a background plugin, I haven't had a chance to use it myself.
Thank you for helping me out about this query.!
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.?
I shared the GitHub URL - you can access the full code there.
Alright! Can this be used in ionic 2?
You would need to modify it. You couldn't just copy and paste.
If the source code is directly copied from Github to ionic, Will I get an fully functional app sir?
No - you would need to generate an Ionic 1 app and then copy in my www folder and any plugins I mentioned above.
Hi, is there some updates for Ionic 3 ?
Can you be more specific?
Hi Raymond, reading below on thread i seen that this your code need modifications for Ionic 3, is it true ?
This code was for Ionic 1, yes. It's over two years old. The general principles would still work of course.
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?
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.
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?
I don't believe you can get that precise with commercial GPS - that level is just for military as far as I know.
Thanks, any guess then how close can one get then ?200 meters?500 meters?
Not sure - best I can suggest is Googling.
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
Not sure what to suggest. Honestly I don't think it is meant for *super* precise locations.
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.
Well one simple way would be to make the hb function simply do nothing if the time isn't right.