0

I have a datasets of X and Y coordinates that are in packed DMS format and I want to transform them to decimal degrees. To do that I have to separate the degrees, minutes and seconds. It would be straightforward if all the numbers in the column had the same length, but some have 7 and some 8. The data set looks something like this:

coords$X <- c(100942.2, 67942.2, 229942.2, 239942.2, 52942.2)

I had the idea to use an if else statement to check the length of each number using this code:

if (nchar(coords$X == 8)){0
coords$degrees.x <- as.numeric(str_sub(coords$X, 1, 2))
coords$min.x <- as.numeric(substr(coords$X, 3, 4))
coords$sec.x <- as.numeric(substr(coords$XC, 5, 7))
} else {
  coords$degrees.x <- as.numeric(str_sub(coords$X, 1, 1))
  coords$min.x <- as.numeric(substr(coords$X, 2, 3))
  coords$sec.x <- as.numeric(substr(coords$X, 4, 5))
}

But I am getting an error:

Error in if (nchar(coords$XCoordinatePlot == 8)) { : 
  the condition has length > 1

Is there a way to make this work?

starski
  • 141
  • 6
  • Your test data are not strings and do not appear to be in DMS format. To do the sort of conversion you want, you need `ifelse()`, not `if {...} else {...}`. – Limey Apr 13 '23 at 06:58
  • Possible duplicate: [https://stackoverflow.com/questions/69484220/convert-dms-coordinates-to-decimal-degrees-in-r](https://stackoverflow.com/questions/69484220/convert-dms-coordinates-to-decimal-degrees-in-r). I'm not certain because of the inconsistency mentioned above. – Limey Apr 13 '23 at 06:59
  • @Limey , my bad I edited the question, but this is a packed DMS format.. – starski Apr 13 '23 at 07:07
  • @Limey and also the other thread does not apply to my question because of the different format, this is a packed DMS with decimal point – starski Apr 13 '23 at 07:13

2 Answers2

2

Something like this? Use sprintf to have all numbers of the same length, then get the substrings.
Note: The function below discards the decimal part.

X <- c(100942.2, 67942.2, 229942.2, 239942.2, 52942.2)

fun <- function(x) {
  y <- sprintf("%06d", as.integer(x))
  D <- as.integer(substr(y, 1, 2))
  M <- as.integer(substr(y, 3, 4))
  S <- as.integer(substr(y, 5, 6))
  cbind(D, M, S)
}

fun(X)
#>       D  M  S
#> [1,] 10  9 42
#> [2,]  6 79 42
#> [3,] 22 99 42
#> [4,] 23 99 42
#> [5,]  5 29 42

Created on 2023-04-13 with reprex v2.0.2

Rui Barradas
  • 70,273
  • 8
  • 34
  • 66
  • Worked likea charm! Could you just explain how it worked, I don't understand. I guess you specified length of 6 digits with "%6d" but how did it work with the 5 digit length? – starski Apr 13 '23 at 07:48
  • 1
    @starski `d` for integers, `%06` for 6 digits padded with zeros, `%06d`. – Rui Barradas Apr 13 '23 at 09:44
2

There are multiple possible ways to convert these data. The best way in my opinion would be to use division and remainders:

coords$degrees <- coords$X %/% 10000
coords$min <- (coords$X %% 10000) %/% 100
coords$sec <- coords$X %% 100

If you want to use your if/else statement, you could place it inside a function and use map_df() from the package purrr to apply it to each element:

library(purrr)
library(stringr)

convert <- function(x) {
  if (nchar(x) == 8){
    degrees <- as.numeric(str_sub(x, 1, 2))
    min <- as.numeric(substr(x, 3, 4))
    sec <- as.numeric(substr(x, 5, 7))
  } else {
    degrees <- as.numeric(str_sub(x, 1, 1))
    min <- as.numeric(substr(x, 2, 3))
    sec <- as.numeric(substr(x, 4, 5))
  }

  return(data.frame(degrees, min, sec))
}

cbind(coords, map_df(coords$X, convert))

(Note that your function does not round but strip the decimal of secs.)

DrEspresso
  • 211
  • 5