Featured Post

How to process multiple zip-files with Ruby on Rails

Sometimes you have to integrate exported data from third party desktop software. One way to do is to pack all data into a zipped file and push it via FTP onto your server. This tutorial will explain how to process zip-files with your Ruby on Rails™ application. I will take an example from a real estate...

Read More

How to import Nokia Sportstracker GPS data with Ruby on Rails

Posted by Lars | Posted in Geocoding, Rails, Ruby, Tutorials | Posted on 08-05-2009

2

In this tutorial i will explain how to import GPS data from Nokia mobile phones with Ruby on Rails and displaying them as route on a google map.

The first part will solve the data import of GPX formated data files. The second part will show you how to display the routes on google map.

As data source i use a sample *.GPX file. It is provided from Marton who asked me to write this tutorial. The full description of xml structure you can find at http://www.topografix.com/GPX/1/1/

I will write a sample Rails Application - navtracker - covering following points of Ruby on Rails programming:

  • Upload a *.GPX file
  • Parse a XML Data
  • Google Maps Api access to display the route

Alright, lets start writing the application. All code examples are based on Rails 2.3.2.

rails navtracker
rake db:create:all

For file upload we need the paperclip plugin.

script/plugin install git://github.com/thoughtbot/paperclip.git

Now we create the “Track”, “Tracksegment” and “Point” resources. One track has many tracksegments has many points.

Tragsegment is needed if you loss your GPS connection. The trackersoftware will create a new tracksegment if your GPS connections is etablished again.

script/generate scaffold track name:string gpx_file_name:string
gpx_content_type:string gpx_file_size:integer
script/generate scaffold tracksegment track:references
script/generate scaffold point name:string latitude:float
longitude:float elevation:float description:string
point_created_at:datetime tracksegment:references

Note: I broke the lines for better readabillity. Script/generate commands have to be written in single line.

After the command

rake db:migrate

we can write the associations into our models.

1
2
3
4
class Track < ActiveRecord::Base
  has_many :tracksegments, :dependent => :destroy
  has_attached_file :gpx
end
1
2
3
4
class Tracksegment < ActiveRecord::Base
  belongs_to :track
  has_many :points, :dependent => :destroy
end
1
2
3
class Point < ActiveRecord::Base
  belongs_to :tracksegment
end

Upload a GPX file

For uploading a GPX file we modify our view/tracks/new.html.erb following way:

1
2
3
4
5
6
7
8
9
10
11
<% form_for(@track, :html => { :multipart => true }) do |f| %>
  <%= f.error_messages %>
    <p>
        <%= f.file_field :gpx %>
    </p>   
  <p>
    <%= f.submit 'Upload GPX' %>
  </p>
<% end %>

<%= link_to 'Back', tracks_path %>

Upload a GPX file for testing before we start with with next section.

Parsing the GPX file

Let’s start with parsing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Track < ActiveRecord::Base
  has_many :tracksegments
  has_attached_file :gpx
 
  def save_attached_files_with_parse_file
    save_attached_files_without_parse_file
    parse_file
  end
 
  alias_method_chain :save_attached_files, :parse_file
 
  def parse_file
    ...
  end
end

We like to parse the uploaded GPX file directly after upload. That tricky because the paperclip pluging uses the after_create callback. If we use after_create it will be executed before paperclip plugin upload. It will cause an error “File not found”. We workaround with alias chain method.

We need following steps to parse an xml file:

  • Open the uploaded GPX file
  • Parse the GPX file
  • Close GPX file

Open GPX file and prepare for parsing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Track < ActiveRecord::Base
  has_many :tracksegments
  has_attached_file :gpx
 
  attr_accessor :file_handler, :gpx_document
 
  def save_attached_files_with_parse_file
    save_attached_files_without_parse_file
    parse_file
  end
 
  alias_method_chain :save_attached_files, :parse_file
   
  def parse_file
    prepare_gpx_file
    parse_xml
    cleanup_gpx_file
  end
  ...
end

In the first step we prepare the file for parsing. We need a file handler to open the gpx xml file with REXML. For this reason i added two attributes to the model in line 5.

1
2
3
def parse_xml
    ...
end

Parse_xml executes the parsing. I will explain it later.

1
2
3
4
5
def prepare_gpx_file
  logger.info "Prepare GPX file ..."
  make_file_handler
  open_gpx_xml
end

Prepare_gpx_file calls functions to make a file_handler and
and gpx_xml handler.

1
2
3
4
5
def make_file_handler
  logger.info "Make file handler ..."
  file_handler = File.new(File.join(RAILS_ROOT,'public',
  gpx.url.split("?")[0]),"r")
end

make_file_handler assigns the file_handler. The file open is a little bit tricky. We have to remove the parameters after ? from gpx.url. So we use a simple “split” and take the first array element from it.

1
2
3
4
def open_gpx_xml
  logger.info "Open GPX XML file ..."
  gpx_document = REXML::Document.new file_handler
end

open_gpx_xml assigns the xml handler. We use REXML to parse the file.

1
2
3
def cleanup_gpx_file
  file_handler.close
end

cleanup_gpx_file closes the file afterwards.

Parse the GPX file

1
attr_accessor ..., :tmp_segment, :tmp_point

We need two more attributes: tmp_segment and tmp_point. This attributes contain the temporary data while parsing.

1
2
3
4
5
def parse_xml
  self.gpx_document.root.each_element do |node|
    parse_tracks(node)
  end
end

parse_xml loops the root level for each node it can find.
For each node we start the function parse_tracks with the current node as parameter.

1
2
3
4
5
6
7
def parse_tracks(node)
  if node.name.eql? "trk"
    node.each_element do |node|
      parse_track_segments(node)
    end  
  end    
end

parse_tracks function checks the node name. If the name is “trk” then we go a level deeper and loop all nodes from the current “trk” node. For each node we call
parse_track_segments(node).

1
2
3
4
5
6
7
8
9
def parse_track_segments(node)
  if node.name.eql? "trkseg"
    tmp_segment = Tracksegment.new
    self.tracksegments << tmp_segment
    node.each_element do |node|
      parse_points(node,tmp_segment)
    end
  end
end

In parse_track_segments we check for the name “trkseg”. If so then create a new segment. We go a level deeper now and loop all nodes of “trkseg” node. For each of this node we start parse_points.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def parse_points(node,tmp_segment)
    if node.name.eql? "trkpt"
      tmp_point = Point.new
      node.attributes.each do |key,value|
        tmp_point.latitude = value if key.eql? 'lat'
        tmp_point.longitude = value if key.eql? 'lon'
      end
      node.each_element do |node|
        tmp_point.name = node.text.to_s if node.name.eql? 'name'
        tmp_point.elevation = node.text.to_s if node.name.eql? 'ele'
        tmp_point.description = node.text.to_s if node.name.eql? 'desc'
        tmp_point.point_created_at = node.text.to_s if node.name.eql? 'time'
      end
      tmp_segment.points << tmp_point
    end
  end
  ...

In parse_points the real action is starting. We check for node name “trkpt”. If so we create a new point object and read this node attributes lat and lon. We save it in the point object attributes latitude and longitude.

Next we start with looping all elements of current node “trkpt”. Inside loop we check for name, ele, desc and time. If one of this node names match we assign it to the corresponding attribute of point. At last we add the temporary point to the tmp_segment.

Comming next

In next tutorial we are going to care about display the route in google maps with Ruby on Rails.

delicious | digg | reddit | facebook | technorati | stumbleupon | chatintamil

Comments (2)

Good morning from Málaga (Spain).

I wrote this document looking for an answer and how to: import KML or GPX to Nokia Sportstracker. Y try to convert gpx to data file of my Nokia 5800 for import a route of Google.Is it possible?

Sorry my poor english.

Nice day. Antonio

Hi Antonio,

thank you for your comment. Unfourtunally i can’t help you with the opposite way to import google data into your Nokia 5800.

cu Lars

Write a comment