Bazen uygulamanızda kullanıcı konumlarını haritalamak veya coğrafi verileri analiz etmek gibi jeo-uzamsal işlevleri gerçekleştirme ihtiyacıyla karşılaşabilirsiniz. Bu görevler için GDAL, Shapely ve Python için Geopandas gibi çok sayıda dile özgü kitaplık mevcuttur.
Alternatif olarak, jeo-uzamsal işlevsellik veritabanları aracılığıyla uygulanabilir; örneğin, PostGIS uzantısını PostgreSQL gibi ilişkisel bir veritabanıyla kullanabilir veya Azure CosmosDB gibi dağıtılmış bir veritabanındaki uzamsal veri türleri için yerel destekten yararlanabilirsiniz.
Ancak Redis veya Google Spanner gibi arka uç depolama alanınız uzamsal sorguları yerel olarak desteklemiyorsa ve büyük ölçekli coğrafi uzamsal sorguları işlemeniz gerekiyorsa bu makale sizin için özel olarak hazırlanmıştır.
Uzamsal verileri işlemek için her zaman başka bir mikro hizmet oluşturabilirsiniz, ancak bu seçenek genellikle ek bir uygulamanın bakımını yapma yükünü içerir. Diğer bir yaklaşım ise S2 ve H3 gibi coğrafi indeksleme kütüphanelerini kullanmaktır. Google tarafından geliştirilen S2, Hilbert eğrisini temel alırken, Uber tarafından geliştirilen H3, jeodezik ayrık küresel ızgara sistemini temel alıyor. S2 ve H3 pek çok benzerliğe sahiptir: her ikisi de belirli bir bölgeyi hücrelere böler ve bu hücreleri indekslemek için 64 bitlik tamsayılar kullanır.
Ancak asıl fark hücrelerin şeklindedir; S2 kare şekilli hücreleri kullanırken H3 altıgen şekilli hücreleri kullanır. Bazı uygulamalar için H3 daha iyi performans sunabilir. Ancak genel olarak her iki kütüphane de yeterli olacaktır. Bu yazımızda S2 kullanacağız ancak benzer işlevleri H3 kullanarak da gerçekleştirebilirsiniz.
Hücre Düzeyleri: Hiyerarşi, büyük bölgelerden küçük hassas alanlara kadar farklı ayrıntı düzeylerine izin verir. Her seviye farklı bir çözünürlüğü temsil eder:
Seviye 0: Dünya yüzeyinin önemli bir bölümünü kaplayan en büyük hücreler.
Daha Yüksek Seviyeler: Hücreler giderek daha küçük çeyreklere bölünür. Örneğin, Düzey 1 hücrelerinin her biri dört Düzey 2 hücreye bölünür ve bu böyle devam eder.
Çözünürlük ve Alan: Daha yüksek seviyeler, daha iyi çözünürlüklere ve daha küçük hücre alanlarına karşılık gelir. Bu hiyerarşi, farklı ayrıntı düzeylerinde hassas indekslemeye ve sorgulamaya olanak tanır.
Aşağıdaki tablo, karşılık gelen alanlarla birlikte çeşitli hücre seviyelerini göstermektedir.
seviye | minimum alan | maksimum alan | ortalama alan | birimler | Hücre sayısı |
---|---|---|---|---|---|
00 | 85011012.19 | 85011012.19 | 85011012.19 | km2 | 6 |
01 | 21252753.05 | 21252753.05 | 21252753.05 | km2 | 24 |
02 | 4919708.23 | 6026521.16 | 5313188.26 | km2 | 96 |
03 | 1055377.48 | 1646455.50 | 1328297.07 | km2 | 384 |
04 | 231564.06 | 413918.15 | 332074.27 | km2 | 1536 |
05 | 53798.67 | 104297.91 | 83018.57 | km2 | 6K |
06 | 12948.81 | 26113.30 | 20754.64 | km2 | 24K |
07 | 3175.44 | 6529.09 | 5188.66 | km2 | 98 bin |
08 | 786.20 | 1632,45 | 1297.17 | km2 | 393 bin |
09 | 195.59 | 408.12 | 324.29 | km2 | 1573K |
10 | 48.78 | 102.03 | 81.07 | km2 | 6 milyon |
11 | 12.18 | 25.51 | 20.27 | km2 | 25 milyon |
12 | 3.04 | 6.38 | 5.07 | km2 | 100 milyon |
13 | 0,76 | 1.59 | 1.27 | km2 | 402 milyon |
14 | 0.19 | 0.40 | 0,32 | km2 | 1610M |
15 | 47520.30 | 99638.93 | 79172.67 | m2 | 6B |
16 | 11880.08 | 24909.73 | 19793.17 | m2 | 25B |
17 | 2970.02 | 6227.43 | 4948.29 | m2 | 103B |
18 | 742.50 | 1556,86 | 1237.07 | m2 | 412B |
19 | 185.63 | 389.21 | 309.27 | m2 | 1649B |
20 | 46.41 | 97.30 | 77.32 | m2 | 7T |
21 | 11.60 | 24.33 | 19.33 | m2 | 26T |
22 | 2.90 | 6.08 | 4.83 | m2 | 105 ton |
23 | 0,73 | 1.52 | 1.21 | m2 | 422T |
24 | 0,18 | 0,38 | 0.30 | m2 | 1689T |
25 | 453.19 | 950.23 | 755.05 | cm2 | 7e15 |
26 | 113.30 | 237.56 | 188.76 | cm2 | 27e15 |
27 | 28.32 | 59.39 | 47.19 | cm2 | 108e15 |
28 | 7.08 | 14.85 | 11.80 | cm2 | 432e15 |
29 | 1.77 | 3.71 | 2.95 | cm2 | 1729e15 |
30 | 0,44 | 0,93 | 0,74 | cm2 | 7e18 |
Sağlanan tablodan, S2 kullanarak 0,44 cm^2'ye kadar haritalama hassasiyeti elde edebileceğiniz açıktır. Bir S2 hücresinin her karesinde, aynı ebeveyni paylaşan bir alt hücre bulunur, bu da hiyerarşik bir yapıya işaret eder. Hücrenin düzeyi statik bir değer olabilir (tüm hücrelere uygulanan aynı düzey) veya S2'nin hangi çözünürlüğün en iyi şekilde çalıştığına karar vereceği dinamik olabilir.
Bir örnekle başlayalım. Seattle bölgesi için yakınlık hizmeti benzeri özellikler sağlayan bir uygulama yazdığımızı düşünün. Verilen civardaki kafelerin bir listesini döndürmek istiyoruz. Bu işlemleri gerçekleştirmek için bu görevi 4 alt göreve ayıracağız:
Bir Google haritasını yüklemek için gmplot kütüphanesini kullanırız. Bu kitaplığın yüklenmesi için bir Google Haritalar API anahtarı gerekiyor. API anahtarını oluşturmak için buradaki talimatları izleyin.
import gmplot import const # plot seattle with zoom level 13 gmap = gmplot.GoogleMapPlotter(47.6061, -122.3328, 13, apikey=const.API_KEY) # Draw the map to an HTML file: gmap.draw('map.html')
Yukarıdaki kod aşağıda gösterildiği gibi bir harita.html dosyası oluşturur:
Artık haritamız olduğuna göre haritalar için bazı S2 hücreleri çizelim:
from s2 import * import gmplot # plot seattle with zoom level 13 gmap = gmplot.GoogleMapPlotter(47.6061, -122.3328, 13, apikey=const.API_KEY) areatobeplotted = [ (47.64395531736767,-122.43597221319135), (47.51369277846956,-122.43597221319135), (47.51369277846956,-122.24156866779164), (47.64395531736767,-122.24156866779164), (47.64395531736767,-122.43597221319135) ] region_rect = S2LatLngRect( S2LatLng.FromDegrees(47.51369277846956,-122.43597221319135), S2LatLng.FromDegrees(47.64395531736767, -122.24156866779164)) coverer = S2RegionCoverer() coverer.set_min_level(8) coverer.set_max_level(15) covering = coverer.GetCovering(region_rect) geoms = 0 for cellid in covering: new_cell = S2Cell(cellid) vertices = [] for i in range(0, 4): vertex = new_cell.GetVertex(i) latlng = S2LatLng(vertex) vertices.append((latlng.lat().degrees(), latlng.lng().degrees())) gmap.polygon(*zip(*vertices), face_color='pink', edge_color='cornflowerblue', edge_width=5) geoms+=1 gmap.polygon(*zip(*areatobeplotted), face_color='red', edge_color='green', edge_width=5) print(f"Total Geometries: {geoms}") gmap.draw('/tmp/map.html')
Output: Total Geometries: 273
Yukarıdaki kodda, öncelikle Google Harita çizicisini Seattle bölgesi etrafında ortalıyoruz. S2RegionCoverer
, bölge kapsayıcıyı minimum seviye 8 ile maksimum seviye 15 arasında dinamik seviyelere sahip olacak şekilde başlatırız. Bu, S2'nin en iyi uyum için tüm hücreleri dinamik olarak belirli hücre boyutlarına sığdırmasına olanak tanır. GetCovering
yöntemi, Seattle alanının etrafındaki bir dikdörtgenin kaplamasını döndürür.
Daha sonra, her bir hücre üzerinde yinelemeler yaparak hücrelerin köşelerini hesaplıyoruz ve bunları harita üzerinde işaretliyoruz. Oluşturulan hücre sayısını 273 civarında tutuyoruz. Son olarak giriş dikdörtgenini kırmızı renkle çiziyoruz. Bu kod, S2 hücrelerini Seattle haritası üzerinde /tmp/map.html
adresinde aşağıda gösterildiği gibi çizecektir:
S2 hücre tanımlayıcılarıyla birlikte kahve dükkanlarının bir veritabanını oluşturalım. Bu hücreleri istediğiniz herhangi bir veritabanında saklayabilirsiniz. Bu eğitim için bir SQLite veri veritabanı kullanacağız. Aşağıdaki kod örneğinde Id
, name
ve cell_id
olmak üzere 3 alanlı CoffeeShops
tablosu oluşturmak için SQLite veritabanına bağlanıyoruz.
Önceki örneğe benzer şekilde hücreleri hesaplamak için S2RegionCoverer
kullanıyoruz ancak bu sefer noktaları çizmek için sabit bir seviye kullanıyoruz. Son olarak hesaplanan kimlik bir dizeye dönüştürülür ve veritabanında saklanır.
import sqlite3 from s2 import S2CellId,S2LatLng,S2RegionCoverer # Connect to SQLite database conn = sqlite3.connect('/tmp/sqlite_cells.db') cursor = conn.cursor() # Create a table to store cell IDs cursor.execute('''CREATE TABLE IF NOT EXISTS CoffeeShops ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, cell_id TEXT )''') coverer = S2RegionCoverer() # Function to generate S2 cell ID for a given latitude and longitude def generate_cell_id(latitude, longitude, level=16): cell=S2CellId(S2LatLng.FromDegrees(latitude, longitude)) return str(cell.parent(level)) # Function to insert cell IDs into the database def insert_cell_ids(name,lat,lng): cell_id = generate_cell_id(lat, lng) cursor.execute("INSERT INTO CoffeeShops (name, cell_id) VALUES (?, ?)", (name, cell_id)) conn.commit() # Insert cell IDs into the database insert_cell_ids("Overcast Coffee", 47.616656277302155, -122.31156460382837) insert_cell_ids("Seattle Sunshine", 47.67366852914391, -122.29051997415843) insert_cell_ids("Sip House", 47.6682364706238, -122.31328618043693) insert_cell_ids("Victoria Coffee",47.624408595334536, -122.3117362652041) # Close connection conn.close()
Bu noktada kahvehanelerin hücre seviyesi için seçilen çözünürlüğe göre belirlenen hücre kimlikleriyle birlikte saklandığı bir veritabanına sahibiz.
Son olarak Üniversite Bölgesi bölgesindeki kafeleri sorgulayalım.
import sqlite3 from s2 import S2RegionCoverer,S2LatLngRect, S2LatLng # Connect to SQLite database conn = sqlite3.connect('/tmp/sqlite_cells.db') cursor = conn.cursor() # Function to query database for cells intersecting with the given polygon def query_intersecting_cells(start_x,start_y,end_x,end_y): # Create S2RegionCoverer region_rect = S2LatLngRect( S2LatLng.FromDegrees(start_x,start_y), S2LatLng.FromDegrees(end_x,end_y)) coverer = S2RegionCoverer() coverer.set_min_level(8) coverer.set_max_level(15) covering = coverer.GetCovering(region_rect) # Query for intersecting cells intersecting_cells = set() for cell_id in covering: cursor.execute("SELECT name FROM CoffeeShops WHERE cell_id >= ? and cell_id<=?", (str(cell_id.range_min()),str(cell_id.range_max()),)) intersecting_cells.update(cursor.fetchall()) return intersecting_cells # Query for intersecting cells intersecting_cells = query_intersecting_cells(47.6527847,-122.3286438,47.6782181, -122.2797203) # Print intersecting cells print("Intersecting cells:") for cell_id in intersecting_cells: print(cell_id[0]) # Close connection conn.close()
Output: Intersecting cells: Sip House Seattle Sunshine
Aşağıda hücrelerin görsel bir temsili bulunmaktadır. Kısalığı korumak için aşağıdaki görselleştirme kodu eklenmemiştir.
Tüm alt ve üst hücreler bir öneki paylaştığından, bu iki değer arasındaki tüm hücreleri elde etmek için minimum ve maksimum arasındaki hücre aralıklarını sorgulayabiliriz. Örneğimizde kahve dükkanını sorgulamak için aynı prensibi kullanıyoruz
Bu makalede, jeo-uzaysal sorgulamayı desteklemeyen veritabanlarında coğrafi-uzaysal verileri depolamak ve sorgulamak için coğrafi-indekslemenin nasıl kullanılacağını gösterdik. Bu, 2 nokta arasındaki yönlendirmenin hesaplanması veya en yakın komşuların alınması gibi birden fazla kullanım durumunu kapsayacak şekilde genişletilebilir.
Tipik olarak, coğrafi indeksli veritabanı sorgulaması için veriler üzerinde bazı ek işlem sonrası işlemler gerçekleştirmeniz gerekir. Düğümü bunaltmadığımızdan emin olmak için sorgulama ve işlem sonrası mantığının dikkatli bir şekilde değerlendirilmesi gerekir.