fdc 0.0.3 → 0.0.4

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/lib/fdc.rb CHANGED
@@ -1,11 +1,6 @@
1
- # Ruby core and stdlib
2
- require 'date'
3
- require 'pathname'
4
-
5
- # Gems
6
- require 'builder'
7
-
8
1
  # Fdc
9
2
  require 'fdc/utilities'
10
3
  require 'fdc/converter'
11
- require 'fdc/exceptions'
4
+ require 'fdc/exceptions'
5
+ require 'fdc/parser'
6
+ require 'fdc/compiler'
@@ -0,0 +1,170 @@
1
+ require 'builder'
2
+ require 'date'
3
+ require 'fdc/parser'
4
+
5
+ # Compile KML documents from a {Fdc::Parser}
6
+ class Fdc::Compiler
7
+
8
+ # The compiled KML document
9
+ attr_reader :kml
10
+ attr_accessor :parser
11
+
12
+ # Create a new instance
13
+ #
14
+ # @param [Fdc::Parser] parser A parser from which the KML document should be compiled.
15
+ def initialize(parser)
16
+ @parser = parser
17
+ end
18
+
19
+ # Compile the KML document from the parsed IGC file.
20
+ #
21
+ # @param [String] track_name The name of the KML track
22
+ # @param [Boolean] clamp Whether the track should be clamped to the ground
23
+ # @param [Boolean] extrude Whether the track should be extruded to the ground
24
+ # @param [Boolean] gps Whether GPS altitude information should be used
25
+ # @raise [RuntimeError] If the supplied parser is not ready
26
+ def compile(track_name, clamp=false, extrude=false, gps=false)
27
+
28
+ raise RuntimeError, "Parser not ready" unless @parser.ready?
29
+
30
+ # Build HTML for balloon description
31
+ html = Builder::XmlMarkup.new(:indent => 2)
32
+ html.div :style => "width: 250;" do
33
+ html.p do
34
+ unless @parser.a_record[3].nil? then
35
+ html.strong "Device:"
36
+ html.dfn @parser.a_record[3].strip
37
+ html.br
38
+ end
39
+ end
40
+ html.p do
41
+ @parser.h_records.each do |h|
42
+ if h.include? "PLT" and not h[2].strip.empty? then
43
+ html.strong "Pilot:"
44
+ html.dfn h[2].strip
45
+ html.br
46
+ end
47
+ if h.include? "CID" and not h[2].strip.empty? then
48
+ html.strong "Competition ID:"
49
+ html.dfn h[2].strip
50
+ html.br
51
+ end
52
+ if h.include? "GTY" and not h[2].strip.empty? then
53
+ html.strong "Glider:"
54
+ html.dfn h[2].strip
55
+ html.br
56
+ end
57
+ if h.include? "GID" and not h[2].strip.empty? then
58
+ html.strong "Glider ID:"
59
+ html.dfn h[2].strip
60
+ html.br
61
+ end
62
+ if h.include? "CCL" and not h[2].strip.empty? then
63
+ html.strong "Competition class:"
64
+ html.dfn h[2].strip
65
+ html.br
66
+ end
67
+ if h.include? "SIT" and not h[2].strip.empty? then
68
+ html.strong "Site:"
69
+ html.dfn h[2].strip
70
+ html.br
71
+ end
72
+ end
73
+
74
+ html.strong "Date:"
75
+ html.dfn @parser.date_record[3..5].join(".")
76
+ html.br
77
+ end
78
+
79
+ # Manufacturer-dependent L records
80
+ case @parser.a_record[1]
81
+ when "XSX"
82
+ @parser.l_records.each do |l|
83
+ if matches = l[1].scan(/(\w*):(-?\d+.?\d+)/) then
84
+ html.p do
85
+ matches.each do |match|
86
+ case match[0]
87
+ when "MC"
88
+ html.strong "Max. climb:"
89
+ html.dfn match[1] << " m/s"
90
+ html.br
91
+ when "MS"
92
+ html.strong "Max. sink:"
93
+ html.dfn match[1] << " m/s"
94
+ html.br
95
+ when "MSP"
96
+ html.strong "Max. speed:"
97
+ html.dfn match[1] << " km/h"
98
+ html.br
99
+ when "Dist"
100
+ html.strong "Track distance:"
101
+ html.dfn match[1] << " km"
102
+ html.br
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ # Build KML
113
+ xml = Builder::XmlMarkup.new(:indent => 2)
114
+ xml.instruct!
115
+ xml.kml "xmlns" => "http://www.opengis.net/kml/2.2", "xmlns:gx" => "http://www.google.com/kml/ext/2.2" do
116
+ xml.Placemark {
117
+ xml.name track_name
118
+ xml.Snippet :maxLines => "2" do
119
+ xml.text! snippet
120
+ end
121
+ xml.description do
122
+ xml.cdata! html.target!
123
+ end
124
+ xml.Style do
125
+ xml.IconStyle do
126
+ xml.Icon do
127
+ xml.href "http://earth.google.com/images/kml-icons/track-directional/track-0.png"
128
+ end
129
+ end
130
+ xml.LineStyle do
131
+ xml.color "99ffac59"
132
+ xml.width "4"
133
+ end
134
+ end
135
+ xml.gx:Track do
136
+
137
+ clamp ? xml.altitudeMode("clampToGround") : xml.altitudeMode("absolute")
138
+ extrude ? xml.extrude("1") : xml.extrude("0")
139
+
140
+ @parser.b_records.each do |b_record|
141
+ time = DateTime.new(2000 + @parser.date_record[5].to_i, @parser.date_record[4].to_i, @parser.date_record[3].to_i,
142
+ b_record[1].to_i, b_record[2].to_i, b_record[3].to_i)
143
+ xml.when time
144
+ end
145
+ @parser.b_records.each do |b_record|
146
+ coords = Fdc::GeoLocation.to_dec(b_record[5], b_record[4])
147
+ gps ? coords << b_record[8].to_f : coords << b_record[7].to_f
148
+ xml.gx :coord, coords.join(" ")
149
+ end
150
+ end
151
+ }
152
+ end
153
+
154
+ @kml = xml.target!
155
+ end
156
+
157
+ private
158
+
159
+ # Generate Snippet tag content
160
+ def snippet
161
+ summary = "Flight"
162
+ @parser.h_records.each do |h|
163
+ if h.include? "SIT" and not h[2].strip.empty? then
164
+ summary << " from #{h[2].strip}"
165
+ end
166
+ end
167
+ summary << " on #{@parser.date_record[3..5].join(".")}"
168
+ end
169
+
170
+ end
@@ -1,10 +1,11 @@
1
- require 'date'
2
1
  require 'pathname'
3
- require 'builder'
2
+
4
3
  require 'fdc/utilities'
5
4
  require 'fdc/exceptions'
5
+ require 'fdc/parser'
6
+ require 'fdc/compiler'
6
7
 
7
- # Class to convert IGC files to KML.
8
+ # Convert IGC Files to KML
8
9
  #
9
10
  # @!attribute [r] kml
10
11
  # @return [String] The KML document
@@ -16,11 +17,14 @@ require 'fdc/exceptions'
16
17
  # converter.export(output/dir)
17
18
  class Fdc::Converter
18
19
 
19
- # FileLoader mixing
20
+ # Mixins
20
21
  include Fdc::FileLoader
21
-
22
- # The compiled KML document
23
- attr_reader :kml
22
+ include Fdc::FileWriter
23
+
24
+ def initialize
25
+ @parser = Fdc::Parser.new
26
+ @compiler = Fdc::Compiler.new @parser
27
+ end
24
28
 
25
29
  # Load and parse an IGC file from the supplied path.
26
30
  #
@@ -29,9 +33,11 @@ class Fdc::Converter
29
33
  # @raise [Fdc::FileReadError] If file could not be loaded
30
34
  # @raise [Fdc::FileFormatError] If the file format is invalid
31
35
  def parse(file, encoding="ISO-8859-1")
32
-
33
- load(file, encoding)
34
- parse_file
36
+
37
+ path = Pathname.new(file)
38
+ load(path, encoding)
39
+ raise Fdc::FileReadError, "Invalid file extension: #{path.to_s}" unless path.extname == ".igc"
40
+ @parser.parse @file
35
41
 
36
42
  end
37
43
 
@@ -44,133 +50,11 @@ class Fdc::Converter
44
50
  def compile(clamp=false, extrude=false, gps=false)
45
51
 
46
52
  # 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
84
- html.br
85
- 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
90
- end
91
- end
92
-
93
- html.strong "Date:"
94
- html.dfn @date[3..5].join(".")
95
- html.br
96
- end
53
+ raise RuntimeError, "Cannot compile without preceding parse" unless @parser.ready?
97
54
 
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
122
- end
123
- end
124
- end
125
- end
126
- end
127
- end
128
-
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
148
- end
149
- xml.LineStyle do
150
- xml.color "99ffac59"
151
- xml.width "4"
152
- 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
163
- 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(" ")
168
- end
169
- end
170
- }
171
- end
55
+ name = @path.basename(@path.extname)
56
+ @compiler.compile name, clamp, extrude, gps
172
57
 
173
- @kml = xml.target!
174
58
  end
175
59
 
176
60
  # Export the compiled KML document
@@ -183,77 +67,17 @@ class Fdc::Converter
183
67
  def export(dir = nil)
184
68
 
185
69
  # Assert state
186
- raise RuntimeError, "Cannot export before compile was called" unless @kml
70
+ raise RuntimeError, "Cannot export before compile was called" unless @compiler.kml
187
71
 
188
72
  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}"
204
- end
205
-
206
- file.write(@kml)
207
- file.close
73
+ write Pathname.new(dir) + (@path.basename(@path.extname).to_s << ".kml"), @compiler.kml
208
74
 
209
75
  end
210
-
211
- private
212
-
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
219
-
220
- # Parse igc file content
221
- def parse_file
222
-
223
- begin
224
-
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
76
 
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}"
244
- end
245
-
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}"
254
- end
255
- end
256
- summary << " on #{@date[3..5].join(".")}"
77
+ # Get the compiled KML document
78
+ # @return [String] The compiled KML document
79
+ def kml
80
+ @compiler.kml
257
81
  end
258
82
 
259
83
  end
@@ -0,0 +1,65 @@
1
+ # Parse record types from IGC file data
2
+ class Fdc::Parser
3
+
4
+ attr_reader :date_record
5
+ attr_reader :a_record
6
+ attr_reader :h_records
7
+ attr_reader :b_records
8
+ attr_reader :l_records
9
+
10
+ def initialize
11
+ @ready = false
12
+ end
13
+
14
+ # Parse the supplied IGC file content
15
+ #
16
+ # @param [String] igc_file The IGC file content
17
+ # @raise [Fdc::FileFormatError] If the file format is invalid
18
+ def parse(igc_file)
19
+
20
+ begin
21
+ # parse utc date
22
+ unless @date_record = igc_file.match(REGEX_H_DTE) then
23
+ @ready = false
24
+ raise Fdc::FileFormatError, "Invalid file format - header date is missing"
25
+ end
26
+
27
+ # parse a records
28
+ unless @a_record = igc_file.match(REGEX_A) then
29
+ @ready = false
30
+ raise Fdc::FileFormatError, "Invalid file format" unless @a_record
31
+ end
32
+
33
+ # parse h records
34
+ @h_records = igc_file.scan(REGEX_H)
35
+
36
+ # parse b records
37
+ @b_records = igc_file.scan(REGEX_B)
38
+
39
+ # parse l records
40
+ @l_records = igc_file.scan(REGEX_L)
41
+
42
+ rescue ArgumentError => e
43
+ @ready = false
44
+ raise Fdc::FileFormatError, "Wrong file encoding: #{e.message}"
45
+ end
46
+
47
+ @ready = true
48
+
49
+ end
50
+
51
+ # @return [Boolean] true if {#parse} was successfully called
52
+ def ready?
53
+ return @ready
54
+ end
55
+
56
+ private
57
+
58
+ # Regular expressions for parsing igc records
59
+ REGEX_A = /^[a]([a-z\d]{3})([a-z\d]{3})?(.*)$/i
60
+ REGEX_H = /^[h][f|o|p]([\w]{3})(.*):(.*)$/i
61
+ REGEX_H_DTE = /^hf(dte)((\d{2})(\d{2})(\d{2}))/i
62
+ REGEX_B = /^(B)(\d{2})(\d{2})(\d{2})(\d{7}[NS])(\d{8}[EW])([AV])(\d{5})(\d{5})/
63
+ REGEX_L = /^l([a-z0-9]{3}|[plt]|[pfc])(.*)/i
64
+
65
+ end
@@ -32,7 +32,7 @@ module Fdc
32
32
 
33
33
  end
34
34
 
35
- # Module for file loading
35
+ # Common file loading operations
36
36
  module FileLoader
37
37
 
38
38
  # Loaded file content
@@ -48,32 +48,51 @@ module Fdc
48
48
 
49
49
  # Load a file from the supplied path
50
50
  #
51
- # @param [String] file The path to the file
51
+ # @param [Pathname] path The path to the file
52
52
  # @param [String] encoding The encoding of the file
53
53
  # @raise [Fdc::FileReadError] If file could not be loaded
54
- def load(file, encoding)
54
+ def load(path, encoding)
55
55
 
56
- # The path of the file
57
- @path = Pathname.new(file)
58
-
59
- # The file encoding
56
+ @path = path
60
57
  @encoding = encoding
61
-
62
- raise Fdc::FileReadError, "Invalid file extension: #{@path.to_s}" unless @path.extname == ".igc"
63
58
 
64
- # Load file
65
59
  begin
66
60
  f = File.new(@path, "r", :encoding => @encoding)
61
+ @file = f.read
67
62
  rescue Errno::EISDIR => e
68
63
  raise Fdc::FileReadError, "Input file is a directory: #{@path.to_s}"
69
64
  rescue Errno::ENOENT => e
70
65
  raise Fdc::FileReadError, "Input file does not exist: #{@path.to_s}"
66
+ ensure
67
+ if f then f.close end
71
68
  end
72
- # The loaded file
73
- @file = f.read
74
- f.close
75
69
  end
76
70
 
77
71
  end
72
+
73
+ # Common file writing operations
74
+ module FileWriter
75
+
76
+ private
77
+ # Write a file to the file system
78
+ #
79
+ # @param [Pathname] path The path for the file
80
+ # @param [String] data The data of the file
81
+ # @raise [Fdc::FileWriteError] If dirname is not a directory or write protected
82
+ def write(path, data)
83
+ begin
84
+ file = File.new(path, "w:UTF-8")
85
+ file.write(data)
86
+ rescue Errno::EACCES => e
87
+ raise Fdc::FileWriteError, "Destination is write-protected: #{path.to_s}"
88
+ rescue Errno::ENOTDIR => e
89
+ raise Fdc::FileWriteError, "Destination is not a directory: #{path.to_s}"
90
+ rescue Errno::ENOENT => e
91
+ raise Fdc::FileWriteError, "Destination does not exist: #{path.to_s}"
92
+ ensure
93
+ if file then file.close end
94
+ end
95
+ end
96
+ end
78
97
 
79
98
  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.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -51,8 +51,10 @@ executables:
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - lib/fdc/compiler.rb
54
55
  - lib/fdc/converter.rb
55
56
  - lib/fdc/exceptions.rb
57
+ - lib/fdc/parser.rb
56
58
  - lib/fdc/utilities.rb
57
59
  - lib/fdc.rb
58
60
  - bin/fdc