Toko buku kita kini punya cabang fisik di beberapa kota, dan pertanyaan pembaca berbunyi: “tampilkan cabang terdekat dari posisi saya”. Query harga atau kategori tidak bisa menjawabnya — yang dibutuhkan adalah pencarian berbasis koordinat. Geospatial index menjawab kebutuhan itu: index khusus yang menyimpan titik lokasi sehingga MongoDB bisa mencari document “dekat dengan” atau “di dalam area” tertentu secara efisien. Chapter ini fokus pada penyiapan indexnya; operator query lokasi dibahas tuntas di Section Search dan Geospatial.

Peta materi Chapter ini:

  • Menyimpan lokasi sebagai GeoJSON Point dan membuat index 2dsphere.
  • Validasi rentang longitude/latitude yang dijaga MongoDB.
  • Perbedaan index 2d (koordinat lama) dan 2dsphere (GeoJSON).
  • Pratinjau query $near dan $geoWithin.

Menyimpan Lokasi sebagai GeoJSON Point #

Format penyimpanan yang direkomendasikan adalah GeoJSON — object dengan field type dan coordinates. Untuk satu titik, type bernilai "Point" dan coordinates berisi array [longitude, latitude]. Mari simpan empat cabang toko buku:

js
use latihan_index
db.cabang.drop()
db.cabang.insertMany([
  { nama: "Toko Buku Mentari", kota: "Jakarta", lokasi: { type: "Point", coordinates: [106.8456, -6.2088] } },
  { nama: "Toko Buku Saguling", kota: "Bandung", lokasi: { type: "Point", coordinates: [107.6098, -6.9147] } },
  { nama: "Toko Buku Tunas", kota: "Surabaya", lokasi: { type: "Point", coordinates: [112.7508, -7.2575] } },
  { nama: "Toko Buku Gadjah", kota: "Yogyakarta", lokasi: { type: "Point", coordinates: [110.3791, -7.7956] } }
])
db.cabang.createIndex({ lokasi: "2dsphere" })

Output yang Dihasilkan:

text
switched to db latihan_index
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3bc1f8a64345aaf597b6b'),
    '1': ObjectId('6ad3bc1f8a64345aaf597b6c'),
    '2': ObjectId('6ad3bc1f8a64345aaf597b6d'),
    '3': ObjectId('6ad3bc1f8a64345aaf597b6e')
  }
}
lokasi_2dsphere

Dua hal pada blok tersebut perlu digarisbawahi. Pertama, createIndex({ lokasi: "2dsphere" }) memakai string "2dsphere" sebagai “arah” — bukan 1 atau -1 — karena tipe index ini bukan B-tree biasa melainkan struktur berbasis grid bola bumi. Kedua, urutan koordinatnya longitude dulu, latitude kemudian, mengikuti konvensi GeoJSON internasional.

Validasi Rentang Koordinat #

Index 2dsphere aktif menjaga mutu data: setiap insert dan update pada field ter-index divalidasi terhadap rentang sah bumi — longitude harus berada di [-180, 180] dan latitude di [-90, 90]. Coba sisipkan titik dengan longitude 200:

js
try {
  db.cabang.insertOne({ nama: "Cabang Uji", kota: "Fiktif", lokasi: { type: "Point", coordinates: [200, -6.2] } })
} catch (e) {
  print("Kode galat: " + e.code)
  print("Pesan: " + e.message)
}

Output yang Dihasilkan:

text
Kode galat: 510
Pesan: Could not extract geo keys at path 'lokasi' for document with _id ObjectId('6ad3bc1f8a64345aaf597b6f'): Longitude/latitude is out of bounds, lng: 200 lat: -6.2

Galat berkode 510 itu muncul pada saat insert — bukan saat query — sehingga data rusak tertahan di pintu masuk. Perlu dicatat: validasi rentang hanya menolak angka di luar batas; koordinat yang sah namun salah tempat (misalnya Jakarta yang tertukar dengan Samudra Hindia) tetap lolos. Pembersihan data lama sebelum migrasi tetap tanggung jawab aplikasi.

2d atau 2dsphere #

MongoDB menyediakan dua tipe index geospatial. Index 2d bekerja pada pasangan koordinat polos ([x, y] langsung di field, tanpa pembungkus GeoJSON) dan menghitung jarak pada bidang datar. Index 2dsphere bekerja pada GeoJSON dan menghitung jarak di permukaan bola bumi:

js
db.petaLama.drop()
db.petaLama.insertMany([
  { nama: "Rak Fiksi", pos: [106.84, -6.20] },
  { nama: "Rak Puisi", pos: [106.85, -6.21] }
])
db.petaLama.createIndex({ pos: "2d" })

Output yang Dihasilkan:

text
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3bc1f8a64345aaf597b70'),
    '1': ObjectId('6ad3bc1f8a64345aaf597b71')
  }
}
pos_2d
Aspek2d2dsphere
Bentuk dataArray [x, y] polosGeoJSON (Point, LineString, Polygon)
Perhitungan jarakBidang datar (Euclidean)Permukaan bola bumi
Rentang koordinatWajib [-180, 180] × [-90, 90]Sama, plus validasi struktur GeoJSON
Operator $nearMendukung (jarak dalam satuan data)Mendukung ($maxDistance dalam meter)
Cocok untukData lama, peta ruangan/rakLokasi di bumi — pilihan default

Untuk aplikasi lokasi nyata, gunakan 2dsphere — jaraknya dihitung dalam meter yang akurat mengikuti kelengkungan bumi. Index 2d umumnya dijumpai pada sistem lama atau pemetaan ruangan seperti posisi rak di dalam toko pada blok di atas.

Pratinjau Query $near dan $geoWithin #

Index yang baru dibuat langsung siap melayani query lokasi. Operator $near mengembalikan document terurut dari yang terdekat, dan $maxDistance membatasinya dalam satuan meter:

js
db.cabang.find(
  { lokasi: { $near: { $geometry: { type: "Point", coordinates: [106.8456, -6.2088] }, $maxDistance: 200000 } } },
  { _id: 0, nama: 1, kota: 1 }
)

Output yang Dihasilkan:

text
[
  { nama: 'Toko Buku Mentari', kota: 'Jakarta' },
  { nama: 'Toko Buku Saguling', kota: 'Bandung' }
]

Hasilnya terurut dari terdekat: Mentari Jakarta, lalu Saguling Bandung — keduanya dalam radius 200 km dari titik pencarian. Operator kedua, $geoWithin, memeriksa keanggotaan di dalam area tanpa pengurutan hasil:

js
db.cabang.find(
  { lokasi: { $geoWithin: { $centerSphere: [[106.8456, -6.2088], 0.05] } } },
  { _id: 0, nama: 1, kota: 1 }
)

Output yang Dihasilkan:

text
[
  { nama: 'Toko Buku Saguling', kota: 'Bandung' },
  { nama: 'Toko Buku Mentari', kota: 'Jakarta' }
]

Perhatikan urutan hasilnya berbeda dan tidak berbasis jarak — $geoWithin sekadar memeriksa masuk atau tidaknya titik ke dalam lingkaran beradius 0.05 radian (sekitar 318 km) dari Jakarta. Satu fakta penting: query $near mensyaratkan index geospatial — tanpa index, MongoDB menolak query tersebut dengan galat unable to find index for $geoNear query.

Rangkuman #

  • Simpan lokasi sebagai GeoJSON Point dengan urutan [longitude, latitude], lalu buat index dengan createIndex({ lokasi: "2dsphere" }).
  • Index 2dsphere memvalidasi rentang koordinat saat insert/update; pelanggaran ditolak dengan galat kode 510.
  • Index 2d untuk koordinat polos dan jarak bidang datar; 2dsphere untuk GeoJSON dan jarak permukaan bumi dalam meter.
  • $near mengembalikan hasil terurut jarak dan wajib didukung index; $geoWithin memeriksa keanggotaan area tanpa pengurutan.