|
| 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 | +}; |
0 commit comments