Для запису, ось повна, коментована реалізація розрахунків індикатриси Тіссота (та пов'язаних з ними) розрахунків у R
, з відпрацьованим прикладом. Джерелом рівнянь є Карта проекції Джона Снайдера - робочий посібник.
tissot <- function(lambda, phi, prj=function(z) z+0, asDegrees=TRUE, A = 6378137, f.inv=298.257223563, ...) {
#
# Compute properties of scale distortion and Tissot's indicatrix at location `x` = c(`lambda`, `phi`)
# using `prj` as the projection. `A` is the ellipsoidal semi-major axis (in meters) and `f.inv` is
# the inverse flattening. The projection must return a vector (x, y) when given a vector (lambda, phi).
# (Not vectorized.) Optional arguments `...` are passed to `prj`.
#
# Source: Snyder pp 20-26 (WGS 84 defaults for the ellipsoidal parameters).
# All input and output angles are in degrees.
#
to.degrees <- function(x) x * 180 / pi
to.radians <- function(x) x * pi / 180
clamp <- function(x) min(max(x, -1), 1) # Avoids invalid args to asin
norm <- function(x) sqrt(sum(x*x))
#
# Precomputation.
#
if (f.inv==0) { # Use f.inv==0 to indicate a sphere
e2 <- 0
} else {
e2 <- (2 - 1/f.inv) / f.inv # Squared eccentricity
}
if (asDegrees) phi.r <- to.radians(phi) else phi.r <- phi
cos.phi <- cos(phi.r) # Convenience term
e2.sinphi <- 1 - e2 * sin(phi.r)^2 # Convenience term
e2.sinphi2 <- sqrt(e2.sinphi) # Convenience term
if (asDegrees) units <- 180 / pi else units <- 1 # Angle measurement units per radian
#
# Lengths (the metric).
#
radius.meridian <- A * (1 - e2) / e2.sinphi2^3 # (4-18)
length.meridian <- radius.meridian # (4-19)
radius.normal <- A / e2.sinphi2 # (4-20)
length.normal <- radius.normal * cos.phi # (4-21)
#
# The projection and its first derivatives, normalized to unit lengths.
#
x <- c(lambda, phi)
d <- numericDeriv(quote(prj(x, ...)), theta="x")
z <- d[1:2] # Projected coordinates
names(z) <- c("x", "y")
g <- attr(d, "gradient") # First derivative (matrix)
g <- g %*% diag(units / c(length.normal, length.meridian)) # Unit derivatives
dimnames(g) <- list(c("x", "y"), c("lambda", "phi"))
g.det <- det(g) # Equivalent to (4-15)
#
# Computation.
#
h <- norm(g[, "phi"]) # (4-27)
k <- norm(g[, "lambda"]) # (4-28)
a.p <- sqrt(max(0, h^2 + k^2 + 2 * g.det)) # (4-12) (intermediate)
b.p <- sqrt(max(0, h^2 + k^2 - 2 * g.det)) # (4-13) (intermediate)
a <- (a.p + b.p)/2 # (4-12a)
b <- (a.p - b.p)/2 # (4-13a)
omega <- 2 * asin(clamp(b.p / a.p)) # (4-1a)
theta.p <- asin(clamp(g.det / (h * k))) # (4-14)
conv <- (atan2(g["y", "phi"], g["x","phi"]) + pi / 2) %% (2 * pi) - pi # Middle of p. 21
#
# The indicatrix itself.
# `svd` essentially redoes the preceding computation of `h`, `k`, and `theta.p`.
#
m <- svd(g)
axes <- zapsmall(diag(m$d) %*% apply(m$v, 1, function(x) x / norm(x)))
dimnames(axes) <- list(c("major", "minor"), NULL)
return(list(location=c(lambda, phi), projected=z,
meridian_radius=radius.meridian, meridian_length=length.meridian,
normal_radius=radius.normal, normal_length=length.normal,
scale.meridian=h, scale.parallel=k, scale.area=g.det, max.scale=a, min.scale=b,
to.degrees(zapsmall(c(angle_deformation=omega, convergence=conv, intersection_angle=theta.p))),
axes=axes, derivatives=g))
}
indicatrix <- function(x, scale=1, ...) {
# Reprocesses the output of `tissot` into convenient geometrical data.
o <- x$projected
base <- ellipse(o, matrix(c(1,0,0,1), 2), scale=scale, ...) # A reference circle
outline <- ellipse(o, x$axes, scale=scale, ...)
axis.major <- rbind(o + scale * x$axes[1, ], o - scale * x$axes[1, ])
axis.minor <- rbind(o + scale * x$axes[2, ], o - scale * x$axes[2, ])
d.lambda <- rbind(o + scale * x$derivatives[, "lambda"], o - scale * x$derivatives[, "lambda"])
d.phi <- rbind(o + scale * x$derivatives[, "phi"], o - scale * x$derivatives[, "phi"])
return(list(center=x$projected, base=base, outline=outline,
axis.major=axis.major, axis.minor=axis.minor,
d.lambda=d.lambda, d.phi=d.phi))
}
ellipse <- function(center, axes, scale=1, n=36, from=0, to=2*pi) {
# Vector representation of an ellipse at `center` with axes in the *rows* of `axes`.
# Returns an `n` by 2 array of points, one per row.
theta <- seq(from=from, to=to, length.out=n)
t((scale * t(axes)) %*% rbind(cos(theta), sin(theta)) + center)
}
#
# Example: analyzing a GDAL reprojection.
#
library(rgdal)
prj <- function(z, proj.in, proj.out) {
z.pt <- SpatialPoints(coords=matrix(z, ncol=2), proj4string=proj.in)
w.pt <- spTransform(z.pt, CRS=proj.out)
return(w.pt@coords[1, ])
}
r <- tissot(130, 54, prj, # Longitude, latitude, and reprojection function
proj.in=CRS("+init=epsg:4267"), # NAD 27
proj.out=CRS("+init=esri:54030")) # World Robinson projection
i <- indicatrix(r, scale=10^4, n=71)
plot(i$outline, type="n", asp=1, xlab="Easting", ylab="Northing")
polygon(i$base, col=rgb(0, 0, 0, .025), border="Gray")
lines(i$d.lambda, lwd=2, col="Gray", lty=2)
lines(i$d.phi, lwd=2, col=rgb(.25, .7, .25), lty=2)
lines(i$axis.major, lwd=2, col=rgb(.25, .25, .7))
lines(i$axis.minor, lwd=2, col=rgb(.7, .25, .25))
lines(i$outline, asp=1, lwd=2)