gpx 1.0.0 → 1.1.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +36 -0
- data/.rubocop.yml +2 -5
- data/.ruby-version +1 -0
- data/.travis.yml +5 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile +2 -0
- data/README.md +19 -4
- data/Rakefile +7 -0
- data/bin/gpx_distance +2 -0
- data/bin/gpx_smooth +5 -2
- data/gpx.gemspec +2 -2
- data/lib/gpx/bounds.rb +3 -0
- data/lib/gpx/geo_json.rb +199 -0
- data/lib/gpx/gpx.rb +2 -0
- data/lib/gpx/gpx_file.rb +53 -51
- data/lib/gpx/magellan_track_log.rb +2 -0
- data/lib/gpx/point.rb +5 -1
- data/lib/gpx/route.rb +4 -1
- data/lib/gpx/segment.rb +14 -9
- data/lib/gpx/track.rb +11 -4
- data/lib/gpx/track_point.rb +2 -0
- data/lib/gpx/version.rb +3 -1
- data/lib/gpx/waypoint.rb +3 -0
- data/lib/gpx.rb +3 -1
- data/tests/geojson_files/combined_data.json +68 -0
- data/tests/geojson_files/line_string_data.json +83 -0
- data/tests/geojson_files/multi_line_string_data.json +74 -0
- data/tests/geojson_files/multi_point_data.json +14 -0
- data/tests/geojson_files/point_data.json +22 -0
- data/tests/geojson_test.rb +92 -0
- data/tests/gpx10_test.rb +2 -0
- data/tests/gpx_file_test.rb +2 -0
- data/tests/gpx_files/one_segment_mixed_times.gpx +884 -0
- data/tests/gpx_files/routes_without_names.gpx +29 -0
- data/tests/magellan_test.rb +2 -0
- data/tests/output_test.rb +3 -1
- data/tests/route_test.rb +52 -0
- data/tests/segment_test.rb +13 -1
- data/tests/track_file_test.rb +3 -0
- data/tests/track_point_test.rb +2 -0
- data/tests/track_test.rb +2 -0
- data/tests/waypoint_test.rb +2 -0
- metadata +22 -9
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f206f4729605d96cdaf65fe3fe52237c14f61eb1ae69558eb826c6f831e4076b
         | 
| 4 | 
            +
              data.tar.gz: b05a8fd9d66446f57640474b3dad428b5eab24617b2e0cb3b19e655d079ea0d6
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 28c8c86253452eb56383790c43189de24929482781b5d94ac9416ab93be596fbbe5259e0226a18bbb7e44070503d6b721671bff2b239a5512a935fcb21dbc19f
         | 
| 7 | 
            +
              data.tar.gz: be64658d5bf4cf3d7653b2457f286401971d3a7a272766002d78296341eedd2c4ad237dc5ac38d38debe7dd7288275185b6ac27bef99460d250c2e62bd3d9c5b
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # This workflow uses actions that are not certified by GitHub.
         | 
| 2 | 
            +
            # They are provided by a third-party and are governed by
         | 
| 3 | 
            +
            # separate terms of service, privacy policy, and support
         | 
| 4 | 
            +
            # documentation.
         | 
| 5 | 
            +
            # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
         | 
| 6 | 
            +
            # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            name: Ruby
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            on:
         | 
| 11 | 
            +
              push:
         | 
| 12 | 
            +
                branches: [ master ]
         | 
| 13 | 
            +
              pull_request:
         | 
| 14 | 
            +
                branches: [ master ]
         | 
| 15 | 
            +
              workflow_dispatch:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            jobs:
         | 
| 18 | 
            +
              test:
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                runs-on: ubuntu-latest
         | 
| 21 | 
            +
                strategy:
         | 
| 22 | 
            +
                  matrix:
         | 
| 23 | 
            +
                    ruby-version: ['2.7', '3.0', '3.1']
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                steps:
         | 
| 26 | 
            +
                - uses: actions/checkout@v2
         | 
| 27 | 
            +
                - name: Set up Ruby
         | 
| 28 | 
            +
                # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
         | 
| 29 | 
            +
                # change this to (see https://github.com/ruby/setup-ruby#versioning):
         | 
| 30 | 
            +
                # uses: ruby/setup-ruby@v1
         | 
| 31 | 
            +
                  uses: ruby/setup-ruby@f0971f0dd45a5cbb3f119f7db77cc58057c53530
         | 
| 32 | 
            +
                  with:
         | 
| 33 | 
            +
                    ruby-version: ${{ matrix.ruby-version }}
         | 
| 34 | 
            +
                    bundler-cache: true # runs 'bundle install' and caches installed gems automatically
         | 
| 35 | 
            +
                - name: Run tests
         | 
| 36 | 
            +
                  run: bundle exec rake
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -1,5 +1,5 @@ | |
| 1 1 | 
             
            AllCops:
         | 
| 2 | 
            -
              TargetRubyVersion: 2. | 
| 2 | 
            +
              TargetRubyVersion: 2.5
         | 
| 3 3 |  | 
| 4 4 | 
             
            Style/Alias:
         | 
| 5 5 | 
             
              Enabled: false
         | 
| @@ -152,7 +152,7 @@ Metrics/ParameterLists: | |
| 152 152 | 
             
            Metrics/PerceivedComplexity:
         | 
| 153 153 | 
             
              Enabled: false
         | 
| 154 154 |  | 
| 155 | 
            -
            Naming/ | 
| 155 | 
            +
            Naming/MethodParameterName:
         | 
| 156 156 | 
             
              Enabled: false
         | 
| 157 157 |  | 
| 158 158 | 
             
            Naming/VariableNumber:
         | 
| @@ -160,6 +160,3 @@ Naming/VariableNumber: | |
| 160 160 |  | 
| 161 161 | 
             
            Style/DateTime:
         | 
| 162 162 | 
             
              Enabled: false
         | 
| 163 | 
            -
             | 
| 164 | 
            -
            Performance/Caller:
         | 
| 165 | 
            -
              Enabled: false
         | 
    
        data/.ruby-version
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            3.0.2
         | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,10 @@ | |
| 1 | 
            +
            ## [1.1.0] - 202
         | 
| 2 | 
            +
              * Specify UTF-8 encoding for XML encoding (#35 via @sh1nduu)
         | 
| 3 | 
            +
              * Added GeoJSON conversion (#38 via @tyrauber and @niborg)
         | 
| 4 | 
            +
              * Support Ruby 3 (#43 via @LocoDelAssembly)
         | 
| 5 | 
            +
              * Fix nil-to-Time comparison (#46 via @frodrigo)
         | 
| 6 | 
            +
              * Fix bug when <rte> GPX file does not specify <name> tag (#41 via @niborg)
         | 
| 7 | 
            +
              * Drop Ruby 2.5 and 2.6 from CI (#50 via @niborg)
         | 
| 1 8 | 
             
            ## [1.0.0] - 2018-03-06
         | 
| 2 9 |  | 
| 3 10 | 
             
              * Fix duplication of points on appending segment to track (#20 via @niborg)
         | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -10,15 +10,19 @@ the data as objects.  For more info on the GPX format, see | |
| 10 10 | 
             
            http://www.topografix.com/gpx.asp.
         | 
| 11 11 |  | 
| 12 12 | 
             
            In addition to parsing GPX files, this library is capable of converting
         | 
| 13 | 
            -
            Magellan NMEA files to GPX,  | 
| 14 | 
            -
             | 
| 15 | 
            -
            the tracks and points in a file (such as distance, duration, average speed,
         | 
| 16 | 
            -
            etc).
         | 
| 13 | 
            +
            Magellan NMEA files to GPX, converting GeoJSON data to GPX, and writing
         | 
| 14 | 
            +
            new GPX files.  It can crop and delete rectangular areas within a file,
         | 
| 15 | 
            +
            and it also calculates some meta-data about the tracks and points in a file (such as distance, duration, average speed, etc).
         | 
| 17 16 |  | 
| 18 17 | 
             
            ## Requirements
         | 
| 19 18 |  | 
| 20 19 | 
             
            As of `1.0.0`, `gpx` requires at least Ruby 2.2 to run.
         | 
| 21 20 |  | 
| 21 | 
            +
            ## Installation
         | 
| 22 | 
            +
            Add to your gemfile:
         | 
| 23 | 
            +
            ```
         | 
| 24 | 
            +
            gem 'gpx'
         | 
| 25 | 
            +
            ```
         | 
| 22 26 | 
             
            ## Examples
         | 
| 23 27 |  | 
| 24 28 | 
             
            Reading a GPX file, and cropping its contents to a given area:
         | 
| @@ -37,6 +41,17 @@ if GPX::MagellanTrackLog::is_magellan_file?(filename) | |
| 37 41 | 
             
            end
         | 
| 38 42 | 
             
            ```
         | 
| 39 43 |  | 
| 44 | 
            +
            Converting GeoJSON data to GPX can be achieved by providing a
         | 
| 45 | 
            +
            file path, file, or the data in string format:
         | 
| 46 | 
            +
            ```ruby
         | 
| 47 | 
            +
            # Converting from a file name
         | 
| 48 | 
            +
            gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_file: 'mygeojsonfile.json')
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            # Converting from a string
         | 
| 51 | 
            +
            data = JSON.generate(my_geojson_hash)
         | 
| 52 | 
            +
            gpx_file = GPX::GeoJSON.convert_to_gpx(geojson_data: data)
         | 
| 53 | 
            +
            ```
         | 
| 54 | 
            +
             | 
| 40 55 | 
             
            Exporting an ActiveRecord to GPXFile (as Waypoints)
         | 
| 41 56 | 
             
            ```ruby
         | 
| 42 57 | 
             
            #
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'bundler/gem_tasks'
         | 
| 2 4 | 
             
            require 'rake/testtask'
         | 
| 3 5 | 
             
            require 'rdoc/task'
         | 
| @@ -32,3 +34,8 @@ Rake::RDocTask.new('doc') do |rdoc| | |
| 32 34 | 
             
              rdoc.rdoc_files.include('README')
         | 
| 33 35 | 
             
              rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 34 36 | 
             
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            desc 'open an irb session preloaded with this gem'
         | 
| 39 | 
            +
            task :console do
         | 
| 40 | 
            +
              sh 'irb -r pp -r ./lib/gpx.rb'
         | 
| 41 | 
            +
            end
         | 
    
        data/bin/gpx_distance
    CHANGED
    
    
    
        data/bin/gpx_smooth
    CHANGED
    
    | @@ -1,12 +1,15 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 2 |  | 
| 3 | 
            +
            # frozen_string_literal: true
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            require File.expand_path('../lib/gpx', __dir__)
         | 
| 4 6 | 
             
            require 'optparse'
         | 
| 5 7 |  | 
| 6 8 | 
             
            def str_to_int_or_time(str)
         | 
| 7 | 
            -
               | 
| 9 | 
            +
              case str
         | 
| 10 | 
            +
              when /\A\d{10}\Z/
         | 
| 8 11 | 
             
                Time.at(str.to_i)
         | 
| 9 | 
            -
               | 
| 12 | 
            +
              when /\A\d+\Z/
         | 
| 10 13 | 
             
                str.to_i
         | 
| 11 14 | 
             
              else
         | 
| 12 15 | 
             
                DateTime.strptime(str, '%Y%m%d-%H:%M:%S').to_time
         | 
    
        data/gpx.gemspec
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 1 2 |  | 
| 2 3 | 
             
            lib = File.expand_path('lib', __dir__)
         | 
| 3 4 | 
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| @@ -11,12 +12,11 @@ Gem::Specification.new do |s| | |
| 11 12 | 
             
              s.summary = 'A basic API for reading and writing GPX files.'
         | 
| 12 13 | 
             
              s.description = 'A basic API for reading and writing GPX files.'
         | 
| 13 14 |  | 
| 14 | 
            -
              s.required_ruby_version = ' | 
| 15 | 
            +
              s.required_ruby_version = '>= 2.5', '< 4'
         | 
| 15 16 |  | 
| 16 17 | 
             
              s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
         | 
| 17 18 | 
             
              s.test_files    = s.files.grep(%r{^(test|spec|features)/})
         | 
| 18 19 | 
             
              s.require_paths = ['lib']
         | 
| 19 | 
            -
              s.has_rdoc = true
         | 
| 20 20 |  | 
| 21 21 | 
             
              s.homepage = 'http://www.github.com/dougfales/gpx'
         | 
| 22 22 | 
             
              s.add_dependency 'nokogiri', '~>1.7'
         | 
    
        data/lib/gpx/bounds.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module GPX
         | 
| 2 4 | 
             
              class Bounds < Base
         | 
| 3 5 | 
             
                attr_accessor :min_lat, :max_lat, :max_lon, :min_lon
         | 
| @@ -5,6 +7,7 @@ module GPX | |
| 5 7 | 
             
                # Creates a new bounds object with the passed-in min and max longitudes
         | 
| 6 8 | 
             
                # and latitudes.
         | 
| 7 9 | 
             
                def initialize(opts = { min_lat: 90.0, max_lat: -90.0, min_lon: 180.0, max_lon: -180.0 })
         | 
| 10 | 
            +
                  super()
         | 
| 8 11 | 
             
                  @min_lat = opts[:min_lat].to_f
         | 
| 9 12 | 
             
                  @max_lat = opts[:max_lat].to_f
         | 
| 10 13 | 
             
                  @min_lon = opts[:min_lon].to_f
         | 
    
        data/lib/gpx/geo_json.rb
    ADDED
    
    | @@ -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
    
    
    
        data/lib/gpx/gpx_file.rb
    CHANGED
    
    | @@ -1,9 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module GPX
         | 
| 2 4 | 
             
              class GPXFile < Base
         | 
| 3 5 | 
             
                attr_accessor :tracks,
         | 
| 4 6 | 
             
                              :routes, :waypoints, :bounds, :lowest_point, :highest_point, :duration, :ns, :time, :name, :version, :creator, :description, :moving_duration
         | 
| 5 7 |  | 
| 6 | 
            -
                DEFAULT_CREATOR = "GPX RubyGem #{GPX::VERSION} -- http://dougfales.github.io/gpx/" | 
| 8 | 
            +
                DEFAULT_CREATOR = "GPX RubyGem #{GPX::VERSION} -- http://dougfales.github.io/gpx/"
         | 
| 7 9 |  | 
| 8 10 | 
             
                # This initializer can be used to create a new GPXFile from an existing
         | 
| 9 11 | 
             
                # file or to create a new GPXFile instance with no data (so that you can
         | 
| @@ -23,6 +25,7 @@ module GPX | |
| 23 25 | 
             
                #         gpx_file = GPXFile.new(:tracks => [some_track])
         | 
| 24 26 | 
             
                #
         | 
| 25 27 | 
             
                def initialize(opts = {})
         | 
| 28 | 
            +
                  super()
         | 
| 26 29 | 
             
                  @duration = 0
         | 
| 27 30 | 
             
                  @attributes = {}
         | 
| 28 31 | 
             
                  @namespace_defs = []
         | 
| @@ -43,7 +46,8 @@ module GPX | |
| 43 46 | 
             
                    @namespace_defs = gpx_element.namespace_definitions
         | 
| 44 47 | 
             
                    @version = gpx_element['version']
         | 
| 45 48 | 
             
                    reset_meta_data
         | 
| 46 | 
            -
                    bounds_element = ( | 
| 49 | 
            +
                    bounds_element = (
         | 
| 50 | 
            +
                    begin
         | 
| 47 51 | 
             
                      @xml.at('metadata/bounds')
         | 
| 48 52 | 
             
                    rescue StandardError
         | 
| 49 53 | 
             
                      nil
         | 
| @@ -58,20 +62,20 @@ module GPX | |
| 58 62 | 
             
                    end
         | 
| 59 63 |  | 
| 60 64 | 
             
                    @time = begin
         | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            +
                      Time.parse(@xml.at('metadata/time').inner_text)
         | 
| 66 | 
            +
                    rescue StandardError
         | 
| 67 | 
            +
                      nil
         | 
| 68 | 
            +
                    end
         | 
| 65 69 | 
             
                    @name = begin
         | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            +
                      @xml.at('metadata/name').inner_text
         | 
| 71 | 
            +
                    rescue StandardError
         | 
| 72 | 
            +
                      nil
         | 
| 73 | 
            +
                    end
         | 
| 70 74 | 
             
                    @description = begin
         | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            +
                      @xml.at('metadata/desc').inner_text
         | 
| 76 | 
            +
                    rescue StandardError
         | 
| 77 | 
            +
                      nil
         | 
| 78 | 
            +
                    end
         | 
| 75 79 | 
             
                    @xml.search('trk').each do |trk|
         | 
| 76 80 | 
             
                      trk = Track.new(element: trk, gpx_file: self)
         | 
| 77 81 | 
             
                      update_meta_data(trk, get_bounds)
         | 
| @@ -87,7 +91,7 @@ module GPX | |
| 87 91 | 
             
                  else
         | 
| 88 92 | 
             
                    reset_meta_data
         | 
| 89 93 | 
             
                    opts.each { |attr_name, value| instance_variable_set("@#{attr_name}", value) }
         | 
| 90 | 
            -
                    unless @tracks.nil? || @tracks. | 
| 94 | 
            +
                    unless @tracks.nil? || @tracks.empty?
         | 
| 91 95 | 
             
                      @tracks.each { |trk| update_meta_data(trk) }
         | 
| 92 96 | 
             
                      calculate_duration
         | 
| 93 97 | 
             
                    end
         | 
| @@ -103,7 +107,8 @@ module GPX | |
| 103 107 | 
             
                    result = el[name]
         | 
| 104 108 | 
             
                    break unless result.nil?
         | 
| 105 109 | 
             
                  end
         | 
| 106 | 
            -
                  ( | 
| 110 | 
            +
                  (
         | 
| 111 | 
            +
                  begin
         | 
| 107 112 | 
             
                    result.to_f
         | 
| 108 113 | 
             
                  rescue StandardError
         | 
| 109 114 | 
             
                    nil
         | 
| @@ -189,6 +194,8 @@ module GPX | |
| 189 194 | 
             
                  @moving_duration = 0.0
         | 
| 190 195 | 
             
                end
         | 
| 191 196 |  | 
| 197 | 
            +
                # rubocop:disable Style/OptionalBooleanParameter
         | 
| 198 | 
            +
             | 
| 192 199 | 
             
                # Updates the meta data for this GPX file.  Meta data includes the
         | 
| 193 200 | 
             
                # bounds, the high and low points, and the distance.  This is useful when
         | 
| 194 201 | 
             
                # you modify the GPX data (i.e. by adding or deleting points) and you
         | 
| @@ -215,6 +222,7 @@ module GPX | |
| 215 222 | 
             
                  doc = generate_xml_doc
         | 
| 216 223 | 
             
                  doc.to_xml
         | 
| 217 224 | 
             
                end
         | 
| 225 | 
            +
                # rubocop:enable Style/OptionalBooleanParameter
         | 
| 218 226 |  | 
| 219 227 | 
             
                def inspect
         | 
| 220 228 | 
             
                  "<#{self.class.name}:...>"
         | 
| @@ -234,13 +242,13 @@ module GPX | |
| 234 242 | 
             
                  # $stderr.puts @namespace_defs.inspect
         | 
| 235 243 | 
             
                  gpx_header = {}
         | 
| 236 244 | 
             
                  @attributes.each do |k, v|
         | 
| 237 | 
            -
                    k = v.namespace.prefix | 
| 245 | 
            +
                    k = "#{v.namespace.prefix}:#{k}" if v.namespace
         | 
| 238 246 | 
             
                    gpx_header[k] = v.value
         | 
| 239 247 | 
             
                  end
         | 
| 240 248 |  | 
| 241 249 | 
             
                  @namespace_defs.each do |nsd|
         | 
| 242 250 | 
             
                    tag = 'xmlns'
         | 
| 243 | 
            -
                    tag +=  | 
| 251 | 
            +
                    tag += ":#{nsd.prefix}" if nsd.prefix
         | 
| 244 252 | 
             
                    gpx_header[tag] = nsd.href
         | 
| 245 253 | 
             
                  end
         | 
| 246 254 | 
             
                  gpx_header
         | 
| @@ -260,7 +268,7 @@ module GPX | |
| 260 268 | 
             
                  # $stderr.puts gpx_header.keys.inspect
         | 
| 261 269 |  | 
| 262 270 | 
             
                  # rubocop:disable Metrics/BlockLength
         | 
| 263 | 
            -
                   | 
| 271 | 
            +
                  Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
         | 
| 264 272 | 
             
                    xml.gpx(gpx_header) do
         | 
| 265 273 | 
             
                      # version 1.0 of the schema doesn't support the metadata element, so push them straight to the root 'gpx' element
         | 
| 266 274 | 
             
                      if @version == '1.0'
         | 
| @@ -285,19 +293,17 @@ module GPX | |
| 285 293 | 
             
                        end
         | 
| 286 294 | 
             
                      end
         | 
| 287 295 |  | 
| 288 | 
            -
                       | 
| 289 | 
            -
                         | 
| 290 | 
            -
                          xml. | 
| 291 | 
            -
             | 
| 292 | 
            -
             | 
| 293 | 
            -
                             | 
| 294 | 
            -
                               | 
| 295 | 
            -
                                 | 
| 296 | 
            -
                                  xml. | 
| 297 | 
            -
             | 
| 298 | 
            -
             | 
| 299 | 
            -
                                    xml << p.extensions.to_xml unless p.extensions.nil?
         | 
| 300 | 
            -
                                  end
         | 
| 296 | 
            +
                      tracks&.each do |t|
         | 
| 297 | 
            +
                        xml.trk do
         | 
| 298 | 
            +
                          xml.name t.name
         | 
| 299 | 
            +
             | 
| 300 | 
            +
                          t.segments.each do |seg|
         | 
| 301 | 
            +
                            xml.trkseg do
         | 
| 302 | 
            +
                              seg.points.each do |p|
         | 
| 303 | 
            +
                                xml.trkpt(lat: p.lat, lon: p.lon) do
         | 
| 304 | 
            +
                                  xml.time p.time.xmlschema unless p.time.nil?
         | 
| 305 | 
            +
                                  xml.ele p.elevation unless p.elevation.nil?
         | 
| 306 | 
            +
                                  xml << p.extensions.to_xml unless p.extensions.nil?
         | 
| 301 307 | 
             
                                end
         | 
| 302 308 | 
             
                              end
         | 
| 303 309 | 
             
                            end
         | 
| @@ -305,27 +311,23 @@ module GPX | |
| 305 311 | 
             
                        end
         | 
| 306 312 | 
             
                      end
         | 
| 307 313 |  | 
| 308 | 
            -
                       | 
| 309 | 
            -
                         | 
| 310 | 
            -
                          xml. | 
| 311 | 
            -
             | 
| 312 | 
            -
                             | 
| 313 | 
            -
                              xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
         | 
| 314 | 
            -
                            end
         | 
| 314 | 
            +
                      waypoints&.each do |w|
         | 
| 315 | 
            +
                        xml.wpt(lat: w.lat, lon: w.lon) do
         | 
| 316 | 
            +
                          xml.time w.time.xmlschema unless w.time.nil?
         | 
| 317 | 
            +
                          Waypoint::SUB_ELEMENTS.each do |sub_elem|
         | 
| 318 | 
            +
                            xml.send(sub_elem, w.send(sub_elem)) if w.respond_to?(sub_elem) && !w.send(sub_elem).nil?
         | 
| 315 319 | 
             
                          end
         | 
| 316 320 | 
             
                        end
         | 
| 317 321 | 
             
                      end
         | 
| 318 322 |  | 
| 319 | 
            -
                       | 
| 320 | 
            -
                         | 
| 321 | 
            -
                          xml. | 
| 322 | 
            -
                            xml.name r.name
         | 
| 323 | 
            +
                      routes&.each do |r|
         | 
| 324 | 
            +
                        xml.rte do
         | 
| 325 | 
            +
                          xml.name r.name
         | 
| 323 326 |  | 
| 324 | 
            -
             | 
| 325 | 
            -
             | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
                              end
         | 
| 327 | 
            +
                          r.points.each do |p|
         | 
| 328 | 
            +
                            xml.rtept(lat: p.lat, lon: p.lon) do
         | 
| 329 | 
            +
                              xml.time p.time.xmlschema unless p.time.nil?
         | 
| 330 | 
            +
                              xml.ele p.elevation unless p.elevation.nil?
         | 
| 329 331 | 
             
                            end
         | 
| 330 332 | 
             
                          end
         | 
| 331 333 | 
             
                        end
         | 
| @@ -333,17 +335,17 @@ module GPX | |
| 333 335 | 
             
                    end
         | 
| 334 336 | 
             
                  end
         | 
| 335 337 | 
             
                  # rubocop:enable Metrics/BlockLength
         | 
| 336 | 
            -
             | 
| 337 | 
            -
                  doc
         | 
| 338 338 | 
             
                end
         | 
| 339 339 |  | 
| 340 340 | 
             
                # Calculates and sets the duration attribute by subtracting the time on
         | 
| 341 341 | 
             
                # the very first point from the time on the very last point.
         | 
| 342 342 | 
             
                def calculate_duration
         | 
| 343 343 | 
             
                  @duration = 0
         | 
| 344 | 
            -
                  if @tracks.nil? || @tracks. | 
| 344 | 
            +
                  if @tracks.nil? || @tracks.empty? || @tracks[0].segments.nil? || @tracks[0].segments.empty?
         | 
| 345 345 | 
             
                    return @duration
         | 
| 346 | 
            +
             | 
| 346 347 | 
             
                  end
         | 
| 348 | 
            +
             | 
| 347 349 | 
             
                  @duration = (@tracks[-1].segments[-1].points[-1].time - @tracks.first.segments.first.points.first.time)
         | 
| 348 350 | 
             
                rescue StandardError
         | 
| 349 351 | 
             
                  @duration = 0
         | 
    
        data/lib/gpx/point.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module GPX
         | 
| 2 4 | 
             
              # The base class for all points.  Trackpoint and Waypoint both descend from this base class.
         | 
| 3 5 | 
             
              class Point < Base
         | 
| @@ -10,6 +12,7 @@ module GPX | |
| 10 12 | 
             
                # addition, you can pass an XML element to this initializer, and the
         | 
| 11 13 | 
             
                # relevant info will be parsed out.
         | 
| 12 14 | 
             
                def initialize(opts = { lat: 0.0, lon: 0.0, elevation: 0.0, time: Time.now })
         | 
| 15 | 
            +
                  super()
         | 
| 13 16 | 
             
                  @gpx_file = opts[:gpx_file]
         | 
| 14 17 | 
             
                  if opts[:element]
         | 
| 15 18 | 
             
                    elem = opts[:element]
         | 
| @@ -18,7 +21,8 @@ module GPX | |
| 18 21 | 
             
                    @latr = (D_TO_R * @lat)
         | 
| 19 22 | 
             
                    @lonr = (D_TO_R * @lon)
         | 
| 20 23 | 
             
                    # '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
         | 
| 21 | 
            -
                    @time = ( | 
| 24 | 
            +
                    @time = (
         | 
| 25 | 
            +
                    begin
         | 
| 22 26 | 
             
                      Time.xmlschema(elem.at('time').inner_text)
         | 
| 23 27 | 
             
                    rescue StandardError
         | 
| 24 28 | 
             
                      nil
         |