/**
* orbits-js
* @author Rossen Georgiev @ {@link https://github.com/rossengeorgiev}
* @description A tiny library that can parse TLE, and display the orbit on the map
* @requires: GMaps API 3
*
* @version 1.2.1
* @namespace
*/
var orbits = {
version: '1.2.1',
/**
* @namespace
*/
util: {}
};
/**
* merge two objects together, b takes precedence
* @param {Object} a - First object instance
* @param {Object} b - Second object instance
* @returns {Object}
*/
orbits.util.mergeOpts = function(a, b) {
var k, result = {};
for(k in a) result[k] = a[k];
for(k in b) result[k] = b[k];
return result;
};
/**
* takes a Date instance and return julian day
* @param {Date} date - Date instance
* @returns {float}
*/
orbits.util.jday = function(date) {
return (date.getTime() / 86400000.0) + 2440587.5;
};
/**
* takes a Date instance and returns Greenwich mean sidereal time in radii
* @param {Date} date - Date instance
* @returns {float}
*/
orbits.util.gmst = function(date) {
var jd = orbits.util.jday(date);
//t is the time difference in Julian centuries of Universal Time (UT1) from J2000.0.
var t = (jd - 2451545.0) / 36525;
// based on http://www.space-plasma.qmul.ac.uk/heliocoords/systems2art/node10.html
var gmst = 67310.54841 + (876600.0*3600 + 8640184.812866) * t + 0.093104 * t*t - 0.0000062 * t*t*t;
gmst = (gmst * (Math.PI/180) / 240.0) % (Math.PI*2);
gmst += (gmst<0) ? Math.PI*2 : 0;
return gmst;
};
/**
* Get distance to true horizon in meters
* @param {float} altitude - In meters
* @returns {float}
*/
orbits.util.getDistanceToHorizon = function(altitude) {
return Math.sqrt(12.756 * altitude) * 1000;
};
orbits.util.halfEarthCircumference = parseInt(6371 * Math.PI * 500);
/**
* Calculate position of the sun for a given date
* @param {Date} date - An instance of Date
* @returns {float[]} [latitude, longitude]
*/
orbits.util.calculatePositionOfSun = function(date) {
date = (date instanceof Date) ? date : new Date();
var rad = 0.017453292519943295;
// based on NOAA solar calculations
var mins_past_midnight = (date.getUTCHours() * 60 + date.getUTCMinutes()) / 1440;
var jc = (this.jday(date) - 2451545)/36525;
var mean_long_sun = (280.46646+jc*(36000.76983+jc*0.0003032)) % 360;
var mean_anom_sun = 357.52911+jc*(35999.05029-0.0001537*jc);
var sun_eq = Math.sin(rad*mean_anom_sun)*(1.914602-jc*(0.004817+0.000014*jc))+Math.sin(rad*2*mean_anom_sun)*(0.019993-0.000101*jc)+Math.sin(rad*3*mean_anom_sun)*0.000289;
var sun_true_long = mean_long_sun + sun_eq;
var sun_app_long = sun_true_long - 0.00569 - 0.00478*Math.sin(rad*125.04-1934.136*jc);
var mean_obliq_ecliptic = 23+(26+((21.448-jc*(46.815+jc*(0.00059-jc*0.001813))))/60)/60;
var obliq_corr = mean_obliq_ecliptic + 0.00256*Math.cos(rad*125.04-1934.136*jc);
var lat = Math.asin(Math.sin(rad*obliq_corr)*Math.sin(rad*sun_app_long)) / rad;
var eccent = 0.016708634-jc*(0.000042037+0.0000001267*jc);
var y = Math.tan(rad*(obliq_corr/2))*Math.tan(rad*(obliq_corr/2));
var rq_of_time = 4*((y*Math.sin(2*rad*mean_long_sun)-2*eccent*Math.sin(rad*mean_anom_sun)+4*eccent*y*Math.sin(rad*mean_anom_sun)*Math.cos(2*rad*mean_long_sun)-0.5*y*y*Math.sin(4*rad*mean_long_sun)-1.25*eccent*eccent*Math.sin(2*rad*mean_anom_sun))/rad);
var true_solar_time = (mins_past_midnight*1440+rq_of_time) % 1440;
var lng = -((true_solar_time/4 < 0) ? true_solar_time/4 + 180 : true_solar_time/4 - 180);
return [lat, lng];
};
/**
* Calculate LatLng of the sun for a given date
* @param {Date} date - An instance of Date
* @returns {google.maps.LatLng}
*/
orbits.util.calculateLatLngOfSun = function(date) {
var pos = orbits.util.calculatePositionOfSun(date);
return new google.maps.LatLng(pos[0], pos[1]);
};
/**
* Parses a string with one or more TLEs
* @param {string} text - A string containing one or more TLEs
* @returns {array.<orbits.TLE>} An array of orbit.TLE instances
*/
orbits.util.parseTLE = function(text) {
"use strict";
if(!text || typeof text != "string" || text === "") return [];
var lines = text.split("\n");
// trim emepty lines
for(var i = 0; i < lines.length; i++) if(lines[i] === "") lines.splice(i,1);
// see if we got somethin reasonable
if(lines.length < 3) return [];
if(lines.length % 3 !== 0)
throw new SyntaxError("The number of lines should be multiple of 3");
// try and make the array
var three;
var array = [];
while(lines.length) array.push(new orbits.TLE(lines.splice(0,3).join("\n")));
return array;
};
/**
*Object with the default options for Satellite object
* @prop {orbits.TLE} tle - An instance of orbits.TLE
* @prop {string} title - Alternative title to use for the marker, instead of the one from TLE
* @prop {float} pathLength - The length is in periods. Length = period * pathLength
* @prop {boolean} visible - Whenever to display the map or not
* @prop {google.maps.Map} map - An instance of google.maps.Map
* @prop {google.maps.MarkerOptions} markerOpts - An instance of google.maps.MarkerOptions
* @prop {google.maps.CircleOptions} horzionOpts - An instance of google.maps.CircleOptions
* @prop {google.maps.PolylineOptions} polylineOpts - An instance of google.maps.PolylineOptions
* @prop {boolean} drawShadowPolylines - Whenever to draw indicators when Satellite is shadowed by Earth
* @prop {google.maps.PolylineOptions} shadowPolylinesOpts - An instance of google.maps.PolylineOptions
*/
orbits.SatelliteOptions = {
tle: "",
title: null,
pathLength: 1,
visible: true,
map: null,
markerOpts: {
zIndex: 50,
},
horizonOpts: {
radius: 0,
zIndex: 10,
strokeWeight: 2,
strokeColor: "white",
strokeOpacity: 0.8,
fillColor: "white",
fillOpacity: 0.2,
},
polylineOpts: {
zIndex: 20,
geodesic: true,
strokeWeight: 2,
strokeColor: "blue",
strokeOpacity: 0.8
},
drawShadowPolylines: true,
shadowPolylinesOpts: {
zIndex: 20,
geodesic: true,
strokeWeight: 5,
strokeColor: "blue",
strokeOpacity: 0.8
},
};
/**
*Initializes a Satellite object (requires Google Maps API3)
* @class
* @param {orbits.SatelliteOptions} options - an obj with options, see orbits.SatelliteOptions
*/
orbits.Satellite = function(options) {
"use strict";
this.tle = null;
this.position = null;
this.path = null;
this.visible = true;
this.orbit = null;
this.date = null;
// handle options
options = (typeof options == 'object') ? options : {};
var opt;
for(opt in orbits.SatelliteOptions) {
if(opt in options) {
if(typeof orbits.SatelliteOptions[opt] === "object" && orbits.SatelliteOptions[opt] !== null) {
this[opt] = orbits.util.mergeOpts(orbits.SatelliteOptions[opt], options[opt]);
}
else {
this[opt] = options[opt];
}
}
else {
this[opt] = orbits.SatelliteOptions[opt];
}
}
// init map elements, if note are set
this.marker = new google.maps.Marker(this.markerOpts);
this.horizon = new google.maps.Circle(this.horizonOpts);
this.horizon.bindTo('center', this.marker, 'position');
this.polyline = new google.maps.Polyline(this.polylineOpts);
this.shadowPolylines = [];
// attach markers to map
if(this.visible) this.setMap(this.map);
// check if we have TLE and init orbit
if(this.tle !== null && !(this.tle instanceof orbits.TLE)) this.tle = null;
if(this.tle !== null) this.setTLE(this.tle);
// refresh
this.refresh();
};
/**
* Set a Date instance or null to use the current datetime.
* Call refresh() to update the position afterward.
* @param {Date} date - An instance of Date
*/
orbits.Satellite.prototype.setDate = function(date) {
this.date = date;
};
/**
* Set the map instance to use
* @param {google.maps.Map} map - An instance of google.maps.Map
*/
orbits.Satellite.prototype.setMap = function(map) {
this.map = map;
this.marker.setMap(this.map);
this.horizon.setMap(this.map);
this.polyline.setMap(this.map);
this.shadowPolylines.forEach(function(v) { v.setMap(this.map); });
};
/**
*Recalculates the position and updates the markers
*/
orbits.Satellite.prototype.refresh = function() {
if(!this.visible || this.orbit === null || this.map === null) return;
this.orbit.setDate(this.date);
this.orbit.propagate();
this.position = this.orbit.getLatLng();
this.marker.setPosition(this.position);
var alt = this.orbit.getAltitude() * 1000;
this.horizon.setRadius(orbits.util.getDistanceToHorizon(alt));
};
/**
*Redraw path
*/
orbits.Satellite.prototype.refresh_path = function() {
if(this.pathLength >= 1.0/180) this._updatePoly();
};
/**
* Set TLE for this satellite
* @param {orbits.TLE} tle - An instance of orbits.TLE
*/
orbits.Satellite.prototype.setTLE = function(tle) {
this.orbit = new orbits.Orbit(tle);
this.marker.setTitle(tle.name);
};
orbits.Satellite.prototype._updatePoly = function() {
var dt = (this.orbit.getPeriod() * 1000) / 180;
var date = (this.date) ? this.date : new Date();
this.path = [];
this.shadowPolylines.forEach(function(v) { v.setMap(null); });
this.shadowPolylines = [];
var night = false;
var curr_path = [];
var curr_poly = null;
var curr_date = null;
var curr_night = null;
var i = 0;
var jj = (180 * this.pathLength) + 1;
for(; i <= jj; i++) {
curr_date = new Date(date.getTime() + dt*i);
this.orbit.setDate(curr_date);
this.orbit.propagate();
var pos = this.orbit.getLatLng();
this.path.push(pos);
if(!this.drawShadowPolylines) continue;
var dist = google.maps.geometry.spherical.computeDistanceBetween(orbits.util.calculateLatLngOfSun(curr_date), pos);
curr_night = dist > orbits.util.halfEarthCircumference + orbits.util.getDistanceToHorizon(this.orbit.getAltitude() * 1000);
if(night === true && curr_night === true) {
curr_path.push(pos);
}
else if(night === true && curr_night === false) {
curr_poly.setPath(curr_path);
}
else if(night === false && curr_night === true) {
curr_poly = new google.maps.Polyline(this.shadowPolylinesOpts);
curr_poly.setMap(this.map);
this.shadowPolylines.push(curr_poly);
curr_path = [pos];
}
night = curr_night;
}
if(night) curr_poly.setPath(curr_path);
this.polyline.setPath(this.path);
};
/**
* Initializes a TLE object containing parsed TLE
* @class
* @param {string} text - A TLE string of 3 lines
*/
orbits.TLE = function(text) {
this.text = text;
this.parse(this.text);
};
/**
* Parses TLE string and sets the proporties
* @param {string} text - A TLE string of 3 lines
*/
orbits.TLE.prototype.parse = function(text) {
"use strict";
var lines = text.split("\n");
if(lines.length != 3) throw new SyntaxError("Invalid TLE syntax");
// parse first line
this.name = lines[0].substring(0,24).trim();
// parse second line
if(lines[1][0] != "1") throw new SyntaxError("Invalid TLE syntax");
// TODO: verify line using the checksum in field 14
/**
* Satellite Number
* @type {int}
* @readonly
*/
this.satelite_number = parseInt(lines[1].substring(2,7));
/**
* Classification (U=Unclassified)
* @type {string}
* @readonly
*/
this.classification = lines[1].substring(7,8);
/**
* International Designator (Last two digits of launch year, eg. '98')
* @type {string}
* @readonly
*/
this.intd_year = lines[1].substring(9,11);
/**
* International Designator (Launch number of the year, eg. '067')
* @type {string}
* @readonly
*/
this.intd_ln = lines[1].substring(11,14);
/**
* International Designator (Piece of the launch, eg. 'A')
* @type {string}
* @readonly
*/
this.intd_place = lines[1].substring(14,17).trim();
/**
* International Designator (eg. 98067A)
* @type {string}
* @readonly
*/
this.intd = lines[1].substring(9,17).trim();
/**
* Epoch Year (Full year)
* @type {int}
* @readonly
*/
this.epoch_year = parseInt(lines[1].substring(18,20));
this.epoch_year += (this.epoch_year < 57) ? 2000 : 1000;
/**
* Epoch (Day of the year and fractional portion of the day)
* @type {float}
* @readonly
*/
this.epoch_day = parseFloat(lines[1].substring(20,32));
/**
* First Time Derivative of the Mean Motion divided by two
* @type {float}
* @readonly
*/
this.ftd = parseFloat(lines[1].substring(33,43));
/**
* Second Time Derivative of Mean Motion divided by six
* @type {float}
* @readonly
*/
this.std = 0;
var tmp = lines[1].substring(44,52).split(/[+-]/);
if(tmp.length == 3) this.std = -1 * parseFloat("."+tmp[1].trim()) * Math.pow(10,-parseInt(tmp[2]));
else this.std = parseFloat("."+tmp[0].trim()) * Math.pow(10,-parseInt(tmp[1]));
/**
* BSTAR drag term
* @type {float}
* @readonly
*/
this.bstar = 0;
tmp = lines[1].substring(53,61).split(/[+-]/);
if(tmp.length == 3) this.bstar = -1 * parseFloat("."+tmp[1].trim()) * Math.pow(10,-parseInt(tmp[2]));
else this.bstar = parseFloat("."+tmp[0].trim()) * Math.pow(10,-parseInt(tmp[1]));
/**
* The number 0 (Originally this should have been "Ephemeris type")
* @type {int}
* @readonly
*/
this.ehemeris_type = parseInt(lines[1].substring(62,63));
/**
* Element set number. incremented when a new TLE is generated for this object.
* @type {int}
* @readonly
*/
this.element_number = parseInt(lines[1].substring(64,68));
// parse third line
if(lines[2][0] != "2") throw new SyntaxError("Invalid TLE syntax");
// TODO: verify line using the checksum in field 14
/**
* Inclination [Degrees]
* @type {float}
* @readonly
*/
this.inclination = parseFloat(lines[2].substring(8,16));
/**
* Right Ascension of the Ascending Node [Degrees]
* @type {float}
* @readonly
*/
this.right_ascension = parseFloat(lines[2].substring(17,25));
/**
* Eccentricity
* @type {float}
* @readonly
*/
this.eccentricity = parseFloat("."+lines[2].substring(26,33).trim());
/**
* Argument of Perigee [Degrees]
* @type {float}
* @readonly
*/
this.argument_of_perigee = parseFloat(lines[2].substring(34,42));
/**
* Mean Anomaly [Degrees]
* @type {float}
* @readonly
*/
this.mean_anomaly = parseFloat(lines[2].substring(43,51));
/**
* Mean Motion [Revs per day]
* @type {float}
* @readonly
*/
this.mean_motion = parseFloat(lines[2].substring(52,63));
/**
* Revolution number at epoch [Revs]
* @type {int}
* @readonly
*/
this.epoch_rev_number = parseInt(lines[2].substring(63,68));
};
/**
* Takes a date instance and returns the different between it and TLE's epoch
* @param {Date} date - A instance of Date
* @returns {int} delta time in millis
*/
orbits.TLE.prototype.dtime = function(date) {
var a = orbits.util.jday(date);
var b = orbits.util.jday(new Date(Date.UTC(this.epoch_year, 0, 0, 0, 0, 0) + this.epoch_day * 86400000));
return (a - b) * 1440.0; // in minutes
};
/**
* Returns the TLE string
* @returns {string} TLE string in 3 lines
*/
orbits.TLE.prototype.toString = function() {
return this.text;
};
/**
* Takes orbit.TLE object and initialized the SGP4 model
* @class
* @param {orbit.TLE} tleObj - An instance of orbits.TLE
*/
orbits.Orbit = function(tleObj) {
"use strict";
this.tle = tleObj;
this.date = null;
// init constants
this.ck2 =5.413080e-4;
this.ck4 = 0.62098875e-6;
this.e6a = 1.0e-6;
this.qoms2t = 1.88027916e-9;
this.s = 1.01222928;
this.xj3 = -0.253881e-5;
this.xke = 0.743669161e-1;
this.xkmper = 6378.137; // Earth's radius WGS-84
this.xflat = 0.00335281066; // WGS-84 flattening
this.xminpday = 1440.0;
this.ae = 1.0;
this.pi = Math.PI;
this.pio2 = this.pi / 2;
this.twopi = 2 * this.pi;
this.x3pio2 = 3 * this.pio2;
this.torad = this.pi/180;
this.tothrd = 0.66666667;
this.xinc = this.tle.inclination * this.torad;
this.xnodeo = this.tle.right_ascension * this.torad;
this.eo = this.tle.eccentricity;
this.omegao = this.tle.argument_of_perigee * this.torad;
this.xmo = this.tle.mean_anomaly * this.torad;
this.xno = this.tle.mean_motion * this.twopi / 1440.0;
this.bstar = this.tle.bstar;
// recover orignal mean motion (xnodp) and semimajor axis (adop)
var a1 = Math.pow(this.xke / this.xno, this.tothrd);
var cosio = Math.cos(this.xinc);
var theta2 = cosio*cosio;
var x3thm1 = 3.0 * theta2 - 1;
var eosq = this.eo * this.eo;
var betao2= 1.0 - eosq;
var betao = Math.sqrt(betao2);
var del1 = 1.5 * this.ck2 * x3thm1 / (a1*a1 * betao*betao2);
var ao = a1 * (1 - del1 * ((1.0/3.0) + del1 * (1.0 + (134.0/81.0) * del1)));
var delo = 1.5 * this.ck2 * x3thm1/(ao * ao * betao * betao2);
var xnodp = this.xno/(1.0 + delo); //original_mean_motion
var aodp = ao/(1.0 - delo); //semi_major_axis
// initialization
this.isimp = ((aodp*(1.0-this.eo)/this.ae) < (220.0/this.xkmper+this.ae)) ? 1 : 0;
var s4 = this.s;
var qoms24 = this.qoms2t;
var perige = (aodp * (1.0-this.eo) - this.ae) * this.xkmper;
if (perige < 156.0){
s4 = perige - 78.0;
if (perige <= 98.0){
s4 = 20.0;
} else {
qoms24 = Math.pow(((120.0 - s4)*this.ae/this.xkmper), 4);
s4 = s4/this.xkmper+this.ae;
}
}
var pinvsq = 1.0/(aodp * aodp * betao2 * betao2);
var tsi = 1.0/(aodp - s4);
var eta = aodp * this.eo * tsi;
var etasq = eta * eta;
var eeta = this.eo * eta;
var psisq = Math.abs(1.0 - etasq);
var coef = qoms24 * Math.pow(tsi,4);
var coef1 = coef/Math.pow(psisq,3.5);
var c2 = coef1 * xnodp * (aodp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) + 0.75 * this.ck2 * tsi/psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)));
var c1 = this.bstar * c2;
var sinio = Math.sin(this.xinc);
var a3ovk2 = -this.xj3/this.ck2 * Math.pow(this.ae,3);
var c3 = coef * tsi * a3ovk2 * xnodp * this.ae * sinio/this.eo;
var x1mth2 = 1.0 - theta2;
var c4 = 2.0 * xnodp * coef1 * aodp * betao2 * (eta * (2.0 + 0.5 * etasq) + this.eo * (0.5 + 2.0 * etasq) - 2.0 * this.ck2 * tsi/(aodp * psisq) * (-3.0 * x3thm1 * (1.0 - 2.0 * eeta + etasq * (1.5 - 0.5 * eeta)) + 0.75 * x1mth2 * (2.0 * etasq - eeta * (1.0 + etasq)) * Math.cos((2.0 * this.omegao))));
this.c5 = 2.0 * coef1 * aodp * betao2 * (1.0 + 2.75 * (etasq + eeta) + eeta * etasq);
var theta4 = theta2 * theta2;
var temp1 = 3.0 * this.ck2 * pinvsq * xnodp;
var temp2 = temp1 * this.ck2 * pinvsq;
var temp3 = 1.25 * this.ck4 * pinvsq * pinvsq * xnodp;
this.xmdot = xnodp + 0.5 * temp1 * betao * x3thm1 + 0.0625 * temp2 * betao * (13.0 - 78.0 * theta2 + 137.0 * theta4);
var x1m5th = 1.0 - 5.0 * theta2;
this.omgdot = -0.5 * temp1 * x1m5th + 0.0625 * temp2 * (7.0 - 114.0 * theta2 + 395.0 * theta4) + temp3 * (3.0 - 36.0 * theta2 + 49.0 * theta4);
var xhdot1 = -temp1 * cosio;
this.xnodot = xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * theta2) + 2.0 * temp3 * (3.0 - 7.0 * theta2)) * cosio;
this.omgcof = this.bstar * c3 * Math.cos(this.omegao);
this.xmcof = -this.tothrd * coef * this.bstar * this.ae/eeta;
this.xnodcf = 3.5 * betao2 * xhdot1 * c1;
this.t2cof = 1.5 * c1;
this.xlcof = 0.125 * a3ovk2 * sinio * (3.0 + 5.0 * cosio)/(1.0 + cosio);
this.aycof = 0.25 * a3ovk2 * sinio;
this.delmo = Math.pow((1.0 + eta * Math.cos(this.xmo)),3);
this.sinmo = Math.sin(this.xmo);
this.x7thm1 = 7.0 * theta2 - 1.0;
var d2, d3, d4;
if (this.isimp != 1){
var c1sq = c1 * c1;
d2 = 4.0 * aodp * tsi * c1sq;
var temp = d2 * tsi * c1/3.0;
d3 = (17.0 * aodp + s4) * temp;
d4 = 0.5 * temp * aodp * tsi * (221.0 * aodp + 31.0 * s4) * c1;
this.t3cof = d2 + 2.0 * c1sq;
this.t4cof = 0.25 * (3.0 * d3 + c1 * (12.0 * d2 + 10.0 * c1sq));
this.t5cof = 0.2 * (3.0 * d4 + 12.0 * c1 * d3 + 6.0 * d2 * d2 + 15.0 * c1sq * (2.0 * d2 + c1sq));
}
// set variables that are needed in the calculate() routine
this.aodp = aodp;
this.c1 = c1;
this.c4 = c4;
this.cosio = cosio;
this.d2 = d2;
this.d3 = d3;
this.d4 = d4;
this.eta = eta;
this.sinio = sinio;
this.x3thm1 = x3thm1;
this.x1mth2 = x1mth2;
this.xnodp = xnodp;
};
/**
*calculates position and velocity vectors based date set on the Orbit object
*/
orbits.Orbit.prototype.propagate = function() {
"use strict";
var date = (this.date === null) ? new Date() : this.date;
var tsince = this.tle.dtime(date);
// update for secular gravity and atmospheric drag
var xmdf = this.xmo + this.xmdot * tsince;
var omgadf = this.omegao + this.omgdot * tsince;
var xnoddf = this.xnodeo + this.xnodot * tsince;
var omega = omgadf;
var xmp = xmdf;
var tsq = tsince * tsince;
var xnode = xnoddf + this.xnodcf * tsq;
var tempa= 1.0 - this.c1 * tsince;
var tempe = this.bstar * this.c4 * tsince;
var templ = this.t2cof * tsq;
var temp;
if (this.isimp != 1){
var delomg = this.omgcof * tsince;
var delm = this.xmcof * (Math.pow((1.0 + this.eta * Math.cos(xmdf)),3) - this.delmo);
temp = delomg + delm;
xmp = xmdf + temp;
omega = omgadf - temp;
var tcube = tsq * tsince;
var tfour = tsince * tcube;
tempa = tempa - this.d2 * tsq - this.d3 * tcube - this.d4 * tfour;
tempe = tempe + this.bstar * this.c5 * (Math.sin(xmp) - this.sinmo);
templ = templ + this.t3cof * tcube + tfour * (this.t4cof + tsince * this.t5cof);
}
var a = this.aodp * tempa * tempa;
var e = this.eo - tempe;
var xl = xmp + omega + xnode + this.xnodp * templ;
var beta = Math.sqrt(1.0 - e*e);
var xn = this.xke/Math.pow(a,1.5);
// long period periodics
var axn = e * Math.cos(omega);
temp = 1.0/(a * beta * beta);
var xll = temp * this.xlcof * axn;
var aynl = temp * this.aycof;
var xlt = xl + xll;
var ayn = e * Math.sin(omega) + aynl;
// solve keplers equation
var capu = (xlt-xnode)%(2.0*Math.PI);
var temp2 = capu;
var i;
var temp3, temp4, temp5, temp6;
var sinepw, cosepw;
for (i=1; i<=10; i++){
sinepw = Math.sin(temp2);
cosepw = Math.cos(temp2);
temp3 = axn * sinepw;
temp4 = ayn * cosepw;
temp5 = axn * cosepw;
temp6 = ayn * sinepw;
var epw = (capu - temp4 + temp3 - temp2)/(1.0 - temp5 - temp6) + temp2;
if (Math.abs(epw - temp2) <= this.e6a){
break;
}
temp2 = epw;
}
// short period preliminary quantities
var ecose = temp5 + temp6;
var esine = temp3 - temp4;
var elsq = axn * axn + ayn * ayn;
temp = 1.0 - elsq;
var pl = a*temp;
var r = a*(1.0 - ecose);
var temp1 = 1.0/r;
var rdot = this.xke * Math.sqrt(a) * esine * temp1;
var rfdot = this.xke * Math.sqrt(pl) * temp1;
temp2 = a*temp1;
var betal = Math.sqrt(temp);
temp3 = 1.0/(1.0 + betal);
var cosu = temp2 * (cosepw - axn + ayn * esine * temp3);
var sinu = temp2 * (sinepw - ayn - axn * esine * temp3);
var u = Math.atan2(sinu,cosu);
u += (u<0) ? 2* Math.PI : 0;
var sin2u = 2.0 * sinu * cosu;
var cos2u = 2.0 * cosu * cosu - 1.0;
temp = 1.0/pl;
temp1 = this.ck2 * temp;
temp2 = temp1 * temp;
// update for short periodics
var rk = r*(1.0 - 1.5 * temp2 * betal * this.x3thm1) + 0.5 * temp1 * this.x1mth2 * cos2u;
var uk = u-0.25 * temp2 * this.x7thm1 * sin2u;
var xnodek = xnode + 1.5 * temp2 * this.cosio * sin2u;
var xinck = this.xinc + 1.5 * temp2 * this.cosio * this.sinio * cos2u;
var rdotk = rdot - xn * temp1 * this.x1mth2 * sin2u;
var rfdotk = rfdot + xn * temp1 * (this.x1mth2 * cos2u + 1.5 * this.x3thm1);
// orientation vectors
var sinuk = Math.sin(uk);
var cosuk = Math.cos(uk);
var sinik = Math.sin(xinck);
var cosik = Math.cos(xinck);
var sinnok = Math.sin(xnodek);
var cosnok = Math.cos(xnodek);
var xmx = -sinnok * cosik;
var xmy = cosnok * cosik;
var ux = xmx * sinuk + cosnok * cosuk;
var uy = xmy * sinuk + sinnok * cosuk;
var uz = sinik * sinuk;
var vx = xmx * cosuk - cosnok * sinuk;
var vy = xmy * cosuk - sinnok * sinuk;
var vz = sinik * cosuk;
// position and velocity in km
this.x = (rk * ux) * this.xkmper;
this.y = (rk * uy) * this.xkmper;
this.z = (rk * uz) * this.xkmper;
this.xdot = (rdotk * ux + rfdotk * vx) * this.xkmper;
this.ydot = (rdotk * uy + rfdotk * vy) * this.xkmper;
this.zdot = (rdotk * uz + rfdotk * vz) * this.xkmper;
/**
* orbit period in seconds
* @type {float}
* @readonly
*/
this.period = this.twopi * Math.sqrt(Math.pow(this.aodp * this.xkmper , 3)/398600.4);
/**
* velocity in km per second
* @type {float}
* @readonly
*/
this.velocity = Math.sqrt(this.xdot*this.xdot + this.ydot*this.ydot + this.zdot*this.zdot) / 60; // kmps
// lat, lon and altitude
// based on http://www.celestrak.com/columns/v02n03/
a = 6378.137;
var b = 6356.7523142;
var R = Math.sqrt(this.x*this.x + this.y*this.y);
var f = (a - b)/a;
var gmst = orbits.util.gmst(date);
var e2 = ((2*f) - (f*f));
var longitude = Math.atan2(this.y, this.x) - gmst;
var latitude = Math.atan2(this.z, R);
var C;
var iterations = 20;
while(iterations--) {
C = 1 / Math.sqrt( 1 - e2*(Math.sin(latitude)*Math.sin(latitude)) );
latitude = Math.atan2 (this.z + (a*C*e2*Math.sin(latitude)), R);
}
/**
* Altitude in kms
* @type {float}
* @readonly
*/
this.altitude = (R/Math.cos(latitude)) - (a*C);
// convert from radii to degrees
longitude = (longitude / this.torad) % 360;
if(longitude > 180) longitude = 360 - longitude;
else if(longitude < -180) longitude = 360 + longitude;
latitude = (latitude / this.torad);
/**
* latitude in degrees
* @type {float}
* @readonly
*/
this.latitude = latitude;
/**
* longtitude in degrees
* @type {float}
* @readonly
*/
this.longitude = longitude;
};
/**
* Change the datetime, or null for to use current
* @param {Date} date
*/
orbits.Orbit.prototype.setDate = function(date) {
this.date = date;
};
/**
* get position
* @returns {float[]} [latitude, longitude]
*/
orbits.Orbit.prototype.getPosition = function() {
return [this.latitude, this.longitude];
};
/**
* get position in LatLng
* @returns {google.maps.LatLng}
*/
orbits.Orbit.prototype.getLatLng = function() {
return new google.maps.LatLng(this.latitude, this.longitude);
};
/**
* get altitude in km
* @returns {float}
*/
orbits.Orbit.prototype.getAltitude = function() {
return this.altitude;
};
/**
* get velocity in km per seconds
* @returns {float}
*/
orbits.Orbit.prototype.getVelocity = function() {
return this.velocity;
};
/**
*get period in seconds
* @returns {float}
*/
orbits.Orbit.prototype.getPeriod = function() {
return this.period;
};