s2-tools
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
299 lines • 10 kB
JavaScript
import { ProjectionBase } from '.';
import { D2R, EPSLN, HALF_PI, QUART_PI, TWO_PI } from '../constants';
import { adjustLon, phi2z, tsfnz } from '../common';
/**
* # Oblique Mercator
*
* **Classification**: Conformal cylindrical
*
* **Available forms**: Forward and inverse, spherical and ellipsoidal
*
* **Defined area**: Global, but reasonably accurate only within 15 degrees of the oblique central line
*
* **Alias**: omerc
*
* **Domain**: 2D
*
* **Input type**: Geodetic coordinates
*
* **Output type**: Projected coordinates
*
* ## Projection String
* ```
* +proj=omerc +lat_1=45 +lat_2=55
* ```
*
* ## Required Parameters
* - `+lat_1=<value>`: Latitude of the first point on the central line.
* - `+lat_2=<value>`: Latitude of the second point on the central line.
*
* ## Optional Parameters
* - `+alpha=<value>`: Azimuth of the centerline clockwise from north at the center point of the line.
* - `+gamma=<value>`: Azimuth of the centerline clockwise from north of the rectified bearing of the centerline.
* - `+lonc=<value>`: Longitude of the projection center (overrides `+lon_0`).
* - `+lat_0=<value>`: Latitude of the projection center.
* - `+no_rot`: Disables rectification (historical reason).
* - `+no_off`: Disables origin offset to the center of projection.
* - `+k_0=<value>`: Scale factor at the central line.
* - `+x_0=<value>`: False easting.
* - `+y_0=<value>`: False northing.
*
* ## Usage Example
* ```
* echo 12 55 | proj +proj=omerc +alpha=90 +ellps=GRS80
* echo 12 55 | proj +proj=omerc +alpha=0 +R=6400000
* echo 12 55 | proj +proj=omerc +lon_1=0 +lat_1=-1 +lon_2=0 +lat_2=0 +R=6400000
* echo 12 55 | proj +proj=tmerc +R=6400000
* echo 12 55 | proj +proj=omerc +lon_1=-1 +lat_1=1 +lon_2=0 +lat_2=0 +ellps=GRS80
* echo 10.536498003 56.229892362 | cs2cs +proj=longlat +ellps=GRS80 +to +proj=omerc +axis=wnu +lonc=9.46 +lat_0=56.13333333 +x_0=-266906.229 +y_0=189617.957 +k=0.9999537 +alpha=-0.76324 +gamma=0 +ellps=GRS80
* ```
*
* ## Caveats
* The two-point method with no rectification is probably only marginally useful.
*
* 
*/
export class HotineObliqueMercator extends ProjectionBase {
name = 'HotineObliqueMercator';
static names = [
'HotineObliqueMercator',
'Hotine_Oblique_Mercator',
'Hotine Oblique Mercator',
'Hotine_Oblique_Mercator_Azimuth_Natural_Origin',
'Hotine_Oblique_Mercator_Two_Point_Natural_Origin',
'Hotine_Oblique_Mercator_Azimuth_Center',
'Oblique_Mercator',
'omerc',
];
// HotineObliqueMercator specific variables
noOff;
noRot;
TOL = 1e-7;
lam0;
A = 0;
B = 0;
E = 0;
singam;
cosgam;
sinrot;
cosrot;
rB;
ArB;
BrA;
u0;
vPoleN;
vPoleS;
/**
* Preps an HotineObliqueMercator projection
* @param params - projection specific parameters
*/
constructor(params) {
const { abs, pow, sin, cos, sqrt, asin, log, atan, PI, tan } = Math;
super(params);
const { TOL } = this;
let con, cosph0, D, F, H, L, sinph0, p, J, gamma = 0, gamma0, lamc = 0, lam1 = 0, lam2 = 0, phi1 = 0, phi2 = 0, alphaC = 0;
// only Type A uses the noOff or noUoff property
// https://github.com/OSGeo/proj.4/issues/104
this.noOff = isTypeA(params ?? {});
this.noRot = params?.noRot ?? false;
let alp = false;
if ('alpha' in this) {
alp = true;
}
let gam = false;
if (this.rectifiedGridAngle !== undefined) {
gam = true;
gamma = this.rectifiedGridAngle * D2R;
}
if (alp) {
alphaC = this.alpha ?? 0;
}
if (alp || gam) {
lamc = this.longc;
}
else {
lam1 = this.long1;
phi1 = this.lat1;
lam2 = this.long2;
phi2 = this.lat2;
if (abs(phi1 - phi2) <= TOL ||
(con = abs(phi1)) <= TOL ||
abs(con - HALF_PI) <= TOL ||
abs(abs(this.lat0) - HALF_PI) <= TOL ||
abs(abs(phi2) - HALF_PI) <= TOL) {
throw new Error();
}
}
const one_es = 1.0 - this.es;
const com = sqrt(one_es);
if (abs(this.lat0) > EPSLN) {
sinph0 = sin(this.lat0);
cosph0 = cos(this.lat0);
con = 1 - this.es * sinph0 * sinph0;
this.B = cosph0 * cosph0;
this.B = sqrt(1 + (this.es * this.B * this.B) / one_es);
this.A = (this.B * this.k0 * com) / con;
D = (this.B * com) / (cosph0 * sqrt(con));
F = D * D - 1;
if (F <= 0) {
F = 0;
}
else {
F = sqrt(F);
if (this.lat0 < 0) {
F = -F;
}
}
this.E = F += D;
this.E *= pow(tsfnz(this.e, this.lat0, sinph0), this.B);
}
else {
this.B = 1 / com;
this.A = this.k0;
this.E = D = F = 1;
}
if (alp || gam) {
if (alp) {
gamma0 = asin(sin(alphaC) / D);
if (!gam) {
gamma = alphaC;
}
}
else {
gamma0 = gamma;
alphaC = asin(D * sin(gamma0));
}
this.lam0 = lamc - asin(0.5 * (F - 1 / F) * tan(gamma0)) / this.B;
}
else {
H = pow(tsfnz(this.e, phi1, sin(phi1)), this.B);
L = pow(tsfnz(this.e, phi2, sin(phi2)), this.B);
F = this.E / H;
p = (L - H) / (L + H);
J = this.E * this.E;
J = (J - L * H) / (J + L * H);
con = lam1 - lam2;
if (con < -PI) {
lam2 -= TWO_PI;
}
else if (con > PI) {
lam2 += TWO_PI;
}
this.lam0 = adjustLon(0.5 * (lam1 + lam2) - atan((J * tan(0.5 * this.B * (lam1 - lam2))) / p) / this.B);
gamma0 = atan((2 * sin(this.B * adjustLon(lam1 - this.lam0))) / (F - 1 / F));
gamma = alphaC = asin(D * sin(gamma0));
}
this.singam = sin(gamma0);
this.cosgam = cos(gamma0);
this.sinrot = sin(gamma);
this.cosrot = cos(gamma);
this.rB = 1 / this.B;
this.ArB = this.A * this.rB;
this.BrA = 1 / this.ArB;
if (this.noOff) {
this.u0 = 0;
}
else {
this.u0 = abs(this.ArB * atan(sqrt(D * D - 1) / cos(alphaC)));
if (this.lat0 < 0) {
this.u0 = -this.u0;
}
}
F = 0.5 * gamma0;
this.vPoleN = this.ArB * log(tan(QUART_PI - F));
this.vPoleS = this.ArB * log(tan(QUART_PI + F));
}
/**
* HotineObliqueMercator forward equations--mapping lon-lat to x-y
* @param p - lon-lat WGS84 point
*/
forward(p) {
const { abs, pow, sin, cos, atan2, log } = Math;
let S, T, U, V, W, temp, u, v;
p.x = p.x - this.lam0;
if (abs(abs(p.y) - HALF_PI) > EPSLN) {
W = this.E / pow(tsfnz(this.e, p.y, sin(p.y)), this.B);
temp = 1 / W;
S = 0.5 * (W - temp);
T = 0.5 * (W + temp);
V = sin(this.B * p.x);
U = (S * this.singam - V * this.cosgam) / T;
if (abs(abs(U) - 1.0) < EPSLN) {
throw new Error();
}
v = 0.5 * this.ArB * log((1 - U) / (1 + U));
temp = cos(this.B * p.x);
if (abs(temp) < this.TOL) {
u = this.A * p.x;
}
else {
u = this.ArB * atan2(S * this.cosgam + V * this.singam, temp);
}
}
else {
v = p.y > 0 ? this.vPoleN : this.vPoleS;
u = this.ArB * p.y;
}
if (this.noRot) {
p.x = u;
p.y = v;
}
else {
u -= this.u0;
p.x = v * this.cosrot + u * this.sinrot;
p.y = u * this.cosrot - v * this.sinrot;
}
p.x = this.a * p.x + this.x0;
p.y = this.a * p.y + this.y0;
}
/**
* HotineObliqueMercator inverse equations--mapping x-y to lon-lat
* @param p - HotineObliqueMercator point
*/
inverse(p) {
const { abs, pow, sin, cos, sqrt, atan2, exp } = Math;
let u, v;
p.x = (p.x - this.x0) * (1.0 / this.a);
p.y = (p.y - this.y0) * (1.0 / this.a);
if (this.noRot) {
v = p.y;
u = p.x;
}
else {
v = p.x * this.cosrot - p.y * this.sinrot;
u = p.y * this.cosrot + p.x * this.sinrot + this.u0;
}
const Qp = exp(-this.BrA * v);
const Sp = 0.5 * (Qp - 1 / Qp);
const Tp = 0.5 * (Qp + 1 / Qp);
const Vp = sin(this.BrA * u);
const Up = (Vp * this.cosgam + Sp * this.singam) / Tp;
if (abs(abs(Up) - 1) < EPSLN) {
p.x = 0;
p.y = Up < 0 ? -HALF_PI : HALF_PI;
}
else {
p.y = this.E / sqrt((1 + Up) / (1 - Up));
p.y = phi2z(this.e, pow(p.y, 1 / this.B));
if (p.y === Infinity) {
throw new Error();
}
p.x = -this.rB * atan2(Sp * this.cosgam - Vp * this.singam, cos(this.BrA * u));
}
p.x += this.lam0;
}
}
/**
* @param P - projection parameters
* @returns - true if projection is of type A
*/
function isTypeA(P) {
const typeAProjections = [
'Hotine_Oblique_Mercator',
'Hotine_Oblique_Mercator_Azimuth_Natural_Origin',
];
return ('noUoff' in P ||
'noOff' in P ||
typeAProjections.indexOf(P.PROJECTION ?? P.name ?? 'XXXXXX') !== -1);
}
//# sourceMappingURL=omerc.js.map