Introduction
While it's certainly not my vocation or even my avocation, I do find myself looking at map coordinates quite often. Whether it's simply to find out where I am on Google Maps, find a particular landmark, locate the origin of a digital picture, or maybe on a geocaching run with friends, those latitude and longitude coordinates are always there. The problem is I never know in what format they are going to be in, and I often need them in some other format for what I am doing at the moment.
Who is this article and app NOT for? If you are a pilot, a cartographer, use a compass on a daily basis, or simply have a knack for crunching numbers mentally, there's probably nothing new for you here. Move on.
I think it's also worth mentioning that this is my first completed app using Android Studio. I've created dozens of other apps with it, but none of them were actually "complete" apps, that is, not worthy of use by anyone other than myself. Getting an Eclipse project ready to upload was simply a matter of deleting the bin folder. The next time the project was opened, that folder would get recreated. With Android Studio, I'm still learning what can be deleted and what has to stay. If you see something that I've missed, drop me a note and let me know.
Background
Latitude is defined as the geographic coordinate that specifies the north–south position of a point on the Earth's surface. It is measured as the angle which ranges from 0° at the Equator to 90° (North or South) at the poles. Longitude is defined as the geographic coordinate that specifies the east-west position of a point on the Earth's surface. It is measured as the angle east or west from the Prime Meridian, ranging from 0° at the Prime Meridian to +180° eastward and −180° westward. Whichever coordinate you are looking at, each degree is sub-divided into 60 minutes, and each minute is divided into 60 seconds.
While there are probably dozens more in existance, the three most common formats I encounter are:
decimal
D:M:S
D° M' S"
These last two formats are different ways of representing sexagesimal notation.
To convert from decimal to D:M:S format, we use the following formula:
degrees = ⌊decimal_degrees⌋
minutes = ⌊60.0 * (decimal_degrees - degrees)⌋
seconds = 3600.0 * (decimal_degrees - degrees) - (60.0 * minutes)
As an example, the decimal coordinates of the Statue of Liberty are 40.689167 latitude and -74.044444 longitude. Plugging those into the above formula would look like:
Latitude:
degrees = floor(40.689167) = 40
minutes = floor(60.0 * (40.689167 - 40)) = 41
seconds = 3600.0 * (40.689167 - 40) - (60.0 * 41) = 21.0012
Longitude:
degrees = floor(74.044444) = 74
minutes = floor(60.0 * (74.044444 - 74)) = 2
seconds = 3600.0 * (74.044444 - 74) - (60.0 * 2) = 39.9984
Inserting the appropriate separators, and taking negative degrees into consideration for directional purposes, our D:M:S format looks like:
40:41:21.0012 N
74:2:39.9984 W
To convert from D:M:S or D° M' S" to decimal format, we use the following formula:
decimal_degrees = degrees + (minutes / 60.0) + (seconds / 3600.0)
Plugging the Statue of Liberty's D:M:S coordinates into the above formula would look like:
Latitude:
decimal_degrees = 40 + (41 / 60.0) + (21.0012 / 3600.0) = 40.689167
Longitude:
decimal_degrees = 74 + (2 / 60.0) + (39.9984 / 3600.0) = 74.044444
Taking negative degrees into consideration for directional purposes, our decimal format looks like:
40.689167 N
74.044444 W
Application
With definitions of what the coordinates are and how they are converted from one format to another, we can now start looking at how all of that segues into code. There is nothing fancy or special about this code or the resulting app. No whiz-bang graphics. Just "techy" numbers on the screen!
While the app consists of several activities (and a fragment), only one of which is of any importance. The main activity UI looks like:
Whether you enter your own coordinates, or click the "My Location" button to use your current location (the latter requires your permission), the conversion is the same and is handled by an "on-click" listener attached to the Up and Down buttons:
btnDown = (ImageButton) findViewById(R.id.btnDown);
btnUp = (ImageButton) findViewById(R.id.btnUp);
btnDown.setOnClickListener(this);
btnUp.setOnClickListener(this);
In the onClick()
function, we figure out which of the two buttons was clicked and then call the appropriate conversion functions:
@Override
public void onClick(View v)
{
if (v.getId() == R.id.btnUp)
{
if (convertLatitudeToDecimal())
{
convertLongitudeToDecimal();
}
}
else if (v.getId() == R.id.btnDown)
{
if (convertLatitudeToDMS())
{
convertLongitudeToDMS();
}
}
}
The four conversion functions have a similar theme: get the value from the appropriate "source" field, validate it, do the conversion, set the result in the corresponding "destination" field. So, converting latitude from D:M:S to decimal format looks like:
EditText etLatitude2 = (EditText) findViewById(R.id.etLatitude2);
m_strLatitudeDMS = etLatitude2.getText().toString();
if (Util.isValidLatitude(m_strLatitudeDMS))
{
String[] strDMS = TextUtils.split(m_strLatitudeDMS, ":");
int nDegrees = Integer.valueOf(strDMS[0]);
int nMinutes = Integer.valueOf(strDMS[1]);
double dSeconds = Double.valueOf(strDMS[2]);
m_dLatitude = nDegrees + (nMinutes / 60.0) + (dSeconds / 3600.0);
EditText etLatitude1 = (EditText) findViewById(R.id.etLatitude1);
etLatitude1.setText(String.format(Locale.getDefault(), "%.6f", m_dLatitude));
Spinner spinLatitudeDir1 = (Spinner) findViewById(R.id.spinLatitudeDir1);
Spinner spinLatitudeDir2 = (Spinner) findViewById(R.id.spinLatitudeDir2);
spinLatitudeDir1.setSelection(spinLatitudeDir2.getSelectedItemPosition());
}
You may be wondering why, when setting the value of the etLatitude1
field, that only 6 digits of precision are used. To put it simply, it makes no sense to show any more granularity than that. For a much better explanation, see whuber's response here.
There are two validation routines, one for each format. To validate latitude's decimal format, we simply need to check the coordinate's range, like:
public static boolean isValidLatitude( double dLatitude )
{
return dLatitude >= 0.0 && dLatitude <= 90.0;
}
Validating latitude's D:M:S format is equally as easy, with just a few more lines of code:
public static boolean isValidLatitude( String strLatitude )
{
String[] lat = TextUtils.split(strLatitude, ":");
if (lat.length == 3)
{
double dDegrees = Double.parseDouble(lat[0]);
double dMinutes = Double.parseDouble(lat[1]);
double dSeconds = Double.parseDouble(lat[2]);
return (dDegrees >= 0.0 && dDegrees <= 90.0) &&
(dMinutes >= 0.0 && dMinutes < 60.0) &&
(dSeconds >= 0.0 && dSeconds < 60.0);
}
return false;
}
The same two routines exist for longitude, except the upper range for degrees is 180.
Converting latitude from decimal to D:M:S format looks like:
EditText etLatitude1 = (EditText) findViewById(R.id.etLatitude1);
String strLatitude = etLatitude1.getText().toString();
if (! strLatitude.isEmpty())
{
m_dLatitude = Double.parseDouble(strLatitude);
if (Util.isValidLatitude(m_dLatitude))
{
EditText etLatitude2 = (EditText) findViewById(R.id.etLatitude2);
m_strLatitudeDMS = Location.convert(m_dLatitude, Location.FORMAT_SECONDS);
etLatitude2.setText(m_strLatitudeDMS);
Spinner spinLatitudeDir1 = (Spinner) findViewById(R.id.spinLatitudeDir1);
Spinner spinLatitudeDir2 = (Spinner) findViewById(R.id.spinLatitudeDir2);
spinLatitudeDir2.setSelection(spinLatitudeDir1.getSelectedItemPosition());
}
}
You undoubtedly noticed the lack of any conversion code like was explained near the top of this article. That's because I chose to use the built-in Location.convert()
function. It achieves the same result, but does so in a different manner, much like what I'd do using pencil and paper. For example, the formula for converting the Statue of Liberty's decimal coordinates to D:M:S format would look like:
degrees = ⌊decimal⌋
decimal = (decimal - degrees) * 60.0 // get rid of the whole degrees
minutes = ⌊decimal⌋
decimal = (decimal - minutes) * 60.0 // get rid of the whole minutes
seconds = decimal
Plugging the Statue of Liberty's 40.689167 latitude and -74.044444 longitude coordinates into the above formula would look like:
Latitude:
degrees = floor(decimal) = 40
decimal = (decimal - degrees) * 60.0 = 41.35002
minutes = floor(decimal) = 41
decimal = (decimal - minutes) * 60.0 = 21.0012
seconds = decimal = 21.0012
Longitude:
degrees = floor(decimal) = 74
decimal = (decimal - degrees) * 60.0 = 2.66664
minutes = floor(decimal) = 2
decimal = (decimal - minutes) * 60.0 = 39.9984
seconds = decimal = 39.9984
Inserting the appropriate separators, and taking negative degrees into consideration for directional purposes, our D:M:S format looks like:
40:41:21.0012 N
74:2:39.9984 W
Extras
The options menu contains a Map option and a Help option. The former allows you to see your converted coordinates using Google Maps. Once there, you can click the pin and verify that it shows the correct coordinates. The latter offers help on how to use the app and what goes in the various fields.
In it's current state, the app is targeted for Android 5 (Lollipop), which uses API 21.
Epilogue
Like I mentioned earlier, this is a no-frills article and app. You can find a ton more information about this on the web, and in a lot more detail. I just took what I needed for my purposes and put it into a handy little mobile app. I've seen web sites that showed dozens more formats and ways to convert between them, but a lot of it seemed redundant in my opinion.
Enjoy!