Ir directamente al contenido de esta página

Pruebas para el futuro inmediato: geolocalización

Iniciamos un conjunto de entradas en las que pretendemos hacer pequeñas demostraciones de las especificaciones de tecnologías del W3C que van ganando soporte en los navegadores actuales más populares. Concretamente, esta primera entrada de la serie —y su demo— se centra en la geolocalización. Y no, no es parte de HTML5.

¿Geolocalización?

Dicho de la manera más simple, es saber dónde está nuestro usuario. La API de geolocalización nos permite acceder a datos sobre la ubicación geográfica del cliente que accede a nuestra página web.

Aunque parezca magia negra, existen diversos procedimientos por los que se puede localizar —bueno, o más bien estimar— la posición del punto de acceso a un sitio web. El más conocido de los métodos es el Global Positioning System, vulgo GPS, pero hay otros como triangular la posición de un dispositivo empleando como referentes las distancias de antenas móviles cercanas, inferir datos de la dirección IP o de la dirección MAC de redes WiFi o Bluetooth, por medio de RFID, y otros medios aún más oscuros.

Como desarroladores web, sin embargo, lo que nos interesa es que la especificación de la API de geolocalización es Candidata a Recomendación desde el pasado 7 de septiembre de este mismo año —por lo que aunque no es la redacción definitiva, ya ha llegado a ser bastante estable—, y en este momento además de en algunos dispositivos móviles como el iPhone, se encuentra disponible en Firefox desde la versión 3.6, y el Chrome desde la 5 —en Google han depreciado su propia API de Gears en favor de la estándar—, aunque por los métodos de localización que hemos mencionado arriba, la precisión sea bastante peor en los navegadores de escritorio, que no cuentan con GPS.

¿El Gran Hermano me vigila?

Lo primero de todo, y para desconsuelo de los teóricos de la conspiración y alivio del resto, una de las principales preocupaciones del W3C ha sido preservar la privacidad de los usuarios, y así, tal como se lee antes incluso de comenzar la descripción de la API, ningún navegador o dispositivo puede compartir la ubicación de un usuario sin su consentimiento expreso:

User agents must not send location information to Web sites without the express permission of the user.

Geolocation API Specification: 4.1 Privacy considerations for implementors of the Geolocation API

La interfaz concreta con la que obtener el permiso es competencia del navegador. Por ejemplo, ésta es la de Firefox 3.6:

La barra de permisos para compartir la ubicación permite rechazar la petición [Firefox 3.6]

Ésta, la de Chrome 6:

Prácticamente la misma interfaz [Chrome 6]

Y ésta la de iPhone:

Cuadro de diálogo de permiso [iPhone 3GS]

Además, en las opciones de navegación de los agentes de usuario existen opciones para desactivar la detección.

La API, en detalle

navigator.geolocation

Como punto de partida, la API de geolocalización proporciona un objeto, geolocation, que contiene toda la información relativa a la localización que puede proporcionar un agente de usuario. Los agentes de usuario que soportan la especificación deben proporcionar acceso a geolocation a través de la interfaz Navigator, por lo que detectar el soporte de la API por medio de JavaScript es bastante simple:


if(!!navigator.geolocation){
  // Código si el navegador soporta geolocalización...
} else {
  // Código si no...
}
    

Con la doble negación (!!) nos aseguramos de que navigator.geolocation nos devuelva un booleano, y si su valor es true podemos empezar a trabajar con él.

getCurrentPosition() y watchPosition()

El objeto geolocation ofrece dos metodos se localización, getCurrentPosition() y watchPosition(). Dependiendo de la aplicación que estemos desarrollando, nos convendrá emplear uno u otro, por lo que es importante conocer las diferencias.

getCurrentPosition() proporciona información sobre la posición en el momento de la petición, y acepta tres parámetros:

Estos parámetros serían opcionales, aunque sin duda alguna facilitan la programación.

Para clarificar, vamos a ver un ejemplo concreto. Supongamos que tengo una línea como ésta:


navigator.geolocation.getCurrentPosition(funcionExito,funcionError,{opciones}); 
 

En este caso, el cliente pide permiso al usuario para compartir su ubicación, comprueba las opciones que hayamos indicado para la petición —más sobre ellas un poco más abajo—, y hace intento de conseguir la información. Si lo logra, se ejecuta funcionExito, y si no, se ejecuta funcionError.

Adicionalmente, para que sepamos qué ha ido mal, a la funcionError se le envía un objeto de error con dos propiedades: message, que proporciona la constante que identifica el error, y code, con su valor numérico.

Información que proporciona la interfaz PositionError
message code Descripción
PERMISSION_DENIED 1 El usuario no ha querido compartir su ubicación.
POSITION_UNAVAILABLE 2 El cliente no ha sido capaz de ubicar su posición.
TIMEOUT 3 Se ha agotado el tiempo que se había establecido para obtener la posición.
Ninguno de los anteriores Algo ha fallado, aunque no se sabe muy bien qué.

Como decía anteriormente, además de las dos funciones podemos establecer unas opciones adicionales. Como parámetro se proporciona como un objeto literal, por ejemplo {maximumAge:60000, timeout:30000, enableHighAccuracy:true}. En cuanto a lo que estamos especificando:

Así, la traducción al español la línea de ejemplo sería algo como: «devuélveme la posición cacheada si ésta tiene menos de un minuto de antigüedad o pide una nueva, espera treinta segundos para ubicarte y si no lanza la función de error, y por último intenta que la posición que me devuelvas sea lo más exacta posible».

Todo esto lo veremos más claramente cuando tratemos nuestra prueba en detalle, pero antes vamos a describir el otro método de localización que es watchPosition().

watchPosition() lo que proporciona es un seguimiento en tiempo real del desplazamiento del cliente. A intervalos regulares —con una duración en principio a discreción del cliente— el método devuelve una posición con la que hacer un seguimiento de la ruta del usuario. Es el cliente también el que debe encargarse del lanzamiento recurrente de la función de éxito o error que hayamos especificado en sus parámetros.

Con respecto a estos, los parámetros son los mismos que para getCurrentPosition(): una función a ejecutar en caso de éxito, una a ejecutar en caso de error y las opciones, que también son las mismas.

Se podría pensar que watchPosition() equivaldría a establecer un setInterval que invocara getCurrentPosition(), pero hay una diferencia sutil: con esta última opción podríamos estar lanzando cada x tiempo la función de éxito para los mismos datos de ubicación; con watchPosition() el cliente se encarga de lanzar esa misma función sólo si la posición ha variado de forma significativa, por lo que es más eficiente.

Por último, tendríamos un tercer método de geolocation, clearWatch(), gracias al cual se puede cancelar un seguimiento iniciado con watchPosition(). Este método acepta un parámetro, que es el identificador de un seguimiento iniciado anteriormente. Y en cuanto a su sintaxis, de nuevo recuerda a setInterval:


var seguimiento = navigator.geolocation.watchPosition(funcionExito); 

  /* Suponemos que la función hace lo que tenga que hacer,
     pero en un momento determinado se decide cancelar el
     seguimiento, de la manera siguiente:                 */

clearWatch(seguimiento); 
 

Bien, con lo dicho hasta ahora ya sabemos cómo pedir la ubicación del usuario y cómo tratar con la respuesta del cliente. Ya sólo nos queda saber de qué datos disponemos cuando todo ha ido bien.

Datos, datos, datos…

Cuando getCurrentPosition() o watchPosition() ejecutan la función de éxito, le envían a ésta un objeto con dos propiedades, timestamp y coords.

timestamp es una marca de tiempo expresada en milisegundos, que indica el momento en que se ha obtenido la ubicación.

A su vez coords es un contenedor para las propiedades que más nos interesan:

Y eso es todo. Ahora, vamos a aplicar todo esto a un ejemplo práctico.

La demo, comentada

Como siempre, presentamos el código completo de nuestro script:


$ = function(id){return document.getElementById(id);}
var gL = {
  geolocalizar : function(){
  
  // Definimos la función que va a manejar los posibles errores
  
  function error(e){
    $('soporte').removeChild($('cargador'));
    $('vProb').className = 'si';
  
    // Indicamos el motivo por el cual ha fallado la localización
  
    switch(e.code){ 
    case 1: 
      $('vProb').innerHTML = 'No ha permitido que su navegador comparta su localización';
      break; 
    case 2: 
      $('vProb').innerHTML = 'No se ha podido determinar su localización';
      break; 
    case 3: 
      $('vProb').innerHTML = 'Se ha agotado el tiempo de espera para determinar su ubicación'; 
      break; 
    default: 
      $('vProb').innerHTML = 'Algo, en algún momento, ha ido mal';
      break; 
    }
  }
  
  // Definimos la función que va a presentar los datos de la interfaz Position
  
  function visualizarCoor(p){
  
    $('vProb').innerHTML = 'Ningún problema';
    $('vProb').className = 'no';
  
    // Obtenemos todos los datos posibles de coords y los presentamos
  
    var lat = p.coords.latitude;
    var lon = p.coords.longitude;
    var prec = p.coords.accuracy;
    var alt = p.coords.altitude;
    var precAlt = p.coords.altitudeAccuracy;
    $('vLat').innerHTML = lat+'º';
    $('vLon').innerHTML = lon+'º';
    (prec<1000) ? prec = prec+' m' : prec = (prec/1000)+' km';
    $('vPrec').innerHTML = prec;
    if(alt===null){
      $('vAlt').innerHTML = 'No se ha podido determinar';
      $('vAlt').className = 'error';
    } else {
      $('vAlt').innerHTML = alt+' m';
    }
    if(alt===null){
      $('vPrecAlt').innerHTML = 'No se ha podido determinar';
      $('vPrecAlt').className = 'error';
    } else {
      $('vPrecAlt').innerHTML = precAlt+' m';
    }
    $('soporte').removeChild($('cargador'));
  
    // Obtenemos la fecha, le damos formato y la mostrarmos
  
    var data = new Date();
    data.setTime(p.timestamp);
    var minuto;
    var mes = Array('enero','febrero','marzo','abril','mayo','junio','julio','agosto','septiembre','octubre','noviembre','diciembre');
    (data.getMinutes()<10) ? minuto = '0'+data.getMinutes() : minuto = data.getMinutes();
    $('soporte').innerHTML += '<em id="data">Posición obtenida el '+data.getDate()+' de '+mes[data.getMonth()]+' de '+data.getFullYear()+' a las '+data.getHours()+':'+minuto+'</em>'; 
  
    // Llamamos a la API de Google y le pasamos los valores de latitud y longitud
  
    if(GBrowserIsCompatible()){
      var mapa = new GMap2($('mapa'));
      mapa.setCenter(new GLatLng(lat,lon),18);
      mapa.openInfoWindow(mapa.getCenter(),
      document.createTextNode('¡Está aquí! (o en un radio de '+prec+')'));
    }
  }
  
    // Pedimos la ubicación actual, pero nos vale la posición de hace 30 segundos si está cacheada
  
    navigator.geolocation.getCurrentPosition(visualizarCoor,error,{maximumAge:30000});
  },
  
  inicio : function(){
  
    // Detectamos el soporte de la API de geolocalización e informamos al usuario
  
    if(!!navigator.geolocation){
      $('vSop').innerHTML = '¡Sí! :)';
      $('vSop').className = 'si';
  
      // Incluimos un pequeño cargador muy 2.0
  
      $('soporte').innerHTML += '<em id="cargador"><img src="cargador.gif" alt="" /> Localizando su posición...</em>';
  
      // Lanzamos la función que realiza todo el trabajo
  
      gL.geolocalizar();
    } else {
      $('vSop').innerHTML = 'No, lo sentimos :(';
      $('vSop').className = 'no';
    } 
  } 
}
window.onload = gL.inicio;

No vamos a analizarlo línea por línea, pero sí vamos a comentar algunos puntos:

Y poco más. Sólo un último comentario: se hará evidente al probar la demo que para aplicaciones prácticas la geolocalización en los navegadores de escritorio es poco eficiente. El radio de error menor que hemos obtenido para Firefox 3.6 y Chrome 6 es de 140 kilómetros. Es más útil, obviamente, para los dispositivos móviles. Por ejemplo, en el iPhone 3GS, que cuenta con los datos de su GPS, el radio se reduce drásticamente a 500 metros.

Esta entrada se publicó el 9 de octubre de 2010, se archivó en , y fue etiquetada como , . Autor: Saúl González Fernández. Hay 13 comentarios ›.

Comentarios

  1. alidhaey dice:

    Parece sencillo. Lo voy a probar con una pequeña aplicación.

  2. Saúl dice:

    Hola tocayo. Muchas gracias por hacer un artículo tan bueno, claro y preciso.

    Hace un tiempo que utilizo la geolocalización en la web y no he conseguido resolver una duda. Quizá podamos entre todos resolverla.

    Estoy desarrollando una web en la que me gustaría permitir al usuario que cambie de opinión en el caso en hubiera rechazado compartir su ubicación. Es decir, que si el usuario no comparte su ubicación y más adelante quiere hacerlo, podamos hacer que por ejemplo pulsando un botón el navegador vuelva a solicitar la localización.

    ¿Qué opináis? ¿Creéis que se puede?

  3. No estoy seguro, pero es muy probable que invocando de nuevo navigator.geolocation.getCurrentPosition() el navegador vuelva a pedir la confirmación del usuario. En cuanto pueda prepararé una demo y comentaré las pruebas.

  4. Saúl dice:

    Gracias por responder. Por desgracia, eso no funciona ya que el navegador tiene la opción de recordar la elección del usuario.

    Empiezo a pensar que no es posible eso que yo demando.

  5. @Saúl: Ya hemos preparado otra demo y hecho algunas pruebas.

    Probamos relanzando getCurrentPosition(), pero sólo conseguimos el efecto deseado en Firefox 3.6, así que probamos otro método menos elegante, que es recargar la página completa.

    En Chrome 6 y Safari 5, como muy bien apuntas, el navegador conserva la decisión del usuario y no logramos obtener la posición, pero en el iPhone 3GS sí nos vuelve a preguntar si queremos volver a compartirla.
    Esperamos que te sirva de ayuda, y disculpa que hayamos tardado tanto en contestar.

  6. Saúl dice:

    No te preocupes por el retraso. Es algo inevitable.

    Muchas gracias por la información. Creo que haré algo parecido yo también. De todas formas, si encuentro algo nuevo, lo compartiré.

    Muchas gracias. Saludos

  7. LUCKY dice:

    Estoy usando la geolocalización en mí blog, lo que me gustaría es capturar la latitud y longitud y enviarla a mí mail para tener detallada la geolocalización de mis usuarios.

    Si alguién puede ayudarme, ¡muchas gracias!

  8. Saúl González Fernández dice:

    @LUCKY: Podrías una vez capturados los datos enviarlos por medio de AJAX a, por ejemplo, un .php que te mande un correo.

    No obstante, revisa la legislación sobre protección de datos privados de tu país. En España es la Ley Orgánica 15/1999, de 13 de diciembre, de Protección de Datos de Carácter Personal, la cual indica que no se pueden guardar datos personales de un usuario sin indicarle expresamente qué datos y con qué fin se están guardando, y proporcionarle algún medio por el que pueda aceptar también de forma expresa que está conforme con ceder los mismos.

  9. kyozuto dice:

    Hola, he llegado aquí por la misma duda que SAUL aunque un par de años más tarde (porque en el post hablan del iphone 3G! :O ) .

    El caso es saber si se ha conseguido algo con eso de conseguir que el navegador vuelva a solicitar al usuario si quiere compartir su posición.

    Está claro que muchas veces los usuarios pulsan NO por acto reflejo y cuando más tarde se dan cuenta que realmente lo necesitan, deberíamos poder darle la oportunidad al usuario a rectificar fácilmente y no tener que decirle que debe entrar en su configuración y etc etc…

    Yo he denegado a una página en el iphone 5 y es un lío buscar en la configuración la forma de volver a permitirlo :/

  10. Saúl González Fernández dice:

    @kyozuto: No, desde el punto de vista del desarrollo no hemos logrado ningún avance.

    Como usuario, quizá si limpias el historial y la caché de Safari consigas que la página te solicite otra vez compartir tu localización.

  11. Carlos J dice:

    Qué buen artículo. Les pido el favor de solucionar el siguiente interrogante:

    ¿Cómo hago para que el navegador pida solamente una vez el permiso para detectar la ubicación?, lo que pasa es que con setInterval identifico cada 10 segundos la ubicación pero en cada ocasión el navegador vuelve a pedir permiso.

    
    setInterval(ubica, 10000);
    function ubica(){
    
    navigator.geolocation.getCurrentPosition(showPosition);
    function showPosition(position)
      {
      alert("Latitude: " + position.coords.latitude + 
      "Longitude: " + position.coords.longitude);	  
      }
    }
     

    Saludos.

  12. Saúl González Fernández dice:

    @Carlos J: Para eso tienes que emplear watchPosition(), que es el método que permite un seguimiento:

    
    function posicion(position){
      alert('Lat: ' + position.coords.latitude + ' | Lon: ' + position.coords.longitude);	  
    }
    function error(){
      alert('ERROR');
    }
    navigator.geolocation.watchPosition(posicion,error,{timeout:5000});	
     

    No obstante, por lo que hemos visto el soporte no es tan bueno como para getCurrentPosition(): nos funciona en Firefox 25, pero en Explorer 10 y en Chrome 31 sólo obtenemos la posición —o el error— una vez.

¿Algún comentario?

* Los campos con un asterisco son necesarios

Últimos proyectos

© Digital Icon, S.L., 2007 – 2017