0

i am coding with R and RStudio. I have a set of polygons in one in my global environment called buek250_shapefile. I am able to plot them with leaflet. For the next step I wrote a function which gets lat and lon values, and should give back information about the shape which haqs the shortest distance to the next polygon shape. My problem: I always get back the same polygon shape for all lat lon values I am trying. And the computed distances are between 5000 and 7000km. But all my shapes and lat lon values are in Germany, and Germany is not so big. My data: https://download.bgr.de/bgr/boden/BUEK250/shp/buek250_mg_utm_v55.zip Can anyone here help me please? Thank you very much

lowestdistancebuek <- function(inputlat, inputlon) {
  # Modify the clicked point. Make the input data into CRS format
  # Order is important: first lat, then lon
  point <- st_point(c(inputlat, inputlon)) %>%
    st_sfc(crs = 25832)
  
  # Is there a shape containing the clickedpoint?
  nearestshape <- st_intersects(point, buek250_shapefile_all, sparse = FALSE)
  # nearestsape is a logical matrix. For every polygon it contains if the point is in or not
  # Which polygon contains the point? Extract it
  nearestshape <- which(nearestshape, arr.ind = TRUE)
  # Extract the column
  nearestshape <- nearestshape[,2]

  # If there is a shape containing the point, nearestshape has length 1
  # In this case, nearestshape is set to the sf
  # Otherwise, the nearestshape is determinded and set to nearestshape
  if(length(nearestshape) == 1) {
    nearestshape <- buek250_shapefile_all[nearestshape, ]
  } else if (length(nearestshape) == 0){ # no polygon contains the point
    # Compute the distances between each polygon and the point. Result is saved in distances
    distances <- st_distance(point, buek250_shapefile_all,
                             by_element = FALSE, # TRUE returns a vector
                             tolerance = TRUE # speeds up computing
                             )
    # Which distance ist the smallest one?
    nearestshape <- which.min(distances)
    # The shortest number was saved in nearestshape, now there is saved the polygone information
    nearestshape <- buek250_shapefile_all[nearestshape, ]
  }
  return(nearestshape)
}

Right now I receive for every input the same output. I expect the code to return the shapefile with the shortest distance to the given point.

6
  • 1
    Welcome to SO! Please make this example reproducible for others. For example we do not know how are you loading that Shapefile, how are you calling lowestdistancebuek() or what are some coordinate values you've been testing with. That said, your st_point(c(inputlat, inputlon)) should read st_point(c(inputlon, inputlat)) . Or better yet, use something like x,y or easting, northing when referring to ETRS89 / UTM zone 32N coordinates in your code.
    – margusl
    Commented Jun 19 at 8:54
  • 1
    Making another assumption from the title ("transform") and code comments ("click") -- if those input coordinates are lat/lon values from leaflet, st_sfc(crs = 25832) does not transform but just forces geometry CRS to epsg:25832. As a result, any lat/lon value from Germany, no matter the order, ends up somewhere in Atlantic, next to Africa, in that same 5000 .... 7000km range from Germany you are witnessing.
    – margusl
    Commented Jun 19 at 9:41
  • Hej margusl, right now I'm just testing with some lat lon values like 52.52, 10.405. I assume that my problem is the false easting, but I am not sure how to solve that. Why makes st_transform that not? I load the shape with: buek250_shapefile_all <- sf::st_read("C:/Users/Documents/Uni/SoilMapExtraData/buek250_mg_utm_v55/buek250_mg_utm_v55.shp") I am able to map the shapes with leaflet. Rigth now, I call lowestdistancebuek(52.52,10.405) or with other values just in the console.
    – odcoder
    Commented Jun 19 at 9:49
  • 1
    As correctly assumed by @margusl, you are entering decimal degrees coordinates into your function and then assigning them a projected coordinate system (PCS) CRS which has metres for units. So those decimal degrees are being treated as metres. Assuming you are entering WGS84/EPSG:4326 values, you need something like st_point(c(inputlon, inputlat)) %>% st_sfc(crs = 4326) %>% st_transform(25832). Whatever you do, you need to know the coordinate system of your input coordinates and you need to ensure which is longitude (assigned first), and which is latitude (assigned second).
    – L Tyrone
    Commented Jun 19 at 10:17
  • 1
    You are still inverting the lat/lon values in your df. Please re-read the above comments carefully. To explain it another way, x == longitude and y == latitude. So it should be df <- data.frame(X = c(inputlon), Y = c(inputlat)).
    – L Tyrone
    Commented Jun 19 at 10:35

1 Answer 1

3

While geographic coordinates are usually represented as latitude, longitude pairs, geometric operations generally expect x, y order, that also applies for sf::st_point(). If you need a mnemonic for lat/lon, you could try "lat" -> "ladder" -> "y" .

To transform from one CRS to another, use st_transform(); but for this to work, input must have CRS assigned and this is the role of crs argument in st_sfc(x, crs = "WGS84") .It defines what you currently have, not what you want to end up with.

I'm using shapes from giscoR for a reproducible example here.

library(giscoR)
library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE

# example shapes from GISCO / giscoR
ger_25832 <- 
  giscoR::gisco_get_nuts(country = "Germany", nuts_level = "1") |>
  st_transform(25832)

plot(ger_25832[,"NUTS_NAME"], axes = TRUE)

First, let's try to build a point from those geographic coordinates so it ends up somewhere in the area of interest:

# input point coordinates, WGS84
inputlat_wgs84 <- 52.52   # mnemonic: latitude like "ladder", y-axis
inputlon_wgs84 <- 10.405

# create WGS84 point, transform, check distances:
ref_pt <- 
  # x,y!
  st_point(c(inputlon_wgs84, inputlat_wgs84)) |> 
  st_sfc(crs = "WGS84") |>
  st_transform(crs = st_crs(ger_25832))

st_distance(ger_25832, ref_pt) |> units::set_units(km)
#> Units: [km]
#>            [,1]
#>  [1,] 221.48755
#>  [2,] 184.04861
#>  [3,]  88.60661
#>  [4,] 114.36606
#>  [5,] 101.80472
#>  [6,] 115.41499
#>  [7,]  87.67142
#>  [8,]   0.00000
#>  [9,]  87.76893
#> [10,] 250.06811
#> [11,] 396.54742
#> [12,] 165.09594
#> [13,]  35.32782
#> [14,]  94.75578
#> [15,]  99.73750
#> [16,] 308.01952

Seems that we got lat/lon order and transformation right this time, let's adjust that function accordingly, we can use st_nearest_feature() to subset nearest feature:

# same in the function + subset with st_nearest_feature()
lowestdistancebuek <- function(inputlat_wgs84, inputlon_wgs84, shapes = ger_25832){
  ref_pt <- 
    st_point(c(inputlon_wgs84, inputlat_wgs84)) |> 
    st_sfc(crs = "WGS84") |>
    st_transform(crs = st_crs(shapes))
  shapes[st_nearest_feature(ref_pt, shapes),]
}

nearest_shape <- lowestdistancebuek(inputlat_wgs84, inputlon_wgs84)
nearest_shape
#> Simple feature collection with 1 feature and 10 fields
#> Geometry type: MULTIPOLYGON
#> Dimension:     XY
#> Bounding box:  xmin: 343686.6 ymin: 5682911 xmax: 674176.7 ymax: 5971541
#> Projected CRS: ETRS89 / UTM zone 32N
#>    NUTS_ID LEVL_CODE URBN_TYPE CNTR_CODE     NAME_LATN     NUTS_NAME MOUNT_TYPE
#> 58     DE9         1         0        DE NIEDERSACHSEN NIEDERSACHSEN          0
#>    COAST_TYPE FID geo                       geometry
#> 58          0 DE9 DE9 MULTIPOLYGON (((486772.8 59...

Plot our test point & resulting shape:

# plot 
plot(st_union(ger_25832))
plot(nearest_shape, add = TRUE)
#> Warning in plot.sf(nearest_shape, add = TRUE): ignoring all but the first
#> attribute
plot(ref_pt, col = "red", pch = 16, add = TRUE)

Created on 2024-06-19 with reprex v2.1.0

Not the answer you're looking for? Browse other questions tagged or ask your own question.