getting users geolocation via html5 and javascript-Collection of common programming errors

  • Here is a script (geolocator.js) I wrote some time ago and updated recently.

    Features:

    • HTML5 geolocation (by user permission)
    • Location by IP (Supported source services: Google, freegeoip, geoplugin, wikimedia)
    • Reverse Geocoding (address lookup)
    • Full address information (street, town, neighborhood, region, country, country code, postal code, etc…)
    • Fallback mechanism (from HTML5-geolocation to IP-geo lookup)
    • Supports Google Loader (loads Google Maps dynamically)
    • Dynamically creates Google Maps (with marker, info window, auto-adjusted zoom)
    • Non-blocking script loading (external sources are loaded on the fly without interrupting page load)

    Usage:

    Inside the of your HTML:

    
    
        //The callback function executed when the location is fetched successfully.
        function onGeoSuccess(location) {
            console.log(location);
        }
        //The callback function executed when the location could not be fetched.
        function onGeoError(message) {
            console.log(message);
        }
    
        window.onload = function() { 
            //geolocator.locateByIP(onGeoSuccess, onGeoError, 2, 'map_canvas');
            var html5Options = { enableHighAccuracy: true, timeout: 3000, maximumAge: 0 };
            geolocator.locate(onGeoSuccess, onGeoError, true, html5Options, 'map_canvas');
        }
    
    

    Also place the line below, inside your if you want to dynamically draw a map (you should also pass the element ID to the corresponding method for the map to be drawn):

    
    

    geolocator.js provides 2 useful methods:

    Methods

    geolocator.locate()

    Use this method to get the location via HTML5 geolocation.

    geolocator.locate( successCallback, [errorCallback], [fallbackToIP], [html5Options], [mapCanvasId] )
    

    Parameters:

    successCallback Function   A callback function to be executed when the location is successfully fetched.

      The recent geolocator.location object is passed to this callback, as the only argument.

    errorCallback Function (optional, default: null)   A callback function to be executed when the location could not be fetched due to an error.

      The recent error message String is passed to this callback, as the only argument.

    fallbackToIP Boolean|Integer (optional, default: false)   Specifies whether geolocator should fallback to IP geo-lookup when HTML5 geolocation is not

      supported or rejected by the user. A positive Integer value will indicate the index of the source

      ip-geo service (if the value is in range). Boolean true will set the default ip-geo service index
      which is 0 (Google).
      Valid values: 0 or true (use Google for ip-geo fallback), 1 (use FreeGeoIP for ip-geo fallback),
      2 (use GeoPlugin for ip-geo fallback), 3 (use Wikimedia for ip-geo fallback), false or -1 or
      null or any other value (will disable ip-geo fallback)

    html5Options Object (optional, default: null)
      HTML5 geolocation options.

    mapCanvasId String (optional, default: null)
      HTML element ID for the Google Maps canvas. If set to null, no map is drawn.

    Example:

    var html5Options = { enableHighAccuracy: true, timeout: 3000, maximumAge: 0 };
    geolocator.locate(onGeoSuccess, onGeoError, true, html5Options, 'map_canvas');
    

    geolocator.locateByIP()

    Use this method to get the location from the user’s IP.

    geolocator.locateByIP( successCallback, [errorCallback], [ipSourceIndex], [mapCanvasId] )
    

    Parameters:

    successCallback Function   A callback function to be executed when the location is successfully fetched.

      The recent geolocator.location object is passed to this callback, as the only argument.

    errorCallback Function (optional, default: null)   A callback function to be executed when the location could not be fetched due to an error.

      The recent error message String is passed to this callback, as the only argument.

    ipGeoSourceIndex Integer (optional, default: 0)   Indicates the index of the IP geo-lookup service.

      Valid values: 0 (Google), 1 (FreeGeoIP), 2 (GeoPlugin), 3 (Wikimedia)

    mapCanvasId String (optional, default: null)
      HTML element ID for the Google Maps canvas. If set to null, no map is drawn.

    Example:

    geolocator.locateByIP(onGeoSuccess, onGeoError, 0, 'map_canvas');
    

    Properties

    geolocator.location Object

    Provides the recent geo-location information.

    Example Output:

    {
        address: {
            city: "New York",
            country: "United States",
            countryCode: "US",
            neighborhood: "Williamsburg",
            postalCode: "11211",
            region: "New York",
            street: "Bedford Avenue",
            street_number: "285",
            town: "Brooklyn"
            },
        coords: {
            accuracy: 65,
            altitude: null,
            altitudeAccuracy: null,
            heading: null,
            latitude: 40.714224,
            longitude: -73.961452,
            speed: null
            },
        map: {
            canvas: HTMLDivElement, //DOM element for the map
            map: Object, //google.maps.Map
            options: Object, //map options
            infoWindow: Object, //google.maps.InfoWindow
            marker: Object //google.maps.Marker
            },
        formattedAddress: "285 Bedford Avenue, Brooklyn, NY 11211, USA",
        ipGeoSource: null,
        timestamp: 1360582737790
    }
    
  • Ok so this is not a code answer, more of an User Experience answer.

    From a UX standpoint, the first thing that stands out is the lack of information you are offering before you trigger the browser to ask them for permission.

    I suggest you have some sort of overlay box showing a screen shot (with a large arrow on it) demonstrating “where” on the screen that they are going to get asked for permission. Also you can take that opportunity to tell them what will happen if they deny permission or fail to accept it within say 10 seconds (ie. where they ignore the prompt bar).

    I suggest you don’t default to showing the IP location, because they essentially ‘could be saying’ I don’t agree to letting you know where I am. Then you show a big map of where they are, that may freak a few people out that clicked deny! Besides it may be very inaccurate.

    The idea of “don’t ask for permission ask for forgiveness”, may work in Biz dev, but not in UI as they just don’t come back.

    I would ask yourself if you really need high accuracy too, because it will drain user battery, take longer, and may not give you much more bang for your buck, especially if you only need it to find out loosely where they are. You can always call it again later for a more accurate reading if you need to.

    The concept of timing out if they don’t click deny or allow could be achieved with a setTimeout. So once they click “Ok I’m ready to click allow” on your overlay box, kick off a timeout and if it does eventually timeout then do what you told them you would do in the above step.

    By using this method, you force the user to either allow or deny(ignore), either-way it puts control back in your court, and keep the user totally informed.

    Although this is not a code specific answer, it is clear from your JS that “code implementation help” is not really the issue here. I hope if nothing else this gets you thinking a little more about your User Experience.

  • This, might be of help: http://jsfiddle.net/sandesh2302/FghFZ/ I used this for my stuff, it worked fine.

    Ex:

        
    
      
            
            
    
        
          function getLocation(){
            navigator.geolocation.getCurrentPosition(handleSuccess,handleError);
          }
    
          function initiate_watchlocation() {  
            if(watchProcess == null){
              watchProcess = navigator.geolocation.watchPosition(handleSuccess,handleError);
            }
          } 
    
          function stop_watchlocation() {  
            if(watchProcess != null){
              navigator.geolocation.clearWatch(watchProcess);
            }
          } 
    
          function handleSuccess(position){
            drawMap(position);
          }
    
          function handleError(error){
            switch(error.code)
            {
              case error.PERMISSION_DENIED: alert("User did not share geolocation data");break;  
              case error.POSITION_UNAVAILABLE: alert("Could not detect current position");break;  
              case error.TIMEOUT: alert("Retrieving position timed out");break;  
              default: alert("Unknown Error");break;  
            }
          }
    
    
          function drawMap(position) {
            var container = $('#map_canvas');
            var myLatLong = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
            var mapOptions = {
              center: myLatLong,
              zoom: 12,
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            var map = new google.maps.Map(container[0],mapOptions);
            container.css('display','block');
            var marker = new google.maps.Marker({ 
              position: myLatLong,
              map:map,
              title:"My Position (Accuracy of Position: " + position.coords.accuracy + " Meters), Altitude: " 
                + position.coords.altitude + ' Altitude Accuracy: ' + position.coords.altitudeAccuracy
            });
          }
    
          function drawStaticMap(position){
            var container = $('#map_canvas');
            var imageUrl = "http://maps.google.com/maps/api/staticmap?sensor=false&center=" + position.coords.latitude + "," +  
                        position.coords.longitude + "&zoom=18&size=640x500&markers=color:blue|label:S|" +  
                        position.coords.latitude + ',' + position.coords.longitude;  
    
            container.css({
              'display':'block',
              'width' : 640
            });
            $('',{
              src : imageUrl
            }).appendTo(container);
          } 
        
      
      
        Find My Location
        
          Put Watch on Your Position
          Stop Position Watching
        
        
        
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

  • You can use this online service to get the lat lng easily:

    http://dev.maxmind.com/geoip/javascript

    Regarding the timeout, I don’t think there’s a way to interfere with the browsers permission mechanism (as in, to close that permission popup after a certain amount of seconds) – though I would gladly be proven wrong. What you could do would be to set a timer and after three seconds, get the IP based geolocation and set the map to it (or, refresh the page after 3 seconds, and set a cookie that triggers the IP based geo and not the HTML5 geo, but that’s a bit over the top if you ask me).

    Then, if they give permission, it would refresh the map with the HTML5 geolocation (which should be much more accurate). You can also encapsulate the IP geo fallback into a function and use it if they don’t have HTML5 geolocation or they hit deny.

    Here’s a fiddle: http://jsfiddle.net/mfNCn/1/

    Here’s the rough cut from the fiddle:

    
    ...
    var time_perm = window.setTimeout(get_by_ip, 3000);
    ...
    function get_by_ip() {
        var lat = geoip_latitude();
        var lng = geoip_longitude();
        map_it(lat, lng);
    }
    ...
    function map_it(lat,lng) {
        // build your map here
    }
    

    (I hesitate to put the whole code chunk onto here, as it’s rather lengthy, so check the fiddle for the rest and full implementation)

  • From UI point of view, I would follow these steps:

    A) show a nice text box explaining what’s going to happen next (I.e. ‘the browser will ask you to grant a permission’, ‘click allow’, etc) and asking to push a button to proceed B) display the map as you do now

  • If there is a timeout or the user denies the request, I would set a default location like New York, NY (40.7142, -74.0064). If a user denies a request, they have to also expect that you won’t know their location so choosing an intelligible default is the next best thing.

    Using a default without changing your code much can be accomplished by calling displayPosition({coords: {latitude: 40.7142, longitude: -74.0064}}) in two places:

    if (navigator.geolocation) {
        var timeoutVal = 10 * 1000 * 1000;
        navigator.geolocation.getCurrentPosition(
            displayPosition, 
            displayError,
            { enableHighAccuracy: true, timeout: timeoutVal, maximumAge: 0 }
        );
    }
    else {
        displayPosition({coords: {latitude: 40.7142, longitude: -74.0064}})
    }
    ....
    function handleError(error){
        switch(error.code)
        {
            case error.PERMISSION_DENIED: alert("User did not share geolocation data");break;  
            case error.POSITION_UNAVAILABLE: alert("Could not detect current position");break;  
            case error.TIMEOUT: alert("Retrieving position timed out");break;  
            default: alert("Unknown Error");break;  
        }
        displayPosition({coords: {latitude: 40.7142, longitude: -74.0064}});
    }
    

    On http://nearbytweets.com I use a “queue” of functions for finding a user’s location, looping through the queue until one of them finds a valid location. The last function returns New York, NY, which means all other attempts have failed. Here’s a sample of the code modified slightly:

    var position_finders = [                                                                                                                                                                                                              
        function () {
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(check_position, check_position);
                return;
            }   
            check_position();
        },  
        function () {
            check_position(JSON.parse(localStorage.getItem('last_location')));
        },  
        function () {
            $.ajax({
                url: 'http://www.google.com/jsapi?key=' + google_api_key,
                dataType: 'script',
                success: check_position
            }); 
        },  
        function () {
            check_position({latitude: 40.7142, longitude: -74.0064}, true);
        }   
    ],
    
    check_position = function (pos, failed) {
        pos = pos || {}; 
        pos = pos.coords ? 
            pos.coords :
            pos.loader ?
            pos.loader.clientLocation :
            pos;
    
        if (typeof pos.latitude === 'undefined' || typeof pos.longitude === 'undefined') {
            position_finders.shift()();
            return;
        }   
    
        localStorage.setItem('last_location', JSON.stringify(pos));
    
        // using your code, I would call this:
        displayPosition(pos);
    };
    
    check_position();
    

    Here’s what each position_finder does:

    1. Tries navigator.geolocation.
    2. Tries pulling their last location from localStorage
    3. Uses Google Loader to find location via I.P.
    4. Uses New York, NY

Originally posted 2013-11-10 00:13:00.