fdc 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/lib/fdc.rb +1 -1
  2. data/lib/fdc/converter.rb +216 -241
  3. data/lib/fdc/utilities.rb +49 -2
  4. metadata +3 -2
data/lib/fdc.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Ruby stdlib
1
+ # Ruby core and stdlib
2
2
  require 'date'
3
3
  require 'pathname'
4
4
 
@@ -4,281 +4,256 @@ require 'builder'
4
4
  require 'fdc/utilities'
5
5
  require 'fdc/exceptions'
6
6
 
7
- module Fdc
7
+ # Class to convert IGC files to KML.
8
+ #
9
+ # @!attribute [r] kml
10
+ # @return [String] The KML document
11
+ #
12
+ # @example
13
+ # converter = Converter.new
14
+ # converter.parse(path/to/file.igc)
15
+ # converter.compile
16
+ # converter.export(output/dir)
17
+ class Fdc::Converter
8
18
 
9
- # Class to convert IGC files to KML.
10
- #
11
- # @!attribute [r] kml
12
- # @return [String] The KML document
19
+ # FileLoader mixing
20
+ include Fdc::FileLoader
21
+
22
+ # The compiled KML document
23
+ attr_reader :kml
24
+
25
+ # Load and parse an IGC file from the supplied path.
13
26
  #
14
- # @example
15
- # converter = Converter.new
16
- # converter.parse(path/to/file.igc)
17
- # converter.compile
18
- # converter.export(output/dir)
19
- class Converter
27
+ # @param [String] file The path to the IGC file
28
+ # @param [String] encoding The encoding of the input file
29
+ # @raise [Fdc::FileReadError] If file could not be loaded
30
+ # @raise [Fdc::FileFormatError] If the file format is invalid
31
+ def parse(file, encoding="ISO-8859-1")
20
32
 
21
- # The compiled KML document
22
- attr_accessor :kml
33
+ load(file, encoding)
34
+ parse_file
23
35
 
24
- # Load and parse an IGC file from the supplied path.
25
- #
26
- # @param [String] file The path to the IGC file
27
- # @param [String] encoding The encoding of the input file
28
- # @raise [Fdc::FileReadError] If file could not be loaded
29
- # @raise [Fdc::FileFormatError] If the file format is invalid
30
- def parse(file, encoding="ISO-8859-1")
31
-
32
- # Update state
33
- @path = Pathname.new(file)
34
- @encoding = encoding
35
-
36
- # Do work
37
- load_file
38
- parse_file
39
-
40
- end
36
+ end
37
+
38
+ # Compile the KML document from the parsed IGC file.
39
+ #
40
+ # @param [Boolean] clamp Whether the track should be clamped to the ground
41
+ # @param [Boolean] extrude Whether the track should be extruded to the ground
42
+ # @param [Boolean] gps Whether GPS altitude information should be used
43
+ # @raise [RuntimeError] If {#parse} was not called before
44
+ def compile(clamp=false, extrude=false, gps=false)
41
45
 
42
- # Compile the KML document from the parsed IGC file.
43
- #
44
- # @param [Boolean] clamp Whether the track should be clamped to the ground
45
- # @param [Boolean] extrude Whether the track should be extruded to the ground
46
- # @param [Boolean] gps Whether GPS altitude information should be used
47
- # @raise [RuntimeError] If {#parse} was not called before
48
- def compile(clamp=false, extrude=false, gps=false)
49
-
50
- # State assertion
51
- raise RuntimeError, "Cannot compile before successfull parse" if @igc.nil? or @date.nil?
52
-
53
- # Build HTML for balloon description
54
- html = Builder::XmlMarkup.new(:indent => 2)
55
- html.div :style => "width: 250;" do
56
- html.p do
57
- unless @a_records[3].nil? then
58
- html.strong "Device:"
59
- html.dfn @a_records[3].strip
46
+ # State assertion
47
+ raise RuntimeError, "Cannot compile before successfull parse" if @file.nil? or @date.nil?
48
+
49
+ # Build HTML for balloon description
50
+ html = Builder::XmlMarkup.new(:indent => 2)
51
+ html.div :style => "width: 250;" do
52
+ html.p do
53
+ unless @a_records[3].nil? then
54
+ html.strong "Device:"
55
+ html.dfn @a_records[3].strip
56
+ html.br
57
+ end
58
+ end
59
+ html.p do
60
+ @h_records.each do |h|
61
+ if h.include? "PLT" and not h[2].strip.empty? then
62
+ html.strong "Pilot:"
63
+ html.dfn h[2].strip
64
+ html.br
65
+ end
66
+ if h.include? "CID" and not h[2].strip.empty? then
67
+ html.strong "Competition ID:"
68
+ html.dfn h[2].strip
69
+ html.br
70
+ end
71
+ if h.include? "GTY" and not h[2].strip.empty? then
72
+ html.strong "Glider:"
73
+ html.dfn h[2].strip
74
+ html.br
75
+ end
76
+ if h.include? "GID" and not h[2].strip.empty? then
77
+ html.strong "Glider ID:"
78
+ html.dfn h[2].strip
79
+ html.br
80
+ end
81
+ if h.include? "CCL" and not h[2].strip.empty? then
82
+ html.strong "Competition class:"
83
+ html.dfn h[2].strip
60
84
  html.br
61
85
  end
62
- end
63
- html.p do
64
- @h_records.each do |h|
65
- if h.include? "PLT" and not h[2].strip.empty? then
66
- html.strong "Pilot:"
67
- html.dfn h[2].strip
68
- html.br
69
- end
70
- if h.include? "CID" and not h[2].strip.empty? then
71
- html.strong "Competition ID:"
72
- html.dfn h[2].strip
73
- html.br
74
- end
75
- if h.include? "GTY" and not h[2].strip.empty? then
76
- html.strong "Glider:"
77
- html.dfn h[2].strip
78
- html.br
79
- end
80
- if h.include? "GID" and not h[2].strip.empty? then
81
- html.strong "Glider ID:"
82
- html.dfn h[2].strip
83
- html.br
84
- end
85
- if h.include? "CCL" and not h[2].strip.empty? then
86
- html.strong "Competition class:"
87
- html.dfn h[2].strip
88
- html.br
89
- end
90
- if h.include? "SIT" and not h[2].strip.empty? then
91
- html.strong "Site:"
92
- html.dfn h[2].strip
93
- html.br
94
- end
86
+ if h.include? "SIT" and not h[2].strip.empty? then
87
+ html.strong "Site:"
88
+ html.dfn h[2].strip
89
+ html.br
95
90
  end
96
-
97
- html.strong "Date:"
98
- html.dfn @date[3..5].join(".")
99
- html.br
100
91
  end
101
92
 
102
- # Manufacturer-dependent L records
103
- case @a_records[1]
104
- when "XSX"
105
- @l_records.each do |l|
106
- if matches = l[1].scan(/(\w*):(-?\d+.?\d+)/) then
107
- html.p do
108
- matches.each do |match|
109
- case match[0]
110
- when "MC"
111
- html.strong "Max. climb:"
112
- html.dfn match[1] << " m/s"
113
- html.br
114
- when "MS"
115
- html.strong "Max. sink:"
116
- html.dfn match[1] << " m/s"
117
- html.br
118
- when "MSP"
119
- html.strong "Max. speed:"
120
- html.dfn match[1] << " km/h"
121
- html.br
122
- when "Dist"
123
- html.strong "Track distance:"
124
- html.dfn match[1] << " km"
125
- html.br
126
- end
93
+ html.strong "Date:"
94
+ html.dfn @date[3..5].join(".")
95
+ html.br
96
+ end
97
+
98
+ # Manufacturer-dependent L records
99
+ case @a_records[1]
100
+ when "XSX"
101
+ @l_records.each do |l|
102
+ if matches = l[1].scan(/(\w*):(-?\d+.?\d+)/) then
103
+ html.p do
104
+ matches.each do |match|
105
+ case match[0]
106
+ when "MC"
107
+ html.strong "Max. climb:"
108
+ html.dfn match[1] << " m/s"
109
+ html.br
110
+ when "MS"
111
+ html.strong "Max. sink:"
112
+ html.dfn match[1] << " m/s"
113
+ html.br
114
+ when "MSP"
115
+ html.strong "Max. speed:"
116
+ html.dfn match[1] << " km/h"
117
+ html.br
118
+ when "Dist"
119
+ html.strong "Track distance:"
120
+ html.dfn match[1] << " km"
121
+ html.br
127
122
  end
128
123
  end
129
124
  end
130
125
  end
131
126
  end
132
-
133
127
  end
134
128
 
135
- # Build KML
136
- xml = Builder::XmlMarkup.new(:indent => 2)
137
- xml.instruct!
138
- xml.kml "xmlns" => "http://www.opengis.net/kml/2.2", "xmlns:gx" => "http://www.google.com/kml/ext/2.2" do
139
- xml.Placemark {
140
- xml.name @path.basename(@path.extname)
141
- xml.Snippet :maxLines => "2" do
142
- xml.text! snippet
129
+ end
130
+
131
+ # Build KML
132
+ xml = Builder::XmlMarkup.new(:indent => 2)
133
+ xml.instruct!
134
+ xml.kml "xmlns" => "http://www.opengis.net/kml/2.2", "xmlns:gx" => "http://www.google.com/kml/ext/2.2" do
135
+ xml.Placemark {
136
+ xml.name @path.basename(@path.extname)
137
+ xml.Snippet :maxLines => "2" do
138
+ xml.text! snippet
139
+ end
140
+ xml.description do
141
+ xml.cdata! html.target!
142
+ end
143
+ xml.Style do
144
+ xml.IconStyle do
145
+ xml.Icon do
146
+ xml.href "http://earth.google.com/images/kml-icons/track-directional/track-0.png"
147
+ end
143
148
  end
144
- xml.description do
145
- xml.cdata! html.target!
149
+ xml.LineStyle do
150
+ xml.color "99ffac59"
151
+ xml.width "4"
146
152
  end
147
- xml.Style do
148
- xml.IconStyle do
149
- xml.Icon do
150
- xml.href "http://earth.google.com/images/kml-icons/track-directional/track-0.png"
151
- end
152
- end
153
- xml.LineStyle do
154
- xml.color "99ffac59"
155
- xml.width "4"
156
- end
153
+ end
154
+ xml.gx:Track do
155
+
156
+ clamp ? xml.altitudeMode("clampToGround") : xml.altitudeMode("absolute")
157
+ extrude ? xml.extrude("1") : xml.extrude("0")
158
+
159
+ @b_records.each do |b_record|
160
+ time = DateTime.new(2000 + @date[5].to_i, @date[4].to_i, @date[3].to_i,
161
+ b_record[1].to_i, b_record[2].to_i, b_record[3].to_i)
162
+ xml.when time
157
163
  end
158
- xml.gx:Track do
159
-
160
- clamp ? xml.altitudeMode("clampToGround") : xml.altitudeMode("absolute")
161
- extrude ? xml.extrude("1") : xml.extrude("0")
162
-
163
- @b_records.each do |b_record|
164
- time = DateTime.new(2000 + @date[5].to_i, @date[4].to_i, @date[3].to_i,
165
- b_record[1].to_i, b_record[2].to_i, b_record[3].to_i)
166
- xml.when time
167
- end
168
- @b_records.each do |b_record|
169
- coords = Fdc::GeoLocation.to_dec(b_record[5], b_record[4])
170
- gps ? coords << b_record[8].to_f : coords << b_record[7].to_f
171
- xml.gx :coord, coords.join(" ")
172
- end
164
+ @b_records.each do |b_record|
165
+ coords = Fdc::GeoLocation.to_dec(b_record[5], b_record[4])
166
+ gps ? coords << b_record[8].to_f : coords << b_record[7].to_f
167
+ xml.gx :coord, coords.join(" ")
173
168
  end
174
- }
175
- end
176
-
177
- @kml = xml.target!
169
+ end
170
+ }
178
171
  end
179
172
 
180
- # Export the compiled KML document
181
- #
182
- # @param [String] dir The alternative output directory.
183
- # If nothing is supplied the files are written to the same location
184
- # as the IGC input file.
185
- # @raise [RuntimeError] If {#parse} and {#compile} were not called before
186
- # @raise [Fdc::FileWriteError] If dirname is not a directory or write protected
187
- def export(dir = nil)
188
-
189
- # Assert state
190
- raise RuntimeError, "Cannot export before compile was called" unless @kml
191
-
192
- dir = @path.dirname.to_s unless dir
193
-
194
- # Create Pathname for easier handling
195
- dest = Pathname.new(dir)
196
-
197
- # Create output file name
198
- dest += @path.basename(@path.extname)
199
-
200
- begin
201
- file = File.new(dest.to_s << ".kml", "w:UTF-8")
202
- rescue Errno::EACCES => e
203
- raise Fdc::FileWriteError, "Destination is write-protected: #{dir.to_s}"
204
- rescue Errno::ENOTDIR => e
205
- raise Fdc::FileWriteError, "Destination is not a directory: #{dir.to_s}"
206
- rescue Errno::ENOENT => e
207
- raise Fdc::FileWriteError, "Destination does not exist: #{dir.to_s}"
208
- end
209
-
210
- file.write(@kml)
211
- file.close
212
-
173
+ @kml = xml.target!
174
+ end
175
+
176
+ # Export the compiled KML document
177
+ #
178
+ # @param [String] dir The alternative output directory.
179
+ # If nothing is supplied the files are written to the same location
180
+ # as the IGC input file.
181
+ # @raise [RuntimeError] If {#parse} and {#compile} were not called before
182
+ # @raise [Fdc::FileWriteError] If dirname is not a directory or write protected
183
+ def export(dir = nil)
184
+
185
+ # Assert state
186
+ raise RuntimeError, "Cannot export before compile was called" unless @kml
187
+
188
+ dir = @path.dirname.to_s unless dir
189
+
190
+ # Create Pathname for easier handling
191
+ dest = Pathname.new(dir)
192
+
193
+ # Create output file name
194
+ dest += @path.basename(@path.extname)
195
+
196
+ begin
197
+ file = File.new("#{dest.to_s}.kml", "w:UTF-8")
198
+ rescue Errno::EACCES => e
199
+ raise Fdc::FileWriteError, "Destination is write-protected: #{dir.to_s}"
200
+ rescue Errno::ENOTDIR => e
201
+ raise Fdc::FileWriteError, "Destination is not a directory: #{dir.to_s}"
202
+ rescue Errno::ENOENT => e
203
+ raise Fdc::FileWriteError, "Destination does not exist: #{dir.to_s}"
213
204
  end
214
205
 
215
- private
206
+ file.write(@kml)
207
+ file.close
216
208
 
217
- # Load igc file from supplied path
218
- def load_file
219
-
220
- raise Fdc::FileReadError, "Invalid file extension: #{@path.to_s}" unless @path.extname == ".igc"
209
+ end
221
210
 
222
- # Load file
223
- begin
224
- file = File.new(@path, "r", :encoding => @encoding)
225
- rescue Errno::EISDIR => e
226
- raise Fdc::FileReadError, "Input file is a directory: #{@path.to_s}"
227
- rescue Errno::ENOENT => e
228
- raise Fdc::FileReadError, "Input file does not exist: #{@path.to_s}"
229
- end
211
+ private
230
212
 
231
- @igc = file.read
232
- file.close
213
+ # Regular expressions for file parsing
214
+ REGEX_A = /^[a]([a-z\d]{3})([a-z\d]{3})?(.*)$/i
215
+ REGEX_H = /^[h][f|o|p]([\w]{3})(.*):(.*)$/i
216
+ REGEX_H_DTE = /^hf(dte)((\d{2})(\d{2})(\d{2}))/i
217
+ REGEX_B = /^(B)(\d{2})(\d{2})(\d{2})(\d{7}[NS])(\d{8}[EW])([AV])(\d{5})(\d{5})/
218
+ REGEX_L = /^l([a-z0-9]{3}|[plt]|[pfc])(.*)/i
233
219
 
234
- end
220
+ # Parse igc file content
221
+ def parse_file
235
222
 
236
- # Regular expressions for file parsing
237
- REGEX_A = /^[a]([a-z\d]{3})([a-z\d]{3})?(.*)$/i
238
- REGEX_H = /^[h][f|o|p]([\w]{3})(.*):(.*)$/i
239
- REGEX_H_DTE = /^hf(dte)((\d{2})(\d{2})(\d{2}))/i
240
- REGEX_B = /^(B)(\d{2})(\d{2})(\d{2})(\d{7}[NS])(\d{8}[EW])([AV])(\d{5})(\d{5})/
241
- REGEX_L = /^l([a-z0-9]{3}|[plt]|[pfc])(.*)/i
223
+ begin
242
224
 
243
- # Parse igc file content
244
- def parse_file
245
-
246
- begin
247
-
248
- # parse utc date
249
- @date = @igc.match(REGEX_H_DTE)
250
- raise Fdc::FileFormatError, "Invalid file format - header date is missing: #{@path.to_s}" unless @date
251
-
252
- # parse a records
253
- @a_records = @igc.match(REGEX_A)
254
- raise Fdc::FileFormatError, "Invalid file format: #{@path.to_s}" unless @a_records
255
-
256
- # parse h records
257
- @h_records = @igc.scan(REGEX_H)
258
-
259
- # parse b records
260
- @b_records = @igc.scan(REGEX_B)
261
-
262
- # parse l records
263
- @l_records = @igc.scan(REGEX_L)
264
-
265
- rescue ArgumentError => e
266
- raise Fdc::FileFormatError, "Wrong file encoding: #{e.message}"
267
- end
268
-
225
+ # parse utc date
226
+ @date = @file.match(REGEX_H_DTE)
227
+ raise Fdc::FileFormatError, "Invalid file format - header date is missing: #{@path.to_s}" unless @date
228
+
229
+ # parse a records
230
+ @a_records = @file.match(REGEX_A)
231
+ raise Fdc::FileFormatError, "Invalid file format: #{@path.to_s}" unless @a_records
232
+
233
+ # parse h records
234
+ @h_records = @file.scan(REGEX_H)
235
+
236
+ # parse b records
237
+ @b_records = @file.scan(REGEX_B)
238
+
239
+ # parse l records
240
+ @l_records = @file.scan(REGEX_L)
241
+
242
+ rescue ArgumentError => e
243
+ raise Fdc::FileFormatError, "Wrong file encoding: #{e.message}"
269
244
  end
270
245
 
271
- # Generate Snippet tag content
272
- def snippet
273
- summary = "Flight"
274
- @h_records.each do |h|
275
- if h.include? "SIT" and not h[2].strip.empty? then
276
- summary << " from #{h[2].strip}"
277
- end
246
+ end
247
+
248
+ # Generate Snippet tag content
249
+ def snippet
250
+ summary = "Flight"
251
+ @h_records.each do |h|
252
+ if h.include? "SIT" and not h[2].strip.empty? then
253
+ summary << " from #{h[2].strip}"
278
254
  end
279
- summary << " on #{@date[3..5].join(".")}"
280
255
  end
281
-
256
+ summary << " on #{@date[3..5].join(".")}"
282
257
  end
283
-
258
+
284
259
  end
@@ -1,7 +1,10 @@
1
- # Utility classes used by {IGCConverter}
1
+ require 'pathname'
2
+ require 'fdc/exceptions'
3
+
4
+ # Utility modules used by {Fdc::Converter}
2
5
  module Fdc
3
6
 
4
- # Module with helper functions for geocoordinate conversion
7
+ # Helper functions for geocoordinate conversion
5
8
  module GeoLocation
6
9
 
7
10
  # Convert geocoordinates from mindec notation of IGC to dec notation
@@ -28,5 +31,49 @@ module Fdc
28
31
  end
29
32
 
30
33
  end
34
+
35
+ # Module for file loading
36
+ module FileLoader
37
+
38
+ # Loaded file content
39
+ attr_reader :file
40
+
41
+ # Loaded file path
42
+ attr_reader :path
43
+
44
+ # Loaded file encoding
45
+ attr_reader :encoding
46
+
47
+ private
48
+
49
+ # Load a file from the supplied path
50
+ #
51
+ # @param [String] file The path to the file
52
+ # @param [String] encoding The encoding of the file
53
+ # @raise [Fdc::FileReadError] If file could not be loaded
54
+ def load(file, encoding)
55
+
56
+ # The path of the file
57
+ @path = Pathname.new(file)
58
+
59
+ # The file encoding
60
+ @encoding = encoding
61
+
62
+ raise Fdc::FileReadError, "Invalid file extension: #{@path.to_s}" unless @path.extname == ".igc"
63
+
64
+ # Load file
65
+ begin
66
+ f = File.new(@path, "r", :encoding => @encoding)
67
+ rescue Errno::EISDIR => e
68
+ raise Fdc::FileReadError, "Input file is a directory: #{@path.to_s}"
69
+ rescue Errno::ENOENT => e
70
+ raise Fdc::FileReadError, "Input file does not exist: #{@path.to_s}"
71
+ end
72
+ # The loaded file
73
+ @file = f.read
74
+ f.close
75
+ end
76
+
77
+ end
31
78
 
32
79
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fdc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-20 00:00:00.000000000 Z
12
+ date: 2012-06-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: builder
@@ -81,3 +81,4 @@ signing_key:
81
81
  specification_version: 3
82
82
  summary: Convert flight data format files (IGC) to keyhole markup language (KML)
83
83
  test_files: []
84
+ has_rdoc: