sf 1.0.0及以上版本中如何正确使用st_intersects()实现几何体相交判断
st_intersects() Behavior in sf 1.0.0+ with S2 Geospatial Engine Hey there, let's break down why your st_intersects() results shifted after upgrading sf, and walk through the fixes to get your code working as expected.
Why the Results Changed
Starting with sf 1.0.0, the library switched to using the S2 Geospatial Engine by default for operations on geographic coordinate systems (like WGS84/EPSG:4326, your lat/long data). Here's the key difference from sf 0.9.7:
- In older versions, sf treated lat/long as flat planar coordinates. Your "global" polygon worked because it covered the entire plane, so all points were inside.
- With S2, operations use spherical geometry (matching the Earth's curved shape). Your global polygon is actually invalid in S2's logic: a polygon that wraps the entire globe is interpreted as the outside of the shape, so none of your points count as intersecting it—hence all
FALSEresults.
Solutions to Get Correct Results
You have two solid options depending on whether you want to stick to planar logic or embrace S2's spherical accuracy.
Option 1: Revert to Planar Logic (Temporarily Disable S2)
If you need exact parity with your old sf 0.9.7 code, you can turn off S2 either globally for your session or just for the specific st_intersects() call:
# Create your point data as before table <- data.frame(type=c("1","2","3"), Longitude=c(0,2,4), Latitude=c(0,2,5)) df <- st_as_sf(table, coords=c("Longitude","Latitude"), crs=4326) # Create the global polygon s <- rbind(c(-180, -90), c(-180, 90), c(180, 90), c(180, -90), c(-180, -90)) %>% list() %>% st_polygon() %>% st_sfc(crs=4326) # Option A: Disable S2 globally for the session sf_use_s2(FALSE) st_intersects(df, s, sparse = FALSE) # Option B: Disable S2 only for this single call (keeps S2 on for other operations) st_intersects(df, s, sparse = FALSE, s2_use = FALSE)
Both will return the same result as your old code:
[,1] [1,] TRUE [2,] TRUE [3,] TRUE
Heads up: Planar calculations on lat/long data introduce distortion, especially near the poles. This is fine for quick parity, but for accurate spatial work with geographic data, go with Option 2.
Option 2: Work With S2's Spherical Logic
To use S2 correctly (and get accurate real-world spatial results), adjust your polygon definition to be valid for spherical geometry.
For Global Polygons (Your First Example)
S2 interprets a polygon that wraps the entire globe as the "outside" space. To make it represent the inside of the globe, reverse the order of the polygon's ring:
# Reverse the ring order to make S2 recognize it as the global interior s <- rbind(c(-180, -90), c(180, -90), c(180, 90), c(-180, 90), c(-180, -90)) %>% list() %>% st_polygon() %>% st_sfc(crs=4326) # Ensure S2 is enabled (default in sf 1.0.0+) sf_use_s2(TRUE) st_intersects(df, s, sparse = FALSE)
This will return all TRUE values as expected.
For Regular Polygons (Your Second Example)
Your square polygon example works because it's a valid, non-global shape that S2 can interpret correctly. To make sure it stays reliable, use st_make_valid() to ensure your polygon is properly formatted for S2:
# Create and validate the square polygon s <- rbind(c(1, 1), c(10, 1), c(10, 10), c(1, 10), c(1, 1)) %>% list() %>% st_polygon() %>% st_sfc(crs=4326) %>% st_make_valid() # Create random points p <- runif(50, 0, 11) %>% cbind(runif(50, 0, 11)) %>% st_multipoint() %>% st_sfc(crs=4326) %>% st_cast("POINT") # Run intersects with S2 enabled st_intersects(p, s, sparse=FALSE)
This will give you accurate TRUE/FALSE results based on spherical geometry.
Key Takeaways
- sf 1.0.0+ uses S2 by default for geographic coordinate systems to handle the Earth's curvature accurately.
- Global polygons need reversed ring order to be recognized as interior space by S2.
- Use
sf_use_s2(TRUE/FALSE)to toggle S2 globally, ors2_use=FALSEin individual function calls to override. - For any polygon,
st_make_valid()helps ensure it's compatible with S2's spherical logic.
内容的提问来源于stack exchange,提问作者Sebastian Garzón

