1. Panoramica
Le mappe possono essere uno strumento molto potente per visualizzare i pattern in un set di dati correlati in qualche modo alla posizione. Questa relazione può essere il nome di un luogo, un valore specifico di latitudine e longitudine o il nome di un'area con un confine specifico, ad esempio un distretto censuario o un codice postale.
Quando questi set di dati diventano molto grandi, può essere difficile eseguire query e visualizzarli utilizzando strumenti convenzionali. Utilizzando Google BigQuery per eseguire query sui dati e le API di Google Maps per creare la query e visualizzare l'output, puoi esplorare rapidamente i pattern geografici nei tuoi dati con una configurazione o una codifica minime e senza dover gestire un sistema per archiviare set di dati molto grandi.
Cosa creerai
In questo codelab, scriverai ed eseguirai alcune query che mostrano come fornire approfondimenti basati sulla posizione in set di dati pubblici molto grandi utilizzando BigQuery. Creerai anche una pagina web che carica una mappa utilizzando l'API JavaScript di Google Maps Platform, quindi esegue e visualizza query spaziali sugli stessi set di dati pubblici di grandi dimensioni utilizzando la libreria client delle API di Google per JavaScript e l'API BigQuery.
Obiettivi didattici
- Come eseguire query sui set di dati sulla posizione su scala di petabyte in pochi secondi con BigQuery, utilizzando query SQL, funzioni definite dall'utente e l'API BigQuery
- Come utilizzare Google Maps Platform per aggiungere una mappa di Google a una pagina web e consentire agli utenti di disegnare forme
- Come visualizzare le query rispetto a set di dati di grandi dimensioni su una mappa Google, come nell'immagine di esempio riportata di seguito, che mostra la densità delle località di arrivo dei taxi nel 2016 per i viaggi iniziati dall'isolato intorno all'Empire State Building.
Che cosa ti serve
- Conoscenza di base di HTML, CSS, JavaScript, SQL e Chrome DevTools
- Un browser web moderno, ad esempio le versioni recenti di Chrome, Firefox, Safari o Edge.
- Un editor di testo o un IDE a tua scelta
Tecnologia
BigQuery
BigQuery è il servizio di analisi dei dati di Google per set di dati di grandi dimensioni. Dispone di un'API RESTful e supporta le query scritte in SQL. Se hai dati con valori di latitudine e longitudine, puoi utilizzarli per eseguire query sui dati in base alla posizione. Il vantaggio è che puoi esplorare visivamente set di dati molto grandi per esaminare i pattern senza dover gestire alcuna infrastruttura di server o database. Puoi ottenere risposte alle tue domande in pochi secondi, indipendentemente dalle dimensioni delle tabelle, utilizzando la scalabilità massiccia e l'infrastruttura gestita di BigQuery.
Google Maps Platform
Google Maps Platform fornisce accesso programmatico ai dati di mappe, luoghi e itinerari di Google. Attualmente, oltre 2 milioni di siti web e app lo utilizzano per fornire mappe incorporate e query basate sulla posizione ai propri utenti.
Il livello di disegno dell'API Maps JavaScript di Google Maps Platform ti consente di disegnare forme sulla mappa. Questi possono essere convertiti in input per eseguire query sulle tabelle BigQuery che hanno valori di latitudine e longitudine archiviati nelle colonne.
Per iniziare, devi avere un progetto Google Cloud con le API BigQuery e Maps attive.
2. Configurazione
Account Google
Se non hai ancora un Account Google (Gmail o Google Apps), devi crearne uno.
Crea un progetto
Accedi alla console Google Cloud ( console.cloud.google.com) e crea un nuovo progetto. Nella parte superiore dello schermo, c'è un menu a discesa Progetto:
Una volta fatto clic su questo menu a discesa del progetto, verrà visualizzata una voce di menu che ti consente di creare un nuovo progetto:
Nella casella "Inserisci un nuovo nome per il progetto", inserisci un nome per il nuovo progetto, ad esempio "BigQuery Codelab":
Verrà generato un ID progetto. L'ID progetto è un nome univoco tra tutti i progetti Google Cloud. Ricorda l'ID progetto, perché lo utilizzerai in un secondo momento. Il nome riportato sopra è già stato utilizzato e non funzionerà per te. Inserisci il tuo ID progetto ovunque vedi YOUR_PROJECT_ID in questo codelab.
Abilita fatturazione
Per registrarti a BigQuery, utilizza il progetto selezionato o creato nel passaggio precedente. La fatturazione deve essere abilitata per questo progetto. Una volta abilitata la fatturazione, puoi abilitare l'API BigQuery.
La modalità di attivazione della fatturazione dipende dal fatto che tu stia creando un nuovo progetto o riattivando la fatturazione per un progetto esistente.
Google offre una prova senza costi di 12 mesi per un utilizzo di Google Cloud Platform fino a 300 $, che potresti utilizzare per questo codelab. Scopri di più all'indirizzo https://cloud.google.com/free/.
Nuovi progetti
Quando crei un nuovo progetto, ti viene chiesto di scegliere quale dei tuoi account di fatturazione vuoi collegare al progetto. Se hai un solo account di fatturazione, questo viene collegato automaticamente al tuo progetto.
Se non hai un account di fatturazione, devi crearne uno e attivare la fatturazione per il tuo progetto prima di poter utilizzare molte funzionalità di Google Cloud Platform. Per creare un nuovo account di fatturazione e attivare la fatturazione per il tuo progetto, segui le istruzioni riportate in Crea un nuovo account di fatturazione.
Progetti esistenti
Se hai un progetto per il quale hai disattivato temporaneamente la fatturazione, puoi riattivarla:
- Vai alla console Cloud Platform.
- Dall'elenco dei progetti, seleziona il progetto per cui riattivare la fatturazione.
- Apri il menu a sinistra della console e seleziona Fatturazione
. Ti viene chiesto di selezionare un account di fatturazione.
- Fai clic su Imposta account.
Crea un nuovo account di fatturazione
Per creare un nuovo account di fatturazione:
- Vai alla console di Cloud Platform e accedi o, se non hai ancora un account, registrati.
- Apri il menu a sinistra della console e seleziona Fatturazione
- Fai clic sul pulsante Nuovo account di fatturazione. Tieni presente che, se non è il tuo primo account di fatturazione, devi prima aprire l'elenco degli account di fatturazione facendo clic sul nome del tuo account di fatturazione esistente nella parte superiore della pagina e poi su Gestisci account di fatturazione.
- Inserisci il nome dell'account di fatturazione e i tuoi dati di fatturazione. Le opzioni visualizzate dipendono dal paese del tuo indirizzo di fatturazione. Tieni presente che per gli account degli Stati Uniti non puoi modificare lo status fiscale dopo la creazione dell'account.
- Fai clic su Invia e abilita fatturazione.
Per impostazione predefinita, la persona che crea l'account di fatturazione è un amministratore della fatturazione per l'account.
Per informazioni sulla verifica dei conti bancari e sull'aggiunta di metodi di pagamento di backup, consulta l'articolo Aggiungere, rimuovere o aggiornare un metodo di pagamento.
Abilita l'API BigQuery
Per abilitare l'API BigQuery nel tuo progetto, vai alla pagina del Marketplace dell'API BigQuery nella console e fai clic sul pulsante blu "Abilita".
3. Eseguire query sui dati sulla posizione in BigQuery
Esistono tre modi per eseguire query sui dati sulla posizione archiviati come valori di latitudine e longitudine in BigQuery.
- Query rettangolari: specifica l'area di interesse come query che seleziona tutte le righe all'interno di un intervallo di latitudine e longitudine minimo e massimo.
- Query sul raggio: specifica l'area di interesse calcolando un cerchio intorno a un punto utilizzando la formula di Haversine e le funzioni matematiche per modellare la forma della Terra.
- Query sui poligoni: specifica una forma personalizzata e utilizza una funzione definita dall'utente per esprimere la logica punto nel poligono necessaria per verificare se la latitudine e la longitudine di ogni riga rientrano nella forma.
Per iniziare, utilizza l'editor di query nella sezione BigQuery della console Google Cloud per eseguire le seguenti query sui dati dei taxi di New York.
SQL standard e SQL precedente
BigQuery supporta due versioni di SQL: SQL precedente e SQL standard. Quest'ultimo è lo standard ANSI 2011. Ai fini di questo tutorial, utilizzeremo SQL standard perché ha una migliore conformità agli standard.
Se vuoi eseguire SQL precedente nell'editor di BigQuery, puoi farlo nel seguente modo:
- Fai clic sul pulsante "Altro".
- Seleziona "Impostazioni query" dal menu a discesa.
- In "Dialetto SQL", seleziona il pulsante di opzione "Legacy".
- Fai clic sul pulsante "Salva".
Query rettangolari
Le query rettangolari sono piuttosto semplici da creare in BigQuery. Devi solo aggiungere una clausola WHERE
che limiti i risultati restituiti a quelli con località comprese tra i valori minimo e massimo di latitudine e longitudine.
Prova l'esempio riportato di seguito nella console BigQuery. Questa query cerca alcune statistiche medie sui viaggi per le corse iniziate in un'area rettangolare che contiene Midtown e Lower Manhattan. Puoi provare due posizioni diverse. Rimuovi il commento dalla seconda clausola WHERE
per eseguire la query sulle corse iniziate all'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
I risultati delle due query mostrano che esistono grandi differenze nella distanza media del viaggio, nella tariffa e nella mancia per i prelievi nelle due località.
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 |
Query per raggio
Le query sul raggio sono anche facili da creare in SQL se conosci un po' di matematica. Utilizzando le funzioni matematiche di SQL precedente di BigQuery, puoi creare una query SQL utilizzando la formula di Haversine, che approssima un'area circolare o una calotta sferica sulla superficie terrestre.
Ecco un esempio di istruzione SQL BigQuery per una query circolare centrata su 40.73943, -73.99585
con un raggio di 0,1 km.
Utilizza un valore costante di 111,045 chilometri per approssimare la distanza rappresentata da un grado.
Questo si basa su un esempio trovato all'indirizzo 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
L'SQL per la formula di Haversine sembra complicato, ma tutto ciò che devi fare è inserire la coordinata del centro del cerchio, il raggio e i nomi di progetto, set di dati e tabella per BigQuery.
Ecco una query di esempio che calcola alcune statistiche medie sui viaggi per i prelievi entro 100 metri dall'Empire State Building. Copia e incolla questo codice nella console web BigQuery per visualizzare i risultati. Modifica la latitudine e la longitudine per confrontarle con altre aree, ad esempio la posizione nel 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
Di seguito sono riportati i risultati della query. Puoi notare grandi differenze nella mancia media, nella tariffa, nella distanza del viaggio, nella dimensione proporzionale della mancia rispetto alla tariffa e nella tariffa media per miglio percorso.
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 |
Firenze
avg_tip | avg_fare | avg_distance | avg_tip_pc | avg_fare_mile |
0,52 | 17,63 | 4,75 | 4,74 | 10.9 |
Query sui poligoni
SQL non supporta le query che utilizzano forme arbitrarie diverse da rettangoli e cerchi. BigQuery non dispone di un tipo di dati di geometria nativo o di un indice spaziale, pertanto per eseguire query utilizzando forme poligonali è necessario un approccio diverso rispetto alle query SQL semplici. Un approccio consiste nel definire una funzione di geometria in JavaScript ed eseguirla come funzione definita dall'utente (UDF) in BigQuery.
Molte operazioni geometriche possono essere scritte in JavaScript, quindi è facile prenderne una ed eseguirla su una tabella BigQuery che contiene valori di latitudine e longitudine. Devi passare il poligono personalizzato tramite una funzione definita dall'utente ed eseguire un test su ogni riga, restituendo solo le righe in cui la latitudine e la longitudine rientrano nel poligono. Scopri di più sulle UDF nella documentazione di riferimento di BigQuery.
Algoritmo Point In Polygon
Esistono molti modi per calcolare se un punto si trova all'interno di un poligono in JavaScript. Ecco un porting da C di un'implementazione ben nota che utilizza un algoritmo di ray tracing per determinare se un punto si trova all'interno o all'esterno di un poligono contando il numero di volte in cui una linea infinitamente lunga attraversa il confine della forma. Richiede solo poche righe di codice:
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;
}
Porting a JavaScript
La versione JavaScript di questo algoritmo ha il seguente aspetto:
/* 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;
}
Quando utilizzi SQL standard in BigQuery, l'approccio UDF richiede una sola istruzione, ma la UDF deve essere definita come funzione temporanea nell'istruzione. Ecco un esempio. Incolla l'istruzione SQL riportata di seguito nella finestra dell'editor di query.
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
Complimenti!
Ora hai eseguito tre tipi di query spaziali utilizzando BigQuery. Come hai visto, la posizione fa una grande differenza nei dati dei risultati per le query su questo set di dati, ma a meno che tu non indovini dove eseguire le query, è difficile scoprire pattern spaziali ad hoc utilizzando solo query SQL.
Se solo potessimo visualizzare i dati su una mappa ed esplorarli definendo aree di interesse arbitrarie. Con le API Google Maps puoi fare proprio questo. Innanzitutto, devi abilitare l'API Maps, configurare una semplice pagina web in esecuzione sulla tua macchina locale e iniziare a utilizzare l'API BigQuery per inviare query dalla tua pagina web.
4. Utilizzo delle API di Google Maps
Dopo aver eseguito alcune semplici query spaziali, il passaggio successivo consiste nel visualizzare l'output per vedere i pattern. Per farlo, devi abilitare l'API Maps, creare una pagina web che invii query da una mappa a BigQuery e quindi disegni i risultati sulla mappa.
Attivare l'API Maps JavaScript
Per questo codelab, devi abilitare l'API Maps JavaScript di Google Maps Platform nel tuo progetto. Procedi nel modo seguente:
- Nella console Google Cloud, vai a Marketplace.
- Nel Marketplace, cerca "API Maps JavaScript".
- Fai clic sul riquadro dell'API Maps JavaScript nei risultati di ricerca.
- Fai clic sul pulsante "Attiva".
Generare una chiave API
Per effettuare richieste a Google Maps Platform, devi generare una chiave API e inviarla con tutte le richieste. Per generare una chiave API:
- Nella console Google Cloud, fai clic sul menu a tre linee per aprire il menu di navigazione a sinistra.
- Seleziona "API e servizi" > "Credenziali".
- Fai clic sul pulsante "Crea credenziali", poi seleziona "Chiave API".
- Copia la nuova chiave API.
Scarica il codice e configura un server web
Fai clic sul pulsante seguente per scaricare tutto il codice per questo codelab:
Decomprimi il file ZIP scaricato. Verrà estratta una cartella principale (bigquery
), che contiene una cartella per ogni passaggio di questo codelab, insieme a tutte le risorse necessarie.
Le cartelle stepN
contengono lo stato finale desiderato di ogni passaggio di questo codelab. Sono presenti a scopo di riferimento. Eseguiremo tutto il lavoro di codifica nella directory denominata work
.
Configurare un server web locale
Sebbene tu possa utilizzare il tuo server web, questo codelab è progettato per funzionare bene con Chrome Web Server. Se non l'hai ancora installata, puoi farlo dal Chrome Web Store.
Una volta installata, apri l'app. In Chrome puoi farlo nel seguente modo:
- Apri Chrome.
- Nella barra degli indirizzi in alto, digita chrome://apps.
- Premi Invio
- Nella finestra che si apre, fai clic sull'icona del server web. Puoi anche fare clic con il tasto destro del mouse su un'app per aprirla in una scheda normale o bloccata, a schermo intero o in una nuova finestra
. Verrà visualizzata la seguente finestra di dialogo, che ti consente di configurare il server web locale:
- Fai clic su "SCEGLI CARTELLA" e seleziona la posizione in cui hai scaricato i file di esempio del codelab.
- Nella sezione "Opzioni", seleziona la casella accanto a "Mostra automaticamente index.html":
- Sposta il pulsante di attivazione/disattivazione con l'etichetta "Server web: AVVIATO" verso sinistra e poi di nuovo verso destra per arrestare e riavviare il server web.
5. Caricamento della mappa e degli strumenti di disegno
Creare una pagina della mappa di base
Inizia con una semplice pagina HTML che carica una mappa di Google utilizzando l'API Maps JavaScript e poche righe di JavaScript. Il codice dell'esempio di mappa semplice di Google Maps Platform è un ottimo punto di partenza. È riprodotto qui per consentirti di copiarlo e incollarlo nell'editor di testo o nell'IDE che preferisci oppure puoi trovarlo aprendo index.html
dal repository che hai scaricato.
- Copia
index.html
nella cartellawork
della tua copia locale del repository. - Copia la cartella img/ nella cartella work/ della tua copia locale del repository
- Apri work/
index.html
nell'editor di testo o nell'IDE - Sostituisci
YOUR_API_KEY
con la chiave API che hai creato in precedenza.
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
async defer></script>
- Nel browser, apri
localhost:<port>/work
, doveport
è il numero di porta specificato nella configurazione del server web locale. La porta predefinita è8887
. Dovresti visualizzare le prime mappe.
Se ricevi un messaggio di errore nel browser, controlla che la chiave API sia corretta e che il server web locale sia attivo.
Modificare la posizione e il livello di zoom predefiniti
Il codice che imposta la posizione e il livello di zoom si trova alle righe 27 e 28 di index.html ed è attualmente centrato su Sydney, in Australia:
<script>
let map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -34.397, lng: 150.644},
zoom: 8
});
}
</script>
Questo tutorial funziona con i dati sulle corse dei taxi di BigQuery per New York, quindi il passaggio successivo consiste nel modificare il codice di inizializzazione della mappa in modo che sia centrata su una località di New York City a un livello di zoom appropriato. I livelli 13 o 14 dovrebbero funzionare bene.
Per farlo, aggiorna il blocco di codice precedente con il seguente per centrare la mappa sull'Empire State Building e regolare il livello di zoom su 14:
<script>
let map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.7484405, lng: -73.9878531},
zoom: 14
});
}
</script>
Poi ricarica la mappa nel browser per visualizzare i risultati.
Carica le librerie di disegno e visualizzazione
Per aggiungere funzionalità di disegno alla mappa, devi modificare lo script che carica l'API Maps JavaScript aggiungendo un parametro facoltativo che indica a Google Maps Platform di abilitare la libreria di disegno.
Questo codelab utilizza anche HeatmapLayer
, quindi aggiornerai anche lo script per richiedere la libreria di visualizzazione. Per farlo, aggiungi il parametro libraries
e specifica le librerie visualization
e drawing
come valori separati da virgole, ad esempio libraries=
visualization,drawing
Dovrebbe avere il seguente aspetto:
<script src='http://maps.googleapis.com/maps/api/js?libraries=visualization,drawing&callback=initMap&key=YOUR_API_KEY' async defer></script>
Aggiungere DrawingManager
Per utilizzare le forme disegnate dall'utente come input per una query, aggiungi DrawingManager
alla mappa con gli strumenti Circle
, Rectangle
e Polygon
attivati.
È consigliabile inserire tutto il codice di configurazione di DrawingManager
in una nuova funzione. Nella tua copia di index.html, procedi nel seguente modo:
- Aggiungi una funzione chiamata
setUpDrawingTools()
con il seguente codice per creareDrawingManager
e impostare la relativa proprietàmap
in modo che faccia riferimento all'oggetto mappa nella pagina.
Le opzioni passate a google.maps.drawing.DrawingManager(options)
impostano il tipo di disegno della forma predefinito e le opzioni di visualizzazione per le forme disegnate. Per selezionare le aree della mappa da inviare come query, le forme devono avere un'opacità pari a zero. Per ulteriori informazioni sulle opzioni disponibili, vedi Opzioni di 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);
}
- Chiama
setUpDrawingTools()
nella funzioneinitMap()
dopo la creazione dell'oggetto mappa
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
zoom: 12
});
setUpDrawingTools();
}
- Ricarica index.html e verifica che gli strumenti di disegno siano visibili. Verifica inoltre di poterli utilizzare per disegnare cerchi, rettangoli e poligoni.
Puoi fare clic e trascinare per disegnare cerchi e rettangoli, ma i poligoni devono essere disegnati facendo clic su ogni vertice e facendo doppio clic per completare la forma.
Gestire gli eventi di disegno
Hai bisogno di un codice per gestire gli eventi che vengono attivati quando un utente finisce di disegnare una forma, così come hai bisogno delle coordinate delle forme disegnate per costruire query SQL.
Aggiungeremo il codice per questo in un passaggio successivo, ma per ora creeremo tre gestori di eventi vuoti per gestire gli eventi rectanglecomplete
, circlecomplete
e polygoncomplete
. In questa fase i gestori non devono eseguire alcun codice.
Aggiungi quanto segue alla fine della funzione 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.
});
Puoi trovare un esempio funzionante di questo codice nella tua copia locale del repository, nella cartella step2
: step2/map.html.
6. Utilizzo dell'API client BigQuery
L'API client Google BigQuery ti aiuterà a evitare di scrivere gran parte del codice boilerplate necessario per creare le richieste, analizzare le risposte e gestire l'autenticazione. Questo codelab utilizza l'API BigQuery tramite la libreria client delle API di Google per JavaScript, poiché svilupperemo un'applicazione basata su browser.
Successivamente, aggiungerai il codice per caricare questa API in una pagina web e utilizzarla per interagire con BigQuery.
Aggiungere l'API client di Google per JavaScript
Utilizzerai l'API Google Client per JavaScript per eseguire query su BigQuery. Nella tua copia di index.html
(nella cartella work
), carica l'API utilizzando un tag <script>
come questo. Inserisci il tag immediatamente sotto il tag <script>
che carica l'API Maps:
<script src='https://apis.google.com/js/client.js'></script>
Dopo aver caricato l'API Google Client, autorizza l'utente ad accedere ai dati in BigQuery. Per farlo, puoi utilizzare OAuth 2.0. Innanzitutto, devi configurare alcune credenziali nel progetto della console Google Cloud.
Crea credenziali OAuth 2.0
- Nella console Google Cloud, dal menu di navigazione, seleziona API e servizi > Credenziali.
Prima di poter configurare le credenziali, devi aggiungere alcune configurazioni per la schermata di autorizzazione che un utente finale della tua applicazione vedrà quando autorizza la tua app ad accedere ai dati BigQuery per suo conto.
Per farlo, fai clic sulla scheda Schermata per il consenso OAuth. 2. Devi aggiungere l'API BigQuery agli ambiti di questo token. Fai clic sul pulsante Aggiungi ambito nella sezione Ambiti per le API di Google. 3. Nell'elenco, seleziona la casella accanto alla voce API BigQuery con l'ambito ../auth/bigquery
. 4. Fai clic su Aggiungi. 5. Inserisci un nome nel campo "Nome applicazione". 6. Fai clic su Salva per salvare le impostazioni. 7. Successivamente, creerai il tuo ID client OAuth. Per farlo, fai clic su Crea credenziali:
- Nel menu a discesa, fai clic su ID client OAuth.
- In Tipo di applicazione, seleziona Applicazione web.
- Nel campo Nome applicazione, digita un nome per il progetto. Ad esempio, "BigQuery e Maps".
- In Limitazioni, nel campo Origini JavaScript autorizzate, inserisci l'URL di localhost, inclusi i numeri di porta. Ad esempio:
http://localhost:8887
- Fai clic su pulsante Crea.
Un popup mostra l'ID client e il client secret. Per eseguire l'autenticazione in BigQuery, è necessario l'ID client. Copialo e incollalo in work/index.html
come nuova variabile JavaScript globale denominata clientId
.
let clientId = 'YOUR_CLIENT_ID';
7. Autorizzazione e inizializzazione
Prima di inizializzare la mappa, la pagina web dovrà autorizzare l'utente ad accedere a BigQuery. In questo esempio utilizziamo OAuth 2.0 come descritto nella sezione relativa all'autorizzazione della documentazione dell'API client JavaScript. Per inviare query, devi utilizzare l'ID client OAuth e l'ID progetto.
Quando l'API Google Client viene caricata nella pagina web, devi eseguire i seguenti passaggi:
- Autorizza l'utente.
- Se autorizzata, carica l'API BigQuery.
- Carica e inizializza la mappa.
Consulta step3/map.html per un esempio dell'aspetto della pagina HTML finale.
Autorizzare l'utente
L'utente finale dell'applicazione deve autorizzare l'applicazione ad accedere ai dati in BigQuery per suo conto. L'API client di Google per JavaScript gestisce la logica OAuth per farlo.
In un'applicazione reale, hai molte scelte su come integrare il passaggio di autorizzazione.
Ad esempio, puoi chiamare authorize()
da un elemento UI come un pulsante o farlo quando la pagina è stata caricata. Qui abbiamo scelto di autorizzare l'utente dopo il caricamento dell'API Google Client per JavaScript, utilizzando una funzione di callback nel metodo gapi.load()
.
Scrivi del codice immediatamente dopo il tag <script>
che carica l'API client di Google per JavaScript per caricare sia la libreria client sia il modulo di autenticazione, in modo da poter autenticare subito l'utente.
<script src='https://apis.google.com/js/client.js'></script>
<script type='text/javascript'>
gapi.load('client:auth', authorize);
</script>
Al momento dell'autorizzazione, carica l'API BigQuery
Dopo aver autorizzato l'utente, carica l'API BigQuery.
Innanzitutto, chiama gapi.auth.authorize()
con la variabile clientId
che hai aggiunto nel passaggio precedente. Gestisci la risposta in una funzione di callback chiamata handleAuthResult
.
Il parametro immediate
controlla se viene mostrato un popup all'utente. Imposta il valore su true
per eliminare il popup di autorizzazione se l'utente è già autorizzato.
Aggiungi alla pagina una funzione chiamata handleAuthResult()
. La funzione deve accettare un parametro authresult
, che ti consentirà di controllare il flusso della logica a seconda che l'utente sia stato autorizzato o meno.
Aggiungi anche una funzione chiamata loadApi
per caricare l'API BigQuery se l'utente è stato autorizzato correttamente.
Aggiungi logica alla funzione handleAuthResult()
per chiamare loadApi()
se alla funzione viene passato un oggetto authResult
e se la proprietà error
dell'oggetto ha un valore di false
.
Aggiungi codice alla funzione loadApi()
per caricare l'API BigQuery utilizzando il metodo 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');
}
Caricare la mappa
Il passaggio finale consiste nell'inizializzare la mappa. Per farlo, devi modificare leggermente l'ordine della logica. Al momento viene inizializzato quando è stato caricato il JavaScript dell'API Maps.
Puoi farlo chiamando la funzione initMap()
dal metodo then()
dopo il metodo load()
sull'oggetto gapi.client
.
// Load BigQuery client API
function loadApi(){
gapi.client.load('bigquery', 'v2').then(
() => initMap()
);
}
8. Concetti dell'API BigQuery
Le chiamate API BigQuery vengono in genere eseguite in pochi secondi, ma potrebbero non restituire immediatamente una risposta. Hai bisogno di una logica per eseguire il polling di BigQuery per scoprire lo stato dei job a lunga esecuzione e recuperare i risultati solo al termine del job.
Il codice completo per questo passaggio si trova in step4/map.html.
Invio di una richiesta
Aggiungi una funzione JavaScript a work/index.html
per inviare una query utilizzando l'API e alcune variabili per archiviare i valori del set di dati e del progetto BigQuery contenenti la tabella da interrogare e l'ID progetto a cui verranno addebitati eventuali costi.
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.
});
}
Controllare lo stato di un job
La funzione checkJobStatus
riportata di seguito mostra come controllare periodicamente lo stato di un job utilizzando il metodo dell'API get
e jobId
restituito dalla richiesta di query originale. Ecco un esempio che viene eseguito ogni 500 millisecondi finché il job non viene completato.
let jobCheckTimer;
function checkJobStatus(jobId){
let request = gapi.client.bigquery.jobs.get({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response =>{
if (response.status.errorResult){
// Handle any errors.
console.log(response.status.error);
return;
}
if (response.status.state == 'DONE'){
// Get the results.
clearTimeout(jobCheckTimer);
getQueryResults(jobId);
return;
}
// Not finished, check again in a moment.
jobCheckTimer = setTimeout(checkJobStatus, 500, [jobId]);
});
}
Modifica il metodo sendQuery
per chiamare il metodo checkJobStatus()
come callback nella chiamata request.execute()
. Trasferisci l'ID job a checkJobStatus
. Questo valore viene esposto dall'oggetto di risposta come 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));
}
Ottenere i risultati di una query
Per ottenere i risultati di una query al termine dell'esecuzione, utilizza la chiamata API jobs.getQueryResults
. Aggiungi alla pagina una funzione chiamata getQueryResults()
che accetta un parametro di jobId
:
function getQueryResults(jobId){
let request = gapi.client.bigquery.jobs.getQueryResults({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => {
// Do something with the results.
})
}
9. Eseguire query sui dati sulla posizione con l'API BigQuery
Esistono tre modi per utilizzare SQL per eseguire query spaziali sui dati in BigQuery:
- selezionare per rettangolo (altrimenti noto come riquadro di delimitazione),
- seleziona per raggio e
- la potente funzionalità Funzioni definite dall'utente.
Nella sezione Funzioni matematiche della Guida di riferimento a BigQuery legacy SQL, in "Esempi avanzati", sono riportati esempi di query per rettangoli di selezione e raggi.
Per le query su rettangolo di selezione e raggio, puoi chiamare il metodo query
dell'API BigQuery. Crea l'SQL per ogni query e trasmettilo alla funzione sendQuery
che hai creato nel passaggio precedente.
Un esempio funzionante del codice per questo passaggio è disponibile in step4/map.html.
Query rettangolari
Il modo più semplice per visualizzare i dati BigQuery su una mappa è richiedere tutte le righe in cui la latitudine e la longitudine rientrano in un rettangolo, utilizzando un confronto minore di e maggiore di. Può trattarsi della visualizzazione mappa corrente o di una forma disegnata sulla mappa.
Per utilizzare una forma disegnata dall'utente, modifica il codice in index.html
per gestire l'evento di disegno attivato al completamento di un rettangolo. In questo esempio, il codice utilizza getBounds()
sull'oggetto rettangolo per ottenere un oggetto che rappresenta l'estensione del rettangolo nelle coordinate della mappa e lo passa a una funzione chiamata rectangleQuery
:
drawingManager.addListener('rectanglecomplete', rectangle => rectangleQuery(rectangle.getBounds()));
La funzione rectangleQuery
deve solo utilizzare le coordinate in alto a destra (nord-est) e in basso a sinistra (sud-ovest) per creare un confronto minore di/maggiore di con ogni riga della tabella BigQuery. Ecco un esempio di query di una tabella con colonne denominate 'pickup_latitude'
e 'pickup_longitude'
che memorizzano i valori di posizione.
Specifica della tabella BigQuery
Per eseguire una query su una tabella utilizzando l'API BigQuery, devi fornire il nome della tabella in formato completo nella query SQL. Il formato in SQL standard è project.dataset.tablename
. In SQL precedente è project.dataset.tablename
.
Sono disponibili molte tabelle di corse di taxi a New York. Per visualizzarli, vai alla console web BigQuery ed espandi la voce di menu "Set di dati pubblici". Individua il set di dati denominato new_york
ed espandilo per visualizzare le tabelle. Scegli la tabella Yellow Taxi trips (bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016
).
Specificare l'ID progetto
Nella chiamata API, devi specificare il nome del tuo progetto Google Cloud Platform a fini di fatturazione. In questo codelab, questo non è lo stesso progetto di quello contenente la tabella. Se stavi lavorando con una tabella che avevi creato nel tuo progetto caricando i dati, questo ID progetto sarebbe lo stesso di quello nell'istruzione SQL.
Aggiungi variabili JavaScript al codice per contenere i riferimenti al progetto Public Datasets che contiene la tabella su cui stai eseguendo query, oltre al nome della tabella e del set di dati. Devi anche avere una variabile separata per fare riferimento al tuo ID progetto di fatturazione.
Aggiungi le variabili JavaScript globali denominate billingProjectId, publicProjectId, datasetId
e tableName
alla tua copia di index.html.
Inizializza le variabili 'publicProjectId'
, 'datasetId'
e 'tableName'
con i dettagli del progetto BigQuery Public Datasets. Inizializza billingProjectId
con il tuo ID progetto (quello che hai creato in "Configurazione" in precedenza in questo codelab).
let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId = 'bigquery-public-data';
let datasetId = 'new_york_taxi_trips';
let tableName = 'tlc_yellow_trips_2016';
Ora aggiungi due funzioni al codice per generare l'SQL e inviare la query a BigQuery utilizzando la funzione sendQuery
creata nel passaggio precedente.
La prima funzione deve essere chiamata rectangleSQL()
e deve accettare due argomenti, una coppia di oggetti google.Maps.LatLng
che rappresentano gli angoli del rettangolo nelle coordinate della mappa.
La seconda funzione deve essere chiamata rectangleQuery()
. In questo modo, il testo della query viene passato alla funzione 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;
}
A questo punto, hai abbastanza codice per inviare una query a BigQuery per tutte le righe contenute in un rettangolo disegnato dall'utente. Prima di aggiungere altri metodi di query per cerchi e forme a mano libera, vediamo come gestire i dati restituiti da una query.
10. Visualizzare la risposta
Le tabelle BigQuery possono essere molto grandi, anche petabyte di dati, e possono crescere di centinaia di migliaia di righe al secondo. Pertanto, è importante cercare di limitare la quantità di dati restituiti in modo che possano essere visualizzati sulla mappa. Il disegno della posizione di ogni riga in un insieme di risultati molto grande (decine di migliaia di righe o più) genererà una mappa illeggibile. Esistono molte tecniche per aggregare le posizioni sia nella query SQL sia sulla mappa e puoi limitare i risultati restituiti da una query.
Il codice completo per questo passaggio è disponibile in step5/map.html.
Per mantenere la quantità di dati trasferiti alla tua pagina web a una dimensione ragionevole per questo codelab, modifica la funzione rectangleSQL()
per aggiungere un'istruzione che limiti la risposta a 10.000 righe. Nell'esempio riportato di seguito, questo valore è specificato in una variabile globale denominata recordLimit
, in modo che tutte le funzioni di query possano utilizzare lo stesso valore.
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;
}
Per visualizzare la densità delle posizioni, puoi utilizzare una mappa termica. L'API Maps JavaScript ha una classe HeatmapLayer a questo scopo. HeatmapLayer accetta un array di coordinate di latitudine e longitudine, quindi è piuttosto facile convertire le righe restituite dalla query in una mappa termica.
Nella funzione getQueryResults
, passa l'array response.result.rows
a una nuova funzione JavaScript chiamata doHeatMap()
che creerà una mappa termica.
Ogni riga avrà una proprietà denominata f
, che è un array di colonne. Ogni colonna avrà una proprietà v
contenente il valore.
Il codice deve scorrere le colonne di ogni riga ed estrarre i valori.
Nella query SQL, hai richiesto solo i valori di latitudine e longitudine dei punti di prelievo dei taxi, quindi nella risposta saranno presenti solo due colonne.
Non dimenticare di chiamare setMap()
nel livello della mappa di calore dopo aver assegnato l'array di posizioni. In questo modo, sarà visibile sulla mappa.
Ecco un esempio:
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);
}
A questo punto, dovresti essere in grado di:
- Apri la pagina e autorizza l'accesso a BigQuery
- Disegna un rettangolo da qualche parte a New York City
- Visualizza i risultati della query risultante sotto forma di mappa termica.
Ecco un esempio del risultato di una query rettangolare sui dati dei taxi gialli di New York del 2016, disegnato come una mappa termica. Questa mappa mostra la distribuzione dei prelievi intorno all'Empire State Building di sabato a luglio:
11. Esecuzione di query per raggio intorno a un punto
Le query per raggio sono molto simili. Utilizzando le funzioni matematiche di BigQuery SQL precedente, puoi creare una query SQL utilizzando la formula di Haversine, che approssima un'area circolare sulla superficie terrestre.
Utilizzando la stessa tecnica per i rettangoli, puoi gestire un evento OverlayComplete
per ottenere il centro e il raggio di un cerchio disegnato dall'utente e creare la query SQL nello stesso modo.
Un esempio funzionante del codice per questo passaggio è incluso nel repository di codice come step6/map.html.
drawingManager.addListener('circlecomplete', circle => circleQuery(circle));
Nella tua copia di index.html, aggiungi due nuove funzioni vuote: circleQuery()
e haversineSQL()
.
Quindi, aggiungi un gestore di eventi circlecomplete
che passa il centro e il raggio a una nuova funzione chiamata circleQuery().
.
La funzione circleQuery()
chiamerà haversineSQL()
per creare l'SQL per la query e poi invierà la query chiamando la funzione sendQuery()
come mostrato nel seguente codice di esempio.
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;
}
Prova
Aggiungi il codice riportato sopra e prova lo strumento "Cerchio" per selezionare un'area della mappa. Il risultato dovrebbe essere simile a questo:
12. Esecuzione di query su forme arbitrarie
Riepilogo: SQL non supporta le query che utilizzano forme arbitrarie diverse da rettangoli e cerchi. BigQuery non dispone di alcun tipo di dati di geometria nativo, quindi per eseguire query utilizzando forme poligonali è necessario un approccio diverso rispetto alle query SQL semplici.
Una funzionalità molto potente di BigQuery che può essere utilizzata a questo scopo sono le funzioni definite dall'utente (UDF). Le UDF eseguono codice JavaScript all'interno di una query SQL.
Il codice funzionante per questo passaggio si trova in step7/map.html.
UDF nell'API BigQuery
L'approccio dell'API BigQuery per le UDF è leggermente diverso rispetto alla console web: dovrai chiamare jobs.insert method
.
Per le query SQL standard tramite l'API, è necessaria una sola istruzione SQL per utilizzare una funzione definita dall'utente. Il valore di useLegacySql
deve essere impostato su false
. L'esempio JavaScript riportato di seguito mostra una funzione che crea e invia un oggetto richiesta per inserire un nuovo job, in questo caso una query con una funzione definita dall'utente.
Un esempio funzionante di questo approccio è disponibile in 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));
}
La query SQL è strutturita nel seguente modo:
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;
}
Qui ci sono due cose da considerare. Innanzitutto, il codice crea l'istruzione CREATE TEMPORARY FUNCTION
che incapsula il codice JavaScript per determinare se un determinato punto si trova all'interno di un poligono. Le coordinate del poligono vengono inserite utilizzando la chiamata al metodo JSON.stringify(poly)
per convertire un array JavaScript di coppie di coordinate x,y in una stringa. L'oggetto poligono viene passato come argomento alla funzione che crea l'SQL.
In secondo luogo, il codice crea l'istruzione SQL principale SELECT
. La UDF viene chiamata nell'espressione WHERE
in questo esempio.
Integrazione con l'API Maps
Per utilizzarlo con la libreria di disegno dell'API Maps, dobbiamo salvare il poligono disegnato dall'utente e passarlo alla parte UDF della query SQL.
Innanzitutto, dobbiamo gestire l'evento di disegno polygoncomplete
per ottenere le coordinate della forma come array di coppie di longitudine e latitudine:
drawingManager.addListener('polygoncomplete', polygon => {
let path = polygon.getPaths().getAt(0);
let queryPolygon = path.map(element => {
return [element.lng(), element.lat()];
});
polygonQuery(queryPolygon);
});
La funzione polygonQuery
può quindi costruire le funzioni JavaScript UDF come stringa, nonché l'istruzione SQL che chiamerà la funzione UDF.
Per un esempio funzionante, consulta step7/map.html.
Output di esempio
Ecco un esempio di risultato della query sui prelievi dai dati dei taxi gialli della TLC di New York del 2016 in BigQuery utilizzando un poligono a mano libera, con i dati selezionati disegnati come una mappa termica.
13. Approfondimento
Ecco alcuni suggerimenti su come estendere questo codelab per esaminare altri aspetti dei dati. Puoi trovare un esempio funzionante di queste idee in step8/map.html nel repository di codice.
Interruzioni della mappatura
Finora abbiamo mappato solo i luoghi di ritiro. Se richiedi le colonne dropoff_latitude
e dropoff_longitude
e modifichi il codice della mappa termica per tracciare queste colonne, puoi visualizzare le destinazioni dei viaggi in taxi iniziati in una posizione specifica.
Ad esempio, vediamo dove tendono a lasciare le persone i taxi quando richiedono un passaggio nei pressi dell'Empire State Building.
Modifica il codice dell'istruzione SQL in polygonSql()
per richiedere queste colonne oltre alla località di ritiro.
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;
}
La funzione doHeatMap
può quindi utilizzare i valori di riconsegna. L'oggetto risultato ha uno schema che può essere esaminato per trovare la posizione di queste colonne nell'array. In questo caso, si troverebbero nelle posizioni di indice 2 e 3. Questi indici possono essere letti da una variabile per rendere il codice più gestibile. Tieni presente che il maxIntensity
della mappa termica è impostato per mostrare una densità di 20 interruzioni per pixel come valore massimo.
Aggiungi alcune variabili per consentirti di modificare le colonne utilizzate per i dati della mappa termica.
// 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);
}
Ecco una mappa termica che mostra la distribuzione delle corse terminate da tutti i punti di partenza nelle immediate vicinanze dell'Empire State Building nel 2016. Puoi vedere grandi concentrazioni (le macchie rosse) di destinazioni di Midtown, soprattutto intorno a Times Square, nonché lungo la 5th Avenue tra la 23rd St e la 14th St. Altri luoghi ad alta densità non mostrati a questo livello di zoom includono gli aeroporti La Guardia e JFK, il World Trade Center e Battery Park.
Applicare uno stile alla mappa base
Quando crei una mappa Google utilizzando l'API Maps JavaScript, puoi impostare lo stile della mappa utilizzando un oggetto JSON. Per le visualizzazioni dei dati può essere utile disattivare i colori nella mappa. Puoi creare e provare gli stili di mappa utilizzando la procedura guidata degli stili dell'API Google Maps all'indirizzo mapstyle.withgoogle.com.
Puoi impostare uno stile della mappa quando inizializzi un oggetto mappa o in un secondo momento. Ecco come aggiungere uno stile personalizzato nella funzione 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();
}
Lo stile di esempio riportato di seguito mostra una mappa in scala di grigi con etichette dei punti d'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"
}
]
}
]
Fornire feedback all'utente
Anche se BigQuery di solito fornisce una risposta in pochi secondi, a volte è utile mostrare all'utente che qualcosa sta succedendo mentre la query è in esecuzione.
Aggiungi un po' di UI alla tua pagina web che mostri la risposta della funzione checkJobStatus()
e un grafico animato per indicare che la query è in corso.
Le informazioni che puoi visualizzare includono la durata della query, la quantità di dati restituiti e la quantità di dati elaborati.
Aggiungi del codice HTML dopo la mappa <div>
per creare un riquadro nella pagina che mostri il numero di righe restituite da una query, il tempo impiegato dalla query e la quantità di dati elaborati.
<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>
L'aspetto e la posizione di questo riquadro sono controllati dal CSS. Aggiungi CSS per posizionare il riquadro nell'angolo in alto a sinistra della pagina sotto i pulsanti del tipo di mappa e la barra degli strumenti di disegno, come nello snippet riportato di seguito.
#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;
}
La grafica animata può essere aggiunta alla pagina, ma nascosta fino a quando non è necessaria. Inoltre, viene utilizzato codice JavaScript e CSS per mostrarla quando è in esecuzione un job BigQuery.
Aggiungi codice HTML per mostrare una grafica animata. Nel repository del codice è presente un file immagine denominato loader.gif
nella cartella img
.
<img id="spinner" src="img/loader.gif">
Aggiungi un po' di CSS per posizionare l'immagine e nasconderla per impostazione predefinita finché non è necessaria.
#spinner {
position: absolute;
top: 50%;
left: 50%;
margin-left: -32px;
margin-top: -32px;
opacity: 0;
z-index: -1000;
}
Infine, aggiungi del codice JavaScript per aggiornare il pannello di stato e mostrare o nascondere il grafico quando viene eseguita una query. Puoi utilizzare l'oggetto response
per aggiornare il riquadro in base alle informazioni disponibili.
Quando controlli un lavoro corrente, puoi utilizzare la proprietà response.statistics
. Una volta completato il job, puoi accedere alle proprietà response.totalRows
e response.totalBytesProcessed
. È utile per l'utente convertire i millisecondi in secondi e i byte in gigabyte per la visualizzazione, come mostrato nell'esempio di codice riportato di seguito.
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';
}
}
Chiama questo metodo quando c'è una risposta a una chiamata checkJobStatus()
e quando vengono recuperati i risultati della query. Ad esempio:
// 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);
})
}
Per attivare/disattivare la grafica animata, aggiungi una funzione per controllarne la visibilità. Questa funzione attiva/disattiva l'opacità di qualsiasi elemento DOM HTML passato.
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;
}
}
Infine, chiama questo metodo prima di elaborare una query e dopo che il risultato della query è stato restituito da BigQuery.
Questo codice chiama la funzione fadeToggle
quando l'utente ha finito di disegnare un rettangolo.
drawingManager.addListener('rectanglecomplete', rectangle => {
//show an animation to indicate that something is happening.
fadeToggle(document.getElementById('spinner'));
rectangleQuery(rectangle.getBounds());
});
Quando ricevi la risposta alla query, chiama di nuovo fadeToggle()
per nascondere la grafica animata.
// When a BigQuery job has completed, fetch the results.
function getQueryResults(jobId){
let request = gapi.client.bigquery.jobs.getQueryResults({
'projectId': billingProjectId,
'jobId': jobId
});
request.execute(response => {
doHeatMap(response.result.rows);
//hide the animation.
fadeToggle(document.getElementById('spinner'));
updateStatus(response);
})
}
La pagina dovrebbe avere un aspetto simile a questo.
Dai un'occhiata all'esempio completo in step8/map.html.
14. Aspetti da considerare
Troppi indicatori
Se lavori con tabelle molto grandi, la query potrebbe restituire troppe righe per essere visualizzate in modo efficiente su una mappa. Limita i risultati aggiungendo una clausola WHERE
o un'istruzione LIMIT
.
Se disegni molti indicatori, la mappa potrebbe diventare illeggibile. Prendi in considerazione l'utilizzo di una HeatmapLayer
per mostrare la densità o di indicatori cluster per indicare dove si trovano molti punti dati utilizzando un unico simbolo per cluster. Per ulteriori dettagli, consulta il nostro tutorial sul clustering dei marcatori.
Ottimizzazione delle query
BigQuery eseguirà la scansione dell'intera tabella a ogni query. Per ottimizzare l'utilizzo della quota BigQuery, seleziona solo le colonne necessarie nella query.
Le query saranno più veloci se memorizzi la latitudine e la longitudine come numeri in virgola mobile anziché come stringhe.
Esportare i risultati interessanti
Gli esempi riportati qui richiedono l'autenticazione dell'utente finale rispetto alla tabella BigQuery, il che non è adatto a tutti i casi d'uso. Una volta scoperti alcuni pattern interessanti, potrebbe essere più facile condividerli con un pubblico più ampio esportando i risultati da BigQuery e creando un set di dati statico utilizzando il livello dati di Google Maps.
La parte legale noiosa
Tieni presente i Termini di servizio di Google Maps Platform. Per ulteriori dettagli sui prezzi di Google Maps Platform, consulta la documentazione online.
Gioca con più dati.
In BigQuery sono disponibili diversi set di dati pubblici con colonne di latitudine e longitudine, ad esempio i set di dati sui taxi di New York dal 2009 al 2016, i dati sui viaggi di Uber e Lyft a New York e il set di dati GDELT.
15. Complimenti!
Ci auguriamo che questo ti aiuti a iniziare rapidamente a utilizzare alcune query geografiche sulle tabelle BigQuery per scoprire pattern e visualizzarli su una mappa di Google. Buona mappatura!
Passaggi successivi
Se vuoi saperne di più su Google Maps Platform o BigQuery, dai un'occhiata ai seguenti suggerimenti.
Consulta la sezione Che cos'è BigQuery? per scoprire di più sul servizio di data warehouse serverless su scala petabyte di Google.
Consulta la guida su come creare un'applicazione semplice utilizzando l'API BigQuery.
Per ulteriori dettagli sull'attivazione dell'interazione dell'utente per disegnare forme su una mappa di Google, consulta la guida per gli sviluppatori della libreria di disegno.
Dai un'occhiata ad altri modi per visualizzare i dati su una mappa di Google.
Consulta la Guida introduttiva all'API client JavaScript per comprendere i concetti di base dell'utilizzo dell'API client per accedere ad altre API di Google.