// Copyright 2015 Olaf Frohn https://github.com/ofrohn, see LICENSE !(function() { var Celestial = { version: '0.6.8', container: null, data: [] }; var ANIMDISTANCE = 0.035, // Rotation animation threshold, ~2deg in radians ANIMSCALE = 1.4, // Zoom animation threshold, scale factor ANIMINTERVAL_R = 2000, // Rotation duration scale in ms ANIMINTERVAL_P = 2500, // Projection duration in ms ANIMINTERVAL_Z = 1500, // Zoom duration scale in ms ZOOMEXTENT = 10; // Maximum extent of zoom (max/min) var cfg, prjMap, zoom, map, circle; // Show it all, with the given config, otherwise with default settings Celestial.display = function(config) { var par, container = Celestial.container, animations = [], current = 0, repeat = false, aID; //Mash config with default settings cfg = settings.set(config); cfg.stars.size = cfg.stars.size || 7; // Nothing works without these cfg.stars.exponent = cfg.stars.exponent || -0.28; cfg.center = cfg.center || [0,0]; if (!cfg.lang || cfg.lang.search(/^de|es$/) === -1) cfg.lang = "name"; var parent = $(cfg.container); if (parent) { par = "#"+cfg.container; var st = window.getComputedStyle(parent, null); if (!parseInt(st.width) && !cfg.width) parent.style.width = px(parent.parentNode.clientWidth); } else { par = "body"; parent = null; } var margin = [16, 16], width = getWidth(), proj = getProjection(cfg.projection); if (cfg.lines.graticule.lat && cfg.lines.graticule.lat.pos[0] === "outline") proj.scale -= 2; if (!proj) return; var trans = cfg.transform || "equatorial", ratio = proj.ratio, height = width / ratio, scale = proj.scale * width/1024, starbase = cfg.stars.size, dsobase = cfg.dsos.size || starbase, starexp = cfg.stars.exponent, dsoexp = cfg.dsos.exponent || starexp, //Object size base & exponent adapt = 1, rotation = getAngles(cfg.center), path = cfg.datapath || ""; path = path.replace(/([^\/]$)/, "$1\/"); if (par != "body") $(cfg.container).style.height = px(height); prjMap = Celestial.projection(cfg.projection).rotate(rotation).translate([width/2, height/2]).scale(scale); zoom = d3.geo.zoom().projection(prjMap).center([width/2, height/2]).scaleExtent([scale, scale*ZOOMEXTENT]).on("zoom.redraw", redraw); var canvas = d3.select(par).selectAll("canvas"); if (canvas[0].length === 0) canvas = d3.select(par).append("canvas"); canvas.attr("width", width).attr("height", height); var context = canvas.node().getContext("2d"); var graticule = d3.geo.graticule().minorStep([15,10]); map = d3.geo.path().projection(prjMap).context(context); //parent div with id #celestial-map or body if (container) container.selectAll("*").remove(); else container = d3.select(par).append("container"); if (cfg.interactive) canvas.call(zoom); else canvas.attr("style", "cursor: default!important"); setClip(proj.clip); d3.select(window).on('resize', resize); d3.select(par).on('dblclick', function () { zoomBy(1.5625); return false; }); if (cfg.controls === true && $("celestial-zoomin") === null) { d3.select(par).append("input").attr("type", "button").attr("id", "celestial-zoomin").attr("value", "\u002b").on("click", function () { zoomBy(1.25); return false; }); d3.select(par).append("input").attr("type", "button").attr("id", "celestial-zoomout").attr("value", "\u2212").on("click", function () { zoomBy(0.8); return false; }); } if (cfg.location === true) { circle = d3.geo.circle().angle([90]); container.append("path").datum(circle).attr("class", "horizon"); if ($("loc") === null) geo(cfg); else if (cfg.follow === "zenith") rotate({center:Celestial.zenith()}); fldEnable("horizon-show", proj.clip); } if (cfg.form === true && $("params") === null) form(cfg); if ($("error") === null) d3.select("body").append("div").attr("id", "error"); function load() { //Celestial planes for (var key in cfg.lines) { if (!has(cfg.lines, key)) continue; if (key === "graticule") { container.append("path").datum(graticule).attr("class", "graticule"); if (has(cfg.lines.graticule, "lon") && cfg.lines.graticule.lon.pos.length > 0) container.selectAll(".gridvalues_lon") .data(getGridValues("lon", cfg.lines.graticule.lon.pos)) .enter().append("path") .attr("class", "graticule_lon"); if (has(cfg.lines.graticule, "lat") && cfg.lines.graticule.lat.pos.length > 0) container.selectAll(".gridvalues_lat") .data(getGridValues("lat", cfg.lines.graticule.lat.pos)) .enter().append("path") .attr("class", "graticule_lat"); } else { container.append("path") .datum(d3.geo.circle().angle([90]).origin(transformDeg(poles[key], euler[trans])) ) .attr("class", key); } } //Milky way outline d3.json(path + "mw.json", function(error, json) { if (error) { //window.alert("Your Browser doesn't support local file loading or the file doesn't exist. See readme.md"); return console.warn(error); } var mw = getData(json, trans); container.selectAll(".mway") .data(mw.features) .enter().append("path") .attr("class", "mw"); redraw(); }); //Constellation names or designation d3.json(path + "constellations.json", function(error, json) { if (error) return console.warn(error); var con = getData(json, trans); container.selectAll(".constnames") .data(con.features) .enter().append("text") .attr("class", "constname"); var l = getConstellationList(json, trans); if ($("constellation")) { var sel = d3.select("#constellation"), selected = 0, list = Object.keys(l).map( function (key, i) { if (key === config.constellation) selected = i; return {o:key, n:l[key].name}; }); list = [{o:"", n:"(Select constellation)"}].concat(list); sel.selectAll('option').data(list).enter().append('option') .attr("value", function (d) { return d.o; }) .text(function (d) { return d.n; }); sel.property("selectedIndex", selected); //$("constellation").firstChild.disabled = true; } Celestial.constellations = l; redraw(); }); //Constellation boundaries d3.json(path + "constellations.bounds.json", function(error, json) { if (error) return console.warn(error); var conb = getData(json, trans); container.selectAll(".bounds") .data(conb.features) .enter().append("path") .attr("class", "boundaryline"); redraw(); }); //Constellation lines d3.json(path + "constellations.lines.json", function(error, json) { if (error) return console.warn(error); var conl = getData(json, trans); container.selectAll(".lines") .data(conl.features) .enter().append("path") .attr("class", "constline"); redraw(); }); //Stars d3.json(path + cfg.stars.data, function(error, json) { if (error) return console.warn(error); var st = getData(json, trans); container.selectAll(".stars") .data(st.features) .enter().append("path") .attr("class", "star"); redraw(); }); //Deep space objects d3.json(path + cfg.dsos.data, function(error, json) { if (error) return console.warn(error); var ds = getData(json, trans); container.selectAll(".dsos") .data(ds.features) .enter().append("path") .attr("class", "dso" ); redraw(); }); //Planets, Sun & (Moon tbi) d3.json(path + "planets.json", function(error, json) { if (error) return console.warn(error); var pl = getPlanets(json, trans); container.selectAll(".planets") .data(pl) .enter().append("path") .attr("class", "planet"); redraw(); }); if (Celestial.data.length > 0) { Celestial.data.forEach( function(d) { if (has(d, "file")) d3.json(d.file, d.callback); else setTimeout(d.callback, 0); }, this); } } // Zoom by factor; >1 larger <1 smaller function zoomBy(factor) { if (!factor || factor === 1) return; var sc0 = prjMap.scale(), sc1 = sc0 * factor, ext = zoom.scaleExtent(), interval = ANIMINTERVAL_Z * Math.sqrt(Math.abs(1-factor)); if (sc1 < ext[0]) sc1 = ext[0]; if (sc1 > ext[1]) sc1 = ext[1]; var zTween = d3.interpolateNumber(sc0, sc1); d3.select({}).transition().duration(interval).tween("scale", function () { return function(t) { var z = zTween(t); prjMap.scale(z); redraw(); }; }).transition().duration(0).tween("scale", function () { zoom.scale(sc1); redraw(); }); return interval; } function apply(config) { cfg = cfg.set(config); redraw(); } function rotate(config) { var cFrom = cfg.center, rot = prjMap.rotate(), sc = prjMap.scale(), interval = ANIMINTERVAL_R, keep = false, cTween, zTween, oTween, oof = cfg.orientationfixed; if (Round(rot[1], 1) === -Round(config.center[1], 1)) keep = true; //keep lat fixed if equal cfg = cfg.set(config); var d = Round(d3.geo.distance(cFrom, cfg.center), 2); var o = d3.geo.distance([cFrom[2],0], [cfg.center[2],0]); if (d < ANIMDISTANCE && o < ANIMDISTANCE) { rotation = getAngles(cfg.center); prjMap.rotate(rotation); redraw(); } else { // Zoom interpolator if (sc > scale * ANIMSCALE) zTween = d3.interpolateNumber(sc, scale); else zTween = function () { return sc; }; // Orientation interpolator if (o === 0) oTween = function () { return rot[2]; }; else oTween = interpolateAngle(cFrom[2], cfg.center[2]); if (d > 3.14) cfg.center[0] -= 0.01; //180deg turn doesn't work well cfg.orientationfixed = false; // Rotation interpolator if (d === 0) cTween = function () { return cfg.center; }; else cTween = d3.geo.interpolate(cFrom, cfg.center); interval = (d !== 0) ? interval * d : interval * o; // duration scaled by ang. distance d3.select({}).transition().duration(interval).tween("center", function () { return function(t) { var c = getAngles(cTween(t)); c[2] = oTween(t); var z = t < 0.5 ? zTween(t) : zTween(1-t); if (keep) c[1] = rot[1]; prjMap.scale(z); prjMap.rotate(c); redraw(); }; }).transition().duration(0).tween("center", function () { cfg.orientationfixed = oof; rotation = getAngles(cfg.center); prjMap.rotate(rotation); redraw(); }); } return interval; } function resize(set) { width = getWidth(); if (cfg.width === width && !set) return; height = width/ratio; scale = proj.scale * width/1024; canvas.attr("width", width).attr("height", height); zoom.scaleExtent([scale, scale*ZOOMEXTENT]).scale(scale); prjMap.translate([width/2, height/2]).scale(scale); if (parent) parent.style.height = px(height); redraw(); } function reproject(config) { var prj = getProjection(config.projection); if (!prj) return; var rot = prjMap.rotate(), ctr = prjMap.center(), sc = prjMap.scale(), ext = zoom.scaleExtent(), prjFrom = Celestial.projection(cfg.projection).center(ctr).translate([width/2, height/2]).scale([ext[0]]), interval = ANIMINTERVAL_P, delay = 0, rTween = d3.interpolateNumber(ratio, prj.ratio); if (proj.clip != prj.clip) interval = 0; // Different clip = no transition var prjTo = Celestial.projection(config.projection).center(ctr).translate([width/2, width/prj.ratio/2]).scale([prj.scale * width/1024]); var bAdapt = cfg.adaptable; if (sc > ext[0]) { delay = zoomBy(0.1); setTimeout(reproject, delay, config); return delay + interval; } if (cfg.location) fldEnable("horizon-show", prj.clip); prjMap = projectionTween(prjFrom, prjTo); cfg.adaptable = false; d3.select({}).transition().duration(interval).tween("projection", function () { return function(_) { prjMap.alpha(_).rotate(rot); map.projection(prjMap); setClip(prj.clip); ratio = rTween(_); height = width/ratio; canvas.attr("width", width).attr("height", height); if (parent) parent.style.height = px(height); redraw(); }; }).transition().duration(0).tween("projection", function () { proj = prj; ratio = proj.ratio; height = width / proj.ratio; scale = proj.scale * width/1024; canvas.attr("width", width).attr("height", height); if (parent) parent.style.height = px(height); cfg.projection = config.projection; prjMap = Celestial.projection(config.projection).rotate(rot).translate([width/2, height/2]).scale(scale); map.projection(prjMap); setClip(proj.clip); zoom.projection(prjMap).scaleExtent([scale, scale*ZOOMEXTENT]).scale(scale); cfg.adaptable = bAdapt; redraw(); }); return interval; } function redraw() { var rot = prjMap.rotate(); if (cfg.adaptable) adapt = Math.sqrt(prjMap.scale()/scale); if (!adapt) adapt = 1; starbase = cfg.stars.size; starexp = cfg.stars.exponent; dsobase = cfg.dsos.size || starbase; dsoexp = cfg.dsos.exponent; if (cfg.orientationfixed) { rot[2] = cfg.center[2]; prjMap.rotate(rot); } cfg.center = [-rot[0], -rot[1], rot[2]]; setCenter(cfg.center, cfg.transform); clear(); drawOutline(); //Draw all types of objects on the canvas if (cfg.mw.show) { container.selectAll(".mw").each(function(d) { setStyle(cfg.mw.style); map(d); context.fill(); }); } for (var key in cfg.lines) { if (!has(cfg.lines, key)) continue; if (cfg.lines[key].show !== true) continue; setStyle(cfg.lines[key]); container.selectAll("."+key).attr("d", map); context.stroke(); } if (has(cfg.lines.graticule, "lon")) { setTextStyle(cfg.lines.graticule.lon); container.selectAll(".graticule_lon").each(function(d, i) { if (clip(d.geometry.coordinates)) { var pt = prjMap(d.geometry.coordinates); gridOrientation(pt, d.properties.orientation); context.fillText(d.properties.value, pt[0], pt[1]); } }); } if (has(cfg.lines.graticule, "lat")) { setTextStyle(cfg.lines.graticule.lat); container.selectAll(".graticule_lat").each(function(d, i) { if (clip(d.geometry.coordinates)) { var pt = prjMap(d.geometry.coordinates); gridOrientation(pt, d.properties.orientation); context.fillText(d.properties.value, pt[0], pt[1]); } }); } drawOutline(true); if (cfg.constellations.show) { if (cfg.constellations.bounds) { container.selectAll(".boundaryline").each(function(d) { setStyle(cfg.constellations.boundstyle); if (Celestial.constellation && Celestial.constellation === d.id) { context.lineWidth *= 1.5; context.setLineDash([]); } map(d); context.stroke(); }); drawOutline(true); } if (cfg.constellations.names) { setTextStyle(cfg.constellations.namestyle); container.selectAll(".constname").each( function(d) { if (clip(d.geometry.coordinates)) { setConstStyle(d.properties.rank, cfg.constellations.namestyle.font); var pt = prjMap(d.geometry.coordinates); context.fillText(constName(d), pt[0], pt[1]); } }); } if (cfg.constellations.lines) { container.selectAll(".constline").each(function(d) { setStyle(cfg.constellations.linestyle); map(d); context.stroke(); }); } } if (cfg.stars.show) { setStyle(cfg.stars.style); container.selectAll(".star").each(function(d) { if (clip(d.geometry.coordinates) && d.properties.mag <= cfg.stars.limit) { var pt = prjMap(d.geometry.coordinates), r = starSize(d); context.fillStyle = starColor(d); context.beginPath(); context.arc(pt[0], pt[1], r, 0, 2 * Math.PI); context.closePath(); context.fill(); if (cfg.stars.names && d.properties.mag <= cfg.stars.namelimit*adapt) { setTextStyle(cfg.stars.namestyle); context.fillText(starName(d), pt[0]+r, pt[1]); } if (cfg.stars.proper && d.properties.mag <= cfg.stars.propernamelimit*adapt) { setTextStyle(cfg.stars.propernamestyle); context.fillText(starProperName(d), pt[0]-r, pt[1]); } } }); } if (cfg.dsos.show) { container.selectAll(".dso").each(function(d) { if (clip(d.geometry.coordinates) && dsoDisplay(d.properties, cfg.dsos.limit)) { var pt = prjMap(d.geometry.coordinates), type = d.properties.type; setStyle(cfg.dsos.symbols[type]); var r = dsoSymbol(d, pt); if (has(cfg.dsos.symbols[type], "stroke")) context.stroke(); else context.fill(); if (cfg.dsos.names && dsoDisplay(d.properties, cfg.dsos.namelimit)) { setTextStyle(cfg.dsos.namestyle); context.fillStyle = cfg.dsos.symbols[type].fill; context.fillText(dsoName(d), pt[0]+r, pt[1]-r); } } }); } if (cfg.location && cfg.transform === "equatorial" && cfg.planets.show && Celestial.origin) { var dt = Celestial.date(), o = Celestial.origin(dt).spherical(); container.selectAll(".planet").each(function(d) { var id = d.id(); var p = d(dt).equatorial(o); if (clip(p.pos)) { var pt = prjMap(p.pos), sym = cfg.planets.symbols[id]; if (id === "lun") { Canvas.symbol().type("crescent").size(144).age(p.age).position(pt)(context); } else if (id === 'added-planet') { const pointStyle = { stroke: 'rgba(255, 0, 204, 1)', fill: 'rgba(255, 0, 204, 0.15)', width: 5, }; const textStyle = { fill:'rgba(255, 0, 204, 1)', font: '15px Helvetica, Arial, sans-serif', align: 'left', baseline: 'bottom' }; const r = 5; setStyle(pointStyle); // Start the drawing path context.beginPath(); // Thats a circle in html5 canvas context.arc(pt[0], pt[1], r, 0, 2 * Math.PI); // Finish the drawing path context.closePath(); // Draw a line along the path with the prevoiusly set stroke color and line width context.stroke(); // Fill the object path with the prevoiusly set fill color context.fill(); // Set text styles //setTextStyle(textStyle); // and draw text on canvas //context.fillText('assss', pt[0]+r, pt[1]+r); } else { setTextStyle(cfg.planets.style); context.fillStyle = sym.fill; context.fillText(sym.symbol, pt[0], pt[1]); } } }); } if (Celestial.data.length > 0) { Celestial.data.forEach( function(d) { d.redraw(); }); } // drawOutline(true); if (cfg.location && cfg.horizon.show && !proj.clip) { circle.origin(Celestial.nadir()); setStyle(cfg.horizon); container.selectAll(".horizon").datum(circle).attr("d", map); context.fill(); if (cfg.horizon.stroke) context.stroke(); } if (cfg.controls) { zoomState(prjMap.scale()); } } function drawOutline(stroke) { var rot = prjMap.rotate(); prjMap.rotate([0,0]); setStyle(cfg.background); container.selectAll(".outline").attr("d", map); if (stroke === true) context.stroke(); else context.fill(); prjMap.rotate(rot); } // Helper functions ------------------------------------------------- function clip(coords) { return proj.clip && d3.geo.distance(cfg.center, coords) > halfπ ? 0 : 1; } function setStyle(s) { context.fillStyle = s.fill || null; context.strokeStyle = s.stroke || null; context.lineWidth = s.width || null; context.globalAlpha = s.opacity || 1; context.font = s.font || null; if (has(s, "dash")) context.setLineDash(s.dash); else context.setLineDash([]); context.beginPath(); } function setTextStyle(s) { context.fillStyle = s.fill; context.textAlign = s.align || "left"; context.textBaseline = s.baseline || "bottom"; context.globalAlpha = s.opacity || 1; context.font = s.font; } function setConstStyle(rank, font) { if (!isArray(font)) context.font = font; else if (font.length === 1) context.font = font[0]; else if (rank > font.length) context.font = font[font.length-1]; else context.font = font[rank-1]; } function zoomState(sc) { var czi = $("celestial-zoomin"), czo = $("celestial-zoomout"); if (!czi || !czo) return; czi.disabled = sc >= scale*ZOOMEXTENT*0.99; czo.disabled = sc <= scale; } function setClip(setit) { if (setit) { prjMap.clipAngle(90); container.selectAll(".outline").remove(); container.append("path").datum(d3.geo.circle().angle([90])).attr("class", "outline"); } else { prjMap.clipAngle(null); container.selectAll(".outline").remove(); container.append("path").datum(graticule.outline).attr("class", "outline"); } } function dsoDisplay(prop, limit) { return prop.mag === 999 && Math.sqrt(parseInt(prop.dim)) > limit || prop.mag !== 999 && prop.mag <= limit; } function dsoSymbol(d, pt) { var prop = d.properties; var size = dsoSize(prop) || 9, type = dsoShape(prop.type); Canvas.symbol().type(type).size(size).position(pt)(context); return Math.sqrt(size)/2; } function dsoShape(type) { if (!type || !has(cfg.dsos.symbols, type)) return "circle"; else return cfg.dsos.symbols[type].shape; } function dsoSize(prop) { if (!prop.mag || prop.mag === 999) return Math.pow(parseInt(prop.dim) * dsobase * adapt / 7, 0.5); return Math.pow(2 * dsobase * adapt - prop.mag, dsoexp); } function dsoName(d) { var prop = d.properties; if (prop.name === "") return; if (cfg.dsos.desig && prop.desig) return prop.desig; return prop.name; } /*Star designation, if desig = false, no long desigs */ function starName(d) { var name = d.properties.desig; if (!cfg.stars.desig) return name.replace(/^(HD|HIP|V\d{3}).+/, ""); return name; } function starProperName(d) { var name = d.properties.name; return name; } function starSize(d) { var mag = d.properties.mag; if (mag === null) return 0.1; var r = starbase * adapt * Math.exp(starexp * (mag+2)); return Math.max(r, 0.1); } function starColor(d) { var bv = d.properties.bv; if (!cfg.stars.colors || isNaN(bv)) {return cfg.stars.style.fill; } return bvcolor(bv); } function constName(d) { return cfg.constellations.desig ? d.properties.desig : d.properties[cfg.lang]; } function gridOrientation(pos, orient) { var o = orient.split(""), h = "center", v = "middle"; for (var i = o.length-1; i >= 0; i--) { switch(o[i]) { case "N": v = "bottom"; break; case "S": v = "top"; break; case "E": h = "left"; pos[0] += 2; break; case "W": h = "right"; pos[0] -= 2; break; } } context.textAlign = h; context.textBaseline = v; return pos; } function clear() { context.clearRect(0, 0, width + margin[0], height + margin[1]); } function getWidth() { if (cfg.width && cfg.width > 0) return cfg.width; if (parent) return parent.clientWidth - margin[0]; return window.innerWidth - margin[0]*2; } function getProjection(p) { if (!has(projections, p)) return; var res = projections[p]; if (!has(res, "ratio")) res.ratio = 2; // Default w/h ratio 2:1 return res; } function getAngles(coords) { if (coords === null) return [0,0,0]; var rot = eulerAngles.equatorial; if (!coords[2]) coords[2] = 0; return [rot[0] - coords[0], rot[1] - coords[1], rot[2] + coords[2]]; } function animate() { if (!animations || animations.length < 1) return; var d, a = animations[current]; switch (a.param) { case "projection": d = reproject({projection:a.value}); break; case "center": d = rotate({center:a.value}); break; case "zoom": d = zoomBy(a.value); } if (a.callback) setTimeout(a.callback, d); current++; if (repeat === true && current === animations.length) current = 0; d = a.duration === 0 || a.duration < d ? d : a.duration; if (current < animations.length) aID = setTimeout(animate, d); } function stop() { clearTimeout(aID); //current = 0; //repeat = false; } // Exported objects and functions for adding data this.container = container; this.clip = clip; this.map = map; this.mapProjection = prjMap; this.context = context; this.setStyle = setStyle; this.setTextStyle = setTextStyle; this.setConstStyle = setConstStyle; this.dsoSymbol = dsoSymbol; this.redraw = redraw; this.resize = function(config) { if (config && has(config, "width")) cfg.width = config.width; resize(true); }; this.reload = function(config) { if (!config || !has(config, "transform")) return; trans = cfg.transform = config.transform; if (trans === "equatorial") graticule.minorStep([15,10]); else graticule.minorStep([10,10]); container.selectAll("*").remove(); setClip(); container.append("path").datum(circle).attr("class", "horizon"); load(); }; this.apply = function(config) { apply(config); }; this.reproject = function(config) { return reproject(config); }; this.rotate = function(config) { if (!config) return cfg.center; return rotate(config); }; this.zoomBy = function(factor) { if (!factor) return prjMap.scale()/scale; return zoomBy(factor); }; this.color = function(type) { if (!type) return "#000"; if (has(cfg.dsos.symbols, type)) return cfg.dsos.symbols[type].fill; return "#000"; }; this.animate = function(anims, dorepeat) { if (!anims) return; animations = anims; current = 0; repeat = dorepeat ? true : false; animate(); }; this.stop = function(wipe) { stop(); if (wipe === true) animations = []; }; this.go = function(index) { if (animations.length < 1) return; if (index && index < animations.length) current = index; animate(); }; if (!has(this, "date")) this.date = function() { console.log("Celestial.date() needs config.location = true to work." ); }; load(); }; //Flipped projection generated on the fly Celestial.projection = function(projection) { var p, raw, forward; if (!has(projections, projection)) { throw new Error("Projection not supported: " + projection); } p = projections[projection]; if (p.arg !== null) { raw = d3.geo[projection].raw(p.arg); } else { raw = d3.geo[projection].raw; } forward = function(λ, φ) { var coords = raw(-λ, φ); return coords; }; forward.invert = function(x, y) { try { var coords = raw.invert(x, y); coords[0] = coords && -coords[0]; return coords; } catch(e) { console.log(e); } }; return d3.geo.projection(forward); }; function projectionTween(a, b) { var prj = d3.geo.projection(raw).scale(1), center = prj.center, translate = prj.translate, α; function raw(λ, φ) { var pa = a([λ *= 180 / Math.PI, φ *= 180 / Math.PI]), pb = b([λ, φ]); return [(1 - α) * pa[0] + α * pb[0], (α - 1) * pa[1] - α * pb[1]]; } prj.alpha = function(_) { if (!arguments.length) return α; α = +_; var ca = a.center(), cb = b.center(), ta = a.translate(), tb = b.translate(); center([(1 - α) * ca[0] + α * cb[0], (1 - α) * ca[1] + α * cb[1]]); translate([(1 - α) * ta[0] + α * tb[0], (1 - α) * ta[1] + α * tb[1]]); return prj; }; delete prj.translate; delete prj.center; return prj.alpha(0); } var eulerAngles = { "equatorial": [0.0, 0.0, 0.0], "ecliptic": [0.0, 0.0, 23.4393], "galactic": [93.5949, 28.9362, -58.5988], "supergalactic": [137.3100, 59.5283, 57.7303] // "mars": [97.5,23.5,29] }; var poles = { "equatorial": [0.0, 90.0], "ecliptic": [-90.0, 66.5607], "galactic": [-167.1405, 27.1283], "supergalactic": [-76.2458, 15.7089] // "mars": [-42.3186, 52.8865] }; Celestial.eulerAngles = function () { return eulerAngles; }; Celestial.poles = function () { return poles; }; var τ = Math.PI*2, halfπ = Math.PI/2, deg2rad = Math.PI/180; //Transform equatorial into any coordinates, degrees function transformDeg(c, euler) { var res = transform( c.map( function(d) { return d * deg2rad; } ), euler); return res.map( function(d) { return d / deg2rad; } ); } //Transform equatorial into any coordinates, radians function transform(c, euler) { var x, y, z, β, γ, λ, φ, dψ, ψ, θ, ε = 1.0e-5; if (!euler) return c; λ = c[0]; // celestial longitude 0..2pi if (λ < 0) λ += τ; φ = c[1]; // celestial latitude -pi/2..pi/2 λ -= euler[0]; // celestial longitude - celestial coordinates of the native pole β = euler[1]; // inclination between the poles (colatitude) γ = euler[2]; // native coordinates of the celestial pole x = Math.sin(φ) * Math.sin(β) - Math.cos(φ) * Math.cos(β) * Math.cos(λ); if (Math.abs(x) < ε) { x = -Math.cos(φ + β) + Math.cos(φ) * Math.cos(β) * (1 - Math.cos(λ)); } y = -Math.cos(φ) * Math.sin(λ); if (x !== 0 || y !== 0) { dψ = Math.atan2(y, x); } else { dψ = λ - Math.PI; } ψ = (γ + dψ); if (ψ > Math.PI) ψ -= τ; if (λ % Math.PI === 0) { θ = φ + Math.cos(λ) * β; if (θ > halfπ) θ = Math.PI - θ; if (θ < -halfπ) θ = -Math.PI - θ; } else { z = Math.sin(φ) * Math.cos(β) + Math.cos(φ) * Math.sin(β) * Math.cos(λ); if (Math.abs(z) > 0.99) { θ = Math.abs(Math.acos(Math.sqrt(x*x+y*y))); if (z < 0) θ *= -1; } else { θ = Math.asin(z); } } return [ψ, θ]; } var euler = { "ecliptic": [-90.0, 23.4393, 90.0], "inverse ecliptic": [90.0, 23.4393, -90.0], "galactic": [-167.1405, 62.8717, 122.9319], "inverse galactic": [122.9319, 62.8717, -167.1405], "supergalactic": [283.7542, 74.2911, 26.4504], "inverse supergalactic": [26.4504, 74.2911, 283.7542], "init": function () { for (var key in this) { if (this[key].constructor == Array) { this[key] = this[key].map( function(val) { return val * deg2rad; } ); } } }, "add": function(name, ang) { if (!ang || !name || ang.length !== 3 || this.hasOwnProperty(name)) return; this[name] = ang.map( function(val) { return val * deg2rad; } ); return this[name]; } }; euler.init(); Celestial.euler = function () { return euler; }; var horizontal = function(dt, pos, loc) { //dt: datetime, pos: celestial coordinates [lat,lng], loc: location [lat,lng] var ha = getMST(dt, loc[1]) - pos[0]; if (ha < 0) ha = ha + 360; ha = ha * deg2rad; var dec = pos[1] * deg2rad; var lat = loc[0] * deg2rad; var alt = Math.asin(Math.sin(dec) * Math.sin(lat) + Math.cos(dec) * Math.cos(lat) * Math.cos(ha)); var az = Math.acos((Math.sin(dec) - Math.sin(alt) * Math.sin(lat)) / (Math.cos(alt) * Math.cos(lat))); if (Math.sin(ha) > 0) az = Math.PI * 2 - az; return [alt / deg2rad, az / deg2rad, 0]; }; horizontal.inverse = function(dt, hor, loc) { var alt = hor[0] * deg2rad; var az = hor[1] * deg2rad; var lat = loc[0] * deg2rad; var dec = Math.asin((Math.sin(alt) * Math.sin(lat)) + (Math.cos(alt) * Math.cos(lat) * Math.cos(az))); var ha = ((Math.sin(alt) - (Math.sin(dec) * Math.sin(lat))) / (Math.cos(dec) * Math.cos(lat))).toFixed(6); ha = Math.acos(ha); ha = ha / deg2rad; var ra = getMST(dt, loc[1]) - ha; //if (ra < 0) ra = ra + 360; return [ra, dec / deg2rad, 0]; }; function getMST(dt, lng) { var yr = dt.getUTCFullYear(); var mo = dt.getUTCMonth() + 1; var dy = dt.getUTCDate(); var h = dt.getUTCHours(); var m = dt.getUTCMinutes(); var s = dt.getUTCSeconds(); if ((mo == 1)||(mo == 2)) { yr = yr - 1; mo = mo + 12; } var a = Math.floor(yr / 100); var b = 2 - a + Math.floor(a / 4); var c = Math.floor(365.25 * yr); var d = Math.floor(30.6001 * (mo + 1)); // days since J2000.0 var jd = b + c + d - 730550.5 + dy + (h + m/60.0 + s/3600.0)/24.0; // julian centuries since J2000.0 var jt = jd/36525.0; // the mean sidereal time in degrees var mst = 280.46061837 + 360.98564736629*jd + 0.000387933*jt*jt - jt*jt*jt/38710000 + lng; // in degrees modulo 360.0 if (mst > 0.0) while (mst > 360.0) mst = mst - 360.0; else while (mst < 0.0) mst = mst + 360.0; return mst; } Celestial.horizontal = horizontal; //Add more JSON data to the map Celestial.add = function(dat) { var res = {}; //dat: {file: path, type:'json|raw', callback: func(), redraw: func()} //or {file:file, size:null, shape:null, color:null} TBI // with size,shape,color: "prop=val:result;.." || function(prop) { .. return res; } if (!has(dat, "type")) return console.log("Missing type"); if ((dat.type === "dso" || dat.type === "json") && (!has(dat, "file") || !has(dat, "callback"))) return console.log("Can't add data file"); if ((dat.type === "line" || dat.type === "raw") && !has(dat, "callback")) return console.log("Can't add line"); if (has(dat, "file")) res.file = dat.file; res.type = dat.type; if (has(dat, "callback")) res.callback = dat.callback; if (has(dat, "redraw")) res.redraw = dat.redraw; Celestial.data.push(res); }; Celestial.remove = function(i) { if (i !== null && i < Celestial.data.length) { return Celestial.data.splice(i,1); } }; Celestial.clear = function() { Celestial.data = []; }; //load data and transform coordinates function getPoint(coords, trans) { return transformDeg(coords, euler[trans]); } function getData(d, trans) { if (trans === "equatorial") return d; var leo = euler[trans], f = d.features; for (var i=0; i Math.PI, termdir = Math.abs(ph) > 0.5 ? dir : !dir; ctx.save(); ctx.fillStyle = "#557"; ctx.moveTo(pos[0], pos[1]); ctx.arc(pos[0], pos[1], r, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#eee"; ctx.beginPath(); ctx.moveTo(pos[0], pos[1]); ctx.arc(pos[0], pos[1], r, -Math.PI/2, Math.PI/2, dir); ctx.scale(e, 1); ctx.arc(pos[0]/e, pos[1], r, Math.PI/2, -Math.PI/2, termdir); ctx.closePath(); ctx.fill(); ctx.restore(); return r; } }; canvas_symbol.type = function(_) { if (!arguments.length) return type; type = d3.functor(_); return canvas_symbol; }; canvas_symbol.size = function(_) { if (!arguments.length) return size; size = d3.functor(_); return canvas_symbol; }; canvas_symbol.age = function(_) { if (!arguments.length) return age; age = d3.functor(_); return canvas_symbol; }; canvas_symbol.text = function(_) { if (!arguments.length) return text; text = d3.functor(_); return canvas_symbol; }; canvas_symbol.position = function(_) { if (!arguments.length) return; pos = _; return canvas_symbol; }; return canvas_symbol; }; Celestial.Canvas = Canvas; /*var color = "#fff", angle = 0, align = "center", baseline = "middle", font = "10px sans-serif", padding = [0,0], aPos, sText; canvas.text = function () { function txt(ctx){ ctx.fillStyle = color; ctx.textAlign = align; ctx.textBaseline = baseline; //var pt = projection(d.geometry.coordinates); if (angle) { canvas.save(); canvas.translate(aPos[0], aPos[1]); canvas.rotate(angle); canvas.fillText(sText, 0, 0); canvas.restore(); } else canvas.fillText(sText, aPos[0], aPos[1]); } txt.angle = function(x) { if (!arguments.length) return angle * 180 / Math.PI; color = x * Math.PI / 180; return txt; }; txt.color = function(s) { if (!arguments.length) return color; color = s; return txt; }; txt.align = function(s) { if (!arguments.length) return align; align = s; return txt; }; txt.baseline = function(s) { if (!arguments.length) return baseline; baseline = s; return txt; }; txt.padding = function(a) { if (!arguments.length) return padding; padding = a; return txt; }; txt.text = function(s) { if (!arguments.length) return sText; sText = s; return txt; }; txt.font = function(s) { if (!arguments.length) return font; font = s; return txt; }; txt.style = function(o) { if (!arguments.length) return; if (o.fill) color = o.fill; if (o.font) font = o.font; return txt; }; } function ctxPath(d) { var pt; //d.map( function(axe, i) { context.beginPath(); for (var i = 0; i < d.length; i++) { pt = projection(d[i]); if (i === 0) context.moveTo(pt[0], pt[1]); else context.lineTo(pt[0], pt[1]); } context.fill(); } function ctxText(d, ang) { var pt = projection(d.geometry.coordinates); if (ang) { canvas.save(); canvas.translate(pt[0], pt[1]); canvas.rotate(Math.PI/2); canvas.fillText(txt, 0, 0); canvas.restore(); } else canvas.fillText(d.properties.txt, pt[0], pt[1]); } */ function $(id) { return document.getElementById(id); } function px(n) { return n + "px"; } function Round(x, dg) { return(Math.round(Math.pow(10,dg)*x)/Math.pow(10,dg)); } function sign(x) { return x ? x < 0 ? -1 : 1 : 0; } function pad(n) { return n < 10 ? '0' + n : n; } function has(o, key) { return o !== null && hasOwnProperty.call(o, key); } function when(o, key, val) { return o !== null && hasOwnProperty.call(o, key) ? o[key] : val; } function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } function isArray(o) { return Object.prototype.toString.call(o) === "[object Array]"; } function isObject(o) { var type = typeof o; return type === 'function' || type === 'object' && !!o; } function isFunction(o) { return typeof o == 'function' || false; } function findPos(o) { var l = 0, t = 0; if (o.offsetParent) { do { l += o.offsetLeft; t += o.offsetTop; } while ((o = o.offsetParent) !== null); } return [l, t]; } function hasParent(t, id){ while(t.parentNode){ if(t.id === id) return true; t = t.parentNode; } return false; } function attach(node, event, func) { if (node.addEventListener) node.addEventListener(event, func, false); else node.attachEvent("on" + event, func); } function stopPropagation(e) { if (typeof e.stopPropagation != "undefined") e.stopPropagation(); else e.cancelBubble = true; } function dateDiff(dt1, dt2, type) { var diff = dt2.valueOf() - dt1.valueOf(), tp = type || "d"; switch (tp) { case 'y': case 'yr': diff /= 31556926080; break; case 'm': case 'mo': diff /= 2629800000; break; case 'd': case 'dy': diff /= 86400000; break; case 'h': case 'hr': diff /= 3600000; break; case 'n': case 'mn': diff /= 60000; break; case 's': case 'sec': diff /= 1000; break; case 'ms': break; } return Math.floor(diff); } function dateParse(s) { if (!s) return; var t = s.split("."); if (t.length < 1) return; t = t[0].split("-"); t[0] = t[0].replace(/\D/g, ""); if (!t[0]) return; t[1] = t[1] ? t[1].replace(/\D/g, "") : "1"; t[2] = t[2] ? t[2].replace(/\D/g, "") : "1"; //Fraction -> h:m:s return new Date(Date.UTC(t[0], t[1]-1, t[2])); } function interpolateAngle(a1, a2, t) { a1 = (a1*deg2rad +τ) % τ; a2 = (a2*deg2rad + τ) % τ; if (Math.abs(a1 - a2) > Math.PI) { if (a1 > a2) a1 = a1 - τ; else if (a2 > a1) a2 = a2 - τ; } return d3.interpolateNumber(a1/deg2rad, a2/deg2rad); } var Trig = { sinh: function (val) { return (Math.pow(Math.E, val)-Math.pow(Math.E, -val))/2; }, cosh: function (val) { return (Math.pow(Math.E, val)+Math.pow(Math.E, -val))/2; }, tanh: function (val) { return 2.0 / (1.0 + Math.exp(-2.0 * val)) - 1.0; }, asinh: function (val) { return Math.log(val + Math.sqrt(val * val + 1)); }, acosh: function (val) { return Math.log(val + Math.sqrt(val * val - 1)); }, normalize0: function(val) { return ((val + Math.PI*3) % (Math.PI*2)) - Math.PI; }, normalize: function(val) { return ((val + Math.PI*2) % (Math.PI*2)); }, cartesian: function(p) { var ϕ = p[0], θ = halfπ - p[1], r = p[2]; return {"x": r * Math.sin(θ) * Math.cos(ϕ), "y": r * Math.sin(θ) * Math.sin(ϕ), "z": r * Math.cos(θ)}; }, spherical: function(p) { var r = Math.sqrt(p.x * p.x + p.y * p.y + p.z * p.z), θ = Math.atan(p.y / p.x), ϕ = Math.acos(p.z / r); return [θ / deg2rad, ϕ / deg2rad, r]; } }; //display settings form in div with id "celestial-form" function form(cfg) { var config = settings.set(cfg); var prj = Celestial.projections(), leo = Celestial.eulerAngles(); var ctrl = d3.select("#celestial-form").append("div").attr("class", "ctrl"); var frm = ctrl.append("form").attr("id", "params").attr("name", "params").attr("method", "get").attr("action" ,"#"); //Map parameters var col = frm.append("div").attr("class", "col").attr("id", "general"); col.append("label").attr("title", "Map width in pixel, 0 indicates full width").attr("for", "width").html("Width "); col.append("input").attr("type", "number").attr("maxlength", "4").attr("max", "20000").attr("min", "0").attr("title", "Map width").attr("id", "width").attr("value", config.width).on("change", resize); col.append("span").html("px"); col.append("label").attr("title", "Map projection, (hemi) indicates hemispherical projection").attr("for", "projection").html("Projection"); var sel = col.append("select").attr("id", "projection").on("change", reproject); var selected = 0; var list = Object.keys(prj).map( function (key, i) { var n = prj[key].clip && prj[key].clip === true ? prj[key].n + " (hemi)" : prj[key].n; if (key === config.projection) selected = i; return {o:key, n:n}; }); sel.selectAll('option').data(list).enter().append('option') .attr("value", function (d) { return d.o; }) .text(function (d) { return d.n; }); sel.property("selectedIndex", selected); selected = 0; col.append("label").attr("title", "Coordinate space in which the map is displayed").attr("for", "transform").html("Coordinates"); sel = col.append("select").attr("id", "transform").on("change", reload); list = Object.keys(leo).map(function (key, i) { if (key === config.transform) selected = i; return {o:key, n:key.replace(/^([a-z])/, function(s, m) { return m.toUpperCase(); } )}; }); sel.selectAll("option").data(list).enter().append('option') .attr("value", function (d) { return d.o; }) .text(function (d) { return d.n; }); sel.property("selectedIndex", selected); col.append("br"); col.append("label").attr("title", "Center coordinates long/lat in selected coordinate space").attr("for", "centerx").html("Center"); col.append("input").attr("type", "number").attr("id", "centerx").attr("title", "Center right ascension/longitude").attr("max", "24").attr("min", "0").attr("step", "0.1").on("change", turn); col.append("span").attr("id", "cxunit").html("h"); //addList("centerx", "ra"); col.append("input").attr("type", "number").attr("id", "centery").attr("title", "Center declination/latitude").attr("max", "90").attr("min", "-90").attr("step", "0.1").on("change", turn); col.append("span").html("\u00b0"); col.append("label").attr("title", "Orientation").attr("for", "centerz").html("Orientation"); col.append("input").attr("type", "number").attr("id", "centerz").attr("title", "Center orientation").attr("max", "180").attr("min", "-180").attr("step", "0.1").on("change", turn); col.append("span").html("\u00b0"); col.append("label").attr("for", "orientationfixed").html("Fixed"); col.append("input").attr("type", "checkbox").attr("id", "orientationfixed").property("checked", config.orientationfixed).on("change", apply); col.append("label").attr("title", "Center and zoom in on this constellation").attr("for", "constellation").html("Show"); col.append("select").attr("id", "constellation").on("change", showConstellation); setCenter(config.center, config.transform); // Stars col = frm.append("div").attr("class", "col").attr("id", "stars"); col.append("label").attr("class", "header").attr("for", "stars-show").html("Stars"); col.append("input").attr("type", "checkbox").attr("id", "stars-show").property("checked", config.stars.show).on("change", apply); col.append("label").attr("for", "stars-limit").html("down to magnitude"); col.append("input").attr("type", "number").attr("id", "stars-limit").attr("title", "Star display limit (magnitude)").attr("value", config.stars.limit).attr("max", "6").attr("min", "-1").attr("step", "0.1").on("change", apply); col.append("label").attr("for", "stars-colors").html("with spectral colors"); col.append("input").attr("type", "checkbox").attr("id", "stars-colors").property("checked", config.stars.colors).on("change", apply); col.append("label").attr("for", "stars-color").html("or default color "); col.append("input").attr("type", "color").attr("autocomplete", "off").attr("id", "stars-style-fill").attr("title", "Star color").property("value", config.stars.style.fill).on("change", apply); col.append("br"); col.append("label").attr("for", "stars-names").html("Show designations"); col.append("input").attr("type", "checkbox").attr("id", "stars-names").property("checked", config.stars.names).on("change", apply); col.append("label").attr("for", "stars-namelimit").html("down to mag"); col.append("input").attr("type", "number").attr("id", "stars-namelimit").attr("title", "Star designaton display limit (magnitude)").attr("value", config.stars.namelimit).attr("max", "6").attr("min", "-1").attr("step", "0.1").on("change", apply); col.append("label").attr("for", "stars-desig").attr("title", "include HD/HIP designations").html("all"); col.append("input").attr("type", "checkbox").attr("id", "stars-desig").property("checked", config.stars.desig).on("change", apply); col.append("label").attr("for", "stars-proper").html("proper names"); col.append("input").attr("type", "checkbox").attr("id", "stars-proper").property("checked", config.stars.proper).on("change", apply); col.append("label").attr("for", "stars-propernamelimit").html("down to mag"); col.append("input").attr("type", "number").attr("id", "stars-propernamelimit").attr("title", "Star name display limit (magnitude)").attr("value", config.stars.propernamelimit).attr("max", "6").attr("min", "-1").attr("step", "0.1").on("change", apply); col.append("br"); col.append("label").attr("for", "stars-size").html("Stellar disk size: base"); col.append("input").attr("type", "number").attr("id", "stars-size").attr("title", "Size of the displayed star disk; base").attr("value", config.stars.size).attr("max", "100").attr("min", "0").attr("step", "0.1").on("change", apply); col.append("label").attr("for", "stars-exponent").html(" * e ^ (exponent"); col.append("input").attr("type", "number").attr("id", "stars-exponent").attr("title", "Size of the displayed star disk; exponent").attr("value", config.stars.exponent).attr("max", "3").attr("min", "-1").attr("step", "0.01").on("change", apply); col.append("span").text(" * (magnitude + 2)) [* adaptation]"); enable($("stars-show")); // DSOs col = frm.append("div").attr("class", "col").attr("id", "dsos"); col.append("label").attr("class", "header").attr("title", "Deep Space Objects").attr("for", "dsos-show").html("DSOs"); col.append("input").attr("type", "checkbox").attr("id", "dsos-show").property("checked", config.dsos.show).on("change", apply); col.append("label").attr("for", "dsos-limit").html("down to mag"); col.append("input").attr("type", "number").attr("id", "dsos-limit").attr("title", "DSO display limit (magnitude)").attr("value", config.dsos.limit).attr("max", "6").attr("min", "0").attr("step", "0.1").on("change", apply); col.append("label").attr("for", "dsos-names").html("with names"); col.append("input").attr("type", "checkbox").attr("id", "dsos-names").property("checked", config.dsos.names).on("change", apply); col.append("label").attr("for", "dsos-desig").html("or designations"); col.append("input").attr("type", "checkbox").attr("id", "dsos-desig").property("checked", config.dsos.desig).on("change", apply); col.append("label").attr("for", "dsos-namelimit").html("down to mag"); col.append("input").attr("type", "number").attr("id", "dsos-namelimit").attr("title", "DSO name display limit (magnitude)").attr("value", config.dsos.namelimit).attr("max", "6").attr("min", "0").attr("step", "0.1").on("change", apply); col.append("br"); col.append("label").attr("for", "dsos-size").html("DSO symbol size: (base"); col.append("input").attr("type", "number").attr("id", "dsos-size").attr("title", "Size of the displayed symbol: base").attr("value", config.dsos.size).attr("max", "100").attr("min", "0").attr("step", "0.1").on("change", apply); col.append("label").attr("for", "dsos-exponent").html(" * 2 [* adaptation] - magnitude) ^ exponent"); col.append("input").attr("type", "number").attr("id", "dsos-exponent").attr("title", "Size of the displayed symbol; exponent").attr("value", config.dsos.exponent).attr("max", "3").attr("min", "-1").attr("step", "0.01").on("change", apply); enable($("dsos-show")); // Constellations col = frm.append("div").attr("class", "col").attr("id", "constellations"); col.append("label").attr("class", "header").html("Constellations"); //col.append("input").attr("type", "checkbox").attr("id", "constellations-show").property("checked", config.constellations.show).on("change", apply); col.append("label").attr("for", "constellations-names").html("Show names"); col.append("input").attr("type", "checkbox").attr("id", "constellations-names").property("checked", config.constellations.names).on("change", apply); col.append("label").attr("for", "constellations-desig").html("abbreviated"); col.append("input").attr("type", "checkbox").attr("id", "constellations-desig").property("checked", config.constellations.desig).on("change", apply); col.append("label").attr("for", "constellations-lines").html("with lines"); col.append("input").attr("type", "checkbox").attr("id", "constellations-lines").property("checked", config.constellations.lines).on("change", apply); col.append("label").attr("for", "constellations-bounds").html("with boundaries"); col.append("input").attr("type", "checkbox").attr("id", "constellations-bounds").property("checked", config.constellations.bounds).on("change", apply); enable($("constellations-names")); // graticules & planes col = frm.append("div").attr("class", "col").attr("id", "lines"); col.append("label").attr("class", "header").html("Lines"); col.append("label").attr("title", "Laitudet/longitude grid lines").attr("for", "lines-graticule").html("Graticule"); col.append("input").attr("type", "checkbox").attr("id", "lines-graticule-show").property("checked", config.lines.graticule.show).on("change", apply); col.append("label").attr("for", "lines-equatorial").html("Equator"); col.append("input").attr("type", "checkbox").attr("id", "lines-equatorial-show").property("checked", config.lines.equatorial.show).on("change", apply); col.append("label").attr("for", "lines-ecliptic").html("Ecliptic"); col.append("input").attr("type", "checkbox").attr("id", "lines-ecliptic-show").property("checked", config.lines.ecliptic.show).on("change", apply); col.append("label").attr("for", "lines-galactic").html("Galactic plane"); col.append("input").attr("type", "checkbox").attr("id", "lines-galactic-show").property("checked", config.lines.galactic.show).on("change", apply); col.append("label").attr("for", "lines-supergalactic").html("Supergalactic plane"); col.append("input").attr("type", "checkbox").attr("id", "lines-supergalactic-show").property("checked", config.lines.supergalactic.show).on("change", apply); // Other col = frm.append("div").attr("class", "col").attr("id", "other"); col.append("label").attr("class", "header").html("Other"); col.append("label").attr("for", "mw-show").html("Milky Way"); col.append("input").attr("type", "checkbox").attr("id", "mw-show").property("checked", config.mw.show).on("change", apply); col.append("label").attr("for", "mw-style-fill").html(" color"); col.append("input").attr("type", "color").attr("id", "mw-style-fill").attr("title", "Milky Way color").attr("value", config.mw.style.fill).on("change", apply); col.append("label").attr("for", "mw-style-opacity").html(" opacity"); col.append("input").attr("type", "number").attr("id", "mw-style-opacity").attr("title", "Transparency of each Milky Way layer").attr("value", config.mw.style.opacity).attr("max", "1").attr("min", "0").attr("step", "0.01").on("change", apply); col.append("br"); col.append("label").attr("for", "background").html("Background color"); col.append("input").attr("type", "color").attr("id", "background-fill").attr("title", "Background color").attr("value", config.background.fill).on("change", apply); col.append("label").attr("title", "Star/DSO sizes are increased with higher zoom-levels").attr("for", "adaptable").html("Adaptable object sizes"); col.append("input").attr("type", "checkbox").attr("id", "adaptable").property("checked", config.adaptable).on("change", apply); setLimits(); setUnit(config.transform); function resize() { var src = this, w = src.value; if (testNumber(src) === false) return; config.width = w; Celestial.resize({width:w}); } function reload() { var src = this, trans = src.value, cx = setUnit(trans, config.transform); if (cx !== null) config.center[0] = cx; config.transform = trans; Celestial.reload({transform:trans}); } function reproject() { var src = this; if (!src) return; config.projection = src.value; Celestial.reproject(config); } function turn() { if (testNumber(this) === false) return; if (getCenter() === false) return; Celestial.rotate(config); } function getCenter() { var cx = $("centerx"), cy = $("centery"), cz = $("centerz"), rot = []; if (!cx || !cy) return; if (config.transform !== "equatorial") config.center[0] = parseFloat(cx.value); else { var vx = parseFloat(cx.value); config.center[0] = vx > 12 ? vx * 15 - 360 : vx * 15; } config.center[1] = parseFloat(cy.value); var vz = parseFloat(cz.value); config.center[2] = isNaN(vz) ? 0 : vz; return cx.value !== "" && cy.value !== ""; } function showConstellation() { var id = this.value, anims = []; if (id === "") { Celestial.constellation = null; Celestial.redraw(); return; } var con = Celestial.constellations[id]; config.center = con.center; setCenter(config.center, config.transform); //config.lines.graticule.lat.pos = [Round(con.center[0])]; //config.lines.graticule.lon.pos = [Round(con.center[1])]; //Celestial.apply(config); //if zoomed, zoom out var z = Celestial.zoomBy(); if (z !== 1) anims.push({param:"zoom", value:1/z, duration:0}); //rotate anims.push({param:"center", value:con.center, duration:0}); //and zoom in var sc = con.scale > 10 ? 10 : con.scale; anims.push({param:"zoom", value:sc, duration:0}); Celestial.constellation = id; Celestial.animate(anims, false); } function apply() { var value, src = this; switch (src.type) { case "checkbox": value = src.checked; enable(src); break; case "number": if (testNumber(src) === false) return; value = parseFloat(src.value); break; case "color": if (testColor(src) === false) return; value = src.value; break; case "text": if (src.id.search(/fill$/) === -1) return; if (testColor(src) === false) return; value = src.value; break; } if (value === null) return; set(src.id, value); getCenter(); Celestial.apply(config); } function set(prop, val) { var a = prop.split("-"); switch (a.length) { case 1: config[a[0]] = val; break; case 2: config[a[0]][a[1]] = val; break; case 3: config[a[0]][a[1]][a[2]] = val; break; default: return; } } } // Dependend fields relations var depends = { "stars-show": ["stars-limit", "stars-colors", "stars-style-fill", "stars-names", "stars-size", "stars-exponent"], "stars-names": ["stars-proper", "stars-desig", "stars-namelimit"], "stars-proper": ["stars-propernamelimit"], "dsos-show": ["dsos-limit", "dsos-names", "dsos-size", "dsos-exponent"], "dsos-names": ["dsos-desig", "dsos-namelimit"], "mw-show": ["mw-style-opacity", "mw-style-fill"], "constellations-names": ["constellations-desig"] }; // De/activate fields depending on selection of dependencies function enable(source) { var fld = source.id, off; switch (fld) { case "stars-show": off = !$(fld).checked; for (var i=0; i< depends[fld].length; i++) { fldEnable(depends[fld][i], off); } /* falls through */ case "stars-names": off = !$("stars-names").checked || !$("stars-show").checked; for (i=0; i< depends["stars-names"].length; i++) { fldEnable(depends["stars-names"][i], off); } /* falls through */ case "stars-proper": off = !$("stars-names").checked || !$("stars-show").checked || !$("stars-proper").checked; for (i=0; i< depends["stars-proper"].length; i++) { fldEnable(depends["stars-proper"][i], off); } break; case "dsos-show": off = !$(fld).checked; for (i=0; i< depends[fld].length; i++) { fldEnable(depends[fld][i], off); } /* falls through */ case "dsos-names": off = !$("dsos-names").checked || !$("dsos-show").checked; for (i=0; i< depends["dsos-names"].length; i++) { fldEnable(depends["dsos-names"][i], off); } break; case "constellations-show": off = !$(fld).checked; for (i=0; i< depends[fld].length; i++) { fldEnable(depends[fld][i], off); } break; case "mw-show": off = !$(fld).checked; for (i=0; i< depends[fld].length; i++) { fldEnable(depends[fld][i], off); } break; } } // Enable/disable field d to status off function fldEnable(d, off) { var node = $(d); node.disabled = off; node.previousSibling.style.color = off ? "#999" : "#000"; } // Error notification function popError(nd, err) { var p = findPos(nd); d3.select("#error").html(err).style( {top:px(p[1] + nd.offsetHeight + 1), left:px(p[0]), opacity:1} ); nd.focus(); } //Check numeric field function testNumber(node) { var v, adj = node.id === "hr" || node.id === "min" || node.id === "sec" ? 1 : 0; if (node.validity) { v = node.validity; if (v.typeMismatch || v.badInput) { popError(node, node.title + ": check field value"); return false; } if (v.rangeOverflow || v.rangeUnderflow) { popError(node, node.title + " must be between " + (parseInt(node.min) + adj) + " and " + (parseInt(node.max) - adj)); return false; } } else { v = node.value; if (!isNumber(v)) { popError(node, node.title + ": check field value"); return false; } v = parseFloat(v); if (v < node.min || v > node.max ) { popError(node, node.title + " must be between " + (node.min + adj) + " and " + (+node.max - adj)); return false; } } d3.select("#error").style( {top:"-9999px", left:"-9999px", opacity:0} ); return true; } //Check color field function testColor(node) { var v; if (node.validity) { v = node.validity; if (v.typeMismatch || v.badInput) { popError(node, node.title + ": check field value"); return false; } if (node.value.search(/^#[0-9A-F]{6}$/i) === -1) { popError(node, node.title + ": not a color value"); return false; } } else { v = node.value; if (v === "") return true; if (v.search(/^#[0-9A-F]{6}$/i) === -1) { popError(node, node.title + ": not a color value"); return false; } } d3.select("#error").style( {top:"-9999px", left:"-9999px", opacity:0} ); return true; } function setUnit(trans, old) { var cx = $("centerx"); if (!cx) return null; if (old) { if (trans === "equatorial" && old !== "equatorial") { cx.value = (cx.value/15).toFixed(1); if (cx.value < 0) cx.value += 24; } else if (trans !== "equatorial" && old === "equatorial") { cx.value = (cx.value * 15).toFixed(1); if (cx.value > 180) cx.value -= 360; } } if (trans === 'equatorial') { cx.min = "0"; cx.max = "24"; $("cxunit").innerHTML = "h"; } else { cx.min = "-180"; cx.max = "180"; $("cxunit").innerHTML = "\u00b0"; } return cx.value; } function setCenter(ctr, trans) { var cx = $("centerx"), cy = $("centery"), cz = $("centerz"); if (!cx || !cy) return; if (ctr === null) ctr = [0,0,0]; if (ctr.length <= 2) ctr[2] = 0; //config.center = ctr; if (trans !== "equatorial") cx.value = ctr[0].toFixed(1); else cx.value = ctr[0] < 0 ? (ctr[0] / 15 + 24).toFixed(1) : (ctr[0] / 15).toFixed(1); cy.value = ctr[1].toFixed(1); cz.value = ctr[2] !== null ? ctr[2].toFixed(1) : 0; } // Set max input limits depending on data function setLimits() { var t, rx = /\d+(\.\d+)?/g, s, d, res = {s:6, d:6}, config = Celestial.settings(); d = config.dsos.data; //test dso limit t = d.match(rx); if (t !== null) { res.d = parseFloat(t[t.length-1]); } if (res.d != 6) { $("dsos-limit").max = res.d; $("dsos-namelimit").max = res.d; } s = config.stars.data; //test star limit t = s.match(rx); if (t !== null) { res.s = parseFloat(t[t.length-1]); } if (res.s != 6) { $("stars-limit").max = res.s; $("stars-namelimit").max = res.s; } return res; } function geo(cfg) { var frm = d3.select("#celestial-form").append("div").attr("id", "loc"), dtFormat = d3.time.format("%Y-%m-%d %H:%M:%S"), zenith = [0,0], geopos = [0,0], date = new Date(), zone = date.getTimezoneOffset(); var dtpick = new datetimepicker(cfg, function(date, tz) { $("datetime").value = dateFormat(date, tz); zone = tz; go(); }); if (has(cfg, "geopos") && cfg.geopos !== null && cfg.geopos.length === 2) geopos = cfg.geopos; var col = frm.append("div").attr("class", "col").attr("id", "location"); //Latitude & longitude fields col.append("label").attr("title", "Location coordinates long/lat").attr("for", "lat").html("Location"); col.append("input").attr("type", "number").attr("id", "lat").attr("title", "Latitude").attr("placeholder", "Latitude").attr("max", "90").attr("min", "-90").attr("step", "0.0001").attr("value", geopos[0]).on("change", function () { if (testNumber(this) === true) go(); }); col.append("span").html("\u00b0"); col.append("input").attr("type", "number").attr("id", "lon").attr("title", "Longitude").attr("placeholder", "Longitude").attr("max", "180").attr("min", "-180").attr("step", "0.0001").attr("value", geopos[1]).on("change", function () { if (testNumber(this) === true) go(); }); col.append("span").html("\u00b0"); //Here-button if supported if ("geolocation" in navigator) { col.append("input").attr("type", "button").attr("value", "Here").attr("id", "here").on("click", here); } //Datetime field with dtpicker-button col.append("label").attr("title", "Local date/time").attr("for", "datetime").html(" Date/time"); col.append("input").attr("type", "button").attr("id", "day-left").attr("title", "One day back").on("click", function () { date.setDate(date.getDate() - 1); $("datetime").value = dateFormat(date, zone); go(); }); col.append("input").attr("type", "text").attr("id", "datetime").attr("title", "Date and time").attr("value", dateFormat(date, zone)) .on("click", showpick, true).on("input", function () { this.value = dateFormat(date, zone); if (!dtpick.isVisible()) showpick(); }); col.append("div").attr("id", "datepick").on("click", showpick); col.append("input").attr("type", "button").attr("id", "day-right").attr("title", "One day forward").on("click", function () { date.setDate(date.getDate() + 1); $("datetime").value = dateFormat(date, zone); go(); }); //Now -button sets current time & date of device col.append("input").attr("type", "button").attr("value", "Now").attr("id", "now").on("click", now); //Horizon marker col.append("br"); col.append("label").attr("title", "Show horizon marker").attr("for", "horizon-show").html(" Horizon marker"); col.append("input").attr("type", "checkbox").attr("id", "horizon-show").property("checked", cfg.horizon.show).on("change", go); col.append("label").attr("title", "Show solar system objects").attr("for", "planets-show").html(" Planets, Sun & Moon"); col.append("input").attr("type", "checkbox").attr("id", "planets-show").property("checked", cfg.planets.show).on("change", go); d3.select(document).on("mousedown", function () { if (!hasParent(d3.event.target, "celestial-date") && dtpick.isVisible()) dtpick.hide(); }); function now() { date.setTime(Date.now()); $("datetime").value = dateFormat(date, zone); go(); } function here() { navigator.geolocation.getCurrentPosition( function(pos) { geopos = [Round(pos.coords.latitude, 4), Round(pos.coords.longitude, 4)]; $("lat").value = geopos[0]; $("lon").value = geopos[1]; go(); }); } function showpick() { dtpick.show(date); } function dateFormat(dt, tz) { var tzs; if (!tz || tz === "0") tzs = " ±0000"; else { var h = Math.floor(Math.abs(tz) / 60), m = Math.abs(tz) - (h * 60), s = tz < 0 ? " +" : " −"; tzs = s + pad(h) + pad(m); } return dtFormat(dt) + tzs; } function go() { var lon = $("lon").value, lat = $("lat").value; date = dtFormat.parse($("datetime").value.slice(0,-6)); var tz = date.getTimezoneOffset(); var dtc = new Date(date.valueOf() + (zone - tz) * 60000); cfg.horizon.show = !!$("horizon-show").checked; cfg.planets.show = !!$("planets-show").checked; if (lon !== "" && lat !== "") { geopos = [parseFloat(lat), parseFloat(lon)]; zenith = Celestial.getPoint(horizontal.inverse(dtc, [90, 0], geopos), cfg.transform); zenith[2] = 0; if (cfg.follow === "zenith") { Celestial.rotate({center:zenith, horizon:cfg.horizon}); } else { Celestial.apply({horizon:cfg.horizon}); } } } Celestial.getPosition = function (p) { }; Celestial.date = function (dt) { if (!dt) return date; if (dtpick.isVisible()) return; date.setTime(dt.valueOf()); $("datetime").value = dateFormat(dt, zone); Celestial.redraw(); }; Celestial.position = function () { return geopos; }; Celestial.zenith = function () { return zenith; }; Celestial.nadir = function () { var b = -zenith[1], l = zenith[0] + 180; if (l > 180) l -= 360; return [l, b-0.001]; }; setTimeout(go, 1000); }  var gmdat = { "sol": 0.0002959122082855911025, // AU^3/d^2 "mer": 164468599544771, //km^3/d^2 "ven": 2425056445892137, "ter": 2975536307796296, "lun": 36599199229256, "mar": 319711652803400, "cer": 467549107200, "ves": 129071530155, "jup": 945905743547733000, "sat": 283225255921345000, "ura": 43256076555832200, "nep": 51034453325494200, "plu": 7327611364884, "eri": 8271175680000 }, symbols = { "sol":"\u2609", "mer":"\u263f", "ven":"\u2640", "ter":"\u2295", "lun":"\u25cf", "mar":"\u2642", "cer":"\u26b3", "ves":"\u26b6", "jup":"\u2643", "sat":"\u2644", "ura":"\u2645", "nep":"\u2646", "plu":"\u2647", "eri":"\u26aa" }, ε = 23.43928 * deg2rad, sinε = Math.sin(ε), cosε = Math.cos(ε), kelements = ["a","e","i","w","M","L","W","N","n","ep","ref","lecl","becl","Tilt"]; /* ep = epoch (iso-date) N = longitude of the ascending node (deg) Ω i = inclination to the refrence plane, default:ecliptic (deg) w = argument of periapsis (deg) ω a = semi-major axis, or mean distance from parent body (AU,km) e = eccentricity (0=circle, 0-1=ellipse, 1=parabola, >1=hyperbola ) (-) M = mean anomaly (0 at periapsis; increases uniformly with time) (deg) n = mean daily motion = 360/P (deg/day) W = N + w = longitude of periapsis ϖ L = M + W = mean longitude q = a*(1-e) = periapsis distance Q = a*(1+e) = apoapsis distance P = 2π * sqrt(a^3/GM) = orbital period (years) T = Epoch_of_M - (M(deg)/360_deg) / P = time of periapsis v = true anomaly (angle between position and periapsis) ν E = eccentric anomaly Mandatory: a, e, i, N, w|W, M|L, dM|n */ var Kepler = function () { var gm = gmdat.sol, parentBody = "sol", elem = {}, dat = {}, id, name, symbol; function kepler(date) { dates(date); if (id === "sol") { dat.x = 0; dat.y = 0; dat.z = 0; return kepler; } coordinates(); return kepler; } var dates = function(date) { var dt; dat = []; if (date) { if (date instanceof Date) { dt = new Date(date.valueOf()); } else { dt = dateParse(date); } } if (!dt) { dt = new Date(); } dat.jd = JD(dt); dt = dateParse(elem.ep); if (!dt) dt = dateParse("2000-01-01"); dat.jd0 = JD(dt); dat.d = dat.jd - dat.jd0; dat.cy = dat.d / 36525; }; var coordinates = function() { var key; if (id === "lun") { dat = moon_elements(dat); if (!dat) return; } else { for (var i=0; i 1.0 ? E*E : -E*E, term = e * anom2 * E / 6.0, rval = (1.0 - e) * E - term, n = 4; while(Math.abs(term) > 1e-15) { term *= anom2 / (n * (n + 1)); rval -= term; n += 2; } return(rval); } function anomaly() { var curr, err, trial, tmod, e = dat.e, M = dat.M, thresh = 1e-8, offset = 0.0, delta_curr = 1.9, is_negative = false, n_iter = 0; if (!M) return(0.0); if (e < 1.0) { if (M < -Math.PI || M > Math.PI) { tmod = Trig.normalize0(M); offset = M - tmod; M = tmod; } if (e < 0.9) { curr = Math.atan2(Math.sin(M), Math.cos(M) - e); do { err = (curr - e * Math.sin(curr) - M) / (1.0 - e * Math.cos(curr)); curr -= err; } while (Math.abs(err) > thresh); return curr; // + offset; } } if ( M < 0.0) { M = -M; is_negative = true; } curr = M; thresh = thresh * Math.abs(1.0 - e); /* Due to roundoff error, there's no way we can hope to */ /* get below a certain minimum threshhold anyway: */ if ( thresh < 1e-15) { thresh = 1e-15; } if ( (e > 0.8 && M < Math.PI / 3.0) || e > 1.0) { /* up to 60 degrees */ trial = M / Math.abs( 1.0 - e); if (trial * trial > 6.0 * Math.abs(1.0 - e)) { /* cubic term is dominant */ if (M < Math.PI) { trial = Math.pow(6.0 * M, 1/3); } else { /* hyperbolic w/ 5th & higher-order terms predominant */ trial = Trig.asinh( M / e); } } curr = trial; } if (e > 1.0 && M > 4.0) { /* hyperbolic, large-mean-anomaly case */ curr = Math.log(M); } if (e < 1.0) { while(Math.abs(delta_curr) > thresh) { if ( n_iter++ > 8) { err = near_parabolic(curr, e) - M; } else { err = curr - e * Math.sin(curr) - M; } delta_curr = -err / (1.0 - e * Math.cos(curr)); curr += delta_curr; } } else { while (Math.abs(delta_curr) > thresh) { if (n_iter++ > 7) { err = -near_parabolic(curr, e) - M; } else { err = e * Trig.sinh(curr) - curr - M; } delta_curr = -err / (e * Trig.cosh(curr) - 1.0); curr += delta_curr; } } return( is_negative ? offset - curr : offset + curr); } function trueAnomaly() { var x, y, r0, g, t; if (dat.e === 1.0) { /* parabolic */ t = dat.jd0 - dat.T; g = dat.w0 * t * 0.5; y = Math.pow(g + Math.sqrt(g * g + 1.0), 1/3); dat.v = 2.0 * Math.atan(y - 1.0 / y); } else { /* got the mean anomaly; compute eccentric, then true */ dat.E = anomaly(); if (dat.e > 1.0) { /* hyperbolic case */ x = (dat.e - Trig.cosh(dat.E)); y = Trig.sinh(dat.E); } else { /* elliptical case */ x = (Math.cos(dat.E) - dat.e); y = Math.sin(dat.E); } y *= Math.sqrt(Math.abs(1.0 - dat.e * dat.e)); dat.v = Math.atan2(y, x); } r0 = dat.q * (1.0 + dat.e); dat.r = r0 / (1.0 + dat.e * Math.cos(dat.v)); } function derive() { if (!dat.hasOwnProperty("w")) { dat.w = dat.W - dat.N; } if (!dat.hasOwnProperty("M")) { dat.M = dat.L - dat.W; } if (dat.e < 1.0) { dat.M = Trig.normalize0(dat.M); } //dat.P = Math.pow(Math.abs(dat.a), 1.5); dat.P = τ * Math.sqrt(Math.pow(dat.a, 3) / gm) / 365.25; dat.T = dat.jd0 - (dat.M / halfπ) / dat.P; if (dat.e !== 1.0) { /* for non-parabolic orbits: */ dat.q = dat.a * (1.0 - dat.e); dat.t0 = dat.a * Math.sqrt(Math.abs(dat.a) / gm); } else { dat.w0 = (3.0 / Math.sqrt(2)) / (dat.q * Math.sqrt(dat.q / gm)); dat.a = 0.0; dat.t0 = 0.0; } dat.am = Math.sqrt(gm * dat.q * (1.0 + dat.e)); } function transpose() { if (!dat.ref || dat.ref === "ecl") { dat.tx = dat.x; dat.ty = dat.y; dat.tz = dat.z; return; } var a0 = dat.lecl,// - Math.PI/2, a1 = Math.PI/2 - dat.becl, angles = [0, a1, -a0]; transform(dat, angles); var tp = Trig.cartesian([dat.tl, dat.tb, dat.r]); dat.tx = tp.x; dat.ty = tp.y; dat.tz = tp.z; } function equatorial(pos) { ε = (23.439292 - 0.0130042 * dat.cy - 1.667e-7 * dat.cy * dat.cy + 5.028e-7 * dat.cy * dat.cy * dat.cy) * deg2rad; sinε = Math.sin(ε); cosε = Math.cos(ε); var o = (id === "lun") ? {x:0, y:0, z:0} : {x:pos.x, y:pos.y, z:pos.z}; dat.xeq = dat.x - o.x; dat.yeq = (dat.y - o.y) * cosε - (dat.z - o.z) * sinε; dat.zeq = (dat.y - o.y) * sinε + (dat.z - o.z) * cosε; dat.ra = Trig.normalize(Math.atan2(dat.yeq, dat.xeq)); dat.dec = Math.atan2(dat.zeq, Math.sqrt(dat.xeq*dat.xeq + dat.yeq*dat.yeq)); if (id === "lun") dat = moon_corr(dat, pos); dat.pos = [dat.ra / deg2rad, dat.dec / deg2rad]; dat.rt = Math.sqrt(dat.xeq*dat.xeq + dat.yeq*dat.yeq + dat.zeq*dat.zeq); } function cartesian() { var u = dat.v + dat.w; dat.x = dat.r * (Math.cos(dat.N) * Math.cos(u) - Math.sin(dat.N) * Math.sin(u) * Math.cos(dat.i)); dat.y = dat.r * (Math.sin(dat.N) * Math.cos(u) + Math.cos(dat.N) * Math.sin(u) * Math.cos(dat.i)); dat.z = dat.r * (Math.sin(u) * Math.sin(dat.i)); return dat; } function spherical() { var lon = Math.atan2(dat.y, dat.x), lat = Math.atan2(dat.z, Math.sqrt(dat.x*dat.x + dat.y*dat.y)); dat.l = Trig.normalize(lon); dat.b = lat; return dat; } function polar2cart(pos) { var rclat = Math.cos(pos.lat) * pos.r; pos.x = rclat * Math.cos(pos.lon); pos.y = rclat * Math.sin(pos.lon); pos.z = pos.r * Math.sin(pos.lat); return pos; } function JD(dt) { var yr = dt.getUTCFullYear(), mo = dt.getUTCMonth() + 1, dy = dt.getUTCDate(), frac = (dt.getUTCHours() - 12 + dt.getUTCMinutes()/60.0 + dt.getUTCSeconds()/3600.0) / 24, IYMIN = -4799; /* Earliest year allowed (4800BC) */ if (yr < IYMIN) return -1; var a = Math.floor((14 - mo) / 12), y = yr + 4800 - a, m = mo + (12 * a) - 3; var jdn = dy + Math.floor((153 * m + 2)/5) + (365 * y) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045; return jdn + frac; } function mst(lon) { var l = lon || 0; // lon=0 -> gmst return (18.697374558 + 24.06570982441908 * dat.d) * 15 + l; } function observer(pos) { var flat = 298.257223563, // WGS84 flattening of earth re = 6378.137, // GRS80/WGS84 semi major axis of earth ellipsoid h = pos.h || 0, cart = {}, gmst = mst(); var cosl = Math.cos(pos.lat), sinl = Math.sin(pos.lat), fl = 1.0 - 1.0 / flat; var fl2 = fl * fl; var u = 1.0 / Math.sqrt (cosl * cosl + fl2 * sinl * sinl), a = re * u + h, b = re * fl2 * u + h, r = Math.sqrt (a * a * cosl * cosl + b * b * sinl * sinl); // geocentric distance from earth center cart.lat = Math.acos (a * cosl / r); cart.lon = pos.lon; cart.r = h; if (pos.lat < 0.0) cart.lat *= -1; polar2cart(cart); // rotate around earth's polar axis to align coordinate system from Greenwich to vernal equinox var angle = gmst * deg2rad; // sideral time gmst given in hours. Convert to radians cart.x = cart.x * Math.cos(angle) - cart.y * Math.sin(angle); cart.y = cart.x * Math.sin(angle) + cart.y * Math.cos(angle); return(cart); } function moon_elements(dat) { if ((typeof Moon !== "undefined")) return Moon.elements(dat); } function moon_corr(dat, pos) { spherical(); if ((typeof Moon !== "undefined")) return Moon.corr(dat, pos); } return kepler; }; Celestial.Kepler = Kepler;  var Moon = { elements: function(dat) { var t = (dat.jd - 2451545) / 36525, t2 = t * t, t3 = t * t2, t4 = t * t3, t5 = t * t4, t2e4 = t2 * 1e-4, t3e6 = t3 * 1e-6, t4e8 = t4 * 1e-8; // semimajor axis var sa = 3400.4 * Math.cos(deg2rad * (235.7004 + 890534.2230 * t - 32.601 * t2e4 + 3.664 * t3e6 - 1.769 * t4e8)) - 635.6 * Math.cos(deg2rad * (100.7370 + 413335.3554 * t - 122.571 * t2e4 - 10.684 * t3e6 + 5.028 * t4e8)) - 235.6 * Math.cos(deg2rad * (134.9634 + 477198.8676 * t + 89.970 * t2e4 + 14.348 * t3e6 - 6.797 * t4e8)) + 218.1 * Math.cos(deg2rad * (238.1713 + 854535.1727 * t - 31.065 * t2e4 + 3.623 * t3e6 - 1.769 * t4e8)) + 181.0 * Math.cos(deg2rad * (10.6638 + 1367733.0907 * t + 57.370 * t2e4 + 18.011 * t3e6 - 8.566 * t4e8)) - 39.9 * Math.cos(deg2rad * (103.2079 + 377336.3051 * t - 121.035 * t2e4 - 10.724 * t3e6 + 5.028 * t4e8)) - 38.4 * Math.cos(deg2rad * (233.2295 + 926533.2733 * t - 34.136 * t2e4 + 3.705 * t3e6 - 1.769 * t4e8)) + 33.8 * Math.cos(deg2rad * (336.4374 + 1303869.5784 * t - 155.171 * t2e4 - 7.020 * t3e6 + 3.259 * t4e8)) + 28.8 * Math.cos(deg2rad * (111.4008 + 1781068.4461 * t - 65.201 * t2e4 + 7.328 * t3e6 - 3.538 * t4e8)) + 12.6 * Math.cos(deg2rad * (13.1347 + 1331734.0404 * t + 58.906 * t2e4 + 17.971 * t3e6 - 8.566 * t4e8)) + 11.4 * Math.cos(deg2rad * (186.5442 + 966404.0351 * t - 68.058 * t2e4 - 0.567 * t3e6 + 0.232 * t4e8)) - 11.1 * Math.cos(deg2rad * (222.5657 - 441199.8173 * t - 91.506 * t2e4 - 14.307 * t3e6 + 6.797 * t4e8)) - 10.2 * Math.cos(deg2rad * (269.9268 + 954397.7353 * t + 179.941 * t2e4 + 28.695 * t3e6 - 13.594 * t4e8)) + 9.7 * Math.cos(deg2rad * (145.6272 + 1844931.9583 * t + 147.340 * t2e4 + 32.359 * t3e6 - 15.363 * t4e8)) + 9.6 * Math.cos(deg2rad * (240.6422 + 818536.1225 * t - 29.529 * t2e4 + 3.582 * t3e6 - 1.769 * t4e8)) + 8.0 * Math.cos(deg2rad * (297.8502 + 445267.1115 * t - 16.300 * t2e4 + 1.832 * t3e6 - 0.884 * t4e8)) - 6.2 * Math.cos(deg2rad * (132.4925 + 513197.9179 * t + 88.434 * t2e4 + 14.388 * t3e6 - 6.797 * t4e8)) + 6.0 * Math.cos(deg2rad * (173.5506 + 1335801.3346 * t - 48.901 * t2e4 + 5.496 * t3e6 - 2.653 * t4e8)) + 3.7 * Math.cos(deg2rad * (113.8717 + 1745069.3958 * t - 63.665 * t2e4 + 7.287 * t3e6 - 3.538 * t4e8)) + 3.6 * Math.cos(deg2rad * (338.9083 + 1267870.5281 * t - 153.636 * t2e4 - 7.061 * t3e6 + 3.259 * t4e8)) + 3.2 * Math.cos(deg2rad * (246.3642 + 2258267.3137 * t + 24.769 * t2e4 + 21.675 * t3e6 - 10.335 * t4e8)) - 3.0 * Math.cos(deg2rad * (8.1929 + 1403732.1410 * t + 55.834 * t2e4 + 18.052 * t3e6 - 8.566 * t4e8)) + 2.3 * Math.cos(deg2rad * (98.2661 + 449334.4057 * t - 124.107 * t2e4 - 10.643 * t3e6 + 5.028 * t4e8)) - 2.2 * Math.cos(deg2rad * (357.5291 + 35999.0503 * t - 1.536 * t2e4 + 0.041 * t3e6 + 0.000 * t4e8)) - 2.0 * Math.cos(deg2rad * (38.5872 + 858602.4669 * t - 138.871 * t2e4 - 8.852 * t3e6 + 4.144 * t4e8)) - 1.8 * Math.cos(deg2rad * (105.6788 + 341337.2548 * t - 119.499 * t2e4 - 10.765 * t3e6 + 5.028 * t4e8)) - 1.7 * Math.cos(deg2rad * (201.4740 + 826670.7108 * t - 245.142 * t2e4 - 21.367 * t3e6 + 10.057 * t4e8)) + 1.6 * Math.cos(deg2rad * (184.1196 + 401329.0556 * t + 125.428 * t2e4 + 18.579 * t3e6 - 8.798 * t4e8)) - 1.4 * Math.cos(deg2rad * (308.4192 - 489205.1674 * t + 158.029 * t2e4 + 14.915 * t3e6 - 7.029 * t4e8)) + 1.3 * Math.cos(deg2rad * (325.7736 - 63863.5122 * t - 212.541 * t2e4 - 25.031 * t3e6 + 11.826 * t4e8)); var sapp = - 0.55 * Math.cos(deg2rad * (238.2 + 854535.2 * t)) + 0.10 * Math.cos(deg2rad * (103.2 + 377336.3 * t)) + 0.10 * Math.cos(deg2rad * (233.2 + 926533.3 * t)); var sma = 383397.6 + sa + sapp * t; // orbital eccentricity var se = 0.014217 * Math.cos(deg2rad * (100.7370 + 413335.3554 * t - 122.571 * t2e4 - 10.684 * t3e6 + 5.028 * t4e8)) + 0.008551 * Math.cos(deg2rad * (325.7736 - 63863.5122 * t - 212.541 * t2e4 - 25.031 * t3e6 + 11.826 * t4e8)) - 0.001383 * Math.cos(deg2rad * (134.9634 + 477198.8676 * t + 89.970 * t2e4 + 14.348 * t3e6 - 6.797 * t4e8)) + 0.001353 * Math.cos(deg2rad * (10.6638 + 1367733.0907 * t + 57.370 * t2e4 + 18.011 * t3e6 - 8.566 * t4e8)) - 0.001146 * Math.cos(deg2rad * (66.5106 + 349471.8432 * t - 335.112 * t2e4 - 35.715 * t3e6 + 16.854 * t4e8)) - 0.000915 * Math.cos(deg2rad * (201.4740 + 826670.7108 * t - 245.142 * t2e4 - 21.367 * t3e6 + 10.057 * t4e8)) + 0.000869 * Math.cos(deg2rad * (103.2079 + 377336.3051 * t - 121.035 * t2e4 - 10.724 * t3e6 + 5.028 * t4e8)) - 0.000628 * Math.cos(deg2rad * (235.7004 + 890534.2230 * t - 32.601 * t2e4 + 3.664 * t3e6 - 1.769 * t4e8)) - 0.000393 * Math.cos(deg2rad * (291.5472 - 127727.0245 * t - 425.082 * t2e4 - 50.062 * t3e6 + 23.651 * t4e8)) + 0.000284 * Math.cos(deg2rad * (328.2445 - 99862.5625 * t - 211.005 * t2e4 - 25.072 * t3e6 + 11.826 * t4e8)) - 0.000278 * Math.cos(deg2rad * (162.8868 - 31931.7561 * t - 106.271 * t2e4 - 12.516 * t3e6 + 5.913 * t4e8)) - 0.000240 * Math.cos(deg2rad * (269.9268 + 954397.7353 * t + 179.941 * t2e4 + 28.695 * t3e6 - 13.594 * t4e8)) + 0.000230 * Math.cos(deg2rad * (111.4008 + 1781068.4461 * t - 65.201 * t2e4 + 7.328 * t3e6 - 3.538 * t4e8)) + 0.000229 * Math.cos(deg2rad * (167.2476 + 762807.1986 * t - 457.683 * t2e4 - 46.398 * t3e6 + 21.882 * t4e8)) - 0.000202 * Math.cos(deg2rad * ( 83.3826 - 12006.2998 * t + 247.999 * t2e4 + 29.262 * t3e6 - 13.826 * t4e8)) + 0.000190 * Math.cos(deg2rad * (190.8102 - 541062.3799 * t - 302.511 * t2e4 - 39.379 * t3e6 + 18.623 * t4e8)) + 0.000177 * Math.cos(deg2rad * (357.5291 + 35999.0503 * t - 1.536 * t2e4 + 0.041 * t3e6 + 0.000 * t4e8)) + 0.000153 * Math.cos(deg2rad * (32.2842 + 285608.3309 * t - 547.653 * t2e4 - 60.746 * t3e6 + 28.679 * t4e8)) - 0.000137 * Math.cos(deg2rad * (44.8902 + 1431596.6029 * t + 269.911 * t2e4 + 43.043 * t3e6 - 20.392 * t4e8)) + 0.000122 * Math.cos(deg2rad * (145.6272 + 1844931.9583 * t + 147.340 * t2e4 + 32.359 * t3e6 - 15.363 * t4e8)) + 0.000116 * Math.cos(deg2rad * (302.2110 + 1240006.0662 * t - 367.713 * t2e4 - 32.051 * t3e6 + 15.085 * t4e8)) - 0.000111 * Math.cos(deg2rad * (203.9449 + 790671.6605 * t - 243.606 * t2e4 - 21.408 * t3e6 + 10.057 * t4e8)) - 0.000108 * Math.cos(deg2rad * (68.9815 + 313472.7929 * t - 333.576 * t2e4 - 35.756 * t3e6 + 16.854 * t4e8)) + 0.000096 * Math.cos(deg2rad * (336.4374 + 1303869.5784 * t - 155.171 * t2e4 - 7.020 * t3e6 + 3.259 * t4e8)) - 0.000090 * Math.cos(deg2rad * (98.2661 + 449334.4057 * t - 124.107 * t2e4 - 10.643 * t3e6 + 5.028 * t4e8)) + 0.000090 * Math.cos(deg2rad * (13.1347 + 1331734.0404 * t + 58.906 * t2e4 + 17.971 * t3e6 - 8.566 * t4e8)) + 0.000056 * Math.cos(deg2rad * (55.8468 - 1018261.2475 * t - 392.482 * t2e4 - 53.726 * t3e6 + 25.420 * t4e8)) - 0.000056 * Math.cos(deg2rad * (238.1713 + 854535.1727 * t - 31.065 * t2e4 + 3.623 * t3e6 - 1.769 * t4e8)) + 0.000052 * Math.cos(deg2rad * (308.4192 - 489205.1674 * t + 158.029 * t2e4 + 14.915 * t3e6 - 7.029 * t4e8)) - 0.000050 * Math.cos(deg2rad * (133.0212 + 698943.6863 * t - 670.224 * t2e4 - 71.429 * t3e6 + 33.708 * t4e8)) - 0.000049 * Math.cos(deg2rad * (267.9846 + 1176142.5540 * t - 580.254 * t2e4 - 57.082 * t3e6 + 26.911 * t4e8)) - 0.000049 * Math.cos(deg2rad * (184.1196 + 401329.0556 * t + 125.428 * t2e4 + 18.579 * t3e6 - 8.798 * t4e8)) - 0.000045 * Math.cos(deg2rad * (49.1562 - 75869.8120 * t + 35.458 * t2e4 + 4.231 * t3e6 - 2.001 * t4e8)) + 0.000044 * Math.cos(deg2rad * (257.3208 - 191590.5367 * t - 637.623 * t2e4 - 75.093 * t3e6 + 35.477 * t4e8)) + 0.000042 * Math.cos(deg2rad * (105.6788 + 341337.2548 * t - 119.499 * t2e4 - 10.765 * t3e6 + 5.028 * t4e8)) + 0.000042 * Math.cos(deg2rad * (160.4159 + 4067.2942 * t - 107.806 * t2e4 - 12.475 * t3e6 + 5.913 * t4e8)) + 0.000040 * Math.cos(deg2rad * (246.3642 + 2258267.3137 * t + 24.769 * t2e4 + 21.675 * t3e6 - 10.335 * t4e8)) - 0.000040 * Math.cos(deg2rad * (156.5838 - 604925.8921 * t - 515.053 * t2e4 - 64.410 * t3e6 + 30.448 * t4e8)) + 0.000036 * Math.cos(deg2rad * (169.7185 + 726808.1483 * t - 456.147 * t2e4 - 46.439 * t3e6 + 21.882 * t4e8)) + 0.000029 * Math.cos(deg2rad * (113.8717 + 1745069.3958 * t - 63.665 * t2e4 + 7.287 * t3e6 - 3.538 * t4e8)) - 0.000029 * Math.cos(deg2rad * (297.8502 + 445267.1115 * t - 16.300 * t2e4 + 1.832 * t3e6 - 0.884 * t4e8)) - 0.000028 * Math.cos(deg2rad * (294.0181 - 163726.0747 * t - 423.546 * t2e4 - 50.103 * t3e6 + 23.651 * t4e8)) + 0.000027 * Math.cos(deg2rad * (263.6238 + 381403.5993 * t - 228.841 * t2e4 - 23.199 * t3e6 + 10.941 * t4e8)) - 0.000026 * Math.cos(deg2rad * (358.0578 + 221744.8187 * t - 760.194 * t2e4 - 85.777 * t3e6 + 40.505 * t4e8)) - 0.000026 * Math.cos(deg2rad * (8.1929 + 1403732.1410 * t + 55.834 * t2e4 + 18.052 * t3e6 - 8.566 * t4e8)); var sedp = -0.0022 * Math.cos(deg2rad * (103.2 + 377336.3 * t)); var ecc = 0.055544 + se + 1e-3 * t * sedp; // sine of half the inclination var sg = 0.0011776 * Math.cos(deg2rad * (49.1562 - 75869.8120 * t + 35.458 * t2e4 + 4.231 * t3e6 - 2.001 * t4e8)) - 0.0000971 * Math.cos(deg2rad * (235.7004 + 890534.2230 * t - 32.601 * t2e4 + 3.664 * t3e6 - 1.769 * t4e8)) + 0.0000908 * Math.cos(deg2rad * (186.5442 + 966404.0351 * t - 68.058 * t2e4 - 0.567 * t3e6 + 0.232 * t4e8)) + 0.0000623 * Math.cos(deg2rad * (83.3826 - 12006.2998 * t + 247.999 * t2e4 + 29.262 * t3e6 - 13.826 * t4e8)) + 0.0000483 * Math.cos(deg2rad * (51.6271 - 111868.8623 * t + 36.994 * t2e4 + 4.190 * t3e6 - 2.001 * t4e8)) + 0.0000348 * Math.cos(deg2rad * (100.7370 + 413335.3554 * t - 122.571 * t2e4 - 10.684 * t3e6 + 5.028 * t4e8)) - 0.0000316 * Math.cos(deg2rad * (308.4192 - 489205.1674 * t + 158.029 * t2e4 + 14.915 * t3e6 - 7.029 * t4e8)) - 0.0000253 * Math.cos(deg2rad * (46.6853 - 39870.7617 * t + 33.922 * t2e4 + 4.272 * t3e6 - 2.001 * t4e8)) - 0.0000141 * Math.cos(deg2rad * (274.1928 - 553068.6797 * t - 54.513 * t2e4 - 10.116 * t3e6 + 4.797 * t4e8)) + 0.0000127 * Math.cos(deg2rad * (325.7736 - 63863.5122 * t - 212.541 * t2e4 - 25.031 * t3e6 + 11.826 * t4e8)) + 0.0000117 * Math.cos(deg2rad * (184.1196 + 401329.0556 * t + 125.428 * t2e4 + 18.579 * t3e6 - 8.798 * t4e8)) - 0.0000078 * Math.cos(deg2rad * (98.3124 - 151739.6240 * t + 70.916 * t2e4 + 8.462 * t3e6 - 4.001 * t4e8)) - 0.0000063 * Math.cos(deg2rad * (238.1713 + 854535.1727 * t - 31.065 * t2e4 + 3.623 * t3e6 - 1.769 * t4e8)) + 0.0000063 * Math.cos(deg2rad * (134.9634 + 477198.8676 * t + 89.970 * t2e4 + 14.348 * t3e6 - 6.797 * t4e8)) + 0.0000036 * Math.cos(deg2rad * (321.5076 + 1443602.9027 * t + 21.912 * t2e4 + 13.780 * t3e6 - 6.566 * t4e8)) - 0.0000035 * Math.cos(deg2rad * (10.6638 + 1367733.0907 * t + 57.370 * t2e4 + 18.011 * t3e6 - 8.566 * t4e8)) + 0.0000024 * Math.cos(deg2rad * (149.8932 + 337465.5434 * t - 87.113 * t2e4 - 6.453 * t3e6 + 3.028 * t4e8)) + 0.0000024 * Math.cos(deg2rad * (170.9849 - 930404.9848 * t + 66.523 * t2e4 + 0.608 * t3e6 - 0.232 * t4e8)); var sgp = - 0.0203 * Math.cos(deg2rad * (125.0 - 1934.1 * t)) + 0.0034 * Math.cos(deg2rad * (220.2 - 1935.5 * t)); var gamma = 0.0449858 + sg + 1e-3 * sgp; // longitude of perigee var sp = - 15.448 * Math.sin(deg2rad * (100.7370 + 413335.3554 * t - 122.571 * t2e4 - 10.684 * t3e6 + 5.028 * t4e8)) - 9.642 * Math.sin(deg2rad * (325.7736 - 63863.5122 * t - 212.541 * t2e4 - 25.031 * t3e6 + 11.826 * t4e8)) - 2.721 * Math.sin(deg2rad * (134.9634 + 477198.8676 * t + 89.970 * t2e4 + 14.348 * t3e6 - 6.797 * t4e8)) + 2.607 * Math.sin(deg2rad * (66.5106 + 349471.8432 * t - 335.112 * t2e4 - 35.715 * t3e6 + 16.854 * t4e8)) + 2.085 * Math.sin(deg2rad * (201.4740 + 826670.7108 * t - 245.142 * t2e4 - 21.367 * t3e6 + 10.057 * t4e8)) + 1.477 * Math.sin(deg2rad * (10.6638 + 1367733.0907 * t + 57.370 * t2e4 + 18.011 * t3e6 - 8.566 * t4e8)) + 0.968 * Math.sin(deg2rad * (291.5472 - 127727.0245 * t - 425.082 * t2e4 - 50.062 * t3e6 + 23.651 * t4e8)) - 0.949 * Math.sin(deg2rad * (103.2079 + 377336.3051 * t - 121.035 * t2e4 - 10.724 * t3e6 + 5.028 * t4e8)) - 0.703 * Math.sin(deg2rad * (167.2476 + 762807.1986 * t - 457.683 * t2e4 - 46.398 * t3e6 + 21.882 * t4e8)) - 0.660 * Math.sin(deg2rad * (235.7004 + 890534.2230 * t - 32.601 * t2e4 + 3.664 * t3e6 - 1.769 * t4e8)) - 0.577 * Math.sin(deg2rad * (190.8102 - 541062.3799 * t - 302.511 * t2e4 - 39.379 * t3e6 + 18.623 * t4e8)) - 0.524 * Math.sin(deg2rad * (269.9268 + 954397.7353 * t + 179.941 * t2e4 + 28.695 * t3e6 - 13.594 * t4e8)) - 0.482 * Math.sin(deg2rad * (32.2842 + 285608.3309 * t - 547.653 * t2e4 - 60.746 * t3e6 + 28.679 * t4e8)) + 0.452 * Math.sin(deg2rad * (357.5291 + 35999.0503 * t - 1.536 * t2e4 + 0.041 * t3e6 + 0.000 * t4e8)) - 0.381 * Math.sin(deg2rad * (302.2110 + 1240006.0662 * t - 367.713 * t2e4 - 32.051 * t3e6 + 15.085 * t4e8)) - 0.342 * Math.sin(deg2rad * (328.2445 - 99862.5625 * t - 211.005 * t2e4 - 25.072 * t3e6 + 11.826 * t4e8)) - 0.312 * Math.sin(deg2rad * (44.8902 + 1431596.6029 * t + 269.911 * t2e4 + 43.043 * t3e6 - 20.392 * t4e8)) + 0.282 * Math.sin(deg2rad * (162.8868 - 31931.7561 * t - 106.271 * t2e4 - 12.516 * t3e6 + 5.913 * t4e8)) + 0.255 * Math.sin(deg2rad * (203.9449 + 790671.6605 * t - 243.606 * t2e4 - 21.408 * t3e6 + 10.057 * t4e8)) + 0.252 * Math.sin(deg2rad * (68.9815 + 313472.7929 * t - 333.576 * t2e4 - 35.756 * t3e6 + 16.854 * t4e8)) - 0.211 * Math.sin(deg2rad * (83.3826 - 12006.2998 * t + 247.999 * t2e4 + 29.262 * t3e6 - 13.826 * t4e8)) + 0.193 * Math.sin(deg2rad * (267.9846 + 1176142.5540 * t - 580.254 * t2e4 - 57.082 * t3e6 + 26.911 * t4e8)) + 0.191 * Math.sin(deg2rad * (133.0212 + 698943.6863 * t - 670.224 * t2e4 - 71.429 * t3e6 + 33.708 * t4e8)) - 0.184 * Math.sin(deg2rad * (55.8468 - 1018261.2475 * t - 392.482 * t2e4 - 53.726 * t3e6 + 25.420 * t4e8)) + 0.182 * Math.sin(deg2rad * (145.6272 + 1844931.9583 * t + 147.340 * t2e4 + 32.359 * t3e6 - 15.363 * t4e8)) - 0.158 * Math.sin(deg2rad * (257.3208 - 191590.5367 * t - 637.623 * t2e4 - 75.093 * t3e6 + 35.477 * t4e8)) + 0.148 * Math.sin(deg2rad * (156.5838 - 604925.8921 * t - 515.053 * t2e4 - 64.410 * t3e6 + 30.448 * t4e8)) - 0.111 * Math.sin(deg2rad * (169.7185 + 726808.1483 * t - 456.147 * t2e4 - 46.439 * t3e6 + 21.882 * t4e8)) + 0.101 * Math.sin(deg2rad * (13.1347 + 1331734.0404 * t + 58.906 * t2e4 + 17.971 * t3e6 - 8.566 * t4e8)) + 0.100 * Math.sin(deg2rad * (358.0578 + 221744.8187 * t - 760.194 * t2e4 - 85.777 * t3e6 + 40.505 * t4e8)) + 0.087 * Math.sin(deg2rad * (98.2661 + 449334.4057 * t - 124.107 * t2e4 - 10.643 * t3e6 + 5.028 * t4e8)) + 0.080 * Math.sin(deg2rad * (42.9480 + 1653341.4216 * t - 490.283 * t2e4 - 42.734 * t3e6 + 20.113 * t4e8)) + 0.080 * Math.sin(deg2rad * (222.5657 - 441199.8173 * t - 91.506 * t2e4 - 14.307 * t3e6 + 6.797 * t4e8)) + 0.077 * Math.sin(deg2rad * (294.0181 - 163726.0747 * t - 423.546 * t2e4 - 50.103 * t3e6 + 23.651 * t4e8)) - 0.073 * Math.sin(deg2rad * (280.8834 - 1495460.1151 * t - 482.452 * t2e4 - 68.074 * t3e6 + 32.217 * t4e8)) - 0.071 * Math.sin(deg2rad * (304.6819 + 1204007.0159 * t - 366.177 * t2e4 - 32.092 * t3e6 + 15.085 * t4e8)) - 0.069 * Math.sin(deg2rad * (233.7582 + 1112279.0417 * t - 792.795 * t2e4 - 82.113 * t3e6 + 38.736 * t4e8)) - 0.067 * Math.sin(deg2rad * (34.7551 + 249609.2807 * t - 546.117 * t2e4 - 60.787 * t3e6 + 28.679 * t4e8)) - 0.067 * Math.sin(deg2rad * (263.6238 + 381403.5993 * t - 228.841 * t2e4 - 23.199 * t3e6 + 10.941 * t4e8)) + 0.055 * Math.sin(deg2rad * (21.6203 - 1082124.7597 * t - 605.023 * t2e4 - 78.757 * t3e6 + 37.246 * t4e8)) + 0.055 * Math.sin(deg2rad * (308.4192 - 489205.1674 * t + 158.029 * t2e4 + 14.915 * t3e6 -7.029 * t4e8)) - 0.054 * Math.sin(deg2rad * (8.7216 + 1589477.9094 * t - 702.824 * t2e4 - 67.766 * t3e6 + 31.939 * t4e8)) - 0.052 * Math.sin(deg2rad * (179.8536 + 1908795.4705 * t + 359.881 * t2e4 + 57.390 * t3e6 - 27.189 * t4e8)) - 0.050 * Math.sin(deg2rad * (98.7948 + 635080.1741 * t - 882.765 * t2e4 - 96.461 * t3e6 + 45.533 * t4e8)) - 0.049 * Math.sin(deg2rad * (128.6604 - 95795.2683 * t - 318.812 * t2e4 - 37.547 * t3e6 + 17.738 * t4e8)) - 0.047 * Math.sin(deg2rad * (17.3544 + 425341.6552 * t - 370.570 * t2e4 - 39.946 * t3e6 + 18.854 * t4e8)) - 0.044 * Math.sin(deg2rad * (160.4159 + 4067.2942 * t - 107.806 * t2e4 - 12.475 * t3e6 + 5.913 * t4e8)) - 0.043 * Math.sin(deg2rad * (238.1713 + 854535.1727 * t - 31.065 * t2e4 + 3.623 * t3e6 - 1.769 * t4e8)) + 0.042 * Math.sin(deg2rad * (270.4555 + 1140143.5037 * t - 578.718 * t2e4 - 57.123 * t3e6 + 26.911 * t4e8)) - 0.042 * Math.sin(deg2rad * (132.4925 + 513197.9179 * t + 88.434 * t2e4 + 14.388 * t3e6 - 6.797 * t4e8)) - 0.041 * Math.sin(deg2rad * (122.3573 - 668789.4043 * t - 727.594 * t2e4 - 89.441 * t3e6 + 42.274 * t4e8)) - 0.040 * Math.sin(deg2rad * (105.6788 + 341337.2548 * t - 119.499 * t2e4 - 10.765 * t3e6 + 5.028 * t4e8)) + 0.038 * Math.sin(deg2rad * (135.4921 + 662944.6361 * t - 668.688 * t2e4 - 71.470 * t3e6 + 33.708 * t4e8)) - 0.037 * Math.sin(deg2rad * (242.3910 - 51857.2124 * t - 460.540 * t2e4 - 54.293 * t3e6 + 25.652 * t4e8)) + 0.036 * Math.sin(deg2rad * (336.4374 + 1303869.5784 * t - 155.171 * t2e4 - 7.020 * t3e6 + 3.259 * t4e8)) + 0.035 * Math.sin(deg2rad * (223.0943 - 255454.0489 * t - 850.164 * t2e4 - 100.124 * t3e6 + 47.302 * t4e8)) - 0.034 * Math.sin(deg2rad * (193.2811 - 577061.4302 * t - 300.976 * t2e4 - 39.419 * t3e6 + 18.623 * t4e8)) + 0.031 * Math.sin(deg2rad * (87.6023 - 918398.6850 * t - 181.476 * t2e4 - 28.654 * t3e6 + 13.594 * t4e8)); var spp = 2.4 * Math.sin(deg2rad * (103.2 + 377336.3 * t)); var lp = 83.353 + 4069.0137 * t - 103.238 * t2e4 - 12.492 * t3e6 + 5.263 * t4e8 + sp + 1e-3 * t * spp; // longitude of the ascending node var sr = - 1.4979 * Math.sin(deg2rad * (49.1562 - 75869.8120 * t + 35.458 * t2e4 + 4.231 * t3e6 - 2.001 * t4e8)) - 0.1500 * Math.sin(deg2rad * (357.5291 + 35999.0503 * t - 1.536 * t2e4 + 0.041 * t3e6 + 0.000 * t4e8)) - 0.1226 * Math.sin(deg2rad * (235.7004 + 890534.2230 * t - 32.601 * t2e4 + 3.664 * t3e6 - 1.769 * t4e8)) + 0.1176 * Math.sin(deg2rad * (186.5442 + 966404.0351 * t - 68.058 * t2e4 - 0.567 * t3e6 + 0.232 * t4e8)) - 0.0801 * Math.sin(deg2rad * (83.3826 - 12006.2998 * t + 247.999 * t2e4 + 29.262 * t3e6 - 13.826 * t4e8)) - 0.0616 * Math.sin(deg2rad * (51.6271 - 111868.8623 * t + 36.994 * t2e4 + 4.190 * t3e6 - 2.001 * t4e8)) + 0.0490 * Math.sin(deg2rad * (100.7370 + 413335.3554 * t - 122.571 * t2e4 - 10.684 * t3e6 + 5.028 * t4e8)) + 0.0409 * Math.sin(deg2rad * (308.4192 - 489205.1674 * t + 158.029 * t2e4 + 14.915 * t3e6 - 7.029 * t4e8)) + 0.0327 * Math.sin(deg2rad * (134.9634 + 477198.8676 * t + 89.970 * t2e4 + 14.348 * t3e6 - 6.797 * t4e8)) + 0.0324 * Math.sin(deg2rad * (46.6853 - 39870.7617 * t + 33.922 * t2e4 + 4.272 * t3e6 - 2.001 * t4e8)) + 0.0196 * Math.sin(deg2rad * (98.3124 - 151739.6240 * t + 70.916 * t2e4 + 8.462 * t3e6 - 4.001 * t4e8)) + 0.0180 * Math.sin(deg2rad * (274.1928 - 553068.6797 * t - 54.513 * t2e4 - 10.116 * t3e6 + 4.797 * t4e8)) + 0.0150 * Math.sin(deg2rad * (325.7736 - 63863.5122 * t - 212.541 * t2e4 - 25.031 * t3e6 + 11.826 * t4e8)) - 0.0150 * Math.sin(deg2rad * (184.1196 + 401329.0556 * t + 125.428 * t2e4 + 18.579 * t3e6 - 8.798 * t4e8)) - 0.0078 * Math.sin(deg2rad * (238.1713 + 854535.1727 * t - 31.065 * t2e4 + 3.623 * t3e6 - 1.769 * t4e8)) - 0.0045 * Math.sin(deg2rad * (10.6638 + 1367733.0907 * t + 57.370 * t2e4 + 18.011 * t3e6 - 8.566 * t4e8)) + 0.0044 * Math.sin(deg2rad * (321.5076 + 1443602.9027 * t + 21.912 * t2e4 + 13.780 * t3e6 - 6.566 * t4e8)) - 0.0042 * Math.sin(deg2rad * (162.8868 - 31931.7561 * t - 106.271 * t2e4 - 12.516 * t3e6 + 5.913 * t4e8)) - 0.0031 * Math.sin(deg2rad * (170.9849 - 930404.9848 * t + 66.523 * t2e4 + 0.608 * t3e6 - 0.232 * t4e8)) + 0.0031 * Math.sin(deg2rad * (103.2079 + 377336.3051 * t - 121.035 * t2e4 - 10.724 * t3e6 + 5.028 * t4e8)) + 0.0029 * Math.sin(deg2rad * (222.6120 - 1042273.8471 * t + 103.516 * t2e4 + 4.798 * t3e6 - 2.232 * t4e8)) + 0.0028 * Math.sin(deg2rad * (184.0733 + 1002403.0853 * t - 69.594 * t2e4 - 0.526 * t3e6 + 0.232 * t4e8)); var srp = 25.9 * Math.sin(deg2rad * (125.0 - 1934.1 * t)) - 4.3 * Math.sin(deg2rad * (220.2 - 1935.5 * t)); var srpp = 0.38 * Math.sin(deg2rad * (357.5 + 35999.1 * t)); var raan = 125.0446 - 1934.13618 * t + 20.762 * t2e4 + 2.139 * t3e6 - 1.650 * t4e8 + sr + 1e-3 * (srp + srpp * t); // mean longitude var sl = - 0.92581 * Math.sin(deg2rad * (235.7004 + 890534.2230 * t - 32.601 * t2e4 + 3.664 * t3e6 - 1.769 * t4e8)) + 0.33262 * Math.sin(deg2rad * (100.7370 + 413335.3554 * t - 122.571 * t2e4 - 10.684 * t3e6 + 5.028 * t4e8)) - 0.18402 * Math.sin(deg2rad * (357.5291 + 35999.0503 * t - 1.536 * t2e4 + 0.041 * t3e6 + 0.000 * t4e8)) + 0.11007 * Math.sin(deg2rad * (134.9634 + 477198.8676 * t + 89.970 * t2e4 + 14.348 * t3e6 - 6.797 * t4e8)) - 0.06055 * Math.sin(deg2rad * (238.1713 + 854535.1727 * t - 31.065 * t2e4 + 3.623 * t3e6 - 1.769 * t4e8)) + 0.04741 * Math.sin(deg2rad * (325.7736 - 63863.5122 * t - 212.541 * t2e4 - 25.031 * t3e6 + 11.826 * t4e8)) - 0.03086 * Math.sin(deg2rad * (10.6638 + 1367733.0907 * t + 57.370 * t2e4 + 18.011 * t3e6 - 8.566 * t4e8)) + 0.02184 * Math.sin(deg2rad * (103.2079 + 377336.3051 * t - 121.035 * t2e4 - 10.724 * t3e6 + 5.028 * t4e8)) + 0.01645 * Math.sin(deg2rad * (49.1562 - 75869.8120 * t + 35.458 * t2e4 + 4.231 * t3e6 - 2.001 * t4e8)) + 0.01022 * Math.sin(deg2rad * (233.2295 + 926533.2733 * t - 34.136 * t2e4 + 3.705 * t3e6 - 1.769 * t4e8)) - 0.00756 * Math.sin(deg2rad * (336.4374 + 1303869.5784 * t - 155.171 * t2e4 - 7.020 * t3e6 + 3.259 * t4e8)) - 0.00530 * Math.sin(deg2rad * (222.5657 - 441199.8173 * t - 91.506 * t2e4 - 14.307 * t3e6 + 6.797 * t4e8)) - 0.00496 * Math.sin(deg2rad * (162.8868 - 31931.7561 * t - 106.271 * t2e4 - 12.516 * t3e6 + 5.913 * t4e8)) - 0.00472 * Math.sin(deg2rad * (297.8502 + 445267.1115 * t - 16.300 * t2e4 + 1.832 * t3e6 - 0.884 * t4e8)) - 0.00271 * Math.sin(deg2rad * (240.6422 + 818536.1225 * t - 29.529 * t2e4 + 3.582 * t3e6 - 1.769 * t4e8)) + 0.00264 * Math.sin(deg2rad * (132.4925 + 513197.9179 * t + 88.434 * t2e4 + 14.388 * t3e6 - 6.797 * t4e8)) - 0.00254 * Math.sin(deg2rad * (186.5442 + 966404.0351 * t - 68.058 * t2e4 - 0.567 * t3e6 + 0.232 * t4e8)) + 0.00234 * Math.sin(deg2rad * (269.9268 + 954397.7353 * t + 179.941 * t2e4 + 28.695 * t3e6 - 13.594 * t4e8)) - 0.00220 * Math.sin(deg2rad * (13.1347 + 1331734.0404 * t + 58.906 * t2e4 + 17.971 * t3e6 - 8.566 * t4e8)) - 0.00202 * Math.sin(deg2rad * (355.0582 + 71998.1006 * t - 3.072 * t2e4 + 0.082 * t3e6 + 0.000 * t4e8)) + 0.00167 * Math.sin(deg2rad * (328.2445 - 99862.5625 * t - 211.005 * t2e4 - 25.072 * t3e6 + 11.826 * t4e8)) - 0.00143 * Math.sin(deg2rad * (173.5506 + 1335801.3346 * t - 48.901 * t2e4 + 5.496 * t3e6 - 2.653 * t4e8)) - 0.00121 * Math.sin(deg2rad * (98.2661 + 449334.4057 * t - 124.107 * t2e4 - 10.643 * t3e6 + 5.028 * t4e8)) - 0.00116 * Math.sin(deg2rad * (145.6272 + 1844931.9583 * t + 147.340 * t2e4 + 32.359 * t3e6 - 15.363 * t4e8)) + 0.00102 * Math.sin(deg2rad * (105.6788 + 341337.2548 * t - 119.499 * t2e4 - 10.765 * t3e6 + 5.028 * t4e8)) - 0.00090 * Math.sin(deg2rad * (184.1196 + 401329.0556 * t + 125.428 * t2e4 + 18.579 * t3e6 - 8.798 * t4e8)) - 0.00086 * Math.sin(deg2rad * (338.9083 + 1267870.5281 * t - 153.636 * t2e4 - 7.061 * t3e6 + 3.259 * t4e8)) - 0.00078 * Math.sin(deg2rad * (111.4008 + 1781068.4461 * t - 65.201 * t2e4 + 7.328 * t3e6 - 3.538 * t4e8)) + 0.00069 * Math.sin(deg2rad * (323.3027 - 27864.4619 * t - 214.077 * t2e4 - 24.990 * t3e6 + 11.826 * t4e8)) + 0.00066 * Math.sin(deg2rad * (51.6271 - 111868.8623 * t + 36.994 * t2e4 + 4.190 * t3e6 - 2.001 * t4e8)) + 0.00065 * Math.sin(deg2rad * (38.5872 + 858602.4669 * t - 138.871 * t2e4 - 8.852 * t3e6 + 4.144 * t4e8)) - 0.00060 * Math.sin(deg2rad * (83.3826 - 12006.2998 * t + 247.999 * t2e4 + 29.262 * t3e6 - 13.826 * t4e8)) + 0.00054 * Math.sin(deg2rad * (201.4740 + 826670.7108 * t - 245.142 * t2e4 - 21.367 * t3e6 + 10.057 * t4e8)) - 0.00052 * Math.sin(deg2rad * (308.4192 - 489205.1674 * t + 158.029 * t2e4 + 14.915 * t3e6 - 7.029 * t4e8)) + 0.00048 * Math.sin(deg2rad * (8.1929 + 1403732.1410 * t + 55.834 * t2e4 + 18.052 * t3e6 - 8.566 * t4e8)) - 0.00041 * Math.sin(deg2rad * (46.6853 - 39870.7617 * t + 33.922 * t2e4 + 4.272 * t3e6 - 2.001 * t4e8)) - 0.00033 * Math.sin(deg2rad * (274.1928 - 553068.6797 * t - 54.513 * t2e4 - 10.116 * t3e6 + 4.797 * t4e8)) + 0.00030 * Math.sin(deg2rad * (160.4159 + 4067.2942 * t - 107.806 * t2e4 - 12.475 * t3e6 + 5.913 * t4e8)); var slp = 3.96 * Math.sin(deg2rad * (119.7 + 131.8 * t)) + 1.96 * Math.sin(deg2rad * (125.0 - 1934.1 * t)); var slpp = 0.463 * Math.sin(deg2rad * (357.5 + 35999.1 * t)) + 0.152 * Math.sin(deg2rad * (238.2 + 854535.2 * t)) - 0.071 * Math.sin(deg2rad * (27.8 + 131.8 * t)) - 0.055 * Math.sin(deg2rad * (103.2 + 377336.3 * t)) - 0.026 * Math.sin(deg2rad * (233.2 + 926533.3 * t)); var slppp = 14 * Math.sin(deg2rad * (357.5 + 35999.1 * t)) + 5 * Math.sin(deg2rad * (238.2 + 854535.2 * t)); var lambda = 218.31665 + 481267.88134 * t - 13.268 * t2e4 + 1.856 * t3e6 - 1.534 * t4e8 + sl + 1e-3 * (slp + slpp * t + slppp * t2e4); dat.a = sma; dat.e = ecc; dat.i = 2.0 * Math.asin(gamma); dat.w = Trig.normalize(deg2rad * (lp - raan)); dat.N = Trig.normalize(deg2rad * raan); dat.M = Trig.normalize(deg2rad * (lambda - lp)); return dat; }, corr: function(dat, sol) { var M = Trig.normalize(sol.M + Math.PI), w = Trig.normalize(sol.w + Math.PI), L = dat.M + dat.w, // Argument of latitude E = L + dat.N - M - w; // Mean elongation var lon = -0.022234 * Math.sin(dat.M - 2*E) + // Evection 0.011494 * Math.sin(2*E) + // Variation -0.003246 * Math.sin(M) + // Yearly Equation -0.001029 * Math.sin(2*dat.M - 2*E) + -9.94838e-4 * Math.sin(dat.M - 2*E + M) + 9.25025e-4 * Math.sin(dat.M + 2*E) + 8.02851e-4 * Math.sin(2*E - M) + 7.15585e-4 * Math.sin(dat.M - M) + -6.10865e-4 * Math.sin(E) + -5.41052e-4 * Math.sin(dat.M + M) + -2.61799e-4 * Math.sin(2*L - 2*E) + 1.91986e-4 * Math.sin(dat.M - 4*E); dat.ra += lon; var lat = -0.003019 * Math.sin(L - 2*E) + -9.59931e-4 * Math.sin(dat.M - L - 2*E) + -8.02851e-4 * Math.sin(dat.M + L - 2*E) + 5.75958e-4 * Math.sin(L + 2*E) + 2.96706e-4 * Math.sin(2*dat.M + L); dat.dec += lat; dat.age = Trig.normalize(dat.l - sol.l + Math.PI); dat.phase = 0.5 * (1 - Math.cos(dat.age)); return dat; } }; var datetimepicker = function(cfg, callback) { var date = new Date(), tzFormat = d3.time.format("%Z"), tz = [{"−12:00":720}, {"−11:00":660}, {"−10:00":600}, {"−09:30":570}, {"−09:00":540}, {"−08:00":480}, {"−07:00":420}, {"−06:00":360}, {"−05:00":300}, {"−04:30":270}, {"−04:00":240}, {"−03:30":210}, {"−03:00":180}, {"−02:00":120}, {"−01:00":60}, {"±00:00":0}, {"+01:00":-60}, {"+02:00":-120}, {"+03:00":-180}, {"+03:30":-210}, {"+04:00":-240}, {"+04:30":-270}, {"+05:00":-300}, {"+05:30":-330}, {"+05:45":-345}, {"+06:00":-360}, {"+06:30":-390}, {"+07:00":-420}, {"+08:00":-480}, {"+08:30":-510}, {"+08:45":-525}, {"+09:00":-540}, {"+09:30":-570}, {"+10:00":-600}, {"+10:30":-630}, {"+11:00":-660}, {"+12:00":-720}, {"+12:45":-765}, {"+13:00":-780}, {"+14:00":-840}], months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], days = ["Su", "M", "Tu", "W", "Th", "F", "Sa"], years = getYears(date), dateFormat = d3.time.format("%Y-%m-%d"), dtrange = cfg.daterange || []; var picker = d3.select("#celestial-form").append("div").attr("id", "celestial-date"); nav("left"); monSel(); yrSel(); nav("right"); var cal = picker.append("div").attr("id", "cal"); daySel(); timeSel(); tzSel(); function daySel() { var mo = $("mon").value, yr = $("yr").value, curdt = new Date(yr, mo, 1), cal = d3.select("#cal"), today = new Date(); yr = parseInt(yr); mo = parseInt(mo); curdt.setDate(curdt.getDate() - curdt.getDay()); var nd = cal.node(); while (nd.firstChild) nd.removeChild(nd.firstChild); for (var i=0; i<7; i++) { cal.append("div").classed({"date": true, "weekday": true}).html(days[i]); } for (i=0; i<42; i++) { var curmon = curdt.getMonth(), curday = curdt.getDay(), curid = dateFormat(curdt); cal.append("div").classed({ "date": true, "grey": curmon !== mo, "weekend": curmon === mo && (curday === 0 || curday === 6), "today": dateDiff(curdt, today) === 0, "selected": dateDiff(curdt, date) === 0 }).attr("id", curid) .on("click", pick) .html(curdt.getDate().toString()); curdt.setDate(curdt.getDate()+1); } } function yrSel() { picker.append("select").attr("title", "Year").attr("id", "yr").on("change", daySel); fillYrSel(); } function fillYrSel() { var sel = d3.select("select#yr"), year = date.getFullYear(), selected = 0, years = getYears(date); sel.selectAll("*").remove(); sel.selectAll('option').data(years).enter().append('option') .text(function (d, i) { if (d === year) selected = i; return d.toString(); }); sel.property("selectedIndex", selected); } function monSel() { var sel = picker.append("select").attr("title", "Month").attr("id", "mon").on("change", daySel), selected = 0, month = date.getMonth(); sel.selectAll('option').data(months).enter().append('option') .attr("value", function (d, i) { if (i === month) selected = i; return i; }) .text(function (d) { return d; }); sel.property("selectedIndex", selected); } function nav(dir) { var lnk = picker.append("div").attr("id", dir).on("click", function () { var mon = $("mon"), yr = $("yr"); if (dir === "left") { if (mon.selectedIndex === 0) { mon.selectedIndex = 11; yr.selectedIndex--; } else mon.selectedIndex--; } else { if (mon.selectedIndex === 11) { mon.selectedIndex = 0; yr.selectedIndex++; } else mon.selectedIndex++; } daySel(); }); } function timeSel() { picker.append("input").attr("type", "number").attr("id", "hr").attr("title", "Hours").attr("max", "24").attr("min", "-1").attr("step", "1").attr("value", date.getHours()).on("change", function () { if (testNumber(this) === true) pick(); }); picker.append("input").attr("type", "number").attr("id", "min").attr("title", "Minutes").attr("max", "60").attr("min", "-1").attr("step", "1").attr("value", date.getMinutes()).on("change", function () { if (testNumber(this) === true) pick(); }); picker.append("input").attr("type", "number").attr("id", "sec").attr("title", "Seconds").attr("max", "60").attr("min", "-1").attr("step", "1").attr("value", date.getSeconds()).on("change", function () { if (testNumber(this) === true) pick(); }); } function tzSel() { var sel = picker.append("select").attr("title", "Time zone offset from UTC").attr("id", "tz").on("change", pick), selected = 15, timezone = date.getTimezoneOffset(); sel.selectAll('option').data(tz).enter().append('option') .attr("value", function (d, i) { var k = Object.keys(d)[0]; if (d[k] === timezone) selected = i; return d[k]; }) .text(function (d) { return Object.keys(d)[0]; }); sel.property("selectedIndex", selected); } function getYears(dt) { var r = getDateRange(dt.getFullYear()), res = []; for (var i = r[0]; i <= r[1]; i++) res.push(i); return res; } function getDateRange(yr) { if (!dtrange || dtrange.length < 1) return [yr - 10, yr + 10]; if (dtrange.length === 1 && isNumber(dtrange[0])) { if (dtrange[0] >= 100) return [dtrange[0] - 10, dtrange[0] + 10]; else return [yr - dtrange[0], yr + dtrange[0]]; } if (dtrange.length === 2 && isNumber(dtrange[0])&& isNumber(dtrange[1])) { if (dtrange[1] >= 100) return [dtrange[0], dtrange[1]]; else return [dtrange[0] - dtrange[1], dtrange[0] + dtrange[1]]; } return [yr - 10, yr + 10]; } function select(id, val) { var sel = $(id); for (var i=0; i