1. Visão geral
Os mapas podem ser uma ferramenta muito eficiente para visualizar os padrões em um conjunto de dados relacionados à localização de alguma forma. Essa relação pode ser o nome de um lugar, um valor específico de latitude e longitude ou o nome de uma área com um limite específico, como um setor censitário ou um CEP.
Quando esses conjuntos de dados ficam muito grandes, é difícil consultá-los e visualizá-los usando ferramentas convencionais. Ao usar o Google BigQuery para consultar os dados e as APIs do Google Maps para construir a consulta e visualizar a saída, é possível analisar rapidamente padrões geográficos nos dados com pouca configuração ou programação e sem precisar gerenciar um sistema para armazenar conjuntos de dados muito grandes.
O que você vai criar
Neste codelab, você vai escrever e executar algumas consultas que mostram como fornecer insights com base em localização em conjuntos de dados públicos muito grandes usando o BigQuery. Você também vai criar uma página da Web que carrega um mapa usando a API JavaScript da Plataforma Google Maps e executa e visualiza consultas espaciais nos mesmos conjuntos de dados públicos muito grandes usando a biblioteca de cliente das APIs do Google para JavaScript e a API BigQuery.
O que você vai aprender
- Como consultar conjuntos de dados de localização em escala de petabytes em segundos com o BigQuery usando consultas SQL, funções definidas pelo usuário e a API BigQuery
- Como usar a Plataforma Google Maps para adicionar um mapa do Google a uma página da Web e permitir que os usuários desenhem formas nele
- Como visualizar consultas em grandes conjuntos de dados em um mapa do Google, como na imagem de exemplo abaixo, que mostra a densidade de locais de desembarque de táxi em 2016 em viagens que começaram no quarteirão ao redor do Empire State Building.
O que é necessário
- Conhecimento básico de HTML, CSS, JavaScript, SQL e Chrome DevTools
- Um navegador da Web moderno, como as versões recentes do Chrome, Firefox, Safari ou Edge.
- Um editor de texto ou ambiente de desenvolvimento integrado de sua escolha
A tecnologia
BigQuery
O BigQuery é o serviço de análise de dados do Google para conjuntos de dados muito grandes. Ele tem uma API RESTful e aceita consultas escritas em SQL. Se você tiver dados com valores de latitude e longitude, eles poderão ser usados para consultar seus dados por local. A vantagem é que você pode analisar visualmente conjuntos de dados muito grandes para identificar padrões sem precisar gerenciar nenhuma infraestrutura de servidor ou banco de dados. Você pode receber respostas para suas perguntas em alguns segundos, não importa o tamanho das tabelas, usando a enorme escalonabilidade e a infraestrutura gerenciada do BigQuery.
Plataforma Google Maps
A Plataforma Google Maps oferece acesso programático aos dados de mapa, lugar e rota do Google. Mais de dois milhões de sites e apps usam a API atualmente para fornecer mapas incorporados e consultas baseadas em localização aos usuários.
A camada de desenho da API JavaScript da Plataforma Google Maps permite desenhar formas no mapa. Esses dados podem ser convertidos em entradas para executar consultas em tabelas do BigQuery que têm valores de latitude e longitude armazenados em colunas.
Para começar, você precisa de um projeto do Google Cloud Platform com as APIs BigQuery e Maps ativadas.
2. Etapas da configuração
Conta do Google
Se você ainda não tem uma Conta do Google (Gmail ou Google Apps), crie uma.
Criar um projeto
Faça login no Console do Google Cloud Platform ( console.cloud.google.com) e crie um novo projeto. Na parte de cima da tela, há um menu suspenso "Projeto":
Depois de clicar no menu suspenso do projeto, você vai encontrar um item que permite criar um novo projeto:
Na caixa "Insira um novo nome para seu projeto", digite um nome para o novo projeto, por exemplo, "Codelab do BigQuery":
Um ID do projeto será gerado para você. O ID do projeto é um nome exclusivo em todos os projetos do Google Cloud. Lembre-se do ID do projeto, porque você vai usá-lo mais tarde. O nome acima já foi escolhido e não vai funcionar para você. Insira seu ID do projeto sempre que vir YOUR_PROJECT_ID neste codelab.
Ativar faturamento
Para se inscrever no BigQuery, use o projeto selecionado ou criado na etapa anterior. O faturamento precisa estar ativado neste projeto. Depois que o faturamento for ativado, você poderá ativar a API BigQuery.
A forma de ativação do faturamento depende se você está criando um projeto novo ou reativando o faturamento de um projeto atual.
O Google oferece um teste sem custo financeiro de 12 meses para até US $300 de uso do Google Cloud Platform, que você pode usar neste codelab. Saiba mais em https://cloud.google.com/free/.
Novos projetos
Ao criar um novo projeto, escolha qual das suas contas de faturamento você quer vincular a ele. Se você tiver apenas uma conta de faturamento, ela será vinculada automaticamente ao seu projeto.
Se você não tiver uma conta de faturamento, crie uma e ative o faturamento para seu projeto antes de usar muitos dos recursos do Google Cloud Platform. Para criar uma conta de faturamento e ativar o faturamento do projeto, siga as instruções em Criar uma conta de faturamento.
Projetos atuais
Se você tiver um projeto em que o faturamento foi temporariamente desativado, é possível reativar o faturamento:
- Acesse o Console do Cloud Platform.
- Na lista de projetos, selecione o projeto para o qual você quer reativar o faturamento.
- Abra o menu lateral à esquerda do console e selecione Faturamento
. Você vai precisar selecionar uma conta de faturamento.
- Clique em Definir conta.
Criar uma conta de faturamento
Para criar uma nova conta de faturamento, siga estas etapas:
- Acesse o console do Cloud Platform e faça login ou inscreva-se se ainda não tiver uma conta.
- Abra o menu lateral esquerdo do console e selecione Faturamento
- Clique no botão Nova conta de faturamento. Se esta não for sua primeira conta de faturamento, abra a lista clicando no nome da conta atual, na parte de cima da página, e em Gerenciar contas de faturamento.
- Digite o nome da conta de faturamento e insira as informações. As opções exibidas dependem do país do endereço de faturamento. Para contas nos Estados Unidos, não é possível mudar o status fiscal depois de criar a conta.
- Clique em Enviar e ativar faturamento.
Por padrão, a pessoa que cria a conta é o administrador de faturamento da conta.
Para informações sobre como verificar contas bancárias e adicionar formas de pagamento alternativas, consulte Adicionar, remover ou atualizar uma forma de pagamento.
Ative a API BigQuery
Para ativar a API BigQuery no seu projeto, acesse a página da API BigQuery no Marketplace no console e clique no botão azul "Ativar".
3. Como consultar dados de local no BigQuery
Há três maneiras de consultar dados de localização armazenados como valores de latitude e longitude no BigQuery.
- Consultas de retângulo: especifique a área de interesse como uma consulta que seleciona todas as linhas em um intervalo mínimo e máximo de latitude e longitude.
- Consultas de raio: especifique a área de interesse calculando um círculo ao redor de um ponto usando a fórmula de Haversine e funções matemáticas para modelar o formato da Terra.
- Consultas de polígonos: especifique uma forma personalizada e use uma função definida pelo usuário para expressar a lógica de ponto no polígono necessária para testar se a latitude e a longitude de cada linha estão dentro da forma.
Para começar, use o Editor de consultas na seção BigQuery do console do Google Cloud Platform para executar as seguintes consultas nos dados de táxi de Nova York.
SQL padrão x SQL legado
O BigQuery é compatível com duas versões do SQL: SQL legado e SQL padrão. O último é o padrão ANSI de 2011. Para este tutorial, vamos usar o SQL padrão porque ele tem melhor compliance com os padrões.
Para executar o SQL legado no editor do BigQuery, faça o seguinte:
- Clique no botão "Mais".
- Selecione "Configurações de consulta" no menu suspenso.
- Em "Dialeto SQL", selecione o botão de opção "Legado".
- Clique no botão "Salvar".
Consultas de retângulo
As consultas de retângulo são bem simples de criar no BigQuery. Basta adicionar uma cláusula WHERE
que limite os resultados retornados àqueles com locais entre os valores mínimo e máximo de latitude e longitude.
Teste o exemplo abaixo no console do BigQuery. Essa consulta busca algumas estatísticas médias de viagens que começaram em uma área retangular que contém o centro e a parte baixa de Manhattan. Há dois locais diferentes que você pode testar. Remova o comentário da segunda cláusula WHERE
para executar a consulta em viagens que começaram no aeroporto 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
Os resultados das duas consultas mostram que há grandes diferenças na distância média da viagem, na tarifa e na gorjeta para embarques nos dois locais.
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 raio
As consultas de raio também são fáceis de criar em SQL se você conhece um pouco de matemática. Usando as funções matemáticas do SQL legado do BigQuery, é possível criar uma consulta SQL com a fórmula de Haversine, que aproxima uma área circular ou um segmento esférico na superfície da Terra.
Confira um exemplo de instrução SQL do BigQuery para uma consulta de círculo centralizada em 40.73943, -73.99585
com um raio de 0,1 km.
Ele usa um valor constante de 111,045 quilômetros para aproximar a distância representada por um grau.
Isso se baseia em um exemplo encontrado em 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
O SQL da fórmula de Haversine parece complicado, mas tudo o que você precisa fazer é inserir a coordenada do centro do círculo, o raio e os nomes do projeto, do conjunto de dados e da tabela para o BigQuery.
Confira um exemplo de consulta que calcula algumas estatísticas médias de viagens para embarques em um raio de 100 metros do Empire State Building. Copie e cole no console da Web do BigQuery para ver os resultados. Mude a latitude e a longitude para comparar com outras áreas, como a localização no 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
Os resultados da consulta estão abaixo. É possível notar grandes diferenças na gorjeta média, na tarifa, na distância da viagem, no tamanho proporcional da gorjeta em relação à tarifa e na tarifa média por milha percorrida.
Empire State Building:
avg_tip | avg_fare | avg_distance | avg_tip_pc | avg_fare_mile |
1.17 | 11.08 | 45,28 | 10,53 | 6,42 |
No 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
O SQL não oferece suporte a consultas usando formas arbitrárias que não sejam retângulos e círculos. O BigQuery não tem um tipo de dados de geometria nativo nem um índice espacial. Portanto, para executar consultas usando formas de polígonos, você precisa de uma abordagem diferente das consultas SQL simples. Uma abordagem é definir uma função de geometria em JavaScript e executá-la como uma função definida pelo usuário (UDF) no BigQuery.
Muitas operações de geometria podem ser escritas em JavaScript. Assim, é fácil pegar uma e executar em uma tabela do BigQuery que contém valores de latitude e longitude. Você precisa transmitir o polígono personalizado usando uma UDF e realizar um teste em cada linha, retornando apenas as linhas em que a latitude e a longitude estão dentro do polígono. Saiba mais sobre UDFs na referência do BigQuery.
Algoritmo ponto em polígono
Há muitas maneiras de calcular se um ponto está dentro de um polígono em JavaScript. Aqui está uma porta de C de uma implementação conhecida que usa um algoritmo de traçado de raio para determinar se um ponto está dentro ou fora de um polígono contando o número de vezes que uma linha infinitamente longa cruza o limite da forma. Isso leva apenas algumas linhas 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;
}
Como migrar para JavaScript
A versão JavaScript desse algoritmo é assim:
/* 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;
}
Ao usar o SQL padrão no BigQuery, a abordagem de UDF exige apenas uma instrução, mas a UDF precisa ser definida como uma função temporária na instrução. Veja um exemplo. Cole a instrução SQL abaixo na janela do 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
Parabéns!
Agora você executou três tipos de consultas espaciais usando o BigQuery. Como você viu, o local faz uma grande diferença nos dados de resultado das consultas nesse conjunto de dados. No entanto, a menos que você adivinhe onde executar as consultas, é difícil descobrir padrões espaciais ad hoc usando apenas consultas SQL.
Se pudéssemos visualizar os dados em um mapa e explorar as informações definindo áreas de interesse arbitrárias! Com as APIs Google Maps, você pode fazer exatamente isso. Primeiro, ative a API Maps, configure uma página da Web simples na sua máquina local e comece a usar a API BigQuery para enviar consultas da sua página da Web.
4. Como trabalhar com as APIs do Google Maps
Depois de executar algumas consultas espaciais simples, a próxima etapa é visualizar a saída para conferir os padrões. Para isso, você vai ativar a API Maps, criar uma página da Web que envia consultas de um mapa para o BigQuery e desenha os resultados no mapa.
Ativar a API Maps JavaScript
Para este codelab, você precisa ativar a API Maps JavaScript da Plataforma Google Maps no seu projeto. Para isso, faça o seguinte:
- No console do Google Cloud Platform, acesse o Marketplace.
- No Marketplace, pesquise "API Maps JavaScript".
- Clique no bloco da API Maps JavaScript nos resultados da pesquisa.
- Clique no botão "Ativar".
Gerar uma chave de API
Para fazer solicitações à Plataforma Google Maps, é necessário gerar uma chave de API e enviá-la com todas as solicitações. Para gerar uma chave de API, faça o seguinte:
- No console do Google Cloud Platform, clique no menu de hambúrguer para abrir a navegação à esquerda.
- Selecione APIs e serviços > Credenciais.
- Clique no botão "Criar credencial" e selecione "Chave de API".
- Copie a nova chave de API
Baixar o código e configurar um servidor da Web
Clique no botão abaixo para fazer o download de todo o código deste codelab:
Descompacte o arquivo ZIP transferido por download. Isso descompacta uma pasta raiz (bigquery
) que contém uma pasta para cada etapa deste codelab, além de todos os recursos necessários.
As pastas stepN
contêm o estado final desejado de cada etapa deste codelab. Elas servem como referência. Vamos fazer todo o trabalho de programação no diretório chamado work
.
Configurar um servidor da Web local
Embora você possa usar seu próprio servidor da Web, este codelab foi projetado para funcionar bem com o Chrome Web Server. Se você ainda não tiver o app instalado, poderá instalá-lo pela Chrome Web Store.
Depois de instalar, abra o app. No Chrome, faça o seguinte:
- Abra o Chrome
- Na barra de endereço na parte de cima, digite chrome://apps.
- Pressione "Enter"
- Na janela que abrir, clique no ícone do servidor da Web. Você também pode clicar com o botão direito do mouse em um app para abrir em uma guia normal ou fixada, em tela cheia ou em uma nova janela
. Em seguida, você verá esta caixa de diálogo, que permite configurar seu servidor da Web local:
- Clique em "ESCOLHER PASTA" e selecione o local em que você baixou os arquivos de exemplo do codelab.
- Na seção "Opções", marque a caixa ao lado de "Mostrar automaticamente index.html":
- Deslize a chave "Servidor da Web: INICIADO" para a esquerda e depois para a direita para parar e reiniciar o servidor da Web.
5. Carregando o mapa e as ferramentas de desenho
Criar uma página de mapa básica
Comece com uma página HTML simples que carrega um mapa do Google usando a API Maps JavaScript e algumas linhas de JavaScript. O código do Exemplo de mapa simples da Plataforma Google Maps é um ótimo ponto de partida. Ele é reproduzido aqui para você copiar e colar no editor de texto ou ambiente de desenvolvimento integrado de sua escolha. Também é possível encontrá-lo abrindo index.html
no repositório baixado.
- Copie
index.html
para a pastawork
na sua cópia local do repositório. - Copie a pasta img/ para a pasta work/ na sua cópia local do repositório.
- Abra work/
index.html
no editor de texto ou ambiente de desenvolvimento integrado. - Substitua
YOUR_API_KEY
pela chave de API que você criou antes.
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
async defer></script>
- No navegador, abra
localhost:<port>/work
, em queport
é o número da porta especificado na configuração do servidor da Web local. A porta padrão é8887
. Seus primeiros mapas vão aparecer.
Se você receber uma mensagem de erro no navegador, verifique se a chave de API está correta e se o servidor da Web local está ativo.
Mudar o local padrão e o nível de zoom
O código que define o local e o nível de zoom está nas linhas 27 e 28 de index.html e está centralizado em Sydney, Austrália:
<script>
let map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}
</script>
Este tutorial funciona com os dados de corridas de táxi do BigQuery em Nova York. Em seguida, você vai mudar o código de inicialização do mapa para centralizar em um local na cidade de Nova York em um nível de zoom adequado. 13 ou 14 devem funcionar bem.
Para isso, atualize o bloco de código acima para o seguinte, centralizando o mapa no Empire State Building e ajustando o nível de zoom para 14:
<script>
let map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.7484405, lng: -73.9878531},
zoom: 14
});
}
</script>
Em seguida, atualize o mapa no navegador para conferir os resultados.
Carregar as bibliotecas de desenho e visualização
Para adicionar recursos de desenho ao mapa, mude o script que carrega a API Maps JavaScript adicionando um parâmetro opcional que informa à Plataforma Google Maps para ativar a biblioteca de desenho.
Este codelab também usa o HeatmapLayer
. Portanto, você também vai atualizar o script para solicitar a biblioteca de visualização. Para fazer isso, adicione o parâmetro libraries
e especifique as bibliotecas visualization
e drawing
como valores separados por vírgula. Por exemplo, libraries=
visualization,drawing
Ele será parecido com o seguinte:
<script src='http://maps.googleapis.com/maps/api/js?libraries=visualization,drawing&callback=initMap&key=YOUR_API_KEY' async defer></script>
Adicionar o DrawingManager
Para usar formas desenhadas pelo usuário como entrada de uma consulta, adicione o DrawingManager
ao mapa com as ferramentas Circle
, Rectangle
e Polygon
ativadas.
É uma boa ideia colocar todo o código de configuração do DrawingManager
em uma nova função. Portanto, na sua cópia de index.html, faça o seguinte:
- Adicione uma função chamada
setUpDrawingTools()
com o código a seguir para criar oDrawingManager
e definir a propriedademap
para referenciar o objeto de mapa na página.
As opções transmitidas para google.maps.drawing.DrawingManager(options)
definem o tipo de desenho de forma padrão e as opções de exibição para formas desenhadas. Para selecionar áreas do mapa e enviar como consultas, as formas precisam ter opacidade zero. Para mais informações sobre as opções disponíveis, consulte Opções do 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);
}
- Chame
setUpDrawingTools()
na funçãoinitMap()
depois que o objeto de mapa for criado.
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
zoom: 12
});
setUpDrawingTools();
}
- Atualize o index.html e verifique se as ferramentas de desenho estão visíveis. Verifique também se é possível usá-los para desenhar círculos, retângulos e polígonos.
Você pode clicar e arrastar para desenhar círculos e retângulos, mas os polígonos precisam ser desenhados clicando em cada vértice e clicando duas vezes para terminar a forma.
Processar eventos de desenho
Você precisa de um código para processar os eventos disparados quando um usuário termina de desenhar uma forma, assim como precisa das coordenadas das formas desenhadas para construir consultas SQL.
Vamos adicionar o código para isso em uma etapa posterior, mas, por enquanto, vamos criar três manipuladores de eventos vazios para processar os eventos rectanglecomplete
, circlecomplete
e polygoncomplete
. Os manipuladores não precisam executar nenhum código nesta etapa.
Adicione o seguinte à parte de baixo da função 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.
});
Você pode encontrar um exemplo funcional desse código na sua cópia local do repositório, na pasta step2
: step2/map.html.
6. Como usar a API cliente do BigQuery
A API de cliente do Google BigQuery ajuda você a evitar a escrita de muito código boilerplate necessário para criar solicitações, analisar respostas e processar a autenticação. Este codelab usa a API BigQuery pela biblioteca de cliente das APIs do Google para JavaScript, já que vamos desenvolver um aplicativo baseado em navegador.
Em seguida, você vai adicionar código para carregar essa API em uma página da Web e usá-la para interagir com o BigQuery.
Adicionar a API de cliente do Google para JavaScript
Você vai usar a API de cliente do Google para JavaScript para executar consultas no BigQuery. Na sua cópia de index.html
(na pasta work
), carregue a API usando uma tag <script>
como esta. Coloque a tag imediatamente abaixo da tag <script>
que carrega a API Maps:
<script src='https://apis.google.com/js/client.js'></script>
Depois de carregar a API de cliente do Google, autorize o usuário a acessar os dados no BigQuery. Para isso, use o OAuth 2.0. Primeiro, você precisa configurar algumas credenciais no projeto do console do Google Cloud.
Criar credenciais do OAuth 2.0
- No console do Google Cloud, no Menu de navegação, selecione APIs e serviços > Credenciais.
Antes de configurar suas credenciais, adicione uma configuração para a tela de autorização que um usuário final do aplicativo vai ver quando autorizar o app a acessar dados do BigQuery em nome dele.
Para fazer isso, clique na guia Tela de permissão OAuth. 2. Você precisa adicionar a API BigQuery aos escopos desse token. Clique no botão Adicionar escopo na seção "Escopos para APIs do Google". 3. Na lista, marque a caixa ao lado da entrada API BigQuery com o escopo ../auth/bigquery
. 4. Clique em Adicionar. 5. Digite um nome no campo "Nome do aplicativo". 6. Clique em Salvar para armazenar suas configurações. 7. Em seguida, crie seu ID do cliente OAuth. Para fazer isso, clique em Criar credenciais:
- No menu suspenso, clique em ID do cliente OAuth.
- Em "Tipo de aplicativo", selecione Aplicativo da Web.
- No campo "Nome do aplicativo", digite um nome para o projeto. Por exemplo, "BigQuery e Maps".
- Em Restrições, no campo "Origens JavaScript autorizadas", insira o URL de localhost, incluindo os números de porta. Por exemplo:
http://localhost:8887
- Clique no botão Criar.
Um pop-up mostra o ID e a chave secreta do cliente. Você precisa do ID do cliente para fazer a autenticação no BigQuery. Copie e cole em work/index.html
como uma nova variável global do JavaScript chamada clientId
.
let clientId = 'YOUR_CLIENT_ID';
7. Autorização e inicialização
Sua página da Web precisa autorizar o usuário a acessar o BigQuery antes de inicializar o mapa. Neste exemplo, usamos o OAuth 2.0, conforme descrito na seção de autorização da documentação da API JavaScript Client. Você precisa usar o ID do cliente OAuth e o ID do projeto para enviar consultas.
Quando a API Google Client for carregada na página da Web, siga estas etapas:
- Autorize o usuário.
- Se autorizado, carregue a API BigQuery.
- Carregue e inicialize o mapa.
Consulte step3/map.html para ver um exemplo de como ficaria a página HTML final.
Autorizar o usuário
O usuário final do aplicativo precisa autorizar o acesso aos dados no BigQuery em nome dele. A API cliente do Google para JavaScript processa a lógica do OAuth para fazer isso.
Em um aplicativo do mundo real, você tem muitas opções de como integrar a etapa de autorização.
Por exemplo, você pode chamar authorize()
de um elemento da interface, como um botão, ou quando a página for carregada. Aqui, escolhemos autorizar o usuário depois que a API cliente do Google para JavaScript for carregada, usando uma função de callback no método gapi.load()
.
Escreva um código imediatamente após a tag <script>
que carrega a API cliente do Google para JavaScript para carregar a biblioteca de cliente e o módulo de autenticação. Assim, podemos autenticar o usuário imediatamente.
<script src='https://apis.google.com/js/client.js'></script>
<script type='text/javascript'>
gapi.load('client:auth', authorize);
</script>
Ao autorizar, carregue a API BigQuery
Depois que o usuário for autorizado, carregue a API BigQuery.
Primeiro, chame gapi.auth.authorize()
com a variável clientId
que você adicionou na etapa anterior. Processe a resposta em uma função de callback chamada handleAuthResult
.
O parâmetro immediate
controla se um pop-up é mostrado ao usuário. Defina como true
para suprimir o pop-up de autorização se o usuário já estiver autorizado.
Adicione uma função à sua página chamada handleAuthResult()
. A função precisa receber um parâmetro authresult
, que permite controlar o fluxo de lógica dependendo se o usuário foi autorizado ou não.
Adicione também uma função chamada loadApi
para carregar a API BigQuery se o usuário for autorizado.
Adicione lógica à função handleAuthResult()
para chamar loadApi()
se um objeto authResult
for transmitido à função e se a propriedade error
do objeto tiver um valor de false
.
Adicione código à função loadApi()
para carregar a API BigQuery usando o 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');
}
Carregar o mapa
A etapa final é inicializar o mapa. Para fazer isso, é necessário mudar um pouco a ordem da lógica. No momento, ele é inicializado quando o JavaScript da API Maps é carregado.
Para fazer isso, chame a função initMap()
do método then()
depois do método load()
no objeto gapi.client
.
// Load BigQuery client API
function loadApi(){
gapi.client.load('bigquery', 'v2').then(
() => initMap()
);
}
8. Conceitos da API BigQuery
As chamadas da API BigQuery geralmente são executadas em segundos, mas podem não retornar uma resposta imediatamente. Você precisa de alguma lógica para pesquisar o BigQuery e descobrir o status de jobs de longa duração, além de buscar os resultados somente quando o job for concluído.
O código completo desta etapa está em step4/map.html.
Como enviar uma solicitação
Adicione uma função JavaScript a work/index.html
para enviar uma consulta usando a API e algumas variáveis para armazenar os valores do conjunto de dados e do projeto do BigQuery que contêm a tabela a ser consultada, além do ID do projeto que vai receber a cobrança por encargos.
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 o status de um job
A função checkJobStatus
abaixo mostra como verificar o status de um job periodicamente usando o método da API get
e o jobId
retornado pela solicitação de consulta original. Confira um exemplo que é executado a cada 500 milissegundos até a conclusão do job.
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]);
});
}
Altere o método sendQuery
para chamar o método checkJobStatus()
como um callback na chamada request.execute()
. Transmita o ID do job para checkJobStatus
. Isso é exposto pelo objeto de resposta 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));
}
Como receber os resultados de uma consulta
Para receber os resultados de uma consulta quando ela terminar de ser executada, use a chamada de API jobs.getQueryResults
. Adicione uma função à página chamada getQueryResults()
, que aceita um 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. Consultar dados de local com a API BigQuery
Há três maneiras de usar o SQL para executar consultas espaciais nos dados do BigQuery:
- selecionar por retângulo (também conhecido como caixa delimitadora);
- selecionar por raio e
- o recurso avançado Funções definidas pelo usuário.
Há exemplos de consultas de caixa delimitadora e raio na seção "Funções matemáticas" da referência do SQL legado do BigQuery, em "Exemplos avançados".
Para consultas de caixa delimitadora e raio, chame o método query
da API BigQuery. Construa o SQL para cada consulta e transmita-o à função sendQuery
criada na etapa anterior.
Um exemplo funcional do código para esta etapa está em step4/map.html.
Consultas de retângulo
A maneira mais simples de mostrar dados do BigQuery em um mapa é solicitar todas as linhas em que a latitude e a longitude estão dentro de um retângulo, usando uma comparação "menor que" e "maior que". Pode ser a visualização atual do mapa ou uma forma desenhada nele.
Para usar uma forma desenhada pelo usuário, mude o código em index.html
para processar o evento de desenho disparado quando um retângulo é concluído. Neste exemplo, o código usa getBounds()
no objeto retângulo para receber um objeto que representa a extensão do retângulo em coordenadas do mapa e o transmite para uma função chamada rectangleQuery
:
drawingManager.addListener('rectanglecomplete', rectangle => rectangleQuery(rectangle.getBounds()));
A função rectangleQuery
só precisa usar as coordenadas superior direita (nordeste) e inferior esquerda (sudoeste) para criar uma comparação menor que/maior que em relação a cada linha na sua tabela do BigQuery. Confira um exemplo que consulta uma tabela com colunas chamadas 'pickup_latitude'
e 'pickup_longitude'
, que armazenam os valores de local.
Especificar a tabela do BigQuery
Para consultar uma tabela usando a API BigQuery, forneça o nome dela de forma totalmente qualificada na sua consulta SQL. O formato no SQL padrão é project.dataset.tablename
. No SQL legado, é project.dataset.tablename
.
Há muitas tabelas de viagens de táxi de Nova York disponíveis. Para conferir, acesse o console da Web do BigQuery e expanda o item de menu "Conjuntos de dados públicos". Encontre o conjunto de dados chamado new_york
e expanda-o para ver as tabelas. Escolha a tabela de corridas de táxi amarelo: bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016
).
Especificar o ID do projeto
Na chamada de API, especifique o nome do projeto do Google Cloud Platform para fins de faturamento. Neste codelab, não é o mesmo projeto que contém a tabela. Se você estivesse trabalhando com uma tabela criada no seu próprio projeto ao fazer upload de dados, esse ID seria o mesmo da instrução SQL.
Adicione variáveis JavaScript ao seu código para manter referências ao projeto de conjuntos de dados públicos que contém a tabela que você está consultando, além do nome da tabela e do conjunto de dados. Você também precisa de uma variável separada para se referir ao seu ID do projeto de faturamento.
Adicione variáveis JavaScript globais chamadas billingProjectId, publicProjectId, datasetId
e tableName
à sua cópia de index.html.
Inicialize as variáveis 'publicProjectId'
, 'datasetId'
e 'tableName'
com os detalhes do projeto de conjuntos de dados públicos do BigQuery. Inicialize billingProjectId
com seu próprio ID do projeto (aquele que você criou em "Como configurar" no início deste codelab).
let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId = 'bigquery-public-data';
let datasetId = 'new_york_taxi_trips';
let tableName = 'tlc_yellow_trips_2016';
Agora adicione duas funções ao seu código para gerar o SQL e enviar a consulta ao BigQuery usando a função sendQuery
criada na etapa anterior.
A primeira função precisa ser chamada de rectangleSQL()
e aceitar dois argumentos, um par de objetos google.Maps.LatLng
que representam os cantos do retângulo em coordenadas do mapa.
A segunda função precisa ser chamada de rectangleQuery()
. Isso transmite o texto da consulta para a função 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;
}
Neste ponto, você tem código suficiente para enviar uma consulta ao BigQuery de todas as linhas contidas em um retângulo desenhado pelo usuário. Antes de adicionar outros métodos de consulta para círculos e formas à mão livre, vamos ver como processar os dados retornados de uma consulta.
10. Visualizar a resposta
As tabelas do BigQuery podem ser muito grandes (petabytes de dados) e crescer em centenas de milhares de linhas por segundo. Por isso, é importante tentar limitar a quantidade de dados retornados para que eles possam ser mostrados no mapa. Desenhar a localização de cada linha em um conjunto de resultados muito grande (dezenas de milhares de linhas ou mais) resulta em um mapa ilegível. Há muitas técnicas para agregar os locais na consulta SQL e no mapa, e você pode limitar os resultados que uma consulta vai retornar.
O código completo desta etapa está disponível em step5/map.html.
Para manter a quantidade de dados transferidos para sua página da Web em um tamanho razoável para este codelab, modifique a função rectangleSQL()
para adicionar uma instrução que limite a resposta a 10.000 linhas. No exemplo abaixo, isso é especificado em uma variável global chamada recordLimit
, para que todas as funções de consulta possam usar o mesmo 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 a densidade de locais, use um mapa de calor. A API Maps JavaScript tem uma classe HeatmapLayer para essa finalidade. O HeatmapLayer usa uma matriz de coordenadas de latitude e longitude. Por isso, é fácil converter as linhas retornadas da consulta em um mapa de calor.
Na função getQueryResults
, transmita a matriz response.result.rows
para uma nova função JavaScript chamada doHeatMap()
, que vai criar um mapa de calor.
Cada linha terá uma propriedade chamada f
, que é uma matriz de colunas. Cada coluna terá uma propriedade v
que contém o valor.
Seu código precisa fazer um loop nas colunas de cada linha e extrair os valores.
Na consulta SQL, você pediu apenas os valores de latitude e longitude dos pontos de embarque de táxi. Portanto, haverá apenas duas colunas na resposta.
Não se esqueça de chamar setMap()
na camada de mapa de calor quando tiver atribuído a matriz de posições a ela. Isso vai deixar o lugar visível no mapa.
Veja um exemplo:
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);
}
Neste ponto, você poderá:
- Abra a página e autorize o BigQuery
- Desenhe um retângulo em algum lugar de Nova York
- Confira os resultados da consulta visualizados como um mapa de calor.
Confira um exemplo do resultado de uma consulta de retângulo nos dados de táxi amarelo de Nova York de 2016, desenhado como um mapa de calor. Isso mostra a distribuição de coletas ao redor do Empire State Building em um sábado de julho:
11. Consultar por raio ao redor de um ponto
As consultas de raio são muito semelhantes. Usando as funções matemáticas do SQL legado do BigQuery, é possível criar uma consulta SQL com a fórmula de Haversine, que aproxima uma área circular na superfície da Terra.
Usando a mesma técnica para retângulos, é possível processar um evento OverlayComplete
para receber o centro e o raio de um círculo desenhado pelo usuário e criar o SQL para a consulta da mesma forma.
Um exemplo funcional do código para esta etapa está incluído no repositório de código como step6/map.html.
drawingManager.addListener('circlecomplete', circle => circleQuery(circle));
Na sua cópia de index.html, adicione duas novas funções vazias: circleQuery()
e haversineSQL()
.
Em seguida, adicione um gerenciador de eventos circlecomplete
que transmite o centro e o raio para uma nova função chamada circleQuery().
.
A função circleQuery()
vai chamar haversineSQL()
para construir o SQL da consulta e, em seguida, enviar a consulta chamando a função sendQuery()
, conforme o exemplo de código a seguir.
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;
}
Confira!
Adicione o código acima e use a ferramenta "Círculo" para selecionar uma área do mapa. O resultado será parecido com este:
12. Como consultar formas arbitrárias
Resumo: o SQL não oferece suporte a consultas usando formas arbitrárias que não sejam retângulos e círculos. O BigQuery não tem um tipo de dados de geometria nativo. Portanto, para executar consultas usando formas de polígonos, você precisa de uma abordagem diferente das consultas SQL simples.
Um recurso muito útil do BigQuery que pode ser usado para isso são as funções definidas pelo usuário (UDFs). As UDFs executam código JavaScript em uma consulta SQL.
O código funcional para esta etapa está em step7/map.html.
UDFs na API BigQuery
A abordagem da API BigQuery para UDFs é um pouco diferente do console da Web: você precisa chamar jobs.insert method
.
Para consultas em SQL padrão pela API, basta uma única instrução SQL para usar uma função definida pelo usuário. O valor de useLegacySql
precisa ser definido como false
. O exemplo de JavaScript abaixo mostra uma função que cria e envia um objeto de solicitação para inserir um novo job, neste caso, uma consulta com uma função definida pelo usuário.
Um exemplo prático dessa abordagem está em step7/map.html.
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));
}
A consulta SQL é criada da seguinte maneira:
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;
}
Há duas coisas acontecendo aqui. Primeiro, o código cria a instrução CREATE TEMPORARY FUNCTION
, que encapsula o código JavaScript para determinar se um determinado ponto está dentro de um polígono. As coordenadas do polígono são inseridas usando a chamada do método JSON.stringify(poly)
para converter uma matriz JavaScript de pares de coordenadas x,y em uma string. O objeto de polígono é transmitido como um argumento para a função que cria o SQL.
Em segundo lugar, o código cria a instrução SQL SELECT
principal. A UDF é chamada na expressão WHERE
neste exemplo.
Integrar com a API Maps
Para usar isso com a biblioteca de desenho da API Maps, precisamos salvar o polígono desenhado pelo usuário e transmiti-lo para a parte da UDF da consulta SQL.
Primeiro, precisamos processar o evento de desenho polygoncomplete
para receber as coordenadas da forma como uma matriz de pares de longitude e latitude:
drawingManager.addListener('polygoncomplete', polygon => {
let path = polygon.getPaths().getAt(0);
let queryPolygon = path.map(element => {
return [element.lng(), element.lat()];
});
polygonQuery(queryPolygon);
});
A função polygonQuery
pode construir as funções JavaScript da UDF como uma string, bem como a instrução SQL que vai chamar a função da UDF.
Consulte step7/map.html para ver um exemplo prático.
Exemplo de saída
Confira um exemplo de resultado da consulta de embarques de Dados de táxi amarelo da TLC de Nova York de 2016 no BigQuery usando um polígono à mão livre, com os dados selecionados desenhados como um mapa de calor.
13. Indo mais longe
Confira algumas sugestões de como estender este codelab para analisar outros aspectos dos dados. Você pode encontrar um exemplo funcional dessas ideias em step8/map.html no repositório de código.
Como mapear desistências
Até agora, só mapeamos locais de retirada. Ao solicitar as colunas dropoff_latitude
e dropoff_longitude
e modificar o código do mapa de calor para representar esses dados, você pode conferir os destinos das viagens de táxi que começaram em um local específico.
Por exemplo, vamos ver onde os táxis costumam deixar as pessoas quando elas pedem uma corrida perto do Empire State Building.
Mude o código da instrução SQL em polygonSql()
para solicitar essas colunas além do local de retirada.
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;
}
A função doHeatMap
pode usar os valores de desistência. O objeto de resultado tem um esquema que pode ser inspecionado para encontrar a localização dessas colunas na matriz. Nesse caso, eles estariam nas posições de índice 2 e 3. Esses índices podem ser lidos de uma variável para tornar o código mais gerenciável. A maxIntensity
do mapa de calor é definida para mostrar a densidade de 20 desistências por pixel como o máximo.
Adicione algumas variáveis para mudar as colunas usadas nos dados do 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 é um mapa de calor que mostra a distribuição de desembarques de todos os embarques imediatamente ao redor do Empire State Building em 2016. É possível ver grandes concentrações (os pontos vermelhos) de destinos no centro, especialmente ao redor da Times Square, bem como ao longo da 5ª Avenida entre a Rua 23 e a Rua 14. Outros locais de alta densidade não mostrados nesse nível de zoom incluem os aeroportos La Guardia e JFK, o World Trade Center e o Battery Park.
Estilizar o mapa de base
Ao criar um mapa do Google usando a API Maps JavaScript, é possível definir o estilo do mapa usando um objeto JSON. Para visualizações de dados, pode ser útil silenciar as cores no mapa. É possível criar e testar estilos de mapa usando o assistente de estilização da API Google Maps em mapstyle.withgoogle.com.
É possível definir um estilo de mapa ao inicializar um objeto de mapa ou a qualquer momento depois. Veja como adicionar um estilo personalizado na função 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();
}
O exemplo de estilo abaixo mostra um mapa em escala de cinza com rótulos de pontos de interesse.
[
{
"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"
}
]
}
]
Dar feedback ao usuário
Embora o BigQuery geralmente dê uma resposta em segundos, às vezes é útil mostrar ao usuário que algo está acontecendo enquanto a consulta está em execução.
Adicione à página da Web uma interface que mostre a resposta da função checkJobStatus()
e um gráfico animado para indicar que a consulta está em andamento.
As informações que você pode mostrar incluem a duração da consulta, a quantidade de dados retornados e a quantidade de dados processados.
Adicione um pouco de HTML depois do mapa <div>
para criar um painel na página que vai mostrar o número de linhas retornadas por uma consulta, o tempo que ela levou e a quantidade de dados processados.
<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>
A aparência e a posição desse painel são controladas por CSS. Adicione CSS para posicionar o painel no canto superior esquerdo da página abaixo dos botões de tipo de mapa e da barra de ferramentas de desenho, como no snippet abaixo.
#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;
}
O gráfico animado pode ser adicionado à página, mas fica oculto até ser necessário. Além disso, alguns códigos JavaScript e CSS são usados para mostrar o gráfico quando um job do BigQuery está em execução.
Adicione um pouco de HTML para mostrar um gráfico animado. Há um arquivo de imagem chamado loader.gif
na pasta img
do repositório de código.
<img id="spinner" src="img/loader.gif">
Adicione um pouco de CSS para posicionar a imagem e ocultá-la por padrão até que seja necessária.
#spinner {
position: absolute;
top: 50%;
left: 50%;
margin-left: -32px;
margin-top: -32px;
opacity: 0;
z-index: -1000;
}
Por fim, adicione um pouco de JavaScript para atualizar o painel de status e mostrar ou ocultar o gráfico quando uma consulta estiver em execução. É possível usar o objeto response
para atualizar o painel de acordo com as informações disponíveis.
Ao verificar um job atual, há uma propriedade response.statistics
que pode ser usada. Quando o job for concluído, você poderá acessar as propriedades response.totalRows
e response.totalBytesProcessed
. É útil para o usuário converter milissegundos em segundos e bytes em gigabytes para exibição, conforme mostrado no exemplo de código abaixo.
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';
}
}
Chame esse método quando houver uma resposta a uma chamada checkJobStatus()
e quando os resultados da consulta forem buscados. Exemplo:
// 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 ativar ou desativar o gráfico animado, adicione uma função que controle a visibilidade dele. Essa função vai alternar a opacidade de qualquer elemento DOM HTML transmitido a ela.
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 fim, chame esse método antes de processar uma consulta e depois que o resultado da consulta retornar do BigQuery.
Esse código chama a função fadeToggle
quando o usuário termina de desenhar um retângulo.
drawingManager.addListener('rectanglecomplete', rectangle => {
//show an animation to indicate that something is happening.
fadeToggle(document.getElementById('spinner'));
rectangleQuery(rectangle.getBounds());
});
Quando a resposta da consulta for recebida, chame fadeToggle()
novamente para ocultar o 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);
})
}
A página vai ficar parecida com esta.
Confira o exemplo completo em step8/map.html.
14. Considerações
Muitos marcadores
Quando você trabalha com tabelas muito grandes, é possível que o excesso de linhas retornadas pelas consultas afete a exibição eficiente no mapa. Para limitar os resultados, adicione uma cláusula WHERE
ou uma instrução LIMIT
.
Desenhar muitos marcadores pode tornar o mapa ilegível. Considere usar um HeatmapLayer
para mostrar a densidade ou marcadores de cluster para indicar onde muitos pontos de dados estão usando um único símbolo por cluster. Confira mais detalhes no nosso tutorial de agrupamento de marcadores.
Como otimizar consultas
O BigQuery verifica a tabela inteira com cada consulta. Para otimizar o uso da cota do BigQuery, selecione apenas as colunas necessárias na consulta.
As consultas serão mais rápidas se você armazenar a latitude e longitude como números de ponto flutuante em vez de strings.
Exportar resultados interessantes
Os exemplos aqui exigem que o usuário final seja autenticado na tabela do BigQuery, o que não é adequado para todos os casos de uso. Depois de descobrir alguns padrões interessantes, é mais fácil compartilhar essas informações com um público maior exportando os resultados do BigQuery e criando um conjunto de dados estático usando a camada de dados do Google Maps.
A parte legal chata
Não se esqueça dos Termos de Serviço da Plataforma Google Maps. Para mais detalhes sobre os preços da Plataforma Google Maps, consulte a documentação on-line.
Jogue com mais dados!
Há vários conjuntos de dados públicos no BigQuery com colunas de latitude e longitude, por exemplo, os conjuntos de dados de táxi de Nova York de 2009 a 2016, os dados de viagens do Uber e da Lyft em Nova York e o conjunto de dados GDELT.
15. Parabéns!
Esperamos que isso ajude você a começar a usar rapidamente algumas consultas geográficas em tabelas do BigQuery para descobrir padrões e visualizá-los em um mapa do Google. Boa navegação!
A seguir
Se quiser saber mais sobre a Plataforma Google Maps ou o BigQuery, confira as seguintes sugestões.
Consulte O que é o BigQuery? para saber mais sobre o serviço de data warehouse sem servidor e em escala de petabyte do Google.
Consulte o guia de instruções para criar um aplicativo simples usando a API BigQuery.
Consulte o guia para desenvolvedores da biblioteca de desenhos para mais detalhes sobre como ativar a interação do usuário para desenhar formas em um mapa do Google.
Confira outras maneiras de visualizar dados em um mapa do Google.
Consulte o guia de primeiros passos da API cliente JavaScript para entender os conceitos básicos de uso da API cliente para acessar outras APIs do Google.