Dari formulir manual sampai halaman web, seluruh sumber yang kita bahas pada section ini menyerahkan data dalam bentuk yang sama: tabel berisi baris dan kolom. Padahal sebagian informasi justru menempel pada lokasi di permukaan bumi — posisi kedai kopi, batas wilayah kecamatan, jalur jalan dan sungai. Data yang membawa informasi lokasi disebut data geospasial, dan pengambilannya memiliki aturan main tersendiri. Pada chapter ini kita berkenalan dengan bentuk-bentuk data geospasial, memahami sistem koordinat yang membuat angka koordinat bermakna, lalu mempraktikkan membaca dan mengolah berkas GeoJSON dengan library geopandas.

Vektor dan Raster: Dua Bentuk Data Geospasial #

Data geospasial umumnya tersaji dalam dua bentuk besar. Bentuk vektor menyimpan dunia sebagai geometri: titik, garis, dan area. Bentuk raster menyimpan dunia sebagai grid sel berpinggir persegi yang tiap selnya menyimpan satu nilai — seperti citra satelit atau peta tutupan lahan. Chapter ini berfokus pada vektor karena bentuk itulah yang paling sering kita ambil dan olah sebagai data proyek analisis; raster cukup dikenali supaya istilahnya tidak asing ketika muncul di dokumentasi.

Tiga bangun vektor tersebut punya peran yang berbeda:

BentukBangun geometrinyaContoh dataPertanyaan yang bisa dijawab
Titik (point)satu pasang koordinatlokasi kedai kopi, lokasi sensor cuacakedai mana yang paling dekat dari kantor?
Garis (line)rangkaian titik yang tersambungjalan raya, sungai, jalur angkutanberapa jauh rute pengiriman lewat jalan tol?
Area (polygon)garis yang menutup kembali ke titik awalbatas kecamatan, danau, kawasan kotaberapa luas wilayah dan berapa penduduk di dalamnya?

Satu berkas vektor boleh memuat ketiga bangun sekaligus, tetapi satu fitur hanya memiliki satu geometri. Kolom atribut pada tiap fitur berperan persis seperti kolom pada tabel biasa — itulah sebabnya pengolahan data geospasial di Python terasa familier bagi kita yang sudah terbiasa dengan pandas.

Sistem Koordinat dan CRS #

Sepasang angka seperti 107.61, -6.90 belum berarti apa-apa sebelum kita sepakat bagaimana angka tersebut dipetakan ke lokasi di permukaan bumi. Kesepakatan itulah yang disebut CRS (coordinate reference system): aturan yang menentukan titik nol, arah sumbu, bentuk bumi yang dipakai sebagai acuan, dan satuan koordinat. Dua CRS yang akan kita jumpai terus-menerus:

CRSNama umumSatuanKegunaan umum
EPSG:4326WGS 84derajattukar-menukar data, GPS, GeoJSON
EPSG:32748UTM zone 48Smeterhitung luas dan jarak di Indonesia bagian barat

EPSG:4326 adalah CRS geografis dunia: posisi dinyatakan sebagai bujur (longitude, garis vertikal dari kutub ke kutub) dan lintang (latitude, garis sejajar khatulistiwa) dalam derajat. Sistem inilah yang dipakai GPS dan diwajibkan spesifikasi GeoJSON. Satuan derajat praktis untuk menunjukkan lokasi, tetapi bukan satuan panjang yang seragam — konsekuensinya kita bahas pada bagian menghitung luas. Untuk pengukuran, dipakai CRS proyeksi seperti UTM yang meratakan wilayah tertentu ke bidang bersatuan meter.

Struktur Berkas GeoJSON #

GeoJSON adalah penerapan format JSON untuk data peta — kita cukup mengulang pengetahuan JSON dari chapter Membaca File JSON . Strukturnya berkisar pada tiga gagasan: FeatureCollection sebagai wadah paling luar, daftar features di dalamnya, dan setiap feature yang terdiri atas properties (atribut) serta geometry (bentuk). Berikut satu fitur titik ilustratif:

json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "nama": "Toko Kopi Kita",
        "kategori": "kedai kopi"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [107.61, -6.9]
      }
    }
  ]
}

Mari kita bongkar strukturnya:

  1. "type": "FeatureCollection" — menandai seluruh berkas sebagai kumpulan fitur; kunci features menampung daftar fiturnya.
  2. properties — atribut bebas milik fitur, semacam satu baris tabel: nama dan kategori di contoh ini. Kolom apa saja boleh kita definisikan sendiri.
  3. geometry.type — bentuk geometrinya: "Point", "LineString", atau "Polygon". Satu fitur hanya punya satu geometri.
  4. geometry.coordinates — angka koordinatnya. Titik memuat satu pasang; garis dan area memuat daftar pasangan; area menutup dengan mengulang titik pertama di akhir daftar.

Berkas latihan pada chapter ini, kecamatan.geojson, memuat empat kecamatan dengan bentuk poligon sederhana dan dua properti: nama serta penduduk:

json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "nama": "Sukajadi",
        "penduduk": 108300
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [107.6, -6.91],
            [107.62, -6.91],
            [107.62, -6.9],
            [107.6, -6.9],
            [107.6, -6.91]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "nama": "Coblong",
        "penduduk": 142500
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [107.62, -6.91],
            [107.65, -6.91],
            [107.65, -6.9],
            [107.62, -6.9],
            [107.62, -6.91]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "nama": "Andir",
        "penduduk": 96800
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [107.6, -6.9],
            [107.62, -6.9],
            [107.62, -6.89],
            [107.6, -6.89],
            [107.6, -6.9]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "nama": "Regol",
        "penduduk": 125400
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [107.62, -6.9],
            [107.65, -6.9],
            [107.65, -6.89],
            [107.62, -6.89],
            [107.62, -6.9]
          ]
        ]
      }
    }
  ]
}

Empat poligon tersebut sengaja dibuat berbentuk persegi panjang dengan koordinat bulat ringan agar mudah dibaca — bukan bentuk batas wilayah yang sesungguhnya. Perhatikan pula bahwa setiap daftar titik poligon menutup kembali: titik pertama 107.6, -6.91 diulang sebagai titik kelima.

Membaca GeoJSON dengan geopandas #

Library geopandas memperluas pandas dengan satu jenis kolom istimewa bernama geometry, sehingga tabel hasil pembacaan — disebut GeoDataFrame — tetap bisa diolah dengan seluruh jurus pandas yang sudah kita kenal. Satu catatan performa: geopandas lebih lambat diimpor daripada pandas karena turut membawa pustaka geometri shapely dan proyeksi pyproj. Latihan pada chapter ini tetap ringan; kita cukup menjaga jumlah blok kode tetap sedikit.

python
import geopandas as gpd

gdf = gpd.read_file("kecamatan.geojson")
print(gdf)
print()
print("Dua baris pertama (gdf.head(2)):")
print(gdf.head(2)[["nama", "penduduk"]])
print()
print("Kolom         :", list(gdf.columns))
print("Jenis geometri:", gdf.geom_type.tolist())
print("CRS           :", gdf.crs)

Output yang Dihasilkan:

text
       nama  penduduk                                           geometry
0  Sukajadi    108300  POLYGON ((107.6 -6.91, 107.62 -6.91, 107.62 -6...
1   Coblong    142500  POLYGON ((107.62 -6.91, 107.65 -6.91, 107.65 -...
2     Andir     96800  POLYGON ((107.6 -6.9, 107.62 -6.9, 107.62 -6.8...
3     Regol    125400  POLYGON ((107.62 -6.9, 107.65 -6.9, 107.65 -6....

Dua baris pertama (gdf.head(2)):
       nama  penduduk
0  Sukajadi    108300
1   Coblong    142500

Kolom         : ['nama', 'penduduk', 'geometry']
Jenis geometri: ['Polygon', 'Polygon', 'Polygon', 'Polygon']
CRS           : EPSG:4326

Perhatikan, gdf.crs tercetak EPSG:4326 — geopandas mengetahuinya karena spesifikasi GeoJSON memang mewajibkan WGS 84. Mari kita bedah baris-barisnya:

  1. Baris 3 (gpd.read_file(...)): berkas dibaca menjadi GeoDataFrame; satu fitur menjadi satu baris, dan properti nama/penduduk menjadi kolom biasa.
  2. Baris 4 (print(gdf)): tampilannya seperti DataFrame pada umumnya, ditambah kolom geometry berisi ringkasan POLYGON (...) yang terpotong agar muat di layar.
  3. Baris 7 (gdf.head(2)): metode head(), tail(), dan penapisan baris bekerja persis seperti pada pandas karena GeoDataFrame adalah turunan DataFrame.
  4. Baris 11 (gdf.geom_type): jenis geometri tiap baris — keempatnya Polygon, sesuai isi berkas.
  5. Baris 12 (gdf.crs): CRS yang melekat pada data. Selalu periksa nilai ini sebelum menghitung apa pun yang bersifat pengukuran.

Luas dalam Derajat: Jebakan CRS Geografis #

Pertanyaan wajar berikutnya: berapa luas tiap kecamatan? Kita coba hitung dengan gdf.geometry.area pada data mentahnya, lalu bandingkan dengan hasil setelah diproyeksikan ke CRS bersatuan meter.

python
import geopandas as gpd

gdf = gpd.read_file("kecamatan.geojson")

# Pada EPSG:4326 satuan koordinat adalah derajat, bukan meter.
gdf["luas_derajat"] = gdf.geometry.area
print("Luas pada CRS geografis (derajat persegi):")
print(gdf[["nama", "luas_derajat"]])
print()

# Proyeksikan ke UTM zone 48S -> satuan meter.
gdf_utm = gdf.to_crs("EPSG:32748")
gdf_utm["luas_km2"] = gdf_utm.geometry.area / 1_000_000
print("Luas pada CRS proyeksi EPSG:32748 (kilometer persegi):")
print(gdf_utm[["nama", "luas_km2"]])

Output yang Dihasilkan:

text
Luas pada CRS geografis (derajat persegi):
       nama  luas_derajat
0  Sukajadi        0.0002
1   Coblong        0.0003
2     Andir        0.0002
3     Regol        0.0003

Luas pada CRS proyeksi EPSG:32748 (kilometer persegi):
       nama  luas_km2
0  Sukajadi  2.447512
1   Coblong  3.671413
2     Andir  2.447562
3     Regol  3.671489

Tabel pertama memuat angka-angka “derajat persegi” yang tidak bermakna sebagai luas, dan geopandas bahkan mengeluarkan UserWarning yang menyarankan proyeksi ulang. Kenapa derajat tidak bisa dipakai? Karena derajat bukan satuan panjang yang seragam: satu derajat lintang memang hampir selalu sekitar 111 km, tetapi satu derajat bujur menyusut dari sekitar 111 km di khatulistiwa sampai nol di kutub. Akibatnya tidak ada satu angka tetap untuk mengonversi derajat persegi menjadi kilometer persegi — buktinya Sukajadi dan Andir sama-sama tercatat 0.0002 derajat persegi, padahal luas sejatinya berbeda tipis (2.4475 berbanding 2.4476 km²).

Solusinya ada pada baris gdf.to_crs("EPSG:32748"): data diproyeksikan ke UTM zone 48S yang cocok untuk Indonesia bagian barat dan bersatuan meter, sehingga geometry.area menghasilkan meter persegi yang kita bagi 1.000.000 menjadi kilometer persegi. Angka kedua inilah yang layak dilaporkan.

Menggabungkan Atribut dengan merge #

Data atribut kerap datang terpisah dari peta: batas kecamatan dari berkas GeoJSON, sementara jumlah kedai kopi per kecamatan dari catatan lapangan. Karena GeoDataFrame adalah turunan DataFrame, penggabungan memakai merge() bekerja persis seperti pada chapter Data Tabular — hasilnya tetap GeoDataFrame yang bisa digambar.

python
import geopandas as gpd
import pandas as pd

gdf = gpd.read_file("kecamatan.geojson")

kedai = pd.DataFrame({
    "nama": ["Sukajadi", "Coblong", "Andir", "Regol"],
    "kedai_kopi": [12, 21, 9, 15],
})

gdf_lengkap = gdf.merge(kedai, on="nama")
gdf_lengkap["kedai_per_10rb"] = (
    gdf_lengkap["kedai_kopi"] / gdf_lengkap["penduduk"] * 10_000
).round(2)
print(gdf_lengkap[["nama", "penduduk", "kedai_kopi", "kedai_per_10rb"]])

Output yang Dihasilkan:

text
       nama  penduduk  kedai_kopi  kedai_per_10rb
0  Sukajadi    108300          12            1.11
1   Coblong    142500          21            1.47
2     Andir     96800           9            0.93
3     Regol    125400          15            1.20

Alurnya tiga langkah: kedai dibangun sebagai DataFrame kecil dengan kolom nama sebagai kunci; merge(on="nama") menempelkan kolom kedai_kopi ke baris kecamatan yang namanya cocok; lalu kolom turunan kedai_per_10rb menghitung kepadatan kedai per 10.000 penduduk. Coblong unggul baik dalam jumlah (21) maupun kepadatan (1.47), sedangkan Andir paling rendah di keduanya (9 dan 0.93).

Menggambar Peta Sederhana #

Peta adalah cara paling jujur untuk memeriksa data geospasial: kesalahan koordinat, urutan bujur-lintang, hingga poligon yang gagal menutup semuanya langsung terlihat. Satu panggilan .plot() sudah cukup, dengan column= untuk mewarnai poligon mengikuti nilai sebuah kolom.

python
import geopandas as gpd
import matplotlib.pyplot as plt

gdf = gpd.read_file("kecamatan.geojson")

kedai = {"Sukajadi": 12, "Coblong": 21, "Andir": 9, "Regol": 15}
gdf["kedai_kopi"] = gdf["nama"].map(kedai)

gdf.plot(column="kedai_kopi", cmap="Greens", legend=True, edgecolor="gray")
plt.title("Jumlah kedai kopi per kecamatan")
plt.xlabel("Bujur (derajat)")
plt.ylabel("Lintang (derajat)")
plt.show()

Gambar yang muncul memperlihatkan empat persegi panjang yang tersusun seperti mozaik dua baris dua kolom: baris atas adalah Andir dan Regol, baris bawah Sukajadi dan Coblong, dan kolom kanan (Coblong, Regol) tampak lebih lebar daripada kolom kiri karena rentang bujurnya memang lebih panjang. Sumbu horizontal menampilkan bujur sekitar 107.60-107.65 dan sumbu vertikal lintang -6.91 sampai -6.89, sesuai koordinat di berkas. Warna hijau makin pekat untuk kecamatan dengan makin banyak kedai: Coblong paling pekat dengan 21 kedai, disusul Regol (15), Sukajadi (12), dan Andir yang paling muda dengan 9 — pola warna yang persis mengikuti kolom kedai_kopi. Peta sederhana seperti ini sudah cukup untuk memeriksa data; penataan yang lebih rapi dibahas pada Section Visualisasi Data .

Rangkuman #

  • Data geospasial vektor terdiri atas titik, garis, dan area; raster menyimpan dunia sebagai grid sel bernilai.
  • CRS menentukan makna angka koordinat; GeoJSON wajib memakai EPSG:4326 (WGS 84) bersatuan derajat dengan urutan [bujur, lintang].
  • geopandas membaca berkas menjadi GeoDataFrame: kolom biasa plus kolom geometry; seluruh operasi pandas tetap berlaku.
  • Luas dan jarak hanya boleh dihitung setelah proyeksi ke CRS meter seperti EPSG:32748 — derajat persegi tidak bermakna sebagai luas.
  • merge() menggabungkan atribut dari tabel lain, dan .plot(column=...) menggambar peta berwarna langsung dari data.

Kita kini memiliki koleksi lengkap cara mengambil data: manual, berkas CSV/Excel/JSON, API, database, web scraping, hingga peta. Namun satu hal masih sering luput dari perhatian: setiap data yang berhasil dikumpulkan itu membawa catatan asal-usul yang wajib dijaga — dari mana, kapan, dengan cara apa, dan apa yang sudah kita lakukan padanya. Chapter penutup section ini membahas kebiasaan kecil yang menyelamatkan seluruh analisis: Pencatatan Asal Data (Provenance).