In Software like ArcMap one can create centroids for polygons within a polygon. In cases like the one shown below this is necessary.
In R it is possible to calculate centroids of spatial polygons with rgeos::gCentroid(). However there is no way to force the calculation of centroids within the polygon.
library(rgdal)
library(rgeos)
x <- readWKT("POLYGON ((1441727.5096940901130438 6550163.0046194596216083,
1150685.2609429201111197 6669225.7427449300885201,
975398.4520359700545669 6603079.7771196700632572,
866257.6087542800232768 6401334.5819626096636057,
836491.9242229099618271 6106985.0349301798269153,
972091.1537546999752522 5835786.5758665995672345,
1547561.0546945100650191 5782869.8033663900569081,
1408654.5268814601004124 5600968.3978968998417258,
720736.4843787000281736 5663807.0652409195899963,
598366.4479719599476084 6001151.4899297598749399,
654590.5187534400029108 6341803.2128998702391982,
869564.9070355399744585 6784981.1825891500338912,
1451649.4045378800947219 6788288.4808704098686576,
1441727.5096940901130438 6550163.0046194596216083))")
plot(x)
This is the polygon x
gCentroid() creates a centroid which in this specific case is located outside of the polygon. Despite being geometrically correct, some applications require centroids within the polygon, as they can be calculated by ArcMap.
xCent <- gCentroid(x, byid = TRUE)
points(xCent, col = "red", pch = 16)
A desired output (from ArcMap) looks like this:
Is there any possibility to generate centroids like this in R?
EDIT:
After some digging, it turns out that ArcMap picks a random point within the Polygon:
"For an input polygon: the output point will be inside the polygon."
Thus the question has to be: is there a function that creates a point at any random position WITHIN the polygons?
sf solution
With the advent of the sf package, things got a bit easier. Just use:
library(sf)
y <- st_as_sf(x) # only necessary when you don't already have an sf object
st_point_on_surface(y)
It "returns a point guaranteed to be on the (multi)surface."
sp solution
As pointed out in the updates of the Question, it seems that ArcMap is just putting a point at a random location within the polygon. This can be achieved by gPointsOnSurface(..., n = 1, type = 'random') as well.
xCent2 <- gPointOnSurface(x, byid = T)
points(xCent2, col = "blue", pch = 16)
I wrote this function which first finds the centroid and, if it is not on within (i.e. it does not overlap / intersect the polygon), it is substituted by a point on the surface. Furhtermore, it returns a new column which indicates if a point is the real centroid or not.
gCentroidWithin <- function(pol) {
require(rgeos)
pol$.tmpID <- 1:length(pol)
# initially create centroid points with gCentroid
initialCents <- gCentroid(pol, byid = T)
# add data of the polygons to the centroids
centsDF <- SpatialPointsDataFrame(initialCents, pol#data)
centsDF$isCentroid <- TRUE
# check whether the centroids are actually INSIDE their polygon
centsInOwnPoly <- sapply(1:length(pol), function(x) {
gIntersects(pol[x,], centsDF[x, ])
})
if(all(centsInOwnPoly) == TRUE){
return(centsDF)
}
else {
# substitue outside centroids with points INSIDE the polygon
newPoints <- SpatialPointsDataFrame(gPointOnSurface(pol[!centsInOwnPoly, ],
byid = T),
pol#data[!centsInOwnPoly,])
newPoints$isCentroid <- FALSE
centsDF <- rbind(centsDF[centsInOwnPoly,], newPoints)
# order the points like their polygon counterpart based on `.tmpID`
centsDF <- centsDF[order(centsDF$.tmpID),]
# remove `.tmpID` column
centsDF#data <- centsDF#data[, - which(names(centsDF#data) == ".tmpID")]
cat(paste(length(pol), "polygons;", sum(centsInOwnPoly), "actual centroids;",
sum(!centsInOwnPoly), "Points corrected \n"))
return(centsDF)
}
Related
I would like to know how to calculate the shortest distance between two properties (points) for my code below. There are two shapefile files, one being a points shapefile, the other a roads shapefile.
For testing, both shapefiles can be downloaded from the following website: https://github.com/JovaniSouza/JovaniSouza5/blob/master/Example.zip
library(sf)
roads <- st_read('Roads/Roads.shp')
pts <- st_read('Points/Points.shp') %>%
st_transform(crs=st_crs(roads))
plot(st_geometry(roads))
plot(st_geometry(pts), add = T, col = 'red', pch = 20)
Example
You can just use st_distance to get a distance matrix and find the minimum. I wrote a function that can process all of that and return a new sf data.frame. The data.frame will contain attributes called nearest and distance which is the index of the nearest point and the distance to that point respectively. Note the distances are in meters reflecting your projection. Your data have repeating points, so some of the points show no distance because of that. If you don't want those points you will have to remove the duplicates.
getNearest <- function(shp){
dist <- as.data.frame(st_distance(shp))
for (i in 1:ncol(dist)){
rows <- seq(1:ncol(dist))
rows <- rows[i != rows]
shp[i, 'nearest'] <- which.min(dist[rows, i])
shp[i, 'distance'] <- dist[which.min(dist[rows, i]), i]
}
return(shp)
}
pts2 <- getNearest(pts)
From what I understand, you are trying to measure the distance along the road to each point and it's closest point. Please see a similar workflow here:
https://community.rstudio.com/t/distance-between-points-along-network-path/49596/2
I have a raster and some points. I want to snap the points closest to the raster based on some general conditions.
library(raster)
##create a diagonal matrix
xy = diag(1, 100, 100)
# Turn the matrix into a raster
rast <- raster(xy)
# Give it lat/lon coords
extent(rast) <- c(-180,180,-90,90)
# ... and assign a projection
projection(rast) <- CRS("+proj=longlat +datum=WGS84")
##create two points just for reference
lonlat <- data.frame(x = c(50,130), y = c(75,-50))
coordinates(lonlat)<-~x+y
crs(lonlat)<- CRS("+proj=longlat +datum=WGS84")
plot(rast)
plot(lonlat,add=T)
It results in
Now I want to snap the points(+) on the green diagonal line. Here, I have provided a diagonal matrix to make it easy but it could be of any shape (for instance curved shape like rivers).
I have found some methods which only snaps the closest raster grid to the points.
##snap raster grid closest to point
Idx = sapply(lonlat$x,function(i) which.min(abs(unique(rasterToPoints(rast, spatial = TRUE)#coords[,1])-i)))
Idy = sapply(lonlat$y,function(i) which.min(abs(unique(rasterToPoints(rast, spatial = TRUE)#coords[,2])-i)))
I basically want two things (a) snap the closest point based on some simple condition ( rast == 1 ). (b) snap the points based on some search radius (lets say neighboring some points).
I'm trying to find the Radii on this map that intercept state borders in R.
Here is my code so far. Thanks to user Gregoire Vincke for providing much of the solution.
library("maps")
library("mapproj")
library("RColorBrewer")
library("mapdata")
library("ggplot2")
library("rgeos")
library("dismo")
library("ggmap")
library("rgdal")
data("stateMapEnv") #US state map
dat <- read.csv("R/longlat.csv",header = T)
map('state',fill = T, col = brewer.pal(9,"Pastel2"))
#draws circles around a point, given lat, long and radius
plotCircle <- function(lonDec, latDec, mile) {
ER <- 3959
angdeg <- seq(1:360)
lat1rad <- latDec*(pi/180)
lon1rad <- lonDec*(pi/180)
angrad <- angdeg*(pi/180)
lat2rad <- asin(sin(lat1rad)*cos(mile/ER) + cos(lat1rad)*sin(mile/ER)*cos(angrad))
lon2rad <- lon1rad + atan2(sin(angrad)*sin(mile/ER)*cos(lat1rad),cos(mile/ER)-sin(lat1rad)*sin(lat2rad))
lat2deg <- lat2rad*(180/pi)
lon2deg <- lon2rad*(180/pi)
polygon(lon2deg,lat2deg,lty = 1 , col = alpha("blue",0.35))
}
point <- mapproject(dat$lng,dat$lat)
points(point, col = alpha("black",0.90), cex = 0.4, pch = 20) #plots points
plotCircle(-71.4868,42.990684,20)
plotCircle(-72.57085,41.707932,12)
...
#this goes on for every point
I want to store the points that intercept state borders in a new data frame, any help would be appreciated!
EDIT: Here's a broad overview of the workflow using the geospatial analyses packages in R (sp, rgdal, rgeos).
Instead of using the maps package and stateMapEnv, you want a polygon shapefile of state boundaries, like one that can be found here:
https://www.census.gov/geo/maps-data/data/cbf/cbf_state.html
You can then load that shapefile in R with readOGR from the rgdal package to get a SpatialPolygons (let's call it state_poly) with one Polygons object per state.
Create a SpatialPoints object from your long/lat coordinates:
pts <- SpatialPoints(dat[, c("lng", "lat")], proj4string = CRS("+proj=longlat"))
At this point your pts and state_poly should be in longitude/latitude coordinates, but to draw circles of a fixed radius around points, you need to convert them to projected coordinates (i.e. in meters). See this question for more details:
Buffer (geo)spatial points in R with gbuffer
Create a vector with the radii of your circles around each point, and use it with gBuffer (from rgeos) and your points layer:
circ <- gBuffer(pts, width = radii, byid = TRUE)
The byid argument means it does it separately for each point, using the different values in radii in the same order as the points.
Convert the state polygons to lines: state_lines <- as(state_poly, "SpatialLines")
Use gIntersects(circ, state_lines, byid = TRUE) .
Because of byid = TRUE, the return value is a matrix with one row per circle in your spgeom1 and one column per state boundaries in spgeom2. Note that if the circle intersect a boundary between two states, it should have two "TRUE" values in that row (one for each state). If it intersects with water or the external perimeter of the US it may have only one "TRUE" value in the row.
Here is the Final Code!
library("maps")
library("mapproj")
library("RColorBrewer")
library("mapdata")
library("ggplot2")
library("rgeos")
library("dismo")
library("ggmap")
library("rgdal")
#import shape file (.shp), make sure all the other files in the zip are included in
#your file location!
state_poly <- readOGR(dsn = 'C:/Users/chopp/Documents/R', layer='cb_2015_us_state_500k')
#data containing lng and lat coordinates with radii
data <- read.csv("R/longlat.csv", header = T)
#create spatial point objects out of your lng and lat data
pts <- SpatialPoints(data[,c("lng","lat")], proj4string = CRS("+proj=longlat"))
#convert spatial points to projected coordinates (points and map lines)
ptsproj <- spTransform(pts, CRS("+init=epsg:3347"))
state_poly_proj<- spTransform(state_poly, CRS("+init=epsg:3347"))
#convert radii units to meters, used in our gBuffer argument later on
radii <- data$rad*1609.344
#create circular polygons with. byid = TRUE will create a circle for each point
circ <- gBuffer(ptsproj, width = radii, byid = TRUE)
#convert state polygons to state lines
state_lines<- as(state_poly_proj, "SpatialLines")
#use gIntersects with byid = TRUE to return a matrix where "TRUE" represents
#crossing state boundaries or water
intdata <- gIntersects(circ, state_lines, byid = TRUE)
#write the matrix out into a csv file
write.csv(intdata,"R/Agents Intercepts 2.csv")
I would like to find the minimum distance between points and polygon boundary (all points lie inside the polygon). If that is possible, how can I extract the values?, so I can find a correlation between numbers of individuals and the distance from the border.
The polygon is on .SHP format and points on X/Y coordinates.
Any missing information please let me know! Your help is greatly appreciated!
unit square polygon:
library(sp)
x = cbind(c(0,1,1,0,0),c(0,0,1,1,0))
pol = SpatialPolygons(list(Polygons(list(Polygon(x)), "ID")))
random points in unit square:
set.seed(131)
pts = SpatialPoints(cbind(runif(10), runif(10)))
plot(pol)
points(pts, col = 'red')
compute distances:
library(rgeos)
gDistance(pts, pol, byid = TRUE) # will be 0, all inside
gDistance(pts, as(pol, "SpatialLines"), byid = TRUE) # dist to line
add to plot:
text(coordinates(pts),
as.character(
round(as.vector(gDistance(pts, as(pol, "SpatialLines"), byid = TRUE)), 3)),
pos = 4)
read your polygon data from a shapefile into R by using readOGR in package rgdal
The spatstat package has a function nncrossthat finds the nearest neighbour between two sets of point or one set of points and a set of segments.
It is relatively easy to load a set of x/y values to create a spatstat point pattern object: if X and Y are two vectors containing your coordinates, you can create a point pattern object with
library(spatstat)
p = ppp(x,y)
You need to convert your shp data to spatstat segment pattern object. To do so, you can load the shp file with commands from maptools and than convert into a spatstat object:
library(maptools)
shp = readShapeSpatial("yourdata.shp") #read shp file
shp = as.psp(shp) # convert to psp object
To calculate your nearest neighbour distance, you have to use nncross
nncross(p,shp)
Follow the steps of #xraynaud (slightly modified):
library(maptools)
shp = readShapeSpatial("yourdata.shp") #read shp file
W = as.owin(shp) # convert to owin object
library(spatstat)
p = ppp(x, y, window = W)
Now p is a point pattern containing the points bounded by the polygon. To compute the distance from each point to the bounding polygon (usually called the window in spatstat terminology):
d = bdist.points(p)
Now d is a vector of distances.
I have a set of about 5000 geographical (WGS84) coordinates. All of them are inside 40km square.
Is there any algorithm / R function to find point, inside square and not in the given set, farthest from any point from set?
I mean how to find point in the square where the distance to the nearest point from set is longest?
Now I do it by generating grid of coordinates equally spaced and finding distance from each grid point to the nearest set point. Is there any less numerical / not brute force method?
EDIT:
I made mistake in previous version of the question. Maybe this will help:
Set of points are coordinates of the 5000 shops in the city. I want to find place in the city where distance to the nearest shop is the longest.
I think that if the point you seek isn't on the edge of the box then it has to be at a vertex of the voronoi tesselation of the points. If it is on the edge of the box then it has to be on the intersection of the box and an edge of the voronoi tesselation.
So if you compute the voronoi tesselation and then use rgeos to intersect it with the box, that gives you a set of possible points. You can then use the FNN package to compute the neighbour distances from those possible points to the data points, sort, and find the possible point with the biggest nearest neighbour.
That gives you an exact point without any of this gridding business. If it wasn't so close to bedtime I'd sort out some code to do it. You probably want the deldir package or voronoi tesselations. It might even already do the box intersection...
Okay, not quite bedtime. Here's the solution:
findM <- function(pts,xmin,xmax,ymin,ymax){
require(deldir)
require(FNN)
d = deldir(pts[,1],pts[,2],rw=c(xmin,xmax,ymin,ymax))
vpts = rbind(as.matrix(d$dirsgs[,1:2]),as.matrix(d$dirsgs[,3:4]))
vpts = rbind(vpts,cbind(c(xmin,xmax,xmin,xmax),c(ymin,ymin,ymax,ymax)))
vpts = vpts[!duplicated(vpts),]
nn = get.knnx(pts,vpts,k=1)
ptmin = which(nn$nn.dist==max(nn$nn.dist))
list(point = vpts[ptmin,,drop=FALSE], dist = nn$nn.dist[ptmin])
}
Edited version now returns one point and adds the corner points as possibles.
Here's an example that uses several functions (distanceFromPoints(), maxValue(), Which(), and xyFromCell()) from the raster package to perform the key calculations:
# Load required libraries
library(sp)
library(rgdal)
library(raster)
# Create a SpatialPoints object with 10 points randomly sampled from
# the area lying between longitudes 0 and 1 and latitudes 0 and 1
bbox <- matrix(c(0,0,1,1), ncol=2, dimnames = list(NULL, c("min", "max")))
PRJ4 <- CRS("+proj=longlat +datum=WGS84 +ellps=WGS84")
S <- Spatial(bbox = bbox, proj4string = PRJ4)
SP <- spsample(S, 10, type="random")
# Create a raster object covering the same area
R <- raster(extent(bbox), nrow=100, ncol=100, crs=PRJ4)
# Find the coordinates of the cell that is farthest from all of the points
D <- distanceFromPoints(object = R, xy = SP)
IDmaxD <- Which(D == maxValue(D), cells=TRUE)
(XY <- xyFromCell(D, IDmaxD))
# x y
# [1,] 0.005 0.795
# Plot the results
plot(D, main = "Distance map, with most distant cell in red")
points(SP)
points(XY, col="red", pch=16, cex=2)