r - 在不规则网格上绘制数据的有效方法

标签 r ggplot2 netcdf map-projections sf

我使用组织在不规则二维网格上的卫星数据,其维度是扫描线(沿轨道维度)和地面像素(跨轨道维度)。每个中心像素的经纬度信息存储在辅助坐标变量中,以及四个角坐标对(经纬度坐标在 WGS84 引用椭球上给出)。数据存储在 netCDF4 文件中。

我想要做的是在投影 map 上有效地绘制这些文件(可能还有文件的组合——下一步!)。

到目前为止我的方法,灵感来自 Jeremy Voisey's回答这个问题 question , 一直在构建一个数据框,将我感兴趣的变量链接到像素边界,并使用 ggplot2geom_polygon对于实际情节。

让我说明我的工作流程,并提前为幼稚的方法道歉:我从一两周开始就开始使用 R 编码。

笔记

要完全重现问题:
1. 下载两个数据框:so2df.Rda (22M) 和 pixel_corners.Rda (26M)
2. 将它们加载到您的环境中,例如

so2df <- readRDS(file="so2df.Rda")
pixel_corners <- readRDS(file="pixel_corners.Rda")
  • 跳转到“合并数据帧”步骤。

  • 最初设定

    我将从我的文件中读取数据和纬度/经度边界。
    library(ncdf4)
    library(ggplot2)
    library(ggmap) 
    # set path and filename
    ncpath <- "/Users/stefano/src/s5p/products/e1dataset/L2__SO2/"
    ncname <- "S5P_OFFL_L2__SO2____20171128T234133_20171129T003956_00661_01_022943_00000000T000000"  
    ncfname <- paste(ncpath, ncname, ".nc", sep="")
    nc <- nc_open(ncfname)
    
    # save fill value and multiplication factors
    mfactor = ncatt_get(nc, "PRODUCT/sulfurdioxide_total_vertical_column", 
                        "multiplication_factor_to_convert_to_DU")
    fillvalue = ncatt_get(nc, "PRODUCT/sulfurdioxide_total_vertical_column", 
                          "_FillValue")
    
    # read the SO2 total column variable
    so2tc <- ncvar_get(nc, "PRODUCT/sulfurdioxide_total_vertical_column")
    
    # read lat/lon of centre pixels
    lat <- ncvar_get(nc, "PRODUCT/latitude")
    lon <- ncvar_get(nc, "PRODUCT/longitude")
    
    # read latitude and longitude bounds
    lat_bounds <- ncvar_get(nc, "GEOLOCATIONS/latitude_bounds")
    lon_bounds <- ncvar_get(nc, "GEOLOCATIONS/longitude_bounds")
    
    # close the file
    nc_close(nc)
    dim(so2tc)
    ## [1]  450 3244
    

    因此,对于这个文件/ channel ,3244 条扫描线中的每一条都有 450 个地面像素。

    创建数据框

    在这里,我创建了两个数据框,一个用于值,进行一些后处理,另一个用于纬度/经度边界,然后合并两个数据框。
    so2df <- data.frame(lat=as.vector(lat), lon=as.vector(lon), so2tc=as.vector(so2tc))
    # add id for each pixel
    so2df$id <- row.names(so2df)
    # convert to DU
    so2df$so2tc <- so2df$so2tc*as.numeric(mfactor$value)
    # replace fill values with NA
    so2df$so2tc[so2df$so2tc == fillvalue] <- NA
    saveRDS(so2df, file="so2df.Rda")
    summary(so2df)
    
    ##       lat              lon              so2tc              id           
    ##  Min.   :-89.97   Min.   :-180.00   Min.   :-821.33   Length:1459800    
    ##  1st Qu.:-62.29   1st Qu.:-163.30   1st Qu.:  -0.48   Class :character  
    ##  Median :-19.86   Median :-150.46   Median :  -0.08   Mode  :character  
    ##  Mean   :-13.87   Mean   : -90.72   Mean   :  -1.43                     
    ##  3rd Qu.: 31.26   3rd Qu.: -27.06   3rd Qu.:   0.26                     
    ##  Max.   : 83.37   Max.   : 180.00   Max.   :3015.55                     
    ##                                     NA's   :200864
    

    我将此数据框保存为 so2df.Rda here (22M)。
    num_points = dim(lat_bounds)[1]
    pixel_corners <- data.frame(lat_bounds=as.vector(lat_bounds), lon_bounds=as.vector(lon_bounds))
    # create id column by replicating pixel's id for each of the 4 corner points
    pixel_corners$id <- rep(so2df$id, each=num_points)
    saveRDS(pixel_corners, file="pixel_corners.Rda")
    summary(pixel_corners)
    
    
    ##    lat_bounds       lon_bounds           id           
    ##  Min.   :-89.96   Min.   :-180.00   Length:5839200    
    ##  1st Qu.:-62.29   1st Qu.:-163.30   Class :character  
    ##  Median :-19.86   Median :-150.46   Mode  :character  
    ##  Mean   :-13.87   Mean   : -90.72                     
    ##  3rd Qu.: 31.26   3rd Qu.: -27.06                     
    ##  Max.   : 83.40   Max.   : 180.00
    

    正如预期的那样,纬度/经度边界数据帧是值数据帧的四倍(每个像素/值有四个点)。
    我将此数据框保存为 pixel_corners.Rda here (26M)。

    合并数据帧

    然后我按 id 合并两个数据框:
    start_time <- Sys.time()
    so2df <- merge(pixel_corners, so2df, by=c("id"))
    time_taken <- Sys.time() - start_time
    print(paste(dim(so2df)[1], "rows merged in", time_taken, "seconds"))
    
    ## [1] "5839200 rows merged in 42.4763631820679 seconds"
    

    如您所见,这是一个 CPU 密集型过程。我想知道如果我一次处理 15 个文件会发生什么(全局覆盖)。

    绘制数据

    现在我已经将像素角链接到像素值,我可以轻松地绘制它们。通常,我对轨道的特定区域感兴趣,所以我创建了一个函数,在绘制之前对输入数据帧进行子集化:
    PlotRegion <- function(so2df, latlon, title) {
      # Plot the given dataset over a geographic region.
      #
      # Args:
      #   df: The dataset, should include the no2tc, lat, lon columns
      #   latlon: A vector of four values identifying the botton-left and top-right corners 
      #           c(latmin, latmax, lonmin, lonmax)
      #   title: The plot title
    
      # subset the data frame first
      df_sub <- subset(so2df, lat>latlon[1] & lat<latlon[2] & lon>latlon[3] & lon<latlon[4])
    
      subtitle = paste("#Pixel =", dim(df_sub)[1], "- Data min =", 
                       formatC(min(df_sub$so2tc, na.rm=T), format="e", digits=2), "max =", 
                       formatC(max(df_sub$so2tc, na.rm=T), format="e", digits=2))
    
      ggplot(df_sub) + 
        geom_polygon(aes(y=lat_bounds, x=lon_bounds, fill=so2tc, group=id), alpha=0.8) +
        borders('world', xlim=range(df_sub$lon), ylim=range(df_sub$lat), 
                colour='gray20', size=.2) + 
        theme_light() + 
        theme(panel.ontop=TRUE, panel.background=element_blank()) +
        scale_fill_distiller(palette='Spectral') +
        coord_quickmap(xlim=c(latlon[3], latlon[4]), ylim=c(latlon[1], latlon[2])) +
        labs(title=title, subtitle=subtitle, 
             x="Longitude", y="Latitude", 
             fill=expression(DU)) 
    }
    

    然后我在感兴趣的区域上调用我的函数,例如让我们看看在夏威夷发生了什么:
    latlon = c(17.5, 22.5, -160, -154)
    PlotRegion(so2df, latlon, expression(SO[2]~total~vertical~column))
    

    SO2 total column over Hawaii

    他们在那里,我的像素,以及似乎是来自莫纳罗亚山的二氧化硫羽流。请暂时忽略负值。正如您所看到的,像素的区域朝着 strip 的边缘而变化(不同的分箱方案)。

    我尝试使用 ggmap 在谷歌地图上显示相同的图:
    PlotRegionMap <- function(so2df, latlon, title) {
      # Plot the given dataset over a geographic region.
      #
      # Args:
      #   df: The dataset, should include the no2tc, lat, lon columns
      #   latlon: A vector of four values identifying the botton-left and top-right corners 
      #           c(latmin, latmax, lonmin, lonmax)
      #   title: The plot title
    
      # subset the data frame first
      df_sub <- subset(so2df, lat>latlon[1] & lat<latlon[2] & lon>latlon[3] & lon<latlon[4])
    
      subtitle = paste("#Pixel =", dim(df_sub)[1], "Data min =", formatC(min(df_sub$so2tc, na.rm=T), format="e", digits=2), 
                       "max =", formatC(max(df_sub$so2tc, na.rm=T), format="e", digits=2))
      base_map <- get_map(location = c(lon = (latlon[4]+latlon[3])/2, lat = (latlon[1]+latlon[2])/2), zoom = 7, maptype="terrain", color="bw")
    
      ggmap(base_map, extent = "normal")  +
        geom_polygon(data=df_sub, aes(y=lat_bounds, x=lon_bounds,fill=so2tc, group=id),  alpha=0.5) +
        theme_light() + 
        theme(panel.ontop=TRUE, panel.background=element_blank()) +
        scale_fill_distiller(palette='Spectral') +
        coord_quickmap(xlim=c(latlon[3], latlon[4]), ylim=c(latlon[1], latlon[2])) +
        labs(title=title, subtitle=subtitle, 
             x="Longitude", y="Latitude", 
             fill=expression(DU)) 
    
    }
    

    这就是我得到的:
    latlon = c(17.5, 22.5, -160, -154)
    PlotRegionMap(so2df, latlon, expression(SO[2]~total~vertical~column))
    

    Plot over google map

    问题
  • 有没有更有效的方法来解决这个问题?我正在阅读 sf包,我想知道我是否可以定义一个点的数据框(值 + 中心像素坐标),并且有 sf自动推断像素边界。这将使我不必依赖原始数据集中定义的纬度/经度边界,并且不必将它们与我的值合并。我可以接受在朝向 strip 边缘的过渡区域的精度损失,否则网格非常规则,每个像素为 3.5x7 km^2 大。
  • 将我的数据重新网格化为常规网格(如何?),可能通过聚合相邻像素来提高性能?我正在考虑使用 raster包,据我所知,它需要常规网格上的数据。这在全局范围内应该很有用(例如,欧洲的绘图),在那里我不需要绘制单个像素——事实上,我什至看不到它们。
  • 在谷歌地图上绘图时是否需要重新投影我的数据?

  • [奖金化妆品问题]
  • 有没有更优雅的方法来在由四个角点标识的区域上对我的数据框进行子集化?
  • 如何更改色标以使较高的值相对于较低的值脱颖而出?我经历过对数比例,但结果很差。
  • 最佳答案

    我想 data.table在这里可能会有所帮助。合并几乎是即时的。

    "5839200 rows merged in 1.24507117271423 seconds"


    library(data.table)
    pixel_cornersDT <- as.data.table(pixel_corners)
    so2dfDT <- as.data.table(so2df)
    
    setkey(pixel_cornersDT, id)
    setkey(so2dfDT, id)
    
    so2dfDT <- merge(pixel_cornersDT, so2dfDT, by=c("id"), all = TRUE)
    

    将数据放在 data.table 中,绘图函数中的子集也会快得多。

  • 问题 1/2/4:

  • 我不认为使用 raster 的过程会更快或 sf但您可以尝试使用函数 rasterFromXYZ()st_make_grid() .但是大部分时间都会花在上转换 到栅格/sf 对象,因为您必须转换整个数据集。

    我建议用 data.table 做所有的数据处理包括裁剪,然后您可以从那里切换到光栅/sf 对象以进行绘图。

  • 问题三:

  • 谷歌图显示正确,但你指定了一张黑白 map ,它覆盖了“光栅”,所以你不会看到很多。
    您可以将 basemap 更改为 卫星背景 .
    base_map <- get_map(location = c(lon = (latlon[4]+latlon[3])/2, lat = (latlon[1]+latlon[2])/2), 
                        zoom = 7, maptype="satellite")
    

  • 问题5:

  • 您可以使用 rescale来自 scales 的函数包裹。我在下面包含了两个选项。
    第一个(未注释)采用 分位数作为休息和其他休息是 单独定义 .我不会像创建 NA 值那样使用对数转换( trans - 参数),因为您也有负值。
    ggplot(df_sub) + 
      geom_polygon(aes(y=lat_bounds, x=lon_bounds, fill=so2tc, group=id), alpha=0.8) +
      borders('world', xlim=range(df_sub$lon), ylim=range(df_sub$lat),
              colour='gray20', size=.2) +
      theme_light() + 
      theme(panel.ontop=TRUE, panel.background=element_blank()) +
      # scale_fill_distiller(palette='Spectral', type="seq", trans = "log2") +
      scale_fill_distiller(palette = "Spectral",
                           # values = scales::rescale(quantile(df_sub$so2tc), c(0,1))) +
                           values = scales::rescale(c(-3,0,1,5), c(0,1))) +
      coord_quickmap(xlim=c(latlon[3], latlon[4]), ylim=c(latlon[1], latlon[2])) +
      labs(title=title, subtitle=subtitle, 
           x="Longitude", y="Latitude", 
           fill=expression(DU)) 
    

    enter image description here

    整个过程大约需要 8 秒 对我来说,包括没有背景 map 的绘图,虽然 map 渲染也需要额外的 1-2 秒。

    关于r - 在不规则网格上绘制数据的有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48952435/

    相关文章:

    r - 打印 lm 或 fastLm() 模型的摘要而不打印系数

    R中固定间隔的滚动平均值

    r - 将 R 中标记为 "N/A"的所有空 & 字段转换为 NA

    r - ggplot2 中的scale_colour_gradient 与scale_fill_gradient

    r - 我们能否整齐地将回归方程与R2和p值对齐?

    python - 从扁平字典创建嵌套字典

    python - 使用 Xarray 从 netCDF 文件中提取数据到高数据帧中的有效方法

    python - xarray - 将字符串存储为 'string' 数据类型,而不是 Python2.7 的 'char' (n 维字符数组)

    Rcpp:C++ 函数在 R 包中不起作用

    r - 选择要在 ggplot2 中绘制的数据框列