MongoDB'de konum verisini sorgulamak için konumu bir GeoJSON nesnesi olarak saklar, o alana bir 2dsphere index oluşturur ve $near, $geoWithin, $geoIntersects operatörleri ya da $geoNear aggregation aşamasıyla sorgularsınız. Örneğin { konum: { type: "Point", coordinates: [ 29.03, 40.99 ] } } biçiminde saklanan noktalara createIndex( { konum: "2dsphere" } ) ekledikten sonra "bana en yakın şubeler", "bu bölgenin içindeki adresler" veya "bu nokta hangi teslimat alanına düşüyor" sorularını yanıtlayabilirsiniz.

Bu yazıda GeoJSON yapısını, 2dsphere index'in özelliklerini, dört temel sorgu aracını uçtan uca bir kurye örneğiyle, mesafe birimlerini (metre ve radyan farkı) ve sık yapılan koordinat hatalarını anlatıyoruz. En çok yapılan hatayı baştan söyleyelim: koordinat dizisinde önce boylam (longitude), sonra enlem (latitude) gelir. Harita uygulamalarında genellikle tersini görmeye alışkınız.

Konum verisini saklamak: GeoJSON ve eski koordinat çiftleri

Konum verisini saklamak: GeoJSON ve eski koordinat çiftleri: Point, LineString, Polygon, MultiPoint

MongoDB konum verisini iki biçimde saklayabilir. Dünya benzeri bir küre üzerinde hesap yapmak istiyorsanız GeoJSON nesneleri kullanırsınız. Düz (Öklid) bir yüzeyde hesap için ise "legacy coordinate pairs" denen eski koordinat çiftleri ve 2d index vardır. Belgeler, küresel hesap gerekiyorsa eski çiftleri GeoJSON Point nesnesine çevirip 2dsphere index kullanmanızı önerir. Gerçek dünya adresleri, şubeler ve teslimat bölgeleri için doğru tercih neredeyse her zaman GeoJSON ve 2dsphere'dir.

Bir GeoJSON nesnesi iki alan içerir: geometrinin türünü belirten type ve koordinatları tutan coordinates. Geçerli boylam değerleri -180 ile 180, geçerli enlem değerleri -90 ile 90 arasındadır (uçlar dahil). Koordinatlar kürenin etrafında "sarılır"; belgelerin örneğiyle -179.9 ve +179.9 birbirine yakın komşulardır.

GeoJSON türüNe temsil ederÖrnek kullanım
PointTek noktaMağaza, kurye, adres
LineStringİki veya daha fazla noktadan oluşan çizgiRota, boru hattı
PolygonKapalı halkalardan oluşan alanTeslimat bölgesi, ilçe sınırı
MultiPointNokta listesiBir zincirin tüm şubeleri
MultiLineStringÇizgi listesiBirden çok rota parçası
MultiPolygonAlan listesiParçalı hizmet bölgesi
GeometryCollectionFarklı geometrilerin karışımıKarma harita verisi

Polygon kuralları

Polygon, GeoJSON LinearRing koordinat dizilerinden oluşur. LinearRing en az dört koordinat çifti içeren kapalı bir çizgidir ve ilk koordinat ile son koordinat aynı olmak zorundadır; bu yüzden üçgen bile dört nokta ile yazılır. Tek halkalı bir polygon kendisini kesemez. Çok halkalı polygonlarda ilk halka dış halkadır; iç halkalar (delikler) tamamen dış halkanın içinde kalmalı, birbirini kesmemeli, üst üste binmemeli ve kenar paylaşmamalıdır. Belgeler ayrıca küre üzerindeki iki nokta arasındaki çizginin düz yüzeydeki çizgiden farklı olabileceğini hatırlatır; koordinatları dikkatle kontrol etmenizi önerir.

javascript
// Kapalı halka: ilk ve son nokta aynı
{
  type: "Polygon",
  coordinates: [ [ [ 29.00, 40.98 ], [ 29.06, 40.98 ], [ 29.06, 41.02 ], [ 29.00, 41.02 ], [ 29.00, 40.98 ] ] ]
}

2dsphere index oluşturmak

2dsphere index, küre üzerinde yakınlık hesabı ve alan sorgularını destekler. İndekslenen alanın değeri ya GeoJSON nesnesi ya da eski koordinat çifti olmalıdır; eski çiftleri index kendi içinde GeoJSON noktasına çevirir.

javascript
use kargo
db.subeler.insertMany( [
   { ad: "Kadıköy", tur: "sube", konum: { type: "Point", coordinates: [ 29.03, 40.99 ] } },
   { ad: "Üsküdar", tur: "sube", konum: { type: "Point", coordinates: [ 29.02, 41.02 ] } },
   { ad: "Beşiktaş", tur: "depo", konum: { type: "Point", coordinates: [ 29.00, 41.04 ] } }
] )

db.subeler.createIndex( { konum: "2dsphere" } )

2dsphere index'in davranışı hakkında bilinmesi gerekenler:

  • Her zaman sparse davranır; sparse seçeneği yok sayılır. Konum alanı olmayan, null ya da boş dizi olan belgeler indekse girmez.
  • Bileşik bir index'te 2dsphere anahtarı başka anahtarlarla birleşebilir ve birden çok konum alanı içerebilir. Belgenin indekse girip girmemesini yalnızca 2dsphere alanı belirler.
  • İndeksli alana geometri olmayan bir değer (örneğin düz bir dize) yazamazsınız; böyle bir belge eklemek ya da böyle veriler içeren alanda index kurmak başarısız olur.
  • MongoDB 8.3'ten beri varsayılan 2dsphereIndexVersion değeri 4'tür. FCV'yi 8.3'ün altına düşürmeniz gerekirse önce sürüm 4 2dsphere index'leri silmelisiniz.
  • Karmaşık şekiller çok sayıda index anahtarı üretebilir. indexMaxNumGeneratedKeysPerDocument parametresi belge başına üretilen anahtarı varsayılan olarak 100.000 ile sınırlar; aşan işlem başarısız olur.

$near: en yakından en uzağa

$near, verdiğiniz noktaya göre belgeleri en yakından en uzağa sıralı döndürür. GeoJSON nokta ile kullanıldığında 2dsphere index gerektirir ve mesafeleri metre cinsinden alır. $minDistance sonuçları en az belirtilen uzaklıktaki belgelerle, $maxDistance en fazla belirtilen uzaklıktaki belgelerle sınırlar.

javascript
// Kullanıcıya 3 km içindeki şubeler, en yakından başlayarak
db.subeler.find( {
   konum: {
      $near: {
         $geometry: { type: "Point", coordinates: [ 29.025, 41.00 ] },
         $maxDistance: 3000
      }
   }
} )

Bu sorgunun bazı özel kuralları vardır. $near zaten mesafeye göre sıraladığı için ayrıca sort() eklerseniz MongoDB ikinci bir sıralama yapar; büyük koleksiyonlarda bu performansı düşürür. Sıra önemli değilse $geoWithin kullanın, o sıralamasız döner ve daha hızlıdır. $near, $text gibi başka bir özel index gerektiren operatörle birleştirilemez ve aggregation pipeline içinde kullanılamaz. MongoDB 8.0'dan beri $near, $nearSphere ve $geoNear verilen noktanın türünün gerçekten Point olduğunu doğrular; başka bir tür hata döndürür. Belgeler ayrıca 9.0 ile $near'in sonuçları tamponlarken kullandığı belleğin izlendiğini, aşama için 100 MB sınırı aşılırsa sorgunun hata ile sonlandığını belirtir; çok sayıda belge eşleşen sorgularda $maxDistance ile alanı daraltmak önerilir. İşlem başına bellek sınırı için MongoDB 9.0 yenilikleri yazısına bakın.

$nearSphere, GeoJSON nokta ve 2dsphere index ile $near ile aynı işlevi görür; belgeler, adı küresel hesap yapıldığını açıkça söylediği için küresel sorgularda $nearSphere'i tercih etmenin daha anlaşılır olabileceğini belirtir.

$geoWithin: bir alanın içindekiler

$geoWithin: bir alanın içindekiler: Daire ile arama: $centerSphere ve radyan, Yarım küreden büyük polygonlar

$geoWithin, geometrisi tamamen belirtilen şeklin içinde kalan belgeleri seçer. Şekil bir GeoJSON Polygon ya da MultiPolygon olabilir. Bu operatör geospatial index gerektirmez ama index sorguyu belirgin biçimde hızlandırır; hem 2dsphere hem 2d index onu destekler. Sonuçlar sıralı değildir.

javascript
// Belirli bir polygonun içindeki tüm şubeler
db.subeler.find( {
   konum: {
      $geoWithin: {
         $geometry: {
            type: "Polygon",
            coordinates: [ [ [ 29.00, 40.98 ], [ 29.06, 40.98 ], [ 29.06, 41.02 ], [ 29.00, 41.02 ], [ 29.00, 40.98 ] ] ]
         }
      }
   }
} )

Daire ile arama: $centerSphere ve radyan

Bir merkez ve yarıçapla dairesel alan aramak için $geoWithin içinde $centerSphere kullanılır. Burada yarıçap metre değil radyan cinsindendir. Belgelere göre kilometreyi radyana çevirmek için değeri 6378.1'e, mili radyana çevirmek için 3963.2'ye bölersiniz (Dünya'nın yaklaşık ekvator yarıçapı).

javascript
// Merkeze 5 km yarıçaplı daire içindeki şubeler (sırasız)
db.subeler.find( {
   konum: { $geoWithin: { $centerSphere: [ [ 29.025, 41.00 ], 5 / 6378.1 ] } }
} )

$near ile $centerSphere farkını şöyle özetleyebiliriz: ilki metre alır ve sıralar, ikincisi radyan alır ve sıralamaz. Sadece "bu yarıçapın içinde olanları say" diyorsanız $centerSphere daha ucuzdur.

Yarım küreden büyük polygonlar

Alanı bir yarım küreden büyük, tek halkalı bir polygon verirseniz, varsayılan koordinat referans sistemi (CRS) ile MongoDB tamamlayıcı geometriyi, yani iki parçadan küçük olanını sorgular. Belgelerdeki örnekle, Dünya yüzeyinin yüzde 75'ini kaplayan bir şekil verirseniz sorgu kalan yüzde 25'lik alan için çalışır. Gerçekten büyük alanı kastediyorsanız $geometry içine crs alanıyla MongoDB'nin özel urn:x-mongodb:crs:strictwinding:EPSG:4326 sistemini yazmanız gerekir; bu sistem saat yönünün tersine sarım sırası kullanır. Şehir ölçeğindeki uygulamalarda bu duruma pek rastlanmaz.

$geoIntersects: hangi bölgeye düşüyor?

$geoIntersects, geometrisi verilen GeoJSON nesnesiyle kesişen belgeleri seçer; kesişim boş değilse belge eşleşir. Küresel geometri kullanır, index gerektirmez ama yalnızca 2dsphere index onu destekler. Tipik kullanım, teslimat bölgelerini polygon olarak saklayıp bir adresin hangi bölgeye düştüğünü bulmaktır:

javascript
db.bolgeler.insertOne( {
   ad: "Kadıköy merkez",
   kurye_ucreti: 40,
   alan: {
      type: "Polygon",
      coordinates: [ [ [ 29.00, 40.98 ], [ 29.06, 40.98 ], [ 29.06, 41.02 ], [ 29.00, 41.02 ], [ 29.00, 40.98 ] ] ]
   }
} )
db.bolgeler.createIndex( { alan: "2dsphere" } )

// Müşterinin adresi hangi bölgeye düşüyor?
db.bolgeler.find( {
   alan: { $geoIntersects: { $geometry: { type: "Point", coordinates: [ 29.03, 40.99 ] } } }
} )

Belgeler, $geoIntersects'in bir polygonun kendi kenarlarıyla, köşeleriyle ya da iç alanı olmadan yalnızca kenar ve köşe paylaşan başka bir polygonla kesiştiğini garanti etmediğini belirtir. Bölge sınırına tam denk gelen noktalarda bu "dejenere geometri" kuralına dikkat edin; komşu bölgelerin ortak kenarına düşen adresler için sonucun hangi bölgeyi döndürdüğünü kendi verinizle test edin. Benzer bir uyarı $geoWithin için de geçerlidir.

$geoNear aggregation aşaması

$geoNear, $near'in aggregation karşılığıdır ve $match, $sort, $limit işlevlerini coğrafi veri için birleştirir. Çıktı belgelerine hesaplanan mesafeyi bir alana yazabilmesi en büyük avantajıdır. Geospatial index gerektirir ve yalnızca pipeline'ın ilk aşaması olabilir.

javascript
db.subeler.aggregate( [
   {
      $geoNear: {
         near: { type: "Point", coordinates: [ 29.025, 41.00 ] },
         distanceField: "mesafe_m",
         maxDistance: 5000,
         query: { tur: "sube" },
         spherical: true
      }
   },
   { $limit: 3 },
   { $project: { _id: 0, ad: 1, mesafe_m: 1 } }
] )
SeçenekGörev
nearReferans nokta; 2dsphere ile GeoJSON nokta ya da eski çift olabilir
distanceFieldHesaplanan mesafenin yazılacağı alan; 8.1 ve sonrasında normal koleksiyonlarda isteğe bağlı, time series koleksiyonlarında zorunlu
maxDistance ve minDistanceGeoJSON noktada metre, eski çiftte radyan
queryEk filtre; içinde $near kullanılamaz
keyBirden çok 2d veya 2dsphere index varsa hangi alanın kullanılacağı; yoksa hata
sphericaltrue ise küresel hesap ($nearSphere anlamı); varsayılan false
distanceMultiplierTüm mesafeleri çarpan katsayı; örneğin radyanı kilometreye çevirmek için
includeLocsMesafe hesabında kullanılan konumun yazılacağı alan

Belgeler, $geoNear'in artık varsayılan olarak 100 belge sınırı uygulamadığını, bu yüzden sonuç sayısını kendinizin $limit ile sınırlamanız gerektiğini belirtir. Mesafe, girdi belgesi bir şekil ise şeklin çevresindeki en yakın noktaya göre hesaplanır. Pipeline mantığı için MongoDB aggregation pipeline yazısına bakabilirsiniz.

2d ve 2dsphere karşılaştırması

2d ve 2dsphere karşılaştırması: Geometri modeli, Veri biçimi, Mesafe birimi ($near), $geoIntersects
Özellik2dsphere2d
Geometri modeliKüreDüz yüzey
Veri biçimiGeoJSON ve eski çiftlerEski koordinat çiftleri
Mesafe birimi ($near)Metre (GeoJSON nokta)Radyan
$geoIntersectsDesteklerDesteklemez
Bileşik indexBirden çok konum ve normal alanBir konum ve bir başka alan
KullanımGerçek dünya konumlarıOyun haritası, düz koordinat sistemi

Belgeler, 2d index ile küresel veri sorgulamanın yanlış sonuç ya da hata üretebileceğini, örneğin kutupların etrafından dolanan küresel sorguları desteklemediğini belirtir. Küresel sorgular için mümkünse 2dsphere kullanın.

Kısıtlar ve 9.0 hata kodu değişikliği

  • Geospatial index shard key olamaz; ama sharded bir koleksiyonda başka bir alanı shard key yapıp coğrafi index oluşturabilirsiniz. Bkz. MongoDB sharding ve shard key
  • Geospatial index'ler bir sorguyu karşılayamaz (covered query olamaz).
  • 2d index collation desteklemez; basit olmayan collation'lı koleksiyonda { collation: { locale: "simple" } } belirtmeniz gerekir.
  • Time series koleksiyonlarında coğrafi sıralama için yalnızca $geoNear kullanılabilir; $near ve $nearSphere çalışmaz ve $geoNear'in query alanı kullanılamaz. Ayrıntı MongoDB time series koleksiyonları yazısında.

MongoDB 9.0 ile 2dsphere index anahtar çıkarma hataları adlandırılmış hata kodlarıyla döner: 510 (GeoKeyExtractionFailed) ve time series koleksiyonları için 511 (GeoKeyExtractionFailedTimeseries). Bu kodlar normal 2dsphere index'lerdeki eski 16755 ve 16756 kodlarının, time series koleksiyonlarındaki 183934 ve 183493 kodlarının yerini alır. Uygulamanız hatalı geometri eklemelerini eski kodlarla yakalıyorsa yükseltme öncesinde 510 ve 511'e göre güncelleyin.

Sık yapılan hatalar

  • Enlem ile boylamı ters yazmak: En yaygın hatadır. Sıra her zaman [ boylam, enlem ]. Harita servislerinden kopyaladığınız değerler çoğu zaman enlem önce gelir; kaydetmeden önce sırayı çevirin.
  • Polygonu kapatmamak: İlk ve son koordinat aynı olmalıdır.
  • $centerSphere'e metre vermek: Yarıçap radyandır; kilometreyi 6378.1'e bölün.
  • $near'den sonra sort eklemek: Gereksiz ikinci sıralama yapar.
  • Index olmadan $near çalıştırmak: $near ve $geoNear geospatial index ister.
  • Birden çok 2dsphere index olup key vermemek: $geoNear hata döndürür.

Sık Sorulan Sorular

İki nokta arasındaki mesafeyi nasıl hesaplarım?

En pratik yol $geoNear aşamasıdır: referans noktayı near olarak verir, distanceField ile mesafeyi bir alana yazdırırsınız. GeoJSON nokta ve 2dsphere index ile sonuç metre cinsindendir.

Konum alanında hem eski çift hem GeoJSON varsa hangisi kullanılır?

Belgelere göre MongoDB 8.2 ve sonrasında, alt belgede herhangi bir GeoJSON nesnesi varsa alan sırasından bağımsız olarak GeoJSON temsili indekslenir. Daha eski sürümler alan sırasına bakar. Karışıklık yaşamamak için tek bir biçimde kalın.

Coğrafi sorguyu metin aramasıyla birleştirebilir miyim?

Aynı sorguda $near ile $text birlikte kullanılamaz, çünkü ikisi de farklı özel index ister. Metin araması için MongoDB text index yazısına bakın; iki sonucu uygulama tarafında birleştirmeniz gerekir.

Konum verisini nasıl doğrularım?

2dsphere index zaten geometri olmayan değerleri reddeder. Ek olarak koleksiyona şema doğrulaması tanımlayabilirsiniz; bkz. MongoDB schema validation.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: MongoDB öğrenme rehberi · MongoDB index oluşturma · Aggregation pipeline · Time series koleksiyonları · Text index ve metin arama