Java中验证字符串是否为有效地理坐标

在本教程中,我们将探索在 Java 中验证地理坐标及其准确性的各种方法。

什么是地理坐标
地理坐标通常表示为纬度和经度值,精确定位球形地球上的位置。纬度测量赤道以北或以南的距离,范围从 -90°(南极)到 90°(北极)。另一方面,经度测量本初子午线以东或以西的距离,跨度为 -180°(国际日期变更线)到 180°。

了解精度和准确度
在检查坐标的有效性时,要考虑的一个关键因素是小数位数的精度。由坐标表示并与位置相关联的详细程度由精度表示,精度由小数位数表示。

此外,舍入坐标可能会导致不准确,尤其是在对邻近度敏感的应用中。例如,绘制建筑物地图可能需要精确到小数点后五位(大约一米)的坐标,而城市级地图可能只需要两位小数(大约一公里)。

经纬度格式
了解各种格式对于坐标输入和验证都是基础。

1. 十进制 (DD)
地理坐标通常以十进制表示,其中纬度和经度都以十进制值表示。在 DD 格式中,有效纬度值的范围通常为 -90 到 90,而有效经度值的范围为 -180 到 180。例如,巴黎埃菲尔铁塔的坐标约为 48.8588445(纬度)和 2.2943506(经度)。

2. 度、分、秒 (DMS)
DMS格式涉及度、分、秒,纬度和经度以度表示。每个度又分为60分钟,每分钟可以选择性地分为60秒。°(度)、'(分)和 ”(秒)等符号用于分隔这些部分。例如,自由女神像的坐标为 40°41'21.7"N(纬度)和 74°02'40.7"W(经度)。

3. 军用网格参考系统 (MGRS)
MGRS 是另一种用于指定地球表面位置的坐标格式。它将世界划分为网格区域并提供它们的简明表示。每个部分是一个 6° 宽的区域,编号为 1 到 60,并进一步分为由两个字母代码标识的 100,000 米正方形。在每个方格内,精确的位置由“东向”和“北向”给出,均以米为单位。例如,中国长城的八达岭部分可以定位为 50TMK6356175784。

使用正则表达式进行基本验证
正则表达式是模式匹配的强大工具。我们可以使用 Java 的Pattern和Matcher类制作正则表达式模式来识别有效的坐标格式。

第一个正则表达式是为 DD 格式设计的,其中两个十进制数字用逗号分隔,并带有可选空格:

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


第二个正则表达式是为了处理 DMS 格式而构建的。它检查两组坐标,包括度、分、秒和基本方向(N、S、W 或 E),从而允许灵活地使用符号:

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])$
";

最终的正则表达式迎合 MGRS 坐标。该代码验证由两个数字表示的 UTM 区域、使用字母字符(不包括 I 和 O)的纬度带以及后跟偶数对排列的 2 到 10 个数字组成的模式:

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

让我们定义实用方法来验证每种格式:

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();
}

正则表达式对于基本验证很有用,但不能保证坐标的正确性。例如,DD 正则表达式不会验证坐标是否落在地球的纬度和经度范围内。

复杂场景的自定义验证逻辑
为了处理复杂的场景,让我们将验证过程分解为更小、更明确的步骤。

1. 十进制度
让我们改进isValidDDFormat()方法来处理边缘情况,例如非数字输入和无效坐标:

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;
    }
}

String.split () 方法使用逗号和空格作为分隔符来分割坐标,以适应不同的输入格式。这确保了恰好有两个部分(纬度和经度)。然后我们验证数值是否在指定范围内。

捕获 NumberFormatException是为了处理解析双精度数失败的情况。

2. 度、分和秒
接下来,我们改进isValidDMSFormat()方法,将坐标字符串拆分为度、分、秒和半球部分,并相应地验证每个部分:

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"));
}

isInvalidLatitude ()方法检查提供的纬度分量是否超出有效范围。如果满足以下任何无效条件,则返回true (无效):
  • 纬度小于 0 或大于 90
  • 纬度分钟小于 0 或大于或等于 60
  • 纬度秒小于 0 或大于或等于 60
  • 半球不是“N”(北)或“S”(南)

同样,我们将为经度创建等效的验证方法:

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"));
}

isInvalidLongitude ()方法检查提供的经度分量,如果满足以下任何无效条件,则返回true (无效):
  • 经度小于 0 或大于 180
  • 经度分钟小于 0 或大于或等于 60
  • 经度秒小于 0 或大于或等于 60
  • 半球不是“E”(东)或“W”(西)

最后,让我们利用这两种验证方法来验证 DMS 坐标:

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;
    }
}

isValidDMSFormatWithCustomValidation ()方法将输入拆分为纬度和纵向分量,并使用辅助方法分别验证它们。如果所有检查都通过,则 DMS 格式被视为有效,并且该方法返回true(有效)。

结论
在本文中,我们探索了两种不同的验证地理坐标的方法。正则表达式为基本验证提供了快速的解决方案,而自定义验证逻辑提供了灵活性和错误反馈,使其适合复杂的场景。