1. Introduction

In this tutorial, we’ll explore various approaches to validating geo coordinates and their accuracy in Java.

2. Understand Geo Coordinates

Geo coordinates are typically expressed as latitude and longitude values, pinpointing locations on our spherical Earth. Latitude measures the distance north or south of the equator and ranges from -90° (South Pole) to 90° (North Pole). On the other hand, longitude measures the distance east or west of the prime meridian and spans -180° (International Date Line) to 180°.

3. Understanding Precision and Accuracy

When checking the validity of coordinates, one crucial factor to consider is the precision of the decimal places. The level of detail expressed by a coordinate and associated with a location is indicated by precision, which is represented by the number of decimal places.

Furthermore, rounding coordinates can cause inaccuracies, especially in applications sensitive to proximity. For example, mapping a building might require coordinates accurate to five decimal places (approximately one meter), while city-level mapping might only need two decimal places (approximately one kilometer).

4. Latitude and Longitude Formats

Understanding the various formats is fundamental for both input and validation of coordinates.

4.1. Decimal Degrees (DD)

Geo coordinates are commonly represented in decimal degrees, where both latitude and longitude are expressed as decimal values. In the DD format, valid latitude values typically range from -90 to 90, while valid longitude values range from -180 to 180. For example, the coordinates for the Eiffel Tower in Paris are approximately 48.8588445 (latitude) and 2.2943506 (longitude).

4.2. Degrees, Minutes, and Seconds (DMS)

The DMS format involves degrees, minutes, and seconds, and latitude and longitude are expressed as degrees. Each degree is further divided into 60 minutes, and each minute can optionally be divided into 60 seconds. Symbols such as ° (degree), ‘ (minute), and ” (second) are used to separate these components. For instance, the coordinates for the Statue of Liberty are 40°41’21.7″N (latitude) and 74°02’40.7″W (longitude).

4.3. Military Grid Reference System (MGRS)

MGRS is another coordinate format used to specify locations on the Earth’s surface. It divides the world into grid zones and provides a concise representation of them. Each section is a 6° wide zone, numbered 1 to 60, further divided into 100,000-meter squares identified by two-letter codes. Within each square, precise positions are given by “Easting” and “Northing”, both measured in meters. For example, the Great Wall of China’s Badaling section can be located as 50TMK6356175784.

5. Basic Validation With Regular Expressions

Regular expressions are powerful tools for pattern matching. We can craft a regex pattern to identify valid coordinate formats using Java’s Pattern and Matcher classes.

The first regex is crafted for DD format, where two decimal numbers are separated by a comma, with an optional space:

public static final String DD_COORDINATE_REGEX = "^(-?\\d+\\.\\d+)(\\s*,\\s*)?(-?\\d+\\.\\d+)$";

The second regex is built to handle the DMS format. It checks for two sets of coordinates, including degrees, minutes, seconds, and cardinal directions (N, S, W, or E), allowing for flexibility in symbol usage:

public static final String DMS_COORDINATE_REGEX = 
  "^(\\d{1,3})°(\\d{1,2})\'(\\d{1,2}(\\.\\d+)?)?\"?([NSns])(\\s*,\\s*)?
    (\\d{1,3})°(\\d{1,2})\'(\\d{1,2}(\\.\\d+)?)?\"?([WEwe])$";

The final regex caters to MGRS coordinates. The code validates patterns that consist of a UTM zone represented by two digits, a latitude band using alphabetical characters (excluding I and O), followed by two to ten digits arranged in even pairs:

public static final String MGRS_COORDINATE_REGEX = 
  "^\\d{1,2}[^IO]{3}(\\d{10}|\\d{8}|\\d{6}|\\d{4}|\\d{2})$";

Let’s define utility methods to validate each format:

boolean validateCoordinates(String coordinateString) {
    return isValidDDFormat(coordinateString) || isValidDMSFormat(coordinateString) || isValidMGRSFormat(coordinateString);
}

boolean isValidDDFormat(String coordinateString) {
    return Pattern.compile(DD_COORDINATE_REGEX).matcher(coordinateString).matches();
}

boolean isValidDMSFormat(String coordinateString) {
    return Pattern.compile(DMS_COORDINATE_REGEX).matcher(coordinateString).matches();
}

boolean isValidMGRSFormat(String coordinateString) {
    return Pattern.compile(MGRS_COORDINATE_REGEX).matcher(coordinateString).matches();
}

Regular expressions are useful for basic validation but cannot guarantee the correctness of coordinates. For instance, the DD regex doesn’t validate whether the coordinates fall within Earth’s latitude and longitude ranges.

6. Custom Validation Logic for Complex Scenarios

To handle complex scenarios, let’s break down the validation process into smaller, more explicit steps.

6.1. Decimal Degrees

Let’s improve the isValidDDFormat() method to handle edge cases such as non-numerical inputs and invalid coordinates:

boolean isValidDDFormat(String coordinateString) {
    try {
        String[] parts = coordinateString.split(",");
        if (parts.length != 2) {
            return false;
        }

        double latitude = Double.parseDouble(parts[0].trim());
        double longitude = Double.parseDouble(parts[1].trim());
        if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180) {
            return false;
        }

        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

The String.split() method splits the coordinates using commas and spaces as delimiters to accommodate different input formats. This ensures there are exactly two parts (latitude and longitude). We then validate that the numeric values fall within the specified ranges.

The NumberFormatException is caught to handle cases where parsing to doubles fails.

6.2. Degrees, Minutes, and Seconds

Next, let’s improve the isValidDMSFormat() method to split the coordinate string into degrees, minutes, seconds, and hemisphere parts, and validate each one accordingly:

boolean isInvalidLatitude(int degrees, int minutes, double seconds, String hemisphere) {
    return degrees < 0 || degrees > 90 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds >= 60 || 
      (!hemisphere.equalsIgnoreCase("N") && !hemisphere.equalsIgnoreCase("S"));
}

The isInvalidLatitude() method checks whether the provided latitude components are outside of the valid ranges. It returns true (invalid) if any of the following invalid conditions are met:

  • Latitude degrees are less than 0 or greater than 90
  • Latitude minutes are less than 0 or greater than or equal to 60
  • Latitude seconds are less than 0 or greater than or equal to 60
  • Hemisphere is not “N” (North) or “S” (South)

Similarly, we’ll create the equivalent validation method for longitude:

boolean isInvalidLongitude(int degrees, int minutes, double seconds, String hemisphere) {
    return degrees < 0 || degrees > 180 || minutes < 0 || minutes >= 60 || seconds < 0 || seconds >= 60 ||
      (!hemisphere.equalsIgnoreCase("E") && !hemisphere.equalsIgnoreCase("W"));
}

The isInvalidLongitude() method checks the provided longitude components, returning true (invalid) if any of the following invalid conditions are met:

  • Longitude degrees are less than 0 or greater than 180
  • Longitude minutes are less than 0 or greater than or equal to 60
  • Longitude seconds are less than 0 or greater than or equal to 60
  • Hemisphere is not “E” (East) or “W” (West)

Lastly, let’s leverage both validation methods to validate DMS coordinates:

boolean isValidDMSFormatWithCustomValidation(String coordinateString) {
    try {
        String[] dmsParts = coordinateString.split("[°',]");
        if (dmsParts.length > 6) {
            return false;
        }

        int degreesLatitude = Integer.parseInt(dmsParts[0].trim());
        int minutesLatitude = Integer.parseInt(dmsParts[1].trim());
        String[] secondPartsLatitude = dmsParts[2].split("\"");
        double secondsLatitude = secondPartsLatitude.length > 1 ? Double.parseDouble(secondPartsLatitude[0].trim()) : 0.0;
        String hemisphereLatitude = secondPartsLatitude.length > 1 ? secondPartsLatitude[1] : dmsParts[2];

        int degreesLongitude = Integer.parseInt(dmsParts[3].trim());
        int minutesLongitude = Integer.parseInt(dmsParts[4].trim());
        String[] secondPartsLongitude = dmsParts[5].split("\"");
        double secondsLongitude = secondPartsLongitude.length > 1 ? Double.parseDouble(secondPartsLongitude[0].trim()) : 0.0;
        String hemisphereLongitude = secondPartsLongitude.length > 1 ? secondPartsLongitude[1] : dmsParts[5];

        if (isInvalidLatitude(degreesLatitude, minutesLatitude, secondsLatitude, hemisphereLatitude) ||
          isInvalidLongitude(degreesLongitude, minutesLongitude, secondsLongitude, hemisphereLongitude)) {
            return false;
        }

        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

The isValidDMSFormatWithCustomValidation() method splits the input into its latitudinal and longitudinal components and validates them separately using the helper methods. If all checks pass, the DMS format is considered valid, and the method returns true (valid).

7. Conclusion

In this article, we’ve explored two distinct approaches to validating geo coordinates. Regular expressions offer a quick solution for basic validation, while the custom validation logic offers flexibility and error feedback, making it suitable for complex scenarios.

As always, the source code for the examples is available over on GitHub.