How to find point related to set of coordinates? - r

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)

Related

Shortest distance in R

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

Snapping a point on the raster based on condition

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).

Calculate Centroid WITHIN / INSIDE a SpatialPolygon

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)
}

Finding the minimum distance between all points and the polygon boundary

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.

Export R plot to shapefile

I am fairly new to R, but not to ArcView. I am plotting some two-mode data, and want to convert the plot to a shapefile. Specifically, I would like to convert the vertices and the edges, if possible, so that I can get the same plot to display in ArcView, along with the attributes.
I've installed the package "shapefiles", and I see the convert.to.shapefile command, but the help doesn't talk about how to assign XY coords to the vertices.
Thank you,
Tim
Ok, I'm making a couple of assumptions here, but I read the question as you're looking to assign spatial coordinates to a bipartite graph and export both the vertices and edges as point shapefiles and polylines for use in ArcGIS.
This solution is a little kludgey, but will make shapefiles with coordinate limits xmin, ymin and xmax, ymax of -0.5 and +0.5. It will be up to you to decide on the graph layout algorithm (e.g. Kamada-Kawai), and project the shapefiles in the desired coordinate system once the shapefiles are in ArcGIS as per #gsk3's suggestion. Additional attributes for the vertices and edges can be added where the points.data and edge.data data frames are created.
library(igraph)
library(shapefiles)
# Create dummy incidence matrix
inc <- matrix(sample(0:1, 15, repl=TRUE), 3, 5)
colnames(inc) <- c(1:5) # Person ID
rownames(inc) <- letters[1:3] # Event
# Create bipartite graph
g.bipartite <- graph.incidence(inc, mode="in", add.names=TRUE)
# Plot figure to get xy coordinates for vertices
tk <- tkplot(g.bipartite, canvas.width=500, canvas.height=500)
tkcoords <- tkplot.getcoords(1, norm=TRUE) # Get coordinates of nodes centered on 0 with +/-0.5 for max and min values
# Create point shapefile for nodes
n.points <- nrow(tkcoords)
points.attr <- data.frame(Id=1:n.points, X=tkcoords[,1], Y=tkcoords[,2])
points.data <- data.frame(Id=points.attr$Id, Name=paste("Vertex", 1:n.points, sep=""))
points.shp <- convert.to.shapefile(points.attr, points.data, "Id", 1)
write.shapefile(points.shp, "~/Desktop/points", arcgis=TRUE)
# Create polylines for edges in this example from incidence matrix
n.edges <- sum(inc) # number of edges based on incidence matrix
Id <- rep(1:n.edges,each=2) # Generate Id number for edges.
From.nodes <- g.bipartite[[4]]+1 # Get position of "From" vertices in incidence matrix
To.nodes <- g.bipartite[[3]]-max(From.nodes)+1 # Get position of "To" vertices in incidence matrix
# Generate index where position alternates between "From.node" to "To.node"
node.index <- matrix(t(matrix(c(From.nodes, To.nodes), ncol=2)))
edge.attr <- data.frame(Id, X=tkcoords[node.index, 1], Y=tkcoords[node.index, 2])
edge.data <- data.frame(Id=1:n.edges, Name=paste("Edge", 1:n.edges, sep=""))
edge.shp <- convert.to.shapefile(edge.attr, edge.data, "Id", 3)
write.shapefile(edge.shp, "~/Desktop/edges", arcgis=TRUE)
Hope this helps.
I'm going to take a stab at this based on a wild guess as to what your data looks like.
Basically you'll want to coerce the data into a data.frame with two columns containing the x and y coordinates (or lat/long, or whatever).
library(sp)
data(meuse.grid)
class(meuse.grid)
coordinates(meuse.grid) <- ~x+y
class(meuse.grid)
Once you have it as a SpatialPointsDataFrame, sp provides some decent functionality, including exporting shapefiles:
writePointsShape(meuse.grid,"/home/myfiles/wherever/myshape.shp")
Relevant help files examples are drawn from:
coordinates
SpatialPointsDataFrame
readShapePoints
At least a few years ago when I last used sp, it was great about projection and very bad about writing projection information to the shapefile. So it's best to leave the coordinates untransformed and manually tell Arc what projection it is. Or use writeOGR rather than writePointsShape.

Resources