R/Spectra-Methods.R

Defines functions Spectra

Documented in Spectra

#' Constructor function for the class \code{Spectra}.
#'
#' @description
#' \code{Spectra} Creates an instance of class \code{Spectra}.
#'
#' @param inDF a long-format \code{data.frame} containing LAT,LON and TIME columns as well as Ancillary data.
#' See \code{\link{stConstruct}} for more information on long DF format.
#' @param Spectra \code{matrix} containing spectral data. Channels are in columns, observations are in rows.
#' If \code{Spectra} is missing, the first \code{length(Wavelengths)} columns of inDF will be taken
#' as spectral data.
#' @param Wavelengths \code{numeric} vector containing wavelengths of spectral channels.
#' @param Units \code{character} defining the units of the wavelengths.
#' @param space a character or integer holding the 
#' column index in inDF where the spatial coordinates are (if length(space)==2) or where the ID of 
#' the spatial location is (if (length(space)==1). If \code{space} is not provided, inDF columns are
#' searched to match one of the following : LAT,lat,latitude,LATITUDE,LON,LONG,lon,long,longitude,LONGITUDE
#' If LAT & LON are not found, they set the dummy value of 1.
#' @param time \code{character} or \code{integer} indicating the column in inDF containing POSIXct TIME
#' data values. if \code{time} is missing, it is set the dummy integer sequential vector of {1:nrow(Spectra)}.
#' @param endTime \code{character} or \code{integer} indicating the column in inDF containing POSIXct
#' ENDTIME data values. If the temporal measurements are performed over an interval, \code{time} and \code{endtime} 
#' contain the time for the start and end of intervals respectively. If the temporal measurements are performed over 
#' a time-instance, then \code{endTime==TIME}. If \code{endTime} is not provided, inDF columns are searched to match 
#' ENDTIME. If none found, then it is assumed that data are time-instance measurements. For more information, see the
#'  documentation of \pkg{spacetime}.
#' @param header \code{SpcHeader} object containing metadata
#' @param ... other input arguments to be passed to the new() function 
#' 
#' @details
#' This constructor function uses The function \code{Spectra()} calls \code{spacetime::stConstruct()}
#' that is the constructor  of the \code{STIDF} class using an input \code{data.frame} object of long-table format.
#'
#' \code{length{@@Wavelengths}==ncol(@@Spectra)}. The default @@WavelengthsUnit is nm^{-1}.
#' 
#' @return Returns an object of class \code{Spectra}.
#'
#' @examples
#' fnm = file.path(base::system.file(package = "geoSpectral"),
#' "test_data","particulate_absorption.csv.gz")
#' fnm=gsub("\\\\", "/", fnm)
#' abs = read.table(fnm,sep=",",header=TRUE)
#' abs$STATION=factor(abs$STATION)
#' abs[1:2,1:17] #Display only the first 2 rows and first 17 columns if the data frame
#' lbd = as.numeric(gsub("X","",colnames(abs)[14:514]))
#' Units="1/m"
#' colnames(abs)= gsub("X",paste("anap","_",sep=""), colnames(abs))
#' colnames(abs)= gsub("PRES","DEPTH", colnames(abs))
#' abs = abs[,c(14:514,1:13)] #Rearrange so that Spectra columns come first
#' tz<-strsplit(as.character(abs$TIME)," ")[[1]][[3]] #Extract the timezone
#' abs$TIME = as.POSIXct(as.character(abs$TIME),tz=tz) #Compute the time
#' 
#' #Space and time columns are automatically found in the column names of inDF
#' myS<-Spectra(abs,Wavelengths=lbd,Units=Units,ShortName="a_nap")
#'
#' #Space and time columns are explicitly chosen from inDF columns
#' myS<-Spectra(abs,Wavelengths=lbd, space=c("LONG","LAT"), time="TIME",
#' Units=Units,ShortName="a_nap")
#'
#' @export
Spectra = function(inDF,Spectra,Wavelengths,Units,space,time,endTime,header,...){
  longcol="";latcol="";timecol=""
  
  #Extract Wavelengths from data frame columns
  if(missing(Wavelengths)){
    Wavelengths = attr(inDF,"Wavelengths")
    lbd.idx = !is.na(Wavelengths)
    Wavelengths = Wavelengths[lbd.idx]
    
    #Extract Spectra from data frame columns
    if(missing(Spectra)){
      Spectra = as.matrix(inDF[,lbd.idx])  	
    }
  }
  #Extract Spectra from data frame columns
  if(missing(Spectra)){
    Spectra = as.matrix(inDF[,1:length(Wavelengths)])
    inDF = cbind(data.frame(idx=1:nrow(inDF)), inDF[,-(1:length(Wavelengths))])
  }
  
  if(missing(space)){
    if ("LAT" %in% names(inDF))
      latcol = "LAT"
    if ("lat" %in% names(inDF))
      latcol = "lat"
    if ("latitude" %in% names(inDF))
      latcol = "latitude"
    if ("LATITUDE" %in% names(inDF))
      latcol = "LATITUDE"
    if ("LON" %in% names(inDF))
      longcol = "LON"
    if ("LONG" %in% names(inDF))
      longcol = "LONG"
    if ("lon" %in% names(inDF))
      longcol = "lon"
    if ("long" %in% names(inDF))
      longcol = "long"
    if ("longitude" %in% names(inDF))
      longcol = "longitude"
    if ("LONGITUDE" %in% names(inDF))
      longcol = "LONGITUDE"
    
    if (!(longcol %in% names(inDF))) {
      inDF$LONG=1
      longcol="LONG"
      warning("Could not find a longitude column named either of: lon,long,LON,LONG,longitue,LONGITUDE. Assigning LONG=1.0 to all rows")
    } 
    if(!(latcol %in% names(inDF))){
      inDF$LAT=1
      latcol="LAT"
      warning("Could not find a latitude column named either of: lat,LAT,latitude,LATITUDE. Assigning LAT=1.0 to all rows")
    }
    space=c( which(longcol==names(inDF)), which(latcol==names(inDF)))
    names(inDF)[space]<-c("LON","LAT")
  }
  if(missing(time)){
    if ("time" %in% names(inDF))
      timecol = "time"
    if ("TIME" %in% names(inDF))
      timecol = "TIME"
    if (!timecol %in% names(inDF)){
      inDF$TIME=as.POSIXct(1:nrow(inDF),origin = "1970-01-01 00:00:00",tz=base::format(Sys.time(), format="%Z"))
      timecol="TIME"
      warning("Could not find a time column named either of : time or TIME. Assigning TIME=1.0 seconds to all rows")
    }
    time=timecol
  }
  if(missing(endTime)){
    if("ENDTIME" %in% names(inDF)){
      endTime = inDF$ENDTIME
    } else{
      endTime = inDF[,time]
    }
  }
  if(missing(Units)){
    #Extract Units
    Units = attr(inDF,"Units")[1]
  }
  if(missing(header)){
    #Extract Units
    header = new("SpcHeader")
  }

  #First construct a STIDF object using stConstruct()
  out = stConstruct(x=inDF,space=space,time=time,endTime=endTime)
  
  #I think stConstruct does not take endTime into account. Force it again
  out@endTime = endTime
  out = new("Spectra",out, Spectra=Spectra,Wavelengths=Wavelengths,Units=Units,header=header,...)
  validObject(out)
  return(out)
}

#' @title Conversion between \code{Spectra} and data.frame objects
#'
#' @description Converting \code{Spectra} object to data.frame is straightforward 
#' while the conversion in the opposite direction requires a set of attributes
#' to be present in the source data.frame object.
#' These attributes are generally created during the conversion of a
#' \code{Spectra} object into data.frame, they can 
#' also be manually set if they are non-existant (see the example below).
#' @param from The input object
#' @param to Name of the class of output object
#' @aliases as,Spectra
#' @rdname Spectra-coerce
#' @name Spectra-coerce
#' @examples 
#' #Convert a Spectra object to data.frame
#' sp <- spc.example_spectra()
#' df <- as(sp, "data.frame")
#' class(df); dim(df)
#' attributes(df)
#' 
#' #Convert the data.frame back to Spectra
#' sp2 <- as(df, "Spectra")
#' 
#' #Convert a bare data.frame to Spectra with minimal attributes
#' df2 <- data.frame(ch1=c(1,2,3,4), ch2=c(5,6,7,8), TIME=Sys.time()+1:4, LAT=1:4, LON=5:8)
#' attr(df2, "Units") <- "m-1"
#' attr(df2, "Wavelengths") <- c(500, 600)
#' attr(df2, "ShortName") <- "abs"
#' as(df2, "Spectra")
setAs(from="Spectra", to="data.frame", def=function(from){
  if(ncol(from@data)>0)
    output = cbind(as.data.frame(from@Spectra),from@data)
  
  delidx = match(c("LON","LAT","TIME","ENDTIME"),names(output))
  delidx = delidx[-which(is.na(delidx))]
  if(length(delidx)>0)
    output = output[,-delidx[!is.na(delidx)]]
  
  output$LON = from@sp@coords[,1]
  output$LAT = from@sp@coords[,2]
  output$TIME= time(from@time)
  output$ENDTIME=from@endTime
  attr(output,"ShortName") = from@ShortName
  attr(output,"LongName") = from@LongName
  attr(output,"Wavelengths") = from@Wavelengths
  attr(output,"Units") = from@Units
  attr(output,"header") = as(from@header,"list")
  names(attr(output,"header")) = names(from@header)
  
  return(output)
})

#' @rdname Spectra-coerce
#' @name Spectra-coerce
setAs(from="data.frame", to="Spectra", def=function(from){
  #This function makes use of geoSpectral::Spectra()
  if(!any(grepl("Wavelengths", names(attributes(from))))) 
    stop("The required data.frame attribute was not found : Wavelengths")
  
  if(!any(grepl("Units", names(attributes(from)))))
    stop("The required data.frame attribute was not found : Units")
  
  if(!any(grepl("ShortName", names(attributes(from)))))
    stop("The required data.frame attribute was not found : ShortName")
  
  Wavelengths= attr(from, "Wavelengths") 
  Units=attr(from,"Units") 
  ShortName = attr(from, "ShortName")
  
  if (any(grepl("LongName", names(attributes(from))))){
    LongName = attr(from, "LongName")
  } else {
    LongName = ShortName
  }
  
  #Create Spectra matrix
  Spectra = as.matrix(from[,1:length(Wavelengths)])
  if(prod(dim(Spectra))==0)
    stop("The Spectra matrix is empty. Cannot create a spectra object")
  if(!all(sapply(Spectra,class)=="numeric"))
    stop(paste("Cannot create a numeric matrix from", length(Wavelengths),
               "columns of the input data.frame. Cannot create a spectra object"))
  
  #Create ancillary data.frame
  if (ncol(from)>length(Wavelengths)) {
    myidx = (length(Wavelengths)+1):ncol(from)
    #Suppress warnings for the below operation ("drop" creates warnings)
    myWarn = options()$warn
    options(warn=-1)
    data = from[myidx,drop=F]
    options(warn=myWarn)				
  } else {
    data = data.frame(1:nrow(Spectra))
  }
  
  #Extract the header
  if(!is.null(attr(from,"header")))
    header = as(attr(from,"header"),"SpcHeader")
  else
    header = new("SpcHeader")
  
  if(!xts::is.timeBased(from$TIME))
    stop("The TIME column does not contain time-based data")
  TIME = xts::xts(1:length(from$TIME), from$TIME)
  if(!xts::is.timeBased(from$ENDTIME)){
    endTime = from$TIME
  }else{
    endTime = from$ENDTIME
  }
  outS = Spectra(data,Spectra,Wavelengths,Units=Units,
                          header=header,ShortName=ShortName,LongName=LongName,endTime=endTime)
  
  validObject(outS)
  return(outS)
})

#' Dimensions of a \code{Spectra} object.
#'
#' @description
#' Gives number of dimension of a \code{Spectra} object
#'
#' @param x A \code{Spectra} object
#' 
#' @return Returns a numeric vector containing \code{nrow} and \code{ncol} of the \code{Spectra} object.
#'
#' @examples
#' sp<-spc.example_spectra()
#' dim(sp)
#' 
#' @export
setMethod("dim", signature = "Spectra", 
          def = function (x){
            return(dim(x@Spectra))  
          })
#########################################################################
# Method : ncol
#########################################################################
#' The Number of Columns  of a Spectra object
#'
#' @description
#' \code{nrow} and \code{ncol} return the number of rows or columns of a \code{Spectra} object 
#'
#' @param  x A \code{Spectra} object 
#'
#' @examples
#' x <- spc.example_spectra() 
#' ncol(x)  #501 
#' nrow(x)  #26
#' 
#' @export
setMethod("ncol", signature = "Spectra", 
          def = function (x){  return(ncol(x@Spectra))  })

########################################################################
# Method : nrow
#########################################################################
#' The Number of rows  of a \code{Spectra} object
#'
#' @description
#' \code{nrow} and \code{ncol} return the number of rows or columns present in a \code{Spectra} object 
#'  
#' @param  x a \code{Spectra} object 
#'
#' @examples
#' x <- spc.example_spectra() 
#' ncol(x)  #501 
#' nrow(x)  #26
#' 
#' @export
setMethod("nrow", signature = "Spectra", 
          def = function (x){  return(nrow(x@Spectra))  })
#########################################################################
# Method : names
#########################################################################
#' The Names of a \code{Spectra} object
#'
#' @description
#'  Retrieve  the names of \code{Spectra} object 
#' @param  x  a \code{Spectra} object
#' @examples
#' 
#' x <- spc.example_spectra() 
#' names(x)
#' 
#' @export
setMethod("names", signature = "Spectra", 
          def = function (x){ 
            #			if(ncol(x@data)>1)
            return(c(colnames(x@Spectra),names(x@data)))
            #			else                
            #				return(names(x@data)) 
          })

setGeneric("endTime",function(x){standardGeneric("endTime")})
setMethod("endTime", signature = "Spectra", def = function (x){  
  return(x@endTime)
})

#########################################################################
# Method : head
#########################################################################
#' Return the first or last part of a \code{Spectra} object
#'
#' @description
#' Return the first or last parts of a \code{Spectra} object 
#'
#' @param  x a \code{Spectra} object
#' @param  ... arguments to be passed to or from other methods
#'  
#' @return Returns a matrix (\code{Spectra} data)
#'
#' @examples
#' x <- spc.example_spectra()
#' head(x)
#' 
#' @export
setMethod("head", signature = "Spectra", 
          def = function (x, ...){  return(head(x@Spectra, ...)) })
#########################################################################
# Method : show
#########################################################################
#' Show a \code{Spectra} object
#'
#' @description Display a \code{Spectra} object 
#'
#' @param object a \code{Spectra} object 
#' @return  show returns an invisible \code{NULL}
#'
#' @examples
#' x <- spc.example_spectra()
#' show(x)
#' 
#' @export
setMethod("show", "Spectra", function(object){
  if(ncol(object)==0){
    LongName = character()
    Units = character()
    LbdStr = character()
  } else {
    LongName = object@LongName[1]
    Units = object@Units[1]
    LbdStr = paste("[",min(object@Wavelengths),",",max(object@Wavelengths), "] ->",sep=" ")                    
  }
  bbx=bbox(object@sp)
  if (length(object@time)>1){
    period = paste(as.character(xts::periodicity(object@time))[1],
                   as.character(xts::periodicity(object@time))[5])
    timerange = as.character(range(time(object@time)),usetz=F)
    tz = format(time(object@time[1]),format="%Z")
    timestr = paste("Time : periodicity of ", period, " between (", 
                    timerange[1]," - ",timerange[2],"), tz=", tz ,sep="")
  } 
  if (length(object@time)==1) { 
    timestr = paste("Time : ", as.character(time(object@time),usetz=T))
  }
  if (length(object@time)==0) { 
    timestr = paste("Time : NA")
  }
  if(ncol(object)==0)
    Str = c("\n","Empty Spectra object","\n")
  else
    Str = c("\n", paste(object@ShortName[1], ' : An object of class "Spectra"\n', 
                        length(object@Wavelengths),"spectral channels in columns and", nrow(object@data), 
                        "observations in rows"), "\n",
            "LongName: ", LongName, "\t", "Units: ", Units, "\n",
            "Wavelengths : ", length(object@Wavelengths), "channels with units of",object@WavelengthsUnit,  LbdStr, head(object@Wavelengths)," ...\n",
            "Spectra Columns: ", head(colnames(object@Spectra)), "...\n",
            "Ancillary Columns: ", head(names(object@data)),"...\n",
            "Bounding box:", "LON(",format(bbx[1,],digits = 7),") LAT(",format(bbx[2,],digits = 7),")\n",
            timestr, "\n")
  if(length(object@Wavelengths)==1)
    Str = gsub("channels","channel",Str)
  cat(Str)
})		

#########################################################################
# Method : $
#########################################################################
#' Extract or replace parts of a \code{Spectra} object
#'
#' @description
#' Operators acting on \code{Spectra} objects to extract parts
#' 
#' @param x A \code{Spectra} object from which to extract element(s) or in which to replace element(s)
#' @param i A numeric (row index) variable
#' @param j A character (column name) or a numeric (column index) variable
#' @param name A character (column name) or a numeric (column index) variable
#' @param value A vector or matrix or data.frame. Values to be replaced with matched \code{Spectra} column.
#' @examples
#'  sp<-spc.example_spectra()
#'  # spc.colnames() is used to extract column names
#'  head(spc.colnames(sp))
#'  head(sp$anap_300)
#'  sp[,"anap_345"]
#'  sp[,"anap_345"] #returns Spectra object with only one channel (column)
#'  sp[1:3,"anap_345"] #returns Spectra object with first 3 rows and only one channel (column)
#' @export
#' @rdname Spectra-Access
#' @aliases $,Spectra
setMethod("$", signature="Spectra", function(x, name) {
  if (name %in% colnames(x@Spectra)){
    Boutput = x@Spectra[,name]
  } 
  if (name %in% names(x@data)){
    Boutput = x@data[,name]				
  }
  if(!exists("Boutput"))
    stop("Could not match any Spectral or Ancillary (@data) columns")
  return(Boutput)
})

#' Replace parts of a \code{Spectra} object
#'
#' @examples
#'  # spc.colnames() is used to extract column names
#'  head(spc.colnames(sp))
#'  head(sp$anap_300)
#'  sp[,"anap_345"]
#' @rdname Spectra-Access
#' @aliases $<-,Spectra
setReplaceMethod("$", signature = "Spectra",  function(x, name, value) {
  x[[name]]=value
  #validObject(x) will be called by the [[ method
  return(x)
})

#########################################################################
# Method : spc.colnames
#########################################################################
#' Column names of \code{Spectra} object
#'
#' @description
#' Set or retrieve column names of a \code{Spectra} object
#'
#' @param x  A \code{Spectra} object
#' @param value character vector containing new column names to be assigned
#' @return spc.colnames() returns the column  names of an object of class \code{Spectra} 
#' as a character vector. spc.colnames()<- returns a \code{Spectra} object.
#'
#' @examples
#' x <- spc.example_spectra()
#' head(spc.colnames(x))
#' # or 
#' spc.colnames(x) <- spc.cname.construct(x)
#' spc.colnames(x)
#' @seealso \code{\link{spc.cname.construct}}
#' @rdname spc.colnames
#' @export
setGeneric("spc.colnames",function(x){standardGeneric("spc.colnames")})

#' @rdname spc.colnames
setMethod("spc.colnames", signature = "Spectra", 
          def = function (x){ return(colnames(x@Spectra)) })

#' Set column names of a \code{Spectra} object
#' @rdname spc.colnames
#' @export
setGeneric("spc.colnames<-",function(x,value){standardGeneric("spc.colnames<-")})

#' @rdname spc.colnames
setReplaceMethod("spc.colnames", signature = "Spectra", def = function (x,value){
  colnames(x@Spectra) = value
  validObject(x)
  return(x) 
})


#########################################################################
# Method : spc.plot
#########################################################################
#'  Plotting \code{Spectra} object
#'
#' @description
#' Generating plot of the intensity of a measurement inside a \code{Spectra} object with respect to the wavelength.
#'
#' @usage 
#' spc.plot(x, Y, maxSp, lab_cex,xlab,ylab,type,pch,lwd,cex,...)
#' @param x and Y	 a \code{Spectra} data 
#' @param Y fskjldsk
#' @param xlab title for x  axix, as in plot().
#' @param ylab title for y axis, as in plot().
#' @param pch character string or vector of 1-characters or integers for plotting characters.
#' See help of \code{\link{par}}.
#' @param ...  any further arguments to be passed to matplot
#' @param lab_cex vector of character expansion sizes, used cyclically
#' @param lwd vector of line widths. See help of \code{\link{par}}. 
#' @param maxSp maximum number of \code{Spectra} to plot
#' @param cex A numerical value giving the amount by which plotting text and symbols should 
#' be magnified relative to the default. See help of \code{\link{par}}.
#' @param type character string (length 1 vector) or vector of 1-character strings indicating 
#' the type of plot for each column of y. See help of matplot() or plot().
#' 
#' @seealso \code{\link{spc.lines}}, \code{\link{par}}
#' @examples
#' x <- spc.example_spectra()
#' spc.plot(x)
#' 
#' @rdname spc.plot
#' @export
setGeneric("spc.plot",function(x,Y,maxSp,lab_cex,xlab,ylab,type="l",pch=19,lwd=2,cex=0.3,...){standardGeneric("spc.plot")})

#' @rdname spc.plot
setMethod("spc.plot", "Spectra", function (x, Y, maxSp, lab_cex,xlab,ylab,type="l",pch=19,lwd=2,cex=0.3,...){						
  if (length(x@InvalidIdx)==0)
    x@InvalidIdx = rep(FALSE,nrow(x@Spectra))
  
  if(!missing(maxSp) && ncol(x)>maxSp)
    idx = seq(1,nrow(x),length.out=maxSp	)
  else
    idx = 1:nrow(x)

  Xidx = rep(FALSE, nrow(x@Spectra))
  Xidx[idx] = TRUE
  
  #if(any(x@InvalidIdx)){
  #  Xidx[x@InvalidIdx]=FALSE
  #}
  #			if(any(x@SelectedIdx)){
  #				mycol = rep("gray", nrow(x@Spectra))
  #				mycol[x@SelectedIdx]="red"
  #			} else
  
  #			if(missing(col)) 
  #				col = 1:10
  
  x@Units = gsub("\\[\\]","",x@Units)
  x@Units = gsub("\\[ \\]","",x@Units)
  
  if(missing(lab_cex))
    lab_cex = 1
  
  YY = x@Spectra[Xidx,]
  if(inherits(YY, "matrix") && nrow(YY)!=length(x@Wavelengths))
    YY = t(YY)
  
  inargs_in <- list(...)
  inargs_out <- c(list(x=x@Wavelengths,y=YY), inargs_in)

  if(! ("xlim" %in% names(inargs_in))){
    xlim = range(x@Wavelengths)
    if (x@WavelengthsUnit=="cm-1")
      xlim = rev(xlim)
    inargs_out$xlim <- xlim
  }
  
  # if("ylim" %in% names(inargs_in))
  #   inargs_out <- c(list(ylim=inargs_in$ylim), inargs_out)
  inargs_out <- c(list(xlab=""), inargs_out)
  inargs_out <- c(list(ylab=""), inargs_out)
  if(! ("type" %in% names(inargs_in)))
    inargs_out <- c(list(type=type), inargs_out)
  if(! ("pch" %in% names(inargs_in)))
    inargs_out <- c(list(pch=pch), inargs_out)
  if(! ("cex" %in% names(inargs_in)))
    inargs_out <- c(list(cex=cex), inargs_out)
  if(! ("cex.axis" %in% names(inargs_in)))
    inargs_out <- c(list(cex.axis=lab_cex), inargs_out)
  if(! ("lwd" %in% names(inargs_in)))
    inargs_out <- c(list(lwd=lwd), inargs_out)

  do.call(matplot, inargs_out)
  # matplot(x@Wavelengths,YY,#lab=x@Wavelengths,#xaxt="n",
  #         ylab= "",xlab="",type=type,xlim=xlim,pch=pch,cex=cex,cex.axis=lab_cex,lwd=lwd,...)

  if((!("ylab" %in% names(inargs_in))))
    ylab = bquote(.(x@ShortName)*", ["*.(x@Units[1])*"]")
  else
    ylab=inargs_in$ylab
  
  if((!("xlab" %in% names(inargs_in))))
    xlab=bquote("Wavelength ["*.(x@WavelengthsUnit)*"]")
  else
    xlab=inargs_in$xlab
  
  mtext(xlab,side=1,line=2,cex=lab_cex)			
  mtext(ylab,side=2,line=2,cex=lab_cex)
  
  abline(h=0)
  grid(col="black")
})

#########################################################################
# Method : spc.lines
#########################################################################
#'  Add spectra to an existing plot
#'
#' @description
#' Adds spectra to an existing plot created by spc.plot() using lines()
#'
#' 
#' @usage 
#' spc.lines(x,...)
#' @param x	 An object of class \code{Spectra}
#' @param ... Additional input arguments to be passed to lines()
#' 
#' @seealso \code{\link{spc.plot}}
#' @examples 
#' sp = spc.example_spectra()
#' spc.plot(sp[2,])
#' spc.lines(sp[3,],col="red")
#' 
#' @rdname spc.lines
#' @export
setGeneric("spc.lines",function(x,...){standardGeneric("spc.lines")})

#' @rdname spc.lines
setMethod("spc.lines",signature = "Spectra",definition = function(x,...){
  a=sapply(1:nrow(x@Spectra), function(S) {
    lines(x@Wavelengths, x@Spectra[S,],...)})
})

#########################################################################
# Method : spc.rbind
#########################################################################
#' Combine \code{Spectra} Objects by Rows
#'
#' @description
#' Take a \code{Spectra} objects and combine by rows
#'
#' @param ... \code{Spectra} object
#' @param compressHeader Compress the header (make multiple all-equal header elements as ONE, default value is TRUE	
#' @return  \code{Spectra} object 
#' @examples
#' x <- spc.example_spectra()
#' nrow(x)  #[1] 26
#' x2=spc.rbind(x,x)
#' nrow(x2)  #[1] 52
#' 
#' @rdname spc.rbind
#' @export
setGeneric (name= "spc.rbind",def=function(...){standardGeneric("spc.rbind")})

#' @rdname spc.rbind
setMethod("spc.rbind", signature = "Spectra", def = function (...,compressHeader=TRUE){
  #Check that column names match
  DFL=sapply(list(...),function(x) names(x@data),simplify=F)
  if(!all(sapply(1:length(DFL),function(x) all(DFL[[x]]==DFL[[1]]))))
    stop("Names of all Ancillary data columns should be the same")
  
  #Check that column names match
  DFL=sapply(list(...),function(x) colnames(x@Spectra),simplify=F)
  if(!all(sapply(1:length(DFL),function(x) all(DFL[[x]]==DFL[[1]]))))
    stop("Names of all Spectral data columns should be the same")
  
  #Check that the number of columns match 
  DFL=sapply(list(...), function(x) ncol(x@Spectra),simplify=F)
  if(!all(sapply(1:length(DFL),function(x) all(DFL[[x]]==DFL[[1]]))))
    stop("All Spectra arrays should have the same number of columns")
  DFL=sapply(list(...), function(x) ncol(x@data),simplify=F)
  if(!all(sapply(1:length(DFL),function(x) all(DFL[[x]]==DFL[[1]]))))
    stop("All Ancillary arrays should have the same number of columns")
  
  DFL=sapply(list(...),spc.getwavelengths)
  #Check that all Wavelengths are equal
  if(!all(apply(DFL,1,diff)==0))
    stop("Wavelengths of all input Spectra objects should be the same")
  #Create the output variable
  outt = ..1
  
  #Error if does not contain SpatialPoints
  if(class(outt@sp)!="SpatialPoints")
    stop("Only support ST* inherited object based on SpatialPoints")
  
  #Get a list of all input arguments
  allinargs = aa=match.call(expand.dots = F)$...
  
  if(length(allinargs)>1){
    #For all input arguments
    for(I in 2:length(allinargs)){
      if(!inherits(eval((allinargs[[I]])),"STI"))
        stop("The input argument should inherit from class STI")
      #Get the slot Names
      sltn = slotNames(..1)
      #Slots to omit in the rbind process 
      sltn = sltn[sltn!="ShortName"]
      sltn = sltn[sltn!="LongName"]
      sltn = sltn[sltn!="Wavelengths"]
      sltn = sltn[sltn!="WavelengthsUnit"]
      sltn = sltn[sltn!="Units"]
      #For all slots
      for(J in 1:length(sltn)){

        myslot = slot(eval((allinargs[[I]])),sltn[J])
        if(class(myslot)[1]=="SpcHeader"){
          aa=rbind(as.data.frame(slot(outt,sltn[J]),stringsAsFactors=F), as.data.frame(myslot,stringsAsFactors=F))
          rownames(aa)=NULL
          bb = as.list(aa)
          bb = lapply(bb,function(x){names(x)<-NULL;x})
          outt@header = as(bb,"SpcHeader")
        }

        if(class(myslot)[1]=="matrix"|class(myslot)[1]=="data.frame")
          slot(outt,sltn[J])<- rbind(slot(outt,sltn[J]),myslot)

        if(class(myslot)[1]=="logical"|class(myslot)[1]=="numeric"|
             class(myslot)[1]=="character"|class(myslot)[1]=="POSIXct")

          if(class(myslot)[1]=="POSIXct"){
            mytz <- format(outt@endTime,"%Z")
            #Check if all values are similar, throw an error otherwise
            if (length(mytz)>1 && !do.call(all.equal, lapply(mytz, function(x)x)))
              stop("Time zone values of all elements are not equal. Stop.")
            slot(outt,sltn[J])<-as.POSIXct(as.POSIXlt(c(slot(outt,sltn[J]),myslot),tz=mytz[1]))
          }

        if(class(myslot)[1]=="xts"){
          slot(outt,sltn[J])<-c(slot(outt,sltn[J]),myslot)
          slot(outt,sltn[J])<-xts::xts(1:length(slot(outt,sltn[J])),time(slot(outt,sltn[J])))
        }	

        if(class(myslot)[1]=="SpatialPoints"){
          prj = slot(outt,sltn[J])@proj4string
          if (!identical(prj@projargs,myslot@proj4string@projargs))
            stop("proj4strings do not match!")
          #rbind the coordinates
          coords = rbind(coordinates(slot(outt,sltn[J])),coordinates(myslot))
          #Create a SpatialPoints object
          slot(outt,sltn[J])<-SpatialPoints(coords,proj4string=prj)
        }
      } #end for all slots
    } #end for all input arguments
  } #end for if(length(allinargs)>1)

  #Compress the header (make multiple all-equal header elements as ONE)
  if(compressHeader){
    for(J in names(outt@header)){
      if(length(outt@header[[J]])>1){
        myO = sapply(2:length(outt@header[[J]]),function(x){
          outt@header[[J]][x]==outt@header[[J]][1]})
        try(if(all(myO)) outt@header[[J]]=outt@header[[J]][1],silent=T)
        if(all(is.na(outt@header[[J]])))
          outt@header[[J]]=outt@header[[J]][1]
      }
    }
  }

  validObject(outt)
  return(outt) 
})

#########################################################################
# Method : spc.rbind
#########################################################################
#' Combine \code{STIDF} objects by Rows
#'
#' @description
#' Take a \code{STIDF} objects and combine by rows
#'
#' @param ... \code{STIDF} object
#'
#' @examples
#' x <- spc.example_spectra()
#' nrow(x)  #[1] 26
#' x2 <- spc.rbind(as(x, "STIDF"),as(x, "STIDF"))
#' nrow(x2)  #[1] 52
#' 
#' @export
setMethod("spc.rbind", signature = "STIDF", def = function (...){
  #Create the output variable
  outt = ..1
  
  #Get a list of all input arguments
  allinargs = aa=match.call(expand.dots = F)$...
  
  #For all input arguments
  for(I in 2:length(allinargs)){
    #Get the slot Names
    sltn = slotNames(outt)
    
    #Error if does not inherit from STI or contain SpatialPoints 
    if(class(eval(allinargs[[I]])@sp)!="SpatialPoints")
      stop("Only support ST* inherited object based on SpatialPoints")				
    if(!inherits(eval((allinargs[[I]])),"STI"))
      stop("The input argument should inherit from class STI")
    #For all slots
    for(J in 1:length(sltn)){
      myslot = slot(eval((allinargs[[I]])),sltn[J])
      if(class(myslot)[1]=="matrix"|class(myslot)[1]=="data.frame")
        slot(outt,sltn[J])<- rbind(slot(outt,sltn[J]),myslot)
      if(class(myslot)[1]=="logical"|class(myslot)[1]=="numeric"|
           class(myslot)[1]=="character"|class(myslot)[1]=="POSIXct")
        if(class(myslot)[1]=="POSIXct"){
          mytz = attr(outt@endTime,"tzone")
          slot(outt,sltn[J])<-as.POSIXct(as.POSIXlt(c(slot(outt,sltn[J]),myslot),tz=mytz))
        }
      if(class(myslot)[1]=="xts"){
        slot(outt,sltn[J])<-c(slot(outt,sltn[J]),myslot)
        slot(outt,sltn[J])<-xts::xts(1:length(slot(outt,sltn[J])),time(slot(outt,sltn[J])))
      }
      if(class(myslot)[1]=="SpatialPoints"){
        prj = slot(outt,sltn[J])@proj4string
        if (!identical(prj@projargs,myslot@proj4string@projargs))
          stop("proj4strings do not match!")
        #rbind the coordinates
        coords = rbind(coordinates(slot(outt,sltn[J])),coordinates(myslot))
        #Create a SpatialPoints object
        slot(outt,sltn[J])<-SpatialPoints(coords,proj4string=prj)
      }
    } #end for all slots
  } #end for all input arguments			
  validObject(outt)
  return(outt) 
})

#########################################################################
# Method : spc.getwavelengths
#########################################################################
#' Extract wave lenghts of a \code{Spectra} object
#'
#' @description
#' Get wave lenghts inside of  a \code{Spectra} object
#'
#' @usage 
#' spc.getwavelengths(object)
#'
#' @param object A \code{Spectra} object
#' 
#' @return numeric vector of  wave lengths
#' @seealso \code{\link{spc.setwavelengths<-}}
#' @examples
#'  x <- spc.example_spectra()
#'  spc.getwavelengths(x)
#'  
#' @rdname spc.getwavelengths
#' @export
setGeneric (name= "spc.getwavelengths",def=function(object){standardGeneric("spc.getwavelengths")})

#' @rdname spc.getwavelengths
setMethod("spc.getwavelengths", signature = "Spectra", def = function (object){
  return(object@Wavelengths)
})

#########################################################################
# Method : spc.setwavelengths
#########################################################################
#' Setting wavelengths  in a \code{Spectra} object
#'
#' @description
#' Function  to change or set wavelengths  inside  of  a \code{Spectra} object
#'
#' @param object A \code{Spectra} object
#' @param value Numeric 
#' 
#' 
#' @seealso \code{\link{spc.getwavelengths}}
#' 
#'
#' @examples
#'  x <- spc.example_spectra()
#'  show(x)
#'  spc.setwavelengths(x) <- 300:800
#'  show(x)
#' @rdname spc.setwavelengths
#' @export
setGeneric("spc.setwavelengths<-",function(object,value){standardGeneric("spc.setwavelengths<-")})

#' @rdname spc.setwavelengths
setReplaceMethod(f="spc.setwavelengths", signature="Spectra",definition=function(object,value){
  object@Wavelengths <-value
  validObject(object)
  return (object)
})

#########################################################################
# Method : spc.cname.construct
#########################################################################
#' Generating column names for a \code{Spectra} object
#' @description
#'Function for a \code{Spectra} object that generates column names made of a 
#'combination of @shortName and @Wavelenght slots. If \code{value} is 
#'omitted, the @ShortName slot is used.
#'
#' @usage 
#' spc.cname.construct(object, value)
#'
#' @param value A character object
#' @param object A variable of class \code{Spectra}
#' 
#' @return vector of characters
#' @examples 
#' sp <- spc.example_spectra()
#' spc.cname.construct(sp)
#' spc.cname.construct(sp,"Newvar")
#' 
#' @rdname spc.cname.construct
#' @export
setGeneric("spc.cname.construct",function(object,value){standardGeneric("spc.cname.construct")})

#' @rdname spc.cname.construct
setMethod(f="spc.cname.construct", signature="Spectra",definition=function(object,value){
            if(missing(value))
              value = object@ShortName
            return(paste(value,round(spc.getwavelengths(object)),sep="_"))
          })

##########################################################################
#spc.timeMatch
##########################################################################
#' Match two time sequences
#' @description
#' Match two time sequences for a \code{Spectra} object, where each can be intervals or instances.
#'
#' @usage 
#' spc.timeMatch(master,searched,returnList,method,limits,report)
#'
#' @param master ordered sequence of variable of class \code{Spectra}
#' @param searched A variable of class \code{Spectra}which is searched
#' @param returnList Boolean; should a list be returned with all matches (TRUE), or a vector with single matches (FALSE)?
#' @param method Method used in time-based matching. See the details section.
#' @param limits the interval limits
#' @param report return character string which has information about searching results, default is False
#' @details 
#' spc.timeMatch is similar to spacetime::timeMatch(), only adding some more matching methods.
#' When method is "over", the same technique used by spacetime::timeMatch() is used. Useful when
#' matched timestamps of both master and searched are exactly equal.
#' When method is "nearest", the nearest measurement will be found, 
#' matching only one data for ALL elements of master.
#' When method is "within", measurements that are within the interval limits=c(upper,lower) (in seconds) 
#' will be found.
#' @examples 
#' #Read the Nomad database inside a SpcList object.
#' dat = SpcList(spc.Read_NOMAD_v2())
#' 
#' #Different list elements containt different parameters
#' names(dat)
#' 
#' #We would like to find elements of Es that match time-wise rows of Kd.
#' nrow(dat$kd); nrow(dat$es)
#' 
#' #Use spc.timeMatch() to get row indexes of Es that would match those of Kd time-wise
#' t_idx=spc.timeMatch((dat$kd), (dat$es))
#' #Verification
#' all(time(dat$es)[t_idx]==time(dat$kd))
#' 
#' 
#' @export
spc.timeMatch = function(master,searched,returnList=FALSE,method="over",limits,report=FALSE) {
  if(!is.timeBased(master))
    if(!(inherits(master,"ST")) & is.timeBased(master))
      stop("Input argument 'master' needs to either inherit from spacetime::ST class or be a timeBased variable")
  stopifnot(inherits(master,"ST"))
  if(!is.timeBased(searched))
    if(!(inherits(searched,"ST")) & is.timeBased(master))
      stop("Input argument 'searched' needs to either inherit from spacetime::ST class or be a timeBased variable")
  stopifnot(inherits(searched,"ST"))
  if(method=="over")
    out = spacetime::timeMatch(time(master),time(searched),returnList=returnList)
  if(method=="nearest"){
    out = sapply(time(master),function(x){mymin = which.min(abs(time(searched)-x))})
    if(returnList)
      out = lapply(out,function(x)x)
  }
  if(method=="within"){
    if(missing(limits))
      stop(simpleError("The input argument 'limits' is required if method=='within'"))
    if(length(limits)==0 || length(limits)>2)
      stop(simpleError("The input argument 'limits' needs to have a length of 1 or 2"))
    if(length(limits)==1)
      limits = c(limits,limits)
    out = which(time(searched)>time(master)[1]-limits[1] & 
                  time(searched)<master@endTime[length(master)]+limits[2])
  }
  if(report){
    print(paste(time(master)[1],master@endTime[length(master)],paste(out,collapse=" ")))
  }
  return(out)
}
#' Report the space and time distance of each row of an STI-inherited object
#' @description
#' Function that reports the space and time distance of each 
#' row of the STI-inherited object \code{searched} to the corresponding row of the 
#' STI-inherited object \code{master}
#'
#' @param master  An STI-inherited object
#' @param searched An STI-inherited object
#' @param report Logical. Default value is FALSE
#' 
#' @details 
#' Reports the space and time distance of each row of the STI-inherited object
#' \code{searched} to the corresponding row of the STI-inherited object \code{master}. 
#' 
#' @return Outputs a data.frame, with two columns : time2master ("difftime", in seconds) and 
#' distance2master ("numeric", in meters) 
#' 
#' @export
spc.STI.stdistance = function(master,searched,report=F){
  stopifnot(length(master)==length(searched))
  
  if(inherits(master,"STI"))
    mastertime = time(master)
  if(is.timeBased(master))
    mastertime = master	
  if(inherits(searched,"STI"))
    searchedtime = time(searched)
  if(is.timeBased(searched))
    searchedtime = searched
  output =  difftime(searchedtime,mastertime,units="secs")
  output = data.frame(time2master = as.numeric(output))
  
  if(inherits(master,"STI") && inherits(searched,"STI"))
    distn = sapply(1:length(master), function(x) {
      spDistsN1(t(as.matrix(coordinates(master)[x,])),t(as.matrix(coordinates(searched)[x,])))*1000
    })
  output = cbind(output,data.frame(distance2master=distn))
  
  if(report){
    a=hist(distn,breaks=50);a$breaks
    plot(master@sp)
    lines(spc.bbox2lines(master@sp))
  }	
  return(output)
}


#########################################################################
# Method : Arith
#########################################################################
#' Apply arithmetic operations on and between \code{Spectra} objects.
#' @description
#' Methods defining Arithmetic and Math operations between two \code{Spectra} objects e1 and e2 or one
#' \code{Spectra} object e1 and a numeric value.
#'
#' @param e1 spectra object 
#' @param e2 spectra object or other
#' @param x spectra object 
#' @details 
#' These methods allow performing arithmetic operations involving \code{Spectra} objects.
#' @seealso \code{\link{Arith}}
#' @rdname Spectra-Arith
setMethod("Arith", signature(e1 = "Spectra", e2 = "Spectra"),function (e1, e2) {
  result <- methods::callGeneric(e1@Spectra, e2@Spectra)
  output = e1
  output@Spectra = result
  validObject(output)
  return(output)
})

#' @rdname Spectra-Arith
setMethod("Arith", signature(e1 = "Spectra", e2 = "numeric"),function (e1, e2) {
  result <- callGeneric(e1@Spectra, e2)
  output = e1
  output@Spectra = result
  validObject(output)
  return(output)
})

#' @rdname Spectra-Arith
setMethod("Math", signature("Spectra"),function (x) {
  x@Spectra <- callGeneric(x@Spectra)
  validObject(x)
  return(x)
})
#############################################################
#spc.colMeans
#############################################################
#' Computes the mean along the rows of a \code{Spectra} object
#' @description
#' Computes the mean along the rows of a \code{Spectra} object. The method finds the measurement 
#' closest in time to the mean time and keeps the spatial/time attributes as well as Ancillary
#' data table (@data) associated to that measurement as that of the mean spectra
#' @usage 
#' spc.colMeans(object)
#'
#' @param object a \code{Spectra} object 
#' @examples 
#' sp=spc.example_spectra()
#' spc.colMeans(sp)
#' @rdname spc.colMeans
#' @export
setGeneric (name= "spc.colMeans",def=function(object){standardGeneric("spc.colMeans")})

#' @rdname spc.colMeans
setMethod("spc.colMeans", signature("Spectra"),function (object) {
  object@Spectra <- t(as.matrix(colMeans(object@Spectra)))
  #			object@data <- as.data.frame(t(callGeneric(object@data)))
  #Find the mean time
  meantime <- xts::xts(1,mean(time(object@time)),tzone=attr(object@time,"tzone"))
  #Find the row index closer in time to meantime
  min.idx = which.min(abs(as.numeric(time(meantime)-time(object@time))))
  object@sp <- object@sp[min.idx]
  object@time <-object@time[min.idx]
  object@data <- object@data[min.idx,,F]
  object@endTime <- mean(object@endTime)
  object@InvalidIdx <- logical()
  object@SelectedIdx <- logical()
  validObject(object)
  return(object)
})
#######################################################################
#spc.bbox2lines
######################################################################
#'  Constructs a rectangle with a \code{Spectra} object
#' @description
#' Constructs a rectangle of sp::Lines using the bounding box of a \code{Spectra} object.
#' @usage 
#' spc.bbox2lines(object)
#'
#' @param object spectra object t 
#' 
#' @examples 
#' sp=spc.example_spectra()
#' spc.bbox2lines(sp)
#' 
#' @rdname spc.bbox2lines
#' @export
setGeneric (name= "spc.bbox2lines",def=function(object){standardGeneric("spc.bbox2lines")})

#' @rdname spc.bbox2lines
setMethod("spc.bbox2lines",signature="Spatial",definition=function(object){
  bb = bbox(object)
  pt = bb[,1]
  pt = rbind(pt, c(bb[1,1],bb[2,2]))
  pt = rbind(pt, c(bb[1,2],bb[2,2]))
  pt = rbind(pt, c(bb[1,2],bb[2,1]))
  #				pt = rbind(pt, bb[,1])
  row.names(pt)<-NULL
  out = Lines(list(Line(pt[1:2,]),Line(pt[2:3,]),
                   Line(pt[3:4,]), Line(pt[c(4,1),])),ID="spc.bbox2lines")
  return(out)
})

#' @rdname spc.bbox2lines
setMethod("spc.bbox2lines",signature="STI",definition=function(object){
  return(callGeneric(object@sp))
})
#' @rdname spc.bbox2lines
setMethod("spc.bbox2lines",signature="Spectra",definition=function(object){
  return(callGeneric(object@sp))
})

#########################################################################
# Method : spc.invalid.detect
#########################################################################
#' Determinate invalid rows of a \code{Spectra} object
#' @description
#' Determine invalid rows (records) of a \code{Spectra} \code{SpcList} object
#'
#' @usage 
#' spc.invalid.detect(source1)
#' @return logical. TRUE for invalid rows
#' @param source1 A  \code{Spectra} object 
#' @examples 
#' sp=spc.example_spectra()
#' nrow(sp)
#' invalid=spc.invalid.detect(sp)
#' show(invalid); length(invalid)
#' 
#' BL = spc.makeSpcList(sp,"CAST")
#' invalid=spc.invalid.detect(BL)
#' show(invalid)
#' 
#' @rdname spc.invalid.detect
#' @export
setGeneric(name= "spc.invalid.detect",
           def=function(source1){standardGeneric("spc.invalid.detect")})

#' @rdname spc.invalid.detect
setMethod("spc.invalid.detect", signature = "Spectra", def=function(source1){
  out = apply(source1@Spectra, 2,is.na)
  if(is.null(dim(out))& nrow(source1@Spectra)==1)
    dim(out)<-c(1,ncol(source1@Spectra))
  out = apply(out,1,all)
})

#########################################################################
# Method : spc.getheader
#########################################################################
#' Extract a field of the @header slot of a \code{Spectra} object
#' @description
#' Extracts the value of a field in the header slot of \code{Spectra} object
#'
#' @usage 
#' spc.getheader(object,name)
#'
#' @seealso \code{\link{spc.setheader<-}}
#' 
#' @param object  A  \code{Spectra} object 
#' @param name of the header field to be extracted
#' 
#' @examples 
#' sp=spc.example_spectra()
#' sp@header
#' spc.getheader(sp,"Latitude")
#' @rdname spc.getheader
#' @export
setGeneric (name= "spc.getheader", def=function(object,name){standardGeneric("spc.getheader")})

#' @rdname spc.getheader
setMethod("spc.getheader", signature = "Spectra", def = function (object,name){
  if(missing(name)){
    out = object@header
  }else {
    if(is.null(object@header[[name]])){
      out = NA
    }else{
      out = object@header[[name]]
    }
    return(out)
  }
})
#########################################################################
# Method : spc.setheader
#########################################################################
#' Set a field of the @header slot of a \code{Spectra} object
#' @description
#' Function sets or changes the value of a field in the header slot of \code{Spectra} object
#'
#' @seealso \code{\link{spc.getheader}}
#' @param value Object of class SpcHeader
#' @param object A \code{Spectra} object 
#' @examples 
#' sp=spc.example_spectra()
#' a=new("SpcHeader") # create new SpcHeader class
#' a$Longitude=123 
#' spc.setheader(sp) <- a
#' sp@header
#' 
#' @rdname spc.setheader
#' @export
setGeneric (name="spc.setheader<-",def=function(object,value){standardGeneric("spc.setheader<-")})

#' @rdname spc.setheader
setReplaceMethod(f="spc.setheader", signature="Spectra",
                 definition=function(object,value){
                   stopifnot(class(value)=="SpcHeader")
                   object@header<-value 
                   validObject(object)
                   return(object)
                 })

#########################################################################
# Method : spc.updateheader
#########################################################################
#' Update a field of the @header slot of a \code{Spectra} object
#' @description
#'  Updates or changes the value of a field in the header slot of \code{Spectra} object 
#'
#' @usage spc.updateheader(object,Name,value,...)
#' @param object A \code{Spectra} object
#' @param Name of the header field to be updated
#' @param value to update header with
#' @param ... arguments to be passed to or from other methods
#' @examples 
#' sp=spc.example_spectra()
#' sp@header
#' sp <- spc.updateheader(sp,"Station", 11)
#' sp@header
#' 
#' #SpcList example
#' sp=spc.example_spectra()
#' BL=spc.makeSpcList(sp,"CAST")
#' BL[[1]]@header
#' BL[[1]] <- spc.updateheader(BL[[1]],"Station", 11)
#' BL[[1]]@header
#' 
#' @rdname spc.updateheader
#' 
#' @export
setGeneric(name="spc.updateheader",
            def=function(object,Name,value,...){standardGeneric("spc.updateheader")})

#' @rdname spc.updateheader
setMethod("spc.updateheader", signature="Spectra", definition=function(object,Name,value,...){
  hdr=spc.getheader(object)
  hdr[[Name]]=value
  spc.setheader(object)<-hdr
  validObject(object)
  return(object)
})

#########################################################################
# Method : spc.getselected.idx
#########################################################################
#' Extract index inside of a \code{Spectra} object
#' @description
#' Extracts index of rows marked as selected
#' 
#' @seealso \code{\link{spc.setselected.idx<-}}
#' 
#' @param object A \code{Spectra} object 
#' @return \code{Spectra} object
#' @examples 
#' x <- spc.example_spectra()
#' idx=rep(FALSE,nrow(x)); 
#' idx[1:5]=TRUE
#' spc.setselected.idx(x)<-idx 
#' spc.getselected.idx(x)
#' 
#' @rdname spc.getselected.idx
#' @export
setGeneric (name= "spc.getselected.idx", def=function(object){standardGeneric("spc.getselected.idx")})

#' @rdname spc.getselected.idx
setMethod("spc.getselected.idx", signature = "Spectra", def = function (object){
  return(object@SelectedIdx)
})

#########################################################################
# Method : spc.setselected.idx	
#########################################################################
#' Set index to a \code{Spectra} object
#' @description
#' Set or change selected  row index of a \code{Spectra} object 
#'
#' @param object A \code{Spectra} object 
#' @param value index for a \code{Spectra} object
#' @seealso \code{\link{spc.getselected.idx}}
#' @examples 
#' x <- spc.example_spectra()
#' idx=rep(FALSE,nrow(x)); 
#' idx[1:5]=TRUE
#' spc.setselected.idx(x)<-idx 
#' spc.plot(x)
#' @return A \code{Spectra} object
#' 
#' @rdname spc.setselected.idx
#' @export 
setGeneric("spc.setselected.idx<-",function(object,value){standardGeneric("spc.setselected.idx<-")})

#' @rdname spc.setselected.idx
setReplaceMethod(f="spc.setselected.idx", signature="Spectra",
                 definition=function(object,value){
                   if(is.numeric(value)){
                     idx = spc.getinvalid.idx(object)
                     if(length(idx)==0)
                       idx = rep(FALSE,nrow(object))
                     idx[value]=TRUE
                     value=idx
                   }
                   object@SelectedIdx<-value
                   validObject(object)
                   return (object)
                 })

#########################################################################
# Method : spc.getinvalid.idx
#########################################################################
#' Get index of \code{Spectra} rows marked as invalid
#' @description
#' Extract the row indexes stored as invalid 
#'
#' @usage 
#' spc.getinvalid.idx(object)
#'
#' @param object A \code{Spectra} object 
#' @return Logical vector 
#' @examples 
#' sp= spc.example_spectra()
#' spc.getinvalid.idx(sp) #No invalid rows
#' 
#' @rdname spc.getinvalid.idx
#' @export
setGeneric (name= "spc.getinvalid.idx",
            def=function(object){standardGeneric("spc.getinvalid.idx")})

#' @rdname spc.getinvalid.idx
setMethod("spc.getinvalid.idx", signature = "Spectra", def = function (object){
  return(object@InvalidIdx)
})
#########################################################################
# Method : spc.setinvalid.idx
#########################################################################
#' Set rows of \code{Spectra} as invalid
#' @description Stores the row indexes to be stored as invalid.
#'
#' @param object A \code{Spectra} object 
#' @param value Logical vector 
#' 
#' @seealso \code{\link{spc.setselected.idx<-}}
#' @examples  
#' sp = spc.example_spectra()
#' spc.getinvalid.idx(sp) #No invalid rows
#' vld = rep(TRUE,26)
#' vld[1:5]<-FALSE
#' spc.setinvalid.idx(sp)<-vld #Mark the first 5 rows as invalid
#' spc.getinvalid.idx(sp)
#'
#' @rdname spc.setinvalid.idx
#' @export
setGeneric("spc.setinvalid.idx<-",function(object,value){standardGeneric("spc.setinvalid.idx<-")})

#' @rdname spc.setinvalid.idx
setReplaceMethod(f="spc.setinvalid.idx", signature="Spectra", definition=function(object,value){
  if(is.numeric(value)){
    idx = spc.getinvalid.idx(object)
    if(length(idx)==0)
      idx = rep(FALSE,nrow(object))
    idx[value]=TRUE
    value=idx
  }
  object@InvalidIdx<-value
  validObject(object)
  return (object)
})

#########################################################################
# Method : spc.data2header
#########################################################################
#' Populate fields of header slot using data from data slot 
#' @description
#' Populates a field of @header with a column data from @data slot.
#'
#' @usage 
#' spc.data2header(object,dataname,headerfield,compress,...)
#'
#' @param dataname A character object specifying the name of @data column to be used.
#' @param object A \code{Spectra} object.
#' @param compress logical. Whether or not to compress data put into the header. 
#' See the description section.
#' @param headerfield A character object specifying the name of the @header field to be changed
#' @param ... arguments to be passed to or from other methods
#' @return object of class \code{Spectra}
#' @details 
#' This function extracts data from a column of the @data slot (specified by dataname)  
#' and creates a new @header field with it. Ifa header field is not provided, the name 
#' of the new header field will be the same as dataname. 
#' 
#' The name of the new header field can be overwritten by providing header field.
#' If all the incoming data rows (dataname) are the same, information put into the header 
#' can be compressed by selecting compress=TRUE (default is FALSE). This would take only 
#' the first element from the @data column.
#' 
#' @examples 
#' sp=spc.example_spectra()
#' sp=spc.data2header(sp,"CAST")
#' sp@header
#' sp=spc.data2header(sp,"CAST","ProjectCast")
#' sp@header
#' sp$CAST=rep(33, nrow(sp))
#' sp=spc.data2header(sp,"CAST","ProjectCast", compress=TRUE)
#' sp@header
#' 
#' @rdname spc.data2header
#' @export
setGeneric(name= "spc.data2header",
           def=function(object,dataname,headerfield,compress=FALSE,...){standardGeneric("spc.data2header")})

#' @rdname spc.data2header
setMethod("spc.data2header", signature = "Spectra", 
          def=function(object,dataname, headerfield,compress,...){
            if(missing(headerfield))
              headerfield = dataname
            object@header[[headerfield]]=object[[dataname]]
            if(compress )
              object@header[[headerfield]]=object[[dataname]][1]
            
            return(object)
          })

#########################################################################
# Method : spc.header2data
#########################################################################
#' Copy header data into the @data slot
#' @description
#' Get the header metadata and place it inside the @data slot
#'
#' @param object A \code{Spectra} object 
#' @param headerfield character. Field name of the header to be copied.
#' @param dataname character. Column name of @data slot to copy the incoming data.
#' @param compress logical. Whether or not to compress data put into the header. 
#' See help of \code{\link{spc.data2header}}.
#' @return object of class \code{Spectra} or \code{SpcList}
#' @param ... arguments to be passed to or from other methods
#' @details 
#' If header element has length >1, its type is checked. If it is "character",
#' its elements will be pasted using paste(...,collapse="|"). If it is another 
#' type, only the first element will be taken. For list and SpcList objects, the same 
#' procedure is repeated for all elements of the list containing \code{Spectra} objects.
#' If \code{dataname} is missing, then it will be taken equal to \code{headerfield}.
#' @examples 
#' sp <- spc.example_spectra()
#' sp <- spc.updateheader(sp,"Zone", "ZoneA")
#' sp <- spc.header2data(sp, "Zone")
#' sp$Zone
#' 
#' @rdname spc.header2data
#' @export
setGeneric(name= "spc.header2data",
           def=function(object,headerfield,dataname,compress,...){standardGeneric("spc.header2data")})

#' @rdname spc.header2data
setMethod("spc.header2data", signature = "Spectra", def=function(object,headerfield,dataname,compress=TRUE,...){
  if(missing(dataname))
    dataname = headerfield
  if (headerfield %in% names(object@header)){
    if(class(object@header[[headerfield]])=="character")
      object[[dataname]] = object@header[[headerfield]][1]
    else
      object[[dataname]] = paste(object@header[[headerfield]],collapse="|")
  }
  else
    stop(simpleError("Could not match a header field"))
  
  return(object)
  #			if(compress )
  #				object[[dataname]]=object@header[[headerfield]][1]
})

#########################################################################
# Method : [
#########################################################################
#' Extract or replace parts of a \code{Spectra} object
#' @description
#' Operators acting on \code{Spectra} object and \code{Spectra} lists to extract or replace parts.
#'
#' @details 
#' These operators are generic. You can write methods to handle indexing of specific classes of objects
#' 
#' @examples 
#' sp=spc.example_spectra()
#' sp #501 spectral channels in columns and 26 observations in rows 
#' sp[1] #returns Spectra object, 501 spectral channels in columns and 1 observations in rows
#' names(sp)
#' sp[["CAST"]] #returns the CAST data column
#' sp[[4]] #returns the CAST data column
#' sp[["CAST"]]=12 #Modify the CAST column
#' sp[["CAST"]] #returns the CAST data column
#' 
#' @rdname Spectra-Access
setMethod("[", signature(x = "Spectra"), function(x, i, j) {
  OUT_ANC = 0
  if(missing(i))
    i <-  1:nrow(x@Spectra)

  if(missing(j))
    j =  1:ncol(x@Spectra)
  
  if (class(j)=="numeric" | class(j)=="character"){
    if (class(j)=="numeric"){
      j.new = match(j,x@Wavelengths)
    }
    if (class(j)=="character"){
      if (!exists("j.new") & any(match(j, colnames(x@Spectra),nomatch=F))) {
        j.new = match(j, names(x))
      }
      if (!exists("j.new") & any(match(j, names(x@data),nomatch=F))) {
        OUT_ANC = 1
        j.new = match(j, names(x@data))						
      }
      if (!exists("j.new") && length(j)==1 && grepl("::",j)) {					
        #The requested input is in format lbd1::lbd2
        temp = strsplit(j, "::")
        mylower = as.numeric(temp[[1]][1])
        myupper = as.numeric(temp[[1]][2])					
        j.new = which(x@Wavelengths>=mylower & x@Wavelengths<=myupper)
      }
      if (!exists("j.new"))
        stop("Could not recognize the wavelength selection format. Use the operator :: or provide spectra or inDF data column indexes or names")
      
    }			
    if (all(is.na(j.new)))
      stop("Could not find matching wavelengths or inDF data")
    if (any(na.idx <-(is.na(j.new)))) {
      j.new=j.new[!is.na(j.new)]
      #					warning(paste("Could not match wavelengths or inDF data :", j[which(na.idx)]))
    }
    if (!all(is.finite(j.new)))
      stop("Could not find matching wavelengths or inDF data")
    j = j.new
  }
  InvalidIdx = x@InvalidIdx
  if (!OUT_ANC) {
    x@Spectra=x@Spectra[i,j,drop=F]
    if(nrow(x@data)>0)
      x@data=x@data[i,,drop=F]
    x@Wavelengths = x@Wavelengths[j]
  } else{
    x@data = x@data[i,j,drop=F]				
  }
  x@sp = x@sp[i]
  x@time = x@time[i,]
  x@endTime = x@endTime[i]
  
  if (length(x@InvalidIdx)>1)
    x@InvalidIdx = x@InvalidIdx[i] 
  
  x@SelectedIdx = logical()
  validObject(x)
  return(x)
})


#########################################################################
# Method : [[
#########################################################################
#' @rdname Spectra-Access
setMethod("[[", signature=c("Spectra","character","missing"), function(x, i, j) {
  Boutput = list()
  for (II in 1:length(i)){		
    if (i[II] %in% colnames(x@Spectra)){
      #					idx = which(i[II]==colnames(x@Spectra))
      Boutput[[II]] = x@Spectra[,i[II]]
      names(Boutput[[II]])<-i[II]
    }
    if (i[II] %in% names(x@data)){
      #					idx = which(i[II]==names(x@data))
      Boutput[[II]] = x@data[[i[II]]]				
      names(Boutput[[II]])<-i[II]
    }
  }
  if(length(Boutput)==0)
    stop("Could not match any Spectral or ancillary data columns")
  
  names(Boutput)<-i
  if(length(i)>1){
    Boutput = as.data.frame(Boutput)
    row.names(Boutput)<-NULL
  } else {
    Boutput = Boutput[[1]]
    names(Boutput)<-NULL				
  }
  validObject(Boutput)            
  return(Boutput)
})

#' @rdname Spectra-Access
setReplaceMethod("[[",  signature=c("Spectra","character","missing"), definition=function(x, i, j, value) {
  #			matched = 0
  if(class(value)=="data.frame")
    stop("The input variable 'value' cannot be a data.frame")
  if (i %in% colnames(x@Spectra))
    stop(simpleError("Matched a Spectra column. Use spc.add.channel() to add a spectral channel"))
  #			if (i %in% names(x@data)){
  #				matched = 1
  x@data[[i]] <- value				
  #			}
  #			if(!matched)
  #				stop("Could not match any Spectral or ancillary data columns")
  validObject(x)
  return(x)
})

#########################################################################
# Method : rep
#########################################################################
#' Replicate rows of \code{Spectra} object
#' @description
#' Operators 
#'
#' @param x A \code{Spectra} object whose rows are to be replicated.
#' @param times A integer vector giving the (non-negative) number of times to repeat each row.
#' See help of \code{\link{rep}}.
#' @param ... further arguments to be passed to or from other methods. 
#' See help of \code{\link{rep}}.
#'  
#' @details Replicates rows of \code{x}, making \code{times} copies of each row. 
#' Replicates \code{Spectra}, \code{data}, \code{sp}, \code{time}, \code{endTime}, 
#' \code{InvalidIdx} slots. Resets the \code{SelectedIdx} slot.
#' 
#' @return A \code{Spectra} object
#' @examples 
#' sp=spc.example_spectra()
#' dim(sp)
#' sp2 = rep(sp, 5)
#' dim(sp2)
#' 
#' @export
setMethod("rep", signature(x = "Spectra"), function(x, times, ...) {
  SP = sapply(1:ncol(x), function(y) rep(x@Spectra[,y], times))
  
  if(prod(dim(x@data))!=0){
    DT = as.data.frame(matrix(rep(matrix(NA,nrow(x@data),ncol(x@data)), times), ncol = ncol(x@data)))
    for (I in 1:ncol(DT))
      DT[,I] = rep(x@data[,I],times, ...)
    names(DT)<-names(x@data)
  }
  
  if (length(x@InvalidIdx)>1)
    x@InvalidIdx = rep(x@InvalidIdx,times)
  
  crds = matrix(rep(x@sp@coords,times),ncol=ncol(x@sp@coords),byrow=T)
  colnames(crds)<-c("LON","LAT")
  x@time = xts::xts(rep(x@time,times),rep(time(x@time),times))
  x@endTime = rep(x@endTime,times)
  x@sp@coords <- crds
  if(prod(dim(x@data))!=0)
    x@data = DT 
  x@Spectra = SP	
  x@InvalidIdx = rep(x@InvalidIdx, times)
  x@SelectedIdx = logical()
  validObject(x)
  return(x)
})

#########################################################################
# Method : spc.interp.spectral
#########################################################################
#'  Interpolate spectral values 
#' @description
#' Estimate spectral data at a new set of wavelengths through interpolation
#' using approx().
#'
#' @usage 
#' spc.interp.spectral(source1,target_lbd,show.plot, ...)
#' 
#' @param source1  A \code{Spectra} object 
#' @param  target_lbd numeric vector giving desired wavelengths  
#' @param show.plot logical TRUE if a graphical representation is required 
#' @param ... further arguments to pass on to approx(). 
#' @examples 
#' sp=spc.example_spectra()
#' lbd = as.numeric(c(412,440,490,555,670))
#' sp2 = spc.interp.spectral(sp[,lbd],c(430,450,500))
#' spc.plot.overlay(SpcList(list(sp,sp2)))
#' 
#' #Quick Plot only the first row
#' spc.interp.spectral(sp[,lbd],c(430,450,500),show.plot=TRUE)
#' 
#' @rdname spc.interp.spectral
#' @export
setGeneric (name= "spc.interp.spectral",
            def=function(source1,target_lbd,show.plot=FALSE,...){standardGeneric("spc.interp.spectral")})

#' @rdname spc.interp.spectral
setMethod("spc.interp.spectral", signature = "Spectra", def = function (source1,target_lbd,show.plot=FALSE){
  if(missing(target_lbd))
    stop("The input argument 'target_lbd' is missing")
  
  out = source1
  lbd_source1 = spc.getwavelengths(source1)
  DF = matrix(nrow=nrow(source1),ncol=length(target_lbd))
  my = list()
  for(x in 1:nrow(DF)) {
    my[[x]] = approx(lbd_source1, source1@Spectra[x,],xout=target_lbd,rule=2)
    DF[x,] = t(my[[x]]$y)
  }
  if(show.plot){
    plot(lbd_source1, source1@Spectra[1,],type="b",ylab=source1@LongName,xlab="Wavelength",pch="o")
    points(my[[x]]$x,my[[1]]$y,col="red",cex=1)
    grid(col="black")
  }
  out@Spectra = DF
  out@Wavelengths = target_lbd
  spc.colnames(out) <- spc.cname.construct(out)
  validObject(out)
  return(out)
})
#########################################################################
# Method : spc.export.text
#########################################################################
#' Exporting into text format
#' @description
#' Save the \code{Spectra} and \code{SpcHeader} objects on disk in text format and read back in.
#'
#' @seealso \code{\link{spc.import.text}}
#' @param input  A \code{Spectra} object 
#' @param  filename Name of the output text file
#' @param ... arguments to be passed to or from other methods
#' @param sep character. the field separator string
#' @param append logical. Only relevant if file is a character string. Default is  TRUE
#' @param writeheader either a logical value indicating whether the header names  are to be written        
#' @examples 
#' x=spc.example_spectra()
#' fn <- tempfile()
#' spc.export.text(x,filename=fn)
#' aa=spc.import.text(fn)
#' dev.new()
#' spc.plot(aa)
#' 
#' #Export the SpcHeader object
#' fn2 <- tempfile()
#' spc.export.text(x@header, filename=fn2)
#' hdr=spc.import.text(fn2)
#' class(hdr)
#' 
#' @rdname spc.export.text
#' @export
setGeneric(name="spc.export.text",
           def=function(input,filename,sep=";",append=FALSE,writeheader=TRUE, ...) {standardGeneric("spc.export.text")})

#' @rdname spc.export.text
setMethod("spc.export.text", signature="Spectra", definition=function(input,filename,sep,append,writeheader,...){
  data = as(input,"data.frame")
  idx.idx = which(colnames(data) == "idx")
  if(length(idx.idx)>0){
    data = data[,-idx.idx]
  }
  data = cbind(data.frame(idx=1:nrow(data)),data)
  clmnnames = colnames(data)
  data$TIME = as.character(data$TIME,usetz=TRUE)
  data$ENDTIME = as.character(data$ENDTIME,usetz=TRUE)
  
  written=0
  if(writeheader){
    spc.export.text(input@header,filename,append=F)
    written=length(input@header)
  }
  slotInfos = .spc.slot.infos(input,sep)
  for(I in 1:length(slotInfos)){
    if(length(slotInfos[[I]])==1)
      mysl=paste(names(slotInfos)[I],slotInfos[[I]],sep=sep)
    else
      mysl = paste(names(slotInfos)[I],paste(slotInfos[[I]],collapse=sep),sep=sep)
    if(written==0)
      write.table(mysl,filename,row.names=F,col.names=F,append=F,quote=F)
    else
      write.table(mysl,filename,row.names=F,col.names=F,append=T,quote=F)
    written = written+1
  }
  
  #Write column names
  write.table(paste(clmnnames,collapse=sep), filename, row.names=F, col.names=F,append=T, quote=F,eol="\n")
  #Write Spectra+Ancillary data
  write.table(data, filename, sep=sep, row.names=F, col.names=F,append=T,quote=F)
  print(paste("Wrote", filename ))			
})

.spc.slot.infos = function(input,sep){
  out=list('Spectra|ShortName'=input@ShortName,
           'Spectra|LongName'=input@LongName,
           'Spectra|Units'=input@Units,'Spectra|proj4string'=input@sp@proj4string@projargs,
           'Spectra|WavelengthsUnit'=input@WavelengthsUnit,
           'Spectra|Wavelengths'=spc.getwavelengths(input))
  return(out)
}

#' @rdname spc.export.text
setMethod("spc.export.text", signature="SpcHeader", definition=function(input,filename,sep=";",append=F,...){
  nms = names(input)
  nms = paste0("Spectra|header",sep,nms)

    out1 = lapply(1:length(input),function(x){
    myfield <- input[[x]]
    if(class(myfield) %in%  c("logical","numeric","character","factor")) {
      #If the separator character exists in the header, then eliminate it 
      if(class(myfield)=="character")
        myfield <-gsub(sep,"",input[[x]])
      
      #If vector (more than one value) then collapse it into one line
      if(length(myfield)>1)
        myfield<-paste(myfield,collapse=sep)
      #Convert it to character
      myfield<-as.character(myfield)
    } else {
      #If it is a complex type, then serialize it
      nms[[x]] <<- paste0(nms[[x]], "|Serialized")

      myfield = rawToChar(serialize(myfield,connection = NULL,ascii = T))
      myfield = gsub('\n','_a_',myfield)
      }
    myfield
  })
  out1 = cbind(nms,out1)
  write.table(out1,filename,row.names=F,col.names=F,append=append,quote=F,sep=sep)
})

#########################################################################
# Method : spc.import.text
#########################################################################
#' @rdname spc.export.text
#' @export
spc.import.text = function(filename,sep=";",...){
  myT = readLines(con=filename)
  
  #Extract the header
  header.idx = grep("Spectra\\|header",myT)
  if(length(header.idx)>0){
    hdr = strsplit(myT[header.idx],sep)
    
    nms = sapply(hdr,function(x)x[2])
    header = sapply(hdr,function(x){
      if(length(x)>2)
        x[3:length(x)]
      else
        ""
    })
    names(header)<- nms
    
    #Extract Serialized fields, if any and unserialized them
    header.idx.ser = grep("\\|Serialized",myT)
    header = spc.header.infos(header) 
    if (length(header.idx.ser)>0) {
      for (JJ in header.idx.ser){
        header[[JJ]] = unserialize(charToRaw(gsub('_a_','\n',header[[JJ]])))
        names(header)[JJ] <- gsub("\\|Serialized","",names(header)[JJ])
      }
    }
    
    if(any(grepl("StationType",nms)))
      if(is.logical(header$StationType))
        header$StationType = "T"
    header = as(header,"SpcHeader")
    myT = myT[-header.idx]
    
  } else {
    header = new("SpcHeader")
  }
  #Extract the Spectra slots
  Slots.idx = grep("Spectra\\|",myT)
  if(length(Slots.idx)>0){
    Slots = strsplit(myT[Slots.idx],sep)
    idx = grep("LongName",Slots)
    LongName = Slots[[idx]][2]
    idx = grep("ShortName",Slots)
    ShortName = Slots[[idx]][2]
    idx = grep("Units",Slots)
    Units= Slots[[idx]][2]
    idx = grep("proj4string",Slots)
    proj4string=Slots[[idx]][2]
    if(grepl("NA",proj4string))
      proj4string = NA
    idx = grep("WavelengthsUnit",Slots)
    WavelengthsUnit=Slots[[idx]][2]
    idx = which("Spectra|Wavelengths"==sapply(Slots,function(x)x[1]))
    try(Wavelengths<-as.numeric(Slots[[idx]][2:length(Slots[[idx]])]),silent=T)
    if(!exists("Wavelengths"))
      stop(simpleError("Could not find Wavelength information"))
    myT = myT[-Slots.idx]
    con = textConnection(myT)
    Spec = read.table(con,header=T,sep=sep)
    close(con)
    
    #Eliminate the first (idx) column
    idx = which(names(Spec)=="idx")
    if(length(idx)>0){
      Spec = Spec[,-idx]
    }
  #browser()
    Spec$TIME<-as.character(Spec$TIME)
    tz = strsplit(Spec$TIME[1]," ")[[1]][3]
    Spec$TIME<-as.POSIXct(strptime(Spec$TIME,"%Y-%m-%d %H:%M:%S",tz=tz))
    Spec$ENDTIME<-as.character(Spec$ENDTIME)
    Spec$ENDTIME<-as.POSIXct(strptime(Spec$TIME,"%Y-%m-%d %H:%M:%S",tz=tz))
    Spec = Spectra(Spec,ShortName=ShortName,Wavelengths=Wavelengths,Units=Units,
                   LongName=LongName,header=header)
  } else {
    Spec <- header
  }
  return(Spec)
}
##########################################
#spc.header.infos
#########################################
#' Getting as input the \code{Spectra} heade
#' @description
#' This internal function takes as input the \code{Spectral} header as a list and 
#' converts its elements to numbers (when possible)
#' evals its elements in case the text contains some R code
#' 
#' @usage 
#' spc.header.infos(header)
#' 
#' @param header A \code{Spectra} header
#' 
#' @examples 
#' sp=spc.example_spectra()
#' spc.header.infos(sp@header)
#' 
#' @export
#This internal function takes as input the Spectra header as a list and 
#1)converts its elements to numbers (when possible)
#2)evals its elements in case the text contains some R code
spc.header.infos = function(header){ 
  #Suppress warnings for the below operation (as.numeric creates warnings)
  myWarn = options()$warn
  options(warn=-1)
  header = lapply(header,function(x) {
    try(y<-as.numeric(x),silent=T)
    if(!is.na(y))
      x<-y
    return(x)
  })
  #	header = lapply(1:length(header),function(x) {
  ##				if(names(header)[x]=="Rsky750")
  #					try(y<-eval(parse(text=header[[x]])),silent=T)
  #				if(exists("y"))
  #					header[[x]]<-y
  #				return(header[[x]])
  #			})
  options(warn=myWarn)
  return(header)
}

#' Exports a \code{Spectra} object into Excel format.
#'
#' @description
#' Exports  a \code{Spectra} object into Excel format.
#' @param input  A \code{Spectra} object 
#' @param  filename Name of the output xlsx file
#' @param sheetName The \code{Spectra} object to be output.
#' @param writeheader A boolean, indicating whether or not the metadata (contents of the 
#' slot \code{header}) is to be included in the excel file. Default : TRUE
#' @param append A boolean, indicating whether or not to append the contents of the \code{Spectra} object
#' into the existing file. Default : FALSE (overwrites the existing Excel file if it exists.)
#' @param sep Not used.
#' @param ... Not used.
#' 
#' @details
#' \code{spc.export.xlsx()} calls functions from package \code{xlsx} to write the contents of 
#' a \code{Spectra} object into an Excel file. For this function to work, make sure the 
#' package \code{xlsx} is installed.
#' 
#' @return None. Simply creates an Excel file on disk.
#'
#' @examples
#' \dontrun{
#'   sp=spc.example_spectra()
#'   if("xlsx" %in% installed.packages())
#'      spc.export.xlsx(sp,"test.xlsx")
#' }
#' 
#' @rdname spc.export.xlsx
#' @export
setGeneric(name="spc.export.xlsx",
           def=function(input,filename,sheetName,writeheader=TRUE,append=F,sep=";",...) {standardGeneric("spc.export.xlsx")})

#' @rdname spc.export.xlsx
setMethod("spc.export.xlsx", signature="Spectra", definition=function(input,filename,sheetName,writeheader,append,sep,...){
  if (!requireNamespace("xlsx", quietly = TRUE)) {
    print("xlsx needed for this function to work. Please install it.")
  } else {
    if(missing(sheetName))
      sheetName = input@ShortName
    data = as(input,"data.frame")
    data$TIME = as.character(data$TIME,usetz=TRUE)
    data$ENDTIME = as.character(data$ENDTIME,usetz=TRUE)
    data = cbind(data.frame(idx=1:nrow(data)),data)
    
    slotInfos = .spc.slot.infos(input,sep)
    if(!append){
      #Create an empty excel workbook and start writing into it
      wb <- xlsx::createWorkbook()
    }else{
      #Create an empty excel workbook and start writing into it
      wb <- xlsx::loadWorkbook(file=filename)
    }
    sheet <- xlsx::createSheet(wb, sheetName=sheetName)
    if(writeheader){
      for(I in 1:length(input@header)){					
        if(length(input@header[[I]])==1)
          myH=cbind("Spectra|header",names(input@header)[I],input@header[[I]])
        else		
          myH = cbind("Spectra|header",names(input@header)[I],t(input@header[[I]]))
        xlsx::addDataFrame(myH, sheet,row.names=F,col.names=F,,startRow=I,startColumn=1)
      }
    }
    written = length(input@header)
    for(I in 1:length(slotInfos)){
      if(length(slotInfos[[I]])==1)
        mysl=cbind(names(slotInfos)[I],slotInfos[[I]])
      else
        mysl = cbind(names(slotInfos)[I],t(slotInfos[[I]]))
      xlsx::addDataFrame(mysl,sheet,row.names=F,col.names=F,startRow=written+1,startColumn=1)
      written = written+1
    }
    xlsx::addDataFrame(data, sheet,row.names=F,startRow=written+1,startColumn=1)
    xlsx::saveWorkbook(wb, filename)
    print(paste("Wrote sheet", sheetName, "to", filename))
  }
})

#########################################################################
# Method : subset
#########################################################################
#' Subsetting for a \code{Spectra} and spcList classes
#' @description
#' Subsetting can be achieved using the implementation of the R function subset() for \code{Spectra} and SpcList classes
#'It is possible to perform a row-wise selection
#'
#' @param drop passed on to [ indexing operator. Default is FALSE 
#' @param ... arguments to be passed to or from other methods.
#' @param x A \code{Spectra} object 
#' @param subset logical expression indicating elements or rows to keep: 
#' missing values are taken as false.
#' @param select Condition selected
#' @examples 
#' fnm = file.path(system.file(package = "geoSpectral"), "test_data","particulate_absorption.csv.gz")
#' abs = read.table(fnm,sep=",",header=TRUE)
#' abs$STATION=factor(abs$STATION)
#' abs[1:2,1:17] #Display only the first 2 rows and first 17 columns if the data frame
#' lbd = as.numeric(gsub("X","",colnames(abs)[14:514]))
#' Units="1/m"
#' colnames(abs)= gsub("X",paste("anap","_",sep=""), colnames(abs))
#' colnames(abs)= gsub("PRES","DEPTH", colnames(abs))
#' abs = abs[,c(14:514,1:13)]
#' tz<-strsplit(as.character(abs$TIME)," ")[[1]][[3]] #Extract the timezone
#' abs$TIME = as.POSIXct(as.character(abs$TIME),tz=tz) 
#' myS<-Spectra(abs,Wavelengths=lbd,Units=Units,ShortName="a_nap")
#' myS
#' head(spc.getwavelengths(myS))
#' spc.setwavelengths(myS) <- 300:800 
#' myS[1:10]
#' myS[,"anap_400"] 
#' myS[,c("anap_400","anap_500")] 
#' myS[1:10,30:50] #Selection of channels by column index
#' lbd = as.numeric(c(412,440,490,555,670))
#' myS[1:10,lbd] #Selection of channels by wavelength
#' myS[1:10,"415::450"] 
#' myS$CAST #Returns Ancillary data
#' myS$anap_400 #Returns spectra as numeric vector
#' head(myS[["anap_400"]]) #Returns spectra as numeric vector
#' head(myS[[c("Snap","Offset")]]) #Returns data.frame
#' #Subsetting rows with respect to the value of Ancillary data
#' subset(myS,DEPTH<=30)
#' #Subsetting rows with respect to the value of Spectral data
#' subset(myS,anap_440<=0.01)
#' #Selecting Ancillary data columns, leaving Spectral columns intact
#' subset(myS,subset=DEPTH<=30,select="CAST") 
#' 
#' @export
setMethod("subset",  signature="Spectra",definition=function(x, subset, select, drop = FALSE, ...) {
  if (missing(subset)) 
    mycall <- TRUE
  else {
    mycall <- substitute(subset)
    if(any(sapply(as.character(mycall),function(y) {y %in% colnames(x@Spectra)})))
      try(xidx <- eval(mycall, as.data.frame(x@Spectra), parent.frame()),silent=T)
    if(any(sapply(as.character(mycall),function(y) {y %in% names(x@data)})))
      try(xidx <- eval(mycall, x@data, parent.frame()),silent=T)		
    if (!exists("xidx") || !is.logical(xidx)) 
      simpleError(stop("'subset' must evaluate to logical"))				
    xidx <- xidx & !is.na(xidx)
    if (length(x@SelectedIdx)>0)
      x@SelectedIdx = x@SelectedIdx[xidx]
    if (length(x@InvalidIdx)>0)
      x@InvalidIdx = x@InvalidIdx[xidx]
    if (nrow(x@data)>0)
      x@data = x@data[xidx,,drop=drop]
    x@Spectra = x@Spectra[xidx,]
    x@sp = x@sp[xidx,]
    x@time= x@time[xidx,]
    x@endTime= x@endTime[xidx]
  }	
  
  if (missing(select)) 
    vars <- TRUE
  else {				
    nl <- as.list(1:ncol(x@Spectra))
    names(nl) <- colnames(x@Spectra)					
    vars <- eval(substitute(select), nl, parent.frame())				
    if(vars %in% colnames(x@Spectra)){
      y_idx = as.integer(nl[vars])
      
      x@Wavelengths = x@Wavelengths[y_idx]
      x@Spectra = x@Spectra[,y_idx,drop=F]
    }					
    
    nl <- as.list(1:ncol(x@data))
    names(nl) <- colnames(x@data)
    vars <- eval(substitute(select), nl, parent.frame())
    if(vars %in% colnames(x@data)){
      y_idx = as.integer(nl[vars])	
      x@data = x@data[,y_idx,drop=F]
    }
  }
  
  validObject(x)
  return(x)
})

#########################################################################
# Method : spc.select Select Spectra with the help of the mouse
#########################################################################
mat_identify <- function(x, y, ...){
  l <- locator(1)
  if(all(x <= l$x) || all(x >= l$x)){
    result=NULL
  } else {
    index <- max(which(x <= l$x))
    f <- (l$x - x[index]) / diff(x[index+(0:1)])
    
    yi <- f * (y[index+1,] - y[index,] ) + y[index,]
    result <- which.min(abs(yi-l$y))
    lines(x, y[,result], lwd=2, col="red")
  }
  #  text(l, label=colnames(y)[result])
  return(result)
}

#########################################################################
# spc.select
#########################################################################
#' Selecting rows of a \code{Spectra} object with the mouse
#' @description
#' This function allows the selection of \code{Spectra} rows that is drawn 
#' with spc.plot or spc.lines. Selected lines will be colored red. Pressing
#' the escape button will end the selection process and return selecion results.
#' @param object A \code{Spectra} object
#' @return logical Row indexes, TRUE for selected data rows.
#' @examples 
#' sp <- spc.example_spectra()
#' spc.plot(sp)
#' spc.setselected.idx(sp)<-spc.select(sp)
#' 
#' @seealso \code{\link{spc.plot}} \code{\link{spc.lines}}
#' @rdname spc.select
#' @export
setGeneric (name= "spc.select",def=function(object){standardGeneric("spc.select")})

#' @rdname spc.select
setMethod("spc.select", signature = "Spectra", 
          def = function (object){
            print("Click on graph to select Spectra, click Esc to quit ")
            
             #Extract the existing selection Index
            if(length(object@SelectedIdx)>0)
              ExSel = object@SelectedIdx
            else
              ExSel = rep(FALSE, nrow(object@Spectra))			
            Sel = rep(FALSE, nrow(object@Spectra))			
            
            lbd = spc.getwavelengths(object)
            idx = mat_identify(lbd, t(object@Spectra))
            print(paste("Selected row",idx))
            oidx = idx
            while(!is.null(idx)){
              idx = mat_identify(lbd, t(object@Spectra))
              print(paste("Selected row",idx))
              oidx=c(oidx, idx)				
            }
            oidx = oidx[!is.null(oidx)]
            Sel[oidx]=T
            
            TrueIdx = isTRUE(ExSel)
            #Apply the XOR operator to Existing Index ExSel 
            #(selecting again an already selected row unselcts it)
            ExSel = xor(ExSel,Sel)
            
            #Update the slot SelectedIdx 
            #			object@SelectedIdx = ExSel
            
            #print(cbind(Sel, ExSel))
            return(ExSel)
          })

#########################################################################
# spc.makeSpcList
#########################################################################
#'  Conversion from \code{Spectra} to \code{Spclist}
#' @description
#'Conversion from \code{Spectra} to \code{Spclist} using a data field
#' @usage 
#'  spc.makeSpcList(myobj, name)
#' @param myobj a \code{Spectra} object
#' @param name  name of station of a \code{Spectra} object
#' @examples 
#' sp <- spc.example_spectra()
#' BL = spc.makeSpcList(sp,"CAST")
#' show(BL)
#' 
#' @export
#Method : Conversion from Spectra to SpcList using a data field (factor)
#Later add the functionality with FUN (i.e. taking means)
spc.makeSpcList = function(myobj, name){
  if(length(name)!=1)
    simpleError(stop("Argument 'name' should have a length of 1"))
  #Get the indexes of each DF row :
  idx = lapply(unique(myobj[[name]]),function(x) {which(x==myobj[[name]])})
  #For each row index in the list, subset the DF, return a list
  output = lapply(idx,function(x) {
    out = myobj[x,]
    out <- spc.updateheader(out, name, as.character(out[[name]][1]))
    out
  })
  output = SpcList(output)
  output@by = name
  return(output)
}

#########################################################################
# Method : spc.plot.time
#########################################################################
#'  Plotting \code{Spectra} object
#'
#' @description
#' Generating plot of the contents of a \code{Spectra} object with respect to time.
#' If xdata is 'time', data is plotted with respect to the 'TIME' column. If xdata 
#' is 'observations', data is plotted with respect to an integer index equal to 1:nrow(object).
#'
#' @param object A \code{Spectra} object.
#' @param Y character. Name of the columns of the \code{Spectra} object to be plotted.
#' @param maxSp numeric. Maximum number of \code{Spectra} to plot.
#' @param xdata character. Type of time-series data. Can be 'time' or 'observations'.
#' @param lab_cex vector of character expansion sizes, used cyclically.
#' @param lwd vector of line widths
#' @param ... any further arguments of plot
#' @seealso \code{\link{spc.plot.depth}}
#' @examples
#' x <- spc.example_spectra()
#' spc.plot.time(x)
#' 
#' @rdname spc.plot.time
#' @export
setGeneric(name= "spc.plot.time",def=function(object, ...){standardGeneric("spc.plot.time")})

#' @rdname spc.plot.time
setMethod("spc.plot.time", signature="Spectra", function (object,Y,maxSp=50,xdata="time",lab_cex,lwd=2, ...){
  stopifnot((xdata=="time") | (xdata == "observations"))
  idx = as(1:ncol(object@data), "logical")
  
  if (length(object@InvalidIdx)==0)
    object@InvalidIdx = rep(FALSE,nrow(object@data))		

  if(missing(Y)){
    Y = spc.colnames(object)
  }
  if(ncol(object)>maxSp)
    Y = Y[seq(1,ncol(object),length.out=maxSp)]
  
  tsdata = object[[Y]] #[!object@InvalidIdx,]
  
  if(missing(lab_cex))
    lab_cex = 1
  
  tsCol = rainbow(ncol(tsdata))
  
  if(xdata=="time") {
    x = time(object)
    x = x[!object@InvalidIdx]
    xlb = "Time"
    XX = xts::xts(tsdata,time(object@time))
    plot.new()
    #xts::plot.xts(XX,screens=1) #,xlab="",ylab="",lwd=lwd,col=tsCol, ...)
    #xtsExtra::plot.xts(XX,screens=1, xlab="",ylab="",lwd=lwd,col=tsCol, ...)#Problem: does not plot inside the function
    zoo::plot.zoo(XX,screens=1,xlab="",ylab="",lwd=lwd,col=tsCol, ...)
  }
  if (xdata == "observations") {
    x = 1:nrow(object)
    xlb = "Observation number"
    x = x[!object@InvalidIdx]
    matplot(x,tsdata, type="l", pch=19,cex=0.3,xlab="",ylab="",lwd=lwd,col=tsCol,...)        
  }
  
  # 			df$Date <- as.Date( df$Date, '%m/%d/%Y')
  # 			require(ggplot2)
  # 			ggplot( data = df, aes( Date, Visits )) + geom_line() 
  
  grid(col="black")
  
  #Draw the legend
  if(length(Y)>1&length(Y)<=10) {
    legend("bottomright",Y,col=1:length(Y),fill=1:length(Y),bty="n",cex=lab_cex)
    ylb = bquote(.(object@LongName[1])*", ["*.(object@Units[1])*"]")	
  }
  else{
    if(length(Y)==1)
      ylb = Y
    else
      if(object@LongName=="spvar2 longname")
        ylb = bquote(.(object@ShortName)*", ["*.(object@Units[1])*"]")  
      else
        ylb = bquote(.(object@LongName[1])*", ["*.(object@Units[1])*"]")  
  }
  mtext(xlb,side=1,line=2,cex=lab_cex)
  mtext(ylb,side=2,line=2,cex=lab_cex)
  #Draw the legend
  if(length(Y)>1 & length(Y)<=10)
    legend("bottomright",Y,col=1:length(Y),lty=1:length(Y),bty="n",lwd=2,cex=lab_cex)
})

#########################################################################
# Method : spc.plot.depth
#########################################################################
#'  Plotting \code{Spectra} object
#'
#' @description
#' Generating plot of the contents of a \code{Spectra} object with respect to depth
#' 
#' @param object a \code{Spectra} data.
#' @param X character. Column names of the a \code{Spectra} object to be plotted.
#' @param maxSp numeric. Maximum number of \code{Spectra} to plot.
#' @param lab_cex vector of character expansion sizes, used cyclically.
#' @param title a chracter string, title for plot
#' @param add logical. If TRUE, plots are added to current one,
#' @param xlab,ylab titles for x and y axes, as in plot.
#' @param ylim,xlim ranges of x and y axes, as in plot.
#' @param lwd numeric vector of line widths
#' @param ... any further arguments of plot
#' @seealso \code{\link{spc.plot}}
#' @examples
#' x <- spc.example_spectra()
#' spc.plot.depth(x)
#' 
#' @rdname spc.plot.depth
#' @export
setGeneric (name= "spc.plot.depth",def=function(object, ...){standardGeneric("spc.plot.depth")})

#' @rdname spc.plot.depth
setMethod("spc.plot.depth", signature="Spectra", function (object,X,maxSp=10,lab_cex, title, add=FALSE, xlab=NULL, ylab=NULL, ylim=NULL,xlim=NULL,lwd=2,...){
  idx = as(1:ncol(object@Spectra), "logical")
  depth=object$DEPTH
  if(length(is.finite(depth))<1)
    stop("Could not find the column DEPTH")
  
  if (length(object@InvalidIdx)==0)
    object@InvalidIdx = rep(FALSE,nrow(object))		
  
  if(missing(X)){
    if(ncol(object)>maxSp)
      X = round(seq(1,ncol(object),length.out=maxSp))
    else
      X = names(object)
  }
  if (is.numeric(X))
    X = names(object)[X]
  
  if(missing(ylab))
    ylab = "Depth [m]"
  
  if(missing(xlab)) {
    if (class(object)=="Spectra") {
      if(object@LongName=="spvar2 longname")
        xlab = bquote(.(object@ShortName)*", ["*.(object@Units[1])*"]")  
      else
        xlab = bquote(.(object@LongName[1])*", ["*.(object@Units[1])*"]")          
    } else {
      if(length(X)==1)
        xlab =  bquote(.(X)*", ["*.(object@Units[1])*"]")	
    }
  }
  if(missing(ylim)){
    #ylim = rev(range(pretty(depth[!object@InvalidIdx],n=10)))
    ylim = rev(range(pretty(depth,n=10)))
    ylim[2]=-0.1	
  }
  #If any, do not draw these parameters
  X = gsub("DEPTH","",X,fixed=T)
  X = gsub("VOLTAGE","",X,fixed=T)
  X = gsub("TIME","",X,fixed=T)
  X=X[X!=""]
  
  mynames = spc.colnames(object)[match(X,names(object))]
  u_units = object@Units 
  colidx = match(X,spc.colnames(object))
  if(any(is.na(colidx)))
    u_units = c(u_units, "unknown") #For now, we can only add unknown units XXX
  my_sides = rep(c(1,3), ceiling(length(u_units)/2))
  
  #Extract the data to plot
  #			myX = object[!object@InvalidIdx,X,drop=F]
  #			myY = depth[!object@InvalidIdx]
  myX = object[[X]]
  myY = depth
  #Sort with respect to depth
  d_idx = sort.int(myY,index.return = TRUE)
  myY = d_idx$x
  
  if(class(myX)=="data.frame"){
    myX = myX[d_idx$ix,,drop=F]
    #Eliminate rows full with zeros
    idx = which(!apply(myX==0,1,all))
    myY = myY[idx]
    myX = myX[idx,,drop=F]
    #Eliminate NAs in depth
    idx = !is.na(myY)
    myY = myY[idx]
    myX = myX[idx,,drop=F]
  }
  else{
    myX = myX[d_idx$ix]
    #Eliminate rows full with zeros
    idx = myX!=0
    myY = myY[idx]
    myX = myX[idx]
    #Eliminate NAs in depth
    idx = !is.na(myY)
    myY = myY[idx]
    myX = myX[idx]
  }
  
  if(missing(lab_cex))
    lab_cex=1
  
  depth_diff = TRUE
  if(length(myY)>1)
    depth_diff = !all(diff(myY)==0) 
  
  mytype = "l"
  if(length(myY)==1)
    mytype = "p"
  
  if (depth_diff & !(length(myY)<1)) {
    if(length(u_units)==1){	
      #All columns to be plotted have the same unit 
      if(add)
        matlines(myX,myY,type=mytype,xlab="",ylab="",ylim=ylim,...)
      else{
        if (all(is.finite(xlim)))
          matplot(myX,myY,type=mytype,pch=19,cex.axis=lab_cex,xlab="",ylab="",ylim=ylim,xlim=xlim,lwd=lwd,...)
        else
          matplot(myX,myY,type=mytype,pch=19,cex.axis=lab_cex,xlab="",ylab="",ylim=ylim,lwd=lwd,...)						
      }
      matpoints(myX,myY,xlab="",ylab="",pch=19,cex=0.4,ylim=ylim,...)					
      
      mtext(ylab,side=2,line=2,cex=lab_cex)
      mtext(xlab,side=1,line=2,cex=lab_cex)
      grid(col="black")		
      #Draw the legend
      if(length(X)>1 & !add & length(X)<=10)
        legend("bottomright",X,col=1:length(X),lty=1:length(X),bty="n",lwd=2,cex=lab_cex)
      
    }else{
      #All columns to be plotted have different units 
      #					for (I in 1:length(u_units)){
      for (I in 1:2){
        if (I!=1)
          par(new=T)
        #						col_idx = which(u_units[I]==myunits)
        xlab = bquote(.(object@LongName[1])*", ["*.(object@Units[1])*"]")
        
        matplot(object[[X[[I]]]],myY,type="l", axes=F,pch=19,cex=0.3, ylim=ylim,col=I,xlab="",ylab="",lwd=lwd,...)
        mtext(my_sides[I],text=xlab,line=2,cex=lab_cex)
        axis(my_sides[I], col=I, pretty(range(object[[X[[I]]]]),10))
        if (I==1){
          box(); 	
          cols = rep(1, length(X))
        }
        else {
          #							cols[col_idx] = I
          cols[I] = I
        }
      }
      grid(col="black")
      axis(2,pretty(range(myY),10))
      mtext(2, text=ylab,line=2)
      #Draw the legend
      if(length(X)>1 & !add & length(X)<=10)
        legend("bottomright",X,col=cols,lty=cols,bty="n",lwd=2,cex=lab_cex)
      
    }
    #Draw the title if provided from the call
    if(!missing(title))
      title(title)
  } else{
    return(0)
  }
})
#################################################
#spc.example_spectra
################################################
#' Create example of Spectral object 
#' @description
#' Example of Spectral object is created by the function
#'
#' 
#' @usage 
#' spc.example_spectra()
#' @examples 
#' sp = spc.example_spectra()
#' class(sp)
#' show(sp)
#' 
#' @export
spc.example_spectra <- function(){
  #Search in the package installation directory
  fnm = file.path(base::system.file(package = "geoSpectral"),"test_data","particulate_absorption.csv.gz")
  #If the previous search fails, search the file in the source code directory
  if(!file.exists(fnm))
    fnm = file.path(base::system.file(package = "geoSpectral"),"inst","test_data","particulate_absorption.csv.gz")
  
  abs = read.table(fnm,sep=",",header=TRUE)
  abs$STATION=factor(abs$STATION)
  abs[1:2,1:17] #Display only the first 2 rows and first 17 columns if the data frame
  lbd = as.numeric(gsub("X","",colnames(abs)[14:514]))
  Units="1/m"
  colnames(abs)= gsub("X",paste("anap","_",sep=""), colnames(abs))
  colnames(abs)= gsub("PRES","DEPTH", colnames(abs))
  abs = abs[,c(14:514,1:13)] #Rearrange so that Spectra columns come first
  tz<-strsplit(as.character(abs$TIME)," ")[[1]][[3]] #Extract the timezone
  abs$TIME = as.POSIXct(as.character(abs$TIME),tz=tz) #Compute the time
  
  #Space and time columns are automatically found in the column names of inDF
  myS<-Spectra(abs,Wavelengths=lbd,Units=Units,ShortName="a_nap",
               LongName="Absorption coefficient by non-algal particles")
  myS
}

#' Plot a Spectra object data 
#' @description
#' Plot a \code{Spectra} object with plotly engine 
#' @param sp A \code{Spectra} object
#' @param plot.max numeric value for a maximum number of data in plot. Default is 10.
#' @param showlegend logical, to display legend or not, default is FALSE 
#' @param legend_field character. Gives the name of the column to be used in the legend.
#' @param hoverinfo a chracter, info about  \code{Spectra} object to be used  in hover box.
#' @param title a chracter string, title for plot.
#' @examples 
#'sp = spc.example_spectra()
#'spc.plot.plotly(sp)
#'spc.plot.plotly(sp,legend_field = "Spectra")
#'spc.plot.plotly(sp,legend_field = "CAST")
#'spc.plot.plotly(sp,legend_field = "NISKIN")
#'spc.plot.plotly(sp,legend_field = "STATION")
#'spc.plot.plotly(sp,legend_field = "anap_440")
#' 
#' @rdname spc.plot.plotly
#' @export
setGeneric (name= "spc.plot.plotly",
            def=function(sp, plot.max=10,showlegend=FALSE,legend_field="row",hoverinfo="title",title=sp@LongName){standardGeneric("spc.plot.plotly")})
#' @rdname spc.plot.plotly
setMethod("spc.plot.plotly", signature="Spectra", function (sp, plot.max=10,showlegend = FALSE,legend_field,hoverinfo,title) {
  #library(reshape2)
  # lbd = spc.getwavelengths(sp)
  # kk = data.frame(Wavelength=lbd,t(sp@Spectra))
  # kk=melt(kk,id.vars=1)
  # p <- plotly::plot_ly(kk, x=~Wavelength, y=~value, type="scatter", mode="lines",color = ~variable,
  #              colors="Spectral", opacity=0.5, line=list(width = 1)) #,evaluate = FALSE) #, colors=pal,line = list(opacity=0.1))
  # require(plotly)
  if (plot.max > nrow(sp))
    plot.max = nrow(sp)

  idx = floor(seq(1, nrow(sp), length.out = plot.max))
  if (legend_field %in% names(sp)) {
    legend_field = paste(legend_field, sp[[legend_field]])
  }
  else
    legend_field = paste(legend_field, 1:nrow(sp))
  
  ylab = paste(sp@ShortName, " [", sp@Units, "]", sep="")
  xlab = paste("Wavelength [", sp@WavelengthsUnit, "]", sep="")
  p <- plotly::plot_ly()
  for(I in 1:length(idx)) {  
    p <- plotly::add_trace(p, x=sp@Wavelengths, y=sp@Spectra[idx[I],],type = "scatter", mode="lines",
                   name=legend_field[idx[I]], hoverinfo=hoverinfo
                   #,marker=list(color=line[['color']])
                   )
  }
  p = plotly::layout(p,
             title = title,
             hovermode = "closest",
             xaxis = list(title = xlab), #rangeslider = list(type = "linear")),
             yaxis = list(title = ylab),
             showlegend=showlegend
             )
  p
})

#' Plot a Spectra object data with respect to time
#' @description
#' Plot a \code{Spectra} object with respect to time
#' @param sp A \code{Spectra} object
#' @param column Number or name , defoult value is 10 if a number or name has not been entered
#' @param plot.max numeric value for a maximum number of data in plot
#' @param showlegend logical, to display legend or not, default is FALSE 
#' @param hoverinfo  a chracter, info about  \code{Spectra} object to be used  in hover box
#' @param title a chracter string, title for plot
#' @examples 
#' \dontrun{
#' sp = spc.example_spectra()
#' spc.plot.time.plotly(sp)
#' spc.plot.time.plotly(sp, plot.max = 3)
#' spc.plot.time.plotly(sp, c("anap_450","anap_550","anap_650"))
#' }
#' 
#' @rdname spc.plot.time.plotly
#' @export
setGeneric (name= "spc.plot.time.plotly",
            def=function(sp, column, plot.max=10,showlegend=FALSE,hoverinfo="name",title=sp@LongName){standardGeneric("spc.plot.time.plotly")})

#' @rdname spc.plot.time.plotly
setMethod("spc.plot.time.plotly", signature="Spectra", function (sp, column, plot.max=10,showlegend,hoverinfo,title) {
  if(missing("column")){
    if(ncol(sp)<10)
      idx = 1:ncol(sp)
    else
      idx = round(seq(1, ncol(sp), length.out = plot.max))
    
    column = colnames(sp@Spectra)[idx]
  }
  ylab = paste(sp@ShortName, " [", sp@Units, "]", sep="")
  myTime = time(sp@time)
  
  p=plotly::plot_ly(x = myTime , y = sp[[column[1]]], type="scatter", mode = "lines + markers",name=column[1])
  if(length(column)>1)
    for(I in 2:length(column))
      p=plotly::add_trace(p, x = myTime , y = sp[[column[I]]], 
                  type="scatter", mode = "lines + markers", 
                  name=column[I], hoverinfo=hoverinfo) 
  p = plotly::layout(p,
             title = title,
             hovermode = "closest",
             xaxis = list(title = "Time",
                          rangeslider = list(type = "date")),
             yaxis = list(title = ylab),
             showlegend=showlegend
             )
  p
})

#########################################################################
#spc.plot.depth.plotly
#########################################################################
#' Display a Spectra object
#' @description
#' Plot a \code{Spectra} object with respect to depth
#' @examples 
#' sp = spc.example_spectra()
#' BL = spc.makeSpcList(sp,"CAST")
#' p1<-spc.plot.depth.plotly(BL[[5]])
#' #p1<-layout(p1,title=paste("CAST =", BL[[5]]$CAST[1]))
#' p2<-spc.plot.depth.plotly(BL[[4]])
#' #p2<-plotly::layout(p2,title=paste("CAST =", BL[[4]]$CAST[1]))
#' p <- plotly::subplot(p1, p2,  margin = 0.05, shareY=TRUE,shareX=TRUE,titleX=TRUE,titleY=TRUE)
#' p <- plotly::layout(p, showlegend = TRUE, 
#' annotations = list(
#' list(x = 0.2 , y = 1.05, text = BL[[5]]$CAST[1], showarrow = FALSE, xref='paper', yref='paper'),
#' list(x = 0.8 , y = 1.05, text = BL[[4]]$CAST[1], showarrow = FALSE, xref='paper', yref='paper')))
#' p
#' @param sp A \code{Spectra} object
#' @param column Number or name , default  value is 10 if a number or name has not been entered
#' @param plot.max numeric value for a maximum number of data in plot
#' @param showlegend logical, to display legend or not, default is FALSE 
#' @param hoverinfo  a chracter, info about  \code{Spectra} object to be used  in hover box
#' @param title a chracter string, title for plot
#' @rdname spc.plot.depth.plotly
#' @export
setGeneric (name= "spc.plot.depth.plotly",
            def=function(sp, column, plot.max=10,showlegend=FALSE,hoverinfo="name",title=sp@LongName){standardGeneric("spc.plot.depth.plotly")})

#' @rdname spc.plot.depth.plotly
setMethod("spc.plot.depth.plotly", signature="Spectra", function (sp, column, plot.max=10,showlegend,hoverinfo,title) {
  if(missing("column")){
    if(ncol(sp)<10)
      idx = 1:ncol(sp)
    else
      idx = round(seq(1,ncol(sp), length.out = plot.max))
    
    column = colnames(sp@Spectra)[idx]
  }
  xlab = paste(sp@ShortName, " [", sp@Units, "]", sep="")
  
  p=plotly::plot_ly(x = sp[[column[1]]] , y = sp$DEPTH, type="scatter", mode = "lines + markers",name=column[1])
  if(length(column)>1)
    for(I in 2:length(column))
      p=plotly::add_trace(p, x = sp[[column[I]]] , y =sp$DEPTH, type="scatter", mode = "lines + markers", 
                  name=column[I], hoverinfo=hoverinfo) 
  # layout(yaxis = list(autorange = "reversed"))
  p = plotly::layout(p,
             title = title,
             hovermode = "closest",
             xaxis = list(title = xlab),
             yaxis = list(title = "Depth [ m ]", 
                          rangeslider = list(type = "linear"),
                          autorange = "reversed"),
             showlegend=showlegend
             )
  p 
})

#########################################################################
#spc.plot.map.plotly
#########################################################################
#' Display a Spectra object
#' @description
#' Create a point map with ploty engine using \code{Spectra} rows 
#' @examples 
#' sp <- spc.example_spectra()
#' spc.plot.map.plotly(sp)
#' 
#' @param sp A \code{Spectra} object
#' @param hover_field A character, column  names of sp object to be used  in hover box
#' @param opacity The opacity transparency of the glyph 
#' between 0 (transparent) and 1 (opaque)
#' @param color Determine color of points
#' 
#' @rdname spc.plot.map.plotly
#' @export
setGeneric (name= "spc.plot.map.plotly",
            def=function(sp,hover_field="row", color="#FF0000", opacity=1){standardGeneric("spc.plot.map.plotly")})

#' @rdname spc.plot.map.plotly
setMethod("spc.plot.map.plotly", signature="Spectra", function (sp, hover_field, color, opacity) {
  # require(plotly)
  bbx = sp@sp@bbox
  bbx[,2] =  bbx[,2] + (0.04 * abs(bbx[,2]))
  if(bbx[2,2]>90)
    bbx[2,2]<-89
  bbx[,1] =  bbx[,1] - (0.04 * abs(bbx[,1]))
  if(bbx[2,1]< -90)
    bbx[2,1]<- -89
  
  g <- list(
    #scope = 'north america',
    showland = TRUE,
    landcolor = plotly::toRGB("grey83"),
    subunitcolor = plotly::toRGB("white"),
    countrycolor = plotly::toRGB("white"),
    showlakes = TRUE,
    lakecolor = plotly::toRGB("blue"),
    showrivers = TRUE,
    showsubunits = TRUE,
    showcountries = TRUE,
    resolution = 50,
    projection = list(
      type = 'conic conformal',
      rotation = list(
        lon = -100
      )
    ),
    lonaxis = list(
      showgrid = TRUE,
      gridwidth = 0.5,
      range = c(bbx[1,1], bbx[1,2]),
      dtick = 5
    ),
    lataxis = list(
      showgrid = TRUE,
      gridwidth = 0.5,
      range = c(bbx[2,1],bbx[2,2]),
      dtick = 5
    )
  )
    
  if(length(color==1))
    color = rep(color, nrow(sp))
  p <- plotly::plot_ly(lat = sp@sp@coords[,"LAT"], lon = sp@sp@coords[,"LON"], 
               #text = hover, color = Globvalue,marker = m
               type = 'scattergeo', color=color, opacity=opacity
  ) 
  p <- plotly::layout(p, geo = g, showlegend=FALSE)
  p
})

###########################################################
# spc.plot.map.leaflet
###########################################################
#' Display a Spectra object
#' @description
#' Create a point map with leaflet engine using \code{Spectra} rows 
#' @param sp \code{Spectra} object
#' @param color Determine color of points
#' @param hover_field A character  or vector of strings giving column 
#' names of \code{Spectra} object. This information will be displayed when 
#' hovering over the glyph
#' @param opacity The opacity transparency of the glyph 
#' between 0 (transparent) and 1 (opaque)
#' @param weight Stroke width in pixels
#' @examples 
#' sp=spc.example_spectra()
#' spc.plot.map.leaflet(sp)
#' 
#' @rdname spc.plot.map.leaflet
#' @export
 setGeneric (name= "spc.plot.map.leaflet",
            def=function(sp,hover_field = "row",color = "#FF0000",opacity = 1,  weight=5){standardGeneric("spc.plot.map.leaflet")})
 
#' @rdname spc.plot.map.leaflet
setMethod("spc.plot.map.leaflet", signature="Spectra", function (sp,hover_field = "row",color = "#FF0000",opacity = 1,  weight=5) {

   hover_field = paste0(hover_field, 1:nrow(sp))
  
  m = leaflet() %>% 
    addCircles(lng = sp@sp@coords[,"LON"], lat=sp@sp@coords[,"LAT"], 
               opacity=opacity,color=color, weight=5) %>% 
    addTiles()# %>%
  #addPopups(lng = sp@sp@coords[,"LON"], lat=sp@sp@coords[,"LAT"], 
  # popup = legend_field  )
  #addLegend(pal = qpal, values = , opacity = 1)
  m
  

 })

###########################################################
# spc.plot.map.rbokeh
###########################################################
#' Display a Spectra object
#' @description
#' Create a point map with rbokeh engine using \code{Spectra} rows 
#' @param sp \code{Spectra} object
#' @param color Determine color of points
#' @param legend not implemented  yet
#' @param hover String or vector of strings giving column 
#' names of \code{Spectra} object. This information will be displayed when 
#' hovering over the glyph
#' @param opacity The opacity transparency of the glyph 
#' between 0 (transparent) and 1 (opaque)
#' @param glyph Value(s) or field name of the glyph to
#'  use \code{\link{point_types}}
#' @examples 
#' \dontrun{
#'   sp=spc.example_spectra()
#'   spc.plot.map.rbokeh(sp, hover = "Snap")
#'   spc.plot.map.rbokeh(sp)
#' }
#' 
#' @rdname spc.plot.map.rbokeh
#' @export
setGeneric (name= "spc.plot.map.rbokeh",
            def=function(sp,glyph = 2,color = "#FF0000", legend=NULL,hover="row",opacity =1){standardGeneric("spc.plot.map.rbokeh")})

#' @rdname spc.plot.map.rbokeh
setMethod("spc.plot.map.rbokeh", signature="Spectra", function (sp,glyph,color, legend,hover,opacity ) {
  # require(rbokeh)
  # require(maps)
  #a=sp$Snap
  df = data.frame(LON = sp@sp@coords[,"LON"])
  df$LAT = sp@sp@coords[,"LAT"]
  df$color = color
  df$opacity  = opacity
  df$row = 1:nrow(sp)
  for (I in 1:length(hover))
  if (hover[I] %in% names(sp))
    df[hover[I]] = sp[[hover[I]]]
 
   bbx = sp@sp@bbox
  bbx[,2] =  bbx[,2] + (0.04 * abs(bbx[,2]))
  if(bbx[2,2]>90)
    bbx[2,2]<-89
  bbx[,1] =  bbx[,1] - (0.04 * abs(bbx[,1]))
  if(bbx[2,1]< -90)
    bbx[2,1]<- -89
  
  figure(xlim=c(bbx[1,1],bbx[1,2]),ylim=c(bbx[2,1],bbx[2,2]),padding_factor = 0) %>%
    #gmap(lat = mean(sp@sp@bbox[2,]), lng = mean(sp@sp@bbox[1,]),zoom = 12, width = 680, height = 600)
    ly_map("world", color = "gray") %>%
    #ly_points(x=sp@sp@coords[,"LON"], y=sp@sp@coords[,"LAT"],legend=legend,hover=hover )
    ly_points(x="LON", y="LAT", data=df, color=color,fill_alpha=opacity,
              line_alpha=opacity,hover=names(df)[5:length(names(df))] )
})

#' Sort a Spectra object
#' @description
#' Sort a \code{Spectra} object with respect to its rows with respect to values of one 
#' given column (specified by which.col). Sorting with respect to multiple columns is not implemented yet.
#' @param x A \code{Spectra} object
#' @param which.col A character, defining the name of the column to be used in the sorting
#' @param decreasing Logical. If TRUE, then the rows are sorted in decreasing order. Passed on to the
#' sort.idx() function from the base package. Default is FALSE.
#' @param na.last for controlling the treatment of NAs. Passed on to the
#' sort.idx() function from the base package. Default is NA.
#' @param ...	arguments to be passed to or from methods. See help of \code{\link{sort}}.
#' @examples 
#' sp <- spc.example_spectra()
#' sp2 <- sort(sp, which.col="Offset")
#' sp2$Offset
#' sp2 <- sort(sp, which.col="CAST", decreasing=TRUE)
#' sp2$CAST
#' 
#' @export
setMethod("sort", signature="Spectra", definition= function (x, decreasing = FALSE, na.last=NA, which.col, ...){
  srt <- sort.int(x[[which.col]], decreasing=decreasing, index.return = TRUE, na.last=na.last, ...)
  x<- x[srt$ix]
  validObject(x)
  return(x)
})
PranaGeo/Spectral documentation built on Feb. 21, 2020, 12:36 p.m.