rozi 0.0.7 → 0.1.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 (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