not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3091 lines
123 KiB

  1. /*!
  2. Virtual Sky
  3. (c) Stuart Lowe, Las Cumbres Observatory Global Telescope
  4. A browser planetarium using HTML5's <canvas>.
  5. */
  6. /*
  7. USAGE:
  8. <!--[if lt IE 9]><script src="http://lcogt.net/virtualsky/embed/excanvas.js" type="text/javascript"></script><![endif]-->
  9. <script src="http://lcogt.net/virtualsky/embed/jquery-1.7.1.min.js" type="text/javascript"></script>
  10. <script src="http://lcogt.net/virtualsky/embed/virtualsky.js" type="text/javascript"></script>
  11. <script type="text/javascript">
  12. <!--
  13. $(document).ready(function(){
  14. planetarium = $.virtualsky({id:'starmapper',projection:'polar'}); // Assumes you want to draw this to a <div> with the id 'starmapper'
  15. });
  16. // -->
  17. </script>
  18. OPTIONS (default values in brackets):
  19. id ('starmap') - The ID for the HTML element where you want the sky inserted
  20. projection ('polar') - The projection type as 'polar', 'stereo', 'lambert', 'equirectangular', or 'ortho'
  21. width (500) - Set the width of the sky unless you've set the width of the element
  22. height (250) - Set the height of the sky unless you've set the height of the element
  23. planets - either an object containing an array of planets or a JSON file
  24. magnitude (5) - the magnitude limit of displayed stars
  25. longitude (53.0) - the longitude of the observer
  26. latitude (-2.5) - the latitude of the observer
  27. clock (now) - a Javascript Date() object with the starting date/time
  28. background ('rgba(0,0,0,0)') - the background colour
  29. transparent (false) - make the sky background transparent
  30. color ('rgb(255,255,255)') - the text colour
  31. az (180) - an azimuthal offset with 0 = north and 90 = east
  32. ra (0 <= x < 360) - the RA for the centre of the view in gnomic projection
  33. dec (-90 < x < 90) - the Declination for the centre of the view in gnomic projection
  34. negative (false) - invert the default colours i.e. to black on white
  35. ecliptic (false) - show the Ecliptic line
  36. meridian (false) - show the Meridian line
  37. gradient (true) - reduce the brightness of stars near the horizon
  38. cardinalpoints (true) - show/hide the N/E/S/W labels
  39. constellations (false) - show/hide the constellation lines
  40. constellationlabels (false) - show/hide the constellation labels
  41. constellationboundaries (false) - show/hide the constellation boundaries (IAU)
  42. showstars (true) - show/hide the stars
  43. showstarlabels (false) - show/hide the star labels for brightest stars
  44. showplanets (true) - show/hide the planets
  45. showplanetlabels (true) - show/hide the planet labels
  46. showorbits (false) - show/hide the orbits of the planets
  47. showgalaxy (false) - show/hide an outline of the plane of the Milky Way
  48. showdate (true) - show/hide the date and time
  49. showposition (true) - show/hide the latitude/longitude
  50. ground (false) - show/hide the local ground (for full sky projections)
  51. keyboard (true) - allow keyboard controls
  52. mouse (true) - allow mouse controls
  53. gridlines_az (false) - show/hide the azimuth/elevation grid lines
  54. gridlines_eq (false) - show/hide the RA/Dec grid lines
  55. gridlines_gal (false) - show/hide the Galactic Coordinate grid lines
  56. gridstep (30) - the size of the grid step when showing grid lines
  57. live (false) - update the display in real time
  58. fontsize - set the font size in pixels if you want to over-ride the auto sizing
  59. fontfamily - set the font family using a CSS style font-family string otherwise it inherits from the container element
  60. objects - a semi-colon-separated string of recognized object names to display e.g. "M1;M42;Horsehead Nebula" (requires internet connection)
  61. */
  62. (function ($) {
  63. /*@cc_on
  64. // Fix for IE's inability to handle arguments to setTimeout/setInterval
  65. // From http://webreflection.blogspot.com/2007/06/simple-settimeout-setinterval-extra.html
  66. (function(f){
  67. window.setTimeout =f(window.setTimeout);
  68. window.setInterval =f(window.setInterval);
  69. })(function(f){return function(c,t){var a=[].slice.call(arguments,2);return f(function(){c.apply(this,a)},t)}});
  70. @*/
  71. // Define a shortcut for checking variable types
  72. function is(a,b){return typeof a===b;}
  73. // Get the URL query string and parse it
  74. $.query = function() {
  75. var r = {length:0};
  76. var q = location.search;
  77. if(q && q != '#'){
  78. // remove the leading ? and trailing &
  79. q = q.replace(/^\?/,'').replace(/\&$/,'');
  80. jQuery.each(q.split('&'), function(){
  81. var key = this.split('=')[0];
  82. var val = this.split('=')[1];
  83. // convert floats
  84. if(/^-?[0-9.]+$/.test(val)) val = parseFloat(val);
  85. if(val == "true") val = true;
  86. if(val == "false") val = false;
  87. if(/^\?[0-9\.]+$/.test(val)) val = parseFloat(val); // convert floats
  88. r[key] = val;
  89. });
  90. }
  91. return r;
  92. };
  93. $.extend($.fn.addTouch = function(){
  94. // Adapted from http://code.google.com/p/rsslounge/source/browse/trunk/public/javascript/addtouch.js?spec=svn115&r=115
  95. this.each(function(i,el){
  96. // Pass the original event object because the jQuery event object
  97. // is normalized to w3c specs and does not provide the TouchList.
  98. $(el).bind('touchstart touchmove touchend touchcancel touchdbltap',function(){ handleTouch(event); });
  99. });
  100. var handleTouch = function(event){
  101. event.preventDefault();
  102. var simulatedEvent;
  103. var touches = event.changedTouches,
  104. first = touches[0],
  105. type = '';
  106. switch(event.type){
  107. case 'touchstart':
  108. type = ['mousedown','click'];
  109. break;
  110. case 'touchmove':
  111. type = ['mousemove'];
  112. break;
  113. case 'touchend':
  114. type = ['mouseup'];
  115. break;
  116. case 'touchdbltap':
  117. type = ['dblclick'];
  118. break;
  119. default:
  120. return;
  121. }
  122. for(var i = 0; i < type.length; i++){
  123. simulatedEvent = document.createEvent('MouseEvent');
  124. simulatedEvent.initMouseEvent(type[i], true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0/*left*/, null);
  125. first.target.dispatchEvent(simulatedEvent);
  126. }
  127. };
  128. });
  129. /*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
  130. * Licensed under the MIT License (LICENSE.txt).
  131. *
  132. * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
  133. * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
  134. * Thanks to: Seamus Leahy for adding deltaX and deltaY
  135. *
  136. * Version: 3.1.3
  137. *
  138. * Requires: 1.2.2+
  139. */
  140. (function (factory) {
  141. if (typeof define==='function' && define.amd){
  142. // AMD. Register as an anonymous module.
  143. define(['jquery'], factory);
  144. } else if (typeof exports==='object') {
  145. // Node/CommonJS style for Browserify
  146. module.exports = factory;
  147. } else {
  148. // Browser globals
  149. factory(jQuery);
  150. }
  151. }(function ($) {
  152. var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
  153. var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
  154. var lowestDelta, lowestDeltaXY;
  155. if ( $.event.fixHooks ) {
  156. for ( var i = toFix.length; i; ) {
  157. $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
  158. }
  159. }
  160. $.event.special.mousewheel = {
  161. setup: function() {
  162. if ( this.addEventListener ) {
  163. for ( var i = toBind.length; i; ) {
  164. this.addEventListener( toBind[--i], handler, false );
  165. }
  166. } else {
  167. this.onmousewheel = handler;
  168. }
  169. },
  170. teardown: function() {
  171. if ( this.removeEventListener ) {
  172. for ( var i = toBind.length; i; ) {
  173. this.removeEventListener( toBind[--i], handler, false );
  174. }
  175. } else {
  176. this.onmousewheel = null;
  177. }
  178. }
  179. };
  180. $.fn.extend({
  181. mousewheel: function(fn) {
  182. return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
  183. },
  184. unmousewheel: function(fn) {
  185. return this.unbind("mousewheel", fn);
  186. }
  187. });
  188. function handler(event) {
  189. var orgEvent = event || window.event,
  190. args = [].slice.call(arguments, 1),
  191. delta = 0,
  192. deltaX = 0,
  193. deltaY = 0,
  194. absDelta = 0,
  195. absDeltaXY = 0,
  196. fn;
  197. event = $.event.fix(orgEvent);
  198. event.type = "mousewheel";
  199. // Old school scrollwheel delta
  200. if (orgEvent.wheelDelta) {delta = orgEvent.wheelDelta;}
  201. if (orgEvent.detail) {delta = orgEvent.detail * -1;}
  202. // New school wheel delta (wheel event)
  203. if (orgEvent.deltaY){
  204. deltaY = orgEvent.deltaY * -1;
  205. delta = deltaY;
  206. }
  207. if (orgEvent.deltaX){
  208. deltaX = orgEvent.deltaX;
  209. delta = deltaX * -1;
  210. }
  211. // Webkit
  212. if (orgEvent.wheelDeltaY!==undefined ) { deltaY = orgEvent.wheelDeltaY; }
  213. if (orgEvent.wheelDeltaX!==undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
  214. // Look for lowest delta to normalize the delta values
  215. absDelta = Math.abs(delta);
  216. if (!lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
  217. absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
  218. if (!lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
  219. // Get a whole value for the deltas
  220. fn = delta > 0 ? 'floor' : 'ceil';
  221. delta = Math[fn](delta / lowestDelta);
  222. deltaX = Math[fn](deltaX / lowestDeltaXY);
  223. deltaY = Math[fn](deltaY / lowestDeltaXY);
  224. // Add event and delta to the front of the arguments
  225. args.unshift(event, delta, deltaX, deltaY);
  226. return ($.event.dispatch || $.event.handle).apply(this, args);
  227. }
  228. }));
  229. /*! VirtualSky */
  230. function VirtualSky(input){
  231. this.version = "0.6.7";
  232. this.ie = false;
  233. this.excanvas = (typeof G_vmlCanvasManager != 'undefined') ? true : false;
  234. /*@cc_on
  235. this.ie = true
  236. @*/
  237. this.q = $.query(); // Query string
  238. this.dir = $('script[src*=virtualsky]').attr('src').match(/^.*\//); // the JS file path
  239. this.dir = this.dir && this.dir[0] || ""; // set dir to match or ""
  240. this.langurl = this.dir + "lang/%LANG%.json"; // The location of the language files
  241. this.id = ''; // The ID of the canvas/div tag - if none given it won't display
  242. this.gradient = true; // Show the sky gradient
  243. this.magnitude = 5; // Limit for stellar magnitude
  244. this.background = "rgba(0,0,0,0)"; // Default background colour is transparent
  245. this.color = ""; // Default background colour is chosen automatically
  246. this.wide = 0; // Default width if not set in the <canvas> <div> or input argument
  247. this.tall = 0;
  248. // Constants
  249. this.d2r = Math.PI/180;
  250. this.r2d = 180.0/Math.PI;
  251. // Set location on the Earth
  252. this.setLongitude(-119.86286);
  253. this.setLatitude(34.4326);
  254. // Toggles
  255. this.spin = false;
  256. this.cardinalpoints = true; // Display N, E, S and W.
  257. this.constellation = { lines: false, boundaries: false, labels: false }; // Display constellations
  258. this.meteorshowers = false; // Display meteor shower radiants
  259. this.negative = false; // Invert colours to make it better for printing
  260. this.showgalaxy = false; // Display the Milky Way
  261. this.showstars = true; // Display current positions of the stars
  262. this.showstarlabels = false; // Display names for named stars
  263. this.showplanets = true; // Display current positions of the planets
  264. this.showplanetlabels = true; // Display names for planets
  265. this.showorbits = false; // Display the orbital paths of the planets
  266. this.showdate = true; // Display the current date
  267. this.showposition = true; // Display the longitude/latitude
  268. this.scalestars = 1; // A scale factor by which to increase the star sizes
  269. this.ground = false;
  270. this.grid = { az: false, eq: false, gal: false, step: 30 }; // Display grids
  271. this.ecliptic = false; // Display the Ecliptic
  272. this.meridian = false; // Display the Meridian
  273. this.keyboard = true; // Allow keyboard controls
  274. this.mouse = true; // Allow mouse controls
  275. this.islive = false; // Update the sky in real time
  276. this.fullscreen = false; // Should it take up the full browser window
  277. this.transparent = false; // Show the sky background or not
  278. this.fps = 10; // Number of frames per second when animating
  279. this.credit = (location.host == "lco.global" && location.href.indexOf("/embed") < 0) ? false : true;
  280. this.callback = { geo:'', mouseenter:'', mouseout:'' };
  281. this.keys = new Array();
  282. this.base = "";
  283. this.az_step = 0;
  284. this.az_off = 0;
  285. this.ra_off = 0;
  286. this.dc_off = 0;
  287. this.fov = 30;
  288. this.plugins = [];
  289. this.calendarevents = [];
  290. this.events = {}; // Let's add some default events
  291. // Projections
  292. this.projections = {
  293. 'polar': {
  294. title: 'Polar projection',
  295. azel2xy: function(az,el,w,h){
  296. var radius = h/2;
  297. var r = radius*((Math.PI/2)-el)/(Math.PI/2);
  298. return {x:(w/2-r*Math.sin(az)),y:(radius-r*Math.cos(az)),el:el};
  299. },
  300. polartype: true,
  301. atmos: true
  302. },
  303. 'fisheye':{
  304. title: 'Fisheye polar projection',
  305. azel2xy: function(az,el,w,h){
  306. var radius = h/2;
  307. var r = radius*Math.sin(((Math.PI/2)-el)/2)/0.70710678; // the field of view is bigger than 180 degrees
  308. return {x:(w/2-r*Math.sin(az)),y:(radius-r*Math.cos(az)),el:el};
  309. },
  310. polartype:true,
  311. atmos: true
  312. },
  313. 'ortho':{
  314. title: 'Orthographic polar projection',
  315. azel2xy: function(az,el,w,h){
  316. var radius = h/2;
  317. var r = radius*Math.cos(el);
  318. return {x:(w/2-r*Math.sin(az)),y:(radius-r*Math.cos(az)),el:el};
  319. },
  320. polartype:true,
  321. atmos: true
  322. },
  323. 'stereo': {
  324. title: 'Stereographic projection',
  325. azel2xy: function(az,el,w,h){
  326. var f = 0.42;
  327. var sinel1 = 0;
  328. var cosel1 = 1;
  329. var cosaz = Math.cos((az-Math.PI));
  330. var sinaz = Math.sin((az-Math.PI));
  331. var sinel = Math.sin(el);
  332. var cosel = Math.cos(el);
  333. var k = 2/(1+sinel1*sinel+cosel1*cosel*cosaz);
  334. return {x:(w/2+f*k*h*cosel*sinaz),y:(h-f*k*h*(cosel1*sinel-sinel1*cosel*cosaz)),el:el};
  335. },
  336. atmos: true
  337. },
  338. 'lambert':{
  339. title: 'Lambert projection',
  340. azel2xy: function(az,el,w,h){
  341. var cosaz = Math.cos((az-Math.PI));
  342. var sinaz = Math.sin((az-Math.PI));
  343. var sinel = Math.sin(el);
  344. var cosel = Math.cos(el);
  345. var k = Math.sqrt(2/(1+cosel*cosaz));
  346. return {x:(w/2+0.6*h*k*cosel*sinaz),y:(h-0.6*h*k*(sinel)),el:el};
  347. },
  348. atmos: true
  349. },
  350. 'gnomic': {
  351. title: 'Gnomic projection',
  352. azel2xy: function(az,el){
  353. if(el >= 0){
  354. var pos = this.azel2radec(az,el);
  355. return this.radec2xy(pos.ra*this.d2r,pos.dec*this.d2r,[el,az]);
  356. }else{
  357. return { x: -1, y: -1, el: el };
  358. }
  359. },
  360. radec2xy: function(ra,dec,coords){
  361. var fov, cd, cd0, sd, sd0, dA, A, F, scale, twopi;
  362. // Only want to project the sky around the map centre
  363. if(Math.abs(dec-this.dc_off) > this.maxangle) return {x:-1,y:-1,el:-1};
  364. var ang = this.greatCircle(this.ra_off,this.dc_off,ra,dec);
  365. if(ang > this.maxangle) return {x:-1,y:-1,el:-1};
  366. if(!coords) coords = this.coord2horizon(ra, dec);
  367. // Should we show things below the horizon?
  368. if(this.ground && coords[0] < -1e-6) return {x:-1, y:-1, el:coords[0]*this.r2d};
  369. // number of pixels per degree in the map
  370. scale = this.tall/this.fov;
  371. cd = Math.cos(dec);
  372. cd0 = Math.cos(this.dc_off);
  373. sd = Math.sin(dec);
  374. sd0 = Math.sin(this.dc_off);
  375. dA = ra-this.ra_off;
  376. dA = inrangeAz(dA);
  377. A = cd*Math.cos(dA);
  378. F = scale*this.r2d/(sd0*sd + A*cd0);
  379. return {x:(this.wide/2)-F*cd*Math.sin(dA),y:(this.tall/2) -F*(cd0*sd - A*sd0),el:coords[0]*this.r2d};
  380. },
  381. draw: function(){
  382. if(!this.transparent){
  383. this.ctx.fillStyle = (this.hasGradient()) ? "rgba(0,15,30, 1)" : ((this.negative) ? this.col.white : this.col.black);
  384. this.ctx.fillRect(0,0,this.wide,this.tall);
  385. this.ctx.fill();
  386. }
  387. },
  388. isVisible: function(el){
  389. return true;
  390. },
  391. atmos: false,
  392. fullsky: true
  393. },
  394. 'equirectangular':{
  395. title: 'Equirectangular projection',
  396. azel2xy: function(az,el,w,h){
  397. while(az < 0) az += 2*Math.PI;
  398. az = (az)%(Math.PI*2);
  399. return {x:(((az-Math.PI)/(Math.PI/2))*h + w/2),y:(h-(el/(Math.PI/2))*h),el:el};
  400. },
  401. maxb: 90,
  402. atmos: true
  403. },
  404. 'mollweide':{
  405. title: 'Mollweide projection',
  406. radec2xy: function(ra,dec){
  407. var dtheta, x, y, coords, sign, outside, normra;
  408. var thetap = Math.abs(dec);
  409. var pisindec = Math.PI*Math.sin(Math.abs(dec));
  410. // Now iterate to correct answer
  411. for(var i = 0; i < 20 ; i++){
  412. dtheta = -(thetap + Math.sin(thetap) - pisindec)/(1+Math.cos(thetap));
  413. thetap += dtheta;
  414. if(dtheta < 1e-4) break;
  415. }
  416. normra = (ra+this.d2r*this.az_off)%(2*Math.PI) - Math.PI;
  417. outside = false;
  418. x = -(2/Math.PI)*(normra)*Math.cos(thetap/2)*this.tall/2 + this.wide/2;
  419. if(x > this.wide) outside = true;
  420. sign = (dec >= 0) ? 1 : -1;
  421. y = -sign*Math.sin(thetap/2)*this.tall/2 + this.tall/2;
  422. coords = this.coord2horizon(ra, dec);
  423. return {x:(outside ? -100 : x%this.wide),y:y,el:coords[0]*this.r2d};
  424. },
  425. draw: function(){
  426. var c = this.ctx;
  427. c.moveTo(this.wide/2,this.tall/2);
  428. c.beginPath();
  429. var x = this.wide/2-this.tall;
  430. var y = 0;
  431. var w = this.tall*2;
  432. var h = this.tall;
  433. var kappa = 0.5522848;
  434. var ox = (w / 2) * kappa; // control point offset horizontal
  435. var oy = (h / 2) * kappa; // control point offset vertical
  436. var xe = x + w; // x-end
  437. var ye = y + h; // y-end
  438. var xm = x + w / 2; // x-middle
  439. var ym = y + h / 2; // y-middle
  440. c.moveTo(x, ym);
  441. c.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  442. c.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  443. c.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  444. c.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  445. c.closePath();
  446. if(!this.transparent){
  447. c.fillStyle = (this.hasGradient()) ? "rgba(0,15,30, 1)" : ((this.negative) ? this.col.white : this.col.black);
  448. c.fill();
  449. }
  450. },
  451. altlabeltext:true,
  452. fullsky:true,
  453. atmos: false
  454. },
  455. 'planechart':{
  456. title: 'Planechart projection',
  457. radec2xy: function(ra,dec){
  458. ra = inrangeAz(ra);
  459. var normra = (ra+this.d2r*this.az_off)%(2*Math.PI)-Math.PI;
  460. var x = -(normra/(2*Math.PI))*this.tall*2 + this.wide/2;
  461. var y = -(dec/Math.PI)*this.tall+ this.tall/2;
  462. var coords = this.coord2horizon(ra, dec);
  463. return {x:x,y:y,el:coords[0]*this.r2d};
  464. },
  465. draw: function(){
  466. if(!this.transparent){
  467. this.ctx.fillStyle = (this.hasGradient()) ? "rgba(0,15,30, 1)" : ((this.negative) ? this.col.white : this.col.black);
  468. this.ctx.fillRect((this.wide/2) - (this.tall),0,this.tall*2,this.tall);
  469. this.ctx.fill();
  470. }
  471. },
  472. fullsky:true,
  473. atmos: false
  474. }
  475. };
  476. // Data for stars < mag 4.5 or that are a vertex for a constellation line - 20 kB [id, mag, right ascension, declination]
  477. // index with Hipparcos number
  478. this.stars = this.convertStarsToRadians([[677,2.1,2.097,29.09],[746,2.3,2.295,59.15],[765,3.9,2.353,-45.75],
  479. [1067,2.8,3.309,15.18],[1562,3.6,4.857,-8.82],[1599,4.2,5.018,-64.87],[1645,5.4,5.149,8.19],
  480. [2021,2.8,6.438,-77.25],[2072,3.9,6.551,-43.68],[2081,2.4,6.571,-42.31],[2484,4.4,7.886,-62.96],
  481. [2920,3.7,9.243,53.9],[3092,3.3,9.832,30.86],[3179,2.2,10.127,56.54],[3419,2,10.897,-17.99],
  482. [3760,5.9,12.073,7.3],[3821,3.5,12.276,57.82],[3881,4.5,12.454,41.08],[4427,2.1,14.177,60.72],
  483. [4436,3.9,14.188,38.5],[4577,4.3,14.652,-29.36],[4889,5.5,15.705,31.8],[4906,4.3,15.736,7.89],
  484. [5165,3.3,16.521,-46.72],[5348,3.9,17.096,-55.25],[5364,3.5,17.147,-10.18],[5447,2.1,17.433,35.62],
  485. [5742,4.7,18.437,24.58],[6193,4.7,19.867,27.26],[6537,3.6,21.006,-8.18],[6686,2.7,21.454,60.24],
  486. [6867,3.4,22.091,-43.32],[7007,4.8,22.546,6.14],[7083,3.9,22.813,-49.07],[7097,3.6,22.871,15.35],
  487. [7588,0.5,24.429,-57.24],[7607,3.6,24.498,48.63],[7884,4.5,25.358,5.49],[8102,3.5,26.017,-15.94],
  488. [8198,4.3,26.348,9.16],[8645,3.7,27.865,-10.34],[8796,3.4,28.27,29.58],[8832,3.9,28.383,19.29],
  489. [8833,4.6,28.389,3.19],[8837,4.4,28.411,-46.3],[8886,3.4,28.599,63.67],[8903,2.6,28.66,20.81],
  490. [9007,3.7,28.989,-51.61],[9236,2.9,29.692,-61.57],[9347,4,30.001,-21.08],[9487,3.8,30.512,2.76],
  491. [9598,4,30.859,72.42],[9640,2.1,30.975,42.33],[9884,2,31.793,23.46],[10064,3,32.386,34.99],
  492. [10324,4.4,33.25,8.85],[10559,5.3,33.985,33.36],[10602,3.6,34.127,-51.51],[10826,6.5,34.837,-2.98],
  493. [11001,4.1,35.437,-68.66],[11345,4.9,36.488,-12.29],[11407,4.2,36.746,-47.7],[11484,4.3,37.04,8.46],
  494. [11767,2,37.955,89.26],[11783,4.7,38.022,-15.24],[12093,4.9,38.969,5.59],[12387,4.1,39.871,0.33],
  495. [12390,4.8,39.891,-11.87],[12394,4.1,39.897,-68.27],[12413,4.7,39.95,-42.89],[12484,5.2,40.165,-54.55],
  496. [12486,4.1,40.167,-39.86],[12706,3.5,40.825,3.24],[12770,4.2,41.031,-13.86],[12828,4.3,41.236,10.11],
  497. [12843,4.5,41.276,-18.57],[13147,4.5,42.273,-32.41],[13209,3.6,42.496,27.26],[13254,4.2,42.646,38.32],
  498. [13268,3.8,42.674,55.9],[13531,3.9,43.564,52.76],[13701,3.9,44.107,-8.9],[13847,2.9,44.565,-40.3],
  499. [13954,4.7,44.929,8.91],[14135,2.5,45.57,4.09],[14146,4.1,45.598,-23.62],[14240,5.1,45.903,-59.74],
  500. [14328,2.9,46.199,53.51],[14354,3.3,46.294,38.84],[14576,2.1,47.042,40.96],[14668,3.8,47.374,44.86],
  501. [14879,3.8,48.019,-28.99],[15197,4.8,48.958,-8.82],[15474,3.7,49.879,-21.76],[15510,4.3,49.982,-43.07],
  502. [15863,1.8,51.081,49.86],[15900,3.6,51.203,9.03],[16083,3.7,51.792,9.73],[16228,4.2,52.267,59.94],
  503. [16537,3.7,53.233,-9.46],[16611,4.3,53.447,-21.63],[17358,3,55.731,47.79],[17378,3.5,55.812,-9.76],
  504. [17440,3.8,56.05,-64.81],[17448,3.8,56.08,32.29],[17499,3.7,56.219,24.11],[17529,3.8,56.298,42.58],
  505. [17573,3.9,56.457,24.37],[17651,4.2,56.712,-23.25],[17678,3.3,56.81,-74.24],[17702,2.9,56.871,24.11],
  506. [17797,4.3,57.149,-37.62],[17847,3.6,57.291,24.05],[17874,4.2,57.364,-36.2],[17959,4.6,57.59,71.33],
  507. [18246,2.8,58.533,31.88],[18505,5,59.356,63.07],[18532,2.9,59.463,40.01],[18543,3,59.507,-13.51],
  508. [18597,4.6,59.686,-61.4],[18614,4,59.741,35.79],[18724,3.4,60.17,12.49],[18907,3.9,60.789,5.99],
  509. [19343,4,62.165,47.71],[19747,3.9,63.5,-42.29],[19780,3.3,63.606,-62.47],[19893,4.3,64.007,-51.49],
  510. [19921,4.4,64.121,-59.3],[20042,3.5,64.474,-33.8],[20205,3.6,64.948,15.63],[20455,3.8,65.734,17.54],
  511. [20535,4,66.009,-34.02],[20648,4.3,66.372,17.93],[20885,3.8,67.144,15.96],[20889,3.5,67.154,19.18],
  512. [20894,3.4,67.166,15.87],[21060,5.1,67.709,-44.95],[21281,3.3,68.499,-55.04],[21393,3.8,68.888,-30.56],
  513. [21421,0.9,68.98,16.51],[21444,3.9,69.08,-3.35],[21594,3.9,69.545,-14.3],[21770,4.4,70.14,-41.86],
  514. [21861,5,70.515,-37.14],[21881,4.3,70.561,22.96],[21949,5.5,70.767,-70.93],[22109,4,71.376,-3.25],
  515. [22449,3.2,72.46,6.96],[22509,4.3,72.653,8.9],[22549,3.7,72.802,5.61],[22701,4.4,73.224,-5.45],
  516. [22730,5.3,73.345,2.51],[22783,4.3,73.513,66.34],[22797,3.7,73.563,2.44],[22845,4.6,73.724,10.15],
  517. [23015,2.7,74.248,33.17],[23123,4.5,74.637,1.71],[23416,3,75.492,43.82],[23453,3.7,75.62,41.08],
  518. [23685,3.2,76.365,-22.37],[23767,3.2,76.629,41.23],[23875,2.8,76.962,-5.09],[23972,4.3,77.287,-8.75],
  519. [24244,4.5,78.075,-11.87],[24305,3.3,78.233,-16.21],[24327,4.4,78.308,-12.94],[24436,0.2,78.634,-8.2],
  520. [24608,0.1,79.172,46],[24674,3.6,79.402,-6.84],[24845,4.3,79.894,-13.18],[24873,5.3,79.996,-12.32],
  521. [25110,5.1,80.64,79.23],[120412],[25281,3.4,81.119,-2.4],[25336,1.6,81.283,6.35],
  522. [25428,1.6,81.573,28.61],[25606,2.8,82.061,-20.76],[25859,3.9,82.803,-35.47],[25918,5.2,82.971,-76.34],
  523. [25930,2.3,83.002,-0.3],[25985,2.6,83.183,-17.82],[26069,3.8,83.406,-62.49],[26207,3.4,83.784,9.93],
  524. [26241,2.8,83.858,-5.91],[26311,1.7,84.053,-1.2],[26451,3,84.411,21.14],[26549,3.8,84.687,-2.6],
  525. [26634,2.6,84.912,-34.07],[26727,1.7,85.19,-1.94],[27072,3.6,86.116,-22.45],[27100,4.3,86.193,-65.74],
  526. [27288,3.5,86.739,-14.82],[27321,3.9,86.821,-51.07],[27366,2.1,86.939,-9.67],[27530,4.5,87.457,-56.17],
  527. [27628,3.1,87.74,-35.77],[27654,3.8,87.83,-20.88],[27673,4,87.872,39.15],[27890,4.7,88.525,-63.09],
  528. [27913,4.4,88.596,20.28],[27989,0.5,88.793,7.41],[28103,3.7,89.101,-14.17],[28199,4.4,89.384,-35.28],
  529. [28328,4,89.787,-42.82],[28358,3.7,89.882,54.28],[28360,1.9,89.882,44.95],[28380,2.6,89.93,37.21],
  530. [28614,4.1,90.596,9.65],[28691,5.1,90.864,19.69],[28734,4.2,91.03,23.26],[28910,4.7,91.539,-14.94],
  531. [29038,4.4,91.893,14.77],[29151,5.7,92.241,2.5],[29426,4.5,92.985,14.21],[29651,4,93.714,-6.27],
  532. [29655,3.3,93.719,22.51],[29807,4.4,94.138,-35.14],[30060,4.4,94.906,59.01],[30122,3,95.078,-30.06],
  533. [30277,3.9,95.528,-33.44],[30324,2,95.675,-17.96],[30343,2.9,95.74,22.51],[30419,4.4,95.942,4.59],
  534. [30438,-0.6,95.988,-52.7],[30867,3.8,97.204,-7.03],[30883,4.1,97.241,20.21],[31416,4.5,98.764,-22.96],
  535. [31592,4,99.171,-19.26],[31681,1.9,99.428,16.4],[31685,3.2,99.44,-43.2],[32246,3.1,100.983,25.13],
  536. [32349,-1.4,101.287,-16.72],[32362,3.4,101.322,12.9],[32607,3.2,102.048,-61.94],[32759,3.5,102.46,-32.51],
  537. [32768,2.9,102.484,-50.61],[33018,3.6,103.197,33.96],[33152,3.9,103.533,-24.18],[33160,4.1,103.547,-12.04],
  538. [33165,6.7,103.554,-23.93],[33347,4.4,104.034,-17.05],[33449,4.3,104.319,58.42],[33579,1.5,104.656,-28.97],
  539. [33856,3.5,105.43,-27.93],[33977,3,105.756,-23.83],[34045,4.1,105.94,-15.63],[34088,4,106.027,20.57],
  540. [34444,1.8,107.098,-26.39],[34481,3.8,107.187,-70.5],[34693,4.4,107.785,30.25],[34769,4.2,107.966,-0.49],
  541. [35037,4,108.703,-26.77],[35228,4,109.208,-67.96],[35264,2.7,109.286,-37.1],[35350,3.6,109.523,16.54],
  542. [35550,3.5,110.031,21.98],[35904,2.5,111.024,-29.3],[36046,3.8,111.432,27.8],[36145,4.6,111.679,49.21],
  543. [36188,2.9,111.788,8.29],[36377,3.3,112.308,-43.3],[36850,1.6,113.649,31.89],[36962,4.1,113.981,26.9],
  544. [37229,3.8,114.708,-26.8],[37279,0.4,114.825,5.22],[37447,3.9,115.312,-9.55],[37504,3.9,115.455,-72.61],
  545. [37677,3.9,115.952,-28.95],[37740,3.6,116.112,24.4],[37819,3.6,116.314,-37.97],[37826,1.2,116.329,28.03],
  546. [38146,5.3,117.257,-24.91],[38170,3.3,117.324,-24.86],[38414,3.7,118.054,-40.58],[38827,3.5,119.195,-52.98],
  547. [39429,2.2,120.896,-40],[39757,2.8,121.886,-24.3],[39794,4.3,121.982,-68.62],[39863,4.4,122.149,-2.98],
  548. [39953,1.8,122.383,-47.34],[40526,3.5,124.129,9.19],[40702,4,124.631,-76.92],[40843,5.1,125.016,27.22],
  549. [41037,1.9,125.628,-59.51],[41075,4.3,125.709,43.19],[41307,3.9,126.415,-3.91],[41312,3.8,126.434,-66.14],
  550. [41704,3.4,127.566,60.72],[42313,4.1,129.414,5.7],[42402,4.5,129.689,3.34],[42515,4,130.026,-35.31],
  551. [42536,3.6,130.073,-52.92],[42568,4.3,130.154,-59.76],[42570,3.8,130.157,-46.65],[42799,4.3,130.806,3.4],
  552. [42806,4.7,130.821,21.47],[42828,3.7,130.898,-33.19],[42911,3.9,131.171,18.15],[42913,1.9,131.176,-54.71],
  553. [43023,3.9,131.507,-46.04],[43103,4,131.674,28.76],[43109,3.4,131.694,6.42],[43234,4.3,132.108,5.84],
  554. [43409,4,132.633,-27.71],[43783,3.8,133.762,-60.64],[43813,3.1,133.848,5.95],[44066,4.3,134.622,11.86],
  555. [44127,3.1,134.802,48.04],[44248,4,135.16,41.78],[44382,4,135.612,-66.4],[44471,3.6,135.906,47.16],
  556. [44511,3.8,136.039,-47.1],[44700,4.6,136.632,38.45],[44816,2.2,136.999,-43.43],[45080,3.4,137.742,-58.97],
  557. [45101,4,137.82,-62.32],[45238,1.7,138.3,-69.72],[45336,3.9,138.591,2.31],[45556,2.2,139.273,-59.28],
  558. [45688,3.8,139.711,36.8],[45860,3.1,140.264,34.39],[45941,2.5,140.528,-55.01],[46390,2,141.897,-8.66],
  559. [46509,4.6,142.287,-2.77],[46651,3.6,142.675,-40.47],[46701,3.2,142.805,-57.03],[46733,3.6,142.882,63.06],
  560. [46776,4.5,142.996,-1.18],[46853,3.2,143.214,51.68],[46952,4.5,143.556,36.4],[47431,3.9,144.964,-1.14],
  561. [47508,3.5,145.288,9.89],[47854,3.7,146.312,-62.51],[47908,3,146.463,23.77],[48002,2.9,146.776,-65.07],
  562. [48319,3.8,147.747,59.04],[48356,4.1,147.87,-14.85],[48402,4.5,148.026,54.06],[48455,3.9,148.191,26.01],
  563. [48774,3.5,149.216,-54.57],[48926,5.2,149.718,-35.89],[49583,3.5,151.833,16.76],[49593,4.5,151.857,35.24],
  564. [49641,4.5,151.985,-0.37],[49669,1.4,152.093,11.97],[49841,3.6,152.647,-12.35],[50099,3.3,153.434,-70.04],
  565. [50191,3.9,153.684,-42.12],[50335,3.4,154.173,23.42],[50371,3.4,154.271,-61.33],[50372,3.5,154.274,42.91],
  566. [50583,2,154.993,19.84],[50801,3.1,155.582,41.5],[50954,4,156.099,-74.03],[51069,3.8,156.523,-16.84],
  567. [51172,4.3,156.788,-31.07],[51232,3.8,156.97,-58.74],[51233,4.2,156.971,36.71],[51437,5.1,157.573,-0.64],
  568. [51576,3.3,158.006,-61.69],[51624,3.8,158.203,9.31],[51839,4.1,158.867,-78.61],[51986,3.8,159.326,-48.23],
  569. [52419,2.7,160.739,-64.39],[52468,4.6,160.885,-60.57],[52727,2.7,161.692,-49.42],[52943,3.1,162.406,-16.19],
  570. [53229,3.8,163.328,34.21],[53253,3.8,163.374,-58.85],[53740,4.1,164.944,-18.3],[53910,2.3,165.46,56.38],
  571. [54061,1.8,165.932,61.75],[54463,3.9,167.147,-58.98],[54539,3,167.416,44.5],[54682,4.5,167.915,-22.83],
  572. [54872,2.6,168.527,20.52],[54879,3.3,168.56,15.43],[55203,3.8],[55219,3.5,169.62,33.09],
  573. [55282,3.6,169.835,-14.78],[55425,3.9,170.252,-54.49],[55687,4.8,171.152,-10.86],[55705,4.1,171.221,-17.68],
  574. [56211,3.8,172.851,69.33],[56343,3.5,173.25,-31.86],[56480,4.6,173.69,-54.26],[56561,3.1,173.945,-63.02],
  575. [56633,4.7,174.17,-9.8],[57283,4.7,176.191,-18.35],[57363,3.6,176.402,-66.73],[57380,4,176.465,6.53],
  576. [57399,3.7,176.513,47.78],[57632,2.1,177.265,14.57],[57757,3.6,177.674,1.76],[57936,4.3,178.227,-33.91],
  577. [58001,2.4,178.458,53.69],[58188,5.2,179.004,-17.15],[59196,2.6,182.09,-50.72],[59199,4,182.103,-24.73],
  578. [59316,3,182.531,-22.62],[59449,4,182.913,-52.37],[59747,2.8,183.786,-58.75],[59774,3.3,183.857,57.03],
  579. [59803,2.6,183.952,-17.54],[60000,4.2,184.587,-79.31],[60030,5.9,184.668,-0.79],[60129,3.9,184.976,-0.67],
  580. [60260,3.6,185.34,-60.4],[60718,0.8,186.65,-63.1],[60742,4.3,186.734,28.27],[60823,3.9,187.01,-50.23],
  581. [60965,2.9,187.466,-16.52],[61084,1.6,187.791,-57.11],[61174,4.3,188.018,-16.2],[61199,3.8,188.117,-72.13],
  582. [61281,3.9,188.371,69.79],[61317,4.2,188.436,41.36],[61359,2.6,188.597,-23.4],[61585,2.7,189.296,-69.14],
  583. [61622,3.9,189.426,-48.54],[61932,2.2,190.379,-48.96],[61941,2.7,190.415,-1.45],[62322,3,191.57,-68.11],
  584. [62434,1.3,191.93,-59.69],[62956,1.8,193.507,55.96],[63090,3.4,193.901,3.4],[63125,2.9,194.007,38.32],
  585. [63608,2.9,195.544,10.96],[63613,3.6,195.568,-71.55],[64166,4.9,197.264,-23.12],[64241,4.3,197.497,17.53],
  586. [64394,4.2,197.968,27.88],[64962,3,199.73,-23.17],[65109,2.8,200.149,-36.71],[65378,2.2,200.981,54.93],
  587. [65474,1,201.298,-11.16],[65477,4,201.306,54.99],[65936,3.9,202.761,-39.41],[66249,3.4,203.673,-0.6],
  588. [66657,2.3,204.972,-53.47],[67301,1.9,206.885,49.31],[67459,4,207.369,15.8],[67464,3.4,207.376,-41.69],
  589. [67472,3.5,207.404,-42.47],[67927,2.7,208.671,18.4],[68002,2.5,208.885,-47.29],[68245,3.8,209.568,-42.1],
  590. [68282,3.9,209.67,-44.8],[68520,4.2,210.412,1.54],[68702,0.6,210.956,-60.37],[68756,3.7,211.097,64.38],
  591. [68895,3.3,211.593,-26.68],[68933,2.1,211.671,-36.37],[69427,4.2,213.224,-10.27],[69673,-0.1,213.915,19.18],
  592. [69701,4.1,214.004,-6],[69996,3.5,214.851,-46.06],[70576,4.3,216.545,-45.38],[70638,4.3,216.73,-83.67],
  593. [71053,3.6,217.957,30.37],[71075,3,218.019,38.31],[71352,2.3,218.877,-42.16],[71536,4,219.472,-49.43],
  594. [71681,1.4,219.896,-60.84],[71683,-0,219.902,-60.83],[71795,3.8,220.287,13.73],[71860,2.3,220.482,-47.39],
  595. [71908,3.2,220.627,-64.98],[71957,3.9,220.765,-5.66],[72105,2.4,221.247,27.07],[72220,3.7,221.562,1.89],
  596. [72370,3.8,221.965,-79.04],[72607,2.1,222.676,74.16],[72622,2.8,222.72,-16.04],[73273,2.7,224.633,-43.13],
  597. [73334,3.1,224.79,-42.1],[73555,3.5,225.487,40.39],[73714,3.3,226.018,-25.28],[73807,3.9,226.28,-47.05],
  598. [74376,3.9,227.984,-48.74],[74395,3.4,228.071,-52.1],[74666,3.5,228.876,33.31],[74785,2.6,229.252,-9.38],
  599. [74824,4.1,229.379,-58.8],[74946,2.9,229.727,-68.68],[75097,3,230.182,71.83],[75141,3.2,230.343,-40.65],
  600. [75177,3.6,230.452,-36.26],[75264,3.4,230.67,-44.69],[75323,4.5,230.844,-59.32],[75458,3.3,231.232,58.97],
  601. [75695,3.7,231.957,29.11],[76127,4.1,233.232,31.36],[76267,2.2,233.672,26.71],[76276,3.8,233.701,10.54],
  602. [76297,2.8,233.785,-41.17],[76333,3.9,233.882,-14.79],[76470,3.6,234.256,-28.14],[76552,4.3,234.513,-42.57],
  603. [76600,3.7,234.664,-29.78],[76952,3.8,235.686,26.3],[77055,4.3,236.015,77.79],[77070,2.6,236.067,6.43],
  604. [77233,3.6,236.547,15.42],[77450,4.1,237.185,18.14],[77512,4.6,237.399,26.07],[77516,3.5,237.405,-3.43],
  605. [77622,3.7,237.704,4.48],[77634,4,237.74,-33.63],[77760,4.6,238.169,42.45],[77853,4.1,238.456,-16.73],
  606. [77952,2.8,238.786,-63.43],[78072,3.9,239.113,15.66],[78104,3.9,239.221,-29.21],[78159,4.1,239.397,26.88],
  607. [78265,2.9,239.713,-26.11],[78384,3.4,240.031,-38.4],[78401,2.3,240.083,-22.62],[78493,5,240.361,29.85],
  608. [78527,4,240.472,58.57],[78639,4.7,240.804,-49.23],[78820,2.6,241.359,-19.81],[78933,3.9,241.702,-20.67],
  609. [78970,5.7,241.818,-36.76],[79509,5,243.37,-54.63],[79593,2.7,243.586,-3.69],[79664,3.9,243.859,-63.69],
  610. [79822,5,244.376,75.76],[79882,3.2,244.58,-4.69],[79992,3.9,244.935,46.31],[80000,4,244.96,-50.16],
  611. [80112,2.9,245.297,-25.59],[80170,3.7,245.48,19.15],[80331,2.7,245.998,61.51],[80582,4.5,246.796,-47.55],
  612. [80763,1.1,247.352,-26.43],[80816,2.8,247.555,21.49],[80883,3.8,247.728,1.98],[81065,3.9,248.363,-78.9],
  613. [81126,4.2,248.526,42.44],[81266,2.8,248.971,-28.22],[81377,2.5,249.29,-10.57],[81693,2.8,250.322,31.6],
  614. [81833,3.5,250.724,38.92],[81852,4.2,250.769,-77.52],[82080,4.2,251.493,82.04],[82273,1.9,252.166,-69.03],
  615. [82363,3.8,252.446,-59.04],[82396,2.3,252.541,-34.29],[82514,3,252.968,-38.05],[82545,3.6,253.084,-38.02],
  616. [82671,4.7,253.499,-42.36],[82729,3.6,253.646,-42.36],[83000,3.2,254.417,9.38],[83081,3.1,254.655,-55.99],
  617. [83207,3.9,255.072,30.93],[83895,3.2,257.197,65.71],[84012,2.4,257.595,-15.72],[84143,3.3,258.038,-43.24],
  618. [84345,2.8,258.662,14.39],[84379,3.1,258.758,24.84],[84380,3.2,258.762,36.81],[84606,4.6,259.418,37.29],
  619. [84880,4.3,260.207,-12.85],[84970,3.3,260.502,-25],[85112,4.2,260.921,37.15],[85258,2.8,261.325,-55.53],
  620. [85267,3.3,261.349,-56.38],[85670,2.8,262.608,52.3],[85693,4.4,262.685,26.11],[85696,2.7,262.691,-37.3],
  621. [85727,3.6,262.775,-60.68],[85755,4.8,262.854,-23.96],[85792,2.8,262.96,-49.88],[85822,4.3,263.054,86.59],
  622. [85829,4.9,263.067,55.17],[85927,1.6,263.402,-37.1],[86032,2.1,263.734,12.56],[86228,1.9,264.33,-43],
  623. [86263,3.5,264.397,-15.4],[86414,3.8,264.866,46.01],[86565,4.2,265.354,-12.88],[86670,2.4,265.622,-39.03],
  624. [86742,2.8,265.868,4.57],[86929,3.6,266.433,-64.72],[86974,3.4,266.615,27.72],[87072,4.5,266.89,-27.83],
  625. [87073,3,266.896,-40.13],[87108,3.8,266.973,2.71],[87261,3.2,267.465,-37.04],[87585,3.7,268.382,56.87],
  626. [87808,3.9,269.063,37.25],[87833,2.2,269.152,51.49],[87933,3.7,269.441,29.25],[88048,3.3,269.757,-9.77],
  627. [88192,3.9,270.161,2.93],[88635,3,271.452,-30.42],[88714,3.6,271.658,-50.09],[88771,3.7,271.837,9.56],
  628. [88794,3.8,271.886,28.76],[88866,4.3,272.145,-63.67],[89341,3.8,273.441,-21.06],[89642,3.1,274.407,-36.76],
  629. [89931,2.7,275.249,-29.83],[89937,3.5,275.264,72.73],[89962,3.2,275.328,-2.9],[90098,4.3,275.807,-61.49],
  630. [90139,3.9,275.925,21.77],[90185,1.8,276.043,-34.38],[90422,3.5,276.743,-45.97],[90496,2.8,276.993,-25.42],
  631. [90568,4.1,277.208,-49.07],[90595,4.7,277.299,-14.57],[90887,5.2,278.089,-39.7],[91117,3.9,278.802,-8.24],
  632. [91262,0,279.235,38.78],[91792,4,280.759,-71.43],[91875,5.1,280.946,-38.32],[91971,4.3,281.193,37.61],
  633. [92041,3.2,281.414,-26.99],[92175,4.2,281.794,-4.75],[92202,5.4,281.871,-5.71],[92420,3.5,282.52,33.36],
  634. [92609,4.2,283.054,-62.19],[92791,4.2,283.626,36.9],[92814,5.1,283.68,-15.6],[92855,2,283.816,-26.3],
  635. [92946,4.6,284.055,4.2],[92953,5.3,284.071,-42.71],[92989,5.4,284.169,-37.34],[93015,4.4,284.238,-67.23],
  636. [93085,3.5,284.433,-21.11],[93174,4.8,284.681,-37.11],[93194,3.3,284.736,32.69],[93244,4,284.906,15.07],
  637. [93506,2.6,285.653,-29.88],[93542,4.7,285.779,-42.1],[93683,3.8,286.171,-21.74],[93747,3,286.353,13.86],
  638. [93805,3.4,286.562,-4.88],[93825,4.2,286.605,-37.06],[93864,3.3,286.735,-27.67],[94005,4.6,287.087,-40.5],
  639. [94114,4.1,287.368,-37.9],[94141,2.9,287.441,-21.02],[94160,4.1,287.507,-39.34],[94376,3.1,288.139,67.66],
  640. [94779,3.8,289.276,53.37],[94820,4.9,289.409,-18.95],[95168,3.9,290.418,-17.85],[95241,4,290.66,-44.46],
  641. [95294,4.3,290.805,-44.8],[95347,4,290.972,-40.62],[95501,3.4,291.375,3.11],[95771,4.4,292.176,24.66],
  642. [95853,3.8,292.426,51.73],[95947,3,292.68,27.96],[96406,5.6,294.007,-24.72],[96757,4.4,295.024,18.01],
  643. [96837,4.4,295.262,17.48],[97165,2.9,296.244,45.13],[97278,2.7,296.565,10.61],[97365,3.7,296.847,18.53],
  644. [97433,3.8,297.043,70.27],[97649,0.8,297.696,8.87],[97804,3.9,298.118,1.01],[98032,4.1,298.815,-41.87],
  645. [98036,3.7,298.828,6.41],[98110,3.9,299.077,35.08],[98337,3.5,299.689,19.49],[98412,4.4,299.934,-35.28],
  646. [98495,4,300.148,-72.91],[98543,4.7,300.275,27.75],[98688,4.4,300.665,-27.71],[98920,5.1,301.29,19.99],
  647. [99240,3.5,302.182,-66.18],[99473,3.2,302.826,-0.82],[99675,3.8,303.408,46.74],[99848,4,303.868,47.71],
  648. [100064,3.6,304.514,-12.54],[100345,3,305.253,-14.78],[100453,2.2,305.557,40.26],[100751,1.9,306.412,-56.74],
  649. [101421,4,308.303,11.3],[101769,3.6,309.387,14.6],[101772,3.1,309.392,-47.29],[101958,3.8,309.91,15.91],
  650. [102098,1.3,310.358,45.28],[102281,4.4,310.865,15.07],[102395,3.4,311.24,-66.2],[102422,3.4,311.322,61.84],
  651. [102485,4.1,311.524,-25.27],[102488,2.5,311.553,33.97],[102532,4.3,311.665,16.12],[102618,3.8,311.919,-9.5],
  652. [102831,4.9,312.492,-33.78],[102978,4.1,312.955,-26.92],[103227,3.7,313.703,-58.45],[103413,3.9,314.293,41.17],
  653. [103738,4.7,315.323,-32.26],[104060,3.7,316.233,43.93],[104139,4.1,316.487,-17.23],[104521,4.7,317.585,10.13],
  654. [104732,3.2,318.234,30.23],[104858,4.5,318.62,10.01],[104887,3.7,318.698,38.05],[104987,3.9,318.956,5.25],
  655. [105140,4.7,319.485,-32.17],[105199,2.5,319.645,62.59],[105319,4.4,319.967,-53.45],[105515,4.3,320.562,-16.83],
  656. [105570,5.2,320.723,6.81],[105858,4.2,321.611,-65.37],[105881,3.8,321.667,-22.41],[106032,3.2,322.165,70.56],
  657. [106278,2.9,322.89,-5.57],[106481,4,323.495,45.59],[106985,3.7,325.023,-16.66],[107089,3.7,325.369,-77.39],
  658. [107310,4.5,326.036,28.74],[107315,2.4,326.046,9.88],[107354,4.1,326.161,25.65],[107556,2.9,326.76,-16.13],
  659. [107608,5,326.934,-30.9],[108085,3,328.482,-37.36],[108661,5.4,330.209,-28.45],[109074,3,331.446,-0.32],
  660. [109111,4.5,331.529,-39.54],[109139,4.3,331.609,-13.87],[109176,3.8,331.753,25.35],[109268,1.7,332.058,-46.96],
  661. [109352,5.6,332.307,33.17],[109422,4.9,332.537,-32.55],[109427,3.5,332.55,6.2],[109492,3.4,332.714,58.2],
  662. [109937,4.1,333.992,37.75],[110003,4.2,334.208,-7.78],[110130,2.9,334.625,-60.26],[110395,3.9,335.414,-1.39],
  663. [110538,4.4,335.89,52.23],[110609,4.5,336.129,49.48],[110960,3.6,337.208,-0.02],[110997,4,337.317,-43.5],
  664. [111022,4.3,337.383,47.71],[111104,4.5,337.622,43.12],[111123,4.8,337.662,-10.68],[111169,3.8,337.823,50.28],
  665. [111188,4.3,337.876,-32.35],[111497,4,338.839,-0.12],[111954,4.2,340.164,-27.04],[112029,3.4,340.366,10.83],
  666. [112122,2.1,340.667,-46.88],[112158,2.9,340.751,30.22],[112405,4.1,341.515,-81.38],[112440,4,341.633,23.57],
  667. [112447,4.2,341.673,12.17],[112623,3.5,342.139,-51.32],[112716,4,342.398,-13.59],[112724,3.5,342.42,66.2],
  668. [112748,3.5,342.501,24.6],[112961,3.7,343.154,-7.58],[113136,3.3,343.663,-15.82],[113246,4.2,343.987,-32.54],
  669. [113368,1.2,344.413,-29.62],[113638,4.1,345.22,-52.75],[113726,3.6,345.48,42.33],[113881,2.4,345.944,28.08],
  670. [113963,2.5,346.19,15.21],[114131,4.3,346.72,-43.52],[114341,3.7,347.362,-21.17],[114421,3.9,347.59,-45.25],
  671. [114855,4.2,348.973,-9.09],[114971,3.7,349.291,3.28],[114996,4,349.357,-58.24],[115102,4.4,349.706,-32.53],
  672. [115438,4,350.743,-20.1],[115738,5,351.733,1.26],[115830,4.3,351.992,6.38],[116231,4.4,353.243,-37.82],
  673. [116584,3.8,354.391,46.46],[116727,3.2,354.837,77.63],[116771,4.1,354.988,5.63],[116928,4.5,355.512,1.78],
  674. [118268,4,359.828,6.86]]);
  675. // Data for star names to display (if showstarlabels is set to true) - indexed by Hipparcos number
  676. this.starnames = {};
  677. // Identify the default base directory
  678. this.dir = $('script[src*=virtualsky]').attr('src').match(/^.*\//); // the JS file path
  679. this.dir = this.dir && this.dir[0] || ""; // set dir to match or ""
  680. // Define extra files (JSON/JS)
  681. this.file = {
  682. stars: this.dir+"stars.json", // Data for faint stars - 54 kB
  683. lines: this.dir+"lines_latin.json", // Data for constellation lines - 12 kB
  684. boundaries: this.dir+"boundaries.json", // Data for constellation boundaries - 20 kB
  685. showers: this.dir+"showers.json", // Data for meteor showers - 4 kB
  686. galaxy: this.dir+"galaxy.json", // Data for milky way - 12 kB
  687. planets: this.dir+"virtualsky-planets.js" // Plugin for planet ephemeris - 12kB
  688. }
  689. this.hipparcos = {}; // Define our star catalogue
  690. this.updateClock(new Date()); // Define the 'current' time
  691. this.fullsky = false; // Are we showing the entire sky?
  692. // Define the colours that we will use
  693. this.colours = {
  694. 'normal' : {
  695. 'txt' : "rgb(255,255,255)",
  696. 'black':"rgb(0,0,0)",
  697. 'white':"rgb(255,255,255)",
  698. 'grey':"rgb(100,100,100)",
  699. 'stars':'rgb(255,255,255)',
  700. 'sun':'rgb(255,215,0)',
  701. 'moon':'rgb(150,150,150)',
  702. 'cardinal':'rgba(163,228,255, 1)',
  703. 'constellation':"rgba(180,180,255,0.8)",
  704. 'constellationboundary':"rgba(255,255,100,0.6)",
  705. 'showers':"rgba(100,255,100,0.8)",
  706. 'galaxy':"rgba(100,200,255,0.5)",
  707. 'az':"rgba(100,100,255,0.4)",
  708. 'eq':"rgba(255,100,100,0.4)",
  709. 'ec':'rgba(255,0,0,0.4)',
  710. 'gal':'rgba(100,200,255,0.4)',
  711. 'meridian':'rgba(25,255,0,0.4)',
  712. 'pointers':'rgb(200,200,200)'
  713. },
  714. 'negative':{
  715. 'txt' : "rgb(0,0,0)",
  716. 'black':"rgb(0,0,0)",
  717. 'white':"rgb(255,255,255)",
  718. 'grey':"rgb(100,100,100)",
  719. 'stars':'rgb(0,0,0)',
  720. 'sun':'rgb(0,0,0)',
  721. 'moon':'rgb(0,0,0)',
  722. 'cardinal':'rgba(0,0,0,1)',
  723. 'constellation':"rgba(0,0,0,0.8)",
  724. 'constellationboundary':"rgba(0,0,0,0.6)",
  725. "showers":"rgba(0,0,0,0.8)",
  726. 'galaxy':"rgba(0,0,0,0.5)",
  727. 'az':"rgba(0,0,255,0.6)",
  728. 'eq':"rgba(255,100,100,0.8)",
  729. 'ec':'rgba(255,0,0,0.6)',
  730. 'gal':'rgba(100,200,255,0.8)',
  731. 'meridian':'rgba(0,255,0,0.6)'
  732. }
  733. };
  734. // Keep a copy of the inputs
  735. this.input = input;
  736. // Overwrite our defaults with input values
  737. this.init(input);
  738. // Country codes at http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
  739. this.language = (typeof this.q.lang==="string") ? this.q.lang : (typeof this.setlang==="string" ? this.setlang : (navigator) ? (navigator.userLanguage||navigator.systemLanguage||navigator.language||browser.language) : "");
  740. var fromqs = (typeof this.q.lang==="string" || typeof this.setlang==="string");
  741. this.langs = {
  742. 'ar': { "language": {"name": "&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;","alignment": "right" } },
  743. 'cs': { "language": {"name": "Čeština","alignment": "left" } },
  744. 'en': { "language": {"name": "English","alignment": "left" } },
  745. 'es': { "language": {"name": "Espa&#241;ol","alignment": "left" } },
  746. 'fr': { "language": {"name": "Fran&#231;ais","alignment": "left" } },
  747. 'it': { "language": {"name": "Italiano","alignment": "left" } },
  748. 'pt': { "language": {"name": "Portugu&#234;s","alignment": "left" } },
  749. }; // The contents of the language will be loaded from the JSON language file
  750. this.lang = this.langs['en']; // default
  751. if(typeof this.polartype=="undefined") this.selectProjection('polar'); // Set the default projection
  752. // Update the colours
  753. this.updateColours();
  754. // Load the language file
  755. this.loadLanguage(this.language,'',fromqs);
  756. // Define some VirtualSky styles
  757. var v,a,b,r,s,p,k,c;
  758. v = '.virtualsky';
  759. a = '#f0f0f0';
  760. b = '#fcfcfc';
  761. k = 'background';
  762. c = k+'-color';
  763. p = 'padding';
  764. bs = 'box-shadow:0px 0px 20px rgba(255,255,255,0.5);';
  765. function br(i){ return 'border-radius:'+i+';-moz-border-radius:'+i+';-webkit-border-radius:'+i+';';}
  766. r = br('0.5em');
  767. s = br('3px');
  768. $('<style type="text/css">'+
  769. v+'_help { '+p+':10px;'+c+':white;'+r+'} '+
  770. v+'_help ul { list-style:none;margin:0px;'+p+':0px; } '+
  771. v+'_infobox { '+c+':'+a+';color:black;'+p+':5px;'+r+bs+'} '+
  772. v+'_infobox img {} '+
  773. v+'_infocredit {color:white;float:left;font-size:0.8em;'+p+':5px;position:absolute;} '+
  774. v+'form { position:absolute;z-index:20;display:block;overflow:hidden;'+c+':#ddd;'+p+':10px;'+bs+r+' } '+
  775. v+'_dismiss { float:right;'+p+': 0 5px 0 5px;margin:0px;font-weight:bold;cursor:pointer;color:black;margin-right:-5px;margin-top:-5px; } '+
  776. v+'form input,'+v+'form .divider { display:inline-block;font-size:1em;text-align:center;margin-right:2px; } '+v+'form .divider { margin-top: 5px; '+p+': 2px;} '+
  777. v+'_help_key:active{ '+k+':#e9e9e9; } '+
  778. v+'_help_key:hover{ border-color: #b0b0b0; } '+
  779. v+'_help_key { cursor:pointer;display:inline-block;text-align:center;'+
  780. k+':'+a+';'+k+':-moz-linear-gradient(top,'+a+','+b+');'+
  781. k+':-webkit-gradient(linear,center top,center bottom,from('+a+'),to('+b+'));'+
  782. s+'-webkit-'+k+'-clip:'+p+'-box;-moz-'+k+'-clip:'+p+';'+k+'-clip:'+
  783. p+'-box;color:#303030;border:1px solid #e0e0e0;border-bottom-width:2px;white-space:nowrap;font-family:monospace'+
  784. ';'+p+':1px 6px;font-size:1.1em;}</style>').appendTo("head");
  785. this.pointers = []; // Define an empty list of pointers/markers
  786. // Internal variables
  787. this.dragging = false;
  788. this.x = "";
  789. this.y = "";
  790. this.theta = 0;
  791. this.skygrad;
  792. this.infobox = "virtualsky_infobox";
  793. this.container = '';
  794. this.times = this.astronomicalTimes();
  795. if(this.id) this.createSky();
  796. // Find out where the Sun and Moon are
  797. p = this.moonPos(this.times.JD);
  798. this.moon = p.moon;
  799. this.sun = p.sun;
  800. if(this.islive) interval = window.setInterval(function(sky){ sky.setClock('now'); },1000,this);
  801. return this;
  802. }
  803. VirtualSky.prototype.init = function(d){
  804. if(!d) return this;
  805. var q = location.search;
  806. if(q && q != '#'){
  807. var bits = q.replace(/^\?|\&$/g,'').split('&'); // remove the leading ? and trailing &
  808. var key,val;
  809. for(var i = 0; i < bits.length ; i++){
  810. key = bits[i].split('=')[0], val = bits[i].split('=')[1];
  811. // convert floats
  812. if(/^-?[0-9.]+$/.test(val)) val = parseFloat(val);
  813. if(val == "true") val = true;
  814. if(val == "false") val = false;
  815. // apply only first key occurency
  816. if(d[key]===undefined) d[key] = val;
  817. }
  818. }
  819. var n = "number";
  820. var s = "string";
  821. var b = "boolean";
  822. var o = "object";
  823. var f = "function";
  824. // Overwrite defaults with variables passed to the function
  825. // directly mapped variables
  826. var pairs = {
  827. id: s,
  828. gradient: b,
  829. cardinalpoints: b,
  830. negative: b,
  831. meteorshowers: b,
  832. showstars: b,
  833. scalestars: n,
  834. showstarlabels: b,
  835. starnames: o,
  836. showplanets: b,
  837. showplanetlabels: b,
  838. showorbits: b,
  839. showgalaxy: b,
  840. showdate: b,
  841. showposition: b,
  842. keyboard: b,
  843. mouse: b,
  844. ground: b,
  845. ecliptic: b,
  846. meridian: b,
  847. magnitude: n,
  848. clock: o,
  849. background: s,
  850. color: s,
  851. fov: n,
  852. objects: s,
  853. base: s,
  854. fullscreen: b,
  855. credit: b,
  856. transparent: b,
  857. plugins: o,
  858. lang: s
  859. }
  860. for(key in pairs)
  861. if(is(d[key], pairs[key]))
  862. this[key] = d[key];
  863. // Undirectly paired values
  864. if(is(d.projection,s)) this.selectProjection(d.projection);
  865. if(is(d.constellations,b)) this.constellation.lines = d.constellations;
  866. if(is(d.constellationboundaries,b)) this.constellation.boundaries = d.constellationboundaries;
  867. if(is(d.constellationlabels,b)) this.constellation.labels = d.constellationlabels;
  868. if(is(d.gridlines_az,b)) this.grid.az = d.gridlines_az;
  869. if(is(d.gridlines_eq,b)) this.grid.eq = d.gridlines_eq;
  870. if(is(d.gridlines_gal,b)) this.grid.gal = d.gridlines_gal;
  871. if(is(d.gridstep,n)) this.grid.step = d.gridstep;
  872. if(is(d.longitude,n)) this.setLongitude(d.longitude);
  873. if(is(d.latitude,n)) this.setLatitude(d.latitude);
  874. if(is(d.clock,s)) this.updateClock(new Date(d.clock.replace(/%20/g,' ')));
  875. if(is(d.az,n)) this.az_off = (d.az%360)-180;
  876. if(is(d.ra,n)) this.setRA(d.ra);
  877. if(is(d.dec,n)) this.setDec(d.dec);
  878. if(is(d.planets,s)) this.file.planets = d.planets;
  879. if(is(d.planets,o)) this.planets = d.planets;
  880. if(is(d.lines,s)) this.file.lines = d.lines;
  881. if(is(d.lines,o)) this.lines = d.lines;
  882. if(is(d.boundaries,s)) this.file.boundaries = d.boundaries;
  883. if(is(d.boundaries,o)) this.boundaries = d.boundaries;
  884. if(is(d.width,n)) this.wide = d.width;
  885. if(is(d.height,n)) this.tall = d.height;
  886. if(is(d.live,b)) this.islive = d.live;
  887. if(is(d.lang,s) && d.lang.length==2) this.language = d.lang;
  888. if(is(d.fontfamily,s)) this.fntfam = d.fontfamily.replace(/%20/g,' ');
  889. if(is(d.fontsize,s)) this.fntsze = d.fontsize;
  890. if(is(d.lang,s)) this.setlang = d.lang;
  891. if(is(d.callback,o)){
  892. if(is(d.callback.geo,f)) this.callback.geo = d.callback.geo;
  893. if(is(d.callback.mouseenter,f)) this.callback.mouseenter = d.callback.mouseenter;
  894. if(is(d.callback.mouseout,f)) this.callback.mouseout = d.callback.mouseout;
  895. }
  896. return this;
  897. }
  898. // Load the specified language
  899. // If it fails and this was the long variation of the language (e.g. "en-gb" or "zh-yue"), try the short version (e.g. "en" or "zh")
  900. VirtualSky.prototype.loadLanguage = function(l,fn,fromquerystring){
  901. l = l || this.language;
  902. var lang = "";
  903. if(this.langs[l]) lang = l;
  904. if(!lang){
  905. // Try loading a short version of the language code
  906. l = (l.indexOf('-') > 0 ? l.substring(0,l.indexOf('-')) : l.substring(0,2));
  907. if(fromquerystring){
  908. // If it was set in the query string we try it
  909. lang = l;
  910. }else{
  911. // If it was just from the browser settings, we'll limit to known translations
  912. if(this.langs[l]) lang = l;
  913. }
  914. }
  915. l = lang;
  916. if(!l) l = "en"; // Use English as a default if we haven't got a language here
  917. var url = this.langurl.replace('%LANG%',l);
  918. this.loadJSON(
  919. url,
  920. function(data){
  921. this.langcode = l;
  922. this.langs[l] = data;
  923. this.langs[l].loaded = true;
  924. // Update any starnames
  925. if(data.starnames) for(var n in data.starnames) this.starnames[n] = data.starnames[n];
  926. this.changeLanguage(l);
  927. if(typeof fn==="function") fn.call(this);
  928. },
  929. function(data){ },
  930. function(e){
  931. // If we tried to load the short version of the language and it failed, default to English
  932. this.loadLanguage('en',fn);
  933. }
  934. );
  935. return this;
  936. }
  937. // Change the active language
  938. VirtualSky.prototype.changeLanguage = function(code,fn){
  939. if(this.langs[code]){
  940. if(!this.langs[code].loaded) this.loadLanguage(code,fn);
  941. else {
  942. this.lang = this.langs[code];
  943. this.langcode = code;
  944. this.draw();
  945. if(typeof fn==="function") fn.call(this);
  946. }
  947. return this;
  948. }
  949. this.lang = this.langs['en'];
  950. return this;
  951. }
  952. VirtualSky.prototype.htmlDecode = function(input){
  953. if(!input) return "";
  954. var e = document.createElement('div');
  955. e.innerHTML = input;
  956. return e.childNodes[0].nodeValue;
  957. }
  958. VirtualSky.prototype.getPhrase = function(key,key2){
  959. if(key===undefined) return undefined;
  960. if(key==="constellations"){
  961. if(key2 && is(this.lang.constellations[key2],"string"))
  962. return this.htmlDecode(this.lang.constellations[key2]);
  963. }else if(key==="planets"){
  964. if(this.lang.planets && this.lang.planets[key2]) return this.htmlDecode(this.lang.planets[key2]);
  965. else return this.htmlDecode(this.lang[key2]);
  966. }else return this.htmlDecode(this.lang[key]) || this.htmlDecode(this.langs['en'][key]) || "";
  967. }
  968. VirtualSky.prototype.resize = function(w,h){
  969. if(!this.canvas) return;
  970. if(!w || !h){
  971. if(this.fullscreen){
  972. this.canvas.css({'width':0,'height':0});
  973. w = $(window).width();
  974. h = $(window).height();
  975. this.canvas.css({'width':w,'height':h});
  976. $(document).css({'width':w,'height':h});
  977. }else{
  978. // We have to zap the width of the canvas to let it take the width of the container
  979. this.canvas.css({'width':0,'height':0});
  980. w = this.container.outerWidth();
  981. h = this.container.outerHeight();
  982. this.canvas.css({'width':w,'height':h});
  983. }
  984. }
  985. if(w == this.wide && h == this.tall) return;
  986. this.setWH(w,h);
  987. this.positionCredit();
  988. this.updateSkyGradient();
  989. this.draw();
  990. }
  991. VirtualSky.prototype.setWH = function(w,h){
  992. if(!w || !h) return;
  993. this.c.width = w;
  994. this.c.height = h;
  995. this.wide = w;
  996. this.tall = h;
  997. this.changeFOV();
  998. // Bug fix for IE 8 which sets a width of zero to a div within the <canvas>
  999. if(this.ie && $.browser.version == 8) $('#'+this.idinner).find('div').css({'width':w,'height':h});
  1000. this.canvas.css({'width':w,'height':h});
  1001. }
  1002. VirtualSky.prototype.changeFOV = function(delta){
  1003. var fov = this.fov;
  1004. if(delta > 0) fov /= 1.2;
  1005. else if(delta < 0) fov *= 1.2;
  1006. return this.setFOV(fov);
  1007. }
  1008. VirtualSky.prototype.setFOV = function(fov){
  1009. if(fov > 60 || typeof fov!=="number") this.fov = 60;
  1010. else if(fov < 1) this.fov = 1;
  1011. else this.fov = fov;
  1012. this.maxangle = this.d2r*this.fov*Math.max(this.wide,this.tall)/this.tall;
  1013. this.maxangle = Math.min(this.maxangle,Math.PI/2)
  1014. return this;
  1015. }
  1016. // Some pseudo-jQuery
  1017. VirtualSky.prototype.hide = function(){ this.container.hide(); return this; }
  1018. VirtualSky.prototype.show = function(){ this.container.show(); return this; }
  1019. VirtualSky.prototype.toggle = function(){ this.container.toggle(); return this; }
  1020. // Our stars are stored in decimal degrees so we will convert them here
  1021. VirtualSky.prototype.convertStarsToRadians = function(stars){
  1022. for(var i = 0; i < stars.length; i++){
  1023. stars[i][2] *= this.d2r;
  1024. stars[i][3] *= this.d2r;
  1025. }
  1026. return stars;
  1027. }
  1028. VirtualSky.prototype.load = function(t,file,fn){
  1029. return this.loadJSON(file,function(data){
  1030. if(t=="stars"){ this.starsdeep = true; this.stars = this.stars.concat(this.convertStarsToRadians(data.stars));}
  1031. else{ this[t] = data[t]; }
  1032. this.draw();
  1033. this.trigger("loaded"+(t.charAt(0).toUpperCase() + t.slice(1)),{data:data});
  1034. },fn);
  1035. }
  1036. VirtualSky.prototype.loadJSON = function(file,callback,complete,error){
  1037. if(typeof file!=="string") return this;
  1038. var dt = file.match(/\.json$/i) ? "json" : "script";
  1039. if(dt=="script"){
  1040. // If we are loading an external script we need to make sure we initiate
  1041. // it first. To do that we will re-write the callback that was provided.
  1042. var tmp = callback;
  1043. callback = function(data){
  1044. // Initialize any plugins
  1045. for (var i = 0; i < this.plugins.length; ++i){
  1046. if(typeof this.plugins[i].init=="function") this.plugins[i].init.call(this);
  1047. }
  1048. tmp.call(this,data);
  1049. };
  1050. }
  1051. var config = {
  1052. dataType: dt,
  1053. url: this.base+file,
  1054. context: this,
  1055. success: callback,
  1056. complete: complete || function(){},
  1057. error: error || function(){}
  1058. };
  1059. if(dt=="json") config.jsonp = 'onJSONPLoad';
  1060. if(dt=="script") config.cache = true; // Use a cached version
  1061. $.ajax(config);
  1062. return this;
  1063. }
  1064. VirtualSky.prototype.createSky = function(){
  1065. this.container = $('#'+this.id);
  1066. this.container.addTouch();
  1067. this.times = this.astronomicalTimes();
  1068. if(this.fntfam) this.container.css({'font-family':this.fntfam});
  1069. if(this.fntsze) this.container.css({'font-size':this.fntsze});
  1070. if(this.container.length == 0){
  1071. // No appropriate container exists. So we'll make one.
  1072. $('body').append('<div id="'+this.id+'"></div>');
  1073. this.container = $('#'+this.id);
  1074. }
  1075. this.container.css('position','relative');
  1076. $(window).resize({me:this},function(e){ e.data.me.resize(); });
  1077. // Get the planet data
  1078. if(!this.planets) this.load('planets',this.file.planets);
  1079. // Get the constellation line data
  1080. if(!this.lines) this.load('lines',this.file.lines);
  1081. // Get the constellation line data
  1082. if(!this.boundaries) this.load('boundaries',this.file.boundaries);
  1083. // Get the meteor showers
  1084. if(!this.showers) this.load('showers',this.file.showers);
  1085. // Get the Milky Way
  1086. if(!this.galaxy) this.load('galaxy',this.file.galaxy);
  1087. // Get the faint star data
  1088. this.changeMagnitude(0);
  1089. // Add named objects to the display
  1090. if(this.objects){
  1091. // To stop lookUp being hammered, we'll only lookup a maximum of 5 objects
  1092. // If you need more objects (e.g. for the Messier catalogue) build a JSON
  1093. // file containing all the results one time only.
  1094. var ob = this.objects.split(';');
  1095. // Build the array of JSON requests
  1096. for(var o = 0; o < ob.length ; o++) ob[o] = ((ob[o].search(/\.json$/) >= 0) ? {'url':ob[o], 'src':'file', 'type':'json' } : {'url': 'http://www.strudel.org.uk/lookUP/json/?name='+ob[o],'src':'lookup','type':'jsonp'});
  1097. // Loop over the requests
  1098. var lookups = 0;
  1099. var ok = true;
  1100. for(var o = 0; o < ob.length ; o++){
  1101. if(ob[o].src == "lookup") lookups++;
  1102. if(lookups > 5) ok = false;
  1103. if(ok || ob[o].src != "lookup"){
  1104. $.ajax({ dataType: ob[o].type, url: ob[o].url, context: this, success: function(data){
  1105. // If we don't have a length property, we only have one result so make it an array
  1106. if(typeof data.length === "undefined") data = [data];
  1107. // Loop over the array of objects
  1108. for(var i = 0; i < data.length ; i++){
  1109. // The object needs an RA and Declination
  1110. if(data[i] && data[i].dec && data[i].ra){
  1111. this.addPointer({
  1112. ra: data[i].ra.decimal,
  1113. dec: data[i].dec.decimal,
  1114. label: data[i].target.name,
  1115. colour: this.col.pointers
  1116. });
  1117. }
  1118. // Update the sky with all the points we've added
  1119. this.draw();
  1120. }
  1121. }});
  1122. }
  1123. }
  1124. }
  1125. // If the Javascript function has been passed a width/height
  1126. // those take precedence over the CSS-set values
  1127. if(this.wide > 0)
  1128. this.container.css('width',this.wide);
  1129. this.wide = this.container.width();
  1130. if(this.tall > 0)
  1131. this.container.css('height',this.tall);
  1132. this.tall = this.container.height();
  1133. // Add a <canvas> to it with the original ID
  1134. this.idinner = this.id+'_inner';
  1135. this.container.html('<canvas id="'+this.idinner+'" style="display:block;"></canvas>');
  1136. this.canvas = $('#'+this.idinner);
  1137. this.c = document.getElementById(this.idinner);
  1138. // For excanvas we need to initialise the newly created <canvas>
  1139. if(this.excanvas)
  1140. this.c = G_vmlCanvasManager.initElement(this.c);
  1141. if(this.c && this.c.getContext){
  1142. this.setWH(this.wide,this.tall);
  1143. var ctx = this.ctx = this.c.getContext('2d');
  1144. ctx.clearRect(0,0,this.wide,this.tall);
  1145. ctx.beginPath();
  1146. var fs = this.fontsize();
  1147. ctx.font = fs+"px Helvetica";
  1148. ctx.fillStyle = 'rgb(0,0,0)';
  1149. ctx.lineWidth = 1.5;
  1150. var loading = 'Loading sky...';
  1151. ctx.fillText(loading,(ctx.wide-ctx.measureText(loading).width)/2,(this.tall-fs)/2)
  1152. ctx.fill();
  1153. $("#"+this.idinner).on('click',{sky:this},function(e){
  1154. var x = e.pageX - $(this).offset().left - window.scrollX;
  1155. var y = e.pageY - $(this).offset().top - window.scrollY;
  1156. matched = e.data.sky.whichPointer(x,y);
  1157. e.data.sky.toggleInfoBox(matched);
  1158. if(matched >= 0) $(e.data.sky.canvas).css({cursor:'pointer'});
  1159. }).on('mousemove',{sky:this},function(e){
  1160. var s = e.data.sky;
  1161. // We don't need scrollX/scrollY as pageX/pageY seem to include this
  1162. var x = e.pageX - $(this).offset().left;
  1163. var y = e.pageY - $(this).offset().top;
  1164. var theta,f,dr;
  1165. if(s.mouse)
  1166. $(s.canvas).css({cursor:'move'});
  1167. if(s.dragging && s.mouse){
  1168. if(s.polartype){
  1169. theta = Math.atan2(y-s.tall/2,x-s.wide/2);
  1170. if(!s.theta) s.theta = theta;
  1171. s.az_off -= (s.theta-theta)*s.r2d;
  1172. s.theta = theta;
  1173. }else if(s.projection.id=="gnomic"){
  1174. f = 0.0015*(s.fov*s.d2r);
  1175. dr = 0;
  1176. if(typeof s.x=="number")
  1177. dr = Math.min(Math.abs(s.x-x)*f/(Math.cos(s.dc_off)),Math.PI/36);
  1178. if(typeof s.y=="number")
  1179. s.dc_off -= (s.y-y)*f;
  1180. s.ra_off -= (s.x-x > 0 ? 1 : -1)*dr;
  1181. s.dc_off = inrangeEl(s.dc_off);
  1182. }else{
  1183. if(typeof s.x=="number")
  1184. s.az_off += (s.x-x)/4;
  1185. }
  1186. s.az_off = s.az_off%360;
  1187. s.x = x;
  1188. s.y = y;
  1189. s.draw();
  1190. $(s.canvas).css({cursor:'-moz-grabbing'});
  1191. }else{
  1192. matched = s.whichPointer(x,y);
  1193. s.toggleInfoBox(matched);
  1194. }
  1195. }).on('mousedown',{sky:this},function(e){
  1196. e.data.sky.dragging = true;
  1197. }).on('mouseup',{sky:this},function(e){
  1198. var s = e.data.sky;
  1199. s.dragging = false;
  1200. s.x = "";
  1201. s.y = "";
  1202. s.theta = "";
  1203. }).on('mouseout',{sky:this},function(e){
  1204. var s = e.data.sky;
  1205. s.dragging = false;
  1206. s.mouseover = false;
  1207. s.x = "";
  1208. s.y = "";
  1209. if(typeof s.callback.mouseout=="function") s.callback.mouseout.call(s);
  1210. }).on('mouseenter',{sky:this},function(e){
  1211. var s = e.data.sky;
  1212. s.mouseover = true;
  1213. if(typeof s.callback.mouseenter=="function") s.callback.mouseenter.call(s);
  1214. }).on('mousewheel',{sky:this},function(e, delta) {
  1215. var s = e.data.sky;
  1216. if(s.mouse && s.projection.id=="gnomic"){
  1217. s.changeFOV(delta).draw();
  1218. return false;
  1219. }else return true;
  1220. });
  1221. $(document).bind('keypress',{sky:this},function(e){
  1222. if(!e) e = window.event;
  1223. var code = e.keyCode || e.charCode || e.which || 0;
  1224. e.data.sky.keypress(code,e);
  1225. });
  1226. }
  1227. this.registerKey('a',function(){ this.toggleAtmosphere(); },'atmos');
  1228. this.registerKey('g',function(){ this.toggleGround(); },'ground');
  1229. this.registerKey('h',function(){ this.cycleProjection(); },'projection');
  1230. this.registerKey('i',function(){ this.toggleNegative(); },'neg');
  1231. this.registerKey(',',function(){ this.toggleEcliptic(); },'ec');
  1232. this.registerKey(';',function(){ this.toggleMeridian(); },'meridian');
  1233. this.registerKey('e',function(){ this.toggleGridlinesEquatorial(); },'eq');
  1234. this.registerKey('z',function(){ this.toggleGridlinesAzimuthal(); },'az');
  1235. this.registerKey('m',function(){ this.toggleGridlinesGalactic(); },'gal');
  1236. this.registerKey('M',function(){ this.toggleGalaxy(); },'galaxy');
  1237. this.registerKey('q',function(){ this.toggleCardinalPoints(); },'cardinal');
  1238. this.registerKey('s',function(){ this.toggleStars(); },'stars');
  1239. this.registerKey('S',function(){ this.toggleStarLabels(); },'starlabels');
  1240. this.registerKey('u',function(){ this.togglePlanetLabels(); },'sollabels');
  1241. this.registerKey('p',function(){ this.togglePlanetHints(); },'sol');
  1242. this.registerKey('o',function(){ this.toggleOrbits(); },'orbits');
  1243. this.registerKey('c',function(){ this.toggleConstellationLines(); },'con');
  1244. this.registerKey('v',function(){ this.toggleConstellationLabels(); },'names');
  1245. this.registerKey('b',function(){ this.toggleConstellationBoundaries(); },'conbound');
  1246. this.registerKey('R',function(){ this.toggleMeteorShowers(); },'meteorshowers');
  1247. this.registerKey('1',function(){ this.toggleHelp(); });
  1248. this.registerKey('8',function(){ this.setClock('now').calendarUpdate(); },'reset');
  1249. this.registerKey('j',function(){ if(!this.islive) this.spinIt("down"); },'slow');
  1250. this.registerKey('k',function(){ this.spinIt(0) },'stop');
  1251. this.registerKey('l',function(){ if(!this.islive) this.spinIt("up"); },'fast');
  1252. this.registerKey('-',function(){ this.setClock(-86400).calendarUpdate(); },'subtractday');
  1253. this.registerKey('=',function(){ this.setClock(86400).calendarUpdate(); },'addday');
  1254. this.registerKey('[',function(){ this.setClock(-86400*7).calendarUpdate(); },'subtractweek');
  1255. this.registerKey(']',function(){ this.setClock(86400*7).calendarUpdate(); },'addweek');
  1256. this.registerKey(37,function(){ this.az_off -= 2; this.draw(); },'azleft'); // left
  1257. this.registerKey(39,function(){ this.az_off += 2; this.draw(); },'azright'); // right
  1258. this.registerKey(38,function(){ this.changeMagnitude(0.25); },'magup'); // up
  1259. this.registerKey(40,function(){ this.changeMagnitude(-0.25);},'magdown'); // down
  1260. this.registerKey(63,function(){ this.toggleHelp(); });
  1261. this.draw();
  1262. }
  1263. VirtualSky.prototype.changeMagnitude = function(m){
  1264. if(typeof m!=="number")
  1265. return this;
  1266. this.magnitude += m;
  1267. if(!this.starsdeep && this.magnitude > 4)
  1268. this.load('stars',this.file.stars);
  1269. else
  1270. this.draw();
  1271. return this;
  1272. }
  1273. VirtualSky.prototype.toggleHelp = function(){
  1274. var v = "virtualsky";
  1275. if($('.'+v+'_dismiss').length > 0) $('.'+v+'_dismiss').trigger('click');
  1276. else{
  1277. // Build the list of keyboard options
  1278. var o = '';
  1279. for(var i = 0; i < this.keys.length ; i++){
  1280. if(this.keys[i].txt)
  1281. o += '<li>'+
  1282. '<strong class="'+v+'_help_key '+v+'_'+this.keys[i].txt+'">'+this.keys[i].str+'</strong> &rarr; <a href="#" class="'+v+'_'+this.keys[i].txt+'" style="text-decoration:none;">'+this.getPhrase(this.keys[i].txt)+'</a>'+
  1283. '</li>'; }
  1284. $('<div class="'+v+'_help">'+
  1285. '<div class="'+v+'_dismiss" title="'+this.getPhrase('close')+'">&times;</div>'+
  1286. '<span>'+this.getPhrase('keyboard')+'</span>'+
  1287. '<div class="'+v+'_helpinner"><ul></ul></div>'+
  1288. '</div>').appendTo(this.container);
  1289. var hlp = $('.'+v+'_help');
  1290. var h = hlp.outerHeight();
  1291. // Add the keyboard option list
  1292. hlp.find('ul').html(o);
  1293. // Set the maximum height for the list and add a scroll bar if necessary
  1294. $('.'+v+'_helpinner').css({'overflow':'auto','max-height':(this.tall-h)+'px'});
  1295. // Add the events for each keyboard option
  1296. for(var i = 0; i < this.keys.length ; i++){
  1297. if(this.keys[i].txt)
  1298. $('.'+v+'_'+this.keys[i].txt)
  1299. .on('click',{fn:this.keys[i].fn,me:this},function(e){
  1300. e.preventDefault(); e.data.fn.call(e.data.me);
  1301. });
  1302. }
  1303. // Create a lightbox
  1304. this.lightbox($('.'+v+'_help'));
  1305. $('.'+v+'_help, .'+v+'_bg').on('mouseout',{sky:this},function(e){ e.data.sky.mouseover = false; }).on('mouseenter',{sky:this},function(e){ e.data.sky.mouseover = true; });
  1306. }
  1307. }
  1308. // Register keyboard commands and associated functions
  1309. VirtualSky.prototype.registerKey = function(charCode,fn,txt){
  1310. if(!is(fn,"function")) return this;
  1311. if(!is(charCode,"object")) charCode = [charCode];
  1312. var aok, ch, c, i, alt, str;
  1313. for(c = 0 ; c < charCode.length ; c++){
  1314. alt = false;
  1315. if(typeof charCode[c]=="string"){
  1316. if(charCode[c].indexOf('alt')==0){
  1317. str = charCode[c];
  1318. alt = true;
  1319. charCode[c] = charCode[c].substring(4);
  1320. }else{
  1321. str = charCode[c];
  1322. }
  1323. ch = charCode[c].charCodeAt(0);
  1324. }else{
  1325. ch = charCode[c];
  1326. var arrows = {37:"left",38:"up",39:"right",40:"down"};
  1327. str = this.getPhrase(arrows[ch]) || String.fromCharCode(ch);
  1328. }
  1329. aok = true;
  1330. for(i = 0 ; i < this.keys.length ; i++){ if(this.keys.charCode == ch && this.keys.altKey == alt) aok = false; }
  1331. if(aok){
  1332. this.keys.push({
  1333. 'str': str,
  1334. 'charCode': ch,
  1335. 'char': String.fromCharCode(ch),
  1336. 'fn': fn,
  1337. 'txt': txt,
  1338. 'altKey': alt
  1339. });
  1340. }
  1341. }
  1342. return this;
  1343. }
  1344. // Work out if the keypress has a function that needs to be called.
  1345. VirtualSky.prototype.keypress = function(charCode,event){
  1346. if(!event) event = { altKey: false };
  1347. if(this.mouseover && this.keyboard){
  1348. for(var i = 0 ; i < this.keys.length ; i++){
  1349. if(this.keys[i].charCode == charCode && event.altKey == this.keys[i].altKey){
  1350. this.keys[i].fn.call(this,{event:event});
  1351. break;
  1352. }
  1353. }
  1354. }
  1355. }
  1356. VirtualSky.prototype.whichPointer = function(x,y){
  1357. for(var i = 0 ; i < this.pointers.length ; i++)
  1358. if(Math.abs(x-this.pointers[i].x) < 5 && Math.abs(y-this.pointers[i].y) < 5)
  1359. return i
  1360. return -1;
  1361. }
  1362. VirtualSky.prototype.toggleInfoBox = function(i){
  1363. if(this.pointers.length == 0 || i >= this.pointers.length || (i>=0 && !this.pointers[i].html))
  1364. return this;
  1365. if($('#'+this.id+'_'+this.infobox).length <= 0)
  1366. this.container.append('<div id="'+this.id+'_'+this.infobox+'" class="'+this.infobox+'" style="display:none;"></div>');
  1367. var el = $('#'+this.id+'_'+this.infobox);
  1368. if(i >= 0 && this.isVisible(this.pointers[i].el) && this.pointers[i].x > 0 && this.pointers[i].y > 0 && this.pointers[i].x < this.wide && this.pointers[i].y < this.tall){
  1369. var offset = this.container.position();
  1370. el.html(this.pointers[i].html);
  1371. var x = this.pointers[i].x - Math.round(el.outerWidth()/2);
  1372. var y = this.pointers[i].y - Math.round(el.outerHeight()/2);
  1373. el.css({'position':'absolute',left:x,top:y,'z-index':10}).fadeIn("fast");
  1374. }else el.hide();
  1375. }
  1376. // compute horizon coordinates from utc, ra, dec
  1377. // ra, dec in radians
  1378. // lat, lon in degrees
  1379. // results returned in hrz_altitude, hrz_azimuth
  1380. VirtualSky.prototype.coord2horizon = function(ra, dec){
  1381. var ha, alt, az, sd, sl, cl;
  1382. // compute hour angle in degrees
  1383. ha = (Math.PI*this.times.LST/12) - ra;
  1384. sd = Math.sin(dec);
  1385. sl = Math.sin(this.latitude);
  1386. cl = Math.cos(this.latitude);
  1387. // compute altitude in radians
  1388. alt = Math.asin(sd*sl + Math.cos(dec)*cl*Math.cos(ha));
  1389. // compute azimuth in radians
  1390. // divide by zero error at poles or if alt = 90 deg (so we should've already limited to 89.9999)
  1391. az = Math.acos((sd - Math.sin(alt)*sl)/(Math.cos(alt)*cl));
  1392. // choose hemisphere
  1393. if (Math.sin(ha) > 0) az = 2*Math.PI - az;
  1394. return [alt,az];
  1395. }
  1396. function inrangeAz(a,deg){
  1397. if(deg){
  1398. while(a < 0) a += 360;
  1399. while(a > 360) a -= 360;
  1400. }else{
  1401. var twopi = (2*Math.PI);
  1402. while(a < 0) a += twopi;
  1403. while(a > twopi) a -= twopi;
  1404. }
  1405. return a;
  1406. }
  1407. function inrangeEl(a,deg){
  1408. if(deg){
  1409. if(a >= 90) a = 89.99999;
  1410. if(a <= -90) a = -89.99999;
  1411. }else{
  1412. if(a >= Math.PI/2) a = (Math.PI/2)*0.999999;
  1413. if(a <= -Math.PI/2) a = (-Math.PI/2)*0.999999;
  1414. }
  1415. return a;
  1416. }
  1417. VirtualSky.prototype.selectProjection = function(proj){
  1418. if(this.projections[proj]){
  1419. this.projection = this.projections[proj];
  1420. this.projection.id = proj;
  1421. this.fullsky = this.projection.fullsky == true;
  1422. this.polartype = this.projection.polartype == true;
  1423. // Set coordinate transforms
  1424. // Convert AZ,EL -> X,Y
  1425. // Inputs: az (rad), el (rad), width (px), height (px)
  1426. if(typeof this.projection.azel2xy==="function")
  1427. this.azel2xy = this.projection.azel2xy;
  1428. else this.azel2xy = function(az,el,w,h){
  1429. if(!w) w = this.wide;
  1430. if(!h) h = this.tall;
  1431. if(az < 0) az += 360;
  1432. return {x:-1,y:-1,el:-1};
  1433. }
  1434. // Convert AZ,EL -> RA,Dec
  1435. // Inputs: az (rad), el (rad)
  1436. // Output: { ra: ra (deg), dec: dec (deg) }
  1437. if(typeof this.projection.azel2radec==="function")
  1438. this.azel2radec = this.projection.azel2radec;
  1439. else this.azel2radec = function(az,el){
  1440. var xt,yt,r,l;
  1441. l = this.latitude;
  1442. xt = Math.asin( Math.sin(el) * Math.sin(l) + Math.cos(el) * Math.cos(l) * Math.cos(az) );
  1443. r = ( Math.sin(el) - Math.sin(l) * Math.sin(xt) ) / ( Math.cos(l) * Math.cos(xt) );
  1444. if(r > 1) r = 1;
  1445. yt = Math.acos(r);
  1446. if(Math.sin(az) > 0.0) yt = Math.PI*2 - yt;
  1447. xt *= this.r2d;
  1448. yt *= this.r2d;
  1449. yt = (this.times.LST*15 - yt + 360)%360.0;
  1450. return { ra: yt, dec: xt }
  1451. }
  1452. if(this.ctx)
  1453. this.updateSkyGradient();
  1454. this.updateColours();
  1455. // Draw update label
  1456. if(this.container){
  1457. var w = this.container.width();
  1458. var h = this.container.height();
  1459. var s = (this.lang.projections && this.lang.projections[proj]) ? this.lang.projections[proj] : this.projections[proj].title;
  1460. if($('.'+this.id+'_projection').length > 0) $('.'+this.id+'_projection').remove();
  1461. this.container.append('<div class="'+this.id+'_projection">'+s+'</div>');
  1462. $('.'+this.id+'_projection')
  1463. .on('mouseover',{me:this},function(e){e.data.me.mouseover = true;})
  1464. .css({
  1465. position:'absolute',
  1466. padding:0,
  1467. width:w+'px',
  1468. top:0,left:0,
  1469. 'text-align':'center',
  1470. 'line-height':h+'px',
  1471. zIndex:20,
  1472. fontSize:'1.5em',
  1473. display:'block',
  1474. overflow:'hidden',
  1475. backgroundColor:'transparent',
  1476. color:(this.negative ? this.col.black : this.col.white)})
  1477. .delay(500).fadeOut(1000,function(){ $(this).remove(); });
  1478. }
  1479. }
  1480. }
  1481. // Cycle through the map projections
  1482. VirtualSky.prototype.cycleProjection = function(){
  1483. var usenext = false;
  1484. var proj = this.projection.id;
  1485. var i = 0;
  1486. var firstkey;
  1487. for(var key in this.projections){
  1488. if(i==0) firstkey = key;
  1489. if(usenext){
  1490. proj = key;
  1491. break;
  1492. }
  1493. if(key == this.projection.id) usenext = true;
  1494. i++;
  1495. }
  1496. if(proj == this.projection.id) proj = firstkey;
  1497. this.draw(proj);
  1498. }
  1499. // Update the sky colours
  1500. VirtualSky.prototype.updateColours = function(){
  1501. // We need to make a copy of the correct colour palette otherwise it'll overwrite it
  1502. this.col = $.extend(true, {}, ((this.negative) ? this.colours.negative : this.colours.normal));
  1503. if(this.color==""){
  1504. if((this.polartype || this.projection.altlabeltext))
  1505. this.col.txt = this.col.grey;
  1506. }else{
  1507. this.col.txt = this.color;
  1508. }
  1509. }
  1510. VirtualSky.prototype.isVisible = function(el){
  1511. if(typeof this.projection.isVisible==="function") return this.projection.isVisible.call(el);
  1512. if(!this.fullsky) return (el > 0);
  1513. else return (this.ground) ? (el > 0) : true;
  1514. }
  1515. VirtualSky.prototype.isPointBad = function(p){
  1516. return p.x==-1 && p.y==-1;
  1517. }
  1518. // Return a structure with the Julian Date, Local Sidereal Time and Greenwich Sidereal Time
  1519. VirtualSky.prototype.astronomicalTimes = function(clock,lon){
  1520. clock = clock || this.clock;
  1521. lon = lon || this.longitude*this.r2d;
  1522. var JD,JD0,S,T,T0,UT,A,GST,d,LST;
  1523. JD = this.getJD(clock);
  1524. JD0 = Math.floor(JD-0.5)+0.5;
  1525. S = JD0-2451545.0;
  1526. T = S/36525.0;
  1527. T0 = (6.697374558 + (2400.051336*T) + (0.000025862*T*T))%24;
  1528. if(T0 < 0) T0 += 24;
  1529. UT = (((clock.getUTCMilliseconds()/1000 + clock.getUTCSeconds())/60) + clock.getUTCMinutes())/60 + clock.getUTCHours();
  1530. A = UT*1.002737909;
  1531. T0 += A;
  1532. GST = T0%24;
  1533. if(GST < 0) GST += 24;
  1534. d = (GST + lon/15.0)/24.0;
  1535. d = d - Math.floor(d);
  1536. if(d < 0) d += 1;
  1537. LST = 24.0*d;
  1538. return { GST:GST, LST:LST, JD:JD };
  1539. }
  1540. // Uses algorithm defined in Practical Astronomy (4th ed) by Peter Duffet-Smith and Jonathan Zwart
  1541. VirtualSky.prototype.moonPos = function(JD,sun){
  1542. var d2r,JD,sun,lo,Po,No,i,e,l,Mm,N,C,Ev,sinMo,Ae,A3,Mprimem,Ec,A4,lprime,V,lprimeprime,Nprime,lppNp,sinlppNp,y,x,lm,Bm;
  1543. d2r = this.d2r;
  1544. JD = JD || this.times.JD;
  1545. sun = sun || this.sunPos(JD);
  1546. lo = 91.929336; // Moon's mean longitude at epoch 2010.0
  1547. Po = 130.143076; // mean longitude of the perigee at epoch
  1548. No = 291.682547; // mean longitude of the node at the epoch
  1549. i = 5.145396; // inclination of Moon's orbit
  1550. e = 0.0549; // eccentricity of the Moon's orbit
  1551. l = (13.1763966*sun.D + lo)%360;
  1552. if(l < 0) l += 360;
  1553. Mm = (l - 0.1114041*sun.D - Po)%360;
  1554. if(Mm < 0) Mm += 360;
  1555. N = (No - 0.0529539*sun.D)%360;
  1556. if(N < 0) N += 360;
  1557. C = l-sun.lon;
  1558. Ev = 1.2739*Math.sin((2*C-Mm)*d2r);
  1559. sinMo = Math.sin(sun.Mo*d2r);
  1560. Ae = 0.1858*sinMo;
  1561. A3 = 0.37*sinMo;
  1562. Mprimem = Mm + Ev -Ae - A3;
  1563. Ec = 6.2886*Math.sin(Mprimem*d2r);
  1564. A4 = 0.214*Math.sin(2*Mprimem*d2r);
  1565. lprime = l + Ev + Ec -Ae + A4;
  1566. V = 0.6583*Math.sin(2*(lprime-sun.lon)*d2r);
  1567. lprimeprime = lprime + V;
  1568. Nprime = N - 0.16*sinMo;
  1569. lppNp = (lprimeprime-Nprime)*d2r;
  1570. sinlppNp = Math.sin(lppNp);
  1571. y = sinlppNp*Math.cos(i*d2r);
  1572. x = Math.cos(lppNp);
  1573. lm = Math.atan2(y,x)/d2r + Nprime;
  1574. Bm = Math.asin(sinlppNp*Math.sin(i*d2r))/d2r;
  1575. if(lm > 360) lm -= 360;
  1576. return { moon: {lon:lm,lat:Bm}, sun:sun };
  1577. }
  1578. // Uses algorithm defined in Practical Astronomy (4th ed) by Peter Duffet-Smith and Jonathan Zwart
  1579. VirtualSky.prototype.sunPos = function(JD){
  1580. var D,eg,wg,e,N,Mo,v,lon,lat;
  1581. D = (JD-2455196.5); // Number of days since the epoch of 2010 January 0.0
  1582. // Calculated for epoch 2010.0. If T is the number of Julian centuries since 1900 January 0.5 = (JD-2415020.0)/36525
  1583. eg = 279.557208; // mean ecliptic longitude in degrees = (279.6966778 + 36000.76892*T + 0.0003025*T*T)%360;
  1584. wg = 283.112438; // longitude of the Sun at perigee in degrees = 281.2208444 + 1.719175*T + 0.000452778*T*T;
  1585. e = 0.016705; // eccentricity of the Sun-Earth orbit in degrees = 0.01675104 - 0.0000418*T - 0.000000126*T*T;
  1586. N = ((360/365.242191)*D)%360;
  1587. if(N < 0) N += 360;
  1588. Mo = (N + eg - wg)%360 // mean anomaly in degrees
  1589. if(Mo < 0) Mo += 360;
  1590. v = Mo + (360/Math.PI)*e*Math.sin(Mo*Math.PI/180);
  1591. lon = v + wg;
  1592. if(lon > 360) lon -= 360;
  1593. lat = 0;
  1594. return {lat:lat,lon:lon,Mo:Mo,D:D,N:N}
  1595. }
  1596. // Input is Julian Date
  1597. // Uses method defined in Practical Astronomy (4th ed) by Peter Duffet-Smith and Jonathan Zwart
  1598. VirtualSky.prototype.meanObliquity = function(JD){
  1599. if(!JD) JD = this.times.JD;
  1600. var T,T2,T3;
  1601. T = (JD-2451545.0)/36525 // centuries since 2451545.0 (2000 January 1.5)
  1602. T2 = T*T;
  1603. T3 = T2*T;
  1604. return (23.4392917 - 0.0130041667*T - 0.00000016667*T2 + 0.0000005027778*T3)*this.d2r;
  1605. }
  1606. // Take input in radians, decimal Sidereal Time and decimal latitude
  1607. // Uses method defined in Practical Astronomy (4th ed) by Peter Duffet-Smith and Jonathan Zwart
  1608. VirtualSky.prototype.ecliptic2azel = function(l,b,LST,lat){
  1609. if(!LST){
  1610. this.times = this.astronomicalTimes();
  1611. LST = this.times.LST;
  1612. }
  1613. if(!lat) lat = this.latitude
  1614. var sl,cl,sb,cb,v,e,ce,se,Cprime,s,ST,cST,sST,B,r,sphi,cphi,A,w,theta,psi;
  1615. sl = Math.sin(l);
  1616. cl = Math.cos(l);
  1617. sb = Math.sin(b);
  1618. cb = Math.cos(b);
  1619. v = [cl*cb,sl*cb,sb];
  1620. e = this.meanObliquity();
  1621. ce = Math.cos(e);
  1622. se = Math.sin(e);
  1623. Cprime = [[1.0,0.0,0.0],[0.0,ce,-se],[0.0,se,ce]];
  1624. s = this.vectorMultiply(Cprime,v);
  1625. ST = LST*15*this.d2r;
  1626. cST = Math.cos(ST);
  1627. sST = Math.sin(ST);
  1628. B = [[cST,sST,0],[sST,-cST,0],[0,0,1]];
  1629. r = this.vectorMultiply(B,s);
  1630. sphi = Math.sin(lat);
  1631. cphi = Math.cos(lat);
  1632. A = [[-sphi,0,cphi],[0,-1,0],[cphi,0,sphi]];
  1633. w = this.vectorMultiply(A,r);
  1634. theta = Math.atan2(w[1],w[0]);
  1635. psi = Math.asin(w[2]);
  1636. return {az:theta,el:psi};
  1637. }
  1638. // Convert from ecliptic l,b -> RA,Dec
  1639. // Inputs: l (rad), b (rad), Julian date
  1640. VirtualSky.prototype.ecliptic2radec = function(l,b,JD){
  1641. var e = this.meanObliquity();
  1642. var sl = Math.sin(l);
  1643. var cl = Math.cos(l);
  1644. var sb = Math.sin(b);
  1645. var cb = Math.cos(b);
  1646. var tb = Math.tan(b);
  1647. var se = Math.sin(e);
  1648. var ce = Math.cos(e);
  1649. ra = Math.atan2((sl*ce - tb*se),(cl));
  1650. dec = Math.asin(sb*ce+cb*se*sl);
  1651. // Make sure RA is positive
  1652. if(ra < 0) ra += Math.PI+Math.PI;
  1653. return { ra:ra, dec:dec };
  1654. }
  1655. // Convert Ecliptic coordinates to x,y position
  1656. // Inputs: l (rad), b (rad), local sidereal time
  1657. // Returns [x, y (,elevation)]
  1658. VirtualSky.prototype.ecliptic2xy = function(l,b,LST){
  1659. LST = LST || this.times.LST;
  1660. if(typeof this.projection.ecliptic2xy==="function") return this.projection.ecliptic2xy.call(this,l,b,LST);
  1661. else{
  1662. if(this.fullsky){
  1663. var pos = this.ecliptic2radec(l,b);
  1664. return this.radec2xy(pos.ra,pos.dec);
  1665. }else{
  1666. var pos = this.ecliptic2azel(l,b,LST);
  1667. var el = pos.el*this.r2d;
  1668. pos = this.azel2xy(pos.az-(this.az_off*this.d2r),pos.el,this.wide,this.tall);
  1669. pos.el = el;
  1670. return pos;
  1671. }
  1672. }
  1673. return 0;
  1674. }
  1675. // Convert RA,Dec -> X,Y
  1676. // Inputs: RA (rad), Dec (rad)
  1677. // Returns [x, y (,elevation)]
  1678. VirtualSky.prototype.radec2xy = function(ra,dec){
  1679. if(typeof this.projection.radec2xy==="function") return this.projection.radec2xy.call(this,ra,dec);
  1680. else{
  1681. var coords = this.coord2horizon(ra, dec);
  1682. // Only return coordinates above the horizon
  1683. if(coords[0] > 0){
  1684. var pos = this.azel2xy(coords[1]-(this.az_off*this.d2r),coords[0],this.wide,this.tall);
  1685. return {x:pos.x,y:pos.y,az:coords[1]*this.r2d,el:coords[0]*this.r2d};
  1686. }
  1687. }
  1688. return 0;
  1689. }
  1690. // Dummy function - overwritten in selectProjection
  1691. // Convert AZ,EL -> X,Y
  1692. // Inputs: az (degrees), el (degrees), width (px), height (px)
  1693. // Output: { x: x, y: y }
  1694. VirtualSky.prototype.azel2xy = function(az,el,w,h){ return {x:-1,y:-1}; }
  1695. // Dummy functions - overwritten in selectProjection
  1696. // Convert AZ,EL -> RA,Dec
  1697. // Inputs: az (rad), el (rad)
  1698. // Output: { ra: ra (deg), dec: dec (deg) }
  1699. VirtualSky.prototype.azel2radec = function(az,el){ return { ra: 0, dec: 0 }; }
  1700. // Convert Galactic -> x,y
  1701. // Inputs: longitude (rad), latitude (rad)
  1702. VirtualSky.prototype.gal2xy = function(l,b){
  1703. var pos = this.gal2radec(l,b);
  1704. return this.radec2xy(pos[0],pos[1]);
  1705. }
  1706. // Convert Galactic -> J2000
  1707. // Inputs: longitude (rad), latitude (rad)
  1708. VirtualSky.prototype.gal2radec = function(l,b){
  1709. // Using SLALIB values
  1710. return this.Transform([l,b], [-0.054875539726, 0.494109453312, -0.867666135858, -0.873437108010, -0.444829589425, -0.198076386122, -0.483834985808, 0.746982251810, 0.455983795705],false);
  1711. }
  1712. // Input is a two element position (degrees) and rotation matrix
  1713. // Output is a two element position (degrees)
  1714. VirtualSky.prototype.Transform = function(p, rot, indeg){
  1715. if(indeg){
  1716. p[0] *= this.d2r;
  1717. p[1] *= this.d2r;
  1718. }
  1719. var cp1 = Math.cos(p[1]);
  1720. var m = [Math.cos(p[0])*cp1, Math.sin(p[0])*cp1, Math.sin(p[1])];
  1721. var s = [m[0]*rot[0] + m[1]*rot[1] + m[2]*rot[2], m[0]*rot[3] + m[1]*rot[4] + m[2]*rot[5], m[0]*rot[6] + m[1]*rot[7] + m[2]*rot[8] ];
  1722. var r = Math.sqrt(s[0]*s[0] + s[1]*s[1] + s[2]*s[2]);
  1723. var b = Math.asin(s[2]/r); // Declination in range -90 -> +90
  1724. var cb = Math.cos(b);
  1725. var a = Math.atan2(((s[1]/r)/cb),((s[0]/r)/cb));
  1726. if (a < 0) a += Math.PI*2;
  1727. if(indeg) return [a*this.r2d,b*this.r2d];
  1728. else return [a,b];
  1729. }
  1730. // Convert from B1875 to J2000
  1731. // Using B = 1900.0 + (JD − 2415020.31352) / 365.242198781 and p73 Practical Astronomy With A Calculator
  1732. VirtualSky.prototype.fk1tofk5 = function(a,b){
  1733. // Convert from B1875 -> J2000
  1734. return this.Transform([a,b], [0.9995358730015703, -0.02793693620138929, -0.012147682028606801, 0.027936935758478665, 0.9996096732234282, -0.00016976035344812515, 0.012147683047201562, -0.00016968744936278707, 0.9999261997781408]);
  1735. }
  1736. VirtualSky.prototype.vectorMultiply = function(A,B){
  1737. if(B.length > 0){
  1738. // 2D (3x3)x(3x3) or 1D (3x3)x(3x1)
  1739. if(B[0].length > 0) return [[(A[0][0]*B[0][0]+A[0][1]*B[1][0]+A[0][2]*B[2][0]),(A[0][0]*B[0][1]+A[0][1]*B[1][1]+A[0][2]*B[2][1]),(A[0][0]*B[0][2]+A[0][1]*B[1][2]+A[0][2]*B[2][2])],
  1740. [(A[1][0]*B[0][0]+A[1][1]*B[1][0]+A[1][2]*B[2][0]),(A[1][0]*B[0][1]+A[1][1]*B[1][1]+A[1][2]*B[2][1]),(A[1][0]*B[0][2]+A[1][1]*B[1][2]+A[1][2]*B[2][2])],
  1741. [(A[2][0]*B[0][0]+A[2][1]*B[1][0]+A[2][2]*B[2][0]),(A[2][0]*B[0][1]+A[2][1]*B[1][1]+A[2][2]*B[2][1]),(A[2][0]*B[0][2]+A[2][1]*B[1][2]+A[2][2]*B[2][2])]];
  1742. else return [(A[0][0]*B[0] + A[0][1]*B[1] + A[0][2]*B[2]),(A[1][0]*B[0] + A[1][1]*B[1] + A[1][2]*B[2]),(A[2][0]*B[0] + A[2][1]*B[1] + A[2][2]*B[2])];
  1743. }
  1744. }
  1745. VirtualSky.prototype.setFont = function(){ this.ctx.font = this.fontsize()+"px "+this.canvas.css('font-family'); }
  1746. VirtualSky.prototype.fontsize = function(){
  1747. if(this.fntsze) return parseInt(this.fntsze);
  1748. var m = Math.min(this.wide,this.tall);
  1749. return (m < 600) ? ((m < 500) ? ((m < 350) ? ((m < 300) ? ((m < 250) ? 9 : 10) : 11) : 12) : 14) : parseInt(this.container.css('font-size'));
  1750. }
  1751. VirtualSky.prototype.positionCredit = function(){
  1752. this.container.find('.'+this.id+'_credit').css({position:'absolute',top:parseFloat(this.tall)-5-this.fontsize(),left:5});
  1753. }
  1754. VirtualSky.prototype.updateSkyGradient = function(){
  1755. var s = null;
  1756. if(this.ctx && this.hasGradient()){
  1757. if(this.projection.polartype){
  1758. if(typeof this.ctx.createRadialGradient==="function"){
  1759. s = this.ctx.createRadialGradient(this.wide/2,this.tall/2,0,this.wide/2,this.tall/2,this.tall/2);
  1760. s.addColorStop(0, 'rgba(0,0,0,1)');
  1761. s.addColorStop(0.7, 'rgba(0,0,0,0.2)');
  1762. s.addColorStop(1, 'rgba(0,50,80,0.3)');
  1763. }
  1764. }else{
  1765. s = this.ctx.createLinearGradient(0,0,0,this.tall);
  1766. s.addColorStop(0.0, 'rgba(0,30,50,0.1)');
  1767. s.addColorStop(0.7, 'rgba(0,30,50,0.35)');
  1768. s.addColorStop(1, 'rgba(0,50,80,0.6)');
  1769. }
  1770. }
  1771. this.skygrad = s;
  1772. return this;
  1773. }
  1774. VirtualSky.prototype.draw = function(proj){
  1775. // Don't bother drawing anything if there is no physical area to draw on
  1776. if(this.wide <= 0 || this.tall <= 0) return this;
  1777. if(!(this.c && this.c.getContext)) return this;
  1778. if(proj !== undefined) this.selectProjection(proj);
  1779. var white = this.col.white;
  1780. var black = this.col.black;
  1781. // Shorthands
  1782. var c = this.ctx;
  1783. var d = this.container;
  1784. c.moveTo(0,0);
  1785. c.clearRect(0,0,this.wide,this.tall);
  1786. c.fillStyle = (this.polartype || this.fullsky) ? this.background : ((this.negative) ? white : black);
  1787. c.fillRect(0,0,this.wide,this.tall);
  1788. c.fill();
  1789. if(this.polartype){
  1790. c.moveTo(this.wide/2,this.tall/2);
  1791. c.closePath();
  1792. c.beginPath();
  1793. c.arc(this.wide/2,this.tall/2,-0.5+this.tall/2,0,Math.PI*2,true);
  1794. c.closePath();
  1795. if(!this.transparent){
  1796. c.fillStyle = (this.hasGradient()) ? "rgba(0,15,30, 1)" : ((this.negative) ? white : black);
  1797. c.fill();
  1798. }
  1799. c.lineWidth = 0.5;
  1800. c.strokeStyle = black;
  1801. c.stroke();
  1802. }else if(typeof this.projection.draw==="function") this.projection.draw.call(this);
  1803. if(this.hasGradient()){
  1804. if(this.skygrad===undefined){
  1805. this.updateSkyGradient();
  1806. }else{
  1807. c.beginPath();
  1808. c.fillStyle = this.skygrad;
  1809. // draw shapes
  1810. if(this.projection.polartype){ c.arc(this.wide/2,this.tall/2,this.tall/2,0,2*Math.PI,false); c.fill(); }
  1811. else c.fillRect(0,0,this.wide,this.tall);
  1812. c.closePath();
  1813. }
  1814. }
  1815. this.drawGridlines("az")
  1816. .drawGridlines("eq")
  1817. .drawGridlines("gal")
  1818. .drawGalaxy()
  1819. .drawConstellationLines()
  1820. .drawConstellationBoundaries()
  1821. .drawStars()
  1822. .drawEcliptic()
  1823. .drawMeridian()
  1824. .drawPlanets()
  1825. .drawMeteorShowers()
  1826. .drawCardinalPoints();
  1827. for(var i = 0; i < this.pointers.length ; i++) this.highlight(i);
  1828. var txtcolour = (this.color!="") ? (this.color) : this.col.txt;
  1829. var fontsize = this.fontsize();
  1830. c.fillStyle = txtcolour;
  1831. c.lineWidth = 1.5;
  1832. this.setFont();
  1833. // Time line
  1834. if(this.showdate){
  1835. var clockstring = this.clock.toDateString()+' '+this.clock.toLocaleTimeString();
  1836. var metric_clock = this.drawText(clockstring,5,5+fontsize);
  1837. }
  1838. // Position line
  1839. if(this.showposition){
  1840. var positionstring = Math.abs(this.latitude*this.r2d).toFixed(2) + ((this.latitude>0) ? this.getPhrase('N') : this.getPhrase('S')) + ', ' + Math.abs(this.longitude*this.r2d).toFixed(2) + ((this.longitude>0) ? this.getPhrase('E') : this.getPhrase('W'));
  1841. var metric_pos = this.drawText(positionstring,5,5+fontsize+fontsize);
  1842. }
  1843. // Credit line
  1844. if(this.credit){
  1845. var credit = this.getPhrase('power');
  1846. var metric_credit = this.drawText(credit,5,this.tall-5);
  1847. // Float a transparent link on top of the credit text
  1848. if(d.find('.'+this.id+'_credit').length == 0) d.append('<div class="'+this.id+'_credit"><a href="http://virtualsky.lco.global/" target="_parent" title="Las Cumbres Observatory Global Telescope">'+this.getPhrase('powered')+'</a></div>');
  1849. d.find('.'+this.id+'_credit').css({padding:0,zIndex:20,display:'block',overflow:'hidden',backgroundColor:'transparent'});
  1850. d.find('.'+this.id+'_credit a').css({display:'block',width:Math.ceil(metric_credit)+'px',height:fontsize+'px','font-size':fontsize+'px'});
  1851. this.positionCredit();
  1852. }
  1853. if(this.showhelp){
  1854. var helpstr = '?';
  1855. if(d.find('.'+this.id+'_help').length == 0)
  1856. d.append('<div class="'+this.id+'_help"><a href="#">'+helpstr+'</a></div>')
  1857. .find('.'+this.id+'_help')
  1858. .css({
  1859. position:'absolute',
  1860. padding:5,
  1861. zIndex:20,
  1862. display:'block',
  1863. overflow:'hidden',
  1864. backgroundColor:'transparent',
  1865. right:0,
  1866. top:0,
  1867. 'font-size':fontsize
  1868. }).find('a').css({
  1869. 'text-decoration':'none',
  1870. color:txtcolour
  1871. }).on('click',{me:this},function(e){ e.data.me.toggleHelp(); });
  1872. d.find('.'+this.id+'_help').css({'font-size':fontsize}).find('a').css({color:txtcolour});
  1873. }
  1874. if(this.container.find('.'+this.id+'_clock').length == 0) this.container.append('<div class="'+this.id+'_clock" title="'+this.getPhrase('datechange')+'">'+clockstring+'</div>');
  1875. var off = $('#'+this.idinner).position();
  1876. this.container.find('.'+this.id+'_clock').css({
  1877. position:'absolute',
  1878. padding:0,
  1879. width:metric_clock,
  1880. cursor:'pointer',
  1881. top:off.top+5,
  1882. left:off.left+5,
  1883. zIndex:20,
  1884. display:'block',
  1885. overflow:'hidden',
  1886. backgroundColor:'transparent',
  1887. fontSize:fontsize+'px',
  1888. color:'transparent'
  1889. }).bind('click',{sky:this},function(e){
  1890. var s = e.data.sky;
  1891. var id = s.id;
  1892. var hid = '#'+id;
  1893. var v = "virtualsky";
  1894. if($(hid+'_calendar').length == 0){
  1895. var off = $(hid).offset();
  1896. var w = 280;
  1897. var h = 50;
  1898. if(s.wide < w) w = s.wide;
  1899. s.container.append(
  1900. '<div id="'+id+'_calendar" class="'+v+'form">'+
  1901. '<div style="" id="'+id+'_calendar_close" class="'+v+'_dismiss" title="'+e.data.sky.getPhrase('close')+'">&times;</div>'+
  1902. '<div style="text-align:center;margin:2px;">'+e.data.sky.getPhrase('date')+'</div>'+
  1903. '<div style="text-align:center;">'+
  1904. '<input type="text" id="'+id+'_year" style="width:3.2em;" value="" />'+
  1905. '<div class="divider">/</div>'+
  1906. '<input type="text" id="'+id+'_month" style="width:1.6em;" value="" />'+
  1907. '<div class="divider">/</div>'+
  1908. '<input type="text" id="'+id+'_day" style="width:1.6em;" value="" />'+
  1909. '<div class="divider">&nbsp;</div>'+
  1910. '<input type="text" id="'+id+'_hours" style="width:1.6em;" value="" />'+
  1911. '<div class="divider">:</div>'+
  1912. '<input type="text" id="'+id+'_mins" style="width:1.6em;" value="" />'+
  1913. '</div>'+
  1914. '</div>');
  1915. $(hid+'_calendar').css({width:w});
  1916. $(hid+'_calendar input').bind('change',{sky:s},function(e){
  1917. e.data.sky.updateClock(new Date(parseInt($('#'+id+'_year').val()), parseInt($('#'+id+'_month').val()-1), parseInt($('#'+id+'_day').val()), parseInt($('#'+id+'_hours').val()), parseInt($('#'+id+'_mins').val()), 0,0));
  1918. e.data.sky.calendarUpdate();
  1919. e.data.sky.draw();
  1920. });
  1921. }
  1922. s.lightbox($(hid+'_calendar'));
  1923. $(hid+'_year').val(s.clock.getFullYear());
  1924. $(hid+'_month').val(s.clock.getMonth()+1);
  1925. $(hid+'_day').val(s.clock.getDate());
  1926. $(hid+'_hours').val(s.clock.getHours());
  1927. $(hid+'_mins').val(s.clock.getMinutes());
  1928. });
  1929. if($('.'+this.id+'_position').length == 0) this.container.append('<div class="'+this.id+'_position" title="'+this.getPhrase('positionchange')+'">'+positionstring+'</div>');
  1930. var off = $('#'+this.idinner).position();
  1931. $('.'+this.id+'_position').css({
  1932. position:'absolute',
  1933. padding:0,
  1934. width:metric_pos,
  1935. cursor:'pointer',
  1936. top:off.top+5+fontsize,
  1937. left:off.left+5,
  1938. zIndex:20,
  1939. fontSize:fontsize+'px',
  1940. display:'block',
  1941. overflow:'hidden',
  1942. backgroundColor:'transparent',
  1943. fontSize:fontsize+'px',
  1944. color:'transparent'
  1945. }).bind('click',{sky:this},function(e){
  1946. var s = e.data.sky;
  1947. var id = s.id;
  1948. var hid = '#'+id;
  1949. var v = "virtualsky";
  1950. if($(hid+'_geo').length == 0){
  1951. var w = 310;
  1952. var narrow = '';
  1953. if(s.wide < w){
  1954. narrow = '<br style="clear:both;margin-top:20px;" />';
  1955. w = w/2;
  1956. }
  1957. s.container.append(
  1958. '<div id="'+id+'_geo" class="'+v+'form">'+
  1959. '<div id="'+id+'_geo_close" class="'+v+'_dismiss" title="'+s.getPhrase('close')+'">&times;</div>'+
  1960. '<div style="text-align:center;margin:2px;">'+s.getPhrase('position')+'</div>'+
  1961. '<div style="text-align:center;">'+
  1962. '<input type="text" id="'+id+'_lat" value="" style="padding-right:10px!important;">'+
  1963. '<div class="divider">'+s.getPhrase('N')+'</div>'+
  1964. narrow+'<input type="text" id="'+id+'_long" value="" />'+
  1965. '<div class="divider">'+s.getPhrase('E')+'</div>'+
  1966. '</div>'+
  1967. '</div>');
  1968. $(hid+'_geo').css({width:w,'align':'center'})
  1969. $(hid+'_geo input').css({width:'6em'});
  1970. $(hid+'_geo_close').bind('click',{sky:s},function(e){
  1971. e.data.sky.setGeo($(hid+'_lat').val()+','+$(hid+'_long').val()).setClock(0).draw();
  1972. });
  1973. }
  1974. s.lightbox($(hid+'_geo'));
  1975. $(hid+'_lat').val(s.latitude*s.r2d)
  1976. $(hid+'_long').val(s.longitude*s.r2d)
  1977. if(typeof s.callback.geo=="function") s.callback.geo.call(s);
  1978. });
  1979. return this;
  1980. }
  1981. VirtualSky.prototype.lightbox = function(lb){
  1982. if(!lb.length) return this;
  1983. function columize(){
  1984. // Make each li as wide as it needs to be so we can calculate the widest
  1985. lb.find('li').css({'display':'inline-block','margin-left':'0px','width':'auto'});
  1986. // Remove positioning so we can work out sizes
  1987. lb.find('ul').css({'width':'auto'});
  1988. lb.css({'position':'relative'});
  1989. w = lb.outerWidth();
  1990. var bar = 24;
  1991. var li = lb.find('ul li');
  1992. var mx = 1;
  1993. for(var i = 0 ; i < li.length; i++){
  1994. if(li.eq(i).width() > mx) mx = li.eq(i).width();
  1995. }
  1996. // If the list items are wider than the space we have we turn them
  1997. // into block items otherwise set their widths to the maximum width.
  1998. var n = Math.floor(w/(mx+bar));
  1999. if(n > 1){
  2000. if(n > 3) n = 3;
  2001. lb.find('li').css({'width':(mx)+'px','margin-left':Math.floor(bar/2)+'px'});
  2002. lb.find('li:nth-child('+n+'n+1)').css({'margin-left':'0px'});
  2003. }else{
  2004. lb.find('li').css({'display':'block','width':'auto'});
  2005. }
  2006. lb.find('ul').css({'width':'100%'}).parent().css({'width':Math.min(w-bar,(mx+bar/2)*n + bar)+'px'});
  2007. lb.css({'z-index': 100,'position': 'absolute'});
  2008. lb.css({'left':Math.floor((this.wide-lb.outerWidth())/2)+'px',top:((this.tall-lb.height())/2)+'px'});
  2009. }
  2010. columize.call(this);
  2011. var n = "virtualsky_bg";
  2012. if(this.container.find('.'+n).length == 0) this.container.append('<div class="'+n+'" style="position:absolute;z-index: 99;left:0px;top: 0px;right: 0px;bottom: 0px;background-color: rgba(0,0,0,0.7);"></div>')
  2013. var bg = this.container.find('.'+n).show();
  2014. lb.css({left:((this.wide-lb.outerWidth())/2)+'px',top:((this.tall-lb.outerHeight())/2)+'px'}).show();
  2015. this.container.find('.virtualsky_dismiss').click({lb:lb,bg:bg},function(e){ lb.remove(); bg.remove(); });
  2016. bg.click({lb:lb,bg:bg},function(e){ lb.hide(); bg.hide(); });
  2017. // Update lightbox when the screen is resized
  2018. $(window).resize({vs:this,fn:columize},function(e){ e.data.fn.call(e.data.vs); });
  2019. return this;
  2020. }
  2021. VirtualSky.prototype.drawStars = function(){
  2022. if(!this.showstars && !this.showstarlabels) return this;
  2023. var mag,i,j,p,d,atmos,fovf;
  2024. var c = this.ctx;
  2025. c.beginPath();
  2026. c.fillStyle = this.col.stars;
  2027. this.az_off = (this.az_off+360)%360;
  2028. atmos = this.hasAtmos();
  2029. fovf = Math.sqrt(30/this.fov);
  2030. var f = 1;
  2031. if(this.negative) f *= 1.4;
  2032. if(typeof this.scalestars==="number" && this.scalestars!=1) f *= this.scalestars;
  2033. if(this.projection.id==="gnomic") f *= fovf;
  2034. for(i = 0; i < this.stars.length; i++){
  2035. if(this.stars[i][1] < this.magnitude){
  2036. mag = this.stars[i][1];
  2037. p = this.radec2xy(this.stars[i][2], this.stars[i][3]);
  2038. if(this.isVisible(p.el) && !isNaN(p.x) && !this.isPointBad(p)){
  2039. d = 0.8*Math.max(3-mag/2.1, 0.5);
  2040. // Modify the 'size' of the star by how close to the horizon it is
  2041. // i.e. smaller when closer to the horizon
  2042. if(atmos) d *= Math.exp(-(90-p.el)*0.01);
  2043. d *= f;
  2044. c.moveTo(p.x+d,p.y);
  2045. if(this.showstars) c.arc(p.x,p.y,d,0,Math.PI*2,true);
  2046. if(this.showstarlabels && this.starnames[this.stars[i][0]]) this.drawLabel(p.x,p.y,d,"",this.htmlDecode(this.starnames[this.stars[i][0]]));
  2047. }
  2048. }
  2049. }
  2050. c.fill();
  2051. return this;
  2052. }
  2053. VirtualSky.prototype.hasAtmos = function(){
  2054. return (typeof this.projection.atmos==="boolean") ? (this.gradient ? this.projection.atmos : this.gradient) : this.gradient;
  2055. }
  2056. VirtualSky.prototype.hasGradient = function(){
  2057. return (this.hasAtmos() && !this.fullsky && !this.negative) ? true : false;
  2058. }
  2059. // When provided with an array of Julian dates, ra, dec, and magnitude this will interpolate to the nearest
  2060. // data = [jd_1, ra_1, dec_1, mag_1, jd_2, ra_2, dec_2, mag_2....]
  2061. VirtualSky.prototype.interpolate = function(jd,data){
  2062. var mindt = jd; // Arbitrary starting value in days
  2063. var mini = 0; // index where we find the minimum
  2064. for(var i = 0 ; i < data.length ; i+=4){
  2065. // Find the nearest point to now
  2066. var dt = (jd-data[i]);
  2067. if(Math.abs(dt) < Math.abs(mindt)){ mindt = dt; mini = i; }
  2068. }
  2069. var dra,ddec,dmag,pos_2,pos_1,fract;
  2070. if(mindt >= 0){
  2071. pos_2 = mini+1+4;
  2072. pos_1 = mini+1;
  2073. fract = mindt/Math.abs(data[pos_2-1]-data[pos_1-1]);
  2074. }else{
  2075. pos_2 = mini+1;
  2076. pos_1 = mini+1-4;
  2077. fract = (1+(mindt)/Math.abs(data[pos_2-1]-data[pos_1-1]));
  2078. }
  2079. // We don't want to attempt to find positions beyond the edges of the array
  2080. if(pos_2 > data.length || pos_1 < 0){
  2081. dra = data[mini+1];
  2082. ddec = data[mini+2];
  2083. dmag = data[mini+3];
  2084. }else{
  2085. dra = (Math.abs(data[pos_2]-data[pos_1]) > 180) ? (data[pos_1]+(data[pos_2]+360-data[pos_1])*fract)%360 : (data[pos_1]+(data[pos_2]-data[pos_1])*fract)%360;
  2086. ddec = data[pos_1+1]+(data[pos_2+1]-data[pos_1+1])*fract;
  2087. dmag = data[pos_1+2]+(data[pos_2+2]-data[pos_1+2])*fract;
  2088. }
  2089. return { ra: dra, dec:ddec, mag:dmag}
  2090. }
  2091. VirtualSky.prototype.drawPlanets = function(){
  2092. if(!this.showplanets && !this.showplanetlabels && !this.showorbits) return this;
  2093. if(!this.planets || this.planets.length <= 0) return this;
  2094. var ra,dec,mag,pos,p;
  2095. var c = this.ctx;
  2096. var oldjd = this.jd;
  2097. this.jd = this.times.JD;
  2098. var colour = this.col.grey;
  2099. var maxl = this.maxLine();
  2100. for(p = 0 ; p < this.planets.length ; p++){
  2101. // We'll allow 2 formats here:
  2102. // [Planet name,colour,ra,dec,mag] or [Planet name,colour,[jd_1, ra_1, dec_1, mag_1, jd_2, ra_2, dec_2, mag_2....]]
  2103. if(!this.planets[p]) continue;
  2104. if(this.planets[p].length == 3){
  2105. // Find nearest JD
  2106. if(this.planets[p][2].length%4 == 0){
  2107. if(this.jd > this.planets[p][2][0] && this.jd < this.planets[p][2][(this.planets[p][2].length-4)]){
  2108. var interp = this.interpolate(this.jd,this.planets[p][2]);
  2109. ra = interp.ra;
  2110. dec = interp.dec;
  2111. mag = interp.mag;
  2112. }else{
  2113. continue; // We don't have data for this planet so skip to the next
  2114. }
  2115. }
  2116. }else{
  2117. ra = this.planets[p][2];
  2118. dec = this.planets[p][3];
  2119. }
  2120. pos = this.radec2xy(ra*this.d2r,dec*this.d2r);
  2121. if(!this.negative) colour = this.planets[p][1];
  2122. if(typeof colour==="string") c.strokeStyle = colour;
  2123. if((this.showplanets || this.showplanetlabels) && this.isVisible(pos.el) && mag < this.magnitude && !this.isPointBad(pos)){
  2124. var d = 0;
  2125. if(mag !== undefined){
  2126. d = 0.8*Math.max(3-mag/2, 0.5);
  2127. if(this.hasAtmos()) d *= Math.exp(-((90-pos.el)*this.d2r)*0.6);
  2128. }
  2129. if(d < 1.5) d = 1.5;
  2130. this.drawPlanet(pos.x,pos.y,d,colour,this.planets[p][0]);
  2131. }
  2132. if(this.showorbits && mag < this.magnitude){
  2133. c.beginPath();
  2134. c.lineWidth = 0.5
  2135. this.setFont();
  2136. c.lineWidth = 1;
  2137. var previous = {x:-1,y:-1,el:-1};
  2138. for(i = 0 ; i < this.planets[p][2].length-4 ; i+=4){
  2139. var point = this.radec2xy(this.planets[p][2][i+1]*this.d2r, this.planets[p][2][i+2]*this.d2r);
  2140. if(previous.x > 0 && previous.y > 0 && this.isVisible(point.el)){
  2141. c.moveTo(previous.x,previous.y);
  2142. // Basic error checking: points behind us often have very long lines so we'll zap them
  2143. if(Math.abs(point.x-previous.x) < maxl){
  2144. c.lineTo(point.x,point.y);
  2145. }
  2146. }
  2147. previous = point;
  2148. }
  2149. c.stroke();
  2150. }
  2151. }
  2152. // Sun & Moon
  2153. if(this.showplanets || this.showplanetlabels){
  2154. // Only recalculate the Moon's ecliptic position if the time has changed
  2155. if(oldjd != this.jd){
  2156. var p = this.moonPos(this.jd);
  2157. this.moon = p.moon;
  2158. this.sun = p.sun;
  2159. }
  2160. var pos;
  2161. // Draw the Sun
  2162. pos = this.ecliptic2xy(this.sun.lon*this.d2r,this.sun.lat*this.d2r,this.times.LST);
  2163. if(this.isVisible(pos.el) && !this.isPointBad(pos)) this.drawPlanet(pos.x,pos.y,5,this.col.sun,"sun");
  2164. // Draw Moon last as it is closest
  2165. pos = this.ecliptic2xy(this.moon.lon*this.d2r,this.moon.lat*this.d2r,this.times.LST);
  2166. if(this.isVisible(pos.el) && !this.isPointBad(pos)) this.drawPlanet(pos.x,pos.y,5,this.col.moon,"moon");
  2167. }
  2168. return this;
  2169. }
  2170. VirtualSky.prototype.drawPlanet = function(x,y,d,colour,label){
  2171. var c = this.ctx;
  2172. c.beginPath();
  2173. c.fillStyle = colour;
  2174. c.strokeStyle = colour;
  2175. c.moveTo(x+d,y+d);
  2176. if(this.showplanets) c.arc(x,y,d,0,Math.PI*2,true);
  2177. label = this.getPhrase('planets',label);
  2178. if(this.showplanetlabels) this.drawLabel(x,y,d,colour,label);
  2179. c.fill();
  2180. return this;
  2181. }
  2182. VirtualSky.prototype.drawText = function(txt,x,y){
  2183. this.ctx.beginPath();
  2184. this.ctx.fillText(txt,x,y);
  2185. return this.ctx.measureText(txt).width;
  2186. }
  2187. // Helper function. You'll need to wrap it with a this.ctx.beginPath() and a this.ctx.fill();
  2188. VirtualSky.prototype.drawLabel = function(x,y,d,colour,label){
  2189. if(label===undefined) return this;
  2190. var c = this.ctx;
  2191. if(colour.length > 0) c.fillStyle = colour;
  2192. c.lineWidth = 1.5;
  2193. var xoff = d;
  2194. if((this.polartype) && c.measureText) xoff = -c.measureText(label).width-3
  2195. if((this.polartype) && x < this.wide/2) xoff = d;
  2196. c.fillText(label,x+xoff,y-(d+2))
  2197. return this;
  2198. }
  2199. VirtualSky.prototype.drawConstellationLines = function(colour){
  2200. if(!(this.constellation.lines || this.constellation.labels)) return this;
  2201. if(!colour) colour = this.col.constellation;
  2202. var x = this.ctx;
  2203. x.beginPath();
  2204. x.strokeStyle = colour;
  2205. x.fillStyle = colour;
  2206. x.lineWidth = 0.75
  2207. var fontsize = this.fontsize();
  2208. this.setFont();
  2209. if(typeof this.lines!=="object") return this;
  2210. var pos,posa,posb,a,b,idx,l;
  2211. var maxl = this.maxLine();
  2212. for(var c = 0; c < this.lines.length; c++){
  2213. if(this.constellation.lines){
  2214. for(l = 3; l < this.lines[c].length; l+=2){
  2215. a = -1;
  2216. b = -1;
  2217. idx1 = ''+this.lines[c][l]+'';
  2218. idx2 = ''+this.lines[c][l+1]+'';
  2219. if(!this.hipparcos[idx1]){
  2220. for(s = 0; s < this.stars.length; s++){
  2221. if(this.stars[s][0] == this.lines[c][l]){
  2222. this.hipparcos[idx1] = s;
  2223. break;
  2224. }
  2225. }
  2226. }
  2227. if(!this.hipparcos[idx2]){
  2228. for(s = 0; s < this.stars.length; s++){
  2229. if(this.stars[s][0] == this.lines[c][l+1]){
  2230. this.hipparcos[idx2] = s;
  2231. break;
  2232. }
  2233. }
  2234. }
  2235. a = this.hipparcos[idx1];
  2236. b = this.hipparcos[idx2];
  2237. if(a >= 0 && b >= 0 && a < this.stars.length && b < this.stars.length){
  2238. posa = this.radec2xy(this.stars[a][2], this.stars[a][3]);
  2239. posb = this.radec2xy(this.stars[b][2], this.stars[b][3]);
  2240. if(this.isVisible(posa.el) && this.isVisible(posb.el)){
  2241. if(!this.isPointBad(posa) && !this.isPointBad(posb)){
  2242. // Basic error checking: constellations behind us often have very long lines so we'll zap them
  2243. if(Math.abs(posa.x-posb.x) < maxl && Math.abs(posa.y-posb.y) < maxl){
  2244. x.moveTo(posa.x,posa.y);
  2245. x.lineTo(posb.x,posb.y);
  2246. }
  2247. }
  2248. }
  2249. }
  2250. }
  2251. }
  2252. if(this.constellation.labels){
  2253. pos = this.radec2xy(this.lines[c][1]*this.d2r,this.lines[c][2]*this.d2r);
  2254. if(this.isVisible(pos.el)){
  2255. label = this.getPhrase('constellations',this.lines[c][0]);
  2256. xoff = (x.measureText) ? -x.measureText(label).width/2 : 0;
  2257. x.fillText(label,pos.x+xoff,pos.y-fontsize/2)
  2258. x.fill();
  2259. }
  2260. }
  2261. }
  2262. x.stroke();
  2263. return this;
  2264. }
  2265. // Draw the boundaries of constellations
  2266. // Input: colour (e.g. "rgb(255,255,0)")
  2267. // We should have all the boundary points stored in this.boundaries. As many of the constellations
  2268. // will share boundaries we don't want to bother drawing lines that we've already done so we will
  2269. // keep a record of the lines we've drawn as we go. As some segments may be large on the sky we will
  2270. // interpolate a few points between so that boundaries follow the curvature of the projection better.
  2271. // As the boundaries are in FK1 we will calculate the J2000 positions once and keep them cached as
  2272. // this speeds up the re-drawing as the user moves the sky. We assume that the user's session << time
  2273. // between epochs.
  2274. VirtualSky.prototype.drawConstellationBoundaries = function(colour){
  2275. if(!this.constellation.boundaries) return this;
  2276. if(!colour) colour = this.col.constellationboundary;
  2277. this.ctx.beginPath();
  2278. this.ctx.strokeStyle = colour;
  2279. this.ctx.fillStyle = colour;
  2280. this.ctx.lineWidth = 0.75;
  2281. if(typeof this.boundaries!=="object") return this;
  2282. var posa, posb, a, b, l, c, d, atob,btoa, move, i, j, ra,dc,dra,ddc,b3;
  2283. // Keys defining a line in both directions
  2284. atob = "";
  2285. btoa = "";
  2286. var n = 5;
  2287. var maxl = this.maxLine(5);
  2288. // Create a holder for the constellation boundary points i.e. a cache of position calculations
  2289. if(!this.constellation.bpts) this.constellation.bpts = new Array(this.boundaries.length);
  2290. // We'll record which boundary lines we've already processed
  2291. var cbdone = [];
  2292. if(this.constellation.boundaries){
  2293. for(c = 0; c < this.boundaries.length; c++){
  2294. if(typeof this.boundaries!=="string" && c < this.boundaries.length){
  2295. if(this.constellation.bpts[c]){
  2296. // Use the old array
  2297. var points = this.constellation.bpts[c];
  2298. }else{
  2299. // Create a new array of points
  2300. var points = [];
  2301. for(l = 1; l < this.boundaries[c].length; l+=2){
  2302. b = [this.boundaries[c][l],this.boundaries[c][l+1]];
  2303. if(a){
  2304. atob = a[0]+','+a[1]+'-'+b[0]+','+b[1];
  2305. btoa = b[0]+','+b[1]+'-'+a[0]+','+a[1];
  2306. }
  2307. if(l > 1){
  2308. move = (cbdone[atob] || cbdone[btoa]);
  2309. ra = (b[0]-a[0])%360;
  2310. if(ra > 180) ra = ra-360;
  2311. if(ra < -180) ra = ra+360;
  2312. dc = (b[1]-a[1]);
  2313. // If we've already done this line we'll only calculate
  2314. // two points on the line otherwise we'll do 5
  2315. n = (move) ? 5 : 2;
  2316. if(ra/2 > n) n = parseInt(ra);
  2317. if(dc/2 > n) n = parseInt(dc);
  2318. dra = ra/n;
  2319. ddc = dc/n;
  2320. for(var i = 1; i <= n; i++){
  2321. ra = a[0]+(i*dra);
  2322. if(ra < 0) ra += 360;
  2323. dc = a[1]+(i*ddc);
  2324. // Convert to J2000
  2325. d = this.fk1tofk5(ra*this.d2r,dc*this.d2r);
  2326. points.push([d[0],d[1],move]);
  2327. }
  2328. }
  2329. // Mark this line as drawn
  2330. cbdone[atob] = true;
  2331. cbdone[btoa] = true;
  2332. a = b;
  2333. }
  2334. this.constellation.bpts[c] = points;
  2335. }
  2336. posa = null;
  2337. // Now loop over joining the points
  2338. for(i = 0; i <= points.length; i++){
  2339. j = (i == points.length) ? 0 : i;
  2340. posb = this.radec2xy(points[j][0],points[j][1]);
  2341. if(posa && this.isVisible(posa.el) && this.isVisible(posb.el) && points[j][2]){
  2342. if(!this.isPointBad(posa) && !this.isPointBad(posb)){
  2343. // Basic error checking: constellations behind us often have very long lines so we'll zap them
  2344. if(Math.abs(posa.x-posb.x) < maxl && Math.abs(posa.y-posb.y) < maxl){
  2345. this.ctx.moveTo(posa.x,posa.y);
  2346. this.ctx.lineTo(posb.x,posb.y);
  2347. }
  2348. }
  2349. }
  2350. posa = posb;
  2351. }
  2352. }
  2353. }
  2354. cbdone = [];
  2355. }
  2356. this.ctx.stroke();
  2357. return this;
  2358. }
  2359. VirtualSky.prototype.drawGalaxy = function(colour){
  2360. if(!this.galaxy || !this.showgalaxy) return this;
  2361. if(!colour) colour = this.col.galaxy;
  2362. this.ctx.beginPath();
  2363. this.ctx.strokeStyle = colour;
  2364. this.ctx.fillStyle = colour;
  2365. this.ctx.lineWidth = 1;
  2366. if(typeof this.boundaries!=="object") return this;
  2367. var p, pa, pb, i, c, old, maxl;
  2368. maxl = this.maxLine(5);
  2369. for(c = 0; c < this.galaxy.length; c++){
  2370. // We will convert all the galaxy outline coordinates to radians
  2371. if(!this.galaxyprocessed) for(i = 1; i < this.galaxy[c].length; i++) this.galaxy[c][i] *= this.d2r;
  2372. // Get a copy of the current shape
  2373. p = this.galaxy[c].slice(0);
  2374. // Get the colour (first element)
  2375. p.shift();
  2376. // Set the initial point to null
  2377. pa = null;
  2378. // Now loop over joining the points
  2379. for(i = 0; i < p.length; i+=2){
  2380. pb = this.radec2xy(p[i], p[i+1]);
  2381. if(pa){
  2382. // Basic error checking: if the line is very long we need to normalize to other side of sky
  2383. if(Math.abs(pa.x-pb.x) < maxl && Math.abs(pa.y-pb.y) < maxl){
  2384. this.ctx.moveTo(pa.x,pa.y);
  2385. this.ctx.lineTo(pb.x,pb.y);
  2386. }
  2387. }
  2388. pa = pb;
  2389. }
  2390. }
  2391. // We've converted the galaxy to radians
  2392. this.galaxyprocessed = true;
  2393. this.ctx.stroke();
  2394. return this;
  2395. }
  2396. VirtualSky.prototype.drawMeteorShowers = function(colour){
  2397. if(!this.meteorshowers || typeof this.showers==="string") return this;
  2398. if(!colour) colour = this.col.showers;
  2399. var shower, pos, label, xoff, c, d, p, start, end, dra, ddc, f;
  2400. c = this.ctx;
  2401. c.beginPath();
  2402. c.strokeStyle = colour;
  2403. c.fillStyle = colour;
  2404. c.lineWidth = 0.75;
  2405. var fs = this.fontsize();
  2406. this.setFont();
  2407. var y = this.clock.getFullYear();
  2408. for(var s in this.showers){
  2409. d = this.showers[s].date;
  2410. p = this.showers[s].pos;
  2411. start = new Date(y,d[0][0]-1,d[0][1]);
  2412. end = new Date(y,d[1][0]-1,d[1][1]);
  2413. if(start > end && this.clock < start) start = new Date(y-1,d[0][0]-1,d[0][1]);
  2414. if(this.clock > start && this.clock < end){
  2415. dra = (p[1][0]-p[0][0]);
  2416. ddc = (p[1][1]-p[0][1]);
  2417. f = (this.clock-start)/(end-start);
  2418. pos = this.radec2xy((this.showers[s].pos[0][0]+(dra*f))*this.d2r,(this.showers[s].pos[0][1]+(ddc*f))*this.d2r);
  2419. if(this.isVisible(pos.el)){
  2420. label = this.htmlDecode(this.showers[s].name);
  2421. xoff = (c.measureText) ? -c.measureText(label).width/2 : 0;
  2422. c.moveTo(pos.x+2,pos.y);
  2423. c.arc(pos.x,pos.y,2,0,Math.PI*2,true);
  2424. c.fillText(label,pos.x+xoff,pos.y-fs/2);
  2425. }
  2426. }
  2427. }
  2428. c.fill();
  2429. return this;
  2430. }
  2431. VirtualSky.prototype.drawEcliptic = function(colour){
  2432. if(!this.ecliptic) return this;
  2433. if(!colour || typeof colour!="string") colour = this.col.ec;
  2434. var c = this.ctx;
  2435. var step = 2*this.d2r;
  2436. c.beginPath();
  2437. c.strokeStyle = colour;
  2438. c.lineWidth = 3;
  2439. var maxl = this.maxLine();
  2440. var old = {x:-1,y:-1,moved:false};
  2441. for(var a = 0 ; a < Math.PI*2 ; a += step) old = joinpoint(this,"ec",a,0,old,maxl);
  2442. c.stroke();
  2443. return this;
  2444. }
  2445. VirtualSky.prototype.drawMeridian = function(colour){
  2446. if(!this.meridian) return this;
  2447. if(!colour || typeof colour!="string") colour = this.col.meridian;
  2448. var c = this.ctx;
  2449. var a, b;
  2450. var minb = 0;
  2451. var maxb = (typeof this.projection.maxb==="number") ? this.projection.maxb*this.d2r : Math.PI/2;
  2452. var step = 2*this.d2r;
  2453. var maxl = this.maxLine();
  2454. c.beginPath();
  2455. c.strokeStyle = colour;
  2456. c.lineWidth = 2;
  2457. var old = {x:-1,y:-1,moved:false};
  2458. for(b = minb, a = 0; b <= maxb ; b+= step) old = joinpoint(this,"az",Math.PI,b,old,maxl);
  2459. for(b = maxb, a = 0; b >= minb ; b-= step) old = joinpoint(this,"az",0,b,old,maxl);
  2460. c.stroke();
  2461. return this;
  2462. }
  2463. // type can be "az" or "eq"
  2464. VirtualSky.prototype.drawGridlines = function(type,step,colour){
  2465. if(!type || !this.grid[type]) return this;
  2466. if(typeof colour!=="string") colour = this.col[type];
  2467. if(typeof step!=="number") step = this.grid.step;
  2468. var maxb,minb,x,y,a,b,pos,c,oldx,oldy,bstep2,ra,dc;
  2469. c = this.ctx;
  2470. oldx = 0;
  2471. oldy = 0;
  2472. c.beginPath();
  2473. c.strokeStyle = colour;
  2474. c.lineWidth = 1.0;
  2475. bstep = 2;
  2476. if(type=="az"){
  2477. maxb = (typeof this.projection.maxb==="number") ? this.projection.maxb : 90-bstep;
  2478. minb = 0;
  2479. }else{
  2480. maxb = 90-bstep;
  2481. minb = -maxb;
  2482. }
  2483. var maxl = this.maxLine(5);
  2484. old = {x:-1,y:-1,moved:false};
  2485. step *= this.d2r;
  2486. bstep *= this.d2r;
  2487. minb *= this.d2r;
  2488. maxb *= this.d2r;
  2489. // Draw grid lines in elevation/declination/latitude
  2490. for(a = 0 ; a < Math.PI*2 ; a += step){
  2491. old.moved = false;
  2492. for(b = minb; b <= maxb ; b+= bstep) old = joinpoint(this,type,a,b,old,maxl);
  2493. }
  2494. c.stroke();
  2495. c.beginPath();
  2496. if(type=="az"){
  2497. minb = 0;
  2498. maxb = 90-bstep*this.r2d;
  2499. }else{
  2500. minb = -90+step*this.r2d;
  2501. maxb = 90;
  2502. }
  2503. minb *= this.d2r;
  2504. maxb *= this.d2r;
  2505. old = {x:-1,y:-1,moved:false};
  2506. // Draw grid lines in azimuth/RA/longitude
  2507. for(b = minb; b < maxb ; b += step){
  2508. old.moved = false;
  2509. for(a = 0 ; a <= 2*Math.PI ; a += bstep) old = joinpoint(this,type,a,b,old,maxl);
  2510. }
  2511. c.stroke();
  2512. return this;
  2513. }
  2514. VirtualSky.prototype.drawCardinalPoints = function(){
  2515. if(!this.cardinalpoints) return this;
  2516. var i,x,y,pos,ang,f,m,r;
  2517. var azs = new Array(0,90,180,270);
  2518. var d = [this.getPhrase('N'),this.getPhrase('E'),this.getPhrase('S'),this.getPhrase('W')];
  2519. var pt = 15;
  2520. var c = this.ctx;
  2521. c.beginPath();
  2522. c.fillStyle = this.col.cardinal;
  2523. var fontsize = this.fontsize();
  2524. for(i = 0 ; i < azs.length ; i++){
  2525. if(c.measureText){
  2526. m = c.measureText(d[i]);
  2527. r = (m.width > fontsize) ? m.width/2 : fontsize/2;
  2528. }else r = fontsize/2;
  2529. ang = (azs[i]-this.az_off)*this.d2r;
  2530. if(this.polartype){
  2531. f = (this.tall/2) - r*1.5;
  2532. x = -f*Math.sin(ang);
  2533. y = -f*Math.cos(ang);
  2534. x = isFinite(x) ? this.wide/2 + x - r : 0;
  2535. y = isFinite(y) ? this.tall/2 + y + r: 0;
  2536. }else{
  2537. pos = this.azel2xy(ang,0,this.wide,this.tall);
  2538. x = isFinite(pos.x) ? pos.x - r : 0;
  2539. y = isFinite(pos.y) ? pos.y - pt/2 : 0;
  2540. if(x < 0 || x > this.wide-pt) x = -r;
  2541. }
  2542. if(x > 0) c.fillText(d[i],x,y);
  2543. }
  2544. c.fill();
  2545. return this;
  2546. }
  2547. // Assume decimal Ra/Dec
  2548. VirtualSky.prototype.highlight = function(i,colour){
  2549. var p = this.pointers[i];
  2550. if(this.pointers[i].ra && this.pointers[i].dec){
  2551. colour = p.colour || colour || "rgba(255,0,0,1)";
  2552. if(this.negative) colour = this.getNegative(colour);
  2553. var pos = this.radec2xy(p.ra*this.d2r, p.dec*this.d2r);
  2554. var c = this.ctx;
  2555. if(this.isVisible(pos.el)){
  2556. p.az = pos.az;
  2557. p.el = pos.el;
  2558. p.x = pos.x;
  2559. p.y = pos.y;
  2560. c.fillStyle = colour;
  2561. c.strokeStyle = colour;
  2562. c.beginPath();
  2563. // Draw a square to distinguish from other objects
  2564. // c.arc(p.x,p.y,p.d/2,0,2*Math.PI);
  2565. c.fillRect(p.x-p.d/2,p.y-p.d/2,p.d,p.d);
  2566. c.fill();
  2567. this.drawLabel(p.x,p.y,p.d,colour,p.label);
  2568. }
  2569. }
  2570. return this;
  2571. }
  2572. // Function to join the dots
  2573. function joinpoint(s,type,a,b,old,maxl){
  2574. var x,y,show,c,pos;
  2575. c = s.ctx;
  2576. if(type=="az") pos = s.azel2xy((a-s.az_off*s.d2r),b,s.wide,s.tall);
  2577. else if(type=="eq") pos = s.radec2xy(a,b);
  2578. else if(type=="ec") pos = s.ecliptic2xy(a,b,s.times.LST);
  2579. else if(type=="gal") pos = s.gal2xy(a,b);
  2580. x = pos.x;
  2581. y = pos.y;
  2582. if(type=="az") show = true;
  2583. else show = ((s.isVisible(pos.el)) ? true : false);
  2584. if(show && isFinite(x) && isFinite(y)){
  2585. if(type=="az"){
  2586. if(!old.moved || Math.sqrt(Math.pow(old.x-x,2)+Math.pow(old.y-y,2)) > s.tall/2) c.moveTo(x,y);
  2587. c.lineTo(x,y);
  2588. old.moved = true;
  2589. }else{
  2590. // If the last point on s contour is more than a canvas width away
  2591. // it is probably supposed to be behind us so we won't draw a line
  2592. if(!old.moved || Math.sqrt(Math.pow(old.x-x,2)+Math.pow(old.y-y,2)) > maxl){
  2593. c.moveTo(x,y);
  2594. old.moved = true;
  2595. }else c.lineTo(x,y);
  2596. }
  2597. old.x = x;
  2598. old.y = y;
  2599. }
  2600. return old;
  2601. }
  2602. VirtualSky.prototype.maxLine = function(f){
  2603. if(this.projection.id==="gnomic") return this.tall;
  2604. if(typeof f!=="number") f = 3;
  2605. return this.tall/f;
  2606. }
  2607. // Expects a latitude,longitude string (comma separated)
  2608. VirtualSky.prototype.setGeo = function(pos){
  2609. if(typeof pos!=="string") return this;
  2610. pos = pos.split(',');
  2611. this.setLatitude(pos[0]);
  2612. this.setLongitude(pos[1]);
  2613. return this;
  2614. }
  2615. // Input: latitude (deg)
  2616. VirtualSky.prototype.setLatitude = function(l){
  2617. this.latitude = inrangeEl(parseFloat(l)*this.d2r);
  2618. return this;
  2619. }
  2620. // Input: longitude (deg)
  2621. VirtualSky.prototype.setLongitude = function(l){
  2622. this.longitude = parseFloat(l)*this.d2r;
  2623. while(this.longitude <= -Math.PI) this.longitude += 2*Math.PI;
  2624. while(this.longitude > Math.PI) this.longitude -= 2*Math.PI;
  2625. return this;
  2626. }
  2627. VirtualSky.prototype.setRADec = function(r,d){
  2628. return this.setRA(r).setDec(d);
  2629. }
  2630. VirtualSky.prototype.setRA = function(r){
  2631. this.ra_off = (r%360)*this.d2r;
  2632. return this;
  2633. }
  2634. VirtualSky.prototype.setDec = function(d){
  2635. this.dc_off = d*this.d2r
  2636. return this;
  2637. }
  2638. // Pan the view to the specified RA,Dec
  2639. // Inputs: RA (deg), Dec (deg), duration (seconds)
  2640. VirtualSky.prototype.panTo = function(ra,dec,s){
  2641. if(!s) s = 1000;
  2642. if(typeof ra!=="number" || typeof dec!=="number") return this;
  2643. this.panning = { s: { ra:this.ra_off*this.r2d, dec:this.dc_off*this.r2d }, e: { ra: ra, dec: dec}, duration: s, start: new Date() };
  2644. this.panning.dr = this.panning.e.ra-this.panning.s.ra;
  2645. this.panning.dd = this.panning.e.dec-this.panning.s.dec;
  2646. if(this.panning.dr > 180) this.panning.dr = -(360-this.panning.dr);
  2647. if(this.panning.dr < -180) this.panning.dr = (360+this.panning.dr);
  2648. return this.panStep();
  2649. }
  2650. // shim layer with setTimeout fallback
  2651. window.requestAnimFrame = (function(){
  2652. return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); };
  2653. })();
  2654. // Animation step for the panning
  2655. VirtualSky.prototype.panStep = function(){
  2656. var ra,dc;
  2657. var now = new Date();
  2658. var t = (now - this.panning.start)/this.panning.duration;
  2659. ra = this.panning.s.ra + (this.panning.dr)*(t);
  2660. dc = this.panning.s.dec + (this.panning.dd)*(t);
  2661. // Still animating
  2662. if(t < 1){
  2663. // update and draw
  2664. this.setRADec(ra,dc).draw();
  2665. var _obj = this;
  2666. // request new frame
  2667. requestAnimFrame(function() { _obj.panStep(); });
  2668. }else{
  2669. // We've ended
  2670. this.setRADec(this.panning.e.ra,this.panning.e.dec).draw();
  2671. }
  2672. return this;
  2673. }
  2674. VirtualSky.prototype.liveSky = function(pos){
  2675. this.islive = !this.islive;
  2676. if(this.islive) interval = window.setInterval(function(sky){ sky.setClock('now'); },1000,this);
  2677. else{
  2678. if(interval!==undefined) clearInterval(interval);
  2679. }
  2680. return this;
  2681. }
  2682. VirtualSky.prototype.start = function(){
  2683. this.islive = true;
  2684. // Clear existing interval
  2685. if(interval!==undefined) clearInterval(interval);
  2686. interval = window.setInterval(function(sky){ sky.setClock('now'); },1000,this);
  2687. }
  2688. VirtualSky.prototype.stop = function(){
  2689. this.islive = false;
  2690. // Clear existing interval
  2691. if(interval!==undefined) clearInterval(interval);
  2692. }
  2693. // Increment the clock by the amount specified
  2694. VirtualSky.prototype.advanceTime = function(by,wait){
  2695. if(by===undefined){
  2696. this.updateClock(new Date());
  2697. }else{
  2698. by = parseFloat(by);
  2699. if(!wait) wait = 1000/this.fps; // ms between frames
  2700. var fn = function(vs,by){ vs.setClock(by); };
  2701. clearInterval(this.interval_time)
  2702. clearInterval(this.interval_calendar)
  2703. this.interval_time = window.setInterval(fn,wait,this,by);
  2704. // Whilst animating we'll periodically check to see if the calendar events need calling
  2705. this.interval_calendar = window.setInterval(function(vs){ vs.calendarUpdate(); },1000,this);
  2706. }
  2707. return this;
  2708. }
  2709. // Send a Javascript Date() object and update the clock
  2710. VirtualSky.prototype.updateClock = function(d){
  2711. this.clock = d;
  2712. this.times = this.astronomicalTimes();
  2713. }
  2714. // Call any calendar-based events
  2715. VirtualSky.prototype.calendarUpdate = function(){
  2716. for(var e = 0; e < this.calendarevents.length; e++){
  2717. if(is(this.calendarevents[e],"function")) this.calendarevents[e].call(this);
  2718. }
  2719. return this;
  2720. }
  2721. VirtualSky.prototype.setClock = function(seconds){
  2722. if(seconds === undefined){
  2723. return this;
  2724. }if(typeof seconds==="string"){
  2725. seconds = convertTZ(seconds);
  2726. if(!this.input.clock){
  2727. if(seconds==="now") this.updateClock(new Date());
  2728. else this.updateClock(new Date(seconds));
  2729. }else{
  2730. this.updateClock((typeof this.input.clock==="string") ? this.input.clock.replace(/%20/g,' ') : this.input.clock);
  2731. if(typeof this.clock==="string") this.updateClock(new Date(this.clock));
  2732. }
  2733. }else if(typeof seconds==="object"){
  2734. this.updateClock(seconds);
  2735. }else{
  2736. this.updateClock(new Date(this.clock.getTime() + seconds*1000));
  2737. }
  2738. this.draw();
  2739. return this;
  2740. }
  2741. VirtualSky.prototype.toggleAtmosphere = function(){ this.gradient = !this.gradient; this.draw(); return this; }
  2742. VirtualSky.prototype.toggleStars = function(){ this.showstars = !this.showstars; this.draw(); return this; }
  2743. VirtualSky.prototype.toggleStarLabels = function(){ this.showstarlabels = !this.showstarlabels; this.draw(); return this; }
  2744. VirtualSky.prototype.toggleNegative = function(){ this.negative = !this.negative; this.col = this.colours[(this.negative ? "negative" : "normal")]; this.draw(); return this; }
  2745. VirtualSky.prototype.toggleConstellationLines = function(){ this.constellation.lines = !this.constellation.lines; this.draw(); return this; }
  2746. VirtualSky.prototype.toggleConstellationBoundaries = function(){ this.constellation.boundaries = !this.constellation.boundaries; this.draw(); return this; }
  2747. VirtualSky.prototype.toggleConstellationLabels = function(){ this.constellation.labels = !this.constellation.labels; this.draw(); return this; }
  2748. VirtualSky.prototype.toggleMeteorShowers = function(){ this.meteorshowers = !this.meteorshowers; this.draw(); return this; }
  2749. VirtualSky.prototype.toggleCardinalPoints = function(){ this.cardinalpoints = !this.cardinalpoints; this.draw(); return this; }
  2750. VirtualSky.prototype.toggleGridlinesAzimuthal = function(){ this.grid.az = !this.grid.az; this.draw(); return this; }
  2751. VirtualSky.prototype.toggleGridlinesEquatorial = function(){ this.grid.eq = !this.grid.eq; this.draw(); return this; }
  2752. VirtualSky.prototype.toggleGridlinesGalactic = function(){ this.grid.gal = !this.grid.gal; this.draw(); return this; }
  2753. VirtualSky.prototype.toggleEcliptic = function(){ this.ecliptic = !this.ecliptic; this.draw(); return this; }
  2754. VirtualSky.prototype.toggleMeridian = function(){ this.meridian = !this.meridian; this.draw(); return this; }
  2755. VirtualSky.prototype.toggleGround = function(){ this.ground = !this.ground; this.draw(); return this; }
  2756. VirtualSky.prototype.toggleGalaxy = function(){ this.showgalaxy = !this.showgalaxy; this.draw(); return this; }
  2757. VirtualSky.prototype.toggleMeteorShowers = function(){ this.meteorshowers = !this.meteorshowers; this.draw(); return this; }
  2758. VirtualSky.prototype.togglePlanetHints = function(){ this.showplanets = !this.showplanets; this.draw(); return this; }
  2759. VirtualSky.prototype.togglePlanetLabels = function(){ this.showplanetlabels = !this.showplanetlabels; this.draw(); return this; }
  2760. VirtualSky.prototype.toggleOrbits = function(){ this.showorbits = !this.showorbits; this.draw(); return this; }
  2761. VirtualSky.prototype.toggleAzimuthMove = function(az){
  2762. if(this.az_step===0){
  2763. this.az_step = (typeof az==="number") ? az : -1;
  2764. this.moveIt();
  2765. }else{
  2766. this.az_step = 0;
  2767. if(this.timer_az!==undefined) clearTimeout(this.timer_az);
  2768. }
  2769. return this;
  2770. }
  2771. VirtualSky.prototype.addPointer = function(input){
  2772. // Check if we've already added this
  2773. var style,url,img,label,credit;
  2774. var matched = -1;
  2775. var p;
  2776. for(var i = 0 ; i < this.pointers.length ; i++){
  2777. if(this.pointers[i].ra == input.ra && this.pointers[i].dec == input.dec && this.pointers[i].label == input.label) matched = i;
  2778. }
  2779. // Hasn't been added already
  2780. if(matched < 0){
  2781. input.ra *= 1; // Correct for a bug
  2782. input.dec *= 1;
  2783. i = this.pointers.length;
  2784. p = input;
  2785. p.d = is(p.d, "number")?p.d:5;
  2786. if(typeof p.html !== "string"){
  2787. style = p.style || "width:128px;height:128px;";
  2788. url = p.url || "http://server1.wikisky.org/v2?ra="+(p.ra/15)+"&de="+(p.dec)+"&zoom=6&img_source=DSS2";
  2789. img = p.img || 'http://server7.sky-map.org/imgcut?survey=DSS2&w=128&h=128&ra='+(p.ra/15)+'&de='+p.dec+'&angle=0.25&output=PNG';
  2790. label = p.credit || "View in Wikisky";
  2791. credit = p.credit || "DSS2/Wikisky";
  2792. p.html = p.html ||
  2793. '<div class="virtualsky_infocredit">'+
  2794. '<a href="'+url+'" style="color: white;">'+credit+'</a>'+
  2795. '</div>'+
  2796. '<a href="'+url+'" style="display:block;'+style+'">'+
  2797. '<img src="'+img+'" style="border:0px;'+style+'" title="'+label+'" />'+
  2798. '</a>';
  2799. }
  2800. this.pointers[i] = p;
  2801. }
  2802. return (this.pointers.length);
  2803. }
  2804. VirtualSky.prototype.changeAzimuth = function(inc){
  2805. this.az_off += (typeof inc==="number") ? inc : 5;
  2806. this.draw();
  2807. return this;
  2808. }
  2809. VirtualSky.prototype.moveIt = function(){
  2810. // Send 'this' context to the setTimeout function so we can redraw
  2811. this.timer_az = window.setTimeout(function(mysky){ mysky.az_off += mysky.az_step; mysky.draw(); mysky.moveIt(); },100,this);
  2812. return this;
  2813. }
  2814. VirtualSky.prototype.spinIt = function(tick,wait){
  2815. if(typeof tick==="number") this.spin = (tick == 0) ? 0 : (this.spin+tick);
  2816. else{
  2817. var t = 1.0/this.fps;
  2818. var s = 2;
  2819. // this.spin is the number of seconds to update the clock by
  2820. if(this.spin == 0) this.spin = (tick == "up") ? t : -t;
  2821. else{
  2822. if(Math.abs(this.spin) < 1) s *= 2;
  2823. if(this.spin > 0) this.spin = (tick == "up") ? (this.spin*s) : (this.spin/s);
  2824. else if(this.spin < 0) this.spin = (tick == "up") ? (this.spin/s) : (this.spin*s);
  2825. if(this.spin < t && this.spin > -t) this.spin = 0;
  2826. }
  2827. }
  2828. if(this.interval_time!==undefined)
  2829. clearInterval(this.interval_time);
  2830. if(this.spin != 0)
  2831. this.advanceTime(this.spin,wait);
  2832. return this;
  2833. }
  2834. VirtualSky.prototype.getOffset = function(el){
  2835. var _x = 0;
  2836. var _y = 0;
  2837. while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
  2838. _x += el.offsetLeft - el.scrollLeft;
  2839. _y += el.offsetTop - el.scrollTop;
  2840. el = el.parentNode;
  2841. }
  2842. return { top: _y, left: _x };
  2843. }
  2844. VirtualSky.prototype.getJD = function(clock) {
  2845. // The Julian Date of the Unix Time epoch is 2440587.5
  2846. if(!clock) clock = this.clock;
  2847. return ( clock.getTime() / 86400000.0 ) + 2440587.5;
  2848. }
  2849. VirtualSky.prototype.getNegative = function(colour){
  2850. var end = (colour.indexOf("rgb") == 0) ? (colour.lastIndexOf(")")) : 0;
  2851. if(end == 0) return colour;
  2852. var rgb = colour.substring(colour.indexOf("(")+1,end).split(",");
  2853. return (rgb.length==3) ? ('rgb('+(255-rgb[0])+','+(255-rgb[1])+','+(255-rgb[2])+')') : ('rgba('+(255-rgb[0])+','+(255-rgb[1])+','+(255-rgb[2])+','+(rgb[3])+')');
  2854. }
  2855. // Calculate the Great Circle angular distance (in radians) between two points defined by d1,l1 and d2,l2
  2856. VirtualSky.prototype.greatCircle = function(l1,d1,l2,d2){
  2857. return Math.acos(Math.cos(d1)*Math.cos(d2)*Math.cos(l1-l2)+Math.sin(d1)*Math.sin(d2));
  2858. }
  2859. // Bind events
  2860. VirtualSky.prototype.bind = function(ev,fn){
  2861. if(typeof ev!=="string" || typeof fn!=="function")
  2862. return this;
  2863. if(this.events[ev])
  2864. this.events[ev].push(fn);
  2865. else this.events[ev] = [fn];
  2866. return this;
  2867. }
  2868. // Trigger a defined event with arguments. This is meant for internal use
  2869. // sky.trigger("zoom",args)
  2870. VirtualSky.prototype.trigger = function(ev,args){
  2871. if(typeof ev!=="string") return;
  2872. if(typeof args!=="object") args = {};
  2873. var o = [];
  2874. var _obj = this;
  2875. if(typeof this.events[ev]==="object")
  2876. for(i = 0 ; i < this.events[ev].length ; i++)
  2877. if(typeof this.events[ev][i]==="function")
  2878. o.push(this.events[ev][i].call(_obj,args))
  2879. if(o.length > 0) return o
  2880. }
  2881. // Some useful functions
  2882. function convertTZ(s){
  2883. function formatHour(h){
  2884. var s = (h >= 0 ? "+" : "-");
  2885. h = Math.abs(h);
  2886. var m = (h - Math.floor(h))*60;
  2887. var h = Math.floor(h);
  2888. return s+(h < 10 ? "0"+h : h)+(m < 10 ? "0"+m : m);
  2889. }
  2890. var tzs = { A:1, ACDT:10.5, ACST:9.5, ADT:-3, AEDT:11, AEST:10, AKDT:-8, AKST:-9,
  2891. AST:-4, AWST:8, B:2, BST:1, C:3, CDT:-5, CEDT:2, CEST:2, CET:1, CST:-6, CXT:7,
  2892. D:4, E:5, EDT:-4, EEDT:3, EEST:3, EET:2, EST:-5, F:6, G:7, GMT:0, H:8, HAA:-3,
  2893. HAC:-5, HADT:-9, HAE:-4, HAP:-7, HAR:-6, HAST:-10, HAT:-2.5, HAY:-8, HNA:-4, HNC:-6,
  2894. HNE:-5, HNP:-8, HNR:-7, HNT:-3.5, HNY:-9, I:9, IST:9, IST:1, JST:9, K:10, L:11,
  2895. M:12, MDT:-6, MESZ:2, MEZ:1, MST:-7, N:-1, NDT:-2.5, NFT:11.5, NST:-3.5, O:-2, P:-3,
  2896. PDT:-7, PST:-8, Q:-4, R:-5, S:-6, T:-7, U:-8, UTC:0, UT:0, V:-9, W:-10, WEDT:1, WEST:1,
  2897. WET:0, WST:8, X:-11, Y:-12, Z:0 }
  2898. // Get location of final space character
  2899. var i = s.lastIndexOf(' ');
  2900. // Replace the time zone with the +XXXX version
  2901. if(i > 0 && tzs[s.substr(i+1)]){
  2902. return s.substring(0,i)+" "+formatHour(tzs[s.substr(i+1)]);
  2903. }
  2904. return s;
  2905. }
  2906. $.virtualsky = function(placeholder,input) {
  2907. if(typeof input==="object") input.container = placeholder;
  2908. else {
  2909. if(typeof placeholder==="string") input = { container: placeholder };
  2910. else input = placeholder;
  2911. }
  2912. input.plugins = $.virtualsky.plugins;
  2913. return new VirtualSky(input);
  2914. };
  2915. $.virtualsky.plugins = [];
  2916. })(jQuery);