gpx 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,3 @@
1
- #--
2
- # Copyright (c) 2006 Doug Fales
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
1
  module GPX
24
2
  # In GPX, a single Track can hold multiple Segments, each of which hold
25
3
  # multiple points (in this library, those points are instances of
@@ -38,24 +16,36 @@ module GPX
38
16
  @segments = []
39
17
  @points = []
40
18
  reset_meta_data
41
- if(opts[:element])
42
- trk_element = opts[:element]
43
- @name = (trk_element.at("name").inner_text rescue "")
44
- @comment = (trk_element.at('cmt').inner_text rescue '')
45
- @description = (trk_element.at('desc').inner_text rescue '')
46
- trk_element.search("trkseg").each do |seg_element|
47
- seg = Segment.new(:element => seg_element, :track => self, :gpx_file => @gpx_file)
48
- update_meta_data(seg)
49
- @segments << seg
50
- end
19
+
20
+ return unless opts[:element]
21
+
22
+ trk_element = opts[:element]
23
+ @name = (begin
24
+ trk_element.at('name').inner_text
25
+ rescue StandardError
26
+ ''
27
+ end)
28
+ @comment = (begin
29
+ trk_element.at('cmt').inner_text
30
+ rescue StandardError
31
+ ''
32
+ end)
33
+ @description = (begin
34
+ trk_element.at('desc').inner_text
35
+ rescue StandardError
36
+ ''
37
+ end)
38
+ trk_element.search('trkseg').each do |seg_element|
39
+ seg = Segment.new(element: seg_element, track: self, gpx_file: @gpx_file)
40
+ append_segment(seg)
51
41
  end
52
42
  end
53
43
 
54
44
  # Append a segment to this track, updating its meta data along the way.
55
45
  def append_segment(seg)
46
+ return if seg.points.empty?
56
47
  update_meta_data(seg)
57
48
  @segments << seg
58
- @points.concat(seg.points) unless seg.nil?
59
49
  end
60
50
 
61
51
  # Returns true if the given time occurs within any of the segments of this track.
@@ -63,7 +53,7 @@ module GPX
63
53
  segments.each do |seg|
64
54
  return true if seg.contains_time?(time)
65
55
  end
66
- return false
56
+ false
67
57
  end
68
58
 
69
59
  # Finds the closest point (to "time") within this track. Useful for
@@ -82,7 +72,7 @@ module GPX
82
72
  seg.crop(area)
83
73
  update_meta_data(seg) unless seg.empty?
84
74
  end
85
- segments.delete_if { |seg| seg.empty? }
75
+ segments.delete_if(&:empty?)
86
76
  end
87
77
 
88
78
  # Deletes all points within a given area and updates the meta data.
@@ -92,13 +82,13 @@ module GPX
92
82
  seg.delete_area(area)
93
83
  update_meta_data(seg) unless seg.empty?
94
84
  end
95
- segments.delete_if { |seg| seg.empty? }
85
+ segments.delete_if(&:empty?)
96
86
  end
97
87
 
98
88
  # Returns true if this track has no points in it. This should return
99
89
  # true even when the track has empty segments.
100
90
  def empty?
101
- (points.nil? or points.size.zero?)
91
+ (points.nil? || points.size.zero?)
102
92
  end
103
93
 
104
94
  # Prints out a friendly summary of this track (sans points). Useful for
@@ -115,7 +105,7 @@ module GPX
115
105
  result << "\tMoving duration: #{moving_duration} km\n"
116
106
  result << "\tLowest Point: #{lowest_point.elevation} \n"
117
107
  result << "\tHighest Point: #{highest_point.elevation}\n "
118
- result << "\tBounds: #{bounds.to_s}"
108
+ result << "\tBounds: #{bounds}"
119
109
  result
120
110
  end
121
111
 
@@ -125,12 +115,12 @@ module GPX
125
115
  @distance += seg.distance
126
116
  end
127
117
  end
128
-
118
+
129
119
  protected
130
120
 
131
121
  def update_meta_data(seg)
132
- @lowest_point = seg.lowest_point if(@lowest_point.nil? or seg.lowest_point.elevation < @lowest_point.elevation)
133
- @highest_point = seg.highest_point if(@highest_point.nil? or seg.highest_point.elevation > @highest_point.elevation)
122
+ @lowest_point = seg.lowest_point if @lowest_point.nil? || (seg.lowest_point.elevation < @lowest_point.elevation)
123
+ @highest_point = seg.highest_point if @highest_point.nil? || (seg.highest_point.elevation > @highest_point.elevation)
134
124
  @bounds.add(seg.bounds)
135
125
  @distance += seg.distance
136
126
  @moving_duration += seg.duration
@@ -145,6 +135,5 @@ module GPX
145
135
  @moving_duration = 0.0
146
136
  @points = []
147
137
  end
148
-
149
138
  end
150
139
  end
@@ -0,0 +1,30 @@
1
+ module GPX
2
+ # Basically the same as a point, the TrackPoint class is supposed to
3
+ # represent the points that are children of Segment elements. So, the only
4
+ # real difference is that TrackPoints hold a reference to their parent
5
+ # Segments.
6
+ class TrackPoint < Point
7
+ RADIUS = 6371 # earth's mean radius in km
8
+
9
+ attr_accessor :segment
10
+
11
+ def initialize(opts = {})
12
+ super(opts)
13
+ @segment = opts[:segment]
14
+ end
15
+
16
+ # Units are in km
17
+ def haversine_distance_from(p2)
18
+ d_lat = p2.latr - latr
19
+ d_lon = p2.lonr - lonr
20
+ a = Math.sin(d_lat / 2) * Math.sin(d_lat / 2) + Math.cos(latr) * Math.cos(p2.latr) * Math.sin(d_lon / 2) * Math.sin(d_lon / 2)
21
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
22
+ RADIUS * c
23
+ end
24
+
25
+ # Units are in km
26
+ def law_of_cosines_distance_from(p2)
27
+ Math.acos(Math.sin(latr) * Math.sin(p2.latr) + Math.cos(latr) * Math.cos(p2.latr) * Math.cos(p2.lonr - lonr)) * RADIUS
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module GPX
2
- VERSION = "0.9.0"
2
+ VERSION = "1.0.0".freeze
3
3
  end
@@ -1,56 +1,29 @@
1
- #--
2
- # Copyright (c) 2006 Doug Fales
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
1
  module GPX
24
2
  # This class supports the concept of a waypoint. Beware that this class has
25
3
  # not seen much use yet, since WalkingBoss does not use waypoints right now.
26
4
  class Waypoint < Point
27
-
28
- SUB_ELEMENTS = %w{ele magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions}
5
+ SUB_ELEMENTS = %w[ele magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions].freeze
29
6
 
30
7
  attr_reader :gpx_file
31
8
  SUB_ELEMENTS.each { |sub_el| attr_accessor sub_el.to_sym }
32
9
 
33
10
  # Not implemented
34
- def crop(area)
35
- end
11
+ def crop(area); end
36
12
 
37
13
  # Not implemented
38
- def delete_area(area)
39
- end
14
+ def delete_area(area); end
40
15
 
41
16
  # Initializes a waypoint from a XML::Node.
42
17
  def initialize(opts = {})
43
- if(opts[:element] and opts[:gpx_file])
18
+ if opts[:element] && opts[:gpx_file]
44
19
  wpt_elem = opts[:element]
45
20
  @gpx_file = opts[:gpx_file]
46
- super(:element => wpt_elem, :gpx_file => @gpx_file)
21
+ super(element: wpt_elem, gpx_file: @gpx_file)
47
22
  instantiate_with_text_elements(wpt_elem, SUB_ELEMENTS)
48
23
  else
49
24
  opts.each do |key, value|
50
25
  assignment_method = "#{key}="
51
- if self.respond_to?(assignment_method)
52
- self.send(assignment_method, value)
53
- end
26
+ send(assignment_method, value) if respond_to?(assignment_method)
54
27
  end
55
28
  end
56
29
  end
@@ -65,7 +38,7 @@ module GPX
65
38
  result << "\tElevation: #{elevation}\n "
66
39
  result << "\tTime: #{time}\n"
67
40
  SUB_ELEMENTS.each do |sub_element_attribute|
68
- val = self.send(sub_element_attribute)
41
+ val = send(sub_element_attribute)
69
42
  result << "\t#{sub_element_attribute}: #{val}\n" unless val.nil?
70
43
  end
71
44
  result
@@ -2,11 +2,10 @@ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
4
  class GPX10Test < Minitest::Test
5
- GPX_FILE = File.join(File.dirname(__FILE__), "gpx_files/gpx10.gpx")
6
-
7
- def test_read
8
- # make sure we can read a GPX 1.0 file
9
- @gpx_file = GPX::GPXFile.new(:gpx_file => GPX_FILE)
10
- end
5
+ GPX_FILE = File.join(File.dirname(__FILE__), 'gpx_files/gpx10.gpx')
11
6
 
7
+ def test_read
8
+ # make sure we can read a GPX 1.0 file
9
+ @gpx_file = GPX::GPXFile.new(gpx_file: GPX_FILE)
10
+ end
12
11
  end
@@ -2,18 +2,18 @@ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
4
  class GPXFileTest < Minitest::Test
5
-
6
- ONE_TRACK_FILE = File.join(File.dirname(__FILE__), "gpx_files/one_track.gpx")
7
- WITH_OR_WITHOUT_ELEV_FILE = File.join(File.dirname(__FILE__), "gpx_files/with_or_without_elev.gpx")
8
- BIG_FILE = File.join(File.dirname(__FILE__), "gpx_files/big.gpx")
5
+ ONE_TRACK_FILE = File.join(File.dirname(__FILE__), 'gpx_files/one_track.gpx')
6
+ WITH_OR_WITHOUT_ELEV_FILE = File.join(File.dirname(__FILE__), 'gpx_files/with_or_without_elev.gpx')
7
+ WITH_EMPTY_TRACKS = File.join(File.dirname(__FILE__), 'gpx_files/with_empty_tracks.gpx')
8
+ BIG_FILE = File.join(File.dirname(__FILE__), 'gpx_files/big.gpx')
9
9
 
10
10
  def test_load_data_from_string
11
- gpx_file = GPX::GPXFile.new(:gpx_data => open(ONE_TRACK_FILE).read)
11
+ gpx_file = GPX::GPXFile.new(gpx_data: File.open(ONE_TRACK_FILE).read)
12
12
  assert_equal(1, gpx_file.tracks.size)
13
13
  assert_equal(8, gpx_file.tracks.first.segments.size)
14
- assert_equal("ACTIVE LOG", gpx_file.tracks.first.name)
15
- assert_equal("active_log.gpx", gpx_file.name)
16
- assert_equal("2006-04-08T16:44:28Z", gpx_file.time.xmlschema)
14
+ assert_equal('ACTIVE LOG', gpx_file.tracks.first.name)
15
+ assert_equal('active_log.gpx', gpx_file.name)
16
+ assert_equal('2006-04-08T16:44:28Z', gpx_file.time.xmlschema)
17
17
  assert_equal(38.681488, gpx_file.bounds.min_lat)
18
18
  assert_equal(-109.606948, gpx_file.bounds.min_lon)
19
19
  assert_equal(38.791759, gpx_file.bounds.max_lat)
@@ -21,45 +21,55 @@ class GPXFileTest < Minitest::Test
21
21
  assert_equal('description of my GPX file with special char like &, <, >', gpx_file.description)
22
22
  assert_equal('description of my GPX file with special char like &, <, >', gpx_file.description)
23
23
  assert_equal(3.0724966849262554, gpx_file.distance)
24
- assert_equal(15237.0, gpx_file.duration)
24
+ assert_equal(15_237.0, gpx_file.duration)
25
25
  assert_equal(3036.0, gpx_file.moving_duration)
26
26
  assert_equal(3.6432767014935834, gpx_file.average_speed)
27
27
  end
28
28
 
29
29
  def test_load_data_from_file
30
- gpx_file = GPX::GPXFile.new(:gpx_file => ONE_TRACK_FILE)
30
+ gpx_file = GPX::GPXFile.new(gpx_file: ONE_TRACK_FILE)
31
31
  assert_equal(1, gpx_file.tracks.size)
32
32
  assert_equal(8, gpx_file.tracks.first.segments.size)
33
- assert_equal("ACTIVE LOG", gpx_file.tracks.first.name)
34
- assert_equal("active_log.gpx", gpx_file.name)
35
- assert_equal("2006-04-08T16:44:28Z", gpx_file.time.xmlschema)
33
+ assert_equal('ACTIVE LOG', gpx_file.tracks.first.name)
34
+ assert_equal('active_log.gpx', gpx_file.name)
35
+ assert_equal('2006-04-08T16:44:28Z', gpx_file.time.xmlschema)
36
36
  assert_equal(38.681488, gpx_file.bounds.min_lat)
37
37
  assert_equal(-109.606948, gpx_file.bounds.min_lon)
38
38
  assert_equal(38.791759, gpx_file.bounds.max_lat)
39
39
  assert_equal(-109.447045, gpx_file.bounds.max_lon)
40
40
  assert_equal('description of my GPX file with special char like &, <, >', gpx_file.description)
41
41
  assert_equal(3.0724966849262554, gpx_file.distance)
42
- assert_equal(15237.0, gpx_file.duration)
42
+ assert_equal(15_237.0, gpx_file.duration)
43
43
  assert_equal(3036.0, gpx_file.moving_duration)
44
44
  assert_equal(3.6432767014935834, gpx_file.average_speed)
45
45
  end
46
46
 
47
47
  def test_big_file
48
- gpx_file = GPX::GPXFile.new(:gpx_file => BIG_FILE)
48
+ gpx_file = GPX::GPXFile.new(gpx_file: BIG_FILE)
49
49
  assert_equal(1, gpx_file.tracks.size)
50
50
  assert_equal(7968, gpx_file.tracks.first.points.size)
51
- assert_equal(105508.0, gpx_file.duration)
52
- assert_equal(57645.0, gpx_file.moving_duration)
51
+ assert_equal(105_508.0, gpx_file.duration)
52
+ assert_equal(57_645.0, gpx_file.moving_duration)
53
53
  assert_in_delta(99.60738958686505, gpx_file.average_speed, 1e-13)
54
54
  end
55
55
 
56
56
  def test_with_or_with_elev
57
- gpx_file = GPX::GPXFile.new(:gpx_file => WITH_OR_WITHOUT_ELEV_FILE)
57
+ gpx_file = GPX::GPXFile.new(gpx_file: WITH_OR_WITHOUT_ELEV_FILE)
58
58
  assert_equal(2, gpx_file.tracks.size)
59
59
  assert_equal(0, gpx_file.duration)
60
60
  assert_equal(0, gpx_file.moving_duration)
61
61
  assert(gpx_file.average_speed.nan?)
62
- #assert_equal(7968, gpx_file.tracks.first.points.size)
62
+ # assert_equal(7968, gpx_file.tracks.first.points.size)
63
63
  end
64
64
 
65
+ def test_with_empty_tracks
66
+ gpx_file = GPX::GPXFile.new(gpx_file: WITH_EMPTY_TRACKS)
67
+ # is read correctly
68
+ assert_equal(1, gpx_file.tracks.size)
69
+ # and ignores empty segments
70
+ assert_equal(1, gpx_file.tracks.first.segments.size)
71
+ assert_equal(21.0, gpx_file.duration)
72
+ assert_equal(21.0, gpx_file.moving_duration)
73
+ assert_equal(6.674040636626879, gpx_file.average_speed)
74
+ end
65
75
  end
@@ -0,0 +1,72 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="Open GPX Tracker for iOS">
3
+ <wpt lat="51.185170" lon="4.662453">
4
+ <time>2018-01-16T11:35:01Z</time>
5
+ <name>12:35:01</name>
6
+ <desc>16 jan. 2018 12:35:01</desc>
7
+ </wpt>
8
+ <trk>
9
+ <trkseg>
10
+ <trkpt lat="51.183019" lon="4.664619">
11
+ <ele>0.751247</ele>
12
+ <time>2018-01-16T11:06:00Z</time>
13
+ </trkpt>
14
+ <trkpt lat="51.183037" lon="4.664587">
15
+ <ele>0.622036</ele>
16
+ <time>2018-01-16T11:06:02Z</time>
17
+ </trkpt>
18
+ <trkpt lat="51.183053" lon="4.664553">
19
+ <ele>0.042019</ele>
20
+ <time>2018-01-16T11:06:04Z</time>
21
+ </trkpt>
22
+ <trkpt lat="51.183073" lon="4.664515">
23
+ <ele>0.177456</ele>
24
+ <time>2018-01-16T11:06:06Z</time>
25
+ </trkpt>
26
+ <trkpt lat="51.183095" lon="4.664480">
27
+ <ele>0.564846</ele>
28
+ <time>2018-01-16T11:06:08Z</time>
29
+ </trkpt>
30
+ <trkpt lat="51.183115" lon="4.664446">
31
+ <ele>0.941067</ele>
32
+ <time>2018-01-16T11:06:10Z</time>
33
+ </trkpt>
34
+ <trkpt lat="51.183143" lon="4.664401">
35
+ <ele>1.960781</ele>
36
+ <time>2018-01-16T11:06:12Z</time>
37
+ </trkpt>
38
+ <trkpt lat="51.183160" lon="4.664372">
39
+ <ele>2.115200</ele>
40
+ <time>2018-01-16T11:06:13Z</time>
41
+ </trkpt>
42
+ <trkpt lat="51.183176" lon="4.664342">
43
+ <ele>2.191250</ele>
44
+ <time>2018-01-16T11:06:14Z</time>
45
+ </trkpt>
46
+ <trkpt lat="51.183192" lon="4.664315">
47
+ <ele>2.492947</ele>
48
+ <time>2018-01-16T11:06:15Z</time>
49
+ </trkpt>
50
+ <trkpt lat="51.183205" lon="4.664291">
51
+ <ele>2.459927</ele>
52
+ <time>2018-01-16T11:06:16Z</time>
53
+ </trkpt>
54
+ <trkpt lat="51.183217" lon="4.664270">
55
+ <ele>2.226833</ele>
56
+ <time>2018-01-16T11:06:17Z</time>
57
+ </trkpt>
58
+ <trkpt lat="51.183240" lon="4.664233">
59
+ <ele>2.175198</ele>
60
+ <time>2018-01-16T11:06:19Z</time>
61
+ </trkpt>
62
+ <trkpt lat="51.183255" lon="4.664207">
63
+ <ele>2.643887</ele>
64
+ <time>2018-01-16T11:06:21Z</time>
65
+ </trkpt>
66
+ </trkseg>
67
+ <trkseg>
68
+ </trkseg>
69
+ <trkseg>
70
+ </trkseg>
71
+ </trk>
72
+ </gpx>
@@ -2,17 +2,16 @@ require 'minitest/autorun'
2
2
  require 'gpx'
3
3
 
4
4
  class MagellanTest < Minitest::Test
5
- MAGELLAN_TRACK_LOG = File.join(File.dirname(__FILE__), "gpx_files/magellan_track.log")
6
- GPX_FILE = File.join(File.dirname(__FILE__), "gpx_files/one_segment.gpx")
5
+ MAGELLAN_TRACK_LOG = File.join(File.dirname(__FILE__), 'gpx_files/magellan_track.log')
6
+ GPX_FILE = File.join(File.dirname(__FILE__), 'gpx_files/one_segment.gpx')
7
7
 
8
- def test_convert
9
- GPX::MagellanTrackLog.convert_to_gpx(MAGELLAN_TRACK_LOG, "/tmp/gpx_from_magellan.gpx")
10
- @gpx_file = GPX::GPXFile.new(:gpx_file => "/tmp/gpx_from_magellan.gpx")
11
- end
12
-
13
- def test_file_type
14
- assert(GPX::MagellanTrackLog::is_magellan_file?(MAGELLAN_TRACK_LOG))
15
- assert(!GPX::MagellanTrackLog::is_magellan_file?(GPX_FILE))
16
- end
8
+ def test_convert
9
+ GPX::MagellanTrackLog.convert_to_gpx(MAGELLAN_TRACK_LOG, '/tmp/gpx_from_magellan.gpx')
10
+ @gpx_file = GPX::GPXFile.new(gpx_file: '/tmp/gpx_from_magellan.gpx')
11
+ end
17
12
 
13
+ def test_file_type
14
+ assert(GPX::MagellanTrackLog.magellan_file?(MAGELLAN_TRACK_LOG))
15
+ assert(!GPX::MagellanTrackLog.magellan_file?(GPX_FILE))
16
+ end
18
17
  end