d235866c2e10e5ed2b32d69d848fabc25d8cdf8f
[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 parser.add_argument('--distance',
97 '-d',
98 type=float,
99 required=True,
100 help='Distance (in km, by default)')
101
102 parser.add_argument('--elevation',
103 '-e',
104 type=float,
105 required=True,
106 help='Elevation change (in m, by default)')
107
108 parser.add_argument('--travel-mode',
109 '-t',
110 type=str,
111 default='uphill',
112 choices=travel_mode_choices, required=False,
113 help='Travel mode (uphill, by default)')
114
115 parser.add_argument('--fitness',
116 '-f',
117 type=str,
118 default='average',
119 choices=fitness_choices, required=False,
120 help='Fitness modifier (average, by default)')
121
122 parser.add_argument('--units',
123 '-u',
124 type=str,
125 default='imperial',
126 required=False,
127 choices=unit_choices,
128 help='Units of input values')
129
130 parser.add_argument('--pretty',
131 '-p',
132 action='store_true',
133 default=False,
134 required=False,
135 help="Make output pretty");
136
137 parser.add_argument('--gui',
138 '-g',
139 action='store_true',
140 default=False,
141 required=False,
142 help='Launch GUI mode (overrides --pretty)')
143
144 return parser
145
146 def main():
147 parser = get_parser()
148 opts = parser.parse_args()
149
150 distance = opts.distance
151 elevation = opts.elevation
152 fitness = opts.fitness
153 units = opts.units
154 travel_mode = opts.travel_mode
155 pretty = opts.pretty
156 gui = opts.gui
157
158 time_estimate = time_calc(distance=distance, elevation=elevation,
159 fitness=fitness, rate=travel_mode, units=units)
160
161 if gui:
162 from . import gui
163 gui.startup()
164 else:
165 if pretty:
166 print_pretty_estimate(time_estimate)
167 else:
168 print_ugly_estimate(time_estimate)
169
170 return 0
171
172 if __name__ == "__main__":
173 sys.exit(main())