Объединение нескольких файлов GPX в один файл GPX с несколькими дорожками

У меня есть несколько файлов .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 хранятся во фрейме данных, tracks. Я могу извлечь каждый из списка, а затем объединить их в один фрейм данных.

# 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)

У меня есть пользовательская функция (адаптированная из здесь) для записи эти данные в новый файл. В результате получается один файл .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")

person twb10    schedule 16.02.2019    source источник
comment
Похоже, ваш файл записывает все точки в 1 узел дорожки, я подозреваю, что вам нужно будет создать несколько ‹trk›‹/trk› для каждого файла.   -  person Dave2e    schedule 16.02.2019
comment
Это может помочь gis. stackexchange.com/questions/190687/   -  person M--    schedule 26.03.2019
comment
Я все еще пытаюсь решить это для себя. @twb10, ты когда-нибудь это понимал?   -  person Nova    schedule 11.08.2020
comment
Привет @Nova, это было для побочного проекта, над которым я работал некоторое время назад. Я думаю, что в конце концов нашел небольшое решение, но похоже, что код, который я написал для этого, отсутствует на этом ПК. Я проверю свой другой компьютер позже. Надеюсь, я не забанил его по какой-то причине =(   -  person twb10    schedule 11.08.2020
comment
К счастью, у меня есть доступ к ArcGIS Pro, поэтому в качестве быстрого исправления я использовал инструмент разнесения, чтобы преобразовать составные объекты в объекты, состоящие из одной части, затем импортировал в R и экспортировал в gpx. Я хотел бы знать, как сделать эту часть в R.   -  person Nova    schedule 11.08.2020
comment
Эй, мне жаль, что у меня больше нет кода. Не уверен, почему я удалил его. Если вы найдете решение (или даже частичное решение) с использованием R или любого другого языка, пожалуйста, опубликуйте его здесь для потомков. Удачи, Тайлер.   -  person twb10    schedule 12.08.2020


Ответы (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 для экспорта объекта sf с помощью драйвера GPX

Пользовательская функция

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 представляют собой файлы xml с определенной схемой GPX 1.1. Пакеты XML и xml2 упрощают нам жизнь при работе с файлами xml. Я выберу xml2, потому что его поддерживает вездесущий Хэдли Уикхем, 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 и книга Геовычисления с 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)

С этой дополнительной информацией в sf-объекте track_points мы экспортируем ее в файл gpx с помощью драйвера GPX.

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

Мне не удалось ни добавить имя к каждой дорожке, ни добавить элемент времени к каждой точке данных. Так что этот третий метод не так совершенен, как два предыдущих. Тем не менее, я включил его, потому что файлы gpx могут быть прочитаны непосредственно в объекты sf, и они могут быть представлены на карте, и все это в R!

Файл gpx можно прочитать в объект sf с помощью st_read. Можно прочитать два слоя:

  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)

который генерирует следующую динамическую карту (я сделал ее снимок)

введите здесь описание изображения

person josep maria porrà    schedule 03.05.2021