Consultar e visualizar dados de local no BigQuery com a Plataforma Google Maps (JavaScript)

1. Visão geral

O Maps pode ser uma ferramenta muito poderosa ao visualizar os padrões em um conjunto de dados relacionados à localização de alguma maneira. Essa relação pode ser o nome de um lugar, um valor de latitude e longitude específico ou o nome de uma área que tem um limite específico, como uma quantidade populacional ou um código postal.

Quando esses conjuntos de dados ficam muito grandes, pode ser 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 criar a consulta e visualizar a saída, você pode explorar rapidamente os padrões geográficos nos seus dados com pouca configuração ou codificação e sem ter que gerenciar um sistema para armazenar conjuntos de dados muito grandes.

O que você vai criar

Neste codelab, você criará e executará algumas consultas que demonstram como fornecer insights com base na localização em conjuntos de dados públicos muito grandes usando o BigQuery. Você também criará uma página da Web que carrega um mapa usando a API JavaScript da Plataforma Google Maps, depois executa e visualiza consultas espaciais nos mesmos grandes conjuntos de dados públicos usando a biblioteca de cliente de APIs do Google para JavaScript e a API BigQuery.

O que você vai aprender

  • Como consultar conjuntos de dados de local 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 Maps a uma página da Web e permitir que os usuários desenhem formas nela
  • Como visualizar consultas em grandes conjuntos de dados em um mapa do Google, como na imagem de exemplo abaixo, que mostra a densidade dos locais de desembarque de táxis em 2016 em viagens que começam do quarteirão ao redor do Empire State Building.

Screen Shot 2017-05-09 at 11.01.12 AM.png

Pré-requisitos

  • Conhecimento básico de HTML, CSS, JavaScript, SQL e Chrome DevTools
  • Um navegador da Web moderno, como 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. Ela 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 é possível explorar visualmente conjuntos de dados muito grandes para analisar os padrões sem precisar gerenciar nenhuma infraestrutura de servidor ou banco de dados. É possível encontrar respostas para suas dúvidas em poucos segundos, não importa o tamanho das suas 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 trajeto do Google. Mais de 2 milhões de sites e apps usam o Google Maps para fornecer mapas incorporados e consultas baseadas em localização aos usuários.

Com a camada de desenho da API JavaScript da Plataforma Google Maps, você pode desenhar formas no mapa. Eles podem ser convertidos em entrada para executar consultas nas 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 projeto. Na parte superior da tela, há um menu suspenso "Project":

f2a353c3301dc649.png

Depois de clicar nesse menu suspenso do projeto, você verá um item que permite criar um novo projeto:

56a42dfa7ac27a35.png

Na caixa que diz "Digite um novo nome para o projeto", digite um nome para o novo projeto, por exemplo, "Codelab do BigQuery":

Codelab: criar projeto (1).png

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 seu ID do projeto, porque você vai usá-lo mais tarde. O nome acima já está em uso e não funcionará para você. Insira seu próprio ID do projeto sempre que encontrar 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 de ativar o faturamento, você pode ativar a API BigQuery.

A forma de ativação do faturamento depende se você está criando um novo projeto ou se está reativando o faturamento de um projeto existente.

O Google oferece um teste sem custo financeiro de 12 meses de até US $300 de uso do Google Cloud Platform. Você pode usá-lo neste codelab. Veja mais detalhes em https://cloud.google.com/free/.

Novos projetos

Ao criar um novo projeto, você precisa escolher quais contas de faturamento quer vincular ao projeto. 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 vários recursos do Google Cloud Platform. Para criar uma nova conta de faturamento e ativar o faturamento no projeto, siga as instruções em Criar uma nova conta de faturamento.

Projetos existentes

Se você tem um projeto com o faturamento temporariamente desativado, é possível reativá-lo:

  1. Acesse o Console do Cloud Platform.
  2. Na lista de projetos, selecione o projeto para o qual você quer reativar o faturamento.
  3. Abra o menu lateral à esquerda e selecione FaturamentoFaturamento. Você precisará selecionar uma conta de faturamento.
  4. Clique em Definir conta.

Criar uma nova conta de faturamento

Para criar uma nova conta de faturamento, siga estas etapas:

  1. Acesse o Console do Cloud Platform e faça login ou, se ainda não tem uma conta, inscreva-se.
  2. Abra o menu lateral à esquerda e selecione FaturamentoFaturamento.
  3. Clique no botão Nova conta de faturamento. Se esta não for sua primeira conta de faturamento, clique na opção "Conta de faturamento existente" na parte superior da página para abrir a lista. Em seguida, clique em Gerenciar contas de faturamento.
  4. 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 dos Estados Unidos, não é possível alterar o status fiscal depois de criar a conta.
  5. Clique em Enviar e ativar faturamento.

Por padrão, a pessoa que cria a conta é o administrador de faturamento da conta.

Para saber mais 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 o Marketplace da página da API BigQuery no console e clique no botão azul "Ativar".

3. Consultar dados de localização 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 dentro de um intervalo mínimo e máximo de latitude e longitude.
  • Consultas de raio: especifique uma área de interesse calculando um círculo ao redor de um ponto usando a fórmula de Hasrsine e as funções matemáticas para modelar a forma da Terra.
  • Consultas de polígono: especifique um formato personalizado e use uma função definida pelo usuário para expressar a lógica de ponto a polígono necessária para testar se a latitude e a longitude de cada linha estão dentro do polígono.

Para começar, use o Editor de consultas na seção "Big Query" do Console do Google Cloud Platform para executar as consultas a seguir com os dados sobre táxis 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 os fins deste tutorial, usaremos o SQL padrão, porque ele tem melhor conformidade com os padrões.

Se você quiser executar o SQL legado no editor do BigQuery, faça o seguinte:

  1. Clique no botão "Mais".
  2. Selecione "Configurações de consulta" no menu suspenso
  3. Em "Dialeto SQL', selecione o botão de opção "Legado'
  4. Clique no botão "Salvar".

Consultas retangulares

Consultas retangulares são bastante 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.

Tente o exemplo abaixo no console do BigQuery. Isso consulta algumas estatísticas de viagem média nas viagens que começaram em uma área retangular que contém Manhattan e uma região inferior de Manhattan. Há dois locais diferentes que você pode testar, remova a marca de comentário da segunda cláusula WHERE para executar a consulta nas 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 (em inglês)

dica_média

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

2,52

12,03

9,97

22,39

5,97

JFK (em inglês)

dica_média

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

9,22

48,49

41,19

22,48

4,36

Consultas por raio

Consultas de raio também são fáceis de construir no SQL, se você souber um pouco de matemática. Com as funções matemáticas de SQL legadas do BigQuery, é possível criar uma consulta SQL usando a fórmula Hasrsine, que se aproxima de uma área circular ou de um limite esférico na superfície da Terra.

Veja 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 se aproximar da distância representada por um grau.

Ele é baseado em um exemplo encontrado em http://www.plumislandmedia.net/mysql/haversine-mysql-closeest-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 

A SQL da fórmula de Hasrsine é complicada. Você só precisa conectar a coordenada central do círculo, o raio e os nomes do projeto, do conjunto de dados e da tabela do BigQuery.

Veja um exemplo de consulta que calcula algumas estatísticas de viagem médias para embarques a até 100 metros do Empire State Building. Copie e cole essas informações no console da Web do BigQuery para ver os resultados. Altere a latitude e a longitude para comparar com outras áreas, como o local 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

Veja os resultados da consulta abaixo. Você pode ver que há grandes diferenças na gorjeta média, tarifa, distância da viagem, tamanho proporcional da gorjeta em relação à tarifa e na tarifa média por milha.

Edifício Empire State:

dica_média

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

1.17

11,08

45,28

10,53

6,42

No Bronx

dica_média

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

0,52

17,63

4,75

4,74

10.9

Consultas de polígono

No SQL, não é possível fazer consultas usando formas arbitrárias que não sejam retângulos e círculos. O BigQuery não tem nenhum tipo de dados nativos da geometria ou um índice espacial. Portanto, para executar consultas usando formas de polígono, você precisa de uma abordagem diferente para consultas SQL simples. Uma abordagem possível é definir uma função de geometria em JavaScript e executá-la como uma função definida pelo usuário (UDF) no BigQuery.

É possível escrever muitas operações de geometria em JavaScript para facilitar a execução delas em uma tabela do BigQuery que contém valores de latitude e longitude. Você precisa passar o polígono personalizado por meio de uma UDF e fazer um teste para cada linha, retornando apenas as linhas em que a latitude e a longitude estejam dentro do polígono. Saiba mais sobre UDFs na referência do BigQuery.

Algoritmo Point In Polygon

Há muitas maneiras de calcular se um ponto está dentro de um polígono em JavaScript. Esta é uma porta de C de uma implementação bem conhecida que usa um algoritmo de rastreamento de raios 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. Ele só usa 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;
}

Portabilidade para JavaScript

A versão JavaScript desse algoritmo tem esta aparência:

/* 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 da UDF requer apenas uma instrução, mas a UDF precisa ser definida como uma função temporária. 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 consulta espacial usando o BigQuery. Como você viu, a localização faz uma grande diferença com relação aos dados do resultado das consultas em relação a esse conjunto de dados, mas, a menos que você adivinhe onde executar suas consultas, é difícil descobrir padrões espaciais ad hoc usando apenas consultas SQL.

Se só pudéssemos visualizar os dados em um mapa e analisá-los, definimos áreas de interesse arbitrárias. Bem, usando as APIs do Google Maps, você pode fazer exatamente isso. Primeiro, você precisa ativar a API Maps, configurar uma página da Web simples em execução na sua máquina local e começar a usar a API BigQuery para enviar consultas a partir da sua página da Web.

4. Trabalhar com as APIs do Google Maps

Depois de executar algumas consultas espaciais simples, a próxima etapa é visualizar a saída para ver os padrões. Para fazer isso, ative a API Maps, crie uma página da Web que envie consultas de um mapa para o BigQuery e desenhe os resultados no mapa.

Ativar a API Maps JavaScript

Para este codelab, você precisará ativar a API Maps JavaScript na Plataforma Google Maps no seu projeto. Para isso, faça o seguinte:

  1. No Console do Google Cloud Platform, acesse o Marketplace.
  2. No Marketplace, pesquise "API Maps JavaScript'
  3. Clique no bloco da API Maps JavaScript nos resultados da pesquisa.
  4. Clique no botão "Ativar".

Gerar uma chave de API

Para fazer solicitações à Plataforma Google Maps, gere e envie uma chave de API com todas as solicitações. Para gerar uma chave de API, faça o seguinte:

  1. No Console do Google Cloud Platform, clique no menu de navegação para abrir a navegação à esquerda.
  2. Selecione ‘APIs & Service' > ‘Credentials'
  3. Clique no botão "Criar credencial" e selecione "Chave de API".
  4. Copie a nova chave de API

Fazer o download do código e configurar um servidor da Web

Clique no botão a seguir para fazer o download de todo o código para este codelab:

Descompacte o arquivo ZIP transferido por download. Isso descompactará 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. Faremos 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 funciona bem com o servidor da Web do Chrome. Se esse app ainda não estiver instalado, você poderá instalá-lo pela Chrome Web Store.

Após a instalação, abra o app. No Chrome, faça o seguinte:

  1. Abrir o Chrome
  2. Na barra de endereço na parte superior, digite chrome://apps
  3. Pressione "Enter"
  4. Na janela exibida, clique no ícone do servidor da Web. Você também pode clicar com o botão direito do mouse em um app para abri-lo em uma guia normal ou fixada, em tela cheia ou em uma nova janela. a3ed00e79b8bfee7.png Você verá esta caixa de diálogo ao lado para permitir a configuração do servidor da Web local: 81b6151c3f60c948.png
  5. Clique em "CHOOSE FOLDER&#39" e selecione o local de onde você fez o download dos arquivos de amostra do codelab.
  6. Na seção "Opções', marque a caixa ao lado de "Mostrar index.html' automaticamente: 17f4913500faa86f.png.
  7. Deslize o botão "Web Server: STARTED&#99" para a esquerda, volte à direita para interromper e reinicie o servidor

a5d554d0d4a91851.png

5. Carregar o mapa e as ferramentas de desenho

Criar uma página básica de mapa

Comece com uma página HTML simples que carregue um mapa do Google Maps usando a API Maps JavaScript e algumas linhas de JavaScript. O código de amostra de mapa simples da Plataforma Google Maps é um ótimo ponto de partida. Ele é reproduzido aqui para que você possa copiar e colar no editor de texto ou no ambiente de desenvolvimento integrado de sua preferência. Para encontrá-lo, abra index.html no repositório que você transferiu por download.

  1. Copie index.html para a pasta work na sua cópia local do repositório
  2. Copie a pasta "img/" para a pasta "work/" na cópia local do repositório.
  3. Abrir trabalho/index.html no editor de texto ou no ambiente de desenvolvimento integrado
  4. Substitua YOUR_API_KEY pela chave de API criada anteriormente.
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
    async defer></script>
  1. No navegador, abra localhost:<port>/work, em que port é o número da porta especificado na configuração do servidor da Web local. A porta padrão é 8887. Você verá seus primeiros mapas.

Se você receber uma mensagem de erro no navegador, verifique se a chave de API está correta e o servidor da Web local ativo.

Mudar o local e o nível de zoom padrão

O código que define o local e o nível de zoom está nas linhas 27 & 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 dados de viagens de táxi do BigQuery para Nova York. Por isso, em seguida, você alterará o código de inicialização do mapa para o centro de uma cidade de Nova York com um nível de zoom adequado, ou seja, 13 ou 14.

Para isso, atualize o bloco de código acima para centralizar o mapa no Empire State Building e ajustar 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>

Atualize o mapa no seu navegador para ver os resultados.

Carregar as bibliotecas de desenho e visualização

Para adicionar recursos de desenho ao seu 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 desenhos.

Esse codelab também usa o HeatmapLayer. Por isso, você também 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

Você verá 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 em uma consulta, adicione o DrawingManager ao seu mapa, com as ferramentas Circle, Rectangle e Polygon ativadas.

É recomendável colocar todo o código de configuração DrawingManager em uma nova função. Portanto, na cópia do index.html, faça o seguinte:

  1. Adicione uma função com o nome setUpDrawingTools() com o código a seguir para criar o DrawingManager e definir a propriedade map para fazer referência ao objeto do mapa na página.

As opções transmitidas para google.maps.drawing.DrawingManager(options) definem o tipo de desenho padrão e as opções de exibição para formas desenhadas. Para selecionar áreas do mapa a serem enviadas como consultas, as formas devem ter uma opacidade zero. Para ver 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);
}
  1. Chame setUpDrawingTools() na sua função initMap() depois de criar o objeto do mapa
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
    zoom: 12
  });

  setUpDrawingTools();
}
  1. Atualize o index.html e verifique se as ferramentas de desenho estão visíveis. Também é possível usá-las para desenhar círculos, retângulos e formas poligonais.

É possível clicar e arrastar para desenhar círculos e retângulos. No entanto, os polígonos precisam ser desenhados clicando em cada vértice, e clicando duas vezes para concluir a forma.

Gerenciar eventos de desenho

Você precisa de um código para processar os eventos que são disparados quando um usuário termina de desenhar uma forma, assim como precisa das coordenadas das formas desenhadas para criar consultas SQL.

Adicionaremos código para isso em uma etapa posterior, mas, por enquanto, criaremos um stub com três manipuladores de eventos para processar os eventos rectanglecomplete, circlecomplete e polygoncomplete. Os gerenciadores não precisam executar nenhum código neste estágio.

Adicione o seguinte à parte inferior 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.
});

Veja um exemplo de código em funcionamento na cópia local do repositório, na pasta step2: step2/map.html.

6. Como usar a API BigQuery Client

A API Google BigQuery Client ajuda a evitar a escrita de muitos códigos boilerplate necessários para criar as solicitações, analisar respostas e lidar com a autenticação. Este codelab usa a API BigQuery por meio da biblioteca de cliente de APIs do Google para JavaScript, já que desenvolveremos um aplicativo baseado em navegador.

Em seguida, você adicionará um código para carregar essa API em uma página da Web e usá-la para interagir com o BigQuery.

Adicionar a API Google Client para JavaScript

Você usará a API Google Client para JavaScript para executar consultas no BigQuery. Na 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>

Após carregar a API Google Client, autorize o usuário a acessar os dados no BigQuery. Para fazer isso, você pode usar o OAuth 2.0. Primeiro, você precisa configurar algumas credenciais no seu projeto do Console do Google Cloud.

Criar credenciais do OAuth 2.0

  1. No Console do Google Cloud, no Menu de navegação, selecione APIs e serviços > Credenciais.

Antes de configurar suas credenciais, é necessário adicionar configurações à tela "Autorização" que o usuário final do aplicativo verá quando autorizar seu aplicativo a acessar dados do BigQuery em nome dele.

Para fazer isso, clique na guia Tela de consentimento OAuth. 2. É necessário adicionar a API Big Query aos escopos desse token. Clique no botão Add Scope na seção "Scopes for Google APIs". 3. Na lista, marque a caixa ao lado da entrada Big Query API 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 o ID do cliente OAuth. Para fazer isso, clique em Criar credenciais:

4d18a965fc760e39.png

  1. No menu suspenso, clique em ID do cliente OAuth. 1f8b36a1c27c75f0.png
  2. Em "Tipo de aplicativo", selecione Aplicativo da Web.
  3. No campo "Application Name", digite um nome para seu projeto. Por exemplo, "quot"BigQuery e Maps".
  4. Em Restrições, no campo "Origens JavaScript autorizadas", digite o URL do localhost, incluindo números de porta. Por exemplo: http://localhost:8887
  1. Clique no botão Criar.

Um pop-up mostra o ID e a chave secreta do cliente. Você precisa do ID do cliente para executar a autenticação no BigQuery. Copie e cole em work/index.html como uma nova variável JavaScript global 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. É necessário usar o ID do cliente OAuth e o ID do projeto para enviar consultas.

Quando a API Google Client é carregada na página da Web, siga estas etapas:

  • Autorize o usuário.
  • Se autorizado, carregue a API BigQuery.
  • Carregar e inicializar o mapa.

Consulte step3/map.html para ver um exemplo de como seria a página HTML concluída.

Autorizar o usuário

O usuário final do aplicativo precisa autorizar o aplicativo a acessar dados no BigQuery em nome dele. A API Google Client para JavaScript processa a lógica do OAuth para fazer isso.

Em um aplicativo real, você tem muitas opções para integrar a etapa de autorização.

Por exemplo, você pode chamar authorize() de um elemento da IU, como um botão, ou fazer isso quando a página for carregada. Aqui, escolhemos autorizar o usuário após o carregamento da API do cliente do Google para JavaScript, usando uma função de retorno de chamada no método gapi.load().

Escreva algum código imediatamente após a tag <script> que carrega a API Google Client para JavaScript a fim de carregar a biblioteca de cliente e o módulo de autenticação para que possamos 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>

Na autorização, carregue a API BigQuery.

Após a autorização do usuário, 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 é exibido para o usuário. Defina-o como true para suprimir o pop-up de autorização se o usuário já tiver autorização.

Adicione uma função à página chamada handleAuthResult(). A função precisa usar um parâmetro authresult, que permitirá controlar o fluxo da 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 tiver autorização.

Adicione uma lógica à função handleAuthResult() para chamar loadApi() caso haja um objeto authResult transmitido à função e se a propriedade error do objeto tiver um valor false.

Adicione um 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, ela é inicializada quando o JavaScript da API Maps é carregado.

Para isso, chame a função initMap() do método then() após o 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 uma lógica para pesquisar no BigQuery para descobrir o status dos jobs de longa duração e 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 ao work/index.html para enviar uma consulta usando a API e algumas variáveis para armazenar os valores do conjunto de dados do BigQuery e o projeto que contém a tabela a ser consultada e o ID do projeto que será cobrado por quaisquer cobranças.

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 de API get e o jobId retornado pela solicitação de consulta original. Veja 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]);    
  });
}

Mude 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 ver os resultados de uma consulta

Para ver os resultados de uma consulta quando ela terminar de usar, use a chamada de API jobs.getQueryResults. Adicione à página uma função 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 no BigQuery:

Há exemplos de consultas com 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, você pode chamar o método query da API BigQuery. Construa o SQL para cada consulta e transmita-o para a função sendQuery criada na etapa anterior.

Um exemplo prático do código desta etapa é step4/map.html.

Consultas retangulares

A maneira mais simples de exibir os dados do BigQuery em um mapa é solicitar todas as linhas em que a latitude e a longitude se enquadram em um retângulo, usando uma comparação menor que e maior que. Pode ser a visualização atual ou uma forma desenhada no mapa.

Para usar uma forma desenhada pelo usuário, mude o código na index.html para processar o evento de desenho disparado quando um retângulo for concluído. Neste exemplo, o código usa getBounds() no objeto retangular para receber um objeto que representa a extensão do retângulo nas 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 (norte) e inferior esquerda (sudoeste) para criar uma comparação menor que ou maior que cada linha da tabela do BigQuery. Veja um exemplo de uma tabela com colunas chamadas 'pickup_latitude' e 'pickup_longitude' que armazena os valores de local.

Como especificar a tabela do BigQuery

Para consultar uma tabela usando a API BigQuery, você precisa fornecer o nome da tabela em formato totalmente qualificado na sua consulta SQL. O formato do SQL padrão é project.dataset.tablename. No SQL legado, é project.dataset.tablename.

Há várias tabelas de corridas de táxi em Nova York. Para vê-los, acesse o console da Web do BigQuery e expanda o item de menu "Conjuntos de dados públicos". Encontre o conjunto de dados new_york e expanda-o para ver as tabelas. Escolha a tabela de viagens de táxi amarelo: bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016.

Como especificar o ID do projeto

Na chamada da API, especifique o nome do seu projeto do Google Cloud Platform para fins de faturamento. Neste codelab, ele não é o mesmo que o que contém a tabela. Se você estivesse trabalhando com uma tabela criada no seu projeto ao fazer upload de dados, esse ID de projeto seria o mesmo da sua instrução SQL.

Adicione variáveis JavaScript ao código para armazenar referências ao projeto Conjuntos de dados públicos que contém a tabela que você está consultando, além do nome da tabela e do nome do conjunto de dados. Você também precisa de uma variável separada para se referir ao 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 "Conjuntos de dados públicos do BigQuery". Inicialize billingProjectId com seu próprio ID do projeto, aquele que você criou em "Primeiros passos" anteriormente neste 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 precisa aceitar dois argumentos, um par de objetos google.Maps.LatLng que representa os cantos do retângulo nas coordenadas do mapa.

A segunda função precisa se chamar 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 referente a todas as linhas contidas em um retângulo desenhado pelo usuário. Antes de adicionar outros métodos de consulta a círculos e formas livres, vamos ver como lidar com os dados retornados de uma consulta.

10. Visualizar a resposta

As tabelas do BigQuery podem ser muito grandes, petabytes de dados, e crescer por centenas de milhares de linhas por segundo. Por isso, é importante tentar limitar a quantidade de dados retornados para que eles possam ser exibidos no mapa. Desenhar o local de cada linha de um conjunto de resultados muito grande (dezenas de linhas ou mais) resultará 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 retornados por uma consulta.

O código completo desta etapa está disponível em step5/map.html.

Para manter a quantidade de dados transferida 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 dos locais, use um mapa de calor. A API Maps JavaScript tem uma classe HeatmapLayer para essa finalidade. A HeatmapLayer usa uma matriz de coordenadas de latitude e longitude para facilitar a conversão das linhas retornadas pela 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 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 entre as colunas em cada linha e extrair os valores.

Na consulta SQL, você pediu apenas os valores de latitude e longitude dos embarques dos táxis. Portanto, haverá apenas duas colunas na resposta.

Não se esqueça de chamar setMap() na camada do mapa de calor quando você atribuir a matriz de posições a ela. Isso ficará 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);
}

Agora, você já conseguirá fazer o seguinte:

  • Abrir a página e autorizar o BigQuery
  • Desenhe um retângulo em Nova York
  • Veja os resultados de consulta resultantes visualizados como um mapa de calor.

Veja 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 embarques ao redor do Empire State Building em um sábado de julho:

7b1face0e7c71c78.png

11. Consultar por raio ao redor de um ponto

As consultas de raio são muito semelhantes. Com as funções matemáticas de SQL legado do BigQuery, é possível criar uma consulta SQL usando a fórmula Hasrsine, que se aproxima de uma área circular na superfície da Terra.

Usando a mesma técnica para retângulos, você pode processar um evento OverlayComplete para receber o centro e o raio de um círculo desenhado pelo usuário e criar o SQL da consulta da mesma forma.

Um exemplo funcional do código desta etapa está incluído no repositório de código como step6/map.html.

drawingManager.addListener('circlecomplete', circle => circleQuery(circle));

Na cópia de index.html, adicione duas novas funções vazias: circleQuery() e haversineSQL().

Em seguida, adicione um manipulador de eventos circlecomplete que transmite o centro e o raio a uma nova função chamada circleQuery()..

A função circleQuery() chamará haversineSQL() para criar o SQL da consulta e, em seguida, enviará a consulta chamando a função sendQuery() conforme o código de exemplo 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 experimente a ferramenta "Círculo" para selecionar uma área do mapa. O resultado deve ser parecido com este:

845418166b7cc7a3.png

12. Como consultar formas arbitrárias

Recapitulação: o SQL não é compatível com consultas por meio de formas arbitrárias que não são retângulos e círculos. O BigQuery não tem nenhum tipo de dados nativos de geometria. Portanto, para executar consultas usando formas de polígono, você precisa de uma abordagem diferente para consultas SQL simples.

Um recurso muito eficiente do BigQuery que pode ser usado para isso são as funções definidas pelo usuário (UDF, na sigla em inglês). As UDFs executam o código JavaScript dentro de uma consulta SQL.

O código de trabalho desta 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 SQL padrão por meio da 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. Nesse caso, uma consulta com uma função definida pelo usuário.

Um exemplo prático dessa abordagem é 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 que ele funcione se um determinado ponto estiver dentro de um polígono. As coordenadas poligonais 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 do polígono é passado como um argumento para a função que cria o SQL.

Em segundo lugar, o código cria a instrução SQL SELECT principal. O UDF é chamado na expressão WHERE neste exemplo.

Integrar com a API Maps

Para usar isso com a biblioteca de desenhos da API Maps, precisamos salvar o polígono desenhado pelo usuário e transmiti-lo para a parte UDF da consulta SQL.

Primeiro, precisamos processar o evento de desenho polygoncomplete para conseguir 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);
});

Em seguida, a função polygonQuery pode criar as funções JavaScript da UDF como uma string, bem como a instrução SQL que chamará a função da UDF.

Consulte step7/map.html para ver um exemplo prático disso.

Exemplo de saída

Veja um exemplo de consulta de retiradas dos dados de táxi amarelo de 2016 da NYC TLC no BigQuery usando um polígono de mão livre, com os dados selecionados desenhados como um mapa de calor.

Screen Shot 2017-05-09 at 10.00.48 AM.png

13. Como ir além

Veja algumas sugestões para ampliar este codelab e analisar outros aspectos dos dados. Você pode encontrar um exemplo prático dessas ideias em step8/map.html, no repositório de códigos.

Desistências de mapeamento

Até agora, apenas mapeamos os locais de retirada. Ao solicitar as colunas dropoff_latitude e dropoff_longitude e modificar o código do mapa de calor para plotá-las, você pode ver os destinos das viagens de táxi que começaram em um local específico.

Por exemplo, vamos ver onde os táxis costumam decolar quando pedem um embarque no Edifício Empire State.

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;
}

Em seguida, 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 2 e 3 do índice. Esses índices podem ser lidos a partir de uma variável para tornar o código mais gerenciável. O maxIntensity do mapa de calor está definido para mostrar a densidade de 20 desistências por pixel como o máximo.

Adicione algumas variáveis para alterar 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);
}

Veja um mapa de calor que mostra a distribuição de desembarques de todos os pontos de embarque imediatamente no Edifício Empire State em 2016. Você pode ver grandes concentrações (os blobs vermelhos) de destinos no centro da cidade, especialmente ao redor da Times Square, bem como ao longo da 5th Avenue, entre a 23rd Street e a 14th St. Outros locais com alta densidade não mostrados neste nível de zoom incluem os aeroportos La Guardia e JFK, World Trade Center e Battery Park.

Screen Shot 2017-05-09 at 10.40.01 AM.png

Estilizar o mapa básico

Ao criar um mapa do Google Maps usando a API Maps JavaScript, você pode definir o estilo usando um objeto JSON. Para visualizações de dados, pode ser útil desativar o som das cores no mapa. Você pode criar e testar estilos de mapa usando o assistente de estilo da API Google Maps em mapstyle.withgoogle.com.

É possível definir um estilo de mapa ao inicializar um objeto de mapa ou posteriormente. 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

Mesmo que o BigQuery geralmente dê uma resposta em segundos, pode ser útil mostrar ao usuário que algo está acontecendo enquanto a consulta está em execução.

Adicione IU à sua página da Web 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 podem ser exibidas incluem a duração da consulta, a quantidade de dados retornados e a quantidade de dados processados.

Adicione HTML após o mapa <div> para criar um painel na página que mostrará o número de linhas retornadas pela 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 deste painel são controladas pelo CSS. Adicione o 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 oculto até que seja necessário. Além disso, são usados alguns códigos JavaScript e CSS para mostrá-los quando um job do BigQuery está em execução.

Adicione HTML para mostrar um gráfico animado. Há um arquivo de imagem chamado loader.gif na pasta img do repositório do código.

<img id="spinner" src="img/loader.gif">

Adicione um CSS para posicionar a imagem e ocultá-la por padrão até que ela seja necessária.

#spinner {
  position: absolute; 
  top: 50%; 
  left: 50%; 
  margin-left: -32px; 
  margin-top: -32px; 
  opacity: 0; 
  z-index: -1000;
}

Por fim, adicione 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, dependendo de quais informações estão disponíveis.

Ao verificar um job atual, há uma propriedade response.statistics que pode ser usada. Quando o job for concluído, será possível acessar as propriedades response.totalRows e response.totalBytesProcessed. É útil para o usuário converter milissegundos em segundos e bytes para gigabytes para exibição, conforme o 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 alternar o gráfico animado, adicione uma função para controlar a visibilidade. Essa função alternará a opacidade de qualquer elemento DOM do 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 o método antes de processar uma consulta e depois que o resultado da consulta voltar 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 ficará parecida com esta:

Screen Shot 2017-05-10 at 2.32.19 PM.png

Veja o exemplo completo em step8/map.html.

14. Considerações

Muitos marcadores

Se você estiver trabalhando com tabelas muito grandes, sua consulta pode retornar muitas linhas para serem exibidas de forma eficiente em um mapa. Limite os resultados adicionando uma cláusula WHERE ou uma instrução LIMIT.

Desenhar vários marcadores pode tornar o mapa ilegível. Use um HeatmapLayer para mostrar a densidade ou marcadores de cluster para indicar onde estão muitos pontos de dados usando um único símbolo por cluster. Veja mais detalhes no tutorial de clustering de marcadores.

Otimizar consultas

O BigQuery verifica toda a tabela 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 a longitude como flutuantes e não como strings.

Exportar resultados interessantes

Nestes exemplos, o usuário final precisa ser autenticado na tabela do BigQuery, o que não é adequado para todos os casos de uso. Quando você descobrir alguns padrões interessantes, poderá ser mais fácil compartilhá-los com um público mais amplo. Basta exportar os resultados do BigQuery e criar um conjunto de dados estático usando a camada de dados do Google Maps.

Leia os 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

Existem vários conjuntos de dados públicos no BigQuery que têm colunas de latitude e longitude, como os conjuntos de dados de táxis de Nova York de 2009 a 2016, dados de viagens da Uber e da Lyft em Nova York e o conjunto de dados da GDELT.

15. Parabéns!

Esperamos que isso ajude você a começar a trabalhar rapidamente com algumas consultas geográficas nas tabelas do BigQuery para descobrir padrões e visualizá-los em um mapa do Google Maps. Bom mapeamento!

A seguir

Se você quiser saber mais sobre a Plataforma Google Maps ou o BigQuery, confira as sugestões a seguir.

Consulte O que é o BigQuery para saber mais sobre o serviço de armazenamento de dados em escala de petabyte sem servidor do Google.

Consulte o guia de instruções para criar um aplicativo simples com a API BigQuery.

Consulte o guia do desenvolvedor da biblioteca de desenhos para mais detalhes sobre como ativar a interação do usuário para desenhar formas em um mapa do Google Maps.

Veja outras maneiras de visualizar dados em um mapa do Google Maps.

Consulte o Guia de primeiros passos da API do cliente JavaScript para entender os conceitos básicos de uso da API do cliente e acessar outras APIs do Google.