7171# ' @param sql Optional character string containing a SQL SELECT statement to
7272# ' execute on `src` (instead of selecting all records, potentially from a subset
7373# ' of columns, i.e., mutually exclusive with `table` and/or `columns`).
74+ # ' @param quoted_cols_as_char A logical value indicating whether to auto-detect
75+ # ' columns that contain quoted values as `"character"` type, `TRUE` by default.
76+ # ' Only used when `src` is a CSV file.
7477# ' @return
7578# ' A data frame containing tree records fetched from `src`.
7679# '
8487# '
8588# ' For more details: \url{https://gdal.org/en/stable/drivers/vector/index.html}
8689# '
90+ # ' `load_tree_data()` requires GDAL built with support for the PostgreSQL client
91+ # ' library for access to PostgreSQL databases.
92+ # '
93+ # ' Column names are generally case-sensitive in \pkg{FIAstemmap} functions and
94+ # ' assumed to follow FIADB all upper case naming.
95+ # '
8796# ' @seealso
8897# ' [DEFAULT_TREE_COLUMNS]
8998# '
95104# ' head(tree)
96105# ' @export
97106load_tree_data <- function (src , table = NULL , columns = DEFAULT_TREE_COLUMNS ,
98- sql = NULL ) {
107+ sql = NULL , quoted_cols_as_char = TRUE ) {
99108
100109 if (missing(src ) || is.null(src ))
101110 stop(" 'src' is required" )
@@ -108,6 +117,12 @@ load_tree_data <- function(src, table = NULL, columns = DEFAULT_TREE_COLUMNS,
108117 stop(" could not connect to 'src'" , call. = FALSE )
109118 }
110119
120+ src_fmt <- gdalraster :: ogr_ds_format(src )
121+ if (is.null(src_fmt )) {
122+ cli :: cli_alert_danger(" unsupported format: {.path {src}}" )
123+ stop(" 'src' is not recognized as a supported format" , call. = FALSE )
124+ }
125+
111126 if (! is.null(table ) && ! is.null(sql ))
112127 stop(" 'table' and 'sql' are mutually exclusive" , call. = FALSE )
113128
@@ -120,18 +135,26 @@ load_tree_data <- function(src, table = NULL, columns = DEFAULT_TREE_COLUMNS,
120135 if (! is.null(sql ) && ! (is.character(sql ) && length(sql == 1 )))
121136 stop(" 'sql' must be a single character string" )
122137
138+ if (missing(quoted_cols_as_char ) || is.null(quoted_cols_as_char )) {
139+ quoted_cols_as_char <- TRUE
140+ } else if (! (is.logical(quoted_cols_as_char ) ||
141+ length(quoted_cols_as_char ) != 1 )) {
142+ stop(" 'quoted_cols_as_char' must be a single logical value" ,
143+ call. = FALSE )
144+ }
145+
123146 if (is.null(sql ) && ! is.null(columns )) {
124147 if (any(c(" DIST" , " AZIMUTH" ) %in% columns )) {
125- tbl <- " "
148+ tbl_tmp <- " "
126149 if (! is.null(table ))
127- tbl <- table
150+ tbl_tmp <- table
128151
129- if (gdalraster :: ogr_field_index(src , tbl , " DIST" ) < 0 ||
130- gdalraster :: ogr_field_index(src , tbl , " AZIMUTH" ) < 0 ) {
152+ if (gdalraster :: ogr_field_index(src , tbl_tmp , " DIST" ) < 0 ||
153+ gdalraster :: ogr_field_index(src , tbl_tmp , " AZIMUTH" ) < 0 ) {
131154
132- cli :: cli_alert_warning(c(
133- " The data source does not have " ,
134- " {.field DIST} and/or {.field AZIMUTH}" ))
155+ cli :: cli_alert_warning(
156+ c( " The data source does not have " ,
157+ " {.field DIST} and/or {.field AZIMUTH}" ))
135158
136159 columns <- columns [! columns %in% c(" DIST" , " AZIMUTH" )]
137160 if (length(columns ) == 0 )
@@ -141,14 +164,47 @@ load_tree_data <- function(src, table = NULL, columns = DEFAULT_TREE_COLUMNS,
141164 }
142165
143166 ds <- NULL
167+ open_options <- character (0 )
168+
169+ if (src_fmt == " CSV" ) {
170+ # auto-detect column data types
171+ open_options <- c(open_options , " AUTODETECT_TYPE=YES" )
172+ if (quoted_cols_as_char ) {
173+ open_options <- c(open_options , " QUOTED_FIELDS_AS_STRING=YES" )
174+ }
175+
176+ # enforce PLT_CN as string data type by schema override (GDAL >= 3.11)
177+ if (gdalraster :: gdal_version_num() > =
178+ gdalraster :: gdal_compute_version(3 , 11 , 0 )) {
179+
180+ tbl_tmp <- table
181+ if (is.null(tbl_tmp )) {
182+ tbl_tmp <- gdalraster ::: .cpl_get_basename(src )
183+ }
184+
185+ schema <- ' OGR_SCHEMA={"layers": [{"name": "%s", "fields":[{
186+ "name": "PLT_CN", "type": "String" }]}]}'
187+
188+ override_schema <- sprintf(schema , tbl_tmp )
189+ open_options <- c(open_options , override_schema )
190+ }
191+ }
192+
193+ gdalraster :: push_error_handler(" quiet" )
144194 if (is.null(table ) && is.null(sql )) {
145- ds <- try(methods :: new(gdalraster :: GDALVector , src ), silent = TRUE )
195+ ds <- try(methods :: new(gdalraster :: GDALVector , src , " " , TRUE ,
196+ open_options ),
197+ silent = TRUE )
146198 } else if (! is.null(table )) {
147- ds <- try(methods :: new(gdalraster :: GDALVector , src , table ),
199+ ds <- try(methods :: new(gdalraster :: GDALVector , src , table , TRUE ,
200+ open_options ),
148201 silent = TRUE )
149202 } else if (! is.null(sql )) {
150- ds <- try(methods :: new(gdalraster :: GDALVector , src , sql ), silent = TRUE )
203+ ds <- try(methods :: new(gdalraster :: GDALVector , src , sql , TRUE ,
204+ open_options ),
205+ silent = TRUE )
151206 }
207+ gdalraster :: pop_error_handler()
152208
153209 if (! methods :: is(ds , " Rcpp_GDALVector" )) {
154210 cli :: cli_alert_danger(" Failed to access tree data in {.path {src}}" )
0 commit comments