[bc386f0] | 1 | # Copyright 2013-2016 Mike Bostock |
---|
| 2 | # All rights reserved. |
---|
| 3 | # |
---|
| 4 | # Redistribution and use in source and binary forms, with or without modification, |
---|
| 5 | # are permitted provided that the following conditions are met: |
---|
| 6 | # |
---|
| 7 | # * Redistributions of source code must retain the above copyright notice, this |
---|
| 8 | # list of conditions and the following disclaimer. |
---|
| 9 | # |
---|
| 10 | # * Redistributions in binary form must reproduce the above copyright notice, |
---|
| 11 | # this list of conditions and the following disclaimer in the documentation |
---|
| 12 | # and/or other materials provided with the distribution. |
---|
| 13 | # |
---|
| 14 | # * Neither the name of the author nor the names of contributors may be used to |
---|
| 15 | # endorse or promote products derived from this software without specific prior |
---|
| 16 | # written permission. |
---|
| 17 | # |
---|
| 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
---|
| 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
---|
| 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
---|
| 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
---|
| 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
---|
| 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
---|
| 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
---|
| 25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
---|
| 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
---|
| 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
| 28 | # |
---|
| 29 | # https://github.com/d3/d3-geo-projection |
---|
| 30 | # commit fd2886555e46b35163c7b898d43c7d1bcebbba7c 2016-07-02 |
---|
| 31 | # |
---|
| 32 | # 2017-11-01 Paul Kienzle |
---|
| 33 | # * converted to python, using degrees rather than radians |
---|
[0f65169] | 34 | from __future__ import division, print_function |
---|
| 35 | |
---|
| 36 | import numpy as np |
---|
| 37 | from numpy import sqrt, pi, tan, cos, sin, log, exp, arctan2 as atan2, sign, radians, degrees |
---|
| 38 | |
---|
| 39 | _ = """ |
---|
[bc386f0] | 40 | # mpmath version of special functions |
---|
[0f65169] | 41 | import mpmath as mp |
---|
| 42 | sn, cn, dn = [mp.ellipfun(v) for v in 'sn', 'cn', 'dn'] |
---|
| 43 | |
---|
| 44 | def ellipticJi(u, v, m): |
---|
| 45 | z = u+v*1j |
---|
| 46 | return sn(z, m), cn(z, m), dn(z, m) |
---|
| 47 | |
---|
| 48 | def ellipticJ(u, m): |
---|
| 49 | s, c, d = sn(u, m), cn(u, m), dn(u, m) |
---|
| 50 | phi = mp.asin(s) |
---|
| 51 | return s, c, d, phi |
---|
| 52 | |
---|
| 53 | def ellipticFi(phi, psi, m): |
---|
| 54 | z = phi + psi*1j |
---|
| 55 | return mp.ellipf(z, m) |
---|
| 56 | |
---|
| 57 | def ellipticF(phi, m): |
---|
| 58 | return mp.ellipf(phi, m) |
---|
| 59 | """ |
---|
| 60 | |
---|
| 61 | # scipy version of special functions |
---|
| 62 | from scipy.special import ellipj as ellipticJ, ellipkinc as ellipticF |
---|
| 63 | from numpy import sinh, sign, arctan as atan |
---|
| 64 | |
---|
| 65 | def ellipticJi(u, v, m): |
---|
| 66 | scalar = np.isscalar(u) and np.isscalar(v) and np.isscalar(m) |
---|
| 67 | u, v, m = np.broadcast_arrays(u, v, m) |
---|
| 68 | result = np.empty_like([u,u,u], 'D') |
---|
| 69 | real = v==0 |
---|
| 70 | imag = u==0 |
---|
| 71 | mixed = ~(real|imag) |
---|
| 72 | result[:, real] = _ellipticJi_real(u[real], m[real]) |
---|
| 73 | result[:, imag] = _ellipticJi_imag(v[imag], m[imag]) |
---|
| 74 | result[:, mixed] = _ellipticJi(u[mixed], v[mixed], m[mixed]) |
---|
| 75 | return result[0,:] if scalar else result |
---|
| 76 | |
---|
| 77 | def _ellipticJi_real(u, m): |
---|
| 78 | sn, cn, dn, phi = ellipticJ(u, m) |
---|
| 79 | return sn, cn, dn |
---|
| 80 | |
---|
| 81 | def _ellipticJi_imag(v, m): |
---|
| 82 | sn, cn, dn, phi = ellipticJ(v, 1-m) |
---|
| 83 | return 1j*sn/cn, 1/cn, dn/cn |
---|
| 84 | |
---|
| 85 | def _ellipticJi(u, v, m): |
---|
| 86 | # Ignoring special cases for now. |
---|
| 87 | # u=0: (1j*b[0]/b[1], 1/b[1], b[2]/b[1]) |
---|
| 88 | # v=0: (a[0], a[1], a[2]) |
---|
| 89 | a = ellipticJ(u, m) |
---|
| 90 | b = ellipticJ(v, 1 - m) |
---|
| 91 | c = b[1]**2 + m * (a[0] * b[0])**2 |
---|
| 92 | return [ |
---|
| 93 | (a[0] * b[2] / c) + 1j*(a[1] * a[2] * b[0] * b[1] / c), |
---|
| 94 | (a[1] * b[1] / c) + 1j*(-a[0] * a[2] * b[0] * b[2] / c), |
---|
| 95 | (a[2] * b[1] * b[2] / c) + 1j*(-m * a[0] * a[1] * b[0] / c), |
---|
| 96 | ] |
---|
| 97 | |
---|
| 98 | # calculate F(phi+ipsi|m). |
---|
| 99 | # see Abramowitz and Stegun, 17.4.11. |
---|
| 100 | def ellipticFi(phi, psi, m): |
---|
| 101 | if np.any(phi == 0): |
---|
| 102 | scalar = np.isscalar(phi) and np.isscalar(psi) and np.isscalar(m) |
---|
| 103 | phi, psi, m = np.broadcast_arrays(phi, psi, m) |
---|
| 104 | result = np.empty_like(phi, 'D') |
---|
| 105 | index = (phi == 0) |
---|
| 106 | result[index] = ellipticF(atan(sinh(abs(phi[index]))), 1-m[index]) * sign(psi[index]) |
---|
| 107 | result[~index] = ellipticFi(phi[~index], psi[~index], m[~index]) |
---|
| 108 | return result.reshape(1)[0] if scalar else result |
---|
| 109 | |
---|
| 110 | r = abs(phi) |
---|
| 111 | i = abs(psi) |
---|
| 112 | sinhpsi2 = sinh(i)**2 |
---|
| 113 | cscphi2 = 1 / sin(r)**2 |
---|
| 114 | cotphi2 = 1 / tan(r)**2 |
---|
| 115 | b = -(cotphi2 + m * (sinhpsi2 * cscphi2) - 1 + m) |
---|
| 116 | c = (m - 1) * cotphi2 |
---|
| 117 | cotlambda2 = (-b + sqrt(b * b - 4 * c)) / 2 |
---|
| 118 | re = ellipticF(atan(1 / sqrt(cotlambda2)), m) * sign(phi) |
---|
[bc386f0] | 119 | im = ellipticF(atan(sqrt(np.maximum(0,(cotlambda2 / cotphi2 - 1) / m))), 1 - m) * sign(psi) |
---|
[0f65169] | 120 | return re + 1j*im |
---|
| 121 | |
---|
[bc386f0] | 122 | sqrt2 = sqrt(2) |
---|
| 123 | |
---|
| 124 | # [PAK] renamed k_ => cos_u, k => sin_u, k*k => sinsq_u to avoid k,K confusion |
---|
| 125 | # cos_u = 0.171572875253809902396622551580603842860656249246103853646... |
---|
| 126 | # sinsq_u = 0.970562748477140585620264690516376942836062504523376878120... |
---|
[9d9fcbd] | 127 | # K = 3.165103454447431823666270142140819753058976299237578486994... |
---|
[bc386f0] | 128 | def guyou(lam, phi): |
---|
| 129 | # [PAK] wrap into [-pi/2, pi/2] radians |
---|
| 130 | x, y = np.asarray(lam), np.asarray(phi) |
---|
| 131 | xn, x = divmod(x+90, 180) |
---|
| 132 | yn, y = divmod(y+90, 180) |
---|
| 133 | xn, lam = xn*180, radians(x-90) |
---|
| 134 | yn, phi = yn*180, radians(y-90) |
---|
| 135 | |
---|
| 136 | # Compute constant K |
---|
| 137 | cos_u = (sqrt2 - 1) / (sqrt2 + 1) |
---|
| 138 | sinsq_u = 1 - cos_u**2 |
---|
| 139 | K = ellipticF(pi/2, sinsq_u) |
---|
| 140 | |
---|
| 141 | # [PAK] simplify expressions, using the fact that f = -1 |
---|
| 142 | # Note: exp(f log(x)) => 1/x, cos(f x) => cos(x), sin(f x) => -sin(x) |
---|
| 143 | r = 1/(tan(pi/4 + abs(phi)/2) * sqrt(cos_u)) |
---|
| 144 | at = atan(r * (cos(lam) - 1j*sin(lam))) |
---|
| 145 | t = ellipticFi(at.real, at.imag, sinsq_u) |
---|
| 146 | x, y = (-t.imag, sign(phi + (phi==0))*(0.5 * K - t.real)) |
---|
| 147 | |
---|
| 148 | # [PAK] convert to degrees, and return to original tile |
---|
| 149 | return degrees(x)+xn, degrees(y)+yn |
---|
| 150 | |
---|
| 151 | def guyou_invert(x, y): |
---|
| 152 | # [PAK] wrap into [-pi/2, pi/2] radians |
---|
| 153 | x, y = np.asarray(x), np.asarray(y) |
---|
| 154 | xn, x = divmod(x+90, 180) |
---|
| 155 | yn, y = divmod(y+90, 180) |
---|
| 156 | xn, x = xn*180, radians(x-90) |
---|
| 157 | yn, y = yn*180, radians(y-90) |
---|
| 158 | |
---|
| 159 | # compute constant K |
---|
| 160 | cos_u = (sqrt2 - 1) / (sqrt2 + 1) |
---|
| 161 | sinsq_u = 1 - cos_u**2 |
---|
| 162 | K = ellipticF(pi/2, sinsq_u) |
---|
| 163 | |
---|
| 164 | # [PAK] simplify expressions, using the fact that f = -1 |
---|
[9d9fcbd] | 165 | j = ellipticJi(K/2 - y, -x, sinsq_u) |
---|
[bc386f0] | 166 | tn = j[0]/j[1] # j[0], j[1] are complex |
---|
[9d9fcbd] | 167 | # Note: -atan2(im(x)/re(x)) => angle(x) |
---|
| 168 | lam = -np.angle(tn) |
---|
| 169 | # Note: exp(0.5/f log(a re(x)^2 + a im(x)^2)) => 1/(sqrt(a) |x|) |
---|
| 170 | phi = 2*atan(1/sqrt(cos_u)/abs(tn)) - pi/2 |
---|
[bc386f0] | 171 | |
---|
| 172 | # [PAK] convert to degrees, and return to original tile |
---|
| 173 | return degrees(lam)+xn, degrees(phi)+yn |
---|
| 174 | |
---|
| 175 | def plot_grid(): |
---|
| 176 | import matplotlib.pyplot as plt |
---|
| 177 | from numpy import linspace |
---|
| 178 | lat_line = linspace(-90, 90, 400) |
---|
| 179 | long_line = linspace(-90, 90, 400) |
---|
| 180 | |
---|
| 181 | #scale = 1 |
---|
| 182 | limit, step, scale = 90, 10, 2 |
---|
| 183 | plt.subplot(211) |
---|
| 184 | for lat in range(-limit, limit+1, step): |
---|
| 185 | x, y = guyou(scale*lat_line, scale*lat) |
---|
| 186 | plt.plot(x, y, 'g') |
---|
| 187 | |
---|
| 188 | for long in range(-limit, limit+1, step): |
---|
| 189 | x, y = guyou(scale*long, scale*long_line) |
---|
| 190 | plt.plot(x, y, 'b') |
---|
| 191 | #plt.xlabel('longitude') |
---|
| 192 | plt.ylabel('latitude') |
---|
| 193 | |
---|
| 194 | plt.subplot(212) |
---|
| 195 | for lat in range(-limit, limit+1, step): |
---|
| 196 | x, y = guyou_invert(scale*lat_line, scale*lat) |
---|
| 197 | plt.plot(x, y, 'g') |
---|
| 198 | |
---|
| 199 | for long in range(-limit, limit+1, step): |
---|
| 200 | x, y = guyou_invert(scale*long, scale*long_line) |
---|
| 201 | plt.plot(x, y, 'b') |
---|
| 202 | plt.xlabel('longitude') |
---|
| 203 | plt.ylabel('latitude') |
---|
| 204 | |
---|
| 205 | if __name__ == "__main__": |
---|
| 206 | plot_grid() |
---|
| 207 | import matplotlib.pyplot as plt; plt.show() |
---|
| 208 | |
---|
| 209 | |
---|
[0f65169] | 210 | _ = """ |
---|
[bc386f0] | 211 | // Javascript source for elliptic functions |
---|
| 212 | // |
---|
[0f65169] | 213 | // Returns [sn, cn, dn](u + iv|m). |
---|
| 214 | export function ellipticJi(u, v, m) { |
---|
| 215 | var a, b, c; |
---|
| 216 | if (!u) { |
---|
| 217 | b = ellipticJ(v, 1 - m); |
---|
| 218 | return [ |
---|
| 219 | [0, b[0] / b[1]], |
---|
| 220 | [1 / b[1], 0], |
---|
| 221 | [b[2] / b[1], 0] |
---|
| 222 | ]; |
---|
| 223 | } |
---|
| 224 | a = ellipticJ(u, m); |
---|
| 225 | if (!v) return [[a[0], 0], [a[1], 0], [a[2], 0]]; |
---|
| 226 | b = ellipticJ(v, 1 - m); |
---|
| 227 | c = b[1] * b[1] + m * a[0] * a[0] * b[0] * b[0]; |
---|
| 228 | return [ |
---|
| 229 | [a[0] * b[2] / c, a[1] * a[2] * b[0] * b[1] / c], |
---|
| 230 | [a[1] * b[1] / c, -a[0] * a[2] * b[0] * b[2] / c], |
---|
| 231 | [a[2] * b[1] * b[2] / c, -m * a[0] * a[1] * b[0] / c] |
---|
| 232 | ]; |
---|
| 233 | } |
---|
| 234 | |
---|
| 235 | // Returns [sn, cn, dn, ph](u|m). |
---|
| 236 | export function ellipticJ(u, m) { |
---|
| 237 | var ai, b, phi, t, twon; |
---|
| 238 | if (m < epsilon) { |
---|
| 239 | t = sin(u); |
---|
| 240 | b = cos(u); |
---|
| 241 | ai = m * (u - t * b) / 4; |
---|
| 242 | return [ |
---|
| 243 | t - ai * b, |
---|
| 244 | b + ai * t, |
---|
| 245 | 1 - m * t * t / 2, |
---|
| 246 | u - ai |
---|
| 247 | ]; |
---|
| 248 | } |
---|
| 249 | if (m >= 1 - epsilon) { |
---|
| 250 | ai = (1 - m) / 4; |
---|
| 251 | b = cosh(u); |
---|
| 252 | t = tanh(u); |
---|
| 253 | phi = 1 / b; |
---|
| 254 | twon = b * sinh(u); |
---|
| 255 | return [ |
---|
| 256 | t + ai * (twon - u) / (b * b), |
---|
| 257 | phi - ai * t * phi * (twon - u), |
---|
| 258 | phi + ai * t * phi * (twon + u), |
---|
| 259 | 2 * atan(exp(u)) - halfPi + ai * (twon - u) / b |
---|
| 260 | ]; |
---|
| 261 | } |
---|
| 262 | |
---|
| 263 | var a = [1, 0, 0, 0, 0, 0, 0, 0, 0], |
---|
| 264 | c = [sqrt(m), 0, 0, 0, 0, 0, 0, 0, 0], |
---|
| 265 | i = 0; |
---|
| 266 | b = sqrt(1 - m); |
---|
| 267 | twon = 1; |
---|
| 268 | |
---|
| 269 | while (abs(c[i] / a[i]) > epsilon && i < 8) { |
---|
| 270 | ai = a[i++]; |
---|
| 271 | c[i] = (ai - b) / 2; |
---|
| 272 | a[i] = (ai + b) / 2; |
---|
| 273 | b = sqrt(ai * b); |
---|
| 274 | twon *= 2; |
---|
| 275 | } |
---|
| 276 | |
---|
| 277 | phi = twon * a[i] * u; |
---|
| 278 | do { |
---|
| 279 | t = c[i] * sin(b = phi) / a[i]; |
---|
| 280 | phi = (asin(t) + phi) / 2; |
---|
| 281 | } while (--i); |
---|
| 282 | |
---|
[ebd2ba6] | 283 | // [PAK] Cephes uses dn = sqrt(1 - m*sin^2 phi) rather than cos(phi)/cos(phi-b) |
---|
| 284 | // DLMF says the second version is unstable near x = K. |
---|
[0f65169] | 285 | return [sin(phi), t = cos(phi), t / cos(phi - b), phi]; |
---|
| 286 | } |
---|
| 287 | |
---|
| 288 | // calculate F(phi+ipsi|m). |
---|
| 289 | // see Abramowitz and Stegun, 17.4.11. |
---|
| 290 | export function ellipticFi(phi, psi, m) { |
---|
| 291 | var r = abs(phi), |
---|
| 292 | i = abs(psi), |
---|
| 293 | sinhpsi = sinh(i); |
---|
| 294 | if (r) { |
---|
| 295 | var cscphi = 1 / sin(r), |
---|
| 296 | cotphi2 = 1 / (tan(r) * tan(r)), |
---|
| 297 | b = -(cotphi2 + m * (sinhpsi * sinhpsi * cscphi * cscphi) - 1 + m), |
---|
| 298 | c = (m - 1) * cotphi2, |
---|
| 299 | cotlambda2 = (-b + sqrt(b * b - 4 * c)) / 2; |
---|
| 300 | return [ |
---|
| 301 | ellipticF(atan(1 / sqrt(cotlambda2)), m) * sign(phi), |
---|
| 302 | ellipticF(atan(sqrt((cotlambda2 / cotphi2 - 1) / m)), 1 - m) * sign(psi) |
---|
| 303 | ]; |
---|
| 304 | } |
---|
| 305 | return [ |
---|
| 306 | 0, |
---|
| 307 | ellipticF(atan(sinhpsi), 1 - m) * sign(psi) |
---|
| 308 | ]; |
---|
| 309 | } |
---|
| 310 | |
---|
| 311 | // Calculate F(phi|m) where m = k^2 = sin(alpha)^2. |
---|
| 312 | // See Abramowitz and Stegun, 17.6.7. |
---|
| 313 | export function ellipticF(phi, m) { |
---|
| 314 | if (!m) return phi; |
---|
| 315 | if (m === 1) return log(tan(phi / 2 + quarterPi)); |
---|
| 316 | var a = 1, |
---|
| 317 | b = sqrt(1 - m), |
---|
| 318 | c = sqrt(m); |
---|
| 319 | for (var i = 0; abs(c) > epsilon; i++) { |
---|
| 320 | if (phi % pi) { |
---|
| 321 | var dPhi = atan(b * tan(phi) / a); |
---|
| 322 | if (dPhi < 0) dPhi += pi; |
---|
| 323 | phi += dPhi + ~~(phi / pi) * pi; |
---|
| 324 | } else phi += phi; |
---|
| 325 | c = (a + b) / 2; |
---|
| 326 | b = sqrt(a * b); |
---|
| 327 | c = ((a = c) - b) / 2; |
---|
| 328 | } |
---|
| 329 | return phi / (pow(2, i) * a); |
---|
| 330 | |
---|
| 331 | |
---|
[bc386f0] | 332 | export function guyouRaw(lambda, phi) { |
---|
| 333 | var k_ = (sqrt2 - 1) / (sqrt2 + 1), |
---|
| 334 | k = sqrt(1 - k_ * k_), |
---|
| 335 | K = ellipticF(halfPi, k * k), |
---|
| 336 | f = -1, |
---|
| 337 | psi = log(tan(pi / 4 + abs(phi) / 2)), |
---|
| 338 | r = exp(f * psi) / sqrt(k_), |
---|
| 339 | at = guyouComplexAtan(r * cos(f * lambda), r * sin(f * lambda)), |
---|
| 340 | t = ellipticFi(at[0], at[1], k * k); |
---|
| 341 | return [-t[1], (phi >= 0 ? 1 : -1) * (0.5 * K - t[0])]; |
---|
| 342 | } |
---|
[0f65169] | 343 | |
---|
[bc386f0] | 344 | function guyouComplexAtan(x, y) { |
---|
| 345 | var x2 = x * x, |
---|
| 346 | y_1 = y + 1, |
---|
| 347 | t = 1 - x2 - y * y; |
---|
| 348 | return [ |
---|
| 349 | 0.5 * ((x >= 0 ? halfPi : -halfPi) - atan2(t, 2 * x)), |
---|
| 350 | -0.25 * log(t * t + 4 * x2) +0.5 * log(y_1 * y_1 + x2) |
---|
| 351 | ]; |
---|
| 352 | } |
---|
[0f65169] | 353 | |
---|
[bc386f0] | 354 | function guyouComplexDivide(a, b) { |
---|
| 355 | var denominator = b[0] * b[0] + b[1] * b[1]; |
---|
| 356 | return [ |
---|
| 357 | (a[0] * b[0] + a[1] * b[1]) / denominator, |
---|
| 358 | (a[1] * b[0] - a[0] * b[1]) / denominator |
---|
| 359 | ]; |
---|
| 360 | } |
---|
[0f65169] | 361 | |
---|
[bc386f0] | 362 | guyouRaw.invert = function(x, y) { |
---|
| 363 | var k_ = (sqrt2 - 1) / (sqrt2 + 1), |
---|
| 364 | k = sqrt(1 - k_ * k_), |
---|
| 365 | K = ellipticF(halfPi, k * k), |
---|
| 366 | f = -1, |
---|
| 367 | j = ellipticJi(0.5 * K - y, -x, k * k), |
---|
| 368 | tn = guyouComplexDivide(j[0], j[1]), |
---|
| 369 | lambda = atan2(tn[1], tn[0]) / f; |
---|
| 370 | return [ |
---|
| 371 | lambda, |
---|
| 372 | 2 * atan(exp(0.5 / f * log(k_ * tn[0] * tn[0] + k_ * tn[1] * tn[1]))) - halfPi |
---|
| 373 | ]; |
---|
| 374 | }; |
---|
| 375 | """ |
---|