我有多个.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")
答案 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")
这三种方法是:
writeGPX
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 中映射它。
GPX 文件是具有已定义架构 GPX 1.1 的 xml 文件。 XML 和 xml2 包都简化了我们使用 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"))
这个包打开了 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 对象。可以读取两层:
本例中将使用两个 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)
生成以下动态地图(我拍了一张快照)