Convert DMS coordinates to decimal degrees in R - r

I have the following coordinates in DMS format. I need to convert them to decimal degrees.
# Libraries
> library(sp)
> library(magrittr)
# Latitide & Longitude as strings
> lat <- '21d11m24.32s'
> lng <- '104d38m26.88s'
I tried:
> lat_d <- char2dms(lat, chd='d', chm='m', chs='s') %>% as.numeric()
> lng_d <- char2dms(lng, chd='d', chm='m', chs='s') %>% as.numeric()
> print(c(lat_d, lng_d))
[1] 21.18333 104.63333
Although close, this result is different from the output I get from this website. According to this site, the correct output should be:
Latitude: 21.190089
Longitude: 104.6408
It seems that sp::char2dms and as.numeric are rounding the coordinates. I noticed this issue when converting a large batch of DMS coordinates using this method because the number of unique values decreases drastically after the conversion.

You are right! To tell you the truth, I didn't notice this problem.
To get around this, here is a solution with the use of the package measurements:
REPREX:
install.packages("measurements")
library(measurements)
lat <- conv_unit('21 11 24.32', from = "deg_min_sec", to = "dec_deg")
long <- conv_unit('104 38 26.88' , from = "deg_min_sec", to = "dec_deg")
print(c(lat, long))
#> [1] "21.1900888888889" "104.6408"
Created on 2021-10-07 by the reprex package (v2.0.1)
Edit from OP
This can also be solved by adding 'N' or 'S' to latitude and 'E' or 'W' to longitude.
# Add character to lat & long strings
> lat_d <- char2dms(paste0(lat,'N'), chd='d', chm='m', chs='s') %>% as.numeric()
> lng_d <- char2dms(paste0(lng,'W'), chd='d', chm='m', chs='s') %>% as.numeric()
> print(c(lat_d, lng_d))
[1] 21.19009 -104.64080

Related

stuck with extracting and converting nc file

i have rainfall file nc and temperature file nc, i do'nt really understand with r, no experience before, so i'm trying this script and get error,
library(ncdf4)
library(data.table)
library(raster)
library(metR)
library(rgdal)
tmax2 <- nc_open("E:/SKRIPSI/prec-tmin-tmax-sumut/tmax2006-2022.nc")
> names(tmax2$var)
[1] "TASMAX"
> names(tmax2$dim)
[1] "NTIME1" "XAXIS23_301" "YAXIS26_132" "M2"
> info.file <- GlanceNetCDF(tmaxsumut)
Error in GlanceNetCDF(tmaxsumut) : could not find function "GlanceNetCDF"
>
> #pemilihan lokasi & waktu
> lat <- 0:4
> lon <- 98:100
> wkt <- seq(from = as.Date("2017-01-01"),
+ to = as.Date("2020-12-31"),
+ by = "days")
>
> tmax2 <- ReadNetCDF(tmaxsumut, vars="TASMAX",
+ subset=list(XAXIS23_301=lon, YAXIS26_132= lat, NTIME1=wkt))
Error in ReadNetCDF(tmaxsumut, vars = "TASMAX", subset = list(XAXIS23_301 = lon, :
could not find function "ReadNetCDF"
You are not describing what you want to achieve, making it very difficult to help. Feel free to edit your question to clarify your goals (do not use the comments for that).
I am guessing that you want to extract values from the ncdf file for point (long/lat) locations. If so, similar questions have been asked many times on this site, so you could probably do some more searches.
With standard compliant ncdf files you can simply do:
library(terra)
tmax2 <- rast("E:/SKRIPSI/prec-tmin-tmax-sumut/tmax2006-2022.nc", "TASMAX")
lat <- 1:3
lon <- 98:100
points <- vect(cbind(lon, lat))
e <- extract(tmax2, points)
This only works if the ncdf file has regular raster data. That is not guaranteed, but you provide no information about the file, nor do you provide the file.

How to use third, fourth etc. decimal values in a calculator

I want to calculate the Body Mass Index for four persons and store the results as object XY and append it to a data frame. That works fine so far, but I only manage to do this with rounding the results
Body Mass Index <- round(as.numeric(data[, 4],
digits=2)/(as.numeric(data[, 3])/100)^2)
Body Mass Index
data <- as.data.frame(cbind(data, Body Mass Index))
data
How can I do the same but round the resulting values to third or fourth decimal place? I know it has something to do with round and obviously I changed it, but the other options do not work...
The argument digits in round defines how many digits will be shown. Becareful because certain objects ou function round the data just for the printing. Here is an example.
Libraries
library(tidyverse)
Example Data
df <-
#Random data
tibble(
height = rnorm(10,1.7,sd = .2),
mass = rnorm(10,70)
) %>%
mutate(
# Calculating index
bmi_index = mass/(height^2),
bmi_index_round4 = round(bmi_index,4),
bmi_index_round3 = round(bmi_index,3),
bmi_index_round2 = round(bmi_index,2)
)
Output
> df$bmi_index
[1] 21.69752 33.96611 19.23116 23.32622 20.93689 26.45366 23.50817 33.12299 27.41969 22.49677
> df$bmi_index_round4
[1] 21.6975 33.9661 19.2312 23.3262 20.9369 26.4537 23.5082 33.1230 27.4197 22.4968
> df$bmi_index_round3
[1] 21.698 33.966 19.231 23.326 20.937 26.454 23.508 33.123 27.420 22.497
> df$bmi_index_round2
[1] 21.70 33.97 19.23 23.33 20.94 26.45 23.51 33.12 27.42 22.50

How to get chron to convert a vector of Excel serial dates

I am trying to convert an element of a matrix from what were Excel serial dates to a vector of Date objects before using plot().
I can create a vetor and I get the expected result:
library(chron)
# set date origin as defined in Excel
options(chron.origin = c(month=1, day=1, year=1900))
test_dates <- c(40917:40920)
test_dates
## [1] 40917 40918 40919 40920
chron(test_dates, out.format = "m/d/y")
## [1] 01/11/12 01/12/12 01/13/12 01/14/12
But when I try to use this on my actual vector, it does not work
# first 10 vlaues in vector
pivot_pred$date
## [1] 40917 40918 40919 40920 40921 40922 40923 40924 40925 40926...
chron(pivot_pred$date, out.format = "m/d/y")
## Error in chron(dates. = floor(dts), times. = tms, format = format, out.format = out.format, :
misspecified chron format(s) length
I'm sure this is simple but I have tried many variations and none worked. Any suggestions on what I'm doing wrong?

Errors attempting to applying function to data frame rows in R

I wrote a very simple function (which works well) that returns the timezone given a set of coordinates:
library(XML)
findTZ <- function(lon, lat, date=Sys.Date())
{ apiurl <- sprintf("https://maps.googleapis.com/maps/api/timezone/%s?location=%s,%s&timestamp=%d&sensor=%s",
"xml", lat, lon, as.numeric(as.POSIXct(date)), "false")
TZ <- xmlParse(readLines(apiurl))[["string(//time_zone_id)"]]
return(TZ)
}
findTZ(-112.86, 53.61) # example
However, when I try to run the function on a list of coordinates in a dataframe I get an error: Error in file(con, "r") : invalid 'description' argument
Any hints at what I'm getting wrong here? It seems like it should be very simple.
Here's the very basic data I'm testing on:
DF <- data.frame(
longitude = c(-122, -112, -102),
latitude = c(54, 53, 52)
)
DF$timezone = findTZ(lon=DF$longitude, lat=DF$latitude)
Thank you for any pointers!
EDIT / ADDITION
After implementing the answer from #Floo0 I tried implementing the same solution with another function for calculating sunrise/set times using the same location data (and that I want to return in local time, hence the timezone function).
Here's the sunrise function:
library(maptools)
SSun <- function(lon, lat, date, deg=0, dir, tzone)
{ # deg = solar depth: rise/set=0, civil=6, nautical=12, astronomical=18
# dir = direction: sunrise="dawn", sunset="dusk"
# tzone = time zone of output, NOT of location
siteX <- SpatialPoints(matrix(c(lon, lat), nrow=1), proj4string=CRS("+proj=longlat +datum=WGS84"))
dateX <- as.POSIXct(date, tz=tzone)
duskX <- crepuscule(siteX, dateX, solarDep=deg, direction=dir, POSIXct.out=TRUE)
duskX <- duskX$time # keep only date and time, discard day_frac
return(duskX)
}
SSun(-112.86, 53.61, "2016-09-25", deg=0, dir="dawn", tzone="America/Edmonton") # example
And the updated timezone function:
library(tidyverse); library(xml2)
findTZ <- function(lon, lat, date=Sys.Date()){
apiurl <- sprintf("https://maps.googleapis.com/maps/api/timezone/%s?location=%s,%s&timestamp=%d&sensor=%s",
"xml", lat, lon, as.numeric(as.POSIXct(date)), "false")
read_xml(apiurl) %>% xml_find_first(".//time_zone_id") %>% xml_text
}
findTZ(-112.86, 53.61) # example
And the code I used to call both functions:
DF %>% mutate(date = as.POSIXct(date),
TZ = map2_chr(longitude, latitude, findTZ),
sunrise = SSun(longitude, latitude, date, deg=0, dir="dawn", tzone=TZ))
I feel like I must be misunderstanding how this works. Any insights?
You can do the following (using xml2 instead of XML as i find it easier to use)
require(xml2)
findTZ <- function(lon, lat, date=Sys.Date()){
apiurl <- sprintf("https://maps.googleapis.com/maps/api/timezone/%s?location=%s,%s&timestamp=%d&sensor=%s",
"xml", lat, lon, as.numeric(as.POSIXct(date)), "false")
read_xml(apiurl) %>% xml_find_first(".//time_zone_id") %>% xml_text
}
To loop through your test data you can use:
require(tidyverse)
DF %>% mutate(TZ = map2_chr(longitude, latitude, findTZ))
Which gives you:
longitude latitude TZ
1 -122 54 America/Vancouver
2 -112 53 America/Edmonton
3 -102 52 America/Regina
As #Rich Scriven points out correctly you need to loop through the data somewhere. This loop is "hidden" in the map2_chr call.
Consider mapply to pass each pair of element-wise values into function to return a vector:
DF$timezones <- mapply(findTZ, DF$longitude, DF$latitude)

Converting geo coordinates from degree to decimal

I want to convert my geographic coordinates from degrees to decimals, my data are as follows:
lat long
105252 30°25.264 9°01.331
105253 30°39.237 8°10.811
105255 31°37.760 8°06.040
105258 31°41.190 8°06.557
105259 31°41.229 8°06.622
105260 31°38.891 8°06.281
I have this code but I can not see why it is does not work:
convert<-function(coord){
tmp1=strsplit(coord,"°")
tmp2=strsplit(tmp1[[1]][2],"\\.")
dec=c(as.numeric(tmp1[[1]][1]),as.numeric(tmp2[[1]]))
return(dec[1]+dec[2]/60+dec[3]/3600)
}
don_convert=don1
for(i in 1:nrow(don1)){don_convert[i,2]=convert(as.character(don1[i,2])); don_convert[i,3]=convert(as.character(don1[i,3]))}
The convert function works but the code where I am asking the loop to do the job for me does not work.
Any suggestion is apperciated.
Use the measurements package from CRAN which has a unit conversion function already so you don't need to make your own:
x = read.table(text = "
lat long
105252 30°25.264 9°01.331
105253 30°39.237 8°10.811
105255 31°37.760 8°06.040
105258 31°41.190 8°06.557
105259 31°41.229 8°06.622
105260 31°38.891 8°06.281",
header = TRUE, stringsAsFactors = FALSE)
Once your data.frame is set up then:
# change the degree symbol to a space
x$lat = gsub('°', ' ', x$lat)
x$long = gsub('°', ' ', x$long)
# convert from decimal minutes to decimal degrees
x$lat = measurements::conv_unit(x$lat, from = 'deg_dec_min', to = 'dec_deg')
x$long = measurements::conv_unit(x$long, from = 'deg_dec_min', to = 'dec_deg')
Resulting in the end product:
lat long
105252 30.4210666666667 9.02218333333333
105253 30.65395 8.18018333333333
105255 31.6293333333333 8.10066666666667
105258 31.6865 8.10928333333333
105259 31.68715 8.11036666666667
105260 31.6481833333333 8.10468333333333
Try using the char2dms function in the sp library. It has other functions that will additionally do decimal conversion.
library("sp")
?char2dms
A bit of vectorization and matrix manipulation will make your function much simpler:
x <- read.table(text="
lat long
105252 30°25.264 9°01.331
105253 30°39.237 8°10.811
105255 31°37.760 8°06.040
105258 31°41.190 8°06.557
105259 31°41.229 8°06.622
105260 31°38.891 8°06.281",
header=TRUE, stringsAsFactors=FALSE)
x
The function itself makes use of:
strsplit() with the regex pattern "[°\\.]" - this does the string split in one step
sapply to loop over the vector
Try this:
convert<-function(x){
z <- sapply((strsplit(x, "[°\\.]")), as.numeric)
z[1, ] + z[2, ]/60 + z[3, ]/3600
}
Try it:
convert(x$long)
[1] 9.108611 8.391944 8.111111 8.254722 8.272778 8.178056
Disclaimer: I didn't check your math. Use at your own discretion.
Thanks for answers by #Gord Stephen and #CephBirk. Sure helped me out.
I thought I'd just mention that I also found that measurements::conv_unit doesn't deal with "E/W" "N/S" entries, it requires positive/negative degrees.
My coordinates comes as character strings "1 1 1W" and needs to first be converted to "-1 1 1".
I thought I'd share my solution for that.
df <- c("1 1 1E", "1 1 1W", "2 2 2N","2 2 2S")
measurements::conv_unit(df, from = 'deg_min_sec', to = 'dec_deg')
[1] "1.01694444444444" NA NA NA
Warning message:
In split(as.numeric(unlist(strsplit(x, " "))) * c(3600, 60, 1), :
NAs introduced by coercion
ewns <- ifelse( str_extract(df,"\\(?[EWNS,.]+\\)?") %in% c("E","N"),"+","-")
dms <- str_sub(df,1,str_length(df)-1)
df2 <- paste0(ewns,dms)
df_dec <- measurements::conv_unit(df2,
from = 'deg_min_sec',
to = 'dec_deg'))
df_dec
[1] "1.01694444444444" "-1.01694444444444" "2.03388888888889" "-2.03388888888889"
as.numeric(df_dec)
[1] 1.016944 -1.016944 2.033889 -2.033889
Have a look at the command degree in the package OSMscale.
As Jim Lewis commented before it seems your are using floating point minutes. Then you only concatenate two elements on
dec=c(as.numeric(tmp1[[1]][1]),as.numeric(tmp2[[1]]))
Having degrees, minutes and seconds in the form 43°21'8.02 which as.character() returns "43°21'8.02\"", I updated your function to
convert<-function(coord){
tmp1=strsplit(coord,"°")
tmp2=strsplit(tmp1[[1]][2],"'")
tmp3=strsplit(tmp2[[1]][2],"\"")
dec=c(as.numeric(tmp1[[1]][1]),as.numeric(tmp2[[1]][1]),as.numeric(tmp3[[1]]))
c<-abs(dec[1])+dec[2]/60+dec[3]/3600
c<-ifelse(dec[1]<0,-c,c)
return(c)
}
adding the alternative for negative coordinates, and works great for me . I still don't get why char2dms function in the sp library didn't work for me.
Thanks
Another less elegant option using substring instead of strsplit. This will only work if all your positions have the same number of digits. For negative co-ordinates just multiply by -1 for the correct decimal degree.
x$LatDD<-(as.numeric(substring(x$lat, 1,2))
+ (as.numeric(substring(x$lat, 4,9))/60))
x$LongDD<-(as.numeric(substring(x$long, 1,1))
+ (as.numeric(substring(x$long, 3,8))/60))

Resources