【问题标题】:Plot circle with a certain radius around point on a map in ggplot2在ggplot2中的地图上绘制具有一定半径的圆
【发布时间】:2020-10-12 02:47:17
【问题描述】:

我有一张地图,上面标有 8 个点:

library(ggplot2)
library(ggmap)
data = data.frame(
    ID = as.numeric(c(1:8)),
    longitude = as.numeric(c(-63.27462, -63.26499, -63.25658, -63.2519, -63.2311, -63.2175, -63.23623, -63.25958)),
    latitude = as.numeric(c(17.6328, 17.64614, 17.64755, 17.64632, 17.64888, 17.63113, 17.61252, 17.62463))
)

island = get_map(location = c(lon = -63.247593, lat = 17.631598), zoom = 13, maptype = "satellite")
islandMap = ggmap(island, extent = "panel", legend = "bottomright")
RL = geom_point(aes(x = longitude, y = latitude), data = data, color = "#ff0000")
islandMap + RL + scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) + scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0))

现在我想在 8 个绘制位置中的每一个周围绘制一个圆圈。圆的半径必须为 450 米。

这就是我的意思,但随后使用 ggplot:https://gis.stackexchange.com/questions/119736/ggmap-create-circle-symbol-where-radius-represents-distance-miles-or-km

我怎样才能做到这一点?

【问题讨论】:

    标签: r ggplot2 gis geometry ggmap


    【解决方案1】:

    如果您只在地球的一小块区域工作,这里是一个近似值。纬度的每一度代表 40075 / 360 公里。每个经度度数代表 (40075 / 360) * cos(latitude) 公里。有了这个,我们可以近似计算一个数据框,包括圆上的所有点,知道圆心和半径。

    library(ggplot2)
    library(ggmap)
    data = data.frame(
        ID = as.numeric(c(1:8)),
        longitude = as.numeric(c(-63.27462, -63.26499, -63.25658, -63.2519, -63.2311, -63.2175, -63.23623, -63.25958)),
        latitude = as.numeric(c(17.6328, 17.64614, 17.64755, 17.64632, 17.64888, 17.63113, 17.61252, 17.62463))
    )
    
    #################################################################################
    # create circles data frame from the centers data frame
    make_circles <- function(centers, radius, nPoints = 100){
        # centers: the data frame of centers with ID
        # radius: radius measured in kilometer
        #
        meanLat <- mean(centers$latitude)
        # length per longitude changes with lattitude, so need correction
        radiusLon <- radius /111 / cos(meanLat/57.3) 
        radiusLat <- radius / 111
        circleDF <- data.frame(ID = rep(centers$ID, each = nPoints))
        angle <- seq(0,2*pi,length.out = nPoints)
    
        circleDF$lon <- unlist(lapply(centers$longitude, function(x) x + radiusLon * cos(angle)))
        circleDF$lat <- unlist(lapply(centers$latitude, function(x) x + radiusLat * sin(angle)))
        return(circleDF)
    }
    
    # here is the data frame for all circles
    myCircles <- make_circles(data, 0.45)
    ##################################################################################
    
    
    island = get_map(location = c(lon = -63.247593, lat = 17.631598), zoom = 13, maptype = "satellite")
    islandMap = ggmap(island, extent = "panel", legend = "bottomright")
    RL = geom_point(aes(x = longitude, y = latitude), data = data, color = "#ff0000")
    islandMap + RL + 
        scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) + 
        scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) +
        ########### add circles
        geom_polygon(data = myCircles, aes(lon, lat, group = ID), color = "red", alpha = 0)
    

    【讨论】:

    • 真的做得很好。跟着111,但57.3从哪里来?
    • 1 弧度 = 57.3 度(180 / pi)
    【解决方案2】:

    好吧,正如referred posting 已经建议的那样 - 切换到以米为单位的投影,然后返回:

    library(rgeos)
    library(sp)
    d <- SpatialPointsDataFrame(coords = data[, -1], 
                                data = data, 
                                proj4string = CRS("+init=epsg:4326"))
    d_mrc <- spTransform(d, CRS("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"))
    

    现在,width 可以以米为单位指定:

    d_mrc_bff_mrc <- gBuffer(d_mrc, byid = TRUE, width = 450)
    

    使用geom_path将其转换回来并添加到绘图中:

    d_mrc_bff <- spTransform(d_mrc_bff_mrc, CRS("+init=epsg:4326"))
    d_mrc_bff_fort <- fortify(d_mrc_bff)
    islandMap + 
      RL + 
      geom_path(data=d_mrc_bff_fort, aes(long, lat, group=group), color="red") + 
      scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) + 
      scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) 
    

    【讨论】:

      【解决方案3】:

      在给定纬度和经度的情况下以千米为单位计算距离并不是很简单;例如,1 度纬度/经度在赤道处比在两极处更大。如果您想要一个简单的解决方法,您可以通过眼球获得准确性,您可以尝试:

      islandMap + RL + 
        scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) + 
        scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) + 
        geom_point(aes(x = longitude, y = latitude), data = data, size = 20, shape = 1,  color = "#ff0000")
      

      您需要调整第二个geom_point 中的size 参数以更接近您想要的。希望对您有所帮助!

      【讨论】:

      • 非常感谢!这可能是一个解决方案,但它并不像我希望的那样准确。任何其他建议都非常受欢迎。
      • 肯定的;它在视觉上不可靠,例如,如果您发布,则不应被视为可靠。我不确定点的大小和轴标记之间的关系是什么。不过,我尝试过做类似的事情,因为地球不是平的(显然),你必须合并一个 z 轴才能在平面地图上准确地显示一个圆圈。
      • 还要注意,如果你制作足够大的“圆圈”,它们会被拉伸成椭圆形,这又是因为球体的缘故。真是一塌糊涂,真的哈哈。
      • 谢谢南希!如果我们留在“地球是平的”这件事上会容易得多..
      【解决方案4】:

      一个准确的解决方案是使用 geosphere::destPoint() 函数。这无需切换投影即可工作。

      定义函数来确定一个点周围一定半径的360个点:

      library(dplyr)
      library(geosphere)
      
      fn_circle <- function(id1, lon1, lat1, radius){ 
         data.frame(ID = id1, degree = 1:360) %>%
            rowwise() %>%
            mutate(lon = destPoint(c(lon1, lat1), degree, radius)[1]) %>%
            mutate(lat = destPoint(c(lon1, lat1), degree, radius)[2]) 
      }
      

      data的每一行应用函数并转换为data.frame:

      circle <- apply(data, 1, function(x) fn_circle(x[1], x[2], x[3], 450))
      circle <- do.call(rbind, circle)
      

      那么地图可以通过以下方式轻松获得:

      islandMap + 
         RL +
         scale_x_continuous(limits = c(-63.280, -63.21), expand = c(0, 0)) + 
         scale_y_continuous(limits = c(17.605, 17.66), expand = c(0, 0)) +
         geom_polygon(data = circle, aes(lon, lat, group = ID), color = "red", alpha = 0)
      

      【讨论】:

        【解决方案5】:

        使用 sf 包中的 st_buffer() 的解决方案。

        library(ggmap)
        library(ggplot2)
        library(sf)
        
        data <- data.frame(
          ID = 1:8,
          longitude = c(-63.27462, -63.26499, -63.25658, -63.2519, 
                        -63.2311, -63.2175, -63.23623, -63.25958),
          latitude = c(17.6328, 17.64614, 17.64755, 17.64632, 
                       17.64888, 17.63113, 17.61252, 17.62463)
        )
        

        将 data.frame 转换为 sf 对象:

        points_sf <- sf::st_as_sf(data, coords = c("longitude", "latitude"), crs = 4326)
        

        对于本例,我们使用 UTM 区域 20,其中包含岛屿的坐标:

        data_sf_utm <- sf::st_transform(points_sf, "+proj=utm +zone=20")
        

        现在我们可以将点缓冲 450 米:

        circle <- sf::st_buffer(data_sf_utm, dist = 450)
        

        ggmap 似乎对 geom_sf 有一些问题。将 inherit.aes 设置为 FALSE 会返回所需的映射。

        island <- ggmap::get_map(location = c(lon = -63.247593, lat = 17.631598), zoom = 14, maptype = "satellite")
        
        ggmap(island, extent = "panel", legend = "bottomright") + 
          geom_sf(data = points_sf, color = "red", inherit.aes = FALSE) +
          geom_sf(data = circle, color = "red", alpha = 0, inherit.aes = FALSE)
        

        reprex package (v0.3.0) 于 2020 年 10 月 11 日创建

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-28
          • 2021-11-21
          • 1970-01-01
          • 1970-01-01
          • 2013-09-24
          相关资源
          最近更新 更多