How to get nowhere in particular
By Bob Mesibov, published 01/08/2014 in Tutorials
In this tutorial I explain one way to generate a random geographical location, using simple command-line tricks.
Latitude and longitude: decimal degrees
Latitude runs from 0 to 90 degrees from the Equator to the North Pole, and 0 to 90 degrees from the Equator to the South Pole. Longitude in degrees goes from 0 degrees (along the Prime Meridian, which runs north-south near London) to 180 degrees (in the mid-Pacific)
Whole degrees are a bit limiting, so we'll expand them into decimal degrees. '30.5000', for example, is 30 and a half degrees (or 30 degrees and 30 minutes). When using decimal degrees, the convention is that latitudes north of the Equator are positive, south of the Equator negative. Longitude is positive going east from 0 degrees, and negative going west.
If we go to 4 decimal places in decimal degrees (as in 45.1173 degrees), we're specifying a latitude to the nearest 0.0001 degree, or about ±15 m, which is only a little more than the uncertainty you get with a handheld GPS unit. To simplify our random pick of a latitude/longitude pair, the range we'll be selecting from is -89.9999 to 89.9999 in latitude and -179.9999 to 179.9999 in longitude. (Missing that last circa 15 m will be OK for our purposes, but if it worries you, you can do what follows with more 9's after the decimal point!)
Shuffling
The shuf command takes a list of things and shuffles them randomly. If we use the -i option and specify a range of numbers, shuf will shuffle those numbers:
The -n option to shuf returns the first 'n' lines in the shuffled list, so -n 1 gives the first item. In effect, shuf with -n 1 randomly chooses a single number from the given range:
To get a randomly selected number in the range -9 to 9, we can get shuf to pick a number from 1 to 19, then subtract 10 from the result. To do the subtraction, we frame the operation in BASH with '$(( ))', like this:
Want proof that this works? I'll run the command 10000 times, then sort the results with sort command and get the frequencies with uniq:
Printing
A randomly chosen latitude between -89 and 89 will come from $(($(shuf -i 1-179 -n 1) - 90)), and a randomly chosen longitude between -179 and 179 will be generated by $(($(shuf -i 1-359 -n 1) - 180)). To print those figures out with a space between them, we'll use the printf command for an integer (%d) in each case, space the integers and follow the pair with a newline (\n):
The next thing we want is a decimal point after each integer:
And now for the decimal places...
Another way to randomise
The BASH function RANDOM generates pseudorandom numbers, from 0 to 32767. [For an explanation of pseudorandomness, see this Wikipedia article. For our purposes, pseudorandom is random.]
To get a random number between 0 and 32767, put RANDOM inside the BASH arithmetic frame '$(( ))':
To get a random number between 0 and 10 (inclusive), use the modulo operator '%', like this:
What the '%' does here is return the remainder after whatever number RANDOM selects is divided by 11. You'll sometimes see modulo 10 in Web tutorials for this particular case, which is incorrect. '%10' only returns the numbers from 0 to 9, while '%11' returns 0 to 10. You can demonstrate this by running each command 10000 times and sorting the output by frequency:
So, to get a random number in the range 0 to 9999, we use modulo 10000:
Back to the printer
The printf command can pad a number with leading zeroes:
which means we can print our RANDOM-selected number as a string from 0000 to 9999:
and simply add it to the string to be printed, for example for latitude:
Composing the result
Here's the final expression for a randomly chosen latitude and longitude (split over a couple of lines in this screenshot for clarity; it's actually a one-liner):
And here I've run the command 15 times and printed the results to the screen in 3 columns with the pr command:
In summary, we didn't actually randomly choose a latitude and longitude pair. Instead we 'composed' a pair with the printf command from randomly chosen degree figures and pseudorandomly chosen decimal places. Not elegant, but it works.
Constraining the result
How about generating a random latitude/longitude pair within a particular area, such as the Australian continent?
Tricky. The latitude/longitude would have to be tested to see if it's within the continental boundaries. This would be an example of a 'point in polygon' test, and the coding isn't simple.
An easier but much rougher solution is to define the Australian continent as a rectangular latitude-longitude box. The Australian mainland and Tasmania lie roughly between -10 and -44 degrees latitude, and between 113 and 153 degrees longitude. That means we'll generate latitude/longitude pairs between -10 and -43.9999 latitude, and 113 and 152.9999 degrees longitude, like this:
and here's where those 15 points landed:
Not bad, but improvement needed! I might have a go at the 'point in polygon' code in future...