.Net Core Üzerinde ElasticSearch ile İstenen Bir Mesafe İçindeki Lokasyonların Filitrelenmesi

Selamlar,

Bu makalede girilen bir konuma göre, belirlenen bir mesafe içinde kalan DB’deki tüm lokasyonların filitrelenmesini, ElasticSearch ve .Net Core kullanarak inceleyeceğiz. ElasticSearch hakkında detaylı bilgiye burdaki makaleden erişebilirsiniz.

Örneğin 1000 tane firmanın 10 tane lokasyonu olan bir DB kaydı olsun. Mobil bir uygulamada, bulunulan konumun 20km çevresindeki lokasyonu olan firmaların sıralanması istendiğinde, bunun matematiksel işlemler yapılarak bulunması büyük bir vakit ve kaynak kaybını neden olacaktır. Bunun için ElasticSearch ve GeoDistance() fliter’ının kullanılması, bize sonucun saniyeler içinde dönmesini sağlıyacaktır.

Öncelikle “Cm_Customer” modelimiz aşağıda görüldüğü gibi oluşturulur:

Cm_Customer:

Örnek amaçlı Customer Kayıt Kümesi:
Cm_CustomerLocations: Herbir müşteriye ait lokasyon bilgisinin tutulduğu tablodur. Aşağıdaki gibi oluşturulur.

Örnek amaçlı CustomerLocations Kayıt Kümesi:

Uygulamada .Net Core EntityFrameWork kullanılmıştır:

LocationContext: Entity’de kullanılan DBContext aşağıdaki gibidir.

Şimdi sıra geldi ElasticSearch üzerinde Lokasyon için gerekli Index’in oluşturulmasına. Bu makalede ElasticSearch local makinada çalıştırılacak ve http://localhost:9200 adresinden erişilecektir.

Öncelikle .Net Core Console application aşağıdaki gibi oluşturulur.

MacOs ortamında, eğer önceden ElasticSearch yüklenmiş ise Bash’de, “ElasticSearch” komutu yazılarak ayağa kaldırılır. Ayrıca monitor tool’u olan Kibana‘da yine yüklenmiş ise, bash ortamında “Kibana” komutu yazılarak ayağa kaldırılır.

ElasticSearch için Browser’a “http://localhost:9200” yazıldığında, aşağıdaki gibi bir ekran ile karşılaşılır. “You Konw, for Search” :)

Kibana için Browser’a “http://localhost:5601” yazıldığında, aşağıdaki gibi bir ekran ile karşılaşılır:

1-)ElasticSearch ConnectionSettings:

  • Connection string yolu : “http://localhost:9200” tanımlanmıştır.
  • Default olarak “customerlocation” indexi kullanılacaktır.
  • ElasticSearch Index’inde tutlacak DataModel “CustomerModel“‘dir.
  • Oluşturulacak index adı “customerlocation“‘dır.
  • ElasticClient, ilgili connection ile oluşturulur.

CustomerModel: ElasticSearch üzerinde tutulacak index’in modeli, aşağıdaki gibidir. Customer tablosundan “CustomerName”, ve geri kalan lokasyon bilgileri de “CustomerLocations” tablosundan alınır. Bir çeşit Sql View’ın, ElasticSearch üzerindeki iz düşümüdür :)

Not: “public GeoLocation Location { get; set; } “: Lokasyon tipi Nest kütüphanesinden “GeoLocation” olarak alınmaktadır. Bu tip, ElasticSearch tarafından Indexlenirken kordinatlar ile çalışma anlamında büyük önem arz etmektedir. GeoLocation tipine bağlı propertyler ve methodlar aşağıda görüldüğü gibidir.

GeoLocation(Model): Nest kütüphanesinde tanımlı olan, GeoLocation sınıfının, property ve methodları aşağıdaki gibidir.

CustomerModel:

2-) ElasticSearch Üstünde Index Oluşturma: Database’e kayıtlı, tüm Customer ve Lokasyon kayıtları çekilip ElasticSearch’e “customerlocation” adında bir index altına atılmaktadır.

  • “using (LocationContext dbContext = new LocationContext())” : LocationContext ile Database’e bağlanılır.
  • “customerLocationList” : Belirlenen koşula göre databaseden, “List<CustomerModel>” çekilir.
  • “Location = new GeoLocation(Convert.ToDouble(s.Latitude), Convert.ToDouble(s.Longitude))” : GeoLocation tipine, Db’den gelen “Latitude” ve “Longitude” koordinatları atılır.
  • “var defaultIndex = “customerlocation”” : Default index adı olarak, “customerlocation” atanır.
  • “if (client.IndexExists(defaultIndex).Exists) { client.DeleteIndex(defaultIndex); }” : Eğer önceden bu isimde bir index var ise silinir.
  • “client.CreateIndex(defaultIndex, c => c” : ElasticSearch üzerinde ilgili index oluşturulur.
    • “.Mappings(m => m .Map<CustomerModel>(mm => mm .AutoMap() )” : Ilgili index, “CustomerModel”‘e göre şablonlanır.
    • .Aliases(a => a.Alias(“location_alias”))” : ElasticSearch’de alias mutlaka kullanılmalıdır. Böylece ilgili index silindiği zaman, indexlenen önceki kayıtlar silinmez.
  • “var bulkIndexer = new BulkDescriptor();” : Bu makalede Database’den çekilen kayıtlar Toplu(Bulk) olarak indexlenmiştir.
    • “foreach (var document in customerLocationList)”: Db’den çekilen herbir kayıt tek tek gezilmiştir.
    • “bulkIndexer.Index<CustomerModel>(i => i .Document(document) .Id(document.LocationId) .Index(“customerlocation”))” : Her bir CustomerModel kayıdı, yani ElasticSearch’de karşılığı document, BulkDescriptor’a tek tek atılır.
    • “elasticClient.Bulk(bulkIndexer)” : ElasticSearch’deki “customerlocation” indexine, tüm documentler toplu olarak atılırlar.

Indexing():

Monitoring: ElasticSearch’de, oluşturulan Index’in monitor edilebilmesi için bu makalede 2 tool kullanılmıştır. 1.si herkesin bildiği :5601 “Kibana” :) Diğeri, pek bilinmeyen Chrome’da Extension olarak bile karşımıza çıkan, “ElasticSearch-Head“. İlgili “customerlocation” index’in oluştuktan sonra, içeri atılan documentlerin Kibana ve ElasticSearch-Head’deki görünümü aşağıdaki gibidir:

Kibana :

ElasticSearch-Head:

3-) ElasticSearch’de Belli Bir Mesafeye Göre Lokasyonları Filitreleme:

Search():

  • “elasticClient.Search<CustomerModel>” : Yapılacak arama işleminden “CustomerModel” tipinde bir result’ın dönmesi beklenmektedir.
  • “GeoDistance()” : Lokasyona göre filitreleme işleminin yapılmasını sağlamaktadır.
  • “.Field(f => f.Location)” : Hangi alana göre GeoLocation Filter yapılacağı belirlenir.
  • “.Distance(“250km”).Location(41, 28)” Bulunulan konum, dummy olarak (41,28) olarak verilmiş ve çevresinde 250 km bir çember içinde kalan tüm kayıtlı lokasyonlar çekilmiştir.
  • “.DistanceType(GeoDistanceType.Plane)” : Kuş bakışı’na göre, ilgili mesafeye bakılmıştır.
  • “foreach (var customer in geoResult.Documents) {” Tüm bulunan lokasyonlar, ekrana basılır.

Not: ElasticSearch Result’da Distance, yani mesafe geri dönülmemektedir. Kısaca bu örnekte olduğu gibi “250km” içinde bulunulan lokasyonların, gerçekte bulunulan konuma olan mesafesi için, ayrıca bir hesaplamanın yapılması gerekmektedir. Aşağıda, “Haversine Formulünün” fonksiyona döndürülmüş hali gözükmektedir. Yukarıda her bir lokasyon ekrana basılırken, ilgili “GetDistanceFromLatLonInKm()” çağırılmış ve bulunulan noktaya olan uzaklık yaklaşık olarak hesaplanarak ekrana basılmıştır.

GetDistanceFromLatLonInKm():

Geldik bir makalenin daha sonuna. Bu makalede, lokasyon ile ilgili bir filitreleme ihtiyacına karşılık olarak ElasticSearch kullanılmıştır. Bu esnada tüm data, ElasticSearch’de GeoLocation tipinde bulk olarak bir index altına atılmış ve sorgulama esnasında “GeoDistance()” filter’ı ile birlikte istenen bir mesafe içindeki documentler, custom bir model şeklinde liste olarak alınmıştır. ElasticSearch geri dönüş tipinde, her bir lokasyonun bulunulan konuma olan mesafesi ayrıca geri dönülmemektedir. Bu durumda, result listden dönen her bir kayıt için bulunulan konuma uzaklığı “Haversine Formulü” kullanılarak hesaplanmıştır.

Yeni bir makalede görüşmek üzere hoşçakalın.

Source Code: https://github.com/borakasmer/ElasticSearchGeolocation.git

Source:

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

6 Cevaplar

  1. Tagi dedi ki:

    Çok faydalı.
    Teşekkürler

  2. Nurullah dedi ki:

    Her bir paylaşımınız için ayrı ayrı çok teşekkür ederim. Bir çok konuda sizin sayenizde bilmediğim teknolojiler ile tanışma fırsatı buldum, buda bende ayrı bir heyecan oluşturuyor ve deneyimleme konusunda öncü oluyor. Paylaşımlarınız için tekrar tekrar teşekkür ederim.

  3. Yakup dedi ki:

    hocam selamlar,

    uzun zamandır elastic search inceliyorum. Fakat aklıma takılan bir nokta var. Turizm uygulamaları geliştiriyrum ve aramalar tamamen değişken kriterler üzerine kurulu. klasik e ticaret sistemleri gibi full text search mantığı ile çalışmıyor. lokasyon, kişi sayısı, tarih vs gibi kriterler her aramada değişebiliyor ve her aramada farklı sonuçlar geliyor doğal olarak. Böyle bir sistemde elasticsearch ne kadar uygun olur?

    • borsoft dedi ki:

      Selam Yakup,
      Arama sırasında kayıt sayın milyon mertebesinde ise, mutlaka elastik search gibi bir yapı kurman gerekir. Ya da MongoDB gibi NoSql bir DB ile ilerleyebilirsin. Elasticsearch kullanmada durumunda, değişen datanın anlık olarak ElasticSearch’e indexlenmesi gerekir. Bu durumda da var olan sistemden bağımsız, Microservisler ile Queueler async okunarak, ilgili ElasticSearch’ü Indexleyebilirsin.

      İyi çalışmalar.

  4. Ali dedi ki:

    Hocam Merhaba,

    Tam aradığım konuydu bulup uygulayabilince çok işime yaradı öncellikle teşekkürler.
    Ben web uygulaması için uyguladım, NEST in 7.x versiyonunda client.IndexExists(defaultIndex).Exists yerine elasticClient.Indices.Exists(defaultIndex).Exists gibi ufak değişiklikler olmuş, 7.x kullananlar takılırsa iletmek istedim.

    İndexletirken desc alanında bir alan ve productnumber adında bir alan daha indexletiyorum, filter için .Distance a ek olarak aranan kelimeleride nasıl sorguya dahil edebilirim. Seçilen noktanın 250km çapında aranan kelimeye karşılık gelen marketlerin lokasyonu gibi. Bu konuda minik bir kod örneği paylaşabilirseniz sevinirim.

    İlerki aşamada yazılan kelimeye benzer sonuçlarında gelmesi ve suggest kısımlarına geçmeyi planlıyorum umarım onlarıda yapabilirim :)

    Çok teşekkürler.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.