[R] code to convert 3D geographical coordinates to Cartesian?
Tom Roche
Tom_Roche at pobox.com
Mon Dec 31 16:10:01 CET 2012
summary: I'm looking for packaged, tested code to convert geographical
coordinates (e.g., longitude, latitude, elevation) to Cartesian
coordinates (x,y,z) in 3-space. I know of R code for 2-space and for
spherical <-> Cartesian. This can be extended (I attach a quick kludge
extending pracma::sph2cart), but a proper extension is non-trivial.
details:
https://stat.ethz.ch/pipermail/r-help/2012-December/332658.html
>>>> Is there packaged code to convert geographical coordinates (e.g.,
>>>> longitude, latitude, elevation) to Cartesian coordinates in
>>>> 3-space? [Since] there's certainly scope for error, [I'd] prefer
>>>> to use tested, well-used code if available.
https://stat.ethz.ch/pipermail/r-help/2012-December/332666.html
>>> Have you checked the spatial stats task view on CRAN?
https://stat.ethz.ch/pipermail/r-help/2012-December/332667.html
>> I have, and can't believe that functionality this fundamental is not
>> API. But I have looked at several packages and am not seeing it.
https://stat.ethz.ch/pipermail/r-help/2012-December/332671.html
(rearranged)
> How did you search?
> install.packages("sos")
> require(sos)
> > findFn("latitude longitude cartesian")
Thanks! I didn't know about that search mechanism. I was googling via
rseek.org and reading <package/>.pdf for packages that looked useful.
Unfortunately
> sphereplot::sph2car "Transforms 3D spherical coordinates to cartesian
> coordinates"
Note this does spherical <-> Cartesian, not geographical <-> Cartesian.
That can be extended (see a quick hack following my .sig to end of post,
extending Borchers' implementation), but the extension is {non-trivial,
error-prone}. Hence my hope to find code that is much better
tested/known/used.
> GEOmap::Lll2xyz "List Lat-Lon to cartesian XYZ"
GEOmap::Lll2xyz (and all the functions I saw in GEOmap) are 2D-input:
they take arguments=(lat, lon), not (lat, lon, elev). Again, this could
be extended; but, again, the same caveats apply.
> You have a very different notion of what would be considered core
> capabilities in a statistics program than I have.
Geospatial information comes in a wide variety of formats. Doing
*geospatial* statistics with *real-world data* requires the ability to
transform those various formats. I consider geospatiality to be a core
competency for R, and geographical <-> Cartesian transforms to be
fundamental to that: YMMV.
thanks again, Tom Roche <Tom_Roche at pobox.com>
------------------sample hack follows to end of post------------------
# Convert geographic coordinates (lon, lat, elevation) to
# Cartesian coordinates (x, y, z) relative to earth center.
geo2cart <- function(lon.lat.elev) {
stopifnot(is.numeric(lon.lat.elev))
library(pracma) # for sph2cart
return(sph2cart(geo2sph(lat.lon.elev)))
} # end function geo2cart(lon.lat.elev)
# Convert geographic coordinates (lon, lat, elevation) to
# spherical coordinates (azimuth/Θ, polar angle/φ, radial distance/r),
# relative to earth center
geo2sph <- function(lon.lat.elev) {
stopifnot(is.numeric(lon.lat.elev))
# Use convention of package=pracma: see http://cran.r-project.org/web/packages/pracma/
# same as described @ http://en.wikipedia.org/wiki/File:3D_Spherical_2.svg
# to convert
# * longitude -> azimuth (Θ)
# * latitude -> polar angle (φ)
# * elevation -> radial distance from earth center (r)
if (is.vector(lon.lat.elev) && length(lon.lat.elev) == 3) {
theta <- lon2azi(lon.lat.elev[1])
phi <- lat2pol(lon.lat.elev[2])
r <- elev2rdist(lon.lat.elev[3])
m <- 1
} else if (is.matrix(lon.lat.elev) && ncol(lon.lat.elev) == 3) {
theta <- lon2azi(lon.lat.elev[,1])
phi <- lat2pol(lon.lat.elev[,2])
r <- elev2rdist(lon.lat.elev[,3])
m <- nrow(lon.lat.elev)
} else {
stop('geo2sph: ERROR: input must be a vector of length 3 or a matrix with 3 columns.')
}
if (m == 1) {
tpr <- c(theta, phi, r)
} else {
tpr <- cbind(theta, phi, r)
}
return(tpr)
} # end function geo2sph(lon.lat.elev)
# Filter and convert longitudes to radians.
# Much-too-simple tests:
# > lon2azi(c(seq(0, 180), seq(-179, -1)))/pi
# > lon2azi(c(0:359))
lon2azi <- function(lon) {
library(pracma) # for deg2rad
stopifnot(is.numeric(lon))
# only handle vectors
if (!is.vector(lon)) {
lon.vec <- c(lon)
} else {
lon.vec <- lon
}
# only take -180 <= lon.vec <= 180
# TODO: better error messages
stopifnot(length(lon.vec[lon.vec < -180]) == 0)
stopifnot(length(lon.vec[lon.vec > 180]) == 0)
# Convert to azimuth=Θ in radians.
# Convert to 0 <= lon.vec <= 360 (hmm ...) e.g., -179 -> 181.
lon.vec[lon.vec < 0] <- 360.0 + lon.vec[lon.vec < 0]
theta.vec <- deg2rad(lon.vec)
if (!is.vector(lon)) {
return(theta.vec[1])
} else {
return(theta.vec)
}
} # end function lon2azi(lon)
# Filter and convert latitudes to radians.
# Much-too-simple tests:
# > lat2pol(c(seq(90, 0), seq(-1, -90)))/pi
# > lat2pol(c(-100:100))
lat2pol <- function(lat) {
library(pracma) # for deg2rad
stopifnot(is.numeric(lat))
# only handle vectors
if (!is.vector(lat)) {
lat.vec <- c(lat)
} else {
lat.vec <- lat
}
# only take -90 <= lat.vec <= 90
# TODO: better error messages
stopifnot(length(lat.vec[lat.vec < -90]) == 0)
stopifnot(length(lat.vec[lat.vec > 90]) == 0)
# Convert to polar angle=φ in radians.
# northern hemisphere + equator:
# lat.vec[lat.vec >= 0] <- 90.0 - lat.vec[lat.vec >= 0]
# southern hemisphere:
# lat.vec[lat.vec < 0] <- 90.0 - lat.vec[lat.vec < 0]
phi.vec <- deg2rad(90.0 - lat.vec)
if (!is.vector(lat)) {
return(phi.vec[1])
} else {
return(phi.vec)
}
} # end function lat2pol(lat)
# Convert elevation to radial distance
# Much-too-simple tests:
# > elev2rdist(c(seq(1000, by=1000, length=10)))
# > elev2rdist(c(seq(-1000, by=1000, length=10)))
elev2rdist <- function(elev) {
r.earth.meters <- 6370000.0 # constant, kludge, see below
stopifnot(is.numeric(elev))
# only handle vectors
if (!is.vector(elev)) {
elev.vec <- c(elev)
} else {
elev.vec <- elev
}
# Only take non-negative elevations.
stopifnot(length(elev.vec[elev.vec < 0]) == 0)
# Convert elevation to radial distance (from earth center).
# TODO: use coordinate reference system.
# Kludge: spherical earth with radius defined above.
r.vec <- r.earth.meters + elev
if (!is.vector(elev)) {
return(r.vec[1])
} else {
return(r.vec)
}
} # end function elev2rdist(elev)
More information about the R-help
mailing list