6

In R I have a SpatialLinesDataFrame (for example from a coastline shapefile) and I want to convert it to a SpatialPointsDataFrame. I want to keep all the vertices from the line shapefile to become point shapes in the point shapefile. I also need each point to have the attributes from the original line. For example if the SpatialLinesDataFrame has a line with 5 points and a "name" attribute, then all 5 new points should retain the attribute value.

I figured out some R code to convert the geometry part:

library(rworldmap)
library(sp)
data(coastsCoarse)
lineshape = coastsCoarse

point_coordinates = c()
i = 1
nLines = nrow(lineshape)
for (i in 1: nLines) {
  line1 <- lineshape[i,]@lines[[1]]@Lines[[1]]
  line1coords <- line1@coords
  point_coordinates = rbind(point_coordinates, line1coords)
}

pointshape <- data.frame(x=point_coordinates[,1], y=point_coordinates[,2])
coordinates(pointshape) <- ~x+y

#test result
plot(lineshape)
points(pointshape)

#now how do I transfer the attributes from the lineshape to the pointshape?

I'm stuck on how to transfer the attributes from the SpatialLinesDataFrame to the SpatialPointsDataFrame. I want my script to be reusable for different kinds of SpatialLinesDataFrame variables and so I don't know beforehand the number, names and data types of the attributes.

How can I transfer the attributes?

PolyGeo
  • 65,136
  • 29
  • 109
  • 338
jirikadlec2
  • 1,330
  • 3
  • 19
  • 28

3 Answers3

11

What's wrong with the one-liner:

> ptsCoarse = as(coastsCoarse, "SpatialPointsDataFrame")

Converts the geometry and preserves the attributes, and gives you some more attributes so you can reconstruct the lines back if you so desire:

> head(ptsCoarse@data)
    ScaleRank FeatureCla Lines.NR Lines.ID Line.NR
0           1  Coastline        1        0       1
0.1         1  Coastline        1        0       1
0.2         1  Coastline        1        0       1
0.3         1  Coastline        1        0       1
0.4         1  Coastline        1        0       1
0.5         1  Coastline        1        0       1
Spacedman
  • 63,755
  • 5
  • 81
  • 115
  • Allways these one-liners ;-), brilliant, what's behind the as (...) process – huckfinn Mar 12 '16 at 08:14
  • Its an S4 "as" method, defined here: https://github.com/cran/sp/blob/master/R/SpatialLinesDataFrame-methods.R#L90 - it uses a couple of other as methods to convert the geom and then does a lookup into the original @data to set the attributes. – Spacedman Mar 12 '16 at 08:20
  • Thanks, https://github.com/cran is a very good advise, where to read and learn something from programmes in a fast way. – huckfinn Mar 12 '16 at 08:53
  • 1
    Now, that is an "oh duh" moment, thanks! Embarrassingly enough, I just covered coercion in one of my classes and discussed it in depth with the students. I should just stay off line when I am sick. – Jeffrey Evans Mar 12 '16 at 18:16
  • 1
    Can this be adapted to create the points at equidistant locations? otherwise you are left with points close together where lines are tortuous, but far apart when lines are straight. – ecologist1234 Nov 21 '17 at 15:43
1

I'm not sure why you want duplicate all the attribut data. I take your script and made some adoptions and comments. At least I duplicate all the attribute data at the right place in the loop...

  # !! Duplicate for each point the data column assumng that we have only on row per line
  for (c in 1: num.dcols) {
    # [[names.dcols[c]]] will copy the column names
    temp.tab[[names.dcols[c]]] <- rep(attr[1,c],num.coord)
  } # eof for duplicate

. Here is the script:

# Load libraries
library(rworldmap)
library(sp)
# Load data
data(coastsCoarse)
# Get coast lines
coast.lines <- coastsCoarse
# Names of the data columns to copy
names.dcols <- names(coast.lines@data)
# Number of data columns
num.dcols <- length(names.dcols) 
# The resulting point data frame
coast.points <- NA
# Number of coast lines
num.lines <- nrow(coast.lines)
# Loop over the datasets 
for (i in 1: num.lines) {
  # Get one line .. sure not more elements? ..lines[[1]]@Lines[[1]] 
  line <- coast.lines[i,]@lines[[1]]@Lines[[1]]
  # Get the attributes
  attr <- coast.lines[i,]@data
  # Create a temporary table with the coords
  temp.tab <- as.data.frame(line@coords)
  # Set the column names for the coords
  names(temp.tab) <- c('x','y');
  # Get the number of coordinates in the temporary table
  num.coord <- nrow(temp.tab)
  # !! Duplicate for each point the data column assumng that we have only on row per line
  for (c in 1: num.dcols) {
    # [[names.dcols[c]]] will copy the column names
    temp.tab[[names.dcols[c]]] <- rep(attr[1,c],num.coord)
  }  # eof for duplicate
  # Assign the temporary tab as result data table in the first loop 
  if ( i == 1 ) {
    coast.points <- temp.tab
  } 
  # Or append the temporary tab to the result
  else {
    coast.points <- rbind(coast.points, temp.tab)
  } # eof if else first loop
} # eof for all lines
# Assign coordinates
coordinates(coast.points)<-~x+y
# Test the result
plot(coast.lines)
points(coast.points)
# EOF

Very interesting.. I've learned from the post of @Jeffrey Evans to handle and run the attribute duplication process in two steps. You can loop over the datasets collect all temporay tables in a list and use do.call on the rbind function to build the coast.points table later. But I build the coordiantes later to prevent the usage of option(warn=XX).

# Load libraries
library(rworldmap)
library(sp)
# Load data
data(coastsCoarse)
# Get coast lines
coast.lines = coastsCoarse
# Point object at a collection list ..later the spatial table
coast.points <- list()
# Number of coast lines
num.lines = nrow(coast.lines)
# Loop over the datasets with the shortcut of @Jeffrey Evans 
# to build the point table via a `list` and a `do.call` of the
# rbind process and build the coordiantes later to prevent
# the usage of `option(warn=XX)`
for (i in 1: num.lines) {
  # Get one line and the attribues ..sure not more elements? ..lines[[1]]@Lines[[1]] 
  coast.points[[i]] <- data.frame(
                  coast.lines[i,]@lines[[1]]@Lines[[1]]@coords,
                  coast.lines[i,]@data)
}  
# Build the spatial table from the collection list 
coast.points <- do.call("rbind", coast.points)
# Assign coordinates from the default Varables
coordinates(coast.points) <- ~X1+X2
# Test the result
plot(coast.lines)
points(coast.points)
# EOF 
huckfinn
  • 3,583
  • 17
  • 35
  • It is not a good practice to name objects the same as R functions as it can cause quite unexpected results. The line 'data <- coast.lines[i,]@data' is the one that could cause problems because of utils::data in base. – Jeffrey Evans Mar 12 '16 at 03:44
  • @Jeffrey, ok to be exact I change data <- coast.lines[i,]@data to attr <- coast.lines[i,]@data – huckfinn Mar 12 '16 at 07:16
  • And, wait for it, "attr" is a primitive in base. Sorry, I am not trying to give you a hard time but, this has come up for me when writing functions that fail unexpectedly. A colleague of mine just had an issue specifying an offset in a negative binomial model that was failing in some very strange ways with the model statement and predict. Turns out that she had a column in her data called "offset" that was in one model being accepted as a covariate but when called using offset=(log(offset)) was tanking because it was seeing the column call as a function. This is just good to keep in mind. – Jeffrey Evans Mar 14 '16 at 16:28
  • @Jeffrey I don't worry, Does R and it's packages left free some letter combination to name an object that could be an attribute or data table ;-) – huckfinn Mar 16 '16 at 18:08
1

You can simplify this a bit using a list to store the vertices, coerce to a SpatialPointsDataFrame object and add line attributes in the loop.

Add sp some example data from rworldmap.

library(sp)
library(rworldmap)
lineshape <- coastsCoarse  

Create an empty list to store results, loop through lines and pull vertices and associated attributes for the associated line. R will throw an error (thus the options(warn=-1)) when the coordinates matrix is combined with a one line data.frame but, it works just fine. The object is then coerced into a SpatialPointsDataFrame and stored in the pts list. The do.call function is used to combine into a single SpatialPointsDataFrame object.

pts <- list()
options(warn=-1)
  for (i in 1:length(lineshape)) {
    l <- data.frame(lineshape[i,]@lines[[1]]@Lines[[1]]@coords,
                    lineshape[i,]@data)
    coordinates(l) <- ~X1+X2                
    pts[[i]] <- l
  }
options(warn=0)
pts <- do.call("rbind", pts)

Plot results by color coding the points using the "FeatureCla" attribute.

pcol <- ifelse( pts@data[,"ScaleRank"] == 0, "blue",
          ifelse( pts@data[,"ScaleRank"] == 1, "red", NA))                
plot(lineshape)
  plot(pts, col = pcol, pch = 20, cex = 0.5, add = TRUE)
  legend("bottomleft", legend=c("Coastline","Country"), pch=c(20,20),
         col=c("blue","red"))
Jeffrey Evans
  • 31,750
  • 2
  • 47
  • 94