// drawing functions
// revised by Pierluigi Panunzi - 2008

var winw = 700, winh = 220;
var djupx=0;
var jupx = winw/2, jupy = winh/2;	// jupiter fixed position
var dxl = 4.66; // per djupx=0
var dxr = 4.66; // per djupx=0
//var dx = 8; // per djupx=250
var xbig = 75; xsmall = 12;	// pixels per jup radius
var zoom = false;	// true if zoomed-in
var satvisible = "#fff", sateclipsed = "#03a";	// colours of satellites
var cont;	// shortcut to container <div> node
var jup,jupi,grs,grsi;	// <div> and <img> nodes
var moon = new Array();	// <div> nodes for satellites
var shad = new Array();	// <div> nodes for shadows


function textelem(label,x,y,l,d) {	// constructor for text-elements
	this.label = label;
	this.x = x;
	this.y = y;
	this.l = l;	// lenght of value
	this.d = d;	// digits after comma
	this.node = document.createElement("p");
	this.node.style.position = "absolute";
	this.node.style.margin = 0 + "px";
	this.node.style.left = x + "px";
	this.node.style.top = y + "px";
	this.node.style.color = "gray";
	this.node.style.fontFamily = "tahoma";
	this.node.style.fontSize = "11px";
	this.node.style.whiteSpace = "pre";
	this.t = document.createTextNode(label);
	this.node.appendChild(this.t);
}

var ALT = 0, AZ = 1, SUNALT = 2, SYS1 = 3, SYS2 = 4, DE = 5, TIMER = 5;

var texts = new Array(
				new textelem("Azm Giove :",10,winh-15,6,1),
				new textelem("Alt Giove :",120,winh-15,6,1),
				new textelem("Alt Sole  :",220,winh-15,6,1)
				,new textelem("",winw-100,winh-30,6,1),
				new textelem("",winw-100,winh-15,6,1)
//				,new textelem("dt  :",winw-120,winh-45,6,3)	// uncomment to enable timer
				);


function drawJup() {
	if (!jup) {	// if node doesn't exist, create it
		jup = document.createElement("div");
		jup.style.position = "absolute";
		jup.style.zIndex = "5";
		jupi = document.createElement("img");
		jup.appendChild(jupi);
		cont.appendChild(jup);
	}
	jupi.setAttribute("src",zoom?"giove1.png":"giove2.png");
	jup.style.left = jupx + djupx - (zoom?xbig+5:xsmall+1) + "px";
	jup.style.top = jupy - (zoom?xbig+5:xsmall+1) + "px";
}

	
function drawGRS() {	// draw the Great Red Spot
	var scale = zoom?xbig:xsmall;
	if (!grs) {	// if element doesn't exist
		grs = document.createElement("div");
		grs.style.position = "absolute";
		grs.style.zIndex = "6";
		grs.style.top = jupy+13 + "px";
		grsi = document.createElement("img");
		grsi.setAttribute("src","grs.png");
		grs.appendChild(grsi);
		cont.appendChild(grs);
	}
	if (!zoom) {
		grs.style.visibility = "hidden"; 
		return;
	}	// only show on zoomed image
	var sqz = cosd(jupiter.grs_a);	// how much is the GRS 'squeezed'?
	var xg = 0.94*sind(jupiter.grs_a);	// 0.94 = half width of Jupiter at GRS latitude
	grs.style.left = Math.round(jupx+djupx+xg*scale-14*sqz) + "px";
	grs.style.visibility = jupiter.grs_vis?"visible":"hidden";
	if (jupiter.grs_vis) {	// IE6 errors if width set for hidden image
		grsi.style.width = Math.round(27*sqz) + "px";	// squeeze GRS if off-center
		grsi.style.height = "17px";
	}
}


function drawSat(sat) {
// paint sat n (dark blue if eclipsed), occult true if sat further away than jup (then potentially occulted)
	var n = sat.no-1;
	var scale = zoom?xbig:xsmall;
	if (!moon[n]) {		// create elements
		moon[n] = new Array();	// two divs needed to draw round moon, and a <p> for the text
		for (var i=0;i<3;i++) {
			moon[n][i] = document.createElement(i==2?"p":"div");
			moon[n][i].style.position = "absolute";
			moon[n][i].style.zIndex = "8";
			if (i==2) {	// create <p> element with sat number
				moon[n][2].style.margin = 0 + "px";
				moon[n][2].appendChild(document.createTextNode(n+1+""));
				moon[n][2].style.color = "#f00";
				moon[n][2].style.fontSize = "12px";
			}
			else {
				if (n==2 || n==3) var a1=1;	// Callisto and Ganym. are bigger than the two inner sats.
				else var a1=0;
				moon[n][i].style.width = 2+2*i+a1 + "px";
				moon[n][i].style.height = 4-2*i+a1 + "px";
				moon[n][i].style.fontSize = "1px";	// ie6 bug fix
			}	
			cont.appendChild(moon[n][i]);
		}
	}
	for (var i=0;i<2;i++) {
		moon[n][i].style.visibility = (sat.vis?"visible":"hidden");
		if (!sat.vis) continue;
		moon[n][i].style.zIndex = (sat.z>0) ? "1" : "8";	// sat is further away than jup
		moon[n][i].style.left = (jupx+djupx+sat.x*scale-1-i) + "px";
		moon[n][i].style.top = (jupy-sat.y*scale-2+i) + "px";
		moon[n][i].style.background = sat.eclipsed?sateclipsed:satvisible;		
	}
	moon[n][2].style.visibility = (sat.vis?"visible":"hidden");
	if (!sat.vis) return;
	moon[n][2].style.zIndex = (sat.z>0?"1":"8");	// sat is further away than jup
	moon[n][2].style.left = (jupx+djupx+sat.x*scale) + "px";
	moon[n][2].style.top = (jupy-sat.y*scale+3) + "px";
}	// drawSat()
	

function drawShad(sat) {
// paint black shadow for sat n
	var n = sat.no-1;
	var scale = zoom?xbig:xsmall;
	if (!shad[n]) {		// create elements
		shad[n] = new Array();	// two divs needed to draw round shadow
		for (var i=0;i<3;i++) {
			shad[n][i] = document.createElement(i==2?"p":"div");
			shad[n][i].style.position = "absolute";
			shad[n][i].style.zIndex = "7";
			if (i==2) {
				shad[n][2].style.margin = 0 + "px";
				shad[n][2].appendChild(document.createTextNode(n+1+""));
				shad[n][2].style.color = "#444";
				shad[n][i].style.fontSize = "12px";
			}
			else {
				if (n==2 || n==3) var a1=1;
				else var a1=0;
				shad[n][i].style.background = "black";		
				shad[n][i].style.width = 2+2*i+a1 + "px";
				shad[n][i].style.height = 4-2*i+a1 + "px";
				shad[n][i].style.fontSize = "1px";	// ie6 bug fix
			}
			cont.appendChild(shad[n][i]);
		}
	}
	for (var i=0;i<2;i++) {
		shad[n][i].style.visibility = (sat.shad_vis?"visible":"hidden");
		shad[n][i].style.left = jupx+djupx+sat.shx*scale-1-i + "px";
		shad[n][i].style.top = jupy-sat.shy*scale-2+i + "px";
	}
	shad[n][2].style.visibility = (sat.shad_vis?"visible":"hidden");
	shad[n][2].style.left = jupx+djupx+sat.shx*scale + "px";
	shad[n][2].style.top = jupy-sat.shy*scale+3 + "px";
}


function writeDat() {
// write data to window
	for (var i=0; i<texts.length; i++) {
		var n = texts[i];
		if (!n.init) {
			cont.appendChild(n.node);
			n.init=true;
		}
		switch (i) {
			case AZ: var v = jupiter.alt; break;
			case ALT: var v = jupiter.az; break;
			case SUNALT: var v = sun.alt; break;
//			case SYS1: var v = jupiter.sys1; break;
//			case SYS2: var v = jupiter.sys2; break;
			case SYS1: var v = datestring(observer); break;
			case SYS2: var v = hmstring2(observer.hours,observer.minutes,0); break;
			case TIMER: var v = dt; break;	// used for performence measurement
			default: v = 0; break;
		}
//		n.t.data = n.label + fixnum(v,n.l,n.d);
		switch (i) {
			case SYS1: n.t.data = n.label + v;	break;
			case SYS2: n.t.data = n.label + v;	break;
			default : n.t.data = n.label + fixnum(v,n.l,n.d); break;
		}
	}
}


function updateDisplay() {
	JupSitu();
	if (jupiter.grs_vis || jupiter.grs_displayed) {
		//drawGRS();
		jupiter.grs_displayed = jupiter.grs_vis;
	}
	for (var i=1; i<=4; i++) {
		sat[i].vis = (zoom && (sat[i].x < -dxl || sat[i].x > dxr)) ? false : true;	// clip if zoomed
		if (sat[i].vis || sat[i].displayed) {	// if displayed it may need to be hidden
	 		drawSat(sat[i]);
	 	}
	 	sat[i].displayed = sat[i].vis;
	 	if (sat[i].shad_vis || sat[i].shad_displayed) {
	 		drawShad(sat[i]);
	 	}
	 	sat[i].shad_displayed = sat[i].shad_vis;
	}
	writeDat();
//	tbl.iox.value = fixnum(sat[1].x,6,2);
//	tbl.ioy.value = fixnum(sat[1].y,6,2);
//	tbl.ioz.value = fixnum(sat[1].z,6,2);
//	tbl.iosx.value = sat[1].shx;
//	tbl.iosy.value = sat[1].shy;
//	tbl.iosz.value = sat[1].shz;
}


function j_init() {
		cont = document.getElementById("container");
		drawJup();
}

function bottone() {
	  zoom = !zoom;
	  document.getElementById("goleft1").disabled=!zoom;
	  document.getElementById("goright1").disabled=!zoom;
	  djupx=0;
      dxl = 4.66;
      dxr = 4.66;
	  drawJup(); 
	  updateDisplay();
		if (document.getElementById("zoom1").value==="Zoom (-)") {
			document.getElementById("zoom1").value="Zoom (+)";
		}else{
			document.getElementById("zoom1").value="Zoom (-)";
	  }
	  document.getElementById("zoom1").blur();
}

function goleft() {
	  if (zoom) {
		  djupx = -250;
		  dxr = 7.8;
		  dxl = 1.3;//4.66;
		  drawJup(); 
		  updateDisplay();
		}
		document.getElementById("goleft1").blur();
}

function goright() {
	  if (zoom) {
		  djupx = 250;
		  dxr = 1.3;//4.66;
		  dxl = 7.3;
		  drawJup(); 
		  updateDisplay();
		}
		document.getElementById("goright1").blur();
}
// various functions for handling events of user interfaces

var anim=false;
var timer0, jday0, speed;	// used by animation
var counter,dt=0; // for benchmarking
var speeds = new Array(1,60,180,600,1800,3600,3600*3,3600*10,3600*24);
var timespans = [1,3,7,14,31,62];

function rewrite2() {
// rewrite2 updates table1 date/time info and redraws Jupiter objects
	tbl.local_date.value = datestring(observer);
	tbl.local_time.value = hmstring2(observer.hours,observer.minutes,0);
	updateDisplay();
}		// end rewrite2()


function reset1() {
// reset1 restores the input table to its default settings
// initialize date from system clock
////	tbl.grs.value = jupiter.grs_lon;
	setNow();			
	tbl.Place.selectedIndex=0;
	updateplace(true);
	j_init();
}		// end reset1()


function setDateTime(rel) {
	// handles changes to the time, date and tz fields in table1. Also handles time step buttons.
	// 'rel' is relative adjustment in minutes. If rel = 0 read date and time fields.
	anim = false;	// stop any ongoing animation;
	if (rel != 0) {
		adjustTime(observer,rel);
		rewrite2();
		return;
	}
	var vald=tbl.local_date.value;
	// date field
	var col1=vald.indexOf("/");
	var col2=vald.lastIndexOf("/");
	var col3=vald.length;
	var day = 1;
	var month = 1;
	if (col1>0) day = parseInt(vald.substring(0,col1),10);
	if (col2>0) month = parseInt(vald.substring(col1+1,col2),10);
	var year = parseInt(vald.substring(col2+1,col3),10);
	month_length[1] = leapyear(year)?29:28;
	if ((month>12) || (month<0)) month = 1;

	if ((day > month_length[month-1]) || (day<0)) {
		day = 1;
		month = 1;
	}
	observer.day = day;
	observer.month = month;
	observer.year = year;

	// time field
	var valt=tbl.local_time.value;
	col1=valt.indexOf(":");
	col2=valt.length;
   	if (col1 <= 0) col1=col2;
   	observer.hours=parseInt(valt.substring(0,col1),10);
   	if (col2 > (col1+1)) {
   		observer.minutes=parseInt(valt.substring(col1+1,col2),10);
   	} else {
		observer.minutes=0;
    }
	observer.seconds=0;
	rewrite2();
}	// setDateTime()


function setNow() {
	// Handles 'Now' button
	var now = new Date();
	anim = false;	// stop animation if running
	observer.year = now.getFullYear();
	month_length[1]=leapyear(observer.year)?29:28;
	observer.month = now.getMonth()+1;
	observer.day = now.getDate();
	observer.hours = now.getHours();
	observer.minutes = now.getMinutes();
	observer.seconds = 0;
	rewrite2();
}		// end clockhandler()


function set12(noon) {
// set to noon or midnight next day
	anim = false;	// stop animation if running
	if (noon) {
		observer.hours = 12;
		observer.minutes = 0;
	}
	else {
		observer.hours = 0;
		observer.minutes = 0;
	}
	rewrite2();
}


function updateplace(fromtable) {
	// updateplace handles the place selection in table1
	// if 'fromtable' is true get data from 'selected' table entry, else just read Placename field
	var ndx=tbl.Place.selectedIndex;
	if (fromtable) {
		if ((ndx >= 0) && (ndx <= atlas.length)) {
			var n=atlas[ndx].name;
			var i=n.indexOf(" ");
			if (i>0) var n = n.substring(0,i);
			observer.name=n;
			observer.latitude=parsecol(atlas[ndx].latitude);
			observer.longitude=parsecol(atlas[ndx].longitude);
			rewritePlace();
			rewrite2();
		} 
	}
	else {
		observer.name=tbl.Placename.value;
	}
}	// end updateplace()


function rewritePlace() {
	tbl.Placename.value = observer.name;
	tbl.Latitude.value = dmsstring(observer.latitude);
	tbl.Longitude.value = dmsstring(observer.longitude);
	tbl.North.selectedIndex = observer.latitude>0?0:1;
	tbl.West.selectedIndex = observer.longitude>0?1:0;
}	// rewritePlace()


function updatell() {
	// updatell handles the latitude/longitude changes in table1
	var lat = parsecol(tbl.Latitude.value);
	observer.latitude = tbl.North.selectedIndex==0?lat:-lat;
	var lon = parsecol(tbl.Longitude.value);
	observer.longitude = tbl.West.selectedIndex==0?-lon:lon;
	observer.name="";
	tbl.Placename.value = "";
	rewrite2();
}		// end updatell()


function changeSpeed(faster) {	// handle faster and slower buttons
	if (faster && tbl.anispeed.selectedIndex < speeds.length-1) tbl.anispeed.selectedIndex++;
	else if (!faster && tbl.anispeed.selectedIndex > 0) tbl.anispeed.selectedIndex--;
	if (anim) startAnim(speed<0);
}


function startAnim(revers) {
	// start or reinitialize animation, revers=true if reverse
	var now = new Date();
	timer0 = now.getTime();
	jday0 = jdnoDelta(observer);
	speed = speeds[tbl.anispeed.selectedIndex]*(revers?-1:1)/(24*3600*1000.0);	// in days per millisec
	if (!anim) {	// do not call if already running
		anim=true;
		animateJup();
	}
}
	
function animateJup() {	
	// run one iteration of the animation and call itself
	if (!anim) return;	// STOP pressed
	var now = new Date();
	var timer = now.getTime();
	var j = jday0 + (timer-timer0)*speed;
	var t = jdtocd(j); 
	tbl.local_date.value=datestring2nodow(t[0],t[1],t[2]);
	tbl.local_time.value=hmstring2(t[4],t[5],0);
	observer.year = t[0];
	observer.month = t[1];
	observer.day = t[2];
	observer.hours = t[4];
	observer.minutes = t[5];
	observer.seconds = t[6];
	updateDisplay();
	setTimeout("animateJup()",10);	// do it again
}


var head1="<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">\n<HTML><HEAD><TITLE>";
var head2="</TITLE><style>\npre {font-size:12px}\n</style></HEAD><BODY>";
var eventnames = new Array("Transito      ",
													 "Occultazione  ",
													 "Transito ombra",
													 "Eclissi       ",
													 "Cong. Inf.    ",
													 "Cong. con ");
var satnames = ["GRS   ","Io (1)","Eur(2)","Gan(3)","Cal(4)"];

function listEvents(grs) {
	// list satellite and grs events
	anim = false;	// stop any ongoing animation;
	var pwin=window.open("","events","menubar,scrollbars,resizable");
	var doc=pwin.document;
	var str = head1 + "Eventi dei satelliti di Giove" + head2;
	str += "<p><A HREF=\"javascript:window.close()\">chiudi la finestra</A></p>\n";
	str += "<h2>Eventi dei satelliti di Giove</h2><p></p>";
	str += "<p>Osservatorio: "+sitename();
	str += "<p>(Gli eventi visibili sono segnalati con l'asterisco)</p>";
	var line1="Vis    Data         Ora   Altezza  Altezza  Satellite          Evento              ";
	var line2="                   Locale  Giove     Sole                                          ";
	var line3="";
	for (var i=0; i<line1.length; i++) line3 += "-";
	str += "<pre>" + line3 + "\n" + line1 + "\n" + line2 + "\n" + line3 + "\n";
	
	var jday = jdnoDelta(observer);
	
	var events = searchEvents(jday,timespans[tbl.spanselect.selectedIndex]);
	for (var i = 0; i<events.length; i++) {
		if (!grs && events[i][1]==0) continue;	// no listing of grs passages desired
		JupDat(events[i][0],observer);
		SunDat(events[i][0],observer);
		var vis = false;
////		if (jupiter.alt > 3.0 && sun.alt < -3.0) vis=true;
		if (jupiter.alt > 10.0 && sun.alt < -6.0) vis=true; ////come mm
		var t = jdtocd(events[i][0]);
/**/
		var delta=60;
		if (myCheckDSTymd(t[0],t[1],t[2])) delta += 60;
		t = jdtocd(events[i][0]+delta/1440.0);
/**/
		if (vis) str += " *  "; else str += "    ";
		str += datestring2(t[0],t[1],t[2]) + "  ";
		str += hmstring2(t[4],t[5],t[6]) + "  ";
		str += fixnum(jupiter.alt,6,1) + "   ";
		str += fixnum(sun.alt,6,1) + "     ";
		str += satnames[events[i][1]] + "    ";
		if (events[i][1]==0) str += "passa al meridiano centrale";
		else {
			if (events[i][2] < 4) str += (events[i][3] ? "inizio" : "fine") + " ";
			str += eventnames[events[i][2]];
			if (events[i][2] == 5) str += satnames[events[i][3]];
		}
		str += "\n";
	}

	str += line3 + "</pre>\n";
	str += "<p><A HREF=\"javascript:window.close()\">chiudi la finestra</A></p>\n";
	str += "</CENTER></BODY></HTML>";
	doc.write(str);
	doc.close();
	pwin.focus();
}

// Functions for jupiter

// Copyright Ole Nielsen 2002-2005
// Please read copyright notice in astrotools2.html source

var jupiter = {	
	lon:0,	// geocentric long/lat
	lat:0,
	lon0:0,	// heliocentric long/lat
	lat0:0,
	tau:5,	// light time to Earth
	dist:0,	// distance earth
	r:0, 		// distance jup-sun
	mag:0,	// magnitude
	psi:0,	// phase angle of Jupiter
	DS:0,	// planetocentric declination of the Sun
	DE:0,	// planetocentric declination of the Earth
	sys1:0,	// System I central meridian longitude
	sys2:0,	// System II central meridian longitude
	grs_lon:98.0,	// longitude of grs, get from S&T (default for May 2005)
	grs_a:180.0,	// distance from central meridian in degrees
	grs_vis:false,	// visible?
	grs_displayed:false	// already displayed?
};
			
var sun = {	
	az: 0,
	alt: 0,
	rise: 0,	// as fraction of day
	set: 0
};

var sat = new Array();
sat[1] = {	no:1, vis:true, displayed:false, eclipsed:false, shad_vis:false, shad_displayed:false};
sat[2] = {	no:2, vis:true, displayed:false, eclipsed:false, shad_vis:false, shad_displayed:false};
sat[3] = {	no:3, vis:true, displayed:false, eclipsed:false, shad_vis:false, shad_displayed:false};
sat[4] = {	no:4, vis:true, displayed:false, eclipsed:false, shad_vis:false, shad_displayed:false};
		

// heliocentric xyz for jupiter
// this is not from Meeus' book, but from Paul Schlyter http://hem.passagen.se/pausch/comp/ppcomp.html
// account for perturbations of Jupiter
// returns heliocentric x, y, z, distance, longitude and latitude of object
function heliosjup(jday) {
	var d = jday-2451543.5;
	var w = 273.8777 + 1.64505E-5*d;		// argument of perihelion
	var e = 0.048498 + 4.469E-9*d; 
	var a = 5.20256;
	var i = 1.3030 + -1.557E-7*d;
	var N = 100.4542 + 2.76854E-5*d;
	var M = rev(19.8950 + 0.0830853001*d ); 	// mean anomaly
	var E0 = M + RAD2DEG*e*sind(M) * ( 1.0+e*cosd(M) );
	var E1 = E0 - ( E0-RAD2DEG*e*sind(E0)-M ) / ( 1.0-e*cosd(E0) );
	while (Math.abs(E0-E1) > 0.0005) {
		E0 = E1;
		E1 = E0 - ( E0 - RAD2DEG*e*sind(E0)-M ) / ( 1.0-e*cosd(E0) );
	}
	var xv = a*(cosd(E1) - e);
	var yv = a*Math.sqrt(1.0 - e*e) * sind(E1);
	var v = rev(atan2d( yv, xv ));		// true anomaly
	var r = Math.sqrt( xv*xv + yv*yv );	// distance
	var xh = r * ( cosd(N)*cosd(v+w) - sind(N)*sind(v+w)*cosd(i) );
	var yh = r * ( sind(N)*cosd(v+w) + cosd(N)*sind(v+w)*cosd(i) );
	var zh = r * ( sind(v+w)*sind(i) );
	var lonecl = atan2d(yh, xh);
	var latecl = atan2d(zh, Math.sqrt(xh*xh + yh*yh + zh*zh));
	// Jupiter perturbations by Saturn
	var Ms = rev(316.9670 + 0.0334442282*d);
	lonecl += (-0.332)*sind(2*M-5*Ms-67.6) - 0.056*sind(2*M-2*Ms+21) + 0.042*sind(3*M-5*Ms+21) -
			0.036*sind(M-2*Ms) + 0.022*cosd(M-Ms) + 0.023*sind(2*M-3*Ms+52) - 0.016*sind(M-5*Ms-69);
	xh=r*cosd(lonecl)*cosd(latecl);		// recalc xh, yh
	yh=r*sind(lonecl)*cosd(latecl);
	return new Array(xh,yh,zh,r,lonecl,latecl);
}	// heliosjup()


// ecliptic position of the Sun relative to Earth (basically simplified version of planetxyz calc)
function sunxyz(jday) {
	// return x,y,z ecliptic coordinates, distance, true longitude
	// days counted from 1999 Dec 31.0 UT
	var d=jday-2451543.5;
	var w = 282.9404 + 4.70935E-5 * d;		// argument of perihelion
	var e = 0.016709 - 1.151E-9 * d; 
	var M = rev(356.0470 + 0.9856002585 * d); // mean anomaly
	var E = M + e*RAD2DEG * sind(M) * ( 1.0 + e * cosd(M) );
	var xv = cosd(E) - e;
	var yv = Math.sqrt(1.0 - e*e) * sind(E);
	var v = atan2d( yv, xv );		// true anomaly
	var r = Math.sqrt( xv*xv + yv*yv );	// distance
	var lonsun = rev(v + w);	// true longitude
	var xs = r * cosd(lonsun);		// rectangular coordinates, zs = 0 for sun 
	var ys = r * sind(lonsun);
	return new Array(xs,ys,0,r,lonsun,0);
}	// sunxyz()


function radecr(obj,sun,jday,obs) {
// radecr returns ra, dec and earth distance
// obj and sun comprise Heliocentric Ecliptic Rectangular Coordinates
// (note Sun coords are really Earth heliocentric coordinates with reverse signs)
		// Equatorial geocentric co-ordinates
		var xg=obj[0]+sun[0];
		var yg=obj[1]+sun[1];
		var zg=obj[2];
		// Obliquity of Ecliptic (exponent corrected, was E-9!)
		var obl = 23.4393 - 3.563E-7 * (jday-2451543.5);
		// Convert to eq. co-ordinates
		var x1=xg;
		var y1=yg*cosd(obl) - zg*sind(obl);
		var z1=yg*sind(obl) + zg*cosd(obl);
		// RA and dec (33.2)
		var ra=rev(atan2d(y1, x1));
		var dec=atan2d(z1, Math.sqrt(x1*x1+y1*y1));
		var dist=Math.sqrt(x1*x1+y1*y1+z1*z1);
		return new Array(ra,dec,dist);
} 	// radecr()


function radec2aa(ra,dec,jday,obs) {
// Convert ra/dec to alt/az, also return hour angle, azimut = 0 when north
// DOES NOT correct for parallax!
// TH0=Greenwich sid. time (eq. 12.4), H=hour angle (chapter 13)
		var TH0 = 280.46061837 + 360.98564736629*(jday-2451545.0);
		var H = rev(TH0-obs.longitude-ra);
		var alt = asind( sind(obs.latitude)*sind(dec) + cosd(obs.latitude)*cosd(dec)*cosd(H) );
		var az = atan2d( sind(H), (cosd(H)*sind(obs.latitude) - tand(dec)*cosd(obs.latitude)) );
		return new Array (alt, rev(az+180.0), H);
}	// radec2aa()


function SunDat(jday,obs) {
// just calc altitude for the moment 
	var sdat=sunxyz(jday);
	var ecl = 23.4393 - 3.563E-7 * (jday-2451543.5); 
	var xe = sdat[0];
	var ye = sdat[1]*cosd(ecl);
	var ze = sdat[1]*sind(ecl);
	var ra = rev(atan2d(ye,xe));
	var dec = atan2d(ze, Math.sqrt(xe*xe+ye*ye));
	var topo=radec2aa(ra,dec,jday,obs);
	sun.alt = topo[0];
}


function JupDat(jday,obs) {
// Alt/Az, diameter, brightness, JupSat should be called first to set lon/lat
	var lon = jupiter.lon;
	var lat = jupiter.lat;
	var obl = 23.4393 - 3.563E-7 * (jday-2451543.5);	// Schlyter
	var ra = atan2d((sind(lon)*cosd(obl) - tand(lat)*sind(obl)), cosd(lon));	// (13.3)
	var dec = asind(sind(lat)*cosd(obl) + cosd(lat)*sind(obl)*sind(lon));	// (13.4)
	var altaz = radec2aa(ra, dec, jday, obs);
	jupiter.alt = altaz[0]; 
	jupiter.az = altaz[1];
	var dist = jupiter.dist;
	jupiter.diam = 196.88/dist;
	jupiter.mag = -9.40 + 5*log10(jupiter.r*dist) + Math.abs(jupiter.psi)*0.005;	
}	// JupDat()


function JupPhys(jday) {
// calc central meridian longitudes of system I and II, planetocentric declinations of Earth and Sun
// uses 'lower accuracy' method in Meeus chap. 43
	var d = jday - 2451545.0;
	var V = rev(172.74 + 0.00111588*d);
	var M = rev(357.529 + 0.9856003*d);	// mean anomaly Earth
	var N = rev(20.020 + 0.0830853*d + 0.329*sind(V));	// mean anomaly Jup
	var J = rev(66.115 + 0.9025179*d - 0.329*sind(V));	// diff between heliocentr lon of E and J
	var A = 1.915*sind(M) + 0.020*sind(2*M);	// Equations of center of Earth
	var B = 5.555*sind(N) + 0.168*sind(2*N);	// of Jupiter
	var K = J + A - B;
	var R = 1.00014 - 0.01671*cosd(M) - 0.00014*cosd(2*M);	// Sun - Earth dist
	var r = 5.20872 - 0.25208*cosd(N) - 0.00611*cosd(2*N);	// Sun - Jup dist
	var dist = Math.sqrt(r*r + R*R - 2*r*R*cosd(K));	// E - J dist
	var psi = asind((R/dist)*sind(K));	// phase angle
	var om1 = rev(210.98 + 877.8169088*(d-dist/173) + psi - B);	// system 1 long of CM
	var om2 = rev(187.23 + 870.1869088*(d-dist/173) + psi - B);	// system 2
	var lamd = rev(34.35 + 0.083091*d + 0.329*sind(V) + B);	
	var DS = 3.12*sind(lamd + 42.8);	// planetocentric declination of Sun
	var DE = DS - 2.22*sind(psi)*cosd(lamd+22) - 1.30*(r-dist)/dist*sind(lamd-100.5);
	jupiter.dist = dist;
	jupiter.r = r;
	jupiter.psi = psi;
	jupiter.DE = DE;
	jupiter.DS = DS;
	jupiter.sys1 = om1;
	jupiter.sys2 = om2;	// the GRS is attached to this system
	jupiter.grs_a = rev2(om2-jupiter.grs_lon);	// angle from central meridean, positive if crossed mer.
}


function JupSat(jday,shadow) {
// updates positions of satellites in units of Jupiter eq. radius
// if shadow, positions as seen from Sun (for eclipse and shadow transit calc), must be called with shadow=false first
// high accuracy method (Meeus chap 44), but with truncated terms and other short cuts
var jup_xyz, x, y, z;
	if (shadow) {	// heliocentric location at jday-light time 
		var tau = jupiter.tau;
		jup_xyz = heliosjup(jday-tau);
		x = jup_xyz[0];
		y = jup_xyz[1];
		z = jup_xyz[2];
	}
	else {	// geocentric position
		var dist = 5.0; 	// initial guess
		var tau = 0.005776*dist;	// light time (33.3)
		// using Schlyter's method
		var sun_xyz = shadow ? [0,0,0]: sunxyz(jday); // for shadows and eclipses see jup from Sun
		for (var i = 1; i <= 4; i++) { //4 iterations
			jup_xyz = heliosjup(jday-tau); // recalc using better light time
			x = jup_xyz[0]+sun_xyz[0];
			y = jup_xyz[1]+sun_xyz[1];
			z = jup_xyz[2]+sun_xyz[2];
			dist = Math.sqrt(x*x+y*y+z*z);	// back to Meeus
			tau = 0.005776*dist;
			jupiter.tau = tau;
		}
	}
	var lon = rev( atan2d(y, x) ); 
	if (shadow) jupiter.lon0 = lon; else jupiter.lon = lon;
	var sinlon = sind(lon); var coslon = cosd(lon);	// precalc sine/cosines, needed later
	var lat = atan2d(z, Math.sqrt(x*x+y*y)); 
	if (shadow) jupiter.lat0 = lat; else jupiter.lat = lat;
	var sinlat = sind(lat); var coslat = cosd(lat);
	var t = jday - 2443000.5 - tau;
	var l1 = rev(106.07947 + 203.488955432*t);
	var l2 = rev(175.72938 + 101.374724550*t);
	var l3 = rev(120.55434 +  50.317609110*t);
	var l4 = rev( 84.44868 +  21.571071314*t);
	var pi1 = rev( 58.3329 + 0.16103936*t);
	var pi2 = rev(132.8959 + 0.04647985*t);
	var pi3 = rev(187.2887 + 0.00712740*t);
	var pi4 = rev(335.3418 + 0.00183998*t);
	var om1 = rev(311.0793 - 0.13279430*t);
	var om2 = rev(100.5099 - 0.03263047*t);
	var om3 = rev(119.1688 - 0.00717704*t);
	var om4 = rev(322.5729 - 0.00175934*t);
	var GAM = 0.33033*sind(163.679+0.0010512*t) + 0.03439*sind(34.486-0.0161731*t);
	var phL = 191.8132 + 0.17390023*t;
	var psi = 316.5182 - 0.00000208*t;
	var G =  30.23756 + 0.0830925701*t + GAM;
	var GP = 31.97853 + 0.0334597339*t;
	
	var T0 = (jday-2433282.423)/36525.0;
	var P = 1.39666*T0 + 0.00031*T0*T0;
	
	// molto approssimato!
	var L1 = l1 + 0.473*sind(2*(l1-l2)) - 0.035*sind(pi3-pi4);
	var B1 = atand(0.0006*sind(L1-om1));
	var R1 = 5.9057*(1 - 0.0041*cosd(2*(l1-l2)));
	L1 += P;
	var L2 = l2 + 1.065*sind(2*(l2-l3)) + 0.043*sind(l1-2*l2+pi3) + 0.036*sind(l2-pi3) + 0.024*sind(l1-2*l2+pi4);
	var B2 = atand(0.0081*sind(L2-om2));
	var R2 = 9.3968*(1 + 0.0094*cosd(l1-l2));
	L2 += P;
	var L3 = l3 + 0.165*sind(l3-pi3) + 0.091*sind(l3-pi4) - 0.069*sind(l2-l3) + 0.038*sind(pi3-pi4) + 0.018*sind(2*(l3-l4));
	var B3 = atand(0.0032*sind(L3-om3) - 0.0017*sind(L3-psi) + 0.0007*sind(L3-om4));
	var R3 = 14.9883*(1 - 0.0014*cosd(l3-pi3) - 0.0008*cosd(l3-pi4) + 0.0006*cosd(l2-l3));
	L3 += P;
	var L4 = l4 + 0.843*sind(l4-pi4) + 0.034*sind(pi4-pi3) - 0.033*sind(psi-13.470) - 0.032*sind(G) - 0.019*sind(l4-pi3);
	var B4 = atand(-0.0077*sind(L4-psi) + 0.0044*sind(L4-om4) - 0.0005*sind(L4-om3));
	var R4 = 26.3627*(1 - 0.0074*cosd(l4-pi4));
	L4 += P;
	psi += P;

	var I = 3.1203 + 0.0006*(jday-2415020.0)/36525.0; var sinI = sind(I); var cosI = cosd(I);
	var Xi = [R1*cosd(L1-psi)*cosd(B1), R2*cosd(L2-psi)*cosd(B2), R3*cosd(L3-psi)*cosd(B3), R4*cosd(L4-psi)*cosd(B4), 0];
	var Yi = [R1*sind(L1-psi)*cosd(B1), R2*sind(L2-psi)*cosd(B2), R3*sind(L3-psi)*cosd(B3), R4*sind(L4-psi)*cosd(B4), 0];
	var Zi = [R1*sind(B1),R2*sind(B2),R3*sind(B3),R4*sind(B4), 1];
	var T = (jday-2451545.0)/36525.0;	// (31.1)
	// get OM (longitude of node of Jup) and i (inclination) from table 31.A, precalc sines and cosines
	var OM = 100.464441 + 1.0209550*T + 0.00040117*T*T + 0.000000569*T*T*T; 
	var sinOM = sind(OM); var cosOM = cosd(OM);
	var PHI = psi - OM; var sinPHI = sind(PHI); var cosPHI = cosd(PHI);
	var incl = 1.303270 - 0.0054966*T + 0.00000465*T*T - 0.000000004*T*T*T; 
	var sini = sind(incl); var cosi=cosd(incl);

	var Ai1 = new Array(), Ai2 = new Array(), Bi1 = new Array(), Bi2 = new Array(), Ci1 = new Array(), Ci2 = new Array();
	for (var i = 4; i>=0; i--) {	// do several rotations on 4 real sats and fifth fictious satellite 
		Ai1[i] = Xi[i];
		Bi1[i] = Yi[i]*cosI - Zi[i]*sinI;
		Ci1[i] = Yi[i]*sinI + Zi[i]*cosI;
		Ai2[i] = Ai1[i]*cosPHI - Bi1[i]*sinPHI;
		Bi2[i] = Ai1[i]*sinPHI + Bi1[i]*cosPHI;
		Ci2[i] = Ci1[i];
		Ai1[i] = Ai2[i];	// A3, B3, C3; reuse the arrays (perché con quello che costano...)
		Bi1[i] = Bi2[i]*cosi - Ci2[i]*sini;
		Ci1[i] = Bi2[i]*sini + Ci2[i]*cosi;
		Ai2[i] = Ai1[i]*cosOM - Bi1[i]*sinOM;	// A4, B4, C4
		Bi2[i] = Ai1[i]*sinOM + Bi1[i]*cosOM;
		Ci2[i] = Ci1[i];
		Ai1[i] = Ai2[i]*sinlon - Bi2[i]*coslon;	// A5, B5, C5
		Bi1[i] = Ai2[i]*coslon + Bi2[i]*sinlon;
		Ai2[i] = Ai1[i];	// A6, B6, C6
		Bi2[i] = Ci1[i]*sinlat + Bi1[i]*coslat;
		Ci2[i] = Ci1[i]*coslat - Bi1[i]*sinlat;
	}
	var D = atan2d(Ai2[4], Ci2[4]); var sinD = sind(D); var cosD = cosd(D);
	for (var j = 0; j<4; j++) {	// finally calculate X, Y, Z in units of Jup. radius
		x = Ai2[j]*cosD - Ci2[j]*sinD;
		y = Ai2[j]*sinD + Ci2[j]*cosD;
		z = Bi2[j];
		if (shadow) {
			sat[j+1].shx = x; sat[j+1].shy = y; sat[j+1].shz = z; 
		}
		else {
			sat[j+1].x = x; sat[j+1].y = y; sat[j+1].z = z; 
		}
	}
}	// JupSat()


function JupSitu() {
	// detect situation (sat xyz, shadow xyz, eclipses, ..)
	var jday = jdDelta(observer);
	JupPhys(jday);
	jupiter.grs_vis = (Math.abs(jupiter.grs_a)<73);	// practical limit of visibility of GRS 
	JupSat(jday,false);
	JupSat(jday,true);
	JupDat(jday,observer);
	SunDat(jday,observer);
	for (var i = 1; i<=4; i++) {
		sat[i].eclipsed = false; sat[i].shad_vis = false;
		var y1 = 1.071374*sat[i].shy;	// compensate for flattening of Jupiter
		var x = sat[i].shx;	// x AS SEEN from Sun, to be corrected for transits
		if ((x*x + y1*y1) < 1) {	// either shadow transit or eclipse
			if (sat[i].shz < 0) {	// shadow transit, calculate earth  perspective of shadows
				// simplified rotation, rotate in longitude only, error small as lon-lon0 always less than 12 deg.
				var xlimb = Math.sqrt(1-y1*y1);
				var a = asind(x/xlimb);	// relative angle of shadow from central meridian (as seen from Sun)
				a = a - jupiter.lon + jupiter.lon0; // rotate to Earth 
				if (Math.abs(a)<90) {	// is visible from Earth
					sat[i].shad_vis = true;
					sat[i].shx = xlimb*sind(a);
				}
			}
			else {
				sat[i].eclipsed = true;
			}
		}
	}
}


function searchEvents(jday,span) {
	// search transit, shadow transits, eclipses, occultations and grs transits
	var events = new Array();	// store all events as records of jd,satnumber (0=GRS),type,true if begin
	// type: 0 = transit;1 = occult;2 = shadow tr;3 = eclipse;4 = inf. conj;5 = sat conj
	var stepsize = 0.07; 
	var grs_step = 870.27*stepsize;	// angle rotated during stepsize
	var jd = jday-3*stepsize, jd1;
	var x0, x1, x2, y0, y1, y2;
	var xx0 = new Array(), xx1 = new Array(), xx2 = new Array();
	var sat0 = new Array(new Object(),new Object(),new Object(),new Object(),new Object());
	var sat1 = new Array(new Object(),new Object(),new Object(),new Object(),new Object());
	var oldsys2 = jupiter.sys2;
	JupPhys(jd);
	JupSat(jd,false);
	JupSat(jd,true);
	for (var i=1; i<=4; i++) {	// save old state, in particular x,y,z,shx,shy,shz
		for (var j in sat[i]) {sat1[i][j] = sat[i][j]};
	}
	jd += stepsize;
	JupPhys(jd);
	JupSat(jd,false);
	JupSat(jd,true);
	while (jd < jday+span) {
		for (var i=1; i<=4; i++) {	// save old state, in particular x,y,z,shx,shy,shz
			for (var j in sat1[i]) {sat0[i][j] = sat1[i][j]};
			for (var j in sat[i]) {sat1[i][j] = sat[i][j]};
		}
		oldsys2 = jupiter.sys2;
		jd1 = jd;
		jd += stepsize;
		JupPhys(jd);
		if (rev2(jupiter.sys2-jupiter.grs_lon) < 0) {	// rev2 normalizes to [-180:180]
			if (rev2(jupiter.sys2+grs_step-jupiter.grs_lon) > 0) {	// grs transits before next time step
				events[events.length] = new Array(jd+rev2(jupiter.grs_lon-jupiter.sys2)/870.27,0,0,true);
			}
		}
		JupSat(jd,false);
		JupSat(jd,true);
		for (var i = 1; i <= 4; i++) {
			x0 = sat0[i].x; x1 = sat1[i].x; x2 = sat[i].x;
			xx0[i] = x0; xx1[i] = x1; xx2[i] = x2;
			y0 = 1.071374*sat0[i].y; y1 = 1.071374*sat1[i].y; y2 = 1.071374*sat[i].y;
//			if (Math.abs(x1) <= Math.abs(x0) && Math.abs(x1) < Math.abs(x2)) {
			if (x1*x2 < 0) {
				var jc = nzero(x0,x1,x2);	// time relative [-1:1] at x=0
				var yc = interpol(jc,y0,y1,y2);	// y at x=0
				if (Math.abs(yc) < 1) {
					var xi = SGN(x0)*Math.sqrt(1-yc*yc);	// 1st guess at x for ingress
					var ji = nzero(x0-xi,x1-xi,x2-xi);
					var yi = interpol(ji,y0,y1,y2);
					xi = SGN(x0)*Math.sqrt(1-yi*yi);	// 2nd iter.
					ji = nzero(x0-xi,x1-xi,x2-xi);
					var je = jc + (jc-ji);	// egress time assuming symmetry
					events[events.length] = new Array(jd1+ji*stepsize,i,(sat[i].z<0?0:1),true);
					events[events.length] = new Array(jd1+je*stepsize,i,(sat[i].z<0?0:1),false); 
					// congiunzione inferiore se xi<0
					if (xi < 0) events[events.length] = new Array(jd1+jc*stepsize,i,4,true); //cong inf
				}
			}
			// check for shadow event in a similar way
			x0 = sat0[i].shx; x1 = sat1[i].shx; x2 = sat[i].shx;
			y0 = 1.071374*sat0[i].shy; y1 = 1.071374*sat1[i].shy; y2 = 1.071374*sat[i].shy;
			if (Math.abs(x1) <= Math.abs(x0) && Math.abs(x1) < Math.abs(x2)) {
				var jc = nzero(x0,x1,x2);	// time relative [-1:1] at x=0
				var yc = interpol(jc,y0,y1,y2);	// y at x=0
				if (Math.abs(yc) < 1) {
					var xi = SGN(x0)*Math.sqrt(1-yc*yc);	// 1st guess at x for ingress
					var ji = nzero(x0-xi,x1-xi,x2-xi);
					var yi = interpol(ji,y0,y1,y2);
					xi = SGN(x0)*Math.sqrt(1-yi*yi);	// 2nd iter.
					ji = nzero(x0-xi,x1-xi,x2-xi);
					var je = jc + (jc-ji);	// egress time assuming symmetry
					events[events.length] = new Array(jd1+ji*stepsize,i,(sat[i].shz<0?2:3),true);	// 
					events[events.length] = new Array(jd1+je*stepsize,i,(sat[i].shz<0?2:3),false);	// 
				}
			}
		}
		for (var i = 1; i < 4; i++) { //congiunzioni tra satelliti
			for (var j = i+1; j <= 4; j++) {
				x0 = xx0[i] - xx0[j]; x1 = xx1[i] - xx1[j]; x2 = xx2[i] - xx2[j];
				if (x1*x2 < 0) {
					var jc = nzero(x0,x1,x2);	// time relative [-1:1] at x=0
					events[events.length] = new Array(jd1+jc*stepsize,i,5,j); //cong i->j
				}
			}
		}
	}

	isort(events);	// put in chronological order
	// filter some non-visible events
	var t, m;
	var occ = [false,false,false,false];	// track if occultation for moon i is in progress
	var ecl = [false,false,false,false];	// track if eclipse for moon i is in progress
	for (var i = 0; i < events.length; i++) {	// length may change!!
		t = events[i][2]; m = events[i][1];
		if (t==1) occ[m] = events[i][3];	// mark if occultation is in progress
		else if (t==3) ecl[m] = events[i][3];	// mark if eclipse in progress
		if ((occ[m] && (t==3 || t==5)) || (ecl[m] && (t==1 || t==5))) {		// delete array element
			events.splice(i,1);
			i--;
		}
	}
	return events;
}

var nosatdates = new Array(
	"08/11/2001","16:27",
	"22/05/2008","03:51",
	"03/09/2009","04:44",
	"09/11/2019","12:17",
	"28/05/2020","11:16",
	"15/08/2021","15:40",
	"28/07/2033","03:08",
	"22/05/2038","09:10",
	"09/12/2038","08:20",
	"15/10/2049","03:46",
	"28/05/2050","17:23",
	"27/04/2056","15:21",
	"15/07/2057","22:53",
	"16/03/2061","18:45",
	"15/10/2067","22:45",
	"03/05/2068","23:31",
	"26/02/2073","03:02",
	"26/11/2080","23:12",
	"14/02/2082","06:30",
	"07/02/2085","14:36",
	"27/04/2086","19:49",
	"14/11/2086","19:18",
	"20/01/2097","02:30",
	"13/02/2097","22:18",
	"04/05/2098","03:27");
                  
function NoSats(){
var i = tbl.noSat.selectedIndex * 2;
	tbl.local_date.value = nosatdates[i];
	tbl.local_time.value = nosatdates[i+1];
	setDateTime(0);
	var delta=60;
	if (myCheckDST(observer)) delta += 60;
	setDateTime(delta);
}
//"OSSERVATORIO=place%3D%27Roma%27%26lat%3D41.88333333333333%26lng%3D-12.5"
//"OSSERVATORIO=place='Roma'&lat=41.88333333333333&lng=-12.5"
// search for cookie "placeid" and initialize observer from it if found, else return 'false'
function getCookie(placeid,obs) {
	var place=""; var lat=9999.0; var lng=9999.0;
	var thisCookie=document.cookie.split("; ");
	if (thisCookie==""){
		tbl.Placename.value = "";
		tbl.Latitude.value = "";
		tbl.Longitude.value = "";
		return false;
	}
	for (var i=0; i<thisCookie.length; i++) {
		if (placeid == thisCookie[i].split("=")[0]) {
			var argstr = unescape(thisCookie[i].split("=")[1]);
			var args = argstr.split('&');
			if (args.length!=3) return false;
			for (var i=0; i<args.length; i++) eval(args[i]);
		}
	}
	// check if valid values were read
	if (Math.abs(lat)>90.0 || Math.abs(lng)>180.0) return false;
	obs.name = place;
	obs.latitude = lat;
	obs.longitude = lng;
	rewritePlace();
	rewrite2();
	// rewrite cookie to refresh expire date
	setCookie(placeid,obs);
	return true;
}


// remember observatory in cookie 
function setCookie(placeid,obs) {
	var expireDate=new Date;
	expireDate.setFullYear(expireDate.getFullYear()+2);
	var argstr="place='"+obs.name+"'&lat="+obs.latitude+"&lng="+obs.longitude;
	document.cookie=placeid+"="+escape(argstr)+"; expires="+expireDate.toGMTString();
}


function deleteCookie(placeid) {
	var expireDate=new Date;
	expireDate.setFullYear(expireDate.getFullYear()-1);
	document.cookie=placeid+"=; expires="+expireDate.toGMTString();
}

// must be updated using leapyear() if year changed
var month_length=new Array(31,28,31,30,31,30,31,31,30,31,30,31);
var dow = ["D ","L ","Ma","Me","G ","V ","S "];
var dow2 = ["Dom","Lun","Mar","Mer","Gio","Ven","Sab"];


function leapyear(year) {
  var leap=false;
  if (year % 4 == 0) leap = true;
  if (year % 100 == 0 ) leap = false;
  if (year % 400 == 0) leap = true;
  return leap;
}


function jd0(year,month,day) {
// The Julian date at 0 hours(*) UT at Greenwich
// (*) or actual UT time if day comprises time as fraction
  var y  = year;
  var m = month;
  if (m < 3) {m += 12; y -= 1};
  var a = Math.floor(y/100);
  var b = 2-a+Math.floor(a/4);
  var j = Math.floor(365.25*(y+4716))+Math.floor(30.6001*(m+1))+day+b-1524.5;
  return j;
}	// jd0()


function jdtocd(jd) {
// The calendar date from julian date, see Meeus p. 63
// Returns year, month, day, day of week, hours, minutes, seconds
  var Z=Math.floor(jd+0.5);
  var F=jd+0.5-Z;
  if (Z < 2299161) {
    var A=Z;
  } else {
    var alpha=Math.floor((Z-1867216.25)/36524.25);
    var A=Z+1+alpha-Math.floor(alpha/4);
  }
  var B=A+1524;
  var C=Math.floor((B-122.1)/365.25);
  var D=Math.floor(365.25*C);
  var E=Math.floor((B-D)/30.6001);
  var d=B-D-Math.floor(30.6001*E)+F;
  if (E < 14) {
    var month=E-1;
  } else {
    var month=E-13;
  }
  if ( month>2) {
    var year=C-4716;
  } else {
    var year=C-4715;
  }
  var day=Math.floor(d);
  var h=(d-day)*24;
  var hours=Math.floor(h);
  var m=(h-hours)*60;
  var minutes=Math.floor(m);
  var seconds=Math.round((m-minutes)*60);
  if (seconds >= 60) {
    minutes=minutes+1;
    seconds=seconds-60;
  }
  if (minutes >= 60) {
    hours=hours+1;
    minutes=0;
  }
  var dw=Math.floor(jd+1.5)-7*Math.floor((jd+1.5)/7);
  return new Array(year,month,day,dw,hours,minutes,seconds);  
}	// jdtocd()


function g_sidereal(year,month,day) {
// sidereal time in hours for Greenwich
  var T=(jd0(year,month,day)-2451545.0)/36525;
  var res=100.46061837+T*(36000.770053608+T*(0.000387933-T/38710000.0));
  return rev(res)/15.0;
}

function myCheckDST(obs){
	// 0 = solare , 1 = legale
	var jdEv = jd0(obs.year,obs.month,obs.day);
	if ((obs.month<3)||(obs.month>10)) return 0;
	var jdM = jd0(obs.year,4,1);
	var dayM = Math.floor(jdM+1.5);
	dayM = dayM%7; //0=D, 1=L, ... 6=S
	if (dayM==0){
		jdM -=7;
	}else{
		jdM -= dayM;
	}
	var jdO = jd0(obs.year,11,1);
	var dayO = Math.floor(jdO+1.5);
	dayO = dayO%7;
	if (dayO==0){
		jdO -=7;
	}else{
		jdO -= dayO;
	}
	if ((jdEv>=jdM)&&(jdEv<jdO)) return 1;
	return 0;
} //myCheckDST

function myCheckDSTymd(year,month,day){
	// 0 = solare , 1 = legale
	var jdEv = jd0(year,month,day);
	if ((month<3)||(month>10)) return 0;
	var jdM = jd0(year,4,1);
	var dayM = Math.floor(jdM+1.5);
	dayM = dayM%7; //0=D, 1=L, ... 6=S
	if (dayM==0){
		jdM -=7;
	}else{
		jdM -= dayM;
	}
	var jdO = jd0(year,11,1);
	var dayO = Math.floor(jdO+1.5);
	dayO = dayO%7;
	if (dayO==0){
		jdO -=7;
	}else{
		jdO -= dayO;
	}
	if ((jdEv>=jdM)&&(jdEv<jdO)) return 1;
	return 0;
} //myCheckDSTjd

var DEG2RAD = Math.PI/180.0;
var RAD2DEG = 180.0/Math.PI;

function rev(angle) 	{return angle-Math.floor(angle/360.0)*360.0;}		// 0<=a<360
function rev2(angle)	{var a = rev(angle); return (a>=180 ? a-360.0 : a);}	// -180<=a<180
function sind(angle) 	{return Math.sin(angle*DEG2RAD);}
function cosd(angle) 	{return Math.cos(angle*DEG2RAD);}
function tand(angle) 	{return Math.tan(angle*DEG2RAD);}
function asind(c) 		{return RAD2DEG*Math.asin(c);}
function acosd(c) 		{return RAD2DEG*Math.acos(c);}
function atand(c) 		{return RAD2DEG*Math.atan(c);}
function atan2d(y,x) 	{return RAD2DEG*Math.atan2(y,x);}

function log10(x) 		{return Math.LOG10E*Math.log(x);}

function sqr(x)				{return x*x;}
function cbrt(x)			{return Math.pow(x,1/3.0);}

function SGN(x) 			{return (x<0)?-1:+1; }

function place(name,latitude,longitude) {
  this.name      = name;
  this.latitude  = latitude;
  this.longitude = longitude;
}

var atlas = new Array(
  new place("Roma           ","41:55:25","-12:27:14"),
  new place("Agrigento      ","37:17:47","-13:36:00"),
  new place("Alessandria    ","44:54:51","-08:36:45"),
  new place("Ancona         ","43:37:21","-13:30:45"),
  new place("Aosta          ","45:44:15","-07:19:00"),
  new place("Arezzo         ","43:27:58","-11:53:00"),
  new place("Ascoli Piceno  ","42:51:09","-13:34:45"),
  new place("Asti           ","44:54:01","-08:12:15"),
  new place("Avellino       ","40:54:52","-14:47:45"),
  new place("Bari           ","41:07:30","-16:52:45"),
  new place("Belluno        ","46:08:14","-12:13:00"),
  new place("Benevento      ","41:07:52","-14:46:30"),
  new place("Bergamo        ","45:42:10","-09:39:45"),
  new place("Biella         ","45:34:00","-08:04:00"),
  new place("Bologna        ","44:29:53","-11:21:00"),
  new place("Bolzano        ","46:29:49","-11:21:15"),
  new place("Brescia        ","45:32:33","-10:13:30"),
  new place("Brindisi       ","40:39:14","-17:46:15"),
  new place("Cagliari       ","39:13:15","-09:07:00"),
  new place("Caltanissetta  ","37:28:12","-14:03:45"),
  new place("Campobasso     ","41:33:47","-14:39:15"),
  new place("Caserta        ","41:04:21","-14:19:45"),
  new place("Catania        ","37:30:13","-15:05:15"),
  new place("Catanzaro      ","38:64:16","-16:35:30"),
  new place("Chieti         ","42:21:02","-14:10:00"),
  new place("Como           ","45:47:37","-09:05:00"),
  new place("Cosenza        ","39:17:07","-16:15:30"),
  new place("Cremona        ","45:08:00","-10:01:30"),
  new place("Crotone        ","39:05:00","-17:07:00"),
  new place("Cuneo          ","44:23:33","-07:33:00"),
  new place("Enna           ","37:32:30","-14:17:15"),
  new place("Ferrara        ","44:50:34","-11:36:00"),
  new place("Firenze        ","43:46:36","-11:15:30"),
  new place("Foggia         ","41:27:45","-15:32:45"),
  new place("Forlì          ","44:13:21","-12:02:15"),
  new place("Frosinone      ","41:38:21","-13:21:15"),
  new place("Genova         ","44:24:15","-08:54:15"),
  new place("Gorizia        ","45:56:30","-13:37:30"),
  new place("Grosseto       ","42:45:33","-11:06:45"),
  new place("Imperia        ","43:52:30","-08:01:00"),
  new place("Isernia        ","41:36:00","-14:14:00"),
  new place("L'Aquila       ","42:21:01","-13:24:00"),
  new place("La Spezia      ","44:05:26","-09:49:00"),
  new place("Latina         ","41:27:59","-12:54:15"),
  new place("Lecce          ","40:21:03","-18:10:15"),
  new place("Lecco          ","45:51:00","-09:24:00"),
  new place("Livorno        ","43:31:37","-10:18:30"),
  new place("Lodi           ","45:19:00","-09:30:00"),
  new place("Lucca          ","43:50:34","-10:30:15"),
  new place("Macerata       ","43:17:45","-13:25:30"),
  new place("Mantova        ","45:09:33","-08:32:45"),
  new place("Massa          ","44:01:20","-10:08:00"),
  new place("Matera         ","40:39:57","-16:36:45"),
  new place("Messina        ","38:11:34","-15:34:30"),
  new place("Milano         ","45:27:59","-09:11:30"),
  new place("Modena         ","44:38:45","-10:55:30"),
  new place("Napoli         ","40:51:46","-14:15:15"),
  new place("Novara         ","45:26:54","-08:37:00"),
  new place("Nuoro          ","40:19:11","-09:20:15"),
  new place("Oristano       ","39:54:00","-08:30:30"),
  new place("Padova         ","45:24:01","-11:52:15"),
  new place("Palermo        ","38:08:12","-13:22:45"),
  new place("Parma          ","44:48:08","-10:20:00"),
  new place("Pavia          ","45:11:04","-09:09:15"),
  new place("Perugia        ","43:06:44","-12:23:30"),
  new place("Pesaro         ","43:54:31","-12:54:45"),
  new place("Pescara        ","42:27:34","-14:12:45"),
  new place("Piacenza       ","45:03:00","-09:41:45"),
  new place("Pisa           ","43:43:20","-10:23:45"),
  new place("Pistoia        ","43:55:58","-10:55:00"),
  new place("Pordenone      ","45:57:00","-12:41:00"),
  new place("Potenza        ","40:38:21","-15:48:30"),
  new place("Prato          ","43:52:51","-11:05:45"),
  new place("Ragusa         ","36:56:26","-14:45:15"),
  new place("Ravenna        ","44:25:07","-12:12:00"),
  new place("Reggio Calabria","38:06:23","-15:38:45"),
  new place("Reggio Emilia  ","44:41:45","-10:37:45"),
  new place("Rieti          ","42:24:06","-12:51:30"),
  new place("Rimini         ","44:03:00","-12:34:00"),
  new place("Rovigo         ","45:04:21","-11:47:30"),
  new place("Salerno        ","40:40:46","-14:45:45"),
  new place("Sassari        ","40:43:28","-08:33:45"),
  new place("Savona         ","44:18:16","-08:29:00"),
  new place("Siena          ","43:19:03","-11:20:00"),
  new place("Siracusa       ","37:03:48","-15:18:30"),
  new place("Sondrio        ","46:10:16","-09:52:15"),
  new place("Taranto        ","40:28:30","-17:13:45"),
  new place("Teramo         ","42:39:27","-13:44:00"),
  new place("Terni          ","42:33:40","-12:38:45"),
  new place("Torino         ","45:04:14","-07:41:00"),
  new place("Trapani        ","38:01:53","-12:30:30"),
  new place("Trento         ","46:03:59","-11:07:15"),
  new place("Treviso        ","45:39:55","-12:14:45"),
  new place("Trieste        ","45:38:35","-13:45:45"),
  new place("Udine          ","46:03:34","-13:14:00"),
  new place("Varese         ","45:49:04","-08:49:45"),
  new place("Venezia        ","45:26:01","-12:20:15"),
  new place("Verbania       ","45:56:00","-08:32:00"),
  new place("Vercelli       ","45:19:46","-08:25:15"),
  new place("Verona         ","45:26:33","-10:59:45"),
  new place("Vibo Valentia  ","38:40:00","-16:05:00"),
  new place("Vicenza        ","45:32:48","-11:32:45"),
  new place("Viterbo        ","42:24:59","-12:06:15")
);


function observatory(place,year,month,day,hr,min,sec) {
// The observatory object holds local date and time,
// timezone correction in minutes with daylight saving if applicable,
// latitude and longitude (west is positive)
	this.name = place.name;
	this.year = year;
	this.month = month;
	this.day = day;
	this.hours = hr;
	this.minutes = min;
	this.seconds = sec;
	this.latitude = place.latitude;
	this.longitude = place.longitude;
}

// The default observatory (Roma noon Jan 1 2000) 
// changed by user setting place and time from menu

var observer  = new observatory(atlas[0],2000,1,1,12,0,0);

// Site name returns name and latitude / longitude as a string
function sitename() {
  var sname=observer.name;
	sname += " [" + dmsstring(observer.latitude) + (observer.latitude>0?" N,":" S,");
	sname += dmsstring(observer.longitude) + (observer.longitude>0?" W]":" E]");
  return sname;
}	// sitename()

function jdDelta(obs) {
// The Julian date at observer time
	var j = jd0(obs.year,obs.month,obs.day);
	j+=(obs.hours+obs.minutes/60.0+obs.seconds/3600.0)/24;
	var delta=60;
	if (myCheckDSTymd(obs.year,obs.month,obs.day)) delta += 60;
	j -= delta/1440.0;
	return j;
}	// jdDelta()

function jdnoDelta(obs) {
// The Julian date at observer time
	var j = jd0(obs.year,obs.month,obs.day);
	j+=(obs.hours+obs.minutes/60.0+obs.seconds/3600.0)/24;
	return j;
}	// jdnoDelta()

function local_sidereal(obs) {
// sidereal time in hours for observer
	var res=g_sidereal(obs.year,obs.month,obs.day);
////	var delta=60;
////	if (myCheckDSTymd(obs.year,obs.month,obs.day)) delta += 60;
	var delta=0;
	res+=1.00273790935*(obs.hours+(obs.minutes+delta+(obs.seconds/60.0))/60.0);
	res-=obs.longitude/15.0;
	while (res < 0) res+=24.0;
	while (res > 24) res-=24.0;
	return res;
}

function datestring(obs) {
// datestring provides a locale independent format
  var datestr = "";  
  datestr += ((obs.day < 10) ? "0" : "") + obs.day;
  datestr += ((obs.month < 10) ? "/0" : "/") + obs.month;
  datestr += "/" + obs.year;
  return datestr;
}		// end datestring()


function datestring2(year,month,day) {
  var datestr = "";  
  datestr += ((day < 10) ? "0" : "") + day;
  datestr += ((month < 10) ? "/0" : "/") + month;
  datestr += "/" + year;
  var j = jd0(year,month,day);
  var d = (j + 1.5) % 7;
  datestr += " " + dow[d];
  return datestr;
}		// end datestring2()


function datestring2nodow(year,month,day) {
  var datestr = "";  
  datestr += ((day < 10) ? "0" : "") + day;
  datestr += ((month < 10) ? "/0" : "/") + month;
  datestr += "/" + year;
  return datestr;
}		// end datestring2()


function adjustTime(obs,amount) {
// update date and time, amount is in minutes (may be negative)
// added 2004
    month_length[1] = leapyear(obs.year) ? 29 : 28;
	if (amount<0) {
		amount = Math.abs(amount);
		obs.minutes -= amount%60; amount = Math.floor(amount/60.0);
		obs.hours -= amount%24; amount = Math.floor(amount/24.0);
		obs.day -= amount;
		if (obs.minutes < 0) {
			obs.minutes += 60;
			obs.hours -= 1;
		}
		if (obs.hours < 0) {
			obs.hours += 24;
			obs.day -= 1;
		}
		while (obs.day < 1) {
			obs.day += month_length[obs.month-2+(obs.month==1?12:0)];
			obs.month -= 1; 
			if (obs.month == 0) {
				obs.year -= 1;
				obs.month = 12;
				month_length[1] = (leapyear(obs.year) ? 29 : 28);
			}
		}
	}
	else {
		obs.minutes+=amount%60; amount=Math.floor(amount/60.0);
		obs.hours+=amount%24; amount=Math.floor(amount/24.0);
		obs.day+=amount;
		if (obs.minutes > 59) {
			obs.minutes -= 60;
			obs.hours += 1;
		}
		if (obs.hours > 23) {
			obs.hours -= 24;
			obs.day += 1;
		}
		while (obs.day > month_length[obs.month-1]) {
			obs.day -= month_length[obs.month-1];
			obs.month += 1; 
			if (obs.month == 13) {
				obs.year += 1;
				obs.month = 1;
				month_length[1] = (leapyear(obs.year) ? 29 : 28);
			}
		}
	}
}		// end adjustTime()


function hmsstring(t) {
// the caller must add a leading + if required.
  var hours = Math.abs(t);
  var minutes = 60.0*(hours-Math.floor(hours));
  hours=Math.floor(hours);
  var seconds = Math.round(60.0*(minutes-Math.floor(minutes)));
  minutes=Math.floor(minutes);
  if (seconds >= 60) { minutes+=1; seconds-=60; }
  if (minutes >= 60) { hour+=1; minutes-=60; }
  if (hours >= 24) { hours-=24; }
  var hmsstr=(t < 0) ? "-" : "";
  hmsstr=((hours < 10) ? "0" : "" )+hours;
  hmsstr+=((minutes < 10) ? "h0" : "h" )+minutes;
  hmsstr+=((seconds < 10) ? "m0" : "m" )+seconds;
  hmsstr+="s";
  return hmsstr;
}		// end hmsstring()


function hmstring(t,plus) {
// hmstring converts hours to a string (+/-)hours:minutes
  var hours = Math.abs(t);
  var minutes = Math.round(60.0*(hours-Math.floor(hours)));
  hours=Math.floor(hours);
  if (minutes >= 60) { hours+=1; minutes-=60; }	// minutes could be 60 due to rounding
  if (hours >= 24) { hours-=24; }
  var hmstr = (t < 0) ? "-" : (plus?"+":"");
  hmstr += ((hours < 10) ? "0" : "" )+hours;
  hmstr += ((minutes < 10) ? ":0" : ":" )+minutes;
  return hmstr;
}		// end hmstring()


function hmstring2(hours,minutes,seconds) {
// hmstring2 returns time as a string HH:MM (added 2004.01.02), seconds needed for rounding
	if (seconds>=30) minutes++;
	if (minutes>=60) {hours++; minutes=0;}
	var timestr = ((hours < 10) ? "0" : "") + hours;	
	timestr += ((minutes < 10) ? ":0" : ":") + minutes;
	return timestr;
}		// end hmstring2()


function dmsstring(d) {
// dmsstring converts lat/long angle to unsigned string d:m:s
  var deg = Math.abs(d);
  var minutes = 60.0*(deg-Math.floor(deg));
  deg=Math.floor(deg);
  var seconds = Math.round(60.0*(minutes-Math.floor(minutes)));
  minutes=Math.floor(minutes);
  if (seconds >= 60) { minutes+=1; seconds-=60; }
  if (minutes >= 60) { deg+=1; minutes-=60; }
  hmsstr=((deg < 10) ? "0" : "" )+deg;
  hmsstr+=((minutes < 10) ? ":0" : ":" )+minutes;
  hmsstr+=((seconds < 10) ? ":0" : ":" )+seconds;
  return hmsstr;
}		// end dmsstring()


function dmsAngle(d) {
// dmsstring converts lat/long angle to string d°m's"
  var deg = Math.abs(d);
  var minutes = 60.0*(deg-Math.floor(deg));
  deg=Math.floor(deg);
  var seconds = Math.round(60.0*(minutes-Math.floor(minutes)));
  minutes=Math.floor(minutes);
  if (seconds >= 60) { minutes+=1; seconds-=60; }
  if (minutes >= 60) { deg+=1; minutes-=60; }
  hmsstr=((deg < 10) ? "0" : "" )+deg;
  hmsstr+=((minutes < 10) ? "°0" : "°" )+minutes;
  hmsstr+=((seconds < 10) ? "'0" : "'" )+seconds+'"';
  return hmsstr;
}		// end dmsstring()


function dmstring(d) {
// dmstring converts lat/long angle to unsigned string d:m
  var deg = Math.abs(d);
  var minutes = 60.0*(deg-Math.floor(deg));
  deg=Math.floor(deg);
  var seconds = Math.round(60.0*(minutes-Math.floor(minutes)));
  minutes=Math.floor(minutes);
  if (seconds >= 30) { minutes+=1; }
  if (minutes >= 60) { deg+=1; minutes-=60; }
  hmstr=((deg < 10) ? "0" : "" )+deg;
	hmstr+=((minutes < 10) ? ":0" : ":" )+minutes;
  return hmstr;
} // end dmstring()


function msstring(d) {
// dmsstring converts lat/long angle to unsigned string d:m:s
  var deg = Math.abs(d)/3600.0;
  var minutes = 60.0*(deg-Math.floor(deg));
  deg=Math.floor(deg);
  var seconds = Math.round(60.0*(minutes-Math.floor(minutes)));
  minutes=Math.floor(minutes);
  if (seconds >= 60) { minutes+=1; seconds-=60; }
  if (minutes >= 60) { deg+=1; minutes-=60; }
//  msstr=((deg < 10) ? "0" : "" )+deg;
	msstr=" ";
  msstr+=((minutes < 10) ? "0" : "" )+minutes;
  msstr+=((seconds < 10) ? "'0" : "'" )+seconds + "\"";
  return msstr;
}		// end msstring()


function anglestring(a,circle,arcmin) {
	// Return angle as degrees:minutes. 'circle' is true for range between 0 and 360 
	// and false for -90 to +90, if 'arcmin' use deg and arcmin symbols
  var ar=Math.round(a*60)/60;
  var deg=Math.abs(ar);
  var min=Math.round(60.0*(deg-Math.floor(deg)));
  if (min >= 60) { deg+=1; min=0; }
  var anglestr="";
  if (!circle) anglestr+=(ar < 0 ? "-" : "+");
  if (circle) anglestr+=((Math.floor(deg) < 100) ? "0" : "" );
  anglestr+=((Math.floor(deg) < 10) ? "0" : "" )+Math.floor(deg);
  if (arcmin) anglestr+=((min < 10) ? "&deg;0" : "&deg;")+(min)+"' ";
	else anglestr+=((min < 10) ? ":0" : ":" )+(min);
  return anglestr;
} // end anglestring()


function fixnum(n,l,d) {
	// convert float n to right adjusted string of length l with d digits after decimal point.
	// the sign always requires one character, allow for that in l!
	var m = 1;
	for (var i=0; i<d; i++) m*=10;
	var n1 = Math.round(Math.abs(n)*m);
	var nint = Math.floor(n1/m);
	var nfract = (n1 - m*nint) + ""; // force conversion to string
	while (nfract.length < d) nfract = "0" + nfract;
	var str = (n<0 ? "-" : " ") + nint;
	if (d > 0) str = str + "." + nfract;
	while (str.length<l) str = " " + str;
	return str;
} // end fixnum()

function fixnumsec(n,l,d) {
	// convert float n to right adjusted string of length l with d digits after decimal point.
	// the sign always requires one character, allow for that in l!
	var m = 1;
	for (var i=0; i<d; i++) m*=10;
	var n1 = Math.round(Math.abs(n)*m);
	var nint = Math.floor(n1/m);
	var nfract = (n1 - m*nint) + ""; // force conversion to string
	while (nfract.length < d) nfract = "0" + nfract;
	var str = (n<0 ? "-" : " ") + nint;
	if (d > 0) str = str + "\"." + nfract;
	while (str.length<l) str = " " + str;
	return str;
} // end fixnum()


function prisec(d){
	if (d < 60){
		return fixnumsec(d,7,1);
	}else{
		return msstring(d);
	}
}


function fixstr(str,l) {
	// returns left-adjusted string of length l, pad with spaces or truncate as necessary
	if (str.length > l) return str.substring(0,l); 
	while (str.length < l) {
		str += " ";
	}
	return str;
}		// end fixstr()


function parsecol(str) {
	// parsecol converts deg:min:sec or hr:min:sec to a number
  var col1=str.indexOf(":");
  var col2=str.lastIndexOf(":");
  if (col1 < 0) return parseInt(str);
  if (str.substring(0,1) == "-") {
    var res=parseInt(str.substring(1,col1),10);
  } else {
    var res=parseInt(str.substring(0,col1),10);
  }
  if (col2 > col1) {
    res+=(parseInt(str.substring(col1+1,col2),10)/60.0) +
         (parseInt(str.substring(col2+1,str.length),10)/3600.0);
  } else {
    res+=(parseInt(str.substring(col1+1,str.length),10)/60.0);
  }
  if (str.substring(0,1) == "-") {
    return -res;
  } else {
    return res;
  }
}	// end parsecol()


function interpol(n,y1,y2,y3) {
	// interpolate y (Meeus 3.3)
	var a = y2-y1;
	var b = y3-y2;
	var c = b-a;
	return y2+(n/2)*(a+b+n*c);
}


function nzero(y1,y2,y3) {
	// Calculate value of interpolation factor for which y=zero. n0 should be within [-1:1] 
	// Meeus formula (3.7)	
	var a = y2-y1; var b = y3-y2; var c = b-a;
	var n0 = 0;
	do {
		dn0 = -(2*y2 + n0*(a+b+c*n0)) / (a+b+2*c*n0);
		n0 += dn0;
	} while (Math.abs(dn0) > 0.0001);
	return n0;
}		// end nzero()


function nextrem(y1,y2,y3) {
	// Calculate value of interpolation factor for which y reaches extremum (-1<n<1);
	var a = y2-y1; var b = y3-y2; var c = b-a;
	var nm = -(a+b)/(2*c);	// (3.5)
	return nm;
}		// end nextrem();


function isort(arr) {
// Sort 2D array in ascending order on first column of each element using insertion sort 
	for (var c=0; c<arr.length-1; c++) {	
		var tmp = arr[c+1]; var a = c;
		while (a>=0 && arr[a][0]>tmp[0]) {
			arr[a+1] = arr[a];
			a--;
		}
		arr[a+1] = tmp;
	}
}	// end isort()


