bfc286bff043c997176e0ebef31cf749a4c9cde2
[munter.git] / munter / munter.py
1 # -*- coding: utf-8 -*-
2
3
4 """
5 Munter Time Calculation
6 Alexander Vasarab
7 Wylark Mountaineering LLC
8
9 A rudimentary program which implements the Munter time calculation.
10 """
11
12 import sys
13 import argparse
14
15 class InvalidUnitsException(Exception):
16 pass
17
18 rates = {
19 'uphill': { 'rate': 4, 'direction': '↑' },
20 'flat': { 'rate': 6, 'direction': '→' }, # or downhill on foot
21 'downhill': { 'rate': 10, 'direction': '↓' },
22 'bushwhacking': { 'rate': 2, 'direction': '↹' },
23 }
24
25 fitnesses = {
26 'slow': 1.2,
27 'average': 1,
28 'fast': .7,
29 }
30
31 unit_choices = ['metric', 'imperial']
32 travel_mode_choices = rates.keys()
33 fitness_choices = fitnesses.keys()
34
35 def time_calc(distance, elevation, fitness='average', rate='uphill',
36 units='imperial'):
37 retval = {}
38
39 if units not in unit_choices:
40 raise InvalidUnitsException
41
42 unit_count = 0
43
44 if 'imperial' == units:
45 # convert to metric
46 distance = (distance * 1.609) # mi to km
47 elevation = (elevation * .305) # ft to m
48
49 unit_count = distance + (elevation / 100.0)
50
51 retval['time'] = (distance + (elevation / 100.0)) / rates[rate]['rate']
52 retval['time'] = retval['time'] * fitnesses[fitness]
53
54 retval['unit_count'] = unit_count
55 retval['direction'] = rates[rate]['direction']
56 retval['pace'] = rates[rate]['rate']
57
58 return retval
59
60 def print_ugly_estimate(est):
61 hours = int(est['time'])
62 minutes = int((est['time'] - hours) * 60)
63 print("{human_time}".format(
64 human_time="{hours} hours {minutes} minutes".format(
65 hours=hours,
66 minutes=minutes)))
67
68 def print_pretty_estimate(est):
69 hours = int(est['time'])
70 minutes = int((est['time'] - hours) * 60)
71
72 # NOTE: Below, the line with the unicode up arrow uses an alignment
73 # value of 31. In the future, consider using e.g. wcwidth
74 # library so that this is more elegant.
75 print("\n\t╒═══════════════════════════════╕")
76 print("\t╎▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╎╮")
77 print("\t╎▒{:^29}▒╎│".format(''))
78 print("\t╎▒{pace_readable:^31}▒╎│".format(
79 pace_readable="{units} {direction} @ {pace}".format(
80 units=round(est['unit_count']),
81 direction=est['direction'],
82 pace=est['pace'])))
83 print("\t╎▒{human_time:^29}▒╎│".format(
84 human_time="{hours} hours {minutes} minutes".format(
85 hours=hours,
86 minutes=minutes)))
87 print("\t╎▒{:^29}▒╎│".format(''))
88 print("\t╎▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╎│")
89 print("\t╘═══════════════════════════════╛│")
90 print("\t └───────────────────────────────┘\n")
91
92 def get_parser():
93 parser = argparse.ArgumentParser(description='Implementation of '
94 'the Munter time calculation')
95
96 # No required args anymore, since -g overrides any requirement
97 parser.add_argument('--distance',
98 '-d',
99 default=0.0,
100 type=float,
101 required=False,
102 help='Distance (in km, by default)')
103
104 parser.add_argument('--elevation',
105 '-e',
106 default=0.0,
107 type=float,
108 required=False,
109 help='Elevation change (in m, by default)')
110
111 parser.add_argument('--travel-mode',
112 '-t',
113 type=str,
114 default='uphill',
115 choices=travel_mode_choices, required=False,
116 help='Travel mode (uphill, by default)')
117
118 parser.add_argument('--fitness',
119 '-f',
120 type=str,
121 default='average',
122 choices=fitness_choices, required=False,
123 help='Fitness modifier (average, by default)')
124
125 parser.add_argument('--units',
126 '-u',
127 type=str,
128 default='imperial',
129 required=False,
130 choices=unit_choices,
131 help='Units of input values')
132
133 parser.add_argument('--pretty',
134 '-p',
135 action='store_true',
136 default=False,
137 required=False,
138 help="Make output pretty");
139
140 parser.add_argument('--gui',
141 '-g',
142 action='store_true',
143 default=False,
144 required=False,
145 help='Launch GUI mode (overrides --pretty)')
146
147 return parser
148
149 def main():
150 parser = get_parser()
151 opts = parser.parse_args()
152
153 distance = opts.distance
154 elevation = opts.elevation
155 fitness = opts.fitness
156 units = opts.units
157 travel_mode = opts.travel_mode
158 pretty = opts.pretty
159 gui = opts.gui
160
161 time_estimate = time_calc(distance=distance, elevation=elevation,
162 fitness=fitness, rate=travel_mode, units=units)
163
164 if gui:
165 from . import gui
166 gui.startup()
167 else:
168 if pretty:
169 print_pretty_estimate(time_estimate)
170 else:
171 print_ugly_estimate(time_estimate)
172
173 return 0
174
175 if __name__ == "__main__":
176 sys.exit(main())