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.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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Leave a Reply