ruby-mapsource 0.0.1
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/.gitignore +4 -0
- data/.rvmrc +71 -0
- data/Gemfile +8 -0
- data/Guardfile +10 -0
- data/LICENSE +19 -0
- data/README.md +16 -0
- data/Rakefile +7 -0
- data/lib/mapsource/defs.rb +44 -0
- data/lib/mapsource/reader.rb +248 -0
- data/lib/mapsource/structure.rb +45 -0
- data/lib/mapsource/version.rb +3 -0
- data/lib/mapsource.rb +30 -0
- data/ruby-mapsource.gemspec +23 -0
- data/spec/assets/sample.gdb +0 -0
- data/spec/assets/track.bin +0 -0
- data/spec/integration/reader_spec.rb +33 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/unit/reader_spec.rb +127 -0
- metadata +90 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="ruby-1.9.3-p0@mapsource_gdb"
|
8
|
+
|
9
|
+
#
|
10
|
+
# Uncomment following line if you want options to be set only for given project.
|
11
|
+
#
|
12
|
+
# PROJECT_JRUBY_OPTS=( --1.9 )
|
13
|
+
#
|
14
|
+
# The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
|
15
|
+
#
|
16
|
+
# chmod +x ${rvm_path}/hooks/after_use_jruby_opts
|
17
|
+
#
|
18
|
+
|
19
|
+
#
|
20
|
+
# First we attempt to load the desired environment directly from the environment
|
21
|
+
# file. This is very fast and efficient compared to running through the entire
|
22
|
+
# CLI and selector. If you want feedback on which environment was used then
|
23
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
24
|
+
#
|
25
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
26
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
27
|
+
then
|
28
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
29
|
+
|
30
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
31
|
+
then
|
32
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
33
|
+
fi
|
34
|
+
else
|
35
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
36
|
+
if ! rvm --create use "$environment_id"
|
37
|
+
then
|
38
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
39
|
+
return 1
|
40
|
+
fi
|
41
|
+
fi
|
42
|
+
|
43
|
+
#
|
44
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
45
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
46
|
+
# necessary.
|
47
|
+
#
|
48
|
+
# filename=".gems"
|
49
|
+
# if [[ -s "$filename" ]]
|
50
|
+
# then
|
51
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
52
|
+
# fi
|
53
|
+
|
54
|
+
# If you use bundler, this might be useful to you:
|
55
|
+
# if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
|
56
|
+
# then
|
57
|
+
# printf "The rubygem 'bundler' is not installed. Installing it now.\n"
|
58
|
+
# gem install bundler
|
59
|
+
# fi
|
60
|
+
# if [[ -s Gemfile ]] && command -v bundle
|
61
|
+
# then
|
62
|
+
# bundle install
|
63
|
+
# fi
|
64
|
+
|
65
|
+
if [[ $- == *i* ]] # check for interactive shells
|
66
|
+
then
|
67
|
+
echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
|
68
|
+
else
|
69
|
+
echo "Using: $GEM_HOME" # don't use colors in interactive shells
|
70
|
+
fi
|
71
|
+
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'minitest' do
|
5
|
+
# with Minitest::Spec
|
6
|
+
watch(%r|^spec/(.*)_spec\.rb|)
|
7
|
+
watch(%r|^lib/mapsource/(.*)\.rb|) { |m| "spec/integration/#{m[1]}_spec.rb" }
|
8
|
+
watch(%r|^lib/mapsource/(.*)\.rb|) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
9
|
+
watch(%r|^spec/spec_helper\.rb|) { "spec" }
|
10
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2012 Vitor Peres Capela Pereira
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
ruby-mapsource is a library that allows ruby programs to read files created by Garmin's MapSource and BaseCamp.
|
2
|
+
|
3
|
+
# Usage
|
4
|
+
|
5
|
+
TODO
|
6
|
+
|
7
|
+
# Tools
|
8
|
+
|
9
|
+
TODO:
|
10
|
+
|
11
|
+
- gdbtokml - makes a kml file from a gdb
|
12
|
+
- gdbtocsv - creates a csv file from a gdb
|
13
|
+
|
14
|
+
# Thanks
|
15
|
+
|
16
|
+
GPSBabel
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module MapSource
|
2
|
+
# Public: A Color attributed to a Track, Route or Waypoint.
|
3
|
+
class Color
|
4
|
+
attr_reader :name, :r, :g, :b
|
5
|
+
|
6
|
+
def initialize(name, r=-1, g=-1, b=-1)
|
7
|
+
@name = name
|
8
|
+
@r = @r
|
9
|
+
@g = @g
|
10
|
+
@b = @b
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Converts GDB color index to color object.
|
14
|
+
#
|
15
|
+
# idx - GDB index
|
16
|
+
#
|
17
|
+
# Returns Color corresponding to index.
|
18
|
+
def self.from_index(idx)
|
19
|
+
COLORS[idx] || COLORS.first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: A set of default colors.
|
24
|
+
COLORS = [
|
25
|
+
Color.new('Unknown'),
|
26
|
+
Color.new('Black', 0, 0, 0),
|
27
|
+
Color.new('DarkRed', 139, 0, 0),
|
28
|
+
Color.new('DarkGreen', 0, 100, 0),
|
29
|
+
Color.new('DarkYellow', 139, 139, 0),
|
30
|
+
Color.new('DarkBlue', 0, 0, 139),
|
31
|
+
Color.new('DarkMagenta', 139, 0, 139),
|
32
|
+
Color.new('DarkCyan', 0, 139, 139),
|
33
|
+
Color.new('LightGray', 211, 211, 211),
|
34
|
+
Color.new('DarkGray', 169, 169, 169),
|
35
|
+
Color.new('Red', 255, 0, 0),
|
36
|
+
Color.new('Green', 0, 255, 0),
|
37
|
+
Color.new('Yellow', 255, 255, 0),
|
38
|
+
Color.new('Blue', 0, 0, 255),
|
39
|
+
Color.new('Magenta', 255, 0, 255),
|
40
|
+
Color.new('Cyan', 0, 255, 255),
|
41
|
+
Color.new('White', 255, 255, 255),
|
42
|
+
Color.new('Transparent')
|
43
|
+
]
|
44
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
module MapSource
|
2
|
+
class InvalidFormatError < StandardError; end
|
3
|
+
class UnsupportedVersionError < StandardError; end
|
4
|
+
|
5
|
+
# Public: Parses GDB files and extracts waypoints, tracks and routes.
|
6
|
+
#
|
7
|
+
# Examples:
|
8
|
+
#
|
9
|
+
# reader = MapSource::Reader.new(open('around_the_world.gdb'))
|
10
|
+
# reader.waypoints
|
11
|
+
# # => [MapSource::Waypoint<...>, ...]
|
12
|
+
class Reader
|
13
|
+
# Public: Range of format versions supported.
|
14
|
+
SUPPORTED_VERSIONS = (1..3)
|
15
|
+
|
16
|
+
attr_reader :header, :waypoints
|
17
|
+
|
18
|
+
# Public: Creates a Reader.
|
19
|
+
#
|
20
|
+
# gdb - An IO object pointing to a GDB.
|
21
|
+
def initialize(gdb)
|
22
|
+
@gdb = gdb
|
23
|
+
@header = read_header
|
24
|
+
|
25
|
+
@parsed = false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Read waypoints from file.
|
29
|
+
#
|
30
|
+
# Returns a list of waypoints.
|
31
|
+
def waypoints
|
32
|
+
read_data
|
33
|
+
@waypoints
|
34
|
+
end
|
35
|
+
|
36
|
+
def tracks
|
37
|
+
read_data
|
38
|
+
@tracks
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
# Internal: Reads data from the GDB file.
|
43
|
+
#
|
44
|
+
# Returns list of waypoints, list of tracks, list of routes.
|
45
|
+
def read_data
|
46
|
+
return if @parsed
|
47
|
+
|
48
|
+
@waypoints = []
|
49
|
+
@tracks = []
|
50
|
+
@routes = []
|
51
|
+
|
52
|
+
while true
|
53
|
+
len = @gdb.read(4).unpack('l').shift
|
54
|
+
record = @gdb.read(len + 1)
|
55
|
+
|
56
|
+
case record
|
57
|
+
when /^W/
|
58
|
+
@waypoints << read_waypoint(record)
|
59
|
+
when /^T/
|
60
|
+
@tracks << read_track(record)
|
61
|
+
when /^V/
|
62
|
+
break
|
63
|
+
else
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@parsed = true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Internal: Converts coordinates in semicircles to degrees.
|
71
|
+
#
|
72
|
+
# v - coordinate as semicircle
|
73
|
+
#
|
74
|
+
# Returns coordinate in degrees.
|
75
|
+
def semicircle_to_degrees(v)
|
76
|
+
(v.to_f / (1 << 31)) * 180.0
|
77
|
+
end
|
78
|
+
|
79
|
+
# Internal: Reads a waypoint record from the GDB.
|
80
|
+
#
|
81
|
+
# record - a binary string containing waypoint data.
|
82
|
+
#
|
83
|
+
# Returns waypoint.
|
84
|
+
def read_waypoint(record)
|
85
|
+
io = StringIO.new(record)
|
86
|
+
|
87
|
+
read_char io
|
88
|
+
shortname = read_string(io)
|
89
|
+
wptclass = read_int(io)
|
90
|
+
|
91
|
+
read_string(io)
|
92
|
+
io.read 22 # skip 22 bytes
|
93
|
+
|
94
|
+
lat = semicircle_to_degrees(read_int(io))
|
95
|
+
lon = semicircle_to_degrees(read_int(io))
|
96
|
+
|
97
|
+
wpt = Waypoint.new(lat, lon)
|
98
|
+
wpt.shortname = shortname
|
99
|
+
|
100
|
+
if read_char(io) == 1
|
101
|
+
alt = read_double(io)
|
102
|
+
|
103
|
+
wpt.altitude = alt if alt < 1.0e24
|
104
|
+
end
|
105
|
+
|
106
|
+
wpt.notes = read_string(io)
|
107
|
+
wpt.proximity = read_double(io) if read_char(io) == 1
|
108
|
+
|
109
|
+
read_int io # display
|
110
|
+
read_int io # color, not implemented
|
111
|
+
|
112
|
+
wpt.icon = read_int(io)
|
113
|
+
wpt.city = read_string(io)
|
114
|
+
wpt.state = read_string(io)
|
115
|
+
wpt.facility = read_string(io)
|
116
|
+
|
117
|
+
wpt.depth = read_double(io) if read_char(io) == 1
|
118
|
+
|
119
|
+
wpt
|
120
|
+
end
|
121
|
+
|
122
|
+
def read_track(record)
|
123
|
+
header = record.unpack('AZ*all')
|
124
|
+
_, name, _, color, npoints = *header
|
125
|
+
contents = record.sub(/^#{Regexp.quote(header.pack('AZ*all'))}/, '')
|
126
|
+
|
127
|
+
track = Track.new(name, Color::from_index(color))
|
128
|
+
io = StringIO.new(contents)
|
129
|
+
|
130
|
+
0.upto(npoints - 1) do
|
131
|
+
lat = semicircle_to_degrees(read_int(io))
|
132
|
+
lon = semicircle_to_degrees(read_int(io))
|
133
|
+
|
134
|
+
wpt = Waypoint.new(lat, lon)
|
135
|
+
|
136
|
+
if read_char(io) == 1
|
137
|
+
alt = read_double(io)
|
138
|
+
wpt.altitude = alt if alt < 1.0e24
|
139
|
+
end
|
140
|
+
|
141
|
+
wpt.creation_time = read_int(io) if read_char(io) == 1
|
142
|
+
wpt.depth = read_double(io) if read_char(io) == 1
|
143
|
+
wpt.temperature = read_double(io) if read_char(io) == 1
|
144
|
+
|
145
|
+
track.add_waypoint wpt
|
146
|
+
end
|
147
|
+
|
148
|
+
track
|
149
|
+
end
|
150
|
+
|
151
|
+
# Internal: Reads a string from an IO object.
|
152
|
+
#
|
153
|
+
# io - an IO object
|
154
|
+
# chars - number of chars to read. If not specified, read_string stops at
|
155
|
+
# the null string terminator.
|
156
|
+
#
|
157
|
+
# Returns a string.
|
158
|
+
def read_string(io, chars=nil)
|
159
|
+
if chars
|
160
|
+
io.read chars
|
161
|
+
else
|
162
|
+
str = ''
|
163
|
+
while c = io.read(1)
|
164
|
+
break if c == "\x00"
|
165
|
+
str += c
|
166
|
+
end
|
167
|
+
|
168
|
+
str
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Internal: Reads an Integer from an IO object, unpacking it appropriately.
|
173
|
+
#
|
174
|
+
# io - an IO object.
|
175
|
+
#
|
176
|
+
# Returns an Integer.
|
177
|
+
def read_int(io)
|
178
|
+
io.read(4).unpack('l').shift
|
179
|
+
end
|
180
|
+
|
181
|
+
# Internal: Reads a Double from an IO object, unpacking it appropriately.
|
182
|
+
#
|
183
|
+
# io - an IO object.
|
184
|
+
#
|
185
|
+
# Returns an Double.
|
186
|
+
def read_double(io)
|
187
|
+
io.read(8).unpack('E').shift
|
188
|
+
end
|
189
|
+
|
190
|
+
# Internal: Reads a single character from an IO object, unpacking it
|
191
|
+
# appropriately.
|
192
|
+
#
|
193
|
+
# io - an IO object.
|
194
|
+
#
|
195
|
+
# Returns a single character.
|
196
|
+
def read_char(io)
|
197
|
+
io.read(1).unpack('c').shift
|
198
|
+
end
|
199
|
+
|
200
|
+
# Internal: Reads a GDB's header to determine the version being parsed, its creator
|
201
|
+
# and signer.
|
202
|
+
#
|
203
|
+
# Returns a properly filled header.
|
204
|
+
# Raises MapSource::InvalidFormatError if it's not a GDB file.
|
205
|
+
# Raises MapSource::InvalidFormatError if GDB is malformed.
|
206
|
+
# Raises MapSource::UnsupportedVersionError if file format version is not supported.
|
207
|
+
def read_header
|
208
|
+
header = Header.new
|
209
|
+
|
210
|
+
mscrf = @gdb.read(6).unpack('A*').shift
|
211
|
+
|
212
|
+
raise InvalidFormatError, "Invalid gdb file" if mscrf != 'MsRcf'
|
213
|
+
|
214
|
+
record_length = @gdb.read(4).unpack('l').shift
|
215
|
+
buffer = @gdb.read(record_length + 1)
|
216
|
+
|
217
|
+
raise InvalidFormatError, "Invalid gdb file" if buffer[0] != ?D
|
218
|
+
gdb_version = buffer[1].getbyte(0) - ?k.getbyte(0) + 1
|
219
|
+
|
220
|
+
raise UnsupportedVersionError, "Unsupported version: #{gdb_version}. Supported versions are #{SUPPORTED_VERSIONS.to_a.join(', ')}" if !SUPPORTED_VERSIONS.member?(gdb_version)
|
221
|
+
|
222
|
+
header.version = gdb_version
|
223
|
+
|
224
|
+
record_length = @gdb.read(4).unpack('l').shift
|
225
|
+
buffer = @gdb.read(record_length + 1)
|
226
|
+
creator = buffer.unpack('Z*').shift
|
227
|
+
|
228
|
+
header.created_by = if creator =~ /SQA$/
|
229
|
+
'MapSource'
|
230
|
+
elsif creator =~ /neaderhi$/
|
231
|
+
'MapSource BETA'
|
232
|
+
end
|
233
|
+
|
234
|
+
signer = @gdb.read(10)
|
235
|
+
signer += @gdb.read(1) until signer =~ /\x00$/
|
236
|
+
|
237
|
+
signer = signer.unpack('Z*').shift
|
238
|
+
|
239
|
+
if signer !~ /MapSource|BaseCamp/
|
240
|
+
raise InvalidFormatError, "Unknown file signature: #{signer}"
|
241
|
+
end
|
242
|
+
|
243
|
+
header.signed_by = signer
|
244
|
+
|
245
|
+
header
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Defines the structure
|
2
|
+
module MapSource
|
3
|
+
# Public: GDB header. Contains name of creator software, signature and
|
4
|
+
# format version.
|
5
|
+
class Header
|
6
|
+
attr_accessor :created_by, :signed_by, :version
|
7
|
+
end
|
8
|
+
|
9
|
+
# Public: A Waypoint.
|
10
|
+
class Waypoint
|
11
|
+
attr_accessor :shortname, :latitude, :longitude, :altitude, :temperature, :depth, :notes, :creation_time, :proximity, :icon, :city, :state, :facility
|
12
|
+
|
13
|
+
def initialize(latitude, longitude)
|
14
|
+
@latitude = latitude
|
15
|
+
@longitude = longitude
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: A Track.
|
20
|
+
class Track
|
21
|
+
attr_reader :name, :color
|
22
|
+
|
23
|
+
def initialize(name, color)
|
24
|
+
@name = name
|
25
|
+
@color = color
|
26
|
+
@waypoints = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def waypoints
|
30
|
+
@waypoints.dup
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_waypoint(wpt)
|
34
|
+
@waypoints << wpt
|
35
|
+
end
|
36
|
+
|
37
|
+
def size
|
38
|
+
@waypoints.size
|
39
|
+
end
|
40
|
+
|
41
|
+
def each
|
42
|
+
@waypoints.each { |wpt| yield wpt if block_given? }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/mapsource.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2012 Vitor Peres Capela Pereira
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
# this software and associated documentation files (the "Software"), to deal in
|
6
|
+
# the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
8
|
+
# of the Software, and to permit persons to whom the Software is furnished to do
|
9
|
+
# so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
12
|
+
# copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
|
+
# SOFTWARE.
|
21
|
+
#++
|
22
|
+
|
23
|
+
mapsource_path = File.dirname(__FILE__)
|
24
|
+
$:.unshift(mapsource_path) if File.directory?(mapsource_path) && !$:.include?(mapsource_path)
|
25
|
+
|
26
|
+
require 'stringio'
|
27
|
+
|
28
|
+
require 'mapsource/structure'
|
29
|
+
require 'mapsource/defs'
|
30
|
+
require 'mapsource/reader'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "mapsource/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ruby-mapsource"
|
7
|
+
s.version = MapSource::VERSION
|
8
|
+
s.authors = ["Vitor Capela"]
|
9
|
+
s.email = ["dodecaphonic@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A Ruby library for reading MapSource/BaseCamp-created GDB files}
|
12
|
+
s.description = %q{A Ruby library for reading MapSource/BaseCamp-created GDB files}
|
13
|
+
|
14
|
+
s.rubyforge_project = "ruby-mapsource"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "minitest", "~> 2.10"
|
22
|
+
s.add_development_dependency "mocha", "~> 0.10"
|
23
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MapSource::Reader do
|
4
|
+
before :each do
|
5
|
+
@gdb_file = open(File.dirname(__FILE__) + '/../assets/sample.gdb')
|
6
|
+
@reader = MapSource::Reader.new(@gdb_file)
|
7
|
+
end
|
8
|
+
|
9
|
+
after :each do
|
10
|
+
@gdb_file.close
|
11
|
+
end
|
12
|
+
|
13
|
+
it "parses the header correctly" do
|
14
|
+
@reader.header.created_by.must_equal 'MapSource'
|
15
|
+
@reader.header.signed_by.must_equal 'MapSource'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "parses all waypoints" do
|
19
|
+
@reader.waypoints.size.must_equal 312
|
20
|
+
end
|
21
|
+
|
22
|
+
it "parses every track" do
|
23
|
+
tracks = @reader.tracks
|
24
|
+
al = tracks.find { |t| t.name == 'ACTIVE LOG 009' }
|
25
|
+
al.wont_be_nil
|
26
|
+
al.size.must_equal 296
|
27
|
+
|
28
|
+
wpt = al.waypoints[9]
|
29
|
+
wpt.latitude.must_be_close_to -23.17172
|
30
|
+
wpt.longitude.must_be_close_to -44.83632
|
31
|
+
wpt.altitude.must_be_close_to 1471, 0.05
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
require 'mapsource'
|
8
|
+
|
9
|
+
module MapSource::Spec
|
10
|
+
# Internal: Creates a basic mock object containing expectations for a part of
|
11
|
+
# (or all of) the header. How much is created is determined by the _options_
|
12
|
+
# hash.
|
13
|
+
#
|
14
|
+
# options - a hash of options. Currently supports ":before" and the following
|
15
|
+
# states:
|
16
|
+
# - :start - before expecting on the first string in the file
|
17
|
+
# - :version - before expectations regarding the version
|
18
|
+
# - :creator - before expectations regarding the creator of the file
|
19
|
+
# - :signer - before expectations regarding the signer of the file
|
20
|
+
#
|
21
|
+
# Returns an array with a mocha mock object and a mocha sequence object.
|
22
|
+
def create_basic_valid_state(options={ before: nil })
|
23
|
+
gdb_file = mock('gdb')
|
24
|
+
header = sequence('parsing')
|
25
|
+
|
26
|
+
unless options[:before] == :start
|
27
|
+
gdb_file.expects(:read).in_sequence(header).with(6).returns "MsRcf\x00"
|
28
|
+
else
|
29
|
+
return [gdb_file, header]
|
30
|
+
end
|
31
|
+
|
32
|
+
unless options[:before] == :version
|
33
|
+
reclen = mock('reclen')
|
34
|
+
reclen.expects(:unpack).with('l').returns [2]
|
35
|
+
gdb_file.expects(:read).in_sequence(header).with(4).returns reclen
|
36
|
+
|
37
|
+
gdb_file.expects(:read).in_sequence(header).with(3).returns "Dm\x00"
|
38
|
+
else
|
39
|
+
return [gdb_file, header]
|
40
|
+
end
|
41
|
+
|
42
|
+
unless options[:before] == :creator
|
43
|
+
reclen = mock('reclen 2')
|
44
|
+
reclen.expects(:unpack).with('l').returns [27]
|
45
|
+
gdb_file.expects(:read).in_sequence(header).with(4).returns reclen
|
46
|
+
|
47
|
+
creator = mock('creator')
|
48
|
+
creator.expects(:unpack).with('Z*').returns ["Ae\u0002SQA"]
|
49
|
+
gdb_file.expects(:read).in_sequence(header).with(28).returns creator
|
50
|
+
else
|
51
|
+
return [gdb_file, header]
|
52
|
+
end
|
53
|
+
|
54
|
+
unless options[:before] == :signer
|
55
|
+
gdb_file.expects(:read).in_sequence(header).with(10).returns "MapSource\x00"
|
56
|
+
else
|
57
|
+
return [gdb_file, header]
|
58
|
+
end
|
59
|
+
|
60
|
+
[gdb_file, header]
|
61
|
+
end
|
62
|
+
|
63
|
+
SAMPLE_TRACK = File.dirname(__FILE__) + '/assets/track.bin'
|
64
|
+
end
|
65
|
+
|
66
|
+
include MapSource::Spec
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MapSource::Reader do
|
4
|
+
describe "when reading the header" do
|
5
|
+
it "fails when first portion of the file doesn't pass validation" do
|
6
|
+
gdb_file, sequence = *create_basic_valid_state(before: :start)
|
7
|
+
|
8
|
+
bogus = mock('bogus')
|
9
|
+
bogus.expects(:unpack).with('A*').returns ['Bogus']
|
10
|
+
gdb_file.expects(:read).with(6).returns bogus
|
11
|
+
|
12
|
+
lambda {
|
13
|
+
MapSource::Reader.new gdb_file
|
14
|
+
}.must_raise MapSource::InvalidFormatError
|
15
|
+
end
|
16
|
+
|
17
|
+
it "fails when header is malformed" do
|
18
|
+
gdb_file, sequence = *create_basic_valid_state(before: :version)
|
19
|
+
|
20
|
+
reclen = mock('reclen')
|
21
|
+
reclen.expects(:unpack).with('l').returns [2]
|
22
|
+
gdb_file.expects(:read).in_sequence(sequence).with(4).returns reclen
|
23
|
+
|
24
|
+
gdb_file.expects(:read).in_sequence(sequence).with(3).returns "Am\x00"
|
25
|
+
|
26
|
+
lambda {
|
27
|
+
MapSource::Reader.new gdb_file
|
28
|
+
}.must_raise MapSource::InvalidFormatError
|
29
|
+
end
|
30
|
+
|
31
|
+
it "fails when version is unsupported" do
|
32
|
+
gdb_file, sequence = *create_basic_valid_state(before: :version)
|
33
|
+
|
34
|
+
reclen = mock('reclen')
|
35
|
+
reclen.expects(:unpack).with('l').returns [2]
|
36
|
+
gdb_file.expects(:read).in_sequence(sequence).with(4).returns reclen
|
37
|
+
|
38
|
+
gdb_file.expects(:read).in_sequence(sequence).with(3).returns "Dn\x00"
|
39
|
+
|
40
|
+
lambda {
|
41
|
+
MapSource::Reader.new gdb_file
|
42
|
+
}.must_raise MapSource::UnsupportedVersionError
|
43
|
+
end
|
44
|
+
|
45
|
+
it "determines which version of the format it has" do
|
46
|
+
gdb_file, _ = *create_basic_valid_state
|
47
|
+
|
48
|
+
reader = MapSource::Reader.new(gdb_file)
|
49
|
+
reader.header.version.must_equal 3
|
50
|
+
end
|
51
|
+
|
52
|
+
it "determines which software created the file" do
|
53
|
+
gdb_file, _ = *create_basic_valid_state
|
54
|
+
|
55
|
+
reader = MapSource::Reader.new(gdb_file)
|
56
|
+
reader.header.created_by.must_equal 'MapSource'
|
57
|
+
end
|
58
|
+
|
59
|
+
it "determines which software signed the file" do
|
60
|
+
gdb_file, _ = *create_basic_valid_state
|
61
|
+
|
62
|
+
reader = MapSource::Reader.new(gdb_file)
|
63
|
+
reader.header.signed_by.must_equal 'MapSource'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "fails when creator is not recognized" do
|
67
|
+
gdb_file, sequence = *create_basic_valid_state(before: :signer)
|
68
|
+
|
69
|
+
gdb_file.expects(:read).in_sequence(sequence).with(10).returns 'BogusSoftw'
|
70
|
+
gdb_file.expects(:read).in_sequence(sequence).with(1).times(3).returns 'x'
|
71
|
+
gdb_file.expects(:read).in_sequence(sequence).with(1).returns "\x00"
|
72
|
+
|
73
|
+
lambda {
|
74
|
+
MapSource::Reader.new gdb_file
|
75
|
+
}.must_raise MapSource::InvalidFormatError
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "when reading the content" do
|
80
|
+
it "parses waypoints" do
|
81
|
+
gdb_file, seq = *create_basic_valid_state
|
82
|
+
|
83
|
+
gdb_file.expects(:read).in_sequence(seq).with(4).returns "\l\x00\x00\x00"
|
84
|
+
gdb_file.expects(:read).in_sequence(seq).with(109).returns "W001\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xE7\xB7\x85\xEF\xDE\xCB\x1D\xE0\x01\x00\x00\x00\x80\xF9X\x97@15-DEZ-11 12:06:43PM\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x8D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
85
|
+
gdb_file.expects(:read).in_sequence(seq).with(4).returns "\x01\x00\x00\x00"
|
86
|
+
gdb_file.expects(:read).in_sequence(seq).with(2).returns "V\x00"
|
87
|
+
|
88
|
+
reader = MapSource::Reader.new(gdb_file)
|
89
|
+
|
90
|
+
reader.waypoints.size.must_equal 1
|
91
|
+
wpt = reader.waypoints.first
|
92
|
+
wpt.shortname.must_equal "001"
|
93
|
+
wpt.latitude.must_be_close_to -23.17171306349337
|
94
|
+
wpt.longitude.must_be_close_to -44.836323726922274
|
95
|
+
wpt.notes.must_equal "15-DEZ-11 12:06:43PM"
|
96
|
+
wpt.altitude.floor.must_be_close_to 1494
|
97
|
+
end
|
98
|
+
|
99
|
+
it "parses tracks" do
|
100
|
+
track = open(SAMPLE_TRACK, 'rb').read
|
101
|
+
gdb_file, seq = *create_basic_valid_state
|
102
|
+
gdb_file.expects(:read).in_sequence(seq).with(4).returns "\xC0r\x00\x00"
|
103
|
+
gdb_file.expects(:read).in_sequence(seq).with(29377).returns track
|
104
|
+
gdb_file.expects(:read).in_sequence(seq).with(4).returns "\x01\x00\x00\x00"
|
105
|
+
gdb_file.expects(:read).in_sequence(seq).with(2).returns "V\x00"
|
106
|
+
|
107
|
+
reader = MapSource::Reader.new(gdb_file)
|
108
|
+
reader.tracks.size.must_equal 1
|
109
|
+
|
110
|
+
track = reader.tracks.first
|
111
|
+
track.name.must_equal "ACTIVE LOG"
|
112
|
+
track.color.name.must_equal "Red"
|
113
|
+
track.size.must_equal 1223
|
114
|
+
|
115
|
+
wpt = track.waypoints.first
|
116
|
+
wpt.latitude.must_be_close_to -22.17333
|
117
|
+
wpt.longitude.must_be_close_to -42.41256
|
118
|
+
wpt.altitude.must_be_close_to 675.0, 0.5
|
119
|
+
|
120
|
+
wpt = track.waypoints[1]
|
121
|
+
wpt.latitude.must_be_close_to -22.17332
|
122
|
+
wpt.longitude.must_be_close_to -42.41264
|
123
|
+
wpt.altitude.must_be_close_to 673.0, 0.5
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-mapsource
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vitor Capela
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: &70324053963740 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.10'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70324053963740
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
requirement: &70324053976040 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.10'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70324053976040
|
36
|
+
description: A Ruby library for reading MapSource/BaseCamp-created GDB files
|
37
|
+
email:
|
38
|
+
- dodecaphonic@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .rvmrc
|
45
|
+
- Gemfile
|
46
|
+
- Guardfile
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- lib/mapsource.rb
|
51
|
+
- lib/mapsource/defs.rb
|
52
|
+
- lib/mapsource/reader.rb
|
53
|
+
- lib/mapsource/structure.rb
|
54
|
+
- lib/mapsource/version.rb
|
55
|
+
- ruby-mapsource.gemspec
|
56
|
+
- spec/assets/sample.gdb
|
57
|
+
- spec/assets/track.bin
|
58
|
+
- spec/integration/reader_spec.rb
|
59
|
+
- spec/spec_helper.rb
|
60
|
+
- spec/unit/reader_spec.rb
|
61
|
+
homepage: ''
|
62
|
+
licenses: []
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project: ruby-mapsource
|
81
|
+
rubygems_version: 1.8.10
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: A Ruby library for reading MapSource/BaseCamp-created GDB files
|
85
|
+
test_files:
|
86
|
+
- spec/assets/sample.gdb
|
87
|
+
- spec/assets/track.bin
|
88
|
+
- spec/integration/reader_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- spec/unit/reader_spec.rb
|