gpx 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/gpx/route.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
4
  # A Route in GPX is very similar to a Track, but it is created by a user
3
5
  # from a series of Waypoints, whereas a Track is created by the GPS device
@@ -7,10 +9,11 @@ module GPX
7
9
 
8
10
  # Initialize a Route from a XML::Node.
9
11
  def initialize(opts = {})
12
+ super()
10
13
  if opts[:gpx_file] && opts[:element]
11
14
  rte_element = opts[:element]
12
15
  @gpx_file = opts[:gpx_file]
13
- @name = rte_element.at('name').inner_text
16
+ @name = rte_element.at('name')&.inner_text
14
17
  @points = []
15
18
  rte_element.search('rtept').each do |point|
16
19
  @points << Point.new(element: point, gpx_file: @gpx_file)
data/lib/gpx/segment.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
4
  # A segment is the basic container in a GPX file. A Segment contains points
3
5
  # (in this lib, they're called TrackPoints). A Track contains Segments. An
@@ -10,6 +12,7 @@ module GPX
10
12
  # If a XML::Node object is passed-in, this will initialize a new
11
13
  # Segment based on its contents. Otherwise, a blank Segment is created.
12
14
  def initialize(opts = {})
15
+ super()
13
16
  @gpx_file = opts[:gpx_file]
14
17
  @track = opts[:track]
15
18
  @points = []
@@ -22,7 +25,8 @@ module GPX
22
25
  @bounds = Bounds.new
23
26
 
24
27
  segment_element = opts[:element]
25
- return unless segment_element && segment_element.is_a?(Nokogiri::XML::Node)
28
+ return unless segment_element.is_a?(Nokogiri::XML::Node)
29
+
26
30
  segment_element.search('trkpt').each do |trkpt|
27
31
  pt = TrackPoint.new(element: trkpt, segment: self, gpx_file: @gpx_file)
28
32
  append_point(pt)
@@ -33,8 +37,8 @@ module GPX
33
37
  def append_point(pt)
34
38
  last_pt = @points[-1]
35
39
  if pt.time
36
- @earliest_point = pt if @earliest_point.nil? || (pt.time < @earliest_point.time)
37
- @latest_point = pt if @latest_point.nil? || (pt.time > @latest_point.time)
40
+ @earliest_point = pt if @earliest_point.nil? || (@earliest_point.time && pt.time < @earliest_point.time)
41
+ @latest_point = pt if @latest_point.nil? || (@latest_point.time && pt.time > @latest_point.time)
38
42
  else
39
43
  # when no time information in data, we consider the points are ordered
40
44
  @earliest_point = @points[0]
@@ -91,6 +95,7 @@ module GPX
91
95
  last_pt = nil
92
96
  points.each do |pt|
93
97
  next if yield(pt)
98
+
94
99
  keep_points << pt
95
100
  update_meta_data(pt, last_pt)
96
101
  last_pt = pt
@@ -124,7 +129,7 @@ module GPX
124
129
  elsif indicator.is_a?(Time)
125
130
  closest_point(indicator)
126
131
  else
127
- raise Exception, 'find_end_point_by_time_or_offset requires an argument of type Time or Integer'
132
+ raise ArgumentError, 'find_end_point_by_time_or_offset requires an argument of type Time or Integer'
128
133
  end
129
134
  end
130
135
 
@@ -174,9 +179,9 @@ module GPX
174
179
 
175
180
  protected
176
181
 
177
- # rubocop:disable Style/GuardClause
178
182
  def find_closest(pts, time)
179
183
  return pts.first if pts.size == 1
184
+
180
185
  midpoint = pts.size / 2
181
186
  if pts.size == 2
182
187
  diff_1 = pts[0].time - time
@@ -184,14 +189,13 @@ module GPX
184
189
  return (diff_1 < diff_2 ? pts[0] : pts[1])
185
190
  end
186
191
  if (time >= pts[midpoint].time) && (time <= pts[midpoint + 1].time)
187
- return pts[midpoint]
192
+ pts[midpoint]
188
193
  elsif time <= pts[midpoint].time
189
- return find_closest(pts[0..midpoint], time)
194
+ find_closest(pts[0..midpoint], time)
190
195
  else
191
- return find_closest(pts[(midpoint + 1)..-1], time)
196
+ find_closest(pts[(midpoint + 1)..-1], time)
192
197
  end
193
198
  end
194
- # rubocop:enable Style/GuardClause
195
199
 
196
200
  # Calculate the Haversine distance between two points. This is the method
197
201
  # the library uses to calculate the cumulative distance of GPX files.
@@ -231,6 +235,7 @@ module GPX
231
235
  @bounds.add(pt)
232
236
 
233
237
  return unless last_pt
238
+
234
239
  @distance += haversine_distance(last_pt, pt)
235
240
  @duration += pt.time - last_pt.time if pt.time && last_pt.time
236
241
  end
data/lib/gpx/track.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
4
  # In GPX, a single Track can hold multiple Segments, each of which hold
3
5
  # multiple points (in this library, those points are instances of
@@ -12,6 +14,7 @@ module GPX
12
14
  # Initialize a track from a XML::Node, or, if no :element option is
13
15
  # passed, initialize a blank Track object.
14
16
  def initialize(opts = {})
17
+ super()
15
18
  @gpx_file = opts[:gpx_file]
16
19
  @segments = []
17
20
  @points = []
@@ -20,17 +23,20 @@ module GPX
20
23
  return unless opts[:element]
21
24
 
22
25
  trk_element = opts[:element]
23
- @name = (begin
26
+ @name = (
27
+ begin
24
28
  trk_element.at('name').inner_text
25
29
  rescue StandardError
26
30
  ''
27
31
  end)
28
- @comment = (begin
32
+ @comment = (
33
+ begin
29
34
  trk_element.at('cmt').inner_text
30
35
  rescue StandardError
31
36
  ''
32
37
  end)
33
- @description = (begin
38
+ @description = (
39
+ begin
34
40
  trk_element.at('desc').inner_text
35
41
  rescue StandardError
36
42
  ''
@@ -44,6 +50,7 @@ module GPX
44
50
  # Append a segment to this track, updating its meta data along the way.
45
51
  def append_segment(seg)
46
52
  return if seg.points.empty?
53
+
47
54
  update_meta_data(seg)
48
55
  @segments << seg
49
56
  end
@@ -88,7 +95,7 @@ module GPX
88
95
  # Returns true if this track has no points in it. This should return
89
96
  # true even when the track has empty segments.
90
97
  def empty?
91
- (points.nil? || points.size.zero?)
98
+ (points.nil? || points.empty?)
92
99
  end
93
100
 
94
101
  # Prints out a friendly summary of this track (sans points). Useful for
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
4
  # Basically the same as a point, the TrackPoint class is supposed to
3
5
  # represent the points that are children of Segment elements. So, the only
data/lib/gpx/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
- VERSION = "1.0.0".freeze
4
+ VERSION = "1.1.0"
3
5
  end
data/lib/gpx/waypoint.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module GPX
2
4
  # This class supports the concept of a waypoint. Beware that this class has
3
5
  # not seen much use yet, since WalkingBoss does not use waypoints right now.
@@ -5,6 +7,7 @@ module GPX
5
7
  SUB_ELEMENTS = %w[ele magvar geoidheight name cmt desc src link sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid extensions].freeze
6
8
 
7
9
  attr_reader :gpx_file
10
+
8
11
  SUB_ELEMENTS.each { |sub_el| attr_accessor sub_el.to_sym }
9
12
 
10
13
  # Not implemented
data/lib/gpx.rb CHANGED
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'nokogiri'
3
5
 
4
6
  require File.expand_path('gpx/version', __dir__)
5
-
6
7
  require File.expand_path('gpx/gpx', __dir__)
7
8
  require File.expand_path('gpx/gpx_file', __dir__)
8
9
  require File.expand_path('gpx/bounds', __dir__)
@@ -13,3 +14,4 @@ require File.expand_path('gpx/point', __dir__)
13
14
  require File.expand_path('gpx/track_point', __dir__)
14
15
  require File.expand_path('gpx/waypoint', __dir__)
15
16
  require File.expand_path('gpx/magellan_track_log', __dir__)
17
+ require File.expand_path('gpx/geo_json', __dir__)
@@ -0,0 +1,68 @@
1
+ {
2
+ "type": "FeatureCollection",
3
+ "features": [{
4
+ "type": "Feature",
5
+ "geometry": {
6
+ "type": "LineString",
7
+ "coordinates": [
8
+ [-118.41511, 34.06298, 80],
9
+ [-118.41506, 34.06301, 80],
10
+ [-118.415, 34.06305, 80],
11
+ [-118.41494, 34.06309, 80]
12
+ ]
13
+ }
14
+ }, {
15
+ "type": "Feature",
16
+ "geometry": {
17
+ "type": "LineString",
18
+ "coordinates": [
19
+ [-118.3689, 34.05826, 40],
20
+ [-118.36894, 34.05818, 40],
21
+ [-118.369, 34.05806, 40]
22
+ ]
23
+ }
24
+ }, {
25
+ "type": "Feature",
26
+ "geometry": {
27
+ "type": "LineString",
28
+ "coordinates": [
29
+ [-118.35042, 34.03788, 30],
30
+ [-118.35046, 34.03779, 30]
31
+ ]
32
+ }
33
+ }, {
34
+ "type": "Feature",
35
+ "geometry": {
36
+ "type": "MultiLineString",
37
+ "coordinates": [
38
+ [
39
+ [-118.41511, 34.06298, 80],
40
+ [-118.41506, 34.06301, 80],
41
+ [-118.415, 34.06305, 80],
42
+ [-118.41494, 34.06309, 80]
43
+ ],
44
+ [
45
+ [-118.3689, 34.05826, 40],
46
+ [-118.36894, 34.05818, 40],
47
+ [-118.369, 34.05806, 40]
48
+ ]
49
+ ]
50
+ }
51
+ }, {
52
+ "type": "Feature",
53
+ "geometry": {
54
+ "type": "Point",
55
+ "coordinates": [-118.41585, 34.06253, 80]
56
+ }
57
+ }, {
58
+ "type": "Feature",
59
+ "geometry": {
60
+ "type": "MultiPoint",
61
+ "coordinates": [
62
+ [-118.41585, 34.06253, 80],
63
+ [-118.4158, 34.06256, 80],
64
+ [-118.41575, 34.06259, 80]
65
+ ]
66
+ }
67
+ }]
68
+ }
@@ -0,0 +1,83 @@
1
+ {
2
+ "type": "FeatureCollection",
3
+ "features": [{
4
+ "type": "Feature",
5
+ "geometry": {
6
+ "type": "LineString",
7
+ "coordinates": [
8
+ [-118.41585, 34.06253, 80],
9
+ [-118.4158, 34.06256, 80],
10
+ [-118.41575, 34.06259, 80],
11
+ [-118.4157, 34.06262, 80],
12
+ [-118.41566, 34.06265, 80],
13
+ [-118.41561, 34.06268, 80],
14
+ [-118.41556, 34.06271, 80],
15
+ [-118.41551, 34.06274, 80],
16
+ [-118.41546, 34.06277, 80],
17
+ [-118.41541, 34.0628, 80],
18
+ [-118.41536, 34.06283, 80],
19
+ [-118.41531, 34.06286, 80],
20
+ [-118.41526, 34.06289, 80],
21
+ [-118.41521, 34.06292, 80],
22
+ [-118.41516, 34.06295, 80],
23
+ [-118.41511, 34.06298, 80],
24
+ [-118.41506, 34.06301, 80],
25
+ [-118.415, 34.06305, 80],
26
+ [-118.41494, 34.06309, 80]
27
+ ]
28
+ }
29
+ }, {
30
+ "type": "Feature",
31
+ "geometry": {
32
+ "type": "LineString",
33
+ "coordinates": [
34
+ [-118.36855, 34.05844, 40],
35
+ [-118.36849, 34.05843, 40],
36
+ [-118.36843, 34.05842, 40],
37
+ [-118.36837, 34.05841, 40],
38
+ [-118.36906, 34.05878, 40],
39
+ [-118.36895, 34.05877, 40],
40
+ [-118.36884, 34.05876, 40],
41
+ [-118.36873, 34.05875, 40],
42
+ [-118.36866, 34.05874, 40],
43
+ [-118.3687, 34.05866, 40],
44
+ [-118.36874, 34.05858, 40],
45
+ [-118.36878, 34.0585, 40],
46
+ [-118.36882, 34.05842, 40],
47
+ [-118.36886, 34.05834, 40],
48
+ [-118.3689, 34.05826, 40],
49
+ [-118.36894, 34.05818, 40],
50
+ [-118.369, 34.05806, 40]
51
+ ]
52
+ }
53
+ }, {
54
+ "type": "Feature",
55
+ "geometry": {
56
+ "type": "LineString",
57
+ "coordinates": [
58
+ [-118.35043, 34.0392, 30],
59
+ [-118.35047, 34.03911, 30],
60
+ [-118.35051, 34.03902, 30],
61
+ [-118.35055, 34.03893, 30],
62
+ [-118.35057, 34.03887, 30],
63
+ [-118.35045, 34.03885, 30],
64
+ [-118.35033, 34.03883, 30],
65
+ [-118.35021, 34.03881, 30],
66
+ [-118.35009, 34.03879, 30],
67
+ [-118.35002, 34.03878, 30],
68
+ [-118.35006, 34.03869, 30],
69
+ [-118.3501, 34.0386, 30],
70
+ [-118.35014, 34.03851, 30],
71
+ [-118.35018, 34.03842, 30],
72
+ [-118.35022, 34.03833, 30],
73
+ [-118.35026, 34.03824, 30],
74
+ [-118.3503, 34.03815, 30],
75
+ [-118.35034, 34.03806, 30],
76
+ [-118.35038, 34.03797, 30],
77
+ [-118.35042, 34.03788, 30],
78
+ [-118.35046, 34.03779, 30],
79
+ [-118.3505, 34.03768, 30]
80
+ ]
81
+ }
82
+ }]
83
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "type": "FeatureCollection",
3
+ "features": [{
4
+ "type": "Feature",
5
+ "geometry": {
6
+ "type": "MultiLineString",
7
+ "coordinates": [
8
+ [
9
+ [-118.41585, 34.06253, 80],
10
+ [-118.4158, 34.06256, 80],
11
+ [-118.41575, 34.06259, 80],
12
+ [-118.4157, 34.06262, 80],
13
+ [-118.41566, 34.06265, 80],
14
+ [-118.41561, 34.06268, 80],
15
+ [-118.41556, 34.06271, 80],
16
+ [-118.41551, 34.06274, 80],
17
+ [-118.41546, 34.06277, 80],
18
+ [-118.41541, 34.0628, 80],
19
+ [-118.41536, 34.06283, 80],
20
+ [-118.41531, 34.06286, 80],
21
+ [-118.41526, 34.06289, 80],
22
+ [-118.41521, 34.06292, 80],
23
+ [-118.41516, 34.06295, 80],
24
+ [-118.41511, 34.06298, 80],
25
+ [-118.41506, 34.06301, 80],
26
+ [-118.415, 34.06305, 80],
27
+ [-118.41494, 34.06309, 80]
28
+ ],
29
+ [
30
+ [-118.36855, 34.05844, 40],
31
+ [-118.36849, 34.05843, 40],
32
+ [-118.36843, 34.05842, 40],
33
+ [-118.36837, 34.05841, 40],
34
+ [-118.36906, 34.05878, 40],
35
+ [-118.36895, 34.05877, 40],
36
+ [-118.36884, 34.05876, 40],
37
+ [-118.36873, 34.05875, 40],
38
+ [-118.36866, 34.05874, 40],
39
+ [-118.3687, 34.05866, 40],
40
+ [-118.36874, 34.05858, 40],
41
+ [-118.36878, 34.0585, 40],
42
+ [-118.36882, 34.05842, 40],
43
+ [-118.36886, 34.05834, 40],
44
+ [-118.3689, 34.05826, 40],
45
+ [-118.36894, 34.05818, 40],
46
+ [-118.369, 34.05806, 40]
47
+ ],
48
+ [
49
+ [-118.35043, 34.0392, 30],
50
+ [-118.35047, 34.03911, 30],
51
+ [-118.35051, 34.03902, 30],
52
+ [-118.35055, 34.03893, 30],
53
+ [-118.35057, 34.03887, 30],
54
+ [-118.35045, 34.03885, 30],
55
+ [-118.35033, 34.03883, 30],
56
+ [-118.35021, 34.03881, 30],
57
+ [-118.35009, 34.03879, 30],
58
+ [-118.35002, 34.03878, 30],
59
+ [-118.35006, 34.03869, 30],
60
+ [-118.3501, 34.0386, 30],
61
+ [-118.35014, 34.03851, 30],
62
+ [-118.35018, 34.03842, 30],
63
+ [-118.35022, 34.03833, 30],
64
+ [-118.35026, 34.03824, 30],
65
+ [-118.3503, 34.03815, 30],
66
+ [-118.35034, 34.03806, 30],
67
+ [-118.35038, 34.03797, 30],
68
+ [-118.35042, 34.03788, 30],
69
+ [-118.35046, 34.03779, 30],
70
+ [-118.3505, 34.03768, 30]
71
+ ]]
72
+ }
73
+ }]
74
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "type": "FeatureCollection",
3
+ "features": [{
4
+ "type": "Feature",
5
+ "geometry": {
6
+ "type": "MultiPoint",
7
+ "coordinates": [
8
+ [-118.41585, 34.06253, 80],
9
+ [-118.4158, 34.06256, 80],
10
+ [-118.41575, 34.06259, 80]
11
+ ]
12
+ }
13
+ }]
14
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "type": "FeatureCollection",
3
+ "features": [{
4
+ "type": "Feature",
5
+ "geometry": {
6
+ "type": "Point",
7
+ "coordinates": [-118.41585, 34.06253, 80]
8
+ }
9
+ }, {
10
+ "type": "Feature",
11
+ "geometry": {
12
+ "type": "Point",
13
+ "coordinates": [-118.36855, 34.05844, 40]
14
+ }
15
+ }, {
16
+ "type": "Feature",
17
+ "geometry": {
18
+ "type": "Point",
19
+ "coordinates": [-118.35043, 34.0392, 30]
20
+ }
21
+ }]
22
+ }
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'gpx'
5
+ require 'json'
6
+
7
+ class GeojsonTest < Minitest::Test
8
+ # Test passing a file name
9
+ def test_geojson_file_name_as_param
10
+ file_name = "#{File.dirname(__FILE__)}/geojson_files/line_string_data.json"
11
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file_name)
12
+ assert_equal(1, gpx_file.tracks.size)
13
+ end
14
+
15
+ # Test passing a file
16
+ def test_geojson_file_as_param
17
+ file_name = "#{File.dirname(__FILE__)}/geojson_files/line_string_data.json"
18
+ file = File.new(file_name, 'r')
19
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file)
20
+ assert_equal(1, gpx_file.tracks.size)
21
+ end
22
+
23
+ def test_raises_arg_error_when_no_params
24
+ assert_raises(ArgumentError) do
25
+ GPX::GeoJSON.convert_to_gpx
26
+ end
27
+ end
28
+
29
+ # Test that lat/lon allocated correctly
30
+ def test_point_to_waypoint
31
+ pt = [-118, 34]
32
+ waypoint = GPX::GeoJSON.send(:point_to_waypoint, pt, nil)
33
+ assert_equal(34, waypoint.lat)
34
+ assert_equal(-118, waypoint.lon)
35
+ end
36
+
37
+ # Test that lat/lon allocated correctly
38
+ def test_point_to_trackpoint
39
+ pt = [-118, 34]
40
+ waypoint = GPX::GeoJSON.send(:point_to_track_point, pt, nil)
41
+ assert_equal(34, waypoint.lat)
42
+ assert_equal(-118, waypoint.lon)
43
+ end
44
+
45
+ def test_line_string_functionality
46
+ file = File.join(File.dirname(__FILE__), 'geojson_files/line_string_data.json')
47
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file)
48
+
49
+ assert_equal(1, gpx_file.tracks.size)
50
+ assert_equal(3, gpx_file.tracks.first.segments.size)
51
+ pts_size = gpx_file.tracks.first.segments[0].points.size +
52
+ gpx_file.tracks.first.segments[1].points.size +
53
+ gpx_file.tracks.first.segments[2].points.size
54
+ assert_equal(58, pts_size)
55
+ end
56
+
57
+ def test_multi_line_string_functionality
58
+ file = File.join(File.dirname(__FILE__), 'geojson_files/multi_line_string_data.json')
59
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file)
60
+ assert_equal(1, gpx_file.tracks.size)
61
+ assert_equal(3, gpx_file.tracks.first.segments.size)
62
+ pts_size = gpx_file.tracks.first.segments[0].points.size +
63
+ gpx_file.tracks.first.segments[1].points.size +
64
+ gpx_file.tracks.first.segments[2].points.size
65
+ assert_equal(58, pts_size)
66
+ end
67
+
68
+ def test_point_functionality
69
+ file = File.join(File.dirname(__FILE__), 'geojson_files/point_data.json')
70
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file)
71
+ assert_equal(3, gpx_file.waypoints.size)
72
+ end
73
+
74
+ def test_multi_point_functionality
75
+ file = File.join(File.dirname(__FILE__), 'geojson_files/multi_point_data.json')
76
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file)
77
+ assert_equal(3, gpx_file.waypoints.size)
78
+ end
79
+
80
+ def test_combined_functionality
81
+ file = File.join(File.dirname(__FILE__), 'geojson_files/combined_data.json')
82
+ gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: file)
83
+
84
+ # 1 for all LineStrings, 1 for MultiLineString
85
+ assert_equal(2, gpx_file.tracks.size)
86
+ assert_equal(3, gpx_file.tracks.first.segments.size)
87
+ assert_equal(2, gpx_file.tracks.last.segments.size)
88
+ pt_sum = gpx_file.tracks.inject(0) { |sum, trk| sum + trk.points.size }
89
+ assert_equal(16, pt_sum)
90
+ assert_equal(4, gpx_file.waypoints.size)
91
+ end
92
+ end
data/tests/gpx10_test.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'minitest/autorun'
2
4
  require 'gpx'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'minitest/autorun'
2
4
  require 'gpx'
3
5