-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathexport_gtfs.R
243 lines (191 loc) · 7.25 KB
/
export_gtfs.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#' Export GTFS objects
#'
#' Writes GTFS objects to disk as GTFS transit feeds. The object must be
#' formatted according to the standards for reading and writing GTFS transit
#' feeds, as specified in \code{\link{gtfs_reference}} (i.e. data types are
#' not checked). If present, does not write auxiliary tables held in a sub-list
#' named \code{"."}.
#'
#' @param gtfs A GTFS object.
#' @param path A string. Where the resulting \code{.zip} file must be written
#' to.
#' @param files A character vector. The name of the elements to be written to
#' the feed.
#' @param standard_only A logical. Whether only standard files and fields should
#' be written (defaults to \code{TRUE}, which drops extra files and fields).
#' @param compression_level A numeric, between 1 and 9. The higher the value,
#' the best the compression, which demands more processing time. Defaults to 9
#' (best compression).
#' @param as_dir A logical. Whether the feed should be exported as a directory,
#' instead of a \code{.zip} file. Defaults to \code{FALSE}.
#' @param overwrite A logical. Whether to overwrite an existing \code{.zip} file
#' (defaults to \code{TRUE}).
#' @param quiet A logical. Whether to hide log messages and progress bars
#' (defaults to \code{TRUE}).
#'
#' @return Invisibly returns the same GTFS object passed to \code{gtfs}.
#'
#' @seealso \code{\link{gtfs_reference}}
#'
#' @family io functions
#'
#' @examples
#' gtfs_path <- system.file("extdata/ggl_gtfs.zip", package = "gtfsio")
#'
#' gtfs <- import_gtfs(gtfs_path)
#'
#' tmpf <- tempfile(pattern = "gtfs", fileext = ".zip")
#'
#' export_gtfs(gtfs, tmpf)
#' zip::zip_list(tmpf)$filename
#'
#' export_gtfs(gtfs, tmpf, files = c("shapes", "trips"))
#' zip::zip_list(tmpf)$filename
#'
#' @export
export_gtfs <- function(gtfs,
path,
files = NULL,
standard_only = FALSE,
compression_level = 9,
as_dir = FALSE,
overwrite = TRUE,
quiet = TRUE) {
# basic input checking
assert_class(gtfs, "gtfs")
assert_vector(path, "character", len = 1L)
assert_vector(files, "character", null_ok = TRUE)
assert_vector(standard_only, "logical", len = 1L)
assert_vector(compression_level, "numeric", len = 1L)
assert_vector(as_dir, "logical", len = 1L)
assert_vector(overwrite, "logical", len = 1L)
assert_vector(quiet, "logical", len = 1L)
if (path == tempdir()) error_tempfile_misused()
# input checks that depend on more than one argument
if (fs::file_exists(path) & !overwrite) error_cannot_overwrite()
if (!as_dir & !has_file_ext(path, "zip")) error_ext_must_be_zip()
if (as_dir & has_file_ext(path, "zip")) error_path_must_be_dir()
extra_files <- setdiff(files, names(gtfsio::gtfs_reference))
if (standard_only & !is.null(files) & !identical(extra_files, character(0))) {
error_non_standard_files(extra_files)
}
# if files is NULL then all 'gtfs' elements should be written
if (is.null(files)) files <- names(gtfs)
# remove '.' from 'files' if it exists ({tidytransit} may place some auxiliary
# tables in a sub-list named '.')
files <- setdiff(files, ".")
# remove extra files from 'files' if 'standard_only' is set to TRUE
# 'extra_files' is re-evaluated because 'files' might have changed in the
# lines above
extra_files <- setdiff(files, names(gtfsio::gtfs_reference))
if (standard_only) files <- setdiff(files, extra_files)
# throw an error if a specified file is not an element of 'gtfs'
missing_files <- setdiff(files, names(gtfs))
if (!identical(missing_files, character(0))) {
error_missing_specified_file(missing_files)
}
# write files either to a temporary directory (if as_dir = FALSE), or to path
# (if as_dir = TRUE)
if (as_dir) {
tmpd <- path
} else {
tmpd <- fs::file_temp(pattern = "gtfsio")
}
if (fs::dir_exists(tmpd)) {
fs::dir_delete(tmpd)
}
fs::dir_create(tmpd, recurse = TRUE)
# write files to 'tmpd'
if (!quiet) message("Writing text files to ", tmpd)
filenames <- append_file_ext(files)
filepaths <- fs::path(tmpd, filenames)
for (i in seq_along(files)) {
filename <- filenames[i]
file <- files[i]
filepath <- filepaths[i]
if (!quiet) message(" - Writing ", filename)
dt <- gtfs[[file]]
if (has_file_ext(filename, "geojson")) {
jsonlite::write_json(dt, filepath, pretty = FALSE, auto_unbox = TRUE, digits = 8)
} else {
# if 'standard_only' is set to TRUE, remove non-standard fields from 'dt'
# before writing it to disk
if (standard_only) {
file_cols <- names(dt)
extra_cols <- setdiff(file_cols, names(gtfsio::gtfs_reference[[file]][["field_types"]]))
if (!identical(extra_cols, character(0))) dt <- dt[, !..extra_cols]
}
# print warning message if warning is raised and 'quiet' is FALSE
withCallingHandlers(
data.table::fwrite(dt, filepath, scipen = 999),
warning = function(cnd) {
if (!quiet) message(" - ", conditionMessage(cnd))
}
)
}
}
# zip the contents of 'tmpd' to 'path', if as_dir = FALSE
# remove the file/directory in 'path' (an error would already have been thrown
# if 'path' pointed to an existing file that should not be overwritten).
# this action prevents zip::zip() from crashing R when 'path' exists, but is a
# directory, not a file
# related issue: https://github.com/r-lib/zip/issues/76
if (!as_dir) {
if (fs::dir_exists(path)) {
fs::dir_delete(path)
}
zip::zip(
path,
filepaths,
compression_level = compression_level,
mode = "cherry-pick"
)
if (!quiet) message("GTFS object successfully zipped to ", path)
}
return(invisible(gtfs))
}
# errors ------------------------------------------------------------------
#' @include gtfsio_error.R
error_tempfile_misused <- parent_function_error(
paste0(
"Please use 'tempfile()' instead of 'tempdir()' to designate ",
"temporary directories."
),
subclass = "tempfile_misused"
)
error_cannot_overwrite <- parent_function_error(
paste0(
"'path' points to an existing file/directory, ",
"but 'overwrite' is set to FALSE."
),
subclass = "cannot_overwrite_file"
)
error_ext_must_be_zip <- parent_function_error(
paste0(
"'path' must have '.zip' extension. ",
"If you meant to create a directory please set 'as_dir' to TRUE."
),
subclass = "ext_must_be_zip"
)
error_path_must_be_dir <- parent_function_error(
"'path' cannot have '.zip' extension when 'as_dir' is TRUE.",
subclass = "path_must_be_dir"
)
error_non_standard_files <- function(extra_files) {
parent_call <- sys.call(-1)
message <- paste0(
"Non-standard file specified in 'files', ",
"even though 'standard_only' is set to TRUE: ",
paste0("'", extra_files, "'", collapse = ", ")
)
gtfsio_error(message, subclass = "non_standard_files", call = parent_call)
}
error_missing_specified_file <- function(missing_files) {
parent_call <- sys.call(-1)
message <- paste0(
"The provided GTFS object does not contain the following ",
"elements specified in 'files': ",
paste0("'", missing_files, "'", collapse = ", ")
)
gtfsio_error(message, subclass = "missing_specified_file", call = parent_call)
}