Marlon
Marlon

Reputation: 1897

How to convert a location in Degrees Minutes Seconds represented as a String to Double

My app lets users search a location and one of the queries I got was

"78°14'09"N 15°29'29"E"

Obviously the user wants to go to this location. First how do I check if this string fits the decimal format correctly. Then how do I convert it to double format?

double latitude = convertToDouble("78°14'09"N")

I searched here on stackoverflow but they are all looking for the opposite: double to decimal.

Upvotes: 2

Views: 8166

Answers (4)

John Bollinger
John Bollinger

Reputation: 181724

78°14'09"N 15°29'29"E

First how do I check if this string fits the decimal format correctly. Then how do I convert it to double format?

The string is not in decimal (degrees) format. It is in degrees, minutes, and seconds, which is more or less the opposite of decimal degrees format. I therefore interpret you to mean that you want to test whether the string is in valid D/M/S format, and if so, to convert it to decimal degrees, represented as a pair of doubles.

This is mostly a parsing problem, and regular expressions are often useful for simple parsing problems such as this one. A suitable regular expression can both check the format and capture the numeric parts that you need to extract. Here is one way to create such a pattern:

    private final static Pattern DMS_PATTERN = Pattern.compile(
            "(-?)([0-9]{1,2})°([0-5]?[0-9])'([0-5]?[0-9])\"([NS])\\s*" +
            "(-?)([0-1]?[0-9]{1,2})°([0-5]?[0-9])'([0-5]?[0-9])\"([EW])");

That's a bit dense, I acknowledge. If you are not familiar with regular expressions then this is no place for a complete explanation; the API docs for Pattern provide an overview, and you can find tutorials in many places. If you find that your input matches this pattern, then not only have you verified the format, but you have also parsed out the correct pieces for the conversion to decimal degrees.

The basic formula is decimal = degrees + minutes / 60 + seconds / 3600. You have the additional complication that coordinates' direction from the equator / prime meridian might be expressed either via N/S, E/W or by signed N, E, or by a combination of both. The above pattern accommodates all of those alternatives.

Putting it all together, you might do something like this:

    private double toDouble(Matcher m, int offset) {
        int sign = "".equals(m.group(1 + offset)) ? 1 : -1;
        double degrees = Double.parseDouble(m.group(2 + offset));
        double minutes = Double.parseDouble(m.group(3 + offset));
        double seconds = Double.parseDouble(m.group(4 + offset));
        int direction = "NE".contains(m.group(5 + offset)) ? 1 : -1;

        return sign * direction * (degrees + minutes / 60 + seconds / 3600);
    }

    public double[] convert(String dms) {
        Matcher m = DMS_PATTERN.matcher(dms.trim());

        if (m.matches()) {
            double latitude = toDouble(m, 0);
            double longitude = toDouble(m, 5);

            if ((Math.abs(latitude) > 90) || (Math.abs(longitude) > 180)) {
                throw new NumberFormatException("Invalid latitude or longitude");
            }

            return new double[] { latitude, longitude };
        } else {
            throw new NumberFormatException(
                    "Malformed degrees/minutes/seconds/direction coordinates");
        }
    }

The convert() method is the main one; it returns the coordinates as an array of two doubles, representing the coordinates in decimal degrees north and east of the intersection of the equator with the prime meridian. Latitudes south of the equator are represented as negative, as are longitudes west of the prime meridian. A NumberFormatException is thrown if the input does not match the pattern, or if the latitude or longitude apparently represented is invalid (the magnitude of the longitude cannot exceed 180°; that of the latitude cannot exceed 90°).

Upvotes: 5

gottfred
gottfred

Reputation: 97

So I've used a regex with capturing groups to grab each of the numbers and the N/S/E/W. After capturing each individually it's just a matter of doing a bit of dividing to get the numbers and then formatting them however you'd like. For example I went with 5 digits of precision here.

public static void main(String[] args) {
    String coords = "78°14'09N 15°29'29E";
    String[] decimalCoords = degreesToDecimal(coords);

    System.out.println(decimalCoords[0]);
    System.out.println(decimalCoords[1]);
}

public static String[] degreesToDecimal(String degMinSec) {
    String[] result = new String[2];
    Pattern p = Pattern.compile("(\\d+).*?(\\d+).*?(\\d+).*?([N|S|E|W]).*?(\\d+).*?(\\d+).*?(\\d+).*?([N|S|E|W]).*?");
    Matcher m = p.matcher(degMinSec);

    if (m.find()) {
        int degLat = Integer.parseInt(m.group(1));
        int minLat = Integer.parseInt(m.group(2));
        int secLat = Integer.parseInt(m.group(3));
        String dirLat = m.group(4);
        int degLon = Integer.parseInt(m.group(5));
        int minLon = Integer.parseInt(m.group(6));
        int secLon = Integer.parseInt(m.group(7));
        String dirLon = m.group(8);

        DecimalFormat formatter = new DecimalFormat("#.#####", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
        formatter.setRoundingMode(RoundingMode.DOWN);

        result[0] = formatter.format(degLat + minLat / 60.0 + secLat / 3600.0) + " " + dirLat;
        result[1] = formatter.format(degLon + minLon / 60.0 + secLon / 3600.0) + " " + dirLon;
    }
    return result;
}

There is no error handling here, it's just a basic example of how you could make this work with your input.

Upvotes: 0

Queue
Queue

Reputation: 456

If you first remove any characters from the string that are not alphanumeric, then something along these lines will work. This code compiles.

public class HelloWorld{

     public static void main(String []args){
        String input = "78 14'09 N 15 29'29 E".replaceAll("[^A-Za-z0-9]", " ");
        String[] array = input.split(" ");

        int nDegree = Integer.parseInt(array[0]);
        int nMinute = Integer.parseInt(array[1]);
        int nSecond = Integer.parseInt(array[2]);

        int eDegree = Integer.parseInt(array[4]);
        int eMinute = Integer.parseInt(array[5]);
        int eSecond = Integer.parseInt(array[6]);

        double nDegrees = nDegree + (double) nMinute/60 + (double) nSecond/3600;
        double eDegrees = eDegree + (double) eMinute/60 + (double) eSecond/3600;

        String nResult = "Decimal = N " + Double.toString(nDegrees).substring(0,10);
        String eResult = "Decimal = E " + Double.toString(eDegrees).substring(0,10);

        System.out.println(nResult);
        System.out.println(eResult);
     }
}

Output:

Decimal = N 78.2358333
Decimal = E 15.4913888

The problem is that Java can't store the degrees ° character as part of a String, or internal quotes (the minute character). If you can find a way to remove them from the string before inputting the data, then this will work.

I don't have a solution for handling the degrees symbol, but you could use an escape symbol \" to allow the use of a quotation mark within a string.

Upvotes: 0

John
John

Reputation: 3

You won't be able to parse that into a double without removing the non number chars but,

String string =  "78°14'09"N";  
Double number = 0;
 try{
       number = Double.parseDouble(string);
                //do something..
    }catch (NumberFormatException e){
       //do something.. can't be parsed
    }

Upvotes: 0

Related Questions