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 | |
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 So this is just a "works enough to develop further" commit so I can work | |
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: - Previously the decoder only worked with a single point (baby steps and 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 | |
2014-10-17
| ||
20:17 |
Fixes for non-negative numbers
But confusingly fixed by fixing the negative numbers which already I.e. I forgot bin_flip returns a string so the If statement I had which 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 | |
Changes to README.md.
1 2 | #Heads I Lose | | > > | > | | > | | < < | > > | > > > > > > > > > > > > > > | > > > > > > | > > > > | 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 | -module(headsilose). | | > > | | 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 | %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). | | < < | > < | | < > > | > | < > | > > | | | < > | | | | | > | > | > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > | | | 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 | -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 | | | | 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 | true -> Shifted_binary end, %Step 2 back to 1 Decoded = if Last_bit =:= "1" -> -1 * Final_binary/100000; true -> | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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" |
︙ | ︙ |