Heads-I-Lose

Changes On Branch origin/master
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch origin/master Excluding Merge-Ins

This is equivalent to a diff from 8d51987b6f to 9c81571ac3

2018-06-05
10:56
Formatting Leaf check-in: 64b10ac7d9 user: noreply@github.com tags: master, trunk
2014-12-26
22:15
Add basic implementation for selecting between route geometries

Not a great implementation, but it works. If no route geometry choice is
made it defaults to the default "route_geometry". If
"alternative_geometries" is specified it uses the first one of the
alternatives (because as far as I can tell there is only ever one
alternative? I supposed at some point I could extend if needed, perhaps
use a tuple argument: {"alternative_geomtries", 1}). Leaf check-in: 9c81571ac3 user: base@atomicules.co.uk tags: origin/master, trunk

2014-12-14
21:54
Remove some redundant comments check-in: 0c4224822a user: base@atomicules.co.uk tags: origin/master, trunk
2014-11-24
22:27
First commit of OSRM API bit to get route geometry

Commiting this in a back-to-front way as I actually first started with
this file before doing the polyline branch. But it was as doing this
that I realised I was going to need a polyline decoder after all because
the API didn't actually return the route instructions and so I'd have to
use the route geometry instead.

So this is just a "works enough to develop further" commit so I can work
on the changes to headsilose.erl next. check-in: a0c2266624 user: base@atomicules.co.uk tags: origin/master, trunk

2014-10-23
10:40
Extend to decode a full example polyline (multiple points instead of single)

As per the Encoded Polyline Algorithm Format page:
https://developers.google.com/maps/documentation/utilities/polylinealgorithm

- Previously the decoder only worked with a single point (baby steps and
all that) and was tested with the example point of `~oia@
- This commit extends it to work with the example of
_p~iF~ps|U_ulLnnqC_mqNvxq`@
- Probably still to do is figure out decoding an unsigned value and the
bit about string literals.

Basically alter functions to use a list of lists approach:

- Adds split_up_six_bits which does what it says on the tin and looks
for the absence of the 0x20 bit and creates a list of lists of each
point's bit chunks.
- five_bit_chunks is now called six_bit_chunks since the groups of
chunks have to be split up at the six bit stage.
- Similarly, five_bit_chunk is now called six_bit_chunk; it is otherwise
exactly the same except from not "un-or-ing" the 0x20 bit to go from
six bits to five bits. Still padding to six bits, but I wonder if this
is entirely unnecessary and I can use this to determine the absence of
the 0x20 bit and thus where to split up the chunks into groups?
- five_bit_chunks now does the "un-or-ing" by mapping over the list of
lists. Perhaps this could be bundled in with the rest of the big
lists:map in the top level decode function?
- most of what was in the top level decode function remains the same,
but is just mapped over a list now. These should probably be at least
pulled out as separate functions that are mapped over. Need to decide
which approach to go for: functions that map or mapping functions.
- Changes to reversing, specifically the un-reversing of the five bit
chunks. This was bundled in with the five_bit_chunks function and the
un-reversing was achieved by simply not doing lists:reverse since the
function naturally reversed it. Now, to follow the algorithm backwards
step by step, there is now a lists:reverse at the end of
six_bit_chunks which then has to be undone by including a
lists:reverse in five_bit_chunks.
- Add in algorithm step numbers to comments. Leaf check-in: 8d51987b6f user: base@atomicules.co.uk tags: polyline, trunk

2014-10-17
20:17
Fixes for non-negative numbers

But confusingly fixed by fixing the negative numbers which already
worked, but just not the way I thought they were working.

I.e. I forgot bin_flip returns a string so the If statement I had which
just returned Shifted_binary in the case of non-negative numbers would
then fall over on the following steps because it wasn't a string.

Change so this bit just deals in numbers and not strings.

There will be lots of bitslike this where things can be simplified by me
not following the algorithm quite so literally. check-in: 9356c1341b user: base@atomicules.co.uk tags: polyline, trunk


Changes to README.md.

1
2
3
4
5
6


7

8
9

10
11
12
13
14
15
16
17
18


19
20
21
22
23














24
25
26
27
28
29
30
31
32
33
34





35
36
37

38
39




#Heads I Lose

A little learning exercise in Erlang. It looks up the wind direction ([via the MetOffice Datapoint API](http://www.metoffice.gov.uk/datapoint/api)) for my cycle commute and lets me know whether I win (tail wind) or lose (head wind).

##Usage



1. Get an [API key](http://www.metoffice.gov.uk/datapoint/support/API) and save in a file called `~/.datapoint`.

2. Compile with `erlc headsilose.erl; erlc weather_types.erl`.
3. Run `erl -run headsilose get_locations <Optional search term> -noshell -s init stop` to get a list of locations and Ids. The optional search term cannot contain spaces.

4. Then run `erl -run headsilose headsilose <location id> <heading in 16-point compass format> -noshell -s init stop` to get the result.

Since I wrote this to be semi-useful for me, the result returned depends on the time of the day. If it's run before 8am it looks for the 6am weather data (since data is in 3 hour periods), run between 8am and 7pm it looks for the 6pm data for going home and run after that time it looks again for the 6am data, but for the next day.

Heading is the overall direction of travel in 16-point compass format. So a heading of N means going from south to north. I only mention this because wind directions are "directions from".

_Hint:_ I have a shell function defined as follows:

	function headsilose { erl -pa /path/to/heads-I-lose/ -run headsilose headsilose XXXXXX "$@" -noshell; }



So I can just call

	headsilose N















_Note:_ I don't use [init stop](http://erlangcentral.org/wiki/index.php?title=Running_Erlang_Code_From_The_Command_Line&oldid=2293) in the command line call as I have that in my script instead. Otherwise, if `headsilose` errors out then `init stop` will crash out (I guess because it is trying to stop something that isn't running).

##Credits

Various posts I've found that have helped me out:

- Initial inspiration from [PragDave - A First Erlang Program](http://pragdave.pragprog.com/pragdave/2007/04/a_first_erlang_.html)
- Putting initial inspiration to practice (i.e. `inets:start`) from [Andrew Locatelli Woodcock - Connecting to Cloudant from Erlang: a quick example of using HTTPS from httpc:request](http://andrewlocatelliwoodcock.com/2012/06/12/connecting-to-cloudant-from-erlang-a-quick-example-of-using-https-from-httpcrequest-17-2/)
- The recursive parsing of XML from [Sam Ruby - Parsing Atom with Erlang](http://intertwingly.net/blog/2007/08/28/Parsing-Atom-with-Erlang)
- Formating dates with leading zeroes from [Warren Young on Stack Overflow](http://stackoverflow.com/a/7599506/208793)
- Figuring out command line arguments from [Cody on Stack Overflow](http://stackoverflow.com/a/8498073/208793)






##Todo


- Make it factor in wind speed and gusts to the win/lose decision as opposed to just direction.
- Sort the list of locations?






|



>
>
|
>
|
|
>
|

|
<
<



|
>
>



|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
|










>
>
>
>
>



>
|

>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#Heads I Lose

This started as a little learning exercise in Erlang, but I've been building on it to make it more useful for my cycle commute. It looks up the wind direction ([via the MetOffice Datapoint API](http://www.metoffice.gov.uk/datapoint/api)) and for a given route (a polyline from [OSRM](http://project-osrm.org/)) determines how much of the route is a headwind sidewind or tailwind. 

##Usage

Since it has just been developed for personal use installation is not very polished.

1. Get a MetOffice Datapoint [API key](http://www.metoffice.gov.uk/datapoint/support/API) and save in a file called `~/.datapoint`.
2. Get/install [Jiffy](https://github.com/davisp/jiffy). (I just cloned the repository, issued `make` and copied the `ebin` and `priv` directories to my `heads-I-lose` directory).
3. Within the `heads-I-lose` directory, compile with `erlc headsilose.erl; erlc weather_types.erl; erlc osrm.erl; erlc polyline.erl`.
4. Run `erl -run headsilose get_locations <Optional search term> -noshell -s init stop` to get a list of locations and Ids. The optional search term cannot contain spaces.
5. From within the erlang shell (because I don't yet know how to pass negative numbers on the command line) run `osrm:get_route([<start_lat> ,<start_lon>],[<end_lat>,<end_lon>])` to get a route from OSRM. You'll have to find the latitude and longitudes by [some other means](http://www.uk-postcodes.com/)) for now. This will save the route in `~/.headsilose-route`.
6. Run `erl -run headsilose headsilose <location id> -noshell` to get the result.

Since I wrote this to be semi-useful for me, the result returned depends on the time of the day. If it's run before 8am it looks for the 6am weather data (since data is in 3 hour periods) and assumes the route is being traversed normally, run between 8am and 7pm it looks for the 6pm data for going home and therefore also traverses the saved route in reverse, and run after that time it looks again for the 6am data, but for the next day, and thus the route is back to being traversed in the normal direction.



_Hint:_ I have a shell function defined as follows:

	function headsilose {
		erl -pa /home/simon/Code/github/atomicules/heads-I-lose /home/simon/Code/github/atomicules/heads-I-lose/ebin -run headsilose headsilose XXXXXX alternative_geometries -noshell;
	}

So I can just call

	headsilose

Which will result in something like the following being printed out:

	It's a draw
	47.1% Headwind
	51.45% Sidewind
	1.45% Tailwind
	Direction: SSW
	Speed: 13 mph
	Gust: 29 mph
	Weather type: Cloudy
	Temperature: 4 deg C

_Note1:_ Since the route is from OSRM there is the option to specify `alternative_geometries` as an argument. In theory OSRM can return more than one alternative route, but in practice (at least as far as I can tell) I think it only ever returns one alternative route so although the argument is `alternative_geometries` it is actually the first alternative that is used. If `alternative_geometries` is not specified then the default route is used. If you'd like to see visually which route is the default and which is the alternative you can see them at [Project OSRM](http://map.project-osrm.org/) as routes A (default) and B (alternative).

_Note2:_ I don't use [init stop](http://erlangcentral.org/wiki/index.php?title=Running_Erlang_Code_From_The_Command_Line&oldid=2293) in my main command line call as I have that in my script instead. Otherwise, if `headsilose` errors out then `init stop` will crash out (I guess because it is trying to stop something that isn't running).

##Credits

Various posts I've found that have helped me out:

- Initial inspiration from [PragDave - A First Erlang Program](http://pragdave.pragprog.com/pragdave/2007/04/a_first_erlang_.html)
- Putting initial inspiration to practice (i.e. `inets:start`) from [Andrew Locatelli Woodcock - Connecting to Cloudant from Erlang: a quick example of using HTTPS from httpc:request](http://andrewlocatelliwoodcock.com/2012/06/12/connecting-to-cloudant-from-erlang-a-quick-example-of-using-https-from-httpcrequest-17-2/)
- The recursive parsing of XML from [Sam Ruby - Parsing Atom with Erlang](http://intertwingly.net/blog/2007/08/28/Parsing-Atom-with-Erlang)
- Formating dates with leading zeroes from [Warren Young on Stack Overflow](http://stackoverflow.com/a/7599506/208793)
- Figuring out command line arguments from [Cody on Stack Overflow](http://stackoverflow.com/a/8498073/208793)
- Figuring out [Jiffy](http://www.snip2code.com/Snippet/51463/how-to-support-chinese-in-http-request-b/)
- [Rounding numbers](http://www.codecodex.com/wiki/index.php?title=Round_a_number_to_a_specific_decimal_place#Erlang)
- [Converting between binary and decimal](http://erlangcentral.org/wiki/index.php/Converting_Between_Binary_and_Decimal)

I did read through a [number](http://www.mathworks.com/matlabcentral/fileexchange/32341-google-maps-api-polyline-decoder) [of](http://jeffreysambells.com/2010/05/27/decoding-polylines-from-google-maps-direction-api-with-java) [posts](http://seewah.blogspot.co.uk/2009/11/gpolyline-decoding-in-python.html)/[implementations](http://unitstep.net/blog/2008/08/02/decoding-google-maps-encoded-polylines-using-php/) [of](https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js) polyline decoders, but they didn't really help me in Erlang so I just worked backwards through the [specification](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) and wrote my own, much less concise, version rather than porting an existing implementation.

##Todo

_I should really put these as issues_

- Sort the list of locations?
- Finish off implementing the list of client "supposed to"s from the OSRM API, such as checksum and hint data, but this would mean caching all previous requests made.
- Use postcodes to get the latitudes and longitudes via [UK Postcodes](http://www.uk-postcodes.com/)
- Figure out how to pass negative numbers on the command line (perhaps use [getopt](http://github.com/jcomellas/getopt)?)
- Use tuples instead of lists where it makes sense

Changes to headsilose.erl.

1
2
3
4


5
6
7
8
9
10
11
12
13
14
15
16
-module(headsilose).
-export([get_locations/0, get_locations/1, get_weather/1, headsilose/2, headsilose/1]).
-include_lib("xmerl/include/xmerl.hrl").
-import(weather_types, [weather_type/1]).



%Supply a direction and location and work out if head wind or not
%For now "know the location id" upfront, but ideally need to search for it at some point or present a choice.

%Initially based on: http://pragdave.pragprog.com/pragdave/2007/04/a_first_erlang_.html 

-define(BASE_URL,
	"http://datapoint.metoffice.gov.uk/public/data/").

-define(WXFCS_SITELIST,
	"val/wxfcs/all/xml/sitelist").


|


>
>




|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-module(headsilose).
-export([get_locations/0, get_locations/1, headsilose/1]).
-include_lib("xmerl/include/xmerl.hrl").
-import(weather_types, [weather_type/1]).
-import(polyline, [decode/1]).
-import(osrm, [read_route/1]).

%Supply a direction and location and work out if head wind or not
%For now "know the location id" upfront, but ideally need to search for it at some point or present a choice.

%Initially based on: http://pragdave.pragprog.com/pragdave/2007/04/a_first_erlang_.html

-define(BASE_URL,
	"http://datapoint.metoffice.gov.uk/public/data/").

-define(WXFCS_SITELIST,
	"val/wxfcs/all/xml/sitelist").

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131

132
133
134
135

136


137
138
139
140

141
142
143
144
145
146
147
148
149
150

151

152

153
154

155























































156

157

158
159
160
161
162
163




























































164


165





166
167
168
169
170
171
172
	%Need to do this recursively
	[ #xmlAttribute{value=Location} ] = xmerl_xpath:string("@name", Node),
	[ #xmlAttribute{value=ID} ] = xmerl_xpath:string("@id", Node),
	io:format(Location++", "++ID++"~n"),
	print_locations(Rest).


get_weather(Location) ->
	inets:start(),
	Key = readapikey(),
	URL = ?BASE_URL ++ ?WXFCS_LOCATIONID ++ Location ++ "?key=" ++ Key ++ "&res=3hourly",
	Date_today = erlang:localtime(),
	{ Date_formatted, Rep } = date_and_rep(Date_today),
	try
		{ ok, {_Status, _Headers, Body }} = httpc:request(get, {URL, []}, [{timeout, timer:seconds(10)}], []),
		{ Xml, _Rest } = xmerl_scan:string(Body),
		[ [ #xmlAttribute{value=Direction} ],
		  [ #xmlAttribute{value=Speed} ],
		  [ #xmlAttribute{value=Gust} ],
		  [ #xmlAttribute{value=Weather} ],
		  [ #xmlAttribute{value=Temperature} ] ] = lists:map(fun(X) ->

				xmerl_xpath:string("//Period[@value='" ++ Date_formatted ++ "']/Rep[.='" ++ Rep ++ "']/@"++X, Xml)
			end,
			["D", "S", "G", "W", "T"]),
		{Direction, Speed, Gust, Weather, Temperature}
	catch
		_:Reason ->
			io:format("API Might be down~n"),
			Reason
	after
		maybe_quit()
	end.



date_and_rep(Date) ->
	{{_Year, _Month, _Day}, {Hours, _Minutes, _Seconds}} = Date,
	date_and_rep(Date, Hours).
date_and_rep(Date, Hours) when Hours < 8 ->
	Rep = "360",
	{format_date(Date), Rep};
date_and_rep(Date, Hours) when Hours >= 8, Hours < 19 ->
	Rep = "1080",
	{format_date(Date), Rep};
date_and_rep(Date, Hours) when Hours >= 19 ->
	Rep = "360",
	{format_date(find_next_day(Date)), Rep}.


format_date(Date_to_format) ->
	{{Year, Month, Day}, {_Hours, _Minutes, _Seconds}} = Date_to_format,
	%Thanks to: http://stackoverflow.com/a/7599506/208793
	DateAsString = io_lib:format("~4..0w-~2..0w-~2..0wZ", [Year, Month, Day]),
	lists:flatten(DateAsString).


nth_wrap(N, List) ->
	Rem = N rem (length(List)),
	if Rem > 0 ->
		lists:nth(Rem, List);
	Rem =:= 0 ->
		1


	end.


find_next_day(Date_today) ->
	%get in seconds
	Seconds_today = calendar:datetime_to_gregorian_seconds(Date_today),
	Date_tomorrow = calendar:gregorian_seconds_to_datetime(Seconds_today+86400),
	Date_tomorrow.


maybe_quit() ->
	Args = init:get_arguments(), 
	Found = lists:keyfind(noshell, 1, Args),
	if Found =:= false ->
		dont_quit;
	true -> init:stop()

	end.	


%For command line usage, from http://stackoverflow.com/a/8498073/208793

headsilose([Location, Heading]) ->


	headsilose(Location, Heading).
headsilose(Location, Heading) ->
	{Direction, Speed, Gust, Weather, Temperature} = get_weather(Location),
	Weather_type = weather_types:weather_type(erlang:list_to_integer(Weather)),

	Compass = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"],
	%Quicker dirtier(?) way to do below would be: http://stackoverflow.com/a/6762191/208793
	Index = length(lists:takewhile(fun(X) -> X  =/= Direction end, Compass))+1,
	%Since heading is to direction and winds are from, opposite is -2 to +2, or to make it easier to wrap, +14 +18
	%Since heading is to direction and winds are from, sidewinds are -5 to -3 and +3 to +5, or to make it easier to wrap, +14 +18
	%And so on
	HeadwindList = lists:seq(14,18),
	SidewindList = lists:seq(3,5)++lists:seq(11,13),
	TailwindList = lists:seq(6,10),
	[Headwinds, Sidewinds, Tailwinds] = lists:map(fun(WindList) ->

		lists:map(fun(X) ->

			nth_wrap(Index+X, Compass) end,

			WindList)
		end,

		[HeadwindList, SidewindList, TailwindList]),























































	[Headwind, Sidewind, Tailwind] = lists:map(fun(Winds) ->

		lists:member(Heading, Winds) end,

		[Headwinds, Sidewinds, Tailwinds]),
	if Headwind ->
		io:format("Heads you lose!~n");
	Sidewind ->
		io:format("It's a draw~n");
	Tailwind ->




























































		io:format("Tails you win!~n")


	end,





	io:format("Direction: ~s~nSpeed: ~s mph~nGust: ~s mph~nWeather type: ~s~nTemperature: ~s deg C~n", [Direction, Speed, Gust, Weather_type, Temperature]).


%Need to read API key from file
readapikey() ->
	{_Status, KeyB} = file:read_file(os:getenv("HOME") ++ "/.datapoint"),
	string:strip(erlang:binary_to_list(KeyB),right,$\n).







|



<
<







|
>











<



















|
|







<
>
>















|
>
|


<
>
|
>
>
|
|
|
<
>


|



|
|
|
|
>
|
>
|
>
|

>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>


|

|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>

>
>
>
>
>





|
|
56
57
58
59
60
61
62
63
64
65
66


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

137
138
139
140
141
142
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
	%Need to do this recursively
	[ #xmlAttribute{value=Location} ] = xmerl_xpath:string("@name", Node),
	[ #xmlAttribute{value=ID} ] = xmerl_xpath:string("@id", Node),
	io:format(Location++", "++ID++"~n"),
	print_locations(Rest).


get_weather(Location, { Date_formatted, Rep }) ->
	inets:start(),
	Key = readapikey(),
	URL = ?BASE_URL ++ ?WXFCS_LOCATIONID ++ Location ++ "?key=" ++ Key ++ "&res=3hourly",


	try
		{ ok, {_Status, _Headers, Body }} = httpc:request(get, {URL, []}, [{timeout, timer:seconds(10)}], []),
		{ Xml, _Rest } = xmerl_scan:string(Body),
		[ [ #xmlAttribute{value=Direction} ],
		  [ #xmlAttribute{value=Speed} ],
		  [ #xmlAttribute{value=Gust} ],
		  [ #xmlAttribute{value=Weather} ],
		  [ #xmlAttribute{value=Temperature} ] ] = lists:map(
			fun(X) ->
				xmerl_xpath:string("//Period[@value='" ++ Date_formatted ++ "']/Rep[.='" ++ Rep ++ "']/@"++X, Xml)
			end,
			["D", "S", "G", "W", "T"]),
		{Direction, Speed, Gust, Weather, Temperature}
	catch
		_:Reason ->
			io:format("API Might be down~n"),
			Reason
	after
		maybe_quit()
	end.



date_and_rep(Date) ->
	{{_Year, _Month, _Day}, {Hours, _Minutes, _Seconds}} = Date,
	date_and_rep(Date, Hours).
date_and_rep(Date, Hours) when Hours < 8 ->
	Rep = "360",
	{format_date(Date), Rep};
date_and_rep(Date, Hours) when Hours >= 8, Hours < 19 ->
	Rep = "1080",
	{format_date(Date), Rep};
date_and_rep(Date, Hours) when Hours >= 19 ->
	Rep = "360",
	{format_date(find_next_day(Date)), Rep}.


format_date(Date_to_format) ->
	{{Year, Month, Day}, {_Hours, _Minutes, _Seconds}} = Date_to_format,
	%Thanks to: http://stackoverflow.com/a/7599506/208793
	Date_as_string = io_lib:format("~4..0w-~2..0w-~2..0wZ", [Year, Month, Day]),
	lists:flatten(Date_as_string).


nth_wrap(N, List) ->
	Rem = N rem (length(List)),
	if Rem > 0 ->
		lists:nth(Rem, List);
	Rem =:= 0 ->

		%Get last of list
		hd(lists:reverse(List))
	end.


find_next_day(Date_today) ->
	%get in seconds
	Seconds_today = calendar:datetime_to_gregorian_seconds(Date_today),
	Date_tomorrow = calendar:gregorian_seconds_to_datetime(Seconds_today+86400),
	Date_tomorrow.


maybe_quit() ->
	Args = init:get_arguments(), 
	Found = lists:keyfind(noshell, 1, Args),
	if Found =:= false ->
		dont_quit;
	true ->
		init:stop()
	end.



%From: http://www.codecodex.com/wiki/index.php?title=Round_a_number_to_a_specific_decimal_place#Erlang
round(Number, Precision) ->
	P = math:pow(10, Precision),
		round(Number * P) / P.


build_list_of_wind_directions(Wind_direction) ->

	%There is only one wind direction, but can build groups of directions that will count as head, side and tail winds
	Compass = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"],
	%Quicker dirtier(?) way to do below would be: http://stackoverflow.com/a/6762191/208793
	Index = length(lists:takewhile(fun(X) -> X  =/= Wind_direction end, Compass))+1,
	%Since heading is to direction and winds are from, opposite is -2 to +2, or to make it easier to wrap, +14 +18
	%Since heading is to direction and winds are from, sidewinds are -5 to -3 and +3 to +5, or to make it easier to wrap, +14 +18
	%And so on
	Headwind_list = lists:seq(14,18),
	Sidewind_list = lists:seq(3,5)++lists:seq(11,13),
	Tailwind_list = lists:seq(6,10),
	lists:map(
		fun(Wind_list) ->
			lists:map(
				fun(X) ->
					nth_wrap(Index+X, Compass)
				end,
				Wind_list)
		end,
		[Headwind_list, Sidewind_list, Tailwind_list]).


convert_lats_longs_to_distance_heading([_Head1 | [ _Head2 | Rest]])  -> 
	%All co-ords are diff, so just ignore first two
	convert_lats_longs_to_distance_heading_(Rest, []).
convert_lats_longs_to_distance_heading_([Lat | [Lon | Rest]], Distance_headings_list) ->
	%Want to map through the list convert co-ords to distance and heading
	Distance = math:sqrt(math:pow(Lat,2) + math:pow(Lon,2)),
	Heading_signed = math:atan2(Lon, Lat),
	%Need to convert heading into a 2π value
	Heading = if Heading_signed < 0 ->
				Heading_signed + 2*math:pi();
			true ->
				Heading_signed
			end,
	convert_lats_longs_to_distance_heading_(Rest, [{Distance, Heading}]++Distance_headings_list);
convert_lats_longs_to_distance_heading_([], Distance_headings_list) ->
	lists:reverse(Distance_headings_list).


journey(Distance_headings_list) ->
	lists:map(
		fun({Distance, Heading}) ->
			Compass_direction = get_compass_direction_for(Heading),
			{Distance, Compass_direction}
		end,
		Distance_headings_list).


reverse_journey(Distance_headings_list) ->
	%Don't actually need a correctly ordered reverse route, as long as we have directions and distances.
	lists:map(
		fun({Distance, Heading}) ->
			%Because can't have functions in guards
			Pi = math:pi(),
			Reverse_heading = if Heading < Pi ->
				Heading + Pi;
			Heading >= Pi ->
				Heading - Pi
			end,
			Compass_direction = get_compass_direction_for(Reverse_heading),
			{Distance, Compass_direction}
		end,
		Distance_headings_list).


get_compass_direction_for(Heading) ->
	%In a way this is a waste of time as could just do headwind, etc based on angles, but since already have some code, why not?
	Segment = 2*math:pi()/16,
	Segments = erlang:round(Heading/Segment)+1,
	Compass = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"],
	%Handle the case of Segments = 16. Need to wrap around.
	nth_wrap(Segments, Compass).


head_side_or_tail_wind(Direction, [Headwinds, Sidewinds, Tailwinds]) ->
	[Headwind, Sidewind, Tailwind] = lists:map(
		fun(Winds) ->
			lists:member(Direction, Winds)
		end,
		[Headwinds, Sidewinds, Tailwinds]),
	if Headwind ->
		headwind;
	Sidewind ->
		sidewind;
	Tailwind ->
		tailwind
	end.
%Something like that?
	

%First two for command line usage
headsilose([Location]) ->
	headsilose_(Location);
headsilose([Location, Route_choice]) ->
	headsilose_(Location, Route_choice).
headsilose_(Location) ->
	%If route choice not specified default to default!
	%The other choice is "alternative_geometries", for now I think there is only ever one alternative so pick this first.
	headsilose_(Location, "route_geometry").
headsilose_(Location, Route_choice) ->
	Date_today = erlang:localtime(),
	{ Date_formatted, Rep } = date_and_rep(Date_today),
	{Direction, Speed, Gust, Weather, Temperature} = get_weather(Location,  { Date_formatted, Rep }),
	Weather_type = weather_types:weather_type(erlang:list_to_integer(Weather)),
	[Headwinds, Sidewinds, Tailwinds] = build_list_of_wind_directions(Direction),
	{_Checksum, Polyline} = osrm:read_route(Route_choice),
	Polyline_decoded = polyline:decode(Polyline),
	Distances_and_headings_list = convert_lats_longs_to_distance_heading(Polyline_decoded),
	%A better representation than 360 or 1080 would be better now this is used here as well.
	Journey = if Rep =:= "360" ->
		journey(Distances_and_headings_list);
	Rep =:= "1080" ->
		reverse_journey(Distances_and_headings_list)
	end,
	%If now have a set of co-ords need to figure out distances and directions
	Distances_and_wind_type_list = lists:map(
		fun({Distance, Compass}) ->
			Wind_type = head_side_or_tail_wind(Compass, [Headwinds, Sidewinds, Tailwinds]),
			{Distance, Wind_type}
		end,
		Journey),
	[Headwind_distances, Sidewind_distances, Tailwind_distances] = lists:map(
		fun(Wind_type_filter) ->
			lists:filter(
				fun({_Distance, Wind_type}) ->
					Wind_type == Wind_type_filter
				end,
				Distances_and_wind_type_list)
		end,
		[headwind, sidewind, tailwind]),
	[Sum_of_headwind_distances, Sum_of_sidewind_distances, Sum_of_tailwind_distances] = lists:map(
		fun(Wind_type) ->
			lists:foldl(
				fun({Distance, _Wind}, Sum) ->
					Distance + Sum
				end,
				0,
				Wind_type)
		end,
		[Headwind_distances, Sidewind_distances, Tailwind_distances]),
	Total_distance = Sum_of_headwind_distances + Sum_of_sidewind_distances + Sum_of_tailwind_distances,
	%Determine which is worse
	if (Sum_of_headwind_distances > Sum_of_sidewind_distances) and (Sum_of_headwind_distances > Sum_of_tailwind_distances) ->
		io:format("Heads you lose!~n");
	(Sum_of_tailwind_distances > Sum_of_sidewind_distances) and (Sum_of_tailwind_distances > Sum_of_headwind_distances) ->
		io:format("Tails you win!~n");
	(Sum_of_sidewind_distances >= Sum_of_headwind_distances) and (Sum_of_sidewind_distances >= Sum_of_tailwind_distances) ->
		io:format("It's a draw~n")
	end,
	Headwind_percent = round((Sum_of_headwind_distances/Total_distance)*100, 2),
	Sidewind_percent = round((Sum_of_sidewind_distances/Total_distance)*100, 2),
	Tailwind_percent = round((Sum_of_tailwind_distances/Total_distance)*100, 2),
	io:format("~w% Headwind~n~w% Sidewind~n~w% Tailwind~n", [Headwind_percent, Sidewind_percent, Tailwind_percent]),
%
	io:format("Direction: ~s~nSpeed: ~s mph~nGust: ~s mph~nWeather type: ~s~nTemperature: ~s deg C~n", [Direction, Speed, Gust, Weather_type, Temperature]).


%Need to read API key from file
readapikey() ->
	{_Status, Key} = file:read_file(os:getenv("HOME") ++ "/.datapoint"),
	string:strip(erlang:binary_to_list(Key),right,$\n).

Added osrm.erl.

















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-module(osrm).
-export([get_route/1, get_route/2, read_route/1]).
-import(polyline, [decode/1]).

%https://github.com/Project-OSRM/osrm-backend/wiki/Server-api
%For now, get weather for one location (probably good enough as relatively short distances weather wise; ultimately consider time as well?)
%To get lats and longs could also do a query for here: http://www.uk-postcodes.com/ (json again)

-define(BASE_URL,
	"http://router.project-osrm.org/").

-define(VIAROUTE,
	"viaroute").

%Need hints and checksum
%First one for command line usage
%%These will be strings from the command line
get_route([Start_lat, Start_lon, Finish_lat, Finish_lon]) ->
	get_route([Start_lat, Start_lon], [Finish_lat, Finish_lon]).
get_route([Start_lat, Start_lon], [Finish_lat, Finish_lon]) ->
	inets:start(),
	URL = ?BASE_URL ++ ?VIAROUTE ++ "?loc=" ++ Start_lat ++ "," ++ Start_lon ++ "&loc=" ++ Finish_lat ++ "," ++ Finish_lon,
	%Need UA to work with OSRM API
	UA = "Mozilla/5.0 (X11; NetBSD i386; rv:28.0) Gecko/20100101 Firefox/28.0",
	try
		%Handling timeouts: http://stackoverflow.com/a/14143762/208793
		{ ok, {_Status, _Headers, Body }} = httpc:request(get, {URL, [{"User-Agent", UA}]}, [{timeout, timer:seconds(10)}], []),
		%For development purposes, write this out and keep it
		_Write_status = file:write_file(os:getenv("HOME") ++ "/.headsilose_route", Body),
		%Need to catch file write errors as well
		Body
	catch
		_:Reason ->
			io:format("API Might be down~n"),
			Reason
	after
		maybe_quit()
	end.


read_route(Route_choice) ->
	{_Status, Route} = file:read_file(os:getenv("HOME") ++ "/.headsilose_route"),
	%Use jiffy
	%http://www.snip2code.com/Snippet/51463/how-to-support-chinese-in-http-request-b/
	{ Props } = jiffy:decode(Route),
	Route_geometry = if Route_choice =:= "route_geometry" ->
		proplists:get_value(binary:list_to_bin(Route_choice), Props);
	Route_choice =:= "alternative_geometries" ->
		%hd only if alternative though!
		%for now I think there is only ever one alternative so that is why we pick hd
		hd(proplists:get_value(binary:list_to_bin(Route_choice), Props))
	end,
	%That is all for now? Because...
	%And these don't seem to actually be returned, hence having to go down the route of polyline decoding
	%Route_Instructions = proplists:get_value(<<"route_instructions">>, Props),
	%Total_Distance = proplists:get_value(<<"total_distance">>, proplists:get_value(<<"route_summary">>, Props)),
	%And need to figure out how to get nested values. I.e like xpath. Just nest the queries? Nope that doesn't work
	%Like so:
	{Hint_data} = proplists:get_value(<<"hint_data">>, Props),
	Checksum = proplists:get_value(<<"checksum">>, Hint_data),
	Route_geometry_as_string = binary:bin_to_list(Route_geometry),
	{Checksum, Route_geometry_as_string}.


maybe_quit() ->
	Args = init:get_arguments(),
	Found = lists:keyfind(noshell, 1, Args),
	if Found =:= false ->
		dont_quit;
	true ->
		init:stop()
	end.

Changes to polyline.erl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-module(polyline).
-export([decode/1, eight_bit_chunks/1, six_bit_chunks/1, six_bit_chunk/1, split_up_six_bits/1, five_bit_chunks/1, bin_flip/1]).

%See https://developers.google.com/maps/documentation/utilities/polylinealgorithm


decode(Encoded_polyline) ->
	%Steps 11 back to 8
	Six_bit_chunks = six_bit_chunks(Encoded_polyline),
	%Step 8
	List_of_groups_of_chunks = split_up_six_bits(Six_bit_chunks),
	%Step 8 back to 6
	Five_bit_chunks = five_bit_chunks(List_of_groups_of_chunks),
	%---TODO
	%Maybe some more of the below need splitting out into different functions or nesting in a map?
	%Which option to go for, a function that maps as per five_bit_chunks, or mapping functions as per below?
	%I don't think I can map all functions in one go because ultimately need to change number of members in groups.
	%I.e. following will go from groups of five to eight.
	%---
	%Step 5










|

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-module(polyline).
-export([decode/1, eight_bit_chunks/1, six_bit_chunks/1, six_bit_chunk/1, split_up_six_bits/1, five_bit_chunks/1, bin_flip/1]).

%See https://developers.google.com/maps/documentation/utilities/polylinealgorithm


decode(Encoded_polyline) ->
	%Steps 11 back to 8
	Six_bit_chunks = six_bit_chunks(Encoded_polyline),
	%Step 8
	Groups_of_chunks_list = split_up_six_bits(Six_bit_chunks),
	%Step 8 back to 6
	Five_bit_chunks = five_bit_chunks(Groups_of_chunks_list),
	%---TODO
	%Maybe some more of the below need splitting out into different functions or nesting in a map?
	%Which option to go for, a function that maps as per five_bit_chunks, or mapping functions as per below?
	%I don't think I can map all functions in one go because ultimately need to change number of members in groups.
	%I.e. following will go from groups of five to eight.
	%---
	%Step 5
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
			true ->
				Shifted_binary
			end,
			%Step 2 back to 1
			Decoded = if Last_bit =:= "1" ->
				-1 * Final_binary/100000;
			true ->
				Final_binary/100000	
			end,
			Decoded
		end,
		Eight_bit_chunks),
	Results.
	

%Step 8 - Split up six bit chunks, per the 0x20 bit
split_up_six_bits(List_of_bit_chunks) ->
	split_up_six_bits_(List_of_bit_chunks, [], []).
split_up_six_bits_([Head | Tail], Group_of_bit_chunks, List_of_groups_of_bit_chunks) when [hd(Head)] == "1" ->
	split_up_six_bits_(Tail, [Head]++Group_of_bit_chunks, List_of_groups_of_bit_chunks);
split_up_six_bits_([Head | Tail], Group_of_bit_chunks, List_of_groups_of_bit_chunks) when [hd(Head)] == "0" ->
	%Then need to start a new list, but after this 0 one!
	split_up_six_bits_(Tail, [], [lists:reverse([Head]++Group_of_bit_chunks)]++List_of_groups_of_bit_chunks);	
split_up_six_bits_([], Group_of_bit_chunks, List_of_groups_of_bit_chunks) when length(Group_of_bit_chunks) > 0 ->
	split_up_six_bits_([], [], [lists:reverse(Group_of_bit_chunks)]++List_of_groups_of_bit_chunks);
split_up_six_bits_([], [], List_of_groups_of_bit_chunks) ->
	%TODO Might be neater to map lists:reverse over the list instead of doing above and here.
	lists:reverse(List_of_groups_of_bit_chunks).


%Step 5
%TODO See if better way of doing this
eight_bit_chunks(List_of_five_bit_chunks) ->
	Five_bit_chunk_string = lists:reverse(lists:flatten(List_of_five_bit_chunks)),
	eight_bit_chunks_(Five_bit_chunk_string, []).
eight_bit_chunks_(Five_bit_chunk_string, List_of_eight_bit_chunks) when length(Five_bit_chunk_string) > 8 ->  
	Eight_bit_chunk = lists:reverse(lists:sublist(Five_bit_chunk_string,1,8)),
	Rest_of_five_bit_chunk_string = lists:nthtail(8,Five_bit_chunk_string),
	eight_bit_chunks_(Rest_of_five_bit_chunk_string, [Eight_bit_chunk]++List_of_eight_bit_chunks);
eight_bit_chunks_(Five_bit_chunk_string, List_of_eight_bit_chunks) when length(Five_bit_chunk_string) =< 8, Five_bit_chunk_string /= [] ->  
	Padded_bit_string = pad_to(8, lists:reverse(Five_bit_chunk_string)),
	eight_bit_chunks_([], [Padded_bit_string]++List_of_eight_bit_chunks);
eight_bit_chunks_([], List_of_eight_bit_chunks) ->
	List_of_eight_bit_chunks.


six_bit_chunks(Encoded_polyline) ->
	six_bit_chunks_(Encoded_polyline, []).
six_bit_chunks_([Head | Rest], List_of_chunks) ->
	Six_bit_chunk = six_bit_chunk(Head),
	%Add to Reversed_chunks
	six_bit_chunks_(Rest, [Six_bit_chunk]++List_of_chunks);
six_bit_chunks_([], List_of_chunks) ->
	lists:reverse(List_of_chunks).


six_bit_chunk(Ascii_bit) ->
	%Step 10
	Shifted_bit = Ascii_bit - 63,
	%Step 9
	%From http://erlangcentral.org/wiki/index.php/Converting_Between_Binary_and_Decimal
	Binary_chunk = hd(io_lib:format("~.2B", [Shifted_bit])),
	%---TODO
	%What if Binary_chunk is shorter than 6?
	%Well in that case, I guess that means we'd want to split, but for now pad to six and check for 0x20 elsewhere.
	%---
	pad_to(6, Binary_chunk).


five_bit_chunks(List_of_groups_of_chunks) ->
	lists:map(
		fun(Group_of_chunks) ->
			%Step 7 - Un-reverse the five bit chunks
			lists:reverse(lists:map(
				fun(Chunk) ->
					%Step 8 - "Un-or" the 0x20 bit
					lists:sublist(Chunk,2,6)
				end,
				Group_of_chunks))
		end,
		List_of_groups_of_chunks).


%I can't figure out padding with io:format etc when printing binary numbers
pad_to(Length, Binary_string) when length(Binary_string) < Length ->
	Padded_binary_string = "0"++Binary_string,
	pad_to(Length, Padded_binary_string);
pad_to(Length, Binary_string) when length(Binary_string) == Length ->
	Binary_string.
	

%bnot doesn't seem to work as I thought it would so do it very inelegantly by switching each "bit" in a string.
bin_flip(Binary_number) ->
	Binary_string =	hd(io_lib:format("~.2B", [Binary_number])),
	bin_flip_(Binary_string, []).
bin_flip_([Head | Rest], Flipped_string) ->
	Head_bit = hd(io_lib:format("~c",[Head])),
	Flipped_bit = if Head_bit =:= "0" ->
		"1";
	true ->
		"0"







|








|
|
|
|
|

|
|
|
|

|




|
|

|


|
|

|
|
|




|


|
|
|















|










|








|



|







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
			true ->
				Shifted_binary
			end,
			%Step 2 back to 1
			Decoded = if Last_bit =:= "1" ->
				-1 * Final_binary/100000;
			true ->
				Final_binary/100000
			end,
			Decoded
		end,
		Eight_bit_chunks),
	Results.
	

%Step 8 - Split up six bit chunks, per the 0x20 bit
split_up_six_bits(Bit_chunks_list) ->
	split_up_six_bits_(Bit_chunks_list, [], []).
split_up_six_bits_([Head | Tail], Group_of_bit_chunks, Groups_of_bit_chunks_list) when [hd(Head)] == "1" ->
	split_up_six_bits_(Tail, [Head]++Group_of_bit_chunks, Groups_of_bit_chunks_list);
split_up_six_bits_([Head | Tail], Group_of_bit_chunks, Groups_of_bit_chunks_list) when [hd(Head)] == "0" ->
	%Then need to start a new list, but after this 0 one!
	split_up_six_bits_(Tail, [], [lists:reverse([Head]++Group_of_bit_chunks)]++Groups_of_bit_chunks_list);
split_up_six_bits_([], Group_of_bit_chunks, Groups_of_bit_chunks_list) when length(Group_of_bit_chunks) > 0 ->
	split_up_six_bits_([], [], [lists:reverse(Group_of_bit_chunks)]++Groups_of_bit_chunks_list);
split_up_six_bits_([], [], Groups_of_bit_chunks_list) ->
	%TODO Might be neater to map lists:reverse over the list instead of doing above and here.
	lists:reverse(Groups_of_bit_chunks_list).


%Step 5
%TODO See if better way of doing this
eight_bit_chunks(Five_bit_chunks_list) ->
	Five_bit_chunk_string = lists:reverse(lists:flatten(Five_bit_chunks_list)),
	eight_bit_chunks_(Five_bit_chunk_string, []).
eight_bit_chunks_(Five_bit_chunk_string, Eight_bit_chunks_list) when length(Five_bit_chunk_string) > 8 ->
	Eight_bit_chunk = lists:reverse(lists:sublist(Five_bit_chunk_string,1,8)),
	Rest_of_five_bit_chunk_string = lists:nthtail(8,Five_bit_chunk_string),
	eight_bit_chunks_(Rest_of_five_bit_chunk_string, [Eight_bit_chunk]++Eight_bit_chunks_list);
eight_bit_chunks_(Five_bit_chunk_string, Eight_bit_chunks_list) when length(Five_bit_chunk_string) =< 8, Five_bit_chunk_string /= [] ->
	Padded_bit_string = pad_to(8, lists:reverse(Five_bit_chunk_string)),
	eight_bit_chunks_([], [Padded_bit_string]++Eight_bit_chunks_list);
eight_bit_chunks_([], Eight_bit_chunks_list) ->
	Eight_bit_chunks_list.


six_bit_chunks(Encoded_polyline) ->
	six_bit_chunks_(Encoded_polyline, []).
six_bit_chunks_([Head | Rest], Chunks_list) ->
	Six_bit_chunk = six_bit_chunk(Head),
	%Add to Reversed_chunks
	six_bit_chunks_(Rest, [Six_bit_chunk]++Chunks_list);
six_bit_chunks_([], Chunks_list) ->
	lists:reverse(Chunks_list).


six_bit_chunk(Ascii_bit) ->
	%Step 10
	Shifted_bit = Ascii_bit - 63,
	%Step 9
	%From http://erlangcentral.org/wiki/index.php/Converting_Between_Binary_and_Decimal
	Binary_chunk = hd(io_lib:format("~.2B", [Shifted_bit])),
	%---TODO
	%What if Binary_chunk is shorter than 6?
	%Well in that case, I guess that means we'd want to split, but for now pad to six and check for 0x20 elsewhere.
	%---
	pad_to(6, Binary_chunk).


five_bit_chunks(Groups_of_chunks_list) ->
	lists:map(
		fun(Group_of_chunks) ->
			%Step 7 - Un-reverse the five bit chunks
			lists:reverse(lists:map(
				fun(Chunk) ->
					%Step 8 - "Un-or" the 0x20 bit
					lists:sublist(Chunk,2,6)
				end,
				Group_of_chunks))
		end,
		Groups_of_chunks_list).


%I can't figure out padding with io:format etc when printing binary numbers
pad_to(Length, Binary_string) when length(Binary_string) < Length ->
	Padded_binary_string = "0"++Binary_string,
	pad_to(Length, Padded_binary_string);
pad_to(Length, Binary_string) when length(Binary_string) == Length ->
	Binary_string.


%bnot doesn't seem to work as I thought it would so do it very inelegantly by switching each "bit" in a string.
bin_flip(Binary_number) ->
	Binary_string = hd(io_lib:format("~.2B", [Binary_number])),
	bin_flip_(Binary_string, []).
bin_flip_([Head | Rest], Flipped_string) ->
	Head_bit = hd(io_lib:format("~c",[Head])),
	Flipped_bit = if Head_bit =:= "0" ->
		"1";
	true ->
		"0"