This weekend I spend a few minutes thrashing together an algorithm that would take in a heading (in degrees) and return a String for the cardinal direction (I'm using it in an android compass application I'm using). What I ended up with was this:
private String headingToString(Float heading)
{
String strHeading = "?";
Hashtable<String, Float> cardinal = new Hashtable<String, Float>();
cardinal.put("North_1", new Float(0));
cardinal.put("Northeast", new Float(45));
cardinal.put("East", new Float(90));
cardinal.put("Southeast", new Float(135));
cardinal.put("South", new Float(180));
cardinal.put("Southwest", new Float(225));
cardinal.put("West", new Float(270));
cardinal.put("Northwest", new Float(315));
cardinal.put("North_2", new Float(360));
for (String key: cardinal.keySet())
{
Float value = cardinal.get(key);
if (Math.abs(heading - value) < 30)
{
strHeading = key;
if (key.contains("North_"))
{
strHeading = "North";
}
break;
}
}
return strHeading;
}
My question is, is this the best way of doing this? It must have been done many times before although I haven't done a search for examples on the web yet. Have any other people tried this and found a neater solution?
Edit for The Reverand's Thilo's, shinjin's and Chrstoffer's responses:
The Solution
public static String headingToString2(double x)
{
String directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"};
return directions[ (int)Math.round(( ((double)x % 360) / 45)) ];
}
That's fine in most cases, though to make it optimized and (IMO) cleaner, what you could do is find a function to relate the input heading to one used in the map.
For example: (I'm pretty sure this is right, but you'll want to check it)
45* (int)Math.round(( ((double)x % 360) / 45))
What this does is first x % 360
makes sure the heading is within a valid range. then
45 * round(.../45)
finds the closest multiple of 45.
Now change your map to be
HashMap<Integer, String> map = new HashMap<Integer, String>()
map.put(0, "North")
map.put(45, "Northeast")
etc...
So, now your algorithm becomes a fast mathemtical calculation rather than iterating through the map. Furthermore, you don't need as Hashtable here since it provides constructs for concurrency (if I remember correctly) and in your case it would actually cause a performance decrease.
Once again, the performance hit may be completely negligible for your needs.
Edit for Thilo's and shinjin's suggestions:
Instead of multiplying by 45, just keep the rest of the equation, which gives you values for 0-7, and make an array of your strings.
String directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}
return directions[ (int)Math.round(( ((double)x % 360) / 45)) % 8 ]
and you've got your problem solved in two lines.
One note: Modulus won't work correctly for negative numbers. If our input heading is negative, you'll need to make it positive first.
Most of the answers here are off by 22.5 degrees for their 45 degree intervals, and map e.g. 0-45 as N, rather than [337.5-360],[0-22.5] to N. You need to offset before doing the math to correct for this.
Here's a solution that uses 22.5 degree intervals, such as you might see for wind directions:
private String formatBearing(double bearing) {
if (bearing < 0 && bearing > -180) {
// Normalize to [0,360]
bearing = 360.0 + bearing;
}
if (bearing > 360 || bearing < -180) {
return "Unknown";
}
String directions[] = {
"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW",
"N"};
String cardinal = directions[(int) Math.floor(((bearing + 11.25) % 360) / 22.5)];
return cardinal + " (" + formatBearing.format(bearing) + " deg)";
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With