├── .gitignore ├── README.md ├── StarJs.debug.js ├── StarJs.min.js ├── build.sh ├── examples ├── comet │ ├── comet.js │ └── index.html └── mars │ ├── index.html │ └── mars.js ├── lib ├── StarJs.js ├── StarJs │ ├── Coord.js │ ├── Kepler.js │ ├── Math.js │ ├── Solar.js │ ├── Time.js │ └── Vector.js └── export.js ├── license.txt └── test └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StarJs is a JavaScript implementation of formulae and algorithms described 2 | in *'Astronomy on the Personal Computer'* by O. Montenbrook and T. Pfleger 3 | (4th edition that uses C instead of FORTRAN). 4 | 5 | There are some examples in the `examples` directory. 6 | 7 | `build.sh` uses Google Closure Compiler. 8 | -------------------------------------------------------------------------------- /StarJs.debug.js: -------------------------------------------------------------------------------- 1 | var StarJs = {}; 2 | StarJs.Math = {}; 3 | 4 | /** 5 | * Scale factor for converting degrees to radians. 6 | * @const 7 | * @type {number} 8 | */ 9 | StarJs.Math.DEG2RAD = Math.PI / 180.0; 10 | /** 11 | * Scale factor for converting radians to degrees. 12 | * @const 13 | * @type {number} 14 | */ 15 | StarJs.Math.RAD2DEG = 180.0 / Math.PI; 16 | /** 17 | * Scale factor for converting radians to arcseconds. 18 | * @const 19 | * @type {number} 20 | */ 21 | StarJs.Math.RAD2ARCS = 648000.0 / Math.PI; 22 | /** 23 | * Scale factor for converting arcseconds to radians. 24 | * @const 25 | * @type {number} 26 | */ 27 | StarJs.Math.ARCS2RAD = Math.PI / 648000.0; 28 | /** 29 | * 2*PI 30 | * @const 31 | * @type {number} 32 | */ 33 | StarJs.Math.PI2 = 2.0 * Math.PI; 34 | 35 | /** 36 | * Scale factor for converting radians to hour measure. 37 | * @const 38 | * @type {number} 39 | */ 40 | StarJs.Math.ANGLE2HMS = 12.0 / Math.PI; 41 | 42 | /** 43 | * Return square of x (x*x) 44 | * @param {number} x argument 45 | * @return {number} x*x 46 | */ 47 | StarJs.Math.sqr = function (x) { 48 | return x * x; 49 | }; 50 | 51 | /** 52 | * Return fractional part of argument 53 | * @param {number} x argument 54 | * @return {number} fractioal part 55 | */ 56 | StarJs.Math.frac = function (x) { 57 | return x - Math.floor(x); 58 | }; 59 | 60 | /** 61 | * Modulo 62 | * @param {number} x dividend 63 | * @param {number} r divisor 64 | * @return {number} remainder 65 | */ 66 | StarJs.Math.mod = function (x, r) { 67 | return r * StarJs.Math.frac(x / r); 68 | }; 69 | 70 | /** 71 | * Degree-minute-second object. 72 | * Either 1 or 4 arguments are accepted. 73 | * @constructor 74 | * @param {number} sign_or_angle sign if four values are passed, angle 75 | * in degrees otherwise 76 | * @param {number=} degree Integer degree part (optional) 77 | * @param {number=} minute Integer minute part (optional) 78 | * @param {number=} second Seconds (optional) 79 | */ 80 | StarJs.Math.Dms = function (sign_or_angle, degree, minute, second) { 81 | if (arguments.length === 4) { 82 | this['sign'] = sign_or_angle; 83 | this['degree'] = degree; 84 | this['minute'] = minute; 85 | this['second'] = second; 86 | } else { 87 | var angle = sign_or_angle; 88 | this['sign'] = 1; 89 | if (angle < 0) { 90 | angle = -angle; 91 | this['sign'] = -1; 92 | } 93 | this['degree'] = Math.floor(angle); 94 | angle = 60 * (angle - this['degree']); 95 | this['minute'] = Math.floor(angle); 96 | angle = 60 * (angle - this['minute']); 97 | this['second'] = angle; 98 | } 99 | }; 100 | 101 | /** 102 | * Convert angle DMS (degree-minute-second) to float degree value. 103 | * @param {number} sign 104 | * @param {number} deg 105 | * @param {number} minute 106 | * @param {number} second 107 | */ 108 | StarJs.Math.dms2deg = function (sign, deg, minute, second) { 109 | return sign * (deg + minute / 60.0 + second / 3600.0); 110 | }; 111 | 112 | /** 113 | * Convert angle (degree-minute-second) to float degree value. 114 | */ 115 | StarJs.Math.Dms.prototype.dms2deg = function () { 116 | return StarJs.Math.dms2deg(this['sign'], this['degree'], this['minute'], this['second']); 117 | }; 118 | 119 | /** 120 | * Convert float degree value to DMS (degree-minute-second). 121 | * @param {number} deg Angle 122 | */ 123 | StarJs.Math.deg2dms = function (deg) { 124 | return new StarJs.Math.Dms(deg); 125 | }; 126 | 127 | /** 128 | * Convert radians to hour measure. 129 | * @param {number} angle Angle in radians. 130 | */ 131 | StarJs.Math.angle2hms = function (angle) { 132 | angle = StarJs.Math.mod(angle, StarJs.Math.PI2); 133 | var a = StarJs.Math.ANGLE2HMS * angle, res = StarJs.Math.deg2dms(a); 134 | // Change field names and remove sign field as it is always 1. 135 | res['hour'] = res['deg']; 136 | delete res['deg']; 137 | delete res['sign']; 138 | 139 | return res; 140 | }; 141 | 142 | /** 143 | * @param {number} ym 144 | * @param {number} y0 145 | * @param {number} yp 146 | */ 147 | StarJs.Math.quadInterpolation = function (ym, y0, yp) { 148 | var a = 0.5 * (yp + ym) - y0, b = 0.5 * (yp - ym), c = y0, xe = -b / (2 * a), ye = (a * xe + b) * xe + c; 149 | var dis = b * b - 4 * a * c, roots = [], dx, r1, r2; 150 | if (dis >= 0) { 151 | dx = 0.5 * Math.sqrt(dis) / Math.abs(a); 152 | 153 | r1 = xe - dx; 154 | r2 = xe + dx; 155 | 156 | if (Math.abs(r1) <= 1.0) { 157 | roots.push(r1); 158 | } 159 | if (Math.abs(r2) <= 1.0) { 160 | roots.push(r2); 161 | } 162 | } 163 | return {'xe': xe, 'ye': ye, 'roots': roots}; 164 | }; 165 | 166 | /** 167 | * Hyperbolic sinus. 168 | * @param {number} x 169 | * @return {number} 170 | */ 171 | StarJs.Math.sinh = function (x) { 172 | return (Math.exp(x) - Math.exp(-x))/2; 173 | }; 174 | 175 | /** 176 | * Hyperbolic cosine. 177 | * @param {number} x 178 | * @return {number} 179 | */ 180 | StarJs.Math.cosh = function (x) { 181 | return (Math.exp(x) + Math.exp(-x))/2; 182 | }; 183 | 184 | StarJs.Vector = {}; 185 | 186 | /** @constructor 187 | * @param {number} x 188 | * @param {number} y 189 | * @param {number} z 190 | */ 191 | StarJs.Vector.Vector3 = function (x, y, z) { 192 | this['x'] = x; 193 | this['y'] = y; 194 | this['z'] = z; 195 | }; 196 | 197 | (function (p) { 198 | p.len = function () { 199 | return Math.sqrt(this['x'] * this['x'] + this['y'] * this['y'] + this['z'] * this['z']); 200 | }; 201 | 202 | /** Return new Vector3 as sum of this and the v3. 203 | * @param {StarJs.Vector.Vector3} v3 204 | */ 205 | p.add = function (v3) { 206 | return new StarJs.Vector.Vector3(this['x'] + v3['x'], 207 | this['y'] + v3['y'], 208 | this['z'] + v3['z']); 209 | }; 210 | 211 | /** Return new Vector3 as v3 substracted from this. 212 | * @param {StarJs.Vector.Vector3} v3 213 | */ 214 | p.sub = function (v3) { 215 | return new StarJs.Vector.Vector3(this['x'] - v3['x'], 216 | this['y'] - v3['y'], 217 | this['z'] - v3['z']); 218 | }; 219 | 220 | p.neg = function () { 221 | return new StarJs.Vector.Vector3(-this['x'], -this['y'], -this['z']); 222 | }; 223 | 224 | /** Return new Vector3 multiplied by number a. 225 | * @param {number} a 226 | */ 227 | p.scale = function (a) { 228 | return new StarJs.Vector.Vector3(a * this['x'], a * this['y'], a * this['z']); 229 | }; 230 | 231 | /** Copy this vector. 232 | */ 233 | p.clone = function () { 234 | return new StarJs.Vector.Vector3(this['x'], this['y'], this['z']); 235 | }; 236 | }(StarJs.Vector.Vector3.prototype)); 237 | 238 | /** @constructor 239 | * @param {number|StarJs.Vector.Vector3} az_or_v3 240 | * @param {number=} elev 241 | * @param {number=} rad 242 | */ 243 | StarJs.Vector.Polar3 = function (az_or_v3, elev, rad) { 244 | var alen = arguments.length; 245 | if (alen === 2) { 246 | rad = 1.0; 247 | } 248 | if (alen === 2 || alen === 3) { 249 | this['phi'] = az_or_v3; 250 | this['theta'] = elev; 251 | this['rad'] = rad; 252 | } else { 253 | var v3 = az_or_v3; 254 | var rho2 = v3['x'] * v3['x'] + v3['y'] * v3['y']; 255 | this['rad'] = Math.sqrt(rho2 + v3['z'] * v3['z']); 256 | this['phi'] = (v3['x'] === 0.0 && v3['y'] === 0.0) ? 0.0 : Math.atan2(v3['y'], v3['x']); 257 | if (this['phi'] < 0.0) { 258 | this['phi'] += StarJs.Math.PI2; 259 | } 260 | var rho = Math.sqrt(rho2); 261 | this['theta'] = (v3['z'] === 0.0 && rho === 0.0) ? 0.0 : Math.atan2(v3['z'], rho); 262 | } 263 | }; 264 | 265 | (function (p) { 266 | p.toVector3 = function () { 267 | var ct = Math.cos(this['theta']); 268 | var rad = this['rad'] 269 | var x = rad * ct * Math.cos(this['phi']); 270 | var y = rad * ct * Math.sin(this['phi']); 271 | var z = rad * Math.sin(this['theta']); 272 | return new StarJs.Vector.Vector3(x, y, z); 273 | }; 274 | 275 | p.clone = function () { 276 | return new StarJs.Vector.Polar3(this['rad'], this['phi'], this['theta']); 277 | }; 278 | }(StarJs.Vector.Polar3.prototype)); 279 | 280 | /** @constructor 281 | * @param {(StarJs.Vector.Vector3|boolean)=} v2_or_reuse_flag 282 | * @param {StarJs.Vector.Vector3=} v3 283 | */ 284 | StarJs.Vector.Matrix3 = function (v1, v2_or_reuse_flag, v3) { 285 | if (arguments.length === 3) { 286 | var v2 = v2_or_reuse_flag; // Just for brevity. 287 | this['mat'] = [[v1['x'], v1['y'], v1['z']], 288 | [v2['x'], v2['y'], v2['z']], 289 | [v3['x'], v3['y'], v3['z']]]; 290 | } else { 291 | if (v2_or_reuse_flag) { 292 | this['mat'] = v1; 293 | } else { 294 | this['mat'] = [[v1[0][0], v1[0][1], v1[0][2]], 295 | [v1[1][0], v1[1][1], v1[1][2]], 296 | [v1[2][0], v1[2][1], v1[2][2]]]; 297 | } 298 | } 299 | }; 300 | 301 | (function (p) { 302 | /** Multiply matrix-to-vector multiplication. 303 | * @param {StarJs.Vector.Vector3} v 304 | */ 305 | p.apply = function (v) { 306 | var l = this['mat'][0]; 307 | var x = l[0] * v['x'] + l[1] * v['y'] + l[2] * v['z']; 308 | 309 | l = this['mat'][1]; 310 | var y = l[0] * v['x'] + l[1] * v['y'] + l[2] * v['z']; 311 | 312 | l = this['mat'][2]; 313 | var z = l[0] * v['x'] + l[1] * v['y'] + l[2] * v['z']; 314 | 315 | return new StarJs.Vector.Vector3(x, y, z); 316 | }; 317 | 318 | /** Perform a matrix multiplication, returning a new matrix. 319 | * @param {StarJs.Vector.Matrix3} matrix right matrix. 320 | */ 321 | p.mult = function (matrix) { 322 | var res = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; 323 | var mat = matrix['mat']; 324 | for (var i = 0; i < 3; i += 1) { 325 | var tline = this['mat'][i]; 326 | var rline = res[i]; 327 | for (var j = 0; j < 3; j += 1) { 328 | for (var k = 0; k < 3; k += 1) { 329 | rline[j] += tline[k] * mat[k][j]; 330 | } 331 | } 332 | } 333 | return new StarJs.Vector.Matrix3(res, true); 334 | }; 335 | }(StarJs.Vector.Matrix3.prototype)); 336 | 337 | /** Return x-rotation matrix by angle phi. 338 | * @param {number} phi. 339 | */ 340 | StarJs.Vector.Matrix3.r_x = function (phi) { 341 | var cp = Math.cos(phi), sp = Math.sin(phi); 342 | return new StarJs.Vector.Matrix3([[1.0, 0.0, 0.0], 343 | [0.0, cp, sp], 344 | [0.0, -sp, cp]]); 345 | }; 346 | 347 | /** Return y-rotation matrix by angle phi. 348 | * @param {number} phi. 349 | */ 350 | StarJs.Vector.Matrix3.r_y = function (phi) { 351 | var cp = Math.cos(phi), sp = Math.sin(phi); 352 | return new StarJs.Vector.Matrix3([[ cp, 0.0, -sp], 353 | [0.0, 1.0, 0.0], 354 | [ sp, 0.0, cp]]); 355 | }; 356 | 357 | /** Return z-rotation matrix by angle phi. 358 | * @param {number} phi. 359 | */ 360 | StarJs.Vector.Matrix3.r_z = function (phi) { 361 | var cp = Math.cos(phi), sp = Math.sin(phi); 362 | return new StarJs.Vector.Matrix3([[ cp, sp, 0.0], 363 | [-sp, cp, 0.0], 364 | [0.0, 0.0, 1.0]]); 365 | 366 | }; 367 | StarJs.Time = {}; 368 | 369 | StarJs.Time['DEFAULT_JULIAN_DATE'] = {'year': 1582, 'month': 10, 'day': 4}; 370 | StarJs.Time['DEFAULT_JULIAN_JD'] = 2299161; 371 | StarJs.Time['JD_MJD'] = 2400000.5; 372 | 373 | /** Convert Date or timestamp to mjd float. 374 | * @param {Date|number} t time 375 | */ 376 | StarJs.Time.time2mjd = function (t) { 377 | if (typeof t !== 'number') { 378 | t = t.getTime(); 379 | } 380 | return t / 86400000.0 + 40587.0; 381 | }; 382 | 383 | /** Convert mjd to date and time object, taking into account Julian 384 | * calendar. 385 | * @param jul {number=} JD of Julian date change. 386 | */ 387 | StarJs.Time.mjd2dt = function (mjd, jul) { 388 | if (typeof jul === 'undefined') { 389 | jul = StarJs.Time['DEFAULT_JULIAN_JD']; 390 | } 391 | var a = Math.floor(mjd) + 2400001, b, c, d, e, f, day, mon, year, hour, t; 392 | if (a < jul) { 393 | // Julian calendar 394 | b = 0; 395 | c = a + 1524; 396 | } else { 397 | // Grigorian calendar 398 | b = Math.floor((a - 1867216.25) / 36524.25); 399 | c = a + b - Math.floor(b / 4) + 1525; 400 | } 401 | d = Math.floor((c - 122.1) / 365.25); 402 | e = 365 * d + Math.floor(d / 4); 403 | f = Math.floor((c - e) / 30.6001); 404 | day = c - e - Math.floor(30.6001 * f); 405 | mon = f - 1 - 12 * Math.floor(f / 14); 406 | year = d - 4715 - Math.floor((7 + mon) / 10); 407 | hour = 24.0 * (mjd - Math.floor(mjd)); 408 | t = StarJs.Time.hour2hms(hour); 409 | t['year'] = year; 410 | t['month'] = mon; 411 | t['day'] = day; 412 | return t; 413 | }; 414 | 415 | /** (h, m, s) to floating-point hour. 416 | * @param {number} h hour 417 | * @param {number} m minute 418 | * @param {number} s second 419 | */ 420 | StarJs.Time.hms2hour = function (h, m, s) { 421 | return h + (m / 60.0) + (s / 3600.0); 422 | }; 423 | 424 | /** Convert floating-point hour to hours, minutes and seconds, 425 | * returing an object. 426 | * @param {number} hour 427 | */ 428 | StarJs.Time.hour2hms = function (hour) { 429 | var dms = new StarJs.Math.Dms(hour); 430 | return {'hour': dms['degree'], 'minute': dms['minute'], 'second': dms['second']}; 431 | }; 432 | 433 | /** Convert data-time object to MDJ float, taking into account a Julian 434 | * calendar. 435 | * @param {number} dt 436 | * @param {number=} jul JD of Julian date change. 437 | */ 438 | StarJs.Time.dt2mjd = function (dt, jul) { 439 | if (typeof jul === 'undefined') { 440 | jul = StarJs.Time['DEFAULT_JULIAN_DATE']; 441 | } 442 | 443 | var year = dt['year'], mon = dt['month'], b; 444 | if (mon <= 2) { 445 | mon += 12; 446 | year -= 1; 447 | } 448 | if (year <= jul['year'] && mon <= jul['month'] && dt['day'] <= jul['day']) { 449 | // Julian 450 | b = -2 + Math.floor((year + 4716) / 4) - 1179; 451 | } else { 452 | // Gregorian 453 | b = Math.floor(year / 400) - Math.floor(year / 100) + Math.floor(year / 4); 454 | } 455 | var mjdMidnight = 365 * year - 679004 + b + Math.floor(30.6001 * (mon + 1)) + dt['day']; 456 | var frac = StarJs.Time.hms2hour(dt['hour'], dt['minute'], dt['second']) / 24.0; 457 | return mjdMidnight + frac; 458 | }; 459 | 460 | /** Convert MJD to JCT. 461 | * @param {number} mjd 462 | */ 463 | StarJs.Time.mjd2jct = function (mjd) { 464 | return (mjd - 51544.5) / 36525.0; 465 | }; 466 | 467 | /** Convert MJD to Greenwich Mean Sidereal Time float. 468 | * @param mjd {number} 469 | */ 470 | StarJs.Time.gmst = function (mjd) { 471 | /* TODO: move to global */ 472 | var SECS = 86400; // 24*60*60 -- number of seconds in day; 473 | var mjd0 = Math.floor(mjd), ut = SECS * (mjd - mjd0), t0, t, gmst; 474 | t0 = StarJs.Time.mjd2jct(mjd0); 475 | t = StarJs.Time.mjd2jct(mjd); 476 | gmst = 24110.54841 + 8640184.812866 * t0 + 1.0027379093 * ut + 477 | (0.093104 - (6.2e-6) * t) * t * t; 478 | return StarJs.Math.PI2 / SECS * StarJs.Math.mod(gmst, SECS); 479 | }; 480 | StarJs.Coord = {}; 481 | 482 | /** Precession matrix (in ecliptical coordinates) from epoch t1 to 483 | * epoch t2. 484 | * @param {number} t1 485 | * @param {number} t2 486 | */ 487 | StarJs.Coord.precessionEclMatrix = function (t1, t2) { 488 | var dt = t2 - t1, p1, p2, pa; 489 | p1 = 174.876383889 * StarJs.Math.DEG2RAD + 490 | (((3289.4789 + 0.60622 * t1) * t1) + 491 | ((-869.8089 - 0.50491 * t1) + 0.03536 * dt) * dt) * StarJs.Math.ARCS2RAD; 492 | p2 = ((47.0029 - (0.06603 - 0.000598 * t1) * t1) + 493 | ((-0.03302 + 0.00598 * t1) + 0.000060 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 494 | pa = ((5029.0966 + (2.22226 - 0.000042 * t1) * t1) + 495 | ((1.11113 - 0.000042 * t1) - 0.000006 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 496 | return StarJs.Vector.Matrix3.r_z(-(p1 + pa)).mult(StarJs.Vector.Matrix3.r_x(p2)).mult(StarJs.Vector.Matrix3.r_z(p1)); 497 | }; 498 | 499 | /** Precession matrix (in equatorial coordinates) from epoch t1 to 500 | * epoch t2. 501 | * @param {number} t1 502 | * @param {number} t2 503 | */ 504 | StarJs.Coord.precessionEquMatrix = function (t1, t2) { 505 | var dt = t2 - t1, zeta, z, theta; 506 | zeta = ((2306.2181 + (1.39656 - 0.000139 * t1) * t1) + 507 | ((0.30188 - 0.000344 * t1) + 0.017998 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 508 | z = zeta + ((0.79280 + 0.000411 * t1) + 0.000205 * dt) * dt * dt * StarJs.Math.ARCS2RAD; 509 | theta = ((2004.3109 - (0.85330 + 0.00217 * t1) * t1) - 510 | ((0.42665 + 0.000217 * t1) + 0.041833 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 511 | return StarJs.Vector.Matrix3.r_z(-z).mult(StarJs.Vector.Matrix3.r_y(theta)).mult(StarJs.Vector.Matrix3.r_z(-zeta)); 512 | 513 | }; 514 | 515 | /** Oliquity of the ecliptic. 516 | * You may use StarJs.Solar.EPS as constant approximation. 517 | * @param {number} jct 518 | */ 519 | StarJs.Coord.eclipticObliquity = function (jct) { 520 | return (23.43929111 - (46.8150 + (0.00059 - 0.001813 * jct) * jct) * jct / 3600.0) * StarJs.Math.DEG2RAD; 521 | }; 522 | 523 | /** Matrix for conversion from equatorial to ecliptic coordinate system. 524 | * @param {number} jct 525 | */ 526 | StarJs.Coord.equ2eclMatrix = function (jct) { 527 | var eps = StarJs.Coord.eclipticObliquity(jct); 528 | return StarJs.Vector.Matrix3.r_x(eps); 529 | }; 530 | 531 | /** Matrix for conversion from ecliptic to equatorial coordinate system. 532 | * @param {number} jct 533 | */ 534 | StarJs.Coord.ecl2equMatrix = function (jct) { 535 | var eps = StarJs.Coord.eclipticObliquity(jct); 536 | return StarJs.Vector.Matrix3.r_x(-eps); 537 | }; 538 | 539 | /** Convert from equatorial to horizontal coordinate system. 540 | * @param {number} dec 541 | * @param {number} tau 542 | * @param {number} lat 543 | */ 544 | StarJs.Coord.equ2hor = function (dec, tau, lat) { 545 | var hor_vec = StarJs.Vector.Matrix3.r_y(Math.PI / 2 - lat).apply(new StarJs.Vector.Polar3(tau, dec).toVector3()); 546 | var hor_pol = new StarJs.Vector.Polar3(hor_vec); 547 | return {'h': hor_pol['theta'], 'az': hor_pol['phi']}; 548 | }; 549 | 550 | /** Convert from horizontal to equatorial coordinate system. 551 | * @param {number} h 552 | * @param {number} az 553 | * @param {number} lat 554 | */ 555 | StarJs.Coord.hor2equ = function (h, az, lat) { 556 | var equ_vec = StarJs.Vector.Matrix3.r_y(-Math.PI / 2 + lat).apply(new StarJs.Vector.Polar3(az, lat).toVector3()); 557 | var equ_pol = new StarJs.Vector.Polar3(equ_vec); 558 | return {'dec': equ_pol['theta'], 'tau': equ_pol['phi']}; 559 | }; 560 | StarJs.Kepler = {}; 561 | 562 | /** Default number of iterations for iterative method. */ 563 | StarJs.Kepler['DEFAULT_ITERATIONS'] = 100; 564 | /** Default iteration precision for iterative method. */ 565 | StarJs.Kepler['DEFAULT_PRECISION'] = 1e-9; 566 | 567 | /** Compute eccentric anomaly for elliptical orbit. 568 | * @param {number} m Mean anomaly. 569 | * @param {number} ec Eccentricity (ec < 1) 570 | * @param {number=} maxiter Optional number of iterations. 571 | */ 572 | StarJs.Kepler.eccAnomaly = function (m, ec, maxiter) { 573 | var i, f, prec = StarJs.Kepler['DEFAULT_PRECISION']; 574 | 575 | if (maxiter === undefined) { 576 | maxiter = StarJs.Kepler['DEFAULT_ITERATIONS']; 577 | } 578 | 579 | m = StarJs.Math.mod(m, StarJs.Math.PI2); 580 | 581 | /* Iterative solution of Kepler equation with Newthon's method. 582 | */ 583 | //var e0 = 0; /* TODO: initial value depends on eccentricity */ 584 | /* Gary R. Smith. A simple, efficient starting value for the 585 | * iterative solution of Kepler's equation. // Celestial 586 | * Mechanics and Dynamical Astronomy. Volume 19, Number 2 (1979) 587 | * DOI: 10.1007/BF01796088. 588 | */ 589 | var sinm = Math.sin(m); 590 | var e0 = m + ec * sinm / (1 - Math.sin(m + ec) + sinm); 591 | 592 | do { 593 | f = e0 - ec * Math.sin(e0) - m; 594 | e0 -= f / (1.0 - ec * Math.cos(e0)); 595 | } while (maxiter-- > 0 && (f > prec)); 596 | return (maxiter > 0) ? e0 : null; 597 | }; 598 | 599 | /** Compute position of a body on elliptical orbit. 600 | * @param {number} gm Gravity constant. 601 | * @param {number} m Mean anomaly. 602 | * @param {number} a Semi-major axis. 603 | * @param {number} ec Eccentricity. 604 | */ 605 | StarJs.Kepler.elliptic = function (gm, m, a, ec) { 606 | var k = Math.sqrt(gm / a), e = StarJs.Kepler.eccAnomaly(m, ec); 607 | var cosE = Math.cos(e), sinE = Math.sin(e); 608 | var fac = Math.sqrt((1.0 - ec) * (1.0 + ec)); 609 | return new StarJs.Vector.Vector3(a * (cosE - ec), a * fac * sinE, 0.0); 610 | // var rho = 1.0 - ec * cosE; 611 | // var vel = StarJs.Vector.Vector3(-k * sinE / rho, k * fac * cosE / rho, 0.0); 612 | }; 613 | 614 | /** Compute position of a body on parabolic orbit. 615 | @param {number} gm Gravity constant. 616 | @param {number} t0 time of pericenter 617 | @param {number} t time to calculate position for 618 | @param {number} q pericenter distance 619 | @param {number} ec Eccentricity. 620 | @param {number=} maxiter Optional maximal number of iterations. 621 | */ 622 | StarJs.Kepler.parabolic = function (gm, t0, t, q, ec, maxiter) { 623 | function stumpff(e2, ret) { 624 | var eps = StarJs.Kepler['DEFAULT_PRECISION']; 625 | var n, add, c1, c2, c3; 626 | 627 | c1 = c2 = c3 = 0.0; 628 | add = n = 1.0; 629 | 630 | do { 631 | c1 += add; add /= (2.0*n); 632 | c2 += add; add /= (2.0*n+1.0); 633 | c3 += add; add *= -e2; 634 | n += 1.0; 635 | } 636 | while (Math.abs(add) >= eps); 637 | 638 | ret.c1 = c1; 639 | ret.c2 = c2; 640 | ret.c3 = c3; 641 | } 642 | 643 | if (maxiter === undefined) { 644 | maxiter = StarJs.Kepler['DEFAULT_ITERATIONS']; 645 | } 646 | 647 | var e2 = 0, e20, fac, c = {}, k, tau, a, b, u, u2, r; 648 | var prec = StarJs.Kepler['DEFAULT_PRECISION']; 649 | 650 | fac = 0.5 * ec; 651 | k = Math.sqrt(gm/(q*(1+ec))); 652 | tau = Math.sqrt(gm)*(t-t0); 653 | do { 654 | e20 = e2; 655 | a = 1.5 * Math.sqrt(fac/(q*q*q))*tau; 656 | b = Math.pow(Math.sqrt(a*a + 1)+a, 1/3.0); 657 | u = b - 1.0/b; 658 | u2 = u*u; 659 | e2 = u2*(1-ec)/fac; 660 | stumpff(e2, c); 661 | fac = 3.0 * ec * c.c3; 662 | } while (Math.abs(e2-e20) >= prec); 663 | return new StarJs.Vector.Vector3(q*(1-u2*c.c2/fac), 664 | q*Math.sqrt((1+ec)/fac)*u*c.c1, 665 | 0); 666 | // // res is position 667 | // r = q * (1+u2*c.c2*ec/fac); 668 | // var vel = new StarJs.Vector.Vector3(-k*res.y/r, 669 | // k*(res.x/r+ec), 670 | // 0.0); 671 | }; 672 | 673 | /** Compute hyperbolic anomaly. 674 | * @param {number} mh 675 | * @param {number} e 676 | * @param {number=} maxiter 677 | */ 678 | StarJs.Kepler.hypAnom = function (mh, e, maxiter) { 679 | if (maxiter === undefined) { 680 | maxiter = StarJs.Kepler['DEFAULT_ITERATIONS']; 681 | } 682 | var prec = StarJs.Kepler['DEFAULT_PRECISION']; 683 | 684 | var i = 0, h, f; 685 | 686 | h = Math.log(2.0*Math.abs(mh)/e+1.8); 687 | if (mh < 0.0) h = -h; 688 | 689 | do { 690 | f = e*StarJs.Math.sinh(h) - h - mh; 691 | h -= f/(e*StarJs.Math.cosh(h) - 1.0); 692 | ++i; 693 | if (i === maxiter) { 694 | // TODO: throw exception? 695 | return null; 696 | } 697 | } while (Math.abs(f) > prec*(1.0 + Math.abs(h+mh))); 698 | 699 | return h; 700 | }; 701 | 702 | /** Compute position of a body on hyperbolic orbit. 703 | * @param {number} gm Gravity constant 704 | * @param {number} t0 Time of pericenter 705 | * @param {number} t Time 706 | * @param {number} a Semi-major axis 707 | * @param {number} e Eccentricity. 708 | */ 709 | StarJs.Kepler.hyperbolic = function (gm, t0, t, a, e) { 710 | var k, mh, h, ch, sh, rho, fac; 711 | 712 | a = Math.abs(a); 713 | k = Math.sqrt(gm/a); 714 | mh = k*(t-t0)/a; 715 | h = StarJs.Kepler.hypAnom(mh, e); 716 | ch = StarJs.Math.cosh(h); 717 | sh = StarJs.Math.sinh(h); 718 | fac = Math.sqrt((e+1)*(e-1)); // (e*e-1) ? 719 | rho = e*ch - 1; 720 | return new StarJs.Vector.Vector3(a*(e-ch), a*fac*sh, 0); 721 | // // ret is position 722 | // vel = new StarJs.Vector.Vector3(-k*sh/rho, k*fac*ch/rho, 0); 723 | }; 724 | 725 | /** Calculate Keplerian orbital position. 726 | @param {number} gm GM 727 | @param {number} t0 time of pericenter 728 | @param {number} t time to calculate position for 729 | @param {number} q pericenter distance 730 | @param {number} e eccentricity 731 | @param {number} pqr Gauss' vector matrix 732 | */ 733 | StarJs.Kepler.keplerPos = function (gm, t0, t, q, e, pqr) { 734 | var M0 = 0.1, EPS = 0.1, m, delta, tau, invax, r; 735 | 736 | delta = Math.abs(1-e); 737 | invax = delta / q; 738 | tau = Math.sqrt(gm)*(t-t0); 739 | m = tau * Math.sqrt(invax*invax*invax); 740 | 741 | if ((m < M0) && (delta < EPS)) { 742 | r = StarJs.Kepler.parabolic(gm, t0, t, q, e); 743 | } else if (e < 1.0) { 744 | r = StarJs.Kepler.elliptic(gm, m, 1.0/invax, e); 745 | } else { 746 | r = StarJs.Kepler.hyperbolic(gm, t0, t, 1.0/invax, e); 747 | } 748 | 749 | return pqr.apply(r); 750 | }; 751 | 752 | /** Return Gauss vectors matrix for given elements. 753 | @param {number} omega longitude of the ascending node (radians) 754 | @param {number} i inclination (radians) 755 | @param {number} w argument of pericenter (radians) 756 | */ 757 | StarJs.Kepler.gaussVec = function (omega, i, w) { 758 | return StarJs.Vector.Matrix3.r_z(-omega).mult(StarJs.Vector.Matrix3.r_x(-i)) 759 | .mult(StarJs.Vector.Matrix3.r_z(-w)); 760 | }; 761 | StarJs.Solar = {}; 762 | 763 | /** 764 | * Earth orbit's axial tilt. 765 | * @const 766 | * @type {number} 767 | */ 768 | StarJs.Solar.EPS = 23.43920111 * StarJs.Math.DEG2RAD; 769 | 770 | /** 771 | * Return approximate position of Moon, quickly. 772 | * @param {number} mct Julian centuries from J2000. TODO 773 | * @return Object with fields ra and dec. 774 | */ 775 | StarJs.Solar.approxMoon = function (mct) { 776 | var l0, l, ls, f, d, dl, s, h, n, ml, mb, me; 777 | 778 | l0 = StarJs.Math.frac(0.606433 + 1336.855225 * mct); 779 | l = StarJs.Math.PI2 * StarJs.Math.frac(0.374897 + 1325.552410 * mct); 780 | ls = StarJs.Math.PI2 * StarJs.Math.frac(0.993133 + 99.997361 * mct); 781 | d = StarJs.Math.PI2 * StarJs.Math.frac(0.827361 + 1236.853086 * mct); 782 | f = StarJs.Math.PI2 * StarJs.Math.frac(0.259086 + 1342.227825 * mct); 783 | 784 | dl = +22640 * Math.sin(l) - 4586 * Math.sin(l - 2 * d) + 2370 * Math.sin(2 * d) + 785 | 769 * Math.sin(2 * l) - 668 * Math.sin(ls) - 412 * Math.sin(2 * f) + 786 | -212 * Math.sin(2 * l - 2 * d) - 206 * Math.sin(l + ls - 2 * d) + 787 | 192 * Math.sin(l + 2 * d) - 165 * Math.sin(ls - 2 * d) + 788 | -125 * Math.sin(d) - 110 * Math.sin(l + ls) + 148 * Math.sin(l - ls) + 789 | -55 * Math.sin(2 * f - 2 * d); 790 | s = f + (dl + 412 * Math.sin(2 * f) + 541 * Math.sin(ls)) * StarJs.Math.ARCS2RAD; 791 | h = f - 2 * d; 792 | n = -526 * Math.sin(h) + 44 * Math.sin(l + h) - 31 * Math.sin(h - l) + 793 | -23 * Math.sin(ls + h) + 11 * Math.sin(h - ls) - 25 * Math.sin(f - 2 * l) + 794 | 21 * Math.sin(f - l); 795 | ml = StarJs.Math.PI2 * StarJs.Math.frac(l0 + dl / 1296.0e3); 796 | mb = (18520.0 * Math.sin(s) + n) * StarJs.Math.ARCS2RAD; 797 | me = StarJs.Vector.Matrix3.r_x(-StarJs.Solar.EPS).apply((new StarJs.Vector.Polar3(ml, mb)).toVector3()); 798 | me = new StarJs.Vector.Polar3(me); 799 | return {'ra': me['phi'], 'dec': me['theta']}; 800 | 801 | }; 802 | 803 | /** 804 | * Return approximate position of Moon, quickly. 805 | * @param {number} mct Julian centuries from J2000. 806 | * @return Object with fields ra and dec. 807 | */ 808 | StarJs.Solar.approxSun = function (mct) { 809 | var l, m, m2, me, se; 810 | m2 = StarJs.Math.frac(0.993133 + 99.997361 * mct); 811 | m = StarJs.Math.PI2 * m2; 812 | l = StarJs.Math.PI2 * StarJs.Math.frac( 813 | 0.7859453 + m2 + (6893.0 * Math.sin(m) + 814 | 72.0 * Math.sin(2 * m) + 815 | 6191.2 * mct) / 1296.0e3); 816 | me = StarJs.Vector.Matrix3.r_x(-StarJs.Solar.EPS).apply((new StarJs.Vector.Polar3(l, 0)).toVector3()); 817 | me = new StarJs.Vector.Polar3(me); 818 | return {'ra': me['phi'], 'dec': me['theta']}; 819 | }; 820 | 821 | /** @const */ 822 | StarJs.Solar.EVENTS = [{ 823 | 'body': 'moon', 824 | 'name': 'day', 825 | 'title': "Moon", 826 | 'sinh0': Math.sin((+8.0 / 60.0) * StarJs.Math.DEG2RAD), 827 | 'posFunc': StarJs.Solar.approxMoon 828 | }, { 829 | 'body': 'sun', 830 | 'name': 'day', 831 | 'title': "Sun", 832 | 'sinh0': Math.sin((-50.0 / 60.0) * StarJs.Math.DEG2RAD), 833 | 'posFunc': StarJs.Solar.approxSun 834 | }, { 835 | 'body': 'sun', 836 | 'name': 'twilightC', 837 | 'title': "Civil twilight", 838 | 'sinh0': Math.sin(-6.0 * StarJs.Math.DEG2RAD), 839 | 'posFunc': StarJs.Solar.approxSun 840 | }, { 841 | 'body': 'sun', 842 | 'name': 'twilightN', 843 | 'title': "Nautical twilight", 844 | 'sinh0': Math.sin(-12.0 * StarJs.Math.DEG2RAD), 845 | 'posFunc': StarJs.Solar.approxSun 846 | }, { 847 | 'body': 'sun', 848 | 'name': 'twilightA', 849 | 'title': "Astronomical twilight", 850 | 'sinh0': Math.sin(-18.0 * StarJs.Math.DEG2RAD), 851 | 'posFunc': StarJs.Solar.approxSun 852 | }]; 853 | 854 | /** Calculate Sun and Moon events: rise, set and twilight. 855 | * 856 | * @param {number} start start time (MJD) 857 | * @param {number} end end day (MJD) 858 | * @param {number} lambda geographical longitude 859 | * @param {number} phi geographical latittude 860 | * @param {function(number)=} tz timezone offset function (converts UTC to local). 861 | * 862 | * Returns array of objects, each object describes particular day in form: 863 | * 864 | * { 865 | * moon: { 866 | * midnight: -0.4575, 867 | * day: { 868 | * rise: 10.2689, 869 | * set: 21.0516 870 | * } 871 | * }, 872 | * sun: { 873 | * midnight: -0.3877, 874 | * day: { rise: 6.3015, set: 20.56 }, 875 | * twilightA: { rise: 3.9416, set: 22.9039 }, 876 | * twilightN: { ... }, 877 | * twilightC: { ... } 878 | * } 879 | * } 880 | * 881 | * 'moon' and 'sun' objects have property 'midnight' that gives sinAlt 882 | * value of Moon and Sun at beginning of the day. If rise and set 883 | * values absent, boolean 'alwaysAbove' field is set. 884 | */ 885 | StarJs.Solar.sunAndMoonEvents = function (start, end, lambda, phi, tz) { 886 | function sinAlt(approxBody, mjd, lambda, cphi, sphi) { 887 | var t, pos, tau; 888 | t = (mjd - 51544.5) / 36525.0; 889 | pos = approxBody(t); 890 | tau = StarJs.Time.gmst(mjd) + lambda - pos['ra']; 891 | return sphi * Math.sin(pos['dec']) + 892 | cphi * Math.cos(pos['dec']) * Math.cos(tau); 893 | } 894 | 895 | /** @const */ 896 | var EVENTS = StarJs.Solar.EVENTS; 897 | 898 | var result = [], today, h, j, ptime, ctime, ntime, H = 1.0 / 24.0, posp = {}, pos0 = {}, posn = {}, cphi = Math.cos(phi), sphi = Math.sin(phi), name, evt, yp = {'sun': {}, 'moon': {}}, y0 = {'sun': {}, 'moon': {}}, yn = {'sun': {}, 'moon': {}}, interp = {}; 899 | if (typeof tz === 'undefined') { 900 | tz = function (a) { 901 | return a; 902 | }; 903 | } 904 | 905 | ptime = start; 906 | 907 | posp['moon'] = sinAlt(StarJs.Solar.approxMoon, ptime, lambda, cphi, sphi); 908 | posp['sun'] = sinAlt(StarJs.Solar.approxSun, ptime, lambda, cphi, sphi); 909 | 910 | for (j = 0; j < EVENTS.length; j += 1) { 911 | evt = EVENTS[j]; 912 | name = evt['name']; 913 | 914 | yp[evt['body']][name] = posp[evt['body']] - evt['sinh0']; 915 | } 916 | 917 | while (ptime < end) { 918 | today = { 919 | 'moon': { 920 | 'midnight': posp['moon'] 921 | }, 922 | 'sun': { 923 | 'midnight': posp['sun'] 924 | } 925 | }; 926 | for (h = 1; h < 24; h += 2) { 927 | ctime = ptime + H; 928 | ntime = ctime + H; // ntime = ctime + 2 * H; 929 | 930 | // Calc new positions... 931 | pos0['moon'] = sinAlt(StarJs.Solar.approxMoon, ctime, lambda, 932 | cphi, sphi); 933 | pos0['sun'] = sinAlt(StarJs.Solar.approxSun, ctime, lambda, 934 | cphi, sphi); 935 | posn['moon'] = sinAlt(StarJs.Solar.approxMoon, ntime, lambda, 936 | cphi, sphi); 937 | posn['sun'] = sinAlt(StarJs.Solar.approxSun, ntime, lambda, 938 | cphi, sphi); 939 | 940 | for (j = 0; j < EVENTS.length; j += 1) { 941 | evt = EVENTS[j]; 942 | name = evt['name']; 943 | today[evt['body']][name] = today[evt['body']][name] || {}; 944 | 945 | y0[evt['body']][name] = pos0[evt['body']] - evt['sinh0']; 946 | yn[evt['body']][name] = posn[evt['body']] - evt['sinh0']; 947 | 948 | interp = StarJs.Math.quadInterpolation(yp[evt['body']][name], y0[evt['body']][name], yn[evt['body']][name]); 949 | 950 | switch (interp['roots'].length) { 951 | case 0: 952 | // No roots 953 | break; 954 | case 1: 955 | // Single root 956 | if (yp[evt['body']][name] < 0.0) { 957 | today[evt['body']][name]['rise'] = h + interp['roots'][0]; 958 | } else { 959 | today[evt['body']][name]['set'] = h + interp['roots'][0]; 960 | } 961 | break; 962 | case 2: 963 | // Two roots 964 | if (interp['ye'] < 0.0) { 965 | today[evt['body']][name]['rise'] = h + interp['roots'][1]; 966 | today[evt['body']][name]['set'] = h + interp['roots'][0]; 967 | } else { 968 | today[evt['body']][name]['rise'] = h + interp['roots'][0]; 969 | today[evt['body']][name]['set'] = h + interp['roots'][1]; 970 | } 971 | break; 972 | } 973 | } 974 | 975 | // Next interval 976 | yp = yn; 977 | yn = {'moon': {}, 'sun': {}}; 978 | 979 | ptime = ntime; 980 | } 981 | 982 | for (j = 0; j < EVENTS.length; j += 1) { 983 | evt = EVENTS[j]; 984 | name = evt['name']; 985 | 986 | if (!today[evt['body']][name]['set'] && !today[evt['body']][name]['rise']) { 987 | today[evt['body']][name]['alwaysAbove'] = (pos0[evt['body']] > evt['sinh0']); 988 | } 989 | } 990 | // Next day 991 | ptime = (start += 1.0); 992 | 993 | result.push(today); 994 | } 995 | 996 | return result; 997 | }; 998 | 999 | /********************************************************************** 1000 | * 1001 | * Solar sytem plantes. 1002 | * 1003 | */ 1004 | 1005 | /** @constructor */ 1006 | StarJs.Solar.Sun = function () { 1007 | this['name'] = 'Sun'; 1008 | }; 1009 | 1010 | /** @const */ 1011 | StarJs.Solar.Sun.$POS = new StarJs.Vector.Vector3(0, 0, 0); 1012 | 1013 | /** @const */ 1014 | StarJs.Solar.GM = StarJs.Math.sqr(0.01720209895); 1015 | 1016 | /** 1017 | * Cooordinates of Sun are constant. 1018 | */ 1019 | StarJs.Solar.Sun.prototype['keplerCoord'] = function (t) { 1020 | return StarJs.Solar.Sun.$POS; 1021 | }; 1022 | 1023 | /** @constructor */ 1024 | StarJs.Solar.Body = function (name, params) { 1025 | this['name'] = name; 1026 | this['a'] = params['a']; 1027 | this['ec'] = params['e']; 1028 | this['m0'] = params['M0']; 1029 | this['n'] = params['n']; 1030 | this['omega'] = params['O']; 1031 | this['i'] = params['i']; 1032 | this['w'] = params['w']; 1033 | this['t0'] = params['T0']; 1034 | // ... 1035 | }; 1036 | 1037 | StarJs.Solar.Body.prototype['keplerCoord'] = function (t) { 1038 | var P = 1.3970; 1039 | var m = StarJs.Math.DEG2RAD * (this['m0'] + this['n'] * (t - this['t0'])); 1040 | var r = StarJs.Kepler.elliptic(StarJs.Solar.GM, m, this['a'], this['ec']); 1041 | var pqr = StarJs.Kepler.gaussVec( 1042 | StarJs.Math.DEG2RAD * (this['omega'] + P * t), 1043 | StarJs.Math.DEG2RAD * this['i'], 1044 | StarJs.Math.DEG2RAD * (this['w'] - this['omega'])); 1045 | return pqr.apply(r); 1046 | }; 1047 | 1048 | /** @const */ 1049 | StarJs.Solar.BODIES = { 1050 | 'Sun': new StarJs.Solar.Sun(), 1051 | 'Mercury': new StarJs.Solar.Body('Mercury', { 1052 | 'a' : 0.387099, 'e' : 0.205634, 'M0' : 174.7947, 'n' : 149472.6738, 1053 | 'O' : 48.331, 'i' : 7.0048, 'w' : 77.4552, 'T0' : 0.0 1054 | }), 1055 | 'Venus': new StarJs.Solar.Body('Venus', { 1056 | 'a' : 0.723332, 'e' : 0.006773, 'M0' : 50.4071, 'n' : 58517.8149, 1057 | 'O' : 76.680, 'i' : 3.3946, 'w' : 131.5718, 'T0' : 0.0 1058 | }), 1059 | 'Earth': new StarJs.Solar.Body('Earth', { 1060 | 'a' : 1.000000, 'e' : 0.016709, 'M0' : 357.5256, 'n' : 35999.3720, 1061 | 'O' : 174.876, 'i' : 0.0000, 'w' : 102.9400, 'T0' : 0.0 1062 | }), 1063 | 'Mars': new StarJs.Solar.Body('Mars', { 1064 | 'a' : 1.523692, 'e' : 0.093405, 'M0' : 19.3879, 'n' : 19140.3023, 1065 | 'O' : 49.557, 'i' : 1.8496, 'w' : 336.0590, 'T0' : 0.0 1066 | }), 1067 | 'Jupiter': new StarJs.Solar.Body('Jupiter', { 1068 | 'a' : 5.204267, 'e' : 0.048775, 'M0' : 18.8185, 'n' : 3033.6272, 1069 | 'O' : 100.4908, 'i' : 1.3046, 'w' : 15.5576, 'T0' : 0.0 1070 | }), 1071 | 'Saturn': new StarJs.Solar.Body('Saturn', { 1072 | 'a' : 9.582018, 'e' : 0.055723, 'M0' : 320.3477, 'n' : 1213.8664, 1073 | 'O' : 113.6427, 'i' : 2.4852, 'w' : 89.6567, 'T0' : 0.0 1074 | }), 1075 | 'Uranus': new StarJs.Solar.Body('Uranus', { 1076 | 'a' : 19.229412, 'e' : 0.044406, 'M0' : 142.9559, 'n' : 426.9282, 1077 | 'O' : 73.9893, 'i' : 0.7726, 'w' : 170.5310, 'T0' : 0.0 1078 | }), 1079 | 'Neptune': new StarJs.Solar.Body('Neptune', { 1080 | 'a' : 30.103658, 'e' : 0.011214, 'M0' : 267.7649, 'n' : 217.9599, 1081 | 'O' : 131.7942, 'i' : 1.7680, 'w' : 37.4435, 'T0' : 0.0 1082 | }) 1083 | }; 1084 | -------------------------------------------------------------------------------- /StarJs.min.js: -------------------------------------------------------------------------------- 1 | var a={};a.Math={};a.Math.u=Math.PI/180;a.Math.ha=180/Math.PI;a.Math.ga=648E3/Math.PI;a.Math.C=Math.PI/648E3;a.Math.v=2*Math.PI;a.Math.da=12/Math.PI;a.Math.ca=function(b){return b*b};a.Math.B=function(b){return b-Math.floor(b)};a.Math.L=function(b,c){return c*a.Math.B(b/c)}; 2 | a.Math.H=function(b,c,d,f){if(4===arguments.length)this.sign=b,this.degree=c,this.minute=d,this.second=f;else{var e=b;this.sign=1;0>e&&(e=-e,this.sign=-1);this.degree=Math.floor(e);e=60*(e-this.degree);this.minute=Math.floor(e);this.second=e=60*(e-this.minute)}};a.Math.K=function(b,c,d,f){return b*(c+d/60+f/3600)};a.Math.H.prototype.K=function(){return a.Math.K(this.sign,this.degree,this.minute,this.second)};a.Math.T=function(b){return new a.Math.H(b)}; 3 | a.Math.ia=function(b){b=a.Math.L(b,a.Math.v);b=a.Math.T(a.Math.da*b);b.hour=b.deg;delete b.deg;delete b.sign;return b};a.Math.ba=function(b,c,d){var f=.5*(d+b)-c,e=.5*(d-b);b=-e/(2*f);d=(f*b+e)*b+c;e=e*e-4*f*c;c=[];0<=e&&(e=.5*Math.sqrt(e)/Math.abs(f),f=b-e,e=b+e,1>=Math.abs(f)&&c.push(f),1>=Math.abs(e)&&c.push(e));return{xe:b,ye:d,roots:c}};a.Math.sinh=function(b){return(Math.exp(b)-Math.exp(-b))/2};a.Math.cosh=function(b){return(Math.exp(b)+Math.exp(-b))/2};a.b={};a.b.l=function(b,c,d){this.x=b;this.y=c;this.z=d};(function(b){b.F=function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)};b.add=function(c){return new a.b.l(this.x+c.x,this.y+c.y,this.z+c.z)};b.sub=function(c){return new a.b.l(this.x-c.x,this.y-c.y,this.z-c.z)};b.qa=function(){return new a.b.l(-this.x,-this.y,-this.z)};b.A=function(c){return new a.b.l(c*this.x,c*this.y,c*this.z)};b.o=function(){return new a.b.l(this.x,this.y,this.z)}})(a.b.l.prototype); 4 | a.b.s=function(b,c,d){var f=arguments.length;2===f&&(d=1);2===f||3===f?(this.phi=b,this.theta=c,this.rad=d):(f=b.x*b.x+b.y*b.y,this.rad=Math.sqrt(f+b.z*b.z),this.phi=0===b.x&&0===b.y?0:Math.atan2(b.y,b.x),0>this.phi&&(this.phi+=a.Math.v),f=Math.sqrt(f),this.theta=0===b.z&&0===f?0:Math.atan2(b.z,f))}; 5 | (function(b){b.o=function(){var c=Math.cos(this.theta),d=this.rad;return new a.b.l(d*c*Math.cos(this.phi),d*c*Math.sin(this.phi),d*Math.sin(this.theta))};b.A=function(){return new a.b.s(this.rad,this.phi,this.theta)}})(a.b.s.prototype);a.b.g=function(b,c,d){this.mat=3===arguments.length?[[b.x,b.y,b.z],[c.x,c.y,c.z],[d.x,d.y,d.z]]:c?b:[[b[0][0],b[0][1],b[0][2]],[b[1][0],b[1][1],b[1][2]],[b[2][0],b[2][1],b[2][2]]]}; 6 | (function(b){b.apply=function(c){var d=this.mat[0],f=d[0]*c.x+d[1]*c.y+d[2]*c.z;d=this.mat[1];var e=d[0]*c.x+d[1]*c.y+d[2]*c.z;d=this.mat[2];return new a.b.l(f,e,d[0]*c.x+d[1]*c.y+d[2]*c.z)};b.G=function(c){var d=[[0,0,0],[0,0,0],[0,0,0]];c=c.mat;for(var f=0;3>f;f+=1)for(var e=this.mat[f],g=d[f],k=0;3>k;k+=1)for(var p=0;3>p;p+=1)g[k]+=e[p]*c[p][k];return new a.b.g(d,!0)}})(a.b.g.prototype);a.b.g.o=function(b){var c=Math.cos(b);b=Math.sin(b);return new a.b.g([[1,0,0],[0,c,b],[0,-b,c]])}; 7 | a.b.g.F=function(b){var c=Math.cos(b);b=Math.sin(b);return new a.b.g([[c,0,-b],[0,1,0],[b,0,c]])};a.b.g.A=function(b){var c=Math.cos(b);b=Math.sin(b);return new a.b.g([[c,b,0],[-b,c,0],[0,0,1]])};a.j={};a.j.DEFAULT_JULIAN_DATE={year:1582,month:10,day:4};a.j.DEFAULT_JULIAN_JD=2299161;a.j.JD_MJD=2400000.5;a.j.ya=function(b){"number"!==typeof b&&(b=b.getTime());return b/864E5+40587}; 8 | a.j.ua=function(b,c){"undefined"===typeof c&&(c=a.j.DEFAULT_JULIAN_JD);var d=Math.floor(b)+2400001;d=f&&(f+=12,--d);return 365*d-679004+(d<=c.year&&f<=c.month&&b.day<=c.day?-2+Math.floor((d+4716)/4)-1179:Math.floor(d/400)-Math.floor(d/100)+Math.floor(d/4))+Math.floor(30.6001*(f+1))+b.day+a.j.X(b.hour,b.minute,b.second)/24};a.j.R=function(b){return(b-51544.5)/36525}; 10 | a.j.W=function(b){var c=Math.floor(b);var d=a.j.R(c);var f=a.j.R(b);return a.Math.v/86400*a.Math.L(24110.54841+8640184.812866*d+86636.55536351999*(b-c)+(.093104-6.2E-6*f)*f*f,86400)};a.m={};a.m.va=function(b,c){c-=b;var d=174.876383889*a.Math.u+((3289.4789+.60622*b)*b+(-869.8089-.50491*b+.03536*c)*c)*a.Math.C;return a.b.g.A(-(d+(5029.0966+(2.22226-4.2E-5*b)*b+(1.11113-4.2E-5*b-6E-6*c)*c)*c*a.Math.C)).G(a.b.g.o((47.0029-(.06603-5.98E-4*b)*b+(-.03302+.00598*b+6E-5*c)*c)*c*a.Math.C)).G(a.b.g.A(d))}; 11 | a.m.wa=function(b,c){c-=b;var d=(2306.2181+(1.39656-1.39E-4*b)*b+(.30188-3.44E-4*b+.017998*c)*c)*c*a.Math.C;return a.b.g.A(-(d+(.7928+4.11E-4*b+2.05E-4*c)*c*c*a.Math.C)).G(a.b.g.F((2004.3109-(.8533+.00217*b)*b-(.42665+2.17E-4*b+.041833*c)*c)*c*a.Math.C)).G(a.b.g.A(-d))};a.m.N=function(b){return(23.43929111-(46.815+(5.9E-4-.001813*b)*b)*b/3600)*a.Math.u};a.m.oa=function(b){return a.b.g.o(a.m.N(b))};a.m.na=function(b){return a.b.g.o(-a.m.N(b))}; 12 | a.m.pa=function(b,c,d){b=a.b.g.F(Math.PI/2-d).apply((new a.b.s(c,b)).o());b=new a.b.s(b);return{h:b.theta,az:b.phi}};a.m.sa=function(b,c,d){b=a.b.g.F(-Math.PI/2+d).apply((new a.b.s(c,d)).o());b=new a.b.s(b);return{dec:b.theta,tau:b.phi}};a.f={};a.f.DEFAULT_ITERATIONS=100;a.f.DEFAULT_PRECISION=1E-9;a.f.U=function(b,c,d){var f=a.f.DEFAULT_PRECISION;void 0===d&&(d=a.f.DEFAULT_ITERATIONS);b=a.Math.L(b,a.Math.v);var e=Math.sin(b);var g=b+c*e/(1-Math.sin(b+c)+e);do e=g-c*Math.sin(g)-b,g-=e/(1-c*Math.cos(g));while(0f);return 0=z);v.ja=t;v.ka=y;v.la=l;l=3*e*k.la}while(Math.abs(g-b)>=p);return new a.b.l(f*(1-n*k.ka/l),f*Math.sqrt((1+e)/l)*d*k.ja,0)}; 14 | a.f.Z=function(b,c,d){void 0===d&&(d=a.f.DEFAULT_ITERATIONS);var f=a.f.DEFAULT_PRECISION,e=0;var g=Math.log(2*Math.abs(b)/c+1.8);0>b&&(g=-g);do{var k=c*a.Math.sinh(g)-g-b;g-=k/(c*a.Math.cosh(g)-1);++e;if(e===d)return null}while(Math.abs(k)>f*(1+Math.abs(g+b)));return g};a.f.$=function(b,c,d,f,e){f=Math.abs(f);b=a.f.Z(Math.sqrt(b/f)*(d-c)/f,e);return new a.b.l(f*(e-a.Math.cosh(b)),f*Math.sqrt((e+1)*(e-1))*a.Math.sinh(b),0)}; 15 | a.f.ta=function(b,c,d,f,e,g){var k=Math.abs(1-e);var p=k/f;var l=Math.sqrt(b)*(d-c)*Math.sqrt(p*p*p);b=.1>l&&.1>k?a.f.aa(b,c,d,f,e):1>e?a.f.P(b,l,1/p,e):a.f.$(b,c,d,1/p,e);return g.apply(b)};a.f.V=function(b,c,d){return a.b.g.A(-b).G(a.b.g.o(-c)).G(a.b.g.A(-d))};a.c={};a.c.M=23.43920111*a.Math.u; 16 | a.c.J=function(b){var c=a.Math.B(.606433+1336.855225*b);var d=a.Math.v*a.Math.B(.374897+1325.55241*b);var f=a.Math.v*a.Math.B(.993133+99.997361*b);var e=a.Math.v*a.Math.B(.827361+1236.853086*b);b=a.Math.v*a.Math.B(.259086+1342.227825*b);var g=22640*Math.sin(d)-4586*Math.sin(d-2*e)+2370*Math.sin(2*e)+769*Math.sin(2*d)-668*Math.sin(f)-412*Math.sin(2*b)+-212*Math.sin(2*d-2*e)-206*Math.sin(d+f-2*e)+192*Math.sin(d+2*e)-165*Math.sin(f-2*e)+-125*Math.sin(e)-110*Math.sin(d+f)+148*Math.sin(d-f)+-55*Math.sin(2* 17 | b-2*e);var k=b+(g+412*Math.sin(2*b)+541*Math.sin(f))*a.Math.C;e=b-2*e;c=a.b.g.o(-a.c.M).apply((new a.b.s(a.Math.v*a.Math.B(c+g/1296E3),(18520*Math.sin(k)+(-526*Math.sin(e)+44*Math.sin(d+e)-31*Math.sin(e-d)+-23*Math.sin(f+e)+11*Math.sin(e-f)-25*Math.sin(b-2*d)+21*Math.sin(b-d)))*a.Math.C)).o());c=new a.b.s(c);return{ra:c.phi,dec:c.theta}}; 18 | a.c.D=function(b){var c=a.Math.B(.993133+99.997361*b);var d=a.Math.v*c;b=a.b.g.o(-a.c.M).apply((new a.b.s(a.Math.v*a.Math.B(.7859453+c+(6893*Math.sin(d)+72*Math.sin(2*d)+6191.2*b)/1296E3),0)).o());b=new a.b.s(b);return{ra:b.phi,dec:b.theta}}; 19 | a.c.fa=[{body:"moon",name:"day",title:"Moon",sinh0:Math.sin(8/60*a.Math.u),posFunc:a.c.J},{body:"sun",name:"day",title:"Sun",sinh0:Math.sin(-50/60*a.Math.u),posFunc:a.c.D},{body:"sun",name:"twilightC",title:"Civil twilight",sinh0:Math.sin(-6*a.Math.u),posFunc:a.c.D},{body:"sun",name:"twilightN",title:"Nautical twilight",sinh0:Math.sin(-12*a.Math.u),posFunc:a.c.D},{body:"sun",name:"twilightA",title:"Astronomical twilight",sinh0:Math.sin(-18*a.Math.u),posFunc:a.c.D}]; 20 | a.c.xa=function(b,c,d,f,e){function g(x,B,D,E,F){x=x((B-51544.5)/36525);return F*Math.sin(x.dec)+E*Math.cos(x.dec)*Math.cos(a.j.W(B)+D-x.ra)}var k=a.c.fa,p=[],l,n,y=1/24,r={},v={},z={},t=Math.cos(f);f=Math.sin(f);var q={sun:{},moon:{}},C={sun:{},moon:{}},A={sun:{},moon:{}},u={};"undefined"===typeof e&&(e=function(x){return x});var w=b;r.moon=g(a.c.J,w,d,t,f);r.sun=g(a.c.D,w,d,t,f);for(n=0;nl;l+=2){n=w+y;w=n+y;v.moon=g(a.c.J,n,d,t,f);v.sun=g(a.c.D,n,d,t,f);z.moon=g(a.c.J,w,d,t,f);z.sun=g(a.c.D,w,d,t,f);for(n=0;nq[h.body][m]?e[h.body][m].rise=l+u.roots[0]:e[h.body][m].set=l+u.roots[0];break;case 2:0>u.ye?(e[h.body][m].rise=l+u.roots[1],e[h.body][m].set= 22 | l+u.roots[0]):(e[h.body][m].rise=l+u.roots[0],e[h.body][m].set=l+u.roots[1])}q=A;A={moon:{},sun:{}}}for(n=0;nh.sinh0);w=b+=1;p.push(e)}return p};a.c.I=function(){this.name="Sun"};a.c.I.o=new a.b.l(0,0,0);a.c.S=a.Math.ca(.01720209895);a.c.I.prototype.keplerCoord=function(){return a.c.I.o}; 23 | a.c.Body=function(b,c){this.name=b;this.a=c.a;this.ec=c.e;this.m0=c.M0;this.n=c.n;this.omega=c.O;this.i=c.i;this.w=c.w;this.t0=c.T0};a.c.Body.prototype.keplerCoord=function(b){var c=a.f.P(a.c.S,a.Math.u*(this.m0+this.n*(b-this.t0)),this.a,this.ec);return a.f.V(a.Math.u*(this.omega+1.397*b),a.Math.u*this.i,a.Math.u*(this.w-this.omega)).apply(c)}; 24 | a.c.ea={Sun:new a.c.I,Mercury:new a.c.Body("Mercury",{a:.387099,e:.205634,M0:174.7947,n:149472.6738,O:48.331,i:7.0048,w:77.4552,T0:0}),Venus:new a.c.Body("Venus",{a:.723332,e:.006773,M0:50.4071,n:58517.8149,O:76.68,i:3.3946,w:131.5718,T0:0}),Earth:new a.c.Body("Earth",{a:1,e:.016709,M0:357.5256,n:35999.372,O:174.876,i:0,w:102.94,T0:0}),Mars:new a.c.Body("Mars",{a:1.523692,e:.093405,M0:19.3879,n:19140.3023,O:49.557,i:1.8496,w:336.059,T0:0}),Jupiter:new a.c.Body("Jupiter",{a:5.204267,e:.048775,M0:18.8185, 25 | n:3033.6272,O:100.4908,i:1.3046,w:15.5576,T0:0}),Saturn:new a.c.Body("Saturn",{a:9.582018,e:.055723,M0:320.3477,n:1213.8664,O:113.6427,i:2.4852,w:89.6567,T0:0}),Uranus:new a.c.Body("Uranus",{a:19.229412,e:.044406,M0:142.9559,n:426.9282,O:73.9893,i:.7726,w:170.531,T0:0}),Neptune:new a.c.Body("Neptune",{a:30.103658,e:.011214,M0:267.7649,n:217.9599,O:131.7942,i:1.768,w:37.4435,T0:0})};window.StarJs=a;a.Math=a.Math;a.Math.DEG2RAD=a.Math.u;a.Math.RAD2DEG=a.Math.ha;a.Math.RAD2ARCS=a.Math.ga;a.Math.ARCS2RAD=a.Math.C;a.Math.PI2=a.Math.v;a.Math.sqr=a.Math.ca;a.Math.frac=a.Math.B;a.Math.mod=a.Math.L;a.Math.Dms=a.Math.H;a.Math.dms2deg=a.Math.K;a.Math.H.prototype.dms2deg=a.Math.H.prototype.K;a.Math.deg2dms=a.Math.T;a.Math.angle2hms=a.Math.ia;a.Math.quadInterpolation=a.Math.ba;a.Math.sinh=a.Math.sinh;a.Math.cosh=a.Math.cosh;a.Coord=a.m;a.m.precessionEclMatrix=a.m.va; 26 | a.m.precessionEquMatrix=a.m.wa;a.m.eclipticObliquity=a.m.N;a.m.equ2eclMatrix=a.m.oa;a.m.ecl2equMatrix=a.m.na;a.m.equ2hor=a.m.pa;a.m.hor2equ=a.m.sa;a.Kepler=a.f;a.f.eccAnomaly=a.f.U;a.f.elliptic=a.f.P;a.f.parabolic=a.f.aa;a.f.hypAnom=a.f.Z;a.f.hyperbolic=a.f.$;a.f.keplerPos=a.f.ta;a.f.gaussVec=a.f.V;a.Solar=a.c;a.c.EPS=a.c.M;a.c.approxMoon=a.c.J;a.c.approxSun=a.c.D;a.c.sunAndMoonEvents=a.c.xa;a.c.Sun=a.c.I;a.c.GM=a.c.S;a.c.Body=a.c.Body;a.c.BODIES=a.c.ea;a.Time=a.j;a.j.time2mjd=a.j.ya;a.j.mjd2dt=a.j.ua; 27 | a.j.hms2hour=a.j.X;a.j.hour2hms=a.j.Y;a.j.dt2mjd=a.j.ma;a.j.mjd2jct=a.j.R;a.j.gmst=a.j.W;a.Vector=a.b;a.b.Vector3=a.b.l;a.b.l.prototype.len=a.b.l.prototype.F;a.b.l.prototype.add=a.b.l.prototype.add;a.b.l.prototype.sub=a.b.l.prototype.sub;a.b.l.prototype.neg=a.b.l.prototype.qa;a.b.l.prototype.scale=a.b.l.prototype.A;a.b.l.prototype.clone=a.b.l.prototype.o;a.b.Polar3=a.b.s;a.b.s.prototype.toVector3=a.b.s.prototype.o;a.b.s.prototype.clone=a.b.s.prototype.A;a.b.Matrix3=a.b.g;a.b.g.prototype.apply=a.b.g.prototype.apply; 28 | a.b.g.prototype.mult=a.b.g.prototype.G;a.b.g.r_x=a.b.g.o;a.b.g.r_y=a.b.g.F;a.b.g.r_y=a.b.g.F; 29 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ~/bin/google-compiler --js=lib/StarJs{.js,/Math.js,/Vector.js,/Time.js,/Coord.js,/Kepler.js,/Solar.js} --js=lib/export.js --js_output_file=StarJs.min.js --compilation_level ADVANCED_OPTIMIZATIONS 3 | cat lib/StarJs{.js,/Math.js,/Vector.js,/Time.js,/Coord.js,/Kepler.js,/Solar.js} > StarJs.debug.js 4 | -------------------------------------------------------------------------------- /examples/comet/comet.js: -------------------------------------------------------------------------------- 1 | function formatDate(date) { 2 | return date.year+'.'+date.month+'.'+date.day+' '+date.hour+':'+date.minute+':'+Math.floor(date.second); 3 | } 4 | 5 | function init() { 6 | var D2R = StarJs.Math.DEG2RAD; 7 | /* Elements from 8 | http://scully.cfa.harvard.edu/cgi-bin/returnprepeph.cgi?d=c&o=CK10X010 9 | 10 | C/2010 X1 (Elenin) 11 | Epoch 2011 Aug. 27.0 TT = JDT 2455800.5 12 | T 2011 Sept. 10.7227 TT MPC 13 | q 0.482465 (2000.0) P Q 14 | z -0.000057 Peri. 343.8056 +0.6023511 +0.7980000 15 | +/-0.000002 Node 323.2267 -0.7287563 +0.5399437 16 | e 1.000028 Incl. 1.8392 -0.3257108 +0.2676879 17 | From 2058 observations 2010 Dec. 10-2011 Aug. 1, mean residual 0".5. 18 | */ 19 | /* 20 | CK10X010 2011 09 10.7226 0.482465 1.000028 343.8056 323.2267 1.8392 20110827 10.0 4.0 C/2010 X1 (Elenin) MPC 75713 21 | */ 22 | var params = { 23 | // By subtracting 2400000.5, we convert element's Julain date to Modified Julian Date aka MJD. 24 | // https://scienceworld.wolfram.com/astronomy/ModifiedJulianDate.html 25 | t0: 2455800.5 - 2400000.5, 26 | q: 0.482465, 27 | z: -0.000057, 28 | e: 1.000028, 29 | peri: 343.8056*D2R, 30 | node: 323.2267*D2R, 31 | incl: 1.8392*D2R 32 | }; 33 | 34 | /* 35 | http://www.minorplanetcenter.net/mpec/K12/K12A52.html 36 | 37 | C/2009 P1 (Garradd) 38 | Epoch 2011 Dec. 25.0 TT = JDT 2455920.5 39 | T 2011 Dec. 23.67758 TT MPC 40 | q 1.5505367 (2000.0) P Q 41 | z -0.0006782 Peri. 90.74770 -0.16661319 -0.82691127 42 | +/-0.0000002 Node 325.99770 -0.58719595 +0.52078702 43 | e 1.0010516 Incl. 106.17749 +0.79211171 +0.21212879 44 | From 4943 observations 2009 Aug. 13-2011 Dec. 4, mean residual 0".4. 45 | */ 46 | var C2009P1_param = { 47 | title: 'C/2009 P1 (Garradd)', 48 | // Again, t0 is MJD 49 | t0: 2455920.5-2400000.5, 50 | q: 1.5505367, 51 | z: -0.0006782, 52 | e: 1.0010516, 53 | peri: 90.74770*D2R, 54 | node: 325.99770*D2R, 55 | incl: 106.17749*D2R 56 | }; 57 | 58 | calc('output', params); 59 | calc('output2', C2009P1_param); 60 | } 61 | 62 | function calc(id, params) { 63 | var output = document.getElementById(id); 64 | var mjNow = StarJs.Time.time2mjd(new Date()); // Current Modified Julian day 65 | var R2D = StarJs.Math.RAD2DEG; 66 | 67 | var text = ''; 68 | 69 | // Gauss vectors for comet's orbit 70 | var pqr = StarJs.Kepler.gaussVec(params.node, params.incl, params.peri); 71 | 72 | // Equatorial prcession matrix. 73 | // It changes slowly, so we use one matrix for whole year 74 | var precessionMatrix = StarJs.Coord.precessionEquMatrix(0, StarJs.Time.mjd2jct(mjNow)); 75 | 76 | for (i = 0; i < 10; ++i) { 77 | // time i weeks from now 78 | var mjdi = mjNow+i*7; 79 | var time = StarJs.Time.mjd2dt(mjdi); 80 | 81 | // Julian centuries after J2000.0. 82 | var jct = StarJs.Time.mjd2jct(mjdi); 83 | 84 | // Heliocentric ecliptic coordinates (StarJs.Math.Vector) of 85 | // comet at that time 86 | var keplerCoord = StarJs.Kepler.keplerPos(StarJs.Solar.GM, 87 | params.t0, 88 | mjdi, 89 | params.q, 90 | params.e, 91 | pqr); 92 | // Heliocentric ecliptic coordinates (x,y,z) of Earth at that 93 | // time (epoch 2000.0) 94 | var earthKeplerCoord = StarJs.Solar.BODIES.Earth.keplerCoord(jct); 95 | 96 | // Earth-centric ecliptic coordinates of the comet 97 | var cometCoord = keplerCoord.sub(earthKeplerCoord); 98 | 99 | var distance = cometCoord.len(); 100 | 101 | // Earth-centric equatorial coordinates of the comet 102 | var cometECoord = StarJs.Coord.ecl2equMatrix(jct).apply(cometCoord); 103 | 104 | var cometPolar = new StarJs.Vector.Polar3(cometECoord); 105 | 106 | // Output text 107 | text += formatDate(time) + ' | ' + (cometPolar.phi*R2D).toFixed(2) + ' | ' + (cometPolar.theta*R2D).toFixed(2) + ' | ' + distance.toFixed(2) + '\n'; 108 | } 109 | 110 | output.innerHTML = text; 111 | } 112 | -------------------------------------------------------------------------------- /examples/comet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Comet C/2010 X1 (Elenin) ephemeris 4 | 5 | 8 |

 9 |     

Fields: UTC date, equatorial coordinates (Ra and Dec), distance 10 | (a.e.). 11 |

12 | Elements used (http://scully.cfa.harvard.edu/cgi-bin/returnprepeph.cgi?d=c&o=CK10X010): 13 |

14 |
15 |     C/2010 X1 (Elenin)
16 | Epoch 2011 Aug. 27.0 TT = JDT 2455800.5
17 | T 2011 Sept. 10.7227 TT                                 MPC
18 | q   0.482465             (2000.0)            P               Q
19 | z  -0.000057       Peri.  343.8056      +0.6023511      +0.7980000
20 |  +/-0.000002       Node   323.2267      -0.7287563      +0.5399437
21 | e   1.000028       Incl.    1.8392      -0.3257108      +0.2676879
22 | From 2058 observations 2010 Dec. 10-2011 Aug. 1, mean residual 0".5.
23 | 
24 | 25 |
26 |     
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/mars/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mars coordinates for next 24 hours 4 | 5 | 8 |

 9 |     

Fields: UTC date, equatorial coordinates (Ra and Dec), horizontal 10 | coordinates (height and azumuth; azumuth 0 is South). 11 |

Observer's coordinates are defined at top of mars.js.

12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/mars/mars.js: -------------------------------------------------------------------------------- 1 | var lon = 83, lat = 54; // Approximate coordinates of Novosibirsk 2 | 3 | function formatDate(date) { 4 | return date.year+'.'+date.month+'.'+date.day+' '+date.hour+':'+date.minute+':'+Math.floor(date.second); 5 | } 6 | 7 | function init(id) { 8 | var output = document.getElementById(id); 9 | 10 | var mjNow = StarJs.Time.time2mjd(new Date()); // Current Modified Julian day 11 | 12 | var R2D = StarJs.Math.RAD2DEG, D2R = StarJs.Math.DEG2RAD; 13 | 14 | var text = ''; 15 | 16 | // Equatorial prcession matrix. 17 | // It changes slowly, so we use one matrix for whole day 18 | var precessionMatrix = StarJs.Coord.precessionEquMatrix(0, StarJs.Time.mjd2jct(mjNow)); 19 | 20 | for (i = 0; i < 24; ++i) { 21 | // time i hours from now 22 | var mjdi = mjNow+i/24; 23 | var time = StarJs.Time.mjd2dt(mjdi); 24 | 25 | // Julian centuries after J2000.0. 26 | var jct = StarJs.Time.mjd2jct(mjdi); 27 | 28 | // Heliocentric ecliptic coordinates (StarJs.Math.Vector) of 29 | // Mars at that time (epoch J2000.0) 30 | var marsKeplerCoord = StarJs.Solar.BODIES.Mars.keplerCoord(jct); 31 | // Heliocentric ecliptic coordinates (x,y,z) of Earth at that 32 | // time (epoch 2000.0) 33 | var earthKeplerCoord = StarJs.Solar.BODIES.Earth.keplerCoord(jct); 34 | 35 | // Earth-centric ecliptic coordinates of Mars (epoch J2000) 36 | var marsCoord = marsKeplerCoord.sub(earthKeplerCoord); 37 | 38 | // Earth-centric equatorial coordinates of Mars (epoch J2000) 39 | var marsECoord = StarJs.Coord.ecl2equMatrix(jct).apply(marsCoord); 40 | 41 | // Convert to current Epoch. Actually, the difference between 42 | // J2000 and 2011 is minimal, so this step can be 43 | // skipped. It is included here just for completeness. 44 | marsECoord = precessionMatrix.apply(marsECoord); 45 | 46 | var marsPolar = new StarJs.Vector.Polar3(marsECoord); 47 | 48 | var gmst = StarJs.Time.gmst(mjdi); 49 | 50 | // Object with fields 'h' (height) and 'az' (azimuth, 0 is 51 | // South, not North, as it is usual in astronomy) of the body 52 | var skyCoord = StarJs.Coord.equ2hor(marsPolar.theta, (gmst-marsPolar.phi+lon*D2R), lat*D2R); 53 | 54 | // Output text 55 | text += formatDate(time) + ' | ' + marsPolar.phi*R2D + ' | ' + marsPolar.theta*R2D + ' | '+skyCoord.h*R2D+' | ' + skyCoord.az*R2D + '\n'; 56 | } 57 | 58 | output.innerHTML = text; 59 | } 60 | -------------------------------------------------------------------------------- /lib/StarJs.js: -------------------------------------------------------------------------------- 1 | var StarJs = {}; 2 | -------------------------------------------------------------------------------- /lib/StarJs/Coord.js: -------------------------------------------------------------------------------- 1 | StarJs.Coord = {}; 2 | 3 | /** Precession matrix (in ecliptical coordinates) from epoch t1 to 4 | * epoch t2. 5 | * @param {number} t1 6 | * @param {number} t2 7 | */ 8 | StarJs.Coord.precessionEclMatrix = function (t1, t2) { 9 | var dt = t2 - t1, p1, p2, pa; 10 | p1 = 174.876383889 * StarJs.Math.DEG2RAD + 11 | (((3289.4789 + 0.60622 * t1) * t1) + 12 | ((-869.8089 - 0.50491 * t1) + 0.03536 * dt) * dt) * StarJs.Math.ARCS2RAD; 13 | p2 = ((47.0029 - (0.06603 - 0.000598 * t1) * t1) + 14 | ((-0.03302 + 0.00598 * t1) + 0.000060 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 15 | pa = ((5029.0966 + (2.22226 - 0.000042 * t1) * t1) + 16 | ((1.11113 - 0.000042 * t1) - 0.000006 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 17 | return StarJs.Vector.Matrix3.r_z(-(p1 + pa)).mult(StarJs.Vector.Matrix3.r_x(p2)).mult(StarJs.Vector.Matrix3.r_z(p1)); 18 | }; 19 | 20 | /** Precession matrix (in equatorial coordinates) from epoch t1 to 21 | * epoch t2. 22 | * @param {number} t1 23 | * @param {number} t2 24 | */ 25 | StarJs.Coord.precessionEquMatrix = function (t1, t2) { 26 | var dt = t2 - t1, zeta, z, theta; 27 | zeta = ((2306.2181 + (1.39656 - 0.000139 * t1) * t1) + 28 | ((0.30188 - 0.000344 * t1) + 0.017998 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 29 | z = zeta + ((0.79280 + 0.000411 * t1) + 0.000205 * dt) * dt * dt * StarJs.Math.ARCS2RAD; 30 | theta = ((2004.3109 - (0.85330 + 0.00217 * t1) * t1) - 31 | ((0.42665 + 0.000217 * t1) + 0.041833 * dt) * dt) * dt * StarJs.Math.ARCS2RAD; 32 | return StarJs.Vector.Matrix3.r_z(-z).mult(StarJs.Vector.Matrix3.r_y(theta)).mult(StarJs.Vector.Matrix3.r_z(-zeta)); 33 | 34 | }; 35 | 36 | /** Oliquity of the ecliptic. 37 | * You may use StarJs.Solar.EPS as constant approximation. 38 | * @param {number} jct 39 | */ 40 | StarJs.Coord.eclipticObliquity = function (jct) { 41 | return (23.43929111 - (46.8150 + (0.00059 - 0.001813 * jct) * jct) * jct / 3600.0) * StarJs.Math.DEG2RAD; 42 | }; 43 | 44 | /** Matrix for conversion from equatorial to ecliptic coordinate system. 45 | * @param {number} jct 46 | */ 47 | StarJs.Coord.equ2eclMatrix = function (jct) { 48 | var eps = StarJs.Coord.eclipticObliquity(jct); 49 | return StarJs.Vector.Matrix3.r_x(eps); 50 | }; 51 | 52 | /** Matrix for conversion from ecliptic to equatorial coordinate system. 53 | * @param {number} jct 54 | */ 55 | StarJs.Coord.ecl2equMatrix = function (jct) { 56 | var eps = StarJs.Coord.eclipticObliquity(jct); 57 | return StarJs.Vector.Matrix3.r_x(-eps); 58 | }; 59 | 60 | /** Convert from equatorial to horizontal coordinate system. 61 | * @param {number} dec 62 | * @param {number} tau 63 | * @param {number} lat 64 | */ 65 | StarJs.Coord.equ2hor = function (dec, tau, lat) { 66 | var hor_vec = StarJs.Vector.Matrix3.r_y(Math.PI / 2 - lat).apply(new StarJs.Vector.Polar3(tau, dec).toVector3()); 67 | var hor_pol = new StarJs.Vector.Polar3(hor_vec); 68 | return {'h': hor_pol['theta'], 'az': hor_pol['phi']}; 69 | }; 70 | 71 | /** Convert from horizontal to equatorial coordinate system. 72 | * @param {number} h 73 | * @param {number} az 74 | * @param {number} lat 75 | */ 76 | StarJs.Coord.hor2equ = function (h, az, lat) { 77 | var equ_vec = StarJs.Vector.Matrix3.r_y(-Math.PI / 2 + lat).apply(new StarJs.Vector.Polar3(az, lat).toVector3()); 78 | var equ_pol = new StarJs.Vector.Polar3(equ_vec); 79 | return {'dec': equ_pol['theta'], 'tau': equ_pol['phi']}; 80 | }; 81 | -------------------------------------------------------------------------------- /lib/StarJs/Kepler.js: -------------------------------------------------------------------------------- 1 | StarJs.Kepler = {}; 2 | 3 | /** Default number of iterations for iterative method. */ 4 | StarJs.Kepler['DEFAULT_ITERATIONS'] = 100; 5 | /** Default iteration precision for iterative method. */ 6 | StarJs.Kepler['DEFAULT_PRECISION'] = 1e-9; 7 | 8 | /** Compute eccentric anomaly for elliptical orbit. 9 | * @param {number} m Mean anomaly. 10 | * @param {number} ec Eccentricity (ec < 1) 11 | * @param {number=} maxiter Optional number of iterations. 12 | */ 13 | StarJs.Kepler.eccAnomaly = function (m, ec, maxiter) { 14 | var i, f, prec = StarJs.Kepler['DEFAULT_PRECISION']; 15 | 16 | if (maxiter === undefined) { 17 | maxiter = StarJs.Kepler['DEFAULT_ITERATIONS']; 18 | } 19 | 20 | m = StarJs.Math.mod(m, StarJs.Math.PI2); 21 | 22 | /* Iterative solution of Kepler equation with Newthon's method. 23 | */ 24 | //var e0 = 0; /* TODO: initial value depends on eccentricity */ 25 | /* Gary R. Smith. A simple, efficient starting value for the 26 | * iterative solution of Kepler's equation. // Celestial 27 | * Mechanics and Dynamical Astronomy. Volume 19, Number 2 (1979) 28 | * DOI: 10.1007/BF01796088. 29 | */ 30 | var sinm = Math.sin(m); 31 | var e0 = m + ec * sinm / (1 - Math.sin(m + ec) + sinm); 32 | 33 | do { 34 | f = e0 - ec * Math.sin(e0) - m; 35 | e0 -= f / (1.0 - ec * Math.cos(e0)); 36 | } while (maxiter-- > 0 && (f > prec)); 37 | return (maxiter > 0) ? e0 : null; 38 | }; 39 | 40 | /** Compute position of a body on elliptical orbit. 41 | * @param {number} gm Gravity constant. 42 | * @param {number} m Mean anomaly. 43 | * @param {number} a Semi-major axis. 44 | * @param {number} ec Eccentricity. 45 | */ 46 | StarJs.Kepler.elliptic = function (gm, m, a, ec) { 47 | var k = Math.sqrt(gm / a), e = StarJs.Kepler.eccAnomaly(m, ec); 48 | var cosE = Math.cos(e), sinE = Math.sin(e); 49 | var fac = Math.sqrt((1.0 - ec) * (1.0 + ec)); 50 | return new StarJs.Vector.Vector3(a * (cosE - ec), a * fac * sinE, 0.0); 51 | // var rho = 1.0 - ec * cosE; 52 | // var vel = StarJs.Vector.Vector3(-k * sinE / rho, k * fac * cosE / rho, 0.0); 53 | }; 54 | 55 | /** Compute position of a body on parabolic orbit. 56 | @param {number} gm Gravity constant. 57 | @param {number} t0 time of pericenter 58 | @param {number} t time to calculate position for 59 | @param {number} q pericenter distance 60 | @param {number} ec Eccentricity. 61 | @param {number=} maxiter Optional maximal number of iterations. 62 | */ 63 | StarJs.Kepler.parabolic = function (gm, t0, t, q, ec, maxiter) { 64 | function stumpff(e2, ret) { 65 | var eps = StarJs.Kepler['DEFAULT_PRECISION']; 66 | var n, add, c1, c2, c3; 67 | 68 | c1 = c2 = c3 = 0.0; 69 | add = n = 1.0; 70 | 71 | do { 72 | c1 += add; add /= (2.0*n); 73 | c2 += add; add /= (2.0*n+1.0); 74 | c3 += add; add *= -e2; 75 | n += 1.0; 76 | } 77 | while (Math.abs(add) >= eps); 78 | 79 | ret.c1 = c1; 80 | ret.c2 = c2; 81 | ret.c3 = c3; 82 | } 83 | 84 | if (maxiter === undefined) { 85 | maxiter = StarJs.Kepler['DEFAULT_ITERATIONS']; 86 | } 87 | 88 | var e2 = 0, e20, fac, c = {}, k, tau, a, b, u, u2, r; 89 | var prec = StarJs.Kepler['DEFAULT_PRECISION']; 90 | 91 | fac = 0.5 * ec; 92 | k = Math.sqrt(gm/(q*(1+ec))); 93 | tau = Math.sqrt(gm)*(t-t0); 94 | do { 95 | e20 = e2; 96 | a = 1.5 * Math.sqrt(fac/(q*q*q))*tau; 97 | b = Math.pow(Math.sqrt(a*a + 1)+a, 1/3.0); 98 | u = b - 1.0/b; 99 | u2 = u*u; 100 | e2 = u2*(1-ec)/fac; 101 | stumpff(e2, c); 102 | fac = 3.0 * ec * c.c3; 103 | } while (Math.abs(e2-e20) >= prec); 104 | return new StarJs.Vector.Vector3(q*(1-u2*c.c2/fac), 105 | q*Math.sqrt((1+ec)/fac)*u*c.c1, 106 | 0); 107 | // // res is position 108 | // r = q * (1+u2*c.c2*ec/fac); 109 | // var vel = new StarJs.Vector.Vector3(-k*res.y/r, 110 | // k*(res.x/r+ec), 111 | // 0.0); 112 | }; 113 | 114 | /** Compute hyperbolic anomaly. 115 | * @param {number} mh 116 | * @param {number} e 117 | * @param {number=} maxiter 118 | */ 119 | StarJs.Kepler.hypAnom = function (mh, e, maxiter) { 120 | if (maxiter === undefined) { 121 | maxiter = StarJs.Kepler['DEFAULT_ITERATIONS']; 122 | } 123 | var prec = StarJs.Kepler['DEFAULT_PRECISION']; 124 | 125 | var i = 0, h, f; 126 | 127 | h = Math.log(2.0*Math.abs(mh)/e+1.8); 128 | if (mh < 0.0) h = -h; 129 | 130 | do { 131 | f = e*StarJs.Math.sinh(h) - h - mh; 132 | h -= f/(e*StarJs.Math.cosh(h) - 1.0); 133 | ++i; 134 | if (i === maxiter) { 135 | // TODO: throw exception? 136 | return null; 137 | } 138 | } while (Math.abs(f) > prec*(1.0 + Math.abs(h+mh))); 139 | 140 | return h; 141 | }; 142 | 143 | /** Compute position of a body on hyperbolic orbit. 144 | * @param {number} gm Gravity constant 145 | * @param {number} t0 Time of pericenter 146 | * @param {number} t Time 147 | * @param {number} a Semi-major axis 148 | * @param {number} e Eccentricity. 149 | */ 150 | StarJs.Kepler.hyperbolic = function (gm, t0, t, a, e) { 151 | var k, mh, h, ch, sh, rho, fac; 152 | 153 | a = Math.abs(a); 154 | k = Math.sqrt(gm/a); 155 | mh = k*(t-t0)/a; 156 | h = StarJs.Kepler.hypAnom(mh, e); 157 | ch = StarJs.Math.cosh(h); 158 | sh = StarJs.Math.sinh(h); 159 | fac = Math.sqrt((e+1)*(e-1)); // (e*e-1) ? 160 | rho = e*ch - 1; 161 | return new StarJs.Vector.Vector3(a*(e-ch), a*fac*sh, 0); 162 | // // ret is position 163 | // vel = new StarJs.Vector.Vector3(-k*sh/rho, k*fac*ch/rho, 0); 164 | }; 165 | 166 | /** Calculate Keplerian orbital position. 167 | @param {number} gm GM 168 | @param {number} t0 time of pericenter 169 | @param {number} t time to calculate position for 170 | @param {number} q pericenter distance 171 | @param {number} e eccentricity 172 | @param {number} pqr Gauss' vector matrix 173 | */ 174 | StarJs.Kepler.keplerPos = function (gm, t0, t, q, e, pqr) { 175 | var M0 = 0.1, EPS = 0.1, m, delta, tau, invax, r; 176 | 177 | delta = Math.abs(1-e); 178 | invax = delta / q; 179 | tau = Math.sqrt(gm)*(t-t0); 180 | m = tau * Math.sqrt(invax*invax*invax); 181 | 182 | if ((m < M0) && (delta < EPS)) { 183 | r = StarJs.Kepler.parabolic(gm, t0, t, q, e); 184 | } else if (e < 1.0) { 185 | r = StarJs.Kepler.elliptic(gm, m, 1.0/invax, e); 186 | } else { 187 | r = StarJs.Kepler.hyperbolic(gm, t0, t, 1.0/invax, e); 188 | } 189 | 190 | return pqr.apply(r); 191 | }; 192 | 193 | /** Return Gauss vectors matrix for given elements. 194 | @param {number} omega longitude of the ascending node (radians) 195 | @param {number} i inclination (radians) 196 | @param {number} w argument of pericenter (radians) 197 | */ 198 | StarJs.Kepler.gaussVec = function (omega, i, w) { 199 | return StarJs.Vector.Matrix3.r_z(-omega).mult(StarJs.Vector.Matrix3.r_x(-i)) 200 | .mult(StarJs.Vector.Matrix3.r_z(-w)); 201 | }; 202 | -------------------------------------------------------------------------------- /lib/StarJs/Math.js: -------------------------------------------------------------------------------- 1 | StarJs.Math = {}; 2 | 3 | /** 4 | * Scale factor for converting degrees to radians. 5 | * @const 6 | * @type {number} 7 | */ 8 | StarJs.Math.DEG2RAD = Math.PI / 180.0; 9 | /** 10 | * Scale factor for converting radians to degrees. 11 | * @const 12 | * @type {number} 13 | */ 14 | StarJs.Math.RAD2DEG = 180.0 / Math.PI; 15 | /** 16 | * Scale factor for converting radians to arcseconds. 17 | * @const 18 | * @type {number} 19 | */ 20 | StarJs.Math.RAD2ARCS = 648000.0 / Math.PI; 21 | /** 22 | * Scale factor for converting arcseconds to radians. 23 | * @const 24 | * @type {number} 25 | */ 26 | StarJs.Math.ARCS2RAD = Math.PI / 648000.0; 27 | /** 28 | * 2*PI 29 | * @const 30 | * @type {number} 31 | */ 32 | StarJs.Math.PI2 = 2.0 * Math.PI; 33 | 34 | /** 35 | * Scale factor for converting radians to hour measure. 36 | * @const 37 | * @type {number} 38 | */ 39 | StarJs.Math.ANGLE2HMS = 12.0 / Math.PI; 40 | 41 | /** 42 | * Return square of x (x*x) 43 | * @param {number} x argument 44 | * @return {number} x*x 45 | */ 46 | StarJs.Math.sqr = function (x) { 47 | return x * x; 48 | }; 49 | 50 | /** 51 | * Return fractional part of argument 52 | * @param {number} x argument 53 | * @return {number} fractioal part 54 | */ 55 | StarJs.Math.frac = function (x) { 56 | return x - Math.floor(x); 57 | }; 58 | 59 | /** 60 | * Modulo 61 | * @param {number} x dividend 62 | * @param {number} r divisor 63 | * @return {number} remainder 64 | */ 65 | StarJs.Math.mod = function (x, r) { 66 | return r * StarJs.Math.frac(x / r); 67 | }; 68 | 69 | /** 70 | * Degree-minute-second object. 71 | * Either 1 or 4 arguments are accepted. 72 | * @constructor 73 | * @param {number} sign_or_angle sign if four values are passed, angle 74 | * in degrees otherwise 75 | * @param {number=} degree Integer degree part (optional) 76 | * @param {number=} minute Integer minute part (optional) 77 | * @param {number=} second Seconds (optional) 78 | */ 79 | StarJs.Math.Dms = function (sign_or_angle, degree, minute, second) { 80 | if (arguments.length === 4) { 81 | this['sign'] = sign_or_angle; 82 | this['degree'] = degree; 83 | this['minute'] = minute; 84 | this['second'] = second; 85 | } else { 86 | var angle = sign_or_angle; 87 | this['sign'] = 1; 88 | if (angle < 0) { 89 | angle = -angle; 90 | this['sign'] = -1; 91 | } 92 | this['degree'] = Math.floor(angle); 93 | angle = 60 * (angle - this['degree']); 94 | this['minute'] = Math.floor(angle); 95 | angle = 60 * (angle - this['minute']); 96 | this['second'] = angle; 97 | } 98 | }; 99 | 100 | /** 101 | * Convert angle DMS (degree-minute-second) to float degree value. 102 | * @param {number} sign 103 | * @param {number} deg 104 | * @param {number} minute 105 | * @param {number} second 106 | */ 107 | StarJs.Math.dms2deg = function (sign, deg, minute, second) { 108 | return sign * (deg + minute / 60.0 + second / 3600.0); 109 | }; 110 | 111 | /** 112 | * Convert angle (degree-minute-second) to float degree value. 113 | */ 114 | StarJs.Math.Dms.prototype.dms2deg = function () { 115 | return StarJs.Math.dms2deg(this['sign'], this['degree'], this['minute'], this['second']); 116 | }; 117 | 118 | /** 119 | * Convert float degree value to DMS (degree-minute-second). 120 | * @param {number} deg Angle 121 | */ 122 | StarJs.Math.deg2dms = function (deg) { 123 | return new StarJs.Math.Dms(deg); 124 | }; 125 | 126 | /** 127 | * Convert radians to hour measure. 128 | * @param {number} angle Angle in radians. 129 | */ 130 | StarJs.Math.angle2hms = function (angle) { 131 | angle = StarJs.Math.mod(angle, StarJs.Math.PI2); 132 | var a = StarJs.Math.ANGLE2HMS * angle, res = StarJs.Math.deg2dms(a); 133 | // Change field names and remove sign field as it is always 1. 134 | res['hour'] = res['deg']; 135 | delete res['deg']; 136 | delete res['sign']; 137 | 138 | return res; 139 | }; 140 | 141 | /** 142 | * @param {number} ym 143 | * @param {number} y0 144 | * @param {number} yp 145 | */ 146 | StarJs.Math.quadInterpolation = function (ym, y0, yp) { 147 | var a = 0.5 * (yp + ym) - y0, b = 0.5 * (yp - ym), c = y0, xe = -b / (2 * a), ye = (a * xe + b) * xe + c; 148 | var dis = b * b - 4 * a * c, roots = [], dx, r1, r2; 149 | if (dis >= 0) { 150 | dx = 0.5 * Math.sqrt(dis) / Math.abs(a); 151 | 152 | r1 = xe - dx; 153 | r2 = xe + dx; 154 | 155 | if (Math.abs(r1) <= 1.0) { 156 | roots.push(r1); 157 | } 158 | if (Math.abs(r2) <= 1.0) { 159 | roots.push(r2); 160 | } 161 | } 162 | return {'xe': xe, 'ye': ye, 'roots': roots}; 163 | }; 164 | 165 | /** 166 | * Hyperbolic sinus. 167 | * @param {number} x 168 | * @return {number} 169 | */ 170 | StarJs.Math.sinh = function (x) { 171 | return (Math.exp(x) - Math.exp(-x))/2; 172 | }; 173 | 174 | /** 175 | * Hyperbolic cosine. 176 | * @param {number} x 177 | * @return {number} 178 | */ 179 | StarJs.Math.cosh = function (x) { 180 | return (Math.exp(x) + Math.exp(-x))/2; 181 | }; 182 | 183 | -------------------------------------------------------------------------------- /lib/StarJs/Solar.js: -------------------------------------------------------------------------------- 1 | StarJs.Solar = {}; 2 | 3 | /** 4 | * Earth orbit's axial tilt. 5 | * @const 6 | * @type {number} 7 | */ 8 | StarJs.Solar.EPS = 23.43920111 * StarJs.Math.DEG2RAD; 9 | 10 | /** 11 | * Return approximate position of Moon, quickly. 12 | * @param {number} mct Julian centuries from J2000. TODO 13 | * @return Object with fields ra and dec. 14 | */ 15 | StarJs.Solar.approxMoon = function (mct) { 16 | var l0, l, ls, f, d, dl, s, h, n, ml, mb, me; 17 | 18 | l0 = StarJs.Math.frac(0.606433 + 1336.855225 * mct); 19 | l = StarJs.Math.PI2 * StarJs.Math.frac(0.374897 + 1325.552410 * mct); 20 | ls = StarJs.Math.PI2 * StarJs.Math.frac(0.993133 + 99.997361 * mct); 21 | d = StarJs.Math.PI2 * StarJs.Math.frac(0.827361 + 1236.853086 * mct); 22 | f = StarJs.Math.PI2 * StarJs.Math.frac(0.259086 + 1342.227825 * mct); 23 | 24 | dl = +22640 * Math.sin(l) - 4586 * Math.sin(l - 2 * d) + 2370 * Math.sin(2 * d) + 25 | 769 * Math.sin(2 * l) - 668 * Math.sin(ls) - 412 * Math.sin(2 * f) + 26 | -212 * Math.sin(2 * l - 2 * d) - 206 * Math.sin(l + ls - 2 * d) + 27 | 192 * Math.sin(l + 2 * d) - 165 * Math.sin(ls - 2 * d) + 28 | -125 * Math.sin(d) - 110 * Math.sin(l + ls) + 148 * Math.sin(l - ls) + 29 | -55 * Math.sin(2 * f - 2 * d); 30 | s = f + (dl + 412 * Math.sin(2 * f) + 541 * Math.sin(ls)) * StarJs.Math.ARCS2RAD; 31 | h = f - 2 * d; 32 | n = -526 * Math.sin(h) + 44 * Math.sin(l + h) - 31 * Math.sin(h - l) + 33 | -23 * Math.sin(ls + h) + 11 * Math.sin(h - ls) - 25 * Math.sin(f - 2 * l) + 34 | 21 * Math.sin(f - l); 35 | ml = StarJs.Math.PI2 * StarJs.Math.frac(l0 + dl / 1296.0e3); 36 | mb = (18520.0 * Math.sin(s) + n) * StarJs.Math.ARCS2RAD; 37 | me = StarJs.Vector.Matrix3.r_x(-StarJs.Solar.EPS).apply((new StarJs.Vector.Polar3(ml, mb)).toVector3()); 38 | me = new StarJs.Vector.Polar3(me); 39 | return {'ra': me['phi'], 'dec': me['theta']}; 40 | 41 | }; 42 | 43 | /** 44 | * Return approximate position of Moon, quickly. 45 | * @param {number} mct Julian centuries from J2000. 46 | * @return Object with fields ra and dec. 47 | */ 48 | StarJs.Solar.approxSun = function (mct) { 49 | var l, m, m2, me, se; 50 | m2 = StarJs.Math.frac(0.993133 + 99.997361 * mct); 51 | m = StarJs.Math.PI2 * m2; 52 | l = StarJs.Math.PI2 * StarJs.Math.frac( 53 | 0.7859453 + m2 + (6893.0 * Math.sin(m) + 54 | 72.0 * Math.sin(2 * m) + 55 | 6191.2 * mct) / 1296.0e3); 56 | me = StarJs.Vector.Matrix3.r_x(-StarJs.Solar.EPS).apply((new StarJs.Vector.Polar3(l, 0)).toVector3()); 57 | me = new StarJs.Vector.Polar3(me); 58 | return {'ra': me['phi'], 'dec': me['theta']}; 59 | }; 60 | 61 | /** @const */ 62 | StarJs.Solar.EVENTS = [{ 63 | 'body': 'moon', 64 | 'name': 'day', 65 | 'title': "Moon", 66 | 'sinh0': Math.sin((+8.0 / 60.0) * StarJs.Math.DEG2RAD), 67 | 'posFunc': StarJs.Solar.approxMoon 68 | }, { 69 | 'body': 'sun', 70 | 'name': 'day', 71 | 'title': "Sun", 72 | 'sinh0': Math.sin((-50.0 / 60.0) * StarJs.Math.DEG2RAD), 73 | 'posFunc': StarJs.Solar.approxSun 74 | }, { 75 | 'body': 'sun', 76 | 'name': 'twilightC', 77 | 'title': "Civil twilight", 78 | 'sinh0': Math.sin(-6.0 * StarJs.Math.DEG2RAD), 79 | 'posFunc': StarJs.Solar.approxSun 80 | }, { 81 | 'body': 'sun', 82 | 'name': 'twilightN', 83 | 'title': "Nautical twilight", 84 | 'sinh0': Math.sin(-12.0 * StarJs.Math.DEG2RAD), 85 | 'posFunc': StarJs.Solar.approxSun 86 | }, { 87 | 'body': 'sun', 88 | 'name': 'twilightA', 89 | 'title': "Astronomical twilight", 90 | 'sinh0': Math.sin(-18.0 * StarJs.Math.DEG2RAD), 91 | 'posFunc': StarJs.Solar.approxSun 92 | }]; 93 | 94 | /** Calculate Sun and Moon events: rise, set and twilight. 95 | * 96 | * @param {number} start start time (MJD) 97 | * @param {number} end end day (MJD) 98 | * @param {number} lambda geographical longitude 99 | * @param {number} phi geographical latittude 100 | * @param {function(number)=} tz timezone offset function (converts UTC to local). 101 | * 102 | * Returns array of objects, each object describes particular day in form: 103 | * 104 | * { 105 | * moon: { 106 | * midnight: -0.4575, 107 | * day: { 108 | * rise: 10.2689, 109 | * set: 21.0516 110 | * } 111 | * }, 112 | * sun: { 113 | * midnight: -0.3877, 114 | * day: { rise: 6.3015, set: 20.56 }, 115 | * twilightA: { rise: 3.9416, set: 22.9039 }, 116 | * twilightN: { ... }, 117 | * twilightC: { ... } 118 | * } 119 | * } 120 | * 121 | * 'moon' and 'sun' objects have property 'midnight' that gives sinAlt 122 | * value of Moon and Sun at beginning of the day. If rise and set 123 | * values absent, boolean 'alwaysAbove' field is set. 124 | */ 125 | StarJs.Solar.sunAndMoonEvents = function (start, end, lambda, phi, tz) { 126 | function sinAlt(approxBody, mjd, lambda, cphi, sphi) { 127 | var t, pos, tau; 128 | t = (mjd - 51544.5) / 36525.0; 129 | pos = approxBody(t); 130 | tau = StarJs.Time.gmst(mjd) + lambda - pos['ra']; 131 | return sphi * Math.sin(pos['dec']) + 132 | cphi * Math.cos(pos['dec']) * Math.cos(tau); 133 | } 134 | 135 | /** @const */ 136 | var EVENTS = StarJs.Solar.EVENTS; 137 | 138 | var result = [], today, h, j, ptime, ctime, ntime, H = 1.0 / 24.0, posp = {}, pos0 = {}, posn = {}, cphi = Math.cos(phi), sphi = Math.sin(phi), name, evt, yp = {'sun': {}, 'moon': {}}, y0 = {'sun': {}, 'moon': {}}, yn = {'sun': {}, 'moon': {}}, interp = {}; 139 | if (typeof tz === 'undefined') { 140 | tz = function (a) { 141 | return a; 142 | }; 143 | } 144 | 145 | ptime = start; 146 | 147 | posp['moon'] = sinAlt(StarJs.Solar.approxMoon, ptime, lambda, cphi, sphi); 148 | posp['sun'] = sinAlt(StarJs.Solar.approxSun, ptime, lambda, cphi, sphi); 149 | 150 | for (j = 0; j < EVENTS.length; j += 1) { 151 | evt = EVENTS[j]; 152 | name = evt['name']; 153 | 154 | yp[evt['body']][name] = posp[evt['body']] - evt['sinh0']; 155 | } 156 | 157 | while (ptime < end) { 158 | today = { 159 | 'moon': { 160 | 'midnight': posp['moon'] 161 | }, 162 | 'sun': { 163 | 'midnight': posp['sun'] 164 | } 165 | }; 166 | for (h = 1; h < 24; h += 2) { 167 | ctime = ptime + H; 168 | ntime = ctime + H; // ntime = ctime + 2 * H; 169 | 170 | // Calc new positions... 171 | pos0['moon'] = sinAlt(StarJs.Solar.approxMoon, ctime, lambda, 172 | cphi, sphi); 173 | pos0['sun'] = sinAlt(StarJs.Solar.approxSun, ctime, lambda, 174 | cphi, sphi); 175 | posn['moon'] = sinAlt(StarJs.Solar.approxMoon, ntime, lambda, 176 | cphi, sphi); 177 | posn['sun'] = sinAlt(StarJs.Solar.approxSun, ntime, lambda, 178 | cphi, sphi); 179 | 180 | for (j = 0; j < EVENTS.length; j += 1) { 181 | evt = EVENTS[j]; 182 | name = evt['name']; 183 | today[evt['body']][name] = today[evt['body']][name] || {}; 184 | 185 | y0[evt['body']][name] = pos0[evt['body']] - evt['sinh0']; 186 | yn[evt['body']][name] = posn[evt['body']] - evt['sinh0']; 187 | 188 | interp = StarJs.Math.quadInterpolation(yp[evt['body']][name], y0[evt['body']][name], yn[evt['body']][name]); 189 | 190 | switch (interp['roots'].length) { 191 | case 0: 192 | // No roots 193 | break; 194 | case 1: 195 | // Single root 196 | if (yp[evt['body']][name] < 0.0) { 197 | today[evt['body']][name]['rise'] = h + interp['roots'][0]; 198 | } else { 199 | today[evt['body']][name]['set'] = h + interp['roots'][0]; 200 | } 201 | break; 202 | case 2: 203 | // Two roots 204 | if (interp['ye'] < 0.0) { 205 | today[evt['body']][name]['rise'] = h + interp['roots'][1]; 206 | today[evt['body']][name]['set'] = h + interp['roots'][0]; 207 | } else { 208 | today[evt['body']][name]['rise'] = h + interp['roots'][0]; 209 | today[evt['body']][name]['set'] = h + interp['roots'][1]; 210 | } 211 | break; 212 | } 213 | } 214 | 215 | // Next interval 216 | yp = yn; 217 | yn = {'moon': {}, 'sun': {}}; 218 | 219 | ptime = ntime; 220 | } 221 | 222 | for (j = 0; j < EVENTS.length; j += 1) { 223 | evt = EVENTS[j]; 224 | name = evt['name']; 225 | 226 | if (!today[evt['body']][name]['set'] && !today[evt['body']][name]['rise']) { 227 | today[evt['body']][name]['alwaysAbove'] = (pos0[evt['body']] > evt['sinh0']); 228 | } 229 | } 230 | // Next day 231 | ptime = (start += 1.0); 232 | 233 | result.push(today); 234 | } 235 | 236 | return result; 237 | }; 238 | 239 | /********************************************************************** 240 | * 241 | * Solar sytem plantes. 242 | * 243 | */ 244 | 245 | /** @constructor */ 246 | StarJs.Solar.Sun = function () { 247 | this['name'] = 'Sun'; 248 | }; 249 | 250 | /** @const */ 251 | StarJs.Solar.Sun.$POS = new StarJs.Vector.Vector3(0, 0, 0); 252 | 253 | /** @const */ 254 | StarJs.Solar.GM = StarJs.Math.sqr(0.01720209895); 255 | 256 | /** 257 | * Cooordinates of Sun are constant. 258 | */ 259 | StarJs.Solar.Sun.prototype['keplerCoord'] = function (t) { 260 | return StarJs.Solar.Sun.$POS; 261 | }; 262 | 263 | /** @constructor */ 264 | StarJs.Solar.Body = function (name, params) { 265 | this['name'] = name; 266 | this['a'] = params['a']; 267 | this['ec'] = params['e']; 268 | this['m0'] = params['M0']; 269 | this['n'] = params['n']; 270 | this['omega'] = params['O']; 271 | this['i'] = params['i']; 272 | this['w'] = params['w']; 273 | this['t0'] = params['T0']; 274 | // ... 275 | }; 276 | 277 | StarJs.Solar.Body.prototype['keplerCoord'] = function (t) { 278 | var P = 1.3970; 279 | var m = StarJs.Math.DEG2RAD * (this['m0'] + this['n'] * (t - this['t0'])); 280 | var r = StarJs.Kepler.elliptic(StarJs.Solar.GM, m, this['a'], this['ec']); 281 | var pqr = StarJs.Kepler.gaussVec( 282 | StarJs.Math.DEG2RAD * (this['omega'] + P * t), 283 | StarJs.Math.DEG2RAD * this['i'], 284 | StarJs.Math.DEG2RAD * (this['w'] - this['omega'])); 285 | return pqr.apply(r); 286 | }; 287 | 288 | /** @const */ 289 | StarJs.Solar.BODIES = { 290 | 'Sun': new StarJs.Solar.Sun(), 291 | 'Mercury': new StarJs.Solar.Body('Mercury', { 292 | 'a' : 0.387099, 'e' : 0.205634, 'M0' : 174.7947, 'n' : 149472.6738, 293 | 'O' : 48.331, 'i' : 7.0048, 'w' : 77.4552, 'T0' : 0.0 294 | }), 295 | 'Venus': new StarJs.Solar.Body('Venus', { 296 | 'a' : 0.723332, 'e' : 0.006773, 'M0' : 50.4071, 'n' : 58517.8149, 297 | 'O' : 76.680, 'i' : 3.3946, 'w' : 131.5718, 'T0' : 0.0 298 | }), 299 | 'Earth': new StarJs.Solar.Body('Earth', { 300 | 'a' : 1.000000, 'e' : 0.016709, 'M0' : 357.5256, 'n' : 35999.3720, 301 | 'O' : 174.876, 'i' : 0.0000, 'w' : 102.9400, 'T0' : 0.0 302 | }), 303 | 'Mars': new StarJs.Solar.Body('Mars', { 304 | 'a' : 1.523692, 'e' : 0.093405, 'M0' : 19.3879, 'n' : 19140.3023, 305 | 'O' : 49.557, 'i' : 1.8496, 'w' : 336.0590, 'T0' : 0.0 306 | }), 307 | 'Jupiter': new StarJs.Solar.Body('Jupiter', { 308 | 'a' : 5.204267, 'e' : 0.048775, 'M0' : 18.8185, 'n' : 3033.6272, 309 | 'O' : 100.4908, 'i' : 1.3046, 'w' : 15.5576, 'T0' : 0.0 310 | }), 311 | 'Saturn': new StarJs.Solar.Body('Saturn', { 312 | 'a' : 9.582018, 'e' : 0.055723, 'M0' : 320.3477, 'n' : 1213.8664, 313 | 'O' : 113.6427, 'i' : 2.4852, 'w' : 89.6567, 'T0' : 0.0 314 | }), 315 | 'Uranus': new StarJs.Solar.Body('Uranus', { 316 | 'a' : 19.229412, 'e' : 0.044406, 'M0' : 142.9559, 'n' : 426.9282, 317 | 'O' : 73.9893, 'i' : 0.7726, 'w' : 170.5310, 'T0' : 0.0 318 | }), 319 | 'Neptune': new StarJs.Solar.Body('Neptune', { 320 | 'a' : 30.103658, 'e' : 0.011214, 'M0' : 267.7649, 'n' : 217.9599, 321 | 'O' : 131.7942, 'i' : 1.7680, 'w' : 37.4435, 'T0' : 0.0 322 | }) 323 | }; 324 | -------------------------------------------------------------------------------- /lib/StarJs/Time.js: -------------------------------------------------------------------------------- 1 | StarJs.Time = {}; 2 | 3 | StarJs.Time['DEFAULT_JULIAN_DATE'] = {'year': 1582, 'month': 10, 'day': 4}; 4 | StarJs.Time['DEFAULT_JULIAN_JD'] = 2299161; 5 | StarJs.Time['JD_MJD'] = 2400000.5; 6 | 7 | /** Convert Date or timestamp to mjd float. 8 | * @param {Date|number} t time 9 | */ 10 | StarJs.Time.time2mjd = function (t) { 11 | if (typeof t !== 'number') { 12 | t = t.getTime(); 13 | } 14 | return t / 86400000.0 + 40587.0; 15 | }; 16 | 17 | /** Convert mjd to date and time object, taking into account Julian 18 | * calendar. 19 | * @param jul {number=} JD of Julian date change. 20 | */ 21 | StarJs.Time.mjd2dt = function (mjd, jul) { 22 | if (typeof jul === 'undefined') { 23 | jul = StarJs.Time['DEFAULT_JULIAN_JD']; 24 | } 25 | var a = Math.floor(mjd) + 2400001, b, c, d, e, f, day, mon, year, hour, t; 26 | if (a < jul) { 27 | // Julian calendar 28 | b = 0; 29 | c = a + 1524; 30 | } else { 31 | // Grigorian calendar 32 | b = Math.floor((a - 1867216.25) / 36524.25); 33 | c = a + b - Math.floor(b / 4) + 1525; 34 | } 35 | d = Math.floor((c - 122.1) / 365.25); 36 | e = 365 * d + Math.floor(d / 4); 37 | f = Math.floor((c - e) / 30.6001); 38 | day = c - e - Math.floor(30.6001 * f); 39 | mon = f - 1 - 12 * Math.floor(f / 14); 40 | year = d - 4715 - Math.floor((7 + mon) / 10); 41 | hour = 24.0 * (mjd - Math.floor(mjd)); 42 | t = StarJs.Time.hour2hms(hour); 43 | t['year'] = year; 44 | t['month'] = mon; 45 | t['day'] = day; 46 | return t; 47 | }; 48 | 49 | /** (h, m, s) to floating-point hour. 50 | * @param {number} h hour 51 | * @param {number} m minute 52 | * @param {number} s second 53 | */ 54 | StarJs.Time.hms2hour = function (h, m, s) { 55 | return h + (m / 60.0) + (s / 3600.0); 56 | }; 57 | 58 | /** Convert floating-point hour to hours, minutes and seconds, 59 | * returing an object. 60 | * @param {number} hour 61 | */ 62 | StarJs.Time.hour2hms = function (hour) { 63 | var dms = new StarJs.Math.Dms(hour); 64 | return {'hour': dms['degree'], 'minute': dms['minute'], 'second': dms['second']}; 65 | }; 66 | 67 | /** Convert data-time object to MDJ float, taking into account a Julian 68 | * calendar. 69 | * @param {number} dt 70 | * @param {number=} jul JD of Julian date change. 71 | */ 72 | StarJs.Time.dt2mjd = function (dt, jul) { 73 | if (typeof jul === 'undefined') { 74 | jul = StarJs.Time['DEFAULT_JULIAN_DATE']; 75 | } 76 | 77 | var year = dt['year'], mon = dt['month'], b; 78 | if (mon <= 2) { 79 | mon += 12; 80 | year -= 1; 81 | } 82 | if (year <= jul['year'] && mon <= jul['month'] && dt['day'] <= jul['day']) { 83 | // Julian 84 | b = -2 + Math.floor((year + 4716) / 4) - 1179; 85 | } else { 86 | // Gregorian 87 | b = Math.floor(year / 400) - Math.floor(year / 100) + Math.floor(year / 4); 88 | } 89 | var mjdMidnight = 365 * year - 679004 + b + Math.floor(30.6001 * (mon + 1)) + dt['day']; 90 | var frac = StarJs.Time.hms2hour(dt['hour'], dt['minute'], dt['second']) / 24.0; 91 | return mjdMidnight + frac; 92 | }; 93 | 94 | /** Convert MJD to JCT. 95 | * @param {number} mjd 96 | */ 97 | StarJs.Time.mjd2jct = function (mjd) { 98 | return (mjd - 51544.5) / 36525.0; 99 | }; 100 | 101 | /** Convert MJD to Greenwich Mean Sidereal Time float. 102 | * @param mjd {number} 103 | */ 104 | StarJs.Time.gmst = function (mjd) { 105 | /* TODO: move to global */ 106 | var SECS = 86400; // 24*60*60 -- number of seconds in day; 107 | var mjd0 = Math.floor(mjd), ut = SECS * (mjd - mjd0), t0, t, gmst; 108 | t0 = StarJs.Time.mjd2jct(mjd0); 109 | t = StarJs.Time.mjd2jct(mjd); 110 | gmst = 24110.54841 + 8640184.812866 * t0 + 1.0027379093 * ut + 111 | (0.093104 - (6.2e-6) * t) * t * t; 112 | return StarJs.Math.PI2 / SECS * StarJs.Math.mod(gmst, SECS); 113 | }; 114 | -------------------------------------------------------------------------------- /lib/StarJs/Vector.js: -------------------------------------------------------------------------------- 1 | StarJs.Vector = {}; 2 | 3 | /** @constructor 4 | * @param {number} x 5 | * @param {number} y 6 | * @param {number} z 7 | */ 8 | StarJs.Vector.Vector3 = function (x, y, z) { 9 | this['x'] = x; 10 | this['y'] = y; 11 | this['z'] = z; 12 | }; 13 | 14 | (function (p) { 15 | p.len = function () { 16 | return Math.sqrt(this['x'] * this['x'] + this['y'] * this['y'] + this['z'] * this['z']); 17 | }; 18 | 19 | /** Return new Vector3 as sum of this and the v3. 20 | * @param {StarJs.Vector.Vector3} v3 21 | */ 22 | p.add = function (v3) { 23 | return new StarJs.Vector.Vector3(this['x'] + v3['x'], 24 | this['y'] + v3['y'], 25 | this['z'] + v3['z']); 26 | }; 27 | 28 | /** Return new Vector3 as v3 substracted from this. 29 | * @param {StarJs.Vector.Vector3} v3 30 | */ 31 | p.sub = function (v3) { 32 | return new StarJs.Vector.Vector3(this['x'] - v3['x'], 33 | this['y'] - v3['y'], 34 | this['z'] - v3['z']); 35 | }; 36 | 37 | p.neg = function () { 38 | return new StarJs.Vector.Vector3(-this['x'], -this['y'], -this['z']); 39 | }; 40 | 41 | /** Return new Vector3 multiplied by number a. 42 | * @param {number} a 43 | */ 44 | p.scale = function (a) { 45 | return new StarJs.Vector.Vector3(a * this['x'], a * this['y'], a * this['z']); 46 | }; 47 | 48 | /** Copy this vector. 49 | */ 50 | p.clone = function () { 51 | return new StarJs.Vector.Vector3(this['x'], this['y'], this['z']); 52 | }; 53 | }(StarJs.Vector.Vector3.prototype)); 54 | 55 | /** @constructor 56 | * @param {number|StarJs.Vector.Vector3} az_or_v3 57 | * @param {number=} elev 58 | * @param {number=} rad 59 | */ 60 | StarJs.Vector.Polar3 = function (az_or_v3, elev, rad) { 61 | var alen = arguments.length; 62 | if (alen === 2) { 63 | rad = 1.0; 64 | } 65 | if (alen === 2 || alen === 3) { 66 | this['phi'] = az_or_v3; 67 | this['theta'] = elev; 68 | this['rad'] = rad; 69 | } else { 70 | var v3 = az_or_v3; 71 | var rho2 = v3['x'] * v3['x'] + v3['y'] * v3['y']; 72 | this['rad'] = Math.sqrt(rho2 + v3['z'] * v3['z']); 73 | this['phi'] = (v3['x'] === 0.0 && v3['y'] === 0.0) ? 0.0 : Math.atan2(v3['y'], v3['x']); 74 | if (this['phi'] < 0.0) { 75 | this['phi'] += StarJs.Math.PI2; 76 | } 77 | var rho = Math.sqrt(rho2); 78 | this['theta'] = (v3['z'] === 0.0 && rho === 0.0) ? 0.0 : Math.atan2(v3['z'], rho); 79 | } 80 | }; 81 | 82 | (function (p) { 83 | p.toVector3 = function () { 84 | var ct = Math.cos(this['theta']); 85 | var rad = this['rad'] 86 | var x = rad * ct * Math.cos(this['phi']); 87 | var y = rad * ct * Math.sin(this['phi']); 88 | var z = rad * Math.sin(this['theta']); 89 | return new StarJs.Vector.Vector3(x, y, z); 90 | }; 91 | 92 | p.clone = function () { 93 | return new StarJs.Vector.Polar3(this['rad'], this['phi'], this['theta']); 94 | }; 95 | }(StarJs.Vector.Polar3.prototype)); 96 | 97 | /** @constructor 98 | * @param {(StarJs.Vector.Vector3|boolean)=} v2_or_reuse_flag 99 | * @param {StarJs.Vector.Vector3=} v3 100 | */ 101 | StarJs.Vector.Matrix3 = function (v1, v2_or_reuse_flag, v3) { 102 | if (arguments.length === 3) { 103 | var v2 = v2_or_reuse_flag; // Just for brevity. 104 | this['mat'] = [[v1['x'], v1['y'], v1['z']], 105 | [v2['x'], v2['y'], v2['z']], 106 | [v3['x'], v3['y'], v3['z']]]; 107 | } else { 108 | if (v2_or_reuse_flag) { 109 | this['mat'] = v1; 110 | } else { 111 | this['mat'] = [[v1[0][0], v1[0][1], v1[0][2]], 112 | [v1[1][0], v1[1][1], v1[1][2]], 113 | [v1[2][0], v1[2][1], v1[2][2]]]; 114 | } 115 | } 116 | }; 117 | 118 | (function (p) { 119 | /** Multiply matrix-to-vector multiplication. 120 | * @param {StarJs.Vector.Vector3} v 121 | */ 122 | p.apply = function (v) { 123 | var l = this['mat'][0]; 124 | var x = l[0] * v['x'] + l[1] * v['y'] + l[2] * v['z']; 125 | 126 | l = this['mat'][1]; 127 | var y = l[0] * v['x'] + l[1] * v['y'] + l[2] * v['z']; 128 | 129 | l = this['mat'][2]; 130 | var z = l[0] * v['x'] + l[1] * v['y'] + l[2] * v['z']; 131 | 132 | return new StarJs.Vector.Vector3(x, y, z); 133 | }; 134 | 135 | /** Perform a matrix multiplication, returning a new matrix. 136 | * @param {StarJs.Vector.Matrix3} matrix right matrix. 137 | */ 138 | p.mult = function (matrix) { 139 | var res = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; 140 | var mat = matrix['mat']; 141 | for (var i = 0; i < 3; i += 1) { 142 | var tline = this['mat'][i]; 143 | var rline = res[i]; 144 | for (var j = 0; j < 3; j += 1) { 145 | for (var k = 0; k < 3; k += 1) { 146 | rline[j] += tline[k] * mat[k][j]; 147 | } 148 | } 149 | } 150 | return new StarJs.Vector.Matrix3(res, true); 151 | }; 152 | }(StarJs.Vector.Matrix3.prototype)); 153 | 154 | /** Return x-rotation matrix by angle phi. 155 | * @param {number} phi. 156 | */ 157 | StarJs.Vector.Matrix3.r_x = function (phi) { 158 | var cp = Math.cos(phi), sp = Math.sin(phi); 159 | return new StarJs.Vector.Matrix3([[1.0, 0.0, 0.0], 160 | [0.0, cp, sp], 161 | [0.0, -sp, cp]]); 162 | }; 163 | 164 | /** Return y-rotation matrix by angle phi. 165 | * @param {number} phi. 166 | */ 167 | StarJs.Vector.Matrix3.r_y = function (phi) { 168 | var cp = Math.cos(phi), sp = Math.sin(phi); 169 | return new StarJs.Vector.Matrix3([[ cp, 0.0, -sp], 170 | [0.0, 1.0, 0.0], 171 | [ sp, 0.0, cp]]); 172 | }; 173 | 174 | /** Return z-rotation matrix by angle phi. 175 | * @param {number} phi. 176 | */ 177 | StarJs.Vector.Matrix3.r_z = function (phi) { 178 | var cp = Math.cos(phi), sp = Math.sin(phi); 179 | return new StarJs.Vector.Matrix3([[ cp, sp, 0.0], 180 | [-sp, cp, 0.0], 181 | [0.0, 0.0, 1.0]]); 182 | 183 | }; 184 | -------------------------------------------------------------------------------- /lib/export.js: -------------------------------------------------------------------------------- 1 | window['StarJs'] = StarJs; 2 | 3 | /* Math */ 4 | StarJs['Math'] = StarJs.Math; 5 | StarJs.Math['DEG2RAD'] = StarJs.Math.DEG2RAD; 6 | StarJs.Math['RAD2DEG'] = StarJs.Math.RAD2DEG; 7 | StarJs.Math['RAD2ARCS'] = StarJs.Math.RAD2ARCS; 8 | StarJs.Math['ARCS2RAD'] = StarJs.Math.ARCS2RAD; 9 | StarJs.Math['PI2'] = StarJs.Math.PI2; 10 | StarJs.Math['sqr'] = StarJs.Math.sqr; 11 | StarJs.Math['frac'] = StarJs.Math.frac; 12 | StarJs.Math['mod'] = StarJs.Math.mod; 13 | StarJs.Math['Dms'] = StarJs.Math.Dms; 14 | StarJs.Math['dms2deg'] = StarJs.Math.dms2deg; 15 | StarJs.Math.Dms.prototype['dms2deg'] = StarJs.Math.Dms.prototype.dms2deg; 16 | StarJs.Math['deg2dms'] = StarJs.Math.deg2dms; 17 | StarJs.Math['angle2hms'] = StarJs.Math.angle2hms; 18 | StarJs.Math['quadInterpolation'] = StarJs.Math.quadInterpolation; 19 | StarJs.Math['sinh'] = StarJs.Math.sinh; 20 | StarJs.Math['cosh'] = StarJs.Math.cosh; 21 | 22 | /* Coord */ 23 | StarJs['Coord'] = StarJs.Coord; 24 | StarJs.Coord['precessionEclMatrix'] = StarJs.Coord.precessionEclMatrix; 25 | StarJs.Coord['precessionEquMatrix'] = StarJs.Coord.precessionEquMatrix; 26 | StarJs.Coord['eclipticObliquity'] = StarJs.Coord.eclipticObliquity; 27 | StarJs.Coord['equ2eclMatrix'] = StarJs.Coord.equ2eclMatrix; 28 | StarJs.Coord['ecl2equMatrix'] = StarJs.Coord.ecl2equMatrix; 29 | StarJs.Coord['equ2hor'] = StarJs.Coord.equ2hor; 30 | StarJs.Coord['hor2equ'] = StarJs.Coord.hor2equ; 31 | 32 | /* Kepler */ 33 | StarJs['Kepler'] = StarJs.Kepler; 34 | StarJs.Kepler['eccAnomaly'] = StarJs.Kepler.eccAnomaly; 35 | StarJs.Kepler['elliptic'] = StarJs.Kepler.elliptic; 36 | StarJs.Kepler['parabolic'] = StarJs.Kepler.parabolic; 37 | StarJs.Kepler['hypAnom'] = StarJs.Kepler.hypAnom; 38 | StarJs.Kepler['hyperbolic'] = StarJs.Kepler.hyperbolic; 39 | StarJs.Kepler['keplerPos'] = StarJs.Kepler.keplerPos; 40 | StarJs.Kepler['gaussVec'] = StarJs.Kepler.gaussVec; 41 | 42 | /* Solar */ 43 | StarJs['Solar'] = StarJs.Solar; 44 | StarJs.Solar['EPS'] = StarJs.Solar.EPS; 45 | StarJs.Solar['approxMoon'] = StarJs.Solar.approxMoon; 46 | StarJs.Solar['approxSun'] = StarJs.Solar.approxSun; 47 | StarJs.Solar['sunAndMoonEvents'] = StarJs.Solar.sunAndMoonEvents; 48 | StarJs.Solar['Sun'] = StarJs.Solar.Sun; 49 | StarJs.Solar['GM'] = StarJs.Solar.GM; 50 | StarJs.Solar['Body'] = StarJs.Solar.Body; 51 | StarJs.Solar['BODIES'] = StarJs.Solar.BODIES; 52 | 53 | /* Time */ 54 | StarJs['Time'] = StarJs.Time; 55 | StarJs.Time['time2mjd'] = StarJs.Time.time2mjd; 56 | StarJs.Time['mjd2dt'] = StarJs.Time.mjd2dt; 57 | StarJs.Time['hms2hour'] = StarJs.Time.hms2hour; 58 | StarJs.Time['hour2hms'] = StarJs.Time.hour2hms; 59 | StarJs.Time['dt2mjd'] = StarJs.Time.dt2mjd; 60 | StarJs.Time['mjd2jct'] = StarJs.Time.mjd2jct; 61 | StarJs.Time['gmst'] = StarJs.Time.gmst; 62 | 63 | /* Vector */ 64 | StarJs['Vector'] = StarJs.Vector; 65 | StarJs.Vector['Vector3'] = StarJs.Vector.Vector3; 66 | 67 | StarJs.Vector.Vector3.prototype['len'] = StarJs.Vector.Vector3.prototype.len; 68 | StarJs.Vector.Vector3.prototype['add'] = StarJs.Vector.Vector3.prototype.add; 69 | StarJs.Vector.Vector3.prototype['sub'] = StarJs.Vector.Vector3.prototype.sub; 70 | StarJs.Vector.Vector3.prototype['neg'] = StarJs.Vector.Vector3.prototype.neg; 71 | StarJs.Vector.Vector3.prototype['scale'] = StarJs.Vector.Vector3.prototype.scale; 72 | StarJs.Vector.Vector3.prototype['clone'] = StarJs.Vector.Vector3.prototype.clone; 73 | 74 | StarJs.Vector['Polar3'] = StarJs.Vector.Polar3; 75 | 76 | StarJs.Vector.Polar3.prototype['toVector3'] = StarJs.Vector.Polar3.prototype.toVector3; 77 | StarJs.Vector.Polar3.prototype['clone'] = StarJs.Vector.Polar3.prototype.clone; 78 | 79 | StarJs.Vector['Matrix3'] = StarJs.Vector.Matrix3; 80 | 81 | StarJs.Vector.Matrix3.prototype['apply'] = StarJs.Vector.Matrix3.prototype.apply; 82 | StarJs.Vector.Matrix3.prototype['mult'] = StarJs.Vector.Matrix3.prototype.mult; 83 | 84 | StarJs.Vector.Matrix3['r_x'] = StarJs.Vector.Matrix3.r_x; 85 | StarJs.Vector.Matrix3['r_y'] = StarJs.Vector.Matrix3.r_y; 86 | StarJs.Vector.Matrix3['r_y'] = StarJs.Vector.Matrix3.r_y; 87 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2020 Ivan Boldyrev 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted (subject to the limitations in the 7 | disclaimer below) provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the 15 | distribution. 16 | 17 | * Neither the name of Ivan Boldyrev. nor the names of his 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE 22 | GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 23 | HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 24 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 25 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 30 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 31 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 32 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 33 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 26 | 27 | 28 | --------------------------------------------------------------------------------