Dünya yuvarlak değil, haritalar yanıltıcı!

Her şey bir projede “Madem mobilini yapıyoruz, kullanıcıya bulunduğu noktanın civarındaki girdileri de gösteremez miyiz?” sorusunun gelmesiyle başladı. Evet evet, gösteririz tabii.

Uğur Arıcı
10 min readMay 10, 2019

--

Medium bu yazıyı okumanızı engelliyorsa blogumdan okuyabilirsiniz. Yazılarımı blogumda yayınladıktan 1 hafta sonra burada yayınlıyorum.

Bu yazıda ilişkisel veritabanları üzerinde konum kaydı ve konuma dayalı sorgulamalardan bahsedeceğim. Bu konuya girişince hiç beklemediğim şekilde karşılaştığım bilgi yığınını toparlamaya çalışacağım ki ileride ihtiyaç duyan başkaları için önden fikir vermiş olalım.

Başlamadan önce belirteyim: haritalama, konuma dayalı uygulamalarla vs pek çalışmış değilim. Bunları daha önce Google Maps JS API ve Elasticsearch’te konum bazlı sorgu ile kullanmıştım. Gün gelip uygulamanın içine gömülmesi gerekince oturdum bi bakayım dedim ve karşıma çıkan ilk bilgilerle HİÇBİR ŞEY ANLAMADIM. Biraz uğraştıktan sonra şu tiviti attım;

Şimdi bu tivitime baktığımda geri kalan her şeyi anlamışım da bir tek SRID olayını anlamamışım gibi görünüyor. Aslında SRID olayını anlamadıysam konuya dair hiçbir fikrim olmadığını sonradan öğrenecektim. Gelin baştan alalım.

Neye ihtiyacımız vardı?

Uygulamamızda “civarımdakileri göster” özelliği olsun istemişlerdi. Bunun için civardaki gönderilerin civarda olduğunu anlamamıza yarayacak şekilde veri saklamamız ve bunun üzerinden sorgu yapabilmemiz gerekiyordu. Bunun temsilini bir görselle görelim.

Lacivert noktalar gönderilerin kayıtlı konumları, kırmızı alan “çevremdekiler” için taradığım alan. Harita: openstreetmap.com

Burada her gönderi bir nokta olarak kayıtlı (lacivert noktalar) ve ben bulunduğum alanın (kırmızı alan) içinde kalanları sorgulamak istiyorum. Kayıtların buna göre tutulabilmesi ve sorguların buna uygun yapılabilmesi gerekir.

Ancak proje sahibi daha farklı bir şey istiyordu; gönderiler bir noktayla değil, kendi ilgi alanlarıyla temsil edilsin. Yani bir alan içinde kalan noktalar değil, kesişen alanlar sorgusu yazmamız gerekiyordu. Görsel bir temsili şöyle oluyor:

Lacivert alanlar gönderilerin etki alanları, kırmızı alan benim taradığım ilgi alanım. Harita: openstreetmap.com

İlk senaryoda (nokta saklamak için) kayıt saklamaya dair bir fikrim var sanıyordum. Neticede konum dediğimiz şey koordinat değil mi? Bu koordinat bilgisini bana tarayıcılar veriyor sağolsunlar. O koordinat bilgisini bir arada metin olarak (“41.0065,28.9776”) ya da enlem ve boylamı ayrı ayrı ondalık sayı (float, double) olarak saklardım olur biterdi.

Ama şimdi işler değişmişti, bunu merkez alan ve belli bir yarıçapla bir daire çizince oluşan alanı saklamam gerekiyordu. Hmm 🤔

Tamam, o zaman veritabanındaki bir sütunda merkez noktasını saklasam, bir sütunda da kaç metrelik bir yarıçapla etki alanının oluşacağını tam sayı olarak saklasam? 🤔 Peki bunun üstünden sorguyu nasıl atacaktım ki? Zaten en başında konum saklayarak “belli bir alan içinde kalan noktaları” nasıl sorgulayacaktım? Nasılsa Google Maps API ve Elasticsearch’te hep merkez noktalarını saklayıp ona göre sorgu yapabildim diye bunda da “merkezi saklayayım gerisini bulurum” gibi bir şey düşünüyordum herhalde. İşte insanın bilgisinin olmadan fikrinin olması dediğimiz hastalık durumu tam da böyle bir şey.

Uzamsal veri tipleri

Bu konu beni kullanmakta olduğumuz MySQL veritabanı sunucusundaki veri tiplerine bakmaya itti. MySQL’e spatial yani uzamsal veri tipleri gelmişti, ben de bir konumu saklamak için bunlar arasından geometry tipini kullanabileceğimi düşündüm. Öncelikle kullanıcının oluşturduğu gönderinin merkez noktasını saklayıp belli bir alana girenlerini bulmakla devam edeyim diyordum.

İlk duvara orada çarptım aslında, çünkü SRID denen şeyi anlamlandıramamıştım. Veritabanımda geometry tipli bir sütun açıp phpMyAdmin’de nasıl bir form gösterecek diye bakayım dedim (hani daha mantıklı bir açıklaması olur falan) ama sonuç yine hüsran oldu.

phpMyAdmin’de GEOMETRY tipinde bir sütuna veri girmek istediğimizde gözüken yardımcı ekran.

İşte “SRID’yi anlamadım” tivitimi tam o zaman attım. Dedim ya, sanki her şeyi anlamışım da SRID eksik kalmış gibi. O zaman öyle düşünmüş olmam şimdi çok komik geliyor :)

Neyse, tivitime Onur Küçük hocamdan bir yanıt geldi:

Tiviti beğendim ama “Ne? Nasıl yani? E koordinat cihazdan gelen X,Y değil mi zaten. Bu koordinatların genel bi standartı yok mu? 🤔” falan diye düşünceler aldı beni. “Küresel yapıda izdüşümü” ne demekti? Kafam iyice karışıyordu. Demek ki daha geriye gidip okumaya başlamak gerekiyordu. Cin olmadan adam çarpmaya çalışmanın alemi yoktu.

Merhaba Coğrafi Bilgi Sistemleri

SRID, uzamsal veri tipleri, uzamsal referans kodu diye diye sürekli artan “bunlara bakmak lazım” listemi hızlıca taradım ve her birine dair yaptığım üstünkörü araştırmalarda mutlaka bir GIS adı geçtiğini gördüm. Demek ki buradan başlamak gerekiyordu. GIS: Geographic Information Systems kısaltması, yani Türkçesiyle Coğrafi Bilgi Sistemleri. Gündemimizdeki konunun temelinde de bu vardı. Coğrafi bir konum bilgisinin tanımlanması, saklanması, sorgulanması için hangi standartı baz alacağına karar vermemiz gerekiyordu. Peki tüm bunlar ne demek?

Haritalar nasıl oluşturuluyor?

Konu her şeyden önce üç boyutlu bir küre (aslında geoid) olan dünyamızın iki boyutlu (masaya serip bakabileceğimiz) haritalara nasıl aktarıldığını ve bunun birkaç farklı yöntemi olduğunu anlamakla başlıyor.

Haritalama için izdüşümün nasıl alındığını anlamak adına bir örnek. Kaynak: http://wiki.gis.com/wiki/index.php/Map_projection

Harita projeksiyonu, yani coğrafi alanın izdüşümünü alarak haritalandırma; küre içindeki bir ışık kaynağından ışık tutulsa, bu ışıkla düz zemin (kağıt) arasında kalan dünyanın görüntüsü kağıdın üzerine nasıl yansır diye yola çıkılarak haritalamaya imkan tanıyan yöntem. “Küresel yapıda izdüşümü” denen şeyi anlamaya başlamıştım, SRID’ler de bu “harita projeksiyonu” yöntemine göre oluşan farklı çıktıların kod adlarıydı aslında. SRID: Spatial Reference Identifier yani Uzamsal Referans Kimliği.

Bir silindirin dünyanın etrafına sarıldığı varsayımıyla oluşturulan Mercator projeksiyonu. Kaynak: http://wiki.gis.com/wiki/index.php/Mercator_projection
Harita projeksiyonu için farklı yöntemler mevcut. Kaynak: http://wiki.gis.com/wiki/index.php/Map_projection

Haritalar oluşturulurken farklı projeksiyon yöntemleri kullanılabiliyor. Bu yöntemler haricinde haritanın merkez alındığı, odaklandığı dünya üzerindeki nokta da değişebiliyor. İşte tüm bunlar belli bir konumu tanımlamak, işaretlemek, bunu anlaşılır bir bilgiye çevirmek ve saklamak için birbirinden farklı uzamsal referansları (spatial references) doğuruyor. Bir konumu/alanı nasıl saklarım arayışına girince sürekli karşıma çıkan 4326 kodu da aslında bir SRID idi. Yani WGS 84 adı ve 4326 koduna sahip uzamsal referansta 41,29 noktasında İstanbul var diyebilmemiz mümkün oluyor.

Farklı projeksiyon yöntemlerinin nasıl uygulandığını gösteren hareketli resim.

4326 referans kodu neden bu kadar yaygın?

Çok basit, GPS sayesinde! Global Positioning System (Küresel Konumlandırma Sistemi) WGS 84 yani 4326 projeksiyonuna göre çalışıyor. Bugün birçok cihazın içinde konum bilgisini almak için GPS alıcıları bulunuyor. GPS alıcıları dünya yörüngesindeki GPS uyduları ile haberleşerek bulunduğu konumun bilgisini alıyor. İşte o koordinatlar da bize 4326 projeksiyonu standartına göre geliyor. Bu yüzden dünya üzerindeki bir konumun elimizdeki koordinatlarla doğru bir şekilde saklanabilmesi için bu koordinat bilgisinin 4326 SRID’li projeksiyona göre olduğunu da bildirmemiz gerekiyor. Bu da uzamsal veri saklayan her yerin bize “tamam noktanın koordinatını söyledin de, hangi projeksiyona göre bu noktada” diye SRID sormasına sebep oluyor.

Uzamsal Hesaplamalar

Dünyanın 2 boyutlu bir zemine nasıl yansıtıldığını ve haritasının çıkarıldığını anlamıştım. Bu haritalar üzerinde yer işaretlemeleri yapmak için ilgili projeksiyonun standartlarındaki koordinat sistemini kullanabilirdim. Demek ki bu koordinat sistemi üzerinde şekiller çizildiğini varsayıp bu şekillerin birbiriyle ilişkisine dayalı da sorgulama yapabilir duruma gelirsem ihtiyacımı karşılıyordum. 2 boyutlu haritalarla koordinat düzlemine inmedik mi kardeşim? Demek ki bundan sonrası lise geometrisi dimi? Heh, değil işte!

Kusursuz bir dairenin WGS84 (4326) projeksiyonunda kutuplara yaklaştıkça nasıl eliptik bir görüntü aldığını gösteren hareketli resim.

Yukarıdaki görüntüde tam ekvator üzerinde küçük bir daire çiziyorum ve bu kusursuz daireyi haritanın büyük kısmını içine alacak şekilde büyütüyorum. Bu harita, silindir yöntemiyle oluşturulan bir harita olduğu için ekvator çevresinde gerçek boyuta yakınken kutuplara doğru bozulma oluyor.

Hatta bunun bir de “dünyanın yarısına vuran güneş ışığı” şeklinde kürenin yarısını kaplayan eşsiz bir daire olmaya çalıştığında nasıl olduğunu görelim.

Ekvator çizgisinin biraz kuzeyini merkez alan bir dairenin dünyanın büyük bir kısmını saracak şekilde büyüdüğünde harita üzerinde aldığı şekil.

Bu dünya haritası üzerinde tekrar eden dalgalı şekil size bir şey hatırlattı mı? Gün/gece durumunu gösteren haritalardan tanıdık gelebilir.

Güneş ve Ay’ın konumuyla beraber gün/gece durumunu gösteren harita.

Peki bu ne anlama geliyor? Dünyanın görüntüsünü bir şekilde 2 boyutlu zemin üzerine almış olsak bile geometriden bildiğimiz x,y koordinatlarını kullanarak mesafe, alan hesaplaması falan yapamazdık. Mesela iki boylamın ekvator hizasındaki mesafesiyle, 45 derece enlemi hizasındaki mesafesi çok başka. 90 derece enleminde ise 0'a iniyor. Ama harita üzerindeki temsilde, boylamlar arasındaki mesafe bütün enlemlerde aynı kalıyor.

Bu yüzden hesaplamalarımızı uygun uzamsal referansa göre gerçekleştirebilmemiz gerekiyor. Neyse ki coğrafi bilgi sistemleri bu konuda da standartları tanımlamış. Hatta ilişkisel veritabanlarında uygulanmak üzere oluşturulan ve SQL’e uyarlanması için duyurulan standartlarda da uzamsal hesaplama fonksiyonları dahili olarak geliyor.

MySQL’deki spatial alanları kullanmayı düşündüğüm için bu spatial hesaplama fonksiyonlarını da kullanabileceğimi düşündüm ve araştırmamı sürdürdüm. Artık ihtiyaç duyduğum şeyi nasıl yapacağımı az çok anlamıştım. MySQL ve Laravel ile geliştirdiğimiz back-end servisinde bunu sağlayabileceğim bazı paketleri inceledim. SQL’de uygulamasını araştırırken MySQL’in bu konuda çok yeni olduğunu, konumsal uygulamalar geliştiren hemen herkesin SQL tarafında PostgreSQL ve PostGIS kullandığını gördüm. Zaten Onur Hocam da bana aynısını önermişti.

Ancak öncelikle PostgreSQL ve PostGIS ile bunu yapabileceğimden emin olmak istedim. Bir test ortamı yaratabilmek için Docker üzerinde PostGIS eklentisi de olan bir PostgreSQL sunucusu ayağa kaldırdım. Yarattığım sütuna daireler kaydetmek, sonra başka daireyle kesişenleri bulmak istiyordum.

PostGIS’te daire yaratmak ve taranan alanla kesişenleri bulmak

Daire yaratmak için bir merkez noktasına ve yarıçapa ihtiyacımız var. Şimdi sizi ortaokul hatta belki ilkokul yıllarından anımsayacağınız bir tanımlama ile gülümsetelim: “Daire, bir noktaya eşit mesafedeki tüm noktaların birleşiminden oluşan geometrik şekildir.

GIS’te bunu sağlayabilmek için ST_Buffer fonksiyonunu kullanıyoruz. Bir nokta ve mesafe verdiğimizde, o noktaya o mesafedeki tüm noktaları ara belleğe alıyor ve bunların birleşiminden bir daire oluşturuyor. Buraya vereceğimiz nokta için ST_MakePoint fonksiyonunu kullanıyoruz. ST_Makepoint’e ::geography eklemesini yaptığımızda bunu WGS 84'e göre, yani 4326 SRID’ye göre oluşturuyor.

Günün sonunda İstanbul’daki Milyon Taşı’nı merkez alan, 50 metre yarıçaplı bir daireyi çizmek için aşağıdaki kodu kullanabiliyordum.

Bu şekilde ürettiğim daireleri bir tablonun uygun tipteki alanına kaydedip, sonra hedef alan ile kesişenleri bulmak gerekiyordu. Bunun için de ST_Intersect fonksiyonunu kullanarak tarama yapabiliyordum.

MySQL’den PostgreSQL’e taşınma

Test etmek için açtığım PostgreSQL+PostGIS kurulumunda ihtiyaç duyduğum şeyi rahatlıkla yapabildiğimi gördüm. Demek ki MySQL’den PostgreSQL’e taşınmak gerekiyordu. Uygulamanın sunucu tarafını Laravel kullanarak geliştirdiğimiz için tüm veritabanı tablolarını migration kullanarak tanımlamıştık. Yani yapmam gereken tek şey PostgreSQL kurulumunu yaparak ayarlarını Laravel’e bildirmek ve migrationları tekrar çalıştırmaktı. PostgreSQL kurulumunun ardından uygulamanın MySQL yerine PostgreSQL üzerinde çalışır hale gelmesi sadece birkaç dakika aldı.

Laravel Eloquent ile sorguyu konuma göre filtrelemek

Geriye sadece uygulama içinde geometri alanının güncellenmesi ve geometri alanına göre kesişen sorgunun atılabilmesi için entegrasyonumuz kalıyordu. Başlangıçta bunun için yardımcı bir paket mi kullansam diye düşündüm ama sonrasında modele ekleyeceğim 2 metod ile çözmenin daha hızlı ve yeterince etkili olduğuna karar verdim.

Öncelikle ilgili kaydın “location” isimli sütununa daireyi eklemek için bir metod yazdım. Bu metodu varolan bir kayıt üzerinde çalıştırmak gerekiyor tabii, buraya kayıt esnasında verdiğimiz bilgiyi sorguya düzgünce ekleyecek bir düzenleme gerekebilir ileride.

İlgili kaydın “location” sütununu güncellemek için modele eklediğim metod.

İşin güzel tarafı ise Laravel Eloquent ile gelen yerel etki alanı (local scope) tanımlama imkanıydı. Model içinde bir “scope” tanımlayarak model üzerinden oluşturulan sorgularda rahatça filtreleme yapabilmemiz sağlanıyor.

Şu şekilde tanımladığımız bir scope sayesinde;

Sorguya kesişen konumlar şartı ekleyebilmek için eklediğim metod.

direkt model üzerinden şöyle kullanabiliyoruz;

Modele eklenen “scope” sayesinde doğrudan filtre uygulayabiliyoruz.

Bu filtrelemeyi tek başına kullanmak zorunda değiliz, Laravel Eloquent’ın diğer yetenekleriyle de beraber kullanabiliriz. Mesela çevremizdeki kayıtları son eklenenden ilk eklenene doğru, yani tersten sıralayarak, her sayfaya 10 tane gelecek şekilde sayfalamak istediğimizde de direkt intersects metodunu kullanabiliriz.

Tanımladığımız scope, Eloquent’in query builder yardımcılarıyla birlikte kullanılabilir hale gelir.

Hayırlısı olsun! Nur topu gibi bir “çevremdekileri göster” özelliğimiz oldu. Hem de kesişen daireler üzerinden hesaplanan, yaşasııın! 🎉

Bu yüzden mi düz dünyacı olmuşlardır? 🤔

Coğrafi Bilgi Sistemleri başta biraz ürkütücü geliyor. Öyle ki; acaba düz dünyacılar bunları görüp hesaplamalarını yapmaktan korktular da o yüzden mi düz dünyacı oldular diye düşündüm 😋 Halbuki birçok özgür GIS aracı bulunuyor, ilişkisel veritabanlarında GIS ihtiyaçlarını karşılamak için de PostgreSQL+PostGIS müthiş çözümler sağlıyor.

Şaka bir yana, başlıkta da belirttiğim gibi; dünya gerçekten de yuvarlak değil. Yani en azından “kusursuz bir küre” şeklinde yuvarlak değil. O yüzden kendine özgü bir şekil olarak ona “geoid” diyoruz. Bu süreçte birçok kurcalamanın içinde bir de Geoid modeli görüntüleyici buldum ve bununla oynayarak bolca vakit geçirdim. Demiştim size; dünya yuvarlak değil!

Yükseltileri abartılarak gösterilen bu geoid modeli üzerinden dünyaya bakınca kafamdaki o “kusursuz dünya” algısı da yıkıldı.

Ayrıca yukarıda “ekvatorda bir daire çizdim kutuplara doğru genişlettim” örneklerini de bir VueJS harita örneğinde uygulamıştım. O örneğe bu adresten erişebilirsiniz. Sol taraftaki seçeneklerden “circle” tikini açınca yuvarlak belirecektir, sonra dilediğinizce üzerinde oynayarak harita projeksiyonu ve deformasyonu konusunda fikir edinebilirsiniz.

Mesela daireyi Meksika’yı içine alacak şekilde boyutlandırın ve sonra Grönland’a doğru sürükleyin. Haritada Grönland, Meksika’nın bilmem kaç katı görünmesine rağmen ikisinin de aynı daireye sığdığını göreceksiniz. Demiştim size; haritalar yanıltıcı!

Herhangi bir şeyi ve bizim alanımıza özel olarak dijital ürünleri doğru tasarlayıp yaratabilmek için, neyin nasıl çalıştığının, haliyle nasıl ortaya çıktığının ve tabiatının anlaşılması gerektiğine inanırım. Haritalar ve coğrafi bilgi sistemiyle tanıştığımda da aynı itkiyle işin kökeninde neler olduğuna bakmaya çalıştım ve bu süreçte hayli keyif aldım. Umarım size de keyifli bir şekilde aktarabilmişimdir. Yazılım tarafında bir şeyler yapmaya çalışan, özellikle meraklı öğrenci arkadaşlarımıza GIS üzerinde araştırmalar ve çalışmalar yapmayı öneririm. Konuya detaylıca bir giriş için de bu videodan başlayabilirsiniz.

Haftalık Sohbetlerime Katılın (gerçekten sohbet, canlı yayın falan)

Öğrendiklerimi olabildiğince aktarmayı çok seviyorum. Bunun için YouTube kanalımda Haftalık Sohbetler canlı yayın serisine başladım. Önceki yayınları izleyebilir, sonraki yayınlar için abone olabilirsiniz. İçinden kimi kısımları ya da başkaca planlı kayıtları yakın zamanda podcast olarak çıkarmayı da düşünüyorum. Ayrıca uzun zamandır bahsi geçen birkaç eğitim setinin kaydına da başladık.

Tüm bunlara dair güncellemeler için 🐦twitter hesabım @ugursus ‘u takip edebilir ve spamsız e-posta bültenleri için e-posta listeme abone olabilirsiniz.

Görüşmek üzere! 🤗

--

--

Uğur Arıcı

Dijital Ürün Yöneticisi, Danışman - 10 yılı aşan web geliştirme tecrübemi isteyen herkes için faydalı kılmaya çalışıyorum.