.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...

3 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.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir