R binary / decimal conversion confusion - AIS data

381 Views Asked by At

I'm working with AIS (automatic Identification System) data for positioning vessels. I was able to follow this guide to decode almost all the bit of information successfully (when compared to online decoding done here).

However, I'm running into a problem with the Longitude part. I think it has to do with the decimal value being negative, but I can't figure out what to change in my code to get it right.

TLDR version: how do I get from the binary string 1101001000001001001110010000 to a decimal value of -48196720 (or 48196720)?

Full version:

toy data:

library(dplyr)
library(tidyr)
library(stringr)

# choose an example - two strings are provided.  
# The first string shows the issue with the longitude, 
# whereas the second string (where longitude is positive) has no issue
s <- "15E3tB001;J@BLPaK5j7qFA406;d" 
# s <- "133m@ogP00PD;88MD5MTDww@2D7k"
### for input into the online decoder - use these: 
# !AIVDM,1,1,,A,14eG;o@034o8sd<L9i:a;WF>062D,0*7D
# !AIVDM,1,1,,A,133m@ogP00PD;88MD5MTDww@2D7k,0*46

temp <- data.frame(V6 = s) %>%
    # splitting the AIS info into separate characters
    mutate(char_split = str_split(V6,pattern=""))
temp$Text <- apply(temp, 1, function(x) paste(unlist(x$char_split), collapse = ","))        

temp <- temp %>%
    select(-char_split) %>% 
    # and then into separate columns    
    separate(Text, into = paste0("v", 1:43, sep = ""), sep = ",", fill = "right") 

ASCII <- temp %>%
    select(v1:v43)
# translating to ASCII
ASCII <- apply(ASCII, c(1, 2), function(x) utf8ToInt(x))
# translating to 6-bit
ASCII <- apply(ASCII, c(1, 2), function(x) ifelse(x <= 88, x - 48, x - 48 - 8))

Once the data are in ASCII, need to translate to binary

# making binary
Binary <- apply(ASCII, c(1, 2), function(x){ paste(rev(as.integer(intToBits(x))[1:6]), collapse = "")})
# pasting all the binary info into a single string
temp$Binary <- apply(Binary, 1, function(x) paste(x, collapse = "" ))
temp <- temp %>%
    select(V6, Binary) %>%
    # selecting bits of the single binary string, 
    #and translating back to decimal, as per the guide
    mutate(MMSI = strtoi(substr(Binary, 9, 38), base = 2),
            SOG = strtoi(substr(Binary, 50, 60), base = 2)/10,
            Accuracy = strtoi(substr(Binary, 61, 61), base = 2),
            Lon = strtoi(substr(Binary, 62, 89), base = 2)/600000,
            Lat = strtoi(substr(Binary, 90, 116), base = 2)/600000,
            COG = strtoi(substr(Binary, 117, 128), base = 2)/10,
            Heading = strtoi(substr(Binary, 129, 137), base = 2))

output:

select(temp, -Binary, -V6)

when I compare to the online decoder, everything matches, other than the longitude. In the decoder, the result is 80.3278667 (even though it's really -80.3278667), whereas mine is 367.0646. Trying to reverse-engineer this, I look at the relevant substring of temp$Binary:

mine <- substr(temp$Binary, 62, 89)
RevEng <- -80.3278667 * 600000
binaryLogic:::as.binary(as.integer(RevEng), signed = FALSE) 
mine

So it looks like the RevEng value matches the right-end tail of my binary string, but I can't figure out why it doesn't match the full binary string, or what to do from here...

1

There are 1 best solutions below

2
On BEST ANSWER

Contrary to what the blog post is telling you, longitude is a signed integer. However, it uses only 28 bits while R uses 32 bits internally. You therefore have to handle the two's compliment conversion your self. For any number with the highest bit set, you have to subtract 2^28, e.g.:

mine <- "1101001000001001001110010000"
strtoi(mine, base = 2) - 2^28
#> [1] -48196720

You can identify those numbers either with substr on the binary string or by looking for numbers >= 2^27.

BTW, the same applies to latitude with the modification that it uses only 27 bits.