1. Descripción general
Los mapas pueden ser una herramienta muy potente para visualizar los patrones de un conjunto de datos que se relacionan con la ubicación de alguna manera. Esta relación podría ser el nombre de un lugar, un valor específico de latitud y longitud, o el nombre de un área que tiene un límite específico, como un distrito censal o un código postal.
Cuando estos conjuntos de datos son muy grandes, puede ser difícil consultarlos y visualizarlos con herramientas convencionales. Si usas Google BigQuery para consultar los datos y las APIs de Google Maps para construir la consulta y visualizar el resultado, puedes explorar rápidamente los patrones geográficos en tus datos con muy poca configuración o codificación, y sin tener que administrar un sistema para almacenar conjuntos de datos muy grandes.
Qué compilarás
En este codelab, escribirás y ejecutarás algunas consultas que demuestran cómo proporcionar estadísticas basadas en la ubicación sobre conjuntos de datos públicos muy grandes con BigQuery. También compilarás una página web que cargue un mapa con la API de JavaScript de Google Maps Platform y, luego, ejecute y visualice consultas espaciales en los mismos conjuntos de datos públicos muy grandes con la biblioteca cliente de las APIs de Google para JavaScript y la API de BigQuery.
Qué aprenderás
- Cómo consultar conjuntos de datos de ubicación a escala de petabytes en segundos con BigQuery, mediante consultas en SQL, funciones definidas por el usuario y la API de BigQuery
- Cómo usar Google Maps Platform para agregar un mapa de Google Maps a una página web y permitir que los usuarios dibujen formas en él
- Cómo visualizar consultas en conjuntos de datos grandes en un mapa de Google, como en la imagen de ejemplo a continuación, que muestra la densidad de las ubicaciones de descenso de taxis en 2016 a partir de viajes que comenzaron en la cuadra alrededor del Empire State Building
Requisitos
- Conocimientos básicos de HTML, CSS, JavaScript, SQL y las Herramientas para desarrolladores de Chrome
- Un navegador web moderno, como las versiones recientes de Chrome, Firefox, Safari o Edge
- El editor de texto o IDE que prefieras
La tecnología
BigQuery
BigQuery es el servicio de análisis de datos de Google para conjuntos de datos muy grandes. Tiene una API de REST y admite consultas escritas en SQL. Si tienes datos con valores de latitud y longitud, puedes usarlos para consultar tus datos por ubicación. La ventaja es que puedes explorar visualmente conjuntos de datos muy grandes para observar los patrones sin tener que administrar ninguna infraestructura de servidor o base de datos. Puedes obtener respuestas a tus preguntas en unos segundos, sin importar qué tan grandes sean tus tablas, gracias a la gran escalabilidad y la infraestructura administrada de BigQuery.
Google Maps Platform
Google Maps Platform proporciona acceso programático a los datos de mapas, lugares y rutas de Google. Actualmente, más de 2 millones de sitios web y apps la usan para proporcionar mapas integrados y búsquedas basadas en la ubicación a sus usuarios.
La capa de dibujo de la API de Google Maps JavaScript Platform te permite dibujar formas en el mapa. Estos se pueden convertir en entradas para ejecutar consultas en tablas de BigQuery que tienen valores de latitud y longitud almacenados en columnas.
Para comenzar, necesitas un proyecto de Google Cloud Platform con las APIs de BigQuery y Maps habilitadas.
2. Cómo prepararte
Cuenta de Google
Si aún no tienes una Cuenta de Google (Gmail o Google Apps), debes crear una.
Crea un proyecto
Accede a Google Cloud Platform Console ( console.cloud.google.com) y crea un proyecto nuevo. En la parte superior de la pantalla, hay un menú desplegable Proyecto:
Una vez que hagas clic en este menú desplegable del proyecto, aparecerá un elemento de menú que te permitirá crear un proyecto nuevo:
En el cuadro que dice "Ingresa un nombre nuevo para tu proyecto", ingresa un nombre para tu proyecto nuevo, por ejemplo, "Codelab de BigQuery":
Se generará un ID del proyecto. El ID del proyecto es un nombre único en todos los proyectos de Google Cloud. Recuerda el ID del proyecto, ya que lo usarás más adelante. El nombre anterior ya se encuentra en uso y no lo podrás usar. Inserta tu propio ID del proyecto donde veas YOUR_PROJECT_ID en este codelab.
Habilitar facturación
Para registrarte en BigQuery, usa el proyecto que seleccionaste o creaste en el paso anterior. La facturación debe estar habilitada en este proyecto. Una vez que se habilite la facturación, podrás habilitar la API de BigQuery.
La manera en la que habilitas la facturación depende de si creas un proyecto nuevo o si vuelves a habilitar la facturación para un proyecto existente.
Google ofrece una prueba gratuita de 12 meses con un valor de hasta USD 300 de uso de Google Cloud Platform que puedes usar para este codelab. Obtén más detalles en https://cloud.google.com/free/.
Proyectos nuevos
Cuando creas un proyecto nuevo, se te solicita que elijas qué cuenta de facturación quieres vincular con el proyecto. Si solo tienes una cuenta de facturación, esa cuenta se vincula de manera automática al proyecto.
Si no tienes una cuenta de facturación, debes crear una y habilitar la facturación para el proyecto antes de poder usar varias de las características de Google Cloud Platform. Si quieres crear una cuenta de facturación nueva y habilitar la facturación para tu proyecto, sigue las instrucciones en Crea una cuenta de facturación nueva.
Proyectos existentes
Si tienes un proyecto en el que inhabilitaste la facturación, puedes volver a habilitarla si sigues los pasos a continuación:
- Ve a Cloud Platform Console.
- Desde la lista de proyectos, selecciona el proyecto en el que se volverá a habilitar la facturación.
- Abre el menú lateral izquierdo de la consola y selecciona Facturación
. Se te pedirá que selecciones una cuenta de facturación.
- Haz clic en Establecer cuenta.
Crea una cuenta de facturación nueva
Para crear una cuenta de facturación nueva, haz lo siguiente:
- Ve a la consola de Cloud Platform y accede con tu cuenta. Si no tienes una, regístrate.
- Abre el menú lateral izquierdo de la consola y selecciona Facturación
- Haz clic en el botón Cuenta de facturación nueva (Ten en cuenta que, si esta no es tu primera cuenta de facturación, primero deberás abrir la lista de cuentas de facturación. Para ello, haz clic en el nombre de tu cuenta de facturación existente cerca de la parte superior de la página y, luego, haz clic en Administrar cuentas de facturación).
- Ingresa el nombre de la cuenta de facturación y tus datos de facturación. Las opciones que ves dependen del país de tu dirección de facturación. Ten en cuenta que, para las cuentas de Estados Unidos, no puedes cambiar el estado fiscal después de que se crea la cuenta.
- Haz clic en Enviar y habilitar la facturación.
De forma predeterminada, la persona que crea la cuenta de facturación es un administrador de facturación de esa cuenta.
Para obtener información sobre cómo verificar cuentas bancarias y agregar métodos de pago de respaldo, consulta Agrega, quita o actualiza una forma de pago.
Habilite la API de BigQuery
Para habilitar la API de BigQuery en tu proyecto, ve a la página de Marketplace de la API de BigQuery en la consola y haz clic en el botón azul "Habilitar".
3. Cómo consultar datos de ubicación en BigQuery
Existen tres formas de consultar los datos de ubicación almacenados como valores de latitud y longitud en BigQuery.
- Consultas de rectángulo: Especifican el área de interés como una consulta que selecciona todas las filas dentro de un rango de latitud y longitud mínimo y máximo.
- Consultas de radio: Especifica el área de interés calculando un círculo alrededor de un punto con la fórmula de Haversine y las funciones matemáticas para modelar la forma de la Tierra.
- Consultas de polígonos: Especifica una forma personalizada y usa una función definida por el usuario para expresar la lógica de punto en polígono necesaria para probar si la latitud y la longitud de cada fila se encuentran dentro de la forma.
Para comenzar, usa el Editor de consultas en la sección BigQuery de la consola de Google Cloud Platform para ejecutar las siguientes consultas en los datos de taxis de NYC.
SQL estándar vs. SQL heredado
BigQuery admite dos versiones de SQL: SQL heredado y SQL estándar. Este último es el estándar ANSI 2011. Para los fines de este instructivo, usaremos SQL estándar porque tiene un mejor cumplimiento de estándares.
Si deseas ejecutar SQL heredado en el editor de BigQuery, puedes hacerlo de la siguiente manera:
- Haz clic en el botón "Más".
- Selecciona "Configuración de la consulta" en el menú desplegable.
- En “Dialecto de SQL”, selecciona el botón de selección “Heredado”.
- Haz clic en el botón “Guardar”.
Consultas de rectángulo
Las consultas de rectángulo son bastante sencillas de construir en BigQuery. Solo debes agregar una cláusula WHERE
que limite los resultados devueltos a aquellos con ubicaciones entre los valores mínimos y máximos de latitud y longitud.
Prueba el siguiente ejemplo en la consola de BigQuery. Esta consulta busca algunas estadísticas promedio de viajes para los viajes que comenzaron en un área rectangular que contiene el centro y el bajo Manhattan. Puedes probar dos ubicaciones diferentes. Quita la marca de comentario de la segunda cláusula WHERE
para ejecutar la consulta sobre los viajes que comenzaron en el aeropuerto JFK.
SELECT
ROUND(AVG(tip_amount),2) as avg_tip,
ROUND(AVG(fare_amount),2) as avg_fare,
ROUND(AVG(trip_distance),2) as avg_distance,
ROUND(AVG(tip_proportion),2) as avg_tip_pc,
ROUND(AVG(fare_per_mile),2) as avg_fare_mile FROM
(SELECT
pickup_latitude, pickup_longitude, tip_amount, fare_amount, trip_distance, (tip_amount / fare_amount)*100.0 as tip_proportion, fare_amount / trip_distance as fare_per_mile
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`
WHERE trip_distance > 0.01 AND fare_amount <100 AND payment_type = "1" AND fare_amount > 0
)
--Manhattan
WHERE pickup_latitude < 40.7679 AND pickup_latitude > 40.7000 AND pickup_longitude < -73.97 and pickup_longitude > -74.01
--JFK
--WHERE pickup_latitude < 40.654626 AND pickup_latitude > 40.639547 AND pickup_longitude < -73.771497 and pickup_longitude > -73.793755
Los resultados de las dos búsquedas muestran que hay grandes diferencias en la distancia, la tarifa y la propina promedio de los viajes para las recogidas en las dos ubicaciones.
Manhattan
avg_tip | avg_fare | avg_distance | avg_tip_pc | avg_fare_mile |
2.52 | 12.03 | 9.97 | 22.39 | 5.97 |
JFK
avg_tip | avg_fare | avg_distance | avg_tip_pc | avg_fare_mile |
9.22 | 48.49 | 41.19 | 22.48 | 4.36 |
Consultas de radio
Las consultas de radio también son fáciles de crear en SQL si conoces un poco de matemáticas. Con las funciones matemáticas del SQL heredado de BigQuery, puedes crear una consulta en SQL con la fórmula de Haversine, que aproxima un área circular o una gorra esférica en la superficie de la Tierra.
A continuación, se muestra un ejemplo de una instrucción de SQL de BigQuery para una consulta de círculo centrada en 40.73943, -73.99585
con un radio de 0.1 km.
Utiliza un valor constante de 111.045 kilómetros para aproximar la distancia que representa un grado.
Esto se basa en un ejemplo que se encuentra en http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/:
SELECT pickup_latitude, pickup_longitude,
(111.045 * DEGREES(
ACOS(
COS( RADIANS(40.73943) ) *
COS( RADIANS( pickup_latitude ) ) *
COS(
RADIANS( -73.99585 ) -
RADIANS( pickup_longitude )
) +
SIN( RADIANS(40.73943) ) *
SIN( RADIANS( pickup_latitude ) )
)
)
) AS distance FROM `project.dataset.tableName`
HAVING distance < 0.1
El SQL de la fórmula de Haversine parece complicado, pero lo único que debes hacer es conectar la coordenada del centro del círculo, el radio y los nombres del proyecto, el conjunto de datos y la tabla de BigQuery.
A continuación, se muestra un ejemplo de una consulta que calcula algunas estadísticas promedio de los viajes para las recogidas que se realizan a menos de 100 m del Empire State Building. Copia y pega este código en la consola web de BigQuery para ver los resultados. Cambia la latitud y la longitud para compararlas con otras áreas, como la ubicación en el Bronx.
#standardSQL
CREATE TEMPORARY FUNCTION Degrees(radians FLOAT64) RETURNS FLOAT64 AS
(
(radians*180)/(22/7)
);
CREATE TEMPORARY FUNCTION Radians(degrees FLOAT64) AS (
(degrees*(22/7))/180
);
CREATE TEMPORARY FUNCTION DistanceKm(lat FLOAT64, lon FLOAT64, lat1 FLOAT64, lon1 FLOAT64) AS (
Degrees(
ACOS(
COS( Radians(lat1) ) *
COS( Radians(lat) ) *
COS( Radians(lon1 ) -
Radians( lon ) ) +
SIN( Radians(lat1) ) *
SIN( Radians( lat ) )
)
) * 111.045
);
SELECT
ROUND(AVG(tip_amount),2) as avg_tip,
ROUND(AVG(fare_amount),2) as avg_fare,
ROUND(AVG(trip_distance),2) as avg_distance,
ROUND(AVG(tip_proportion), 2) as avg_tip_pc,
ROUND(AVG(fare_per_mile),2) as avg_fare_mile
FROM
-- EMPIRE STATE BLDG 40.748459, -73.985731
-- BRONX 40.895597, -73.856085
(SELECT pickup_latitude, pickup_longitude, tip_amount, fare_amount, trip_distance, tip_amount/fare_amount*100 as tip_proportion, fare_amount / trip_distance as fare_per_mile, DistanceKm(pickup_latitude, pickup_longitude, 40.748459, -73.985731)
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`
WHERE
DistanceKm(pickup_latitude, pickup_longitude, 40.748459, -73.985731) < 0.1
AND fare_amount > 0 and trip_distance > 0
)
WHERE fare_amount < 100
A continuación, se muestran los resultados de la búsqueda. Puedes ver que hay grandes diferencias en la propina promedio, la tarifa, la distancia del viaje, el tamaño proporcional de la propina en relación con la tarifa y la tarifa promedio por milla recorrida.
Edificio Empire State:
avg_tip | avg_fare | avg_distance | avg_tip_pc | avg_fare_mile |
1.17 | 11.08 | 45.28 | 10.53 | 6.42 |
Bronx
avg_tip | avg_fare | avg_distance | avg_tip_pc | avg_fare_mile |
0.52 | 17.63 | 4.75 | 4.74 | 10.9 |
Consultas de polígonos
SQL no admite consultas con formas arbitrarias que no sean rectángulos y círculos. BigQuery no tiene ningún tipo de datos de geometría nativo ni índice espacial, por lo que, para ejecutar consultas con formas de polígonos, necesitas un enfoque diferente al de las consultas SQL sencillas. Un enfoque es definir una función de geometría en JavaScript y ejecutarla como una función definida por el usuario (UDF) en BigQuery.
Muchas operaciones de geometría se pueden escribir en JavaScript, por lo que es fácil tomar una y ejecutarla en una tabla de BigQuery que contenga valores de latitud y longitud. Debes pasar el polígono personalizado a través de una UDF y realizar una prueba en cada fila, devolviendo solo las filas en las que la latitud y la longitud se encuentran dentro del polígono. Obtén más información sobre las UDF en la referencia de BigQuery.
Algoritmo de punto dentro del polígono
Hay muchas formas de calcular si un punto se encuentra dentro de un polígono en JavaScript. Aquí tienes una que es un puerto de C de una implementación conocida que usa un algoritmo de trazado de rayos para determinar si un punto está dentro o fuera de un polígono contando la cantidad de veces que una línea infinitamente larga cruza el límite de la forma. Solo ocupa unas pocas líneas de código:
function pointInPoly(nvert, vertx, verty, testx, testy){
var i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
Cómo portar a JavaScript
La versión de JavaScript de este algoritmo se ve de la siguiente manera:
/* This function includes a port of C code to calculate point in polygon
* see http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for license
*/
function pointInPoly(polygon, point){
// Convert a JSON poly into two arrays and a vertex count.
let vertx = [],
verty = [],
nvert = 0,
testx = point[0],
testy = point[1];
for (let coord of polygon){
vertx[nvert] = coord[0];
verty[nvert] = coord[1];
nvert ++;
}
// The rest of this function is the ported implementation.
for (let i = 0, let j = nvert - 1; i < nvert; j = i++) {
if ( ((verty[i] > testy) != (verty[j] > testy)) &&
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
Cuando se usa SQL estándar en BigQuery, el enfoque de UDF requiere solo una instrucción, pero la UDF debe definirse como una función temporal en la instrucción. A continuación, se muestra un ejemplo. Pega la siguiente instrucción de SQL en la ventana del Editor de consultas.
CREATE TEMPORARY FUNCTION pointInPolygon(latitude FLOAT64, longitude FLOAT64)
RETURNS BOOL LANGUAGE js AS """
let polygon=[[-73.98925602436066,40.743249676056955],[-73.98836016654968,40.74280666503313],[-73.98915946483612,40.741676770346295],[-73.98967981338501,40.74191656974406]];
let vertx = [],
verty = [],
nvert = 0,
testx = longitude,
testy = latitude,
c = false,
j = nvert - 1;
for (let coord of polygon){
vertx[nvert] = coord[0];
verty[nvert] = coord[1];
nvert ++;
}
// The rest of this function is the ported implementation.
for (let i = 0; i < nvert; j = i++) {
if ( ((verty[i] > testy) != (verty[j] > testy)) &&
(testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ) {
c = !c;
}
}
return c;
""";
SELECT pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, pickup_datetime
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016`
WHERE pointInPolygon(pickup_latitude, pickup_longitude) = TRUE
AND (pickup_datetime BETWEEN CAST("2016-01-01 00:00:01" AS DATETIME) AND CAST("2016-02-28 23:59:59" AS DATETIME))
LIMIT 1000
¡Felicitaciones!
Ahora ejecutaste tres tipos de consultas espaciales con BigQuery. Como viste, la ubicación marca una gran diferencia en los datos de resultados de las consultas en este conjunto de datos, pero, a menos que adivines dónde ejecutar tus consultas, es difícil descubrir patrones espaciales ad hoc solo con consultas de SQL.
Si tan solo pudiéramos visualizar los datos en un mapa y explorarlos definiendo áreas de interés arbitrarias. Con las APIs de Google Maps, puedes hacer precisamente eso. Primero, debes habilitar la API de Maps, configurar una página web simple que se ejecute en tu máquina local y comenzar a usar la API de BigQuery para enviar consultas desde tu página web.
4. Trabaja con las APIs de Google Maps
Después de ejecutar algunas consultas espaciales simples, el siguiente paso es visualizar el resultado para ver los patrones. Para ello, habilitarás la API de Maps, compilarás una página web que envíe consultas desde un mapa a BigQuery y, luego, dibujarás los resultados en el mapa.
Habilita la API de Maps JavaScript
Para este codelab, deberás habilitar la API de Maps JavaScript de Google Maps Platform en tu proyecto. Para ello, haz lo siguiente:
- En Google Cloud Platform Console, ve a Marketplace.
- En Marketplace, busca "API de Maps JavaScript".
- Haz clic en la tarjeta de la API de Maps JavaScript en los resultados de la búsqueda.
- Haz clic en el botón “Habilitar”.
Genera una clave de API
Para realizar solicitudes a Google Maps Platform, deberás generar una clave de API y enviarla con todas las solicitudes. Para generar una clave de API, haz lo siguiente:
- En la consola de Google Cloud Platform, haz clic en el menú de hamburguesas para abrir la navegación de la izquierda.
- Selecciona "APIs & Service" > "Credentials".
- Haz clic en el botón “Crear credencial” y, luego, selecciona “Clave de API”.
- Copia la nueva clave de API
Descarga el código y configura un servidor web
Haz clic en el siguiente botón para descargar todo el código de este codelab:
Descomprime el archivo zip descargado. Con esto, se descomprimirá una carpeta raíz (bigquery
) que contiene una carpeta para cada paso de este codelab, junto con todos los recursos que necesitarás.
Las carpetas stepN
contienen el estado final deseado de cada paso de este codelab. Están disponibles como referencia. Haremos todo nuestro trabajo de codificación en el directorio llamado work
.
Configura un servidor web local
Si bien puedes usar tu propio servidor web, este codelab está diseñado para funcionar bien con el servidor web de Chrome. Si aún no tienes esa app instalada, puedes instalarla desde Chrome Web Store.
Una vez instalada, abre la app. En Chrome, puedes hacerlo de la siguiente manera:
- Abre Chrome.
- En la barra de direcciones de la parte superior, escribe chrome://apps.
- Presione Intro
- En la ventana que se abre, haz clic en el ícono del servidor web. También puedes hacer clic con el botón derecho en una app para abrirla en una pestaña normal o fijada, en pantalla completa o en una ventana nueva
. Luego, verás este diálogo, que te permitirá configurar tu servidor web local:
- Haz clic en “ELEGIR CARPETA” y selecciona la ubicación en la que descargaste los archivos de muestra del codelab.
- En la sección "Opciones", marca la casilla junto a "Mostrar index.html automáticamente":
- Desliza el interruptor etiquetado como "Web Server: STARTED" hacia la izquierda y, luego, hacia la derecha para detener y reiniciar el servidor web.
5. Cómo cargar el mapa y las herramientas de dibujo
Crea una página de mapa básica
Comienza con una página HTML simple que cargue un mapa de Google Maps con la API de Maps JavaScript y algunas líneas de JavaScript. El código del ejemplo de mapa simple de Google Maps Platform es un excelente punto de partida. Se reproduce aquí para que lo copies y pegues en el editor de texto o IDE que prefieras, o bien puedes encontrarlo abriendo index.html
desde el repositorio que descargaste.
- Copia
index.html
en la carpetawork
de tu copia local del repo. - Copia la carpeta img/ en la carpeta work/ de tu copia local del repo.
- Abre work/
index.html
en tu editor de texto o IDE. - Reemplaza
YOUR_API_KEY
por la clave de API que creaste antes.
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
async defer></script>
- En tu navegador, abre
localhost:<port>/work
, dondeport
es el número de puerto especificado en la configuración de tu servidor web local. El puerto predeterminado es8887
. Deberías ver tus primeros mapas.
Si recibes un mensaje de error en el navegador, verifica que tu clave de API sea correcta y que tu servidor web local esté activo.
Cómo cambiar la ubicación predeterminada y el nivel de zoom
El código que establece la ubicación y el nivel de zoom se encuentra en las líneas 27 y 28 de index.html, y actualmente está centrado en Sídney, Australia:
<script>
let map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}
</script>
En este instructivo, trabajarás con los datos de viajes en taxi de BigQuery para Nueva York, por lo que, a continuación, cambiarás el código de inicialización del mapa para centrarlo en una ubicación de la ciudad de Nueva York con un nivel de zoom adecuado (13 o 14 deberían funcionar bien).
Para ello, actualiza el bloque de código anterior al siguiente para centrar el mapa en el Empire State Building y ajustar el nivel de zoom a 14:
<script>
let map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.7484405, lng: -73.9878531},
zoom: 14
});
}
</script>
A continuación, vuelve a cargar el mapa en tu navegador para ver los resultados.
Carga las bibliotecas de dibujo y visualización
Para agregar capacidades de dibujo a tu mapa, cambiarás la secuencia de comandos que carga la API de Maps JavaScript agregando un parámetro opcional que le indica a Google Maps Platform que habilite la biblioteca de Drawing.
En este codelab, también se usa HeatmapLayer
, por lo que también actualizarás la secuencia de comandos para solicitar la biblioteca de visualización. Para ello, agrega el parámetro libraries
y especifica las bibliotecas visualization
y drawing
como valores separados por comas, p. ej., libraries=
visualization,drawing
.
Se verá de la siguiente manera:
<script src='http://maps.googleapis.com/maps/api/js?libraries=visualization,drawing&callback=initMap&key=YOUR_API_KEY' async defer></script>
Agrega DrawingManager
Para usar formas dibujadas por el usuario como entrada para una búsqueda, agrega DrawingManager
a tu mapa con las herramientas Circle
, Rectangle
y Polygon
habilitadas.
Es una buena idea colocar todo el código de configuración de DrawingManager
en una función nueva, por lo que, en tu copia de index.html, haz lo siguiente:
- Agrega una función llamada
setUpDrawingTools()
con el siguiente código para crear elDrawingManager
y establecer su propiedadmap
para hacer referencia al objeto del mapa en la página.
Las opciones que se pasan a google.maps.drawing.DrawingManager(options)
establecen el tipo de dibujo de forma predeterminado y las opciones de visualización para las formas dibujadas. Para seleccionar áreas del mapa y enviarlas como consultas, las formas deben tener una opacidad de cero. Para obtener más información sobre las opciones disponibles, consulta Opciones de DrawingManager.
function setUpDrawingTools() {
// Initialize drawing manager
drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.CIRCLE,
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_LEFT,
drawingModes: [
google.maps.drawing.OverlayType.CIRCLE,
google.maps.drawing.OverlayType.POLYGON,
google.maps.drawing.OverlayType.RECTANGLE
]
},
circleOptions: {
fillOpacity: 0
},
polygonOptions: {
fillOpacity: 0
},
rectangleOptions: {
fillOpacity: 0
}
});
drawingManager.setMap(map);
}
- Llama a
setUpDrawingTools()
en tu funcióninitMap()
después de que se cree el objeto del mapa.
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
zoom: 12
});
setUpDrawingTools();
}
- Vuelve a cargar index.html y verifica que las herramientas de dibujo estén visibles. También verifica que puedas usarlos para dibujar círculos, rectángulos y formas de polígonos.
Puedes hacer clic y arrastrar para dibujar círculos y rectángulos, pero los polígonos deben dibujarse haciendo clic en cada vértice y haciendo doble clic para terminar la forma.
Cómo controlar eventos de dibujo
Necesitas código para controlar los eventos que se activan cuando un usuario termina de dibujar una forma, al igual que necesitas las coordenadas de las formas dibujadas para construir consultas SQL.
Agregaremos código para esto en un paso posterior, pero, por ahora, crearemos tres controladores de eventos vacíos para controlar los eventos rectanglecomplete
, circlecomplete
y polygoncomplete
. En esta etapa, los controladores no tienen que ejecutar ningún código.
Agrega lo siguiente a la parte inferior de tu función setUpDrawingTools()
:
drawingManager.addListener('rectanglecomplete', rectangle => {
// We will add code here in a later step.
});
drawingManager.addListener('circlecomplete', circle => {
// We will add code here in a later step.
});
drawingManager.addListener('polygoncomplete', polygon => {
// We will add code here in a later step.
});
Puedes encontrar un ejemplo funcional de este código en tu copia local del repositorio, en la carpeta step2
: step2/map.html.
6. Usa la API del cliente de BigQuery
La API de cliente de Google BigQuery te ayudará a evitar escribir una gran cantidad de código estándar necesario para compilar las solicitudes, analizar las respuestas y controlar la autenticación. En este codelab, se usa la API de BigQuery a través de la biblioteca cliente de las APIs de Google para JavaScript, ya que desarrollaremos una aplicación basada en el navegador.
A continuación, agregarás código para cargar esta API en una página web y usarla para interactuar con BigQuery.
Agrega la API de Google Client para JavaScript
Usarás la API de cliente de Google para JavaScript para ejecutar consultas en BigQuery. En tu copia de index.html
(en tu carpeta work
), carga la API con una etiqueta <script>
como esta. Coloca la etiqueta inmediatamente debajo de la etiqueta <script>
que carga la API de Maps:
<script src='https://apis.google.com/js/client.js'></script>
Después de cargar la API de Google Client, autoriza al usuario para que acceda a los datos en BigQuery. Para ello, puedes usar OAuth 2.0. Primero, debes configurar algunas credenciales en tu proyecto de la consola de Google Cloud.
Crea credenciales de OAuth 2.0
- En el menú de navegación de la consola de Google Cloud, selecciona APIs y servicios > Credenciales.
Antes de configurar tus credenciales, debes agregar algunos parámetros de configuración para la pantalla de autorización que verá un usuario final de tu aplicación cuando autorice a tu app a acceder a los datos de BigQuery en su nombre.
Para ello, haz clic en la pestaña Pantalla de consentimiento de OAuth. 2. Debes agregar la API de BigQuery a los permisos de este token. Haz clic en el botón Add Scope en la sección Scopes for Google APIs. 3. En la lista, marca la casilla junto a la entrada de la API de BigQuery con el alcance ../auth/bigquery
. 4. Haga clic en Add. 5. Ingresa un nombre en el campo “Nombre de la aplicación”. 6. Haz clic en Guardar para guardar la configuración. 7. A continuación, crearás tu ID de cliente de OAuth. Para ello, haz clic en Crear credenciales:
- En el menú desplegable, haz clic en ID de cliente de OAuth.
- En Tipo de aplicación, selecciona Aplicación web.
- En el campo Nombre de la aplicación, escribe un nombre para tu proyecto. Por ejemplo, "BigQuery y Maps".
- En Restricciones, en el campo Orígenes JavaScript autorizados, ingresa la URL de localhost, incluidos los números de puerto. Por ejemplo:
http://localhost:8887
.
- Haz clic en el botón Crear.
En una ventana emergente, se muestran el ID de cliente y el secreto del cliente. Necesitas el ID de cliente para realizar la autenticación en BigQuery. Cópialo y pégalo en work/index.html
como una nueva variable global de JavaScript llamada clientId
.
let clientId = 'YOUR_CLIENT_ID';
7. Autorización e inicialización
Tu página web deberá autorizar al usuario para que acceda a BigQuery antes de inicializar el mapa. En este ejemplo, usamos OAuth 2.0, como se describe en la sección de autorización de la documentación de la API del cliente de JavaScript. Debes usar el ID de cliente de OAuth y el ID de tu proyecto para enviar consultas.
Cuando se cargue la API de cliente de Google en la página web, deberás seguir estos pasos:
- Autoriza al usuario.
- Si se autoriza, carga la API de BigQuery.
- Carga e inicializa el mapa.
Consulta step3/map.html para ver un ejemplo de cómo se vería la página HTML terminada.
Autoriza al usuario
El usuario final de la aplicación debe autorizarla para que acceda a los datos de BigQuery en su nombre. La API de Google Client para JavaScript controla la lógica de OAuth para hacerlo.
En una aplicación real, tienes muchas opciones para integrar el paso de autorización.
Por ejemplo, podrías llamar a authorize()
desde un elemento de IU, como un botón, o hacerlo cuando se cargue la página. Aquí, elegimos autorizar al usuario después de que se cargue la API de cliente de Google para JavaScript, mediante una función de devolución de llamada en el método gapi.load()
.
Escribe código inmediatamente después de la etiqueta <script>
que carga la API de Google Client para JavaScript para cargar la biblioteca cliente y el módulo de autenticación, de modo que podamos autenticar al usuario de inmediato.
<script src='https://apis.google.com/js/client.js'></script>
<script type='text/javascript'>
gapi.load('client:auth', authorize);
</script>
Carga la API de BigQuery en la autorización
Después de que se autorice al usuario, carga la API de BigQuery.
Primero, llama a gapi.auth.authorize()
con la variable clientId
que agregaste en el paso anterior. Controla la respuesta en una función de devolución de llamada llamada handleAuthResult
.
El parámetro immediate
controla si se muestra una ventana emergente al usuario. Establécelo en true
para suprimir la ventana emergente de autorización si el usuario ya está autorizado.
Agrega una función a tu página llamada handleAuthResult()
. La función debe tomar un parámetro authresult
, que te permitirá controlar el flujo de lógica según si el usuario se autorizó correctamente o no.
También agrega una función llamada loadApi
para cargar la API de BigQuery si el usuario se autoriza correctamente.
Agrega lógica en la función handleAuthResult()
para llamar a loadApi()
si se pasa un objeto authResult
a la función y si la propiedad error
del objeto tiene un valor de false
.
Agrega código a la función loadApi()
para cargar la API de BigQuery con el método gapi.client.load()
.
let clientId = 'your-client-id-here';
let scopes = 'https://www.googleapis.com/auth/bigquery';
// Check if the user is authorized.
function authorize(event) {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
return false;
}
// If authorized, load BigQuery API
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
loadApi();
return;
}
console.error('Not authorized.')
}
// Load BigQuery client API
function loadApi(){
gapi.client.load('bigquery', 'v2');
}
Carga el mapa
El paso final es inicializar el mapa. Para ello, debes cambiar ligeramente el orden de la lógica. Actualmente, se inicializa cuando se carga el código JavaScript de la API de Maps.
Para ello, llama a la función initMap()
desde el método then()
después del método load()
en el objeto gapi.client
.
// Load BigQuery client API
function loadApi(){
gapi.client.load('bigquery', 'v2').then(
() => initMap()
);
}
8. Conceptos de la API de BigQuery
Las llamadas a la API de BigQuery suelen ejecutarse en segundos, pero es posible que no devuelvan una respuesta de inmediato. Necesitas cierta lógica para sondear BigQuery y averiguar el estado de los trabajos de larga duración, y solo recuperar los resultados cuando el trabajo se complete.
El código completo de este paso se encuentra en step4/map.html.
Envía una solicitud
Agrega una función de JavaScript a work/index.html
para enviar una consulta con la API y algunas variables para almacenar los valores del conjunto de datos y el proyecto de BigQuery que contienen la tabla para consultar, y el ID del proyecto al que se facturarán los cargos.
let datasetId = 'your_dataset_id';
let billingProjectId = 'your_project_id';
let publicProjectId = 'bigquery-public-data';
function sendQuery(queryString){
let request = gapi.client.bigquery.jobs.query({
'query': queryString,
'timeoutMs': 30000,
'datasetId': datasetId,
'projectId': billingProjectId,
'useLegacySql':false
});
request.execute(response => {
//code to handle the query response goes here.
});
}
Cómo verificar el estado de un trabajo
La siguiente función checkJobStatus
muestra cómo verificar el estado de un trabajo de forma periódica con el método de la API de get
y el jobId
que muestra la solicitud de consulta original. Este es un ejemplo que se ejecuta cada 500 milisegundos hasta que se completa el trabajo.
let jobCheckTimer;
function checkJobStatus(jobId){
let request = gapi.client.bigquery.jobs.get({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response =>{
if (response.status.errorResult){
// Handle any errors.
console.log(response.status.error);
return;
}
if (response.status.state == 'DONE'){
// Get the results.
clearTimeout(jobCheckTimer);
getQueryResults(jobId);
return;
}
// Not finished, check again in a moment.
jobCheckTimer = setTimeout(checkJobStatus, 500, [jobId]);
});
}
Modifica el método sendQuery
para llamar al método checkJobStatus()
como una devolución de llamada en la llamada request.execute()
. Pasa el ID del trabajo a checkJobStatus
. El objeto de respuesta lo expone como jobReference.jobId
.
function sendQuery(queryString){
let request = gapi.client.bigquery.jobs.query({
'query': queryString,
'timeoutMs': 30000,
'datasetId': datasetId,
'projectId': billingProjectId,
'useLegacySql':false
});
request.execute(response => checkJobStatus(response.jobReference.jobId));
}
Cómo obtener los resultados de una búsqueda
Para obtener los resultados de una consulta cuando finalice su ejecución, usa la llamada a la API de jobs.getQueryResults
. Agrega a tu página una función llamada getQueryResults()
que acepte un parámetro de jobId
:
function getQueryResults(jobId){
let request = gapi.client.bigquery.jobs.getQueryResults({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => {
// Do something with the results.
})
}
9. Cómo consultar datos de ubicación con la API de BigQuery
Existen tres formas de usar SQL para ejecutar consultas espaciales con datos en BigQuery:
- Seleccionar por rectángulo (también conocido como cuadro delimitador)
- seleccionar por radio
- la potente función de funciones definidas por el usuario
Existen ejemplos de consultas de cuadro de límite y radio en la sección Funciones matemáticas de la referencia de SQL heredado de BigQuery, en “Ejemplos avanzados”.
Para las consultas de radio y cuadro delimitador, puedes llamar al método query
de la API de BigQuery. Construye el código SQL para cada consulta y pásalo a la función sendQuery
que creaste en el paso anterior.
En step4/map.html, se incluye un ejemplo funcional del código para este paso.
Consultas de rectángulo
La forma más sencilla de mostrar los datos de BigQuery en un mapa es solicitar todas las filas en las que la latitud y la longitud se encuentran dentro de un rectángulo, usando una comparación de menor que y mayor que. Puede ser la vista de mapa actual o una forma dibujada en el mapa.
Para usar una forma dibujada por el usuario, cambia el código en index.html
para controlar el evento de dibujo que se activa cuando se completa un rectángulo. En este ejemplo, el código usa getBounds()
en el objeto rectángulo para obtener un objeto que representa la extensión del rectángulo en coordenadas del mapa y lo pasa a una función llamada rectangleQuery
:
drawingManager.addListener('rectanglecomplete', rectangle => rectangleQuery(rectangle.getBounds()));
La función rectangleQuery
solo necesita usar las coordenadas superior derecha (noreste) e inferior izquierda (sudoeste) para construir una comparación de menor que/mayor que con cada fila de tu tabla de BigQuery. Este es un ejemplo que consulta una tabla que tiene columnas llamadas 'pickup_latitude'
y 'pickup_longitude'
que almacenan los valores de ubicación.
Cómo especificar la tabla de BigQuery
Para consultar una tabla con la API de BigQuery, debes proporcionar el nombre de la tabla en formato completamente calificado en tu consulta SQL. En SQL estándar, el formato es project.dataset.tablename
. En SQL heredado, es project.dataset.tablename
.
Hay muchas tablas de viajes en taxi de la ciudad de Nueva York disponibles. Para verlos, ve a la consola web de BigQuery y expande el elemento de menú "Conjuntos de datos públicos". Busca el conjunto de datos llamado new_york
y expándelo para ver las tablas. Elige la tabla de viajes de taxis amarillos: bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016
).
Cómo especificar el ID del proyecto
En la llamada a la API, debes especificar el nombre de tu proyecto de Google Cloud Platform para fines de facturación. En este codelab, no es el mismo proyecto que el que contiene la tabla. Si trabajaras con una tabla que creaste en tu propio proyecto subiendo datos, este ID del proyecto sería el mismo que el de tu instrucción SQL.
Agrega variables de JavaScript a tu código para conservar referencias al proyecto Public Datasets que contiene la tabla que consultas, además del nombre de la tabla y el nombre del conjunto de datos. También necesitas una variable independiente para hacer referencia a tu propio ID del proyecto de facturación.
Agrega las variables globales de JavaScript llamadas billingProjectId, publicProjectId, datasetId
y tableName
a tu copia de index.html.
Inicializa las variables 'publicProjectId'
, 'datasetId'
y 'tableName'
con los detalles del proyecto de conjuntos de datos públicos de BigQuery. Inicializa billingProjectId
con tu propio ID del proyecto (el que creaste en "Preparación" anteriormente en este codelab).
let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId = 'bigquery-public-data';
let datasetId = 'new_york_taxi_trips';
let tableName = 'tlc_yellow_trips_2016';
Ahora agrega dos funciones a tu código para generar el código SQL y enviar la consulta a BigQuery con la función sendQuery
que creaste en el paso anterior.
La primera función debe llamarse rectangleSQL()
y debe aceptar dos argumentos, un par de objetos google.Maps.LatLng
que representan las esquinas del rectángulo en coordenadas del mapa.
La segunda función debe llamarse rectangleQuery()
. Esto pasa el texto de la búsqueda a la función sendQuery
.
let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId = 'bigquery-public-data';
let datasetId = 'new_york';
let tableName = 'tlc_yellow_trips_2016';
function rectangleQuery(latLngBounds){
let queryString = rectangleSQL(latLngBounds.getNorthEast(), latLngBounds.getSouthWest());
sendQuery(queryString);
}
function rectangleSQL(ne, sw){
let queryString = 'SELECT pickup_latitude, pickup_longitude '
queryString += 'FROM `' + publicProjectId +'.' + datasetId + '.' + tableName + '`'
queryString += ' WHERE pickup_latitude > ' + sw.lat();
queryString += ' AND pickup_latitude < ' + ne.lat();
queryString += ' AND pickup_longitude > ' + sw.lng();
queryString += ' AND pickup_longitude < ' + ne.lng();
return queryString;
}
En este punto, tienes suficiente código para enviar una consulta a BigQuery sobre todas las filas que contiene un rectángulo dibujado por el usuario. Antes de agregar otros métodos de consulta para círculos y formas a mano alzada, veamos cómo controlar los datos que se devuelven de una consulta.
10. Visualiza la respuesta
Las tablas de BigQuery pueden ser muy grandes (petabytes de datos) y pueden crecer en cientos de miles de filas por segundo. Por lo tanto, es importante intentar limitar la cantidad de datos que se muestran para que se puedan dibujar en el mapa. Si se dibuja la ubicación de cada fila en un conjunto de resultados muy grande (decenas de miles de filas o más), se generará un mapa ilegible. Existen muchas técnicas para agregar las ubicaciones tanto en la consulta de SQL como en el mapa, y puedes limitar los resultados que devolverá una consulta.
El código completo de este paso está disponible en step5/map.html.
Para que la cantidad de datos transferidos a tu página web sea de un tamaño razonable para este codelab, modifica la función rectangleSQL()
para agregar una instrucción que limite la respuesta a 10,000 filas. En el siguiente ejemplo, esto se especifica en una variable global llamada recordLimit
, de modo que todas las funciones de consulta puedan usar el mismo valor.
let recordLimit = 10000;
function rectangleSQL(ne, sw){
var queryString = 'SELECT pickup_latitude, pickup_longitude '
queryString += 'FROM `' + publicProjectId +'.' + datasetId + '.' + tableName + '`'
queryString += ' WHERE pickup_latitude > ' + sw.lat();
queryString += ' AND pickup_latitude < ' + ne.lat();
queryString += ' AND pickup_longitude > ' + sw.lng();
queryString += ' AND pickup_longitude < ' + ne.lng();
queryString += ' LIMIT ' + recordLimit;
return queryString;
}
Para visualizar la densidad de las ubicaciones, puedes usar un mapa de calor. La API de Maps JavaScript tiene una clase HeatmapLayer para este propósito. La capa HeatmapLayer toma un array de coordenadas de latitud y longitud, por lo que es bastante fácil convertir las filas que se muestran en la consulta en un mapa de calor.
En la función getQueryResults
, pasa el array response.result.rows
a una nueva función de JavaScript llamada doHeatMap()
que creará un mapa de calor.
Cada fila tendrá una propiedad llamada f
, que es un array de columnas. Cada columna tendrá una propiedad v
que contiene el valor.
Tu código debe recorrer las columnas de cada fila y extraer los valores.
En la consulta en SQL, solo solicitaste los valores de latitud y longitud de las ubicaciones de partida de los taxis, por lo que solo habrá dos columnas en la respuesta.
No olvides llamar a setMap()
en la capa de mapa de calor cuando le hayas asignado el array de posiciones. Esto hará que se vea en el mapa.
Por ejemplo:
function getQueryResults(jobId){
let request = gapi.client.bigquery.jobs.getQueryResults({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => doHeatMap(response.result.rows))
}
let heatmap;
function doHeatMap(rows){
let heatmapData = [];
if (heatmap != null){
heatmap.setMap(null);
}
for (let i = 0; i < rows.length; i++) {
let f = rows[i].f;
let coords = { lat: parseFloat(f[0].v), lng: parseFloat(f[1].v) };
let latLng = new google.maps.LatLng(coords);
heatmapData.push(latLng);
}
heatmap = new google.maps.visualization.HeatmapLayer({
data: heatmapData
});
heatmap.setMap(map);
}
En este punto, deberías poder hacer lo siguiente:
- Abre la página y autoriza el acceso a BigQuery
- Dibuja un rectángulo en algún lugar de Nueva York
- Observa los resultados de la consulta resultantes visualizados como un mapa de calor.
Este es un ejemplo del resultado de una consulta de rectángulo en los datos de los taxis amarillos de la ciudad de Nueva York de 2016, que se muestra como un mapa de calor. En este gráfico, se muestra la distribución de las recogidas alrededor del Empire State Building un sábado de julio:
11. Cómo realizar consultas por radio alrededor de un punto
Las búsquedas por radio son muy similares. Con las funciones matemáticas del SQL heredado de BigQuery, puedes construir una consulta de SQL con la fórmula de Haversine, que aproxima un área circular en la superficie de la Tierra.
Con la misma técnica para los rectángulos, puedes controlar un evento OverlayComplete
para obtener el centro y el radio de un círculo dibujado por el usuario, y compilar el SQL para la consulta de la misma manera.
En el repositorio de código, se incluye un ejemplo funcional del código para este paso como step6/map.html.
drawingManager.addListener('circlecomplete', circle => circleQuery(circle));
En tu copia de index.html, agrega dos funciones vacías nuevas: circleQuery()
y haversineSQL()
.
Luego, agrega un controlador de eventos circlecomplete
que pase el centro y el radio a una nueva función llamada circleQuery().
.
La función circleQuery()
llamará a haversineSQL()
para construir el SQL de la consulta y, luego, enviará la consulta llamando a la función sendQuery()
según el siguiente código de ejemplo.
function circleQuery(circle){
let queryString = haversineSQL(circle.getCenter(), circle.radius);
sendQuery(queryString);
}
// Calculate a circular area on the surface of a sphere based on a center and radius.
function haversineSQL(center, radius){
let queryString;
let centerLat = center.lat();
let centerLng = center.lng();
let kmPerDegree = 111.045;
queryString = 'CREATE TEMPORARY FUNCTION Degrees(radians FLOAT64) RETURNS FLOAT64 LANGUAGE js AS ';
queryString += '""" ';
queryString += 'return (radians*180)/(22/7);';
queryString += '"""; ';
queryString += 'CREATE TEMPORARY FUNCTION Radians(degrees FLOAT64) RETURNS FLOAT64 LANGUAGE js AS';
queryString += '""" ';
queryString += 'return (degrees*(22/7))/180;';
queryString += '"""; ';
queryString += 'SELECT pickup_latitude, pickup_longitude '
queryString += 'FROM `' + publicProjectId +'.' + datasetId + '.' + tableName + '` ';
queryString += 'WHERE '
queryString += '(' + kmPerDegree + ' * DEGREES( ACOS( COS( RADIANS('
queryString += centerLat;
queryString += ') ) * COS( RADIANS( pickup_latitude ) ) * COS( RADIANS( ' + centerLng + ' ) - RADIANS('
queryString += ' pickup_longitude ';
queryString += ') ) + SIN( RADIANS('
queryString += centerLat;
queryString += ') ) * SIN( RADIANS( pickup_latitude ) ) ) ) ) ';
queryString += ' < ' + radius/1000;
queryString += ' LIMIT ' + recordLimit;
return queryString;
}
Pruébalo
Agrega el código anterior y prueba la herramienta "Círculo" para seleccionar un área del mapa. El resultado debería ser similar a este:
12. Cómo consultar formas arbitrarias
Recapitulamos: SQL no admite consultas con formas arbitrarias que no sean rectángulos y círculos. BigQuery no tiene ningún tipo de datos de geometría nativo, por lo que, para ejecutar consultas con formas de polígonos, necesitas un enfoque diferente a las consultas SQL sencillas.
Una función muy potente de BigQuery que se puede usar para esto son las funciones definidas por el usuario (UDF). Las UDF ejecutan código JavaScript dentro de una consulta de SQL.
El código de trabajo para este paso se encuentra en step7/map.html.
UDF en la API de BigQuery
El enfoque de la API de BigQuery para las UDF es ligeramente diferente al de la consola web: deberás llamar a jobs.insert method
.
Para las consultas de SQL estándar a través de la API, solo se necesita una sola sentencia SQL para usar una función definida por el usuario. El valor de useLegacySql
debe establecerse en false
. En el siguiente ejemplo de JavaScript, se muestra una función que crea y envía un objeto de solicitud para insertar un trabajo nuevo, en este caso, una consulta con una función definida por el usuario.
En step7/map.html, se incluye un ejemplo de cómo funciona este enfoque.
function polygonQuery(polygon) {
let request = gapi.client.bigquery.jobs.insert({
'projectId' : billingProjectId,
'resource' : {
'configuration':
{
'query':
{
'query': polygonSql(polygon),
'useLegacySql': false
}
}
}
});
request.execute(response => checkJobStatus(response.jobReference.jobId));
}
La consulta en SQL se construye de la siguiente manera:
function polygonSql(poly){
let queryString = 'CREATE TEMPORARY FUNCTION pointInPolygon(latitude FLOAT64, longitude FLOAT64) ';
queryString += 'RETURNS BOOL LANGUAGE js AS """ ';
queryString += 'var polygon=' + JSON.stringify(poly) + ';';
queryString += 'var vertx = [];';
queryString += 'var verty = [];';
queryString += 'var nvert = 0;';
queryString += 'var testx = longitude;';
queryString += 'var testy = latitude;';
queryString += 'for(coord in polygon){';
queryString += ' vertx[nvert] = polygon[coord][0];';
queryString += ' verty[nvert] = polygon[coord][1];';
queryString += ' nvert ++;';
queryString += '}';
queryString += 'var i, j, c = 0;';
queryString += 'for (i = 0, j = nvert-1; i < nvert; j = i++) {';
queryString += ' if ( ((verty[i]>testy) != (verty[j]>testy)) &&(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ){';
queryString += ' c = !c;';
queryString += ' }';
queryString += '}';
queryString += 'return c;';
queryString += '"""; ';
queryString += 'SELECT pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, pickup_datetime ';
queryString += 'FROM `' + publicProjectId + '.' + datasetId + '.' + tableName + '` ';
queryString += 'WHERE pointInPolygon(pickup_latitude, pickup_longitude) = TRUE ';
queryString += 'LIMIT ' + recordLimit;
return queryString;
}
Aquí suceden dos cosas. En primer lugar, el código crea la sentencia CREATE TEMPORARY FUNCTION
que encapsula el código de JavaScript para determinar si un punto determinado se encuentra dentro de un polígono. Las coordenadas del polígono se insertan con la llamada al método JSON.stringify(poly)
para convertir un array de JavaScript de pares de coordenadas X,Y en una cadena. El objeto de polígono se pasa como argumento a la función que compila el SQL.
En segundo lugar, el código compila la instrucción SELECT
principal de SQL. En este ejemplo, se llama a la UDF en la expresión WHERE
.
Integración con la API de Maps
Para usar esto con la biblioteca de dibujo de la API de Maps, debemos guardar el polígono que dibuja el usuario y pasarlo a la parte de la UDF de la consulta de SQL.
Primero, debemos controlar el evento de dibujo polygoncomplete
para obtener las coordenadas de la forma como un array de pares de longitud y latitud:
drawingManager.addListener('polygoncomplete', polygon => {
let path = polygon.getPaths().getAt(0);
let queryPolygon = path.map(element => {
return [element.lng(), element.lat()];
});
polygonQuery(queryPolygon);
});
Luego, la función polygonQuery
puede construir las funciones de JavaScript de la UDF como una cadena, así como la instrucción SQL que llamará a la función de la UDF.
Consulta step7/map.html para ver un ejemplo de esto en funcionamiento.
Ejemplo de resultado:
Este es un ejemplo del resultado de una consulta de viajes en taxi desde The 2016 NYC TLC Yellow Taxi data en BigQuery con un polígono a mano alzada, con los datos seleccionados dibujados como un mapa de calor.
13. Lleva tu experiencia más allá
A continuación, se incluyen algunas sugerencias para extender este codelab y analizar otros aspectos de los datos. Puedes encontrar un ejemplo funcional de estas ideas en step8/map.html en el repositorio de código.
Cómo mapear las llegadas
Hasta ahora, solo trazamos las ubicaciones de retiro. Si solicitas las columnas dropoff_latitude
y dropoff_longitude
y modificas el código del mapa de calor para que se representen estas columnas, podrás ver los destinos de los viajes en taxi que comenzaron en una ubicación específica.
Por ejemplo, veamos dónde suelen dejar a las personas los taxis cuando solicitan que los recojan cerca del Empire State Building.
Cambia el código de la sentencia SQL en polygonSql()
para solicitar estas columnas además de la ubicación de retiro.
function polygonSql(poly){
let queryString = 'CREATE TEMPORARY FUNCTION pointInPolygon(latitude FLOAT64, longitude FLOAT64) ';
queryString += 'RETURNS BOOL LANGUAGE js AS """ ';
queryString += 'var polygon=' + JSON.stringify(poly) + ';';
queryString += 'var vertx = [];';
queryString += 'var verty = [];';
queryString += 'var nvert = 0;';
queryString += 'var testx = longitude;';
queryString += 'var testy = latitude;';
queryString += 'for(coord in polygon){';
queryString += ' vertx[nvert] = polygon[coord][0];';
queryString += ' verty[nvert] = polygon[coord][1];';
queryString += ' nvert ++;';
queryString += '}';
queryString += 'var i, j, c = 0;';
queryString += 'for (i = 0, j = nvert-1; i < nvert; j = i++) {';
queryString += ' if ( ((verty[i]>testy) != (verty[j]>testy)) &&(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ){';
queryString += ' c = !c;';
queryString += ' }';
queryString += '}';
queryString += 'return c;';
queryString += '"""; ';
queryString += 'SELECT pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, pickup_datetime ';
queryString += 'FROM `' + publicProjectId + '.' + datasetId + '.' + tableName + '` ';
queryString += 'WHERE pointInPolygon(pickup_latitude, pickup_longitude) = TRUE ';
queryString += 'LIMIT ' + recordLimit;
return queryString;
}
Luego, la función doHeatMap
puede usar los valores de abandono. El objeto de resultado tiene un esquema que se puede inspeccionar para encontrar la ubicación de estas columnas en el array. En este caso, estarían en las posiciones de índice 2 y 3. Estos índices se pueden leer desde una variable para que el código sea más fácil de administrar. Nota: El maxIntensity
del mapa de calor está configurado para mostrar una densidad de 20 abandonos por píxel como máximo.
Agrega algunas variables para permitirte cambiar las columnas que usas para los datos del mapa de calor.
// Show query results as a Heatmap.
function doHeatMap(rows){
let latCol = 2;
let lngCol = 3;
let heatmapData = [];
if (heatmap!=null){
heatmap.setMap(null);
}
for (let i = 0; i < rows.length; i++) {
let f = rows[i].f;
let coords = { lat: parseFloat(f[latCol].v), lng: parseFloat(f[lngCol].v) };
let latLng = new google.maps.LatLng(coords);
heatmapData.push(latLng);
}
heatmap = new google.maps.visualization.HeatmapLayer({
data: heatmapData,
maxIntensity: 20
});
heatmap.setMap(map);
}
Este es un mapa de calor que muestra la distribución de las paradas desde todas las ubicaciones de partida inmediatamente alrededor del Empire State Building en 2016. Se pueden ver grandes concentraciones (las manchas rojas) de destinos del centro de la ciudad, especialmente alrededor de Times Square, así como a lo largo de la 5ª Avenida entre las calles 23 y 14. Otros lugares de alta densidad que no se muestran en este nivel de zoom incluyen los aeropuertos de La Guardia y JFK, el World Trade Center y Battery Park.
Cómo aplicar diseño al mapa base
Cuando creas un mapa de Google Maps con la API de Maps JavaScript, puedes establecer el diseño del mapa con un objeto JSON. Para las visualizaciones de datos, puede ser útil silenciar los colores en el mapa. Puedes crear y probar diseños de mapas con el asistente de diseño de la API de Google Maps en mapstyle.withgoogle.com.
Puedes establecer un estilo de mapa cuando inicializas un objeto de mapa o en cualquier momento posterior. A continuación, te mostramos cómo agregar un diseño personalizado en la función initMap()
:
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
zoom: 12,
styles: [
{
"elementType": "geometry",
"stylers": [
{
"color": "#f5f5f5"
}
]
},
{
"elementType": "labels.icon",
"stylers": [
{
"visibility": "on"
}
]
},
{
"featureType": "water",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
}
]
});
setUpDrawingTools();
}
El siguiente ejemplo de diseño muestra un mapa en escala de grises con etiquetas de lugares de interés.
[
{
"elementType": "geometry",
"stylers": [
{
"color": "#f5f5f5"
}
]
},
{
"elementType": "labels.icon",
"stylers": [
{
"visibility": "on"
}
]
},
{
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#616161"
}
]
},
{
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#f5f5f5"
}
]
},
{
"featureType": "administrative.land_parcel",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#bdbdbd"
}
]
},
{
"featureType": "poi",
"elementType": "geometry",
"stylers": [
{
"color": "#eeeeee"
}
]
},
{
"featureType": "poi",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#757575"
}
]
},
{
"featureType": "poi.park",
"elementType": "geometry",
"stylers": [
{
"color": "#e5e5e5"
}
]
},
{
"featureType": "poi.park",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
},
{
"featureType": "road",
"elementType": "geometry",
"stylers": [
{
"color": "#ffffff"
}
]
},
{
"featureType": "road.arterial",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#757575"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry",
"stylers": [
{
"color": "#dadada"
}
]
},
{
"featureType": "road.highway",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#616161"
}
]
},
{
"featureType": "road.local",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
},
{
"featureType": "transit.line",
"elementType": "geometry",
"stylers": [
{
"color": "#e5e5e5"
}
]
},
{
"featureType": "transit.station",
"elementType": "geometry",
"stylers": [
{
"color": "#eeeeee"
}
]
},
{
"featureType": "water",
"elementType": "geometry",
"stylers": [
{
"color": "#c9c9c9"
}
]
},
{
"featureType": "water",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
}
]
Cómo brindar comentarios al usuario
Aunque BigQuery suele dar una respuesta en segundos, a veces es útil mostrarle al usuario que algo está sucediendo mientras se ejecuta la consulta.
Agrega algo de IU a tu página web que muestre la respuesta de la función checkJobStatus()
y un gráfico animado para indicar que la búsqueda está en curso.
La información que puedes mostrar incluye la duración de la consulta, la cantidad de datos devueltos y la cantidad de datos procesados.
Agrega código HTML después del mapa <div>
para crear un panel en la página que muestre la cantidad de filas que devolvió una consulta, el tiempo que tardó la consulta y la cantidad de datos procesados.
<div id="menu">
<div id="stats">
<h3>Statistics:</h3>
<table>
<tr>
<td>Total Locations:</td><td id="rowCount"> - </td>
</tr>
<tr>
<td>Query Execution:</td><td id="duration"> - </td>
</tr>
<tr>
<td>Data Processed:</td><td id="bytes"> - </td>
</tr>
</table>
</div>
</div>
La apariencia y la posición de este panel se controlan con CSS. Agrega CSS para colocar el panel en la esquina superior izquierda de la página, debajo de los botones de tipo de mapa y la barra de herramientas de dibujo, como se muestra en el siguiente fragmento.
#menu {
position: absolute;
background: rgba(255, 255, 255, 0.8);
z-index: 1000;
top: 50px;
left: 10px;
padding: 15px;
}
#menu h1 {
margin: 0 0 10px 0;
font-size: 1.75em;
}
#menu div {
margin: 5px 0px;
}
El gráfico animado se puede agregar a la página, pero se oculta hasta que sea necesario. Además, se usa código JavaScript y CSS para mostrarlo cuando se ejecuta un trabajo de BigQuery.
Agrega código HTML para mostrar un gráfico animado. Hay un archivo de imagen llamado loader.gif
en la carpeta img
del repositorio de código.
<img id="spinner" src="img/loader.gif">
Agrega algo de CSS para posicionar la imagen y ocultarla de forma predeterminada hasta que sea necesario.
#spinner {
position: absolute;
top: 50%;
left: 50%;
margin-left: -32px;
margin-top: -32px;
opacity: 0;
z-index: -1000;
}
Por último, agrega algo de JavaScript para actualizar el panel de estado y mostrar u ocultar el gráfico cuando se ejecute una búsqueda. Puedes usar el objeto response
para actualizar el panel según la información disponible.
Cuando verificas un trabajo actual, hay una propiedad response.statistics
que puedes usar. Cuando el trabajo se complete, podrás acceder a las propiedades response.totalRows
y response.totalBytesProcessed
. Es útil para el usuario convertir milisegundos en segundos y bytes en gigabytes para mostrarlos, como se muestra en la siguiente muestra de código.
function updateStatus(response){
if(response.statistics){
let durationMs = response.statistics.endTime - response.statistics.startTime;
let durationS = durationMs/1000;
let suffix = (durationS ==1) ? '':'s';
let durationTd = document.getElementById("duration");
durationTd.innerHTML = durationS + ' second' + suffix;
}
if(response.totalRows){
let rowsTd = document.getElementById("rowCount");
rowsTd.innerHTML = response.totalRows;
}
if(response.totalBytesProcessed){
let bytesTd = document.getElementById("bytes");
bytesTd.innerHTML = (response.totalBytesProcessed/1073741824) + ' GB';
}
}
Llama a este método cuando haya una respuesta a una llamada de checkJobStatus()
y cuando se recuperen los resultados de la búsqueda. Por ejemplo:
// Poll a job to see if it has finished executing.
function checkJobStatus(jobId){
let request = gapi.client.bigquery.jobs.get({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => {
//Show progress to the user
updateStatus(response);
if (response.status.errorResult){
// Handle any errors.
console.log(response.status.error);
return;
}
if (response.status.state == 'DONE'){
// Get the results.
clearTimeout(jobCheckTimer);
getQueryResults(jobId);
return;
}
// Not finished, check again in a moment.
jobCheckTimer = setTimeout(checkJobStatus, 500, [jobId]);
});
}
// When a BigQuery job has completed, fetch the results.
function getQueryResults(jobId){
let request = gapi.client.bigquery.jobs.getQueryResults({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => {
doHeatMap(response.result.rows);
updateStatus(response);
})
}
Para alternar el gráfico animado, agrega una función que controle su visibilidad. Esta función activará o desactivará la opacidad de cualquier elemento DOM de HTML que se le pase.
function fadeToggle(obj){
if(obj.style.opacity==1){
obj.style.opacity = 0;
setTimeout(() => {obj.style.zIndex = -1000;}, 1000);
} else {
obj.style.zIndex = 1000;
obj.style.opacity = 1;
}
}
Por último, llama a este método antes de procesar una consulta y después de que BigQuery devuelva el resultado de la consulta.
Este código llama a la función fadeToggle
cuando el usuario termina de dibujar un rectángulo.
drawingManager.addListener('rectanglecomplete', rectangle => {
//show an animation to indicate that something is happening.
fadeToggle(document.getElementById('spinner'));
rectangleQuery(rectangle.getBounds());
});
Cuando se reciba la respuesta de la búsqueda, vuelve a llamar a fadeToggle()
para ocultar el gráfico animado.
// When a BigQuery job has completed, fetch the results.
function getQueryResults(jobId){
let request = gapi.client.bigquery.jobs.getQueryResults({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => {
doHeatMap(response.result.rows);
//hide the animation.
fadeToggle(document.getElementById('spinner'));
updateStatus(response);
})
}
La página debería verse así.
Consulta el ejemplo completo en step8/map.html.
14. Aspectos para tener en cuenta
Demasiados marcadores
Si trabajas con tablas muy grandes, es posible que tu consulta muestre demasiadas filas para visualizarlas de manera eficiente en un mapa. Limita los resultados agregando una cláusula WHERE
o una declaración LIMIT
.
Dibujar muchos marcadores puede hacer que el mapa sea ilegible. Considera usar un HeatmapLayer
para mostrar la densidad o agrupar los marcadores en clústeres para indicar dónde se encuentran muchos puntos de datos con un solo símbolo por clúster. Encontrarás más detalles en nuestro instructivo sobre la agrupación de marcadores.
Optimización de consultas
BigQuery analizará la tabla completa con cada consulta. Para optimizar el uso de tu cuota de BigQuery, selecciona solo las columnas que necesitas en tu consulta.
Las consultas serán más rápidas si almacenas la latitud y la longitud como números de punto flotante en lugar de cadenas.
Exporta resultados interesantes
Los ejemplos que se muestran aquí requieren que el usuario final se autentique en la tabla de BigQuery, lo que no se adaptará a todos los casos de uso. Cuando descubras algunos patrones interesantes, puede ser más fácil compartirlos con un público más amplio si exportas los resultados de BigQuery y creas un conjunto de datos estático con la capa de datos de Google Maps.
La parte legal aburrida
Ten en cuenta las Condiciones del Servicio de Google Maps Platform. Para obtener más información sobre los precios de Google Maps Platform, consulta la documentación en línea.
¡Juega con más datos!
En BigQuery, hay varios conjuntos de datos públicos que tienen columnas de latitud y longitud, por ejemplo, los conjuntos de datos de taxis de Nueva York de 2009 a 2016, los datos de viajes de Uber y Lyft en Nueva York y el conjunto de datos de GDELT.
15. ¡Felicitaciones!
Esperamos que esto te ayude a comenzar a usar rápidamente algunas consultas geográficas en tablas de BigQuery para que puedas descubrir patrones y visualizarlos en un mapa de Google. ¡Que disfrutes la creación de mapas!
Próximos pasos
Si deseas obtener más información sobre Google Maps Platform o BigQuery, consulta las siguientes sugerencias.
Consulta ¿Qué es BigQuery? para obtener más información sobre el servicio de almacén de datos sin servidores a escala de petabytes de Google.
Consulta la guía práctica para crear una aplicación simple con la API de BigQuery.
Consulta la guía para desarrolladores de la biblioteca de dibujo para obtener más detalles sobre cómo habilitar la interacción del usuario para dibujar formas en un mapa de Google.
Consulta otras formas de visualizar datos en un mapa de Google Maps.
Consulta la guía de introducción a la API del cliente de JavaScript para comprender los conceptos básicos del uso de la API del cliente para acceder a otras APIs de Google.