├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── export.js ├── jspredict.js ├── package.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | .npm/ 4 | build/ 5 | test 6 | *~ 7 | .versions 8 | *# 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Spire Global Inc 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the Spire Global Inc nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 22 | Spire Global Inc BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 25 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 28 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JsPredict 2 | 3 | A Javascript port of the popular `predict` satellite tracking library. 4 | 5 | ### Based on: 6 | - PREDICT: http://www.qsl.net/kd2bd/predict.html 7 | - PyPredict: https://github.com/nsat/pypredict 8 | - Python-SGP4: https://github.com/brandon-rhodes/python-sgp4 9 | 10 | ### Depends on: 11 | - Satellite.js: https://github.com/shashwatak/satellite-js 12 | - Moment.js: https://github.com/moment/moment 13 | 14 | ## Installation 15 | 16 | JsPredict has been pushed to the `NPM`, `Meteor` (Atmosphere), and `Bower` package registries, and can also be used by including the src file directly. 17 | 18 | ### NPM 19 | 20 | ``` 21 | npm install jspredict 22 | ``` 23 | 24 | ### Meteor 25 | 26 | ``` 27 | meteor add rosh93:jspredict 28 | ``` 29 | 30 | ### Bower 31 | 32 | ``` 33 | bower install jspredict 34 | ``` 35 | 36 | ### Manual Include 37 | 38 | Download and include `moment.js`: http://momentjs.com/ 39 | 40 | Include both `satellite.js` and `jspredict.js` to get `satellite` and `jspredict` available on the global `window` namespace. 41 | 42 | ## API 43 | 44 | #### Input Types 45 | 46 | ```js 47 | tle = 3 line string with "\n" character line breaks 48 | 49 | qth = 3 element array [latitude (degrees), longitude (degrees), altitude (km)] 50 | 51 | time, start, or end = unix timestamp (ms) or date object "new Date()" 52 | ``` 53 | 54 | #### Methods 55 | 56 | ```js 57 | observe(tle 'required', qth 'optional', time 'optional') 58 | 59 | observes(tle 'required', qth 'optional', start 'optional', end 'required', interval 'optional') 60 | 61 | transits(tle 'required', qth 'required', start 'optional', end 'required', minElevation 'optional', maxTransits 'optional') 62 | ``` 63 | 64 | ## Examples 65 | 66 | ### Observe a Satellite: 67 | 68 | ```js 69 | > var tle = '0 LEMUR-2 JEROEN\n1 40934U 15052E 15306.10048119 .00001740 00000-0 15647-3 0 9990\n2 40934 6.0033 141.2190 0010344 133.6141 226.4604 14.76056230 5130'; 70 | > var jspredict = require('jspredict'); 71 | > jspredict.observe(tle, null); 72 | { eci: 73 | { position: 74 | { x: 6780.217861682045, 75 | y: -1754.945569075624, 76 | z: -382.1001487529574 }, 77 | velocity: 78 | { x: 1.8548312182745958, 79 | y: 7.28225574805238, 80 | z: -0.6742937006920255 } }, 81 | gmst: 1.2743405900207918, 82 | latitude: -3.141891992384467, 83 | longitude: -87.52591692501754, 84 | altitude: 635.9975103859342, 85 | footprint: 5474.178485006438 } 86 | ``` 87 | 88 | ### Observe a Satellite from an Observer at 15 lat, 130, lon, 10m alt: 89 | 90 | ```js 91 | > var tle = '0 LEMUR-2 JEROEN\n1 40934U 15052E 15306.10048119 .00001740 00000-0 15647-3 0 9990\n2 40934 6.0033 141.2190 0010344 133.6141 226.4604 14.76056230 5130'; 92 | > var qth = [15, 130, .1]; 93 | > jspredict.observe(tle, qth); 94 | { eci: 95 | { position: 96 | { x: 6808.890168241923, 97 | y: -1638.1745052042197, 98 | z: -392.83171494347425 }, 99 | velocity: 100 | { x: 1.729088700801128, 101 | y: 7.313653076194647, 102 | z: -0.6671038712037236 } }, 103 | gmst: 1.275507328110315, 104 | latitude: -3.2301661539920232, 105 | longitude: -86.6090669346031, 106 | altitude: 636.124394452163, 107 | footprint: 5474.682764305541, 108 | azimuth: 75.42118188269167, 109 | elevation: -70.0809770796008, 110 | rangeSat: 12666.306550391646, 111 | doppler: 1.0000075435881037 } 112 | ``` 113 | 114 | ### Get Transits for Satellite and Observer (minimum elevation of 2 degrees; obtain a maximum of 4 transits) 115 | 116 | ```js 117 | > var tle = '0 LEMUR-2 JEROEN\n1 40934U 15052E 15306.10048119 .00001740 00000-0 15647-3 0 9990\n2 40934 6.0033 141.2190 0010344 133.6141 226.4604 14.76056230 5130'; 118 | > var qth = [15, 130, .1]; 119 | > jspredict.transits(tle, qth, 1446516345242, 1446545135046, 2, 4); 120 | [ { start: 1446519623929.2715, 121 | end: 1446520436786.1265, 122 | maxElevation: 26.592307317708126, 123 | apexAzimuth: 173.44894443969358, 124 | maxAzimuth: 244.2708297009277, 125 | minAzimuth: 108.07476128814045, 126 | duration: 812856.8549804688 }, 127 | { start: 1446525901933.6611, 128 | end: 1446526693580.5254, 129 | maxElevation: 24.777958881102588, 130 | apexAzimuth: 170.71484739848532, 131 | maxAzimuth: 244.97838417889344, 132 | minAzimuth: 110.85020906380568, 133 | duration: 791646.8642578125 }, 134 | { start: 1446532176864.1306, 135 | end: 1446533027054.9875, 136 | maxElevation: 20.48579856021555, 137 | apexAzimuth: 194.49205827738396, 138 | maxAzimuth: 242.43145831257118, 139 | minAzimuth: 114.97146874644389, 140 | duration: 850190.8569335938 }, 141 | { start: 1446538461828.8735, 142 | end: 1446539183964.2942, 143 | maxElevation: 15.359176537330036, 144 | apexAzimuth: 188.34763284223402, 145 | maxAzimuth: 236.24036969182643, 146 | minAzimuth: 123.49296057832372, 147 | duration: 722135.4206542969 } ] 148 | > 149 | ``` 150 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jspredict", 3 | "main": "jspredict.js", 4 | "version": "1.2.0", 5 | "authors": [ 6 | "Roshan Jobanputra " 7 | ], 8 | "description": "javascript port of predict open-source satellite tracking library", 9 | "keywords": [ 10 | "satellite", 11 | "orbit", 12 | "tle", 13 | "qth", 14 | "predict", 15 | "space", 16 | "sgp4", 17 | "norad", 18 | "geostationary", 19 | "leo", 20 | "azimuth", 21 | "elevation", 22 | "spacecraft", 23 | "spire" 24 | ], 25 | "license": "MIT", 26 | "homepage": "https://github.com/nsat/jspredict", 27 | "ignore": [ 28 | "**/.*", 29 | "node_modules", 30 | "bower_components", 31 | "test", 32 | "tests" 33 | ], 34 | "dependencies": { 35 | "satellite.js": "~3.0.1", 36 | "moment": "~2.24.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /export.js: -------------------------------------------------------------------------------- 1 | // jspredict.js makes `jspredict` global on the window (or global) object, while Meteor expects a file-scoped global variable 2 | jspredict = this.jspredict; 3 | try { 4 | delete this.jspredict; 5 | } catch (e) { 6 | } 7 | -------------------------------------------------------------------------------- /jspredict.js: -------------------------------------------------------------------------------- 1 | // jspredict v1.1.1 2 | // Author: Roshan Jobanputra 3 | // https://github.com/nsat/jspredict 4 | 5 | // Changelog: 6 | // v1.2.0 (rachaelacollins) - Add transitSegment() 7 | // v1.1.1 (cantino) - Update satellite.js dependency 8 | // v1.0.3 (rosh93) - If we cant approximate our aos within max_iterations, return null and dont attempt to return a bad transit object. Fix a few jslint warnings 9 | // v1.0.2 (jotenko) - Added parameter 'maxTransits' to function 'transits' (allows the user to define a maximum number of transits to be calculated, for performance management) 10 | // v1.0.1 (nsat) - First release 11 | 12 | // Copyright (c) 2015, Spire Global Inc 13 | // All rights reserved. 14 | // 15 | // Redistribution and use in source and binary forms, with or without 16 | // modification, are permitted provided that the following conditions are met: 17 | // * Redistributions of source code must retain the above copyright 18 | // notice, this list of conditions and the following disclaimer. 19 | // * Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // * Neither the name of the Spire Global Inc nor the 23 | // names of its contributors may be used to endorse or promote products 24 | // derived from this software without specific prior written permission. 25 | // 26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 30 | // Spire Global Inc BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 33 | // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 34 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 36 | // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 37 | // SUCH DAMAGE. 38 | 39 | // Based on: 40 | // PREDICT: http://www.qsl.net/kd2bd/predict.html 41 | // PyPredict: https://github.com/nsat/pypredict 42 | // Python-SGP4: https://github.com/brandon-rhodes/python-sgp4 43 | // Depends on: 44 | // Satellite.js: https://github.com/shashwatak/satellite-js 45 | // Moment.js: https://github.com/moment/moment 46 | 47 | // API 48 | 49 | // jspredict 50 | // 51 | // Inputs: 52 | // tle = 3 line string 53 | // qth = 3 element array [latitude (degrees), longitude (degrees), altitude (km)] 54 | // time/start/end = unix timestamp (ms) or date object (new Date()) 55 | 56 | // observe(tle 'required', qth 'optional', time 'optional') 57 | // 58 | // observes(tle 'required', qth 'optional', start 'optional', end 'required', interval 'optional') 59 | // 60 | // transits(tle 'required', qth 'required', start 'optional', end 'required', minElevation 'optional') 61 | // 62 | // transitSegment(tle 'required', qth 'required', start 'required', end 'required') 63 | 64 | (function (global, factory) { 65 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 66 | typeof define === 'function' && define.amd ? define(factory) : 67 | global.jspredict = factory() 68 | }(this, function () { 69 | // Meteor includes the moment dependency differently than Npm or Bower, 70 | // so we need this hack (using m_moment) else it gets upset about moment = require('moment'); 71 | var m_moment; 72 | 73 | // Npm 74 | if (typeof require !== 'undefined') { 75 | var satellite = require('satellite.js'); 76 | m_moment = require('moment'); 77 | } 78 | // Meteor 79 | if (this.satellite) { 80 | var satellite = this.satellite; 81 | } 82 | m_moment = m_moment || moment; 83 | 84 | var xkmper = 6.378137E3; // earth radius (km) wgs84 85 | var astro_unit = 1.49597870691E8; // Astronomical unit - km (IAU 76) 86 | var solar_radius = 6.96000E5; // solar radius - km (IAU 76) 87 | var deg2rad = Math.PI / 180; 88 | var ms2day = 1000 * 60 * 60 * 24; // milliseconds to day 89 | var max_iterations = 250; 90 | var defaultMinElevation = 4; // degrees 91 | 92 | var _jspredict = { 93 | observe: function(tle, qth, start) { 94 | var tles = tle.split('\n'); 95 | var satrec = satellite.twoline2satrec(tles[1], tles[2]); 96 | 97 | if (this._badSat(satrec, qth, start)) { 98 | return null; 99 | } 100 | 101 | return this._observe(satrec, qth, start) 102 | }, 103 | 104 | observes: function(tle, qth, start, end, interval) { 105 | start = m_moment(start); 106 | end = m_moment(end); 107 | 108 | var tles = tle.split('\n'); 109 | var satrec = satellite.twoline2satrec(tles[1], tles[2]); 110 | 111 | if (this._badSat(satrec, qth, start)) { 112 | return null; 113 | } 114 | 115 | var observes = [], observed; 116 | var iterations = 0; 117 | while (start < end && iterations < max_iterations) { 118 | observed = this._observe(satrec, qth, start); 119 | if (!observed) { 120 | break; 121 | } 122 | observes.push(observed); 123 | start.add(interval); 124 | iterations += 1; 125 | } 126 | 127 | return observes 128 | }, 129 | 130 | transits: function(tle, qth, start, end, minElevation, maxTransits) { 131 | start = m_moment(start); 132 | end = m_moment(end); 133 | 134 | if (!minElevation) { 135 | minElevation = defaultMinElevation; 136 | } 137 | 138 | if (!maxTransits) { 139 | maxTransits = max_iterations; 140 | } 141 | 142 | var tles = tle.split('\n'); 143 | var satrec = satellite.twoline2satrec(tles[1], tles[2]); 144 | if (this._badSat(satrec, qth, start)) { 145 | return []; 146 | } 147 | 148 | var time = start.valueOf(); 149 | var transits = []; 150 | var nextTransit; 151 | var iterations = 0; 152 | 153 | while (iterations < max_iterations && transits.length < maxTransits) { 154 | transit = this._quickPredict(satrec, qth, time); 155 | if (!transit) { 156 | break; 157 | } 158 | if (transit.end > end.valueOf()) { 159 | break; 160 | } 161 | if (transit.end > start.valueOf() && transit.maxElevation > minElevation) { 162 | transits.push(transit); 163 | } 164 | time = transit.end + 60 * 1000; 165 | iterations += 1; 166 | } 167 | 168 | return transits 169 | }, 170 | 171 | transitSegment: function(tle, qth, start, end) { 172 | start = m_moment(start); 173 | end = m_moment(end); 174 | 175 | var tles = tle.split('\n'); 176 | var satrec = satellite.twoline2satrec(tles[1], tles[2]); 177 | if (this._badSat(satrec, qth, start)) { 178 | return []; 179 | } 180 | 181 | return this._quickPredict(satrec, qth, start.valueOf(), end.valueOf()); 182 | }, 183 | 184 | _observe: function(satrec, qth, start) { 185 | start = m_moment(start); 186 | var eci = this._eci(satrec, start); 187 | var gmst = this._gmst(start); 188 | if (!eci.position) { 189 | return null; 190 | } 191 | var geo = satellite.eciToGeodetic(eci.position, gmst); 192 | 193 | var solar_vector = this._calculateSolarPosition(start.valueOf()); 194 | var eclipse = this._satEclipsed(eci.position, solar_vector); 195 | 196 | var track = { 197 | eci: eci, 198 | gmst: gmst, 199 | latitude: geo.latitude / deg2rad, 200 | longitude: this._boundLongitude(geo.longitude / deg2rad), 201 | altitude: geo.height, 202 | footprint: 12756.33 * Math.acos(xkmper / (xkmper + geo.height)), 203 | sunlit: !eclipse.eclipsed, 204 | eclipseDepth: eclipse.depth / deg2rad 205 | } 206 | 207 | // If we have a groundstation let's get those additional observe parameters 208 | if (qth && qth.length == 3) { 209 | var observerGd = { 210 | longitude: qth[1] * deg2rad, 211 | latitude: qth[0] * deg2rad, 212 | height: qth[2] 213 | } 214 | 215 | var positionEcf = satellite.eciToEcf(eci.position, gmst), 216 | velocityEcf = satellite.eciToEcf(eci.velocity, gmst), 217 | observerEcf = satellite.geodeticToEcf(observerGd), 218 | lookAngles = satellite.ecfToLookAngles(observerGd, positionEcf), 219 | doppler = satellite.dopplerFactor(observerEcf, positionEcf, velocityEcf); 220 | 221 | track.azimuth = lookAngles.azimuth / deg2rad; 222 | track.elevation = lookAngles.elevation / deg2rad; 223 | track.rangeSat = lookAngles.rangeSat; 224 | track.doppler = doppler; 225 | } 226 | 227 | return track 228 | }, 229 | 230 | _quickPredict: function(satrec, qth, start, end) { 231 | var transit = {}; 232 | var lastel = 0; 233 | var iterations = 0; 234 | 235 | if (this._badSat(satrec, qth, start)) { 236 | return null; 237 | } 238 | 239 | var daynum = this._findAOS(satrec, qth, start); 240 | if (!daynum) { 241 | return null; 242 | } 243 | transit.start = daynum; 244 | 245 | var observed = this._observe(satrec, qth, daynum); 246 | if (!observed) { 247 | return null; 248 | } 249 | 250 | var iel = Math.round(observed.elevation); 251 | 252 | var maxEl = 0, apexAz = 0, minAz = 360, maxAz = 0; 253 | 254 | while (iel >= 0 && iterations < max_iterations && (!end || daynum < end)) { 255 | lastel = iel; 256 | daynum = daynum + ms2day * Math.cos((observed.elevation-1.0)*deg2rad)*Math.sqrt(observed.altitude)/25000.0; 257 | observed = this._observe(satrec, qth, daynum); 258 | iel = Math.round(observed.elevation); 259 | if (maxEl < observed.elevation) { 260 | maxEl = observed.elevation; 261 | apexAz = observed.azimuth; 262 | } 263 | maxAz = Math.max(maxAz, observed.azimuth); 264 | minAz = Math.min(minAz, observed.azimuth); 265 | iterations += 1; 266 | } 267 | if (lastel !== 0) { 268 | daynum = this._findLOS(satrec, qth, daynum); 269 | } 270 | 271 | transit.end = daynum; 272 | transit.maxElevation = maxEl; 273 | transit.apexAzimuth = apexAz; 274 | transit.maxAzimuth = maxAz; 275 | transit.minAzimuth = minAz; 276 | transit.duration = transit.end - transit.start; 277 | 278 | return transit 279 | }, 280 | 281 | _badSat: function(satrec, qth, start) { 282 | if (qth && !this._aosHappens(satrec, qth)) { 283 | return true 284 | } else if (start && this._decayed(satrec, start)) { 285 | return true 286 | } else { 287 | return false 288 | } 289 | }, 290 | 291 | _aosHappens: function(satrec, qth) { 292 | var lin, sma, apogee; 293 | var meanmo = satrec.no * 24 * 60 / (2 * Math.PI); // convert rad/min to rev/day 294 | if (meanmo === 0) { 295 | return false 296 | } else { 297 | lin = satrec.inclo / deg2rad; 298 | 299 | if (lin >= 90.0) { 300 | lin = 180.0 - lin; 301 | } 302 | 303 | sma = 331.25 * Math.exp(Math.log(1440.0/meanmo)*(2.0/3.0)); 304 | apogee = sma * (1.0 + satrec.ecco) - xkmper; 305 | 306 | if ((Math.acos(xkmper/(apogee+xkmper))+(lin*deg2rad)) > Math.abs(qth[0]*deg2rad)) { 307 | return true 308 | } else { 309 | return false 310 | } 311 | } 312 | }, 313 | 314 | _decayed: function(satrec, start) { 315 | start = m_moment(start); 316 | 317 | var satepoch = m_moment.utc(satrec.epochyr, "YY").add(satrec.epochdays, 'days').valueOf(); 318 | 319 | var meanmo = satrec.no * 24 * 60 / (2 * Math.PI); // convert rad/min to rev/day 320 | var drag = satrec.ndot * 24 * 60 * 24 * 60 / (2 * Math.PI); // convert rev/day^2 321 | 322 | if (satepoch + ms2day * ((16.666666-meanmo)/(10.0*Math.abs(drag))) < start) { 323 | return true 324 | } else { 325 | return false 326 | } 327 | }, 328 | 329 | _findAOS: function(satrec, qth, start) { 330 | var current = start; 331 | var observed = this._observe(satrec, qth, current); 332 | if (!observed) { 333 | return null; 334 | } 335 | var aostime = 0; 336 | var iterations = 0; 337 | 338 | if (observed.elevation > 0) { 339 | return current 340 | } 341 | while (observed.elevation < -1 && iterations < max_iterations) { 342 | current = current - ms2day * 0.00035*(observed.elevation*((observed.altitude/8400.0)+0.46)-2.0); 343 | observed = this._observe(satrec, qth, current); 344 | if (!observed) { 345 | break; 346 | } 347 | iterations += 1; 348 | } 349 | iterations = 0; 350 | while (aostime === 0 && iterations < max_iterations) { 351 | if (!observed) { 352 | break; 353 | } 354 | if (Math.abs(observed.elevation) < 0.50) { // this was 0.03 but switched to 0.50 for performance 355 | aostime = current; 356 | } else { 357 | current = current - ms2day * observed.elevation * Math.sqrt(observed.altitude)/530000.0; 358 | observed = this._observe(satrec, qth, current); 359 | } 360 | iterations += 1; 361 | } 362 | if (aostime === 0) { 363 | return null; 364 | } 365 | return aostime 366 | }, 367 | 368 | _findLOS: function(satrec, qth, start) { 369 | var current = start; 370 | var observed = this._observe(satrec, qth, current); 371 | var lostime = 0; 372 | var iterations = 0; 373 | 374 | while (lostime === 0 && iterations < max_iterations) { 375 | if (Math.abs(observed.elevation) < 0.50) { // this was 0.03 but switched to 0.50 for performance 376 | lostime = current; 377 | } else { 378 | current = current + ms2day * observed.elevation * Math.sqrt(observed.altitude)/502500.0; 379 | observed = this._observe(satrec, qth, current); 380 | if (!observed) { 381 | break; 382 | } 383 | } 384 | iterations += 1; 385 | } 386 | return lostime 387 | }, 388 | 389 | _eci: function(satrec, date) { 390 | date = new Date(date.valueOf()); 391 | return satellite.propagate( 392 | satrec, 393 | date.getUTCFullYear(), 394 | date.getUTCMonth() + 1, // months range 1-12 395 | date.getUTCDate(), 396 | date.getUTCHours(), 397 | date.getUTCMinutes(), 398 | date.getUTCSeconds() 399 | ); 400 | }, 401 | 402 | _gmst: function(date) { 403 | date = new Date(date.valueOf()); 404 | return satellite.gstime( 405 | date.getUTCFullYear(), 406 | date.getUTCMonth() + 1, // months range 1-12 407 | date.getUTCDate(), 408 | date.getUTCHours(), 409 | date.getUTCMinutes(), 410 | date.getUTCSeconds() 411 | ); 412 | }, 413 | 414 | _boundLongitude: function(longitude) { 415 | while (longitude < -180) { 416 | longitude += 360; 417 | } 418 | while (longitude > 180) { 419 | longitude -= 360; 420 | } 421 | return longitude 422 | }, 423 | 424 | _satEclipsed: function(pos, sol) { 425 | var sd_earth = Math.asin(xkmper / this._magnitude(pos)); 426 | var rho = this._vecSub(sol, pos); 427 | var sd_sun = Math.asin(solar_radius / rho.w); 428 | var earth = this._scalarMultiply(-1, pos); 429 | var delta = this._angle(sol, earth); 430 | 431 | var eclipseDepth = sd_earth - sd_sun - delta; 432 | var eclipse; 433 | if (sd_earth < sd_sun) { 434 | eclipse = false; 435 | } else if (eclipseDepth >= 0) { 436 | eclipse = true; 437 | } else { 438 | eclipse = false; 439 | } 440 | return { 441 | depth: eclipseDepth, 442 | eclipsed: eclipse 443 | } 444 | }, 445 | 446 | _calculateSolarPosition: function(start) { 447 | var time = start / ms2day + 2444238.5; // jul_utc 448 | 449 | var mjd = time - 2415020.0; 450 | var year = 1900 + mjd / 365.25; 451 | var T = (mjd + this._deltaET(year) / (ms2day / 1000)) / 36525.0; 452 | var M = deg2rad * ((358.47583 + ((35999.04975 * T) % 360) - (0.000150 + 0.0000033 * T) * Math.pow(T, 2)) % 360); 453 | var L = deg2rad * ((279.69668 + ((36000.76892 * T) % 360) + 0.0003025 * Math.pow(T, 2)) % 360); 454 | var e = 0.01675104 - (0.0000418 + 0.000000126 * T) * T; 455 | var C = deg2rad * ((1.919460 - (0.004789 + 0.000014 * T) * T) * Math.sin(M) + (0.020094 - 0.000100 * T) * Math.sin(2 * M) + 0.000293 * Math.sin(3 * M)); 456 | var O = deg2rad * ((259.18 - 1934.142 * T) % 360.0); 457 | var Lsa = (L + C - deg2rad * (0.00569 - 0.00479 * Math.sin(O))) % (2 * Math.PI); 458 | var nu = (M + C) % (2 * Math.PI); 459 | var R = 1.0000002 * (1 - Math.pow(e, 2)) / (1 + e * Math.cos(nu)); 460 | var eps = deg2rad * (23.452294 - (0.0130125 + (0.00000164 - 0.000000503 * T) * T) * T + 0.00256 * Math.cos(O)); 461 | var R = astro_unit * R; 462 | 463 | return { 464 | x: R * Math.cos(Lsa), 465 | y: R * Math.sin(Lsa) * Math.cos(eps), 466 | z: R * Math.sin(Lsa) * Math.sin(eps), 467 | w: R 468 | } 469 | }, 470 | 471 | _deltaET: function(year) { 472 | return 26.465 + 0.747622 * (year - 1950) + 1.886913 * Math.sin((2 * Math.PI) * (year - 1975) / 33) 473 | }, 474 | 475 | _vecSub: function(v1, v2) { 476 | var vec = { 477 | x: v1.x - v2.x, 478 | y: v1.y - v2.y, 479 | z: v1.z - v2.z 480 | } 481 | vec.w = this._magnitude(vec); 482 | return vec 483 | }, 484 | 485 | _scalarMultiply: function(k, v) { 486 | return { 487 | x: k * v.x, 488 | y: k * v.y, 489 | z: k * v.z, 490 | w: v.w ? Math.abs(k) * v.w : undefined 491 | } 492 | }, 493 | 494 | _magnitude: function(v) { 495 | return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2) + Math.pow(v.z, 2)) 496 | }, 497 | 498 | _angle: function(v1, v2) { 499 | var dot = (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); 500 | return Math.acos(dot / (this._magnitude(v1) * this._magnitude(v2))) 501 | } 502 | } 503 | 504 | return _jspredict; 505 | })); 506 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | summary: "javascript port of predict open-source satellite tracking library", 3 | version: "1.2.0", 4 | name: "nsat:jspredict", 5 | git: "https://github.com/nsat/jspredict" 6 | }); 7 | 8 | Package.onUse(function(api) { 9 | api.use("momentjs:moment@2.24.0"); 10 | api.addFiles('satellite.js', ['client', 'server'], { 11 | bare: true 12 | }); 13 | api.addFiles([ 14 | "jspredict.js", 15 | "export.js" 16 | ]); 17 | api.export("jspredict"); 18 | }); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jspredict", 3 | "version": "1.2.0", 4 | "description": "javascript port of predict open-source satellite tracking library", 5 | "main": "jspredict.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/nsat/jspredict" 9 | }, 10 | "keywords": [ 11 | "satellite", 12 | "orbit", 13 | "tle", 14 | "qth", 15 | "predict", 16 | "space", 17 | "sgp4", 18 | "norad", 19 | "geostationary", 20 | "leo", 21 | "azimuth", 22 | "elevation", 23 | "spacecraft", 24 | "spire" 25 | ], 26 | "author": "Roshan Jobanputra ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/nsat/jspredict/issues" 30 | }, 31 | "homepage": "https://github.com/nsat/jspredict", 32 | "dependencies": { 33 | "moment": "^2.24.0", 34 | "satellite.js": "^3.0.1" 35 | } 36 | } 37 | --------------------------------------------------------------------------------