broutes 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/broutes/formats/factory.rb +22 -0
- data/lib/broutes/formats/fit_file.rb +41 -0
- data/lib/broutes/formats/gpx_route.rb +9 -0
- data/lib/broutes/formats/gpx_track.rb +30 -0
- data/lib/broutes/formats/tcx.rb +96 -0
- data/lib/broutes/formats.rb +9 -0
- data/lib/broutes/geo_point.rb +56 -0
- data/lib/broutes/geo_route.rb +109 -0
- data/lib/broutes/maths.rb +26 -0
- data/lib/broutes/version.rb +3 -0
- data/lib/broutes.rb +26 -0
- data/spec/broutes/formats/factory_spec.rb +34 -0
- data/spec/broutes/formats/fit_file_spec.rb +54 -0
- data/spec/broutes/formats/gpx_route_spec.rb +7 -0
- data/spec/broutes/formats/gpx_track_spec.rb +63 -0
- data/spec/broutes/formats/tcx_spec.rb +152 -0
- data/spec/broutes/geo_point_spec.rb +68 -0
- data/spec/broutes/geo_route_spec.rb +190 -0
- data/spec/broutes/maths_spec.rb +16 -0
- data/spec/broutes_spec.rb +26 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/support/garmin_training_center.tcx +115353 -0
- data/spec/support/gpx_route.xml +755 -0
- data/spec/support/no_gps_coordinates.tcx +264 -0
- data/spec/support/sample fit read log.txt +18 -0
- data/spec/support/sample.fit +0 -0
- data/spec/support/single_lap.tcx +10732 -0
- data/spec/support/single_lap_gpx_track.gpx +11087 -0
- data/spec/support/single_lap_gpx_track_no_elevation.gpx +2 -0
- data/spec/support/summary_no_points.tcx +52 -0
- metadata +119 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Formats::Tcx do
|
4
|
+
describe "#load" do
|
5
|
+
before(:all) do
|
6
|
+
@file = open_file('single_lap.tcx')
|
7
|
+
@target = Formats::Tcx.new
|
8
|
+
@route = GeoRoute.new
|
9
|
+
|
10
|
+
@target.load(@file, @route)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "sets the start point lat" do
|
14
|
+
@route.start_point.lat.should eq(52.94124)
|
15
|
+
end
|
16
|
+
it "sets the start point lon" do
|
17
|
+
@route.start_point.lon.should eq(-1.26039)
|
18
|
+
end
|
19
|
+
it "sets the total distance" do
|
20
|
+
@route.total_distance.should eq(76037)
|
21
|
+
end
|
22
|
+
it "sets the total ascent" do
|
23
|
+
@route.total_ascent.round.should eq(1203)
|
24
|
+
end
|
25
|
+
it "sets the total descent" do
|
26
|
+
@route.total_descent.round.should eq(1204)
|
27
|
+
end
|
28
|
+
it "sets the total time" do
|
29
|
+
@route.total_time.round.should eq(10631)
|
30
|
+
end
|
31
|
+
it "sets the started_at" do
|
32
|
+
@route.started_at.to_i.should eq(Time.new(2012, 3, 15, 21, 20, 38).to_i)
|
33
|
+
end
|
34
|
+
it "sets the ended_at" do
|
35
|
+
@route.ended_at.to_i.should eq(Time.new(2012, 3, 16, 00, 17, 49).to_i)
|
36
|
+
end
|
37
|
+
it "can create hash" do
|
38
|
+
@route.to_hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#load Garmin Training Centre" do
|
43
|
+
before(:all) do
|
44
|
+
@file = open_file('garmin_training_center.tcx')
|
45
|
+
@target = Formats::Tcx.new
|
46
|
+
@route = GeoRoute.new
|
47
|
+
|
48
|
+
@target.load(@file, @route)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "sets the start point lat" do
|
52
|
+
@route.start_point.lat.should eq(52.930873)
|
53
|
+
end
|
54
|
+
it "sets the start point lon" do
|
55
|
+
@route.start_point.lon.should eq(-1.2236503)
|
56
|
+
end
|
57
|
+
it "extracts the heart rate" do
|
58
|
+
@route.start_point.heart_rate.should eq(77)
|
59
|
+
end
|
60
|
+
it "extracts the cadence" do
|
61
|
+
@route.start_point.cadence.should eq(85)
|
62
|
+
end
|
63
|
+
it "extracts the speed" do
|
64
|
+
@route.start_point.speed.should eq(11.3190002)
|
65
|
+
end
|
66
|
+
it "extracts the power" do
|
67
|
+
@route.start_point.power.should eq(297)
|
68
|
+
end
|
69
|
+
it "sets the total distance" do
|
70
|
+
@route.total_distance.should eq(43892)
|
71
|
+
end
|
72
|
+
it "sets the total ascent" do
|
73
|
+
@route.total_ascent.round.should eq(416)
|
74
|
+
end
|
75
|
+
it "sets the total descent" do
|
76
|
+
@route.total_descent.round.should eq(404)
|
77
|
+
end
|
78
|
+
it "sets the total time" do
|
79
|
+
@route.total_time.round.should eq(6926)
|
80
|
+
end
|
81
|
+
it "can create hash" do
|
82
|
+
@route.to_hash
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "file without GPS coordinates" do
|
87
|
+
before(:all) do
|
88
|
+
@file = open_file('no_gps_coordinates.tcx')
|
89
|
+
@target = Formats::Tcx.new
|
90
|
+
@route = GeoRoute.new
|
91
|
+
|
92
|
+
@target.load(@file, @route)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "sets the total time" do
|
96
|
+
@route.total_time.should eq(653)
|
97
|
+
end
|
98
|
+
it "sets the start point lat" do
|
99
|
+
@route.start_point.lat.should be_nil
|
100
|
+
end
|
101
|
+
it "sets the start point lon" do
|
102
|
+
@route.start_point.lon.should be_nil
|
103
|
+
end
|
104
|
+
it "sets the total distance" do
|
105
|
+
@route.total_distance.should eq(0)
|
106
|
+
end
|
107
|
+
it "sets the total ascent" do
|
108
|
+
@route.total_ascent.should eq(2.403564500000016)
|
109
|
+
end
|
110
|
+
it "sets the total descent" do
|
111
|
+
@route.total_descent.should eq(2.8841553000000033)
|
112
|
+
end
|
113
|
+
it "can create hash" do
|
114
|
+
@route.to_hash
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "file without points only summary" do
|
119
|
+
before(:all) do
|
120
|
+
@file = open_file('summary_no_points.tcx')
|
121
|
+
@target = Formats::Tcx.new
|
122
|
+
@route = GeoRoute.new
|
123
|
+
|
124
|
+
@target.load(@file, @route)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "sets the total time" do
|
128
|
+
@route.total_time.should eq(2490)
|
129
|
+
end
|
130
|
+
it "start_point should be nil" do
|
131
|
+
@route.start_point.should be_nil
|
132
|
+
end
|
133
|
+
it "sets the total distance" do
|
134
|
+
@route.total_distance.should eq(21146)
|
135
|
+
end
|
136
|
+
it "sets the total ascent" do
|
137
|
+
@route.total_ascent.should eq(0)
|
138
|
+
end
|
139
|
+
it "sets the total descent" do
|
140
|
+
@route.total_descent.should eq(0)
|
141
|
+
end
|
142
|
+
it "can create hash" do
|
143
|
+
@route.to_hash
|
144
|
+
end
|
145
|
+
it "sets the started_at" do
|
146
|
+
@route.started_at.to_i.should eq(DateTime.parse("2012-09-05T18:55:11Z").to_time.to_i)
|
147
|
+
end
|
148
|
+
it "include the started at in the hash" do
|
149
|
+
@route.to_hash['started_at'].should_not be_nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GeoPoint do
|
4
|
+
describe "#to_hash" do
|
5
|
+
before(:each) do
|
6
|
+
@point = GeoPoint.new(lat: random_lat, lon: random_lon)
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { @point.to_hash }
|
10
|
+
|
11
|
+
it "contains lat" do
|
12
|
+
subject['lat'].should eq(@point.lat)
|
13
|
+
end
|
14
|
+
it "contains lon" do
|
15
|
+
subject['lon'].should eq(@point.lon)
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when only lat lon set" do
|
19
|
+
it "should not contain distance" do
|
20
|
+
subject.keys.should_not include('distance')
|
21
|
+
end
|
22
|
+
it "should not contain elevation" do
|
23
|
+
subject.keys.should_not include('elevation')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
context "when elevation set" do
|
27
|
+
before(:each) do
|
28
|
+
@point.elevation = random_elevation
|
29
|
+
end
|
30
|
+
it "should contain elevation" do
|
31
|
+
subject['elevation'].should eq(@point.elevation)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context "when distance set" do
|
35
|
+
before(:each) do
|
36
|
+
@point.distance = random_distance
|
37
|
+
end
|
38
|
+
it "should contain distance" do
|
39
|
+
subject['distance'].should eq(@point.distance)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#time=" do
|
45
|
+
let(:point) { GeoPoint.new }
|
46
|
+
|
47
|
+
subject { point.time = @value }
|
48
|
+
|
49
|
+
context "when parseable string" do
|
50
|
+
before(:each) do
|
51
|
+
@value = "2013-01-12T08:33:11Z"
|
52
|
+
end
|
53
|
+
it "should eq the time" do
|
54
|
+
subject
|
55
|
+
point.time.to_i.should eq(Time.utc(2013, 1, 12, 8, 33, 11).to_i)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "when is a time" do
|
59
|
+
before(:each) do
|
60
|
+
@value = Time.new(2013, 4, 12, 12, 34, 45)
|
61
|
+
end
|
62
|
+
it "should eq the time" do
|
63
|
+
subject
|
64
|
+
point.time.to_i.should eq(@value.to_i)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GeoRoute do
|
4
|
+
describe "#add_point" do
|
5
|
+
before(:each) do
|
6
|
+
@route = GeoRoute.new
|
7
|
+
@lat = random_lat
|
8
|
+
@lon = random_lon
|
9
|
+
@elevation = 35.6000000
|
10
|
+
@new_point = GeoPoint.new(lat: @lat, lon: @lon, elevation: @elevation, distance: 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { @route.add_point(lat: @lat, lon: @lon, elevation: @elevation) }
|
14
|
+
|
15
|
+
context "when route is empty" do
|
16
|
+
|
17
|
+
it "sets the start point to the new_point" do
|
18
|
+
subject
|
19
|
+
@route.start_point.should eq(@new_point)
|
20
|
+
end
|
21
|
+
it "should set the total distance to zero" do
|
22
|
+
subject
|
23
|
+
@route.total_distance.should eq(0)
|
24
|
+
end
|
25
|
+
it "should add the start point to the points list" do
|
26
|
+
subject
|
27
|
+
@route.points.first.should eq(@route.start_point)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
context "when route already has a start point" do
|
31
|
+
before(:each) do
|
32
|
+
@start_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: random_elevation, distance: 0)
|
33
|
+
@route.add_point(lat: @start_point.lat, lon: @start_point.lon, elevation: @start_point.elevation)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should not change start_point" do
|
37
|
+
subject
|
38
|
+
@route.start_point.should eq(@start_point)
|
39
|
+
end
|
40
|
+
it "should set the total distance to be haversine distance between the start_point and the new point" do
|
41
|
+
subject
|
42
|
+
@route.total_distance.should eq(Maths.haversine_distance(@start_point, @new_point).round)
|
43
|
+
end
|
44
|
+
it "set the distance of the point to be the haverside_distance between the start_point" do
|
45
|
+
subject
|
46
|
+
last(@route.points).distance.should eq(Maths.haversine_distance(@start_point, @new_point))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when route already has at least two points" do
|
51
|
+
before(:each) do
|
52
|
+
@start_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: random_elevation)
|
53
|
+
@next_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: random_elevation)
|
54
|
+
@route.add_point(lat: @start_point.lat, lon: @start_point.lon, elevation: @start_point.elevation)
|
55
|
+
@route.add_point(lat: @next_point.lat, lon: @next_point.lon, elevation: @next_point.elevation)
|
56
|
+
end
|
57
|
+
it "should set the total distance to haversine distance along all points" do
|
58
|
+
subject
|
59
|
+
@route.total_distance.should be_within(1).of(
|
60
|
+
Maths.haversine_distance(@start_point, @next_point).round +
|
61
|
+
Maths.haversine_distance(@next_point, @new_point).round
|
62
|
+
)
|
63
|
+
end
|
64
|
+
it "set the distance of the point to haversine distance along all points" do
|
65
|
+
subject
|
66
|
+
last(@route.points).distance.should eq(
|
67
|
+
Maths.haversine_distance(@start_point, @next_point) +
|
68
|
+
Maths.haversine_distance(@next_point, @new_point)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
describe "#process_elevation_delta" do
|
74
|
+
before(:each) do
|
75
|
+
@route = GeoRoute.new
|
76
|
+
@next_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: random_elevation)
|
77
|
+
end
|
78
|
+
|
79
|
+
subject { @route.process_elevation_delta(@last_point, @next_point) }
|
80
|
+
|
81
|
+
context "when last_point is nil" do
|
82
|
+
it "has an total_ascent of nil" do
|
83
|
+
subject
|
84
|
+
@route.total_ascent.should eq(0)
|
85
|
+
end
|
86
|
+
it "has an total_descent of nil" do
|
87
|
+
subject
|
88
|
+
@route.total_descent.should eq(0)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
context "when last_point is same elevation as next point" do
|
92
|
+
before(:each) do
|
93
|
+
@last_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: @next_point.elevation)
|
94
|
+
end
|
95
|
+
it "has an total_ascent of zero" do
|
96
|
+
subject
|
97
|
+
@route.total_ascent.should eq(0)
|
98
|
+
end
|
99
|
+
it "has an total_descent of zero" do
|
100
|
+
subject
|
101
|
+
@route.total_descent.should eq(0)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
context "when last_point is lower than the next point" do
|
105
|
+
before(:each) do
|
106
|
+
@delta = random_elevation
|
107
|
+
@last_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: @next_point.elevation - @delta)
|
108
|
+
end
|
109
|
+
it "the delta is added to the total_ascent" do
|
110
|
+
subject
|
111
|
+
round_to(@route.total_ascent, 3).should eq(@delta)
|
112
|
+
end
|
113
|
+
it "has an total_descent of zero" do
|
114
|
+
subject
|
115
|
+
@route.total_descent.should eq(0)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
context "when last_point is higher than the next point" do
|
119
|
+
before(:each) do
|
120
|
+
@delta = random_elevation
|
121
|
+
@last_point = GeoPoint.new(lat: random_lat, lon: random_lon, elevation: @next_point.elevation + @delta)
|
122
|
+
end
|
123
|
+
it "has an total_ascent of zero" do
|
124
|
+
subject
|
125
|
+
@route.total_ascent.should eq(0)
|
126
|
+
end
|
127
|
+
it "the delta is added to the total_descent" do
|
128
|
+
subject
|
129
|
+
round_to(@route.total_descent, 3).should eq(@delta)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
describe "#hilliness" do
|
134
|
+
before(:each) do
|
135
|
+
@route = GeoRoute.new
|
136
|
+
end
|
137
|
+
|
138
|
+
subject { @route.hilliness }
|
139
|
+
|
140
|
+
context "when 1000 m ascent in 100km" do
|
141
|
+
before(:each) do
|
142
|
+
@route.stub(:total_distance) { 100000 }
|
143
|
+
@route.stub(:total_ascent) { 1000 }
|
144
|
+
end
|
145
|
+
it "is 10" do
|
146
|
+
subject.should eq(10)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
context "when 0 ascent in 100km" do
|
150
|
+
before(:each) do
|
151
|
+
@route.stub(:total_distance) { 100000 }
|
152
|
+
@route.stub(:total_ascent) { 0 }
|
153
|
+
end
|
154
|
+
it "is 0" do
|
155
|
+
subject.should eq(0)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
context "when 1000 ascent in 0km" do
|
159
|
+
before(:each) do
|
160
|
+
@route.stub(:total_distance) { 0 }
|
161
|
+
@route.stub(:total_ascent) { 1000 }
|
162
|
+
end
|
163
|
+
it "is 0" do
|
164
|
+
subject.should eq(0)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe ".from_hash" do
|
170
|
+
let(:started_at) { Time.now }
|
171
|
+
let(:points) {[
|
172
|
+
GeoPoint.new(lat: random_lat, lon: random_lon, time: started_at),
|
173
|
+
GeoPoint.new(lat: random_lat, lon: random_lon, time: started_at + 1),
|
174
|
+
GeoPoint.new(lat: random_lat, lon: random_lon, time: started_at + 2)
|
175
|
+
]}
|
176
|
+
let(:hash) {{
|
177
|
+
'started_at' => started_at,
|
178
|
+
'points' => points.collect { |p| p.to_hash }
|
179
|
+
}}
|
180
|
+
|
181
|
+
subject { GeoRoute.from_hash hash }
|
182
|
+
|
183
|
+
it "set the started_at" do
|
184
|
+
subject.started_at.to_i.should eq(started_at.to_i)
|
185
|
+
end
|
186
|
+
it "has the requisite number of points" do
|
187
|
+
subject.points.count.should eq(points.count)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Maths do
|
4
|
+
describe ".haversine_distance" do
|
5
|
+
before(:each) do
|
6
|
+
@p1 = GeoPoint.new(lat: 39.06546, lon: -104.88544)
|
7
|
+
@p2 = GeoPoint.new(lat: 39.06546, lon: -104.80)
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { Maths.haversine_distance( @p1, @p2 ) }
|
11
|
+
|
12
|
+
it "equals 7376.435 to 3dp" do
|
13
|
+
round_to(subject, 3).should eq(7376.435)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Broutes do
|
4
|
+
describe ".from_file" do
|
5
|
+
before(:all) do
|
6
|
+
@file = open_file('single_lap_gpx_track.gpx')
|
7
|
+
@route = Broutes.from_file(@file, 'single_lap_gpx_track.gpx')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "sets the start point lat" do
|
11
|
+
@route.start_point.lat.should eq(52.9552055)
|
12
|
+
end
|
13
|
+
it "sets the start point lon" do
|
14
|
+
@route.start_point.lon.should eq(-1.1558583)
|
15
|
+
end
|
16
|
+
it "sets the total distance" do
|
17
|
+
@route.total_distance.should eq(7088)
|
18
|
+
end
|
19
|
+
it "sets the total ascent" do
|
20
|
+
@route.total_ascent.round.should eq(34)
|
21
|
+
end
|
22
|
+
it "sets the total descent" do
|
23
|
+
@route.total_descent.round.should eq(37)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rspec'
|
3
|
+
require "#{Rake.application.original_dir}/lib/broutes"
|
4
|
+
|
5
|
+
include Broutes
|
6
|
+
Broutes.logger.level = Logger::FATAL
|
7
|
+
|
8
|
+
def random_lat
|
9
|
+
rand + rand(180)
|
10
|
+
end
|
11
|
+
|
12
|
+
def random_lon
|
13
|
+
rand
|
14
|
+
end
|
15
|
+
|
16
|
+
def random_integer
|
17
|
+
rand(100000).to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def random_elevation
|
21
|
+
round_to(rand + rand(999), 3)
|
22
|
+
end
|
23
|
+
|
24
|
+
def random_distance
|
25
|
+
round_to(rand + rand(999), 3)
|
26
|
+
end
|
27
|
+
|
28
|
+
def random_string
|
29
|
+
(0...24).map{ ('a'..'z').to_a[rand(26)] }.join
|
30
|
+
end
|
31
|
+
|
32
|
+
def round_to(number, decimal_places)
|
33
|
+
if number
|
34
|
+
if decimal_places > 0
|
35
|
+
((number * 10**decimal_places).round.to_f / 10**decimal_places)
|
36
|
+
else
|
37
|
+
number.round
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def last(enum)
|
43
|
+
enum.collect{|item| item}.reverse.first
|
44
|
+
end
|
45
|
+
|
46
|
+
def open_file(name)
|
47
|
+
File.open("#{Rake.application.original_dir}/spec/support/#{name}")
|
48
|
+
end
|