Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split functions to parse gpx data and factor out track parsing #43

Merged
merged 1 commit into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: osmapiR
Title: 'OpenStreetMap' API
Version: 0.1.0.9004
Version: 0.1.0.9005
Authors@R: c(
person("Joan", "Maspons", , "joanmaspons@gmail.com", role = c("aut", "cre", "cph"),
comment = c(ORCID = "0000-0003-2286-8727")),
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Add format = "sf" for `osm_get_gpx_metadata()` (#38)
* Updated links to the new osmapiR home at rOpenSci (#40)
* Add format = "sf" for `osm_list_gpxs()` (#42)
* Split functions to parse gpx data from different API endpoints and different properties (#43)

# osmapiR 0.1.0

Expand Down
23 changes: 1 addition & 22 deletions R/osmapi_gps_traces.R
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,6 @@

if (format == "R") {
out <- gpx_xml2list(obj_xml)
names(out) <- vapply(out, function(x) {
url <- attr(x, "url")
if (is.null(url)) { # for private traces?
url <- ""
}
url
}, FUN.VALUE = character(1))
} else {
out <- obj_xml
}
Expand Down Expand Up @@ -422,22 +415,8 @@ osm_get_data_gpx <- function(gpx_id, format) {
if (missing(format) || format %in% c("xml", "gpx")) {
out <- obj_xml
} else {
out <- gpx_xml2list(obj_xml)
out <- gpx_xml2df(obj_xml)

if (length(out) > 1) {
warning(
"Unexpected output format at osm_get_data_gpx().",
"Please, open and issue with the `gpx_id` or the original file if the gpx is not public ",
"at https://github.com/ropensci/osmapiR/issues"
)
} else {
attrs <- attributes(out)
attrs <- attrs[setdiff(names(attrs), "class")]
names(attrs) <- paste0("gpx_", names(attrs))
out <- out[[1]]
attributes(out) <- c(attributes(out), attrs)
class(out) <- c("osmapi_gps_track", "data.frame")
}
}

return(out)
Expand Down
153 changes: 107 additions & 46 deletions R/xml_to_R.R
Original file line number Diff line number Diff line change
Expand Up @@ -284,81 +284,142 @@

# GPX files----

gpx_xml2list <- function(xml) {
# xml_attrs <- xml2::xml_attrs(xml)

gpx_xml2df <- function(xml) {
xml_attrs <- xml2::xml_attrs(xml)
gpx <- xml2::xml_children(xml)

if ("metadata" %in% xml2::xml_name(gpx)) { # Always present for gpx/id responses
gpx_metadata <- xml2::as_list(gpx[xml2::xml_name(gpx) == "metadata"])
if (length(gpx_metadata) == 1) { # Only one <metadata> node
gpx_metadata <- gpx_metadata[[1]]
}
# For bounds as vector when creator="JOSM GPX export"
if (xml_attrs["creator"] == "JOSM GPX export") {
metadata <- xml2::xml_children(gpx[xml2::xml_name(gpx) == "metadata"])
metadata_attrs <- xml2::xml_attrs(metadata)
names(metadata_attrs) <- xml2::xml_name(metadata)
metadata_attrs <- metadata_attrs[vapply(metadata_attrs, length, FUN.VALUE = integer(1)) > 0]
gpx_metadata <- c(unlist(gpx_metadata, recursive = FALSE), metadata_attrs)
}
} else {
gpx_metadata <- NULL

Check warning on line 305 in R/xml_to_R.R

View check run for this annotation

Codecov / codecov/patch

R/xml_to_R.R#L305

Added line #L305 was not covered by tests
}

trk <- gpx[xml2::xml_name(gpx) == "trk"]
# xml_find_all(trk, xpath = ".//name") ## TODO: doesn't work :(
# xml2::xml_find_all(trk, xpath = ".//name") ## TODO: doesn't work :(

if (length(trk) == 0) {
return(empty_gpx())
out <- empty_gpx_df()

Check warning on line 312 in R/xml_to_R.R

View check run for this annotation

Codecov / codecov/patch

R/xml_to_R.R#L312

Added line #L312 was not covered by tests
} else if (length(trk) == 1) {
out <- trk_xml2df(trk = trk)
attributes(out) <- c(attributes(out), list(gpx_attributes = xml_attrs), gpx_metadata)
class(out) <- c("osmapi_gps_track", "data.frame")
} else if (length(trk) > 1) {
warning(
"GPX with more than one track. The result will be a list of class `osmapi_gpx`.",
"Please, open and issue with the `gpx_id` or the original file if the gpx is not public ",
"at https://github.com/ropensci/osmapiR/issues"
)
out <- gpx_xml2list(xml = xml)

Check warning on line 323 in R/xml_to_R.R

View check run for this annotation

Codecov / codecov/patch

R/xml_to_R.R#L317-L323

Added lines #L317 - L323 were not covered by tests
}

trkL <- lapply(trk, function(x) {
x_ch <- xml2::xml_children(x)
x_names <- xml2::xml_name(x_ch)
return(out)
}

trk_details <- structure(xml2::xml_text(x_ch[x_names != "trkseg"]), names = x_names[x_names != "trkseg"])

trkseg <- x_ch[x_names == "trkseg"]
empty_gpx_df <- function() {
out <- data.frame(lon = character(), lat = character(), ele = character(), time = as.POSIXct(Sys.time())[-1])
class(out) <- c("osmapi_gps_track", "data.frame")

Check warning on line 332 in R/xml_to_R.R

View check run for this annotation

Codecov / codecov/patch

R/xml_to_R.R#L331-L332

Added lines #L331 - L332 were not covered by tests

trkseg_ch <- xml2::xml_children(trkseg)
trkseg_names <- xml2::xml_name(trkseg_ch)
trkpt <- trkseg_ch[trkseg_names == "trkpt"]
lat_lon <- do.call(rbind, xml2::xml_attrs(trkpt))
# xml2::xml_find_all(trkpt, ".//time") ## TODO: doesn't work :(
return(out)

Check warning on line 334 in R/xml_to_R.R

View check run for this annotation

Codecov / codecov/patch

R/xml_to_R.R#L334

Added line #L334 was not covered by tests
}

elem_points <- lapply(trkpt, function(y) {
pt <- xml2::xml_children(y)
elem_name <- vapply(pt, xml2::xml_name, FUN.VALUE = character(1))
sel <- elem_name %in% c("ele", "time")
vals <- structure(
vapply(pt[sel], xml2::xml_text, FUN.VALUE = character(1)),
names = elem_name[sel]
)

return(vals)
})
point_data <- do.call(rbind, elem_points)
gpx_xml2list <- function(xml) {
xml_attrs <- xml2::xml_attrs(xml)
gpx <- xml2::xml_children(xml)

trkpt <- data.frame(lat_lon, point_data)
if ("time" %in% names(trkpt)) {
trkpt$time <- as.POSIXct(trkpt$time, format = "%Y-%m-%dT%H:%M:%OS", tz = "GMT")
if ("metadata" %in% xml2::xml_name(gpx)) { # Never seen for bbox trackpoints responses
gpx_metadata <- xml2::as_list(gpx[xml2::xml_name(gpx) == "metadata"])
if (length(gpx_metadata) == 1) {
gpx_metadata <- gpx_metadata[[1]]

Check warning on line 345 in R/xml_to_R.R

View check run for this annotation

Codecov / codecov/patch

R/xml_to_R.R#L343-L345

Added lines #L343 - L345 were not covered by tests
}
} else {
gpx_metadata <- NULL
}

out <- trkpt
attributes(out) <- c(attributes(out), trk_details)

return(out)
})

if ("metadata" %in% xml2::xml_name(gpx)) {
metaL <- xml2::as_list(gpx[xml2::xml_name(gpx) == "metadata"])

meta <- xml2::xml_children(gpx[xml2::xml_name(gpx) == "metadata"])
meta_attrs <- xml2::xml_attrs(meta)
names(meta_attrs) <- xml2::xml_name(meta)
meta_attrs <- meta_attrs[vapply(meta_attrs, length, FUN.VALUE = integer(1)) > 0]
trk <- gpx[xml2::xml_name(gpx) == "trk"]
# xml_find_all(trk, xpath = ".//name") ## TODO: doesn't work :(

attributes(trkL) <- c(attributes(trkL), unlist(metaL, recursive = FALSE), meta_attrs)
if (length(trk) == 0) {
return(empty_gpx_list())
}

class(trkL) <- c("osmapi_gpx", class(trkL))
out <- lapply(trk, trk_xml2df)
names(out) <- vapply(out, function(x) {
url <- attr(x, "track_url")
if (is.null(url)) { # for private traces
url <- ""
}
url
}, FUN.VALUE = character(1))

attributes(out) <- c(attributes(out), list(gpx_attributes = xml_attrs), gpx_metadata)
class(out) <- c("osmapi_gpx", class(out))

return(trkL)
return(out)
}


empty_gpx <- function() {
empty_gpx_list <- function() {
out <- list()
class(out) <- c("osmapi_gpx", "list")

return(out)
}


trk_xml2df <- function(trk) {
trk_ch <- xml2::xml_children(trk)
trk_names <- xml2::xml_name(trk_ch)

trk_details <- structure(xml2::xml_text(trk_ch[trk_names != "trkseg"]), names = trk_names[trk_names != "trkseg"])
if (length(trk_details)) {
names(trk_details) <- paste0("track_", names(trk_details))
}

trkseg <- trk_ch[trk_names == "trkseg"]

trkseg_ch <- xml2::xml_children(trkseg)
trkseg_names <- xml2::xml_name(trkseg_ch)
trkpt <- trkseg_ch[trkseg_names == "trkpt"]
lat_lon <- do.call(rbind, xml2::xml_attrs(trkpt))
# xml2::xml_find_all(trkpt, ".//time") ## TODO: doesn't work :(

elem_points <- lapply(trkpt, function(y) {
pt <- xml2::xml_children(y)
elem_name <- vapply(pt, xml2::xml_name, FUN.VALUE = character(1))
sel <- elem_name %in% c("ele", "time")
vals <- structure(
vapply(pt[sel], xml2::xml_text, FUN.VALUE = character(1)),
names = elem_name[sel]
)

return(vals)
})
point_data <- do.call(rbind, elem_points)

out <- data.frame(lat_lon, point_data)
if ("time" %in% names(out)) {
out$time <- as.POSIXct(out$time, format = "%Y-%m-%dT%H:%M:%OS", tz = "GMT")
}

attributes(out) <- c(attributes(out), trk_details)

return(out)
}


## user_details ----

user_details_xml2DF <- function(xml) {
Expand Down
6 changes: 3 additions & 3 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"codeRepository": "https://github.com/ropensci/osmapiR",
"issueTracker": "https://github.com/ropensci/osmapiR/issues",
"license": "https://spdx.org/licenses/GPL-3.0",
"version": "0.1.0.9004",
"version": "0.1.0.9005",
"programmingLanguage": {
"@type": "ComputerLanguage",
"name": "R",
Expand Down Expand Up @@ -163,7 +163,7 @@
"SystemRequirements": null
},
"keywords": ["openstreetmap", "OSM", "openstreetmap-api", "osmapi", "API", "osm", "r", "r-package"],
"fileSize": "14112.96KB",
"fileSize": "14113.689KB",
"citation": [
{
"@type": "SoftwareSourceCode",
Expand All @@ -180,7 +180,7 @@
"name": "osmapiR: OpenStreetMap API",
"identifier": "10.32614/CRAN.package.osmapiR",
"url": "https://docs.ropensci.org/osmapiR/",
"description": "R package version 0.1.0.9003 \nhttps://github.com/ropensci/osmapiR",
"description": "R package version 0.1.0.9005 \nhttps://github.com/ropensci/osmapiR",
"@id": "https://doi.org/10.32614/CRAN.package.osmapiR",
"sameAs": "https://doi.org/10.32614/CRAN.package.osmapiR"
}
Expand Down
2 changes: 2 additions & 0 deletions tests/testthat/test-gps_traces.R
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ test_that("osm_get_metadata_gpx works", {
test_that("osm_get_data_gpx works", {
trk_data <- list()
with_mock_dir("mock_get_data_gpx", {
# gpx_id = 3458743: creator="JOSM GPX export" <metadata> bounds c("minlat", "minlon", "maxlat", "maxlon")
# gpx_id = 3498170: creator="Garmin Connect"
trk_data$raw <- osm_get_data_gpx(gpx_id = 3458743)
trk_data$gpx <- osm_get_data_gpx(gpx_id = 3458743, format = "gpx") # identical to xml resp but heavier mock file
## gpx responses has `content-type` = "application/gpx+xml and httptest2 save them as raw instead of xml files
Expand Down
Loading