Bekerja dengan data geospasial di Bigtable

Halaman ini menjelaskan cara menyimpan dan membuat kueri data geospasial di Bigtable menggunakan fungsi geografi GoogleSQL. Data geospasial mencakup representasi geometris permukaan Bumi dalam bentuk titik, garis, dan poligon. Anda dapat mengelompokkan entitas ini ke dalam koleksi untuk melacak lokasi, rute, atau area menarik lainnya.

Operasi dasar

Fungsi geografi GoogleSQL khusus memungkinkan Bigtable mengoptimalkan performa database Anda untuk perhitungan geospasial dan waktu kueri. Dalam contoh penggunaan dasar, kita menggunakan tabel tourist_points_of_interest dengan satu family kolom bernama poi_data.

poi_data
kunci baris nama lokasi
p1 Menara Eiffel POINT(2.2945 48.8584)

Menulis data geografi

Untuk menulis data geospasial ke kolom Bigtable, Anda harus memberikan representasi string entitas geografi dalam format GeoJSON atau Well-Known Text (WKT). Bigtable menyimpan data pokok sebagai byte mentah, sehingga Anda dapat memberikan kombinasi entity apa pun dalam satu kolom (seperti Points, LineStrings, Polygons, MultiPoints, MultiLineStrings, atau MultiPolygons). Jenis entity geospasial yang Anda berikan penting pada waktu kueri karena Anda harus menggunakan fungsi geografi yang sesuai untuk menafsirkan data.

Misalnya, untuk menambahkan beberapa tempat wisata ke tabel tourist_points_of_interest, Anda dapat menggunakan alat CLI cbt seperti berikut:

  • Dengan 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)'
  • Dengan 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] ]] }'

Mengkueri data geografi

Anda dapat membuat kueri tabel tourist_points_of_interest dengan GoogleSQL di Bigtable Studio dan memfilter hasilnya dengan fungsi geografi. Misalnya, untuk menemukan semua objek wisata dalam jarak 1.500 meter dari Trocadéro, Anda dapat menggunakan fungsi 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);

Hasilnya akan mirip dengan berikut ini:

Screenshot yang menampilkan tabel tourist_points_of_interest di Bigtable Studio
    dengan hasil kueri yang difilter menggunakan fungsi geografi ST_DWITHIN.

Kolom location menampilkan representasi tekstual asli dari data yang diberikan dalam operasi penulisan, tetapi Bigtable menyimpannya sebagai byte. Dalam contoh ini, meskipun Place Charles de Gaulle ditentukan sebagai poligon yang membentang lebih jauh dari batas 1.500 meter, poligon tersebut disertakan dalam hasil kueri. Hal itu karena fungsi ST_DWITHIN menampilkan nilai benar jika titik mana pun yang ada dalam entitas geospasial berada dalam jarak yang diharapkan.

Penggunaan lanjutan

Bagian berikut menjelaskan cara menggunakan fungsi geografi untuk skenario kompleks seperti analisis geofencing. Bagian Pengoptimalan kueri menjelaskan cara menggunakan tampilan terwujud berkelanjutan untuk meningkatkan performa.

Untuk menggambarkan penggunaan lanjutan fungsi geografi di Bigtable, bayangkan skenario saat Anda adalah peneliti yang memantau perilaku penguin kaisar yang tinggal di sisi selatan Pulau Snow Hill.

Peralatan Anda memberikan ratusan ping lokasi untuk setiap penguin, jadi Anda membuat tabel penguin_movements untuk menyimpannya:

penguin_details
kunci baris penguin_id timestamp lokasi
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)

Contoh: Menemukan siapa yang melintasi batas wilayah

Selama penelitian, Anda mengamati perilaku penguin di sekitar tempat makan tertentu. Area yang ditentukan dengan batas virtual biasanya disebut sebagai geofence. Area pemberian makan dapat dianggap sebagai area yang dibatasi secara geografis. Anda ingin mengetahui apakah ada penguin yang mengunjungi tempat pemberian makan pada 3 Desember 2025. Untuk menjawab pertanyaan, Anda menggunakan fungsi ST_CONTAINS, seperti berikut:

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

Anda akan mendapatkan hasil yang mirip dengan berikut ini:

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

Contoh: Mengubah titik menjadi rute

Untuk memvisualisasikan perjalanan penguin dengan lebih baik, Anda memutuskan untuk mengatur setiap ping lokasi ke dalam LineStrings menggunakan fungsi ST_MAKELINE seperti berikut:

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

Dengan kueri ini, Anda bisa mendapatkan insight yang lebih canggih untuk riset Anda. Luaskan bagian berikut untuk melihat contoh kueri dan hasil.

Berapa jarak tempuh harian untuk setiap penguin?

Untuk menghitung jarak yang ditempuh setiap penguin setiap hari, Anda dapat menggunakan fungsi 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;

Kueri ini menampilkan hasil yang mirip dengan berikut ini:

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

Berapa banyak rute harian yang berada dalam area ber-geofence?

Untuk menjawab pertanyaan ini, Anda memfilter hasil dengan fungsi ST_INTERSECTION untuk memeriksa bagian rute penguin yang berpotongan dengan area ber-geofence. Kemudian, fungsi ST_LENGTH dapat membantu menghitung rasio pergerakan di dalam area mencari makan dengan jarak penuh yang ditempuh pada hari itu.

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

Kueri ini menampilkan hasil yang mirip dengan berikut ini:

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

Mengoptimalkan kueri dengan tampilan terwujud berkelanjutan

Kueri geospasial pada set data besar dapat berjalan lambat karena mungkin memerlukan pemindaian banyak baris. Bigtable tidak mendukung indeks geospasial khusus, tetapi Anda dapat meningkatkan performa kueri dengan tampilan terwujud berkelanjutan.

Untuk membuat kunci baris unik untuk tampilan tersebut, Anda dapat mengubah jenis GEOGRAPHY menjadi sel S2. S2 adalah library yang memungkinkan Anda melakukan trigonometri kompleks untuk geometri bola. Menghitung jarak berdasarkan koordinat geografis dapat memerlukan banyak komputasi. Sel S2 dapat merepresentasikan area tertentu di permukaan Bumi sebagai bilangan bulat 64-bit, sehingga ideal untuk pengindeksan geospasial dengan tampilan terwujud berkelanjutan.

Untuk menentukan sel S2, Anda memanggil fungsi S2_CELLIDFROMPOINT(location, level) dan memberikan argumen perincian level. Argumen ini adalah angka antara 0 dan 30 dan menentukan ukuran setiap sel: makin tinggi angkanya, makin kecil selnya.

Dalam contoh riset penguin, Anda dapat membuat tampilan terwujud berkelanjutan yang diindeks berdasarkan lokasi dan stempel waktu setiap penguin:

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

Mengirim kueri tampilan ini untuk menjawab pertanyaan Siapa yang melintasi geofence? menjadi jauh lebih efisien dengan pendekatan dua fase. Anda terlebih dahulu menggunakan indeks cell_id dalam tampilan materialis berkelanjutan untuk menemukan baris kandidat dengan cepat di sekitar lokasi umum, lalu menerapkan fungsi ST_CONTAINS yang akurat pada set data yang diperkecil:

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

Batasan

Bigtable tidak mendukung fitur berikut saat bekerja dengan data geografi:

  • Geometri tiga dimensi. Geometri ini mencakup akhiran "Z" dalam format WKT, dan koordinat ketinggian dalam format GeoJSON.
  • Sistem referensi linear. Opsi ini mencakup akhiran "M" dalam format WKT.
  • Objek geometri WKT selain primitif geometri atau geometri multibagian. Secara khusus, Bigtable hanya mendukung Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, dan GeometryCollection.
  • Fungsi ST_CLUSTERDBSCAN tidak didukung.

Untuk batasan khusus format input GeoJson dan WKT, lihat ST_GEOGFROMGEOJSON dan ST_GEOGFROMTEXT.

Apa langkah selanjutnya?