dersblog

Haritalamak

Folium

Bu haritalama sistemi ile dünyanın herhangi bir yerini alıp, istenen odaklama seviyesinde göstermek, üzerini işaretlemek mümkün, ayrıca haritanın üzerinde isteğe göre çizgiler, çemberler vs çizilmesine izin veren çağrılar var. Folium arka planda bir Javascript kütüphanesi Leaflet kullanır, bu sebeple haritaları HTML olarak kaydediyorsunuz, bu HTML içine bakıldığında Leaflet çağrıları orada görülüyor. Leaflet son derece basit, hızlı işleyecek şekilde yazılmış, verisini arka planda direk OpenStreetMap dosyalarını okuyarak alıyor, onların gerekli yerlerini, hangisini alacağını arka planda hallediyor. Programcıya tek kalan harita merkezini, ek bazı görsel ayarları yapmak.

Not: Elde edilen HTML haritası etkileşimli (interactive) çalışıyor. Büyütme, küçültme, haritayı sağa sola kaydırma bu HTML sayfasında kullanıcı tarafından yapılabilir hala. Yani tek ölü bir görüntü dosyası almıyorsunuz.

Kurmak icin pip install folium

En basit kullanim belli noktalara yuvarlak isaretler koymak,

İşaretler

Harita üzerine konan işaretler içine tıklanabilir URL bağlantıları konabilir. Ayrıca bu işaretler fare ile üzerinde durunca bir araç ipucu (tooltip) gösterebilir.

m = folium.Map(location=[45.372, -121.6972], zoom_start=12, tiles="Stamen Terrain")
tooltip = "Click me!"
folium.Marker(
    [45.3288, -121.6625], popup="<i>Mt. Hood Meadows</i>", tooltip=tooltip
).add_to(m)
m.save("/tmp/index.html")

Üstteki kodda görülen popup içine a href ifadesi de konulabilir, böylece fare üstünde olunca önce ipucu, ona tıklanınca çıkan bölgede tıklanabilir bir URL bağlantısı olacaktır. Biz ayrıca bu bağlantıyı target='_blank' rel='noopener noreferrer' ile yarattık, böylece tıklama bizi tarayıcıda ayrı bir tab'e götürüyor.

Kapalı Alanlar, Poligon

import folium
m = folium.Map(location=[lat1,lon1], tiles='Stamen Terrain', zoom_start=7)
coordinates=[(43,35),(44,27),(45,32)]
folium.Polygon(locations=coordinates,weight=2,color = 'blue').add_to(m)
m.save("/tmp/index.html")

Eğer kapalı alan yerine sadece başlangıç ve bitiş kordinatları arasında bir parçalı çizgi çizilsin istiyorsak

folium.PolyLine(locations=coordinates, color="blue").add_to(map)

gibi bir çağrı gerekirdi.

Başlık Eklemek

import folium

lat1,lon1 = 43.4521793912598, 25.597125946810632
lat2,lon2 = 44.572754823623114, 27.250000972553345
m = folium.Map(location=[lat1,lon1], tiles='Stamen Terrain', zoom_start=7)
folium.CircleMarker(location=(lat2,lon2),
                            fill_color='blue', radius=10, rotation=1.5).add_to(m)

title_html = "<h3>Title</h3>"
m.get_root().html.add_child(folium.Element(title_html))
m.save("/tmp/index.html")

lat1,lon1 merkezli bir harita çizdik, odak seviyesi 7, ve lat1,lon1 den başlayıp lat2,lon2 de biten bir çizgi çektik, ve çizgi sonuna ufak bir daire koyduk.

Ek bilgiler [5],[6],[7] de bulunabilir,

Cartopy

Kullanisli bir paket; fakat isletim sistemi seviyesinde GEOS paketinin kurulmasi gerekli, bu bazi barindirilan, idareli bulut sistemlerinde problem cikartabilir, cunku bu tur servislerde direk isletim sistemine erisim her zaman mumkun olmayabiliyor.

sudo apt install libproj-dev proj-data proj-bin libgeos-dev

pip install cartopy

pip uninstall shapely

pip install shapely --no-binary shapely

Dünya haritası üzerinde bir nokta

import cartopy.crs as ccrs
import cartopy
fig = plt.figure(figsize=(5, 2))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_global()
ax.stock_img()
ax.coastlines()
ax.plot(42.0, 21.53, 'ro', transform=ccrs.PlateCarree())

plt.savefig('har1.png')

Eger daha yakindan gostermek istersek, ilgilendigimiz bolge uzerinden bir kutu yaratabiliriz,

import cartopy.crs as ccrs
import cartopy

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_global()
ax.stock_img()
ax.coastlines()
ax.plot(42.0, 21.53, 'ro', transform=ccrs.PlateCarree())
ax.set_extent([30, 50, 10, 30])
plt.savefig('har3.png')

Ülke ismi bazlı tüm ülkeyi renklendirmek,

import matplotlib.pyplot as plt
import cartopy.io.shapereader as shpreader
import cartopy.crs as ccrs
import cartopy.feature as cfeature

def area(ax, iso, clr) :
    shp = shpreader.natural_earth(resolution='10m',category='cultural',
                                  name='admin_0_countries')
    reader = shpreader.Reader(shp)
    for n in reader.records() :
        if n.attributes['ADM0_A3'] == iso: 
            ax.add_geometries(n.geometry, ccrs.PlateCarree(), facecolor=clr) 
    return ax

fig = plt.figure(figsize=(5, 2))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax = plt.axes(projection=ccrs.PlateCarree())
ax.stock_img()
ax.coastlines()
area(ax, "USA", "red")
plt.savefig('har2.png')

Izgara Yaratmak

İki köşesi tanımlanan bir dikdörtgen üzerinden bir izgara tanımlayabiliriz,

import simplegeomap as sm, numpy as np, matplotlib.pyplot as plt

from pygeodesy.sphericalNvector import LatLon
res1 = LatLon(36.52259447316748, 27.612981046240638) # sol alt
res2 = LatLon(41.05628025861666, 42.58542464923075) # sag ust

41.40662366577505, 27.255002668187043

lowlat = np.min([res1.lat,res2.lat])
lowlon = np.min([res1.lon,res2.lon])
hilat = np.max([res1.lat,res2.lat])
hilon = np.max([res1.lon,res2.lon])

x = np.linspace(lowlon,hilon,7)
y = np.linspace(lowlat,hilat,4)

xx,yy = np.meshgrid(x,y)

Eğer bir merkez nokta etrafında tek bir mesafe uzaklığı baz alıp bir ızgara yaratmak istiyorsak,

p1 = LatLon(clat,clon)
EARTH_RAD = 6371
upright = p1.destination (dist, bearing=45, radius=EARTH_RAD)
lowleft = p1.destination (dist, bearing=225, radius=EARTH_RAD)
latmin = np.min([lowleft.lat, upright.lat])
latmax = np.max([lowleft.lat, upright.lat])
lonmin = np.min([lowleft.lon, upright.lon])
lonmax = np.max([lowleft.lon, upright.lon])
x = np.linspace(lonmin,lonmax,N)
y = np.linspace(latmin,latmax,N)
xx,yy =  np.meshgrid(x,y)
latints = np.unique(x.astype(np.int))
lonints = np.unique(y.astype(np.int))

Bu kod 45 derece kuzeydoğu 225 derece güneybatıya dist mesafesi kadar adım atıp o noktaları sağ üst, sol alt köşe olarak kabul etti ve bu dikdörtgeni baz alıp bir N*N noktalı bir ızgara yarattı. Ayrıca o ızgara noktalarındaki tüm kordinatların enlem, boylam değerlerinin tam sayı bazlı bir listesini de hesapladı, bu bilgi pek çok uygulamada lazım olabiliyor (mesela tam sayı enlem/boylam bazlı bazı bilgiler önbellekte, tabanda tutulmuş olabilir, ve üstteki tam sayılar ile bu bölgesel bilgiler tabandan alınabilir).

Oklar, Quiver

Cartopy haritaları üzerinde klasik matplotlib komutlarını hala kullanabiliriz, enlem, boylam kordinatları y ve x eksenleri haline geliyor, ve gerisi bildiğimiz gibi. Mesela ok çizmek için kullanılan quiver hala geçerli, mesela o ızgara noktalarındaki rüzgar yönü verisi elimizde olsaydı, bunu harita üzerinde grafikleyebilirdik. Altta bu veri sin ve cos üzerinden uyduruk şekilde yaratıldı,

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_global()
ax.stock_img()
ax.coastlines()
u = np.sin(xx.flatten()*2)
v = np.cos(yy.flatten()*3)
ax.quiver(xx.flatten(), yy.flatten(), u, v)
ax.set_extent([28, 31, 40, 42])
plt.savefig('har5.png')

Eğer ana enlem, boylamları kordinate eksenleri üzerinde görmek istersek, basmadan önce

gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=0)

uygulayabiliriz.

Katmanlar

Üstteki karayı ve denizleri gösteren renklendirme sonradan uygulanan bir katmandır. Cartopy bu şekilde pek çok farklı bilgiyi katman olarak haritalara uygulayabilir. Mesela yol, sehir bilgileri de birer katman olarak alttaki gibi alınabilir,

import cartopy.crs as ccrs
import cartopy
import cartopy.io.img_tiles
lat,lon=40.84343206497589, 29.926342357515754
fig = plt.figure()
imagery = cartopy.io.img_tiles.OSM()
ax = fig.add_subplot(projection=imagery.crs)
zoom = 10
ax.add_image(imagery, zoom)
ax.plot(lon, lat, 'ro', transform=ccrs.PlateCarree())
EXT = 0.1
ax.set_extent([lon-EXT, lon+EXT, lat-EXT, lat+EXT])
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=0)
plt.savefig('har6.png')

SHP ile Kendimiz Sınır Çizelim

Bazen Cartopy ve hatta Folium bile uygulamamıza biraz yük getirebiliyor, mesela Cartopy kurulumu için arka planda GDAL gerekebilir, bu kütüphane arka planda işletim sistemi ek kurulumu gerektirir, ve bulut ortamında uygun olmayabilir. Folium hafif olsa da arka planda bağlantı gerektiriyor, çünkü zoom yapınca ek bilgiler servisten alınıyor.

Eğer bize gayet basit, ülke sınırlarını bir kerede ağ bağlantısı olmadan pat diye çizebilecek bir kod gerekiyorsa, bu bilgileri taşıyan dosya bazlı veri tabanları kullanabiliriz. PyShp ile SHP bazlı bu tabanlara ulaşmak mümkündür. Mesela ülke sınırları [8]'de var, TM_WORLD_BORDERS-0.3.zip indirilir, açılır, /tmp altında .shp ve .dbf dosyaları olsun (ikisi de lazım),

import shapefile
sf = shapefile.Reader("/tmp/TM_WORLD_BORDERS-0.3.shp", encoding = "ISO8859-1")

Kaç ülke var burada,

r = sf.records()
countries = sf.shapes()
print (len(countries))
246

Bu ülkelerden indis 2'de olana bakalım,

idx = 2
country = countries[idx]
name = r[idx]
print (name)
Record #2: ['AJ', 'AZ', 'AZE', 31, 'Azerbaijan', 8260, 8352021, 142, 145, 47.395, 40.43]

Azerbeycan burada.

Peki ülkenin sınırları nerede?

print (len(country.points))
print (country.parts)
871
[0, 108, 118, 131, 859]

Ülke sınır noktaları points ile alınıyor, bir enlem/boylam listesidir, kabaca bir polygonun köşe noktalarıdır. Fakat ülkeler bazen tek kesintisiz bir parça olmayabiliyor, adalar, ya da diğer ülkelerin içinde ufak parçalar vs var ise, bunları farklı poligon öbekleri olarak temsil etmek lazım. Üstteki points içinde tüm parçalar için noktalar var, hangi öbeğin nerede başlayıp bittiğini bilmek gerekiyor sadece, parts içinde bu bilgi var [9]. Mesela 1'inci parça 0'dan başlayıp 107 indisine (dahil olmak üzere) uzanıyor, 108 nokta var. Sonraki 108'den başlayıp 117'ye gidiyor, vs.

Bu mantığı bir fonksiyon içine koyalım, ve indis 2 ve indis 4'teki ülkeleri grafikleyelim,

def plot_country(idx,color='r'):
   country = countries[idx]
   name = r[idx]
   print (name)
   bounds = list(country.parts) + [len(country.points)]
   print (bounds)
   for previous, current in zip(bounds, bounds[1:]):    
       geo = [[x[0],x[1]] for x in country.points[previous:current]]
       if len(geo) < 1: continue
       geo = np.array(geo)
       if geo.shape[0] > 0:
           plt.plot(geo[:,0],geo[:,1],color)

plot_country(2,'r')
plot_country(4,'b')

plt.savefig('harita-az-arm.png')
Record #2: ['AJ', 'AZ', 'AZE', 31, 'Azerbaijan', 8260, 8352021, 142, 145, 47.395, 40.43]
[0, 108, 118, 131, 859, 871]
Record #4: ['AM', 'AM', 'ARM', 51, 'Armenia', 2820, 3017661, 142, 145, 44.563, 40.534]
[0, 12, 395, 408, 418]

Azerbeycan ve Ermenistan'ı grafiklemiş olduk.

Not: Üstte plot çağrısının çizgisel kullanımına çağrı yapıldı, bazılarımızın bildiği gibi plot(10,20,'.') x=10,y=20 kordinatlarına bir nokta koyar, ama bir x kordinat ve y kordinat listesi plot çağrısına geçilirse, bu tür çağrı verili noktalar arasına düz çizgiler çek demektir.

Bir diğer ek, eğer o çizgilerle çevirdiğimiz bölgelerin içini bir renkle doldurmak isteseydik, mesela elimizde bir göl için poligon noktaları varsa, plt.plot yerine plt.fill kullanırdık, bu çağrıya geçilen üçüncü parametre renk ile objenin içi doldurulur.

Kaynaklar

[1] https://rabernat.github.io/researchcomputing2018/maps-with-cartopy.html

[2] https://scitools.org.uk/cartopy/docs/latest/gallery/global_map.html#sphx-glr-gallery-global-map-py

[3] https://github.com/SciTools/cartopy/issues/1303

[4] Renk isimleri - https://matplotlib.org/3.1.0/gallery/color/named_colors.html

[5] https://python-visualization.github.io/folium/quickstart.html

[6] https://stackoverflow.com/questions/39093250/is-it-possible-to-draw-lines-with-arrowheads-in-a-folium-map

[7] https://www.geeksforgeeks.org/stamen-toner-stamen-terrain-and-mapbox-bright-maps-in-python-folium/

[8] https://thematicmapping.org/downloads/world_borders.php

[9] Sileika, Pro Python System Administration

[10] [Yükseklik (Elevation) Verileri](2019/04/elevation.html)


Yukarı