cat("Retrieving Input Parameters\n")
inputFile<-'tacsat.csv'
outputFile<-'tacsat_interpolated.csv'

require(data.table)
print(Sys.time())

memory.size(max = TRUE)
memory.limit(size = 4000)

interCubicHermiteSpline <- function(spltx,spltCon,res,params,headingAdjustment){
  
  #Formula of Cubic Hermite Spline
  t   <- seq(0,1,length.out=res)
  F00 <- 2*t^3 -3*t^2 + 1
  F10 <- t^3-2*t^2+t
  F01 <- -2*t^3+3*t^2
  F11 <- t^3-t^2
  
  #Making tacsat dataset ready
  spltx[spltCon[,1],"SI_HE"][which(is.na(spltx[spltCon[,1],"SI_HE"]))] <- 0
  spltx[spltCon[,2],"SI_HE"][which(is.na(spltx[spltCon[,2],"SI_HE"]))] <- 0
  
  #Heading at begin point in degrees
  Hx0 <- sin(spltx[spltCon[,1],"SI_HE"]/(180/pi))
  Hy0 <- cos(spltx[spltCon[,1],"SI_HE"]/(180/pi))
  
  #Heading at end point in degrees
  Hx1 <- sin(spltx[spltCon[,2]-headingAdjustment,"SI_HE"]/(180/pi))
  Hy1 <- cos(spltx[spltCon[,2]-headingAdjustment,"SI_HE"]/(180/pi))
  
  #Start and end positions
  Mx0 <- spltx[spltCon[,1],"SI_LONG"]
  Mx1 <- spltx[spltCon[,2],"SI_LONG"]
  My0 <- spltx[spltCon[,1],"SI_LATI"]
  My1 <- spltx[spltCon[,2],"SI_LATI"]
  
  #Corrected for longitude lattitude effect
  Hx0 <- Hx0 * params$fm * spltx[spltCon[,1],"SI_SP"] /((params$st[2]-params$st[1])/2+params$st[1])
  Hx1 <- Hx1 * params$fm * spltx[spltCon[,2],"SI_SP"] /((params$st[2]-params$st[1])/2+params$st[1])
  Hy0 <- Hy0 * params$fm * lonLatRatio(spltx[spltCon[,1],"SI_LONG"],spltx[spltCon[,1],"SI_LATI"]) * spltx[spltCon[,1],"SI_SP"]/((params$st[2]-params$st[1])/2+params$st[1])
  Hy1 <- Hy1 * params$fm * lonLatRatio(spltx[spltCon[,2],"SI_LONG"],spltx[spltCon[,2],"SI_LATI"]) * spltx[spltCon[,2],"SI_SP"]/((params$st[2]-params$st[1])/2+params$st[1])
  
  #Get the interpolation
  fx  <- outer(F00,Mx0,"*")+outer(F10,Hx0,"*")+outer(F01,Mx1,"*")+outer(F11,Hx1,"*")
  fy  <- outer(F00,My0,"*")+outer(F10,Hy0,"*")+outer(F01,My1,"*")+outer(F11,Hy1,"*")
  
  #Create output format
  intsx   <- lapply(as.list(1:nrow(spltCon)),function(x){
    matrix(rbind(spltx$ID[spltCon[x,]],cbind(fx[,x],fy[,x])),ncol=2,
           dimnames=list(c("startendVMS",seq(1,res,1)),c("x","y")))})
  return(intsx)}

rbindTacsat <- function(set1,set2){
  cln1  <- colnames(set1)
  cln2  <- colnames(set2)
  if(any(duplicated(cln1)==TRUE) || any(duplicated(cln2)==TRUE)) stop("Duplicate column names in datasets")
  idx1  <- which(is.na(pmatch(cln1,cln2))==TRUE)
  idx2  <- which(is.na(pmatch(cln2,cln1))==TRUE)
  
  if(length(idx1)>0){
    for(i in idx1) set2 <- cbind(set2,NA)
    colnames(set2) <- c(cln2,cln1[idx1])}
  if(length(idx2)>0){
    for(i in idx2) set1 <- cbind(set1,NA)
    colnames(set1) <- c(cln1,cln2[idx2])}
  cln1  <- colnames(set1)
  cln2  <- colnames(set2)
  mtch  <- pmatch(cln1,cln2)
  if(any(is.na(mtch))==TRUE) stop("Cannot find nor create all matching column names")
  set3  <- rbind(set1,set2[,cln2[mtch]])
  return(set3)}

bearing <- function(lon,lat,lonRef,latRef){
  
  x1  <- lon
  y1  <- lat
  x2  <- lonRef
  y2  <- latRef
  
  y   <- sin((x2-x1)*pi/180) * cos(y2*pi/180)
  x   <- cos(y1*pi/180) * sin(y2*pi/180) - sin(y1*pi/180) * cos(y2*pi/180) * cos((x2-x1)*pi/180)
  bearing <- atan2(y,x)*180/pi
  bearing <- (bearing + 360)%%360
  return(bearing)}

`distance` <-
  function(lon,lat,lonRef,latRef){
    
    pd <- pi/180
    
    a1<- sin(((latRef-lat)*pd)/2)
    a2<- cos(lat*pd)
    a3<- cos(latRef*pd)
    a4<- sin(((lonRef-lon)*pd)/2)
    a <- a1*a1+a2*a3*a4*a4
    
    c <- 2*atan2(sqrt(a),sqrt(1-a));
    return(6371*c)}

distanceInterpolation <- function(interpolation){
  
  res <- unlist(lapply(interpolation,function(x){
    dims        <- dim(x)
    res         <- distance(x[3:dims[1],1],x[3:dims[1],2],x[2:(dims[1]-1),1],x[2:(dims[1]-1),2])
    return(sum(res,na.rm=TRUE))}))
  
  return(res)}


equalDistance <- function(interpolation,res=10){
  
  #Calculate ditance of all interpolations at the same time
  totDist <- distanceInterpolation(interpolation)
  #Get dimensions of interpolations
  lngInt  <- lapply(interpolation,dim)
  
  #Warn if resolution of equal distance is too high compared to original resolution of interpolation
  if(min(unlist(lngInt)[seq(1,length(totDist),2)],na.rm=TRUE) < 9*res) warnings("Number of intermediate points in the interpolation might be too small for the equal distance pionts chosen")
  
  #Get distance steps to get equal distance
  eqStep  <- totDist/(res-1)
  
  #Get x-y values of all interpolations
  intidx  <- matrix(unlist(lapply(interpolation,function(x){return(x[1,])})),ncol=2,byrow=TRUE)
  
  #Do the calculation
  result  <- lapply(interpolation,function(ind){
    i       <- which(intidx[,1] == ind[1,1] & intidx[,2] == ind[1,2])
    idx     <- apply(abs(outer(
      cumsum(distance(ind[3:lngInt[[i]][1],1],ind[3:lngInt[[i]][1],2],ind[2:(lngInt[[i]][1]-1),1],ind[2:(lngInt[[i]][1]-1),2])),
      seq(eqStep[i],totDist[i],eqStep[i]),
      "-")),
      2,which.min)+1
    idx     <- c(1,idx)
    return(ind[c(1,idx+1),])})
  #Return the equal distance interpolated set in the same format as the interpolated dataset (as a list)
  return(result)}

interStraightLine <- function(spltx,spltCon,res){

     fx <- mapply(seq,spltx[spltCon[,1],"SI_LONG"],spltx[spltCon[,2],"SI_LONG"],length.out=res)
     fy <- mapply(seq,spltx[spltCon[,1],"SI_LATI"],spltx[spltCon[,2],"SI_LATI"],length.out=res)

     #Create output format
     intsx   <- lapply(as.list(1:nrow(spltCon)),function(x){
                   matrix(rbind(spltx$ID[spltCon[x,]],cbind(fx[,x],fy[,x])),ncol=2,
                          dimnames=list(c("startendVMS",seq(1,res,1)),c("x","y")))})
  return(intsx)}
  
interpolation2Tacsat <- function(interpolation,tacsat,npoints=10,equalDist=TRUE){
  
  # This function takes the list of tracks output by interpolateTacsat and converts them back to tacsat format.
  # The npoints argument is the optional number of points between each 'real' position.
  tacsat            <- sortTacsat(tacsat)
  if(!"HL_ID" %in% colnames(tacsat)) tacsat$HL_ID <- 1:nrow(tacsat)
  if(!"SI_DATIM" %in% colnames(tacsat)) tacsat$SI_DATIM  <- as.POSIXct(paste(tacsat$SI_DATE,  tacsat$SI_TIME,   sep=" "), tz="GMT", format="%d/%m/%Y  %H:%M")
  if(equalDist){
    interpolationEQ <- equalDistance(interpolation,npoints)  #Divide points equally along interpolated track (default is 10).
  } else {
    interpolationEQ <- lapply(interpolation,function(x){idx <- round(seq(2,nrow(x),length.out=npoints)); return(x[c(1,idx),])})
  }
  res <- lapply(interpolationEQ,function(x){
    idx                     <- unlist(x[1,1:2]@.Data); x <- data.frame(x)
    colnames(x)             <- c("SI_LONG","SI_LATI")
    cls                     <- which(apply(tacsat[c(idx),],2,function(y){return(length(unique(y)))})==1)
    for(i in cls){
      x           <- cbind(x,rep(tacsat[idx[1],i],nrow(x)));
      colnames(x) <- c(colnames(x)[1:(ncol(x)-1)],colnames(tacsat)[i])
    }
    if(!"VE_COU" %in% colnames(x)) x$VE_COU                <- rep(tacsat$VE_COU[idx[1]],nrow(x))
    if(!"VE_REF" %in% colnames(x)) x$VE_REF                <- rep(tacsat$VE_REF[idx[1]],nrow(x))
    if(!"FT_REF" %in% colnames(x)) x$FT_REF                <- rep(tacsat$FT_REF[idx[1]],nrow(x))
    x$SI_DATIM              <- tacsat$SI_DATIM[idx[1]]
    x$SI_DATIM[-c(1:2)]     <- as.POSIXct(cumsum(rep(difftime(tacsat$SI_DATIM[idx[2]],tacsat$SI_DATIM[idx[1]],units="secs")/(nrow(x)-2),nrow(x)-2))+tacsat$SI_DATIM[idx[1]],tz="GMT",format = "%d/%m/%Y  %H:%M")
    x$SI_DATE               <- format(x$SI_DATIM,format="%d/%m/%Y")
    timeNotation            <- ifelse(length(unlist(strsplit(tacsat$SI_TIME[1],":")))>2,"secs","mins")
    if(timeNotation == "secs") x$SI_TIME  <- format(x$SI_DATIM,format="%H:%M:%S")
    if(timeNotation == "mins") x$SI_TIME  <- format(x$SI_DATIM,format="%H:%M")
    x$SI_SP                 <- mean(c(tacsat$SI_SP[idx[1]],tacsat$SI_SP[idx[2]]),na.rm=TRUE)
    x$SI_HE                 <- NA;
    x$SI_HE[-c(1,nrow(x))]  <- bearing(x$SI_LONG[2:(nrow(x)-1)],x$SI_LATI[2:(nrow(x)-1)],x$SI_LONG[3:nrow(x)],x$SI_LATI[3:nrow(x)])
    x$HL_ID                 <- tacsat$HL_ID[idx[1]]
    return(x[-c(1,2,nrow(x)),])})
  
  #interpolationTot  <- do.call(rbind,res)
  interpolationTot  <- res[[1]][,which(duplicated(colnames(res[[1]]))==FALSE)]
  if(length(res)>1){
    for(i in 2:length(res)){
      if(nrow(res[[i]])>0)
        interpolationTot  <- rbindTacsat(interpolationTot,res[[i]][,which(duplicated(colnames(res[[i]]))==FALSE)])
    }
  }
  #tacsatInt         <- rbind(interpolationTot,tacsat[,colnames(interpolationTot)])
  tacsatInt         <- rbindTacsat(tacsat,interpolationTot)
  tacsatInt         <- sortTacsat(tacsatInt)
  
  return(tacsatInt)
  
}

`sortTacsat` <-
  function(dat){
    require(doBy)
    
    if(!"SI_DATIM" %in% colnames(dat)) dat$SI_DATIM  <- as.POSIXct(paste(dat$SI_DATE,  dat$SI_TIME,   sep=" "), tz="GMT", format="%d/%m/%Y  %H:%M")
    
    #Sort the tacsat data first by ship, then by date
    if("VE_REF" %in% colnames(dat)) dat <- orderBy(~VE_REF+SI_DATIM,data=dat)
    if("OB_REF" %in% colnames(dat)) dat <- orderBy(~OB_REF+SI_DATIM,data=dat)
    
    return(dat)}

`lonLatRatio` <-
  function(x1,lat){
    #Based on the Haversine formula
    #At the position, the y-position remains the same, hence, cos(lat)*cos(lat) instead of cos(lat) * cos(y2)
    a <- cos(lat*pi/180)*cos(lat*pi/180)*sin((0.1*pi/180)/2)*sin((0.1*pi/180)/2);
    c <- 2*atan2(sqrt(a),sqrt(1-a));
    R <- 6371;
    dx1 <- R*c
    
    return(c(dx1/11.12))}


`an` <-
  function(x){return(as.numeric(x))}


`findEndTacsat` <-
  function(tacsat
           ,startTacsat #Starting point of VMS
           ,interval #Specify in minutes, NULL means use all points  
           ,margin   #Specify the margin in minutes it might deviate from the interval time, in minutes
  ){
    VMS         <- tacsat
    if(!"SI_DATIM" %in% colnames(VMS)) VMS$SI_DATIM   <- as.POSIXct(paste(tacsat$SI_DATE,  tacsat$SI_TIME,   sep=" "), tz="GMT", format="%d/%m/%Y  %H:%M")
    
    startVMS    <- startTacsat
    clStartVMS  <- startVMS #Total VMS list starting point instead of subset use
    iShip       <- VMS$VE_REF[startVMS]
    VMS.        <- subset(VMS,VE_REF==iShip)
    startVMS    <- which(VMS$VE_REF[startVMS] == VMS.$VE_REF & VMS$SI_DATIM[startVMS] == VMS.$SI_DATIM)
    if(clStartVMS != dim(VMS)[1]){
      if(VMS$VE_REF[clStartVMS] != VMS$VE_REF[clStartVMS+1]){
        #End of dataset reached
        endDataSet <- 1
        endVMS <- NA
      } else {
        #Calculate the difference in time between the starting VMS point and its succeeding points
        diffTime  <- difftime(VMS.$SI_DATIM[(startVMS+1):dim(VMS.)[1]],VMS.$SI_DATIM[startVMS],units=c("mins"))
        if(length(which(diffTime >= (interval-margin) & diffTime <= (interval+margin)))==0){
          warning("No succeeding point found, no interpolation possible")
          endVMS  <- NA
          #Check if end of dataset has been reached
          ifelse(all((diffTime < (interval-margin))==TRUE),endDataSet <- 1,endDataSet <- 0)
        } else {
          res <- which(diffTime >= (interval-margin) & diffTime <= (interval+margin))
          if(length(res)>1){
            res2        <- which.min(abs(interval-an(diffTime[res])))
            endVMS      <- startVMS + res[res2]
            endDataSet  <- 0
          } else {
            endVMS      <- startVMS + res
            endDataSet  <- 0
          }
        }
        #Build-in check
        if(is.na(endVMS)==FALSE){
          if(!an(difftime(VMS.$SI_DATIM[endVMS],VMS.$SI_DATIM[startVMS],units=c("mins"))) %in% seq((interval-margin),(interval+margin),1)) stop("found endVMS point not within interval range")
          endVMS <- clStartVMS + (endVMS - startVMS)
        }
        
      }
    } else { endDataSet <- 1; endVMS <- NA}
    
    return(c(endVMS,endDataSet))}

`interpolateTacsat` <-
function(tacsat                          #VMS datapoints
                              ,interval=120             #Specify in minutes, NULL means use all points
                              ,margin=12                #Specify the margin in minutes that the interval might deviate in a search for the next point
                              ,res=100                  #Resolution of interpolation method (default = 100)
                              ,method="cHs"             #Specify the method to be used: Straight line (SL) of cubic Hermite spline (cHs)
                              ,params=list(fm=0.5,distscale=20,sigline=0.2,st=c(2,6))  #Specify the three parameters: fm, distscale, sigline, speedthreshold
                              ,headingAdjustment=0
                              ,fast=FALSE){

if(!"SI_DATIM" %in% colnames(tacsat)) tacsat$SI_DATIM     <- as.POSIXct(paste(tacsat$SI_DATE,  tacsat$SI_TIME,   sep=" "), tz="GMT", format="%d/%m/%Y  %H:%M")
                              
  #Start interpolating the data
if(!method %in% c("cHs","SL"))  stop("method selected that does not exist")

#-------------------------------------------------------------------------------
#Fast method or not
#-------------------------------------------------------------------------------
if(fast){
  #Interpolation only by vessel, so split tacsat up
  tacsat$ID <- 1:nrow(tacsat)
  splitTa   <- split(tacsat,tacsat$VE_REF)
  spltTaCon <- lapply(splitTa,function(spltx){
                  #Calculate time different between every record
                  dftimex <- outer(spltx$SI_DATIM,spltx$SI_DATIM,difftime,units="mins")
                  iStep   <- 1
                  connect <- list()
                  counter <- 1
                  #Loop over all possible combinations and store if a connection can be made
                  while(iStep <= nrow(spltx)){
                    endp <- which(dftimex[,iStep] >= (interval - margin) & dftimex[,iStep] <= (interval + margin))
                    if(length(endp)>0){
                      if(length(endp)>1) endp <- endp[which.min(abs(interval - dftimex[endp,iStep]))][1]
                      connect[[counter]]    <- c(iStep,endp)
                      counter               <- counter + 1
                      iStep                 <- endp
                    } else { iStep          <- iStep + 1}
                  }
                  #Return matrix of conenctions
                  return(do.call(rbind,connect))})

  if(method=="cHs") returnInterpolations <- unlist(lapply(as.list(names(unlist(lapply(spltTaCon,nrow)))),function(y){
                                              return(interCubicHermiteSpline(spltx=splitTa[[y]],spltCon=spltTaCon[[y]],res,params,headingAdjustment))}),recursive=FALSE)
  if(method=="SL")  returnInterpolations <- unlist(lapply(as.list(names(unlist(lapply(spltTaCon,nrow)))),function(y){
                                              return(interStraightLine(splitTa[[y]],spltTaCon[[y]],res))}),recursive=FALSE)

} else {
  

    #Initiate returning result object
  returnInterpolations <- list()

    #Start iterating over succeeding points
  for(iStep in 1:(dim(tacsat)[1]-1)){
    if(iStep == 1){
      iSuccess    <- 0
      endDataSet  <- 0
      startVMS    <- 1
      ship        <- tacsat$VE_REF[startVMS]
    } else {
        if(is.na(endVMS)==TRUE) endVMS <- startVMS + 1
        startVMS <- endVMS
        #-Check if the end of the dataset is reached
        if(endDataSet == 1 & rev(unique(tacsat$VE_REF))[1] != ship){
          startVMS  <- which(tacsat$VE_REF == unique(tacsat$VE_REF)[which(unique(tacsat$VE_REF)==ship)+1])[1]
          ship      <- tacsat$VE_REF[startVMS]
          endDataSet<- 0
        }
        if(endDataSet == 1 & rev(unique(tacsat$VE_REF))[1] == ship) endDataSet <- 2 #Final end of dataset
      }

    #if end of dataset is not reached, try to find succeeding point
    if(endDataSet != 2){
      result      <- findEndTacsat(tacsat,startVMS,interval,margin)
      endVMS      <- result[1]
      endDataSet  <- result[2]
      if(is.na(endVMS)==TRUE) int <- 0  #No interpolation possible
      if(is.na(endVMS)==FALSE) int <- 1  #Interpolation possible

        #Interpolate according to the Cubic Hermite Spline method
      if(method == "cHs" & int == 1){

          #Define the cHs formula
        F00 <- numeric()
        F10 <- numeric()
        F01 <- numeric()
        F11 <- numeric()
        i   <- 0
        t   <- seq(0,1,length.out=res)
        F00 <- 2*t^3 -3*t^2 + 1
        F10 <- t^3-2*t^2+t
        F01 <- -2*t^3+3*t^2
        F11 <- t^3-t^2

        if (is.na(tacsat[startVMS,"SI_HE"])=="TRUE") tacsat[startVMS,"SI_HE"] <- 0
        if (is.na(tacsat[endVMS,  "SI_HE"])=="TRUE") tacsat[endVMS,  "SI_HE"] <- 0

          #Heading at begin point in degrees
        Hx0 <- sin(tacsat[startVMS,"SI_HE"]/(180/pi))
        Hy0 <- cos(tacsat[startVMS,"SI_HE"]/(180/pi))
          #Heading at end point in degrees
        Hx1 <- sin(tacsat[endVMS-headingAdjustment,"SI_HE"]/(180/pi))
        Hy1 <- cos(tacsat[endVMS-headingAdjustment,"SI_HE"]/(180/pi))

        Mx0 <- tacsat[startVMS, "SI_LONG"]
        Mx1 <- tacsat[endVMS,   "SI_LONG"]
        My0 <- tacsat[startVMS, "SI_LATI"]
        My1 <- tacsat[endVMS,   "SI_LATI"]

          #Corrected for longitude lattitude effect
        Hx0 <- Hx0 * params$fm * tacsat[startVMS,"SI_SP"] /((params$st[2]-params$st[1])/2+params$st[1])
        Hx1 <- Hx1 * params$fm * tacsat[endVMS,"SI_SP"]   /((params$st[2]-params$st[1])/2+params$st[1])
        Hy0 <- Hy0 * params$fm * lonLatRatio(tacsat[c(startVMS,endVMS),"SI_LONG"],tacsat[c(startVMS,endVMS),"SI_LATI"])[1] * tacsat[startVMS,"SI_SP"]/((params$st[2]-params$st[1])/2+params$st[1])
        Hy1 <- Hy1 * params$fm * lonLatRatio(tacsat[c(startVMS,endVMS),"SI_LONG"],tacsat[c(startVMS,endVMS),"SI_LATI"])[2] * tacsat[endVMS,"SI_SP"]/((params$st[2]-params$st[1])  /2+params$st[1])

          #Finalizing the interpolation based on cHs
        fx <- numeric()
        fy <- numeric()
        fx <- F00*Mx0+F10*Hx0+F01*Mx1+F11*Hx1
        fy <- F00*My0+F10*Hy0+F01*My1+F11*Hy1

          #Add one to list of successful interpolations
        iSuccess <- iSuccess + 1
        returnInterpolations[[iSuccess]] <- matrix(rbind(c(startVMS,endVMS),cbind(fx,fy)),ncol=2,dimnames=list(c("startendVMS",seq(1,res,1)),c("x","y")))
      }

        #Interpolate according to a straight line
      if(method == "SL" & int == 1){
        fx <- seq(tacsat$SI_LONG[startVMS],tacsat$SI_LONG[endVMS],length.out=res)
        fy <- seq(tacsat$SI_LATI[startVMS],tacsat$SI_LATI[endVMS],length.out=res)

          #Add one to list of successful interpolations
        iSuccess <- iSuccess + 1
        returnInterpolations[[iSuccess]] <- matrix(rbind(c(startVMS,endVMS),cbind(fx,fy)),ncol=2,dimnames=list(c("startendVMS",seq(1,res,1)),c("x","y")))
      }
    }
  }
}

return(returnInterpolations)}


cat("Loading Table\n")
tacsatX <-read.table(inputFile,sep=",",header=T)
cat("Adjusting Columns Types\n")
tacsatX<-transform(tacsatX,  VE_COU= as.character(VE_COU), VE_REF= as.character(VE_REF), SI_LATI= as.numeric(SI_LATI), SI_LONG= as.numeric(SI_LONG), SI_DATE= as.character(SI_DATE),SI_TIME= as.character(SI_TIME),SI_SP= as.numeric(SI_SP),SI_HE= as.numeric(SI_HE))
tacsatX$SI_DATIM=NULL
cat("Sorting dataset\n")
tacsatS <- sortTacsat(tacsatX)
tacsatCut<-tacsatS
tacsatCut <- tacsatS[1:1000,]

cat("Interpolating\n")
interpolation <- interpolateTacsat(tacsatCut,interval=interval,margin=margin,res=res, method=method,params=list(fm=fm,distscale=distscale,sigline=sigline,st=st),headingAdjustment=headingAdjustment,fast=fast)
cat("Reconstructing Dataset\n")
tacsatInt <- interpolation2Tacsat(interpolation=interpolation,tacsat=tacsatCut,npoints=npoints,equalDist=equalDist)
tacsatInt <- sortTacsat(tacsatInt)
cat("Writing output file\n")
write.csv(tacsatInt, outputFile, row.names=T)
print(Sys.time())
cat("All Done.\n")
