Open Street Map#

Open Street Map (OSM) on avoin karttapalvelu, jossa käyttäjät voivat itse osallistua karttojen luomiseen. Koska kyseessä on avoin projekti, sen aineistoa voidaan hyödyntää vapaasti omiin tarkoituksiin (myös kaupallisiin). Pääsemme pythonilla käsiksi OSM:n aineistoon osmnx-kirjaston avulla. Aineistosta voidaan hakea paljon kaikenlaista paikkatietoa, kuten tieverkostoja, rakennuksia, puistoja, ravintoloita jne.

Tässä harjoituksessa tutustutaan osmnx-kirjastoon, haetaan sen avulla paikkatietoja Tampereen keskustorin lähettyviltä ja piirretään niitä kartalle. Lisäksi tässä harjoituksessa tarvitaan matplotlib-kirjastoa, joka sisältää funktioita kuvaajien piirtämistä varten.

Aloitetaan hakemalla tarvittavat kirjastot.

import osmnx as ox  # Paikkatietojen hakemiseen ja käsittelyyn tarvittavia funktioita
import matplotlib.pyplot as plt  # Kuvaajien piirtämiseen tarvittavia funktioita

Määritellään seuraavaksi osoite, jonka ympäristöstä haluamme etsiä paikkatietoja, sekä etäisyys. Etsitään paikkatietoja 1km säteellä Tampereen keskustorista.

# Määritellään osoite ja etäisyys ja tallennetaan ne muuttujiin "address" ja "distance"

address = "Keskustori 1, 33100, Tampere, Finland"
distance = 1000 # metriä

Seuraavaksi voidaan hakea kuvaajaan tarvittavat tiedot ox.graph_from_address()-funktion avulla. Haetaan tiedot ja piirretään niistä kuvaaja ox.plot_graph()-funktiolla.

# Haetaan OpenStreetMapin katuverkosto "graph_from_address"-funktion avulla
graph = ox.graph_from_address(address, dist=distance)

# Piirretään verkosto näytölle
ox.plot_graph(graph)
../_images/openstreetmap_5_0.png
(<Figure size 576x576 with 1 Axes>, <AxesSubplot:>)

Hienolta näyttää! Saimme parilla koodirivillä piirrettyä Tampereen keskustorin ympäristössä olevat tiet ja niiden risteyskohdat. Tavoitteenamme on seuraavaksi piirtää samaan kuvaan kaikki tiet sekä alueella olevat rakennukset.

Aluksi eritellään kuvaajasta omaksi muuttujikseen teiden solmukohdat sekä tiet ox.graph_to_gdfs()-funktion avulla. Saamme haluamamme tiedot GeoDataFrame-muodossa, jotka ovat käytännössä taulukoita, jotka sisältävät muiden tietojen lisäksi paikkatietoja.

Etsitään lisäksi lähistöllä olevat rakennukset ox.geometries_from_address-funktiolla. Saamme tiedot kaikista rakennuksista lisäämällä funktion parametreihin tags = {'building': True}. Tämäkin funktio antaa rakennukset GeoDataFramena.

# eritellään kuvasta solmupisteet (nodes) ja reunat, eli tässä siis kadut (edges) omiksi muuttujikseen
nodes, edges = ox.graph_to_gdfs(graph)

# etsitään osoitteen avulla alueella olevat rakennukset käyttäen tagia "building": True
buildings = ox.geometries_from_address(address, dist=distance, tags = {'building': True} )

# GeoDataFramen sisältämiä sarakkeita voidaan tarkastella seuraavasti
print(buildings.columns)
Index(['amenity', 'geometry', 'addr:street', 'name', 'opening_hours',
       'parking', 'wheelchair', 'entrance', 'building', 'addr:city',
       ...
       'addr:flats', 'building:min_level', 'seamark:landmark:category',
       'seamark:type', 'covered', 'official_name', 'construction', 'ways',
       'old_name:en', 'old_name:fi'],
      dtype='object', length=147)

Huomataan, että rakennuksista on paljon erilaista tietoa (kolme pistettä keskellä viittaa siihen, että kaikki tiedot eivät mahdu näytölle), kuten niille määritelty nimi tai pääsy pyörätuolilla. Tässä harjoituksessa piirretään kartalle kuitenkin kaikki rakennukset, joten dataa ei tarvitse enempää manipuloida.

Nyt voimme piirtää rakennukset ja kadut samaan kuvaan. Tätä varten tehdään kuvalle pohja subplots()-funktiolla. Tämä funktio antaa paluuarvoina itse kuvan sekä kuvan akselit, joita tarvitsemme, jotta saamme samalle akselille piirrettyä sekä rakennukset että tieverkoston.

Lisäksi käytetään seuraavia funktioita:

# Jotta tiet ja rakennukset saadaan samaan kuvaan, tehdään kuvalle pohja:
fig, ax = plt.subplots(figsize=(24,16)) # Määritellään kuvan koko tuumina

# Asetetaan taustaväriksi musta, jotta tiet ja rakennukset erottuvat paremmin
ax.set_facecolor('black')

# Asetetaan kuvalle otsikko
plt.title('Kadut ja rakennukset kilometrin etäisyydellä Tampereen keskustorista', fontsize=24)

# Piirretään tieverkosto, alpha-parametrilla säädetään värin läpinäkyvyyttä
# Huom. akseliksi pitää määritellä ax
edges.plot(ax=ax, color='yellow', alpha=0.7)

# Piirretään rakennukset
# Huom. akseliksi pitää määritellä ax
buildings.plot(ax=ax, facecolor='silver', alpha=0.7) 

plt.show() # Näytetään kuva show()-funktiolla
../_images/openstreetmap_9_0.png

Nyt olemme saaneet piirrettyä jo sekä tieverkoston, että rakennukset. Lisätään vielä puistot kuvaan. Ne löytyvät vapaa-aikaan viittaavalla tagilla ”leisure”. Tällä tagilla löytyy kuitenkin muitakin paikkoja kuin puistoja, joten dataa pitää hieman karsia.

# etsitään osoitteen avulla alueella olevat vapaa-aikaan liittyvät paikkatiedot käyttäen tagia "leisure": True
leisure = ox.geometries_from_address(address, dist=distance, tags={"leisure":True})

# katsotaan, mitä sarakkeita "leisure" pitää sisällään:
leisure.columns
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Input In [6], in <cell line: 2>()
      1 # etsitään osoitteen avulla alueella olevat vapaa-aikaan liittyvät paikkatiedot käyttäen tagia "leisure": True
----> 2 leisure = ox.geometries_from_address(address, dist=distance, tags={"leisure":True})
      4 # katsotaan, mitä sarakkeita "leisure" pitää sisällään:
      5 leisure.columns

File /opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/osmnx/geometries.py:160, in geometries_from_address(address, tags, dist)
    157 center_point = geocoder.geocode(query=address)
    159 # create GeoDataFrame of geometries around this point
--> 160 gdf = geometries_from_point(center_point, tags, dist=dist)
    162 return gdf

File /opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/osmnx/geometries.py:118, in geometries_from_point(center_point, tags, dist)
    115 polygon = utils_geo.bbox_to_poly(north, south, east, west)
    117 # create GeoDataFrame of geometries within this polygon
--> 118 gdf = geometries_from_polygon(polygon, tags)
    120 return gdf

File /opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/osmnx/geometries.py:278, in geometries_from_polygon(polygon, tags)
    270     raise TypeError(
    271         "Boundaries must be a shapely Polygon or MultiPolygon. If you requested "
    272         "geometries from place name, make sure your query resolves to a Polygon or "
    273         "MultiPolygon, and not some other geometry, like a Point. See OSMnx "
    274         "documentation for details."
    275     )
    277 # download the geometry data from OSM
--> 278 response_jsons = downloader._osm_geometries_download(polygon, tags)
    280 # create GeoDataFrame from the downloaded data
    281 gdf = _create_gdf(response_jsons, polygon, tags)

File /opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/osmnx/downloader.py:584, in _osm_geometries_download(polygon, tags)
    582 for polygon_coord_str in polygon_coord_strs:
    583     query_str = _create_overpass_query(polygon_coord_str, tags)
--> 584     response_json = overpass_request(data={"data": query_str})
    585     response_jsons.append(response_json)
    587 utils.log(
    588     f"Got all geometries data within polygon from API in {len(polygon_coord_strs)} request(s)"
    589 )

File /opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/osmnx/downloader.py:768, in overpass_request(data, pause, error_pause)
    766     this_pause = _get_pause(base_endpoint)
    767 utils.log(f"Pausing {this_pause} seconds before making HTTP POST request")
--> 768 time.sleep(this_pause)
    770 # transmit the HTTP POST request
    771 utils.log(f"Post {prepared_url} with timeout={settings.timeout}")

KeyboardInterrupt: 

Vapaa-aikaan liittyvissä paikkatiedoissa on sarake nimeltä ”leisure”. Katsotaan vielä mitä eri kohteita se pitää sisällään.

print(leisure['leisure'].unique())

Voisimme etsiä paikkoja, kuten leikkikenttiä, saunoja tai huvipuistoja. Halusimme kuitenkin piirtää puistot kartalle. Niitä vastaavilla paikoilla sarakkeessa on sana ’park’. Eritellään puistot muusta datasta ja tallennetaan ne muuttujaan ”parks”.

# Valitaan kaikista vapaa-aikaan liittyvistä paikkatiedoista (leisure) sarake nimeltään leisure ja valitaan ne rivit, joissa tuo sarake on 'park'
parks = leisure[leisure["leisure"].isin(["park"])]

Nyt voimme piirtää paikkatiedot näytölle samaan tapaan kuin aiemmin.

fig, ax = plt.subplots(figsize=(24,16))

ax.set_facecolor('black')
plt.title('Kadut, rakennukset ja puistot kilometrin etäisyydellä Tampereen keskustorista', fontsize=24)

edges.plot(ax=ax, color='yellow', alpha=0.7)
buildings.plot(ax=ax, facecolor='silver', alpha=0.7) 

# Piirretään puistot
parks.plot(ax=ax, facecolor='green', alpha=0.7)

plt.show()

Huomataan, että piirretyt puistot ulottuvat kilometriä kauemmas keskustorilta. Määritellään vielä kuvaajalle rajat, jotta saamme rajattua saman alueen kuin aikaisemminkin. Tämä onnistuu plt.xlim()- ja plt.ylim()-funktioilla. Katsotaan rajoille sopivat lukuarvot kuvan akseleilta.

fig, ax = plt.subplots(figsize=(24,16))

ax.set_facecolor('black')
plt.title('Kadut, rakennukset ja puistot kilometrin etäisyydellä Tampereen keskustorista', fontsize=24)

edges.plot(ax=ax, color='yellow', alpha=0.7)
buildings.plot(ax=ax, facecolor='silver', alpha=0.7) 
parks.plot(ax=ax, facecolor='green', alpha=0.7)

# Määritellään kuvalle rajat. Katsotaan sopivat rajat edellisestä kuvan akseleilta.
plt.xlim([23.742, 23.782])
plt.ylim([61.489, 61.507])

plt.show()

Karttamme on nyt valmis. Sen avulla voimme tarkastella Tampereen keskustan katuverkostoa, rakennuksia ja puistoja. Kokeile itse tehdä oma karttasi jostain muusta paikasta tai lisää eri paikkatietoja tähän karttaan!