rozi 0.0.7 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +255 -0
  3. data/lib/rozi.rb +6 -3
  4. data/lib/rozi/file_wrapper_base.rb +52 -0
  5. data/lib/rozi/module_functions.rb +19 -69
  6. data/lib/rozi/name_search.rb +176 -0
  7. data/lib/rozi/{ozi_functions.rb → shared.rb} +36 -9
  8. data/lib/rozi/tracks.rb +170 -0
  9. data/lib/rozi/version.rb +1 -1
  10. data/lib/rozi/waypoints.rb +315 -0
  11. data/test/rozi/file_wrapper_base_test.rb +29 -0
  12. data/test/rozi/module_functions_test.rb +11 -72
  13. data/test/rozi/name_search_test.rb +248 -0
  14. data/test/rozi/{ozi_functions_test.rb → shared_test.rb} +25 -5
  15. data/test/rozi/tracks_test.rb +105 -0
  16. data/test/rozi/waypoints_test.rb +268 -0
  17. data/test/test_data/expected_output_1.nst +6 -0
  18. data/{test_data → test/test_data}/expected_output_1.plt +9 -9
  19. data/{test_data → test/test_data}/expected_output_1.wpt +7 -7
  20. data/test/test_data/input_1.wpt +6 -0
  21. metadata +128 -34
  22. data/README.rdoc +0 -127
  23. data/lib/rozi/name.rb +0 -23
  24. data/lib/rozi/name_search_text.rb +0 -43
  25. data/lib/rozi/name_search_text_writer.rb +0 -71
  26. data/lib/rozi/track.rb +0 -67
  27. data/lib/rozi/track_point.rb +0 -39
  28. data/lib/rozi/track_writer.rb +0 -50
  29. data/lib/rozi/waypoint.rb +0 -90
  30. data/lib/rozi/waypoint_writer.rb +0 -38
  31. data/test/rozi/name_search_text_test.rb +0 -28
  32. data/test/rozi/name_search_text_writer_test.rb +0 -114
  33. data/test/rozi/track_point_test.rb +0 -32
  34. data/test/rozi/track_test.rb +0 -25
  35. data/test/rozi/track_writer_test.rb +0 -62
  36. data/test/rozi/waypoint_test.rb +0 -31
  37. data/test/rozi/waypoint_writer_test.rb +0 -60
@@ -0,0 +1,176 @@
1
+
2
+ require "rozi/file_wrapper_base"
3
+ require "rozi/shared"
4
+
5
+ module Rozi
6
+ ##
7
+ # Writes an enumerable of names to a file
8
+ #
9
+ # All keyword arguments are used as track properties.
10
+ #
11
+ # @param [Enumerable] enumerable
12
+ # @param [String] file_path
13
+ #
14
+ def write_nst(enumerable, file_path, **properties)
15
+ NameSearchTextFile.open(file_path, "w") { |nst|
16
+ nst.write_properties NameSearchProperties.new(**properties)
17
+
18
+ enumerable.each { |name|
19
+ nst.write_name name
20
+ }
21
+ }
22
+
23
+ return nil
24
+ end
25
+
26
+ ##
27
+ # This class represents a name in an Ozi Explorer name database.
28
+ #
29
+ # @note The +name+, +latitude+ and +longitude+ fields must be set, or runtime errors will
30
+ # be raised when attempting to write to file.
31
+ #
32
+ class Name < DataStruct
33
+ PROPERTIES = [:name, :feature_code, :zone, :latitude, :longitude]
34
+ end
35
+
36
+ ##
37
+ # Represents the global properties of a name search text file
38
+ #
39
+ class NameSearchProperties < DataStruct
40
+ PROPERTIES = [
41
+ :comment, :datum, :latlng, :utm, :utm_zone, :hemisphere
42
+ ]
43
+
44
+ include Shared
45
+ include Shared::DatumSetter
46
+
47
+ def initialize(*args, **kwargs)
48
+ update(
49
+ comment: "",
50
+ datum: "WGS 84",
51
+ latlng: true,
52
+ utm: false,
53
+ utm_zone: nil,
54
+ hemisphere: nil
55
+ )
56
+
57
+ super
58
+ end
59
+ end
60
+
61
+ ##
62
+ # A thin layer above +File+ that handles reading and writing of names to name
63
+ # search text files
64
+ #
65
+ class NameSearchTextFile < FileWrapperBase
66
+ ##
67
+ # Writes an enumerable of {Rozi::Name} objects to the file
68
+ #
69
+ # @param [Enumerable] enumerable
70
+ # @return [nil]
71
+ #
72
+ def write(enumerable)
73
+ enumerable.each { |name|
74
+ write_name name
75
+ }
76
+
77
+ nil
78
+ end
79
+
80
+ ##
81
+ # Writes a {Rozi::Name} to the file
82
+ #
83
+ # @note If no properties have been written to the file before this method is
84
+ # called, a default set of properties will be automatically written to the
85
+ # file first
86
+ # @param [Rozi::Name] name
87
+ # @return [nil]
88
+ #
89
+ def write_name(name)
90
+ ensure_properties
91
+
92
+ @file.write serialize_name(name)
93
+ @file.write "\n"
94
+
95
+ nil
96
+ end
97
+
98
+ ##
99
+ # Writes name search properties to the file
100
+ #
101
+ # The file must be empty when this method is called!
102
+ #
103
+ # @raise [RuntimeError] if the file isn't empty
104
+ # @param [NameSearchProperties] properties
105
+ # @return [nil]
106
+ #
107
+ def write_properties(properties)
108
+ if @file.size > 0
109
+ raise "Can't write file properties, file is not empty"
110
+ end
111
+
112
+ @file.write serialize_properties(properties)
113
+ @file.write "\n"
114
+
115
+ nil
116
+ end
117
+
118
+ private
119
+
120
+ ##
121
+ # Ensures that properties have been written to the file
122
+ #
123
+ def ensure_properties
124
+ return if @properties_written
125
+
126
+ @properties_written = true
127
+
128
+ if @file.size == 0
129
+ write_properties NameSearchProperties.new
130
+ end
131
+ end
132
+
133
+ def serialize_name(name)
134
+ if not name.name or not name.latitude or not name.longitude
135
+ fail ArgumentError, "name, latitude and longitude must be set!"
136
+ end
137
+
138
+ feature_code = name.feature_code || ""
139
+
140
+ if name.name.include?(",") or feature_code.include?(",")
141
+ fail ArgumentError, "Text cannot contain commas"
142
+ end
143
+
144
+ "%s,%s,%s,%s,%s" % [
145
+ name.name, name.feature_code, name.zone,
146
+ name.latitude.round(6), name.longitude.round(6)
147
+ ]
148
+ end
149
+
150
+ def serialize_properties(properties)
151
+ out = ""
152
+
153
+ if properties.comment
154
+ properties.comment.each_line { |line|
155
+ out << ";#{line.chomp}\n"
156
+ }
157
+ end
158
+
159
+ out << "#1,"
160
+
161
+ if properties.utm
162
+ out << "UTM,#{properties.utm_zone}"
163
+
164
+ if properties.hemisphere
165
+ out << ",#{properties.hemisphere}"
166
+ end
167
+ else
168
+ out << ","
169
+ end
170
+
171
+ out << "\n#2,#{properties.datum}"
172
+
173
+ return out
174
+ end
175
+ end
176
+ end
@@ -1,24 +1,30 @@
1
1
 
2
2
  module Rozi
3
-
4
3
  ##
5
- # Contains general functions for working with Ozi Explorer file formats.
4
+ # Contains general functions for working with Ozi Explorer file formats
6
5
  #
7
- module OziFunctions
6
+ module Shared
8
7
  ##
9
- # Escapes commas so the text can be used in Ozi file formats.
8
+ # Escapes commas so the text can be used in Ozi file formats
10
9
  #
11
10
  # @param [String] text
12
11
  # @return [String]
13
12
  #
14
13
  def escape_text(text)
15
- text.gsub(/,/, 209.chr.encode("UTF-8", "ISO-8859-1"))
14
+ text.gsub(/,/, "Ñ")
16
15
  end
17
16
 
18
17
  ##
19
- # Converts the input to an RGB color represented by an integer.
18
+ # Undoes the effect of {Rozi::Shared#escape_text}
20
19
  #
21
- # @param [String, Integer] color Can be a RRGGBB hex string or an integer.
20
+ def unescape_text(text)
21
+ text.gsub(/Ñ/, ",")
22
+ end
23
+
24
+ ##
25
+ # Converts the input to an RGB color represented by an integer
26
+ #
27
+ # @param [String, Integer] color Can be a RRGGBB hex string or an integer
22
28
  # @return [Integer]
23
29
  #
24
30
  # @example
@@ -36,13 +42,34 @@ module Rozi
36
42
  end
37
43
 
38
44
  ##
39
- # Checks if +datum+ is a valid datum according to Ozi Explorer.
45
+ # Checks if +datum+ is a valid datum according to Ozi Explorer
40
46
  #
41
47
  # @return [Boolean] true if +datum+ is valid
42
48
  #
43
49
  def datum_valid?(datum)
44
50
  Rozi::DATUMS.include? datum
45
51
  end
46
- end
47
52
 
53
+ ##
54
+ # All data structures with a datum property should include this module
55
+ #
56
+ module DatumSetter
57
+ include Shared
58
+
59
+ ##
60
+ # Sets the datum property
61
+ #
62
+ # @param [String] datum
63
+ # @raise [ArgumentError] on invalid datum
64
+ # @return [void]
65
+ #
66
+ def datum=(datum)
67
+ if not datum_valid?(datum)
68
+ fail ArgumentError, "Invalid datum: #{datum}"
69
+ end
70
+
71
+ super datum
72
+ end
73
+ end
74
+ end
48
75
  end
@@ -0,0 +1,170 @@
1
+
2
+ require "rozi/file_wrapper_base"
3
+ require "rozi/shared"
4
+
5
+ module Rozi
6
+ ##
7
+ # Writes an enumerable of track points to a file
8
+ #
9
+ # All keyword arguments are used as track properties.
10
+ #
11
+ # @param [Enumerable] enumerable
12
+ # @param [String] file_path
13
+ #
14
+ def write_track(enumerable, file_path, **properties)
15
+ TrackFile.open(file_path, "w") { |track_file|
16
+ track_file.write_track_properties TrackProperties.new(**properties)
17
+ track_file.write enumerable
18
+ }
19
+ end
20
+
21
+ ##
22
+ # Represents a point in an Ozi Explorer track
23
+ #
24
+ class TrackPoint < DataStruct
25
+ PROPERTIES = [
26
+ :latitude, :longitude, :break, :altitude,
27
+ :date, :date_string, :time_string
28
+ ]
29
+
30
+ def initialize(*args, **kwargs)
31
+ update(
32
+ break: false,
33
+ altitude: -777,
34
+ date: 0,
35
+ date_string: "",
36
+ time_string: ""
37
+ )
38
+
39
+ super
40
+ end
41
+
42
+ def break
43
+ super == 1
44
+ end
45
+
46
+ def break=(brk)
47
+ super(brk ? 1 : 0)
48
+ end
49
+ end
50
+
51
+ ##
52
+ # Represents the track properties at the top of an Ozi Explorer track file
53
+ #
54
+ class TrackProperties < DataStruct
55
+ PROPERTIES = [
56
+ :datum, :line_width, :color, :description, :skip_value,
57
+ :type, :fill_style, :fill_color
58
+ ]
59
+
60
+ include Shared
61
+ include Shared::DatumSetter
62
+
63
+ def initialize(*args, **kwargs)
64
+ update(
65
+ datum: "WGS 84",
66
+ line_width: 2,
67
+ color: 255,
68
+ description: "",
69
+ skip_value: 1,
70
+ type: 0,
71
+ fill_style: 0,
72
+ fill_color: 0
73
+ )
74
+
75
+ super
76
+ end
77
+
78
+ def color=(color)
79
+ super interpret_color(color)
80
+ end
81
+ end
82
+
83
+ ##
84
+ # A thin wrapper around a file object made for reading and writing tracks
85
+ #
86
+ class TrackFile < FileWrapperBase
87
+ include Shared
88
+
89
+ ##
90
+ # Writes track properties to the file
91
+ #
92
+ # The file must be empty when this method is called!
93
+ #
94
+ # @raise [RuntimeError] if the file isn't empty
95
+ # @param [TrackProperties] properties
96
+ # @return [nil]
97
+ #
98
+ def write_track_properties(properties)
99
+ if @file.size > 0
100
+ raise "Can't write file properties, file is not empty"
101
+ end
102
+
103
+ @file.write serialize_track_properties(properties)
104
+
105
+ nil
106
+ end
107
+
108
+ ##
109
+ # Writes a track point to the file
110
+ #
111
+ # @param [TrackPoint] track_point
112
+ # @return [nil]
113
+ #
114
+ def write_track_point(track_point)
115
+ ensure_track_properties
116
+
117
+ @file.write serialize_track_point(track_point)
118
+ @file.write "\n"
119
+
120
+ nil
121
+ end
122
+
123
+ ##
124
+ # Writes an enumerable of track points to the file
125
+ #
126
+ # @param [Enumerable] enumerable
127
+ #
128
+ def write(enumerable)
129
+ enumerable.each { |track_point|
130
+ self.write_track_point(track_point)
131
+ }
132
+
133
+ nil
134
+ end
135
+
136
+ private
137
+
138
+ ##
139
+ # Ensures that track properties has been written to the file
140
+ #
141
+ def ensure_track_properties
142
+ return if @properties_written
143
+
144
+ @properties_written = true
145
+
146
+ if @file.size == 0
147
+ write_track_properties TrackProperties.new
148
+ end
149
+ end
150
+
151
+ def serialize_track_properties(track_properties)
152
+ props = track_properties.to_a
153
+ props.delete_at(0) # The datum isn't a part of this list
154
+ props.map! { |item| item.is_a?(String) ? escape_text(item) : item }
155
+
156
+ <<-TEXT.gsub(/^[ ]{8}/, "") % props
157
+ OziExplorer Track Point File Version 2.1
158
+ #{track_properties.datum}
159
+ Altitude is in Feet
160
+ Reserved 3
161
+ 0,%d,%d,%s,%d,%d,%d,%d
162
+ 0
163
+ TEXT
164
+ end
165
+
166
+ def serialize_track_point(track_point)
167
+ " %.6f,%.6f,%d,%.1f,%.7f,%s,%s" % track_point.to_a
168
+ end
169
+ end
170
+ end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module Rozi
3
- VERSION = "0.0.7"
3
+ VERSION = "0.1.3"
4
4
  end
@@ -0,0 +1,315 @@
1
+
2
+ require "rozi/file_wrapper_base"
3
+ require "rozi/shared"
4
+
5
+ module Rozi
6
+ ##
7
+ # Writes an enumerable of waypoints to a file
8
+ #
9
+ # @param [Enumerable] waypoints
10
+ # @param [String] file_path
11
+ # @param [Hash] properties Any extra keyword arguments are processed as
12
+ # waypoint file properties
13
+ #
14
+ def self.write_waypoints(waypoints, file_path, **properties)
15
+ wpt_file = WaypointFile.open(file_path, "w")
16
+
17
+ wpt_file.write_properties(WaypointFileProperties.new(**properties))
18
+ wpt_file.write waypoints
19
+
20
+ wpt_file.close
21
+
22
+ nil
23
+ end
24
+
25
+ ##
26
+ # Represents a waypoint in Ozi Explorer
27
+ #
28
+ class Waypoint < DataStruct
29
+ PROPERTIES = [
30
+ :number, :name, :latitude, :longitude, :date, :symbol, :display_format,
31
+ :fg_color, :bg_color, :description, :pointer_direction, :altitude,
32
+ :font_size, :font_style, :symbol_size
33
+ ]
34
+
35
+ DISPLAY_FORMATS = {
36
+ :number_only => 0,
37
+ :name_only => 1,
38
+ :number_and_name => 2,
39
+ :name_with_dot => 3,
40
+ :name_with_symbol => 4,
41
+ :symbol_only => 5,
42
+ :comment_with_symbol => 6,
43
+ :man_overboard => 7,
44
+ :marker => 8
45
+ }
46
+
47
+ include Shared
48
+
49
+ def initialize(*args, **kwargs)
50
+ update(
51
+ number: -1,
52
+ name: "",
53
+ latitude: 0.0,
54
+ longitude: 0.0,
55
+ date: nil,
56
+ symbol: 0,
57
+ display_format: :name_with_dot,
58
+ fg_color: 0,
59
+ bg_color: 65535,
60
+ description: "",
61
+ pointer_direction: 0,
62
+ altitude: -777,
63
+ font_size: 6,
64
+ font_style: 0,
65
+ symbol_size: 17
66
+ )
67
+
68
+ super
69
+ end
70
+
71
+ ##
72
+ # Returns the value of the display format property
73
+ #
74
+ # @param [Boolean] raw If true, returns the raw value with no processing
75
+ #
76
+ def display_format(raw: false)
77
+ if raw
78
+ super
79
+ else
80
+ DISPLAY_FORMATS.invert[super]
81
+ end
82
+ end
83
+
84
+ def display_format=(display_format)
85
+ if display_format.is_a? Symbol
86
+ @data[:display_format] = DISPLAY_FORMATS[display_format]
87
+ else
88
+ @data[:display_format] = display_format
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Sets the foreground color
94
+ #
95
+ def fg_color=(color)
96
+ @data[:fg_color] = interpret_color(color)
97
+ end
98
+
99
+ ##
100
+ # Sets the background color
101
+ #
102
+ def bg_color=(color)
103
+ @data[:bg_color] = interpret_color(color)
104
+ end
105
+ end
106
+
107
+ ##
108
+ # This class represents the waypoint file properties contained in the top 4
109
+ # lines of a waypoint file
110
+ #
111
+ class WaypointFileProperties < DataStruct
112
+ include Shared::DatumSetter
113
+
114
+ PROPERTIES = [:datum, :version]
115
+
116
+ def initialize(*args, **kwargs)
117
+ update(
118
+ datum: "WGS 84",
119
+ version: "1.1"
120
+ )
121
+
122
+ super
123
+ end
124
+ end
125
+
126
+ ##
127
+ # A thin layer above +File+ that handles reading and writing of waypoints to
128
+ # files
129
+ #
130
+ class WaypointFile < FileWrapperBase
131
+ include Shared
132
+
133
+ #@group Writing methods
134
+
135
+ ##
136
+ # Writes waypoints to the file
137
+ #
138
+ # @param [Enumerator<Waypoint>] waypoints
139
+ # @return [nil]
140
+ #
141
+ def write(waypoints)
142
+ waypoints.each { |wpt|
143
+ write_waypoint wpt
144
+ }
145
+
146
+ nil
147
+ end
148
+
149
+ ##
150
+ # Writes waypoint file properties to the file
151
+ #
152
+ # The file must be empty when this method is called!
153
+ #
154
+ # @raise [RuntimeError] if the file isn't empty
155
+ # @param [WaypointFileProperties] properties
156
+ # @return [nil]
157
+ #
158
+ def write_properties(properties)
159
+ if @file.size > 0
160
+ raise "Can't write file properties, file is not empty"
161
+ end
162
+
163
+ @file.write serialize_waypoint_file_properties(properties)
164
+ @file.write "\n"
165
+
166
+ nil
167
+ end
168
+
169
+ ##
170
+ # Writes a waypoint to the file
171
+ #
172
+ # @param [Waypoint] waypoint
173
+ # @return [nil]
174
+ #
175
+ def write_waypoint(waypoint)
176
+ ensure_file_properties
177
+
178
+ @file.write serialize_waypoint(waypoint)
179
+ @file.write "\n"
180
+
181
+ nil
182
+ end
183
+
184
+ #@group Reading methods
185
+
186
+ ##
187
+ # Reads and yields all waypoints
188
+ #
189
+ def each_waypoint
190
+ return to_enum(:each_waypoint) unless block_given?
191
+
192
+ @file.rewind
193
+
194
+ loop { yield read_waypoint }
195
+ rescue EOFError
196
+ return nil
197
+ end
198
+
199
+ ##
200
+ # Reads the waypoint file properties
201
+ #
202
+ # @raise [RuntimeError] If the file position isn't 0
203
+ # @return [WaypointFileProperties]
204
+ #
205
+ def read_properties
206
+ if @file.pos != 0
207
+ raise "File position must be 0 to read properties"
208
+ end
209
+
210
+ text = ""
211
+
212
+ 4.times { text << @file.readline }
213
+
214
+ parse_waypoint_file_properties text
215
+ end
216
+
217
+ ##
218
+ # Reads the next waypoint
219
+ #
220
+ # @raise [EOFError] When EOF is reached
221
+ # @return [Waypoint]
222
+ #
223
+ def read_waypoint
224
+ if @file.pos == 0
225
+ read_properties
226
+ end
227
+
228
+ parse_waypoint @file.readline
229
+ end
230
+
231
+ #@endgroup
232
+
233
+ private
234
+
235
+ def parse_waypoint(text)
236
+ map = {
237
+ 0 => {symbol: :number, cast: method(:Integer)},
238
+ 1 => {symbol: :name, cast: method(:String)},
239
+ 2 => {symbol: :latitude, cast: method(:Float)},
240
+ 3 => {symbol: :longitude, cast: method(:Float)},
241
+ 4 => {symbol: :date, cast: method(:Float)},
242
+ 5 => {symbol: :symbol, cast: method(:Integer)},
243
+ 7 => {symbol: :display_format, cast: method(:Integer)},
244
+ 8 => {symbol: :fg_color, cast: method(:Integer)},
245
+ 9 => {symbol: :bg_color, cast: method(:Integer)},
246
+ 10 => {symbol: :description, cast: method(:String)},
247
+ 11 => {symbol: :pointer_direction, cast: method(:Integer)},
248
+ 14 => {symbol: :altitude, cast: method(:Integer)},
249
+ 15 => {symbol: :font_size, cast: method(:Integer)},
250
+ 16 => {symbol: :font_style, cast: method(:Integer)},
251
+ 17 => {symbol: :symbol_size, cast: method(:Integer)},
252
+ }
253
+
254
+ text = text.strip
255
+ fields = text.split(",").map { |x| x.strip }
256
+
257
+ waypoint = Waypoint.new
258
+
259
+ map.each_pair { |index, data|
260
+ value = fields[index]
261
+
262
+ next if value.empty?
263
+
264
+ value = data[:cast].call(value)
265
+
266
+ if value.is_a? String
267
+ value = unescape_text(value)
268
+ end
269
+
270
+ waypoint.set(data[:symbol], value)
271
+ }
272
+
273
+ waypoint
274
+ end
275
+
276
+ def parse_waypoint_file_properties(text)
277
+ lines = text.lines
278
+
279
+ version = lines[0].strip[-3..-1]
280
+ datum = lines[1].strip
281
+
282
+ WaypointFileProperties.new(datum, version)
283
+ end
284
+
285
+ def serialize_waypoint(waypoint)
286
+ array = waypoint.to_a
287
+ array.map! { |item| item.is_a?(String) ? escape_text(item) : item }
288
+ array.map! { |item| item.nil? ? "" : item }
289
+ array.map! { |item| item.is_a?(Float) ? item.round(6) : item }
290
+
291
+ "%d,%s,%f,%f,%s,%d,1,%d,%d,%d,%s,%d,,,%d,%d,%d,%d" % array
292
+ end
293
+
294
+ def serialize_waypoint_file_properties(properties)
295
+ <<-TEXT.gsub(/^[ ]{8}/, "")
296
+ OziExplorer Waypoint File Version #{properties.version}
297
+ #{properties.datum}
298
+ Reserved 2
299
+ TEXT
300
+ end
301
+
302
+ ##
303
+ # Ensures that waypoint file properties has been written to the file
304
+ #
305
+ def ensure_file_properties
306
+ return if @properties_written
307
+
308
+ @properties_written = true
309
+
310
+ if @file.size == 0
311
+ write_properties WaypointFileProperties.new
312
+ end
313
+ end
314
+ end
315
+ end