gpx 0.8.3 → 1.1.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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop +1 -0
  5. data/.rubocop.yml +162 -0
  6. data/.ruby-version +1 -0
  7. data/.tool-versions +1 -0
  8. data/.travis.yml +4 -6
  9. data/CHANGELOG.md +32 -0
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +38 -17
  13. data/Rakefile +22 -12
  14. data/UPGRADING.md +7 -0
  15. data/bin/gpx_distance +5 -6
  16. data/bin/gpx_smooth +25 -26
  17. data/gpx.gemspec +14 -11
  18. data/lib/gpx/bounds.rb +13 -31
  19. data/lib/gpx/geo_json.rb +199 -0
  20. data/lib/gpx/gpx.rb +4 -26
  21. data/lib/gpx/gpx_file.rb +140 -134
  22. data/lib/gpx/magellan_track_log.rb +34 -66
  23. data/lib/gpx/point.rb +22 -35
  24. data/lib/gpx/route.rb +10 -31
  25. data/lib/gpx/segment.rb +63 -90
  26. data/lib/gpx/track.rb +38 -42
  27. data/lib/gpx/track_point.rb +32 -0
  28. data/lib/gpx/version.rb +3 -1
  29. data/lib/gpx/waypoint.rb +10 -34
  30. data/lib/gpx.rb +13 -34
  31. data/tests/geojson_files/combined_data.json +68 -0
  32. data/tests/geojson_files/line_string_data.json +83 -0
  33. data/tests/geojson_files/multi_line_string_data.json +74 -0
  34. data/tests/geojson_files/multi_point_data.json +14 -0
  35. data/tests/geojson_files/point_data.json +22 -0
  36. data/tests/geojson_test.rb +92 -0
  37. data/tests/gpx10_test.rb +7 -6
  38. data/tests/gpx_file_test.rb +31 -19
  39. data/tests/gpx_files/one_segment_mixed_times.gpx +884 -0
  40. data/tests/gpx_files/routes_without_names.gpx +29 -0
  41. data/tests/gpx_files/with_empty_tracks.gpx +72 -0
  42. data/tests/magellan_test.rb +12 -11
  43. data/tests/output_test.rb +93 -94
  44. data/tests/route_test.rb +75 -30
  45. data/tests/segment_test.rb +104 -93
  46. data/tests/track_file_test.rb +50 -70
  47. data/tests/track_point_test.rb +22 -11
  48. data/tests/track_test.rb +73 -61
  49. data/tests/waypoint_test.rb +46 -48
  50. metadata +45 -13
  51. data/lib/gpx/trackpoint.rb +0 -60
data/lib/gpx/bounds.rb CHANGED
@@ -1,58 +1,41 @@
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
- #++
1
+ # frozen_string_literal: true
2
+
23
3
  module GPX
24
4
  class Bounds < Base
25
- attr_accessor :min_lat, :max_lat, :max_lon, :min_lon, :center_lat, :center_lon
5
+ attr_accessor :min_lat, :max_lat, :max_lon, :min_lon
26
6
 
27
7
  # Creates a new bounds object with the passed-in min and max longitudes
28
8
  # and latitudes.
29
- def initialize(opts = { :min_lat => 90.0, :max_lat => -90.0, :min_lon => 180.0, :max_lon => -180.0})
30
- @min_lat, @max_lat = opts[:min_lat].to_f, opts[:max_lat].to_f
31
- @min_lon, @max_lon = opts[:min_lon].to_f, opts[:max_lon].to_f
9
+ def initialize(opts = { min_lat: 90.0, max_lat: -90.0, min_lon: 180.0, max_lon: -180.0 })
10
+ super()
11
+ @min_lat = opts[:min_lat].to_f
12
+ @max_lat = opts[:max_lat].to_f
13
+ @min_lon = opts[:min_lon].to_f
14
+ @max_lon = opts[:max_lon].to_f
32
15
  end
33
16
 
34
17
  # Returns the middle latitude.
35
18
  def center_lat
36
- distance = (max_lat - min_lat)/2.0
19
+ distance = (max_lat - min_lat) / 2.0
37
20
  (min_lat + distance)
38
21
  end
39
22
 
40
23
  # Returns the middle longitude.
41
24
  def center_lon
42
- distance = (max_lon - min_lon)/2.0
25
+ distance = (max_lon - min_lon) / 2.0
43
26
  (min_lon + distance)
44
27
  end
45
28
 
46
29
  # Returns true if the pt is within these bounds.
47
30
  def contains?(pt)
48
- (pt.lat >= min_lat and pt.lat <= max_lat and pt.lon >= min_lon and pt.lon <= max_lon)
31
+ ((pt.lat >= min_lat) && (pt.lat <= max_lat) && (pt.lon >= min_lon) && (pt.lon <= max_lon))
49
32
  end
50
33
 
51
34
  # Adds an item to itself, expanding its min/max lat/lon as needed to
52
35
  # contain the given item. The item can be either another instance of
53
36
  # Bounds or a Point.
54
37
  def add(item)
55
- if(item.respond_to?(:lat) and item.respond_to?(:lon))
38
+ if item.respond_to?(:lat) && item.respond_to?(:lon)
56
39
  @min_lat = item.lat if item.lat < @min_lat
57
40
  @min_lon = item.lon if item.lon < @min_lon
58
41
  @max_lat = item.lat if item.lat > @max_lat
@@ -69,6 +52,5 @@ module GPX
69
52
  def to_s
70
53
  "min_lat: #{min_lat} min_lon: #{min_lon} max_lat: #{max_lat} max_lon: #{max_lon}"
71
54
  end
72
-
73
55
  end
74
56
  end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module GPX
6
+ # Class to parse GeoJSON LineStrings, MultiLineStrings, Points,
7
+ # and MultiPoint geometric objects to GPX format. For the full
8
+ # specification of GeoJSON, see:
9
+ # http://geojson.org/geojson-spec.html
10
+ # Note that GeoJSON coordinates are specified in lon/lat format,
11
+ # instead of the more traditional lat/lon format.
12
+ #
13
+ class GeoJSON
14
+ class << self
15
+ FEATURE = 'Feature'
16
+ LINESTRING = 'LineString'
17
+ MULTILINESTRING = 'MultiLineString'
18
+ POINT = 'Point'
19
+ MULTIPOINT = 'MultiPoint'
20
+
21
+ # Conversion can be initiated by either specifying a file,
22
+ # file name, or by passing in GeoJSON data as a string.
23
+ # Examples:
24
+ # GPX::GeoJSON.convert_to_gpx(geojson_file: 'mygeojsonfile.json')
25
+ # or
26
+ # file = File.new('mygeojsonfile.json', 'r')
27
+ # GPX::GeoJSON.convert_to_gpx(geojson_file: file)
28
+ # or
29
+ # data = JSON.generate(my_geojson_hash)
30
+ # GPX::GeoJSON.convert_to_gpx(geojson_data: data)
31
+ #
32
+ # Returns a GPX::GPX_File object populated with the converted data.
33
+ #
34
+ def convert_to_gpx(opts = {})
35
+ geojson = geojson_data_from(opts)
36
+ gpx_file = GPX::GPXFile.new
37
+ add_tracks_to(gpx_file, geojson)
38
+ add_waypoints_to(gpx_file, geojson)
39
+ gpx_file
40
+ end
41
+
42
+ private
43
+
44
+ def geojson_data_from(opts)
45
+ if opts[:geojson_file]
46
+ parse_geojson_data_from_file(opts[:geojson_file])
47
+ elsif opts[:geojson_data]
48
+ parse_geojson_data(opts[:geojson_data])
49
+ else
50
+ raise ArgumentError,
51
+ 'Must pass value for \':geojson_file\' ' \
52
+ 'or \':geojson_data\' to convert_to_gpx'
53
+ end
54
+ end
55
+
56
+ def parse_geojson_data_from_file(filename)
57
+ parse_geojson_data(IO.read(filename))
58
+ end
59
+
60
+ def parse_geojson_data(data)
61
+ JSON.parse(data)
62
+ end
63
+
64
+ def add_tracks_to(gpx_file, geojson)
65
+ tracks = [line_strings_to_track(geojson)] +
66
+ multi_line_strings_to_tracks(geojson)
67
+ tracks.compact!
68
+ gpx_file.tracks += tracks
69
+ gpx_file.tracks.each { |t| gpx_file.update_meta_data(t) }
70
+ end
71
+
72
+ def add_waypoints_to(gpx_file, geojson)
73
+ gpx_file.waypoints +=
74
+ points_to_waypoints(geojson, gpx_file) +
75
+ multi_points_to_waypoints(geojson, gpx_file)
76
+ end
77
+
78
+ # Converts GeoJSON 'LineString' features.
79
+ # Current strategy is to convert each LineString into a
80
+ # Track Segment, returning a Track for all LineStrings.
81
+ #
82
+ def line_strings_to_track(geojson)
83
+ line_strings = line_strings_in(geojson)
84
+ return nil unless line_strings.any?
85
+
86
+ track = GPX::Track.new
87
+ line_strings.each do |ls|
88
+ coords = ls['geometry']['coordinates']
89
+ track.append_segment(coords_to_segment(coords))
90
+ end
91
+ track
92
+ end
93
+
94
+ # Converts GeoJSON 'MultiLineString' features.
95
+ # Current strategy is to convert each MultiLineString
96
+ # into a Track, with each set of LineString coordinates
97
+ # within a MultiLineString a Track Segment.
98
+ #
99
+ def multi_line_strings_to_tracks(geojson)
100
+ tracks = []
101
+ multi_line_strings_in(geojson).each do |mls|
102
+ track = GPX::Track.new
103
+ mls['geometry']['coordinates'].each do |coords|
104
+ seg = coords_to_segment(coords)
105
+ seg.track = track
106
+ track.append_segment(seg)
107
+ end
108
+ tracks << track
109
+ end
110
+ tracks
111
+ end
112
+
113
+ # Converts GeoJSON 'Point' features.
114
+ # Current strategy is to convert each Point
115
+ # feature into a GPX waypoint.
116
+ #
117
+ def points_to_waypoints(geojson, gpx_file)
118
+ points_in(geojson).reduce([]) do |acc, pt|
119
+ coords = pt['geometry']['coordinates']
120
+ acc << point_to_waypoint(coords, gpx_file)
121
+ end
122
+ end
123
+
124
+ # Converts GeoJSON 'MultiPoint' features.
125
+ # Current strategy is to convert each coordinate
126
+ # point in a MultiPoint to a GPX waypoint.
127
+ #
128
+ # NOTE: It is debatable that a MultiPoint feature
129
+ # might translate best into a GPX route, which is
130
+ # described as
131
+ # "an ordered list of waypoints representing a
132
+ # series of turn points leading to a destination."
133
+ # See http://www.topografix.com/gpx/1/1/#type_rteType
134
+ #
135
+ def multi_points_to_waypoints(geojson, gpx_file)
136
+ multi_points_in(geojson).reduce([]) do |acc, mpt|
137
+ mpt['geometry']['coordinates'].each do |coords|
138
+ acc << point_to_waypoint(coords, gpx_file)
139
+ end
140
+ end
141
+ end
142
+
143
+ # Given an array of [lng, lat, ele] coordinates,
144
+ # return a GPX track segment.
145
+ #
146
+ def coords_to_segment(coords)
147
+ seg = GPX::Segment.new
148
+ coords.each do |pt|
149
+ seg.append_point(point_to_track_point(pt, seg))
150
+ end
151
+ seg
152
+ end
153
+
154
+ # Given a GeoJSON coordinate point, return
155
+ # a GPX::Waypoint
156
+ def point_to_waypoint(point, gpx_file)
157
+ GPX::Waypoint.new(gpx_file: gpx_file,
158
+ lon: point[0],
159
+ lat: point[1],
160
+ elevation: point[2])
161
+ end
162
+
163
+ # Given a GeoJSON coorindate point, and
164
+ # GPX segment, return a GPX::TrackPoint.
165
+ #
166
+ def point_to_track_point(point, seg)
167
+ GPX::TrackPoint.new(segment: seg,
168
+ lon: point[0],
169
+ lat: point[1],
170
+ elevation: point[2])
171
+ end
172
+
173
+ # Returns all features in the passed geojson
174
+ # that match the type.
175
+ #
176
+ def features_for(geojson, type)
177
+ geojson['features'].find_all do |f|
178
+ f['type'] == FEATURE && f['geometry']['type'] == type
179
+ end
180
+ end
181
+
182
+ def points_in(geojson)
183
+ features_for(geojson, POINT)
184
+ end
185
+
186
+ def multi_points_in(geojson)
187
+ features_for(geojson, MULTIPOINT)
188
+ end
189
+
190
+ def line_strings_in(geojson)
191
+ features_for(geojson, LINESTRING)
192
+ end
193
+
194
+ def multi_line_strings_in(geojson)
195
+ features_for(geojson, MULTILINESTRING)
196
+ end
197
+ end
198
+ end
199
+ end
data/lib/gpx/gpx.rb CHANGED
@@ -1,25 +1,5 @@
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
- #++
1
+ # frozen_string_literal: true
2
+
23
3
  module GPX
24
4
  # A common base class which provides a useful initializer method to many
25
5
  # class in the GPX library.
@@ -33,14 +13,12 @@ module GPX
33
13
  # attributes to this method.
34
14
  def instantiate_with_text_elements(parent, text_elements)
35
15
  text_elements.each do |el|
36
- child_xpath = "#{el}"
16
+ child_xpath = el.to_s
37
17
  unless parent.at(child_xpath).nil?
38
18
  val = parent.at(child_xpath).inner_text
39
- self.send("#{el}=", val)
19
+ send("#{el}=", val)
40
20
  end
41
21
  end
42
-
43
22
  end
44
-
45
23
  end
46
24
  end