Geospatial Analytics'e Sürükleyici Bir Giriş
Singapur'da en uygun konuma sahip daireleri aramak
2021'de ekibim ve ben, 400'den fazla takımın katıldığı bir veri görselleştirme yarışmasını kazandık. Gönderimimiz, birincil arama kriteri olarak olanaklara erişilebilirliğe odaklanarak daire aramak için yeni bir yol keşfetti.
Bu proje beni bu makalede paylaşacağım bir dizi jeo-uzamsal analiz tekniğiyle tanıştırdı. Jeo -uzamsal analize sürükleyici bir giriş için takip edebilmeniz için GitHub sayfamda sunumumuz için kullanılan veri kümelerini ve Python kodunu da paylaştım .
İçindekiler: (bölüme atlamak için tıklayın)
1. Coğrafi Veri Setleri
2. Coğrafi Kodlama
3. Koordinat Referans Sistemleri
4. Görselleştirme
5. Analiz
6. Hepsini Bir Araya Koymak
Coğrafi Veri Kümeleri
Jeo uzamsal veri kümeleri, uzamsal bilgiyi (bir şeyin nerede olduğu) ve nitelik bilgisini (bir şeyin ne olduğu) entegre eder . Örneğin, Google Haritalar'daki trafik katmanı, her yol boyunca trafiğin konumunu ve hızını entegre eden bir veri kümesine dayanır.
Coğrafi veri kümeleri çeşitli biçimlerde gelir. Bu yazımda sıklıkla karşılaştığım iki dosya ile nasıl çalışılacağına değineceğim: şekil dosyaları ve KML dosyaları. Her iki durumda da Geopandas python kütüphanesinden faydalanacağız .
Şekil dosyaları (.shp)
Shapefile formatı, jeo-uzamsal analitik yazılımında pazar lideri olan Esri tarafından geliştirilmiştir. Şekil dosyaları, ArcGIS gibi Esri yazılımlarının yanı sıra Python ve Tableau gibi diğer platformlarla uyumludur.
Singapur'da daire ararken, birincil ulaşım modu olduğu için tren istasyonlarına yakınlık önemli bir husustur. Tren istasyonlarının konumunu buradan şekil dosyası olarak indirebilirsiniz . Şekil dosyalarında olduğu gibi, veri kümesinin bir dizi dosyadan (.shp, .shx, .dbf vb.) oluştuğunu fark edeceksiniz. Bu dosyaların aynı adı koruduğundan ve aynı klasörde saklandığından emin olun, aksi takdirde düzgün açılmazlar.
Şekil dosyaları, geopandas read_file işlevi kullanılarak okunabilir .
import geopandas as gpd
filepath = "<insert path to folder with shapefile>/MRTLRTStnPtt.shp"
mrt = gpd.read_file(filepath)
First 3 lines of mrt GeoDataframe
Anahtar Deliği İşaretleme Dili dosyaları (.kml)
KML biçimi başlangıçta Google Earth ile kullanılmak üzere geliştirilmiştir, ancak o zamandan beri uluslararası bir standart haline gelmiştir. En önemli avantajlarından biri, farklı geometrilerin (örn. noktalar ve çokgenler) bir karışımını depolayabilmeleridir, halbuki şekil dosyaları yalnızca homojen geometri tipini destekler.
KML, iç içe öğeler içeren etiket tabanlı bir yapı kullanır. Örneğin, aşağıda Singapur'daki süpermarketlerin konumlarının bir KML dosyasından bir alıntıdır ( buradan indirin ). Her bir <Placemark id=XXX>…</Placemark> etiketi çifti, bir uzamsal nesne, bu durumda bir süpermarket için verileri ayırır. İç içe geçmiş etiketler, bu nesne için çeşitli veri alanlarını etiketlemek için kullanılır.
Geopandas kullanarak kml dosyalarını okurken, bu etiketlerin yalnızca bir kısmı otomatik olarak sütunlara ayrıştırılır.
import geopandas as gpd
gpd.io.file.fiona.drvsupport.supported_drivers['KML'] = 'rw' # activate KML driver
filepath = "<insert path to folder with kml file>/supermarkets-kml.kml"
sup_mkt = gpd.read_file(filepath,driver="KML")
First 3 lines of sup_mkt GeoDataframe. ‘Description’ column requires further parsing
def parse_supmkt(string):
tags = ['LIC_NAME', 'BLK_HOUSE', 'STR_NAME', 'UNIT_NO', 'POSTCODE']
output = []
for tag in tags:
start = "<th>" + tag + "</th> <td>"
end = "</td>"
len_start = len(start)
i_start = string.find(start)
i_end = string.find(end,i_start+len_start)
entry = string[(i_start+len_start):i_end]
output.append(entry)
return output
supmkt_1 = sup_mkt.copy()
supmkt_1['Lic_Name'] = supmkt_1['Description'].apply(lambda x: parse_supmkt(x)[0])
supmkt_1['Address'] = supmkt_1['Description'].apply(lambda x: parse_supmkt(x)[1] + " " + parse_supmkt(x)[2]
+ ", #" + parse_supmkt(x)[3])
supmkt_1['Postcode'] = supmkt_1['Description'].apply(lambda x: parse_supmkt(x)[4])
supmkt_1.drop(columns=['Name','Description'],inplace=True)
First 3 lines of supmkt_1 GeoDataframe. Licensee name, Address and Postcode have now been parsed.
Coğrafi kodlama, adreslerin coğrafi koordinatlara dönüştürülmesidir (veya tersi) . Ham veri kümeleri bazen ilgi noktalarının konumunu sokak adreslerine göre tanımlar. Bir örnek, Singapur'daki apartman blokları hakkında bilgi sağlayan HDB Emlak Bilgileri veri kümesidir .
Noktalar arasındaki mesafelerin hesaplanması gibi doğru jeo uzamsal analiz için bu adreslerin coğrafi koordinatlara dönüştürülmesi gerekir. Bu dönüştürme genellikle sokak adreslerini API'lerini kullanarak çevrimiçi bir coğrafi kodlama hizmetine ileterek yapılır.
OneMap API ile coğrafi kodlama
Singapur'daki adresler için Singapur Kara Kurumu tarafından sağlanan OneMap Arama API'si ücretsiz ve doğru bir hizmettir.
Esasen her apartman bloğunun koordinatları, sokak adresini (veri setimizin 'blk_no' ve 'street' sütunlarının birleşimi) aşağıdaki işleve geçirerek elde edilebilir:
import requests
def getcoordinates(address):
req = requests.get('https://developers.onemap.sg/commonapi/search?searchVal='+address+'&returnGeom=Y&getAddrDetails=Y&pageNum=1')
resultsdict = eval(req.text) #evaluate as python expression
#keep only those with results
if len(resultsdict['results'])>0:
return resultsdict['results'][0]['LATITUDE'], resultsdict['results'][0]['LONGITUDE'], resultsdict['results'][0]['POSTAL'] #i believe the '[0]' returns the top entry
else:
pass
from tqdm import tqdm # to view progress bar
coordinateslist= []
failed_count = 0
addresslist = resi_addresses['addr'] # 'addr' is a concatenation of 'blk_no' and 'street'
print(f"Total no. of addresses: {len(addresslist)}")
for address in tqdm(addresslist, desc="Geocoding..."): # update progress bar at each iteration
try: #try executing the following
coordinates = getcoordinates(address)
if len(coordinates)>0:
coordinateslist.append(coordinates)
except: #if you receive an error, do this
failed_count += 1
print(f"No. of addresses with no coordinates (cummulative): {failed_count}")
coordinateslist.append(None)
df_coordinates = pd.DataFrame(coordinateslist)
df_combined = resi_addresses.join(df_coordinates)
df_combined = df_combined .rename(columns={0:'Latitude', 1:'Longitude', 2:'Postcode'}).drop(columns = ['addr'])
3 lines from df_combined. ‘blk_no’ and ‘street’ are from the original HDB Property Info dataset. ‘Latitude’, ‘Longitude’ and ‘Postcode’ were retrieved through geocoding.
Kullanılabilir başka birçok coğrafi kodlama API'si vardır. Geopy python kitaplığı, her bir API'nin benzersiz sözdizimini öğrenmek yerine , bu API'lerin birçoğunu standartlaştırılmış sözdizimi kullanarak uygulamanıza olanak tanır.
Nominatim, Geopy aracılığıyla sağlanan ücretsiz bir coğrafi kodlama hizmetidir. Küresel kapsamı nedeniyle OneMap'ten daha çok yönlüdür, ancak Singapur'daki adresler için daha doğru olduğu için bu proje için OneMap'i kullanmayı seçtim.
Koordinat Referans Sistemleri
Şimdiye kadar bu makalede coğrafi koordinatlara oldukça gevşek bir şekilde atıfta bulundum. Birçok farklı Koordinat Referans Sistemi (CRS) olduğunu ve koordinat değerlerinin dayandıkları CRS'ye göre yorumlanması gerektiğini unutmamak önemlidir . Farklı CRS'lerin farklı xy grid düzenleri, birimleri, başlangıç noktası/başlangıç noktası vb. vardır.
CRS'nin iki ana sınıfı vardır : coğrafi ve öngörülen CRS . Aşağıdaki tablo bunları karşılaştırmaktadır.
Mevcut CRS'yi Tanımlama
Şekil dosyaları ve kml dosyaları gibi jeo-uzamsal veri kümeleri genellikle CRS bilgileriyle gömülür. GeoDataframe olarak içe aktarıldıktan sonra CRS'leri kolayca okunabilir:
CRS atama
Bazen jeo uzamsal veri kümeleri gömülü bir CRS ile birlikte gelmez. Örneğin, HDB Özellik Bilgisi veri setini coğrafi olarak kodladığımızda, CRS bilgisini veri çerçevesine yerleştirmedik. Bunu yapmak için, öncelikle her bir nesnenin uzamsal bilgisini bir Shapely geometri sınıfı olarak depolamak için bir 'geometri' sütunu oluşturmamız gerekir.
import pandas as pd
from shapely.geometry import Point
filepath = "<insert filepath for geocoded HDB Property Info Dataset>"
resi_blks = pd.read_csv(filepath, usecols=['blk_no','street','Latitude','Longitude','Postcode'])
# Represent each apartment block as a Shapely geometry (point)
resi_blks['geometry'] = resi_blks.apply(lambda row: Point(row.Longitude, row.Latitude), axis=1)
First 3 lines of resi_blks dataframe, including the newly created ‘geometry’ column
import geopandas as gpd
resi_blks2 = gpd.GeoDataFrame(resi_blks,crs="EPSG:4326") #convert to GeoDataframe
resi_blks2.to_file("resi_blks2.shp") # save as shapefile
Yeniden projeksiyon, coğrafi koordinatların bir CRS'den diğerine dönüştürülmesi anlamına gelir. Aşağıdaki örnekte, daha doğru mesafe ölçümünü kolaylaştırmak için HDB özellik bilgimizi GeoDataframe'i EPSG 4326'dan (bir coğrafi CRS) EPSG 32648'e (Singapur için uygun bir CRS) yeniden tasarlıyoruz.
resi_blks2 = resi_blks2.to_crs("EPSG:32648")
Kapsamlı görselleştirme, pano oluşturma vb. için Tableau veya ArcGIS gibi daha uygun yazılımlar kullanmak isteyebilirsiniz. Ancak bunlar ücretsiz değil. Ek olarak, atılan coğrafi işleme adımlarının mantıklı sonuçlar döndürdüğünü doğrulayabilmeniz için verilerinizi python ortamında görselleştirmek genellikle yararlıdır . Bunun örneklerini bir sonraki bölümde göreceğiz.
Geopandas , herhangi bir GeoDataframe'e doğrudan uygulanabilen bir çizim yöntemine sahiptir . Ancak, uzamsal bağlamı verecek bir temel harita olmadığı için bu genellikle anlamlı değildir.
Folium ile etkileşimli haritalama
Folium kitaplığı, verilerinizi bir temel harita üzerinde çizmenize izin verir ve bir temel harita ile etkileşimli haritalamayı (örneğin , yakınlaştırabilir, kaydırabilirsiniz) etkinleştirir . Farklı özelleştirmeleri destekleyen birçok işlev vardır. Bu yazıda sadece en önemli olanları ele alacağım.
Folium'un Harita işlevini kullanarak bir temel harita oluşturarak başlayın. Haritanızı ortalamak için uygun bir konum seçmek ve yakınlaştırma düzeyini başlatmak biraz deneme yanılma gerektirecektir. Bu örnekte Singapur'u gösterecek şekilde ayarlanmıştır.
import folium
map1 = folium.Map(location=([1.3521,103.8198]), # [latitude, longitude]. CRS: EPSG 4326 CRS
zoom_start = 11.5) #initial zoom level
map1 # display
map1 (base map only). By default, OpenStreetMap map tiles are used. Other options are available.
for _,row in mrt.iterrows():
row['location'] = list(reversed((row['geometry'].x,row['geometry'].y))) # folium requires location as [latitude, longitude]
folium.map.Marker(location=row['location'], # marker location
icon=folium.Icon(color='red', # custom marker style
icon_color='black',
icon='train',
prefix='fa'), # use this prefix for fontawesome icons
popup=row['STN_NAME']).add_to(map1) #info to display in pop-up
map1 # display
map1 with markers at train stations. Top left: full map; bottom right: zoomed in view showing a pop-up.
Analiz
Geopandas kitaplığı, çeşitli analiz türleri için kullanabileceğiniz çok çeşitli işlevlere sahiptir. Neyin mümkün olduğuna dair bir fikir vermek için işte bazı örnekler:
Tamponlar oluşturuluyor
Geopandas tampon yöntemi, giriş geometrisinden belirli bir mesafe içindeki tüm noktaları kapsayan yeni bir geometri oluşturur. Bu proje için, sonraki analizlerin öncüsü olarak her apartman bloğunun etrafındaki belirli bir yarıçap içindeki alanı tanımlamak için tamponlar kullanıldı.
Daha önce gördüğümüz coğrafi kodlu HDB Özellik Bilgisi veri kümesini hatırlayın:
Her apartman bloğunun etrafında 400m — 2km yarıçaplı tamponlar oluşturmak için aşağıdaki kodu kullanın.
# use projected CRS as buffer generation involves distance measurement
resi_blks2 = resi_blks2.to_crs("EPSG:32648")
buffer_distances = [400,800,1200,1600,2000]
resi_blks3 = resi_blks2.copy()
for i in range(len(buffer_distances)):
resi_blks3['buffer_' + str(buffer_distances[i])] = resi_blks3['geometry'].buffer(buffer_distances[i])
First 3 lines of resi_blks3
test_buffers = resi_blks3.loc[[0,1],:] #extract first 2 blocks
# Convert crs to epsg 4326 to match what Folium supports
test_buffers = test_buffers.to_crs("EPSG:4326")
for i in range(len(buffer_distances)):
test_buffers = test_buffers.set_geometry('buffer_' + str(buffer_distances[i])).to_crs("EPSG:4326")
map1 = folium.Map(location=([1.3521,103.8198]),zoom_start=12) #folium basemap
for _,row in test_buffers.iterrows():
#add markers for blk locations
row['location'] = (row['geometry'].x,row['geometry'].y)
folium.map.Marker(location=list(reversed(row['location'])),
icon=folium.Icon(color='red',
icon_color='black',
icon='home'),
popup=row['blk_no']+" "+row['street']).add_to(map1)
#add buffers
for i in range(len(buffer_distances)):
buffer_gs = gpd.GeoSeries(row['buffer_' + str(buffer_distances[i])])
buffer_json = buffer_gs.to_json()
folium.GeoJson(data=buffer_json).add_to(map1)
map1 # display map
map1 showing 400m — 2km buffers generated around two apartment blocks
Geopandas in yöntemi, bir geometrinin diğerinin içinde olup olmadığını belirler. Bunu, her apartman bloğunun etrafındaki 400m'lik tamponlarda (5-10 dakikalık yürüyüş) bulunan olanakları saymak için kullanabiliriz.
Örnek olarak, bu makalede daha önce gördüğümüz süpermarketler veri setini kullanacağız:
Her dairenin 400 m yarıçapındaki süpermarket sayısını saymak için aşağıdaki kodu kullanın.
def count_amenity(amenity_gdf, poly_buffer):
"""
Compute number of amenities within the specified buffer polygon
amenity_gdf = geodataframe for amenity type of interest
polygon_buffer = buffer polygon to search within
"""
return (len(amenity_gdf.loc[amenity_gdf.within(poly_buffer)]))
amenities_1 = resi_blks3.copy()
supmkt = supmkt.to_crs(amenities_1.crs) # ensure same crs for meaningful overlay
amenities_1['n_supmkt_400m'] = amenities_1['buffer_400'].apply(lambda x: count_amenity(supmkt, x))
First 3 lines of amenities_1
Aşağıdaki harita, Singapur'daki yeşil alanların konumunu göstermektedir ( buradan indirebilirsiniz ).
Geopandas bindirme yöntemi, iki giriş geometrisi kümesinin nasıl örtüştüğüne (veya örtüşmediğine) bağlı olarak yeni geometriler çıkarır.
Bu proje için, her apartman bloğunun etrafındaki 400 m'lik tamponlar (5–10 dakikalık yürüme mesafesinde) içinde yer alan yeşil alanları belirlemek için kesişme seçeneğini kullanabiliriz . Daha sonra , tanımlanan bu yeşil alanların alanını hesaplamak için Geopandas alan yöntemini kullanabiliriz . Aşağıdaki resim bunu bir apartman bloğu için göstermektedir.
Tüm apartman blokları için hesaplamayı çalıştırmak için aşağıdaki kodu kullanın:
def total_green_area(poly_buffer):
"""Sum up total green area (in ha) within specified polygon buffer (poly_buffer)"""
#convert poly_buffer to geodataframe
polybuffer_gdf = gpd.GeoDataFrame({'geometry':[poly_buffer]},crs=greenery.crs)
#find the intersection polygons between poly_buffer and greenery geodataframes
green_spaces = gpd.overlay(polybuffer_gdf,greenery,how='intersection')
#calculate area of each green space
green_spaces['area'] = green_spaces['geometry'].area
total_area = green_spaces['area'].sum()
total_area = total_area/10000 #convert m2 to ha
return (total_area)
amenities_2 = amenities_1.copy()
amenities_2['grnarea_400'] = amenities_2['buffer_400'].apply(lambda x: total_green_area(x))
First 3 lines of amenities_2
Geopandas mesafe yöntemi, iki giriş geometrisi arasındaki mesafeyi hesaplar. Bunu, apartman blokları ile her bir tren istasyonu arasındaki mesafeyi daha önce gördüğümüz veri setinden hesaplamak için kullanabiliriz:
Her daireden her tren istasyonuna olan mesafeyi hesaplayabilir ve en yakın istasyonu belirleyebilirdik ama bu çok yavaş olurdu. Bunun yerine, önce aramamızı her dairenin etrafındaki 400m'lik tampon bölge ile sınırlandırıyoruz. Bu alan içinde hiç istasyon yoksa, aramamızı 800m arabelleğe genişletiriz ve bu şekilde maksimum 2km'ye kadar adım adım devam ederiz.
def dist_to_amenity(amenity_gdf, row):
"""
Compute distance to nearest amenity
amenity_gdf = geodataframe for amenity type of interest
row = row of data with various buffer polygons to search within
"""
#list amenities within 400m
mybuffer = row.buffer_400
list_amen = amenity_gdf.loc[amenity_gdf.within(mybuffer)]
#if none within 400m, widen buffer to 800m and so on
if len(list_amen)==0:
mybuffer = row.buffer_800
list_amen = amenity_gdf.loc[amenity_gdf.within(mybuffer)]
if len(list_amen)==0:
mybuffer = row.buffer_1200
list_amen = amenity_gdf.loc[amenity_gdf.within(mybuffer)]
if len(list_amen)==0:
mybuffer = row.buffer_1600
list_amen = amenity_gdf.loc[amenity_gdf.within(mybuffer)]
if len(list_amen)==0:
mybuffer = row.buffer_2000
list_amen = amenity_gdf.loc[amenity_gdf.within(mybuffer)]
#if no amenity within 2km, return null for distance
if len(list_amen)==0:
return(np.nan)
else:
# compute distance from each amenity to apartment block
list_amen['distance'] = list_amen['geometry'].distance(row.geometry)
return(min(list_amen['distance'])) # return minimum distance
amenities_3 = amenities_2.copy()
mrt = mrt.to_crs(amenities_3.crs) # ensure same CRS for meaningful overlay
amenities_3['dist_mrt']=amenities_3.apply(lambda row: dist_to_amenity(mrt, row), axis=1)
First 3 lines of amenities_3
Bu projenin amacı, birincil arama kriteri olarak olanaklara erişilebilirliğe odaklanarak Singapur'da daire aramak için yeni bir yol keşfetmekti. Bu makalede, Singapur'daki dairelerin konumunun coğrafi kodlama yoluyla nasıl haritalanacağını ve coğrafi veri kümelerinden tesislerin konumunun nasıl çıkarılacağını gösterdim. Son bölümde tartışılan analiz tekniklerini kullanarak, her daireden farklı olanaklara erişilebilirliği ölçebilir ve karşılaştırabiliriz.
Python kodunun tamamını ve kullanılan Tableau Çalışma Kitabını görüntülemek için GitHub sayfamı ziyaret edin .
Referanslar
- Python'da Mekansal Vektör Verilerine Giriş (Earth Data Science tarafından)
- Coğrafi Kodlama Singapur Koordinatları: OneMap API (Shawn Tham tarafından, Veri Bilimine Doğru)

![Bağlantılı Liste Nedir? [Bölüm 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































