Jälki-pyöräreittipalvelu

Reittipalvelu pyöräilyn aktiiviharrastajille

Teknologiat: Ruby, Rails, Coffeescript, C++
Demo

Tämä artikkeli koskee Jäljen 2. sukupolven arkkitehtuuria; palvelusta on sittemmin julkaistu nykyinen 3. sukupolven versio.

Jälki on kehittämäni reittipalvelu, joka on suunnattu pyöräilyn aktiiviharrastajille. Palvelu sisältää käyttäjien tuottamaa reittidataa ja muita karttapalveluita pyöräilijöiden tarpeisiin.

Palvelun tekninen toteutus jakaantuu neljään eri osakokonaisuuteen:

  1. Sovelluspalvelin
  2. Reittieditori
  3. GIS-apupalvelin
  4. Taustakartat

1. Sovelluspalvelin

Pääsovelluspalvelin muodostaa Jälki-palvelun rungon. Kyseessä on jokseenkin tavanomainen Rails-sovellus, joka pitää sisällään kymmenkunta erilaista mallia. Ohjelmisto huolehtii useimmista Jälki-verkkopalvelun käyttäjälle näkyvistä toiminnoista.

Avoimesta lähdekoodista löytyy paljon hyödyllisiä rutiineja. Esimerkkiksi reitin tallentaminen gpx-formaatissa onnistuu valmiin gemin avulla:

def to_gpx
    gpx = GPX::GPXFile.new
    track_point_data = JSON.parse(self.route_data)
    track = GPX::Track.new(:name => title)
    segment = GPX::Segment.new

    track_point_data.each do |point|
        segment.points << GPX::TrackPoint.new(
            {lat: point[1], lon: point[0]}
        )
    end
    
    track.segments << segment
    track.name = title
    gpx.tracks << track
    gpx.name = title
    
    gpx.to_s
end

Vastaavasti avoimia rajapintoja hyödyntää. Tässä haetaan koordinaattien perusteella paikkakunnan nimi:

query = "lat=#{lat}&lon=#{lon}&zoom=10&addressdetails=1"
uri = URI.parse "https://nominatim.openstreetmap.org/reverse?format=json&#{query}"

response = nil

Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    request = Net::HTTP::Get.new uri
    response = http.request request
end

2. Reittieditori

Jäljen uusi Jem-niminen reittieditori valmistui keväällä 2019. Tarve räätälöidylle editorille tuli, kun valmiit komponentit – esimerkiksi Leaflet Draw – eivät tarjonneet kaikkea tarvittavaa toiminnallisuutta.

Sisäisesti Jem muodostaa reitin käyttämällä segmenttejä. Segmentti koostuu alku- ja loppupisteestä ja niiden välillä kulkevasta reittiviivasta. Reittiviiva itsessään voi koostua useasta välipisteestä, mutta editori ei ota niiden muodostumiseen kantaa. Koko reitin kattava reittiviiva muodostetaan yhdiställä kaikki segmentit yhdeksi yhtenäiseksi viivaksi.

3. GIS-apupalvelin

Pääsovelluspalvelimen apuna on tällä hetkellä erillisenä ohjelmistona ja REST-rajapintana toteutettu paikkatietoa tarjoileva palvelin. Se huolehtii mm. korkeuskäyrien laskentaan ja reititykseen liittyvistä toiminnoista.

Alunperin pelkästään korkeusdatan käsittelyyn suunniteltu palvelinohjelmisto alkoi laajentua uuden reittieditorin kehityksen yhteydessä. Jälki-palvelun suuren käyttäjämäärän takia en kokenut mieluisaksi Openstreetmapin ilmaisten rajapintoinen käyttöä, vaan päätin laajentaa jo olemassaolevan GIS-palvelimen toimintoja.

Tämä Elevator-niminen ohjelmisto tarjoaa muutaman itsenäisen rajapinnan, joiden taustatoteutus poikkeaa toisistaan. Rajapinnan palauttaman datan generointiin käytettäviä valmiita apuohjelmia ovat Gdal ja Overpass, jonka lisäksi kirjoitin reititystoimintoja varten oman C++-kielisen ohjelman. Päätökseni käyttää yksittäisiä pienempiä apuohjelmia isompien Overpass- ja Osm3s-palvelimien sijaan perustui siihen, että halusin pitää järjestelmän mahdollisimman yksinkertaisena ja helppona ylläpitää.

source_data = body["coordinates"].map do |item|
  lat = item[0]
  lon = item[1]
  datasources = GeotiffFile.where(
    "min_lat <= ? AND min_lon <= ?\
    AND max_lat >= ? AND max_lon >= ?",
    lat, lon, lat, lon
  ).pluck(:filename)
	
  {lat: lat, lon: lon, datasources: datasources}
end

...


data_sources.each do |data_source|
  path = "#{Rails.application.config.elevation_data_dir}/MML/#{data_source}"
  output = `gdallocationinfo -wgs84 -valonly #{path} #{coordinate[:lon]} #{coordinate[:lat]}` unless path.blank?
	
  if output.present?
    result = output.to_f
    result = nil if output.start_with? "Error" or result.blank?
    break
  end
end

4. Taustakartat

Suuresta käyttäjämäärästä johtuen Jälki käyttää omia taustakarttoja. Ilmaisten Openstreetmapin karttapalvelimien käyttö näillä käyttäjämäärillä ei enää olisi kohtuullista, ja aikaisemmin käyttämäni Mapbox alkoi Jäljen suosion kasvaesas tulla liian kalliiksi.

Taustakartat perustuvat Openstreetmapin dataan. Ylläpidon yksinkertaistamisen ja toimintavarmuuden vuoksi käytän esirenderöityä karttatiiliä. Näin ollen palvelin tarjoilee tuiki tavallisia png-kuvia, eikä mitään monimutkaisempaa palvelinsysteemiä tähän tarvita.

Karttatiilien generointi tapahtuu omalla työasemallani. Tavanomaiseen tapaan paikalliseen Postgis-laajennuksilla varustettuun PostgreSQL-tietokantaan ladataan ensin datasetti Openstreetmapista. Karttatyylinä on hieman ehostettu OSM-perustyyli; sisemmille zoomaustasoille on vain lisätty maastopolkujen värikoodaus niiden vaikeuden mukaan.

Renderöinti tapahtuu käyttäen Mapnik-kirjastoa ja alunperin Openstreetmapin luomia, mutta pitkälle kustomoituja Python-skriptejä. Kartan generoinnin nopeuttamiseksi olen hionut ja optimoinut renderöintiputkea Bash-skriptejä, rinnakkaisprosessointia ja Overpass Turbo -palvelua apuna käyttäen. Lopputuloksena Suomen alueelta ja zoomaustasoilta 0-16 on noin kymmenen gigatavua karttakuvia.