R/geoflow_utils.R

Defines functions build_hierarchical_list create_object_identification_id describeOGCRelation fetch_layer_styles_from_dbi create_geoflow_data_from_dbi getDBTableColumnComment getDBTableComment get_config_resource_path get_epsg_code get_union_bbox get_absolute_path is_absolute_path unload_workflow_environment load_workflow_environment add_config_logger check_packages enrich_text_from_entity get_line_separator set_line_separator extract_cell_components filter_sf_by_cqlfilter posix_to_str str_to_posix set_i18n set_locales_to get_locales_from extract_kvps extract_kvp sanitize_date sanitize_str

Documented in add_config_logger build_hierarchical_list check_packages create_geoflow_data_from_dbi create_object_identification_id describeOGCRelation enrich_text_from_entity extract_cell_components extract_kvp extract_kvps fetch_layer_styles_from_dbi filter_sf_by_cqlfilter get_absolute_path get_config_resource_path getDBTableColumnComment getDBTableComment get_epsg_code get_line_separator get_locales_from get_union_bbox is_absolute_path load_workflow_environment posix_to_str sanitize_date sanitize_str set_i18n set_line_separator set_locales_to str_to_posix unload_workflow_environment

#' @name sanitize_str
#' @aliases sanitize_str
#' @title sanitize_str
#' @description \code{sanitize_str} sanitizes a string definition in geoflow
#'
#' @usage sanitize_str(str)
#'                 
#' @param str a string as object of class \code{character}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
sanitize_str <- function(str){
  if(!is(str, "character")) return(str)
  if(is.na(str)) return(NA)
  if(!is.na(str) & str=="") return(NA)
  startwith_n <- startsWith(str, "\n")
  while(startwith_n){
    str <- substr(str, 2, nchar(str))
    startwith_n <- startsWith(str, "\n")
  }
  str <- gsub(";;", ";", str)
  str <- gsub(",;", ",", str)
  str <- gsub(":;", ":", str)
  return(str)
}

#' @name sanitize_date
#' @aliases sanitize_date
#' @title sanitize_date
#' @description \code{sanitize_date} sanitizes a date in geoflow
#'
#' @usage sanitize_date(date)
#'                 
#' @param date an object o class \code{character}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
sanitize_date <- function(date){
  if(is(date, "character")){
    if(date==""){
      date <- NA 
    }else{
      if(nchar(date)==10){
        date <- as.Date(date)
      }else if(nchar(date)==7){
        date <- as.Date(paste0(date,"-01"))
      }else if(nchar(date)==4){
        date <- as.Date(paste0(date,"-01-01"))
      }else{
        date <- as.POSIXct(date) 
      }
    }
  }
  return(date)
}

#' @name extract_kvp
#' @aliases extract_kvp
#' @title extract_kvp
#' @description \code{extract_kvp} parses a string into a key value pair represented by
#' a \code{geoflow_kvp} object.
#'
#' @usage extract_kvp(str)
#'                 
#' @param str a string as object of class \code{character}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
extract_kvp <- function(str){
  if(endsWith(str,":")) return(NA)
  #kvp <- unlist(strsplit(str, ":(?!//|\\d)", perl = T))
  kvp <- unlist(strsplit(str, ':(?!//)\\s*(?=([^"]*"[^"]*")*[^"]*$)', perl = TRUE))
  if(length(kvp)==1) stop("Error while splitting kvp key/value")
  if(length(kvp)>2) kvp[2] <- paste(kvp[2:length(kvp)], collapse=":", sep="")
  
  #key
  key <- kvp[1]
  key_splits <- unlist(strsplit(key, "@"))
  if(length(key_splits)>1){
    key <- key_splits[1]
    attr(key,"uri") <- key_splits[2]
  }
  hasDescription <- regexpr("\\[", key)>0 & endsWith(key, "]")
  if(hasDescription){
    attrs <- attributes(key)
    value_splits <- unlist(strsplit(key, "\\["))
    key <- value_splits[1]
    if(startsWith(key, "\"") && endsWith(key, "\"")) key <- substr(key, 2, nchar(key)-1)
    attributes(key) <- attrs
    des <- value_splits[2]
    des <- substr(des, 1, nchar(des)-1)
    if(startsWith(des, "\"") && endsWith(des, "\"")) des <- substr(des, 2, nchar(des)-1)
    attr(key, "description") <- des
  }else{
    if(startsWith(key, "\"") && endsWith(key, "\"")) key <- substr(key, 2, nchar(key)-1)
  }
  
  #values
  values <- unlist(strsplit(kvp[2], ',\\s*(?=([^"]*"[^"]*")*[^"]*$)', perl = TRUE))
  values <- lapply(values, function(value){
    value_splits <- unlist(strsplit(value, "@"))
    if(length(value_splits)>1){
      value <- value_splits[1]
      link <- value_splits[2]
      attr(value, "uri") <- link
    }
    hasDescription <- regexpr("\\[", value)>0 & endsWith(value, "]")
    if(hasDescription){
      attrs <- attributes(value)
      value_splits <- unlist(strsplit(value, "\\["))
      value <- value_splits[1]
      if(startsWith(value, "\"") && endsWith(value, "\"")) value <- substr(value, 2, nchar(value)-1)
      attributes(value) <- attrs
      des <- value_splits[2]
      des <- substr(des, 1, nchar(des)-1)
      if(startsWith(des, "\"") && endsWith(des, "\"")) des <- substr(des, 2, nchar(des)-1)
      attr(value, "description") <- des
    }else{
      if(startsWith(value, "\"") && endsWith(value, "\"")) value <- substr(value, 2, nchar(value)-1)
    }
    
    return(value)
  })
  
  #locale management
  locale = NULL
  key_attrs <- attributes(key)
  key_parts <- unlist(strsplit(key, "#"))
  if(length(key_parts)>1){
    key <- key_parts[1]
    attributes(key) <- key_attrs
    locale <- key_parts[2]
  }
  return(geoflow_kvp$new(key = key, values = values, locale = locale))
}

#' @name extract_kvps
#' @aliases extract_kvps
#' @title extract_kvps
#' @description \code{extract_kvp} parses a string into a key value pair represented by
#' a \code{geoflow_kvp} object.
#'
#' @usage extract_kvps(strs, collapse)
#'                 
#' @param strs a string as object of class \code{character}
#' @param collapse collapse by. Default is \code{NULL}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
extract_kvps <- function(strs, collapse = NULL){
  kvps <- lapply(strs, function(str){
    kvp <- extract_kvp(str)
    if(!is.null(collapse)) kvp$values <- list(paste0(kvp$values, collapse = collapse))
    if(length(kvp$values)==1) kvp$values <- kvp$values[[1]]
    return(kvp)
  })
  keys <- unique(sapply(kvps, "[[", "key"))
  kvps <- do.call("c", lapply(keys, function(key){
    kvps_for_key <- kvps[sapply(kvps, function(kvp){kvp$key == key})]
    with_locales <- any(sapply(kvps_for_key, function(x){!is.null(x$locale)}))
    if(!with_locales){
      return(kvps_for_key)
    }
    kvp_with_default_locale <- kvps_for_key[sapply(kvps_for_key, function(x){is.null(x$locale)})]
    kvp_with_locale <- kvps_for_key[sapply(kvps_for_key, function(x){!is.null(x$locale)})]
    if(length(kvp_with_default_locale)>0){
      kvp_with_default_locale <- kvp_with_default_locale[[1]]
    }else{
      #TODO support default language in geoflow
    }
    #localization
    #locale key descriptions
    for(kvp in kvp_with_locale){
      if(!is.null(attr(kvp$key, "uri"))) attr(key, paste0("uri#", kvp$locale)) <- attr(kvp$key, "uri")
      if(!is.null(attr(kvp$key, "description"))) attr(key, paste0("description#", kvp$locale)) <- attr(kvp$key, "description")
    }
    #locale key uris
    #locale values
    locale_values <- kvp_with_default_locale$values
    #if(length(locale_values)==1) locale_values <- locale_values[[1]]
    for(item in kvp_with_locale){
      attr(locale_values, paste0("locale#", toupper(item$locale))) <- item$values
    }
    
    kvp_with_locales <- list(geoflow_kvp$new(key = key, values = locale_values))
    return(kvp_with_locales)
  }))
  
  return(kvps)
}

#'@name get_locales_from
#'@aliases get_locales_from
#'@title get_locales_from
#'@description Get locales from a property values set
#'
#'@usage get_locales_from(values)
#'
#'@param values values
#'
#'@export
get_locales_from <- function(values){
  if(is.null(attributes(values))) return(NULL)
  locales <- lapply(attributes(values), function(x){
    if(is.list(x)) x <- x[[1]]
    return(x)
  })
  names(locales) <- sapply(names(attributes(values)), function(x){unlist(strsplit(x, "locale#"))[2]})
  return(locales)
}

#'@name set_locales_to
#'@aliases set_locales_to
#'@title set_locales_to
#'@description Set locales to a property values set
#'
#'@usage set_locales_to(values,locales)
#'
#'@param values values
#'@param locales locales
#'
#'@export
set_locales_to <- function(values, locales = list()){
  for(lang in names(locales)){
    attr(values, paste0("locale#", lang)) <- locales[[lang]]
  }
  return(values)
}

#'@name set_i18n
#'@aliases set_i18n
#'@title set_i18n
#'@description Set default locales to a property values set
#'
#'@usage set_i18n(term_key, default, expr, ...)
#'
#'@param term_key term key
#'@param default default
#'@param expr expr
#'@param ... named values to be passed to expr
#'
#'@export
set_i18n <- function(term_key, default = NULL, expr = "{{term}}", ...){
  
  i18n_terms = jsonlite::read_json(system.file("metadata/i18n.json", package = "geoflow"))
  if(!term_key %in% names(i18n_terms)) stop(sprintf("Term '%s' not defined in i18n.json file!"))
  locales = i18n_terms[[term_key]]
  
  if(regexpr("\\{\\{term\\}\\}", expr) == -1) stop(sprintf("Expression 'expr' should at least include the key '{{term}}'"))
  
  set_locales_to(
    values = whisker::whisker.render(expr, c(term = if(!is.null(default)) default else locales[[1]], list(...))), 
    locales = lapply(locales, function(x){
      whisker::whisker.render(expr, c(term = x, list(...)))
    })
  )
}

#' @name str_to_posix
#' @aliases str_to_posix
#' @title str_to_posix
#' @description \code{str_to_posix} parses a string into a \code{POSIX} object
#'
#' @usage str_to_posix(str)
#'                 
#' @param str a string as object of class \code{character}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
str_to_posix <- function(str){
  out <- str
  if(is(str,"character")) if(nchar(str)>7){
    str_format <- if(nchar(str)==10) "%Y-%m-%d" else "%Y-%m-%dT%H:%M:%S"
    out <- as.POSIXct(str, format = str_format, tz = ifelse(endsWith(str,"Z"), "UTC", ""))
  }
  return(out)
}

#' @name posix_to_str
#' @aliases posix_to_str
#' @title posix_to_str
#' @description \code{posix_to_str} converts a \code{POSIX} object to ISO string
#'
#' @usage posix_to_str(posix)
#'                 
#' @param posix a \code{POSIX} object
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
posix_to_str <- function(posix){
  out <- posix
  if(!class(posix)[1] %in% c("Date","POSIXct")) return(out)
  str_format <- "%Y-%m-%dT%H:%M:%S"
  if(is(posix,"Date")) str_format = "%Y-%m-%d"
  out <- format(posix, format = str_format)
  tzone <- attr(posix,"tzone")
  if(!is.null(tzone)) if(tzone %in% c("UTC","GMT")) out <- paste0(out, "Z")
  return(out)
}

#' @name filter_sf_by_cqlfilter
#' @aliases filter_sf_by_cqlfilter
#' @title filter_sf_by_cqlfilter
#' @description \code{filter_sf_by_cqlfilter} filters an object of class \code{sf} using
#' a CQL syntax. This function is minimalistic and only basic CQL filters are supported.
#' 
#' @usage filter_sf_by_cqlfilter(sfdata, cqlfilter)
#'                 
#' @param sfdata object of class \code{sf}
#' @param cqlfilter object of class \code{character} representing a CQL filter
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
filter_sf_by_cqlfilter <- function(sfdata, cqlfilter){
  out <- NULL
  rfilter <- gsub(" AND ", " & ", cqlfilter)
  rfilter <- gsub(" OR ", " | ", rfilter)
  rfilter <- gsub(" IN\\(", " %in% c(", rfilter)
  rfilter <- gsub("'","\"", rfilter)
  rfilter <- gsub("=", "==", rfilter)
  rfilter <- paste0("sfdata$", rfilter)
  sfdata.filtered <- try(eval(parse(text= sprintf("sfdata[%s,]",rfilter))))
  if(class(sfdata.filtered)[1]!="try-error") out <- sfdata.filtered
  return(out)
}

#' @name extract_cell_components
#' @aliases extract_cell_components
#' @title extract_cell_components
#' @description \code{extract_cell_components} extracts the components of a cell
#' when using tabular data content handlers (for entity and contact).
#'
#' @usage extract_cell_components(str)
#'                 
#' @param str a string as object of class \code{character}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
extract_cell_components <- function(str){
  lines <- unlist(strsplit(str, get_line_separator()))
  return(lines)
}

#' @name set_line_separator
#' @aliases set_line_separator
#' @title set_line_separator
#' @description \code{set_line_separator} set the line separator to be used by geoflow
#' when extracting cell components for tabular data content handling.
#'
#' @usage set_line_separator(x)
#'                 
#' @param x a string as object of class \code{character} representing the line separator.
#  Default is set to an underscore followed by a line break.
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
set_line_separator <- function(x = "_\n"){
  if(!is(x,"character")) stop("The line separator should be an object of class 'character'")
  .geoflow$LINE_SEPARATOR <- x
}

#' @name get_line_separator
#' @aliases get_line_separator
#' @title get_line_separator
#' @description \code{get_line_separator} get the line separator used by geoflow
#' when extracting cell components for tabular data content handling. Default is 
#' set to an underscore followed by a line break.
#'
#' @usage get_line_separator()
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
get_line_separator <- function(){
  return(.geoflow$LINE_SEPARATOR)
}

#' @name enrich_text_from_entity
#' @aliases enrich_text_from_entity
#' @title enrich_text_from_entity
#' @description \code{enrich_text_from_entity} will attempt to enrich an entity text property
#' from other entity metadata, depending on text variables handled by a pattern in the form
#' %property%. 
#' 
#' - If the entity property is a text, only the name of the property name is required.
#' 
#' - If the entity property is a list, then 2 subcases can be distinguished:
#' 
#' If it is a named list (such as entity descriptions), the text variable will be compound by
#' the entity property name and the element_property name, in the form %property:element_property%
#' 
#' If it is a unnamed list (such as list of keywords, list of relations, etc), the text variable will handle
#' four elements: property (entity property name to look at), a key value pair to use for search
#' within the list, an element_property for which the value should be picked up to enrich the text.
#' The variable willbe in the form %property:keye:value:element_property%
#'
#' @usage enrich_text_from_entity(str, entity)
#' 
#' @param str a text to be enriched
#' @param entity an object of class \code{geoflow_entity}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
#'
enrich_text_from_entity = function(str, entity){
  if(!is.character(str)) return(str)
  outstr <- str
  indexes <- gregexpr(pattern = "\\%(.*?)\\%", str)[[1]]
  if(length(indexes)==1) if(indexes == -1) return(str)
  for(index in indexes){
    newvalue <- NULL
    metavar <- unlist(strsplit(substr(str, index+1, nchar(str)),"%"))[1]
    metavars <- unlist(strsplit(metavar, ":"))
    meta_prop <- metavars[1]
    target_props <- entity[[meta_prop]]
    
    if(!is.null(target_props)){
      #character
      if(is.character(target_props)){
        newvalue <- target_props
        #lists
      }else if(is.list(target_props)){
        #named lists
        if(length(names(target_props))){
          meta_keyname <- metavars[2]
          newvalue <- target_props[[meta_keyname]]
        }else{
          if(length(metavars)!=4) next
          meta_keyname <- metavars[2]
          meta_keyvalue <- metavars[3]
          meta_value <- metavars[4]
          search <- target_props[sapply(target_props, function(x){x[[meta_keyname]]==meta_keyvalue})]
          if(length(search)==0) next
          newvalue <- search[[1]][[meta_value]]
        }
      }
    }
    if(is.null(newvalue)) next
    outstr <- sub(pattern = "\\%(.*?)\\%", newvalue, outstr)
  }
  return(outstr)
}

#' @name check_packages
#' @aliases check_packages
#' @title check_packages
#' @description \code{check_packages} checks availability of a list of R packages in R. This
#' function is essentially used internally by \pkg{geoflow} in assocation to \code{geoflow_software} and
#' \code{geoflow_action} that would need specific packages to be imported in R.
#' 
#' @usage check_packages(pkgs)
#' 
#' @param pkgs a vector of package names
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
check_packages <- function(pkgs){
  
  if(length(pkgs)==0) return(NULL)
  pkgs_df <- do.call("rbind", lapply(pkgs, function(pkg){
    pkg_loaded <- suppressWarnings(require(pkg, character.only = TRUE))
    pkg_df <- data.frame(
      package = pkg,
      installed = pkg_loaded,
      version = ifelse(pkg_loaded, as(packageVersion(pkg), "character"), NA),
      stringsAsFactors = FALSE
    )
    return(pkg_df)
  }))
  if(any(!pkgs_df$installed)){
    pkgs_not_installed <- pkgs_df[!pkgs_df$installed,]
    print(pkgs_not_installed)
    stop(sprintf("The following package(s) are required but not installed: %s",
                paste0(pkgs_not_installed$package, collapse=", ")))
  }
  return(pkgs_df)
}

#'@name add_config_logger
#'@aliases add_config_logger
#'@title add_config_logger
#'@description \code{add_config_logger} enables a logger (managed with the internal 
#'class \link{geoflowLogger}).
#'
#'@usage add_config_logger(config)
#'
#'@param config object of class \link{list}
#'
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
add_config_logger <- function(config){
  id <- if(!is.null(config$profile$id)) config$profile$id else config$id
  if(is.null(config$verbose)) config$verbose = TRUE
  if(is.null(config$debug)) config$debug = FALSE
  config$logger <- geoflowLogger$new(verbose = config$verbose, debug = config$debug)
  return(config)
}

#' @name load_workflow_environment
#' @aliases load_workflow_environment
#' @title load_workflow_environment
#' @description \code{load_workflow_environment} loads a workflow environment by evaluating variable expressions in the 
#' form \code{${{variable}}}. If no variable expression pattern is identified in the string, 
#' the function will return the original string.
#' 
#' @usage load_workflow_environment(config, session)
#' 
#' @param config object of class \link{list}
#' @param session a \pkg{shiny} session object (optional) to run geoflow in a \pkg{shiny} context.
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
load_workflow_environment <- function(config, session = NULL){
  config$logger = NULL
  config_str <- jsonlite::toJSON(config, auto_unbox = TRUE)
  
  #grab shiny session userData if any session specified
  userdata <- list()
  if(!is.null(session)){
    if(!is(session, "ShinySession")){
      stop("The 'session' argument should specify an object of class 'ShinySession'")
    }
    userdata <- as.list(session$userData)
    userdata <- userdata[!sapply(userdata, is.function)]
  }
  
  #evaluate environment variables + eventual shiny session userData
  config_str <- whisker::whisker.render(config_str, c(as.list(Sys.getenv()), userdata))

  config <- jsonlite::parse_json(config_str)
  config <- add_config_logger(config)
  return(config)
}

#from dotenv internal functions - see https://github.com/gaborcsardi/dotenv/issues/11
#dotenv_ignore_comments
dotenv_ignore_comments <- function (lines) {
  grep("^#", lines, invert = TRUE, value = TRUE)
}
#dotenv_ignore_empty_lines
dotenv_ignore_empty_lines <- function (lines) {
  grep("^\\s*$", lines, invert = TRUE, value = TRUE)
}
#dotenv_extract_match
dotenv_extract_match <- function (line, match) {
  tmp <- mapply(attr(match, "capture.start"), attr(match, "capture.length"), 
                FUN = function(start, length) {
                  tmp <- substr(line, start, start + length - 1)
                })
  names(tmp) <- attr(match, "capture.names")
  tmp
}
#dotenv_parse_dot_line
dotenv_parse_dot_line <- function (line) {
  line_regex = "^\\s*(?<export>export\\s+)?(?<key>[^=]+)=(?<q>['\"]?)(?<value>.*)\\g{q}\\s*$"
  match <- regexpr(line_regex, line, perl = TRUE)
  if (match == -1) 
    stop("Cannot parse dot-env line: ", substr(line, 1, 40), 
         call. = FALSE)
  as.list(dotenv_extract_match(line, match)[c("key", "value")])
}

#' @name unload_workflow_environment
#' @aliases unload_workflow_environment
#' @title unload_workflow_environment
#' @description \code{unload_workflow_environment} unloads a workflow environment, in the case environment 
#' was provided by means of a dotenv file, and loaded using \pkg{dotenv} by \pkg{geoflow}. The function will
#' recover the session environment variables values (useful in case an environment variable was overwriten for
#' the workflow execution).
#' 
#' @usage unload_workflow_environment(config)
#' 
#' @param config object of class \link{list}
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
unload_workflow_environment <- function(config){
  if(!is.null(config$profile_config$environment$file)){
    env_vars_workflow <- as.list(Sys.getenv())
    envfile <- get_absolute_path(config$profile_config$environment$file, base = config$wd)
    if(!is.null(envfile)){
      tmp <- readLines(envfile)
      tmp <- dotenv_ignore_comments(tmp)
      tmp <- dotenv_ignore_empty_lines(tmp)
      if (length(tmp) > 0){
        tmp <- lapply(tmp, dotenv_parse_dot_line)
        tmp <- structure(.Data = lapply(tmp, "[[", "value"), .Names = sapply(tmp, "[[", "key"))
        
        #remove env vars based on .env file
        Sys.unsetenv(names(tmp))
        
        #reset env vars previously in session env
        env_vars_before <- config$session_env
        env_vars_to_reset <- setdiff(env_vars_before, env_vars_workflow)
        if(length(env_vars_to_reset)>0) do.call(Sys.setenv, env_vars_to_reset)
      }
    }
  }
}

#' @name is_absolute_path
#' @aliases is_absolute_path
#' @title is_absolute_path
#' @description \code{is_absolute_path} evaluate if a \code{${{path}}} expression is an absolute path, 
#' the function will return a boolean argument.
#' 
#' @usage is_absolute_path(path)
#' 
#' @param path a path in character string
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export

is_absolute_path <- function(path) {
  grepl("^(/|[A-Za-z]:|\\\\|~)", path)
}

#' @name get_absolute_path
#' @aliases get_absolute_path
#' @title get_absolute_path
#' @description \code{get_absolute_path} allows to get the absolute path of a resource
#' given a base directory
#' 
#' @usage get_absolute_path(path, base, mustWork, expand_tilde)
#' 
#' @param path a path in character string
#' @param base a base direcotry
#' @param mustWork must work?
#' @param expand_tilde expand tilde?
#' 
#' @author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#' @export
get_absolute_path <- function(path, base = getwd(), mustWork = FALSE, expand_tilde = TRUE) {
  path <- as.character(path)
  base <- as.character(base)[1L]
  if (expand_tilde) {
    path <- ifelse(startsWith(path, "~"), path.expand(path), path)
    base <- path.expand(base)
  }
  combined <- ifelse(is_absolute_path(path), path, file.path(base, path))
  normalizePath(combined, winslash = "/", mustWork = mustWork)
}

#'@name get_union_bbox
#'@aliases get_union_bbox
#'@title get_union_bbox
#'@description \code{get_union_bbox} will build a unified bounding box from a list of \code{geoflow_data} objects
#'
#'@usage get_union_bbox(data_objects)
#'
#'@param data_objects list of \code{geoflow_data} objects
#'
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
get_union_bbox <- function(data_objects){
  
  bbox.df <- as.data.frame(do.call("rbind", lapply(data_objects, function(data_object){
    data_object.bbox <- NULL
    if(!is.null(data_object$features)){
      data_object.bbox <- sf::st_bbox(data_object$features)
    }else if(!is.null(data_object$coverages)){
      vec = data_object$coverages@ptr$extent$vector
      data_object.bbox <- c(xmin = vec[1], ymin = vec[3], xmax = vec[2], ymax = vec[4])
      class(data_object.bbox) <- "bbox"
    }
    return(data_object.bbox)
  })))
  
  union.bbox <- c(xmin = min(bbox.df[,1]), ymin = min(bbox.df[,2]), xmax = max(bbox.df[,3]), ymax = max(bbox.df[,4]))
  class(union.bbox) <- "bbox"
  return(union.bbox)
}

#'@name get_epsg_code
#'@aliases get_epsg_code
#'@title get_epsg_code
#'@description \code{get_epsg_code} is a consolidated method to get EPSG code (srid) from a CRS
#'
#'@usage get_epsg_code(x)
#'
#'@param x an object of class 'sf'
#'
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
get_epsg_code = function(x){
  epsgcode = NA
  sf.crs <- sf::st_crs(x)
  if(!is.na(sf.crs)){
    epsgcode <- sf.crs$epsg
    if(!is.null(epsgcode)) {
      if(is.na(epsgcode)){
        #try to inherit epsg code from WKT definition (thanks to rspatial/terra)
        crs_wkt <- sf.crs$wkt
        if(!is.na(crs_wkt)) if(nzchar(crs_wkt)){
          crs_def <- terra::crs(crs_wkt, describe = TRUE)
          if(!is.null(crs_def$authority)) if(!is.na(crs_def$authority)) if(crs_def$authority == "EPSG"){
            epsgcode <-crs_def$code 
          }
        }
      }
      if(!is.na(epsgcode)) epsgcode = as.integer(epsgcode)
    }
  }
  return(epsgcode)
}

#'@name get_config_resource_path
#'@aliases get_config_resource_path
#'@title get_config_resource_path
#'@usage get_config_resource_path(config, path)
#'
#'@param config a \pkg{geoflow} config
#'@param path a resource path to resolve vs. the config root dir
#'
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
get_config_resource_path <- function(config, path){
  is_url <- regexpr("(http|https)[^([:blank:]|\\\"|<|&|#\n\r)]+", path) > 0
  if(is_url) return(path)
  if(!is_absolute_path(path)){
    path_root = config$root
    mtch = gregexpr("\\.\\./", path)[[1]]
    mtch = mtch[mtch != -1]
    if(length(mtch)>0) for(i in 1:length(mtch)){
      path_root = dirname(path_root)
    }
    path = gsub("\\.\\./", "", path)
    if(startsWith("./", path)) path = unlist(strsplit(path, "\\./"))[2]
    path = file.path(path_root, path)
  }
  return(path)
}

#'@name getDBTableComment
#'@aliases getDBTableComment
#'@title getDBTableComment
#'
#'@usage getDBTableComment(dbi, schema, table)
#'
#'@param dbi a dbi connection
#'@param schema schema
#'@param table table
#'@return the table comment

#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
getDBTableComment = function(dbi, schema, table){
  get_comment_sql = sprintf("select obj_description('%s.%s'::regclass, 'pg_class')",
                            paste0('"',schema,'"'), paste0('"',table,'"'))
  get_comment = DBI::dbGetQuery(dbi, get_comment_sql)
  return(get_comment$obj_description)
}

#'@name getDBTableColumnComment
#'@aliases getDBTableColumnComment
#'@title getDBTableColumnComment
#'
#'@usage getDBTableColumnComment(dbi, schema, table, column_index)
#'
#'@param dbi a dbi connection
#'@param schema schema
#'@param table table
#'@param column_index table column index
#'@return the table comment
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
getDBTableColumnComment = function(dbi, schema, table, column_index){
  get_comment_sql = sprintf("select col_description('%s.%s'::regClass, %s)",
                            paste0('"',schema,'"'), paste0('"',table,'"'), column_index)
  get_comment = DBI::dbGetQuery(dbi, get_comment_sql)
  return(get_comment$col_description)
}

#'@name create_geoflow_data_from_dbi
#'@aliases create_geoflow_data_from_dbi
#'@title create_geoflow_data_from_dbi
#'
#'@usage create_geoflow_data_from_dbi(dbi, schema, table)
#'
#'@param dbi a dbi connection
#'@param schema schema
#'@param table table
#'
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
create_geoflow_data_from_dbi <- function(dbi, schema, table){
  entity_data = geoflow_data$new()
  sql = sprintf("select * from %s.%s", paste0('"',schema,'"'), paste0('"',table,'"'))
  entity_data$setSourceSql(sql)
  entity_data$setSourceType("dbquery")
  entity_data$setSpatialRepresentationType("vector")
  entity_data$setUploadType("dbtable")
  entity_data$setUploadSource(table)
  #data/feature type
  fto = geoflow_featuretype$new(id = table)
  data_sample = sf::st_read(dbi, query = paste(sql, "limit 1;"))
  for(colname in colnames(data_sample)){
    col_idx = which(colnames(data_sample) == colname)
    col_comment = getDBTableColumnComment(dbi, schema, table, col_idx)
    if(is.na(col_comment)) col_comment = colname
    
    ftm = geoflow_featuremember$new(
      type = if(is(data_sample[[colname]], "character")) "attribute" else "variable",
      code = colname,
      name = col_comment,
      def = col_comment,
      defSource = NA,
      minOccurs = 0,
      maxOccurs = 1,
      uom = NA
    )
    fto$addMember(ftm)
  }
  entity_data$setFeatureType(table)
  entity_data$setFeatureTypeObj(fto)
  return(entity_data)
}

#'@name fetch_layer_styles_from_dbi
#'@aliases fetch_layer_styles_from_dbi
#'@title fetch_layer_styles_from_dbi
#'
#'@usage fetch_layer_styles_from_dbi(entity, dbi, schema, table)
#'
#'@param entity a \link{geoflow_entity} to be used and enriched
#'@param dbi a dbi connection
#'@param schema schema
#'@param table table
#'@return the entity, enriched with layer styles
#'
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
fetch_layer_styles_from_dbi <- function(entity, dbi, schema, table){
  if(DBI::dbExistsTable(dbi, "layer_styles")){
    #assume this is a special table
    styles_sql = sprintf("select * from layer_styles where f_table_schema='%s' and f_table_name='%s'", 
                         schema, table)
    styles = DBI::dbGetQuery(dbi, statement = styles_sql)
    if(nrow(styles)>0){
      styles[order(styles$useasdefault,decreasing = T),] #make sure we list the default one first
      #add style names in geoflow_data
      for(i in 1:nrow(styles)){
        style = styles[i,]
        entity$data$addStyle(style$stylename)
      }
      #add style defs as entity resource to delegate copy after entity dir is created
      entity$addResource("layer_styles", styles)
    }
  }
  return(entity)
}

#'@name describeOGCRelation
#'@aliases describeOGCRelation
#'@title describeOGCRelation
#'
#'@usage describeOGCRelation(entity, data_object, service, download, format,
#'                           handle_category, handle_ogc_service_description, handle_format)
#'
#'@param entity the entity considered
#'@param data_object data object
#'@param service service acronym
#'@param download whether the relation should be a download one or not
#'@param format format
#'@param handle_category append the relation category
#'@param handle_ogc_service_description append the OGC service description
#'@param handle_format append the download format
#'
#'@author Emmanuel Blondel, \email{emmanuel.blondel1@@gmail.com}
#'@export
describeOGCRelation <- function(entity, data_object, service, download = FALSE, format = NULL,
                                handle_category = TRUE, handle_ogc_service_description = TRUE, handle_format = TRUE){
  
  layername <- if(!is.null(data_object$layername)) data_object$layername else entity$identifiers$id
  layertitle = if(!is.null(data_object$layertitle)) data_object$layertitle else layername
  
  out <- switch(tolower(service),
                "wms" = {
                  out_wms_link = layertitle
                  if(handle_category) out_wms_link = set_i18n(
                    term_key = "map_access", 
                    expr = {
                      the_expr = "{{out_wms_link}} - {{term}}"
                      if(handle_ogc_service_description) the_expr = paste0(the_expr," - OGC Web Map Service (WMS)")
                      the_expr
                    },
                    out_wms_link = out_wms_link
                  )
                  out_wms_link
                },
                "wfs" = {
                  out_wfs_link = layertitle
                  if(handle_category) out_wfs_link = set_i18n(
                    term_key = if(download) "data_download" else "data_features_access",
                    expr = {
                      the_expr = "{{out_wfs_link}} - {{term}}"
                      if(handle_ogc_service_description) the_expr = paste0(the_expr, " - OGC Web Feature Service (WFS)")
                      if(handle_format && !is.null(format)) the_expr = paste0(the_expr, " - ", format)
                      the_expr
                    },
                    out_wfs_link = out_wfs_link
                  )
                  out_wfs_link
                },
                "wcs" = {
                  out_wcs_link = layertitle
                  if(handle_category) out_wcs_link = set_i18n(
                    term_key = if(download) "data_download" else "data_coverage_access",
                    expr = {
                      the_expr = "{{out_wcs_link}} - {{term}}"
                      if(handle_ogc_service_description) the_expr = paste0(the_expr, " - OGC Web Coverage Service (WCS)")
                      if(handle_format && !is.null(format)) the_expr = paste0(the_expr, " - ", format)
                      the_expr
                    },
                    out_wcs_link = out_wcs_link
                  )
                  out_wcs_link
                }
  )
  return(out)
}

#'@name create_object_identification_id
#'@aliases create_object_identification_id
#'@title create_object_identification_id
#'
#'@usage create_object_identification_id(prefix, str)
#'
#'@param prefix a character string
#'@param str a character string
#'@return a digested character string
#'@export
create_object_identification_id = function(prefix, str){
  paste(prefix, digest::digest(object = str, algo = "crc32", serialize = FALSE), sep = "_")
}


#'@description precompute_relationships
#'@aliases precompute_relationships
#'@title precompute_relationships
#'
#'@usage precompute_relationships(data, parent_key, child_key, child_label)
#'
#'@param data data
#'@param parent_key parent_key
#'@param child_key child_key
#'@param child_label child_label
#'@return a list of relationships
#'@export
precompute_relationships <- function (data, parent_key, child_key, child_label) {
  ordered_data <- data[order(data[[parent_key]], data[[child_key]]), ]
  relationships <- split(ordered_data[[child_key]], ordered_data[[parent_key]])
  rel_names = names(relationships)
  relationships <- lapply(relationships, function(x) {
      lapply(x, function(x_el) {
          attr(x_el, "label") = data[data[, child_key] == x_el, 
              child_label][1]
          return(x_el)
      })
  })
  names(relationships) = rel_names
  return(relationships)
}


#'@name build_hierarchical_list
#'@aliases build_hierarchical_list
#'@title build_hierarchical_list
#'
#'@usage build_hierarchical_list(parent, relationships)
#'
#'@param parent parent
#'@param relationships relationships
#'@return a hierarchical list
#'@export
build_hierarchical_list <- function(parent, relationships) {
  children <- relationships[[parent]]
  children_names <- sapply(children, function(x){attr(x, "label")})
  children = children[order(children_names)]
  out <- list(text = if(parent == "<root>") parent else attr(parent, "label") )
  if(is.null(children)){
    out$icon = "fa-regular fa-note-sticky"
  }else{
    out$children <- lapply(children, build_hierarchical_list, relationships)
  }
  return(out)
}

Try the geoflow package in your browser

Any scripts or data that you put into this service are public.

geoflow documentation built on Dec. 12, 2025, 5:08 p.m.