Skip to content

Commit

Permalink
Merge pull request #29 from jmaspons/autopagination_gps_points
Browse files Browse the repository at this point in the history
Allow to fetch gps points from all & more than one page (`osm_get_points_gps()`)
  • Loading branch information
jmaspons authored Jun 19, 2024
2 parents 13fb451 + 29d830d commit 24c573a
Show file tree
Hide file tree
Showing 11 changed files with 108,556 additions and 1,494 deletions.
145 changes: 145 additions & 0 deletions R/osm_get_points_gps.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#' Get GPS Points
#'
#' Use this to retrieve the GPS track points that are inside a given bounding box (formatted in a GPX format).
#'
#' @param bbox Coordinates for the area to retrieve the notes from (`left,bottom,right,top`). Floating point numbers in
#' degrees, expressing a valid bounding box. The maximal width (`right - left`) and height (`top - bottom`) of the
#' bounding box is 0.25 degree.
#' @param page_number Specifies which groups of 5,000 points, or page, to return. The API call does not return more
#' than 5,000 points at a time. In order to retrieve all of the points for a bounding box, set `page_number = -1`.
#' When this parameter is 0 (zero), the command returns the first 5,000 points; when it is 1, the command returns
#' points 5,001–10,000, etc. A vector is also valid (e.g. `0:2` to get the first 3 pages).
#' @param format Format of the output. Can be `"R"` (default) or `"gpx"`.
#'
#' @note In violation of the [GPX standard](https://www.topografix.com/GPX/1/1/#type_trksegType) when downloading public
#' GPX traces through the API, all waypoints of non-trackable traces are randomized (or rather sorted by lat/lon) and
#' delivered as one trackSegment for privacy reasons. Trackable traces are delivered, sorted by descending upload
#' time, before the waypoints of non-trackable traces.
#'
#' Private traces without `name`, `desc` and `url` can be separated in different items in the result if they get
#' split due to server pagination. Public traces are united using matching URL.
#'
#' @return
#' If `format = "R"`, returns a list of data frames with the points for each trace. For public traces, the data frame
#' include the attributes `name`, `desc` and `url`.
#'
#' ## `format = "gpx"`
#' Returns a [xml2::xml_document-class] with the following format:
#' ``` xml
#' <?xml version="1.0" encoding="UTF-8"?>
#' <gpx version="1.0" creator="OpenStreetMap.org" xmlns="http://www.topografix.com/GPX/1/0">
#' <trk>
#' <name>20190626.gpx</name>
#' <desc>Footpaths near Blackweir Pond, Epping Forest</desc>
#' <url>https://api.openstreetmap.org/user/John%20Leeming/traces/3031013</url>
#' <trkseg>
#' <trkpt lat="51.6616100" lon="0.0534560">
#' <time>2019-06-26T14:27:58Z</time>
#' </trkpt>
#' ...
#' </trkseg>
#' ...
#' </trk>
#' ...
#' </gpx>
#' ```
#' * This response is NOT wrapped in an OSM xml parent element.
#' * The file format is GPX Version 1.0 which is not the current version. Verify that your tools support it.
#'
#' @family get GPS' functions
#' @export
#'
#' @examples
#' pts_gps <- osm_get_points_gps(bbox = c(-0.3667545, 40.2153246, -0.3354263, 40.2364915))
#' ## bbox as a character value also works. Equivalent call:
#' # pts_gps <- osm_get_points_gps(bbox = "-0.3667545,40.2153246,-0.3354263,40.2364915")
#' pts_gps
#'
#' ## get attributes
#' lapply(pts_gps, function(x) attributes(x)[c("name", "desc", "url")])
osm_get_points_gps <- function(bbox, page_number = 0, format = c("R", "gpx")) {
format <- match.arg(format)
bbox <- paste(bbox, collapse = ",")

if (page_number >= 0) { # concrete pages
outL <- lapply(page_number, function(x) .osm_get_points_gps(bbox = bbox, page_number = x, format = format))
} else { # get all pages
outL <- list()
n <- 1
i <- 1
while (n > 0) {
outL[[i]] <- .osm_get_points_gps(bbox = bbox, page_number = i - 1, format = format)
if (format == "R") {
n <- length(outL[[i]])
} else { # format == "gpx"
n <- length(xml2::xml_children(outL[[i]]))
}
i <- i + 1
}
if (length(outL) > 1) { # non empty result in the first page
outL <- outL[-length(outL)]
}
}

if (length(outL) == 1) {
return(outL[[1]])
}

if (format == "R") {
# rbind the last and first trkseg of consecutive pages if they have the same url (non private traces)
url_1n_page <- lapply(outL, function(x) names(x)[c(1, length(x))])
# TODO: length(url_1n_page[[i]]) == 1 OR trace divided in > 2 pages
for (i in seq_along(url_1n_page)[-1]) {
if (url_1n_page[[i]][1] == url_1n_page[[i - 1]][2] && "" != url_1n_page[[i]][1]) {
n <- length(outL[[i - 1]])
if (n == 0) next
outL[[i - 1]][[n]] <- rbind(outL[[i - 1]][[n]], outL[[i]][[1]])
outL[[i]][[1]] <- NULL
}
}
outL <- outL[vapply(outL, length, FUN.VALUE = integer(1)) > 0]

out <- do.call(c, outL)
class(out) <- c("osmapi_gpx", "list")
} else { # format == "gpx"
# unite the last and first trkseg of consecutive pages if they have the same url (non private traces)
# lapply(outL, function(x) xml2::xml_find_all(x, "//url", flatten = FALSE)) # TODO: doesn't work :(
url_1n_page <- lapply(outL, function(x) {
trkseg <- xml2::xml_children(x)
trkseg_1n <- trkseg[c(1, length(trkseg))]
do.call(c, lapply(trkseg_1n, function(y) {
if ("url" %in% xml2::xml_name(xml2::xml_children(y))) {
xml2::xml_text(xml2::xml_child(y, search = 3)) # TODO: search = "url" fail
} else {
""
}
}))
})

for (i in seq_along(url_1n_page)[-1]) {
# TODO: length(url_1n_page[[i]]) == 1 OR trace divided in > 2 pages
if ("" != url_1n_page[[i]][1] && url_1n_page[[i]][1] == url_1n_page[[i - 1]][2]) {
n <- xml2::xml_length(outL[[i - 1]])
part_0 <- xml2::xml_child(outL[[i - 1]], n)
part_1 <- xml2::xml_child(outL[[i]], 1)
trkseg_0 <- xml2::xml_child(part_0, search = 4) # TODO: search = "trkseg" fail
trkseg_1 <- xml2::xml_child(part_1, search = 4) # TODO: search = "trkseg" fail

lapply(xml2::xml_children(trkseg_1), function(point) {
xml2::xml_add_child(trkseg_0, point)
})

xml2::xml_remove(part_1)
}
}

out <- xml2::xml_new_root(outL[[1]])
for (i in seq_along(outL[-1]) + 1) {
lapply(xml2::xml_children(outL[[i]]), function(node) {
xml2::xml_add_child(out, node)
})
}
}

return(out)
}
6 changes: 3 additions & 3 deletions R/osmapi_gps_traces.R
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@
#' * This response is NOT wrapped in an OSM xml parent element.
#' * The file format is GPX Version 1.0 which is not the current version. Verify that your tools support it.
#'
#' @family get GPS' functions
#' @export
# @family get GPS' functions
#' @noRd
#'
#' @examples
#' pts_gps <- osm_get_points_gps(bbox = c(-0.3667545, 40.2153246, -0.3354263, 40.2364915))
#' ## bbox as a character value also works. Equivalent call:
#' # pts_gps <- osm_get_points_gps(bbox = "-0.3667545,40.2153246,-0.3354263,40.2364915")
#' pts_gps
osm_get_points_gps <- function(bbox, page_number = 0, format = c("R", "gpx")) {
.osm_get_points_gps <- function(bbox, page_number = 0, format = c("R", "gpx")) {
format <- match.arg(format)

req <- osmapi_request()
Expand Down
20 changes: 13 additions & 7 deletions man/osm_get_points_gps.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 24c573a

Please sign in to comment.