Правильный способ построения климатических данных на нерегулярной сетке

Я задал этот вопрос как часть Эффективного способа построения данных по нерегулярной сетке, но общая обратная связь заключалась в том, чтобы разделить исходный вопрос на более управляемый ломти. Следовательно, этот новый вопрос.

Я работаю со спутниковыми данными, организованными на нерегулярной двумерной сетке, размеры которой являются сканированием (по размеру трека, то есть по оси Y) и по наземному пикселю (по размеру дорожки, то есть по оси X). Информация о широте и долготе для каждого центрального пикселя хранится в вспомогательных переменных координат, а также в четырех координатных координатах угла (координаты широты и долготы указаны на эталонном эллипсоиде WGS84).

Позвольте построить набор данных игрушек, состоящий из потенциально нерегулярной сетки 12x10 и связанных с ней измерений температуры поверхности.

library(pracma) # for the meshgrid function
library(ggplot2)

num_sl <- 12 # number of scanlines
num_gp <- 10 # number of ground pixels
l <- meshgrid(seq(from=-20, to=20, length.out = num_gp), 
              seq(from=30, to=60, length.out = num_sl))

lon <- l[[1]] + l[[2]]/10
lat <- l[[2]] + l[[1]]/10

data <- matrix(seq(from = 30, to = 0, length.out = num_sl*num_gp), 
               byrow = TRUE, nrow = num_sl, ncol = num_gp) +
  matrix(runif(num_gp*num_sl)*6, nrow = num_sl, ncol = num_gp)

df <- data.frame(lat=as.vector(lat), lon=as.vector(lon), temp=as.vector(data))

Данные lon и lat содержат координаты центрального пикселя, как указано в исходном продукте, с которым я работаю, хранятся в виде двумерной матрицы, оси которой являются ground_pixel (ось X) и scanline (ось Y), Матрица data - те же размеры - содержит мои измерения. Затем я выравниваю три матрицы и сохраняю их в кадре данных.

Я хотел бы построить пиксели земли (как четырехугольники) на карте, заполненные соответственно измерением температуры.

Используя плитки, я получаю:

ggplot(df, aes(y=lat, x=lon, fill=temp)) + 
  geom_tile(width=2, height=2) +
  geom_point(size=.1) +
  borders('world', colour='gray50', size=.2) + 
  coord_quickmap(xlim=range(lon), ylim=range(lat)) +
  scale_fill_distiller(palette='Spectral') +
  theme_minimal()

Использование плит

Но это не то, что я хочу. Я мог бы играть с width и height, чтобы плитки "касались друг друга", но, конечно, это даже не приблизилось бы к моей желаемой цели, которая заключается в построении фактических проецируемых пикселов земли на карте. < ш > Python xarray может, например, автоматически выводить границы пикселей с учетом координат центра пикселей:

Решение Xarray

Вопрос

Есть ли способ достичь тех же результатов в R, то есть: автоматически ли границы пикселов выводятся из центров пикселей и нарисовать пиксели в виде заполненных полигонов на карте? Может быть, с помощью пакета sf?

Я вижу это в ответ на этот question, но ответ, который относится к использованию sf, для меня немного неясен, так как он с различными проекциями и потенциально регулярными сетками, тогда как в моем случае я полагаю, что мне не нужно повторно проектировать мои данные, и, кроме того, мои данные не находятся на регулярной сетке.

Если это невозможно, я полагаю, что я могу прибегнуть к использованию информации о границах пикселей в своих продуктах, но, возможно, что тема для другого вопроса должна оказаться нелегкой для решения этой проблемы.

Ответ 1

Вот один из способов сделать это. Там может быть что-то попроще, но это работает.

Сначала я собираюсь использовать растровый пакет для управления координатами. Растры, которые я создаю здесь, являются "нетрадиционными" в том смысле, что содержащиеся в них значения являются данными о местоположении. Но использование растров, а не матриц для этого дает доступ к нескольким полезным функциям, таким как extend и, что наиболее полезно, resample с ее функцией билинейной интерполяции, которую я буду использовать для поиска вершин.

library(raster)
latr = raster(lat)
lonr = raster(lon)

find.vertices = function(m){
  r = raster(m)
  vertices = raster(matrix(nrow = dim(r)[1]+1, ncol = dim(r)[2]+1))
  x = extend(r, c(1,1))
  x[1,] = 2*x[2,] - x[3,]
  x[dim(x)[1],] = 2*x[dim(x)[1]-1,] - x[dim(x)[1]-2,]
  x[,1] = 2*x[,2] - x[,3]
  x[,dim(x)[2]] = 2*x[,dim(x)[2]-1] - x[,dim(x)[2]-2,]
  extent(vertices) = extent(r) + res(r)
  vertices = resample(x, vertices)
}

latv = find.vertices(lat)
lonv = find.vertices(lon)
df2 = data.frame(xc = lonv[], yc = latv[])

Позвольте построить эти вершины, чтобы проверить, что мы находимся на правильном пути:

ggplot(df, aes(y=lat, x=lon, fill=temp)) + 
  geom_tile(width=2, height=2) +
  geom_point(size=.1) +
  geom_point(aes(xc, yc), data=df2, inherit.aes =F) +
  borders('world', colour='gray50', size=.2) + 
  coord_quickmap(xlim=range(lon), ylim=range(lat)) +
  scale_fill_distiller(palette='Spectral') +
  theme_minimal()

enter image description here

Теперь мы создадим Polygon из этих вершин.

nx = NCOL(latv)
ny = NROW(lonv)
polys = list()
for (i in 1:length(data)) {
  x = col(data)[i]
  y = row(data)[i]
  polys[[i]] = Polygon(cbind(
      lonv[c((x-1)*ny + y, (x-1)*ny + y + 1, x*ny + y + 1, x*ny + y, (x-1)*ny + y)], 
      latv[c((x-1)*ny + y, (x-1)*ny + y + 1, x*ny + y + 1, x*ny + y, (x-1)*ny + y)]
    ))
}

Преобразуйте список Polygon в SpatialPolygonsDataFrame

Polys = sapply(1:length(polys), function(i) Polygons(polys[i], i))
SPolys = sapply(1:length(polys), function(i) SpatialPolygons(Polys[i], i))
SPolys = do.call(bind, SPolys)
SPolysdf = SpatialPolygonsDataFrame(SPolys, data.frame(data=as.vector(data)))

Чтобы отобразить этот объект в ggplot, нам нужно преобразовать его в обычный data.frame. Традиционно большинство людей использовали fortify для этого. Но документация ggplot предупреждает, что это может быть устаревшим, и рекомендует вместо этого использовать пакет метлы. Я не слишком знаком с метлой, но я решил последовать этому совету так:

library(broom)
ggSPolysdf = tidy(SPolysdf)
ggSPolysdf = cbind(ggSPolysdf, data = rep(as.vector(data), each=5))

И, наконец, мы можем построить:

ggplot(df, aes(y=lat, x=lon, fill=temp)) + 
  geom_polygon(aes(long,lat,fill=data, group = id), data=ggSPolysdf) +
  borders('world', colour='gray50', size=.2) + 
  coord_quickmap(xlim=range(lon), ylim=range(lat)) +
  scale_fill_distiller(palette='Spectral') +
  theme_minimal()

enter image description here

Ответ 2

Приведенное ниже решение по существу использует ответ от @dww и вносит некоторые изменения, которые необходимы для получения цифр (по крайней мере, на моей платформе). Эти изменения относятся, во-первых, к определению полигонов, определяющих "перекос пикселей" из последней фигуры; и, во-вторых, к вопросу о том, как сжать многоугольники во фрейм данных. Для второго вопроса используется пакет sf, предложенный @SymbolixAU.

library(raster)
latr = raster(lat)
lonr = raster(lon)

find.vertices = function(m){
 r = raster(m)
 vertices = raster(matrix(nrow = dim(r)[1]+1, ncol = dim(r)[2]+1))
 x = extend(r, c(1,1))
 x[1,] = 2*x[2,] - x[3,]
 x[dim(x)[1],] = 2*x[dim(x)[1]-1,] - x[dim(x)[1]-2,]
 x[,1] = 2*x[,2] - x[,3]
 x[,dim(x)[2]] = 2*x[,dim(x)[2]-1] - x[,dim(x)[2]-2,]
 extent(vertices) = extent(r) + res(r)
 vertices = resample(x, vertices)
}

latv = find.vertices(lat)
lonv = find.vertices(lon)
df2 = data.frame(xc = lonv[], yc = latv[])

Позвольте построить эти вершины, чтобы проверить, что мы находимся на пути:

ggplot(df, aes(y=lat, x=lon, fill=temp)) + 
  geom_tile(width=2, height=2) +
  geom_point(size=.1) +
  geom_point(aes(xc, yc), data=df2, inherit.aes=F) +
  borders('world', colour='gray50', size=.2) + 
  coord_quickmap(xlim=range(lon), ylim=range(lat)) +
  scale_fill_distiller(palette='Spectral') +
  theme_minimal()

Теперь мы создаем полигоны из этих вершин:

nx = NCOL(latv)

polys = list()
for (i in 1:length(data)) {
  x = col(data)[i]
  y = row(data)[i]

  polys[[i]] = Polygon(cbind(
      lonv[c((y-1)*nx + x, (y-1)*nx + x + 1, y*nx + x + 1, y*nx + x, (y-1)*nx + x)], 
      latv[c((y-1)*nx + x, (y-1)*nx + x + 1, y*nx + x + 1, y*nx + x, (y-1)*nx + x)]
    ))
}

Преобразуйте список полигонов в элемент SpatialPolygonsDataFrame:

Polys = sapply(1:length(polys), function(i) Polygons(polys[i], i))
SPolys = sapply(1:length(polys), function(i) SpatialPolygons(Polys[i], i))
SPolys = do.call(bind, SPolys)
SPolysdf = SpatialPolygonsDataFrame(SPolys, data.frame(data=as.vector(data)))

Использование fortify для преобразования во фрейм данных будет осуществляться следующими двумя строками: (Предостережение: как отмечено @dww, это решение не рекомендуется в документации ggplot2.)

ggSPolysdf_0 = fortify(SPolysdf)
ggSPolysdf = cbind(ggSPolysdf_0, data = rep(as.vector(data), each=5))

Альтернативой является использование пакета sf. В следующем коде, команда st_coordinates играет роль fortify в ggplot2. Обратите внимание, что в настоящем методе имена переменных теряются при преобразовании и должны быть восстановлены вручную:

library(sf)
sfSPolys = st_as_sf(SPolysdf)
coord_xy_SPolys = st_coordinates(sfSPolys)
coord_xyz_SPolys = cbind(coord_xy_SPolys, data = rep(as.vector(data), each=5))
ggSPolysdf = as.data.frame(coord_xyz_SPolys)
colnames(ggSPolysdf) <- c("long", "lat", "piece", "id", "data")

И, наконец, мы можем построить:

ggplot(df, aes(y=lat, x=lon, fill=temp)) + 
  geom_polygon(mapping=aes(long,lat,fill=data, group=id), data=ggSPolysdf) +
  borders('world', colour='gray50', size=.2) + 
  coord_quickmap(xlim=range(lon), ylim=range(lat)) +
  scale_fill_distiller(palette='Spectral') +
  theme_minimal()