Query $geoWithin di Chapter Data Geospatial berjalan tanpa index — tetapi dengan pemindaian collection penuh, dan query “terdekat” bahkan menolak jalan sama sekali. Dua index geospatial MongoDB menuntaskan keduanya: 2d untuk pasangan koordinat legacy di bidang datar, dan 2dsphere untuk data GeoJSON di permukaan bola bumi. Chapter ini membuat keduanya, memverifikasi strukturnya lewat getIndexes(), dan menutup dengan pola compound index untuk query lokasi yang selalu disertai filter lain.
Peta materi Chapter ini:
- Membuat index
2dspheredan2d, lalu membaca strukturnya. - Perbedaan keduanya: model perhitungan, bentuk data, dan batas koordinat.
- Compound index geospatial untuk filter
jenis+ lokasi.
Membuat Index 2dsphere dan 2d #
Kita bangun ulang data toko lengkap dengan index-nya. Pembuatannya sama seperti index biasa — yang berbeda hanya nilai key: string "2dsphere" atau "2d" menggantikan angka 1/-1:
use latihan_geo
db.toko.drop()
db.toko_lama.drop()
db.toko.insertMany([
{ nama: "Toko Menteng", kota: "Jakarta", lokasi: { type: "Point", coordinates: [106.83, -6.2] } },
{ nama: "Toko Dago", kota: "Bandung", lokasi: { type: "Point", coordinates: [107.62, -6.9] } },
{ nama: "Toko Gajah Mada", kota: "Semarang", lokasi: { type: "Point", coordinates: [110.42, -6.97] } },
{ nama: "Toko Malioboro", kota: "Yogyakarta", lokasi: { type: "Point", coordinates: [110.37, -7.8] } },
{ nama: "Toko Tunjungan", kota: "Surabaya", lokasi: { type: "Point", coordinates: [112.73, -7.25] } },
{ nama: "Toko Pusat Medan", kota: "Medan", lokasi: { type: "Point", coordinates: [98.67, 3.59] } },
{ nama: "Toko Losari", kota: "Makassar", lokasi: { type: "Point", coordinates: [119.42, -5.13] } },
{ nama: "Toko Denpasar", kota: "Denpasar", lokasi: { type: "Point", coordinates: [115.22, -8.65] } }
])
db.toko_lama.insertMany([
{ nama: "Kios Pasar Baru", kota: "Jakarta", posisi: [106.83, -6.16] },
{ nama: "Kios Cikini", kota: "Jakarta", posisi: [106.84, -6.18] },
{ nama: "Kios Dago", kota: "Bandung", posisi: [107.62, -6.9] }
])
db.toko.createIndex({ lokasi: "2dsphere" })
db.toko_lama.createIndex({ posisi: "2d" })
db.toko.getIndexes()
db.toko_lama.getIndexes()Output yang Dihasilkan:
switched to db latihan_geo
true
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3e22cc65ea689c77a15ba'),
'1': ObjectId('6ad3e22cc65ea689c77a15bb'),
'2': ObjectId('6ad3e22cc65ea689c77a15bc'),
'3': ObjectId('6ad3e22cc65ea689c77a15bd'),
'4': ObjectId('6ad3e22cc65ea689c77a15be'),
'5': ObjectId('6ad3e22cc65ea689c77a15bf'),
'6': ObjectId('6ad3e22cc65ea689c77a15c0'),
'7': ObjectId('6ad3e22cc65ea689c77a15c1')
}
}
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3e22cc65ea689c77a15c2'),
'1': ObjectId('6ad3e22cc65ea689c77a15c3'),
'2': ObjectId('6ad3e22cc65ea689c77a15c4')
}
}
lokasi_2dsphere
posisi_2d
[
{
v: 2,
key: { _id: 1 },
name: '_id_',
collation: { locale: 'simple' }
},
{
v: 2,
key: { lokasi: '2dsphere' },
name: 'lokasi_2dsphere',
'2dsphereIndexVersion': 4,
collation: { locale: 'simple' }
}
]
[
{
v: 2,
key: { _id: 1 },
name: '_id_',
collation: { locale: 'simple' }
},
{
v: 2,
key: { posisi: '2d' },
name: 'posisi_2d',
collation: { locale: 'simple' }
}
]Dua keluaran getIndexes() di atas — yang pertama milik toko, yang kedua milik toko_lama — masing-masing memuat index _id_ bawaan plus index geospatial kita. Tiga field pantas dicermati:
key: nilai'2dsphere'dan'2d'menggantikan arah1/-1index biasa — inilah penanda index geospatial.'2dsphereIndexVersion': 4: versi internal index2dsphere; letaknya hanya di entri miliktoko.nameotomatis:lokasi_2dspheredanposisi_2d, mengikuti pola<field>_<tipe>seperti index lain.
Dengan index berdiri, query $near yang tadinya mustahil kini bisa jalan, dan $geoWithin tidak lagi memindai seluruh collection.
Dua Index, Dua Dunia Perhitungan #
Perbedaan keduanya bukan sekadar nama — dokumentasi resminya ringkas: index 2d melakukan perhitungan pada bidang datar Euclidean, sedangkan 2dsphere menghitung di permukaan bola untuk data GeoJSON. Konsekuensi praktisnya:
| Aspek | 2d | 2dsphere |
|---|---|---|
| Bentuk data | pasangan [lng, lat] | GeoJSON dan pasangan legacy |
| Model hitung | bidang datar (Euclidean) | permukaan bola bumi |
$near, jarak | satuan koordinat | meter (GeoJSON) |
| Rentang bawaan | -180 sampai 180 | validasi GeoJSON penuh |
| Kecocokan | data warisan, peta datar | lokasi dunia nyata |
Batas rentang 2d bisa kita buktikan langsung — index ini menolak koordinat di luar -180..180 saat insert:
try {
db.toko_lama.insertOne({ nama: "Kios Aneh", posisi: [999, 10] })
} catch (e) {
print("Galat: " + e.code + " :: " + e.message)
}Output yang Dihasilkan:
Galat: 13027 :: point not in interval of [ -180, 180 ] :: caused by :: { _id: ObjectId('6ad3e22cc65ea689c77a15c5'), nama: "Kios Aneh", posisi: [ 999, 10 ] }Error 13027 menyebut interval yang diperiksa secara eksplisit: point not in interval of [ -180, 180 ], lengkap dengan document yang menyebabkannya. Perhatikan bahwa batas ini berlaku seragam untuk kedua sumbu — tidak seperti GeoJSON yang membedakan rentang longitude (-180..180) dan latitude (-90..90). Rentang itu memang bawaan 2d; opsi min/max pada createIndex bisa mempersempitnya untuk data yang berada di peta kecil, misalnya denah satu gedung.
Compound Index Geospatial #
Query lokasi di aplikasi nyata hampir selalu disertai filter biasa: “toko (bukan kios) dalam radius 130 km”. Field equality bisa digabungkan langsung ke satu index bersama field geospatial — urutan penulisan mengikuti pola compound index biasa:
db.mitra.drop()
db.mitra.insertMany([
{ nama: "Toko Menteng", jenis: "toko", lokasi: { type: "Point", coordinates: [106.83, -6.2] } },
{ nama: "Toko Dago", jenis: "toko", lokasi: { type: "Point", coordinates: [107.62, -6.9] } },
{ nama: "Kios Cikini", jenis: "kios", lokasi: { type: "Point", coordinates: [106.84, -6.18] } },
{ nama: "Kios Dago Atas", jenis: "kios", lokasi: { type: "Point", coordinates: [107.61, -6.88] } }
])
db.mitra.createIndex({ jenis: 1, lokasi: "2dsphere" })
db.mitra.find({ jenis: "toko", lokasi: { $geoWithin: { $centerSphere: [[106.83, -6.2], 130 / 6371] } } }, { _id: 0, nama: 1, jenis: 1 })
db.mitra.find({ jenis: "toko", lokasi: { $near: { $geometry: { type: "Point", coordinates: [106.83, -6.2] }, $maxDistance: 200000 } } }, { _id: 0, nama: 1, jenis: 1 })Output yang Dihasilkan:
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3e22cc65ea689c77a15c6'),
'1': ObjectId('6ad3e22cc65ea689c77a15c7'),
'2': ObjectId('6ad3e22cc65ea689c77a15c8'),
'3': ObjectId('6ad3e22cc65ea689c77a15c9')
}
}
jenis_1_lokasi_2dsphere
[
{ nama: 'Toko Dago', jenis: 'toko' },
{ nama: 'Toko Menteng', jenis: 'toko' }
]
[
{ nama: 'Toko Menteng', jenis: 'toko' },
{ nama: 'Toko Dago', jenis: 'toko' }
]{ jenis: 1, lokasi: "2dsphere" }: field equality di depan, field geospatial di belakang — satu index melayani kombinasi filterjenis+ query lokasi.$geoWithin+jenis:Toko Menteng(jarak 0) danToko Dago(±117 km) lolos radius 130 km; dua kios yang berada di dekat keduanya tersaring olehjenis. Urutan hasilnya mengikuti pemindaian index —$geoWithintidak mengurutkan.$near+jenis: dua toko yang sama, tetapi kali ini terurut dari terdekat —Toko Menteng(0 km) mendahuluiToko Dago(±117 km). Kontras urutan dua query inilah bukti bahwa hanya$nearyang mengurutkan hasil berdasarkan jarak.
Pola ini menjadi penting saat satu collection memuat banyak jenis entitas lokasi. Tanpa compound index, server harus memilih: memakai index geo lalu menyaring jenis satu per satu, atau memakai index jenis lalu menghitung jarak tanpa index. Dengan compound index, keduanya terjadi sekaligus dalam satu tahap.
Rangkuman #
- Buat index geospatial dengan
createIndex({ field: "2dsphere" })ataucreateIndex({ field: "2d" }); verifikasi lewatgetIndexes()pada fieldkey. 2d= pasangan legacy, bidang datar, rentang bawaan -180..180 kedua sumbu; error 13027 menolak koordinat di luar rentang.2dsphere= GeoJSON (dan pasangan legacy), perhitungan bola bumi, jarak dalam meter untuk GeoJSON.- Compound index
{ equality: 1, lokasi: "2dsphere" }melayani filter jenis + query lokasi dalam satu index.
Index sudah berdiri. Chapter berikutnya menggunakannya untuk pertanyaan paling sering diajukan aplikasi lokasi: “mana toko terdekat?”