Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with:

Geocoding A User's Location Using Javascript's GeoLocation API

By Ben Nadel on

A couple of days ago, I demonstrated how to geocode a user's IP address for free using ColdFusion and IPInfoDB. This provided us with a server-side approach to obtaining location information for a incoming web request. As Christian Ready demonstrated at CFUNITED 2010, however, many of the modern browsers now provide us with a Javascript geolocation API that can be used to determine a user's location right from within the web page. Now that I've looked at the server-side approach, I thought it would be fun to explore this client-side approach.

In browsers that support geocoding, the geolocation API is exposed in Javascript as a member of the navigator object:

  • navigator.geolocation

Therefore, an easy way to check for geolocation support is simply to check for the existence of this navigator property:

  • if (navigator.geolocation){ ... geolocation support ... }

Once geolocation support is determined to exist, the API exposes three simple methods:

  • getCurrentPosition( success [, error, options ] )
  • watchPosition( success [, error, options ] )
  • clearWatch( watchTimerID )

The getCurrentPosition() method launches an asynchronous request that tries to find the location (latitude and longitude) of the current browser. This initial position uses efficient lookups like IP routing and may not be the most accurate. On browsers that support GPS (or other forms of triangulation), a secondary more accurate positions may be found using the watchPosition() method.

The watchPosition() method acts very much like Javascript's core setInterval() method. When you call the watchPosition() method, it creates a timer that periodically gathers the user's current position. And, just like setInterval(), the watchPosition() method returns a timer ID that can be used in conjunction with the clearWatch() method to stop this timer (think clearInterval()). Not only can the watchPosition() method be used to find secondary, more accurate location information, it can also be used to track a user's location as they move about from one position to another (primarily for those using mobile devices).

While you could just as easily set up your own timer to periodically call the getCurrentPosition() method, Mozilla recommends the watchPosition() approach as it is more energy efficient:

Note that you can also watch for changes in position by calling getCurrentPosition on a regular basis. But for power savings and performance reasons we suggest that you use watchPosition when you can. Callback APIs generally save power and are only called when required. This will make the browser more responsive, especially on mobile devices.

When the success callback is invoked from within either the getCurrentPosition() method or the watchPosition() method, a Position object is passed in as the only invocation argument. This Position object contains a coords object which, in turn, contains our latitude and longitude values. These latitudinal and longitudinal values can then be used to interact with location-based services like Google Maps.

Now that we have a general idea of how this ties together, let's take a look at some code to see the API in action. In the following demo, I am going to be using the navigator.geolocation API in conjunction with the Google Maps API to plot the location of the current user.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Geocoding Browser Position Using Javascript's Geolocation API</title>
  •  
  • <style type="text/css">
  •  
  • html,
  • body {
  • height: 100% ;
  • margin: 0px 0px 0px 0px ;
  • overflow: hidden ;
  • padding: 0px 0px 0px 0px ;
  • width: 100% ;
  • }
  •  
  • #mapContainer {
  • height: 100% ;
  • width: 100% ;
  • }
  •  
  • </style>
  • <!--- Include jQuery and Google Map scripts. --->
  • <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  • <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
  • </head>
  • <body>
  •  
  •  
  • <div id="mapContainer">
  • <!-- This is where Google map will go. --->
  • </div>
  •  
  •  
  • <!---
  • Now that we have defined our map container, we should be
  • able to immediately load our Google Map.
  • --->
  • <script type="text/javascript">
  •  
  • // Get the map container node.
  • var mapContainer = $( "#mapContainer" );
  •  
  • // Create the new Goole map controller using our
  • // map (pass in the actual DOM object). Center it
  • // above the first Geolocated IP address.
  • map = new google.maps.Map(
  • mapContainer[ 0 ],
  • {
  • zoom: 11,
  • center: new google.maps.LatLng(
  • 40.700683,
  • -73.925972
  • ),
  • mapTypeId: google.maps.MapTypeId.ROADMAP
  • }
  • );
  •  
  •  
  • // I add a marker to the map using the given latitude
  • // and longitude location.
  • function addMarker( latitude, longitude, label ){
  • // Create the marker - this will automatically place it
  • // on the existing Google map (that we pass-in).
  • var marker = new google.maps.Marker({
  • map: map,
  • position: new google.maps.LatLng(
  • latitude,
  • longitude
  • ),
  • title: (label || "")
  • });
  •  
  • // Return the new marker reference.
  • return( marker );
  • }
  •  
  •  
  • // I update the marker's position and label.
  • function updateMarker( marker, latitude, longitude, label ){
  • // Update the position.
  • marker.setPosition(
  • new google.maps.LatLng(
  • latitude,
  • longitude
  • )
  • );
  •  
  • // Update the title if it was provided.
  • if (label){
  •  
  • marker.setTitle( label );
  •  
  • }
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Check to see if this browser supports geolocation.
  • if (navigator.geolocation) {
  •  
  • // This is the location marker that we will be using
  • // on the map. Let's store a reference to it here so
  • // that it can be updated in several places.
  • var locationMarker = null;
  •  
  •  
  • // Get the location of the user's browser using the
  • // native geolocation service. When we invoke this method
  • // only the first callback is requied. The second
  • // callback - the error handler - and the third
  • // argument - our configuration options - are optional.
  • navigator.geolocation.getCurrentPosition(
  • function( position ){
  •  
  • // Check to see if there is already a location.
  • // There is a bug in FireFox where this gets
  • // invoked more than once with a cahced result.
  • if (locationMarker){
  • return;
  • }
  •  
  • // Log that this is the initial position.
  • console.log( "Initial Position Found" );
  •  
  • // Add a marker to the map using the position.
  • locationMarker = addMarker(
  • position.coords.latitude,
  • position.coords.longitude,
  • "Initial Position"
  • );
  •  
  • },
  • function( error ){
  • console.log( "Something went wrong: ", error );
  • },
  • {
  • timeout: (5 * 1000),
  • maximumAge: (1000 * 60 * 15),
  • enableHighAccuracy: true
  • }
  • );
  •  
  •  
  • // Now tha twe have asked for the position of the user,
  • // let's watch the position to see if it updates. This
  • // can happen if the user physically moves, of if more
  • // accurate location information has been found (ex.
  • // GPS vs. IP address).
  • //
  • // NOTE: This acts much like the native setInterval(),
  • // invoking the given callback a number of times to
  • // monitor the position. As such, it returns a "timer ID"
  • // that can be used to later stop the monitoring.
  • var positionTimer = navigator.geolocation.watchPosition(
  • function( position ){
  •  
  • // Log that a newer, perhaps more accurate
  • // position has been found.
  • console.log( "Newer Position Found" );
  •  
  • // Set the new position of the existing marker.
  • updateMarker(
  • locationMarker,
  • position.coords.latitude,
  • position.coords.longitude,
  • "Updated / Accurate Position"
  • );
  •  
  • }
  • );
  •  
  •  
  • // If the position hasn't updated within 5 minutes, stop
  • // monitoring the position for changes.
  • setTimeout(
  • function(){
  • // Clear the position watcher.
  • navigator.geolocation.clearWatch( positionTimer );
  • },
  • (1000 * 60 * 5)
  • );
  •  
  • }
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, this code makes use of both the getCurrentPosition() and the watchPosition() methods. In a desktop browser, it is likely that the getCurrentPosition() method is the only one that will hold any value; however, on a mobile device like the iPhone, the watchPosition() method will allow us to tap into the GPS-based location information.

When the browser goes to find the user's location, it cannot do so without the user's expressed consent. As such, when the browser first tries to invoke the navigator.geolocation API, the user is presented with a confirmation form:

 
 
 
 
 
 
Javascript Geolocation API Needs To Get Authorization From The User Before It Can Be Used. 
 
 
 

Once the user confirms that it is OK for the browser to use the current location, the getCurrentPosition() callback can be invoked:

 
 
 
 
 
 
Javascript Geolocation API Has Successfully Plotted The User's Location On Google Maps. 
 
 
 

As you can see here, Firefox has obtained my location - with about a mile or two accuracy - and has plotted the point on the Google map. Only the getCurrentPosition() callback gets invoked because the position of my browser will not change (or become more accurate over time).

While I had originally thought that only Firefox supported this geolocation API, some quick testing confirmed that this, in fact, also worked on Google Chrome, Opera, and Safari. However, while Safari supports the API, it failed to find my location:

 
 
 
 
 
 
While Safari Supports The Javascript Geolocation API, It Failed To Find My Current Location. 
 
 
 

Mobile Safari, on the other hand, worked perfectly. And, not only did it get the initial position using getCurrentPosition(), it was the only browser I tested that could find a further, more accurate position through GPS and the watchPosition() method:

 
 
 
 
 
 
Mobile Safari On The iPhone Supports The Javascript Geolocation API And Can Get More Accurate, GPS-Based Information. 
 
 
 

If you look at the mobile console, you'll see that after the initial position was determined, the GPS functionality continued to invoke our watchPosition() callback periodically, updating my location marker as the position of my phone changed:

 
 
 
 
 
 
Javascript Geolocation API On The iPhone Allows For Primary And Secondary Location Information. 
 
 
 

This is some pretty cool stuff. At first, I was somewhat turned off by the fact that the user needed to approve the browser's access to the location information; however, with the prevalence of mobile apps that now do this, I don't think that this will be any cause for concern.




Reader Comments

Nice tutorial and good job pointing out watchPosition() Ben. At first, I thought the obvious - that it was for the user moving around - but now I realise it has the potential to provide more precise info too. I had an unsuccessful experiment using GPS without 3G/wifi (perfect for international roaming), and I think the solution may be watchPosition() combined with the enableHighAccuracy flag, to force GPS.

@Michael,

Yeah, from what I have read, the GPS info can take some time to calculate; as such, the GPS location might not be available for some time after the page initially loads.

As far as the enableHighAccuracy flag, I put that in there to demonstrate its use; however, in Firefox this flag doesn't actually do anything yet - they have put it in for future compatibility. On the iPhone, I believe this does affect the GPS. Now, I don't know if it means the GPS is used or not used.... or, if it simply requires a more accurate calculation on the GPS.

Very well explained. I have just the thing for this. BTW...if you add

  • // Center the marker on the map
  • map.setCenter(new google.maps.LatLng(latitude, longitude));

when you add/update your markers, then users outside New York will have the map centered to their position.

Bob

@Bob,

Good suggestion, thanks. When ever I use the Google Maps API, I'm always blown away at how easy they make stuff!

Brilliant! I have a question which you may be able to answer and are free to use as example on your blog. On my website-a global social support network- i have location based support groups in all 7 countries and all 50 US states. Is it possible to geocode those individual group pages in such a way that they will show up as "local" in search in those areas? I would like for those pages to show up as though they are first authored in the location and second producing GeoRSS data from that location. I've looked into hcards and geolocated metadata but can't seem to find a strait forward solution to this. Any help will be much appreciated and you seem like the guy who knows the deal! thanks in advance.

@Justin,

I am not sure I understand what you are asking. I assume from then IP address or from the geolocation API, you can figure out where someone is and then alter your server-side code to reflect that.

The difference with using the geolocation API is that the page has to load FIRST; as such, the initial request might be more generic (before you have the user's location).

If you use the IP address from the CGI object reported in the request, however, you can use that to build the initial response.

Does that help at all? Sorry if I am misunderstanding you.

@Ben
Nice tutorial mister! Unfortunately, even I dont get an error, it's not working properly to me.
Once, I access the page, the map is stacked over New York. Is there something I can do to fix it?
Thank you in advance and do keep up the god job!

ben just wanted to thank you for the great blog. ive landed on several of your posts over the years and find them to be informative, concise, and helpful !

Nice tutorial!!

I am also looking for some software where I can enter thousands of GPS coordinates at once and get the places for those GPS coordinates. Do you know any of such software or code tutorials? I would be very thankful if you could suggest one.

Thanks..

Dear Ben,
Thanks for providing this example, I think it's very usefull for a beginner like me, but when I try to launch the file in my browser, the maps is shown, and the browser also ask for my permission, but the maps is still not show me my location, I test it in Google Chrome and Firefox 4, none of this browser can show me my current location, I check my Modem IP Address is 10.76.176.94. do this IP that make a google maps could not find my current location?thanks for the answer

Dear Ben,

I have finally found the error, when I turn on the firebug console, it shows me an error that says the marker is null when the updateMarker function executed, so I try to change the updateMarker with

locationMarker = addMarker(
position.coords.latitude,
position.coords.longitude,
"Newer Position"
);

And the browser finally show me my current location, I don't know why Firefox fail to make "Initial Position" so locationMarker variable is still null, so when updateMarker is executed the console return an error.

Firefox asks for permission to use geolocation but doesn't set the marker; Chrome doesn't even ask, as doesn't ios...

I'll attach the console error log:

Something went wrong:
PositionError
code: 3
message: "Timeout expired"
__proto__: PositionError
PERMISSION_DENIED: 1
POSITION_UNAVAILABLE: 2
TIMEOUT: 3
constructor: function PositionError() { [native code] }
PERMISSION_DENIED: 1
POSITION_UNAVAILABLE: 2
TIMEOUT: 3
arguments: null
caller: null
length: 0
name: "PositionError"
prototype: PositionError
toString: function toString() { [native code] }
__proto__: Object
__proto__: Object

@Ben,

It is very nice tutorial. I am developing mibile version of website with Netbiscuits. I want to add POIList Biscuit and Google Maps API V3 to locate visiting user's location and display and save the information to my database. Can we add this functionality in netbiscuits too?.

Looking for your response.

Thanks,
Vijaya Laxmi.

Hi, nice tutorial.

I need to replace this lines:

center: new google.maps.LatLng(
40.700683,
-73.925972

to get my actual location.

What I need to do this?

Regards.

Hi Ben, thanks for the tutorial! I'm doing exactly that but it seems that on Android 'getCurrentPosition' does not return 'position' object. I know that Androids require 'enableHighAccuracy' be set to true, but it still doesn't do the trick.
Would you happen to know what's happening,why, and what is the work around for the problem?

Thanks for this example - very helpful.

Read many of the comments and I too was thinking this was not working for me.

The issue is that the map in this example is centered on New York. If you zoom out, you will indeed see that a marker has been placed at your location.

To fix this, do the fix that @Bob posted above. I placed the following code:

after ::::: 'if (label){ marker.setTitle( label );}'

place ::::: 'map.setCenter(new google.maps.LatLng(latitude, longitude));'

(without the quotes of course)

Now map auto-centres to my location after each marker update!

Thanks.

Hi Ben,

This is great! After searching the web for 2 hours i found what I'm looking for.

I'm currently developing a site wherein I want to get the users locations (specially mobile users) but not with the map.

Is it possible to get the Actual ADDRESS as TEXT and display the address to a PAGE instead of map marker?

or any way to convert lat. and long. value to an actual address?

Live working example: http://ocupado.eu/info whit latitude longitude converted to DMS

@Irene Android still doesn't support this 100% from web pages yet :/

Try TUQ GEO IP API it's free and really neat and sweet with jsonp support

http://tuq.in/tools/geo

http://tuq.in/tools/geo+stats