R/sf-Track-methods.R

Defines functions sfTracks.to_geojson sfTrack.to_geojson intersection_cube.sfTracks vscube.sfTracks vscube.sfTrack pv_stcube.sfTracks pv_stcube.sfTrack normalize map3d OSM sft_length cube.sfTrack sft_bbox print.sfTracks print.sfTrack distance.sfTrack cluster.sfTracks intersection.sfTrack sft_coordinates.sfTracks sft_coordinates.sfTrack transform.sfTrack

transform.sfTrack <- function(x, crs = ""){
  x@line = sf::st_transform(x@line, crs)
  x@geometry = sf::st_transform(x@geometry, crs)
  return(x)
}

setMethod("transform.sfTrack", "sfTrack", transform.sfTrack)

setGeneric("sft_coordinates", function(sft, ...)
  standardGeneric("sft_coordinates"))

sft_coordinates.sfTrack <- function(sft) {return(as.data.frame(sf::st_coordinates(sft@geometry)))}

setMethod("sft_coordinates", "sfTrack", sft_coordinates.sfTrack)


sft_coordinates.sfTracks <- function(sft) {
  return(do.call(rbind, lapply(sft@tracks,
                        function(x) sft_coordinates(x))))
}
setMethod("sft_coordinates", "sfTracks", sft_coordinates.sfTracks)


setGeneric(
  name = "intersection",
  def = function(sft1, sft2, ...) standardGeneric("intersection")
)

#Intersection between two tracks
intersection.sfTrack <- function(sft1, sft2, zoom = FALSE){
  intersection <- sf::st_intersection(sft1@line, sft2@line)
  zoom_box <- st_bbox(intersection)
  if(is_empty(intersection)){
    warning("Trajectories do not intersect")
  }
  if(missing(zoom)){
    p <- ggplot() +
      geom_sf(data = sft1@line, aes(color = sft1@id)) +
      geom_sf(data = sft2@line, aes(color = sft2@id)) +
      geom_sf(data = intersection, size = 5, shape = 1) +
    labs(color = "Track ID")
    return(p)
  }
  else{
    p <- ggplot() +
      geom_sf(data = sft1@line, aes(color = sft1@id)) +
      geom_sf(data = sft2@line, aes(color = sft2@id)) +
      geom_sf(data = intersection, size = 5, shape = 1) +
      coord_sf(xlim = c(as.numeric(zoom_box[1]), as.numeric(zoom_box[3])), ylim = c(as.numeric(zoom_box[2]), as.numeric(zoom_box[4]))) +
      labs(color = "Track ID")
    return(p)
  }
}

setMethod("intersection", "sfTrack", intersection.sfTrack)

cluster.sfTracks <- function(sftc, num_clusters){
  trajectories <- as(sftc, "data.frame")
  trajectories.nest <- geodata_to_sf(trajectories, "track.id")
  clusters <- hclust(as.dist(st_distance(trajectories.nest$geometry, which = "Frechet")))
  trajectories.nest$cluster = as.factor(cutree(clusters, num_clusters))
  return((trajectories[,"cluster"]))
}
setMethod("cluster.sfTracks", "sfTrack", cluster.sfTracks)



setGeneric(
  name = "distance",
  def = function(sft1, sft2, ...) standardGeneric("distance")
)

distance.sfTrack <- function(sft1, sft2, which = ""){
  dist <- sf::st_distance(sft1@line, sft2@line, which = which)
  return(dist)
}

setMethod("distance", "sfTrack", distance.sfTrack)


print.sfTrack <- function(object){
  track = object
  cat("An object of class sfTrack \n");
  cat(paste0(nrow(as.data.frame(track@geometry)), "points"),"\n");
  cat(paste0("bbox:"),"\n");
  print(sf::st_bbox(track@geometry));
  cat(paste0("Time period: [",min(track@time),", ", max(track@time),"]"))
}
setMethod("show", "sfTrack", print.sfTrack)

print.sfTracks <- function(object){
  cat("An object of class sfTracks" ,"\n");
  cat(paste0(length(object@tracks)), "sftracks")
}
setMethod("show", "sfTracks", print.sfTracks)

#Coerce to Track
setAs("sfTrack", "Track",
      function(from){
        geometry <- sf::as_Spatial(from@geometry)
        geometry@proj4string = CRS("+proj=longlat +datum=WGS84")
        stidf <- STIDF(sp::geometry(geometry), from@time, from@data)
        track = trajectories::Track(stidf)
        return(track)
      })

#Coerce to data frame
setAs("sfTrack", "data.frame",
      function(from){
        df <- as(from@data, "data.frame")
        df$track.id <- from@id
        return(df)
      }
)

setAs("sfTracks", "data.frame",
      function(from){
        l <- lapply(from@tracks, function(x) rbind(as(x, "data.frame"), NA))
        for(i in 1:length(l)){
          l[[i]]$track.id <- from@tracks[[i]]@id
        }
        d = do.call(rbind, l)
        rownames(d) <- NULL
        d
      }
      )

#Coerce to sf
setAs("sfTrack", "sf",
      function(from){
        df = as(from, 'data.frame')
        df = st_sf(df)
        return(df)
      }
      )

setAs("sfTracks", "sf",
      function(from){
        df = as(from, 'data.frame')
        df = st_sf(df)
        return(df)
      }
      )


sft_bbox <- function(sft){
  return(sf::st_bbox(sft@line))
}

setMethod("sft_bbox", "sfTrack", sft_bbox)

cube.sfTrack <- function(x, ...){
  trajectories::stcube(as(x, "Track"))
}

setMethod("cube.sfTrack", "sfTrack", cube.sfTrack)


sft_length <- function(sft){
  return(sf::st_length(x@line))
}
setMethod("sft_length", "sfTrack", sft_length)

#Functions for OSM and 3d map support from trajectories package
#Authors: Benedikt Graeler and Edzer Pebesma
OSM = function(xlim, ylim, mapZoom, mapType, projection) {
  if (!requireNamespace("OpenStreetMap", quietly = TRUE))
    stop("package OpenStreetMap required")
  bboxSp <- SpatialPoints(rbind(c(xlim[1], ylim[2]),
                                c(xlim[2], ylim[1])))
  bboxSp@proj4string <- CRS(projection)
  bboxSp <- spTransform(bboxSp, CRS("+init=epsg:4326"))
  map = OpenStreetMap::openmap(upperLeft = bboxSp@coords[1,2:1],
                               lowerRight = bboxSp@coords[2,2:1], zoom = mapZoom, type = mapType)
  OpenStreetMap::openproj(x = map, projection = projection)
}

map3d = function(map, z, ...) {
  if (!requireNamespace("rgl", quietly = TRUE))
    stop("rgl required")
  if(length(map$tiles) != 1)
    stop("Pass single map tile only.")
  nx = map$tiles[[1]]$xres
  ny = map$tiles[[1]]$yres
  xmin = map$tiles[[1]]$bbox$p1[1]
  xmax = map$tiles[[1]]$bbox$p2[1]
  ymin = map$tiles[[1]]$bbox$p1[2]
  ymax = map$tiles[[1]]$bbox$p2[2]
  xc = seq(xmin, xmax, len = ny)
  yc = seq(ymin, ymax, len = nx)
  col = matrix(data = map$tiles[[1]]$colorData, nrow = ny, ncol = nx)
  m = matrix(data = z, nrow = ny, ncol = nx)

  rgl::surface3d(x = xc, y = yc, z = m, col = col, lit = FALSE, ...)
}

normalize = function(time, by = "week") {
  tn = as.numeric(time)

  switch(by,
         minute = (tn %% 60),
         hour = (tn %% 3600) / 60 , # decimal minute of the hour
         day = (tn %% (3600 * 24)) / 3600, # decimal hour of the day
         week = (tn %% (3600 * 24 * 7)) / 24 / 3600, # decimal day of the week
         stop(paste("unknown value for by: ", by)))
}

if(!isGeneric("pv_stcube"))
  setGeneric("pv_stcube", function(x, ...)
    standardGeneric("pv_stcube"))

pv_stcube.sfTrack <- function(x, value, map=FALSE, ...){
  coords = sf::st_coordinates(x@geometry)
  xlim = c(st_bbox(x@geometry)[[1]], st_bbox(x@geometry)[[3]])
  ylim = c(st_bbox(x@geometry)[[2]], st_bbox(x@geometry)[[4]])
  time = index(x@time)
  time <- time - min(time)
  if(missing(value)){
    plot3Drgl::scatter3Drgl(x = coords[, 1], y = coords[, 2], z = time, xlab = "x", ylab = "y", zlab = "t",
                            ticktype = "detailed",
                            clab = "Time")

  }

  else{
    plot3Drgl::scatter3Drgl(x = st_coordinates(x@geometry)[,1], y = st_coordinates(x@geometry)[,2], z = time,
                            xlim = xlim,
                            ylim = ylim,
                            colvar = x@data[[value]],
                            ticktype = "detailed",
                            clab = value,
                            xlab = "x", ylab = "y", zlab ="t")
  }
  if(map){
    maplimx = xlim + c(-0.1,0.1) * diff(xlim)
    maplimy = ylim + c(-0.1,0.1) * diff(ylim)
    map <- OSM(xlim = maplimx, ylim= maplimy, mapType = "osm", mapZoom = NULL, projection = "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84")
    map3d(map, z = time[1], add=T)
  }
}

setMethod("pv_stcube", "sfTrack", pv_stcube.sfTrack)

pv_stcube.sfTracks <- function(x, value, map=FALSE, normalizeBy = "week", xlab='x', ylab='y', zlab='z', ...){

  xlim = c(st_bbox(x@tracks[[1]]@geometry)[[1]], st_bbox(st_bbox(x@tracks[[1]]@geometry))[[3]])
  ylim = c(st_bbox(x@tracks[[1]]@geometry)[[2]], st_bbox(x@tracks[[1]]@geometry)[[4]])

  dim = length(x@tracks[[1]]@geometry)
  coordsAll = sft_coordinates(x)
  timeAll = normalize(do.call(c, lapply(x@tracks,
                                        function(x) index(x@time))), normalizeBy)
  zlim = range(timeAll)
  col = rainbow(length(x@tracks))

  if(missing(value)){
    rgl::plot3d(x = coordsAll[1:dim, 1], y = coordsAll[1:dim, 2],
                z = timeAll[1:dim], col=col[1], xlim=xlim, ylim=ylim, xlab=xlab, ylab=ylab, zlab=zlab)

    tracks = x@tracks[-1]
    for(t in seq_along(tracks)) {
      coords = sf::st_coordinates(tracks[[t]]@geometry)
      time = normalize(index(tracks[[t]]@time), normalizeBy)
      rgl::lines3d(x = coords[, 1], y = coords[, 2], z = time, col = col[t+1])
    }
  }
  else{
    rgl::plot3d(x = coordsAll[1:dim, 1], y = coordsAll[1:dim, 2],
                z = timeAll[1:dim], col=col[1], xlim=xlim, ylim=ylim, xlab=xlab, ylab=ylab, zlab=zlab)

    tracks = x@tracks
    for(t in seq_along(tracks)) {
      coords = sf::st_coordinates(tracks[[t]]@geometry)
      time = normalize(index(tracks[[t]]@time), normalizeBy)
      plot3Drgl::scatter3Drgl(x = coords[, 1], y = coords[, 2], z = time, colvar = tracks[[t]]@data[[value]], add=TRUE)
    }
  }

  if(map){
    maplimx = xlim + c(-0.1,0.1) * diff(xlim)
    maplimy = ylim + c(-0.1,0.1) * diff(ylim)
    map <- OSM(xlim = maplimx, ylim= maplimy, mapType = "osm", mapZoom = NULL, projection = "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84")
    map3d(map, z = timeAll[1], add=T)
  }
}

setMethod("pv_stcube", "sfTracks", pv_stcube.sfTracks)

if(!isGeneric("vscube"))
  setGeneric("vscube", function(x, ...)
    standardGeneric("vscube"))


vscube.sfTrack <- function(x, value, map=TRUE, ...){
  coords = sf::st_coordinates(x@geometry)
  xlim = c(st_bbox(x@geometry)[[1]], st_bbox(x@geometry)[[3]])
  ylim = c(st_bbox(x@geometry)[[2]], st_bbox(x@geometry)[[4]])
  if(missing(value)){
    stop("Missing value for value space cube")

  }

  else{
    plot3Drgl::scatter3Drgl(x = st_coordinates(x@geometry)[,1], y = st_coordinates(x@geometry)[,2], z = x@data[[value]],
                            xlim = xlim,
                            ylim = ylim,
                            colvar = x@data[[value]],
                            ticktype = "detailed",
                            clab = value,
                            xlab = "x", ylab = "y", zlab =value)
  }
  if(map){
    maplimx = xlim + c(-0.1,0.1) * diff(xlim)
    maplimy = ylim + c(-0.1,0.1) * diff(ylim)
    map <- OSM(xlim = maplimx, ylim= maplimy, mapType = "osm", mapZoom = NULL, projection = "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84")
    map3d(map, z = 0, add=T)
  }
}

setMethod("vscube", "sfTrack", vscube.sfTrack)

vscube.sfTracks <- function(x, value, map=FALSE, xlab='x', ylab='y', ...){

  xlim = c(st_bbox(x@tracks[[1]]@geometry)[[1]], st_bbox(st_bbox(x@tracks[[1]]@geometry))[[3]])
  ylim = c(st_bbox(x@tracks[[1]]@geometry)[[2]], st_bbox(x@tracks[[1]]@geometry)[[4]])

  dim = length(x@tracks[[1]]@geometry)
  coordsAll = sft_coordinates(x)
  stsdf = as(x, 'data.frame')
  valuesAll = stsdf[[value]]
  col = rainbow(length(x@tracks))

  if(missing(value)){
    stop("Missing value for value space cube")
  }

  else{
    rgl::plot3d(x = coordsAll[1:dim, 1], y = coordsAll[1:dim, 2],
                z = valuesAll[1:dim], col=col[1], xlim=xlim, ylim=ylim, xlab=xlab, ylab=ylab, zlab=value, colvar = valuesAll, clab = value, plot=FALSE)

    for(t in seq_along(x@tracks)) {
      coords = sf::st_coordinates(x@tracks[[t]]@geometry)
      v = x@tracks[[t]]@data[[value]]
      plot3Drgl::scatter3Drgl(x = coords[, 1], y = coords[, 2], z = v, clab = value, add=TRUE)
    }
  }

  if(map){
    maplimx = xlim + c(-0.1,0.1) * diff(xlim)
    maplimy = ylim + c(-0.1,0.1) * diff(ylim)
    map <- OSM(xlim = maplimx, ylim= maplimy, mapType = "osm", mapZoom = NULL, projection = "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84")
    map3d(map, z = 0, add=T)
  }
}




if(!isGeneric("intersection_cube"))
  setGeneric("intersection_cube", function(x, ...)
    standardGeneric("intersection_cube"))

#TODO:
# Add intersection points
intersection_cube.sfTracks <- function(x, map=FALSE, normalizeBy = "week", xlab='x', ylab='y', zlab='z', ...){
  xlim = c(st_bbox(x@tracks[[1]]@geometry)[[1]], st_bbox(st_bbox(x@tracks[[1]]@geometry))[[3]])
  ylim = c(st_bbox(x@tracks[[1]]@geometry)[[2]], st_bbox(x@tracks[[1]]@geometry)[[4]])

  dim = length(x@tracks[[1]]@geometry)
  coordsAll = sft_coordinates(x)
  timeAll = normalize(do.call(c, lapply(x@tracks,
                                        function(x) index(x@time))), normalizeBy)
  zlim = range(timeAll)
  col = rainbow(length(x@tracks))

  i_points = sf::st_intersection(sft63@line, sft64@line)

  rgl::plot3d(x = coordsAll[1:dim, 1], y = coordsAll[1:dim, 2],
                z = timeAll[1:dim], col=col[1], xlim=xlim, ylim=ylim, xlab=xlab, ylab=ylab, zlab=zlab)
  tracks = x@tracks[-1]
  for(t in seq_along(tracks)) {
    coords = sf::st_coordinates(tracks[[t]]@geometry)
    time = normalize(index(tracks[[t]]@time), normalizeBy)
    rgl::lines3d(x = coords[, 1], y = coords[, 2], z = time, col = col[t+1])
  }
}

setMethod("intersection_cube", "sfTracks", intersection_cube.sfTracks)


#to geojson
if(!isGeneric("to_geojson"))
  setGeneric("to_geojson", function(x, ...)
    standardGeneric("to_geojson"))

sfTrack.to_geojson <- function(x){
  return(geojsonsf::sfc_geojson(x@geometry))
}
setMethod("to_geojson", "sfTrack", sfTrack.to_geojson)

sfTracks.to_geojson <- function(x){
  return(lapply(x@tracks, to_geojson))
}

setMethod("to_geojson", "sfTracks", sfTracks.to_geojson)
JamMurz/traviz documentation built on Sept. 11, 2020, 6:56 a.m.