broutes 0.1.3
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.
- 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
|