在 BigQuery 中使用 Google 地圖平台查詢資料,並以視覺化的方式呈現資料 (JavaScript)

在 BigQuery 中使用 Google 地圖平台查詢資料並以視覺化方式呈現資料 (JavaScript)

程式碼研究室簡介

subject上次更新時間:12月 3, 2021
account_circle作者:Google Maps Platform Team

1. 總覽

當您在資料集中以特定方式查看資料集中的模式時,Google 地圖可是非常強大的工具。這類連結可能是地點名稱、特定的緯度值和經度值,或是含有特定邊界的區域名稱,例如人口普查或郵遞區號。

當這些資料集變得非常龐大時,就很難使用傳統工具進行查詢及視覺化呈現。只要使用 Google BigQuery 來查詢資料,並使用 Google Maps API 建構查詢,並以視覺化方式呈現輸出結果,您就能輕鬆在資料中探索地理模式,而且只需要花少許設定或程式碼就能快速完成,而且不必管理系統來儲存大型資料集。

建構項目

在這個程式碼研究室中,您將編寫並執行一些查詢,示範如何使用 BigQuery 將位置相關深入分析結果提供給超大型公開資料集。您也會建構使用 Google 地圖平台 JavaScript API 載入地圖的網頁,然後使用 JavaScript API 的 Google API 用戶端程式庫BigQuery API,對相同規模龐大的公開資料集執行空間,並以視覺化的方式呈現這些查詢。

課程內容

  • 如何使用 SQL 查詢使用者定義的函式BigQuery API,透過 BigQuery 在幾秒內查詢 PB 規模的位置資料集
  • 如何使用 Google 地圖平台將 Google 地圖新增至網頁,讓使用者能夠在網頁上繪圖形狀
  • 以視覺化方式呈現 Google 地圖上的大型資料集查詢範例 (如下方範例圖片所示),顯示從 2016 年開始,從計程車前往帝國大廈周圍的街區,即可看到計程車上車地點的密度。

2017 年 5 月 9 日上午 11.01.12 AM.png 的螢幕擷取畫面

軟硬體需求

  • HTML、CSS、JavaScript、SQL 和 Chrome 開發人員工具的基本概念
  • 新世代網路瀏覽器,例如最新版 Chrome、Firefox、Safari 或 Edge。
  • 您選擇的文字編輯器或 IDE

科技

BigQuery

BigQuery 是 Google 專為規模龐大的資料集所打造的資料分析服務。它具備 RESTful API,並支援以 SQL 編寫的查詢。如果您的資料含有緯度和經度值,可以用來按位置查詢資料。如此一來,您不需要管理任何伺服器或資料庫基礎架構,就可以透過視覺化的方式探索大量資料集來查看模式。無論您的 BigQuery 規模有多大,您都能快速取得所需解答,但 BigQuery 的擴充性和代管基礎架構也十分豐富。

Google 地圖平台

Google 地圖平台會透過程式存取 Google 地圖、地點和路線資料。目前有超過 2 百萬個網站和應用程式使用 Google 地圖,透過地圖為使用者提供內嵌的地圖和地點查詢。

Google 地圖平台 JavaScript API 繪製圖層可讓您在地圖上繪製形狀。您可以將這些查詢轉換為輸入內容,以便針對儲存資料欄的經緯度值,對 BigQuery 資料表執行查詢。

首先,您需要有已啟用 BigQuery 和 Maps API 的 Google Cloud Platform 專案。

2. 開始設定

Google 帳戶

如果您還沒有 Google 帳戶 (Gmail 或 Google Apps),請先建立帳戶

建立專案

登入 Google Cloud Platform 主控台 (console.cloud.google.com),然後建立新專案。畫面頂端會顯示 [專案] 下拉式選單:

f2a353c3301dc649.png

按一下這個專案下拉式選單後,你會看到一個選單項目,可用來建立新專案:

56a42dfa7ac27a35.png

在顯示「為您的專案輸入新的名稱」的方塊中,輸入新專案的名稱,例如「BigQuery 程式碼研究室」。

程式碼研究室 - 建立專案 (1).png

系統將為您產生專案 ID。專案 ID 是所有 Google Cloud 專案中的專屬名稱。請記住您的專案 ID,因為您之後會用到。上方名稱已經有人使用,因此無法使用。在這組程式碼研究室中,將 YOUR_PROJECT_ID 插入您自己的專案 ID。

啟用計費功能

如要申請使用 BigQuery,請使用您在上一步選取或建立的專案。必須為這項專案啟用計費功能。啟用計費功能後,即可啟用 BigQuery API。

帳單的啟用方式取決於您,要建立新專案,還是重新啟用現有專案的計費功能。

Google 提供 12 個月的免費試用期,可讓您體驗價值最高 $300 美元的 Google Cloud Platform 用量,供您用於這個程式碼研究室。詳情請參閱 https://cloud.google.com/free/

新專案

建立新專案時,系統會提示您選擇要連結專案的帳單帳戶。如果您只有一個帳單帳戶,該帳戶會自動連結至您的專案。

如果您沒有帳單帳戶,請先為專案建立帳戶並啟用帳單功能,才能使用多項 Google Cloud Platform 功能。如要建立新的帳單帳戶並啟用專案的計費功能,請按照建立新帳單帳戶一文的說明操作。

現有專案

如果您的專案已暫時停用計費功能,您可以重新啟用計費功能:

  1. 前往 Cloud Platform 主控台
  2. 在專案清單中,選取您要重新啟用計費功能的專案。
  3. 開啟主控台左側選單,然後選取「Billing」(帳單) 帳單。系統要求您選取帳單帳戶。
  4. 按一下 [設定帳戶]

建立新的帳單帳戶

建立新帳單帳戶的方式如下:

  1. 請前往 Cloud Platform Console 登入;如果還沒有帳戶,請先申請。
  2. 開啟主控台左側選單,然後選取「Billing」(帳單) 帳單
  3. 按一下 [新增帳單帳戶] 選項。(請注意,如果這不是您的第一個帳單帳戶,請先在頁面頂端按一下現有帳單帳戶的名稱,開啟帳單帳戶清單並按一下 [管理帳單帳戶])。
  4. 輸入帳單帳戶名稱,並輸入您的帳單資訊。系統提供的選項將取決於您帳單地址所在的國家/地區。請注意,如果是美國帳戶,帳戶建立後就無法變更稅籍。
  5. 按一下 [提交資訊並啟用計費功能]。

預設情況下,建立帳單帳戶的人就是該帳戶的帳單管理員。

如要瞭解如何驗證銀行帳戶及新增備用付款方式,請參閱新增、移除或更新付款方式

啟用 BigQuery API

如要在專案中啟用 BigQuery API,請前往主控台的 BigQuery API 頁面市集,然後按一下藍色的 [啟用] 按鈕。

3. 在 BigQuery 中查詢位置資料

有三種方法可以查詢以 BigQuery 經緯度資料形式儲存的位置資料。

  • 矩形查詢:指定感興趣的區域,以選取最小和最大緯度與經度範圍內的所有列。
  • 半徑查詢:使用 Hasrsine 公式和數學函式計算特定點的圓,藉此指定感興趣的區域,以計算地球的形狀。
  • 多邊形查詢:指定自訂形狀,並使用「使用者定義的函式」來表達要測試各點的緯度和經度是否位於形狀內所需的多邊形點邏輯。

如要開始使用,請使用 Google Cloud Platform 主控台「大量查詢」專區中的查詢編輯器,針對紐約市的計程車資料執行以下查詢。

標準 SQL 與舊版 SQL

BigQuery 支援兩種版本的 SQL:舊版 SQL標準 SQL。後者是 2011 ANSI 標準。在本教學課程中,我們將使用標準 SQL,因為它的標準標準比較好。

如果您想在 BigQuery 編輯器中執行舊版 SQL,可執行以下動作:

  1. 按一下 [更多] 按鈕
  2. 在下拉式選單中,選取 [查詢設定]
  3. 在「SQL 方言」下方,選取「舊版」圓形按鈕;
  4. 按一下 [儲存] 按鈕

矩形查詢

矩形查詢在 BigQuery 中建構相當簡單。您只需要新增 WHERE 子句,將搜尋範圍限制在緯度和經度最小值與最大值之間的結果。

請在 BigQuery 主控台中試用以下範例。這項查詢會查詢在法屬中城和曼哈頓中城的矩形區域所搭乘的行程平均統計資料。您可以嘗試兩個不同的位置,將第二個 WHERE 子句取消註解,以便在 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

這兩項查詢的結果顯示,這兩個地區的平均行程距離、票價和小費取貨方式有顯著差異。

曼哈頓

平均提示

平均票價

平均距離

平均提示數量

平均里程數

2.52

12.03

9.97

22.39

5.97

日本福林

平均提示

平均票價

平均距離

平均提示數量

平均里程數

9.22

48.49

41.19

22.48

4.36

半徑查詢

如果您知道有數項數學,那麼在 SQL 中也可以建立半徑查詢。您可以使用 BigQuery 的舊版 Math 函式,利用猶太方公式來建構 SQL 查詢。

以下的 BigQuery SQL 陳述式範例是關於以 40.73943, -73.99585 為中心,且半徑為 0.1 公里的圓形查詢。

它使用 111.045 公里的常數值近似為一度表示的距離。

上述範例是以 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

哈薩林公式的 SQL 看起來很複雜,但您只要插入 BigQuery 的圓形中心座標、半徑與專案、資料集和表格名稱即可。

下面是查詢範例,能夠計算在帝國大廈 100 公尺內,提供部分自取次數的平均行程統計資料。請將其貼到 BigQuery 網路主控台中,以查看結果。變更緯度和經度,與其他地區 (例如布朗克斯地區) 進行比較。

#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

查詢結果如下所示。您會發現平均小費、票價、行程距離、小費與票價的乘積比例,以及每英里平均車程的票價差異皆有顯著差異。

帝國大廈:

平均提示

平均票價

平均距離

平均提示數量

平均里程數

1.17

11 月 8 日

45.28

10.53

6.42

Bronx

平均提示

平均票價

平均距離

平均提示數量

平均里程數

0.52

17.63

4.75

4.74

10.9

多邊形查詢

SQL 不支援使用矩形和圓形以外的任意形狀進行查詢。BigQuery 沒有任何原生的幾何圖形資料類型或空間索引,因此如要利用多邊形形狀執行查詢,就必須透過簡單的方法查詢 SQL 查詢。其中一個方法是在 JavaScript 中定義幾何圖形函式,並以 BigQuery 中的「使用者定義函式」(UDF) 執行。

許多幾何圖形運算都是以 JavaScript 編寫,因此可以輕鬆執行,並對包含緯度和經度的 BigQuery 資料表執行。您必須以 UDF 將自訂多邊形傳入,然後只對每一列執行測試,只傳回緯度和經度落在多邊形內的列。進一步瞭解 BigQuery 參考資料中的 UDF

Point Polygon 演算法

有許多方法可以計算點是否落在 JavaScript 中的多邊形內。這裡是知名實作 C 的連接埠,該連接埠使用光柵追蹤演算法,藉由計算不限形狀線條的數量來確定點是否在形狀內外的次數。這只需要幾行程式碼:

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

正在轉移至 JavaScript

此演算法的 JavaScript 版本如下所示:

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

在 BigQuery 中使用標準 SQL 時,UDF 方法只需要單一陳述式,但您必須在 UDF 中定義為暫時函式。這裡是範例。將以下 SQL 陳述式貼到「查詢編輯器」視窗中。

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

恭喜!

您現在可以使用 BigQuery 執行三種類型的空間查詢。如您所見,位置會對這個資料集的查詢產生顯著的結果差異,但除非您確實執行執行查詢的位置,否則只須透過 SQL 查詢即可探索臨時空間模式。

如果我們只能在地圖上顯示資料,並定義出您感興趣的區域,即可盡情探索資料!只要利用 Google Maps API 就能達成這個目標。首先,您必須啟用 Maps API、在本機電腦上執行簡單的網頁,然後開始使用 BigQuery API 從您的網頁傳送查詢。

4. 使用 Google Maps API

執行一些簡單的空間查詢後,下一步就是將輸出以視覺化的方式呈現,以瞭解模式。如要達成這個目標,您必須啟用 Maps API 並建立一個網頁,將查詢從地圖傳送至 BigQuery,然後在地圖上繪製結果。

啟用 Maps JavaScript API

在這個程式碼研究室中,您需要在專案中啟用 Google 地圖平台的 Maps JavaScript API。若要這麼做,請執行下列作業:

  1. 在 Google Cloud Platform 主控台中,前往 Marketplace
  2. 在「市集」中搜尋「Maps JavaScript API」。
  3. 按一下搜尋結果中的 Maps JavaScript API 圖塊
  4. 按一下 [啟用] 按鈕

產生 API 金鑰

如要向 Google 地圖平台發出要求,您必須產生 API 金鑰,並與所有要求一起傳送。如要產生 API 金鑰,請執行下列步驟:

  1. 在 Google Cloud Platform 主控台中,按一下漢堡選單以開啟左側導覽列
  2. 選取 ‘APIs & Service' > ‘憑證'
  3. 按一下 [Create Credential'] 按鈕,然後選取 [API 金鑰]
  4. 複製新的 API 金鑰

下載程式碼並設定網路伺服器

點選下方按鈕即可下載此程式碼研究室的所有程式碼:

將下載的 ZIP 檔案解壓縮。這會將 Root 資料夾 (bigquery) 解壓縮,其中包含此程式碼研究室中每個步驟一個資料夾,以及您需要的所有資源。

stepN 資料夾包含這個程式碼研究室每個步驟的結束狀態。這些資料僅供參考。我們會在《work》目錄中執行所有程式設計作業。

設定本機網路伺服器

雖然您可以自由使用自己的網路伺服器,但這個程式碼研究室是專為 Chrome 網路伺服器而設計。如果尚未安裝該應用程式,請前往 Chrome 線上應用程式商店安裝。

安裝完畢後,請在 Chrome 中執行下列步驟:

  1. 開啟 Chrome
  2. 在頂端的網址列中輸入 chrome://apps
  3. 按下 Enter 鍵。
  4. 在隨即開啟的視窗中,按一下「網路伺服器」圖示 您也可以在應用程式上按一下滑鼠右鍵,即可以一般分頁或固定分頁、全螢幕或新視窗開啟應用程式a3ed00e79b8bfee7.png接下來,您會看到這個對話方塊,讓您設定本機的網路伺服器:81b6151c3f60c948.png
  5. 按一下 [選擇資料夾] > [下載程式碼研究室範例檔案的位置]
  6. 在 [選項] 部分中,勾選 [自動顯示 index.html'] 旁邊的方塊:17f4913500faa86f.png
  7. 將標有「網路伺服器:STARTED&#39」的切換按鈕滑動至左側,然後往右滑動停止,然後重新啟動網路伺服器

a5d554d0d4a91851.png

5. 載入地圖和繪圖工具

建立基本地圖頁面

先從簡單的 HTML 網頁開始載入,並使用 Maps JavaScript API 和幾行 JavaScript 載入「Google 地圖」。Google 地圖平台簡易地圖樣本中的程式碼是很好的入門磚。我們在這裡複製了這些內容,方便你複製及貼到所選文字編輯器或 IDE 中。或者,你也可以從下載的存放區中開啟 index.html,即可找到該檔案。

  1. index.html 複製到本機存放區的 work 資料夾中
  2. 將 img/ 資料夾複製到本機副本的 Work/ 資料夾。
  3. 在文字編輯器或 IDE 中開啟 Work/index.html
  4. YOUR_API_KEY 替換為您先前建立的 API 金鑰
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
   
async defer></script>
  1. 在瀏覽器中開啟 localhost:<port>/work,其中 port 是您本機網路伺服器設定中指定的通訊埠編號。預設通訊埠為 8887。系統應該會顯示您的第一個地圖。

如果在瀏覽器中顯示錯誤訊息,請檢查 API 金鑰是否正確且您的本機網路伺服器是否已啟用。

變更預設位置和縮放等級

設定位置和縮放等級的程式碼位於第 27 行 (index.html) 的第 28 行,目前以澳洲雪梨為中心:

<script>
      let map
;
     
function initMap() {
        map
= new google.maps.Map(document.getElementById('map'), {
          center
: {lat: -34.397, lng: 150.644},
          zoom
: 8
       
});
     
}
</script>

本教學課程適用於紐約的 BigQuery 計程車乘車資料,因此您將在地圖中,將地圖初始化程式碼的中心設為適當縮放等級 (13 或 14 的效果最好)。

如要這麼做,請將上方的程式碼區塊更新為以下內容,將地圖置於帝國大廈的地圖中心,並將縮放等級調整為 14:

<script>
      let map
;
     
function initMap() {
        map
= new google.maps.Map(document.getElementById('map'), {
          center
: {lat: 40.7484405, lng: -73.9878531},
          zoom
: 14
       
});
     
}
</script>

接著,在瀏覽器中重新載入地圖以查看結果。

載入繪圖和視覺化程式庫

如要在地圖中加入繪圖功能,您必須新增可加入 Google 地圖平台的繪圖程式庫的選用參數,以變更載入 Maps JavaScript API 的指令碼。

此程式碼研究室也使用 HeatmapLayer,因此您也會更新指令碼來要求視覺化資料庫。做法是新增 libraries 參數,並將 visualizationdrawing 程式庫指定為以逗號分隔的值,例如 libraries=visualization,drawing

它看起來應該像這樣:

<script src='http://maps.googleapis.com/maps/api/js?libraries=visualization,drawing&callback=initMap&key=YOUR_API_KEY' async defer></script>

新增 DrawingManager

如要把使用者繪圖形狀當做查詢輸入,請在啟用 CircleRectanglePolygon 工具的情況下,將 DrawingManager 加到地圖中。

建議您將所有 DrawingManager 設定程式碼加入新的函式,因此在 index.html 的副本中,執行以下操作:

  1. 加入含有以下程式碼的 setUpDrawingTools() 函式,建立 DrawingManager 並設定其 map 屬性以參照網頁中的地圖物件。

傳送到 google.maps.drawing.DrawingManager(options) 的選項會設定預設的圖形繪製類型和繪製圖形的顯示選項。如要選取可傳送的查詢的地圖區域,圖形的透明度應為零。如要進一步瞭解可用的選項,請參閱 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. 建立地圖物件後,透過 initMap() 函式呼叫 setUpDrawingTools()
function initMap() {
  map
= new google.maps.Map(document.getElementById('map'), {
    center
: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
    zoom
: 12
 
});

  setUpDrawingTools
();
}
  1. 重新載入 index.html,並確認您已開啟繪圖工具。並確定您可以用來繪製圓形、矩形和多邊形。

您可以按住並拖曳以繪製圓圈和矩形,但是必須對每個頂點進行繪製,然後按兩下來繪製多邊形。

處理繪圖事件

您需要一些程式碼來處理使用者完成形狀繪製時所觸發的事件,就像繪製圖形形狀的座標來建構 SQL 查詢一樣。

我們會在稍後的步驟中加進此程式碼。不過,目前我們只能測試三個空白的事件處理常式以處理 rectanglecompletecirclecompletepolygoncomplete 事件。處理常式在這個階段不需要執行任何程式碼。

請將以下內容新增到 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.
});

您可以在存放區的本機副本中找到這個程式碼的有效範例,位於 step2 資料夾:step2/map.html

6. 使用 BigQuery Client API

Google BigQuery Client API 可協助您避免編寫大量樣板程式碼,以建立要求、剖析回應及處理驗證。本程式碼研究室透過 JavaScript 專用 Google API 用戶端程式庫使用 BigQuery API,因為我們會開發瀏覽器應用程式。

接下來,您需加入在網頁中載入這個 API 的程式碼,並用來與 BigQuery 互動。

新增 Google Client API for JavaScript

您將使用 JavaScript 專用的 Google 用戶端 API,對 BigQuery 執行查詢。在您的 index.html 副本中 (位於 work 資料夾中),使用 <script> 標記載入 API,就像這樣。請將標記緊鄰載入 Maps API 的 <script> 標記下方:

<script src='https://apis.google.com/js/client.js'></script>

載入 Google Client API 後,請授權使用者存取 BigQuery 中的資料。如要這麼做,您可以使用 OAuth 2.0。首先,您必須在 Google Cloud Console 專案中設定部分憑證。

建立 OAuth 2.0 憑證

  1. 在 Google Cloud Console 的導覽選單中,選取 [API 與服務] > [憑證]

設定憑證之前,您必須新增「授權」畫面的部分設定選項,讓應用程式使用者在授權您的應用程式存取自己的 BigQuery 資料時會看到這個畫面。

方法是點選 [OAuth 同意畫面] 分頁標籤。2. 您必須將 Big Query API 新增至這個憑證的範圍。在「Scopes for Google API」(範圍) 部分中,按一下 [Add Scope] (新增範圍) 按鈕。3. 在清單中勾選具有 ../auth/bigquery 範圍的 Big Query API 項目旁的方塊。4. 按一下 [新增]。5. 在「應用程式名稱」欄位輸入名稱。6. 按一下 [儲存] 以儲存設定。7. 接下來,請建立 OAuth 用戶端 ID。如要這麼做,請按一下 [建立憑證]

4d18a965fc760e39.png

  1. 然後點選下拉式選單中的 [OAuth 用戶端 ID]1f8b36a1c27c75f0.png
  2. 在「應用程式類型」之下選取 [網路應用程式]。
  3. 在「Application Name」(應用程式名稱) 欄位中,輸入專案名稱。例如:「BigQuery 和地圖」。
  4. 在「限制」下方的「授權的 JavaScript 來源」欄位中,輸入 localhost 的網址,包括通訊埠編號。例如:http://localhost:8887
  1. 按一下 [Create] (建立) 按鈕。

彈出式視窗會顯示用戶端 ID 和用戶端密鑰。您需要用戶端 ID 才能對 BigQuery 進行驗證。複製這個變數並將其貼至 work/index.html,做為 clientId 的新全域 JavaScript 變數。

let clientId = 'YOUR_CLIENT_ID';

7. 授權與初始化

您的網頁將需要授權使用者存取 BigQuery,才能初始化地圖。在本範例中,我們使用 JavaScript Client API 說明文件的授權部分說明的 OAuth 2.0。您必須使用 OAuth 用戶端 ID 和專案 ID 傳送查詢。

在網頁中載入 Google Client API 後,必須執行下列步驟:

  • 授權使用者。
  • 如果已獲授權,請載入 BigQuery API。
  • 載入並初始化地圖。

請參閱 step3/map.html 的範例,瞭解完成 HTML 網頁後的範例。

授權使用者

應用程式的使用者需要授權應用程式以存取 BigQuery 中的資料。JavaScript 專用的 Google Client API 會處理 OAuth 邏輯。

在實際應用程式中,您有許多選擇可以整合授權步驟。

舉例來說,您可以從按鈕等 UI 元素呼叫 authorize(),或在網頁載入後執行這項動作。我們選擇透過 gapi.load() 方法的回呼函式,在載入 JavaScript 的 Google Client API 後,為使用者授權。

請在載入 JavaScript 專用 Google Client API 的 <script> 標記後立即編寫一些程式碼,以載入用戶端程式庫和驗證模組,以便我們直接驗證使用者。

<script src='https://apis.google.com/js/client.js'></script>
<script type='text/javascript'>
  gapi
.load('client:auth', authorize);
</script>

在授權時載入 BigQuery API

使用者獲得授權後,請載入 BigQuery API。

首先,請以您在上一步中新增的 clientId 變數呼叫 gapi.auth.authorize()。在名為 handleAuthResult 的回呼函式中處理回應。

immediate 參數可控制是否要向使用者顯示彈出式視窗。將這項政策設為 true,當使用者已獲得授權,就能略過授權彈出式視窗。

在您的頁面上新增名為 handleAuthResult() 的函式。此函式需要使用 authresult 參數,讓您根據使用者是否成功授權,控制邏輯流程。

新增「loadApi」函式,即可在使用者成功授權時載入 BigQuery API。

如果將 authResult 物件傳遞至函式,且物件的 error 屬性的值為 false,則請在 handleAuthResult() 函式中新增邏輯來呼叫 loadApi()

將程式碼新增至 loadApi() 函式,使用 gapi.client.load() 方法載入 BigQuery API。

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

載入地圖

最後一步是初始化地圖。您必須稍微變更邏輯的順序,才能進行這項作業。目前,它會在 Maps API JavaScript 載入時初始化。

方法是在 gapi.client 物件上的 load() 方法之後,從 then() 方法呼叫 initMap() 函式。

// Load BigQuery client API
function loadApi(){
  gapi
.client.load('bigquery', 'v2').then(
   
() => initMap()
 
);
}

8. BigQuery API 概念

BigQuery API 呼叫通常會在幾秒內執行,但可能不會立即傳回回應。您需要一些邏輯來輪詢 BigQuery,找出長時間工作的狀態,而且只在工作完成時擷取結果。

這個步驟的完整程式碼位於 step4/map.html

傳送要求

將 JavaScript 函式新增至 work/index.html,以便使用 API 傳送查詢;此外,有些變數可以儲存 BigQuery 資料集和含有查詢的資料表的專案值,以及會收取專案費用的帳單 ID。

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

檢查工作狀態

下列 checkJobStatus 函式示範如何使用 get API 方法,以及原始查詢要求傳回的 jobId,定期檢查工作狀態。這裡的例子是每 500 毫秒執行一次工作,直到工作完成為止。

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

修改 sendQuery 方法,在 request.execute() 呼叫中呼叫 checkJobStatus() 方法做為回呼。將工作 ID 傳送至 checkJobStatus。回應物件會以 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));
}

取得查詢結果

如要在執行查詢完成時取得查詢結果,請使用 jobs.getQueryResults API 呼叫。在您的網頁上新增名為 getQueryResults() 的函式,該函式可接受 jobId 參數:

function getQueryResults(jobId){
  let request
= gapi.client.bigquery.jobs.getQueryResults({
   
'projectId': billingProjectId,
   
'jobId': jobId
 
});
  request
.execute(response => {
   
// Do something with the results.
 
})
}

9. 使用 BigQuery API 查詢位置資料

有三種方法可以使用 SQL,對 BigQuery 的資料執行空間查詢:

請參閱 BigQuery 舊版 SQL 參考資料中「進階範例」一節,瞭解一些定界框和半徑查詢範例。

查詢定界框和半徑範圍時,您可以呼叫 BigQuery API query 方法。為每個查詢建構 SQL,並將其傳送至您在前一個步驟建立的 sendQuery 函式。

這個步驟的程式碼範例如下:step4/map.html

矩形查詢

在地圖上顯示 BigQuery 資料最簡單的方法,就是要求使用緯度和經度在矩形內的所有資料列,並使用「小於」和「大於」來要求所有列。這可能是目前的地圖檢視,或是在地圖上繪製的圖形。

如要使用使用者繪製的圖形,請變更 index.html 中的程式碼,以處理完成矩形時觸發的繪圖事件。在本範例中,程式碼在矩形物件上使用 getBounds(),以便取得代表地圖座標中矩形程度的物件,並將物件傳遞至名為 rectangleQuery 的函式:

drawingManager.addListener('rectanglecomplete', rectangle => rectangleQuery(rectangle.getBounds()));

rectangleQuery 函式只需要使用右上方的 (東北方) 和左下角 (西南方) 的座標,即可建構與 BigQuery 資料表中每一列之間的小於/大於右側座標。以下這個查詢範例會查詢內含資料欄 'pickup_latitude''pickup_longitude' 的資料欄 (這些資料欄會儲存位置值)。

指定 BigQuery 資料表

如要使用 BigQuery API 查詢資料表,您必須在 SQL 查詢中提供完整表格的資料表名稱。標準 SQL 的格式為 project.dataset.tablename。在舊版 SQL 中,則是 project.dataset.tablename

目前市面上有許多紐約計程車之旅。如要查看這些資料,請前往 BigQuery 網路主控台,然後展開「公開資料集」選單項目。找出名為 new_york 的資料集並展開以查看資料表。選擇黃色計程車行程表:bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016)。

指定專案 ID

為進行計費,在 API 呼叫中,您需要指定 Google Cloud Platform 專案名稱。在這個程式碼研究室中,此專案與包含資料表的專案相同。如果您使用的表格由您自行在專案中建立,那麼這份資料 ID 會與 SQL 陳述式中的表格 ID 相同。

將 JavaScript 變數加進程式碼,以包含對您所查詢資料表之公開資料集專案的參照,以及資料表名稱和資料集名稱。您還需要另一個變數來參照您自己的帳單專案 ID。

將名為 billingProjectId, publicProjectId, datasetIdtableName 的全域 JavaScript 變數加入 index.html 的副本。

使用 BigQuery 公開資料集專案中的詳細資料,將變數 'publicProjectId''datasetId''tableName' 初始化。使用您自己的專案 ID (在本程式碼研究室的「開始設定」中所建立的 ID) 初始化 billingProjectId

let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId
= 'bigquery-public-data';
let datasetId
= 'new_york_taxi_trips';
let tableName
= 'tlc_yellow_trips_2016';

現在,請在程式碼中新增兩個函式來產生 SQL,並使用您在上一步建立的 sendQuery 函式來傳送查詢至 BigQuery。

第一個函式的名稱應該是 rectangleSQL(),且必須接受兩個引數,其中一組 google.Maps.LatLng 物件代表地圖座標中的矩形角落。

第二個函式應命名為 rectangleQuery()。這會將查詢文字傳送至 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;
}

目前,您有足夠的程式碼可以向 BigQuery 傳送查詢,針對使用者繪製的矩形所包含的所有資料列。在新增圓形和自由形狀的查詢方法之前,讓我們先瞭解如何處理查詢傳回的資料。

10. 以視覺化方式呈現回應

BigQuery 資料表可以有極大 (PB 規模的資料),且每秒可增加數萬列。因此,請務必限制系統傳回的資料量,以便在地圖上繪製資料。繪製非常大型的結果集中每一個資料列的位置 (數十萬列以上) 會導致地圖無法讀取。在 SQL 查詢和地圖上,許多技巧可以用來匯總位置,而您可以限制查詢傳回的結果。

這個步驟的完整程式碼可在 step5/map.html 中找到。

為了讓程式碼移轉到網頁的資料量較小,請調整此程式碼研究室的 rectangleSQL() 函式,加入一個陳述式來新增回應限制為 10, 000 列的陳述式。下方範例是在名為 recordLimit 的全域變數中指定,因此所有查詢函式都可使用相同的值。

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

如要以視覺化方式顯示地點的密度,您可以使用熱視圖。Maps JavaScript API 有 HeatmapLayer 類別可達到此目的。HotmapLayer 採用緯度與經度座標陣列,因此可以輕鬆將查詢傳回的資料列轉換成熱視圖。

getQueryResults 函式中,將 response.result.rows 陣列傳遞至名為 doHeatMap() 的新 JavaScript 函式,以建立熱視圖。

每個資料列都會有一個稱為「f」的屬性,這是資料欄陣列。每一欄將會有一個包含值的 v 屬性。

您的程式碼必須循環瀏覽各列中的資料欄,然後擷取相關的值。

在 SQL 查詢中,您只要求了計程車接送的緯度和經度值,所以回應中只會包含兩個資料欄。

指派位置陣列後,別忘了在熱視圖圖層上呼叫 setMap()。這會在地圖上顯示。

範例如下:

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

此時,您應該可以:

  • 開啟頁面並對 BigQuery 進行授權
  • 在紐約市某處繪製矩形
  • 以視覺化方式查看產生的查詢結果。

下方範例是針對以 2016 年紐約市黃色計程車資料進行矩形查詢的結果 (以熱視圖繪製),此處顯示帝國大廈在 7 月週六的取貨地點:

7b1face0e7c71c78.png

11. 依點周圍的半徑查詢

半徑查詢非常相似,您可以使用 BigQuery 的舊版 SQL 數學函式,使用哈氏公式來建構 SQL 查詢。

對矩形使用相同技術時,您可以處理 OverlayComplete 事件來取得使用者繪圖圓圈的中心和半徑,並用同樣的方式建立查詢的 SQL。

這個步驟的程式碼有效範例會在程式碼存放區中納入 step6/map.html

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

在您的 index.html 複本中,加入兩個新的空白函式:circleQuery()haversineSQL()

接著,新增 circlecomplete 事件處理常式,將中心和半徑傳遞至名為 circleQuery(). 的新函式

circleQuery() 函式會呼叫 haversineSQL() 來建構查詢的 SQL,然後呼叫 sendQuery() 函式,如下列範例程式碼所示來傳送查詢。

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

立即體驗!

加入上方的程式碼,並嘗試使用「Circle'」工具選取地圖區域。結果應如下所示:

845418166b7cc7a3.png

12. 查詢任意形狀

回顧:SQL 不支援使用矩形和圓形以外的任意形狀進行查詢。BigQuery 沒有任何原生幾何圖形資料類型,因此如要利用多邊形形狀執行查詢,就必須透過簡單的方法來執行 SQL 查詢。

這項功能非常強大的 BigQuery 功能就是使用者定義的函式 (UDF)。UDF 會在 SQL 查詢中執行 JavaScript 程式碼。

這個步驟的工作代碼位於 step7/map.html

BigQuery API 中的 UDF

UDF 的 BigQuery API 方法與網頁版控制台略有不同,您必須呼叫 jobs.insert method

透過 API 執行標準 SQL 查詢時,只要有一個 SQL 陳述式,即可使用使用者定義的函式。useLegacySql 的值必須設為 false。下方 JavaScript 範例顯示了建立和傳送要求物件以插入新工作的函式 (本例中是指使用「使用者定義函式」的查詢)。

這個步驟的有效範例位於 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));
}

SQL 查詢的結構如下:

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

這裡有兩個項目首先,程式碼會建立一個 CREATE TEMPORARY FUNCTION 陳述式,在特定的多邊形內置入 JavaScript 程式碼,以指出某個路徑點位於多邊形內。系統會使用 JSON.stringify(poly) 方法呼叫插入多邊形座標,將 x,y 座標組合的 JavaScript 陣列轉換成字串。多邊形物件會以引數的形式傳送至建立 SQL 的函式。

其次,程式碼會建構主要的 SQL SELECT 陳述式。在這個範例中,系統會呼叫 WHERE 的 UDF。

與 Maps API 整合

為了搭配 Maps API 繪製程式庫使用,我們必須儲存使用者繪製的多邊形,並傳送至 SQL 查詢的 UDF 部分。

首先,我們必須處理 polygoncomplete 繪圖事件,以取得形狀的經緯度座標的形狀。

drawingManager.addListener('polygoncomplete', polygon => {
  let path
= polygon.getPaths().getAt(0);
  let queryPolygon
= path.map(element => {
   
return [element.lng(), element.lat()];
 
});
  polygonQuery
(queryPolygon);
});

接著,polygonQuery 函式可以建構為字串的 UDF JavaScript 函式,以及會呼叫 UDF 函式的 SQL 陳述式。

請參閱步驟 7/map.html 取得實際範例。

輸出範例

以下舉例說明使用自由多邊形查詢 BigQuery 中 2016 年紐約市 TLC 黃色計程車服務資料的選取結果,並將選取的資料繪製成熱視圖。

2017-05-09 上午 10.00.48 AM.png 屏幕拍攝

13. 深入探索

以下提供幾個建議,協助您擴充這個程式碼研究室,看看資料的其他層面。您可以前往程式碼存放區的 step8/map.html,查看這些提案的有效範例。

對應下車地點

目前我們尚未標示出取貨地點。要求 dropoff_latitudedropoff_longitude 資料欄,並修改熱視圖程式碼,改為繪製熱視圖,以檢視從特定地點開始的計程車之旅目的地。

例如,我們來看看計程車在哪地點下車時,在國王大廈周圍正在下車。

請在 polygonSql() 中變更 SQL 陳述式的程式碼,以便要求取貨地點以及取貨地點。

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

doHeatMap 函式接著可以使用下推值。結果物件具有可檢查的結構定義,您可以檢查這些陣列在陣列中的位置。在這種情形下,它們位於索引位置 2 和 3。您可以從索引讀取這些索引,讓程式碼更易於管理。熱視圖的 maxIntensity 設定為顯示每像素 20 個下車的密度做為最大值。

新增一些變數,以更改用於熱視圖資料的欄。

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

以下熱視圖顯示 2016 年帝國大廈周圍的所有自取車點分佈情況。您可以看到大中城目的地的大濃度(紅色 blob),尤其是時代廣場,以及第 23 街和第 14 街之後的第 5 大道。其他在此縮放等級下未顯示的其他高密度位置,包括拉瓜迪亞和 JFK 機場、世界貿易中心和電池公園。

2017 年 5 月 9 日上午 10.40.01 上午.

設定基本地圖樣式

使用 Maps JavaScript API 建立 Google 地圖時,您可以使用 JSON 物件來設定地圖樣式。對於資料視覺化來說,將地圖的顏色設為靜音是很實用的做法。您可以透過 Google Maps API 樣式精靈 (mapstyle.withgoogle.com) 建立和試用地圖樣式。

您可以在初始化地圖物件時,或是在之後的任何時間設定地圖樣式。以下是如何在 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
();
}

以下範例樣式顯示了具有搜尋點標籤的灰階地圖。

[
 
{
   
"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"
     
}
   
]
 
}
]

提供使用者意見

儘管 BigQuery 通常會在幾秒內做出回應,但對使用者顯示查詢進行期間的某些操作有用,

在網頁上新增顯示 checkJobStatus() 函式回應的使用者介面,並以動畫圖像指出查詢進行中。

可顯示的資訊包括查詢時間長度、傳回的資料量,以及處理的資料量。

在地圖 <div> 的後面加入幾個 HTML,將面板新增至顯示查詢傳回的列數、查詢所需時間以及處理的資料量。

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

這個面板的外觀和位置是由 CSS 所控制。新增 CSS 即可將面板放在頁面左上方按鈕類型和繪圖工具列下方,如以下程式碼片段所示。

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

動畫圖片可以加到網頁中,不過在必要時會隱藏,而且有些指令碼和 CSS 程式碼是用來在 BigQuery 工作執行期間顯示。

新增一些 HTML 以顯示動畫圖片。在程式碼存放區的「img」資料夾中,有一個名為「loader.gif」的圖片檔。

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

新增一些 CSS 以定位圖片,並預設為隱藏,直到其需要為止。

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

最後,新增一些 JavaScript 以更新狀態面板,並在執行查詢時顯示或隱藏圖形。您可以使用 response 物件,根據可用的資訊更新面板。

查看目前工作時,您可以使用 response.statistics 屬性。工作完成後,您就可以存取 response.totalRowsresponse.totalBytesProcessed 屬性。如下列範例程式碼所示,使用者可以將毫秒轉換為秒或位元組轉換成 GB 以顯示。

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

有回應 checkJobStatus() 呼叫及擷取查詢結果時,請呼叫這個方法。例如:

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

如要切換動畫圖像,請新增函式來控制顯示設定。此函式將會切換任何傳遞給它的 HTML DOM 元素透明度。

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

最後,在處理查詢以及 BigQuery 傳回查詢結果之後,請呼叫這個方法。

此程式碼會在使用者完成繪製矩形後呼叫 fadeToggle 函式。

drawingManager.addListener('rectanglecomplete', rectangle => {
 
//show an animation to indicate that something is happening.
  fadeToggle
(document.getElementById('spinner'));
  rectangleQuery
(rectangle.getBounds());
});

收到查詢回應後,請再次呼叫 fadeToggle() 以隱藏動畫圖片。

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

網頁看起來應該像這樣。

2017-05-10 下午 2.32.19 PM.png

請查看 step8/map.html 中的完整範例。

14. 注意事項

標記過多

如果您處理的表格規模非常龐大,您的查詢可能會傳回太多列,無法有效在地圖上顯示。加入 WHERE 子句或 LIMIT 陳述式來限制結果範圍。

繪製多個標記可能會使地圖無法讀取。建議使用 HeatmapLayer 來顯示密度,或使用叢集標記指出每個資料點使用單一符號的位置。如需詳細資訊,請參閱標記叢集教學課程

最佳化查詢

BigQuery 會在每次查詢中掃描整個資料表。如要提升 BigQuery 配額用量,請只在查詢中選取所需資料欄。

如果您將緯度和經度儲存為浮動值 (而非字串),查詢就會更快。

匯出有趣的結果

這裡的範例需要以 BigQuery 資料表驗證使用者,此表格無法配合各種用途。如果您發現一些有趣的模式,建議您從 BigQuery 匯出結果並使用 Google 地圖資料層建立靜態資料集,以便與更多目標對象共用,

請注意,Google 地圖平台服務條款。如要進一步瞭解 Google 地圖平台的定價,請參閱線上說明文件

提供更多資料來玩遊戲!

在 BigQuery 中,許多公開資料集都含有緯度和經度資料欄,例如 2009-2016 年的紐約市計程車資料集Uber 和 Lyft 紐約市的行程資料,以及 GDELT 資料集

15. 恭喜!

希望能協助您快速地針對 BigQuery 表格快速設定和執行地理查詢,以便在「Google 地圖」上探索這些模型,並以視覺化的方式呈現。祝您地圖愉快!

後續步驟

如要進一步瞭解 Google 地圖平台或 BigQuery,請參考下列建議。

如要進一步瞭解 Google 的 PB 規模無伺服器資料倉儲服務,請參閱關於 BigQuery 一文。

參閱使用指南,瞭解如何使用 BigQuery API 建立簡易型應用程式

如要進一步瞭解如何讓使用者透過 Google 地圖繪製形狀,請參閱繪圖程式庫開發人員指南

查看其他在 Google 地圖上視覺化資料的方式。

請參閱 JavaScript 用戶端 AP 入門指南,瞭解使用 Client API 存取其他 Google API 的基本概念。