quickroute 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # QuickRoute JPEG Parser for Ruby
2
+
3
+ Parses the GPS data embedded to JPEG files output by [QuickRoute](http://www.matstroeng.se/quickroute/en/).
4
+
5
+ © Jarkko Laine 2011. Licensed under the [WTFPL](http://en.wikipedia.org/wiki/WTFPL).
6
+
7
+ ## Installation
8
+
9
+ gem install quickroute
10
+
11
+
12
+ **Note!** Only works in Ruby >1.9.2
13
+
14
+ ## Usage
15
+
16
+ ```ruby
17
+ @filename = '2010-silja-rastit.jpg'
18
+ qp = QuickrouteJpegParser.new(@filename, true) # true => calculates
19
+ times and distances.
20
+ qp.sessions.first.route.elapsed_time / 60
21
+ # => 43.766666666666666
22
+ qp.sessions.first.route.segments.first.waypoints.size
23
+ # => 992
24
+ qp.sessions.first.route.segments.first.waypoints.map{|wp| [wp.position.longitude, wp.position.latitude]}
25
+ # => [[22.78103777777778, 60.29233194444444],
26
+ # ...
27
+ # [22.77652638888889, 60.293416388888886],
28
+ # [22.77655638888889, 60.29344361111111]]
29
+ ```
@@ -0,0 +1,12 @@
1
+ module DateTimeParser
2
+ def read_date_time(data)
3
+ val = BinData::Uint64le.read(data)
4
+ LOGGER.debug "val is #{val.inspect}"
5
+ val -= 9223372036854775808 if val >= 9223372036854775808
6
+ val -= 4611686018427387904 if val >= 4611686018427387904
7
+ LOGGER.debug "after tweaks val is #{val.inspect}"
8
+ val = (val - 621355968000000000) / 10000000.0
9
+ LOGGER.debug "time was #{Time.at(val)}"
10
+ val
11
+ end
12
+ end
data/lib/handle.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'matrix'
2
+
3
+ class Handle
4
+ attr_reader :transformation_matrix,
5
+ :parameterized_location,
6
+ :pixel_location,
7
+ :type
8
+
9
+ include BinData
10
+ def initialize(data)
11
+ @transformation_matrix = Matrix.build(3,3) do
12
+ DoubleLe.read(data)
13
+ end
14
+ LOGGER.debug "matrix is #{@transformation_matrix.inspect}"
15
+ @parameterized_location = ParameterizedLocation.new(
16
+ BinData::Uint32le.read(data),
17
+ DoubleLe.read(data)
18
+ )
19
+ LOGGER.debug "parameterized location is #{@parameterized_location.inspect}"
20
+
21
+ # pixel location
22
+ @pixel_location = Point.new(
23
+ DoubleLe.read(data),
24
+ DoubleLe.read(data)
25
+ )
26
+ LOGGER.debug "pixel_location is #{@pixel_location.inspect}"
27
+
28
+ @type = BinData::Int16le.read(data)
29
+ LOGGER.debug "handle type is #{@type}"
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module JpegReader
2
+ def self.fetch_data_from(filename)
3
+ data = ""
4
+
5
+ File.open(filename, "r") do |f|
6
+ if f.read(2) == "\xff\xd8".to_b
7
+ while !f.eof?
8
+ break if f.read(1) != "\xff".to_b
9
+
10
+ if f.read(1) == "\xe0".to_b # APP0
11
+ quickroute_segment = false
12
+ length = BinData::Uint16be.read(f)
13
+
14
+ if length >= 12
15
+ if f.read(10) == "QuickRoute".to_b
16
+ data << f.read(length - 12)
17
+ quickroute_segment = true
18
+ else
19
+ f.seek(length - 12, ::IO::SEEK_CUR)
20
+ end
21
+ else
22
+ f.seek(length - 2, ::IO::SEEK_CUR)
23
+ end
24
+
25
+ break if !quickroute_segment && !data.empty?
26
+ else
27
+ break
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ data
34
+ end
35
+ end
data/lib/lap.rb ADDED
@@ -0,0 +1,25 @@
1
+ class Lap
2
+ TYPES = {
3
+ :start => 0,
4
+ :lap => 1,
5
+ :stop => 2
6
+ }
7
+
8
+ attr_reader :time, :type
9
+ attr_accessor :position, :distance, :straight_line_distance
10
+
11
+ include DateTimeParser
12
+
13
+ def initialize(data = nil)
14
+ read_data(data) if data
15
+ end
16
+
17
+ def read_data(data)
18
+ @time = read_date_time(data)
19
+ @type = BinData::Uint8be.read(data)
20
+ end
21
+
22
+ def is_of_type?(*types)
23
+ types.any?{|t| type == TYPES[t]}
24
+ end
25
+ end
data/lib/long_lat.rb ADDED
@@ -0,0 +1,53 @@
1
+ class LongLat
2
+ # earth radius in meters
3
+ RHO = 6378200
4
+
5
+ attr_accessor :longitude, :latitude
6
+
7
+ def initialize(longitude, latitude)
8
+ @longitude, @latitude = longitude, latitude
9
+ end
10
+
11
+ def self.from_data(data)
12
+ new(
13
+ BinData::Int32le.read(data) / 3600000.0,
14
+ BinData::Int32le.read(data) / 3600000.0
15
+ )
16
+ end
17
+
18
+ def distance_to(other)
19
+ distance_point_to_point(point_matrix, other.point_matrix)
20
+ end
21
+
22
+ def point_matrix
23
+ Matrix[[RHO * sin_phi * cos_theta],
24
+ [RHO * sin_phi * sin_theta],
25
+ [RHO * cos_phi]]
26
+ end
27
+
28
+ private
29
+
30
+ def sin_phi
31
+ Math::sin(0.5 * Math::PI + latitude / 180 * Math::PI)
32
+ end
33
+
34
+ def cos_phi
35
+ Math::cos(0.5 * Math::PI + latitude / 180 * Math::PI)
36
+ end
37
+
38
+ def sin_theta
39
+ Math::sin(longitude / 180 * Math::PI)
40
+ end
41
+
42
+ def cos_theta
43
+ Math::cos(longitude / 180 * Math::PI)
44
+ end
45
+
46
+ def distance_point_to_point(p0, p1)
47
+ sum = 0
48
+ p0.each_with_index do |el, row, col|
49
+ sum += ((p1[row, col] - el)**2)
50
+ end
51
+ Math.sqrt(sum)
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ class ParameterizedLocation
2
+ attr_reader :segment_index, :value
3
+
4
+ def initialize(segment_index, value)
5
+ @segment_index = segment_index
6
+ @value = value
7
+ end
8
+ end
data/lib/parse_test.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative 'quickroute'
2
+ @filename = "../2010-ankkurirastit.jpg"
3
+ qp = QuickrouteJpegParser.new(@filename, false)
4
+ LOGGER.debug qp.inspect
data/lib/person.rb ADDED
@@ -0,0 +1,16 @@
1
+ class Person
2
+ attr_reader :name, :club, :id
3
+
4
+ def initialize(data)
5
+ length = BinData::Uint16le.read(data)
6
+ LOGGER.debug "person length is #{length}"
7
+ @name = BinData::String.new(:length => length).read(data)
8
+ LOGGER.debug "person name is #{@name}"
9
+ length = BinData::Uint16le.read(data)
10
+ LOGGER.debug "club length is #{length}"
11
+ @club = BinData::String.new(:length => length).read(data)
12
+
13
+ @id = BinData::Uint32be.read(data)
14
+ LOGGER.debug "id is #{@id}"
15
+ end
16
+ end
data/lib/point.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Point
2
+ attr_reader :x, :y
3
+
4
+ def initialize(x, y)
5
+ @x, @y = x, y
6
+ end
7
+ end
data/lib/quickroute.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'bindata'
3
+ require "logger"
4
+ require 'binary_search/pure'
5
+
6
+ require_relative 'string_extensions'
7
+ require_relative 'tag_data_extractor'
8
+ require_relative 'date_time_parser'
9
+ require_relative 'jpeg_reader'
10
+
11
+ require_relative 'handle'
12
+ require_relative 'point'
13
+ require_relative 'parameterized_location'
14
+ require_relative 'lap'
15
+ require_relative 'person'
16
+ require_relative 'route'
17
+ require_relative 'route_segment'
18
+ require_relative 'session'
19
+ require_relative 'session_info'
20
+ require_relative 'waypoint'
21
+
22
+ require_relative 'long_lat'
23
+ require_relative 'rectangle'
24
+ require_relative 'quickroute_jpeg_parser'
25
+
26
+ LOGGER = Logger.new(STDOUT)
27
+ LOGGER.level = Logger::WARN
28
+
29
+ #@filename = "../2010-ankkurirastit.jpg"
30
+ #@f = File.open(@filename, 'r')
@@ -0,0 +1,108 @@
1
+ TAGS ={
2
+ 1 => :version,
3
+ 2 => :map_corner_positions,
4
+ 3 => :image_corner_positions,
5
+ 4 => :map_location_and_size_in_pixels,
6
+ 5 => :sessions,
7
+ 6 => :session,
8
+ 7 => :route,
9
+ 8 => :handles,
10
+ 9 => :projection_origin,
11
+ 10 => :laps,
12
+ 11 => :session_info
13
+ }
14
+
15
+ class QuickrouteJpegParser
16
+ include BinData
17
+ attr_reader :sessions, :map_corner_positions, :image_corner_positions,
18
+ :map_location_and_size_in_pixels, :version
19
+
20
+ def initialize(filename, calculate)
21
+ @map_corner_positions = {}
22
+ @image_corner_positions = {}
23
+
24
+ start_time = Time.now
25
+
26
+ data = fetch_data_from(filename)
27
+
28
+ if !data.empty?
29
+ process_data(data)
30
+ calculate_data if calculate
31
+ end
32
+
33
+ end_time = Time.now
34
+ @execution_time = end_time - start_time
35
+ end
36
+
37
+ private
38
+
39
+ def calculate_data
40
+ sessions.each{|s| s.calculate}
41
+ end
42
+
43
+ def fetch_data_from(filename)
44
+ JpegReader.fetch_data_from(filename)
45
+ end
46
+
47
+ def process_data(data)
48
+ LOGGER.debug "Starting to process data"
49
+ data = StringIO.new(data)
50
+
51
+ while !data.eof?
52
+ tag = TagDataExtractor.read(data)
53
+ LOGGER.debug "tag: #{tag.inspect}, tag length: #{tag.data_length.inspect}"
54
+ read_tag(tag, data)
55
+ end
56
+ end
57
+
58
+ def read_tag(tag, data)
59
+ send("set_#{TAGS[tag.tag]}", data)
60
+ end
61
+
62
+ def set_version(data)
63
+ LOGGER.debug "Reading version number"
64
+ @version = version_number(data)
65
+ LOGGER.debug "version is: #{@version}"
66
+ end
67
+
68
+ def set_map_corner_positions(data)
69
+ LOGGER.debug "Reading map corner positions:"
70
+ @map_corner_positions = corner_positions(data)
71
+ LOGGER.debug "#{@map_corner_positions.inspect}"
72
+ end
73
+
74
+ def set_image_corner_positions(data)
75
+ LOGGER.debug "Reading image corner number"
76
+ @image_corner_positions = corner_positions(data)
77
+ LOGGER.debug "#{@image_corner_positions.inspect}"
78
+ end
79
+
80
+ def set_map_location_and_size_in_pixels(data)
81
+ LOGGER.debug "Reading map location and size"
82
+ @map_location_and_size_in_pixels = Rectangle.read(data)
83
+ LOGGER.debug "#{@map_location_and_size_in_pixels.inspect}"
84
+ end
85
+
86
+ def set_sessions(data)
87
+ @sessions = Session.read_sessions(data)
88
+ end
89
+
90
+ def corner_positions(data)
91
+ {:sw => read_long_lat(data),
92
+ :nw => read_long_lat(data),
93
+ :ne => read_long_lat(data),
94
+ :se => read_long_lat(data)}
95
+ end
96
+
97
+ def version_number(data)
98
+ [BinData::Uint8be.read(data),
99
+ BinData::Uint8be.read(data),
100
+ BinData::Uint8be.read(data),
101
+ BinData::Uint8be.read(data)].join(".")
102
+ end
103
+
104
+ def read_long_lat(data)
105
+ LongLat.from_data(data)
106
+ end
107
+
108
+ end
data/lib/rectangle.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Rectangle < BinData::Record
2
+ endian :little
3
+ uint16 :x
4
+ uint16 :y
5
+ uint16 :width
6
+ uint16 :height
7
+ end
data/lib/route.rb ADDED
@@ -0,0 +1,100 @@
1
+ class Route
2
+ WAYPOINT_ATTRIBUTES = {
3
+ :position => 1,
4
+ :time => 2,
5
+ :heart_rate => 4,
6
+ :altitude => 8
7
+ }
8
+
9
+ attr_reader :attributes, :extra_waypoints_attributes_length,
10
+ :segments, :distance, :elapsed_time
11
+
12
+ def self.from_data(data)
13
+ new.read_data(data)
14
+ end
15
+
16
+ def initialize
17
+ LOGGER.debug "initializing new route"
18
+ @segments = []
19
+ @distance = 0
20
+ @elapsed_time = 0
21
+ end
22
+
23
+ def read_data(data)
24
+ @attributes = BinData::Uint16le.read(data)
25
+ @extra_waypoints_attributes_length = BinData::Uint16be.read(data)
26
+ read_segments_from(data)
27
+ self
28
+ end
29
+
30
+ def has_attribute?(attribute)
31
+ 0 != (attributes & WAYPOINT_ATTRIBUTES[attribute])
32
+ end
33
+
34
+ def calculate_parameters
35
+ segments.each{|s| s.calculate_waypoints}
36
+ end
37
+
38
+ def parameterized_location_from_time(time)
39
+ return unless segment = segment_for_time(time)
40
+ segment.parameterized_location_from_time(time)
41
+ end
42
+
43
+ def position_from_parameterized_location(location)
44
+ return unless location
45
+ w0, w1, t = waypoints_and_parameter_from_parameterized_location(location)
46
+
47
+ LongLat.new(
48
+ w0.position.longitude + t * (w1.position.longitude - w0.position.longitude),
49
+ w0.position.latitude + t * (w1.position.latitude - w0.position.latitude)
50
+ )
51
+ end
52
+
53
+ def distance_from_parameterized_location(location)
54
+ return unless location
55
+ w0, w1, t = waypoints_and_parameter_from_parameterized_location(location)
56
+ w0.distance + t * (w1.distance - w0.distance)
57
+ end
58
+
59
+ def waypoints_and_parameter_from_parameterized_location(location)
60
+ return unless location && segment = segments[location.segment_index]
61
+
62
+ waypoints = segment.waypoints
63
+
64
+ value = location.value.to_i
65
+
66
+ if value >= waypoints.size - 1
67
+ value = waypoints.size - 2
68
+ end
69
+
70
+ t = location.value - value
71
+
72
+ waypoints.size < 2 ?
73
+ [waypoints[0], waypoints[0], 0] :
74
+ [waypoints[value], waypoints[value + 1], t]
75
+ end
76
+
77
+ def add_distance(dist)
78
+ @distance += dist
79
+ end
80
+
81
+ def add_elapsed_time(time)
82
+ @elapsed_time += time
83
+ end
84
+
85
+ private
86
+
87
+ def segment_for_time(time)
88
+ segments.find{|s| s.has_time?(time) }
89
+ end
90
+
91
+ def read_segments_from(data)
92
+ segment_count = BinData::Uint32le.read(data)
93
+ LOGGER.debug "reading #{segment_count} segments"
94
+ segment_count.times do |i|
95
+ segment = RouteSegment.new(self, data)
96
+
97
+ @segments << segment
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,77 @@
1
+ class RouteSegment
2
+ EPSILON = 0.001
3
+
4
+ attr_accessor :route, :last_time
5
+ attr_reader :waypoints
6
+
7
+ def initialize(route, data = nil)
8
+ LOGGER.debug "Initializing route segment"
9
+ @route = route
10
+
11
+ @waypoints = []
12
+ read_waypoints(data) if data
13
+ end
14
+
15
+ def read_waypoints(data)
16
+ waypoint_count = BinData::Uint32le.read(data)
17
+ LOGGER.debug "reading #{waypoint_count} waypoints"
18
+ waypoint_count.times do |j|
19
+ waypoint = Waypoint.new(self, data)
20
+ add_waypoint(waypoint)
21
+ @last_time = waypoint.time
22
+ end
23
+ end
24
+
25
+ def add_waypoint(*points)
26
+ points.each do |point|
27
+ waypoints << point
28
+ end
29
+ end
30
+
31
+ def calculate_waypoints
32
+ segment_distance = 0
33
+ waypoints.each_with_index do |wp, idx|
34
+ segment_distance += (idx == 0 ? 0 : wp.distance_to(waypoints[idx - 1]))
35
+ wp.distance = route.distance + segment_distance
36
+
37
+ wp.elapsed_time = route.elapsed_time + wp.time - waypoints.first.time
38
+ end
39
+ @route.add_distance(segment_distance)
40
+ @route.add_elapsed_time(waypoints.last.time - waypoints.first.time)
41
+ end
42
+
43
+ def index
44
+ @route.segments.index(self)
45
+ end
46
+
47
+ def parameterized_location_from_time(time)
48
+ lower, upper = 0, waypoints.size - 1
49
+
50
+ # Binary search to find the closest waypoint
51
+ while lower <= upper
52
+ idx = lower + (upper - lower) / 2
53
+ currtime = waypoints[idx].time
54
+ if (time - currtime).abs < EPSILON
55
+ return ParameterizedLocation.new(index, idx)
56
+ end
57
+
58
+ if time < currtime
59
+ upper = idx - 1
60
+ else
61
+ lower = idx + 1
62
+ end
63
+ end
64
+
65
+ t0 = waypoints[upper].time
66
+ t1 = waypoints[lower].time
67
+ if t1 == t0
68
+ return ParameterizedLocation.new(index, upper)
69
+ end
70
+
71
+ ParameterizedLocation.new(index, upper + (time - t0) / (t1 - t0))
72
+ end
73
+
74
+ def has_time?(time)
75
+ ((waypoints.first.time - EPSILON)..(waypoints.last.time + EPSILON)).include?(time)
76
+ end
77
+ end
data/lib/session.rb ADDED
@@ -0,0 +1,113 @@
1
+ class Session
2
+ include BinData
3
+ attr_accessor :route
4
+ attr_reader :laps, :handles,
5
+ :projection_origin, :session_info,
6
+ :straight_line_distance
7
+
8
+ def self.read_sessions(data)
9
+ sessions = []
10
+ session_count = BinData::Uint32le.read(data)
11
+ LOGGER.debug "reading #{session_count} sessions"
12
+
13
+ session_count.times do |i|
14
+ tag = TagDataExtractor.read(data)
15
+
16
+ LOGGER.debug "in session #{i}, tag is #{tag} and is #{tag.data_length} bytes long"
17
+
18
+ if TAGS[tag.tag] == :session
19
+ sessions << read_session(data, tag.data_length)
20
+ end
21
+ end
22
+ sessions
23
+ end
24
+
25
+ def self.read_session(data, tag_data_length)
26
+ new.parse_data(data, tag_data_length)
27
+ end
28
+
29
+ def initialize
30
+ @laps = []
31
+ @handles = []
32
+ @straight_line_distance = 0
33
+ LOGGER.debug "Reading new session"
34
+ end
35
+
36
+ def calculate
37
+ LOGGER.debug("starting to calculate shit for #{self.inspect}")
38
+ route.calculate_parameters
39
+ calculate_laps
40
+ end
41
+
42
+ def calculate_laps
43
+ last_distance, last_lap = 0, nil
44
+
45
+ @laps.each do |lap|
46
+ pl = route.parameterized_location_from_time(lap.time)
47
+ lap.position = route.position_from_parameterized_location(pl)
48
+
49
+ distance = route.distance_from_parameterized_location(pl)
50
+ if lap.is_of_type?(:lap, :stop)
51
+ lap.distance = distance - last_distance
52
+
53
+ if last_lap
54
+ lap.straight_line_distance = lap.position.distance_to(last_lap.position)
55
+ else
56
+ lap.straight_line_distance = 0
57
+ end
58
+ @straight_line_distance += lap.straight_line_distance
59
+ end
60
+ last_distance = distance
61
+ last_lap = lap
62
+ end
63
+
64
+ LOGGER.debug("Laps after calculation: #{@laps.inspect}")
65
+ end
66
+
67
+ def parse_data(data, tag_data_length)
68
+ start_pos = data.pos
69
+ while data.pos < (start_pos + tag_data_length)
70
+ tag = TagDataExtractor.read(data)
71
+
72
+ LOGGER.debug "tag is #{tag}, #{tag.data_length} bytes"
73
+
74
+ send("set_#{TAGS[tag.tag]}", data)
75
+ end
76
+ self
77
+ end
78
+
79
+ private
80
+
81
+ def set_route(data)
82
+ LOGGER.debug "reading route"
83
+ @route = Route.new.read_data(data)
84
+ end
85
+
86
+ def set_handles(data)
87
+ LOGGER.debug "reading handles"
88
+ handle_count = Uint32le.read(data)
89
+ LOGGER.debug "reading #{handle_count} handles"
90
+ handle_count.times do |i|
91
+ @handles << Handle.new(data)
92
+ end
93
+ end
94
+
95
+ def set_projection_origin(data)
96
+ LOGGER.debug "reading project origin"
97
+ @projection_origin = LongLat.from_data(data)
98
+ end
99
+
100
+ def set_laps(data)
101
+ LOGGER.debug "reading laps"
102
+ lap_count = Uint32le.read(data)
103
+ LOGGER.debug "reading #{lap_count} laps"
104
+ lap_count.times do
105
+ @laps << Lap.new(data)
106
+ end
107
+ end
108
+
109
+ def set_session_info(data)
110
+ LOGGER.debug "reading session info"
111
+ @session_info = SessionInfo.new(data)
112
+ end
113
+ end
@@ -0,0 +1,11 @@
1
+ class SessionInfo
2
+ attr_reader :person
3
+
4
+ def initialize(data)
5
+ @person = Person.new(data)
6
+
7
+ length = BinData::Uint16le.read(data)
8
+ LOGGER.debug "description length is #{length}"
9
+ @description = BinData::String.new(:length => length).read(data)
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_b
3
+ BinData::String.new(self).to_binary_s
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class TagDataExtractor < BinData::Record
2
+ endian :little
3
+ uint8be :tag
4
+ uint32 :data_length
5
+ end
data/lib/waypoint.rb ADDED
@@ -0,0 +1,58 @@
1
+ class Waypoint
2
+ include BinData
3
+ include DateTimeParser
4
+
5
+ attr_reader :segment, :time, :heart_rate, :altitude
6
+ attr_accessor :distance, :position, :elapsed_time
7
+
8
+ def initialize(segment, data = nil)
9
+ @segment = segment
10
+ @distance = 0
11
+ read_data(data) if data
12
+ end
13
+
14
+ def read_data(data)
15
+ if route.has_attribute?(:position)
16
+ LOGGER.debug "route did have the position attribute"
17
+ @position = LongLat.from_data(data)
18
+ LOGGER.debug "waypoint position: #{@position.inspect}"
19
+ end
20
+
21
+ if route.has_attribute?(:time)
22
+ LOGGER.debug "route did have the time attribute"
23
+ time_type = BinData::Uint8le.read(data)
24
+ if time_type == 0
25
+ time = read_date_time(data)
26
+ else
27
+ time = last_time + BinData::Uint16le.read(data) / 1000
28
+ end
29
+ LOGGER.debug "time was #{Time.at(time)}"
30
+ @time = time
31
+ end
32
+
33
+ if route.has_attribute?(:heart_rate)
34
+ LOGGER.debug "route did have the heart rate attribute"
35
+ @heartrate = Uint8be.read(data)
36
+ end
37
+
38
+ if route.has_attribute?(:altitude)
39
+ LOGGER.debug "route did have the altitude attribute"
40
+ @altitude = Uint16le.read(data)
41
+ LOGGER.debug "altitude was #{@altitude}"
42
+ end
43
+
44
+ data.seek(route.extra_waypoints_attributes_length, ::IO::SEEK_CUR)
45
+ end
46
+
47
+ def last_time
48
+ segment.last_time
49
+ end
50
+
51
+ def route
52
+ segment.route
53
+ end
54
+
55
+ def distance_to(other)
56
+ position.distance_to(other.position)
57
+ end
58
+ end
@@ -0,0 +1,108 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe "Parsing existing jpg file without calculation" do
4
+ before(:all) do
5
+ @filename = File.join(File.expand_path(File.dirname(__FILE__)),
6
+ '../fixtures/2010-Ankkurirastit.jpg')
7
+
8
+ @qp = QuickrouteJpegParser.new(File.join(@filename), false)
9
+ end
10
+
11
+ it "should have correct version" do
12
+ @qp.version.should == "1.0.0.0"
13
+ end
14
+
15
+ it "should have correct size in pixels" do
16
+ @qp.map_location_and_size_in_pixels.width.should == 1365
17
+ @qp.map_location_and_size_in_pixels.height.should == 1556
18
+ end
19
+
20
+ it "should have one session" do
21
+ @qp.sessions.size.should equal(1)
22
+ end
23
+
24
+ describe "session" do
25
+ before do
26
+ @it = @qp.sessions.first
27
+ end
28
+
29
+ it "should have correct session info" do
30
+ @it.session_info.person.name.should == "kari ja maria"
31
+ end
32
+
33
+ it "should have 17 laps" do
34
+ @it.laps.size.should equal(17)
35
+ end
36
+
37
+ describe "lap" do
38
+ it "should have correct time" do
39
+ Time.at(@it.laps.first.time).to_s.should == "2010-04-18 11:48:03 +0300"
40
+ end
41
+ end
42
+
43
+ it "should have 39 handles" do
44
+ @it.handles.size.should equal(39)
45
+ end
46
+
47
+ it "should have correct projection origin" do
48
+ @it.projection_origin.latitude.should == 60.33081694444444
49
+ @it.projection_origin.longitude.should == 22.894455
50
+ end
51
+
52
+ describe "route" do
53
+ it "should have one segment" do
54
+ @it.route.segments.size.should equal(1)
55
+ end
56
+
57
+ describe "segment" do
58
+ it "should have 1001 waypoints" do
59
+ @it.route.segments.first.waypoints.size.should equal(1001)
60
+ end
61
+
62
+ describe "first waypoint" do
63
+ before do
64
+ @wp = @it.route.segments.first.waypoints.first
65
+ end
66
+
67
+ it "should have correct time set" do
68
+ Time.at(@wp.time).to_s.should == "2010-04-18 11:48:03 +0300"
69
+ end
70
+
71
+ it "should have correct position" do
72
+ @wp.position.latitude.should == 60.34066361111111
73
+ @wp.position.longitude.should == 22.904983333333334
74
+ end
75
+
76
+ it "should have correct altitude" do
77
+ @wp.altitude.should == 62
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ describe "Parsing existing jpg file with calculation" do
86
+ before(:all) do
87
+ @filename = File.join(File.expand_path(File.dirname(__FILE__)),
88
+ '../fixtures/2010-Ankkurirastit.jpg')
89
+
90
+ @qp = QuickrouteJpegParser.new(File.join(@filename), true)
91
+ end
92
+
93
+ describe "session" do
94
+ it "should have correct straight line distance" do
95
+ @qp.sessions.first.straight_line_distance.round.should == 6750
96
+ end
97
+ end
98
+
99
+ describe "route" do
100
+ it "should have correct distance" do
101
+ @qp.sessions.first.route.distance.round.should == 7566
102
+ end
103
+
104
+ it "should have correct elapsed time" do
105
+ @qp.sessions.first.route.elapsed_time.should == 2706.0
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Lap do
4
+ before(:each) do
5
+ @it = Lap.new
6
+ end
7
+
8
+ describe "setting position" do
9
+ it "should set the position ivar and be readable" do
10
+ long_lat = LongLat.new(0,0)
11
+ @it.position = long_lat
12
+
13
+ @it.position.should == long_lat
14
+ end
15
+ end
16
+
17
+ describe "setting distance" do
18
+ it "should work" do
19
+ @it.distance = 69
20
+ @it.distance.should == 69
21
+ end
22
+ end
23
+
24
+ describe "setting straight line distance" do
25
+ it "should work" do
26
+ @it.straight_line_distance = 69
27
+ @it.straight_line_distance.should == 69
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe QuickrouteJpegParser do
4
+ describe '#new' do
5
+ describe 'with calculate == false' do
6
+ it "should not calculate values" do
7
+ #TagDataExtractor.stub!(:extract_tag_data).and_return([5, 16])
8
+ JpegReader.stub!(:fetch_data_from).and_return("")
9
+ @session = Session.new
10
+ Session.stub!(:read_sessions).and_return([@session])
11
+ @session.should_not_receive(:calculate)
12
+ @parser = QuickrouteJpegParser.new("filename", false)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,50 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe RouteSegment do
4
+ before(:each) do
5
+ @route = Route.new
6
+ @it = RouteSegment.new(@route)
7
+ @wp1 = Waypoint.new(@it)
8
+ @wp2 = Waypoint.new(@it)
9
+ @wp3 = Waypoint.new(@it)
10
+ end
11
+
12
+ describe "#add_waypoints" do
13
+ it "should add waypoints to the segment" do
14
+ @it.add_waypoint(@wp1, @wp2, @wp3)
15
+ @it.waypoints.should == [@wp1, @wp2, @wp3]
16
+ end
17
+ end
18
+
19
+ describe "#calculate_waypoints" do
20
+ before(:each) do
21
+ @it.add_waypoint(@wp1, @wp2, @wp3)
22
+ @wp2.should_receive(:distance_to).with(@wp1).and_return(10)
23
+ @wp3.should_receive(:distance_to).with(@wp2).and_return(20)
24
+
25
+ t1 = Time.now.to_i
26
+ @wp1.should_receive(:time).at_least(:once).and_return(t1)
27
+ @wp2.should_receive(:time).at_least(:once).and_return(t1 + 60)
28
+ @wp3.should_receive(:time).at_least(:once).and_return(t1 + 180)
29
+
30
+ @it.calculate_waypoints
31
+ end
32
+
33
+ it "should calculate the total distance for each waypoint" do
34
+ @wp1.distance.should == 0
35
+ @wp2.distance.should == 10
36
+ @wp3.distance.should == 30
37
+ end
38
+
39
+ it "should calculate the total elapsed time for each waypoint" do
40
+ @wp1.elapsed_time.should == 0
41
+ @wp2.elapsed_time.should == 60
42
+ @wp3.elapsed_time.should == 180
43
+ end
44
+
45
+ it "should update route's distance and time in the end" do
46
+ @route.distance.should == 30
47
+ @route.elapsed_time == 180
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Route do
4
+ before(:each) do
5
+ @it = Route.new
6
+ end
7
+
8
+ describe "#calculate_parameters" do
9
+ before(:each) do
10
+ @segment = stub
11
+ @it.stub(:segments).and_return([@segment])
12
+ end
13
+
14
+ it "should calculate total distance for each segment" do
15
+ @segment.should_receive(:calculate_waypoints)
16
+ @it.calculate_parameters
17
+ end
18
+ end
19
+
20
+ describe "#add_distance" do
21
+ it "should add given distance" do
22
+ @it.distance.should == 0
23
+ @it.add_distance(180)
24
+ @it.distance.should == 180
25
+ end
26
+ end
27
+
28
+ describe "#add_elapsed_time" do
29
+ it "should add given elapsed time" do
30
+ @it.elapsed_time.should == 0
31
+ @it.add_elapsed_time(180)
32
+ @it.elapsed_time.should == 180
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Session do
4
+ before(:each) do
5
+ @it = Session.new
6
+ @it.route = @route = Route.new
7
+ end
8
+
9
+ describe "calculate" do
10
+ it "should call route.calculate" do
11
+ @route.should_receive(:calculate_parameters)
12
+ @it.calculate
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Waypoint do
4
+ before(:each) do
5
+ @route = Route.new
6
+ @segment = stub(:segment, :route => @route)
7
+ @position = LongLat.new(0,0)
8
+ LongLat.stub(:from_data).and_return(@position)
9
+ @it = Waypoint.new(@segment)
10
+ end
11
+
12
+ it "should have a zero distance" do
13
+ @it.distance.should == 0
14
+ end
15
+
16
+ it "should be able to set elapsed time" do
17
+ @it.elapsed_time = 180
18
+ @it.elapsed_time.should == 180
19
+ end
20
+
21
+ describe "#distance_to" do
22
+ it "should delegate to position" do
23
+ @other = Waypoint.new(@segment)
24
+ @op = LongLat.new(0,1)
25
+ @other.position = @op
26
+ @it.position = @position
27
+ @position.should_receive(:distance_to).with(@op).and_return(50)
28
+ @it.distance_to(@other).should == 50
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1 @@
1
+ require_relative '../lib/quickroute'
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quickroute
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jarkko Laine
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70239720740460 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.7.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70239720740460
25
+ - !ruby/object:Gem::Dependency
26
+ name: bindata
27
+ requirement: &70239720739980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.4.3
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70239720739980
36
+ - !ruby/object:Gem::Dependency
37
+ name: binary_search
38
+ requirement: &70239720739520 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.2.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70239720739520
47
+ description: Library for parsing the GPS data embedded to JPG files by QuickRoute
48
+ (http://www.matstroeng.se/quickroute/en/).
49
+ email: jarkko@jlaine.net
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/date_time_parser.rb
55
+ - lib/handle.rb
56
+ - lib/jpeg_reader.rb
57
+ - lib/lap.rb
58
+ - lib/long_lat.rb
59
+ - lib/parameterized_location.rb
60
+ - lib/parse_test.rb
61
+ - lib/person.rb
62
+ - lib/point.rb
63
+ - lib/quickroute.rb
64
+ - lib/quickroute_jpeg_parser.rb
65
+ - lib/rectangle.rb
66
+ - lib/route.rb
67
+ - lib/route_segment.rb
68
+ - lib/session.rb
69
+ - lib/session_info.rb
70
+ - lib/string_extensions.rb
71
+ - lib/tag_data_extractor.rb
72
+ - lib/waypoint.rb
73
+ - spec/fixtures/2010-Ankkurirastit.jpg
74
+ - spec/integration/parsing_spec.rb
75
+ - spec/models/lap_spec.rb
76
+ - spec/models/quickroute_jpeg_parser_spec.rb
77
+ - spec/models/route_segment_spec.rb
78
+ - spec/models/route_spec.rb
79
+ - spec/models/session_spec.rb
80
+ - spec/models/waypoint_spec.rb
81
+ - spec/spec_helper.rb
82
+ - README.md
83
+ homepage: https://github.com/jarkko/quickroute-ruby
84
+ licenses: []
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: 1.9.2
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.13
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Parser library for QuickRoute JPG files with embedded route data
107
+ test_files: []