JQuery Mobile y Google Maps

Parece sencillo integrar un mapa de Google Maps en una aplicación Web para móviles con JQuery Mobile, pero cuando te pones a ello aparecen problemillas…

Supongamos la siguiente aplicación:

Tenemos almancenados en una base de datos (MySQL) en un servidor las un conjunto de posiciones con su longitud, latitud, marca de tiempo y algún parámetro más. (Datos obtenidos por ejemlo con un GPS tracker que envía sus datos a un servidor que configuramos)

Queremos desarrollar una aplicación que permita a un dispositivo móvil conectarse al servidor, obtener las posiciones de la base de datos (mediante una petición JSON) y pintar el mapa. Una opción muy sencilla para ello es usar JQuery Mobile (jquerymobile.com).

Muestro un sencillo ejemplo, en el que la aplicación muestra una pantalla inicial, con una lista de los datos, y en una segunda pantalla muestra el mapa.

1. En el Servidor…

El servidor debe implementar una función que ante una petición, devuelva los datos en formato JSON (por ejemplo)

Lo más fácil, usar PHP y la multitud de funciones que existen para tratamiento de bases de datos MySQL (conectar a la base de datos, etc). Todo mediante una petición GET (también por simplificar)

Lo importante a tener en cuenta es que ha de permitirse el Cross-Domain Request, para permitir que nuestro navegador móvil, que ejecuta la página Web de JQuery Mobile, pueda hacer una petición AJAX al servidor. Y también implementar la función “Callback” del servidor, mediante un parámetro GET, donde se incluye la respuesta JSON

Se mira en la base de datos y se elabora un array  que se devuelve en un objeto JSON:

La Tabla de la Base de Datos MySQL incluye los siguientes campos:

account password deviceID timestamp statusCode longitude latitude

La respuesta JSON es unarray donde se incluye el índice de cada fila encontrada.
Abajo, en el punto 3 incluyo el código de la función a la que se llama en el servidor y proporciona la respuesta.

2. Aplicación móvil con JQuery Mobile

Usando lo básico de JQuery mobile, con dos páginas para la aplicación. Lo importante es implementar la petición AJAX con JSONP al servidor, para obtener las posiciones y rellenar con ellas una tabla en la primera página y pintar el mapa en la segunda.

app_jquery

Al pulsar el boton “obtener valores” se efectúa la petición JSONP al servidor, que devuelve los datos y que se incluyen en una lista con JQuery Mobile:

app_jsonp

Se crea también un botón nuevo para ver el mapa, y pulsándolo:

app_mapa

El código HTML de esta aplicación:

<!DOCTYPE html> 
<html> 
<head> 
<title>My Page</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> 
		<link href="jquery.mobile-1.2.0/jquery.mobile-1.2.0.css" rel="stylesheet" /><script type="text/javascript" src="jquery.mobile-1.2.0/jquery.js"></script><script type="text/javascript">// <![CDATA[
// Aquí el javascript JQuery antes de cargar el mobile
// ]]></script><script type="text/javascript" src="jquery.mobile-1.2.0/jquery.mobile-1.2.0.js"></script><script type="text/javascript">// <![CDATA[
// Acciones en las páginas jQueryMobile
$('#inicio').live('pageinit',function(){
	$("#lista").listview;
	$("#mapBtnDiv").button;
	$('#boton').click(function(){
		//alert('Ejecutar acción');
		$.ajax({
			url: 'http://www.XXXXXX/access2.php?callback=',
			data: 'account=nombre&password=clave&tag=m',
			datatype: 'jsonp',
			crossDomain: true,
			success: function(data){
				var content=[];
				var arr=$.parseJSON(data);
				$.each(arr,function(i,val){
					//alert('i='+i+' val='+val);
					content.push("
	<li><a href='#'>");
					content.push("i:"+i);
					content.push(" tiempo "+val.timestamp);
					content.push(" Long "+val.longitude);
					content.push(" Lat "+val.latitude);
					content.push(" status  "+val.statusCode);
					content.push("</a></li>
");
				});
				$("#lista").append(content.join(""));
				$("#lista").listview('refresh');
				$("#mapBtnDiv").append("<a id='mapBtn' href='#mapaPage'>VER MAPA</a>");
				$("#mapBtn").button();
			},
			error: function(jqXHR, textStatus, errorThrown){
				alert('Error: '+jqXHR+textStatus+errorThrown);
			}
		});
	});
});
 
function initializeMaps() {
 
	var myOptions={
		mapTypeId:google.maps.MapTypeId.ROADMAP
	}
 
	var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
	var infowindow = new google.maps.InfoWindow();
	var marker, i;
	var bounds = new google.maps.LatLngBounds();
	$.getJSON( 'http://www.XXXXXXX/access2.php?callback=',	
                  'account=nombre&password=clave&tag=m',
 
	function(data) { 
		$.each( data, function(i, markers) {
			var pos = new google.maps.LatLng(markers.latitude,
			 markers.longitude);
			bounds.extend(pos);
			marker = new google.maps.Marker({
				position: pos,
				map: map
				});
			if (i==(data.length-1)){
			  iconFile = 'http://maps.google.com/mapfiles/ms/icons/green-dot.png';
			marker.setIcon(iconFile) };
	 		google.maps.event.addListener(marker, 'click', 
			(function(marker, i) {
				return function() {
					infowindow.setContent(markers.timestamp);
					infowindow.open(map, marker);
	  			};
			})(marker, i));
		}); // fin each
 
	map.fitBounds(bounds);
	google.maps.event.trigger(map, 'resize'); 
	}); // fin function(data) y getJson
	// lanzamos el evento pagecreate de jquery mobile:
	//$('mapaPage').trigger('create');
};	// fin initializeMaps
 
$('#mapaPage').live('pageinit', function() {
	initializeMaps();
});
// ]]></script><script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<div id="inicio" data-role="page" data-theme="b">
<div data-role="header">
<h1>App</h1>
</div>
<!-- /header -->
<div data-role="content">Hello world
<div style="width: 40%; float: left;"><input id="boton" type="button" value="Obtener valores" data-icon="arrow-r" data-iconpos="right" data-theme="b" /></div>
<div style="width: 40%; float: right;"><a id="boton2" data-role="button" data-icon="arrow-r" data-iconpos="right"></a> otro boton<script type="text/javascript">// <![CDATA[
$("#boton2").click(function(){ alert('boton2');});
// ]]></script></div>
<div style="clear: both;"></div>
<ul id="lista" data-role="listview" data-filter="true" data-inset="true" data-theme="c"></ul>
<div style="width: 50%; float: left;"></div>
</div>
<!-- /content -->
<div data-role="footer" data-postion="fixed">Pie de pagina</div>
</div>
<!-- /page -->
<div id="mapaPage" data-role="page" data-theme="b">
<div data-role="header"><a href="#inicio" data-icon="back">Volver</a>
<h1>Mapa</h1>
</div>
<!-- /header -->
<div data-role="content">Mapa con ultimas pos
<div id="map_canvas" style="width: 400px; height: 400px;"></div>
</div>
<!-- /content -->
<div data-role="footer" data-position="fixed">Pie de pagina</div>
</div>
<!-- /page -->

3. Función en el servidor

En la petición JSON que se hace en la aplicación móvil, se llama a la función access2.php, que porporciona la respuesta.
Usa un parámetro adicional “tag” que en un principio usé para diferenciar una llamada desde un dispositivo móvil (tag=’m’) o desde un navegador de un PC (tag=’w’), en el primer caso devuelve un array JSON, que la aplicación móvil lee y usa para pintar el mapa, y en el segundo caso pintaba el mapa directamente (lo he borrado, pues no interesa en este caso).
Mira en la base de datos el usuario y si es correcto lee las posiciones y las devuelve.

<?php
/**
 * File to handle all API requests
 * Accepts GET and POST
 *
 * Each request will be identified by TAG
 * Response will be JSON data
 
  /**
 * check for POST request
 */
header("Access-Control-Allow-Origin: *");
if (isset($_GET['tag']) && $_GET['tag'] != '') {
    // get tag
    $tag = $_GET['tag'];
 
    // include db handler
    require_once 'DB_Functions.php';
    require_once 'Web_Functions.php';
    $db = new DB_Functions();
    $web = new Web_Functions();
    // response Array
    $response = array("tag" => $tag, "success" => 0, "error" => 0);
 
    // check for tag type
    if (($tag == 'w')||($tag == "m")) {
        // Request type is check Login
        //$email = $_POST['email'];
	$account = $_GET['account'];        
	$password = $_GET['password'];
 
	$user = $db->getUserByAccountAndPassword($account, $password);        
	if ($user != false) {
            // user found
            // echo json with success = 1
            $response["success"] = 1;
	    	$posDB = $db->getPos($account, "device");
	    	$pos3=array();
 
		foreach($posDB as $p){
		  $pos3[]=array('id'=>$j,
			 	 	    'uid' => $p['accountID'],
		  				'deviceId'=> $p['deviceID'],
		  				'timestamp'=>$p['timestamp'],
		  				'statusCode'=>$p['statusCode'],
		  				'longitude'=>$p['longitude'],
		  				'latitude'=>$p['latitude']);
 
 
		  $i++;
		}
        if ($tag == "m") {
		    // implementando el callback para JSONP
		    //
		    // el header se manda en DB funtions. 
		    //header('Content-Type: application/json');
		    echo $_GET['callback'].json_encode($pos3);
		}
	    if ($tag == "w") {
			// mostrar pag web con mapa			
			}
 
    } else {
            // user not found
            // echo json with error = 1
            $response["error"] = 1;
            $response["error_msg"] = "Incorrect password!";
            echo json_encode($response);
        }
    } else {
        echo "Invalid Request";
    }
} else {
    echo "Access Denied";
	}
?>

NOTA: Esto es una aplicación preliminar, el desarrollo de la misma queda pendiente…

25 Comments

  1. chuyr · miércoles, 19 junio, 2013 Reply

    Muy buen ejemplo, me ayuda para un proyecto que estoy empezando con estas herramientas, pero tengo una pregunta, de donde se obtiene o cual es la estructura del archivo DB_functions¿ lo podrías subir o mandarmelo por correo, Saludos y gracias

    • abrazalaweb · viernes, 21 junio, 2013 Reply

      Hola,
      el DB_functions.php incluye las funciones para conectar a la base de datos y efectuar las consultas. Hay muchas implementaciones en internte. En mi caso incluye:

      < ?php class DB_Functions { private $db; // constructor function __construct() { require_once 'DB_Connect.php'; // connecting to database $this->db = new DB_Connect();
      $this->db->connect();
      }

      // destructor
      function __destruct() {

      }
      /**
      * Get user by account and password
      */
      public function getUserByAccountAndPassword($account, $password) {
      $result = mysql_query("SELECT * FROM Account WHERE accountID = '$account'") or die(mysql_error());
      // check for result
      $no_of_rows = mysql_num_rows($result);
      if ($no_of_rows > 0) {
      $result = mysql_fetch_array($result);
      $dbPassword = $result['password'];
      if ($dbPassword == $password) {
      // user authentication details are correct
      return $result;
      }
      } else {
      // user not found
      return false;
      }
      }
      /** GET POS **/
      public function getPos($account, $device) {
      $result = mysql_query("SELECT accountID, deviceID, timestamp,statusCode, longitude, latitude FROM EventData WHERE accountID = '$account'") or die(mysql_error());
      // check for result
      $no_of_rows = mysql_num_rows($result);
      $i=0;
      if ($no_of_rows > 0) {
      while ( $row = mysql_fetch_array($result)){
      $posiciones[]=$row;
      }
      return $posiciones;
      } else {
      // not found
      return false;
      }
      }
      }
      ?>


      A su vez, la función de conexión a la base de datos está en DB_Connect.php:

      < ?php class DB_Connect { // constructor function __construct() { } // destructor function __destruct() { // $this->close();
      }

      // Connecting to database
      public function connect() {
      require_once 'config.php';
      // connecting to mysql
      $con = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
      // selecting database
      mysql_select_db(DB_DATABASE);

      // return database handler
      return $con;
      }

      // Closing database connection
      public function close() {
      mysql_close();
      }

      }
      ?>

      Y por último los datos de la bse de datos en config.php:

      < ?php /** * Database config variables */ define("DB_HOST", "www.xxxxxx.com"); define("DB_USER", "usuario_de_la_bd"); define("DB_PASSWORD", "contraseña"); define("DB_DATABASE", "nombre_de_la_db"); ?>

  2. Invader · jueves, 23 enero, 2014 Reply

    Enhorabuena por el tuto, esta genial!! ..voy comprendiendo poco a poco el codigo pero aun me surgen dudas con todo esto, ¿se necesitará que el usuario esté registrado, no? para indicarle el nombre y el password como se hace notar en la llamada al servidor.

    ¿Entonces primero se prepara un formulario de registro y una vez registrado se envia al usuario a la pantalla de la aplicación? Si es así, ¿como le asignas a la llamada el nombre y el password?

    Perdona mi duda e ignorancia, seguro que lo que digo es una locura..

    Saludos

    • abrazalaweb · viernes, 24 enero, 2014 Reply

      Hola,
      el ejemplo es muy simple y tiene picado a mano el usuario y el password en la base de datos, es decir sin registro previo ni nada.
      Ambos parámetros (usuario y contraseña) se pasan al servidor mediante la llamada JSon (petición GET) al hacer click en el botón:
      $.ajax({url: ‘http://www.XXXXXX/access2.php?callback=’,
      data: ‘account=nombre&password=clave&tag=m’,
      datatype: ‘jsonp’,
      […]
      Por supuesto puede hacerse un formulario de registro para crear ususarios en la base de datos. Luego la petición al servidor se haría enviando los valores escritos en ese formulario, con la sintaxis de variables Javascript, p.ej:
      var datos_formulario = {
      “usuario”:$(“#campo1_form”).val(),
      “password”:$(“#campo2_form”).val()
      };
      eso crea las variables,
      y luego en la etición JSON:
      data: $.toJSON(datos_formulario)

  3. Invader · miércoles, 29 enero, 2014 Reply

    Lo siento, pero tu ejemplo no me funciona, lo he copiado y pegado tal cual y nada, haga lo que haga siempre me da el error -Error: [object Object]error- cuando presiono el boton de obtener coordenadas.

    No se si tendra que ver que mi dominio es gratuito luce asi: http://www.midominio.url.ph

    La llamada la hago como tu indicas:
    $.ajax({
    url: ‘http://www.midominio.url.ph/geovitol/DB_Functions.php?callback=’,
    data: ‘account=nombre&password=clave&tag=m’,
    datatype: ‘jsonp’,
    crossDomain: true,
    success: function(data){
    var content=[];
    var arr=$.parseJSON(data);
    $.each(arr,function(i,val){

    En la base de datos he creado varios registros con el accountID=nombre y el password=clave. Una cosa que me lleva loco y tampoco entiendo es lo del “tag=m”. Te agradezco tu ayuda si fuera posible.

    La verdad que estoy intentando crear una aplicación movil donde los usuarios al iniciar la aplicación envian su posición a la base de datos y reciben la posición de todos los usuarios conectados en ese momento además de la de ellos mismos en un mapa que se va actualizando cada 30 segundos para poder hacer un seguimiento en tiempo real y despues una opción donde puedan hacer lo de tu ejemplo y asi ver cual ha sido su recorrido.

    En la comunidad de phonegapspain.com he abierto un hilo donde he dejado el codigo que de momento me está permitiendo mediante google maps representar en el mapa mi posición con un punto además de utilizar tambien la geolocalizacion inversa de la api de google maps para que me indique en el InfoWindow la calle, numero, localidad, comunidad y pais.

    Solo necesito que estos datos se registren en el servidor y que me devuelva los ultimos registros de la base de datos de cada usuario activo. Te dejo la dirección del hilo donde tengo el codigo (es el ultimo codigo de todos los de la entrada que hago el dia 22 enero, 2014 con nickname Invader) http://www.phonegapspain.com/topic/geolocalizacion-inversa-en-phonegap-posible/

    Gracias de antemano

    • abrazalaweb · viernes, 31 enero, 2014 Reply

      Hola,
      he modificado la entrada añadiendo el código de la función en el servidor a la que se hace la llamada JSON (access2.php), fíjate que en tu código llamas a
      http://www.midominio.url.ph/geovitol/DB_Functions.php?callback=
      pero eso no es correcto, ya que DB_functions no implementa el callback, la que lo implementa es access2.php.
      Mi aplicación funciona, y está en: http://abrazalaweb.net/gps/mobile/app3.html
      Si necesitas algo más, te envío por e-mail todo el código.

  4. Invader · lunes, 3 febrero, 2014 Reply

    Agradeceria mucho que me pudieses pasar los archivos, he provado con esto nuevo pero sigo sin obtener resultados, supongo que la base de datos será la causante de mis problemas. ¿tienes un campo en la base de datos con nombre “id”?.. …voy más perdido que un calamar en el desierto.. agradezco tu inestimable ayuda.

  5. CELS · miércoles, 5 febrero, 2014 Reply

    Hola, que interesante post y es justo lo que necesito, pero lo que no entiendo es la parte de la función callback, según mencionas es una función de php pero no la observo en el código. Serias tan amable de enviarme el código completo, realmente me seria de mucha ayuda.

    Gracias.

  6. CELEMUS · miércoles, 5 febrero, 2014 Reply

    Hola, es justo lo que necesito podrias enviarme el codigo ya que tengo dudas de la funcion callback que según entiendo se define en el server.
    Gracias.

    • abrazalaweb · miércoles, 5 febrero, 2014 Reply

      Hola,
      el callback está implementado en la función php del servidor, que está en el punto 3 del post con todo el código, en concreto es la línea:
      echo $_GET[‘callback’].json_encode($pos3);
      en realidad se pasa como parámetro GET, para que se comuniquen servidor y cliente.

  7. Gus · martes, 11 marzo, 2014 Reply

    Hola que tal estoy teniendo problemas con la base de datos me gustaria que me ayudaras para saber quecampos y que tipo son y como se deben de rellenar. gracias

    • abrazalaweb · martes, 11 marzo, 2014 Reply

      En el post y en el código se ven los campos de la base de datos. El tipo es el que quieras definir, da igual, siempre que el texto sea texto y los números números.

  8. Gus · miércoles, 12 marzo, 2014 Reply

    oye estoy viendo que haces referencia a Web_Functions.php esto para que? es necesario este archivo php?
    // include db handler
    require_once ‘DB_Functions.php’;
    require_once ‘Web_Functions.php’;
    $db = new DB_Functions();
    $web = new Web_Functions();

    seria mucha molestia si me pudieras pasar los archivos a mi correo?

    • abrazalaweb · jueves, 13 marzo, 2014 Reply

      Hola, en realidad Web_functions.php la usaba para mostrar el mapa en un navegador web en un PC, en este ejemplo no vale para nada. El contenido de DB_functions.php está en una respuesta a un comentario anterior.

  9. miguel · martes, 30 junio, 2015 Reply

    Hola que tal me podrias hechar la mano explicandome como hiciste todo eso y que es lo que tengo que subir al dominio y los datos de mysql xfavor

  10. miguel · martes, 30 junio, 2015 Reply

    hola me puedes ayudar en como hacer todo eso

  11. Alejandro Ventura · viernes, 10 julio, 2015 Reply

    Pero si en vez de recibir los datos en JSON para pintar el mapa los estoy recibiendo en XML desde un web service de Java como lo transformo a JSON para poder operarlo de la misma manera que tú?

  12. Chris · lunes, 13 julio, 2015 Reply

    Master muchas gracias por toda este explicación, está fenómeno el ejemplo de tu aplicación, danos el Web_Functions.php por favor para completar y no quedar con la ansiedad y la posibilidad de generar algún error.

  13. Chris · miércoles, 15 julio, 2015 Reply

    Master, no me fuciona tampoco porque falta require_once ‘Web_Functions.php’; lo mas seguro, súbelo por favor y tb una lista de datos sql para que funcione por favor, lo necesito urgente,
    saludos y gracias.

    • abrazalaweb · domingo, 19 julio, 2015 Reply

      Hola, esto ya está contestado en un comentario anterior: en realidad Web_functions.php la usaba para mostrar el mapa en un navegador web en un PC, y en este ejemplo no vale para nada (puedes eliminar esa línea “require”. El contenido de DB_functions.php está en una respuesta a un comentario anterior, y los datos, puedes usar los que quieras

  14. edwin · martes, 4 agosto, 2015 Reply

    hola Master gracias por el aporte creo que me servirá mucho para una proyecto en el que trabajo, pero como soy nuevo en esto me gustraia que enviaras los archivos fuentes. de antemano gracias.

  15. Ros · jueves, 20 agosto, 2015 Reply

    Genial el tutorial, yo soy bastante nuevo en esto y estoy intentando crear una app en la que se muestre en el mapa automáticamente la ultima posición de varios gps, estas se almacenan en la misma tabla, y en principio me gustaría eliminar la parte del código de acceso a la cuenta.
    Muchas gracias de antemano

Leave a Reply

Tu dirección de correo electrónico no será publicada.