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

在r中解码声纳二进制数据,r,binary,seek,R,Binary,Seek,作为一名水文学家而不是程序员,我被困于解决简单的任务——从LOWRENCE Fish Finder中提取数据。它以二进制格式写入轨迹、水深、温度等。这是一个144字节的长度和一个10字节的头。有许多解码实现是用不同的语言编写的(例如or) 基于和堆栈溢出,我试图从中提取至少深度。可以在.csv中找到 我知道,depth是从60开始的4字节浮点。但是,返回的代码建议为零: # Open binary file toread <- file("Chart 09_07_2018 [0].s

作为一名水文学家而不是程序员,我被困于解决简单的任务——从LOWRENCE Fish Finder中提取数据。它以二进制格式写入轨迹、水深、温度等。这是一个144字节的长度和一个10字节的头。有许多解码实现是用不同的语言编写的(例如or)

基于和堆栈溢出,我试图从中提取至少深度。可以在.csv中找到

我知道,
depth
是从60开始的4字节浮点。但是,返回的代码建议为零:

 # 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)

文件中的数据存在-我能够通过Sonar Viewer(声纳查看器)提取数据。

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

下面的内容对grok来说应该相当简单,但如果其中任何一个需要“splainin”,请发表评论。您需要处理经度/纬度编码(etc)

请注意,您可以跳过读取函数源代码,只需执行以下操作:

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)

}

read\u sl2这太棒了!非常感谢你<代码>读取sl2
工作正常,速度慢但很好。读取示例
sl2
需要18秒,读取18 Mb
sl2
文件需要10分钟((我不知道为什么我无法在我的Windows机器上安装R3.5.1下的
arabia
软件包。安装甚至在
rstudio.cloud
中失败。这两个都带有
系统警告(cmd):“make”未找到。你知道为什么吗?这似乎是一个
RTools
依赖性问题,但
find\u RTools()
返回
TRUE
…我不再需要在
arabia
包中进行编译,并且大大加快了base R版本的速度。我还将更快的base R版本函数复制到了答案中。我忘了将代码从不必要地复制大量数据切换到只移动.Gi周围的流位置指针如果有机会的话,我可以试一试。Supergeil!我仍然无法安装
arabia
软件包,但加快了base R
read\u sl2
的速度,效果非常好:18Mb
sl2
文件需要16秒!没有什么新的东西可以添加,但看到有人发现我的nodejs代码很有用,这很有趣。
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)

}
xdf <- read_sl2("~/Downloads/Chart 09_07_2018 [2].sl2")
## Format: sl2
## Block size: downscan
## .............

xdf
## # A tibble: 1,308 x 22
##    channel      upperLimit lowerLimit frequency waterDepth keelDepth speedGps temperature lng_enc lat_enc
##    <chr>             <dbl>      <dbl> <chr>          <dbl>     <dbl>    <dbl>       <dbl>   <int>   <int>
##  1 Secondary             0       13.3 200 KHz         2.62     0.328      0.5        15.8 4433307 7003054
##  2 DSI (Downsc…          0       13.4 200 KHz         2.62     0.328      0.5        15.8 4433307 7003054
##  3 Primary               0       13.3 200 KHz         2.62     0.328      0.5        15.9 4433307 7003054
##  4 Secondary             0       13.3 200 KHz         2.62     0.328      0.5        15.9 4433307 7003054
##  5 DSI (Downsc…          0       13.4 200 KHz         2.59     0.328      0          15.8 4433307 7003054
##  6 Secondary             0       13.3 200 KHz         2.59     0.328      0          15.8 4433307 7003054
##  7 Secondary             0       13.3 200 KHz         2.52     0.328      0          15.9 4433307 7003054
##  8 DSI (Downsc…          0       13.4 200 KHz         2.52     0.328      0          15.9 4433307 7003054
##  9 Primary               0       13.3 200 KHz         2.52     0.328      0          15.8 4433307 7003054
## 10 DSI (Downsc…          0       13.4 200 KHz         2.52     0.328      0          15.8 4433307 7003054
## # ... with 1,298 more rows, and 12 more variables: speedWater <dbl>, track <dbl>, altitude <dbl>,
## #   heading <dbl>, timeOffset <int>, headingValid <lgl>, altitudeValid <lgl>, gpsSpeedValid <lgl>,
## #   waterTempValid <lgl>, positionValid <lgl>, waterSpeedValid <lgl>, trackValid <lgl>
glimpse(xdf)
## Observations: 1,308
## Variables: 22
## $ channel         <chr> "Secondary", "DSI (Downscan)", "Primary", "Secondary", "DSI (Downscan)", "Sec...
## $ upperLimit      <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ lowerLimit      <dbl> 13.3, 13.4, 13.3, 13.3, 13.4, 13.3, 13.3, 13.4, 13.3, 13.4, 13.3, 13.4, 13.3,...
## $ frequency       <chr> "200 KHz", "200 KHz", "200 KHz", "200 KHz", "200 KHz", "200 KHz", "200 KHz", ...
## $ waterDepth      <dbl> 2.620, 2.620, 2.620, 2.620, 2.586, 2.586, 2.516, 2.516, 2.516, 2.516, 2.516, ...
## $ keelDepth       <dbl> 0.328084, 0.328084, 0.328084, 0.328084, 0.328084, 0.328084, 0.328084, 0.32808...
## $ speedGps        <dbl> 0.5, 0.5, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0....
## $ temperature     <dbl> 15.84112, 15.84112, 15.86293, 15.86293, 15.79128, 15.79128, 15.86293, 15.8629...
## $ lng_enc         <int> 4433307, 4433307, 4433307, 4433307, 4433307, 4433307, 4433307, 4433307, 44333...
## $ lat_enc         <int> 7003054, 7003054, 7003054, 7003054, 7003054, 7003054, 7003054, 7003054, 70030...
## $ speedWater      <dbl> 0.5, 0.5, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0....
## $ track           <dbl> 4.974188, 4.974188, 4.974188, 4.974188, 4.974188, 4.974188, 4.974188, 4.97418...
## $ altitude        <dbl> 324.7375, 324.7375, 324.7375, 324.7375, 324.8687, 324.8687, 324.8687, 324.868...
## $ heading         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ timeOffset      <int> 1317703, 1317706, 1318036, 1318905, 1318946, 1318982, 1319130, 1319140, 13192...
## $ headingValid    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, ...
## $ altitudeValid   <lgl> TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FAL...
## $ gpsSpeedValid   <lgl> TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FAL...
## $ waterTempValid  <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, ...
## $ positionValid   <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, ...
## $ waterSpeedValid <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, ...
## $ trackValid      <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, ...