Jaringan toko buku kita akhirnya butuh peta: pembaca ingin tahu toko terdekat, kurir ingin tahu toko yang melayani alamatnya, dan manajemen ingin melihat cakupan kota. Sebelum bicara query dan index, ada satu keputusan yang harus diambil lebih dulu dan menentukan segalanya: bagaimana menyimpan koordinat di dalam document. MongoDB menerima dua bentuk — legacy coordinate pairs dan GeoJSON — dan keduanya tidak bisa dicampur seenaknya.
Peta materi Chapter ini:
- Dua bentuk data lokasi beserta struktur document-nya.
- Aturan penulisan koordinat: longitude lebih dulu dan rentang nilainya.
- Panduan memilih antara pasangan koordinat legacy dan GeoJSON.
Dua Bentuk Data Lokasi #
Bentuk legacy menyimpan koordinat sebagai array dua angka langsung di field. Bentuk GeoJSON membungkusnya dalam object bertanda type. Kita isi latihan_geo dengan keduanya — collection toko untuk jaringan utama (GeoJSON) dan toko_lama untuk data kios warisan (legacy):
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.find({ kota: "Jakarta" }, { _id: 0, nama: 1, kota: 1, lokasi: 1 })
db.toko_lama.find({ kota: "Bandung" }, { _id: 0, nama: 1, kota: 1, posisi: 1 })Output yang Dihasilkan:
switched to db latihan_geo
true
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3e1385bc318e9b86ef7cd'),
'1': ObjectId('6ad3e1385bc318e9b86ef7ce'),
'2': ObjectId('6ad3e1385bc318e9b86ef7cf'),
'3': ObjectId('6ad3e1385bc318e9b86ef7d0'),
'4': ObjectId('6ad3e1385bc318e9b86ef7d1'),
'5': ObjectId('6ad3e1385bc318e9b86ef7d2'),
'6': ObjectId('6ad3e1385bc318e9b86ef7d3'),
'7': ObjectId('6ad3e1385bc318e9b86ef7d4')
}
}
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3e1385bc318e9b86ef7d5'),
'1': ObjectId('6ad3e1385bc318e9b86ef7d6'),
'2': ObjectId('6ad3e1385bc318e9b86ef7d7')
}
}
[
{
nama: 'Toko Menteng',
kota: 'Jakarta',
lokasi: { type: 'Point', coordinates: [ 106.83, -6.2 ] }
}
]
[ { nama: 'Kios Dago', kota: 'Bandung', posisi: [ 107.62, -6.9 ] } ]Perbedaan strukturnya tampak jelas pada dua hasil terakhir:
- GeoJSON (
toko.lokasi): object dengan fieldtype: "Point"dancoordinatesberisi array[longitude, latitude]. Labeltypeinilah yang memberi tahu MongoDB bentuk geometri apa yang disimpan — titik, garis, atau area. - Legacy (
toko_lama.posisi): array[x, y]telanjang tanpa pembungkus. MongoDB mengartikannya sebagai titik pada bidang datar. - Angka koordinat sama persis:
[107.62, -6.9]muncul di kedua bentuk — yang berbeda hanya bungkusnya, bukan isinya.
Longitude Selalu di Depan #
MongoDB mengikuti konvensi GeoJSON: longitude ditulis lebih dulu, baru latitude — [106.83, -6.2] berarti Jakarta, bukan sebaliknya. Urutan ini bertentangan dengan kebiasaan menulis koordinat sehari-hari (lintang dulu), dan itulah sumber bug geospatial paling umum. Rentang nilainya pun asimetris:
| Sumbu | Rentang valid | Contoh Indonesia |
|---|---|---|
| Longitude (x, ditulis dulu) | -180 sampai 180 | 95 (Banda Aceh) sampai 141 (Papua) |
| Latitude (y, ditulis kedua) | -90 sampai 90 | 6 (Medan) sampai -11 (Kupang) |
Kapan Legacy, Kapan GeoJSON #
Kedua bentuk itu dilayani dua index yang berbeda, dan pilihannya menentukan jenis hitungan yang bisa dilakukan:
| Aspek | Legacy pairs | GeoJSON |
|---|---|---|
| Index pendamping | 2d | 2dsphere |
| Model perhitungan | bidang datar (Euclidean) | permukaan bola bumi |
| Bentuk geometri | titik saja | Point, LineString, Polygon, MultiPolygon |
Satuan jarak $near | radian / satuan koordinat | meter |
| Kecocokan | data warisan, peta datar, game 2D | aplikasi lokasi dunia nyata |
Untuk jaringan toko buku yang menghitung jarak nyata antar kota, pilihan yang tepat adalah GeoJSON dengan 2dsphere — jarak Jakarta–Bandung harus mengikuti kelengkungan bumi, bukan garis lurus di kertas. Bentuk legacy kita pertahankan di toko_lama justru untuk dipakai membandingkan perilaku keduanya di chapter-chapter berikutnya. GeoJSON juga satu-satunya jalan bila nanti kita butuh lebih dari titik: rute pengiriman (LineString) dan area cakupan toko (Polygon) — materi Chapter GeoJSON.
Query Pertama, Tanpa Index #
Kedua collection di atas belum punya index geospatial apa pun — dan ternyata query keanggotaan area tetap jalan. Operator $geoWithin menerima titik pusat dan radius tanpa mensyaratkan index:
db.toko.find({ lokasi: { $geoWithin: { $centerSphere: [[106.83, -6.2], 300 / 6371] } } }, { _id: 0, nama: 1, kota: 1 })Output yang Dihasilkan:
[
{ nama: 'Toko Menteng', kota: 'Jakarta' },
{ nama: 'Toko Dago', kota: 'Bandung' }
]Angka 300 / 6371 adalah radius 300 km dikonversi ke radian — jari-jari bumi ±6.371 km. Hasilnya masuk akal: hanya Bandung (±117 km) yang berada dalam 300 km dari Jakarta. Yang perlu dicatat adalah mekanismenya: tanpa index, server memindai seluruh collection dan menghitung satu per satu — cukup untuk delapan toko, tidak untuk delapan ribu. Dan tidak semua operator sebebas ini: query “terdekat” memakai $near yang memang mensyaratkan index geospatial, sebagaimana akan kita buktikan di Chapter Geospatial Indexes.
Rangkuman #
- Dua bentuk data lokasi: legacy pairs
[lng, lat]telanjang dan GeoJSON{type, coordinates}. - Longitude selalu ditulis lebih dulu; rentang valid -180..180 untuk longitude, -90..90 untuk latitude.
- Legacy pairs dipasangkan dengan index
2ddan hitungan bidang datar; GeoJSON dengan2dspheredan hitungan bola bumi — pilihan GeoJSON untuk jarak nyata. $geoWithinbisa berjalan tanpa index (dengan pemindaian penuh);$neartidak.
Struktur GeoJSON menyimpan lebih dari titik. Chapter berikutnya membedah tipe-tipe geometrinya sampai ke aturan polygon dan validasi otomatisnya.