在 R 中解码 sl2 文件的声纳视图的探测数据

Posted

技术标签:

【中文标题】在 R 中解码 sl2 文件的声纳视图的探测数据【英文标题】:Decode sounding data for sonar view of sl2 file in R 【发布时间】:2021-08-03 12:45:48 【问题描述】:

在阅读了很多关于如何使用 .sl2 Sonar Log 格式分析使用诸如 Lowrance 等娱乐性声纳收集的数据的帖子之后,我能够使用 arabia 包的 read_sl2 函数从 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+54)], "int", size=1, endian="little", signed=FALSE),
      val1 = readBin(dat[(pos+55):(pos+56)], "int", size=2, endian="little"),
      val2 = readBin(dat[(pos+57):(pos+60)], "int", size=4, endian="little"),
      date = readBin(dat[(pos+61):(pos+64)], "int", size=4, endian="little"),
      
      waterDepth = readBin(dat[(pos+65):(pos+68)], "double", size=4, endian="little"),
      keelDepth = readBin(dat[(pos+69):(pos+72)], "double", size=4, endian="little"),
      
      byt1 = readBin(dat[(pos+73)], "int", size=1, endian="little", signed=FALSE),
      byt2 = readBin(dat[(pos+74)], "int", size=1, endian="little", signed=FALSE),
      val3 = readBin(dat[(pos+75):(pos+76)], "int", size=2, endian="little"),
      val4 = readBin(dat[(pos+77):(pos+78)], "int", size=2, endian="little"),
      val5 = readBin(dat[(pos+79):(pos+80)], "int", size=2, endian="little"),
      val6 = readBin(dat[(pos+81):(pos+84)], "double", size=4, endian="little"),
      val7 = readBin(dat[(pos+85):(pos+88)], "double", size=4, endian="little"),
      val8 = readBin(dat[(pos+89):(pos+92)], "double", size=4, endian="little"),
      val9 = readBin(dat[(pos+93):(pos+96)], "double", size=4, endian="little"),
      byt3 = readBin(dat[(pos+97)], "int", size=1, endian="little", signed=FALSE),
      byt4 = readBin(dat[(pos+98)], "int", size=1, endian="little", signed=FALSE),
      byt5 = readBin(dat[(pos+99)], "int", size=1, endian="little", signed=FALSE),
      byt6 = readBin(dat[(pos+100)], "int", size=1, endian="little", signed=FALSE),
      
      speedGps_kn = readBin(dat[(pos+101):(pos+104)], "double", size=4, endian="little"),
      temperature_c = 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_kn = 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"),

      sounding = readBin(dat[(pos+145):(pos+148)], size=4, "double", 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)
  


data <- read_sl2("myfile.sl2")

#Conversions
#Lat/Lon
PolarEarthRadius = 6356752.3142
RadiansToDegrees = 180.0 / pi

data$longitude <- (data$lng_enc / PolarEarthRadius) * RadiansToDegrees
data$latitude <- ((2.0 * atan(exp(data$lat_enc / PolarEarthRadius))) - (pi / 2.0)) * RadiansToDegrees

#InternationalUnits
FeettoMeter = 0.3048

data$waterDepth_m <- data$waterDepth*FeettoMeter
data$keelDepth_m <- data$keelDepth*FeettoMeter

但是,我不仅对深度感兴趣,而且对整个水柱(所有探测点)的声纳图像感兴趣。

sounding/bounce 数据似乎记录在包的末尾,但我不知道如何解码。 https://wiki.openstreetmap.org/wiki/SL2#:~:text=SL2%20is%20a%20binary%20format,downloaded%20on%20their%20web%20site.

当我在 ReefMaster 等商业软件中读取 sl2 文件时,我能够使用从 sl2 文件中读取的数据查看水柱,所以我猜信息在里面,但我不知道如何解码。

有什么想法吗?

【问题讨论】:

【参考方案1】:

我终于自己想通了。

答案不是很漂亮,数据很重,所以函数返回内存分配错误的频率比较高,但是这里是:

read_sound <- function(path, verbose=TRUE) 
  
  #Unit transformations
  PolarEarthRadius = 6356752.3142
  RadiansToDegrees = 180.0 / pi
  FeettoMeter = 0.3048
  
  
  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
  sound_list <- 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")
    
    upperLimit = readBin(dat[(pos+41):(pos+44)], "double", size=4, endian="little")*FeettoMeter
    lowerLimit = readBin(dat[(pos+45):(pos+48)], "double", size=4, endian="little")*FeettoMeter
    
    dplyr::tibble(
      sounding = readBin(dat[(pos+145):(pos+145+packetSize)], n=packetSize, "integer", size=1, endian="little"),
      sounddepth = round(seq(upperLimit,lowerLimit,length.out = length(sounding)),2),
      longitude = readBin(dat[(pos+109):(pos+112)], "integer", size=4, endian="little")*RadiansToDegrees/PolarEarthRadius,
      latitude = (2*atan(readBin(dat[(pos+113):(pos+116)], "integer", size=4, endian="little")/ PolarEarthRadius)-(pi/2))*RadiansToDegrees,
      waterDepth_m = readBin(dat[(pos+65):(pos+68)], "double", size=4, endian="little")*FeettoMeter,
      ping = idx
    )-> sound_list[[idx]]
    
    idx <- idx + 1
    
    pos <- pos + (packetSize+145-1)
    
  
  
  sound <- bind_rows(sound_list)
  sound$sounding <- ifelse(sound$sounding<0, sound$sounding+256, sound$sounding)   #The data is decoded adding +256 to negative values
  sound$ID <- cumsum(!duplicated(sound[3:4]))
  sound <- aggregate(. ~ sounddepth+ID, sound, mean)
  
  return(sound)

【讨论】:

以上是关于在 R 中解码 sl2 文件的声纳视图的探测数据的主要内容,如果未能解决你的问题,请参考以下文章

sqlmap基础

数据采集卡用于测风雷达

Coda 公司Echoscope三维图像声纳视频系列

使用声纳扫描仪在我的反应项目中意外的令牌 =(在模块模式下使用 espree 解析器)

声纳的 TeamCity dotCover 报告路径

声纳覆盖率:lcov 覆盖率报告中的文件路径不正确