gpx 0.8.3 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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