gooby 0.9.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.
- data/README +242 -0
- data/bin/example_usage.rb +38 -0
- data/bin/tests_gen.rb +16 -0
- data/data/20050305_corporate_cup_hm.csv +251 -0
- data/data/20050305_corporate_cup_hm.xml +2208 -0
- data/data/20050430_nashville_marathon.csv +1208 -0
- data/data/20050430_nashville_marathon.xml +10043 -0
- data/data/20051119_dowd_ymca_hm.csv +251 -0
- data/data/20051119_dowd_ymca_hm.xml +2210 -0
- data/data/20051124_hyatt_turkey_trot_8K.csv +321 -0
- data/data/20051124_hyatt_turkey_trot_8K.xml +2651 -0
- data/data/2007_03_03.tcx +6207 -0
- data/data/forerunner_2007.xml +259014 -0
- data/data/geo_data.txt +171 -0
- data/data/phx.csv +1280 -0
- data/data/phx.xml +10620 -0
- data/data/run_2007_01_01_16_38_27.xml +2020 -0
- data/data/run_2007_01_06_15_27_31.xml +2020 -0
- data/data/run_2007_01_10_12_25_47.xml +820 -0
- data/data/run_2007_01_10_22_44_54.csv +112 -0
- data/data/run_2007_01_10_22_44_54.xml +908 -0
- data/data/run_2007_01_11_10_48_45.xml +1292 -0
- data/data/run_2007_01_13_15_37_06.xml +1964 -0
- data/data/run_2007_01_14_15_46_02.xml +1368 -0
- data/data/run_2007_01_15_14_01_48.xml +1868 -0
- data/data/run_2007_01_20_16_22_05.xml +1702 -0
- data/data/run_2007_01_27_17_32_13.xml +3626 -0
- data/data/run_2007_01_28_19_14_52.xml +2538 -0
- data/data/run_2007_02_03_14_30_20.xml +2016 -0
- data/data/run_2007_02_04_18_02_30.xml +1476 -0
- data/data/run_2007_02_17_16_29_35.xml +1236 -0
- data/data/run_2007_02_19_14_44_33.xml +1816 -0
- data/data/run_2007_02_23_15_53_55.xml +36 -0
- data/data/run_2007_02_23_15_55_20.xml +1296 -0
- data/data/run_2007_02_24_15_01_35.csv +484 -0
- data/data/run_2007_02_24_15_01_35.xml +3884 -0
- data/data/test1.txt +4 -0
- data/img/gicons/blank.png +0 -0
- data/img/gicons/dd-end.png +0 -0
- data/img/gicons/dd-start.png +0 -0
- data/img/gicons/marker.png +0 -0
- data/img/gicons/marker0.png +0 -0
- data/img/gicons/marker00.png +0 -0
- data/img/gicons/marker01.png +0 -0
- data/img/gicons/marker02.png +0 -0
- data/img/gicons/marker03.png +0 -0
- data/img/gicons/marker04.png +0 -0
- data/img/gicons/marker05.png +0 -0
- data/img/gicons/marker06.png +0 -0
- data/img/gicons/marker07.png +0 -0
- data/img/gicons/marker08.png +0 -0
- data/img/gicons/marker09.png +0 -0
- data/img/gicons/marker1.png +0 -0
- data/img/gicons/marker10.png +0 -0
- data/img/gicons/marker11.png +0 -0
- data/img/gicons/marker12.png +0 -0
- data/img/gicons/marker13.png +0 -0
- data/img/gicons/marker14.png +0 -0
- data/img/gicons/marker15.png +0 -0
- data/img/gicons/marker16.png +0 -0
- data/img/gicons/marker17.png +0 -0
- data/img/gicons/marker18.png +0 -0
- data/img/gicons/marker19.png +0 -0
- data/img/gicons/marker2.png +0 -0
- data/img/gicons/marker20.png +0 -0
- data/img/gicons/marker21.png +0 -0
- data/img/gicons/marker22.png +0 -0
- data/img/gicons/marker23.png +0 -0
- data/img/gicons/marker24.png +0 -0
- data/img/gicons/marker25.png +0 -0
- data/img/gicons/marker26.png +0 -0
- data/img/gicons/marker27.png +0 -0
- data/img/gicons/marker28.png +0 -0
- data/img/gicons/marker29.png +0 -0
- data/img/gicons/marker3.png +0 -0
- data/img/gicons/marker30.png +0 -0
- data/img/gicons/marker31.png +0 -0
- data/img/gicons/marker32.png +0 -0
- data/img/gicons/marker33.png +0 -0
- data/img/gicons/marker34.png +0 -0
- data/img/gicons/marker35.png +0 -0
- data/img/gicons/marker36.png +0 -0
- data/img/gicons/marker37.png +0 -0
- data/img/gicons/marker38.png +0 -0
- data/img/gicons/marker39.png +0 -0
- data/img/gicons/marker4.png +0 -0
- data/img/gicons/marker40.png +0 -0
- data/img/gicons/marker41.png +0 -0
- data/img/gicons/marker42.png +0 -0
- data/img/gicons/marker43.png +0 -0
- data/img/gicons/marker44.png +0 -0
- data/img/gicons/marker45.png +0 -0
- data/img/gicons/marker46.png +0 -0
- data/img/gicons/marker47.png +0 -0
- data/img/gicons/marker48.png +0 -0
- data/img/gicons/marker49.png +0 -0
- data/img/gicons/marker5.png +0 -0
- data/img/gicons/marker50.png +0 -0
- data/img/gicons/marker51.png +0 -0
- data/img/gicons/marker52.png +0 -0
- data/img/gicons/marker53.png +0 -0
- data/img/gicons/marker54.png +0 -0
- data/img/gicons/marker55.png +0 -0
- data/img/gicons/marker56.png +0 -0
- data/img/gicons/marker57.png +0 -0
- data/img/gicons/marker58.png +0 -0
- data/img/gicons/marker59.png +0 -0
- data/img/gicons/marker6.png +0 -0
- data/img/gicons/marker60.png +0 -0
- data/img/gicons/marker61.png +0 -0
- data/img/gicons/marker62.png +0 -0
- data/img/gicons/marker63.png +0 -0
- data/img/gicons/marker64.png +0 -0
- data/img/gicons/marker65.png +0 -0
- data/img/gicons/marker66.png +0 -0
- data/img/gicons/marker67.png +0 -0
- data/img/gicons/marker68.png +0 -0
- data/img/gicons/marker69.png +0 -0
- data/img/gicons/marker7.png +0 -0
- data/img/gicons/marker70.png +0 -0
- data/img/gicons/marker71.png +0 -0
- data/img/gicons/marker72.png +0 -0
- data/img/gicons/marker73.png +0 -0
- data/img/gicons/marker74.png +0 -0
- data/img/gicons/marker75.png +0 -0
- data/img/gicons/marker76.png +0 -0
- data/img/gicons/marker77.png +0 -0
- data/img/gicons/marker78.png +0 -0
- data/img/gicons/marker79.png +0 -0
- data/img/gicons/marker8.png +0 -0
- data/img/gicons/marker80.png +0 -0
- data/img/gicons/marker81.png +0 -0
- data/img/gicons/marker82.png +0 -0
- data/img/gicons/marker83.png +0 -0
- data/img/gicons/marker84.png +0 -0
- data/img/gicons/marker85.png +0 -0
- data/img/gicons/marker86.png +0 -0
- data/img/gicons/marker87.png +0 -0
- data/img/gicons/marker88.png +0 -0
- data/img/gicons/marker89.png +0 -0
- data/img/gicons/marker9.png +0 -0
- data/img/gicons/marker90.png +0 -0
- data/img/gicons/marker91.png +0 -0
- data/img/gicons/marker92.png +0 -0
- data/img/gicons/marker93.png +0 -0
- data/img/gicons/marker94.png +0 -0
- data/img/gicons/marker95.png +0 -0
- data/img/gicons/marker96.png +0 -0
- data/img/gicons/marker97.png +0 -0
- data/img/gicons/marker98.png +0 -0
- data/img/gicons/marker99.png +0 -0
- data/img/gicons/markerA.png +0 -0
- data/img/gicons/markerB.png +0 -0
- data/img/gicons/markerC.png +0 -0
- data/img/gicons/markerD.png +0 -0
- data/img/gicons/markerE.png +0 -0
- data/img/gicons/markerF.png +0 -0
- data/img/gicons/markerG.png +0 -0
- data/img/gicons/markerH.png +0 -0
- data/img/gicons/markerI.png +0 -0
- data/img/gicons/markerJ.png +0 -0
- data/img/gicons/mm_20_red.png +0 -0
- data/img/gicons/mm_20_shadow.png +0 -0
- data/img/gicons/readme.txt +11 -0
- data/img/gicons/shadow50.png +0 -0
- data/lib/gooby/cls_counter_hash.rb +78 -0
- data/lib/gooby/cls_delim_line.rb +35 -0
- data/lib/gooby/cls_dttm.rb +79 -0
- data/lib/gooby/cls_duration.rb +79 -0
- data/lib/gooby/cls_forerunner_xml_parser.rb +178 -0
- data/lib/gooby/cls_forerunner_xml_splitter.rb +109 -0
- data/lib/gooby/cls_geo_data.rb +181 -0
- data/lib/gooby/cls_gooby_command.rb +35 -0
- data/lib/gooby/cls_gooby_object.rb +18 -0
- data/lib/gooby/cls_google_map_generator.rb +363 -0
- data/lib/gooby/cls_history.rb +33 -0
- data/lib/gooby/cls_lap.rb +22 -0
- data/lib/gooby/cls_line.rb +75 -0
- data/lib/gooby/cls_options.rb +67 -0
- data/lib/gooby/cls_position.rb +44 -0
- data/lib/gooby/cls_run.rb +194 -0
- data/lib/gooby/cls_simple_xml_parser.rb +41 -0
- data/lib/gooby/cls_test_regen.rb +182 -0
- data/lib/gooby/cls_track.rb +47 -0
- data/lib/gooby/cls_trackpoint.rb +200 -0
- data/lib/gooby/mod_introspect.rb +26 -0
- data/lib/gooby/mod_io.rb +58 -0
- data/lib/gooby/mod_project_info.rb +80 -0
- data/lib/gooby/mod_string.rb +19 -0
- data/lib/gooby/mod_test_helper.rb +15 -0
- data/lib/gooby.rb +2265 -0
- data/pkg/code_header.txt +21 -0
- data/pkg/pkg.rb +236 -0
- data/pkg/test_header.txt +19 -0
- data/samples/20041113_richmond_marathon.html +532 -0
- data/samples/20050305_corporate_cup_hm.html +448 -0
- data/samples/20050430_nashville_marathon.html +530 -0
- data/samples/gps_point_capture.html +54 -0
- data/samples/phoenix_marathon.html +542 -0
- data/samples/run_2007_01_10_22_44_54.html +201 -0
- data/samples/run_2007_02_24_15_01_35.html +298 -0
- data/tests/tc_cls_counter_hash.rb +107 -0
- data/tests/tc_cls_delim_line.rb +74 -0
- data/tests/tc_cls_dttm.rb +131 -0
- data/tests/tc_cls_duration.rb +51 -0
- data/tests/tc_cls_forerunner_xml_parser.rb +70 -0
- data/tests/tc_cls_geo_data.xxx +71 -0
- data/tests/tc_cls_gooby_object.rb +26 -0
- data/tests/tc_cls_google_map_generator.rb +109 -0
- data/tests/tc_cls_history.rb +46 -0
- data/tests/tc_cls_lap.rb +38 -0
- data/tests/tc_cls_line.rb +110 -0
- data/tests/tc_cls_options.rb +79 -0
- data/tests/tc_cls_position.rb +66 -0
- data/tests/tc_cls_run.rb +142 -0
- data/tests/tc_cls_simple_xml_parser.rb +50 -0
- data/tests/tc_cls_track.rb +70 -0
- data/tests/tc_cls_trackpoint.rb +145 -0
- data/tests/tc_mod_introspect.rb +32 -0
- data/tests/tc_mod_io.rb +53 -0
- data/tests/tc_mod_project_info.rb +79 -0
- data/tests/tc_mod_string.rb +58 -0
- data/tests/ts_gooby.rb +1237 -0
- metadata +270 -0
data/lib/gooby.rb
ADDED
|
@@ -0,0 +1,2265 @@
|
|
|
1
|
+
# Packaged on Sat Mar 03 16:56:38 EST 2007
|
|
2
|
+
|
|
3
|
+
=begin
|
|
4
|
+
|
|
5
|
+
This file contains the classes and modules for the Gooby project.
|
|
6
|
+
Gooby = Google APIs + Ruby
|
|
7
|
+
|
|
8
|
+
See file 'tests/ts_gooby.rb' for the regression test suite.
|
|
9
|
+
|
|
10
|
+
Gooby - Copyright 2007 by Chris Joakim.
|
|
11
|
+
Gooby is available under GNU General Public License (GPL) license.
|
|
12
|
+
|
|
13
|
+
=end
|
|
14
|
+
|
|
15
|
+
require 'date'
|
|
16
|
+
require 'find'
|
|
17
|
+
require 'rbconfig'
|
|
18
|
+
require 'rexml/document'
|
|
19
|
+
require 'rexml/streamlistener'
|
|
20
|
+
require 'time'
|
|
21
|
+
require 'yaml'
|
|
22
|
+
|
|
23
|
+
module Gooby
|
|
24
|
+
|
|
25
|
+
=begin rdoc
|
|
26
|
+
This module is used to embed information about the Gooby project and
|
|
27
|
+
version into the codebase.
|
|
28
|
+
|
|
29
|
+
Gooby - Copyright 2007 by Chris Joakim.
|
|
30
|
+
Gooby is available under GNU General Public License (GPL) license.
|
|
31
|
+
=end
|
|
32
|
+
|
|
33
|
+
module GoobyProjectInfo
|
|
34
|
+
|
|
35
|
+
# Return a String version number, like '1.0.0'.
|
|
36
|
+
def project_version_number
|
|
37
|
+
'0.9.3'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Return a String date, like '2007/02/25'.
|
|
41
|
+
def project_date
|
|
42
|
+
'2007/03/03'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return a String year, like '2007'.
|
|
46
|
+
def project_year
|
|
47
|
+
project_date[0...4] # start, length
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Return a String containing copyright, year, and author.
|
|
51
|
+
def project_copyright
|
|
52
|
+
"Copyright (C) #{project_year} #{project_author}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Return a String containing GNU/GPL, and the gpl.html URL.
|
|
56
|
+
def project_license
|
|
57
|
+
'GNU General Public License (GPL). See http://www.gnu.org/copyleft/gpl.html'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Return a String containing the project author name.
|
|
61
|
+
def project_author
|
|
62
|
+
'Chris Joakim'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def google_maps_api_level
|
|
66
|
+
'2'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def some_new_thing
|
|
70
|
+
'2'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def tested_files
|
|
74
|
+
array = Array.new
|
|
75
|
+
array << 'cls_counter_hash.rb'
|
|
76
|
+
array << 'cls_delim_line.rb'
|
|
77
|
+
array << 'cls_dttm.rb'
|
|
78
|
+
array << 'cls_duration.rb'
|
|
79
|
+
array << 'cls_forerunner_xml_parser.rb'
|
|
80
|
+
array << 'cls_geo_data.rb'
|
|
81
|
+
array << 'cls_gooby_object.rb'
|
|
82
|
+
array << 'cls_google_map_generator.rb'
|
|
83
|
+
array << 'cls_history.rb'
|
|
84
|
+
array << 'cls_lap.rb'
|
|
85
|
+
array << 'cls_line.rb'
|
|
86
|
+
array << 'cls_options.rb'
|
|
87
|
+
array << 'cls_position.rb'
|
|
88
|
+
array << 'cls_run.rb'
|
|
89
|
+
array << 'cls_simple_xml_parser.rb'
|
|
90
|
+
array << 'cls_track.rb'
|
|
91
|
+
array << 'cls_trackpoint.rb'
|
|
92
|
+
array << 'mod_constants.rb'
|
|
93
|
+
array << 'mod_introspect.rb'
|
|
94
|
+
array << 'mod_io.rb'
|
|
95
|
+
array << 'mod_project_info.rb'
|
|
96
|
+
array << 'mod_string.rb'
|
|
97
|
+
array
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
module Con
|
|
104
|
+
|
|
105
|
+
public
|
|
106
|
+
|
|
107
|
+
def default_delimiter
|
|
108
|
+
return '|'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def invalid_distance
|
|
112
|
+
return -99999999
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def invalid_minutes
|
|
116
|
+
return -99999999
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def invalid_time
|
|
120
|
+
return -99999999
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def invalid_latitude
|
|
124
|
+
return -1
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def invalid_longitude
|
|
128
|
+
return -1
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def invalid_altitude
|
|
132
|
+
return -1
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
module Introspect
|
|
139
|
+
|
|
140
|
+
# Return a String w/instance variable and method info.
|
|
141
|
+
def introspect
|
|
142
|
+
c = self.class
|
|
143
|
+
s = "Class: #{c} "
|
|
144
|
+
iv = self.instance_variables.sort
|
|
145
|
+
s << " ivc=#{iv.size}"
|
|
146
|
+
iv.each { |v| s << " #{v}" }
|
|
147
|
+
meth = self.methods.sort
|
|
148
|
+
s << " mc=#{meth.size}"
|
|
149
|
+
meth.each { |m| s << " #{m}" }
|
|
150
|
+
s << ""
|
|
151
|
+
s
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Return 'self.instance_variables'.
|
|
155
|
+
def to_yaml_properties
|
|
156
|
+
self.instance_variables
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
module GoobyIO
|
|
163
|
+
|
|
164
|
+
# Return an Array of lines in file, optionally stripped.
|
|
165
|
+
def read_lines(filename, strip=false)
|
|
166
|
+
|
|
167
|
+
array = IO.readlines(filename)
|
|
168
|
+
if strip
|
|
169
|
+
array = strip_lines(array)
|
|
170
|
+
end
|
|
171
|
+
return array
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Return an Array of lines in file per the given delimeter, optionally stripped.
|
|
175
|
+
def read_as_ascii_lines(filename, delim=10, strip=false)
|
|
176
|
+
|
|
177
|
+
array = Array.new
|
|
178
|
+
file = File.new(filename)
|
|
179
|
+
currLine = ''
|
|
180
|
+
bytesRead = 0
|
|
181
|
+
linesRead = 0
|
|
182
|
+
|
|
183
|
+
file.each_byte { |b|
|
|
184
|
+
bytesRead = bytesRead + 1
|
|
185
|
+
if (b == delim) # delim is 13 for quicken, 10 for address book xml
|
|
186
|
+
array << currLine
|
|
187
|
+
currLine = ''
|
|
188
|
+
linesRead = linesRead + 1
|
|
189
|
+
else
|
|
190
|
+
if (b < 127)
|
|
191
|
+
currLine << "#{b.chr}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if currLine.size > 0
|
|
197
|
+
array << currLine
|
|
198
|
+
end
|
|
199
|
+
if strip
|
|
200
|
+
array = strip_lines(array)
|
|
201
|
+
end
|
|
202
|
+
return array
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Strip the lines/Strings; return a new Array.
|
|
206
|
+
def strip_lines(array)
|
|
207
|
+
|
|
208
|
+
newArray = Array.new
|
|
209
|
+
if (array != nil)
|
|
210
|
+
array.each { |line| line.strip! ; newArray << line }
|
|
211
|
+
end
|
|
212
|
+
return newArray
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
module GoobyString
|
|
219
|
+
|
|
220
|
+
def tokenize(string, delim, strip=false)
|
|
221
|
+
if string
|
|
222
|
+
tokens = string.split(delim)
|
|
223
|
+
if strip
|
|
224
|
+
tokens.each { |tok| tok.strip! }
|
|
225
|
+
end
|
|
226
|
+
tokens
|
|
227
|
+
else
|
|
228
|
+
Array.new
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
module TestHelper
|
|
236
|
+
|
|
237
|
+
def setup
|
|
238
|
+
puts "test: #{name}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def teardown
|
|
242
|
+
@debug = false
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
=begin rdoc
|
|
249
|
+
This is the abstract superclass of several Gooby classes.
|
|
250
|
+
Includes modules GoobyIO, Introspect, and GoobyProjectInfo.
|
|
251
|
+
=end
|
|
252
|
+
|
|
253
|
+
class GoobyObject
|
|
254
|
+
|
|
255
|
+
include Gooby::GoobyIO
|
|
256
|
+
include Gooby::GoobyString
|
|
257
|
+
include Gooby::Introspect
|
|
258
|
+
include Gooby::GoobyProjectInfo
|
|
259
|
+
include Gooby::Con
|
|
260
|
+
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
=begin rdoc
|
|
265
|
+
This class wrappers a Hash object and provides increment/decrement functionality
|
|
266
|
+
for a given key. It is used to sum the number of things (i.e. - xml tags) in a
|
|
267
|
+
collection.
|
|
268
|
+
=end
|
|
269
|
+
|
|
270
|
+
class CounterHash < GoobyObject
|
|
271
|
+
|
|
272
|
+
def initialize
|
|
273
|
+
@hash = Hash.new(0)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Return the Integer count for the given key; zero default.
|
|
277
|
+
def value(key)
|
|
278
|
+
(@hash.has_key?(key)) ? @hash[key] : 0
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Increment the count for the given key.
|
|
282
|
+
def increment(key)
|
|
283
|
+
if key == nil
|
|
284
|
+
return
|
|
285
|
+
end
|
|
286
|
+
if (@hash.has_key?(key))
|
|
287
|
+
val = @hash[key]
|
|
288
|
+
@hash[key] = val + 1
|
|
289
|
+
else
|
|
290
|
+
@hash[key] = 1
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Decrement the count for the given key.
|
|
295
|
+
def decrement(key)
|
|
296
|
+
if key == nil
|
|
297
|
+
return
|
|
298
|
+
end
|
|
299
|
+
if (@hash.has_key?(key))
|
|
300
|
+
val = @hash[key]
|
|
301
|
+
@hash[key] = val - 1
|
|
302
|
+
else
|
|
303
|
+
@hash[key] = -1
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Return an Array of the sorted keys.
|
|
308
|
+
def sorted_keys
|
|
309
|
+
@hash.keys.sort
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Return a String containing all key=val pairs.
|
|
313
|
+
def to_s
|
|
314
|
+
s = "CHash:"
|
|
315
|
+
sorted_keys.each { |key|
|
|
316
|
+
val = @hash[key]
|
|
317
|
+
s << " key: [#{key}] val: [#{val}]"
|
|
318
|
+
}
|
|
319
|
+
s
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Return an XML String containing all key=val pairs, optionally aligned.
|
|
323
|
+
def to_xml(aligned=false)
|
|
324
|
+
s = "<CHash>"
|
|
325
|
+
sorted_keys.each { |key|
|
|
326
|
+
val = @hash[key]
|
|
327
|
+
(aligned) ? s << "\n " : s << ''
|
|
328
|
+
s << " <entry key='#{key}' value='#{val}'/>"
|
|
329
|
+
}
|
|
330
|
+
if aligned
|
|
331
|
+
s << "\n "
|
|
332
|
+
end
|
|
333
|
+
s << " </CHash>"
|
|
334
|
+
s
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
=begin rdoc
|
|
341
|
+
Instances of this class represent a delimited line of text, such as csv.
|
|
342
|
+
=end
|
|
343
|
+
|
|
344
|
+
class DelimLine < GoobyObject
|
|
345
|
+
|
|
346
|
+
attr_reader :line, :trim, :delim, :tokens
|
|
347
|
+
|
|
348
|
+
def initialize(line, trim=true, delim=default_delimiter)
|
|
349
|
+
@line = line
|
|
350
|
+
@trim = trim
|
|
351
|
+
@delim = delim
|
|
352
|
+
@tokens = @line.split(@delim)
|
|
353
|
+
if trim
|
|
354
|
+
@tokens.each { | token | token.strip! }
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def as_trackpoint(num_idx, lat_idx, lng_idx, alt_idx, dttm_idx)
|
|
359
|
+
Trackpoint.new(@tokens[num_idx], @tokens[lat_idx], @tokens[lng_idx], @tokens[alt_idx], @tokens[dttm_idx])
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def is_comment?
|
|
363
|
+
@line.strip.match('^#') ? true : false
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def to_s
|
|
367
|
+
"DelimLine: length: #{@line.size} trim: #{@trim} delim: #{@delim} tokens: #{@tokens.size}"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
=begin rdoc
|
|
374
|
+
Instances of this class represent a Date and Time as parsed from a value
|
|
375
|
+
such as '2006-01-15T13:41:40Z' in an XML file produced by a GPS device.
|
|
376
|
+
It wrappers both a DateTime and Time object.
|
|
377
|
+
=end
|
|
378
|
+
|
|
379
|
+
class DtTm < GoobyObject
|
|
380
|
+
|
|
381
|
+
attr_accessor :rawdata, :dateTime, :time, :valid
|
|
382
|
+
|
|
383
|
+
# Constructor; arg is a String like '2006-01-15T13:41:40Z'.
|
|
384
|
+
def initialize(raw)
|
|
385
|
+
if raw
|
|
386
|
+
@rawdata = raw.strip
|
|
387
|
+
if @rawdata.size > 18
|
|
388
|
+
@dateTime = DateTime.parse(@rawdata[0..18])
|
|
389
|
+
@time = Time.parse(@dateTime.to_s)
|
|
390
|
+
@valid = true
|
|
391
|
+
else
|
|
392
|
+
@valid = false
|
|
393
|
+
end
|
|
394
|
+
else
|
|
395
|
+
@rawdata = ''
|
|
396
|
+
@valid = false
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
public
|
|
401
|
+
|
|
402
|
+
# Return @time.to_i
|
|
403
|
+
def to_i()
|
|
404
|
+
(@time) ? @time.to_i : invalid_time
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Calculates and returns diff between another instance.
|
|
408
|
+
def seconds_diff(anotherDtTm)
|
|
409
|
+
if anotherDtTm
|
|
410
|
+
to_i - anotherDtTm.to_i
|
|
411
|
+
else
|
|
412
|
+
invalid_time
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def yyyy_mm_dd
|
|
417
|
+
@time.strftime("%Y-%m-%d")
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def yyyy_mm_dd_hh_mm_ss
|
|
421
|
+
@time.strftime("%Y-%m-%d %H:%M:%S")
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def hh_mm_ss
|
|
425
|
+
@time.strftime("%H:%M:%S")
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Calculate and return time diff in 'hh:mm:ss' format.
|
|
429
|
+
def hhmmss_diff(anotherDtTm)
|
|
430
|
+
if anotherDtTm
|
|
431
|
+
t = @time - (anotherDtTm.to_i)
|
|
432
|
+
t.strftime("%H:%M:%S")
|
|
433
|
+
else
|
|
434
|
+
'??:??:??'
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def to_s
|
|
439
|
+
"#{@rawdata}"
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Return a String with state values for debugging.
|
|
443
|
+
def print_string
|
|
444
|
+
"DtTm: #{yyyy_mm_dd_hh_mm_ss} #{to_i} #{@rawdata}"
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
=begin rdoc
|
|
451
|
+
Instances of this class represent the contents of a Forerunner extract
|
|
452
|
+
<Duration> tag, such as:
|
|
453
|
+
<Duration>PT507.870S</Duration>
|
|
454
|
+
=end
|
|
455
|
+
|
|
456
|
+
class Duration < GoobyObject
|
|
457
|
+
|
|
458
|
+
attr_accessor :rawdata, :seconds, :minutes, :mmss
|
|
459
|
+
|
|
460
|
+
# Constructor; arg is a String like 'PT507.870S'.
|
|
461
|
+
def initialize(raw)
|
|
462
|
+
if raw
|
|
463
|
+
@rawdata = scrub(raw)
|
|
464
|
+
@seconds = @rawdata.to_f
|
|
465
|
+
@minutes = @seconds / 60
|
|
466
|
+
@base_min = @minutes.floor
|
|
467
|
+
@frac_min = @minutes - @base_min
|
|
468
|
+
@frac_sec = @frac_min * 60
|
|
469
|
+
|
|
470
|
+
@mmss = ''
|
|
471
|
+
if (@base_min < 10)
|
|
472
|
+
@mmss << "0#{@base_min}:"
|
|
473
|
+
else
|
|
474
|
+
@mmss << "#{@base_min}:"
|
|
475
|
+
end
|
|
476
|
+
if (@frac_sec < 10)
|
|
477
|
+
@mmss << "0#{@frac_sec}"
|
|
478
|
+
else
|
|
479
|
+
@mmss << "#{@frac_sec}"
|
|
480
|
+
end
|
|
481
|
+
if (@mmss.size > 8)
|
|
482
|
+
@mmss = @mmss[0..8]
|
|
483
|
+
end
|
|
484
|
+
else
|
|
485
|
+
@rawdata = ''
|
|
486
|
+
@seconds = invalidDistance
|
|
487
|
+
@minutes = invalidMinutes
|
|
488
|
+
@mmss = '??:??.?'
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
private
|
|
493
|
+
|
|
494
|
+
def scrub(raw)
|
|
495
|
+
if (raw)
|
|
496
|
+
raw.strip!
|
|
497
|
+
newStr = ''
|
|
498
|
+
raw.each_byte { | b |
|
|
499
|
+
if ((b >= 48) && (b <= 57))
|
|
500
|
+
newStr << b
|
|
501
|
+
end
|
|
502
|
+
if (b == 46)
|
|
503
|
+
newStr << b
|
|
504
|
+
end
|
|
505
|
+
}
|
|
506
|
+
return newStr
|
|
507
|
+
else
|
|
508
|
+
''
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
public
|
|
513
|
+
|
|
514
|
+
def to_s
|
|
515
|
+
"#{@mmss}"
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Return a String with state values for debugging.
|
|
519
|
+
def print_string
|
|
520
|
+
"Duration: #{@rawdata} sec: #{@seconds} min: #{@minutes} mmss: #{@mmss} bm: #{@base_min} fm: #{@frac_min} fs: #{@frac_sec}"
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
=begin rdoc
|
|
527
|
+
Instances of this class are used to parse a Forerunner XML file in a SAX-like
|
|
528
|
+
manner. Instances of the model classes - History, Run, Track, Trackpoint,
|
|
529
|
+
etc. are created in this parsing process.
|
|
530
|
+
|
|
531
|
+
See http://www.garmin.com/xmlschemas/ForerunnerLogbookv1.xsd for the XML Schema
|
|
532
|
+
Definition for the Garmin Forerunner XML. The Gooby object model mirrors this XSD.
|
|
533
|
+
=end
|
|
534
|
+
|
|
535
|
+
class ForerunnerXmlParser
|
|
536
|
+
|
|
537
|
+
DETAIL_TAGS = %w( Notes StartTime Duration Length Latitude Longitude Altitude Time BeginPosition EndPosition )
|
|
538
|
+
|
|
539
|
+
include REXML::StreamListener
|
|
540
|
+
|
|
541
|
+
attr_reader :history, :cvHash, :tagCount
|
|
542
|
+
|
|
543
|
+
def initialize
|
|
544
|
+
@cvHash = Hash.new("")
|
|
545
|
+
@tagCount = 0
|
|
546
|
+
@runCount = 0
|
|
547
|
+
@lapCount = 0
|
|
548
|
+
@trackCount = 0
|
|
549
|
+
@trackpoint_count = 0
|
|
550
|
+
@currText = "";
|
|
551
|
+
@history = History.new
|
|
552
|
+
@currRun = nil
|
|
553
|
+
@currLap = nil
|
|
554
|
+
@currTrack = nil
|
|
555
|
+
@currBeginPosition = nil
|
|
556
|
+
@currEndPosition = nil
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
public
|
|
560
|
+
|
|
561
|
+
# SAX API method; handles 'Run', 'Lap', 'Track'.
|
|
562
|
+
def tag_start(tagname, attrs)
|
|
563
|
+
@tagCount += 1
|
|
564
|
+
@currTag = tagname
|
|
565
|
+
@cvHash[tagname] = ''
|
|
566
|
+
|
|
567
|
+
if detail_tag?(tagname)
|
|
568
|
+
@inDetail = true
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
if is_tag?('Run', tagname)
|
|
572
|
+
@runCount = @runCount + 1
|
|
573
|
+
@lapCount = 0
|
|
574
|
+
@trackCount = 0
|
|
575
|
+
@currRun = Run.new(@runCount)
|
|
576
|
+
@history.add_run(@currRun)
|
|
577
|
+
@cvHash['Notes'] = ''
|
|
578
|
+
return
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
if is_tag?('Lap', tagname)
|
|
582
|
+
@lapCount = @lapCount + 1
|
|
583
|
+
@currLap = Lap.new(@lapCount)
|
|
584
|
+
return
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
if is_tag?('Track', tagname)
|
|
588
|
+
@trackCount = @trackCount + 1
|
|
589
|
+
@currTrack = Track.new(@trackCount)
|
|
590
|
+
@trackpoint_count = 0
|
|
591
|
+
return
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# SAX API method; handles 'Position', 'Trackpoint', 'Track', 'Lap', 'Run'.
|
|
597
|
+
def tag_end(tagname)
|
|
598
|
+
if @inDetail
|
|
599
|
+
@cvHash[tagname] = @currText
|
|
600
|
+
else
|
|
601
|
+
if is_tag?('Position', tagname)
|
|
602
|
+
lat = @cvHash['Latitude']
|
|
603
|
+
long = @cvHash['Longitude']
|
|
604
|
+
@currBeginPosition = Position.new(lat.strip, long.strip, '')
|
|
605
|
+
@currEndPosition = Position.new(lat.strip, long.strip, '')
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
if is_tag?('BeginPosition', tagname)
|
|
609
|
+
lat = @cvHash['Latitude']
|
|
610
|
+
long = @cvHash['Longitude']
|
|
611
|
+
@currBeginPosition = Position.new(lat.strip, long.strip, '')
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
if is_tag?('EndPosition', tagname)
|
|
615
|
+
lat = @cvHash['Latitude']
|
|
616
|
+
long = @cvHash['Longitude']
|
|
617
|
+
@currEndPosition = Position.new(lat.strip, long.strip, '')
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
if is_tag?('Trackpoint', tagname)
|
|
621
|
+
@trackpoint_count = @trackpoint_count + 1
|
|
622
|
+
lat = @cvHash['Latitude']
|
|
623
|
+
long = @cvHash['Longitude']
|
|
624
|
+
alt = @cvHash['Altitude']
|
|
625
|
+
time = @cvHash['Time']
|
|
626
|
+
tp = Trackpoint.new(@trackpoint_count, lat, long, alt, time)
|
|
627
|
+
@currTrack.add_trackpoint(tp)
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
if is_tag?('Track', tagname)
|
|
631
|
+
if @currRun != nil
|
|
632
|
+
@currRun.add_track(@currTrack)
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
if is_tag?('Lap', tagname)
|
|
637
|
+
@currLap.startTime = @cvHash['StartTime']
|
|
638
|
+
@currLap.duration = Duration.new(@cvHash['Duration'])
|
|
639
|
+
@currLap.length = @cvHash['Length']
|
|
640
|
+
@currLap.beginPosition = @currBeginPosition
|
|
641
|
+
@currLap.endPosition = @currEndPosition
|
|
642
|
+
@currRun.add_lap(@currLap)
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
if is_tag?('Run', tagname)
|
|
646
|
+
@currRun.notes = @cvHash['Notes']
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
@inDetail = false
|
|
651
|
+
@currText = ""
|
|
652
|
+
@currTag = ""
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# SAX API method.
|
|
656
|
+
def text(txt)
|
|
657
|
+
if @inDetail
|
|
658
|
+
@currText = @currText + txt
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Iterate all parsed Run objects and print each with to_s.
|
|
663
|
+
def gdump()
|
|
664
|
+
@history.runs().each { |run| puts run.to_s }
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Iterate all parsed Run objects and print each with to_s.
|
|
668
|
+
def dump()
|
|
669
|
+
@history.runs().each { |run| puts run.to_s }
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
# Iterate all parsed Run objects and print each with put_csv.
|
|
673
|
+
def put_run_csv()
|
|
674
|
+
@history.runs().each { |run| run.put_csv() }
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Iterate all parsed Run objects and print each with put_tkpt_csv.
|
|
678
|
+
def put_all_run_tkpt_csv(with_header_comment)
|
|
679
|
+
@history.runs.each { |run|
|
|
680
|
+
run.put_tkpt_csv(with_header_comment)
|
|
681
|
+
}
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
private
|
|
685
|
+
|
|
686
|
+
def is_tag?(tagname, value)
|
|
687
|
+
tagname == value
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def detail_tag?(tagname)
|
|
691
|
+
DETAIL_TAGS.each { |typ|
|
|
692
|
+
if typ == tagname
|
|
693
|
+
return true
|
|
694
|
+
end
|
|
695
|
+
}
|
|
696
|
+
return false
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
=begin rdoc
|
|
703
|
+
Instances of this class are used to split a large ForerunnerLogbook
|
|
704
|
+
XML file into individual 'run_' files.
|
|
705
|
+
=end
|
|
706
|
+
|
|
707
|
+
class ForerunnerXmlSplitter < GoobyObject
|
|
708
|
+
|
|
709
|
+
attr_reader :out_dir, :forerunner_files, :out_files_hash
|
|
710
|
+
|
|
711
|
+
def initialize(xml_file, out_dir)
|
|
712
|
+
@out_dir = out_dir
|
|
713
|
+
@forerunner_files = Array.new
|
|
714
|
+
@forerunner_files << xml_file
|
|
715
|
+
@out_files_hash = Hash.new
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
def split
|
|
719
|
+
@forerunner_files.each { |f| process_file(f) }
|
|
720
|
+
write_files
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
private
|
|
724
|
+
|
|
725
|
+
def process_file(forerunnerXmlFile)
|
|
726
|
+
@file_name = forerunnerXmlFile
|
|
727
|
+
@xml_lines = read_lines(@file_name, false)
|
|
728
|
+
@line_num = 0
|
|
729
|
+
@run_num = 0
|
|
730
|
+
@curr_run_lines = Array.new
|
|
731
|
+
@curr_run_tkpts = 0
|
|
732
|
+
@start_line_num = 0
|
|
733
|
+
@end_line_num = 0
|
|
734
|
+
@first_start_time = nil
|
|
735
|
+
|
|
736
|
+
@xml_lines.each { |line|
|
|
737
|
+
@line_num = @line_num + 1
|
|
738
|
+
if (line.match(/<Run>/))
|
|
739
|
+
@run_num = @run_num + 1
|
|
740
|
+
@start_line_num = @line_num
|
|
741
|
+
@curr_run_lines = Array.new
|
|
742
|
+
@curr_run_lines << line
|
|
743
|
+
elsif (line.match(/<StartTime>/)) # <StartTime>2007-01-13T15:37:06Z</StartTime>
|
|
744
|
+
@curr_run_lines << line
|
|
745
|
+
if @first_start_time == nil
|
|
746
|
+
clone = String.new(line)
|
|
747
|
+
clone.gsub!(/[<>]/, ' ')
|
|
748
|
+
clone.gsub!(/[-:T]/, '_')
|
|
749
|
+
clone.gsub!(/[Z]/, '')
|
|
750
|
+
tokens = clone.split
|
|
751
|
+
@first_start_time = tokens[1]
|
|
752
|
+
end
|
|
753
|
+
elsif (line.match(/<Trackpoint>/))
|
|
754
|
+
@curr_run_tkpts = @curr_run_tkpts + 1
|
|
755
|
+
@curr_run_lines << line
|
|
756
|
+
elsif (line.match(/<\/Run>/))
|
|
757
|
+
@end_line_num = @line_num
|
|
758
|
+
@curr_run_lines << line
|
|
759
|
+
end_run
|
|
760
|
+
elsif (@curr_run_lines.size > 0)
|
|
761
|
+
@curr_run_lines << line
|
|
762
|
+
end
|
|
763
|
+
}
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
def end_run
|
|
767
|
+
out_file = "#{@out_dir}/run_#{@first_start_time}.xml"
|
|
768
|
+
comment = "<!-- file: #{out_file} lines: #{@curr_run_lines.size} (#{@start_line_num} to #{@end_line_num}) tkpts: #{@curr_run_tkpts} --> \n"
|
|
769
|
+
@curr_run_lines.insert(0, comment)
|
|
770
|
+
|
|
771
|
+
prev_entry = @out_files_hash[out_file]
|
|
772
|
+
if prev_entry
|
|
773
|
+
if (@curr_run_lines.size >= prev_entry.size)
|
|
774
|
+
puts "previous entry overlaid for #{out_file}. curr=#{@curr_run_lines.size} prev=#{prev_entry.size}"
|
|
775
|
+
@out_files_hash[out_file] = @curr_run_lines
|
|
776
|
+
else
|
|
777
|
+
puts "previous entry retained for #{out_file}. curr=#{@curr_run_lines.size} prev=#{prev_entry.size}"
|
|
778
|
+
end
|
|
779
|
+
else
|
|
780
|
+
puts "new entry for #{out_file}. curr=#{@curr_run_lines.size}"
|
|
781
|
+
@out_files_hash[out_file] = @curr_run_lines
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
@curr_run_lines = Array.new
|
|
785
|
+
@curr_run_tkpts = 0
|
|
786
|
+
@start_line_num = 0
|
|
787
|
+
@end_line_num = 0
|
|
788
|
+
@first_start_time = nil
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
def write_files
|
|
792
|
+
out_names = @out_files_hash.keys.sort
|
|
793
|
+
puts "Writing #{out_names.size} extract files..."
|
|
794
|
+
out_names.each { |out_name|
|
|
795
|
+
lines = @out_files_hash[out_name]
|
|
796
|
+
out = File.new out_name, "w+"
|
|
797
|
+
lines.each { |line| out.write line }
|
|
798
|
+
out.flush
|
|
799
|
+
out.close
|
|
800
|
+
puts "File written: #{out_name}"
|
|
801
|
+
}
|
|
802
|
+
puts "output files written."
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
=begin rdoc
|
|
809
|
+
Instances of this class represent a the set of Geographic data defined in file geo.txt
|
|
810
|
+
=end
|
|
811
|
+
|
|
812
|
+
class GeoData < GoobyObject
|
|
813
|
+
|
|
814
|
+
attr_reader :filename, :lines, :poi_hash, :poi_array, :track_hash, :track_array, :route_hash, :route_array
|
|
815
|
+
|
|
816
|
+
def initialize(filename)
|
|
817
|
+
@filename = filename
|
|
818
|
+
@filename = 'data/geo_data.txt' if @filename == nil
|
|
819
|
+
@lines = read_lines(@filename, true)
|
|
820
|
+
@poi_hash = Hash.new
|
|
821
|
+
@poi_array = Array.new
|
|
822
|
+
@track_hash = Hash.new
|
|
823
|
+
@track_array = Array.new
|
|
824
|
+
@route_hash = Hash.new
|
|
825
|
+
@route_array = Array.new
|
|
826
|
+
parse_poi
|
|
827
|
+
parse_tracks
|
|
828
|
+
parse_routes
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
private
|
|
832
|
+
|
|
833
|
+
def parse_poi
|
|
834
|
+
in_poi, poi_number = false, 0
|
|
835
|
+
@lines.each { |line|
|
|
836
|
+
line_obj = Line.new(line, nil, true)
|
|
837
|
+
tok_count = line_obj.token_count
|
|
838
|
+
is_point = line_obj.token_idx_equals(0, '.')
|
|
839
|
+
|
|
840
|
+
if line_obj.is_populated_non_comment
|
|
841
|
+
if line_obj.match('points_of_interest_start')
|
|
842
|
+
in_poi = true
|
|
843
|
+
elsif line_obj.match('points_of_interest_end')
|
|
844
|
+
in_poi = false
|
|
845
|
+
elsif in_poi && tok_count > 2 && is_point
|
|
846
|
+
poi_number = poi_number + 1
|
|
847
|
+
tkpt = Trackpoint.new(
|
|
848
|
+
poi_number, line_obj.tokens[1], line_obj.tokens[2],
|
|
849
|
+
'0', '', line_obj.concatinate_tokens(3))
|
|
850
|
+
add_poi(tkpt)
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
}
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
def parse_tracks
|
|
857
|
+
in_track, trk_number, tkpt_number = false, 0, 0
|
|
858
|
+
curr_trk, curr_run = nil, nil
|
|
859
|
+
@lines.each { |line|
|
|
860
|
+
line_obj = Line.new(line, nil, true)
|
|
861
|
+
tok_count = line_obj.token_count
|
|
862
|
+
is_point = line_obj.token_idx_equals(0, '.')
|
|
863
|
+
|
|
864
|
+
if line_obj.is_populated_non_comment
|
|
865
|
+
if line_obj.match('track_start')
|
|
866
|
+
in_track = true
|
|
867
|
+
trk_number = trk_number + 1
|
|
868
|
+
tkpt_number = 0
|
|
869
|
+
curr_trk = Track.new(0, line_obj.concatinate_tokens(1))
|
|
870
|
+
curr_run = Run.new(trk_number, line_obj.concatinate_tokens(1))
|
|
871
|
+
curr_run.add_track(curr_trk)
|
|
872
|
+
elsif line_obj.match('track_end')
|
|
873
|
+
in_track = false
|
|
874
|
+
curr_run.finish
|
|
875
|
+
add_track(curr_trk)
|
|
876
|
+
add_route(curr_run)
|
|
877
|
+
elsif in_track && tok_count > 2 && is_point
|
|
878
|
+
tkpt_number = tkpt_number + 1
|
|
879
|
+
tkpt = Trackpoint.new(
|
|
880
|
+
tkpt_number, line_obj.tokens[1], line_obj.tokens[2],
|
|
881
|
+
'0', '', line_obj.concatinate_tokens(3))
|
|
882
|
+
curr_trk.add_trackpoint(tkpt)
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
}
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
def parse_routes
|
|
889
|
+
in_route, route_number, trk_number, tkpt_number = false, 0, 0, 0
|
|
890
|
+
curr_trk, curr_run = nil, nil
|
|
891
|
+
@lines.each { |line|
|
|
892
|
+
line_obj = Line.new(line, nil, true)
|
|
893
|
+
tok_count = line_obj.token_count
|
|
894
|
+
is_point = line_obj.token_idx_equals(0, '.')
|
|
895
|
+
|
|
896
|
+
if line_obj.is_populated_non_comment
|
|
897
|
+
if line_obj.match('route_start')
|
|
898
|
+
in_route = true
|
|
899
|
+
trk_number = trk_number + 1
|
|
900
|
+
tkpt_number = 0
|
|
901
|
+
curr_trk = Track.new(0, line_obj.concatinate_tokens(1))
|
|
902
|
+
curr_run = Run.new(trk_number, line_obj.concatinate_tokens(1))
|
|
903
|
+
curr_run.add_track(curr_trk)
|
|
904
|
+
elsif line_obj.match('route_end')
|
|
905
|
+
in_route = false
|
|
906
|
+
curr_run.finish
|
|
907
|
+
add_route(curr_run)
|
|
908
|
+
elsif in_route && tok_count > 2 && is_point
|
|
909
|
+
tkpt_number = tkpt_number + 1
|
|
910
|
+
tkpt = Trackpoint.new(
|
|
911
|
+
tkpt_number, line_obj.tokens[1], line_obj.tokens[2],
|
|
912
|
+
'0', '', line_obj.concatinate_tokens(3))
|
|
913
|
+
curr_trk.add_trackpoint(tkpt)
|
|
914
|
+
elsif in_route && line_obj.token_idx_equals(0, 'track') && tok_count > 1
|
|
915
|
+
trk_desc = line_obj.concatinate_tokens(1)
|
|
916
|
+
trk = @track_hash[trk_desc]
|
|
917
|
+
if trk
|
|
918
|
+
trk.trackpoints.each { |tkpt| curr_trk.add_trackpoint(tkpt) }
|
|
919
|
+
end
|
|
920
|
+
elsif in_route && line_obj.token_idx_equals(0, 'track_rev') && tok_count > 1
|
|
921
|
+
trk_desc = line_obj.concatinate_tokens(1)
|
|
922
|
+
trk = @track_hash[trk_desc]
|
|
923
|
+
if trk
|
|
924
|
+
array = trk.trackpoints
|
|
925
|
+
trk.trackpoints.each { |tkpt| curr_trk.add_trackpoint(tkpt) }
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
end
|
|
929
|
+
}
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
def add_poi(tkpt)
|
|
933
|
+
if tkpt
|
|
934
|
+
descr = tkpt.descr
|
|
935
|
+
if @poi_hash.has_key? descr
|
|
936
|
+
puts "Duplicate POI key ignored - '#{descr}'"
|
|
937
|
+
else
|
|
938
|
+
#puts "Adding POI: #{tkpt.to_poi_csv}"
|
|
939
|
+
@poi_hash[descr] = tkpt
|
|
940
|
+
@poi_array << tkpt
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
def add_track(trk)
|
|
946
|
+
if trk
|
|
947
|
+
descr = trk.descr
|
|
948
|
+
if @track_hash.has_key? descr
|
|
949
|
+
puts "Duplicate Track key ignored - '#{descr}'"
|
|
950
|
+
else
|
|
951
|
+
@track_hash[descr] = trk
|
|
952
|
+
@track_array << trk
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
def add_route(run)
|
|
958
|
+
if run
|
|
959
|
+
descr = run.descr
|
|
960
|
+
if @route_hash.has_key? descr
|
|
961
|
+
puts "Duplicate Route key ignored - '#{descr}'"
|
|
962
|
+
else
|
|
963
|
+
@route_hash[descr] = run
|
|
964
|
+
@route_array << run
|
|
965
|
+
end
|
|
966
|
+
end
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
public
|
|
970
|
+
|
|
971
|
+
def to_s
|
|
972
|
+
return "GeoData lines: #{lines.size} poi: #{@poi_hash.size} tracks: #{@track_hash.size} routes: #{@route_hash.size} "
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
def dump
|
|
976
|
+
puts "#{self.class} dump:"
|
|
977
|
+
@poi_array.each { |tkpt| puts "POI: #{tkpt.to_geo_s}" }
|
|
978
|
+
@track_array.each { |trk| trk.dump }
|
|
979
|
+
@route_hash.keys.sort.each { |key|
|
|
980
|
+
puts "Route: '#{key}'"
|
|
981
|
+
}
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
=begin rdoc
|
|
988
|
+
Instances of this class represent a <Run> aggregate object from a
|
|
989
|
+
Forerunner XML file.
|
|
990
|
+
|
|
991
|
+
Additionally, there is distance, pace, and Google Map generation logic
|
|
992
|
+
in this class.
|
|
993
|
+
=end
|
|
994
|
+
|
|
995
|
+
class GoogleMapGenerator < GoobyObject
|
|
996
|
+
|
|
997
|
+
attr_reader :csv_file, :csv_lines, :dttm_idx, :num_idx, :lat_idx, :lng_idx, :alt_idx, :cdist_idx
|
|
998
|
+
attr_reader :run, :tkpts, :content_hash, :center_longitude, :center_latitude, :gpoint_array, :overlay_points, :notes
|
|
999
|
+
attr_reader :center_longitude, :center_latitude
|
|
1000
|
+
|
|
1001
|
+
# The default csv input file format is as follows:
|
|
1002
|
+
# 1 | 2006-01-15T18:31:10Z | 1279 | 33.42601 | -111.92927 | 347.654 | 26.3514930151813
|
|
1003
|
+
# 1 | 2004-11-13T13:05:20Z | 2 | 37.54318 | -77.43636 | -58.022 | 0.00297286231747969
|
|
1004
|
+
|
|
1005
|
+
def initialize(csv_file, dttm_idx=1, num_idx=2, lat_idx=3, lng_idx=4, alt_idx=5, cdist_idx=6)
|
|
1006
|
+
@csv_file = csv_file
|
|
1007
|
+
@dttm_idx = dttm_idx
|
|
1008
|
+
@num_idx = num_idx
|
|
1009
|
+
@lat_idx = lat_idx
|
|
1010
|
+
@lng_idx = lng_idx
|
|
1011
|
+
@alt_idx = alt_idx
|
|
1012
|
+
@cdist_idx = cdist_idx
|
|
1013
|
+
@options = Gooby::Options.new(nil)
|
|
1014
|
+
@content_hash = Hash.new('')
|
|
1015
|
+
@run = Gooby::Run.new(1)
|
|
1016
|
+
@track = Gooby::Track.new(1)
|
|
1017
|
+
@run.add_track(@track)
|
|
1018
|
+
@tkpts = Array.new
|
|
1019
|
+
|
|
1020
|
+
@csv_lines = read_as_ascii_lines(@csv_file, 10, true)
|
|
1021
|
+
@csv_lines.each { | line |
|
|
1022
|
+
dline = Gooby::DelimLine.new(line)
|
|
1023
|
+
if (!dline.is_comment?)
|
|
1024
|
+
tkpt = dline.as_trackpoint(@num_idx, @lat_idx, @lng_idx, @alt_idx, @dttm_idx)
|
|
1025
|
+
if tkpt
|
|
1026
|
+
@track.add_trackpoint(tkpt)
|
|
1027
|
+
end
|
|
1028
|
+
end
|
|
1029
|
+
}
|
|
1030
|
+
@run.finish
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
=begin
|
|
1034
|
+
Returns a Hash with specific generated content at the following keys:
|
|
1035
|
+
=end
|
|
1036
|
+
def generate(options)
|
|
1037
|
+
if (options == nil)
|
|
1038
|
+
@options = Gooby::Options.new(nil)
|
|
1039
|
+
else
|
|
1040
|
+
@options = options
|
|
1041
|
+
end
|
|
1042
|
+
@content_hash['when_generated'] = Time.now
|
|
1043
|
+
@content_hash['title'] = @options.get('gmap_title')
|
|
1044
|
+
filter_trackpoints
|
|
1045
|
+
compute_center_point
|
|
1046
|
+
generate_key_js
|
|
1047
|
+
generate_map_div
|
|
1048
|
+
generate_messages_div
|
|
1049
|
+
generate_main_js_start
|
|
1050
|
+
generate_main_js_route_overlay
|
|
1051
|
+
generate_main_js_checkpoint_overlays
|
|
1052
|
+
generate_main_js_map_clicked_listeners
|
|
1053
|
+
generate_main_js_end
|
|
1054
|
+
@content_hash
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
def filter_trackpoints
|
|
1058
|
+
count, @tkpts = 0, Array.new
|
|
1059
|
+
firstTkpt = @options.get('gmap_first_tkpt_number')
|
|
1060
|
+
lastTkpt = @options.get('gmap_last_tkpt_number')
|
|
1061
|
+
@run.tracks.each { |trk|
|
|
1062
|
+
trk.trackpoints.each { |tkpt|
|
|
1063
|
+
count = count + 1
|
|
1064
|
+
if ((count >= firstTkpt) && (count <= lastTkpt))
|
|
1065
|
+
@tkpts.push(tkpt)
|
|
1066
|
+
end
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
=begin
|
|
1072
|
+
Returns a Hash with specific generated content at the following keys:
|
|
1073
|
+
=end
|
|
1074
|
+
def generate_page(options)
|
|
1075
|
+
|
|
1076
|
+
# puts "generate_page #{@csv_file} #{@csv_lines.size}"
|
|
1077
|
+
content_hash = generate(nil)
|
|
1078
|
+
s = String.new(@csv_file)
|
|
1079
|
+
s.gsub("/", " ")
|
|
1080
|
+
tokens = tokenize(s, nil)
|
|
1081
|
+
out_file = "#{tokens[-2]}.html"
|
|
1082
|
+
#content_hash.keys.sort.each { | key | puts key }
|
|
1083
|
+
|
|
1084
|
+
s = <<HERE
|
|
1085
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
1086
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
1087
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
1088
|
+
<head>
|
|
1089
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
|
|
1090
|
+
<title> Google Map by Gooby </title>
|
|
1091
|
+
#{content_hash['key_js']}
|
|
1092
|
+
#{content_hash['main_js_start']}
|
|
1093
|
+
#{content_hash['main_js_route_overlay']}
|
|
1094
|
+
#{content_hash['main_js_checkpoint_overlays']}
|
|
1095
|
+
#{content_hash['main_js_map_clicked_listeners']}
|
|
1096
|
+
#{content_hash['main_js_end']}
|
|
1097
|
+
</head>
|
|
1098
|
+
<body onload="load()" onunload="GUnload()">
|
|
1099
|
+
<center>
|
|
1100
|
+
<h3> #{content_hash['title']} </h3>
|
|
1101
|
+
<h5> Generated by Gooby #{content_hash['when_generated']} <br> Gooby = Google APIs + Ruby </h5>
|
|
1102
|
+
#{content_hash['map_div']}
|
|
1103
|
+
#{content_hash['messages_div']}
|
|
1104
|
+
</center>
|
|
1105
|
+
</body>
|
|
1106
|
+
</html>
|
|
1107
|
+
HERE
|
|
1108
|
+
|
|
1109
|
+
# html_file = File.new(out_file, "w+")
|
|
1110
|
+
# html_file.write(s)
|
|
1111
|
+
# html_file.flush
|
|
1112
|
+
# html_file.close
|
|
1113
|
+
puts s # Output is redirected by shell.
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
private
|
|
1117
|
+
|
|
1118
|
+
def compute_center_point
|
|
1119
|
+
highLat = -999.0
|
|
1120
|
+
highLong = -999.0
|
|
1121
|
+
lowLat = 999.0
|
|
1122
|
+
lowLong = 999.0
|
|
1123
|
+
@tkpts.each { |tkpt|
|
|
1124
|
+
highLat = tkpt.latitude_as_float if tkpt.latitude_as_float > highLat
|
|
1125
|
+
lowLat = tkpt.latitude_as_float if tkpt.latitude_as_float < lowLat
|
|
1126
|
+
highLong = tkpt.longitude_as_float if tkpt.longitude_as_float > highLong
|
|
1127
|
+
lowLong = tkpt.longitude_as_float if tkpt.longitude_as_float < lowLong
|
|
1128
|
+
}
|
|
1129
|
+
@center_longitude = (highLong + lowLong) / 2
|
|
1130
|
+
@center_latitude = (highLat + lowLat) / 2
|
|
1131
|
+
@content_hash['center_longitude'] = @center_longitude
|
|
1132
|
+
@content_hash['center_latitude'] = @center_latitude
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
def generate_key_js
|
|
1136
|
+
key = @options.get('gmap_key')
|
|
1137
|
+
key.strip!
|
|
1138
|
+
s = '<script src="http://maps.google.com/maps?file=api&v=2&key='
|
|
1139
|
+
s << key
|
|
1140
|
+
s << '" type="text/javascript"></script>'
|
|
1141
|
+
@content_hash['key_js'] = s
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
def generate_map_div
|
|
1145
|
+
width = @options.get('gmap_width')
|
|
1146
|
+
height = @options.get('gmap_height')
|
|
1147
|
+
id = @options.get('gmap_map_element_id')
|
|
1148
|
+
s = '<div id="'
|
|
1149
|
+
s << id
|
|
1150
|
+
s << '" style="width: '
|
|
1151
|
+
s << width
|
|
1152
|
+
s << '; height: '
|
|
1153
|
+
s << height
|
|
1154
|
+
s << '"></div>'
|
|
1155
|
+
@content_hash['map_width'] = width
|
|
1156
|
+
@content_hash['map_height'] = height
|
|
1157
|
+
@content_hash['map_div'] = s
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
def generate_messages_div
|
|
1161
|
+
s = "<div id=\"messages\"></div>"
|
|
1162
|
+
@content_hash['messages_div'] = s
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
def generate_main_js_start
|
|
1166
|
+
id = @options.get('gmap_map_element_id')
|
|
1167
|
+
size = @options.get('gmap_size_control')
|
|
1168
|
+
type = @options.get('gmap_type')
|
|
1169
|
+
zoom = @options.get('gmap_zoom_level')
|
|
1170
|
+
title = @options.get('gmap_title')
|
|
1171
|
+
if size
|
|
1172
|
+
if size == 'smallmap'
|
|
1173
|
+
size = 'GSmallMapControl'
|
|
1174
|
+
elsif size == 'smallzoom'
|
|
1175
|
+
size = 'GSmallMapControl'
|
|
1176
|
+
else
|
|
1177
|
+
size = 'GLargeMapControl'
|
|
1178
|
+
end
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
if type
|
|
1182
|
+
if type == 'satellite'
|
|
1183
|
+
type = 'G_SATELLITE_MAP'
|
|
1184
|
+
elsif type == 'hybrid'
|
|
1185
|
+
type = 'G_HYBRID_MAP'
|
|
1186
|
+
else
|
|
1187
|
+
type = 'G_NORMAL_MAP'
|
|
1188
|
+
end
|
|
1189
|
+
else
|
|
1190
|
+
type = 'G_NORMAL_MAP'
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
s = '<script type="text/javascript">'
|
|
1194
|
+
s << "\n"
|
|
1195
|
+
s << "//<![CDATA[ \n"
|
|
1196
|
+
s << " function load() { \n"
|
|
1197
|
+
s << " if (GBrowserIsCompatible()) { \n"
|
|
1198
|
+
s << ' var map = new GMap2(document.getElementById("'
|
|
1199
|
+
s << id
|
|
1200
|
+
s << '")); '
|
|
1201
|
+
s << "\n"
|
|
1202
|
+
|
|
1203
|
+
if size
|
|
1204
|
+
s << ' map.addControl(new '
|
|
1205
|
+
s << size
|
|
1206
|
+
s << '());'
|
|
1207
|
+
s << "\n"
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1210
|
+
if type
|
|
1211
|
+
s << ' map.addControl(new GMapTypeControl());'
|
|
1212
|
+
s << "\n"
|
|
1213
|
+
# s << ' map.setMapType('
|
|
1214
|
+
# s << type
|
|
1215
|
+
# s << ');'
|
|
1216
|
+
s << "\n"
|
|
1217
|
+
end
|
|
1218
|
+
s << " var centerPoint = new GLatLng(#{@center_latitude}, #{@center_longitude}); \n"
|
|
1219
|
+
s << " map.setCenter(centerPoint, #{zoom}); \n"
|
|
1220
|
+
s << "\n"
|
|
1221
|
+
@content_hash['main_js_start'] = s
|
|
1222
|
+
@content_hash['title'] = title
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
def generate_main_js_route_overlay
|
|
1226
|
+
tkpt_count = @tkpts.size.to_f
|
|
1227
|
+
app_max = @options.get('gmap_approx_max_points').to_f
|
|
1228
|
+
comments = @options.get('gmap_gen_comments')
|
|
1229
|
+
ratio = tkpt_count / app_max
|
|
1230
|
+
@start_dttm = nil
|
|
1231
|
+
if ratio > 1.0
|
|
1232
|
+
increment = (tkpt_count / app_max).to_i
|
|
1233
|
+
else
|
|
1234
|
+
increment = 1
|
|
1235
|
+
end
|
|
1236
|
+
curr_idx, next_idx, gpoint_count, last_idx = -1, 0, 0, @tkpts.size - 1
|
|
1237
|
+
s = " var points = new Array(); "
|
|
1238
|
+
@tkpts.each { |tkpt|
|
|
1239
|
+
curr_idx = curr_idx + 1
|
|
1240
|
+
if curr_idx == 0
|
|
1241
|
+
@start_dttm = tkpt.dttm
|
|
1242
|
+
@start_pos = tkpt.position
|
|
1243
|
+
time = Time.parse(@start_dttm.dateTime().to_s)
|
|
1244
|
+
end
|
|
1245
|
+
if ((curr_idx == next_idx) || (curr_idx == last_idx) || (tkpt.is_split()))
|
|
1246
|
+
gpoint_count = gpoint_count + 1
|
|
1247
|
+
s << tkpt.as_glatlng(comments, tkpt_count, curr_idx, gpoint_count, @start_dttm)
|
|
1248
|
+
next_idx = curr_idx + increment
|
|
1249
|
+
end
|
|
1250
|
+
}
|
|
1251
|
+
s << "\n"
|
|
1252
|
+
s << "\n var routePolyline = new GPolyline(points); "
|
|
1253
|
+
s << "\n map.addOverlay(routePolyline); "
|
|
1254
|
+
@content_hash['main_js_route_overlay'] = s
|
|
1255
|
+
@content_hash['main_js_route_overlay_increment'] = increment
|
|
1256
|
+
end
|
|
1257
|
+
|
|
1258
|
+
def generate_main_js_checkpoint_overlays
|
|
1259
|
+
s = "\n // Create a base icon for all of our markers that specifies the "
|
|
1260
|
+
s << "\n // shadow, icon dimensions, etc."
|
|
1261
|
+
s << "\n var baseIcon = new GIcon();"
|
|
1262
|
+
s << "\n baseIcon.shadow = \"http://www.joakim-systems.com/gicons/shadow50.png\";"
|
|
1263
|
+
s << "\n baseIcon.iconSize = new GSize(20, 34);"
|
|
1264
|
+
s << "\n baseIcon.shadowSize = new GSize(37, 34);"
|
|
1265
|
+
s << "\n baseIcon.iconAnchor = new GPoint(9, 34);"
|
|
1266
|
+
s << "\n baseIcon.infoWindowAnchor = new GPoint(9, 2);"
|
|
1267
|
+
s << "\n baseIcon.infoShadowAnchor = new GPoint(18, 25);"
|
|
1268
|
+
s << "\n"
|
|
1269
|
+
|
|
1270
|
+
curr_idx = -1
|
|
1271
|
+
last_idx = @tkpts.size - 1
|
|
1272
|
+
next_checkpoint = 0.0
|
|
1273
|
+
@start_dttm = nil
|
|
1274
|
+
@tkpts.each { | tkpt |
|
|
1275
|
+
curr_idx = curr_idx + 1
|
|
1276
|
+
if curr_idx == 0
|
|
1277
|
+
@start_dttm = tkpt.dttm
|
|
1278
|
+
info_window_html = tkpt.as_info_window_html('Start', @start_dttm)
|
|
1279
|
+
s << "\n var iconStart = new GIcon(baseIcon); "
|
|
1280
|
+
s << "\n iconStart.image = \"http://www.joakim-systems.com/gicons/dd-start.png\";"
|
|
1281
|
+
s << "\n var pStart = new GPoint(#{tkpt.longitude_as_float}, #{tkpt.latitude_as_float});"
|
|
1282
|
+
s << "\n var mStart = new GMarker(pStart, iconStart);"
|
|
1283
|
+
s << "\n GEvent.addListener(mStart, \"click\", function() { "
|
|
1284
|
+
s << "\n mStart.openInfoWindowHtml(#{info_window_html});"
|
|
1285
|
+
s << "\n }); "
|
|
1286
|
+
s << "\n map.addOverlay(mStart);"
|
|
1287
|
+
s << "\n "
|
|
1288
|
+
next_checkpoint = 1.0
|
|
1289
|
+
elsif curr_idx == last_idx
|
|
1290
|
+
info_window_html = tkpt.as_info_window_html('Finish', @start_dttm)
|
|
1291
|
+
s << "\n var iconFinish = new GIcon(baseIcon); "
|
|
1292
|
+
s << "\n iconFinish.image = \"http://www.joakim-systems.com/gicons/dd-end.png\";"
|
|
1293
|
+
s << "\n var pFinish = new GPoint(#{tkpt.longitude_as_float}, #{tkpt.latitude_as_float});"
|
|
1294
|
+
s << "\n var mFinish = new GMarker(pFinish, iconFinish);"
|
|
1295
|
+
s << "\n GEvent.addListener(mFinish, \"click\", function() { "
|
|
1296
|
+
s << "\n mFinish.openInfoWindowHtml(#{info_window_html});"
|
|
1297
|
+
s << "\n }); "
|
|
1298
|
+
s << "\n map.addOverlay(mFinish);"
|
|
1299
|
+
s << "\n "
|
|
1300
|
+
next_checkpoint = 999999
|
|
1301
|
+
else
|
|
1302
|
+
if (tkpt.cumulativeDistance >= next_checkpoint)
|
|
1303
|
+
integer = next_checkpoint.to_i
|
|
1304
|
+
info_window_html = tkpt.as_info_window_html("#{integer}", @start_dttm)
|
|
1305
|
+
s << "\n var icon#{integer} = new GIcon(baseIcon); "
|
|
1306
|
+
s << "\n icon#{integer}.image = \"http://www.joakim-systems.com/gicons/marker#{integer}.png\";"
|
|
1307
|
+
s << "\n var p#{integer} = new GPoint(#{tkpt.longitude_as_float}, #{tkpt.latitude_as_float});"
|
|
1308
|
+
s << "\n var m#{integer} = new GMarker(p#{integer}, icon#{integer});"
|
|
1309
|
+
s << "\n GEvent.addListener(m#{integer}, \"click\", function() { "
|
|
1310
|
+
s << "\n m#{integer}.openInfoWindowHtml(#{info_window_html});"
|
|
1311
|
+
s << "\n }); "
|
|
1312
|
+
s << "\n map.addOverlay(m#{integer});"
|
|
1313
|
+
s << "\n "
|
|
1314
|
+
next_checkpoint = next_checkpoint + 1.0
|
|
1315
|
+
end
|
|
1316
|
+
end
|
|
1317
|
+
}
|
|
1318
|
+
s << "\n"
|
|
1319
|
+
@content_hash['main_js_checkpoint_overlays'] = s
|
|
1320
|
+
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
def generate_main_js_map_clicked_listeners
|
|
1324
|
+
s = "\n"
|
|
1325
|
+
s << "\n GEvent.addListener(map, \"click\", function() { "
|
|
1326
|
+
s << "\n var center = map.getCenter(); \n"
|
|
1327
|
+
s << "\n document.getElementById(\"messages\").innerHTML = 'click: ' + center.toString(); "
|
|
1328
|
+
s << "\n });"
|
|
1329
|
+
s << "\n GEvent.addListener(map, \"moveend\", function() { "
|
|
1330
|
+
s << "\n var center = map.getCenter(); \n"
|
|
1331
|
+
s << "\n document.getElementById(\"messages\").innerHTML = 'moveend: ' + center.toString(); "
|
|
1332
|
+
s << "\n });"
|
|
1333
|
+
@content_hash['main_js_map_clicked_listeners'] = s
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
def generate_main_js_end
|
|
1337
|
+
s = "\n } "
|
|
1338
|
+
s << "\n } "
|
|
1339
|
+
s << "\n//]]> \n"
|
|
1340
|
+
s << "\n</script>"
|
|
1341
|
+
|
|
1342
|
+
@content_hash['main_js_end'] = s
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1345
|
+
end
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
=begin rdoc
|
|
1349
|
+
Instances of this class represent a <History> aggregate object from a
|
|
1350
|
+
Forerunner XML file.
|
|
1351
|
+
=end
|
|
1352
|
+
|
|
1353
|
+
class History < GoobyObject
|
|
1354
|
+
|
|
1355
|
+
attr_reader :runs
|
|
1356
|
+
|
|
1357
|
+
def initialize
|
|
1358
|
+
@runs = Array.new
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1361
|
+
# Adds a Run during XML parsing.
|
|
1362
|
+
def add_run(run)
|
|
1363
|
+
@runs.push(run)
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1366
|
+
def to_s
|
|
1367
|
+
return "Hist: runs: #{@runs.size}"
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
def print_string
|
|
1371
|
+
s = "History: run count=#{@runs.size} \n"
|
|
1372
|
+
runs.each { | run | s << run.print_string }
|
|
1373
|
+
s
|
|
1374
|
+
end
|
|
1375
|
+
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
=begin rdoc
|
|
1380
|
+
Instances of this class represent a <Lap> aggregate object from a
|
|
1381
|
+
Forerunner XML file.
|
|
1382
|
+
=end
|
|
1383
|
+
|
|
1384
|
+
class Lap < GoobyObject
|
|
1385
|
+
|
|
1386
|
+
attr_accessor :number, :startTime, :duration, :length, :beginPosition, :endPosition
|
|
1387
|
+
|
|
1388
|
+
def initialize(num)
|
|
1389
|
+
@number = num
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
def to_s
|
|
1393
|
+
return "Lap: num: #{@number} start: #{@startTime} dur: #{@duration} len: #{@length} begin: #{@beginPosition.to_s} end: #{@endPosition.to_s}"
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
=begin rdoc
|
|
1400
|
+
|
|
1401
|
+
=end
|
|
1402
|
+
|
|
1403
|
+
class Line < GoobyObject
|
|
1404
|
+
|
|
1405
|
+
attr_accessor :raw_data, :tokens
|
|
1406
|
+
|
|
1407
|
+
def initialize(raw='', delim=nil, strip=false)
|
|
1408
|
+
if strip
|
|
1409
|
+
@raw_data = raw.strip
|
|
1410
|
+
else
|
|
1411
|
+
@raw_data = raw
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
@tokens = tokenize(@raw_data, delim, strip=false)
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1417
|
+
public
|
|
1418
|
+
|
|
1419
|
+
def token(idx)
|
|
1420
|
+
@tokens[idx]
|
|
1421
|
+
end
|
|
1422
|
+
|
|
1423
|
+
def token_count
|
|
1424
|
+
@tokens.size
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
def token_idx_equals(idx, value)
|
|
1428
|
+
if idx < token_count
|
|
1429
|
+
if @tokens[idx] == value
|
|
1430
|
+
return true
|
|
1431
|
+
end
|
|
1432
|
+
end
|
|
1433
|
+
false
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
def match(pattern)
|
|
1437
|
+
@raw_data.match(pattern)
|
|
1438
|
+
end
|
|
1439
|
+
|
|
1440
|
+
def is_comment
|
|
1441
|
+
s = @raw_data.strip
|
|
1442
|
+
(s.match('^#')) ? true : false
|
|
1443
|
+
end
|
|
1444
|
+
|
|
1445
|
+
def is_populated_non_comment
|
|
1446
|
+
s = @raw_data.strip
|
|
1447
|
+
if s.size == 0
|
|
1448
|
+
return false
|
|
1449
|
+
end
|
|
1450
|
+
if is_comment
|
|
1451
|
+
return false
|
|
1452
|
+
end
|
|
1453
|
+
return true
|
|
1454
|
+
end
|
|
1455
|
+
|
|
1456
|
+
def concatinate_tokens(start_idx = 0)
|
|
1457
|
+
s = ''
|
|
1458
|
+
idx = -1
|
|
1459
|
+
@tokens.each { |tok|
|
|
1460
|
+
idx = idx + 1
|
|
1461
|
+
if idx >= start_idx
|
|
1462
|
+
s << tok
|
|
1463
|
+
s << ' '
|
|
1464
|
+
end
|
|
1465
|
+
}
|
|
1466
|
+
s.strip!
|
|
1467
|
+
s
|
|
1468
|
+
end
|
|
1469
|
+
end
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
class Options < GoobyObject
|
|
1473
|
+
|
|
1474
|
+
attr_reader :yamlFilename, :options
|
|
1475
|
+
|
|
1476
|
+
def initialize(filename) # Constructor.
|
|
1477
|
+
filename ? @yamlFilename = filename : @yamlFilename = 'gooby_options.yaml'
|
|
1478
|
+
loadFile()
|
|
1479
|
+
end
|
|
1480
|
+
|
|
1481
|
+
public
|
|
1482
|
+
|
|
1483
|
+
# Load the @yamlFilename
|
|
1484
|
+
def loadFile
|
|
1485
|
+
File.open("#{@yamlFilename}") { |fn|
|
|
1486
|
+
@options = YAML::load(fn)
|
|
1487
|
+
}
|
|
1488
|
+
end
|
|
1489
|
+
|
|
1490
|
+
def get(name)
|
|
1491
|
+
if name == nil
|
|
1492
|
+
return ''
|
|
1493
|
+
end
|
|
1494
|
+
s = @options["#{name}"]
|
|
1495
|
+
|
|
1496
|
+
# Provide "sensible defaults".
|
|
1497
|
+
if s == nil
|
|
1498
|
+
if (name == '')
|
|
1499
|
+
return ''
|
|
1500
|
+
elsif (name == 'gmap_first_tkpt_number')
|
|
1501
|
+
return 1
|
|
1502
|
+
elsif (name == 'gmap_last_tkpt_number')
|
|
1503
|
+
return 5000
|
|
1504
|
+
elsif (name == 'gmap_map_element_id')
|
|
1505
|
+
return 'map'
|
|
1506
|
+
elsif (name == 'gmap_height')
|
|
1507
|
+
return '600'
|
|
1508
|
+
elsif (name == 'gmap_key')
|
|
1509
|
+
return 'enter your Google Map Key here'
|
|
1510
|
+
elsif (name == 'gmap_type_control')
|
|
1511
|
+
return true
|
|
1512
|
+
elsif (name == 'gmap_approx_max_points')
|
|
1513
|
+
return '200'
|
|
1514
|
+
elsif (name == 'gmap_gen_comments')
|
|
1515
|
+
return true
|
|
1516
|
+
elsif (name == 'gmap_size_control')
|
|
1517
|
+
return nil
|
|
1518
|
+
elsif (name == 'gmap_type')
|
|
1519
|
+
return 'G_NORMAL_MAP'
|
|
1520
|
+
elsif (name == 'gmap_zoom_level')
|
|
1521
|
+
return 5
|
|
1522
|
+
else
|
|
1523
|
+
return ''
|
|
1524
|
+
end
|
|
1525
|
+
end
|
|
1526
|
+
s
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
# Return a String containing yaml filename and entry count.
|
|
1530
|
+
def to_s
|
|
1531
|
+
return "Options: filename: #{@yamlFilename} entries: #{@options.size}"
|
|
1532
|
+
end
|
|
1533
|
+
|
|
1534
|
+
end
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
=begin rdoc
|
|
1538
|
+
Instances of this class represent a <Position> aggregate object from a
|
|
1539
|
+
Forerunner XML file. Each contains a latitude and longitude.
|
|
1540
|
+
Instances within a Trackpoint will also contain an altitude.
|
|
1541
|
+
=end
|
|
1542
|
+
|
|
1543
|
+
class Position < GoobyObject
|
|
1544
|
+
|
|
1545
|
+
attr_accessor :latitude, :longitude, :altitude, :note
|
|
1546
|
+
|
|
1547
|
+
def initialize(lat, lng, alt='0', note='')
|
|
1548
|
+
@latitude = lat.to_s
|
|
1549
|
+
@longitude = lng.to_s
|
|
1550
|
+
@altitude = alt.to_s
|
|
1551
|
+
@note = note.to_s
|
|
1552
|
+
end
|
|
1553
|
+
|
|
1554
|
+
public
|
|
1555
|
+
|
|
1556
|
+
def to_s
|
|
1557
|
+
return "lat: #{@latitude} lng: #{@longitude} alt: #{@altitude} note: #{@note}"
|
|
1558
|
+
end
|
|
1559
|
+
|
|
1560
|
+
def to_csv
|
|
1561
|
+
return "#{@latitude} | #{@longitude} | #{@altitude}"
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
def latitude_as_float
|
|
1565
|
+
@latitude ? @latitude.to_f : invalid_latitude
|
|
1566
|
+
end
|
|
1567
|
+
|
|
1568
|
+
def longitude_as_float
|
|
1569
|
+
@longitude ? @longitude.to_f : invalid_longitude
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
def altitude_as_float
|
|
1573
|
+
@altitude ? @altitude.to_f : invalid_altitude
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
end
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
=begin rdoc
|
|
1580
|
+
Instances of this class represent a <Run> aggregate object from a
|
|
1581
|
+
Forerunner XML file.
|
|
1582
|
+
|
|
1583
|
+
Additionally, there is distance, pace, and Google Map generation logic
|
|
1584
|
+
in this class.
|
|
1585
|
+
=end
|
|
1586
|
+
|
|
1587
|
+
class Run < GoobyObject
|
|
1588
|
+
|
|
1589
|
+
attr_accessor :number, :descr, :notes, :tracks, :tkpts, :laps, :distance
|
|
1590
|
+
|
|
1591
|
+
def initialize(number=0, descr='')
|
|
1592
|
+
@number = number
|
|
1593
|
+
@descr = descr
|
|
1594
|
+
@notes = ''
|
|
1595
|
+
@tracks = Array.new
|
|
1596
|
+
@tkpts = Array.new
|
|
1597
|
+
@laps = Array.new
|
|
1598
|
+
@distance = 0
|
|
1599
|
+
@options = Hash.new
|
|
1600
|
+
@logProgress = true
|
|
1601
|
+
@finished = false
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
public
|
|
1605
|
+
|
|
1606
|
+
# This method is invoked at end-of-parsing.
|
|
1607
|
+
def finish()
|
|
1608
|
+
@logProgress = false
|
|
1609
|
+
unless @finished
|
|
1610
|
+
@tracks.each { |trk|
|
|
1611
|
+
trk.trackpoints().each { |tkpt|
|
|
1612
|
+
tkpt.runNumber = @number
|
|
1613
|
+
@tkpts.push(tkpt)
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
compute_distance_and_pace
|
|
1617
|
+
compute_splits
|
|
1618
|
+
@finished = true
|
|
1619
|
+
end
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
public
|
|
1623
|
+
|
|
1624
|
+
def add_track(trk)
|
|
1625
|
+
if trk != nil
|
|
1626
|
+
@tracks.push(trk)
|
|
1627
|
+
end
|
|
1628
|
+
end
|
|
1629
|
+
|
|
1630
|
+
def trackpoint_count()
|
|
1631
|
+
@tkpts.size()
|
|
1632
|
+
end
|
|
1633
|
+
|
|
1634
|
+
def add_lap(lap)
|
|
1635
|
+
@laps.push(lap)
|
|
1636
|
+
end
|
|
1637
|
+
|
|
1638
|
+
def lapCount()
|
|
1639
|
+
@laps.size
|
|
1640
|
+
end
|
|
1641
|
+
|
|
1642
|
+
def start_dttm()
|
|
1643
|
+
count = 0
|
|
1644
|
+
@tracks.each { |trk|
|
|
1645
|
+
trk.trackpoints().each { |tkpt|
|
|
1646
|
+
return tkpt.dttm()
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return nil
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
def end_dttm()
|
|
1653
|
+
lastOne = nil
|
|
1654
|
+
@tracks.each { |trk|
|
|
1655
|
+
trk.trackpoints().each { |tkpt|
|
|
1656
|
+
lastOne = tkpt.dttm()
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
lastOne
|
|
1660
|
+
end
|
|
1661
|
+
|
|
1662
|
+
def duration()
|
|
1663
|
+
first = start_dttm()
|
|
1664
|
+
last = end_dttm()
|
|
1665
|
+
if first
|
|
1666
|
+
if last
|
|
1667
|
+
return last.hhmmss_diff(first)
|
|
1668
|
+
end
|
|
1669
|
+
end
|
|
1670
|
+
return "??:??:??"
|
|
1671
|
+
end
|
|
1672
|
+
|
|
1673
|
+
def start_yyyy_mm_dd
|
|
1674
|
+
if start_dttm()
|
|
1675
|
+
start_dttm().yyyy_mm_dd()
|
|
1676
|
+
else
|
|
1677
|
+
""
|
|
1678
|
+
end
|
|
1679
|
+
end
|
|
1680
|
+
|
|
1681
|
+
def start_hh_mm_ss
|
|
1682
|
+
if start_dttm()
|
|
1683
|
+
start_dttm().hh_mm_ss()
|
|
1684
|
+
else
|
|
1685
|
+
""
|
|
1686
|
+
end
|
|
1687
|
+
end
|
|
1688
|
+
|
|
1689
|
+
def end_hh_mm_ss
|
|
1690
|
+
if end_dttm()
|
|
1691
|
+
end_dttm().hh_mm_ss()
|
|
1692
|
+
else
|
|
1693
|
+
""
|
|
1694
|
+
end
|
|
1695
|
+
end
|
|
1696
|
+
|
|
1697
|
+
def to_s
|
|
1698
|
+
finish() unless @finished
|
|
1699
|
+
s = "Run: #{@number} date: #{start_yyyy_mm_dd} distance: #{distance} duration: #{duration} "
|
|
1700
|
+
s << " tracks: #{@tracks.size} tkpts: #{trackpoint_count} laps: #{lapCount} "
|
|
1701
|
+
s << " notes: #{@notes} "
|
|
1702
|
+
s
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
def print_string
|
|
1706
|
+
finish() unless @finished
|
|
1707
|
+
"Run number=#{@number} tracks=#{@tracks.size} tkpts=#{@tkpts.size} laps=#{@laps.size} distance=#{@distance} "
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
def put_csv()
|
|
1711
|
+
finish() unless @finished
|
|
1712
|
+
puts "#{@number}|#{}|#{start_yyyy_mm_dd()}|#{start_hh_mm_ss()}|#{end_hh_mm_ss}|#{duration()}|#{@distance}|#{@tracks.size}|#{trackpoint_count()}|#{lapCount}|#{@notes.strip}"
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
def put_tkpt_csv(with_header_comment=false)
|
|
1716
|
+
finish() unless @finished
|
|
1717
|
+
if with_header_comment
|
|
1718
|
+
puts "# Run: #{@number} date: #{start_yyyy_mm_dd} dist: #{distance} dur: #{duration} trks: #{@tracks.size} tkpts: #{trackpoint_count} laps: #{lapCount} "
|
|
1719
|
+
end
|
|
1720
|
+
@tkpts.each { | tkpt | puts tkpt.to_csv }
|
|
1721
|
+
end
|
|
1722
|
+
|
|
1723
|
+
def put_laps
|
|
1724
|
+
@laps.each { | lap | puts lap.to_s }
|
|
1725
|
+
end
|
|
1726
|
+
|
|
1727
|
+
private
|
|
1728
|
+
|
|
1729
|
+
def compute_distance_and_pace
|
|
1730
|
+
cumulative_dist = 0.to_f;
|
|
1731
|
+
curr_index = -1
|
|
1732
|
+
prev_tkpt = nil
|
|
1733
|
+
start_dttm = nil
|
|
1734
|
+
@tkpts.each { | tkpt |
|
|
1735
|
+
curr_index = curr_index + 1
|
|
1736
|
+
if curr_index == 0
|
|
1737
|
+
start_dttm = tkpt.dttm()
|
|
1738
|
+
prev_tkpt = tkpt
|
|
1739
|
+
else
|
|
1740
|
+
cumulative_dist = tkpt.compute_distance_and_pace(curr_index, start_dttm, cumulative_dist, prev_tkpt, 'M')
|
|
1741
|
+
prev_tkpt = tkpt
|
|
1742
|
+
end
|
|
1743
|
+
}
|
|
1744
|
+
@distance = cumulative_dist
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
def compute_splits
|
|
1748
|
+
nextSplitDist = 1.00
|
|
1749
|
+
prevSplitTkpt = nil
|
|
1750
|
+
loop1Count = 0;
|
|
1751
|
+
@tkpts.each { |tkpt|
|
|
1752
|
+
loop1Count = loop1Count + 1
|
|
1753
|
+
if tkpt.cumulativeDistance() >= nextSplitDist
|
|
1754
|
+
tkpt.set_split(0 + nextSplitDist, prevSplitTkpt)
|
|
1755
|
+
nextSplitDist = nextSplitDist + 1.00
|
|
1756
|
+
prevSplitTkpt = tkpt
|
|
1757
|
+
end
|
|
1758
|
+
}
|
|
1759
|
+
# set first and last booleans
|
|
1760
|
+
count = 0
|
|
1761
|
+
@tkpts.each { |tkpt|
|
|
1762
|
+
count = count + 1
|
|
1763
|
+
tkpt.first = true if count == 1
|
|
1764
|
+
tkpt.last = true if count == loop1Count
|
|
1765
|
+
}
|
|
1766
|
+
end
|
|
1767
|
+
|
|
1768
|
+
end
|
|
1769
|
+
|
|
1770
|
+
|
|
1771
|
+
=begin rdoc
|
|
1772
|
+
Sample implementation of a REXML::StreamListener SAX parser.
|
|
1773
|
+
=end
|
|
1774
|
+
|
|
1775
|
+
class SimpleXmlParser
|
|
1776
|
+
|
|
1777
|
+
include REXML::StreamListener
|
|
1778
|
+
|
|
1779
|
+
attr_accessor :tag_count, :watched_tags
|
|
1780
|
+
|
|
1781
|
+
def initialize
|
|
1782
|
+
@tag_count = 0
|
|
1783
|
+
@counter_hash = CounterHash.new
|
|
1784
|
+
end
|
|
1785
|
+
|
|
1786
|
+
public
|
|
1787
|
+
|
|
1788
|
+
# SAX API method. Increments the tagname in the counter hash.
|
|
1789
|
+
def tag_start(tag_name, attrs)
|
|
1790
|
+
@tag_count = @tag_count + 1
|
|
1791
|
+
@counter_hash.increment(tag_name)
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1794
|
+
# SAX API method. No impl.
|
|
1795
|
+
def tag_end(tagname)
|
|
1796
|
+
end
|
|
1797
|
+
|
|
1798
|
+
# SAX API method. No impl.
|
|
1799
|
+
def text(txt)
|
|
1800
|
+
end
|
|
1801
|
+
|
|
1802
|
+
# Prints the state of this object (the counter hash).
|
|
1803
|
+
def dump
|
|
1804
|
+
puts @counter_hash.to_s
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
end
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
=begin rdoc
|
|
1811
|
+
This class is used to generate, on an ongoing basis, the various Gooby test
|
|
1812
|
+
classes. Regeneration retains the current test methods, and adds stubs for
|
|
1813
|
+
new test methods. All methods, old and new, appear in the merged output in
|
|
1814
|
+
propper sort sequence.
|
|
1815
|
+
=end
|
|
1816
|
+
|
|
1817
|
+
class TestGenerator < GoobyObject
|
|
1818
|
+
|
|
1819
|
+
def initialize
|
|
1820
|
+
tested_files.each { | base_rb_file |
|
|
1821
|
+
test_file = "tc_#{base_rb_file}"
|
|
1822
|
+
if true
|
|
1823
|
+
@codeLines = read_lines("lib/#{base_rb_file}")
|
|
1824
|
+
@testLines = read_lines("tests/#{test_file}")
|
|
1825
|
+
@codeHash = Hash.new
|
|
1826
|
+
@testHash = Hash.new
|
|
1827
|
+
@mergedHash = Hash.new
|
|
1828
|
+
@excludeClasses = %w( TestHelper TestGenerator )
|
|
1829
|
+
puts "current codeLines = #{@codeLines.size}"
|
|
1830
|
+
puts "current testLines = #{@testLines.size}"
|
|
1831
|
+
parse_code_lines
|
|
1832
|
+
parse_test_lines
|
|
1833
|
+
merge_keys
|
|
1834
|
+
regenerate(test_file)
|
|
1835
|
+
end
|
|
1836
|
+
}
|
|
1837
|
+
end
|
|
1838
|
+
|
|
1839
|
+
private
|
|
1840
|
+
|
|
1841
|
+
# Produce a Hash with keys and values like the following:
|
|
1842
|
+
# test|class|Trackpoint|deg2rad Trackpoint|deg2rad(degrees)
|
|
1843
|
+
|
|
1844
|
+
def parse_code_lines
|
|
1845
|
+
type = ''
|
|
1846
|
+
type_name = ''
|
|
1847
|
+
meth_name = ''
|
|
1848
|
+
@codeLines.each { | line |
|
|
1849
|
+
line.strip!
|
|
1850
|
+
if (line.match(/^module /))
|
|
1851
|
+
type = 'module'
|
|
1852
|
+
tokens = line.split
|
|
1853
|
+
type_name = tokens[1]
|
|
1854
|
+
elsif (line.match(/^class /))
|
|
1855
|
+
type = 'class'
|
|
1856
|
+
tokens = line.split
|
|
1857
|
+
type_name = tokens[1]
|
|
1858
|
+
elsif (line.match(/^def /))
|
|
1859
|
+
signature = line[4...999]
|
|
1860
|
+
short_method = parse_meth_name("#{signature}")
|
|
1861
|
+
@codeHash["test_#{type}_#{type_name}"] = "#{type_name}"
|
|
1862
|
+
@codeHash["test_#{type}_#{type_name}_#{short_method}"] = "#{type_name}|#{signature}"
|
|
1863
|
+
end
|
|
1864
|
+
}
|
|
1865
|
+
end
|
|
1866
|
+
|
|
1867
|
+
def parse_meth_name(string)
|
|
1868
|
+
string.gsub!('(', ' ')
|
|
1869
|
+
string.gsub!(')', ' ')
|
|
1870
|
+
tokens = string.split
|
|
1871
|
+
tokens[0]
|
|
1872
|
+
end
|
|
1873
|
+
|
|
1874
|
+
def parse_test_lines
|
|
1875
|
+
in_method = true
|
|
1876
|
+
method_name = 'a_start'
|
|
1877
|
+
method_lines = Array.new
|
|
1878
|
+
line_num = 0
|
|
1879
|
+
|
|
1880
|
+
@testLines.each { | line |
|
|
1881
|
+
line_num = line_num + 1
|
|
1882
|
+
line.chomp!
|
|
1883
|
+
prefix = line[0...5] # ' def'
|
|
1884
|
+
prefix21 = line[0...21] # ' ### Start of tests.'
|
|
1885
|
+
|
|
1886
|
+
if ((prefix == ' def') || (prefix == "\tdef"))
|
|
1887
|
+
in_method = true
|
|
1888
|
+
tokens = line.split
|
|
1889
|
+
method_name = tokens[1]
|
|
1890
|
+
method_lines = Array.new
|
|
1891
|
+
end
|
|
1892
|
+
if in_method
|
|
1893
|
+
method_lines << "#{line}"
|
|
1894
|
+
end
|
|
1895
|
+
if prefix21 == ' ### Start of tests.'
|
|
1896
|
+
in_method = false
|
|
1897
|
+
@testHash["#{method_name}"] = method_lines
|
|
1898
|
+
end
|
|
1899
|
+
if ((prefix == ' end') || (prefix == "\tend"))
|
|
1900
|
+
in_method = false
|
|
1901
|
+
@testHash["#{method_name}"] = method_lines
|
|
1902
|
+
end
|
|
1903
|
+
}
|
|
1904
|
+
end
|
|
1905
|
+
|
|
1906
|
+
def merge_keys
|
|
1907
|
+
@codeHash.keys.sort.each { |key| @mergedHash["#{key}"] = "code" }
|
|
1908
|
+
@testHash.keys.sort.each { |key| @mergedHash["#{key}"] = "test" }
|
|
1909
|
+
end
|
|
1910
|
+
|
|
1911
|
+
def regenerate(test_file)
|
|
1912
|
+
code = ''
|
|
1913
|
+
@mergedHash.keys.sort.each { |key|
|
|
1914
|
+
|
|
1915
|
+
tokens = key.split('_')
|
|
1916
|
+
type, name, meth = tokens[1], tokens[2], tokens[3]
|
|
1917
|
+
|
|
1918
|
+
processThisKey = true
|
|
1919
|
+
@excludeClasses.each { |xc|
|
|
1920
|
+
if xc == name
|
|
1921
|
+
processThisKey = false
|
|
1922
|
+
end
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
next if !processThisKey
|
|
1926
|
+
|
|
1927
|
+
if @testHash.has_key?(key)
|
|
1928
|
+
# We already have a test method written in the test class,
|
|
1929
|
+
# so keep this currently existing test code!
|
|
1930
|
+
|
|
1931
|
+
if @codeHash.has_key?(key)
|
|
1932
|
+
comment = nil
|
|
1933
|
+
else
|
|
1934
|
+
if key != 'a_start'
|
|
1935
|
+
comment = "# Warning: possible obsolete test method - #{key}"
|
|
1936
|
+
end
|
|
1937
|
+
end
|
|
1938
|
+
|
|
1939
|
+
code << "\n"
|
|
1940
|
+
if comment != nil
|
|
1941
|
+
code << "\n#{comment}"
|
|
1942
|
+
code << "\n"
|
|
1943
|
+
end
|
|
1944
|
+
array = @testHash["#{key}"]
|
|
1945
|
+
array.each { |line| code << "\n#{line}" }
|
|
1946
|
+
else
|
|
1947
|
+
# We don't have this test method in the current test class,
|
|
1948
|
+
# so generate a test method stub.
|
|
1949
|
+
|
|
1950
|
+
code << "\n"
|
|
1951
|
+
code << "\n def #{key}"
|
|
1952
|
+
code << "\n"
|
|
1953
|
+
|
|
1954
|
+
if @gen_impl_stub
|
|
1955
|
+
if type = 'class'
|
|
1956
|
+
code << "\n #obj = #{type}.new"
|
|
1957
|
+
code << "\n #result = obj.#{meth}"
|
|
1958
|
+
code << "\n #expected = ''"
|
|
1959
|
+
s = "\n"
|
|
1960
|
+
s << ' #assert_equal(expected, actual, "'
|
|
1961
|
+
s << "#{type}.#{meth} "
|
|
1962
|
+
s << 'values are not as expected; #{result} vs #{expected}")'
|
|
1963
|
+
code << s
|
|
1964
|
+
else
|
|
1965
|
+
code << "\n #result = #{type}.#{meth}"
|
|
1966
|
+
code << "\n #expected = ''"
|
|
1967
|
+
s = "\n"
|
|
1968
|
+
s << ' #assert_equal(expected, actual, "'
|
|
1969
|
+
s << "#{type}.#{meth} "
|
|
1970
|
+
s << 'values are not as expected; #{result} vs #{expected}")'
|
|
1971
|
+
code << s
|
|
1972
|
+
end
|
|
1973
|
+
end
|
|
1974
|
+
code << "\n end"
|
|
1975
|
+
end
|
|
1976
|
+
}
|
|
1977
|
+
code << "\nend" # end of class
|
|
1978
|
+
code << "\n"
|
|
1979
|
+
fn = "tests/#{test_file}"
|
|
1980
|
+
out = File.new fn, "w+"
|
|
1981
|
+
out.write code
|
|
1982
|
+
out.flush
|
|
1983
|
+
out.close
|
|
1984
|
+
puts "file written: #{fn}"
|
|
1985
|
+
end
|
|
1986
|
+
|
|
1987
|
+
end
|
|
1988
|
+
|
|
1989
|
+
|
|
1990
|
+
=begin rdoc
|
|
1991
|
+
Instances of this class represent a <Track> aggregate object from a Forerunner
|
|
1992
|
+
XML file. Note that a <Run> may contain more than one <Track> aggregates.
|
|
1993
|
+
=end
|
|
1994
|
+
|
|
1995
|
+
class Track < GoobyObject
|
|
1996
|
+
|
|
1997
|
+
attr_reader :number, :descr, :trackpoints
|
|
1998
|
+
|
|
1999
|
+
def initialize(num=0, descr='')
|
|
2000
|
+
@number = num
|
|
2001
|
+
@descr = descr
|
|
2002
|
+
@trackpoints = Array.new
|
|
2003
|
+
end
|
|
2004
|
+
|
|
2005
|
+
public
|
|
2006
|
+
|
|
2007
|
+
def add_trackpoint(tkpt)
|
|
2008
|
+
@trackpoints.push(tkpt)
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
def size
|
|
2012
|
+
@trackpoints.size
|
|
2013
|
+
end
|
|
2014
|
+
|
|
2015
|
+
def first_tkpt
|
|
2016
|
+
@trackpoints.size > 0 ? @trackpoints[0] : nil
|
|
2017
|
+
end
|
|
2018
|
+
|
|
2019
|
+
def last_tkpt
|
|
2020
|
+
@trackpoints.size > 0 ? @trackpoints[-1] : nil
|
|
2021
|
+
end
|
|
2022
|
+
|
|
2023
|
+
def to_s
|
|
2024
|
+
return "Trk: #{@descr} tkpts: #{size}"
|
|
2025
|
+
end
|
|
2026
|
+
|
|
2027
|
+
def dump
|
|
2028
|
+
puts "Track: '#{@descr}' tkpts: #{size}"
|
|
2029
|
+
@trackpoints.each { |tkpt| puts tkpt.to_csv } # to_geo_s
|
|
2030
|
+
end
|
|
2031
|
+
|
|
2032
|
+
end
|
|
2033
|
+
|
|
2034
|
+
|
|
2035
|
+
=begin rdoc
|
|
2036
|
+
Instances of this class represent a <Trackpoint> aggregate from a Forerunner
|
|
2037
|
+
XML file. Additionally, there is distance, pace, and Google Map generation
|
|
2038
|
+
logic in this class.
|
|
2039
|
+
|
|
2040
|
+
<Trackpoint>
|
|
2041
|
+
<Position>
|
|
2042
|
+
<Latitude>35.49577</Latitude>
|
|
2043
|
+
<Longitude>-80.83281</Longitude>
|
|
2044
|
+
<Altitude>232.296</Altitude>
|
|
2045
|
+
</Position>
|
|
2046
|
+
<Time>2007-01-06T15:27:51Z</Time>
|
|
2047
|
+
</Trackpoint>
|
|
2048
|
+
=end
|
|
2049
|
+
|
|
2050
|
+
class Trackpoint < Position
|
|
2051
|
+
|
|
2052
|
+
attr_reader :first, :last, :number, :runNumber, :dttm, :prevTkpt, :descr
|
|
2053
|
+
attr_reader :cumulativeDistance, :cumulativePace, :incrementalDistance, :split, :prevSplit
|
|
2054
|
+
attr_writer :first, :last, :runNumber
|
|
2055
|
+
|
|
2056
|
+
def initialize(num, lat, lng, alt, time_string, descr='')
|
|
2057
|
+
@number = num
|
|
2058
|
+
@runNumber = 0
|
|
2059
|
+
|
|
2060
|
+
# initialize superclass variables:
|
|
2061
|
+
@latitude = lat.to_s
|
|
2062
|
+
@longitude = lng.to_s
|
|
2063
|
+
@altitude = alt.to_s
|
|
2064
|
+
@note = note.to_s
|
|
2065
|
+
@dttm = DtTm.new(time_string)
|
|
2066
|
+
@first = false
|
|
2067
|
+
@last = false
|
|
2068
|
+
@prevTkpt = nil
|
|
2069
|
+
@cumulativeDistance, @split = 0.0, 0.0
|
|
2070
|
+
@cumulativePace = ""
|
|
2071
|
+
@descr = descr
|
|
2072
|
+
end
|
|
2073
|
+
|
|
2074
|
+
public
|
|
2075
|
+
|
|
2076
|
+
def position
|
|
2077
|
+
Position.new(@latitude, @longitude, @altitude, @note)
|
|
2078
|
+
end
|
|
2079
|
+
|
|
2080
|
+
def to_s
|
|
2081
|
+
"Tkpt: #{@number} #{super.to_s} date: #{@dttm.to_s} cdist: #{@cumulativeDistance}"
|
|
2082
|
+
end
|
|
2083
|
+
|
|
2084
|
+
def to_csv
|
|
2085
|
+
ss = position.to_csv
|
|
2086
|
+
"#{@runNumber} | #{@dttm.to_s} | #{@number} | #{ss} | #{@cumulativeDistance} "
|
|
2087
|
+
end
|
|
2088
|
+
|
|
2089
|
+
def to_geo_s
|
|
2090
|
+
ss = position.to_csv
|
|
2091
|
+
"Tkpt: #{@number} | #{ss} | #{@descr}"
|
|
2092
|
+
end
|
|
2093
|
+
|
|
2094
|
+
def compute_distance_and_pace(curr_index, start_dttm, prev_cumulative_dist, prev_trackpoint, units)
|
|
2095
|
+
@prev_tkpt = prev_trackpoint
|
|
2096
|
+
@cumulativeDistance = prev_cumulative_dist.to_f
|
|
2097
|
+
|
|
2098
|
+
if @prev_tkpt
|
|
2099
|
+
arg1 = latitude().to_f
|
|
2100
|
+
arg2 = @prev_tkpt.latitude().to_f
|
|
2101
|
+
arg3 = latitude().to_f
|
|
2102
|
+
arg4 = @prev_tkpt.latitude().to_f
|
|
2103
|
+
theta = longitude().to_f - @prev_tkpt.longitude().to_f
|
|
2104
|
+
|
|
2105
|
+
res1 = Math.sin(deg2rad(arg1))
|
|
2106
|
+
res2 = Math.sin(deg2rad(arg2))
|
|
2107
|
+
res3 = Math.cos(deg2rad(arg3))
|
|
2108
|
+
res4 = Math.cos(deg2rad(arg4))
|
|
2109
|
+
res5 = Math.cos(deg2rad(theta.to_f))
|
|
2110
|
+
|
|
2111
|
+
incremental_distance = ((res1 * res2) + (res3 * res4 * res5)).to_f
|
|
2112
|
+
|
|
2113
|
+
if (!incremental_distance.nan?)
|
|
2114
|
+
incremental_distance = Math.acos(incremental_distance.to_f)
|
|
2115
|
+
if (!incremental_distance.nan?)
|
|
2116
|
+
incremental_distance = rad2deg(incremental_distance)
|
|
2117
|
+
if (!incremental_distance.nan?)
|
|
2118
|
+
incremental_distance = incremental_distance * 60 * 1.1515;
|
|
2119
|
+
if (!incremental_distance.nan?)
|
|
2120
|
+
if units == "K"
|
|
2121
|
+
incremental_distance = incremental_distance * 1.609344;
|
|
2122
|
+
end
|
|
2123
|
+
if units == "N"
|
|
2124
|
+
incremental_distance = incremental_distance * 0.8684;
|
|
2125
|
+
end
|
|
2126
|
+
@cumulativeDistance = @cumulativeDistance + incremental_distance.to_f
|
|
2127
|
+
end
|
|
2128
|
+
end
|
|
2129
|
+
end
|
|
2130
|
+
end
|
|
2131
|
+
compute_cumulative_pace(start_dttm)
|
|
2132
|
+
@cumulativeDistance
|
|
2133
|
+
else
|
|
2134
|
+
0
|
|
2135
|
+
end
|
|
2136
|
+
end
|
|
2137
|
+
|
|
2138
|
+
def compute_cumulative_pace(start_dttm)
|
|
2139
|
+
if @cumulativeDistance > 0
|
|
2140
|
+
secsDiff = @dttm.seconds_diff(start_dttm)
|
|
2141
|
+
secsMile = ((secsDiff.to_f) / (@cumulativeDistance.to_f))
|
|
2142
|
+
minsMile = (secsMile / 60)
|
|
2143
|
+
wholeMins = minsMile.floor
|
|
2144
|
+
secsBal = secsMile - (wholeMins * 60)
|
|
2145
|
+
s1 = "#{secsDiff} #{secsMile} #{minsMile} #{wholeMins} #{secsBal} #{@cumulativeDistance} | "
|
|
2146
|
+
s2 = sprintf("%d:%2.1f", minsMile, secsBal)
|
|
2147
|
+
@cumulativePace = "#{s2}"
|
|
2148
|
+
else
|
|
2149
|
+
@cumulativePace = ""
|
|
2150
|
+
end
|
|
2151
|
+
end
|
|
2152
|
+
|
|
2153
|
+
def set_split(n, tkpt)
|
|
2154
|
+
@split, @prevSplit = n, tkpt
|
|
2155
|
+
end
|
|
2156
|
+
|
|
2157
|
+
def is_split()
|
|
2158
|
+
(@split >= 1)
|
|
2159
|
+
end
|
|
2160
|
+
|
|
2161
|
+
def is_first()
|
|
2162
|
+
@first
|
|
2163
|
+
end
|
|
2164
|
+
|
|
2165
|
+
def is_last()
|
|
2166
|
+
@last
|
|
2167
|
+
end
|
|
2168
|
+
|
|
2169
|
+
def split_info(dtTm)
|
|
2170
|
+
if is_split
|
|
2171
|
+
hhmmss = ''
|
|
2172
|
+
if @prevSplit
|
|
2173
|
+
return "#{@split} #{@dttm.hhmmss_diff(@prevSplit.dttm())}"
|
|
2174
|
+
else
|
|
2175
|
+
return "#{@split} #{@dttm.hhmmss_diff(dtTm)}"
|
|
2176
|
+
end
|
|
2177
|
+
else
|
|
2178
|
+
""
|
|
2179
|
+
end
|
|
2180
|
+
end
|
|
2181
|
+
|
|
2182
|
+
private
|
|
2183
|
+
|
|
2184
|
+
def deg2rad(degrees)
|
|
2185
|
+
(((0 + degrees) * Math::PI) / 180)
|
|
2186
|
+
end
|
|
2187
|
+
|
|
2188
|
+
def rad2deg(radians)
|
|
2189
|
+
(((0 + radians) * 180) / Math::PI)
|
|
2190
|
+
end
|
|
2191
|
+
|
|
2192
|
+
public
|
|
2193
|
+
|
|
2194
|
+
def as_glatlng(comments, tkpt_count, curr_idx, gpoint_count, start_dttm)
|
|
2195
|
+
if comments
|
|
2196
|
+
secs_diff = @dttm.seconds_diff(start_dttm)
|
|
2197
|
+
fmt_time = @dttm.hhmmss_diff(start_dttm)
|
|
2198
|
+
"\n points.push(new GLatLng(#{latitude_as_float},#{longitude_as_float})); " +
|
|
2199
|
+
"// #{gpoint_count} (#{curr_idx + 1} of #{tkpt_count}) #{@dttm.to_s} #{secs_diff} #{fmt_time} #{@cumulativeDistance} #{split_info(start_dttm)} "
|
|
2200
|
+
else
|
|
2201
|
+
"\n points.push(new GLatLng(#{latitude_as_float},#{longitude_as_float})); "
|
|
2202
|
+
end
|
|
2203
|
+
end
|
|
2204
|
+
|
|
2205
|
+
def as_info_window_html(checkpoint, start_dttm)
|
|
2206
|
+
s = "\"<table align='left'>"
|
|
2207
|
+
if checkpoint
|
|
2208
|
+
secs_diff = @dttm.seconds_diff(start_dttm)
|
|
2209
|
+
fmt_time = @dttm.hhmmss_diff(start_dttm)
|
|
2210
|
+
|
|
2211
|
+
if checkpoint == 'Start'
|
|
2212
|
+
s << "<tr><td colspan='2'><b>Start!</b></td></tr>"
|
|
2213
|
+
elsif checkpoint == 'Finish'
|
|
2214
|
+
s << "<tr><td colspan='2'><b>Finish!</b></td></tr>"
|
|
2215
|
+
else
|
|
2216
|
+
s << "<tr><td colspan='2'><b>Checkpoint #{checkpoint}</b></td></tr>"
|
|
2217
|
+
end
|
|
2218
|
+
s << "<tr><td>Distance: </td><td>#{@cumulativeDistance}</td></tr>"
|
|
2219
|
+
s << "<tr><td>Time of Day: </td><td>#{@dttm.to_s} </td></tr>"
|
|
2220
|
+
s << "<tr><td>Elapsed Time: </td><td>#{fmt_time} </td></tr>"
|
|
2221
|
+
s << "<tr><td>Average Pace: </td><td>#{@cumulativePace} </td></tr>"
|
|
2222
|
+
s << "<tr><td>Lat/Lng: </td><td>#{latitude_as_float} , #{longitude_as_float} </td></tr>"
|
|
2223
|
+
s << "<tr><td>Altitude: </td><td>#{altitude_as_float}m </td></tr>"
|
|
2224
|
+
s
|
|
2225
|
+
end
|
|
2226
|
+
s << "</table>\""
|
|
2227
|
+
s
|
|
2228
|
+
end
|
|
2229
|
+
|
|
2230
|
+
end
|
|
2231
|
+
|
|
2232
|
+
|
|
2233
|
+
=begin rdoc
|
|
2234
|
+
This class provides "user friendly DSL" functionality for the use
|
|
2235
|
+
of Gooby.
|
|
2236
|
+
=end
|
|
2237
|
+
|
|
2238
|
+
class GoobyCommand < GoobyObject
|
|
2239
|
+
|
|
2240
|
+
def initialize
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
def options(yaml_filename)
|
|
2244
|
+
Gooby::Options.new(yaml_filename)
|
|
2245
|
+
end
|
|
2246
|
+
|
|
2247
|
+
def split_forerunner_xml(xml_filename, out_dir)
|
|
2248
|
+
splitter = Gooby::ForerunnerXmlSplitter.new(xml_filename, out_dir)
|
|
2249
|
+
splitter.split
|
|
2250
|
+
end
|
|
2251
|
+
|
|
2252
|
+
def parse_forerunner_xml(xml_filename)
|
|
2253
|
+
handler = Gooby::ForerunnerXmlParser.new
|
|
2254
|
+
Document.parse_stream((File.new xml_filename), handler)
|
|
2255
|
+
handler.put_all_run_tkpt_csv(true)
|
|
2256
|
+
end
|
|
2257
|
+
|
|
2258
|
+
def generate_google_map(csv_filename, options_obj)
|
|
2259
|
+
generator = Gooby::GoogleMapGenerator.new(csv_filename)
|
|
2260
|
+
generator.generate_page(options_obj)
|
|
2261
|
+
end
|
|
2262
|
+
|
|
2263
|
+
end
|
|
2264
|
+
|
|
2265
|
+
end # end of module
|