OpenStreetMapin ja Maanmittauslaitoksen nimistöjen vertailu

Monet Lajitietokeskuksen lajihavainnoista ovat kansalaistiedettä ja vieläpä usein ei-teknisten ihmisten syöttämiä. Luontoharrastajien keski-ikäkin melko korkea. Tämä johtaa väistämättä vahinkoihin lajihavaintoja syöttäessä. Koordinaatit ovat asiantuntijoillekin monimutkaisia, ja saattavat helposti mennä väärin.

Yksi keino havaintojen laadunvarmistamiseen on verrata koordinaatteja havaintoa tehdessä sanallisesti ilmoitettuun paikkaan. Jos luontoharrastaja on kertonut havaintonsa löytyneen Suomenlinnasta, mutta koordinaatit osoittavat Keravalle, jokin saattaa olla pielessä. Datan laadun kannalta se ei ole hyvä asia.

Tätä ongelmaa lähdin ratkomaan vertaamalla sanallisesti ilmoitettuja sijainteja Maanmittauslaitoksen nimistöaineistoon ja Nominatim-geokoodauspalveluun, joka perustuu OpenStreetMapin paikannimiin. Nominatim-tietokannan asensin paikallisesti Dockeriin (ohje) ja vertailun tein Pythonin Geopandas ja Pandas -kirjastoilla.

Ennen itse vertailua siivosin ilmoitetut paikannimet poistamalla niistä ylimääräiset välilyönnit, numerot (esim. postinumerot ja kadunnumerot) ja kaikki erikoismerkit. Tämän jälkeen tarkistin vielä sisältävätkö ne kuntien nimiä. Jos kunnan nimi löytyi ja havainnon koordinaatit sijaitsivat kyseisen kunnan alueella, skippasin näiden havaintojen tarkemman analysoinnin. Tällöin datastakin sai karsittua kolmasosan pois.

Vertailu Maanmittauslaitoksen dataan

Sen jälkeen tarkastin, onko tekstimuotoinen sijainti sellaisenaan Maanmittauslaitoksen nimistöaineistossa. Alkuun kokeilin erilaisia fuzzy search -menetelmiä, jolloin osuman ei tarvitsisi olla täsmällinen, mutta se tuntui vain sekoittavan asiaa. Arjalasta tuli helposti Karjala.

obs['is_in_MML_data'] = obs['Sijainti'].str.lower().isin(MML_Places['teksti'])

2 095 484 havainnoista 661 756:lle löytyi täsmällinen vastine Maanmittauslaitokselta. Sen jälkeen valitsin matchaavat havainnot.

obs_filtered = obs[obs['is_in_MML_data']]

Yhdistin ne Maanmittauslaitoksen paikannimiin niin, että yhdelle havainnoille voi tulla useampi osuma, jokainen uudelle riville. Merge()-funktion käyttäminen on huomattavasti for-looppia nopeampi tapa löytää vastaavuudet.

merged = obs_filtered.merge(place_data, left_on='Sijainti', right_on='teksti', suffixes=('_obs', '_mml'))

Tämän jälkeen laskin jokaisesta pisteestä sijainnin jokaiseen pisteeseen. Sitä varten Series-tietotyyppi pitää muuntaa GeoDataFrameksi.

merged = gpd.GeoDataFrame(merged, geometry='geometry_obs', crs=obs.crs)
merged['distance_MML'] = merged['geometry_obs'].distance(merged['geometry_place'])

Lähin samanniminen sijainti löytyy helposti grouppaamalla aineisto havaintojen tunnisteen perusteella ja aggregoimalla data pienimmän etäisyyden mukaan.

closest_places = merged.loc[merged.groupby('Havainnon_tunniste')['distance_MML'].idxmin()]

Lopuksi lasketut sijainnit voi vielä liittää alkuperäisiin havaintoihin. Havainnoilla, joille vastaavuutta ei referenssidatasta löytynyt, jää etäisyydeksi tyhjä arvo.

obs = obs.merge(closest_places, on='Havainnon_tunniste', how='left', suffixes=('', '_closest'))
Kuva 1. Tuloksena voi nähdä, että Nisskallan niminen viivamuotoinen havainto on 17 m päässä Nisskallan nimisestä Maanmittauslaitoksen referenssipisteestä. Havainnon koordinaatit lienevät siis kunnossa.

 

Etäisyydet samannimisten referenssipisteiden ja havaintojen välillä vaihtelevat muutamasta sentistä satoihin kilometreihin. Variaatiota siis löytyy, mutta onneksi suurin osa havainnoista on lähellä referenssipistettä, kuten yllä olevasta kuvasta näkee.

Vertailu OpenStreetMapin dataan

OpenStreetMap sijaintien kanssa tein vähän samalla tavalla, mutta havainnot hain Dockerissa pyörivästä Nominatim-tietokannasta. Alkuun yhdistin tietokannan Python-koodiin.

locator = Nominatim(user_agent="myGeocoder", domain="localhost:8080", scheme="http")
Sitten loin hyvin yksinkertaisen cache-mekaniikan, joka nopeutti paikannimien hakemista moninkertaisesti.
geocode_cache = {}
distance_cache = {}
Ja funktion, joka hakee merkkijonomuotoisen paikannimen tietokannasta.
def geocode_with_retry(location_str):
    if location_str in geocode_cache:  # Check cache first
        return geocode_cache[location_str]

    result = locator.geocode(location_str, exactly_one=False)  # Get the location
    if result:
        geocode_cache[location_str] = result  # Cache the result
        return result

    geocode_cache[location_str] = None
    return None
Toinen funktio laskee pituuden Nominatim-tietokannasta saatujen tuloksien ja havainnon geometrian välille. Jos tietokannasta löytyy monta samannimistä paikkaa, funktio palauttaa lähimmän.
def compute_distance(row):
    location_str = row['Sijainti_cleaned']

    if location_str in distance_cache:  # Check cache first
        return distance_cache[location_str]

    locations = geocode_with_retry(location_str)
    if not locations:
        return None

    row_geometry = row['geometry']

    points_wgs84 = [Point(loc.longitude, loc.latitude) for loc in locations]
    points_proj = gpd.GeoSeries(points_wgs84, crs=4326).to_crs(obs.crs)
    distances = points_proj.distance(row_geometry)
    min_distance = distances.min() if not distances.empty else float('inf')  # Get the minimum distance

    if min_distance != float('inf'):
        distance_cache[location_str] = min_distance
        return min_distance

    return None
Kutsun funktiota progress_apply-komennolla, joka näyttää edistymisen kivasti tqdm-kirjaston edistymispalkissa. Tulokset tallentuvat MML-esimerkin mukaisesti omaan sarakkeeseensa.
tqdm.pandas(desc="Computing distances") 
obs['distance_osm'] = obs.progress_apply(compute_distance, axis=1)
obs['distance_osm'] = pd.to_numeric(obs['distance_osm'], errors='coerce').round(2)

Olisi myös mahdollista hakea sijainnit verkossa toimivalta Nominatim-geokoodauspalvelusta, joka olisi helpompaa, mutta se toimii sen verran hitaasti, että miljoonien havaintojen hakemiseen olisi mennyt päiviä.

Tulokset ja yhteenveto

Lopulta tallensin molemmat ’distance_osm’ ja ’distance_mml’ sarakkeet uuden tiedostoon ja visualisoin datan QGIS:ssä. Yksinkertaisimmillaan tulokset näyttävät kartalla tältä:

Kuva 2. Strömmingsbådan nimellä kirjattu havainto on 22 metrin päästä MML:n referenssipisteestä ja 20 m päässä OSM paikannimestä.


Ei sinänsä ollut yllättävää, että OSM:sta löytyi MML:n dataa enemmän matcheja, sillä aineisto oli isompi.

Paikannimiaineisto

Kohteet aineistossa

Löydetyt osumat

Geometriatyyppi

OSM/Nominatum

2 116 866

711 138 / 2 065 692 (34.4 %)

Alueet, viivat, pisteet

MML Nimistö

1 172 377

650 952 / 2 065 692 (31.5 %)

Pisteet

Kuitenkaan määrä ei korvaa laatua tällä kertaa. Alla olevasta kuvasta huomaa, että MML:n data antaa paljon lyhyempiä etäisyyksiä, kuin OSM:n. 

Kuva 3. Mediaanietäisyydet havaintojen ja samannimisten kohteiden välillä referenssiaineistoissa. Vuonna 2015 aloitettiin Vihko-palvelun käyttö, jossa lajihavaintojen sijainti perustuu pääosin GPS-sijaintitietoon.

Ero tuntuu kummalliselta, sillä OSM:n datassa on enemmän kohteita (2.1 milj > 1.1 milj) ja mukana on myös aluemuotoisia geometrioita. Toisaalta OSM:ssä on paljon muutakin sekalaista, kuin vain kuratoituja paikannimiä. Mukaan mahtuu risteyksiä, kauppoja ja vaikkapa tunneleita. Osalla kohteista ei myöskään ole nimeä

Johtopäätelmänä tuumaisin, että MML:n nimiaineisto on toistaiseksi laadukkaampi, kuin OSM:n, ainakin tähän tarkoitukseen. Suurin osa referenssipisteistä on ihan havainnon vieressä, kuten histogrammista näkyy.


Kuva 4. Suurin osa MML:n etäisyyksistä on alle 1000 metriä. Tämä antaa osviittaa siitä, että koordinaatit ja tekstimuotoisesti annettu sijainti pääosin täsmäävät.

Kun tulosten potentiaalisesti epätarkkoja havaintoja tutkii, useimmiten syy on yksinkertainen. Jos käyttäjä on kirjoittanut havainnon sijainniksi vaikkapa "viereinen koulu", "kesämökki", tai "puisto", on hakusanalla aika mahdotonta etsiä mitään vastaavuutta. Lisäksi haastavuuksia aiheutti mm. saamenkieliset nimet, homonyymit (esim. Kallio), kirjoitusvirheet tai erilaiset kirjoitusasut. 

Toki mukaan mahtuu myös oikeasti ristiriitaisia sijainteja, mutta niitä täytyi hieman etsiä (hyvä juttu!). Esimerkiksi Larsholmen-sijaintiin nimetty havainto sijaitsee koordinaattien perusteella Raaseporissa, mutta lähin Larsholmen-niminen referenssipiste Uusikaarlepyyssä.

Tiivistettynä OSM ja MML tuottavat kyllä tuloksia, mutta niihin sisältyy aina epävarmuuksia. On hankala sanoa, onko kyseessä oikeasti havainnon väärä sijainti, vai onko kyseessä kirjoitusvirhe, eri kirjoitusasu vai puute referenssidatassa. 

Olisi kiva kuulla myös muiden ajatuksia geokoodauspalveluiden käytöstä. Saa laittaa viestiä vaikka linkedinissä.



Kommentit

Tämän blogin suosituimmat tekstit

Miksi paikkatietoa on niin vaikea löytää?

Paikkatietotekoäly olisi hieno

OGC API -paikkatietopalvelut