R: Добавить данные, когда точки перекрываются / находятся на расстоянии;добавить прямоугольник буфера в set1, добавить радиус в set2 - PullRequest
1 голос
/ 29 мая 2019

У меня есть база данных (netcdf) вихрей, движущихся через океан, и множество следов рыбы, делающих то же самое. Я пытаюсь добавить информацию о вихрях к следу рыбы, когда рыба находится в пределах досягаемости вихря (местоположения в пределах (изменяющегося) порогового расстояния и даты идентичны). Структура данных, сложности и воспроизводимый пример приведены ниже. Я подозреваю, что это не супер сложно, я просто завязываю себя в узлы, пытаясь найти правильное решение. Заранее спасибо за любые мысли! fish&eddyboxes

Структура данных / воспроизводимый пример: 3 дня рыбы и вихревой дорожки

library(lubridate)
fish <- data.frame(lat = c(42.1, 42.6, 43.2), lon = c(-10, -10.1, -10.2), date = ymd(c("1990-01-01", "1990-01-02", "1990-01-02")))
eddy <- data.frame(lat = c(44, 42.3, 40), lon = c(-15, -10.1, -6), date = ymd(c("1990-01-01", "1990-01-02", "1990-01-02")), track = c(1,1,1), radius = c(81,82,83), vals = c(11,12,13))

Вот тут немного сложнее:

  1. круговой вихревой буфер с переменным радиусом

Размер вихря (из которого рассчитывается перекрытие или расстояние) задается в столбце радиуса, поэтому:

sf_eddy <- st_as_sf(eddy)
st_buffer(sf_eddy, dist = radius)

Для st_buffer «Все операции работают для каждого объекта», поэтому я не уверен, будет ли это рассматривать sf_eddy как один объект (возможно, (много) линейная строка) или ряд отдельных точек. Я полагаю, что могу поиграть с этим, пока не получу его на работу, так что каждый вихревой день (n = 151937) представляет собой буферный круг sf, к которому я могу предположительно добавить его vals, если они еще не включены.

  1. Заданный на расстоянии латунный прямоугольный буфер рыбы

Фиксированное расстояние ошибки для положений рыбы составляет + -1,9 ° широты, + -0,77 ° долготы. Так что концептуально я хочу создать высокий тонкий многоугольник вокруг каждого дня рыбы. st_buffer не позволяет этого, но могут быть и другие варианты , возможно, путем создания многоугольника вокруг каждого центроида согласно square примеру здесь .

  1. Если рыба и любой вихрь перекрываются в пространстве и времени, добавьте вихревые значения к рыбе

Я мог бы потенциально сократить рабочую нагрузку, выполнив только вышеуказанные шаги, когда eddy $ date == fish $ date? Хотя расчет вихрей будет разовым, и я могу сохранить результаты. Я подозреваю, что расчет рыбных ящиков не будет особенно обременительным. В любом случае:

fish$vals <- rep(NA, nrow(fish))    
sf_fish <- sf_as_sf(fish)
for (i in seq(along = sf_fish)){
if(st_intersects(sf_fish[i,],sf_eddy)) sf_fish[i,"vals"] <- sf_eddy$vals}

Это, вероятно, ужасный способ сделать это, очевидно.

Но является ли какой-либо из этих способов хоть немного разумным способом решения этой проблемы, или я упускаю гораздо более изящные решения из-за своего любительского знания sf, sp, rgeos и т. Д.? У меня 166 рыбных файлов со средним числом мест 286 дней = всего 47476 поисков. Было бы хорошо / быстро создать индекс пар совпадений дат рыба / вихрь (может быть несколько вихрей в один и тот же день), а затем проверять только пересечения на них? Поскольку мне нужно добавить eddy$vals (на самом деле 2 или 3 столбца) к fish, было бы лучше использовать пространственное соединение, возможно, st_join?

Редактировать: комментарии и выходные странности карты: 600 000 выводов ggplot рыбы / вихревой строки:

North atlantic fish & eddy tracks

Похоже, мне нужно исправить проекцию, так как вихри не печатаются к западу от 0. Теперь они точно в том же CRS; возможно, оригиналы 0-360, а не -180: 180. Кроме того, в то время как коробки с рыбой печатаются хорошо, вихри слишком малы: имеют значение r = 80 км = ~ 1 градус @ 45N, и они определенно печатают намного меньше, чем это. Я тоже подумаю об этом. Также: возникнет ли проблема для for (), если в один день существуют 2 вихря?

Edit2: оригиналы были 0: 360, которые я исправил. Радиусы вихрей все еще слишком малы: small radii eddies Согласно обсуждению кода, оригинальный вихревой файл с латлонами был преобразован с st_as_sf в crs4326 (wgs84), а затем st_transform(sf_eddy,6931), который является Северной Атлантикой, согласно изображению выше. Столбец геометрии в результирующем файле имеет размер POLYGON, размер XY, значения в проецируемых координатах, а не в латлонах, например, sf_eddy_buffered$geometry[1] bbox: xmin -4894790 ymin -2418555 xmax -4894648 ymax -2418413. speed_radius равно 71, поэтому разница между xmin и xmax составляет 142, что составляет 71 * 2, что является правильным. Разве единица радиуса не равна единице проекционного расстояния? Примерами Стюарта были 81000. Единица радиуса вихря: км. Единица проекции / геометрия: https://epsg.io/6931 говорит, что единицы измерения действительно являются метрами. Умножьте радиус на 1000 и попробуйте снова: big beautiful eddies Такэто все хорошо. Только что попробовал обновленный код st_join, думаю, что он может использовать некоторую работу - возможно, это будет длиться вечно, вероятно, из-за 3639209 рядов вихрей против 461 ряда рыб. Также он создал несколько строк для нескольких вихрей для одной и той же рыбы в один и тот же день. Потенциально функция относительно большой возможности перекрытия, учитывая буфер ошибок вокруг рыбы. Я подозреваю, что смогу исправить это, отметив дубликаты и удалив дубликаты, которые находятся дальше всего. Текущий способ (через v и rbind) лучше, чем предыдущий (прямое присвоение ячейки значения) IMO, так как в противном случае несколько значений будут отправлены в одну ячейку, либо сбой, либо перезапись без вывода сообщений.

Для ускорения кода я подумал, что я мог бы установить для файла вихря только совпадение в диапазоне дат текущей рыбы и использовать это подмножество в цикле соединения. Цикл соединения уже подразделяет файлы fish и eddy по дате в любом случае, так что, может быть, это избыточно, или, возможно, уменьшение размера вихревых данных, над которыми нужно работать, будет каждый раз уменьшать нагрузку на CPU / RAM?

Edit3: это УДОВОЛЬСТВЕННО ускоряет вещи. Добавление простого счетчика циклов для thisDate показало, что оно завершено на 275 из 461 строки. И создал файл длиной 481. Возможно, есть много дней, когда рыба не была вихрем (цикл thisDate приводит к отсутствию v и rbind), и много дней, когда рыба находилась в 2+ вихрях (* 1077) * цикл приводит к многорядным v и rbind). По-прежнему ощущается, что он должен обработать 461 цикл, однако ...

Edit4: небольшая настройка:

v <- st_join(thisFishRow, thisEddyRow, left = F, largest = TRUE)

largest означает, что он присоединится к вихрю только с наибольшей площадью перекрытия, в результате чего максимальная длина строки v будет равна 1, и, следовательно, 1 значение в день будет максимально. Тем не менее, нюансы путей означают, что рыба может двигаться вперед и назад между различными продолжающимися вихрями. Мое подозрение (основанное на аналогичном исследовании) состоит в том, что они, скорее всего, останутся в сильной антициклонической вихревой системе. Радиус (и, следовательно, площадь) вихревых элементов управления перекрывают, возможно, больше, чем положение. Для полуторамесячного раздела я нашел наибольшее число перекрывающихся треков вихрей, отразившихся между 3 вихрями. Вихревой трек № 217008 является наиболее мощным антициклоническим. Вот дерьмовый сюжет: 1.5monthoverlaps Как вы можете видеть, 217008 находится прямо в центре окна ошибки рыбы, тогда как более крупные, более рассеянные, более слабые вихри 217343 и 39625 смещены к краю. Однако их больший размер часто приводит к тому, что они сталкиваются с вершиной, потому что у них больше места, и близость центроида не учитывается. Итак: если fishbox перекрывается с eddy в тот же день, то включите eddy в короткий список (thisFishRow & thisEddyRow остаются прежними). Затем: выберите вихрь из списка, основываясь на минимальном расстоянии от центроидов (что-то вместо st_join). Продолжение следует!

Edit5:

fishNearEddies <- NULL
for (thisDate in sf_df_nona$date) {
 thisFishRow <- sf_df_nona[sf_df_nona$date == thisDate, ]
 thisEddyRow <- sebDateSub[sebDateSub$date == thisDate, ]
 overlapToday <- st_join(thisFishRow, thisEddyRow, left = F)
 if (nrow(overlapToday) > 0) {
  overlapEddies <- thisEddyRow[thisEddyRow$track %in% overlapToday$track,] 
  st_distance(x = thisFishRow, y = overlapEddies, by_element = TRUE) #0 0 due to overlap min dist = 0
 } #close if
} #close for

st_distance не будет работать, поскольку пространственные объекты (буферная ячейка и буферная окружность) перекрываются, поэтому минимальное расстояние = 0. Мне также нужны центроиды для проверки, я полагаю?

Edit6: Final Edit с рабочим кодом и ответ наградить Стюарта за всю его помощь. Еще раз спасибо, сэр.

# overlap join loop####
fishNearEddies <- NULL
EdCentroidDist <- NULL
counter <- 1
ofhowmany <- length(sf_df_nona$date)
for (thisDate in sf_df_nona$date) {
  thisFishRow <- sf_df_nona[sf_df_nona$date == thisDate, ] #will be 1 row per day
  thisEddyRow <- sebDateSub[sebDateSub$date == thisDate, ] #all eddies that day, could be multi rows
  overlapToday <- st_join(thisFishRow, thisEddyRow, left = F) #will join only if they spatial intersect and (already) time match
  if (nrow(overlapToday) > 0) {
    # now need to join based on closest centroid and NOT on highest overlap
    overlapEddies <- thisEddyRow[thisEddyRow$track %in% overlapToday$track,] #subset TER by overlap tracks
    fishcentroid <- st_centroid(thisFishRow)
    eddycentroid <- st_centroid(overlapEddies)
    fishEdDists <- st_distance(x = fishcentroid, y = eddycentroid, by_element = TRUE) #result vector corresponding to overlapEddies rownumbers
    fishNearEddies <- rbind(fishNearEddies, overlapEddies[which.min(fishEdDists),]) #row index for overlapEddies, no index but has date
    EdCentroidDist <- rbind(EdCentroidDist, as.numeric(min(fishEdDists)))
  } #close if
  print(paste0(counter, " of ", ofhowmany, " fish days"))
  counter <- counter + 1
} #close for

if (!is.null(fishNearEddies)) {  #if there are overlaps, do processing. If not it'll fail

  fishNearEddies %<>% as.data.frame() %>% # convert to nonspatial so I can remove buffer column
    select(-geometry) %>% # remove geometry column. Attributes still remain. Whatever?
    cbind(EdCentroidDist) #add to FNE as column

1 Ответ

2 голосов
/ 29 мая 2019

Вот что вам нужно:

library(lubridate)
library(sf)
library(ggplot2)

# sample data
fish <- data.frame(
    lat = c(41.1, 43.6, 44.2),
    lon = c(-7, -11, -15),
    date = ymd(c("1990-01-01", "1990-01-02", "1990-01-03"))
)

# Add a blank column for the eddy values we want
fish$vals <- rep(NA, nrow(fish))

eddy <- data.frame(
    lat = c(44, 42.3, 40),
    lon = c(-6, -10.1, -15),
    date = ymd(c("1990-01-01", "1990-01-02", "1990-01-03")),
    track = c(1,1,1),
    radius = c(81000,82000,83000),
    vals = c(11,12,13)
)

# Convert eddy to simple features, using WGS84 CRS
sf_eddy <- st_as_sf(eddy,  coords = c("lon", "lat"), crs=4326)

# Transform from geographical to projected so we can buffer correctly. You might need to pick a different CRS
sf_eddy <- st_transform(sf_eddy, 3035) # ETRS89 / LAEA Europe

# Buffer the eddy points based on their radii
sf_eddy_buffered <- st_buffer(sf_eddy, dist = sf_eddy$radius)

# Add error to fish position. There's probably a better way to do this.
fishErrLat <- 1.9
fishErrLon <- 0.77
fish$buffer <- paste('POLYGON((',
                    fish$lon - fishErrLon, ' ', fish$lat + fishErrLat, ',',
                    fish$lon + fishErrLon, ' ', fish$lat + fishErrLat, ',',
                    fish$lon + fishErrLon, ' ', fish$lat - fishErrLat, ',',
                    fish$lon - fishErrLon, ' ', fish$lat - fishErrLat, ',',

                    fish$lon - fishErrLon, ' ', fish$lat + fishErrLat,
                    '))',
                    sep=''
                )

# Convert fish to simple features, using WGS84 CRS
sf_fish <- st_as_sf(fish, wkt='buffer', crs=4326)

# Transform from geographical to projected
sf_fish <- st_transform(sf_fish, 3035)


#Plot what we've got so far    
g <- ggplot() + 
        geom_sf(data=sf_eddy_buffered, aes(fill=date)) +
        geom_sf(data=sf_fish, aes(fill=date))

print(g)

GGPlot of eddies and fish

# Check for overlap
fishNearEddies <- NULL

for (thisDate in unique(sf_fish$date)) {

    thisFishRow <- sf_fish[sf_fish$date==thisDate, ]
    thisEddyRow <- sf_eddy_buffered[sf_eddy_buffered$date==thisDate, ]

    v <- st_join(thisFishRow, thisEddyRow, left=F)
    if (nrow(v) > 0) {
        fishNearEddies <- rbind(fishNearEddies, v)
    }

}

И проверьте результаты:

> fishNearEddies
Simple feature collection with 1 feature and 7 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 2588691 ymin: 2402830 xmax: 2703342 ymax: 2624501
epsg (SRID):    3035
proj4string:    +proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
   lat lon     date.x     date.y track radius vals                         buffer
2 43.6 -11 1990-01-02 1990-01-02     1  82000   12 POLYGON ((2644792 2624501, ...

Это даст вам только тех рыб, которые пересекаются с вихрем.

...