Consulta y visualiza datos de ubicación en BigQuery con Google Maps Platform (JavaScript)
Acerca de este codelab
1. Descripción general
Maps puede ser una herramienta muy eficaz cuando permite visualizar los patrones de un conjunto de datos que están relacionados 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 tramo de censo o un código postal.
Cuando estos conjuntos de datos se vuelven muy grandes, puede ser difícil usarlos y visualizarlos con herramientas convencionales. Si usas Google BigQuery para consultar los datos y las API de Google Maps a fin de construir la consulta y visualizar el resultado, puedes explorar rápidamente los patrones geográficos de 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é crearás
En este codelab, escribirás y ejecutarás algunas consultas que demuestran cómo proporcionar estadísticas basadas en la ubicación en 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, ejecutará y visualizará consultas espaciales en los mismos grandes conjuntos de datos públicos mediante la Biblioteca cliente de las API 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 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 ella
- Cómo visualizar consultas en grandes conjuntos de datos en un mapa de Google Maps, como en la siguiente imagen de ejemplo, que muestra la densidad de los lugares de abandono en taxi en el año 2016 a partir de los viajes que comenzaron en el bloque del edificio Empire State.
Requisitos
- Conocimientos básicos de Herramientas para desarrolladores de HTML, CSS, JavaScript, SQL y 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 RESTful y admite consultas escritas en SQL. Si tienes datos con valores de latitud y longitud, se pueden utilizar para consultar los datos por ubicación. La ventaja es que puede 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 pocos segundos, sin importar qué tan grandes sean tus tablas con la masiva infraestructura de BigQuery y su infraestructura administrada.
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 los usan para proporcionarles mapas incorporados y búsquedas basadas en la ubicación a sus usuarios.
La capa de dibujo de la API de JavaScript de Google Maps 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 API 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, verás el menú desplegable Project (Project):
Cuando hagas clic en el menú desplegable de este proyecto, obtendrás un elemento de menú que te permitirá crear un proyecto nuevo:
En el cuadro que dice “Enter a new name for your project”, 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 existe y no funcionará. Inserta tu propio ID del proyecto donde veas YOUR_PROJECT_ID en este codelab.
Habilitar facturación
Para registrarte en BigQuery, usa el proyecto seleccionado o creado en el paso anterior. La facturación debe estar habilitada en este proyecto. Una vez habilitada la facturación, puede habilitar la API de BigQuery.
La forma de habilitar la facturación depende de si quieres crear un proyecto nuevo o volver a habilitar la facturación para un proyecto existente.
Google ofrece una prueba gratuita de 12 meses por un valor de hasta $300 en el uso de Google Cloud Platform que puedes usar para este Codelab. Obtén más información en https://cloud.google.com/free/.
Proyectos nuevos
Cuando creas un proyecto nuevo, se te pide que elijas cuál de tus cuentas de facturación quieres vincular al proyecto. Si solo tienes una cuenta de facturación, esta se vinculará automáticamente a tu proyecto.
Si no tienes una cuenta de facturación, debes crear una y habilitar la facturación para el proyecto antes de usar muchas funciones de Google Cloud Platform. A fin de crear una cuenta de facturación nueva y habilitar la facturación para tu proyecto, sigue las instrucciones que se indican en Crea una cuenta de facturación nueva.
Proyectos existentes
Si tienes un proyecto en el que inhabilitaste la facturación temporalmente, puedes volver a habilitarla:
- 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 Cloud Platform Console 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. Tenga en cuenta que para las cuentas de Estados Unidos, no puede cambiar el estado fiscal una vez creada 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 más información sobre cómo verificar cuentas bancarias y agregar formas de pago alternativas, consulta Cómo agregar, quitar o actualizar una forma de pago.
Habilite la API de BigQuery
Para habilitar la API de BigQuery en tu proyecto, ve a la página de la API de BigQuery Marketplace en la consola y haz clic en el botón azul "Habilitar".
3. Consulta datos de ubicación en BigQuery
Existen tres maneras de consultar datos de ubicación almacenados como valores de latitud y longitud en BigQuery.
- Consultas rectangulares: Especifica el área de interés como una consulta que selecciona todas las filas en una latitud y un rango de longitud mínimos y máximos.
- Consultas de radio: Especifica el área de interés calculando un círculo alrededor de un punto con la fórmula de Hazrsine y las funciones de matemática 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 están dentro de la forma.
Para comenzar, utilice el Editor de consultas en la sección BigQuery de Google Cloud Platform Console a fin de ejecutar las siguientes consultas en los datos de taxis de la ciudad de Nueva York.
SQL estándar vs. SQL heredado
BigQuery es compatible con dos versiones de SQL: SQL heredado y SQL estándar. Esta última es la norma ANSI 2011. Para los fines de este instructivo, usaremos SQL estándar porque tiene un mejor cumplimiento de estándares.
Si desea ejecutar SQL heredado en el editor de BigQuery, puede hacerlo de la siguiente manera:
- Haz clic en el botón "Más".
- Selecciona "Configuración de 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 rectangulares
Las consultas de rectángulos son bastante fáciles de construir en BigQuery. Solo debes agregar una cláusula WHERE
que limite los resultados que se muestran a aquellos con ubicaciones entre los valores mínimo y máximo para la latitud y la longitud.
Prueba el siguiente ejemplo en BigQuery Console. Esto muestra estadísticas de viajes promedio correspondientes a viajes que comenzaron en un área rectangular que contiene el centro de la ciudad y el sur de Manhattan. Existen dos ubicaciones diferentes que puedes probar y quitar la segunda cláusula WHERE
para ejecutar la consulta en 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 ambas búsquedas muestran que existen grandes diferencias en la distancia promedio del viaje, las tarifas y la propina para buscarlos en las dos ubicaciones.
Manhattan
prom.prom. | promedio_tarifa | promedio_distancia | prox._tip_pc | Avg_fare_mile |
2,52 | 12:03 | 9.97 | 22,39 | 5,97 |
GRU
prom.prom. | promedio_tarifa | promedio_distancia | prox._tip_pc | Avg_fare_mile |
9.22 | 48,49 | 41,19 | 22,48 | 4,36 |
Búsquedas por radio
Las consultas por distancia también son fáciles de construir en SQL si sabes un poco de matemáticas. Con las funciones matemáticas de SQL heredado de BigQuery, puedes crear una consulta de SQL con la Fórmula Hasrsine, que se aproxima a un área circular o un tope 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 circular 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 representada por 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 Hazrsine parece complicado, pero todo lo que necesitas hacer es conectar la coordenada central de tu círculo, el radio y los nombres de proyecto, conjunto de datos y tabla de BigQuery.
Esta es una consulta de ejemplo que calcula algunas estadísticas promedio del viaje para recogidas en un radio de 100 m del edificio Empire State. Copie y pegue esto 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
Los resultados de la consulta se muestran a continuación. Puedes ver que hay grandes diferencias en la propina promedio, la tarifa, la distancia del viaje, el tamaño proporcional de la propina y la tarifa promedio por milla conducida.
Edificio Empire State:
prom.prom. | promedio_tarifa | promedio_distancia | prox._tip_pc | Avg_fare_mile |
1.17 | 11,08 | 45,28 | 10,53 | 6,42 |
Bronx
prom.prom. | promedio_tarifa | promedio_distancia | prox._tip_pc | Avg_fare_mile |
0.52 | 17,63 | 4,75 | 4,74 | 10.9 |
Consultas de polígonos
SQL no admite consultas mediante formas arbitrarias que no sean rectángulos o círculos. BigQuery no tiene ningún tipo de datos de geometría o espacio espacial nativos, por lo que para ejecutar consultas con formas poligonales necesitas un enfoque diferente para consultas de SQL sencillas. Uno de los enfoques consiste en definir una función de geometría en JavaScript y ejecutarla como una función definida por el usuario (UDF) en BigQuery.
Muchas operaciones geométricas 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, de modo que solo se muestren las filas en las que la latitud y la longitud se encuentren dentro del polígono. Obtén más información sobre las UDF en la referencia de BigQuery.
Algoritmo de polígono de entrada
Hay muchas maneras de calcular si un punto se encuentra dentro de un polígono en JavaScript. Este es un puerto de C de una implementación conocida que usa un algoritmo de rastreo de rayos para determinar si un punto se encuentra dentro o fuera de un polígono mediante el conteo de la cantidad de veces que una línea infinitamente larga cruza el límite de la forma. Solo requiere 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;
}
Portación de JavaScript
La versión de JavaScript de este algoritmo tiene el siguiente aspecto:
/* 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 declaración, pero la UDF debe definirse como una función temporal en la instrucción. A continuación, se muestra un ejemplo. Pegue 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!
Ya ejecutó tres tipos de consultas espaciales con BigQuery. Como viste, la ubicación marca una gran diferencia en los datos de los 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 una zona de interés arbitraria. Con las API de Google Maps, puedes hacer precisamente eso. Primero, debes habilitar la API de Google Maps, configurar una página web simple que se ejecute en tu máquina local y comenzar a utilizar la API de BigQuery para enviar consultas desde tu página web.
4. Cómo trabajar con las API 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á la API de Google Maps, creará una página web que envíe consultas de un mapa a BigQuery y, luego, dibujará 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, vaya a Marketplace
- En Marketplace, busca "API de Maps JavaScript"
- Haz clic en el mosaico 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, debes generar una clave de API y enviarla con todas las solicitudes. Para generar una clave de API, haz lo siguiente:
- En Google Cloud Platform Console, haz clic en el menú de opciones para abrir el panel de navegación izquierdo.
- Selecciona "API" y "Servicio" y "Credenciales"
- Haz clic en el botón “Crear credencial” y selecciona “Clave de API”
- Copia la clave de API nueva
Descargue el código y configure un servidor web
Haz clic en el siguiente botón a fin de descargar todo el código de este codelab:
Descomprime el archivo zip descargado. Esto 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 tu 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 Chrome. Si aún no la tienes instalada, puedes instalarla desde Chrome Web Store.
Una vez instalada, abre la app. En Chrome, puedes hacer lo siguiente:
- Abrir 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 de Servidor web. También puedes hacer clic con el botón derecho en una aplicación para abrirla en una pestaña común o fija, o en pantalla completa o en una ventana nueva
A continuación, verás este cuadro de diálogo que te permite 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 que aparece junto a "Mostrar automáticamente el archivo index.html":
.
- Mueve el botón de activación "Web Server: STARTED" hacia la izquierda y, luego, hacia la derecha para detenerlo y reinicia el servidor web
5. Cómo cargar el mapa y las herramientas de dibujo
Cómo crear 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 de la muestra de mapa simple de Google Maps Platform es un excelente punto de partida. Se reproducirá aquí para que lo copies y pegues en el editor de texto o IDE que prefieras, o puedes encontrarlo abriendo index.html
desde el repositorio que descargaste.
- Copia
index.html
en la carpetawork
de tu copia local del repositorio. - Copia la carpeta img/ del trabajo en la carpeta local en la copia local del repositorio.
- Abre Work/
index.html
en tu IDE o editor de texto. - 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 que se especifica en la configuración del servidor web local. El puerto predeterminado es8887
. Deberías ver tus primeros mapas.
Si recibes un mensaje de error en el navegador, comprueba 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 para establecer la ubicación y el nivel de zoom se encuentra en las líneas 27 y 28 del índice.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>
Este instructivo funciona con los datos del viaje en taxi de BigQuery para Nueva York, luego cambiarás el código de inicialización del mapa para centrarlo en una ubicación en la ciudad de Nueva York a un nivel de zoom apropiado: 13 o 14 deberían funcionar bien.
Para ello, actualiza el bloque de código anterior para centrar el mapa en el edificio Empire State 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>
Luego, vuelve a cargar el mapa en el navegador para ver los resultados.
Carga las bibliotecas de dibujo y visualización
Para agregar capacidades de dibujo a tu mapa, cambia 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 dibujo.
En este codelab, también se usa HeatmapLayer
, de modo que también actualizarás la secuencia de comandos a fin de 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
Debe tener el siguiente aspecto:
<script src='http://maps.googleapis.com/maps/api/js?libraries=visualization,drawing&callback=initMap&key=YOUR_API_KEY' async defer></script>
Cómo agregar DrawingManager
Para usar formas dibujadas por el usuario como entrada para una consulta, agrega el DrawingManager
a tu mapa, con las herramientas Circle
, Rectangle
y Polygon
habilitadas.
Es conveniente colocar todo el código de configuración de DrawingManager
en una función nueva. Por lo tanto, en tu copia de index.html, haz lo siguiente:
- Agrega una función llamada
setUpDrawingTools()
con el siguiente código para crear elDrawingManager
y configura su propiedadmap
a fin de hacer referencia al objeto de mapa en la página.
Las opciones que se pasan a google.maps.drawing.DrawingManager(options)
establecen el tipo de dibujo de formas predeterminado y muestran las opciones de las formas dibujadas. A fin de seleccionar áreas de mapa para enviar como consultas, las formas deben tener una opacidad 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 crear el objeto de 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 comprueba que puedas ver las herramientas de dibujo. Además, comprueba que puedes usarlos para dibujar círculos, rectángulos y formas poligonales.
Puedes hacer clic y arrastrar para dibujar círculos y rectángulos, pero los polígonos se deben dibujar haciendo clic en cada vértice y haciendo doble clic para finalizar 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, del mismo modo que necesitas las coordenadas de las formas dibujadas para construir las consultas de SQL.
Agregaremos código para esto en un paso posterior, pero, por ahora, implementaremos tres controladores de eventos vacíos para controlar los eventos rectanglecomplete
, circlecomplete
y polygoncomplete
. En esta etapa, no es necesario que los controladores ejecuten un código.
Agrega lo siguiente al final de la 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. Usar la API cliente de BigQuery
La API de cliente de Google BigQuery lo ayudará a evitar escribir una gran cantidad de código estándar necesario para compilar las solicitudes, analizar las respuestas y administrar la autenticación. Este codelab usa la API de BigQuery mediante la Biblioteca cliente de las API 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 la usarás para interactuar con BigQuery.
Agrega la API del cliente de Google para JavaScript
Utilizará la API cliente de Google para JavaScript a fin de 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 Google Maps:
<script src='https://apis.google.com/js/client.js'></script>
Después de cargar la API del cliente de Google, autorice al usuario a acceder a los datos en BigQuery. Para hacerlo, puedes usar OAuth 2.0. Primero, debes configurar algunas credenciales en tu proyecto de Google Cloud Console.
Crea credenciales de OAuth 2.0
- En Google Cloud Console, en Menú de navegación, seleccione API y servicios > Credenciales.
Antes de configurar tus credenciales, debes agregar algunos parámetros de configuración para la pantalla de autorización que un usuario final de tu aplicación verá cuando autorice a tu app para acceder a los datos de BigQuery en su nombre.
Para ello, haz clic en la pestaña Pantalla de consentimiento de OAuth. 2. Debe 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 API. 3. En la lista, marca la casilla junto a la entrada 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. Haga clic en Guardar para guardar la configuración. 7. A continuación, creará su 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, seleccione Aplicación web.
- En el campo Application Name, escribe un nombre para tu proyecto. Por ejemplo, BigQuery y Maps.
- En Restricciones, en el campo Orígenes autorizados de JavaScript, ingresa la URL del host local, incluidos los números de puerto. Por ejemplo:
http://localhost:8887
.
- Haz clic en el botón Crear.
Una ventana emergente te muestra el ID 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 a acceder a BigQuery antes de inicializar el mapa. En este ejemplo, se utiliza OAuth 2.0 como se describe en la sección de autorización de la documentación de la API del cliente de JavaScript. Para enviar consultas, debes utilizar el ID de cliente de OAuth y el ID del proyecto.
Cuando la API cliente de Google se carga en la página web, debe realizar los siguientes pasos:
- Autorizar al usuario
- Si tiene autorización, cargue la API de BigQuery.
- Carga e inicializa el mapa.
Consulta step3/map.html para ver un ejemplo del aspecto que tendrá la página HTML terminada.
Autorizar al usuario
El usuario final de la aplicación debe autorizar a la aplicación para que acceda a datos en BigQuery en su nombre. La API cliente de Google para JavaScript maneja la lógica de OAuth para hacer esto.
En una aplicación real, tiene muchas opciones para integrar el paso de autorización.
Por ejemplo, puedes llamar a authorize()
desde un elemento de la IU, como un botón, o hacerlo cuando la página se haya cargado. Aquí decidimos autorizar al usuario después de que se haya cargado la API 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 del cliente de Google para JavaScript a fin de cargar la biblioteca cliente y el módulo auth para 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>
Una vez realizada la autorización, cargue la API de BigQuery
Una vez que se haya autorizado al usuario, cargue la API de BigQuery.
En primer lugar, 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. Configúralo 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.
Además, agrega una función llamada loadApi
para cargar la API de BigQuery si el usuario se autorizó correctamente.
Agrega lógica en la función handleAuthResult()
para llamar a loadApi()
si hay un objeto authResult
pasado 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, debe cambiar el orden de la lógica ligeramente. Actualmente, se inicializa cuando se carga el código JavaScript de la API de Google 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 pueden no mostrar una respuesta de inmediato. Necesita un poco de lógica para consultar con BigQuery a fin de averiguar el estado de los trabajos de larga duración y solo recuperar los resultados cuando se complete el trabajo.
El código completo para este paso se encuentra en step4/map.html.
Envía una solicitud
Agrega una función de JavaScript a work/index.html
a fin de enviar una consulta mediante la API y algunas variables para almacenar los valores del conjunto de datos y el proyecto de BigQuery que contiene la tabla que se debe consultar, y el ID del proyecto que se facturará por 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.
});
}
Verificar el estado de un trabajo
La función checkJobStatus
a continuación muestra cómo verificar el estado de un trabajo de forma periódica mediante el método get
de la API y el objeto jobId
que muestra la solicitud de consulta original. A continuación, se muestra 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 devolución de llamada en la llamada request.execute()
. Pasa el ID de 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 consulta
Para obtener los resultados de una consulta cuando haya terminado de ejecutarse, usa la llamada a la API jobs.getQueryResults
. Agrega una función a tu página 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 en BigQuery:
- Seleccionar por rectángulo (también conocido como cuadro de límite)
- seleccionar por radio
- la poderosa función de funciones definidas por el usuario
Hay ejemplos de cuadros de límite y radios en la sección Funciones matemáticas de la referencia en SQL heredado de BigQuery, en la sección "Ejemplos avanzados".
Para las consultas de radio y cuadro de límite, puedes llamar al método query
de la API de BigQuery. Construye el SQL para cada consulta y pásalo a la función sendQuery
que creaste en el paso anterior.
En step4/map.html, se muestra un ejemplo práctico del código de este paso.
Consultas rectangulares
La manera 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 encuentren dentro de un rectángulo, con una comparación menor 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
a fin de 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 rectangular para obtener un objeto que represente la extensión del rectángulo en las 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 (nordeste) e inferior izquierda (sur oeste) para crear una comparación menor o mayor que la de cada fila de la 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 la ubicación.
Especifica la tabla de BigQuery
Para consultar una tabla con la API de BigQuery, debe proporcionar el nombre de la tabla en su totalidad en su consulta de SQL. El formato en SQL estándar es project.dataset.tablename
. En SQL heredado, es project.dataset.tablename
.
Hay muchas mesas disponibles para viajes en taxi en la ciudad de Nueva York. 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 Yellow Taxi: 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 contiene la tabla. Si trabajaba con una tabla que creó en su propio proyecto subiendo datos, este ID del proyecto sería el mismo que el de su instrucción de SQL.
Agrega variables de JavaScript a tu código para conservar las referencias al proyecto de conjuntos de datos públicos que contenga la tabla que consultas, además del nombre de la tabla y el nombre del conjunto de datos. También necesita una variable diferente para hacer referencia a su propio ID del proyecto de facturación.
Agrega 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 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 SQL y enviar la consulta a BigQuery mediante 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 las coordenadas del mapa.
La segunda función debe llamarse rectangleQuery()
. Así se pasa el texto de la consulta 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 de todas las filas contenidas en un rectángulo que dibujó el usuario. Antes de agregar otros métodos de búsqueda para círculos y formas independientes, veamos cómo controlar los datos que provienen 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. Dibujar la ubicación de cada fila en un conjunto de resultados muy grande (decenas de miles de filas o más) 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 muestra una consulta.
En step5/map.html se encuentra el código completo para este paso.
A fin de mantener una cantidad razonable de datos transferidos a tu página web en este codelab, modifica la función rectangleSQL()
para agregar una sentencia que limite la respuesta a 10,000 filas. En el siguiente ejemplo, 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. A tal fin, la API de Maps JavaScript tiene una clase HeatmapLayer. HeatmapLayer toma un arreglo de coordenadas de latitud y longitud, de modo que es bastante fácil convertir las filas que se muestran de la consulta en un mapa de calor.
En la función getQueryResults
, pasa el array de 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 arreglo de columnas. Cada columna tendrá una propiedad v
que contiene el valor.
El código debe recorrer las columnas de cada fila y extraer los valores.
En la consulta de SQL, solo solicitó los valores de latitud y longitud de los viajes en taxi, por lo que solo habrá dos columnas en la respuesta.
No te olvides de llamar a setMap()
en la capa del mapa de calor cuando le asignes el arreglo de posiciones. Esto hará que sea visible 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:
- Abra la página y autorice a BigQuery
- Dibuja un rectángulo en alguna parte de la ciudad de Nueva York
- Visualiza los resultados de la consulta resultantes visualizados como un mapa de calor.
A continuación, se muestra un ejemplo del resultado de una consulta rectangular con los datos de Yellow Taxi de 2016 en la ciudad de Nueva York, dibujados como un mapa de calor. Muestra la distribución de los retiros en torno al edificio Empire State un sábado de julio:
11. Consulta por radio alrededor de un punto
Las consultas por distancia son muy similares. Con las funciones matemáticas de SQL heredado de BigQuery, puedes crear una consulta de SQL con la Fórmula Hasrsine, que se aproxima a un área circular en la superficie de la Tierra.
Usando la misma técnica para rectángulos, puedes controlar un evento OverlayComplete
a fin de obtener el centro y el radio de un círculo dibujado por el usuario y crear 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 nuevas funciones vacías: 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ébala
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
Resumen: SQL no admite consultas mediante formas arbitrarias que no sean rectángulos o círculos. BigQuery no tiene ningún tipo de datos de geometría nativa, por lo que para ejecutar consultas con formas de polígonos, necesitas un enfoque diferente a consultas de SQL sencillas.
Una función muy útil de BigQuery que se puede utilizar para este fin son las funciones definidas por el usuario (UDF). Las UDF ejecutan el código de JavaScript en 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 UDF es ligeramente diferente al de la consola web; deberás llamar a jobs.insert method
.
En las consultas de SQL estándar a través de la API, solo se necesita una sola instrucción de 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 muestra un ejemplo práctico de 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 de 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;
}
Lo que sucede son dos cosas. En primer lugar, el código crea la sentencia CREATE TEMPORARY FUNCTION
que encapsula el código de JavaScript para entrenarlo si un punto determinado se encuentra dentro de un polígono. Las coordenadas del polígono se insertan mediante la llamada de método JSON.stringify(poly)
para convertir un arreglo de JavaScript de pares de coordenadas x,y en una string. El objeto de polígono se pasa como un argumento a la función que compila el SQL.
En segundo lugar, el código compila la declaración SELECT
de SQL principal. En este ejemplo, se llama a la UDF en la expresión WHERE
.
Cómo integrar la API de Google Maps
Para usar esto con la biblioteca de dibujo de la API de Google Maps, debemos guardar el polígono dibujado por el usuario y pasarlo a la parte de la UDF de la consulta en SQL.
Primero, debemos controlar el evento de dibujo de polygoncomplete
para obtener las coordenadas de la forma como un arreglo 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 string, así como la instrucción de SQL que llamará a la función de la UDF.
Consulta step7/map.html para ver un ejemplo práctico.
Resultado de ejemplo
Este es un ejemplo de resultado de la consulta de retiros de Datos de taxis amarillos de la ciudad de Nueva York de 2016 en BigQuery con un polígono a mano alzada, con los datos seleccionados como un mapa de calor.
13. Ve más allá
A continuación, se muestran algunas sugerencias sobre cómo extender este codelab para observar otros aspectos de los datos. Puedes encontrar un ejemplo práctico de estas ideas en step8/map.html, en el repositorio de código.
Destinos de mapeo
Hasta ahora, solo asignamos ubicaciones de retiro. Si solicitas las columnas dropoff_latitude
y dropoff_longitude
, y modificas el código del mapa de calor para trazarlas, podrás ver los destinos de los viajes en taxi que comenzaron en una ubicación específica.
Por ejemplo, veamos dónde los taxis suelen dejar a las personas cuando solicitan un retiro en torno al edificio Empire State.
Cambia el código de la instrucción de 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;
}
En ese caso, la función doHeatMap
podrá usar los valores de destino. El objeto resultante 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 2 y 3 del índice. Estos índices se pueden leer desde una variable para que el código sea más fácil de administrar. NB el maxIntensity
del mapa de calor está configurado para mostrar la densidad de 20 abandonos por píxel como el máximo.
Agregue algunas variables para poder cambiar las columnas que utiliza en 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);
}
A continuación, se incluye un mapa de calor en el que se muestra la distribución de los descensos de todas las partidas de forma inmediata alrededor del Empire State Building en 2016. Puedes ver grandes concentraciones (los blobs rojos) de los destinos del centro de la ciudad, especialmente alrededor de Times Square, y también a lo largo de la Quinta Avenida entre las calles 23 y 14 y St. Otras ubicaciones de alta densidad que no se muestran en este nivel de zoom incluyen los aeropuertos La Guardia y JFK, el World Trade Center y el parque de las baterías.
Aplica diferentes estilos al mapa base
Cuando creas un mapa de Google Maps con la API de Maps JavaScript, puedes configurar el estilo de mapa mediante un objeto JSON. Para las visualizaciones de datos, puede resultar útil silenciar los colores del mapa. Puedes crear y probar estilos de mapa con el Asistente de diseño de la API de Google Maps en mapstyle.withgoogle.com.
Puedes configurar un estilo de mapa cuando inicializas un objeto de mapa o en cualquier momento posterior. A continuación, te mostramos cómo agregar un estilo 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 estilo 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 enviar comentarios al usuario
Si bien BigQuery suele proporcionar una respuesta en segundos, puede resultar útil mostrar al usuario que algo está sucediendo mientras se ejecuta la consulta.
Agrega una IU a tu página web que muestre la respuesta de la función checkJobStatus()
y un gráfico animado para indicar que la consulta está en curso.
La información que puede mostrar incluye la duración de la consulta, la cantidad de datos que se muestran y la cantidad de datos procesados.
Agrega HTML después del mapa <div>
para crear un panel de la página que muestre la cantidad de filas que muestra 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 mediante CSS. Agrega CSS para posicionar el panel en la esquina superior izquierda de la página, debajo de los botones de tipo de mapa y de la barra de herramientas de dibujo, como se muestra en el fragmento de abajo.
#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 puede ocultar hasta que sea necesario. También se usa código JavaScript y CSS para mostrarlo cuando se ejecuta un trabajo de BigQuery.
Agrega 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 CSS para posicionar la imagen y ocultarla de forma predeterminada hasta que sea necesaria.
#spinner {
position: absolute;
top: 50%;
left: 50%;
margin-left: -32px;
margin-top: -32px;
opacity: 0;
z-index: -1000;
}
Por último, agrega JavaScript para actualizar el panel de estado y ocultar o mostrar el gráfico cuando se esté ejecutando una consulta. 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 se complete el trabajo, podrás acceder a las propiedades response.totalRows
y response.totalBytesProcessed
. Resulta útil para el usuario convertir milisegundos a segundos y bytes a gigabytes de modo que se muestren 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 consulta. 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 activar o desactivar el gráfico animado, agrega una función a fin de controlar 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, llame a este método antes de procesar una consulta y, luego de que el resultado de la consulta haya regresado de BigQuery.
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 haya recibido la respuesta a la consulta, 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 ser similar a la siguiente:
Consulta el ejemplo completo en step8/map.html.
14. Consideraciones
Demasiados marcadores
Si trabajas con tablas muy grandes, es posible que tu consulta muestre demasiadas filas para mostrarlas de manera eficiente en un mapa. Para limitar los resultados, agrega 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 marcadores de clústeres a fin de indicar dónde se encuentran muchos datos usando un solo símbolo por clúster. Puedes obtener más información en nuestro instructivo sobre el agrupamiento de marcadores en clústeres.
Optimice las consultas
BigQuery analizará toda la tabla con cada consulta. Para optimizar el uso de la 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 flotantes en lugar de strings.
Exportar resultados interesantes
En los ejemplos que aparecen a continuación, se requiere que el usuario final se autentique en la tabla de BigQuery, lo que no es adecuado para todos los casos de uso. Cuando descubras algunos patrones interesantes, puede ser más fácil compartirlos con un público más amplio mediante la exportación de los resultados de BigQuery y la creación de un conjunto de datos estático mediante el uso de la capa de datos de Google Maps.
El aburrimiento legal
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
Hay varios conjuntos de datos públicos en BigQuery que tienen columnas de latitud y longitud, por ejemplo, los conjuntos de datos de NYC Taxi de 2009 a 2016, Uber y Lyft NYC NYC y el conjunto de datos GDELT.
15. ¡Felicitaciones!
Esperamos que este cambio lo ayude a comenzar rápidamente con algunas consultas geográficas en las tablas de BigQuery para que pueda descubrir patrones y visualizarlos en un mapa de Google. ¡Disfruta de los 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.
Consulte la guía práctica para crear una aplicación simple con la API de BigQuery.
Si deseas obtener más información sobre cómo permitir la interacción del usuario para dibujar formas en un mapa de Google, consulta la guía para desarrolladores de la biblioteca de dibujos.
Echa un vistazo a otras formas de visualizar datos en un mapa de Google.
Consulta la Guía de introducción del AP de cliente de JavaScript para comprender los conceptos básicos del uso de la API cliente a fin de acceder a otras API de Google.