2

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.

Marlon
  • 1,839
  • 2
  • 19
  • 42
  • 1
    so, in your answer you want the Lat Long to be in the format of something similar to 78.343234N, 15.5050532E? – Rabbit Guy Jul 01 '16 at 18:33
  • What does it mean to convert a latitude (in degrees, minutes, seconds) to a double. If its value is 1.0, what does that represent? One degree? One second? – FredK Jul 01 '16 at 18:35
  • You are mixing concepts. "Double" is a data type; "decimal" is a style of data representation. Moreover, the coordinates you present are *not* "decimal" in the sense of "decimal degrees", which is the one usually relevant to cases like this. – John Bollinger Jul 01 '16 at 18:35
  • http://stackoverflow.com/questions/19423531/convert-latitude-and-longitude-values-degrees-to-double-java – Rabbit Guy Jul 01 '16 at 18:39
  • I believe you'll need some sort of way to clean the input first, since you will need an escape character "\" before the inner quotes if you want to use any sort of method like `String.parse()`. – Queue Jul 01 '16 at 18:47
  • @blahfunk Yes that's my goal, also what does the last letter signify? I want to convert `String = "78°14'09"N"'` to `Double = 78.343234` – Marlon Jul 01 '16 at 19:10
  • @JohnBollinger Yes I know. The "decimal degree" here is represented as a string. – Marlon Jul 01 '16 at 19:12
  • @Marlon The last letter denotes if the degrees are (N)orth or the equator, or (S)outh of it. You can also have (E)ast of the prime maridian and (W)est of it. To convert that to "decimal": N or E == positive, S or W == negative. – Rabbit Guy Jul 01 '16 at 19:18
  • @Marlon: all of this is covered in the link I provided above, including what to do with that letter – Rabbit Guy Jul 01 '16 at 19:20

4 Answers4

5
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°).

Kris
  • 14,426
  • 7
  • 55
  • 65
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • This was almost exactly what I was looking for, except I needed a bit more flexibility with reading the input. Referring to the capture groups by number made it tricky to adjust the regex so I rewrote it to use named capture groups instead. Sharing here: https://pastebin.com/Tiqrhmgv – Kris Jul 08 '21 at 10:40
0

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
    }
John
  • 3
  • 4
0

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.

Queue
  • 446
  • 7
  • 23
  • What makes you think Java can't store the degrees symbol? Also, it is the backslash (`\ `) not the forward slash (`/`) that is used as an escape character. – John Bollinger Jul 01 '16 at 19:31
0

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.

gottfred
  • 97
  • 2
  • 6