将多个GPX文件合并为具有多个轨道的单个GPX文件

时间:2019-02-16 19:18:13

标签: r gpx plotkml

我有多个.gpx文件,我想使用R合并成具有多个轨道的单个文件。例如,可以在此处下载两个文件: https://github.com/twesleyb/StackOverflow/blob/master/Afternoon_Ride.gpx https://github.com/twesleyb/StackOverflow/blob/master/Evening_Run.gpx

注意:我尝试使用download.file()下载这些文件,但是.gpx文件的格式被弄乱了,所以不要这样做。手动下载它们。另外,您可以复制下面粘贴的一些数据作为最小示例。

gpx_files <- c("Evening_Run.gpx","Afternoon_Ride.gpx")

我可以使用plotKML软件包加载文件。

library(plotKML)

# Create empty list for storing .gpx files.
list_gpx <- list()

# Loop to read files, store in a list with name:
for (i in seq_along(gpx_files)){
  list_gpx[[i]] <- readGPX(gpx_files[1])
  names(list_gpx)[[i]] <- gpx_files[i]
}

gpx数据存储在数据帧轨道中。我可以从列表中提取每个,然后将它们合并到一个数据框中。

# Loop through list_gpx, get track df, clean up columns, and save in list. 
# Empty list for tracks. 
track_list <- list()

# Loop
for (i in 1:length(list_gpx)){
  track_list[[i]] <- do.call(cbind,list_gpx[[i]]$tracks[[1]])[,c(1:4)]
  if (grepl("Run",colnames(track_list[[i]]))==TRUE){
    track_list[[i]]$activity <- rep("Run",nrow(track_list[[i]]))
  }else{
    track_list[[i]]$activity <- rep("Bike",nrow(track_list[[i]]))
  }
  names(track_list[[i]]) <- c("lon","lat","ele","time","activity")
}

# Merge dataframes in track_list.
data <- do.call(rbind,track_list)

我有一个自定义函数(改编自here),用于将该数据写入新文件。结果是单个.gpx文件,同时包含两个文件的跟踪信息。

# A function for writting GPX files. 
writeGPX <- function(lat,lon,ele,time,file="file.gpx"){
  o <- c('<gpx version="1.1" creator="R">','<trk>','<trkseg>')
  o <- c(o, paste('<trkpt lat="',lat,'" lon="',lon,'"><time>',
                  paste("<ele>",ele,"</ele>",sep=""),
                  paste(gsub(' ','T', as.character(time)), 'Z', sep=''),'</time></trkpt>', sep=''))
  o <- c(o, '</trkseg>', '</trk>', '</gpx>')
  cat(o, file=file, sep='\n')
}

# Write gpx data to a new file. 
lat <- data$lat
lon <- data$lon
ele <- data$ele
time <- data$time

writeGPX(lat,lon,ele,time,file=paste(Sys.Date(),"merged.gpx",sep="_"))

问题是,这导致带有单个轨道的.gpx文件。由于这两个原始文件的开始和结束位置在不同的地方,因此当您将其加载到Google Earth中时,这会导致一个音轨的结尾与另一个音轨的开头之间出现很大的跳跃,我想避免这种情况。如何修改writeGPX函数或使用其他一些现有函数来编写具有多个轨道的单个.gpx文件?

附录:

一个简单的.gpx轨道可能看起来像这样:

<trk>
<trkseg>
<trkpt lat="40.779" lon="-74.428" />
<trkpt lat="40.777" lon="-74.418" />
</trkseg>
</trk>
</gpx>

因此,天真的解决方案可能是这样的:

<gpx version="1.1" creator="R">
<trk>
<trkseg>
<trkpt lat="40.779" lon="-74.428" />
<trkpt lat="40.777" lon="-74.418" />
</trkseg>
<trkseg>
<trkpt lat="50.779" lon="-64.428" />
<trkpt lat="50.777" lon="-64.418" />
</trkseg>
</trk>
</gpx>

但是,这不起作用(如果将其另存为.gpx并尝试将其加载到Google Earth中,则不会发生任何事情-在Google Earth中未检测到)。

谢谢!

数据

## The last 10 lines of evening_run and first ten lines of afternoon_ride:
data <- structure(list(lon = c(-79.045899, -79.045919, -79.045937, -79.045951, 
-79.045967, -79.046174, -79.04619, -79.046203, -79.046302, -79.046311, 
-79.046704, -79.046694, -79.046687, -79.046702, -79.046727, -79.046735, 
-79.046739, -79.046752, -79.046879, -79.046885), lat = c(35.898049, 
35.89805, 35.898054, 35.898059, 35.898066, 35.8981, 35.898108, 
35.898115, 35.898169, 35.898177, 35.898017, 35.898038, 35.898021, 
35.89801, 35.898004, 35.897989, 35.897964, 35.897954, 35.897897, 
35.897905), ele = c("99.6", "99.6", "99.8", "99.8", "99.8", "101.2", 
"101.2", "101.2", "101.6", "102.0", "105.8", "134.2", "134.2", 
"134.2", "107.2", "107.0", "107.2", "107.4", "107.6", "107.6"
), time = c("2019-02-06T01:34:35Z", "2019-02-06T01:34:36Z", "2019-02-06T01:34:37Z", 
"2019-02-06T01:34:38Z", "2019-02-06T01:34:39Z", "2019-02-06T01:34:52Z", 
"2019-02-06T01:34:53Z", "2019-02-06T01:34:54Z", "2019-02-06T01:35:02Z", 
"2019-02-06T01:35:07Z", "2019-02-06T00:15:59Z", "2019-02-06T00:16:00Z", 
"2019-02-06T00:16:01Z", "2019-02-06T00:16:03Z", "2019-02-06T00:16:04Z", 
"2019-02-06T00:16:05Z", "2019-02-06T00:16:09Z", "2019-02-06T00:16:10Z", 
"2019-02-06T00:16:15Z", "2019-02-06T00:16:17Z"), activity = c("Run", 
"Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", 
"Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", 
"Run")), row.names = c(1020L, 1021L, 1022L, 1023L, 1024L, 1025L, 
1026L, 1027L, 1028L, 1029L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 
10L), class = "data.frame")

1 个答案:

答案 0 :(得分:1)

正如@Dave2e 在第一条评论中指出的那样,您问题的天真解决方案是

<gpx version="1.1" creator="R">
<trk>
<trkseg>
<trkpt lat="40.779" lon="-74.428" />
<trkpt lat="40.777" lon="-74.418" />
</trkseg>
</trk>
<trk>
<trkseg>
<trkpt lat="50.779" lon="-64.428" />
<trkpt lat="50.777" lon="-64.418" />
</trkseg>
</trk>
</gpx>

每个音轨都必须在 <trk> ... </trk> 之内。您可以使用元素 name 为每个轨道命名

<trk>
<name>First track</name>
...

我将展示将两个 gpx 文件合并为一个 gpx 文件的三种方法。为简单起见,我假设您已经使用代码阅读了这两个文件,并且您有一个包含两条轨道信息(纬度、经度、海拔和时间)的数据框。我将使用您提供的数据:

df <- structure(list(lon = c(-79.045899, -79.045919, -79.045937, -79.045951, 
-79.045967, -79.046174, -79.04619, -79.046203, -79.046302, -79.046311, 
-79.046704, -79.046694, -79.046687, -79.046702, -79.046727, -79.046735, 
-79.046739, -79.046752, -79.046879, -79.046885), lat = c(35.898049, 
35.89805, 35.898054, 35.898059, 35.898066, 35.8981, 35.898108, 
35.898115, 35.898169, 35.898177, 35.898017, 35.898038, 35.898021, 
35.89801, 35.898004, 35.897989, 35.897964, 35.897954, 35.897897, 
35.897905), ele = c("99.6", "99.6", "99.8", "99.8", "99.8", "101.2", 
"101.2", "101.2", "101.6", "102.0", "105.8", "134.2", "134.2", 
"134.2", "107.2", "107.0", "107.2", "107.4", "107.6", "107.6"
), time = c("2019-02-06T01:34:35Z", "2019-02-06T01:34:36Z", "2019-02-06T01:34:37Z", 
"2019-02-06T01:34:38Z", "2019-02-06T01:34:39Z", "2019-02-06T01:34:52Z", 
"2019-02-06T01:34:53Z", "2019-02-06T01:34:54Z", "2019-02-06T01:35:02Z", 
"2019-02-06T01:35:07Z", "2019-02-06T00:15:59Z", "2019-02-06T00:16:00Z", 
"2019-02-06T00:16:01Z", "2019-02-06T00:16:03Z", "2019-02-06T00:16:04Z", 
"2019-02-06T00:16:05Z", "2019-02-06T00:16:09Z", "2019-02-06T00:16:10Z", 
"2019-02-06T00:16:15Z", "2019-02-06T00:16:17Z"), activity = c("Run", 
"Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", 
"Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", "Run", 
"Run")), row.names = c(1020L, 1021L, 1022L, 1023L, 1024L, 1025L, 
1026L, 1027L, 1028L, 1029L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 
10L), class = "data.frame")

这三种方法是:

  1. 使用自定义函数,如 writeGPX
  2. 使用包 xml2 将 xml 树写入文件
  3. 使用包 sf 使用“GPX”驱动程序导出 sf 对象

自定义功能

library(stringr)
library(purrr)
library(glue)

与使用 writeGPX 之类的函数不同,我更喜欢创建四个函数,让您可以更好地控制创建多个轨道。也可以推广到添加航点和路线。

# Creates a track point list
gpx_trkpt <- function(lat, lon, ele = NULL, time = NULL){
  trkpt <- str_c("<trkpt lat=", double_quote(lat), " lon=", double_quote(lon), ">")
  if (!is.null(ele) && !is.na(ele)) trkpt <- c(trkpt, str_c("<ele>", ele, "</ele>"))
  ## check time is a in character with format %Y-%m-%dT%H:%M:%sZ (UTC time zone)
  if (!is.null(time) && !is.na(time)) trkpt <- c(trkpt, str_c("<time>", time, "</time>"))
  trkpt <- c(trkpt, "</trkpt>")
  return(trkpt)
}

# creates track
gpx_trk <- function(df, name = NULL) {
  trk <- "<trk>"
  if (!is.null(name)) trk <- c(trk, str_c("<name>", name, "</name>"))
  trk <- c(trk, "<trkseg>")
  list_resu <- pmap(df, gpx_trkpt) %>% unlist()
  trk <- c(trk, list_resu, "</trkseg>", "</trk>")
  return(trk)
}

# creates the start of gpx file
gpx_header <- function(creator = "R - pep"){
  header <- c("<?xml version='1.0' encoding='UTF-8' ?>",
    str_c("<gpx version=", double_quote("1.1"), " creator=", double_quote(creator)),
    "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"",
    "xmlns=\"http://www.topografix.com/GPX/1/1\"",
    "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">")
  return(header)
}

# creates the end of a gpx file
gpx_end <- function(){
  return("</gpx>")
}

使用这些函数,您可以构建包含两条轨道的文件。我将包括为每个曲目添加名称的可能性。

track1 <- gpx_trk(df[1:10, -5], name = "track 1")
track2 <- gpx_trk(df[11:20, -5], name = "track 2")

gpx_file_content <- c(gpx_header(), track1, track2, gpx_end()) %>% 
  str_c(collapse = "\n")

我们将 gpx_file_content 保存到一个扩展名为“gpx”的文件中

cat(gpx_file_content, file = "output_1.gpx")

您可以使用 GPSVisualizer 读取创建的 gpx 文件。最后,我将解释如何阅读它并在 R 中映射它。

使用xml2

GPX 文件是具有已定义架构 GPX 1.1 的 xml 文件。 XMLxml2 包都简化了我们使用 xml 文件的工作。我会选择 xml2,因为它由无所不在的 Hadley Wickham xml2

维护
library(xml2)

在本例中,我们创建了几个函数来构建 gpx 根节点和一个 trk 节点。

add_gpx <-  function(creator = "R - pep"){
  xml_new_root("gpx",
  version = "1.1",
  creator = creator,
  "xmlns:xsi"="http://www.w3.org/2001/XMLSchema-instance",
  xmlns="http://www.topografix.com/GPX/1/1",
  "xsi:schemaLocation"="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd",
  "xmlns:gpxtpx"="http://www.garmin.com/xmlschemas/TrackPointExtension/v1")
}

add_trk <- function(parent, df, name = NULL) {
  trk_node <- xml_add_child(parent, "trk")
  n_points <- nrow(df)
  if(!is.null(name)) xml_add_child(trk_node, "name", name)
  # add trk, name, trkseg, trkpt
  trkseg_node <- xml_add_child(trk_node, "trkseg")
  for(k in 1:n_points) {xml_add_child(trkseg_node, "trkpt")}
  trkpt_nodes <- xml_find_all(trkseg_node, ".//trkpt")
  # create attribute lon, lat
  xml_set_attr(trkpt_nodes, "lat", df$lat)
  xml_set_attr(trkpt_nodes, "lon", df$lon)
  # create nodes ele and time if they exist 
  if("ele" %in% names(df)) xml_add_child(trkpt_nodes, "ele", df$ele)
  if("time" %in% names(df)) xml_add_child(trkpt_nodes, "time", df$time)
  return(invisible(parent))
}

现在,您使用两个轨道节点构建完整的 xml 树

doc2 <- add_gpx()
add_trk(doc2, df[1:10, ], name = "track_1")
add_trk(doc2, df[11:20, ], name = "track_2")
resp <- write_xml(doc2, file = "output_2.gpx", options = c("format", "no_empty_tags"))

使用sf

这个包打开了 R 中地理计算的大门。有很多很好的资源可以了解它,我最喜欢包主页 sf 和书籍 Geocomputation with R

首先创建一个 sf 对象,其中包含轨道几何(纬度和经度)以及每个点的高程和时间属性。使用 GPX 文件中使用的坐标参考系统为坐标赋予几何意义:WGS84,EPSG 代码为 4326。

library(sf)
tracks_geometry <- matrix(c(df$lon, df$lat), ncol = 2) %>% #coordinates
  st_multipoint() %>% # create a multipoint  
  st_sfc(crs = 4326) %>% 
  st_cast("POINT")
tracks_points <- st_sf(df[, c("ele", "time")], geometry = tracks_geometry)

我们需要创建一个新变量来标识每个轨道。根据 GPX 驱动程序 gdal 的规范,每个轨道用不同的 track_seg_id 标识,轨道中的每个点由 track_seg_point_id 标识。也可以在每个轨道的第一个点定义每个轨道的名称。

tracks_points$track_fid <- c(rep(1, 10), rep(2, 10))
tracks_points$track_seg_id <- 0
tracks_points$track_seg_point_id <- c(1:10, 1:10)

利用tracks_points sf 对象中的这些附加信息,我们使用GPX 驱动程序将其导出到gpx 文件

st_write(tracks_points, dsn = "output_3.gpx", driver = "GPX", layer = "track_points")

我没有设法为每个轨道添加名称,也没有为每个数据点添加时间元素。所以这第三种方法不如前两种方法完整。尽管如此,我还是包含了它,因为 gpx 文件可以直接读入 sf 对象,并且可以在地图中表示,所有这些都在 R 中!

gpx 文件可以用 st_read 读入一个 sf 对象。可以读取两层:

  1. 具有两个轨道对象的“轨道”层
  2. “track_points”层,每个轨道中的单个点

本例中将使用两个 sf 对象

sf_tracks <- st_read(dsn = "output_2.gpx", layer = "tracks")
sf_track_points <- st_read(dsn = "output_2.gpx", layer = "track_points")

带轨道的地图

使用 leaflet (CRAN) 包,我们可以绘制我们之前创建的 gpx 文件中包含的两条轨道。

library(leaflet)
coord <- st_coordinates(sf_track_points)
# Find mid point of all coordinates to center the map
center_plot <- apply(coord, 2, mean)
# Find mid points of each track to locate track pop-ups
track_mid <- matrix(0, 2, 2)
track_mid[1, ] <- apply(coord[1:10,], 2, mean)
track_mid[2, ]  <- apply(coord[11:20,], 2, mean)
names(center_plot) <- NULL
names(track_mid) <- NULL
leaflet(sf_tracks) %>% addTiles() %>%
  addPolylines(color = c("red", "blue"), group = sf_tracks$name) %>% 
  addScaleBar(position = "bottomleft") %>% 
  addLayersControl(
    overlayGroups = sf_tracks$name,
    options = layersControlOptions(collapsed = FALSE)) %>% 
  addPopups(lng=track_mid[, 1], lat=track_mid[, 2], sf_tracks$name) %>% 
  setView(lng=center_plot[1], lat=center_plot[2], zoom = 18)

生成以下动态地图(我拍了一张快照)

enter image description here