dersblog

En Kısa Yol Algoritması, Yol Ağı, OSMNX

OpenStreetMap verisi tüm dünya coğrafi verilerini kapsayan açık kaynak, herkesin bilgi ekleyebildiği devasa bir veri deposudur, bedava olarak paylaşılır. İçinde yollar, restoranlar, dükkanlar, önemli yerler gibi pek çok coğrafi bilgileri içerir. Fakat veriyi işlemek bazıları için zor olabilir.

Bu verinin daha rahat işlenmesini sağlayan bir paket OSMNX. Özellikle yol ağ yapısını rahat şekilde indirilmesini ve çizit (graph) olarak doğru şekilde gelmesini sağlıyor. Çizitler bilindiği gibi matematiğin bir dalı, bize düğüm-bağlantı yapılarını işleyen algoritmalar sağlar, tabii ki bir yol ağı çizit teorisinin en bariz uygulama alanıdır, düğümler duraklar, köşe başları vs olabilir bağlantılar onlar arasındaki yollar olacaktır.

OSMNX kullanıcının tanımladığı bölgeler içindeki yol yapısını döndürme kabiliyetine sahiptir, ve bu veriyi diskte önbellekleme yaparak saklayabilir, böylece aynı bölge için sonraki yükleme çağrılarının OSM'e bağlanması gerekmez. Eğer veride çizit yapısına uymayan yerler varsa bunlar döndürülmeden önce tamir edilir.

Kurmak icin Ubuntu uzerinde gdal-bin, libgdal-dev, libspatialindex-dev apt install ile kurulmali. Sonra pip install ile scikit-learn ve osmnx.

Örnek olarak [1]'deki yere bakalım,

import matplotlib.pyplot as plt
import osmnx as ox

ox.config(use_cache=True, cache_folder='/tmp/osmnx')

north, south, east, west = 37.79, 37.78, -122.41, -122.43

G = ox.graph_from_bbox(north, south, east, west, network_type="walk")
fig, ax = ox.plot_graph(G, node_color="r",show=False, figsize=(15,7))
plt.savefig('osmnx-01.jpg',quality=50)

cache_folder ile önbellek dosyalarının yazılacağı yer tanımlandı. Üstteki çağrı için baktık 30 520ecdb05972a5893b8a541266157cd0b30a6381.json diye bir dosya oraya yazılmış, büyüklüğü 1.8 MB.

graph_from_bbox ile belli kuzey, güney, doğu, batı uç noktalarının oluşturduğu kutunun içine düşen yol ağını aldık, fakat tek bir nokta verip ona belli uzaklıktaki tüm yol ağını da alabilirdik, mesela graph_from_point((37.79, -122.41), dist=750 ile verili noktanın 750 metre çevresindeki ağ alınabilir.

import osmnx as ox

ox.config(use_cache=True, cache_folder='/tmp/osmnx')

G = ox.graph_from_point((37.79, -122.41), dist=750, network_type="walk")

Network tipi network_type ile tanımlanıyor, walk, drive, bike değerleri geçilebiliyor, bu değerler sırasıyla arabaların geçebildiği, ya da bisiklet, ya da yürünebilen yol yapılarını döndürür. Uygulamanın ihtiyacına göre farklı ağ yapıları gerekebilir (arabaların geçebildiği her yol bisiklete uygun olmayabilir mesela), bu sebeple bu seçenek gerekli.

Çağrı yapıldı, ve artık geri döndürülen G değişkeni içinde yol yapısı var, buna düğümlerden oluşan bir liste olarak erişilebilir, mesela 0'inci ve 20'inci düğümler

print (list(G)[0])
print (list(G)[20])
65281835
65295291

İlk 10 düğüm

print (list(G)[:10])
[65281835, 65281838, 65287183, 65287185, 65290169, 65290173, 65290750, 65290756, 65291738, 65291741]

Kısa yol bulmaya gelelim; ilk önce eldeki başlangıç ve bitiş coğrafi kordinatlarına en yakın çizit düğümlerini bulmak lazım,

origin = (37.784825495166544, -122.40208526405367)
destination = (37.79584463577157, -122.40724290129684)
origin_res = ox.get_nearest_node(G, origin,method='euclidean',return_dist=True)
destination_res = ox.get_nearest_node(G, destination,method='euclidean',return_dist=True)
print (origin_res)
print (destination_res)
(5554084244, 5.332404779362496)
(7233579607, 15.949918872077847)

Sonuçlar bir Python tüpü (tuple) olarak verildi, birinci değer düğüm kimliği, ikincisi bizim verdiğimiz kordinata olan metre olarak yakınlık. Şimdi bu ID'ler ile yine OSMNX içinde mevcut olan en kısa yol algoritmasını işletiyoruz,

route = ox.shortest_path(G, origin_res[0], destination_res[0])
route[:10]
Out[1]: 
[5554084244,
 5554084275,
 5554084256,
 5554084269,
 995847660,
 5554084309,
 1270745639,
 275431510,
 5554083274,
 1332541752]

Geçilecek yolun ilk 10 düğümü listeledik, en kısa yol bu noktalardan oluşuyor. Noktalar hakkında daha detaylı bilgiyi G çizit objesinden alabiliriz, mesela enlem, boylam değerleri bu objeye sorulabiliyor, ilk noktayı soralım,

G.nodes[route[0]]
Out[1]: {'y': 37.7848105, 'x': -122.4021429, 'street_count': 3}

Tüm noktaları enlem/boylam listesine çevirelim,

coords = [[G.nodes[r]['y'],G.nodes[r]['x']] for r in route]
print (coords)
[[37.7848105, -122.4021429], [37.7845016, -122.4030429], [37.7846474, -122.4031738], [37.7847412, -122.4032112], [37.784882, -122.403207], [37.7850366, -122.4034089], [37.7850702, -122.4034505], [37.7851231, -122.4035159], [37.7851702, -122.4035743], [37.7855328, -122.40403], [37.7862746, -122.4049529], [37.7864463, -122.404735], [37.7865865, -122.4048136], [37.7867459, -122.4048752], [37.7868265, -122.4049267], [37.7868355, -122.4048551], [37.7869147, -122.404873], [37.7877685, -122.4050568], [37.788237, -122.4051523], [37.7887033, -122.4052473], [37.7891462, -122.4053364], [37.7892111, -122.4053494], [37.7893261, -122.4053725], [37.7896352, -122.4054347], [37.7901017, -122.4055285], [37.7905698, -122.4055991], [37.7915047, -122.4057597], [37.7917761, -122.4058148], [37.7923696, -122.4059355], [37.7924582, -122.4059535], [37.7925624, -122.4059745], [37.7933866, -122.4061403], [37.793828, -122.4062291], [37.7942666, -122.4063174], [37.7950988, -122.4064848], [37.7951483, -122.4064948], [37.7952034, -122.4065059], [37.7959786, -122.4066619], [37.7959709, -122.4067263], [37.7959246, -122.4070922]]

Kordinatları bir Folium haritasında gösterebiliriz artık,

import folium
map = folium.Map(location=origin,zoom_start=16,control_scale=True)
folium.Marker(origin, popup="Park").add_to(map)
folium.Marker(destination, popup="Çin Mahallesi").add_to(map)
folium.PolyLine(locations=coords, color="red").add_to(map)
map.save('direction1.html')

Tarif

Haritada görülen kırmızı çizgiler yürünüş için en kısa yolu gösteriyor.

Ham OSM Dosyaları, Format Değişimi

Eger bir OSM dosyasini indirip direk kendi yerel diskimizden okutmak istiyorsak graph_from_xml cagrisi var. [7]'den mesela ufak bir dosya Seychelles indirelim, .osm.bz2 dosyasi

import osmnx as ox

G = ox.graph_from_xml("seychelles-latest.osm.bz2")

ile okuyabiliriz. Eğer bu çiziti başka bir formatta yazmak istiyorsak, mesela her satır "düğüm1 - düğüm2 - değerler" olacak şekilde, yani 1'inci düğümün bağlı olduğu 2'inci düğüm ve aralarındaki kenar ağırlığı her satıra yazılacak, bunu

import networkx as nx

nx.write_edgelist(G, "test.edgelist.gz",data=["oneway","length"])

ile yapabiliriz. Her düğümün coğrafi kordinatını .osm dosyasını direk tarayarak yapabiliriz, okunabilir bir dosya bu, ve lat, lon değerleri her düğüm için var.

Dezavantajlar

Bu paketin hafıza idare problemi var. Eğer .osm dosyası çok büyük ise graph_from_xml için yerel bilgisayarın hafızası yetmeyebilir, mesela Lüksemburg gibi ufak bir harita için bile 4 GB yeterli olmadı.

Bölge tanımlayıp o bölge için gerekli OSM bilgilerini indirmek, aynı anda onu işleyip başka bir formata döndürmek teoride iyi fakat pratikte network yavaşlığı, kesilmesi gibi envai türden problemde bizi yolda bırakabilir. Bazı kavramları öğrenmek için bu paket faydalı olsa da nihai bir uygulama için eksikler olacaktır.

Kaynaklar

[1] Geoff Boeing

[2] OSRM Yol Tarifi

[3] NetworkX Multidigraph

[4] OSMNX Belge 1

[5] OSMNX Belge 2

[6] OSMNX Belge 3

[7] http://download.geofabrik.de


Yukarı