fdc 0.0.2 → 0.0.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.
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: