Skip to content

Commit 2aa52ec

Browse files
committed
Add support for Tilted Perspective projection
1 parent b90773e commit 2aa52ec

5 files changed

Lines changed: 250 additions & 3 deletions

File tree

Gruntfile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ var projs = [
3030
'ortho',
3131
'qsc',
3232
'robin',
33-
'geocent'
33+
'geocent',
34+
'tpers'
3435
];
3536
module.exports = function (grunt) {
3637
grunt.initConfig({

lib/includedProjections.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import etmerc from './projections/etmerc';
2424
import qsc from './projections/qsc';
2525
import robin from './projections/robin';
2626
import geocent from './projections/geocent';
27+
import tpers from './projections/tpers';
2728

2829
var projs = [
2930
tmerc,
@@ -51,7 +52,8 @@ var projs = [
5152
etmerc,
5253
qsc,
5354
robin,
54-
geocent
55+
geocent,
56+
tpers
5557
];
5658

5759
export default function (proj4) {

lib/projections/tpers.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
2+
var mode = {
3+
N_POLE: 0,
4+
S_POLE: 1,
5+
EQUIT: 2,
6+
OBLIQ: 3
7+
};
8+
9+
import { D2R, HALF_PI, EPSLN } from "../constants/values";
10+
import hypot from "../common/hypot";
11+
12+
var params = {
13+
h: { def: 100000, num: true }, // default is Karman line, no default in PROJ.7
14+
azi: { def: 0, num: true, degrees: true }, // default is North
15+
tilt: { def: 0, num: true, degrees: true }, // default is Nadir
16+
long0: { def: 0, num: true }, // default is Greenwich, conversion to rad is automatic
17+
lat0: { def: 0, num: true } // default is Equator, conversion to rad is automatic
18+
};
19+
20+
export function init() {
21+
Object.keys(params).forEach(function (p) {
22+
if (typeof this[p] === "undefined") {
23+
this[p] = params[p].def;
24+
} else if (params[p].num && isNaN(this[p])) {
25+
throw new Error("Invalid parameter value, must be numeric " + p + " = " + this[p]);
26+
} else if (params[p].num) {
27+
this[p] = parseFloat(this[p]);
28+
}
29+
if (params[p].degrees) {
30+
this[p] = this[p] * D2R;
31+
}
32+
}.bind(this));
33+
34+
if (Math.abs((Math.abs(this.lat0) - HALF_PI)) < EPSLN) {
35+
this.mode = this.lat0 < 0 ? mode.S_POLE : mode.N_POLE;
36+
} else if (Math.abs(this.lat0) < EPSLN) {
37+
this.mode = mode.EQUIT;
38+
} else {
39+
this.mode = mode.OBLIQ;
40+
this.sinph0 = Math.sin(this.lat0);
41+
this.cosph0 = Math.cos(this.lat0);
42+
}
43+
44+
this.pn1 = this.h / this.a; // Normalize relative to the Earth's radius
45+
46+
if (this.pn1 <= 0 || this.pn1 > 1e10) {
47+
throw new Error("Invalid height");
48+
}
49+
50+
this.p = 1 + this.pn1;
51+
this.rp = 1 / this.p;
52+
this.h1 = 1 / this.pn1;
53+
this.pfact = (this.p + 1) * this.h1;
54+
this.es = 0;
55+
56+
var omega = this.tilt;
57+
var gamma = this.azi;
58+
this.cg = Math.cos(gamma);
59+
this.sg = Math.sin(gamma);
60+
this.cw = Math.cos(omega);
61+
this.sw = Math.sin(omega);
62+
}
63+
64+
export function forward(p) {
65+
p.x -= this.long0;
66+
var sinphi = Math.sin(p.y);
67+
var cosphi = Math.cos(p.y);
68+
var coslam = Math.cos(p.x);
69+
var x, y;
70+
switch (this.mode) {
71+
case mode.OBLIQ:
72+
y = this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
73+
break;
74+
case mode.EQUIT:
75+
y = cosphi * coslam;
76+
break;
77+
case mode.S_POLE:
78+
y = -sinphi;
79+
break;
80+
case mode.N_POLE:
81+
y = sinphi;
82+
break;
83+
}
84+
y = this.pn1 / (this.p - y);
85+
x = y * cosphi * Math.sin(p.x);
86+
87+
switch (this.mode) {
88+
case mode.OBLIQ:
89+
y *= this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
90+
break;
91+
case mode.EQUIT:
92+
y *= sinphi;
93+
break;
94+
case mode.N_POLE:
95+
y *= -(cosphi * coslam);
96+
break;
97+
case mode.S_POLE:
98+
y *= cosphi * coslam;
99+
break;
100+
}
101+
102+
// Tilt
103+
var yt, ba;
104+
yt = y * this.cg + x * this.sg;
105+
ba = 1 / (yt * this.sw * this.h1 + this.cw);
106+
x = (x * this.cg - y * this.sg) * this.cw * ba;
107+
y = yt * ba;
108+
109+
p.x = x * this.a;
110+
p.y = y * this.a;
111+
return p;
112+
}
113+
114+
export function inverse(p) {
115+
p.x /= this.a;
116+
p.y /= this.a;
117+
var r = { x: p.x, y: p.y };
118+
119+
// Un-Tilt
120+
var bm, bq, yt;
121+
yt = 1 / (this.pn1 - p.y * this.sw);
122+
bm = this.pn1 * p.x * yt;
123+
bq = this.pn1 * p.y * this.cw * yt;
124+
p.x = bm * this.cg + bq * this.sg;
125+
p.y = bq * this.cg - bm * this.sg;
126+
127+
var rh = hypot(p.x, p.y);
128+
if (Math.abs(rh) < EPSLN) {
129+
r.x = 0;
130+
r.y = p.y;
131+
} else {
132+
var cosz, sinz;
133+
sinz = 1 - rh * rh * this.pfact;
134+
sinz = (this.p - Math.sqrt(sinz)) / (this.pn1 / rh + rh / this.pn1);
135+
cosz = Math.sqrt(1 - sinz * sinz);
136+
switch (this.mode) {
137+
case mode.OBLIQ:
138+
r.y = Math.asin(cosz * this.sinph0 + p.y * sinz * this.cosph0 / rh);
139+
p.y = (cosz - this.sinph0 * Math.sin(r.y)) * rh;
140+
p.x *= sinz * this.cosph0;
141+
break;
142+
case mode.EQUIT:
143+
r.y = Math.asin(p.y * sinz / rh);
144+
p.y = cosz * rh;
145+
p.x *= sinz;
146+
break;
147+
case mode.N_POLE:
148+
r.y = Math.asin(cosz);
149+
p.y = -p.y;
150+
break;
151+
case mode.S_POLE:
152+
r.y = -Math.asin(cosz);
153+
break;
154+
}
155+
r.x = Math.atan2(p.x, p.y);
156+
}
157+
158+
p.x = r.x + this.long0;
159+
p.y = r.y;
160+
return p;
161+
}
162+
163+
export var names = ["Tilted_Perspective", "tpers"];
164+
export default {
165+
init: init,
166+
forward: forward,
167+
inverse: inverse,
168+
names: names
169+
};

projs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import ortho from './lib/projections/ortho';
2525
import qsc from './lib/projections/qsc';
2626
import robin from './lib/projections/robin';
2727
import geocent from './lib/projections/geocent';
28+
import tpers from './lib/projections/tpers';
2829
export default function(proj4){
2930
proj4.Proj.projections.add(tmerc);
3031
proj4.Proj.projections.add(etmerc);
@@ -53,4 +54,5 @@ export default function(proj4){
5354
proj4.Proj.projections.add(qsc);
5455
proj4.Proj.projections.add(robin);
5556
proj4.Proj.projections.add(geocent);
57+
proj4.Proj.projections.add(tpers);
5658
}

test/testData.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,78 @@ var testPoints = [
626626
ll: 0,
627627
xy: 0
628628
}
629+
},
630+
{
631+
code: '+proj=tpers +a=6400000 +h=1000000 +azi=20',
632+
ll: [2, 1],
633+
xy: [170820.288955531, 180460.865555805],
634+
acc: {
635+
ll: 5,
636+
xy: 0
637+
}
638+
},
639+
{
640+
code: '+proj=tpers +a=6400000 +h=1000000 +azi=20',
641+
ll: [2, -1],
642+
xy: [246853.941538942, -28439.878035775],
643+
acc: {
644+
ll: 5,
645+
xy: 0
646+
}
647+
},
648+
{
649+
code: '+proj=tpers +a=6400000 +h=1000000 +azi=20',
650+
ll: [-2, 1],
651+
xy: [-246853.941538942, 28439.878035775],
652+
acc: {
653+
ll: 5,
654+
xy: 0
655+
}
656+
},
657+
{
658+
code: '+proj=tpers +a=6400000 +h=1000000 +azi=20',
659+
ll: [-2, -1],
660+
xy: [-170820.288955531, -180460.865555805],
661+
acc: {
662+
ll: 5,
663+
xy: 0
664+
}
665+
},
666+
{
667+
code: '+proj=tpers +a=6400000 +h=1000000 +tilt=20',
668+
ll: [2, 1],
669+
xy: [213598.340357101, 113687.930830744],
670+
acc: {
671+
ll: 5,
672+
xy: 0
673+
}
674+
},
675+
{
676+
code: '+proj=tpers +a=6400000 +h=1000000 +tilt=20',
677+
ll: [2, -1],
678+
xy: [231609.982792523, -123274.645577324],
679+
acc: {
680+
ll: 5,
681+
xy: 0
682+
}
683+
},
684+
{
685+
code: '+proj=tpers +a=6400000 +h=1000000 +tilt=20',
686+
ll: [-2, 1],
687+
xy: [-213598.340357101, 113687.930830744],
688+
acc: {
689+
ll: 5,
690+
xy: 0
691+
}
692+
},
693+
{
694+
code: '+proj=tpers +a=6400000 +h=1000000 +tilt=20',
695+
ll: [-2, -1],
696+
xy: [-231609.982792523, -123274.645577324],
697+
acc: {
698+
ll: 5,
699+
xy: 0
700+
}
629701
}
630702
];
631703
if (typeof module !== 'undefined') {
@@ -634,4 +706,5 @@ if (typeof module !== 'undefined') {
634706
define(function () {
635707
return testPoints;
636708
});
637-
}
709+
}
710+

0 commit comments

Comments
 (0)