在R中解码声纳二进制数据

时间:2018-09-11 16:53:28

标签: r binary seek

作为水文学家而不是程序员,我被困于解决简单的任务-从L​​OWRENCE Fish Finder提取数据。它以二进制格式SL2写入轨迹,水深,温度等。长度为144字节,标头为10字节。有很多用不同语言(例如JavaRuby)编写的解码实现。

基于wikiRuby code和堆栈溢出,我试图至少从this example file中提取深度。在.csv中哪个可以找到here

我知道depth是从60开始的4字节浮点数。但是,this answer建议的代码返回zero

 # Open binary file
 toread <-  file("Chart 09_07_2018 [0].sl2", "rb")
 # all data
 alldata <- readBin(toread, raw(), n = 144, size = 1, endian = "little")
 # read WaterDepth
 readBin(alldata[59:65], double(), size = 4) 
> [1] 0
 close(toread)

如果我使用seek函数,则返回相同的结果:

con <-  file("Chart 09_07_2018 [2].sl2", "rb")
seek(con, 60L)
readBin(con, double(), endian="little")
close(con)

文件中的数据存在-我能够通过Sonar Viewersee .csv example)提取数据。

1 个答案:

答案 0 :(得分:1)

制作javascript / node.js版本的家伙修复了字段转录中的一些错误,值得一两枚奖章。

下面的内容应该很容易理解,但是如果其中任何一个需要'splainin,请删除注释。您需要处理经度/纬度编码(等)。

请注意,您可以跳过阅读功能源代码,而只需执行以下操作:

select reverse(replace(reversedWholeNumber, '.', '')) + reverse(reversedDecimal)
from (
    select substring(myvar, 1, charindex('.', myvar)) reversedDecimal, 
           substring(myvar, charindex('.', myvar) + 1, len(myvar)) reversedWholeNumber
    from (
        select reverse(@myvar) myvar
    ) a
) a

您可以在此处或此处查看源代码,

select reverse(replace(substring(myvar, charindex('.', myvar) + 1, len(myvar)), '.', ''))  +
       reverse(substring(myvar, 1, charindex('.', myvar)))
from (
    select reverse(@myvar) myvar
) a

尝试一下:

devtools::install_git("https://gitlab.com/hrbrmstr/arabia")

library(arabia) # b/c I like puns way too much

read_sl2("your-sl2-file.sl2")

再看一遍:

read_sl2 <- function(path, verbose=TRUE) {

  f <- file(path.expand(path), "rb")
  dat <- readBin(f, "raw", n = file.size(path.expand(path)), endian="little")
  close(f)

  # read in the header
  header <- readBin(dat, what = "raw", n = 10)

  format <- readBin(header[1:2], "int", size=2, endian="little", signed=FALSE)

  if (!(format %in% 1:3)) stop("Invalid 'format' in header; Likely not an slg/sl2/sl3 file")

  ok_formats <- c("slg", "sl2", "sl3")
  if (verbose) message("Format: ", ok_formats[format])

  version <- readBin(header[3:4], "int", size=2, endian="little", signed=FALSE)
  blockSize <- readBin(header[5:6], "int", size=2, endian="little", signed=FALSE)

  if (blockSize == 1970) {
    if (verbose) message("Block size: downscan")
  } else if (blockSize == 3200) {
    if (verbose) message("Block size: sidescan")
  } else {
    stop("Block size is not 'downscan' or 'sidescan'; Likely not an slg/sl2/sl3 file")
  }

  alwaysZero <- readBin(header[7:8], "int", size=2, endian="little", signed=FALSE)

  # yep, we're going to build a list the hard/slow way
  sl2_lst <- vector("list")
  idx <- 1
  pos <- 8 # keeping track of our place in the stream

  while (pos < length(dat)) {

    # if verbose mode echo a "." every 100 records
    if (verbose && ((idx %% 100) == 0)) cat(".")

    blockSize <- readBin(dat[(pos+29):(pos+30)], "int", size=2, endian="little", signed=FALSE)
    prevBlockSize <- readBin(dat[(pos+31):(pos+32)], "int", size=2, endian="little", signed=FALSE)
    packetSize <- readBin(dat[(pos+35):(pos+36)], "int", size=2, endian="little", signed=FALSE)
    frameIndex <- readBin(dat[(pos+37):(pos+40)], "int", size=4, endian="little")

    dplyr::data_frame(
      channel = readBin(dat[(pos+33):(pos+34)], "int", size=2,endian="little", signed=FALSE),
      upperLimit = readBin(dat[(pos+41):(pos+44)], "double", size=4, endian="little"),
      lowerLimit = readBin(dat[(pos+45):(pos+48)], "double", size=4, endian="little"),
      frequency = readBin(dat[(pos+51)], "int", size=1, endian="little", signed=FALSE),
      waterDepth = readBin(dat[(pos+65):(pos+68)], "double", size=4, endian="little"),
      keelDepth = readBin(dat[(pos+69):(pos+72)], "double", size=4, endian="little"),
      speedGps = readBin(dat[(pos+101):(pos+104)], "double", size=4, endian="little"),
      temperature = readBin(dat[(pos+105):(pos+108)], "double", size=4, endian="little"),
      lng_enc = readBin(dat[(pos+109):(pos+112)], "integer", size=4, endian="little"),
      lat_enc = readBin(dat[(pos+113):(pos+116)], "integer", size=4, endian="little"),
      speedWater = readBin(dat[(pos+117):(pos+120)], "double", size=4, endian="little"),
      track = readBin(dat[(pos+121):(pos+124)], "double", size=4, endian="little"),
      altitude = readBin(dat[(pos+125):(pos+128)], "double", size=4, endian="little"),
      heading = readBin(dat[(pos+129):(pos+132)], "double", size=4, endian="little"),
      timeOffset = readBin(dat[(pos+141):(pos+144)], "integer", size=4, endian="little"),
      flags = list(
        dat[(pos+133):(pos+134)] %>%
          rawToBits() %>%
          as.logical() %>%
          set_names(
            c(
              "headingValid", "altitudeValid", sprintf("unk%d", 1:7),
              "gpsSpeedValid", "waterTempValid", "unk8", "positionValid",
              "unk9", "waterSpeedValid", "trackValid"
            )
          ) %>%
          .[c(1:2, 10:11, 13, 15:16)] %>%
          as.list() %>%
          purrr::flatten_df()
      )
    ) -> sl2_lst[[idx]]

    idx <- idx + 1

    pos <- pos + (packetSize+145-1)

  }

  if (verbose) cat("\n")

  dplyr::bind_rows(sl2_lst) %>%
    dplyr::mutate(
      channel = dplyr::case_when(
        channel == 0 ~ "Primary",
        channel == 1 ~ "Secondary",
        channel == 2 ~ "DSI (Downscan)",
        channel == 3 ~ "Left (Sidescan)",
        channel == 4 ~ "Right (Sidescan)",
        channel == 5 ~ "Composite",
        TRUE ~ "Other/invalid"
      )
    ) %>%
    dplyr::mutate(
      frequency = dplyr::case_when(
        frequency == 0 ~ "200 KHz",
        frequency == 1 ~ "50 KHz",
        frequency == 2 ~ "83 KHz",
        frequency == 4 ~ "800 KHz",
        frequency == 5 ~ "38 KHz",
        frequency == 6 ~ "28 KHz",
        frequency == 7 ~ "130-210 KHz",
        frequency == 8 ~ "90-150 KHz",
        frequency == 9 ~ "40-60 KHz",
        frequency == 10~ "25-45 KHz",
        TRUE ~ "Other/invalid"
      )
    ) %>%
    tidyr::unnest(flags)

}

您可能想与您创建的CSV进行比较,因为我只是盯着视线。请注意,当出口商在标志中将其标记为“无效”时,似乎使用了“结转”值。我只是把它们留作读后。

如果您不是一个“ tidyverse”的人,那么“ un-tidyverse”(一项向问询者保留的练习)应该很容易。