Smart Phone Android
Android

Location Converter (Android)

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 )
{
    // this handles the format but doesn't check the ranges (without extra symbols)
    // return strLatitude.matches("[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}(\\.[0-9]+)?");

    String[] lat = TextUtils.split(strLatitude, ":");
    if (lat.length == 3) // all three parts must exist
    {
        double dDegrees = Double.parseDouble(lat[0]);
        double dMinutes = Double.parseDouble(lat[1]);
        double dSeconds = Double.parseDouble(lat[2]);

        // and each must be within range
        return (dDegrees >= 0.0 && dDegrees <= 90.0) &&
               (dMinutes >= 0.