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