Using watchPosition to get accurate Geolocation in a Worklight App
DavidDhuyvetter 10000034CQ Visits (12415)
It had been my intention to move on from the Google Map posts to explore more Worklight specific topics. But before I do, it seems that one more map focused post is in order.
The code here is based on the Map widget I documented in previous posts. I've refactored it a bit based on some lessons learned since I originally wrote it, but at its core it is the same. The biggest change I made was to align the widget module name exactly with the folder structure under the Worklight App "common" folder. (including the top level "js" folder name.) the Widget now is located in: js/w
There are 2 advantages to this. First, there is no need to specify the baseUrl or package mappings in the dojo require function calls. Second, this allows the Rich Page Editor palette to be used to add the widget to the HTML using drag and drop.
I thought that I had tried this (and had run into some problem) when I wrote the initial posts, but I guess I just missed it. In any case, The module loading is simpler, and the Rich Page Editor handles adding the widget to the dojo require when it is added to the page.
I've put the rest of the the code for this example in a subclass of the MapWidget named BroadbandMap. This allows me to have a base widget that manages the housekeeping of putting a map in a view, and a widget that inherits this housekeeping behavior and adds some application specific behavior. In the case of the application I'm writing, it is BroadbandMap rather than MapWidget that gets added directly to the HTML.
In a previous post, I briefly addressed the fact that navi
Searching the internet has turned up suggestions to use navi
That seemed like a simple enough solution, but as always, the details turned out to be ... interesting.
The behavior that I saw when I started to use watchPosition() is that the success callback is invariably called right away with a low accuracy position. That was what I was expecting. What surprised me was what happened next.
After the initial callback, there were no more callbacks with gradually improving accuracy as I was expecting. I fiddled with the timeout and the maximumAge ... even with the frequency (which the Cordova docs say not to use) with no real change in this behavior.
The thing that I was missing was that the definition of timeout for watchPosition() was not a period in which my app could be expected to be regularly called back. Instead, it is the amount of time from when new location data is available that the API has to call the app backback. That means that as long as there is no new location information, it doesn't have to call back after the first time. Ever.
This behavior is easy to see in a desktop browser, where the location is determined by IP address, and is not changed or refined over time. In Chrome, watchPosition() results in 1 or 2 callbacks right away, and no more. (regardless of how long you wait)
The behavior that had confused me on a real mobile device (which should be able to refine the position over time) was that there was an initial low accuracy callback, and then ... nothing. It turns out that the problem was that I wasn't waiting long enough. After the initial callback, the devices that I have tested do not call back again until the GPS fix is established. This can tak from 20 seconds to several minutes, depending on the conditions. Once the GPS fix was established, there were fairly frequent callbacks, as accuracy improved. But the initial period of dead air had me convinced that I was doing it wrong.
With that discussion out of the way ... on to the code.
I overrode _createMap in my BroadbandMap widget to start watchPosition() when the map was created.
References to this.* where the member isn't defined in BroadbandMap of couse refer to definitions in the superclass (MapWidget). This provides the Google API loading, resize support (including the _performResize() function), and defines the map attribute for the widget.
I initialize some overlays for the map that will be used when the watchPosition() success callback is called, and then call watchPosition(). Notice that I save the return value. I'm going to need that when I want to cancel the watch. I also set a timeout which I will have more to say about later. The arguments to watchPosition are a success callback, a failure callback and an options object. The only option I'm setting is enableHighAccuracy, but I could also specify a timeout and a maximumAge if necessary. I'm using bind on all the callback functions to ensure that they are called with "this" set to the BroadbandMap object. I could have used dojo.hitch instead.
The failure callback isn't all that interesting. All the useful work happens in the success callback.
Most of the above code is about centering the map on the latest position and adding a marker to indicate the position, as well as a circle to indicate the accuracy of the position. The call to this
The end result should look reasonably familiar to anyone who has used the native Google Map mobile app.
After drawing the marker and the circle on the map, I check to see if the position accuracy meets my requirements. If it does, then I accept the position. Otherwise, I do nothing and just wait to be called back again with a more accurate position.
The accuracy available in different platforms varies widely. My desktop browser gets stuck at 22,000 meters. The iPad I tested (which uses wireless access points to determine location) can get down to 60 meters, but would never represent a position as being more accurate than that. My Android phone (with GPS) will pause while the GPS finds its satellites. Once a GPS fix is established, it gets down to < 10 meters fairly quickly.
Accepting the position doesn't do much more than housekeeping yet.
It clears the watch, which is important, because that is what tells the mobile device that it is OK to shut down the GPS. It also clears the timeout, which I will talk about in a bit, and it removes the accuracy circle from the map.
This is the point I need to extend the app further. Now that I have an acceptably accurate position, I need to have the application do something with that position.
... which just leaves that timeout. I set it just after calling watchPosition(), and I cleared it after calling clearWatch(). The purpose of the timeout is to allow the user to either wait some more, or give up waiting in the case where the position accuracy does not meet the threshold for an extended period of time.
I use a Worklight simple dialog to give the user the option to keep waiting or just use the
After putting together the timeout mechanism, I realized that it would be just as easy (easier) to have UI elements right in the map view that would allow the user to stop waiting at any time without having to use a timeout to pop up a dialog.
I've uploaded a Project Interchange. As before, I haven't included any of the environments, so if you build the app for Android, be sure to edit the Manifest file to specify the location permissions.
With that, I _really_ think I'm done with mapping for now. Not that it isn't interesting in its own way. It is just that I'm hoping to delve deeper into Worklight features like patterns around using adapters. But that is another post...