Google Matrix API

Índice

Google nos permite, a partir de una serie de puntos, obtener las distancias y tiempos de viaje previstos para llegar hasta ellos desde una ubicación fija o desde la ubicación obtenida a partir de la posición del dispositivo móvil mediante la API para cálculo de distancias.

La API tiene una serie de limitacíones de número de consultas, pero salvo que pongamos un huevo y peguemos el pelotazo, en principio será más que suficiente para nuestros intereses.

Podemos optar por usar la API o simlemente hacer una petición mediante http(s) y trabajar sobre los resultados para extraer la información que queramos, que será precisamente lo que haremos aquí.

La API está bastante bien explicada en la propia página de Google. Ahí tenemos cómo montar el archivo, hacer la pertición y cómo recibir como respuesta un Json o un XML. Optaremos por la opción recomendada, Json, sobretodo por la ligereza del mismo, lo que al final siempre supone un menor consumo de datos, importante en caso de que queramos usar este servicio en una App de Android (origen de todo esto).

Primero montamos la petición a partir de nuestros datos:

final static String PETITION_INI = "https://maps.googleapis.com/maps/api/distancematrix/json?origins=";
final static String PETITION_MID = "&destinations=";
final static String PETITION_END = "&mode=walking&language=es-ES&sensor=false";
final static String ORIGIN = "39.5457444,-0.390079";
final static String[] DESTINATIONS = { "39.896844,-0.10079", "39.5957444,-0.371079", "40.296844,0.10079", "39.977844,-0.00079" } ;

private static String formarPeticion()
{
    StringBuffer sb = new StringBuffer();
    sb.append(PETICION_INI).append(ORIGIN).append(PETITION_MID);
    for (String s : DESTINATIONS)
        sb.append(s).append("|");
    sb.deleteCharAt(sb.length() - 1).append(PETITION_END);
    System.out.println(sb.toString());

    return sb.toString();
}

Se ha usado un StringBuffer por que concatenamos dentro de un bucle. En caso contrario no sería necesario, con sumar cadenas es suficiente. La JVM lo detecta y genera un StringBuffer cuando se encuentra con ello (una distinta en cada línea de código eso sí). Para verlo, poned un breakpoint y dadle al bichito.

La petición que se ha generado tiene un único punto de origen, pero puede usarse con todos los puntos de origen que queramos, siempre eso sí cumpliendo las limitaciones de google. Una vez montada la petición, la enviamos a google:

private static String obtenerRespuesta(String URL)
{
    try (BufferedReader br = new BufferedReader(
            new InputStreamReader(new URL(URL).openConnection().getInputStream())))
    {
        int charRead = 0;
        char[] buffer = new char[1024];
        StringBuilder sb = new StringBuilder();

        while ((charRead = br.read(buffer)) > 0)
            sb.append(buffer, 0, charRead);

        return sb.toString();
    } catch (Exception ex)
    {
        ex.printStackTrace();
    }
    return null;
}

Podéis tener la tentación, como yo tuve, de usar una opción más sencilla y extremadamente más elegante y legible:

private static String obtenerRespuesta_available(String URL)
{
    try (InputStream stream = new URL(URL).openStream())
    {
        // stream.available() da problemas. Muy bien explicado en
        // http://stackoverflow.com/questions/6160432/java-inputstream-reading-problem
        byte[] array = new byte[stream.available()];
        stream.read(array);
        return new String(array);
    } catch (Exception ex)
    {
        ex.printStackTrace();
    }
    return null;
}

Como está explicado en el comentario dentro del código, nos puede dar problemas al leer antes de haber recibido todos los dato debido a que el hilo no se bloquea hasta que finalice la recepción de los mismos (simplemente establecemos la conexión; no olvidarse que la java.io sí bloquea en un proceso de lectura normal, al contrario que la java.nio).

Y ya solo nos queda procesar. En mi caso necesito obtener la distancia. Usando expresiones regulares:

private static void extraerDistancias(String json)
{
    Pattern pattern = Pattern.compile(": \"(.*?) km\"");
    Matcher matcher = pattern.matcher(json);
    while (matcher.find())
        System.out.println(matcher.group(1));
}

Todo esto podría haberse hecho parseando desde el Json al objeto que fuese necesario, usando por ejemplo la librería GSon. Eso lo dejaremos para otro día. De todas formas, cuando queramos algo muy específico y no necesitemos toda la información que google nos devuelve, realizar todo el mapeo objeto-relacional es una tarea lo suficientemente exigente como para desperdiciar después todo lo creado, y con un proceso más ligero como el descrito aquí arriba es más que suficiente; así el rendimiento de nuestra aplicación no se verá especialmente penalizado.

Enlaces