如何从二进制结构计算AIS经纬度坐标及Java高效实现
一、二进制转十进制(含负值处理)
AIS消息1/2/3的经纬度采用缩放补码编码,具体转换规则如下:
1. 经度(28位二进制)
- 步骤1:将28位二进制视为无符号整数,计算其十进制值
code。 - 步骤2:判断符号:若
code的最高位(第28位,1-based)为1(即code >= 2^27),说明是负值,需将code减去2^28得到实际编码值;若最高位为0,编码值即为code。 - 步骤3:实际经度 = 编码值 / 600000.0(AIS将经度按度放大600000倍存储)。
示例中的经度二进制1011100111111110001111110100:
- 无符号十进制值为
195031456 - 因
195031456 >= 2^27(134217728),编码值 =195031456 - 2^28(268435456)= -73404000 - 经度 =
-73404000 / 600000 = -122.34,与示例结果一致。
2. 纬度(27位二进制)
- 步骤1:将27位二进制视为无符号整数,计算十进制值
code。 - 步骤2:判断符号:若
code >= 2^26(最高位为1),编码值 =code - 2^27;否则编码值为code。 - 步骤3:实际纬度 = 编码值 / 600000.0。
示例中的纬度二进制001101100111010001001000100:
- 无符号十进制值为
28548084 - 因
28548084 < 2^26(67108864),编码值即为28548084 - 纬度 =
28548084 / 600000 ≈ 47.58,与示例结果一致。
二、Java高效实现(无字符串操作)
核心思路是直接将AIS载荷的每个字符转换为6位数值,通过位运算构建完整的二进制序列,再提取经纬度对应的位段计算结果,全程避免字符串拼接与解析,最大化效率。
完整实现代码
import java.math.BigInteger; public class AisDecoder { // 将AIS字符转换为6位二进制对应的十进制值(遵循ITU-R M.1371标准) private static int charTo6Bit(char c) { if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'A' && c <= 'Z') { return 10 + c - 'A'; } else if (c == '?') { return 63; } else { // 处理特殊符号,对应编码表中32-62的范围 return 32 + (c - ' '); } } // 存储经纬度结果的内部类 public static class AisPosition { public final double longitude; public final double latitude; public AisPosition(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } } // 解码AIS消息1/2/3的经纬度 public static AisPosition decodePosition(String payload) { // 构建完整的二进制位序列(用BigInteger处理超长位) BigInteger fullBits = BigInteger.ZERO; for (char c : payload.toCharArray()) { int sixBitValue = charTo6Bit(c); fullBits = fullBits.shiftLeft(6).or(BigInteger.valueOf(sixBitValue)); } int totalBits = payload.length() * 6; // 解码经度:提取1-based 61-88位(共28位) BigInteger lonMask = BigInteger.ONE.shiftLeft(28).subtract(BigInteger.ONE); BigInteger lonBits = fullBits.shiftRight(totalBits - 88).and(lonMask); BigInteger twoTo28 = BigInteger.ONE.shiftLeft(28); BigInteger lonCode = lonBits.bitLength() == 28 ? lonBits.subtract(twoTo28) : lonBits; double longitude = lonCode.doubleValue() / 600000.0; // 解码纬度:提取1-based 89-115位(共27位) BigInteger latMask = BigInteger.ONE.shiftLeft(27).subtract(BigInteger.ONE); BigInteger latBits = fullBits.shiftRight(totalBits - 115).and(latMask); BigInteger twoTo27 = BigInteger.ONE.shiftLeft(27); BigInteger latCode = latBits.bitLength() == 27 ? latBits.subtract(twoTo27) : latBits; double latitude = latCode.doubleValue() / 600000.0; return new AisPosition(longitude, latitude); } // 测试示例 public static void main(String[] args) { String samplePayload = "177KQJ5000G?tO`K>RA1wUbN0TKH"; AisPosition position = decodePosition(samplePayload); System.out.printf("经度:%.2f,纬度:%.2f%n", position.longitude, position.latitude); // 输出:经度:-122.34,纬度:47.58 } }
效率说明
- 全程无字符串操作,直接通过位运算构建和提取位段,减少内存开销与计算耗时。
- 使用
BigInteger处理超长二进制序列(AIS载荷最长可达168位),确保兼容性的同时,位运算效率优于字符串解析。 - 单次遍历载荷字符构建完整位序列,经纬度解码共享同一序列,避免重复计算。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

