Datos en la nube con Google Fusion Tables

Introducir una capa de datos compartidos en una app se está convirtiendo poco a poco en un procedimiento sencillo. Si antes era necesario contar con espacio en un servidor propio y manejar los rudimentos de algún lenguaje de programación para servidor, como php por ejemplo, hoy numerosos servicios online gratuitos -aunque no públicos ni libres- proporcionan esta capacidad a desarrolladores sin estos recursos o conocimientos. El servicio más extendido es el de Amazon AWS, pero Google con sus Docs es una de las opciones más asequibles técnicamente.

Las hojas de cálculo de Google Docs llevan ya un tiempo sirviendo como repositorio de datos que podían ser utilizados en proyectos de visualización con Processing, tal y como lo explica Jer Thorp en un post muy claro aquí. En este apartado exploraremos otro de los servicios de Google Docs, su Fusion Tables, que nos permitirá contar con una base de datos online con la que interactuar desde nuestros proyectos para dispositivos móviles.

Las Google Fusion Tables son tablas de datos que cualquier usuario de Google Docs puede abrir y utilizar en sus proyectos. Entre los campos que uno puede incluir en las tablas están los habituales como textos o números, e incluyen también la posibilidad de referenciar imágenes e incluso, y esto es bien interesante, posiciones geográficas. Es esta dimensión de base de datos geográfica lo que hace tan interesante a esta herramienta.

Otra particularidad de este servicio es que su API interpreta directamente sentencias SQL, el lenguaje más extendido y documentado de introducción de datos en bases de datos, que como veremos nos permitirá fácilmente recuperar e introducir información de forma selectiva.

Crear una tabla con Fusion Tables

El procedimiento para crear una de estas tablas comienza con el login con nuestros datos de usuario en Google Docs. Una vez dentro, pinchamos en “Crear” y a continuación “Tabla (beta)”. Una nueva ventana en el navegador nos permitirá elegir entre tres métodos de creación, unos a través de la importación de datos ya existentes otro a partir de una tabla de datos vacía. Elegimos este último.

Accederemos entonces a un lista de tablas con una nueva, “New table”. Pinchando sobre su nombre entraremos a la página de gestión de ésta, donde podremos editar su nombre, sus columnas e introducir datos manualmente y controlar los que otros usuarios puedan estar subiendo.

Edit > Modify Table Info es la ruta para cambiar el nombre; escribimos el que nos apetezca -“Tests” en este ejemplo-. A continuación, podemos cambiar la configuración de las columnas en Edit > Modify columns. Para este ejemplo sólo dejaremos dos columnas o campos: una de texto, que llamaremos “texto”, y otra de números, que llamaremos “id”. Esta configuración de las columnas sólo puede realizarse al inicio, cuando no hay datos en las tablas.

A continuación tenemos que permitir que la tabla sea visible para el resto de usuarios. Pulsamos en el botón Share de la esquina superior derecha y seleccionamos una de las opciones: Public o Unlisted, según nuestro objetivo.

Antes de continuar con el código capaz de conectar a esta nueva tabla de datos desde Processing para Android, nos hace falta conocer el identificador encriptado de nuestra tabla. Este es una secuencia larga de caracteres que puede encontrarse en File > About con el nombre de “Encrypted id”. Copiamos esa secuencia, puesto que nos servirá para conectar a la tabla desde Processing.

Conectar la tabla con Processing Android

El método que emplearemos para remitir y recibir datos utiliza dos clases que forman parte de la demostración de integración fusion-tables-android. La primera, RequestHandler.java, puede descargarse de aquí, y la segunda, ClientLogin, de aquí. La primera clase se encargará de enviar y recibir apropiadamente los mensajes a Google Fusion Tables, mientras que el segundo se encargará de la secuencia de login en el servicio.

IMPORTANTE: Processing para Android no admite la importación de archivos .java que formen parte de packages. Es necesario eliminar entonces de estos archivos de Google la línea de referencia package com.google.fusiontables… en cada uno de ellos. Para poder seguir empleando tras este cambio será necesario introducirlas en nuestro código como estáticas. Esto lo hacemos escribiendo la palabra static antes de class RequestHandler y class ClientLogin. Esto es:

public static class RequestHandler {...
public static class ClientLogin {...

Antes de continuar, y en tanto que esta app requiere emplear la conexión a internet de nuestro dispositivo, tenemos que indicar al entorno que solicite el permiso de utilizar la conectividad del aparato. Este es un paso que podemos realizar sin salir de Processing: en el menú Android > Sketch permissions seleccionamos INTERNET y ya podremos continuar.

Una vez introducido el código GPL de estas clases en nuestro proyecto, podemos escribir un código como este en nuestro skecth:

import java.net.URLEncoder;
 
String baseUrl, tableId;
String auth;
 
void setup() {
  auth = ClientLogin.authorize("mi_nombre_de_usuario", "mi_password");
 
  baseUrl = "https://www.google.com/fusiontables/api/query";
  tableId = "mi_encrypted_id";  
 
  String select = "?sql=SELECT+texto+FROM+"+tableId+"+WHERE+id='1'";
  String respuesta = RequestHandler.sendHttpRequest(baseUrl+select, "GET", null, null);
  println(respuesta);   
}
 
void draw() {
}
 
void mouseReleased() {
  String insert = "INSERT INTO " + tableId + " (texto,id) " + "VALUES (" + "'texto'" + "," + "'" + frameCount + "'" +")";
  insert = "sql="+URLEncoder.encode(insert);
  Map headers = new HashMap();
  headers.put("Authorization","GoogleLogin auth="+auth);
 
  String respuesta = RequestHandler.sendHttpRequest(baseUrl, "POST", insert, headers);
  println(respuesta);                     
}

El código nos demuestra las tres funciones principales en nuestra conversación de datos con Google Fusion Tables. Vemos en primer lugar que almacenamos un código de autorización en una String auth, que nos permitirá -a nosotros y a los usuarios del app- enviar datos desde nuestro dispositivo a la tabla en la nube cuando queramos. Debemos en el setup obtenerlo con ayuda del método de la clase ClientLogin:

auth = ClientLogin.authorize("mi_nombre_de_usuario", "mi_password");

La segunda operación es la de recibir los datos almacenados en la tabla. Esto implica la escritura de una instrucción “SELECT” para la base de datos, y su envío a Google. Podemos hacer esto con la clase RequestHandler del siguiente modo:

String respuesta = RequestHandler.sendHttpRequest(baseUrl+select, "GET", null, null);

En el caso del código de ejemplo de comunicación que hemos escrito más arriba, esta solicitud de datos sólo ocurre en el arranque del programa, esto es, en el setup(). Por supuesto, ésta podría tener lugar en cualquier momento.

Finalmente, la tercera operación es la de insertar datos nuestros, desde nuestro dispositivo, a la tabla online. La instrucción SQL movilizada en este caso es “INSERT”, que introducimos y codificamos en el código de la forma que sigue:

  String insert = "INSERT INTO " + tableId + " (texto,id) " + "VALUES (" + "'texto'" + "," + "'" + frameCount + "'" +")";
  insert = "sql="+URLEncoder.encode(insert);
  Map headers = new HashMap();
  headers.put("Authorization","GoogleLogin auth="+auth);
 
  String respuesta = RequestHandler.sendHttpRequest(baseUrl, "POST", insert, headers);

En este caso no estamos más que subiendo a la tabla el número de frame (frameCount) cada vez que pulsamos la pantalla del dispositivo.

Clase FusionTable

Toda la parte de código para Processing expuesta hasta ahora se puede sintetizar en una clase, FusionTable. Con ésta se hace muy sencillo recibir y subir datos de una tabla creada en Google Fusion Tables.

Veamos paso a paso cómo utilizar esta clase, que copiamos más abajo.

1. Declaración

FusionTable ft;

2. Inicialización

// En setup()
ft = new FusionTable("ENCRYPTED_IP_DE_LA_TABLA");

3. Uso

  String respuesta; // string para recibir los datos de las respuestas del servidor 
 
 
  // Ejemplo solicitud del campo "id" de aquellos registros 
  // en los que el campo "texto" tenga como valor la palabra "Juan":
 
  respuesta = ft.select("id","texto='Juan'");
  lineas = split(respuesta, "\n");
  println(lineas);
 
 
  // Ejemplo solicitud del listado del campo "texto" para todos
  // los registros de la tabla
 
  respuesta = ft.selectAll("texto");
  String[] lineas = split(respuesta, "\n");
  println(lineas);
 
 
  // Ejemplo inserción de una fila de valores de la tabla
  // con dos campos, "texto" e "id"
 
  respuesta = ft.insert("(texto,id)", "('prueba', 100)");
  println(respuesta);
 
 
  // Ejemplo de actualización de la entrada de la tabla
  // en la que el campo "id" es igual a 101
 
  respuesta = ft.update("texto='prueba'", "id=101");
  println(respuesta);

El código de la clase sigue a continuación. Es necesario recordar que sigue siendo imprescidible descargar y modificar el código de las clases RequestHandler y ClientLogin proporcionadas por Google tal y como hemos explicado más arriba.

import java.net.URLEncoder;
 
class FusionTable {
 
  String baseUrl, tableId;
  String auth;
  boolean querying;
 
  FusionTable(String tableId_) {  
    auth = ClientLogin.authorize("NOMBRE_DE_USUARIO_GOOGLE_DOCS", "PASSWORD");  
    tableId = tableId_; 
    baseUrl = "https://www.google.com/fusiontables/api/query";
  }
 
  String select(String fromField, String condition) {
    querying = true;
    String query = "SELECT " + fromField + " FROM " + tableId + " WHERE " + condition;
    println(query);
    query = "?sql=" + URLEncoder.encode(query);
    String respuesta = RequestHandler.sendHttpRequest(baseUrl+query, "GET", null, null);
    querying = false;
    return respuesta;
  }
 
  String selectAll(String fromField) {
    querying = true;    
    String query = "SELECT " + fromField + " FROM " + tableId;
    println(query);
    query = "?sql=" + URLEncoder.encode(query);
    String respuesta = RequestHandler.sendHttpRequest(baseUrl+query, "GET", null, null);
    querying = false;    
    return respuesta;
  }  
 
  String selectGeoCircle(String fromField, String locationField, float lat, float lon, float radio) {
    querying = true;
    String query = "SELECT " + fromField + " FROM " + tableId + " WHERE ST_INTERSECTS (" + locationField + ", CIRCLE(LATLNG(" + lat + "," + lon +"),"+ radio +"))";
    println(query);
    query = "?sql=" + URLEncoder.encode(query);
    String respuesta = RequestHandler.sendHttpRequest(baseUrl+query, "GET", null, null);
    querying = false;    
    return respuesta;
  }
 
  String selectCustom(String query_) {
    querying = true;
    String query = "?sql=" + URLEncoder.encode(query);
    String respuesta = RequestHandler.sendHttpRequest(baseUrl+query, "GET", null, null);
    querying = false;    
    return respuesta;
  }
 
  String insert(String fields, String values) {
    querying = true;
    String query = "INSERT INTO " + tableId + " " + fields + " VALUES " + values;
    println(query);
    query = "sql=" + URLEncoder.encode(query);
    Map headers = new HashMap();
    headers.put("Authorization", "GoogleLogin auth="+auth);
 
    String respuesta = RequestHandler.sendHttpRequest(baseUrl, "POST", query, headers);
    querying = false;    
    return respuesta; // ROWID si todo va bien
  }
 
 
  // Sólo hace update del primer registro que encuentra
  String update(String busqueda, String cambio) {
    querying = true;    
    String query = "SELECT ROWID FROM " + tableId + " WHERE " + busqueda;
    println(query);
    query = "?sql=" + URLEncoder.encode(query);
    String respuesta = RequestHandler.sendHttpRequest(baseUrl+query, "GET", null, null);    
    if (respuesta != null) {
      String[] lineas = split(respuesta, "\n");
      if (lineas.length > 1) {
        String rowid = lineas[1];
        query = "UPDATE " + tableId + " SET " + cambio + " WHERE ROWID = '" + rowid + "'";
        query = "sql=" + URLEncoder.encode(query);
        Map headers = new HashMap();
        headers.put("Authorization", "GoogleLogin auth="+auth);      
        respuesta = RequestHandler.sendHttpRequest(baseUrl, "POST", query, headers);
        querying = false;      
        return respuesta; // OK si todo va bien
      }
      else {
        querying = false;      
        return null;
      }
    }
    else {
      querying = false;      
      return null;
    }
  }
 
  String delete(String busqueda) {
    querying = true;    
    String query = "SELECT ROWID FROM " + tableId + " WHERE " + busqueda;
    println(query);
    query = "?sql=" + URLEncoder.encode(query);
    String respuesta = RequestHandler.sendHttpRequest(baseUrl+query, "GET", null, null);    
    if (respuesta != null) {
      String[] lineas = split(respuesta, "\n");
      int cuenta = 0;
      if (lineas.length > 1) {
        for (int n = 1; n < lineas.length; n++) {
          String rowid = lineas[n];
          query = "DELETE FROM " + tableId + " { WHERE ROWID = '" + rowid + "'}";
          query = "sql=" + URLEncoder.encode(query);
          Map headers = new HashMap();
          headers.put("Authorization", "GoogleLogin auth="+auth);      
          respuesta = RequestHandler.sendHttpRequest(baseUrl, "POST", query, headers);
          println(respuesta);
          cuenta++;
        }
        querying = false;      
        return cuenta + " filas borradas";
      }
      else {
        querying = false;      
        return null;
      }
    }
    else {
      querying = false;      
      return null;
    }
  }
}

Ejemplo completo

Un ejemplo completo de uso de estas clases puede descargarse a continuación. En el archivo se encuentran comprimidas las tres clases puestas en juego en este tutorial: FusionTable, RequestHandler y ClientLogin. El ejemplo sube y recibe datos a una tabla previamente abierta y configurada en Google Fusion Tables.

Descarga el ejemplo completo aquí: google_fusion_tables.zip