piste_pal 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 893460445f1be425c25ce56ae12ef4e629c77c91dbed7e90667a0659a1d74bf6
4
+ data.tar.gz: da58a5516d4f98b355a6bc2fe2f3b55fe5b5b31300818bf7915982688cfce609
5
+ SHA512:
6
+ metadata.gz: 006fe9c2eb0e264d762fadd5cccc2aabbd9a58b901e16cacf153e85e3727d045c9d3cd5914a51559f6ba811a0272212f91b980e892583f3451547ebd01461f81
7
+ data.tar.gz: c2a92c099977e96d9c18df1fc6d7b094a076f4dd149486d1232f99d194bd2a7d451da4f8081d5a993dd41e5940d645ca76d8e8a43d1c7d733e505afc1afbd51b
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.3
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in piste_pal.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ piste_pal (1.0.0)
5
+ nokogiri (~> 1.10)
6
+ rake (~> 10.0)
7
+ vincenty_distance (~> 1.0.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ byebug (11.0.1)
13
+ diff-lcs (1.3)
14
+ mini_portile2 (2.4.0)
15
+ nokogiri (1.10.3)
16
+ mini_portile2 (~> 2.4.0)
17
+ rake (10.5.0)
18
+ rspec (3.8.0)
19
+ rspec-core (~> 3.8.0)
20
+ rspec-expectations (~> 3.8.0)
21
+ rspec-mocks (~> 3.8.0)
22
+ rspec-core (3.8.2)
23
+ rspec-support (~> 3.8.0)
24
+ rspec-expectations (3.8.4)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.8.0)
27
+ rspec-mocks (3.8.1)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.8.0)
30
+ rspec-support (3.8.2)
31
+ vincenty_distance (1.0.0)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ bundler (~> 2.0)
38
+ byebug (~> 11.0)
39
+ piste_pal!
40
+ rake (~> 10.0)
41
+ rspec (~> 3.0)
42
+
43
+ BUNDLED WITH
44
+ 2.0.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Josh Menden
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # PistePal
2
+
3
+ Fill your time off the slopes thinking about them. This gem takes .gpx files from popular activity tracking apps and turns them into fun stats.
4
+
5
+ The code can be accessed at a high level by "purchasing" either Day Passes or Season Passes, but most of the functionality is modularized to allow you access to the stats you want.
6
+
7
+ It is tailored for .gpx files exported from the [Slopes](https://getslopes.com/) app for now.
8
+
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'piste_pal'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install piste_pal
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require 'piste_pal'
30
+
31
+ # "Purchase" your day pass with a single .gpx data object
32
+
33
+ gpx_data = File.read('/path/to/gpx/file')
34
+ sundance = PistePal::DayPass.purchase(gpx_data)
35
+
36
+ sundance.resort # Sundance Mountain Resort
37
+ sundance.date # 2019-03-01 00:00:00 -0700
38
+ sundance.maximum_speed # {:value=>30.290415, :unit=>"mph"}
39
+ sundance.peak_altitude # {:value=>8261.18916855752, :unit=>"feet"}
40
+ sundance.vertical # {:value=>3797.6879430483204, :unit=>"feet"}
41
+ sundance.distance # {:value=>4.796952318157319, :unit=>"miles"}
42
+ sundance.tallest_run # {:value=>1282.958728718721, :unit=>"feet"}
43
+ sundance.longest_run # {:value=>1.9052295991405417, :unit=>"miles"}
44
+ sundance.runs # [[@trackpoint1, @trackpoint2, ...], [@trackpoint1, @trackpoint2, ...]]
45
+ sundance.lifts # [[@trackpoint1, @trackpoint2, ...], [@trackpoint1, @trackpoint2, ...]]
46
+
47
+ # Or, purchase a season pass with an array of .gpx data objects
48
+
49
+ season_data = []
50
+ season_data.push(File.read('/path/to/gpx/file1'))
51
+ season_data.push(File.read('/path/to/gpx/file2'))
52
+ season_data.push(File.read('/path/to/gpx/file3'))
53
+
54
+ season = PistePal::SeasonPass.purchase(season_data)
55
+
56
+ season.days # [<PistePal::DayPass>, <PistePal::DayPass>, ...]
57
+ season.days(timestamp_only: true) # [2019-03-30 00:00:00 -0600, 2019-03-01 00:00:00 -0700, ...]
58
+ season.runs # 134
59
+ season.resorts # ["Sundance Resort", "Solitude Mountain Resort", "Park City Mountain Resort"]
60
+ season.vertical # {:value=>51900.38487275453, :unit=>"feet"}
61
+ season.distance # {:value=>52.35386405859661, :unit=>"miles"}
62
+ ```
63
+
64
+ ## Contributing
65
+
66
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/joshmenden/piste_pal). No PR is too small.
67
+
68
+ Some things that need cleaning up / fixing:
69
+
70
+ * Should probably write some tests...
71
+ * Cleaner, more comprehensive way to handle different measurement systems (Metric vs. Imperial). I have this half-baked in some places but it could use a lot of improvment.
72
+ * Save trackpoint nodes to object and only extract data when necessary -- that is, only calculate the maximum_velocity when that method is invoked
73
+ * Check compatibility with files exported from other Apps (right now it is mostly tailored towards the "Slopes" app)
74
+
75
+ ## License
76
+
77
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "piste_pal"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ require 'nokogiri'
2
+ require 'time'
3
+ module PistePal
4
+ module DataServices
5
+ class DateAndResort
6
+ def self.call
7
+ new().call
8
+ end
9
+
10
+ def call
11
+ name_node = @doc.xpath("//xmlns:name")
12
+ @date, @resort_name = name_node.children.first.text.strip.split(" - ")
13
+ @date = Time.parse(@date)
14
+ return [@date, @resort_name]
15
+ end
16
+
17
+ private
18
+
19
+ def initialize
20
+ @doc = PistePal::DataServices::GpxDoc.instance.doc
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,51 @@
1
+ require 'vincenty'
2
+ module PistePal
3
+ module DataServices
4
+ class Distance
5
+ def self.call(trackpoints:)
6
+ new(trackpoints: trackpoints).call
7
+ end
8
+
9
+ def call
10
+ if @trackpoints.first.is_a? Array
11
+ distance = 0
12
+ @trackpoints.each do |trackpoints|
13
+ distance += calculate_distance trackpoints
14
+ end
15
+ else
16
+ distance = calculate_distance @trackpoints
17
+ end
18
+ distance
19
+ end
20
+
21
+ private
22
+
23
+ def initialize(trackpoints:)
24
+ @trackpoints = trackpoints
25
+ end
26
+
27
+ def calculate_distance trackpoints
28
+ distance = 0
29
+ total_points = trackpoints.count - 1
30
+
31
+ point_a = nil
32
+ point_b = nil
33
+
34
+ for i in 0..total_points do
35
+ if i == 0
36
+ point_a = { latitude: trackpoints[i].lat, longitude: trackpoints[i].lon }
37
+ next
38
+ end
39
+
40
+ point_b = { latitude: trackpoints[i].lat, longitude: trackpoints[i].lon }
41
+ distance += Vincenty.distance_between_points(point_a, point_b)
42
+ point_a = point_b
43
+ end
44
+
45
+ distance
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+
@@ -0,0 +1,24 @@
1
+ require 'nokogiri'
2
+ module PistePal
3
+ module DataServices
4
+ class GpxDoc
5
+ # include Singleton
6
+ attr_accessor :doc
7
+
8
+ def self.set_instance(file_content)
9
+ @instance = new(file_content)
10
+ end
11
+ def self.instance
12
+ @instance || nil
13
+ end
14
+
15
+ private
16
+ def initialize file_content
17
+ # TODO: Support Nokogiri reading from http website instead of file path
18
+ @doc = Nokogiri::XML(file_content) do |config|
19
+ config.strict.noblanks
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module PistePal
2
+ module DataServices
3
+ class MaxSpeedAndAltitude
4
+ def self.call(trackpoints:)
5
+ new(trackpoints).call
6
+ end
7
+
8
+ def call
9
+ max_speed_and_altitude
10
+ end
11
+
12
+ private
13
+
14
+ def initialize(trackpoints)
15
+ @trackpoints = trackpoints
16
+ end
17
+
18
+ def max_speed_and_altitude
19
+ max_speed = 0
20
+ max_alt = 0
21
+ @trackpoints.each do |trackpoint|
22
+ max_speed = trackpoint.speed if trackpoint.speed > max_speed
23
+ max_alt = trackpoint.elevation if trackpoint.elevation > max_alt
24
+ end
25
+ [max_speed, max_alt]
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,119 @@
1
+ module PistePal
2
+ module DataServices
3
+ class RunsAndLifts
4
+
5
+ MINIMUM_DEVIATION = 5
6
+
7
+ def self.call(trackpoints:)
8
+ new(trackpoints: trackpoints).call
9
+ end
10
+
11
+ def call
12
+ runs_and_lifts
13
+ end
14
+
15
+ private
16
+
17
+ def initialize(trackpoints:)
18
+ @trackpoints = trackpoints
19
+ end
20
+
21
+ def runs_and_lifts
22
+ group_runs_and_lifts(aggregate_runs_by_direction)
23
+ end
24
+
25
+ def group_runs_and_lifts groupings
26
+ runs = []
27
+ lifts = []
28
+ total_groups_count = groupings.count - 1
29
+
30
+ previous_group = nil
31
+
32
+ for i in 1..total_groups_count do
33
+ previous_group = groupings[i - 1] if i == 1
34
+
35
+ if groupings[i][:trackpoints].count > MINIMUM_DEVIATION
36
+ if groupings[i][:direction] == previous_group[:direction]
37
+ previous_group[:trackpoints].concat(groupings[i][:trackpoints])
38
+ else
39
+ if previous_group[:direction] == "ascending"
40
+ lifts.push(previous_group)
41
+ else
42
+ runs.push(previous_group)
43
+ end
44
+ previous_group = groupings[i]
45
+ end
46
+ elsif groupings[i][:trackpoints].count <= MINIMUM_DEVIATION
47
+ previous_group[:trackpoints].concat(groupings[i][:trackpoints])
48
+ end
49
+
50
+ if i == total_groups_count
51
+ if previous_group[:direction] == "ascending"
52
+ lifts.push(previous_group)
53
+ else
54
+ runs.push(previous_group)
55
+ end
56
+ end
57
+ end
58
+
59
+ # Not a perfect system, but for now let's delete the runs that have bad data
60
+ runs = runs.delete_if {|run| run[:trackpoints].first.elevation < run[:trackpoints].last.elevation}
61
+
62
+ [runs.map {|run| run[:trackpoints]}, lifts.map {|lift| lift[:trackpoints]}]
63
+ end
64
+
65
+ def aggregate_runs_by_direction
66
+ groupings = []
67
+ current_group = []
68
+ total_points = @trackpoints.count - 1
69
+ descending = true
70
+
71
+ for i in 1..total_points do
72
+ if i == 1
73
+ current_group.push(@trackpoints[i - 1])
74
+ current_group.push(@trackpoints[i])
75
+ if @trackpoints[i].elevation < @trackpoints[i - 1].elevation
76
+ descending = true
77
+ elsif @trackpoints[i].elevation > @trackpoints[i - 1].elevation
78
+ descending = false
79
+ # else they are equal, and it doesn't matter what direction we choose to start
80
+ end
81
+ next
82
+ end
83
+
84
+ if @trackpoints[i].elevation < @trackpoints[i - 1].elevation
85
+ if !descending
86
+ groupings.push({ direction: descending ? "descending" : "ascending", trackpoints: current_group })
87
+ current_group = []
88
+ descending = true
89
+ end
90
+ elsif @trackpoints[i].elevation > @trackpoints[i - 1].elevation
91
+ if descending
92
+ groupings.push({ direction: descending ? "descending" : "ascending", trackpoints: current_group })
93
+ current_group = []
94
+ descending = false
95
+ end
96
+ end
97
+
98
+ current_group.push(@trackpoints[i])
99
+
100
+ if i == total_points
101
+ groupings.push({ direction: descending ? "descending" : "ascending", trackpoints: current_group })
102
+ end
103
+
104
+ end
105
+ #
106
+ # groupings.each do |group|
107
+ # puts "\n\nNew Group:\nDirection: #{group[:direction]}\n"
108
+ # group[:trackpoints].each do |point|
109
+ # puts point.elevation
110
+ # end
111
+ # end
112
+ groupings
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+
119
+
@@ -0,0 +1,30 @@
1
+ module PistePal
2
+ module DataServices
3
+ class TallestAndLongestRun
4
+ def self.call(runs:)
5
+ new(runs: runs).call
6
+ end
7
+
8
+ def call
9
+ tallest = 0
10
+ longest = 0
11
+ @runs.each do |run|
12
+ vertical = run.first.elevation - run.last.elevation
13
+ distance = PistePal::DataServices::Distance.call(trackpoints: run)
14
+
15
+ tallest = vertical if vertical > tallest
16
+ longest = distance if distance > longest
17
+ end
18
+
19
+ [tallest, longest]
20
+ end
21
+
22
+ private
23
+
24
+ def initialize(runs:)
25
+ @runs = runs
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,50 @@
1
+ require 'nokogiri'
2
+ module PistePal
3
+ module DataServices
4
+ class Trackpoints
5
+
6
+ MINIMUM_ACCEPTABLE_DOP = 10
7
+
8
+ def self.call
9
+ new().call
10
+ end
11
+
12
+ def call
13
+ extract_trackpoints
14
+ @trackpoints
15
+ end
16
+
17
+ private
18
+
19
+ def initialize()
20
+ @doc = PistePal::DataServices::GpxDoc.instance.doc
21
+ @trackpoints = []
22
+ end
23
+
24
+ def extract_trackpoints
25
+ trackpoints = @doc.xpath("//xmlns:trkpt")
26
+ trackpoints.each do |tp|
27
+ trackpoint = PistePal::Trackpoint.new(**extract_params_from_trackpoint_node(tp))
28
+ @trackpoints.push(trackpoint) if trackpoint.hdop <= MINIMUM_ACCEPTABLE_DOP && trackpoint.vdop <= MINIMUM_ACCEPTABLE_DOP
29
+ end
30
+ end
31
+
32
+ def extract_params_from_trackpoint_node trackpoint
33
+ params = Hash.new
34
+ params[:lat] = trackpoint.xpath('@lat').to_s.to_f
35
+ params[:lon] = trackpoint.xpath('@lon').to_s.to_f
36
+ trackpoint.children.each do |child|
37
+ params[:elevation] = child.text.strip.to_f if child.name == "ele"
38
+ params[:time] = child.text.strip if child.name == "time"
39
+ params[:hdop] = child.text.strip.to_i if child.name == "hdop"
40
+ params[:vdop] = child.text.strip.to_i if child.name == "vdop"
41
+ end
42
+ extensions = trackpoint.children.search("extensions").first
43
+ extensions.children.each do |child|
44
+ params[:speed] = child.attributes["speed"].value.to_s.to_f if child.name == "gps" && !child.attributes["speed"].nil?
45
+ end
46
+ return params
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ module PistePal
2
+ module DataServices
3
+ class Vertical
4
+ def self.call(trackpoints:)
5
+ new(trackpoints: trackpoints).call
6
+ end
7
+
8
+ def call
9
+ vertical = 0
10
+ @trackpoints.each do |trackpoints|
11
+ vertical += (trackpoints.first.elevation - trackpoints.last.elevation)
12
+ end
13
+ vertical
14
+ end
15
+
16
+ private
17
+
18
+ def initialize(trackpoints:)
19
+ @trackpoints = trackpoints
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+
@@ -0,0 +1,100 @@
1
+ module PistePal
2
+ class DayPass
3
+ attr_accessor :resort, :date, :trackpoints, :maximum_speed, :peak_altitude, :vertical, :distance, :tallest_run, :longest_run, :runs, :lifts
4
+
5
+ def self.purchase file_content
6
+ new(file_content: file_content)
7
+ end
8
+
9
+ # convert mp/h to km/h
10
+ def maximum_speed(system = "imperial")
11
+ if system == "metric"
12
+ value = (@maximum_speed * 1.60934)
13
+ unit = "kmh"
14
+ elsif system == "imperial"
15
+ value = @maximum_speed
16
+ unit = "mph"
17
+ end
18
+
19
+ { :value => value, :unit => unit }
20
+ end
21
+
22
+ # convert meters to feet
23
+ def peak_altitude(system = "imperial")
24
+ if system == "metric"
25
+ value = @peak_altitude
26
+ unit = "meters"
27
+ elsif system == "imperial"
28
+ value = @peak_altitude * 3.28084
29
+ unit = "feet"
30
+ end
31
+
32
+ { :value => value, :unit => unit }
33
+ end
34
+
35
+ # convert meters to km / miles
36
+ def distance(system = "imperial")
37
+ if system == "metric"
38
+ value = @distance / 1000
39
+ unit = "kilometers"
40
+ elsif system == "imperial"
41
+ value = @distance / 1609.344
42
+ unit = "miles"
43
+ end
44
+
45
+ { :value => value, :unit => unit }
46
+ end
47
+
48
+ # convert meters to feet
49
+ def vertical(system = "imperial")
50
+ if system == "metric"
51
+ value = @vertical
52
+ unit = "meters"
53
+ elsif system == "imperial"
54
+ value = @vertical * 3.28084
55
+ unit = "feet"
56
+ end
57
+
58
+ { :value => value, :unit => unit }
59
+ end
60
+
61
+ # convert meters to feet
62
+ def tallest_run(system = "imperial")
63
+ if system == "metric"
64
+ value = @tallest_run
65
+ unit = "meters"
66
+ elsif system == "imperial"
67
+ value = @tallest_run * 3.28084
68
+ unit = "feet"
69
+ end
70
+
71
+ { :value => value, :unit => unit }
72
+ end
73
+
74
+ # convert meters to miles
75
+ def longest_run(system = "imperial")
76
+ if system == "metric"
77
+ value = @longest_run / 1000
78
+ unit = "kilometers"
79
+ elsif system == "imperial"
80
+ value = @longest_run / 1609.344
81
+ unit = "miles"
82
+ end
83
+
84
+ { :value => value, :unit => unit }
85
+ end
86
+
87
+ private
88
+
89
+ def initialize(file_content:)
90
+ PistePal::DataServices::GpxDoc.set_instance(file_content)
91
+ @trackpoints = PistePal::DataServices::Trackpoints.call
92
+ @date, @resort = PistePal::DataServices::DateAndResort.call
93
+ @maximum_speed, @peak_altitude = PistePal::DataServices::MaxSpeedAndAltitude.call(trackpoints: @trackpoints)
94
+ @runs, @lifts = PistePal::DataServices::RunsAndLifts.call(trackpoints: @trackpoints)
95
+ @distance = PistePal::DataServices::Distance.call(trackpoints: @runs)
96
+ @vertical = PistePal::DataServices::Vertical.call(trackpoints: @runs)
97
+ @tallest_run, @longest_run = PistePal::DataServices::TallestAndLongestRun.call(runs: @runs)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,35 @@
1
+ module PistePal
2
+ class SeasonPass
3
+ attr_accessor :days, :resorts, :runs, :vertical, :distance
4
+
5
+ def self.purchase files
6
+ new(files: files)
7
+ end
8
+
9
+ def days(timestamp_only: false)
10
+ return @days if !timestamp_only
11
+ return @days.map {|day| day.date }
12
+ end
13
+
14
+ private
15
+
16
+ def initialize(files:)
17
+ @days = []
18
+ files.each do |f|
19
+ @days.push(PistePal::DayPass.purchase(f))
20
+ end
21
+
22
+ @resorts = @days.map {|day| day.resort }.uniq
23
+ @runs = @days.map {|day| day.runs.count }.reduce(0, :+)
24
+ @vertical = {
25
+ :value => @days.map {|day| day.vertical[:value] }.reduce(0, :+),
26
+ :unit => @days.first.vertical[:unit]
27
+ }
28
+ @distance = {
29
+ :value => @days.map {|day| day.distance[:value] }.reduce(0, :+),
30
+ :unit => @days.first.distance[:unit]
31
+ }
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,15 @@
1
+ module PistePal
2
+ class Trackpoint
3
+ attr_accessor :lat, :lon, :elevation, :time, :hdop, :vdop, :speed
4
+
5
+ def initialize(lat: nil, lon: nil, elevation: nil, time: nil, hdop: nil, vdop: nil, speed: nil)
6
+ @lat = lat
7
+ @lon = lon
8
+ @elevation = elevation
9
+ @time = time
10
+ @hdop = hdop
11
+ @vdop = vdop
12
+ @speed = speed
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module PistePal
2
+ VERSION = "1.0.0"
3
+ end
data/lib/piste_pal.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "piste_pal/version"
2
+ require "piste_pal/day_pass"
3
+ require "piste_pal/season_pass"
4
+ require "piste_pal/trackpoint"
5
+ require "piste_pal/data_services/gpx_doc"
6
+ require "piste_pal/data_services/trackpoints"
7
+ require "piste_pal/data_services/date_and_resort"
8
+ require "piste_pal/data_services/max_speed_and_altitude"
9
+ require "piste_pal/data_services/distance"
10
+ require "piste_pal/data_services/runs_and_lifts"
11
+ require "piste_pal/data_services/vertical"
12
+ require "piste_pal/data_services/tallest_and_longest_run"
data/piste_pal.gemspec ADDED
@@ -0,0 +1,34 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "piste_pal/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "piste_pal"
8
+ spec.version = PistePal::VERSION
9
+ spec.authors = ["Josh Menden"]
10
+ spec.email = ["josh.menden@gmail.com"]
11
+
12
+ spec.summary = %q{A Ruby Gem that helps calculate Skiing statistics from GPS data.}
13
+ # spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ spec.homepage = "https://github.com/joshmenden/piste_pal"
15
+ spec.license = "MIT"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_development_dependency "bundler", "~> 2.0"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency "byebug", "~> 11.0"
31
+ spec.add_dependency "nokogiri", "~> 1.10"
32
+ spec.add_dependency "rake", "~> 10.0"
33
+ spec.add_dependency "vincenty_distance", "~> 1.0.0"
34
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: piste_pal
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Menden
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '11.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '11.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.10'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vincenty_distance
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.0.0
111
+ description:
112
+ email:
113
+ - josh.menden@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - bin/console
127
+ - bin/setup
128
+ - lib/piste_pal.rb
129
+ - lib/piste_pal/data_services/date_and_resort.rb
130
+ - lib/piste_pal/data_services/distance.rb
131
+ - lib/piste_pal/data_services/gpx_doc.rb
132
+ - lib/piste_pal/data_services/max_speed_and_altitude.rb
133
+ - lib/piste_pal/data_services/runs_and_lifts.rb
134
+ - lib/piste_pal/data_services/tallest_and_longest_run.rb
135
+ - lib/piste_pal/data_services/trackpoints.rb
136
+ - lib/piste_pal/data_services/vertical.rb
137
+ - lib/piste_pal/day_pass.rb
138
+ - lib/piste_pal/season_pass.rb
139
+ - lib/piste_pal/trackpoint.rb
140
+ - lib/piste_pal/version.rb
141
+ - piste_pal.gemspec
142
+ homepage: https://github.com/joshmenden/piste_pal
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.7.6
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: A Ruby Gem that helps calculate Skiing statistics from GPS data.
166
+ test_files: []