在 Bigtable 中使用地理空間資料

本頁面說明如何使用 GoogleSQL 地理函式,在 Bigtable 中儲存及查詢地理空間資料。地理空間資料包括地表的幾何表示法,以點、線和多邊形的形式呈現。您可以將這些實體分組為集合,追蹤地點、路線或其他感興趣的區域。

基本操作

專屬的 GoogleSQL 地理位置函式可讓 Bigtable 針對地理空間計算和查詢時間,將資料庫效能最佳化。在基本用法範例中,我們使用 tourist_points_of_interest 資料表,其中包含名為 poi_data 的單一資料欄系列。

poi_data
資料列索引鍵 名稱 位置
p1 艾菲爾鐵塔 POINT(2.2945 48.8584)

寫入地理位置資料

如要將地理空間資料寫入 Bigtable 欄,您必須以 GeoJSONWell-Known Text (WKT) 格式提供地理實體的字串表示法。Bigtable 會將基礎資料儲存為原始位元組,因此您可以在單一資料欄中提供任何實體組合 (例如 PointsLineStringsPolygonsMultiPointsMultiLineStringsMultiPolygons)。您在查詢時提供的地理空間實體類型非常重要,因為您需要使用適當的地理位置函式來解讀資料。

舉例來說,如要將一些景點新增至 tourist_points_of_interest 資料表,可以使用 cbt CLI 工具,如下所示:

  • 使用 WKT:
    cbt set tourist_points_of_interest p2 poi_data:name='Louvre Museum'
    cbt set tourist_points_of_interest p2 poi_data:location='POINT(2.3376 48.8611)'
    
    cbt set tourist_points_of_interest p3 poi_data:name='Place Charles de Gaulle'
    cbt set tourist_points_of_interest p3 poi_data:location='POLYGON(2.2941 48.8733, 2.2941 48.8742, 2.2957 48.8742, 2.2958 48.8732, 2.2941 48.8733)'
  • 使用 GeoJSON:
    cbt set tourist_points_of_interest p2 poi_data:name='Louvre Museum'
    cbt set tourist_points_of_interest p2 poi_data:location='{"type": "Point", "coordinates": [2.3376, 48.8611]}'
    
    cbt set tourist_points_of_interest p3 poi_data:name='Place Charles de Gaulle'
    cbt set tourist_points_of_interest p3 poi_data:location='{"type": "Polygon", "coordinates": [[ [2.2941, 48.8733], [2.2941, 48.8742], [2.2957, 48.8742], [2.2958, 48.8732], [2.2941, 48.8733] ]] }'

查詢地理位置資料

您可以在 Bigtable Studio 中使用 GoogleSQL 查詢 tourist_points_of_interest 資料表,並使用地理位置函式篩選結果。舉例來說,如要尋找距離特羅卡德羅 1500 公尺內的所有觀光景點,可以使用 ST_DWITHIN 函式:

SELECT _key, poi_data['name'], poi_data['location']
FROM points_of_interest
WHERE ST_DWITHIN(CAST(poi_data['location'] AS STRING), ST_GEOGPOINT(2.2874, 48.86322), 1500);

結果類似於以下內容:

螢幕截圖:顯示 Bigtable Studio 中的 tourist_points_of_interest 資料表,
    查詢結果已透過 ST_DWITHIN 地理位置函式篩選。

location」欄會顯示寫入作業中提供的原始文字資料,但 Bigtable 會將其儲存為位元組。在這個範例中,即使「戴高樂廣場」定義為延伸超過 1500 公尺限制的多邊形,仍會納入查詢結果。這是因為如果地理空間實體中任何一點位於預期距離內,ST_DWITHIN 函式就會傳回 true。

進階用法

以下各節說明如何使用地理函式處理複雜情境,例如地理圍欄分析。「查詢最佳化」一節說明如何使用持續具體化檢視區塊來提升效能。

為說明 Bigtable 中地理函式的高階用途,假設您是研究人員,負責監控雪丘島南側的帝企鵝行為。

您的設備會為每隻企鵝提供數百個位置連線偵測 (ping),因此您會建立 penguin_movements 資料表來儲存這些連線偵測 (ping):

penguin_details
資料列索引鍵 penguin_id 時間戳記 位置
ping#123 pen_01 2025-12-06 08:15:22+00 POINT(-57.51 -64.42)
ping#124 pen_01 2025-12-06 10:22:05+00 POINT(-57.55 -64.43)
ping#125 pen_01 2025-12-07 12:35:45+00 POINT(-57.58 -64.41)
ping#126 pen_02 2025-12-12 06:05:11+00 POINT(-57.49 -64.39)

範例:找出跨越地理圍欄的人

在研究過程中,您觀察到企鵝在特定覓食地附近的行為。以虛擬界線定義的區域通常稱為「地理圍欄」。餵食地點可視為設有地理圍欄的區域。 您想瞭解 2025 年 12 月 3 日是否有企鵝前往覓食地。如要回答這個問題,請使用 ST_CONTAINS 函式,如下所示:

SELECT
  penguin_details['penguin_id'],
  penguin_details['location'],
  penguin_details['timestamp']
FROM
  penguin_movements
WHERE
  penguin_details['timestamp'] >= '2025-12-03 00:00:00 UTC'
  AND penguin_details['timestamp'] < '2025-12-04 00:00:00 UTC'
  -- The feeding ground boundary is defined with a WKT POLYGON entity
  -- Polygon's last point must be equal to its first point to close the loop
  AND ST_CONTAINS(
        ST_GEOGFROMTEXT('POLYGON((-57.21 -64.51, -57.23 -64.55, -57.08 -64.56, -57.06 -64.51, -57.21 -64.51))'),
        ST_GEOGFROMTEXT(CAST(penguin_details['location'] AS STRING))
      )

您會看到類似下列的結果:

+------------+----------------------+------------------------+
| penguin_id |       location       |       timestamp        |
+------------+----------------------+------------------------+
| pen_01     | POINT(-57.15 -64.53) | 2025-12-03 08:15:22+00 |
| pen_02     | POINT(-57.18 -64.54) | 2025-12-03 14:30:05+00 |
| pen_10     | POINT(-57.10 -64.52) | 2025-12-03 11:45:10+00 |
+------------+----------------------+------------------------+

範例:將點轉換為路線

為了更清楚呈現企鵝的移動方式,您決定使用 ST_MAKELINE 函式,將個別位置 ping 整理成 LineStrings,如下所示:

SELECT
  penguin_id,
  DATE(timestamp, 'UTC') AS route_date,
  ST_MAKELINE(ARRAY_AGG(location_cast_to_geog)) AS route
FROM (
  -- Sub-expression to sort all location pings by timestamp
  -- This way you make sure individual points can form a realistic route
  SELECT
    penguin_details['penguin_id'] AS penguin_id,
    -- Extract and cast timestamp once
    CAST(CAST(penguin_details['timestamp'] AS STRING) AS TIMESTAMP) AS timestamp,
    -- Extract and cast location once
    ST_GEOGFROMTEXT(CAST(penguin_details['location'] AS STRING)) AS location_cast_to_geog
  FROM
    penguin_movements
  ORDER BY
    -- Pre-sorts location pings for the ARRAY_AGG function
    penguin_id, timestamp ASC
)
GROUP BY
  penguin_id,
  route_date

透過這項查詢,您可以取得更進階的深入分析資訊,以利研究。展開以下部分以參閱查詢範例和結果。

每隻企鵝的每日移動距離是多少?

如要計算每隻企鵝每天移動的距離,可以使用 ST_LENGTH 函式。

SELECT
  penguin_id,
  route_date,
  ST_LENGTH(route) AS daily_distance_meters
FROM (
  -- Subquery to aggregate points into daily routes
  SELECT
    penguin_id,
    DATE(timestamp, 'UTC') AS route_date,
    ST_MAKELINE(ARRAY_AGG(location_cast_to_geog)) AS route
  FROM (
    -- Sub-expression to sort all location pings by timestamp
    -- This way you make sure individual points can form a realistic route
    SELECT
      penguin_details['penguin_id'] AS penguin_id,
      -- Extract and cast timestamp once
      CAST(CAST(penguin_details['timestamp'] AS STRING) AS TIMESTAMP) AS timestamp,
      -- Extract and cast location once
      ST_GEOGFROMTEXT(CAST(penguin_details['location'] AS STRING)) AS location_cast_to_geog
    FROM
      penguin_movements
    ORDER BY
      -- Pre-sorts location pings for the ARRAY_AGG function
      penguin_id, timestamp ASC
  )
  GROUP BY
    penguin_id,
    route_date
) AS DailyRoutes;

這項查詢會傳回類似下列內容的結果:

+------------+------------+-----------------------+
| penguin_id | route_date | daily_distance_meters |
+------------+------------+-----------------------+
| pen_01     | 2025-12-02 | 15420.7               |
| pen_03     | 2025-12-03 | 22105.1               |
| pen_32     | 2025-12-03 | 9850.3                |
+------------+------------+-----------------------+

每日路線有多少比例位於地理圍欄區域內?

如要回答這個問題,請使用 ST_INTERSECTION 函式篩選結果,檢查企鵝路徑的哪些部分與設有地理圍欄的區域相交。接著,ST_LENGTH 函式可協助計算當天在覓食地移動的距離與總移動距離的比率。

SELECT
  penguin_id,
  route_date,
  ST_LENGTH(ST_INTERSECTION(route, ST_GEOGFROMTEXT('POLYGON((-57.21 -64.51, -57.23 -64.55, -57.08 -64.56, -57.06 -64.51, -57.21 -64.51))'))) / ST_LENGTH(route) AS proportion_in_ground
FROM (
  -- Subquery to aggregate points into daily routes
  SELECT
    penguin_id,
    DATE(timestamp, 'UTC') AS route_date,
    ST_MAKELINE(ARRAY_AGG(location_cast_to_geog)) AS route
  FROM (
    -- Sub-expression to sort all location pings by timestamp
    -- This way you make sure individual points can form a realistic route
    SELECT
      penguin_details['penguin_id'] AS penguin_id,
      -- Extract and cast timestamp once
      CAST(CAST(penguin_details['timestamp'] AS STRING) AS TIMESTAMP) AS timestamp,
      -- Extract and cast location once
      ST_GEOGFROMTEXT(CAST(penguin_details['location'] AS STRING)) AS location_cast_to_geog
    FROM
      penguin_movements
    ORDER BY
      -- Pre-sorts location pings for the ARRAY_AGG function
      penguin_id, timestamp ASC
  )
  GROUP BY
    penguin_id,
    route_date
) AS DailyRoutes
WHERE
  ST_INTERSECTS(route, ST_GEOGFROMTEXT('POLYGON((-57.21 -64.51, -57.23 -64.55, -57.08 -64.56, -57.06 -64.51, -57.21 -64.51))'))

這項查詢會傳回類似下列內容的結果:

+------------+------------+----------------------+
| penguin_id | route_date | proportion_in_ground |
+------------+------------+----------------------+
| pen_01     | 2025-12-03 | 0.652                |
| pen_03     | 2025-12-03 | 0.918                |
| pen_32     | 2025-12-04 | 0.231                |
+------------+------------+----------------------+

使用持續性具體化檢視表最佳化查詢

對大型資料集執行地理空間查詢時,可能需要掃描許多資料列,因此速度較慢。Bigtable 不支援專用的地理空間索引,但您可以透過持續性具體化檢視區塊提升查詢效能。

如要為這類檢視畫面建立專屬資料列索引鍵,您可以將 GEOGRAPHY 類型轉換為 S2 cell。S2 程式庫可讓您對球體幾何圖形執行複雜的三角函數運算。根據地理座標計算距離可能需要大量運算資源。S2 儲存格可以將地球表面上的特定區域表示為 64 位元整數,因此非常適合搭配連續具體化檢視區塊進行地理空間索引。

如要定義 S2 網格,請呼叫 S2_CELLIDFROMPOINT(location, level) 函式,並提供精細度 level 引數。這個引數是介於 030 之間的數字,可定義每個儲存格的大小。數字越大,儲存格越小。

在企鵝研究範例中,您可以建立持續性具體化檢視,並根據每隻企鵝的位置和時間戳記建立索引:

-- Query used to create the continuous materialized view
SELECT
  -- Create S2 cells for each penguin's location.
  -- Note that the `level` value is the same for each location so that
  -- every cell is the same size. This ensures consistency for your data.
  S2_CELLIDFROMPOINT(ST_GEOGFROMTEXT(CAST(penguin_details['location'] AS STRING)), level => 16) AS s2_cell_id,
  penguin_details['timestamp'] AS observation_time,
  penguin_details['penguin_id'] AS penguin_id,
  penguin_details['location'] AS location
FROM
  penguin_movements

採用兩階段做法,可大幅提升查詢這個檢視區塊的效率,進而解答「誰跨越了地理圍欄?」這個問題。您首先使用連續具體化檢視區塊中的 cell_id 索引,在一般鄰近範圍內快速找出候選資料列,然後對縮減的資料集套用準確的 ST_CONTAINS 函式:

SELECT
  idx.penguin_id,
  idx.location,
  idx.observation_time
FROM
  penguin_s2_time_index AS idx
WHERE
  -- Part one: use an approximate spatial filter and timestamp filter
  -- for fast scans on continuous materialized view keys.
  -- Use the S2_COVERINGCELLIDS function to create an array of S2 cells
  -- that cover the feeding ground polygon. The `level` argument must be the
  -- same value as the one you used to create the continuous materialized view.
  idx.s2_cell_id IN UNNEST(S2_COVERINGCELLIDS(
    ST_GEOGFROMTEXT('POLYGON((-57.21 -64.51, -57.23 -64.55, -57.08 -64.56, -57.06 -64.51, -57.21 -64.51))'),
    min_level => 16,
    max_level => 16,
    max_cells => 500
  ))
  AND idx.observation_time >= '2025-12-03 00:00:00 UTC'
  AND idx.observation_time < '2025-12-04 00:00:00 UTC'
  -- Part two: use ST_CONTAINS() on the returned set to ensure precision.
  -- S2 cells are squares, so they don't equal arbitrary geofence polygons.
  -- You still need to check if a specific point is contained within the area,
  -- but the filter applies to a smaller data set and is much faster.
  AND ST_CONTAINS(ST_GEOGFROMTEXT('POLYGON((-57.21 -64.51, -57.23 -64.55, -57.08 -64.56, -57.06 -64.51, -57.21 -64.51))'), idx.location);

限制

使用地理位置資料時,Bigtable 不支援下列功能:

  • 3D 幾何圖形。包括 WKT 格式的「Z」字尾,以及 GeoJSON 格式的高度座標。
  • 線性參考系統。包括 WKT 格式的「M」字尾。
  • 幾何圖元或多部分幾何圖形以外的 WKT 幾何圖形物件。 具體來說,Bigtable 僅支援 Point、MultiPoint、LineString、MultiLineString、Polygon、MultiPolygon 和 GeometryCollection。
  • 系統不支援 ST_CLUSTERDBSCAN 函式。

如要瞭解 GeoJSON 和 WKT 輸入格式的特定限制,請參閱 ST_GEOGFROMGEOJSONST_GEOGFROMTEXT

後續步驟