Merge pull request #716 from AndreasK79/sat_stuff

This commit is contained in:
Andreas Kristiansen
2024-08-08 16:50:01 +02:00
committed by GitHub
38 changed files with 9297 additions and 14 deletions

View File

@@ -22,7 +22,7 @@ $config['migration_enabled'] = TRUE;
|
*/
$config['migration_version'] = 213;
$config['migration_version'] = 214;
/*
|--------------------------------------------------------------------------

View File

@@ -138,4 +138,135 @@ class Satellite extends CI_Controller {
echo json_encode($sat_list, JSON_FORCE_OBJECT);
}
public function flightpath() {
$this->load->model('satellite_model');
$this->load->model('stations');
$pageData['satellites'] = $this->satellite_model->get_all_satellites_with_tle();
$footerData = [];
$footerData['scripts'] = [
'assets/js/sections/satellite.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/satellite.js")),
'assets/js/sections/three-orbit-controls.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/three-orbit-controls.js")),
'assets/js/sections/satellite_functions.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/satellite_functions.js")),
'assets/js/sections/flightpath.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/flightpath.js")),
];
$homegrid = explode(',', $this->stations->find_gridsquare());
$this->load->library('Qra');
$pageData['latlng'] = $this->qra->qra2latlong($homegrid[0]);
// Render Page
$pageData['page_title'] = "Satellite Flightpath";
$this->load->view('interface_assets/header', $pageData);
$this->load->view('satellite/flightpath');
$this->load->view('interface_assets/footer', $footerData);
}
public function get_tle() {
$sat = $this->security->xss_clean($this->input->post('sat'));
$this->load->model('satellite_model');
$satellite_data = $this->satellite_model->get_tle($sat);
header('Content-Type: application/json');
echo json_encode($satellite_data, JSON_FORCE_OBJECT);
}
public function pass() {
$this->load->model('satellite_model');
$this->load->model('stations');
$active_station_id = $this->stations->find_active();
$pageData['activegrid'] = $this->stations->gridsquare_from_station($active_station_id);
$pageData['satellites'] = $this->satellite_model->get_all_satellites_with_tle();
$footerData = [];
$footerData['scripts'] = [
'assets/js/sections/satpasses.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/satpasses.js")),
];
// Render Page
$pageData['page_title'] = "Satellite pass";
$this->load->view('interface_assets/header', $pageData);
$this->load->view('satellite/pass');
$this->load->view('interface_assets/footer', $footerData);
}
public function searchpasses() {
try {
$result = $this->get_tle_for_predict();
$this->calcpass($result);
}
catch (Exception $e) {
header("Content-type: application/json");
echo json_encode(['ok' => 'Error', 'message' => $e->getMessage() . $e->getCode()]);
}
}
public function get_tle_for_predict() {
$sat = $this->security->xss_clean($this->input->post('sat'));
$this->load->model('satellite_model');
return $this->satellite_model->get_tle($sat);
}
function calcpass($sat_tle) {
require_once realpath(__DIR__ . "/../../predict/Predict.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Sat.php");
require_once realpath(__DIR__ . "/../../predict/Predict/QTH.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Time.php");
require_once realpath(__DIR__ . "/../../predict/Predict/TLE.php");
// The observer or groundstation is called QTH in ham radio terms
$predict = new Predict();
$qth = new Predict_QTH();
$qth->alt = $this->security->xss_clean($this->input->post('altitude')); // Altitude in meters
$strQRA = $this->security->xss_clean($this->input->post('yourgrid'));
if ((strlen($strQRA) % 2 == 0) && (strlen($strQRA) <= 10)) { // Check if QRA is EVEN (the % 2 does that) and smaller/equal 8
$strQRA = strtoupper($strQRA);
if (strlen($strQRA) == 4) $strQRA .= "LL"; // Only 4 Chars? Fill with center "LL" as only A-R allowed
if (strlen($strQRA) == 6) $strQRA .= "55"; // Only 6 Chars? Fill with center "55"
if (strlen($strQRA) == 8) $strQRA .= "LL"; // Only 8 Chars? Fill with center "LL" as only A-R allowed
if (!preg_match('/^[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}$/', $strQRA)) {
return false;
}
}
if(!$this->load->is_loaded('Qra')) {
$this->load->library('Qra');
}
$homecoordinates = $this->qra->qra2latlong($this->security->xss_clean($this->input->post('yourgrid')));
$qth->lat = $homecoordinates[0];
$qth->lon = $homecoordinates[1];
$temp = preg_split('/\n/', $sat_tle->tle);
$tle = new Predict_TLE($sat_tle->satellite, $temp[0], $temp[1]); // Instantiate it
$sat = new Predict_Sat($tle); // Load up the satellite data
$now = Predict_Time::get_current_daynum(); // get the current time as Julian Date (daynum)
// You can modify some preferences in Predict(), the defaults are below
//
$predict->minEle = intval($this->security->xss_clean($this->input->post('minelevation'))); // Minimum elevation for a pass
$predict->timeRes = 1; // Pass details: time resolution in seconds
$predict->numEntries = 20; // Pass details: number of entries per pass
// $predict->threshold = -6; // Twilight threshold (sun must be at this lat or lower)
// Get the passes and filter visible only, takes about 4 seconds for 10 days
$results = $predict->get_passes($sat, $qth, $now, 1);
$filtered = $predict->filterVisiblePasses($results);
$zone = $this->security->xss_clean($this->input->post('timezone'));
$format = 'm-d-Y H:i:s'; // Time format from PHP's date() function
$data['filtered'] = $filtered;
$data['zone'] = $zone;
$data['format'] = $format;
$this->load->view('satellite/passtable', $data);
}
}

View File

@@ -372,5 +372,60 @@ class Update extends CI_Controller {
}
public function update_tle() {
$mtime = microtime();
$mtime = explode(" ",$mtime);
$mtime = $mtime[1] + $mtime[0];
$starttime = $mtime;
$url = 'https://www.amsat.org/tle/dailytle.txt';
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($curl);
$count = 0;
if ($response === false) {
echo 'Error: ' . curl_error($curl);
} else {
$this->db->empty_table("tle");
// Split the response into an array of lines
$lines = explode("\n", $response);
$satname = '';
$tleline1 = '';
$tleline2 = '';
// Process each line
for ($i = 0; $i < count($lines); $i += 3) {
$count++;
// Check if there are at least three lines remaining
if (isset($lines[$i], $lines[$i + 1], $lines[$i + 2])) {
// Get the three lines
$satname = $lines[$i];
$tleline1 = $lines[$i + 1];
$tleline2 = $lines[$i + 2];
$sql = "INSERT INTO tle (satelliteid, tle) select id, ? from satellite where name = ? or exportname = ?";
$this->db->query($sql,array($tleline1."\n".$tleline2,$satname,$satname));
}
}
}
curl_close($curl);
$mtime = microtime();
$mtime = explode(" ",$mtime);
$mtime = $mtime[1] + $mtime[0];
$endtime = $mtime;
$totaltime = ($endtime - $starttime);
echo "This page was created in ".$totaltime." seconds <br />";
echo "Records inserted: " . $count . " <br/>";
$datetime = new DateTime("now", new DateTimeZone('UTC'));
$datetime = $datetime->format('Ymd h:i');
$this->optionslib->update('tle_update', $datetime , 'no');
}
}
?>

View File

@@ -0,0 +1,55 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Migration_tle_table extends CI_Migration {
public function up()
{
if (!$this->db->table_exists('tle')) {
$this->dbforge->add_field(array(
'id' => array(
'type' => 'INT',
'constraint' => 6,
'unsigned' => TRUE,
'auto_increment' => TRUE,
'null' => FALSE
),
'satelliteid' => array(
'type' => 'INT',
'constraint' => '6',
'unsigned' => TRUE,
'null' => FALSE,
),
'tle' => array(
'type' => 'TEXT',
'null' => TRUE,
),
'updated' => array(
'type' => 'timestamp',
'null' => false,
'default' => 'CURRENT_TIMESTAMP'
),
));
$this->dbforge->add_key('id', TRUE);
$this->dbforge->create_table('tle');
$this->db->query("INSERT INTO tle (satelliteid, tle) select id, '1 53106U 22080B 24108.26757684 -.00000003 00000-0 00000-0 0 9991
2 53106 70.1428 213.6795 0007982 119.5170 240.6286 6.42557181 41352' from satellite where name = 'IO-117'");
$this->db->query("INSERT INTO tle (satelliteid, tle) select id, '1 43700U 18090A 24108.89503844 .00000137 00000-0 00000-0 0 9999
2 43700 0.0157 247.0647 0001348 170.1236 137.3539 1.00275203 19758' from satellite where name = 'QO-100'");
$this->db->query("INSERT INTO tle (satelliteid, tle) select id, '1 27607U 02058C 24108.20375939 .00004950 00000+0 68675-3 0 9992
2 27607 64.5563 48.9470 0031938 141.6898 218.6483 14.78629101147515' from satellite where name = 'SO-50'");
}
}
public function down()
{
$this->dbforge->drop_table('tle');
}
}

View File

@@ -11,6 +11,16 @@ class Satellite_model extends CI_Model {
return $this->db->query($sql)->result();
}
function get_all_satellites_with_tle() {
$sql = "select satellite.id, satellite.name as satname, tle.tle
from satellite
join tle on satellite.id = tle.satelliteid
order by satellite.name
";
return $this->db->query($sql)->result();
}
function delete($id) {
// Clean ID
$clean_id = $this->security->xss_clean($id);
@@ -102,19 +112,27 @@ class Satellite_model extends CI_Model {
return $query->result();
}
function array_group_by($flds, $arr) {
$groups = array();
foreach ($arr as $rec) {
$keys = array_map(function($f) use($rec) { return $rec[$f]; }, $flds);
$k = implode('@', $keys);
if (isset($groups[$k])) {
$groups[$k][] = $rec;
} else {
$groups[$k] = array($rec);
}
}
return $groups;
}
function array_group_by($flds, $arr) {
$groups = array();
foreach ($arr as $rec) {
$keys = array_map(function($f) use($rec) { return $rec[$f]; }, $flds);
$k = implode('@', $keys);
if (isset($groups[$k])) {
$groups[$k][] = $rec;
} else {
$groups[$k] = array($rec);
}
}
return $groups;
}
function get_tle($sat) {
$this->db->select('satellite.name AS satellite, tle.tle');
$this->db->join('tle', 'satellite.id = tle.satelliteid');
$this->db->where('name', $sat);
$query = $this->db->get('satellite');
return $query->row();
}
}

View File

@@ -251,6 +251,12 @@
<li><a class="dropdown-item" href="<?php echo site_url('bandmap/list'); ?>" title="Bandmap"><i class="fa fa-id-card"></i> <?= __("Bandmap"); ?></a></li>
<div class="dropdown-divider"></div>
<li><a class="dropdown-item" href="<?php echo site_url('sattimers'); ?>" title="SAT Timers"><i class="fas fa-satellite"></i> <?= __("SAT Timers"); ?></a></li>
<?php if (ENVIRONMENT == "development") { ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('satellite/flightpath'); ?>" title="Manage Satellites"><i class="fas fa-satellite"></i> <?= __("Satellite Flightpath"); ?> <span class="badge text-bg-danger">Beta</span></a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('satellite/pass'); ?>" title="Search for satellite passes"><i class="fas fa-satellite"></i> <?= __("Satellite Pass"); ?> <span class="badge text-bg-danger">Beta</span></a>
<?php } ?>
</ul>
</li>
<?php } ?>

View File

@@ -0,0 +1,49 @@
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8.1/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.0/dist/fetch.umd.min.js"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script>
const homelat = "<?php echo $latlng[0]; ?>";
const homelon = "<?php echo $latlng[1]; ?>";
var icon_home_url = "<?php echo base_url();?>assets/images/dot.png";
</script>
<style>
.footprint--LEO {
fill: rgba(255, 0, 0, 0.5);
stroke: rgba(255, 0, 0, 0.5);
}
.footprint--MEO {
fill: rgba(0, 255, 0, 0.5);
stroke: rgba(0, 255, 0, 0.5);
}
.footprint--GEO {
fill: rgba(0, 0, 255, 0.5);
stroke: rgba(0, 0, 255, 0.5);
}
</style>
<div class="container">
<br>
<h2><?php echo $page_title; ?></h2>
<form class="d-flex align-items-center">
<label class="my-1 me-2" id="satslabel" for="distplot_sats"><?= __("Satellite"); ?></label>
<select class="form-select my-1 me-sm-2 w-auto" id="sats">
<?php foreach($satellites as $sat) {
echo '<option value="' . $sat->satname . '"' . '>' . $sat->satname . '</option>'."\n";
} ?>
</select>
<button id="plot" type="button" name="plot" class="btn btn-primary me-1 ld-ext-right ld-ext-right-plot" onclick="plot_sat()"><?= __("Plot"); ?><div class="ld ld-ring ld-spin"></div></button>
</form>
</div>
<div id="satcontainer">
<div id="sat_map" class="map-leaflet" style="width: 100%; height: 85vh"></div>
</div>

View File

@@ -0,0 +1,555 @@
<div class="container container-fluid">
<h2><?= __("Satellite passes"); ?></h2>
<div class="card">
<div class="card-body">
<form class="d-flex align-items-center">
<div class="row">
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" id="satslabel" for="satslist">Min Satellite Elevation</label>
<input class="my-1 me-sm-2 w-auto form-control form-control-sm" id="minelevation" type="number" name="minelevation" value="0" />
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="minazimuth">Min Azimut</label>
<select class="my-1 me-sm-2 w-auto form-select" id="minazimuth" name="minazimuth">
<?php for ($i = 0; $i <= 350; $i += 10): ?>
<option value="<?= $i ?>" <?= $i === 0 ? 'selected' : '' ?>><?= $i ?> &deg;</option>
<?php endfor; ?>
</select>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="maxazimuth">Max Azimut</label>
<select class="my-1 me-sm-2 w-auto form-select" id="maxazimuth" name="maxazimuth">
<?php for ($i = 10; $i <= 360; $i += 10): ?>
<option value="<?= $i ?>" <?= $i === 360 ? 'selected' : '' ?>><?= $i ?> &deg;</option>
<?php endfor; ?>
</select>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="yourgrid">Gridsquare</label>
<input class="my-1 me-sm-2 w-auto form-control form-control-sm" id="yourgrid" type="text" name="gridsquare" value="<?php echo $activegrid; ?>"/>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="altitude">Altitude (meters)</label>
<input class="my-1 me-sm-2 w-auto form-control form-control-sm" id="altitude" type="number" name="altitude" value="0" />
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="timezone">Timezone</label>
<select class="my-1 me-sm-2 w-auto form-select" id="timezone" name="timezone">
<option>Africa/Abidjan</option>
<option>Africa/Accra</option>
<option>Africa/Addis_Ababa</option>
<option>Africa/Algiers</option>
<option>Africa/Asmara</option>
<option>Africa/Bamako</option>
<option>Africa/Bangui</option>
<option>Africa/Banjul</option>
<option>Africa/Bissau</option>
<option>Africa/Blantyre</option>
<option>Africa/Brazzaville</option>
<option>Africa/Bujumbura</option>
<option>Africa/Cairo</option>
<option>Africa/Casablanca</option>
<option>Africa/Ceuta</option>
<option>Africa/Conakry</option>
<option>Africa/Dakar</option>
<option>Africa/Dar_es_Salaam</option>
<option>Africa/Djibouti</option>
<option>Africa/Douala</option>
<option>Africa/El_Aaiun</option>
<option>Africa/Freetown</option>
<option>Africa/Gaborone</option>
<option>Africa/Harare</option>
<option>Africa/Johannesburg</option>
<option>Africa/Juba</option>
<option>Africa/Kampala</option>
<option>Africa/Khartoum</option>
<option>Africa/Kigali</option>
<option>Africa/Kinshasa</option>
<option>Africa/Lagos</option>
<option>Africa/Libreville</option>
<option>Africa/Lome</option>
<option>Africa/Luanda</option>
<option>Africa/Lubumbashi</option>
<option>Africa/Lusaka</option>
<option>Africa/Malabo</option>
<option>Africa/Maputo</option>
<option>Africa/Maseru</option>
<option>Africa/Mbabane</option>
<option>Africa/Mogadishu</option>
<option>Africa/Monrovia</option>
<option>Africa/Nairobi</option>
<option>Africa/Ndjamena</option>
<option>Africa/Niamey</option>
<option>Africa/Nouakchott</option>
<option>Africa/Ouagadougou</option>
<option>Africa/Porto-Novo</option>
<option>Africa/Sao_Tome</option>
<option>Africa/Tripoli</option>
<option>Africa/Tunis</option>
<option>Africa/Windhoek</option>
<option>America/Adak</option>
<option>America/Anchorage</option>
<option>America/Anguilla</option>
<option>America/Antigua</option>
<option>America/Araguaina</option>
<option>America/Argentina/Buenos_Aires</option>
<option>America/Argentina/Catamarca</option>
<option>America/Argentina/Cordoba</option>
<option>America/Argentina/Jujuy</option>
<option>America/Argentina/La_Rioja</option>
<option>America/Argentina/Mendoza</option>
<option>America/Argentina/Rio_Gallegos</option>
<option>America/Argentina/Salta</option>
<option>America/Argentina/San_Juan</option>
<option>America/Argentina/San_Luis</option>
<option>America/Argentina/Tucuman</option>
<option>America/Argentina/Ushuaia</option>
<option>America/Aruba</option>
<option>America/Asuncion</option>
<option>America/Atikokan</option>
<option>America/Bahia</option>
<option>America/Bahia_Banderas</option>
<option>America/Barbados</option>
<option>America/Belem</option>
<option>America/Belize</option>
<option>America/Blanc-Sablon</option>
<option>America/Boa_Vista</option>
<option>America/Bogota</option>
<option>America/Boise</option>
<option>America/Cambridge_Bay</option>
<option>America/Campo_Grande</option>
<option>America/Cancun</option>
<option>America/Caracas</option>
<option>America/Cayenne</option>
<option>America/Cayman</option>
<option>America/Chicago</option>
<option>America/Chihuahua</option>
<option>America/Ciudad_Juarez</option>
<option>America/Costa_Rica</option>
<option>America/Creston</option>
<option>America/Cuiaba</option>
<option>America/Curacao</option>
<option>America/Danmarkshavn</option>
<option>America/Dawson</option>
<option>America/Dawson_Creek</option>
<option>America/Denver</option>
<option>America/Detroit</option>
<option>America/Dominica</option>
<option>America/Edmonton</option>
<option>America/Eirunepe</option>
<option>America/El_Salvador</option>
<option>America/Fort_Nelson</option>
<option>America/Fortaleza</option>
<option>America/Glace_Bay</option>
<option>America/Goose_Bay</option>
<option>America/Grand_Turk</option>
<option>America/Grenada</option>
<option>America/Guadeloupe</option>
<option>America/Guatemala</option>
<option>America/Guayaquil</option>
<option>America/Guyana</option>
<option>America/Halifax</option>
<option>America/Havana</option>
<option>America/Hermosillo</option>
<option>America/Indiana/Indianapolis</option>
<option>America/Indiana/Knox</option>
<option>America/Indiana/Marengo</option>
<option>America/Indiana/Petersburg</option>
<option>America/Indiana/Tell_City</option>
<option>America/Indiana/Vevay</option>
<option>America/Indiana/Vincennes</option>
<option>America/Indiana/Winamac</option>
<option>America/Inuvik</option>
<option>America/Iqaluit</option>
<option>America/Jamaica</option>
<option>America/Juneau</option>
<option>America/Kentucky/Louisville</option>
<option>America/Kentucky/Monticello</option>
<option>America/Kralendijk</option>
<option>America/La_Paz</option>
<option>America/Lima</option>
<option>America/Los_Angeles</option>
<option>America/Lower_Princes</option>
<option>America/Maceio</option>
<option>America/Managua</option>
<option>America/Manaus</option>
<option>America/Marigot</option>
<option>America/Martinique</option>
<option>America/Matamoros</option>
<option>America/Mazatlan</option>
<option>America/Menominee</option>
<option>America/Merida</option>
<option>America/Metlakatla</option>
<option>America/Mexico_City</option>
<option>America/Miquelon</option>
<option>America/Moncton</option>
<option>America/Monterrey</option>
<option>America/Montevideo</option>
<option>America/Montserrat</option>
<option>America/Nassau</option>
<option>America/New_York</option>
<option>America/Nome</option>
<option>America/Noronha</option>
<option>America/North_Dakota/Beulah</option>
<option>America/North_Dakota/Center</option>
<option>America/North_Dakota/New_Salem</option>
<option>America/Nuuk</option>
<option>America/Ojinaga</option>
<option>America/Panama</option>
<option>America/Paramaribo</option>
<option>America/Phoenix</option>
<option>America/Port-au-Prince</option>
<option>America/Port_of_Spain</option>
<option>America/Porto_Velho</option>
<option>America/Puerto_Rico</option>
<option>America/Punta_Arenas</option>
<option>America/Rankin_Inlet</option>
<option>America/Recife</option>
<option>America/Regina</option>
<option>America/Resolute</option>
<option>America/Rio_Branco</option>
<option>America/Santarem</option>
<option>America/Santiago</option>
<option>America/Santo_Domingo</option>
<option>America/Sao_Paulo</option>
<option>America/Scoresbysund</option>
<option>America/Sitka</option>
<option>America/St_Barthelemy</option>
<option>America/St_Johns</option>
<option>America/St_Kitts</option>
<option>America/St_Lucia</option>
<option>America/St_Thomas</option>
<option>America/St_Vincent</option>
<option>America/Swift_Current</option>
<option>America/Tegucigalpa</option>
<option>America/Thule</option>
<option>America/Tijuana</option>
<option>America/Toronto</option>
<option>America/Tortola</option>
<option>America/Vancouver</option>
<option>America/Whitehorse</option>
<option>America/Winnipeg</option>
<option>America/Yakutat</option>
<option>Antarctica/Casey</option>
<option>Antarctica/Davis</option>
<option>Antarctica/DumontDUrville</option>
<option>Antarctica/Macquarie</option>
<option>Antarctica/Mawson</option>
<option>Antarctica/McMurdo</option>
<option>Antarctica/Palmer</option>
<option>Antarctica/Rothera</option>
<option>Antarctica/Syowa</option>
<option>Antarctica/Troll</option>
<option>Antarctica/Vostok</option>
<option>Arctic/Longyearbyen</option>
<option>Asia/Aden</option>
<option>Asia/Almaty</option>
<option>Asia/Amman</option>
<option>Asia/Anadyr</option>
<option>Asia/Aqtau</option>
<option>Asia/Aqtobe</option>
<option>Asia/Ashgabat</option>
<option>Asia/Atyrau</option>
<option>Asia/Baghdad</option>
<option>Asia/Bahrain</option>
<option>Asia/Baku</option>
<option>Asia/Bangkok</option>
<option>Asia/Barnaul</option>
<option>Asia/Beirut</option>
<option>Asia/Bishkek</option>
<option>Asia/Brunei</option>
<option>Asia/Chita</option>
<option>Asia/Choibalsan</option>
<option>Asia/Colombo</option>
<option>Asia/Damascus</option>
<option>Asia/Dhaka</option>
<option>Asia/Dili</option>
<option>Asia/Dubai</option>
<option>Asia/Dushanbe</option>
<option>Asia/Famagusta</option>
<option>Asia/Gaza</option>
<option>Asia/Hebron</option>
<option>Asia/Ho_Chi_Minh</option>
<option>Asia/Hong_Kong</option>
<option>Asia/Hovd</option>
<option>Asia/Irkutsk</option>
<option>Asia/Jakarta</option>
<option>Asia/Jayapura</option>
<option>Asia/Jerusalem</option>
<option>Asia/Kabul</option>
<option>Asia/Kamchatka</option>
<option>Asia/Karachi</option>
<option>Asia/Kathmandu</option>
<option>Asia/Khandyga</option>
<option>Asia/Kolkata</option>
<option>Asia/Krasnoyarsk</option>
<option>Asia/Kuala_Lumpur</option>
<option>Asia/Kuching</option>
<option>Asia/Kuwait</option>
<option>Asia/Macau</option>
<option>Asia/Magadan</option>
<option>Asia/Makassar</option>
<option>Asia/Manila</option>
<option>Asia/Muscat</option>
<option>Asia/Nicosia</option>
<option>Asia/Novokuznetsk</option>
<option>Asia/Novosibirsk</option>
<option>Asia/Omsk</option>
<option>Asia/Oral</option>
<option>Asia/Phnom_Penh</option>
<option>Asia/Pontianak</option>
<option>Asia/Pyongyang</option>
<option>Asia/Qatar</option>
<option>Asia/Qostanay</option>
<option>Asia/Qyzylorda</option>
<option>Asia/Riyadh</option>
<option>Asia/Sakhalin</option>
<option>Asia/Samarkand</option>
<option>Asia/Seoul</option>
<option>Asia/Shanghai</option>
<option>Asia/Singapore</option>
<option>Asia/Srednekolymsk</option>
<option>Asia/Taipei</option>
<option>Asia/Tashkent</option>
<option>Asia/Tbilisi</option>
<option>Asia/Tehran</option>
<option>Asia/Thimphu</option>
<option>Asia/Tokyo</option>
<option>Asia/Tomsk</option>
<option>Asia/Ulaanbaatar</option>
<option>Asia/Urumqi</option>
<option>Asia/Ust-Nera</option>
<option>Asia/Vientiane</option>
<option>Asia/Vladivostok</option>
<option>Asia/Yakutsk</option>
<option>Asia/Yangon</option>
<option>Asia/Yekaterinburg</option>
<option>Asia/Yerevan</option>
<option>Atlantic/Azores</option>
<option>Atlantic/Bermuda</option>
<option>Atlantic/Canary</option>
<option>Atlantic/Cape_Verde</option>
<option>Atlantic/Faroe</option>
<option>Atlantic/Madeira</option>
<option>Atlantic/Reykjavik</option>
<option>Atlantic/South_Georgia</option>
<option>Atlantic/St_Helena</option>
<option>Atlantic/Stanley</option>
<option>Australia/Adelaide</option>
<option>Australia/Brisbane</option>
<option>Australia/Broken_Hill</option>
<option>Australia/Darwin</option>
<option>Australia/Eucla</option>
<option>Australia/Hobart</option>
<option>Australia/Lindeman</option>
<option>Australia/Lord_Howe</option>
<option>Australia/Melbourne</option>
<option>Australia/Perth</option>
<option>Australia/Sydney</option>
<option>Europe/Amsterdam</option>
<option>Europe/Andorra</option>
<option>Europe/Astrakhan</option>
<option>Europe/Athens</option>
<option>Europe/Belgrade</option>
<option>Europe/Berlin</option>
<option>Europe/Bratislava</option>
<option>Europe/Brussels</option>
<option>Europe/Bucharest</option>
<option>Europe/Budapest</option>
<option>Europe/Busingen</option>
<option>Europe/Chisinau</option>
<option>Europe/Copenhagen</option>
<option>Europe/Dublin</option>
<option>Europe/Gibraltar</option>
<option>Europe/Guernsey</option>
<option>Europe/Helsinki</option>
<option>Europe/Isle_of_Man</option>
<option>Europe/Istanbul</option>
<option>Europe/Jersey</option>
<option>Europe/Kaliningrad</option>
<option>Europe/Kirov</option>
<option>Europe/Kyiv</option>
<option>Europe/Lisbon</option>
<option>Europe/Ljubljana</option>
<option>Europe/London</option>
<option>Europe/Luxembourg</option>
<option>Europe/Madrid</option>
<option>Europe/Malta</option>
<option>Europe/Mariehamn</option>
<option>Europe/Minsk</option>
<option>Europe/Monaco</option>
<option>Europe/Moscow</option>
<option>Europe/Oslo</option>
<option>Europe/Paris</option>
<option>Europe/Podgorica</option>
<option>Europe/Prague</option>
<option>Europe/Riga</option>
<option>Europe/Rome</option>
<option>Europe/Samara</option>
<option>Europe/San_Marino</option>
<option>Europe/Sarajevo</option>
<option>Europe/Saratov</option>
<option>Europe/Simferopol</option>
<option>Europe/Skopje</option>
<option>Europe/Sofia</option>
<option>Europe/Stockholm</option>
<option>Europe/Tallinn</option>
<option>Europe/Tirane</option>
<option>Europe/Ulyanovsk</option>
<option>Europe/Vaduz</option>
<option>Europe/Vatican</option>
<option>Europe/Vienna</option>
<option>Europe/Vilnius</option>
<option>Europe/Volgograd</option>
<option>Europe/Warsaw</option>
<option>Europe/Zagreb</option>
<option>Europe/Zurich</option>
<option>Indian/Antananarivo</option>
<option>Indian/Chagos</option>
<option>Indian/Christmas</option>
<option>Indian/Cocos</option>
<option>Indian/Comoro</option>
<option>Indian/Kerguelen</option>
<option>Indian/Mahe</option>
<option>Indian/Maldives</option>
<option>Indian/Mauritius</option>
<option>Indian/Mayotte</option>
<option>Indian/Reunion</option>
<option>Pacific/Apia</option>
<option>Pacific/Auckland</option>
<option>Pacific/Bougainville</option>
<option>Pacific/Chatham</option>
<option>Pacific/Chuuk</option>
<option>Pacific/Easter</option>
<option>Pacific/Efate</option>
<option>Pacific/Fakaofo</option>
<option>Pacific/Fiji</option>
<option>Pacific/Funafuti</option>
<option>Pacific/Galapagos</option>
<option>Pacific/Gambier</option>
<option>Pacific/Guadalcanal</option>
<option>Pacific/Guam</option>
<option>Pacific/Honolulu</option>
<option>Pacific/Kanton</option>
<option>Pacific/Kiritimati</option>
<option>Pacific/Kosrae</option>
<option>Pacific/Kwajalein</option>
<option>Pacific/Majuro</option>
<option>Pacific/Marquesas</option>
<option>Pacific/Midway</option>
<option>Pacific/Nauru</option>
<option>Pacific/Niue</option>
<option>Pacific/Norfolk</option>
<option>Pacific/Noumea</option>
<option>Pacific/Pago_Pago</option>
<option>Pacific/Palau</option>
<option>Pacific/Pitcairn</option>
<option>Pacific/Pohnpei</option>
<option>Pacific/Port_Moresby</option>
<option>Pacific/Rarotonga</option>
<option>Pacific/Saipan</option>
<option>Pacific/Tahiti</option>
<option>Pacific/Tarawa</option>
<option>Pacific/Tongatapu</option>
<option>Pacific/Wake</option>
<option>Pacific/Wallis</option>
<option selected>UTC</option>
<option ">Etc/GMT-12</option><option ">Etc/GMT-11</option>
<option ">Etc/GMT-10</option><option ">Etc/GMT-9</option>
<option ">Etc/GMT-8</option><option ">Etc/GMT-7</option>
<option ">Etc/GMT-6</option><option ">Etc/GMT-5</option>
<option ">Etc/GMT-4</option><option ">Etc/GMT-3</option>
<option ">Etc/GMT-2</option><option ">Etc/GMT-1</option>
<option ">Etc/GMT+0</option><option ">Etc/GMT+1</option>
<option ">Etc/GMT+2</option><option ">Etc/GMT+3</option>
<option ">Etc/GMT+4</option><option ">Etc/GMT+5</option>
<option ">Etc/GMT+6</option><option ">Etc/GMT+7</option>
<option ">Etc/GMT+8</option><option ">Etc/GMT+9</option>
<option ">Etc/GMT+10</option><option ">Etc/GMT+11</option>
<option ">Etc/GMT+12</option>
</select>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="date">Date</label>
<select class="my-1 me-sm-2 w-auto form-select" id="date" name="start">
<option selected value="0">Today</option>
</select>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="mintime">Min time</label>
<select class="my-1 me-sm-2 w-auto form-select" id="mintime" name="mintime">
<option value="0">0:00</option>
<option value="1">1:00</option>
<option value="2">2:00</option>
<option value="3">3:00</option>
<option value="4">4:00</option>
<option value="5">5:00</option>
<option value="6">6:00</option>
<option value="7">7:00</option>
<option selected value="8">8:00</option>
<option value="9">9:00</option>
<option value="10">10:00</option>
<option value="11">11:00</option>
<option value="12">12:00</option>
<option value="13">13:00</option>
<option value="14">14:00</option>
<option value="15">15:00</option>
<option value="16">16:00</option>
<option value="17">17:00</option>
<option value="18">18:00</option>
<option value="19">19:00</option>
<option value="20">20:00</option>
<option value="21">21:00</option>
<option value="22">22:00</option>
<option value="23">23:00</option>
<option value="24">24:00</option>
</select>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" for="maxtime">Max time</label>
<select class="my-1 me-sm-2 w-auto form-select" id="maxtime" name="maxtime">
<option value="0">0:00</option>
<option value="1">1:00</option>
<option value="2">2:00</option>
<option value="3">3:00</option>
<option value="4">4:00</option>
<option value="5">5:00</option>
<option value="6">6:00</option>
<option value="7">7:00</option>
<option value="8">8:00</option>
<option value="9">9:00</option>
<option value="10">10:00</option>
<option value="11">11:00</option>
<option value="12">12:00</option>
<option value="13">13:00</option>
<option value="14">14:00</option>
<option value="15">15:00</option>
<option value="16">16:00</option>
<option value="17">17:00</option>
<option value="18">18:00</option>
<option value="19">19:00</option>
<option value="20">20:00</option>
<option value="21">21:00</option>
<option selected value="22">22:00</option>
<option value="23">23:00</option>
<option value="24">24:00</option>
</select>
</div>
<div class="mb-3 w-auto">
<label class="my-1 me-sm-2 w-auto" id="satslabel" for="satlist">Satellites</label>
<select class="my-1 me-sm-2 w-auto form-select" id="satlist">
<?php foreach($satellites as $sat) {
echo '<option value="' . $sat->satname . '"' . '>' . $sat->satname . '</option>'."\n";
} ?>
</select>
</div>
</form>
</div>
<button id="plot" type="button" name="searchpass" class="btn-sm btn btn-primary me-1 ld-ext-right ld-ext-right-plot" onclick="searchpasses()"><?= __("Load predictions"); ?><div class="ld ld-ring ld-spin"></div></button>
</div>
<div id="resultpasses">
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,47 @@
<?php
if ($filtered) {
echo '<table style="width:100%" class="table-sm table table-bordered table-hover table-striped table-condensed text-center">
<tr id="toptable">
<th>Satellite</th>
<th>AOS Time</th>
<th>Duration</th>
<th>AOS az</th>
<th>AOS el</th>
<th>Max El</th>
<th>LOS Time</th>
<th>LOS Az</th>
<th>LOS El</th>
</tr>';
foreach ($filtered as $pass) {
echo '<tr>';
echo '<td>' . $pass->satname . '</td>';
echo '<td>' . Predict_Time::daynum2readable($pass->visible_aos, $zone, $format) . '</td>';
echo '<td>' . returntimediff(Predict_Time::daynum2readable($pass->visible_aos, $zone, $format), Predict_Time::daynum2readable($pass->visible_los, $zone, $format)) . '</td>';
echo '<td>' . round($pass->visible_aos_az) . ' (' . azDegreesToDirection($pass->visible_aos_az) . ')</td>';
echo '<td>' . round($pass->visible_aos_el) . '</td>';
echo '<td>' . round($pass->max_el) . '</td>';
echo '<td>' . Predict_Time::daynum2readable($pass->visible_los, $zone, $format) . '</td>';
echo '<td>' . round($pass->visible_los_az) . ' (' . azDegreesToDirection($pass->visible_los_az) . ')</td>';
echo '<td>' . round($pass->visible_los_el) . '</td>';
echo '</tr>';
}
echo '</table>';
}
function returntimediff($start, $end) {
$datetime1 = DateTime::createFromFormat('m-d-Y H:i:s', $end);
$datetime2 = DateTime::createFromFormat('m-d-Y H:i:s', $start);
$interval = $datetime1->diff($datetime2);
$minutesDifference = ($interval->days * 24 * 60) + ($interval->h * 60) + $interval->i + ($interval->s / 60);
return round($minutesDifference) . ' min';
}
function azDegreesToDirection($az = 0) {
$i = floor($az / 22.5);
$m = (22.5 * (2 * $i + 1)) / 2;
$i = ($az >= $m) ? $i + 1 : $i;
return trim(substr('N NNENE ENEE ESESE SSES SSWSW WSWW WNWNW NNWN ', $i * 3, 3));
}

BIN
assets/icons/saticon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,486 @@
var satmarker;
var icon_dot_url = base_url + "assets/icons/saticon.png";
var saticon = L.icon({ iconUrl: icon_dot_url, iconSize: [30, 30] });
var homeicon = L.icon({ iconUrl: icon_home_url, iconSize: [15, 15] });
var sats = (function (L, d3, satelliteJs) {
var RADIANS = Math.PI / 180;
var DEGREES = 180 / Math.PI;
var R_EARTH = 6378.137; // equatorial radius (km)
/* =============================================== */
/* =============== CLOCK ========================= */
/* =============================================== */
/**
* Factory function for keeping track of elapsed time and rates.
*/
function Clock() {
this._rate = 60; // 1ms elapsed : 60sec simulated
this._date = d3.now();
this._elapsed = 0;
};
Clock.prototype.date = function (timeInMs) {
if (!arguments.length) return this._date + (this._elapsed * this._rate);
this._date = timeInMs;
return this;
};
Clock.prototype.elapsed = function (ms) {
if (!arguments.length) return this._date - d3.now(); // calculates elapsed
this._elapsed = ms;
return this;
};
Clock.prototype.rate = function (secondsPerMsElapsed) {
if (!arguments.length) return this._rate;
this._rate = secondsPerMsElapsed;
return this;
};
/* ==================================================== */
/* =============== CONVERSION ========================= */
/* ==================================================== */
function satrecToFeature(satrec, date, props) { // DJ7NT: This is never called
var properties = props || {};
var positionAndVelocity = satelliteJs.propagate(satrec, date);
var gmst = satelliteJs.gstime(date);
var positionGd = satelliteJs.eciToGeodetic(positionAndVelocity.position, gmst);
properties.height = positionGd.height;
return {
type: "FeatureCollection",
"features": [ {
type: 'Feature',
properties: properties,
geometry: {
type: 'Point',
coordinates: [
positionGd.longitude * DEGREES,
positionGd.latitude * DEGREES
]
}
},
{
type: 'Feature',
properties: {infoText: 'blabla'},
geometry: {
type: 'Point',
coordinates: [
positionGd.longitude * DEGREES,
positionGd.latitude * DEGREES
]
}
}]
};
};
/* ==================================================== */
/* =============== TLE ================================ */
/* ==================================================== */
/**
* Factory function for working with TLE.
*/
function TLE() {
this._properties;
this._date;
};
TLE.prototype._lines = function (arry) {
return arry.slice(0, 2);
};
TLE.prototype.satrecs = function (tles) {
return tles.map(function (d) {
return satelliteJs.twoline2satrec.apply(null, this._lines(d));
});
};
TLE.prototype.features = function (tles) {
var date = this._date || d3.now();
return tles.map(function (d) {
var satrec = satelliteJs.twoline2satrec.apply(null, this._lines(d));
return satrecToFeature(satrec, date, this._properties(d));
});
};
TLE.prototype.lines = function (func) {
if (!arguments.length) return this._lines;
this._lines = func;
return this;
};
TLE.prototype.properties = function (func) {
if (!arguments.length) return this._properties;
this._properties = func;
return this;
};
TLE.prototype.date = function (ms) {
if (!arguments.length) return this._date;
this._date = ms;
return this;
};
/* ==================================================== */
/* =============== PARSE ============================== */
/* ==================================================== */
/**
* Parses text file string of tle into groups.
* @return {string[][]} Like [['tle line 1', 'tle line 2'], ...]
*/
function parseTle(tleString) {
// remove last newline so that we can properly split all the lines
var lines = tleString.replace(/\r?\n$/g, '').split(/\r?\n/);
return lines.reduce(function (acc, cur, index) {
if (index % 2 === 0) acc.push([]);
acc[acc.length - 1].push(cur);
return acc;
}, []);
};
/* ==================================================== */
/* =============== SATELLITE ========================== */
/* ==================================================== */
/**
* Satellite factory function that wraps satellitejs functionality
* and can compute footprints based on TLE and date
*
* @param {string[][]} tle two-line element
* @param {Date} date date to propagate with TLE
*/
function Satellite(tle, date) {
this._satrec = satelliteJs.twoline2satrec(tle[0], tle[1]);
this._satNum = this._satrec.satnum; // NORAD Catalog Number
this._altitude; // km
this._position = {
lat: null,
lng: null
};
this._halfAngle; // degrees
this._date;
this._gmst;
this.setDate(date);
this.update();
this._orbitType = this.orbitTypeFromAlt(this._altitude); // LEO, MEO, or GEO
};
/**
* Updates satellite position and altitude based on current TLE and date
*/
Satellite.prototype.update = function () {
try {
var positionAndVelocity = satelliteJs.propagate(this._satrec, this._date);
var positionGd = satelliteJs.eciToGeodetic(positionAndVelocity.position, this._gmst);
this._position = {
lat: positionGd.latitude * DEGREES,
lng: positionGd.longitude * DEGREES
};
this._altitude = positionGd.height;
satmarker.setLatLng(this._position);
} catch (e) {
// Malicious // non-calcable SAT Found
} finally {
return this;
}
};
/**
* @returns {GeoJSON.Polygon} GeoJSON describing the satellite's current footprint on the Earth
*/
Satellite.prototype.getFootprint = function () {
var theta = this._halfAngle * RADIANS;
coreAngle = this._coreAngle(theta, this._altitude, R_EARTH) * DEGREES;
return d3.geoCircle()
.center([this._position.lng, this._position.lat])
.radius(coreAngle)();
};
Satellite.prototype.getLocation = function () {
return d3.geoCircle()
.center([this._position.lng, this._position.lat])
.radius(1)();
};
/**
* A conical satellite with half angle casts a circle on the Earth. Find the angle
* from the center of the earth to the radius of this circle
* @param {number} theta: Satellite half angle in radians
* @param {number} altitude Satellite altitude
* @param {number} r Earth radius
* @returns {number} core angle in radians
*/
Satellite.prototype._coreAngle = function (theta, altitude, r) {
// if FOV is larger than Earth, assume it goes to the tangential point
// if (Math.sin(theta) != r / (altitude + r)) {
return Math.acos(r / (r + altitude));
// }
// return Math.abs(Math.asin((r + altitude) * Math.sin(theta) / r)) - theta;
};
Satellite.prototype.halfAngle = function (halfAngle) {
if (!arguments.length) return this._halfAngle;
this._halfAngle = halfAngle;
return this;
};
Satellite.prototype.satNum = function (satNum) {
if (!arguments.length) return this._satNum;
this._satNum = satNum;
return this;
};
Satellite.prototype.altitude = function (altitude) {
if (!arguments.length) return this._altitude;
this._altitude = altitude;
return this;
};
Satellite.prototype.position = function (position) {
if (!arguments.length) return this._position;
this._position = position;
return this;
};
Satellite.prototype.getOrbitType = function () {
return this._orbitType;
};
/**
* sets both the date and the Greenwich Mean Sidereal Time
* @param {Date} date
*/
Satellite.prototype.setDate = function (date) {
this._date = date;
this._gmst = satelliteJs.gstime(date);
return this;
};
/**
* Maps an altitude to a type of satellite
* @param {number} altitude (in KM)
* @returns {'LEO' | 'MEO' | 'GEO'}
*/
Satellite.prototype.orbitTypeFromAlt = function (altitude) {
console.log(altitude);
this._altitude = altitude || this._altitude;
return this._altitude < 1200 ? 'LEO' : this._altitude > 22000 ? 'GEO' : 'MEO';
};
/* =============================================== */
/* =============== LEAFLET MAP =================== */
/* =============================================== */
// Approximate date the tle data was aquired from https://www.space-track.org/#recent
// var TLE_DATA_DATE = new Date(2024, 04, 18).getTime();
var TLE_DATA_DATE = Date.now();
var leafletMap;
var attributionControl;
var activeClock;
var sats;
var svgLayer;
function projectPointCurry(map) {
return function (x, y) {
const point = map.latLngToLayerPoint(L.latLng(y, x));
this.stream.point(point.x, point.y);
}
};
function init(satellite) {
svgLayer = L.svg();
leafletMap = L.map('sat_map', {
zoom: 3,
center: [20, 0],
// attributionControl: false,
layers: [
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
// noWrap: false,
}),
svgLayer
]
});
satmarker = L.marker(
[0, 0], {
icon: saticon,
title: satellite,
zIndex: 1000,
}
).addTo(leafletMap);
L.marker(
[homelat, homelon], {
icon: homeicon,
title: 'Home',
zIndex: 1000,
}
).addTo(leafletMap);
/*Legend specific*/
var legend = L.control({ position: "topright" });
legend.onAdd = function(map) {
var div = L.DomUtil.create("div", "legend");
div.innerHTML += "<h4>Satellite Orbit</h4>";
div.innerHTML += "<i style='background: rgba(255, 0, 0, 0.5)'></i><span>LEO</span><br>";
div.innerHTML += "<i style='background: rgba(0, 255, 0, 0.5)'></i><span>MEO</span><br>";
div.innerHTML += "<i style='background: rgba(0, 0, 255, 0.5)'></i><span>GEO</span><br>";
return div;
};
legend.addTo(leafletMap);
attributionControl = L.control.attribution({
prefix: ''
}).addTo(leafletMap);
var transform = d3.geoTransform({
point: projectPointCurry(leafletMap)
});
path = d3.geoPath()
.projection(transform)
.pointRadius(2.5);
};
function updateSats(date) {
sats.forEach(function (sat) {
sat.setDate(date).update();
});
return sats
};
/**
* Create satellite objects for each record in the TLEs and begin animation
* @param {string[][]} parsedTles
*/
function initSats(parsedTles) {
activeClock = new Clock()
.rate(1)
.date(TLE_DATA_DATE);
sats = parsedTles.map(function (tle) {
var sat = new Satellite(tle, new Date());
sat.halfAngle(30);
// sat.halfAngle(sat.getOrbitType() === 'LEO' ? Math.random() * (30 - 15) + 15 : Math.random() * 4 + 1);
return sat;
});
leafletMap.on('zoom', draw);
window.requestAnimationFrame(animateSats);
return sats;
};
function invertProjection(projection) {
return function (x, y) {
const point = projection.invert([x, y]);
this.stream.point(point[0], point[1]);
};
}
function clipMercator(geoJson) {
const mercator = d3.geoMercator();
const inverseMercator = d3.geoTransform({
point: invertProjection(mercator)
});
// D3 geoProject handles Mercator clipping
const newJson = d3.geoProject(geoJson, mercator);
return d3.geoProject(newJson, inverseMercator);
}
function draw() {
var transform = d3.geoTransform({
point: projectPointCurry(leafletMap)
});
var geoPath = d3.geoPath()
.projection(transform);
d3.select(svgLayer._container)
.selectAll('.footprint')
.data(sats, function (sat) {
return sat._satNum; // DJ7NT: This is the Number of the SAT
})
.join(
function (enter) {
return enter.append('path').attr('class', function (sat) {
return 'footprint footprint--' + sat.getOrbitType();
});
},
function (update) {
return update;
},
function (exit) {
return exit.remove();
}
).attr('d', function (sat) {
// return geoPath(clipMercator(sat.getLocation())); // DJ7NT: this is the "point" of the SAT
let xx= geoPath(clipMercator(sat.getFootprint()));
return xx;
});
};
function animateSats(elapsed) {
var dateInMs = activeClock.elapsed(elapsed)
.date();
var date = new Date(dateInMs);
attributionControl.setPrefix(date);
updateSats(date);
draw();
window.requestAnimationFrame(animateSats);
}
function start(data) {
init(data.satellite);
initSats(parseTle(data.tle));
}
return {
start: start
};
}(window.L, window.d3, window.satellite))
function plot_sat() {
var container = L.DomUtil.get('sat_map');
if(container != null){
container._leaflet_id = null;
container.remove();
}
amap = $('#sat_map').val();
if (amap == undefined) {
$("#satcontainer").append('<div id="sat_map" class="map-leaflet" style="width: 100%; height: 85vh"></div>');
}
$.ajax({
url: base_url + 'index.php/satellite/get_tle',
type: 'post',
data: {
sat: $("#sats").val(),
},
success: function (data) {
sats.start(data);
},
error: function (data) {
alert('Something went wrong!');
},
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
function searchpasses() {
$.ajax({
url: base_url + 'index.php/satellite/searchpasses',
type: 'post',
data: {'sat': $("#satlist").val(),
'yourgrid': $("#yourgrid").val(),
'minelevation': $("#minelevation").val(),
'minazimuth': $("#minazimuth").val(),
'maxazimuth': $("#maxazimuth").val(),
'altitude': $("#altitude").val(),
'timezone': $("#timezone").val(),
'date': $("#date").val(),
'mintime': $("#mintime").val(),
'maxtime': $("#maxtime").val(),
},
success: function (html) {
$("#resultpasses").html(html);
},
error: function(e) {
modalloading=false;
}
});
}

File diff suppressed because it is too large Load Diff

877
predict/Predict.php Normal file
View File

@@ -0,0 +1,877 @@
<?php
/*
A limited PHP port of the gpredict program, done by Bill Shupp.
Original notes and author information is below. GPL2 license.
===============================================================
Gpredict: Real-time satellite tracking and orbit prediction program
Copyright (C) 2001-2009 Alexandru Csete, OZ9AEC.
Parts are Copyright John A. Magliacane, KD2BD 1991-2003 (indicated below)
Authors: Alexandru Csete <oz9aec@gmail.com>
John A. Magliacane, KD2BD.
Comments, questions and bugreports should be submitted via
http://sourceforge.net/projects/gpredict/
More details can be found at the project home page:
http://gpredict.oz9aec.net/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, visit http://www.fsf.org/
*/
require_once realpath(__DIR__ . "/../predict/Predict/Time.php");
require_once realpath(__DIR__ . "/../predict/Predict/Math.php");
require_once realpath(__DIR__ . "/../predict/Predict/Pass.php");
require_once realpath(__DIR__ . "/../predict/Predict/PassDetail.php");
require_once realpath(__DIR__ . "/../predict/Predict/Vector.php");
require_once realpath(__DIR__ . "/../predict/Predict/Geodetic.php");
require_once realpath(__DIR__ . "/../predict/Predict/ObsSet.php");
require_once realpath(__DIR__ . "/../predict/Predict/Solar.php");
require_once realpath(__DIR__ . "/../predict/Predict/SGPSDP.php");
require_once realpath(__DIR__ . "/../predict/Predict/SGPSDP.php");
/**
* The main Predict class. Contains constants for use by other classes, as well as
* the prediction logic.
*/
class Predict
{
const de2ra = 1.74532925E-2; /* Degrees to Radians */
const pi = 3.1415926535898; /* Pi */
const pio2 = 1.5707963267949; /* Pi/2 */
const x3pio2 = 4.71238898; /* 3*Pi/2 */
const twopi = 6.2831853071796; /* 2*Pi */
const e6a = 1.0E-6;
const tothrd = 6.6666667E-1; /* 2/3 */
const xj2 = 1.0826158E-3; /* J2 Harmonic */
const xj3 = -2.53881E-6; /* J3 Harmonic */
const xj4 = -1.65597E-6; /* J4 Harmonic */
const xke = 7.43669161E-2;
const xkmper = 6.378135E3; /* Earth radius km */
const xmnpda = 1.44E3; /* Minutes per day */
const km2mi = 0.621371; /* Kilometers per Mile */
const ae = 1.0;
const ck2 = 5.413079E-4;
const ck4 = 6.209887E-7;
const __f = 3.352779E-3;
const ge = 3.986008E5;
const __s__ = 1.012229;
const qoms2t = 1.880279E-09;
const secday = 8.6400E4; /* Seconds per day */
const omega_E = 1.0027379;
const omega_ER = 6.3003879;
const zns = 1.19459E-5;
const c1ss = 2.9864797E-6;
const zes = 1.675E-2;
const znl = 1.5835218E-4;
const c1l = 4.7968065E-7;
const zel = 5.490E-2;
const zcosis = 9.1744867E-1;
const zsinis = 3.9785416E-1;
const zsings = -9.8088458E-1;
const zcosgs = 1.945905E-1;
const zcoshs = 1;
const zsinhs = 0;
const q22 = 1.7891679E-6;
const q31 = 2.1460748E-6;
const q33 = 2.2123015E-7;
const g22 = 5.7686396;
const g32 = 9.5240898E-1;
const g44 = 1.8014998;
const g52 = 1.0508330;
const g54 = 4.4108898;
const root22 = 1.7891679E-6;
const root32 = 3.7393792E-7;
const root44 = 7.3636953E-9;
const root52 = 1.1428639E-7;
const root54 = 2.1765803E-9;
const thdt = 4.3752691E-3;
const rho = 1.5696615E-1;
const mfactor = 7.292115E-5;
const __sr__ = 6.96000E5; /*Solar radius - kilometers (IAU 76)*/
const AU = 1.49597870E8; /*Astronomical unit - kilometers (IAU 76)*/
/* visibility constants */
const SAT_VIS_NONE = 0;
const SAT_VIS_VISIBLE = 1;
const SAT_VIS_DAYLIGHT = 2;
const SAT_VIS_ECLIPSED = 3;
/* preferences */
public $minEle = 10; // Minimum elevation
public $timeRes = 10; // Pass details: time resolution
public $numEntries = 20; // Pass details: number of entries
public $threshold = -6; // Twilight threshold
/**
* Predict the next pass.
*
* This function simply wraps the get_pass function using the current time
* as parameter.
*
* Note: the data in sat will be corrupt (future) and must be refreshed
* by the caller, if the caller will need it later on (eg. if the caller
* is GtkSatList).
*
* @param Predict_Sat $sat The satellite data.
* @param Predict_QTH $qth The observer data.
* @param int $maxdt The maximum number of days to look ahead.
*
* @return Predict_Pass Pointer instance or NULL if no pass can be
* found.
*/
public function get_next_pass(Predict_Sat $sat, Predict_QTH $qth, $maxdt)
{
/* get the current time and call the get_pass function */
$now = Predict_Time::get_current_daynum();
return $this->get_pass($sat, $qth, $now, $maxdt);
}
/** Predict first pass after a certain time.
*
* @param Predict_Sat $sat The satellite data.
* @param Predict_QTH $qth The observer's location data.
* @param float $start Starting time.
* @param int $maxdt The maximum number of days to look ahead (0 for no limit).
*
* @return Predict_Pass or NULL if there was an error.
*
* This function will find the first upcoming pass with AOS no earlier than
* t = start and no later than t = (start+maxdt).
*
* note For no time limit use maxdt = 0.0
*
* note the data in sat will be corrupt (future) and must be refreshed
* by the caller, if the caller will need it later on
*/
public function get_pass(Predict_Sat $sat_in, Predict_QTH $qth, $start, $maxdt)
{
$aos = 0.0; /* time of AOS */
$tca = 0.0; /* time of TCA */
$los = 0.0; /* time of LOS */
$dt = 0.0; /* time diff */
$step = 0.0; /* time step */
$t0 = $start;
$tres = 0.0; /* required time resolution */
$max_el = 0.0; /* maximum elevation */
$pass = null;
$detail = null;
$done = false;
$iter = 0; /* number of iterations */
/* FIXME: watchdog */
/*copy sat_in to a working structure*/
$sat = clone $sat_in;
$sat_working = clone $sat_in;
/* get time resolution; sat-cfg stores it in seconds */
$tres = $this->timeRes / 86400.0;
/* loop until we find a pass with elevation > SAT_CFG_INT_PRED_MIN_EL
or we run out of time
FIXME: we should have a safety break
*/
while (!$done) {
/* Find los of next pass or of current pass */
$los = $this->find_los($sat, $qth, $t0, $maxdt); // See if a pass is ongoing
$aos = $this->find_aos($sat, $qth, $t0, $maxdt);
/* sat_log_log(SAT_LOG_LEVEL_MSG, "%s:%s:%d: found aos %f and los %f for t0=%f", */
/* __FILE__, */
/* __FUNCTION__, */
/* __LINE__, */
/* aos, */
/* los, */
/* t0); */
if ($aos > $los) {
// los is from an currently happening pass, find previous aos
$aos = $this->find_prev_aos($sat, $qth, $t0);
}
/* aos = 0.0 means no aos */
if ($aos == 0.0) {
$done = true;
} else if (($maxdt > 0.0) && ($aos > ($start + $maxdt)) ) {
/* check whether we are within time limits;
maxdt = 0 mean no time limit.
*/
$done = true;
} else {
//los = find_los (sat, qth, aos + 0.001, maxdt); // +1.5 min later
$dt = $los - $aos;
/* get time step, which will give us the max number of entries */
$step = $dt / $this->numEntries;
/* but if this is smaller than the required resolution
we go with the resolution
*/
if ($step < $tres) {
$step = $tres;
}
/* create a pass_t entry; FIXME: g_try_new in 2.8 */
$pass = new Predict_Pass();
$pass->aos = $aos;
$pass->los = $los;
$pass->max_el = 0.0;
$pass->aos_az = 0.0;
$pass->los_az = 0.0;
$pass->maxel_az = 0.0;
$pass->vis = '---';
$pass->satname = $sat->nickname;
$pass->details = array();
/* iterate over each time step */
for ($t = $pass->aos; $t <= $pass->los; $t += $step) {
/* calculate satellite data */
$this->predict_calc($sat, $qth, $t);
/* in the first iter we want to store
pass->aos_az
*/
if ($t == $pass->aos) {
$pass->aos_az = $sat->az;
$pass->orbit = $sat->orbit;
}
/* append details to sat->details */
$detail = new Predict_PassDetail();
$detail->time = $t;
$detail->pos->x = $sat->pos->x;
$detail->pos->y = $sat->pos->y;
$detail->pos->z = $sat->pos->z;
$detail->pos->w = $sat->pos->w;
$detail->vel->x = $sat->vel->x;
$detail->vel->y = $sat->vel->y;
$detail->vel->z = $sat->vel->z;
$detail->vel->w = $sat->vel->w;
$detail->velo = $sat->velo;
$detail->az = $sat->az;
$detail->el = $sat->el;
$detail->range = $sat->range;
$detail->range_rate = $sat->range_rate;
$detail->lat = $sat->ssplat;
$detail->lon = $sat->ssplon;
$detail->alt = $sat->alt;
$detail->ma = $sat->ma;
$detail->phase = $sat->phase;
$detail->footprint = $sat->footprint;
$detail->orbit = $sat->orbit;
$detail->vis = $this->get_sat_vis($sat, $qth, $t);
/* also store visibility "bit" */
switch ($detail->vis) {
case self::SAT_VIS_VISIBLE:
$pass->vis[0] = 'V';
break;
case self::SAT_VIS_DAYLIGHT:
$pass->vis[1] = 'D';
break;
case self::SAT_VIS_ECLIPSED:
$pass->vis[2] = 'E';
break;
default:
break;
}
// Using an array, no need to prepend and reverse the list
// as gpredict does
$pass->details[] = $detail;
// Look up apparent magnitude if this is a visible pass
if ($detail->vis === self::SAT_VIS_VISIBLE) {
$apmag = $sat->calculateApparentMagnitude($t, $qth);
if ($pass->max_apparent_magnitude === null || $apmag < $pass->max_apparent_magnitude) {
$pass->max_apparent_magnitude = $apmag;
}
}
/* store elevation if greater than the
previously stored one
*/
if ($sat->el > $max_el) {
$max_el = $sat->el;
$tca = $t;
$pass->maxel_az = $sat->az;
}
/* g_print ("TIME: %f\tAZ: %f\tEL: %f (MAX: %f)\n", */
/* t, sat->az, sat->el, max_el); */
}
/* calculate satellite data */
$this->predict_calc($sat, $qth, $pass->los);
/* store los_az, max_el and tca */
$pass->los_az = $sat->az;
$pass->max_el = $max_el;
$pass->tca = $tca;
/* check whether this pass is good */
if ($max_el >= $this->minEle) {
$done = true;
} else {
$done = false;
$t0 = $los + 0.014; // +20 min
$pass = null;
}
$iter++;
}
}
return $pass;
}
/**
* Calculate satellite visibility.
*
* @param Predict_Sat $sat The satellite structure.
* @param Predict_QTH $qth The QTH
* @param float $jul_utc The time at which the visibility should be calculated.
*
* @return int The visiblity constant, 0, 1, 2, or 3 (see above)
*/
public function get_sat_vis(Predict_Sat $sat, Predict_QTH $qth, $jul_utc)
{
/* gboolean sat_sun_status;
gdouble sun_el;
gdouble threshold;
gdouble eclipse_depth;
sat_vis_t vis = SAT_VIS_NONE; */
$eclipse_depth = 0.0;
$zero_vector = new Predict_Vector();
$obs_geodetic = new Predict_Geodetic();
/* Solar ECI position vector */
$solar_vector = new Predict_Vector();
/* Solar observed az and el vector */
$solar_set = new Predict_ObsSet();
/* FIXME: could be passed as parameter */
$obs_geodetic->lon = $qth->lon * self::de2ra;
$obs_geodetic->lat = $qth->lat * self::de2ra;
$obs_geodetic->alt = $qth->alt / 1000.0;
$obs_geodetic->theta = 0;
Predict_Solar::Calculate_Solar_Position($jul_utc, $solar_vector);
Predict_SGPObs::Calculate_Obs($jul_utc, $solar_vector, $zero_vector, $obs_geodetic, $solar_set);
if (Predict_Solar::Sat_Eclipsed($sat->pos, $solar_vector, $eclipse_depth)) {
/* satellite is eclipsed */
$sat_sun_status = false;
} else {
/* satellite in sunlight => may be visible */
$sat_sun_status = true;
}
if ($sat_sun_status) {
$sun_el = Predict_Math::Degrees($solar_set->el);
if ($sat->el >= 0.0) {
$vis = self::SAT_VIS_VISIBLE;
} else {
$vis = self::SAT_VIS_DAYLIGHT;
}
} else {
$vis = self::SAT_VIS_ECLIPSED;
}
return $vis;
}
/** Find the AOS time of the next pass.
* @author Alexandru Csete, OZ9AEC
* @author John A. Magliacane, KD2BD
* @param Predict_Sat $sat The satellite data.
* @param Predict_QTH $qth The observer's location (QTH) data.
* @param float $start The julian date where calculation should start.
* @param int $maxdt The upper time limit in days (0.0 = no limit)
* @return The julain date of the next AOS or 0.0 if the satellite has no AOS.
*
* This function finds the time of AOS for the first coming pass taking place
* no earlier that start.
* If the satellite is currently within range, the function first calls
* find_los to get the next LOS time. Then the calculations are done using
* the new start time.
*
*/
public function find_aos(Predict_Sat $sat, Predict_QTH $qth, $start, $maxdt)
{
$t = $start;
$aostime = 0.0;
/* make sure current sat values are
in sync with the time
*/
$this->predict_calc($sat, $qth, $start);
/* check whether satellite has aos */
if (($sat->otype == Predict_SGPSDP::ORBIT_TYPE_GEO) ||
($sat->otype == Predict_SGPSDP::ORBIT_TYPE_DECAYED) ||
!$this->has_aos($sat, $qth)) {
return 0.0;
}
if ($sat->el > 0.0) {
$t = $this->find_los($sat, $qth, $start, $maxdt) + 0.014; // +20 min
}
/* invalid time (potentially returned by find_los) */
if ($t < 0.1) {
return 0.0;
}
/* update satellite data */
$this->predict_calc($sat, $qth, $t);
/* use upper time limit */
if ($maxdt > 0.0) {
/* coarse time steps */
while (($sat->el < -1.0) && ($t <= ($start + $maxdt))) {
$t -= 0.00035 * ($sat->el * (($sat->alt / 8400.0) + 0.46) - 2.0);
$this->predict_calc($sat, $qth, $t);
}
/* fine steps */
while (($aostime == 0.0) && ($t <= ($start + $maxdt))) {
if (abs($sat->el) < 0.005) {
$aostime = $t;
} else {
$t -= $sat->el * sqrt($sat->alt) / 530000.0;
$this->predict_calc($sat, $qth, $t);
}
}
} else {
/* don't use upper time limit */
/* coarse time steps */
while ($sat->el < -1.0) {
$t -= 0.00035 * ($sat->el * (($sat->alt / 8400.0) + 0.46) - 2.0);
$this->predict_calc($sat, $qth, $t);
}
/* fine steps */
while ($aostime == 0.0) {
if (abs($sat->el) < 0.005) {
$aostime = $t;
} else {
$t -= $sat->el * sqrt($sat->alt) / 530000.0;
$this->predict_calc($sat, $qth, $t);
}
}
}
return $aostime;
}
/** SGP4SDP4 driver for doing AOS/LOS calculations.
* @param Predict_Sat $sat The satellite data.
* @param Predict_QTH $qth The QTH observer location data.
* @param float $t The time for calculation (Julian Date)
*
*/
public function predict_calc(Predict_Sat $sat, Predict_QTH $qth, $t)
{
$obs_set = new Predict_ObsSet();
$sat_geodetic = new Predict_Geodetic();
$obs_geodetic = new Predict_Geodetic();
$obs_geodetic->lon = $qth->lon * self::de2ra;
$obs_geodetic->lat = $qth->lat * self::de2ra;
$obs_geodetic->alt = $qth->alt / 1000.0;
$obs_geodetic->theta = 0;
$sat->jul_utc = $t;
$sat->tsince = ($sat->jul_utc - $sat->jul_epoch) * self::xmnpda;
/* call the norad routines according to the deep-space flag */
$sgpsdp = Predict_SGPSDP::getInstance($sat);
if ($sat->flags & Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG) {
$sgpsdp->SDP4($sat, $sat->tsince);
} else {
$sgpsdp->SGP4($sat, $sat->tsince);
}
Predict_Math::Convert_Sat_State($sat->pos, $sat->vel);
/* get the velocity of the satellite */
$sat->vel->w = sqrt($sat->vel->x * $sat->vel->x + $sat->vel->y * $sat->vel->y + $sat->vel->z * $sat->vel->z);
$sat->velo = $sat->vel->w;
Predict_SGPObs::Calculate_Obs($sat->jul_utc, $sat->pos, $sat->vel, $obs_geodetic, $obs_set);
Predict_SGPObs::Calculate_LatLonAlt($sat->jul_utc, $sat->pos, $sat_geodetic);
while ($sat_geodetic->lon < -self::pi) {
$sat_geodetic->lon += self::twopi;
}
while ($sat_geodetic->lon > (self::pi)) {
$sat_geodetic->lon -= self::twopi;
}
$sat->az = Predict_Math::Degrees($obs_set->az);
$sat->el = Predict_Math::Degrees($obs_set->el);
$sat->range = $obs_set->range;
$sat->range_rate = $obs_set->range_rate;
$sat->ssplat = Predict_Math::Degrees($sat_geodetic->lat);
$sat->ssplon = Predict_Math::Degrees($sat_geodetic->lon);
$sat->alt = $sat_geodetic->alt;
$sat->ma = Predict_Math::Degrees($sat->phase);
$sat->ma *= 256.0 / 360.0;
$sat->phase = Predict_Math::Degrees($sat->phase);
/* same formulas, but the one from predict is nicer */
//sat->footprint = 2.0 * xkmper * acos (xkmper/sat->pos.w);
$sat->footprint = 12756.33 * acos(self::xkmper / (self::xkmper + $sat->alt));
$age = $sat->jul_utc - $sat->jul_epoch;
$sat->orbit = floor(($sat->tle->xno * self::xmnpda / self::twopi +
$age * $sat->tle->bstar * self::ae) * $age +
$sat->tle->xmo / self::twopi) + $sat->tle->revnum - 1;
}
/** Find the LOS time of the next pass.
* @author Alexandru Csete, OZ9AEC
* @author John A. Magliacane, KD2BD
* @param Predict_Sat $sat The satellite data.
* @param Predict_QTH $qth The QTH observer location data.
* @param float $start The time where calculation should start. (Julian Date)
* @param int $maxdt The upper time limit in days (0.0 = no limit)
* @return The time (julian date) of the next LOS or 0.0 if the satellite has no LOS.
*
* This function finds the time of LOS for the first coming pass taking place
* no earlier that start.
* If the satellite is currently out of range, the function first calls
* find_aos to get the next AOS time. Then the calculations are done using
* the new start time.
* The function has a built-in watchdog to ensure that we don't end up in
* lengthy loops.
*
*/
public function find_los(Predict_Sat $sat, Predict_QTH $qth, $start, $maxdt)
{
$t = $start;
$lostime = 0.0;
$this->predict_calc($sat, $qth, $start);
/* check whether satellite has aos */
if (($sat->otype == Predict_SGPSDP::ORBIT_TYPE_GEO) ||
($sat->otype == Predict_SGPSDP::ORBIT_TYPE_DECAYED) ||
!$this->has_aos ($sat, $qth)) {
return 0.0;
}
if ($sat->el < 0.0) {
$t = $this->find_aos($sat, $qth, $start, $maxdt) + 0.001; // +1.5 min
}
/* invalid time (potentially returned by find_aos) */
if ($t < 0.01) {
return 0.0;
}
/* update satellite data */
$this->predict_calc($sat, $qth, $t);
/* use upper time limit */
if ($maxdt > 0.0) {
/* coarse steps */
while (($sat->el >= 1.0) && ($t <= ($start + $maxdt))) {
$t += cos(($sat->el - 1.0) * self::de2ra) * sqrt($sat->alt) / 25000.0;
$this->predict_calc($sat, $qth, $t);
}
/* fine steps */
while (($lostime == 0.0) && ($t <= ($start + $maxdt))) {
$t += $sat->el * sqrt($sat->alt) / 502500.0;
$this->predict_calc($sat, $qth, $t);
if (abs($sat->el) < 0.005) {
$lostime = $t;
}
}
} else {
/* don't use upper limit */
/* coarse steps */
while ($sat->el >= 1.0) {
$t += cos(($sat->el - 1.0) * self::de2ra) * sqrt($sat->alt) / 25000.0;
$this->predict_calc($sat, $qth, $t);
}
/* fine steps */
while ($lostime == 0.0) {
$t += $sat->el * sqrt($sat->alt) / 502500.0;
$this->predict_calc($sat, $qth, $t);
if (abs($sat->el) < 0.005)
$lostime = $t;
}
}
return $lostime;
}
/** Find AOS time of current pass.
* @param Predict_Sat $sat The satellite to find AOS for.
* @param Predict_QTH $qth The ground station.
* @param float $start Start time, prefereably now.
* @return The time of the previous AOS or 0.0 if the satellite has no AOS.
*
* This function can be used to find the AOS time in the past of the
* current pass.
*/
public function find_prev_aos(Predict_Sat $sat, Predict_QTH $qth, $start)
{
$aostime = $start;
/* make sure current sat values are
in sync with the time
*/
$this->predict_calc($sat, $qth, $start);
/* check whether satellite has aos */
if (($sat->otype == Predict_SGPSDP::ORBIT_TYPE_GEO) ||
($sat->otype == Predict_SGPSDP::ORBIT_TYPE_DECAYED) ||
!$this->has_aos($sat, $qth)) {
return 0.0;
}
while ($sat->el >= 0.0) {
$aostime -= 0.0005; // 0.75 min
$this->predict_calc($sat, $qth, $aostime);
}
return $aostime;
}
/** Determine whether satellite ever reaches AOS.
* @author John A. Magliacane, KD2BD
* @author Alexandru Csete, OZ9AEC
* @param Predict_Sat $sat The satellite data.
* @param Predict_QTH $qth The observer's location data
* @return bool true if the satellite will reach AOS, false otherwise.
*
*/
public function has_aos(Predict_Sat $sat, Predict_QTH $qth)
{
$retcode = false;
/* FIXME */
if ($sat->meanmo == 0.0) {
$retcode = false;
} else {
/* xincl is already in RAD by select_ephemeris */
$lin = $sat->tle->xincl;
if ($lin >= self::pio2) {
$lin = self::pi - $lin;
}
$sma = 331.25 * exp(log(1440.0 / $sat->meanmo) * (2.0 / 3.0));
$apogee = $sma * (1.0 + $sat->tle->eo) - self::xkmper;
if ((acos(self::xkmper / ($apogee + self::xkmper)) + ($lin)) > abs($qth->lat * self::de2ra)) {
$retcode = true;
} else {
$retcode = false;
}
}
return $retcode;
}
/** Predict passes after a certain time.
*
*
* This function calculates num upcoming passes with AOS no earlier
* than t = start and not later that t = (start+maxdt). The function will
* repeatedly call get_pass until
* the number of predicted passes is equal to num, the time has reached
* limit or the get_pass function returns NULL.
*
* note For no time limit use maxdt = 0.0
*
* note the data in sat will be corrupt (future) and must be refreshed
* by the caller, if the caller will need it later on (eg. if the caller
* is GtkSatList).
*
* note Prepending to a singly linked list is much faster than appending.
* Therefore, the elements are prepended whereafter the GSList is
* reversed
*
*
* @param Predict_Sat $sat The satellite data
* @param Predict_QTH $qth The observer's location data
* @param float $start The start julian date
* @param int $maxdt The max # of days to look
* @param int $num The max # of passes to get
* @return array of Predict_Pass instances if found, empty array otherwise
*/
public function get_passes(Predict_Sat $sat, Predict_QTH $qth, $start, $maxdt, $num = 0)
{
$passes = array();
/* if no number has been specified
set it to something big */
if ($num == 0) {
$num = 100;
}
$t = $start;
for ($i = 0; $i < $num; $i++) {
$pass = $this->get_pass($sat, $qth, $t, $maxdt);
if ($pass != null) {
$passes[] = $pass;
$t = $pass->los + 0.014; // +20 min
/* if maxdt > 0.0 check whether we have reached t = start+maxdt
if yes finish predictions
*/
if (($maxdt > 0.0) && ($t >= ($start + $maxdt))) {
$i = $num;
}
} else {
/* we can't get any more passes */
$i = $num;
}
}
return $passes;
}
/**
* Filters out visible passes and adds the visible aos, tca, los, and
* corresponding az and ele for each.
*
* @param array $passes The passes returned from get_passes()
*
* @author Bill Shupp
* @return array
*/
public function filterVisiblePasses(array $passes)
{
$filtered = array();
foreach ($passes as $result) {
// Dummy check
if ($result->vis[0] != 'V') {
continue;
}
$aos = false;
$aos_az = false;
$aos = false;
$tca = false;
$los_az = false;
$aos_el = 0;
$max_el = 0;
foreach ($result->details as $detail) {
if ($detail->vis != Predict::SAT_VIS_VISIBLE) {
continue;
}
if ($detail->el < $this->minEle) {
continue;
}
if ($aos == false) {
$aos = $detail->time;
$aos_az = $detail->az;
$aos_el = $detail->el;
$tca = $detail->time;
$los = $detail->time;
$los_az = $detail->az;
$los_el = $detail->el;
$max_el = $detail->el;
$max_el_az = $detail->el;
continue;
}
$los = $detail->time;
$los_az = $detail->az;
$los_el = $detail->el;
if ($detail->el > $max_el) {
$tca = $detail->time;
$max_el = $detail->el;
$max_el_az = $detail->az;
}
}
if ($aos === false) {
// Does not reach minimum elevation, skip
continue;
}
$result->visible_aos = $aos;
$result->visible_aos_az = $aos_az;
$result->visible_aos_el = $aos_el;
$result->visible_tca = $tca;
$result->visible_max_el = $max_el;
$result->visible_max_el_az = $max_el_az;
$result->visible_los = $los;
$result->visible_los_az = $los_az;
$result->visible_los_el = $los_el;
$filtered[] = $result;
}
return $filtered;
}
/**
* Translates aziumuth degrees to compass direction:
*
* N (0°), NNE (22.5°), NE (45°), ENE (67.5°), E (90°), ESE (112.5°),
* SE (135°), SSE (157.5°), S (180°), SSW (202.5°), SW (225°),
* WSW (247.5°), W (270°), WNW (292.5°), NW (315°), NNW (337.5°)
*
* @param int $az The azimuth in degrees, defaults to 0
*
* @return string
*/
public function azDegreesToDirection($az = 0)
{
$i = floor($az / 22.5);
$m = (22.5 * (2 * $i + 1)) / 2;
$i = ($az >= $m) ? $i + 1 : $i;
return trim(substr('N NNENE ENEE ESESE SSES SSWSW WSWW WNWNW NNWN ', $i * 3, 3));
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Port of deep_arg_t struct from sgp4sdp4.h
*/
/* Common arguments between deep-space functions */
class Predict_DeepArg
{
/* Used by dpinit part of Deep() */
public $eosq;
public $sinio;
public $cosio;
public $betao;
public $aodp;
public $theta2;
public $sing;
public $cosg;
public $betao2;
public $xmdot;
public $omgdot;
public $xnodot;
public $xnodp;
/* Used by dpsec and dpper parts of Deep() */
public $xll;
public $omgadf;
public $xnode;
public $em;
public $xinc;
public $xn;
public $t;
/* Used by thetg and Deep() */
public $ds50;
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Port of deep_static_t struct from sgp4sdp4.h
*/
/* static data for DEEP */
class Predict_DeepStatic
{
public $thgr;
public $xnq;
public $xqncl;
public $omegaq;
public $zmol;
public $zmos;
public $savtsn;
public $ee2;
public $e3;
public $xi2;
public $xl2;
public $xl3;
public $xl4;
public $xgh2;
public $xgh3;
public $xgh4;
public $xh2;
public $xh3;
public $sse;
public $ssi;
public $ssg;
public $xi3;
public $se2;
public $si2;
public $sl2;
public $sgh2;
public $sh2;
public $se3;
public $si3;
public $sl3;
public $sgh3;
public $sh3;
public $sl4;
public $sgh4;
public $ssl;
public $ssh;
public $d3210;
public $d3222;
public $d4410;
public $d4422;
public $d5220;
public $d5232;
public $d5421;
public $d5433;
public $del1;
public $del2;
public $del3;
public $fasx2;
public $fasx4;
public $fasx6;
public $xlamo;
public $xfact;
public $xni;
public $atime;
public $stepp;
public $stepn;
public $step2;
public $preep;
public $pl;
public $sghs;
public $xli;
public $d2201;
public $d2211;
public $sghl;
public $sh1;
public $pinc;
public $pe;
public $shs;
public $zsingl;
public $zcosgl;
public $zsinhl;
public $zcoshl;
public $zsinil;
public $zcosil;
}

View File

@@ -0,0 +1,5 @@
<?php
class Predict_Exception extends Exception
{
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Geodetic position data structure.
*
*/
class Predict_Geodetic
{
public $lat; /*!< Lattitude [rad] */
public $lon; /*!< Longitude [rad] */
public $alt; /*!< Altitude [km] */
public $theta;
}

196
predict/Predict/Math.php Normal file
View File

@@ -0,0 +1,196 @@
<?php
/**
* Predict_Math
*
* Ported to PHP by Bill Shupp. Original comments below
*/
require_once realpath(__DIR__ . "/../../predict/Predict.php");
/*
* Unit SGP_Math
* Author: Dr TS Kelso
* Original Version: 1991 Oct 30
* Current Revision: 1998 Mar 17
* Version: 3.00
* Copyright: 1991-1998, All Rights Reserved
*
* ported to C by: Neoklis Kyriazis April 9 2001
*/
class Predict_Math
{
/* Returns sign of a float */
public static function Sign($arg)
{
if ($arg > 0 ) {
return 1;
} else if ($arg < 0 ) {
return -1;
} else {
return 0;
}
}
/* Returns the arcsine of the argument */
public static function ArcSin($arg)
{
if (abs($arg) >= 1 ) {
return (self::Sign($arg) * Predict::pio2);
} else {
return(atan($arg / sqrt(1 - $arg * $arg)));
}
}
/* Returns arccosine of rgument */
public static function ArcCos($arg)
{
return Predict::pio2 - self::ArcSin($arg);
}
/* Adds vectors v1 and v2 together to produce v3 */
public static function Vec_Add(Predict_Vector $v1, Predict_Vector $v2, Predict_Vector $v3)
{
$v3->x = $v1->x + $v2->x;
$v3->y = $v1->y + $v2->y;
$v3->z = $v1->z + $v2->z;
$v3->w = sqrt($v3->x * $v3->x + $v3->y * $v3->y + $v3->z * $v3->z);
}
/* Subtracts vector v2 from v1 to produce v3 */
public static function Vec_Sub(Predict_Vector $v1, Predict_Vector $v2, Predict_Vector $v3)
{
$v3->x = $v1->x - $v2->x;
$v3->y = $v1->y - $v2->y;
$v3->z = $v1->z - $v2->z;
$v3->w = sqrt($v3->x * $v3->x + $v3->y * $v3->y + $v3->z * $v3->z);
}
/* Multiplies the vector v1 by the scalar k to produce the vector v2 */
public static function Scalar_Multiply($k, Predict_Vector $v1, Predict_Vector $v2)
{
$v2->x = $k * $v1->x;
$v2->y = $k * $v1->y;
$v2->z = $k * $v1->z;
$v2->w = abs($k) * $v1->w;
}
/* Multiplies the vector v1 by the scalar k */
public static function Scale_Vector($k, Predict_Vector $v)
{
$v->x *= $k;
$v->y *= $k;
$v->z *= $k;
$v->w = sqrt($v->x * $v->x + $v->y * $v->y + $v->z * $v->z);
}
/* Returns the dot product of two vectors */
public static function Dot(Predict_Vector $v1, Predict_Vector $v2)
{
return ($v1->x * $v2->x + $v1->y * $v2->y + $v1->z * $v2->z);
}
/* Calculates the angle between vectors v1 and v2 */
public static function Angle(Predict_Vector $v1, Predict_Vector $v2)
{
$v1->w = sqrt($v1->x * $v1->x + $v1->y * $v1->y + $v1->z * $v1->z);
$v2->w = sqrt($v2->x * $v2->x + $v2->y * $v2->y + $v2->z * $v2->z);
return (self::ArcCos(self::Dot($v1, $v2) / ($v1->w * $v2->w)));
}
/* Produces cross product of v1 and v2, and returns in v3 */
public static function Cross(Predict_Vector $v1, Predict_Vector $v2 ,Predict_Vector $v3)
{
$v3->x = $v1->y * $v2->z - $v1->z * $v2->y;
$v3->y = $v1->z * $v2->x - $v1->x * $v2->z;
$v3->z = $v1->x * $v2->y - $v1->y * $v2->x;
$v3->w = sqrt($v3->x * $v3->x + $v3->y * $v3->y + $v3->z * $v3->z);
}
/* Normalizes a vector */
public static function Normalize(Predict_Vector $v )
{
$v->x /= $v->w;
$v->y /= $v->w;
$v->z /= $v->w;
}
/* Four-quadrant arctan function */
public static function AcTan($sinx, $cosx)
{
if ($cosx == 0) {
if ($sinx > 0) {
return Predict::pio2;
} else {
return Predict::x3pio2;
}
} else {
if ($cosx > 0) {
if ($sinx > 0) {
return atan($sinx / $cosx);
} else {
return Predict::twopi + atan($sinx / $cosx);
}
} else {
return Predict::pi + atan($sinx / $cosx);
}
}
}
/* Returns mod 2pi of argument */
public static function FMod2p($x)
{
$ret_val = $x;
$i = (int) ($ret_val / Predict::twopi);
$ret_val -= $i * Predict::twopi;
if ($ret_val < 0) {
$ret_val += Predict::twopi;
}
return $ret_val;
}
/* Returns arg1 mod arg2 */
public static function Modulus($arg1, $arg2)
{
$ret_val = $arg1;
$i = (int) ($ret_val / $arg2);
$ret_val -= $i * $arg2;
if ($ret_val < 0) {
$ret_val += $arg2;
}
return $ret_val;
}
/* Returns fractional part of double argument */
public static function Frac($arg)
{
return $arg - floor($arg);
}
/* Converts the satellite's position and velocity */
/* vectors from normalised values to km and km/sec */
public static function Convert_Sat_State(Predict_Vector $pos, Predict_Vector $vel)
{
self::Scale_Vector(Predict::xkmper, $pos);
self::Scale_Vector(Predict::xkmper * Predict::xmnpda / Predict::secday, $vel);
}
/* Returns angle in radians from arg in degrees */
public static function Radians($arg)
{
return $arg * Predict::de2ra;
}
/* Returns angle in degrees from arg in rads */
public static function Degrees($arg)
{
return $arg / Predict::de2ra;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Bearing to satellite from observer
*/
class Predict_ObsSet
{
public $az = 0.0; /*!< Azimuth [deg] */
public $el = 0.0; /*!< Elevation [deg] */
public $range = 0.0; /*!< Range [km] */
public $range_rate = 0.0; /*!< Velocity [km/sec] */
}

27
predict/Predict/Pass.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
/** Brief satellite pass info. */
class Predict_Pass
{
public $satname; /*!< satellite name */
public $aos; /*!< AOS time in "jul_utc" */
public $tca; /*!< TCA time in "jul_utc" */
public $los; /*!< LOS time in "jul_utc" */
public $max_el; /*!< Maximum elevation during pass */
public $aos_az; /*!< Azimuth at AOS */
public $los_az; /*!< Azimuth at LOS */
public $orbit; /*!< Orbit number */
public $maxel_az; /*!< Azimuth at maximum elevation */
public $vis; /*!< Visibility string, e.g. VSE, -S-, V-- */
public $details = array(); /*!< List of pass_detail_t entries */
public $max_apparent_magnitude = null; /* maximum apparent magnitude, experimental */
public $visible_aos;
public $visible_aos_az;
public $visible_aos_el;
public $visible_tca;
public $visible_max_el;
public $visible_max_el_az;
public $visible_los;
public $visible_los_az;
public $visible_los_el;
}

View File

@@ -0,0 +1,38 @@
<?php
require_once realpath(__DIR__ . "/../../predict/Predict/Vector.php");
/** Pass detail entry.
*
* In order to ensure maximum flexibility at a minimal effort, only the
* raw position and velocity is calculated. Calculations of the
* "human readable" parameters are the responsibility of the consumer.
* This way we can use the same prediction engine for various consumers
* without having too much overhead and complexity in the low level code.
*/
class Predict_PassDetail
{
public $time; /*!< time in "jul_utc" */
public $pos; /*!< Raw unprocessed position at time */
public $vel; /*!< Raw unprocessed velocity at time */
public $velo;
public $az;
public $el;
public $range;
public $range_rate;
public $lat;
public $lon;
public $alt;
public $ma;
public $phase;
public $footprint;
public $vis;
public $orbit;
public function __construct()
{
$this->pos = new Predict_Vector();
$this->vel = new Predict_Vector();
}
}

20
predict/Predict/QTH.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
/**
* Predict_QTH
*
* Holds information about the observer's location (aka ground station)
*/
class Predict_QTH
{
public $name; /*!< Name, eg. callsign. */
public $loc; /*!< Location, eg City, Country. */
public $desc; /*!< Short description. */
public $lat; /*!< Latitude in dec. deg. North. */
public $lon; /*!< Longitude in dec. deg. East. */
public $alt; /*!< Altitude above sea level in meters. */
public $qra; /*!< QRA locator */
public $wx; /*!< Weather station code (4 chars). */
public $data; /*!< Raw data from cfg file. */
}

156
predict/Predict/SGPObs.php Normal file
View File

@@ -0,0 +1,156 @@
<?php
/**
* Predict_SGPObs
*
* Ported to PHP by Bill Shupp. Original comments below
*/
require_once realpath(__DIR__ . "/../../predict/Predict/Math.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Time.php");
require_once realpath(__DIR__ . "/../../predict/Predict.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Vector.php");
require_once realpath(__DIR__ . "/../../predict/Predict/ObsSet.php");
/*
* Unit SGP_Obs
* Author: Dr TS Kelso
* Original Version: 1992 Jun 02
* Current Revision: 1992 Sep 28
* Version: 1.40
* Copyright: 1992, All Rights Reserved
*
* Ported to C by: Neoklis Kyriazis April 9 2001
* Ported to PHP by Bill Shupp August, 2011
*/
class Predict_SGPObs
{
/* Procedure Calculate_User_PosVel passes the user's geodetic position */
/* and the time of interest and returns the ECI position and velocity */
/* of the observer. The velocity calculation assumes the geodetic */
/* position is stationary relative to the earth's surface. */
public static function Calculate_User_PosVel(
$_time, Predict_Geodetic $geodetic, Predict_Vector $obs_pos, Predict_Vector $obs_vel
)
{
/* Reference: The 1992 Astronomical Almanac, page K11. */
$sinGeodeticLat = sin($geodetic->lat); /* Only run sin($geodetic->lat) once */
$geodetic->theta = Predict_Math::FMod2p(Predict_Time::ThetaG_JD($_time) + $geodetic->lon);/*LMST*/
$c = 1 / sqrt(1 + Predict::__f * (Predict::__f - 2) * $sinGeodeticLat * $sinGeodeticLat);
$sq = (1 - Predict::__f) * (1 - Predict::__f) * $c;
$achcp = (Predict::xkmper * $c + $geodetic->alt) * cos($geodetic->lat);
$obs_pos->x = $achcp * cos($geodetic->theta); /*kilometers*/
$obs_pos->y = $achcp * sin($geodetic->theta);
$obs_pos->z = (Predict::xkmper * $sq + $geodetic->alt) * $sinGeodeticLat;
$obs_vel->x = -Predict::mfactor * $obs_pos->y; /*kilometers/second*/
$obs_vel->y = Predict::mfactor * $obs_pos->x;
$obs_vel->z = 0;
$obs_pos->w = sqrt($obs_pos->x * $obs_pos->x + $obs_pos->y * $obs_pos->y + $obs_pos->z * $obs_pos->z);
$obs_vel->w = sqrt($obs_vel->x * $obs_vel->x + $obs_vel->y * $obs_vel->y + $obs_vel->z * $obs_vel->z);
}
/* Procedure Calculate_LatLonAlt will calculate the geodetic */
/* position of an object given its ECI position pos and time. */
/* It is intended to be used to determine the ground track of */
/* a satellite. The calculations assume the earth to be an */
/* oblate spheroid as defined in WGS '72. */
public static function Calculate_LatLonAlt($_time, Predict_Vector $pos, Predict_Geodetic $geodetic)
{
/* Reference: The 1992 Astronomical Almanac, page K12. */
/* double r,e2,phi,c; */
$geodetic->theta = Predict_Math::AcTan($pos->y, $pos->x); /*radians*/
$geodetic->lon = Predict_Math::FMod2p($geodetic->theta - Predict_Time::ThetaG_JD($_time)); /*radians*/
$r = sqrt(($pos->x * $pos->x) + ($pos->y * $pos->y));
$e2 = Predict::__f * (2 - Predict::__f);
$geodetic->lat = Predict_Math::AcTan($pos->z, $r); /*radians*/
do {
$phi = $geodetic->lat;
$sinPhi = sin($phi);
$c = 1 / sqrt(1 - $e2 * ($sinPhi * $sinPhi));
$geodetic->lat = Predict_Math::AcTan($pos->z + Predict::xkmper * $c * $e2 * $sinPhi, $r);
} while (abs($geodetic->lat - $phi) >= 1E-10);
$geodetic->alt = $r / cos($geodetic->lat) - Predict::xkmper * $c;/*kilometers*/
if ($geodetic->lat > Predict::pio2) {
$geodetic->lat -= Predict::twopi;
}
}
/* The procedures Calculate_Obs and Calculate_RADec calculate */
/* the *topocentric* coordinates of the object with ECI position, */
/* {pos}, and velocity, {vel}, from location {geodetic} at {time}. */
/* The {obs_set} returned for Calculate_Obs consists of azimuth, */
/* elevation, range, and range rate (in that order) with units of */
/* radians, radians, kilometers, and kilometers/second, respectively. */
/* The WGS '72 geoid is used and the effect of atmospheric refraction */
/* (under standard temperature and pressure) is incorporated into the */
/* elevation calculation; the effect of atmospheric refraction on */
/* range and range rate has not yet been quantified. */
/* The {obs_set} for Calculate_RADec consists of right ascension and */
/* declination (in that order) in radians. Again, calculations are */
/* based on *topocentric* position using the WGS '72 geoid and */
/* incorporating atmospheric refraction. */
public static function Calculate_Obs($_time, Predict_Vector $pos, Predict_Vector $vel, Predict_Geodetic $geodetic, Predict_ObsSet $obs_set)
{
$obs_pos = new Predict_Vector();
$obs_vel = new Predict_Vector();
$range = new Predict_Vector();
$rgvel = new Predict_Vector();
self::Calculate_User_PosVel($_time, $geodetic, $obs_pos, $obs_vel);
$range->x = $pos->x - $obs_pos->x;
$range->y = $pos->y - $obs_pos->y;
$range->z = $pos->z - $obs_pos->z;
$rgvel->x = $vel->x - $obs_vel->x;
$rgvel->y = $vel->y - $obs_vel->y;
$rgvel->z = $vel->z - $obs_vel->z;
$range->w = sqrt($range->x * $range->x + $range->y * $range->y + $range->z * $range->z);
$sin_lat = sin($geodetic->lat);
$cos_lat = cos($geodetic->lat);
$sin_theta = sin($geodetic->theta);
$cos_theta = cos($geodetic->theta);
$top_s = $sin_lat * $cos_theta * $range->x
+ $sin_lat * $sin_theta * $range->y
- $cos_lat * $range->z;
$top_e = -$sin_theta * $range->x
+ $cos_theta * $range->y;
$top_z = $cos_lat * $cos_theta * $range->x
+ $cos_lat * $sin_theta * $range->y
+ $sin_lat * $range->z;
$azim = atan(-$top_e / $top_s); /*Azimuth*/
if ($top_s > 0) {
$azim = $azim + Predict::pi;
}
if ($azim < 0 ) {
$azim = $azim + Predict::twopi;
}
$el = Predict_Math::ArcSin($top_z / $range->w);
$obs_set->az = $azim; /* Azimuth (radians) */
$obs_set->el = $el; /* Elevation (radians)*/
$obs_set->range = $range->w; /* Range (kilometers) */
/* Range Rate (kilometers/second)*/
$obs_set->range_rate = Predict_Math::Dot($range, $rgvel) / $range->w;
/* Corrections for atmospheric refraction */
/* Reference: Astronomical Algorithms by Jean Meeus, pp. 101-104 */
/* Correction is meaningless when apparent elevation is below horizon */
// obs_set->el = obs_set->el + Radians((1.02/tan(Radians(Degrees(el)+
// 10.3/(Degrees(el)+5.11))))/60);
if ($obs_set->el < 0) {
$obs_set->el = $el; /*Reset to true elevation*/
}
}
}

1061
predict/Predict/SGPSDP.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
<?php
/**
* Ported to PHP from sgp4sdp4.h by Bill Shupp
*/
/**
* static data for SGP4 and SDP4
*/
class Predict_SGSDPStatic
{
public $aodp;
public $aycof;
public $c1;
public $c4;
public $c5;
public $cosio;
public $d2;
public $d3;
public $d4;
public $delmo;
public $omgcof;
public $eta;
public $omgdot;
public $sinio;
public $xnodp;
public $sinmo;
public $t2cof;
public $t3cof;
public $t4cof;
public $t5cof;
public $x1mth2;
public $x3thm1;
public $x7thm1;
public $xmcof;
public $xmdot;
public $xnodcf;
public $xnodot;
public $xlcof;
}

324
predict/Predict/Sat.php Normal file
View File

@@ -0,0 +1,324 @@
<?php
/**
* Ported to PHP from gpredict by Bill Shupp
*/
require_once realpath(__DIR__ . "/../Predict.php");
require_once realpath(__DIR__ . "/../Predict/SGPSDP.php");
require_once realpath(__DIR__ . "/../Predict/Vector.php");
require_once realpath(__DIR__ . "/../Predict/SGSDPStatic.php");
require_once realpath(__DIR__ . "/../Predict/SGSDPStatic.php");
require_once realpath(__DIR__ . "/../Predict/SGPObs.php");
require_once realpath(__DIR__ . "/../Predict/Solar.php");
require_once realpath(__DIR__ . "/../Predict/DeepArg.php");
require_once realpath(__DIR__ . "/../Predict/DeepStatic.php");
require_once realpath(__DIR__ . "/../Predict/Geodetic.php");
require_once realpath(__DIR__ . "/../Predict/ObsSet.php");
require_once realpath(__DIR__ . "/../Predict/Time.php");
require_once realpath(__DIR__ . "/../Predict/Math.php");
/**
* Contains satellite data and related methods.
*/
class Predict_Sat
{
// Fifth root of a hundred, used for magnitude calculation
const POGSONS_RATIO = 2.5118864315096;
public $name = null;
public $nickname = null;
public $website = null;
public $tle = null; /*!< Keplerian elements */
public $flags = 0; /*!< Flags for algo ctrl */
public $sgps = null;
public $dps = null;
public $deep_arg = null;
public $pos = null; /*!< Raw position and range */
public $vel = null; /*!< Raw velocity */
/*** FIXME: REMOVE */
public $bearing = null; /*!< Az, El, range and vel */
public $astro = null; /*!< Ra and Decl */
/*** END */
/* time keeping fields */
public $jul_epoch = null;
public $jul_utc = null;
public $tsince = null;
public $aos = null; /*!< Next AOS. */
public $los = null; /*!< Next LOS */
public $az = null; /*!< Azimuth [deg] */
public $el = null; /*!< Elevation [deg] */
public $range = null; /*!< Range [km] */
public $range_rate = null; /*!< Range Rate [km/sec] */
public $ra = null; /*!< Right Ascension [deg] */
public $dec = null; /*!< Declination [deg] */
public $ssplat = null; /*!< SSP latitude [deg] */
public $ssplon = null; /*!< SSP longitude [deg] */
public $alt = null; /*!< altitude [km] */
public $velo = null; /*!< velocity [km/s] */
public $ma = null; /*!< mean anomaly */
public $footprint = null; /*!< footprint */
public $phase = null; /*!< orbit phase */
public $meanmo = null; /*!< mean motion kept in rev/day */
public $orbit = null; /*!< orbit number */
public $otype = null; /*!< orbit type. */
public function __construct(Predict_TLE $tle)
{
$headerParts = explode(' ', $tle->header);
$this->name = $headerParts[0];
$this->nickname = $this->name;
$this->tle = $tle;
$this->pos = new Predict_Vector();
$this->vel = new Predict_Vector();
$this->sgps = new Predict_SGSDPStatic();
$this->deep_arg = new Predict_DeepArg();
$this->dps = new Predict_DeepStatic();
$this->select_ephemeris();
$this->sat_data_init_sat($this);
}
/* Selects the apropriate ephemeris type to be used */
/* for predictions according to the data in the TLE */
/* It also processes values in the tle set so that */
/* they are apropriate for the sgp4/sdp4 routines */
public function select_ephemeris()
{
/* Preprocess tle set */
$this->tle->xnodeo *= Predict::de2ra;
$this->tle->omegao *= Predict::de2ra;
$this->tle->xmo *= Predict::de2ra;
$this->tle->xincl *= Predict::de2ra;
$temp = Predict::twopi / Predict::xmnpda / Predict::xmnpda;
/* store mean motion before conversion */
$this->meanmo = $this->tle->xno;
$this->tle->xno = $this->tle->xno * $temp * Predict::xmnpda;
$this->tle->xndt2o *= $temp;
$this->tle->xndd6o = $this->tle->xndd6o * $temp / Predict::xmnpda;
$this->tle->bstar /= Predict::ae;
/* Period > 225 minutes is deep space */
$dd1 = Predict::xke / $this->tle->xno;
$dd2 = Predict::tothrd;
$a1 = pow($dd1, $dd2);
$r1 = cos($this->tle->xincl);
$dd1 = 1.0 - $this->tle->eo * $this->tle->eo;
$temp = Predict::ck2 * 1.5 * ($r1 * $r1 * 3.0 - 1.0) / pow($dd1, 1.5);
$del1 = $temp / ($a1 * $a1);
$ao = $a1 * (1.0 - $del1 * (Predict::tothrd * 0.5 + $del1 *
($del1 * 1.654320987654321 + 1.0)));
$delo = $temp / ($ao * $ao);
$xnodp = $this->tle->xno / ($delo + 1.0);
/* Select a deep-space/near-earth ephemeris */
if (Predict::twopi / $xnodp / Predict::xmnpda >= .15625) {
$this->flags |= Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG;
} else {
$this->flags &= ~Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG;
}
}
/** Initialise satellite data.
* @param sat The satellite to initialise.
* @param qth Optional QTH info, use (0,0) if NULL.
*
* This function calculates the satellite data at t = 0, ie. epoch time
* The function is called automatically by gtk_sat_data_read_sat.
*/
public function sat_data_init_sat(Predict_Sat $sat, Predict_QTH $qth = null)
{
$obs_geodetic = new Predict_Geodetic();
$obs_set = new Predict_ObsSet();
$sat_geodetic = new Predict_Geodetic();
/* double jul_utc, age; */
$jul_utc = Predict_Time::Julian_Date_of_Epoch($sat->tle->epoch); // => tsince = 0.0
$sat->jul_epoch = $jul_utc;
/* initialise observer location */
if ($qth != null) {
$obs_geodetic->lon = $qth->lon * Predict::de2ra;
$obs_geodetic->lat = $qth->lat * Predict::de2ra;
$obs_geodetic->alt = $qth->alt / 1000.0;
$obs_geodetic->theta = 0;
}
else {
$obs_geodetic->lon = 0.0;
$obs_geodetic->lat = 0.0;
$obs_geodetic->alt = 0.0;
$obs_geodetic->theta = 0;
}
/* execute computations */
$sdpsgp = Predict_SGPSDP::getInstance($sat);
if ($sat->flags & Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG) {
$sdpsgp->SDP4($sat, 0.0);
} else {
$sdpsgp->SGP4($sat, 0.0);
}
/* scale position and velocity to km and km/sec */
Predict_Math::Convert_Sat_State($sat->pos, $sat->vel);
/* get the velocity of the satellite */
$sat->vel->w = sqrt($sat->vel->x * $sat->vel->x + $sat->vel->y * $sat->vel->y + $sat->vel->z * $sat->vel->z);
$sat->velo = $sat->vel->w;
Predict_SGPObs::Calculate_Obs($jul_utc, $sat->pos, $sat->vel, $obs_geodetic, $obs_set);
Predict_SGPObs::Calculate_LatLonAlt($jul_utc, $sat->pos, $sat_geodetic);
while ($sat_geodetic->lon < -Predict::pi) {
$sat_geodetic->lon += Predict::twopi;
}
while ($sat_geodetic->lon > Predict::pi) {
$sat_geodetic->lon -= Predict::twopi;
}
$sat->az = Predict_Math::Degrees($obs_set->az);
$sat->el = Predict_Math::Degrees($obs_set->el);
$sat->range = $obs_set->range;
$sat->range_rate = $obs_set->range_rate;
$sat->ssplat = Predict_Math::Degrees($sat_geodetic->lat);
$sat->ssplon = Predict_Math::Degrees($sat_geodetic->lon);
$sat->alt = $sat_geodetic->alt;
$sat->ma = Predict_Math::Degrees($sat->phase);
$sat->ma *= 256.0 / 360.0;
$sat->footprint = 2.0 * Predict::xkmper * acos (Predict::xkmper/$sat->pos->w);
$age = 0.0;
$sat->orbit = floor(($sat->tle->xno * Predict::xmnpda / Predict::twopi +
$age * $sat->tle->bstar * Predict::ae) * $age +
$sat->tle->xmo / Predict::twopi) + $sat->tle->revnum - 1;
/* orbit type */
$sat->otype = $sat->get_orbit_type($sat);
}
public function get_orbit_type(Predict_Sat $sat)
{
$orbit = Predict_SGPSDP::ORBIT_TYPE_UNKNOWN;
if ($this->geostationary($sat)) {
$orbit = Predict_SGPSDP::ORBIT_TYPE_GEO;
} else if ($this->decayed($sat)) {
$orbit = Predict_SGPSDP::ORBIT_TYPE_DECAYED;
} else {
$orbit = Predict_SGPSDP::ORBIT_TYPE_UNKNOWN;
}
return $orbit;
}
/** Determinte whether satellite is in geostationary orbit.
* @author John A. Magliacane, KD2BD
* @param sat Pointer to satellite data.
* @return TRUE if the satellite appears to be in geostationary orbit,
* FALSE otherwise.
*
* A satellite is in geostationary orbit if
*
* fabs (sat.meanmotion - 1.0027) < 0.0002
*
* Note: Appearantly, the mean motion can deviate much more from 1.0027 than 0.0002
*/
public function geostationary(Predict_Sat $sat)
{
if (abs($sat->meanmo - 1.0027) < 0.0002) {
return true;
} else {
return false;
}
}
/** Determine whether satellite has decayed.
* @author John A. Magliacane, KD2BD
* @author Alexandru Csete, OZ9AEC
* @param sat Pointer to satellite data.
* @return TRUE if the satellite appears to have decayed, FALSE otherwise.
* @bug Modified version of the predict code but it is not tested.
*
* A satellite is decayed if
*
* satepoch + ((16.666666 - sat.meanmo) / (10.0*fabs(sat.drag))) < "now"
*
*/
public function decayed(Predict_Sat $sat)
{
/* tle.xndt2o/(twopi/xmnpda/xmnpda) is the value before converted the
value matches up with the value in predict 2.2.3 */
/*** FIXME decayed is treated as a static quantity.
It is time dependent. Also sat->jul_utc is often zero
when this function is called
***/
if ($sat->jul_epoch + ((16.666666 - $sat->meanmo) /
(10.0 * abs($sat->tle->xndt2o / (Predict::twopi / Predict::xmnpda / Predict::xmnpda)))) < $sat->jul_utc) {
return true;
} else {
return false;
}
}
/**
* Experimental attempt at calculating apparent magnitude. Known intrinsic
* magnitudes are listed inside the function for now.
*
* @param float $time The daynum the satellite is calculated for
* @param Predict_QTH $qth The observer location
*
* @return null on failure, float otherwise
*/
public function calculateApparentMagnitude($time, Predict_QTH $qth)
{
// Recorded intrinsic magnitudes and their respective
// illumination and distance from heavens-above.com
static $intrinsicMagnitudes = array(
'25544' => array(
'mag' => -1.3,
'illum' => .5,
'distance' => 1000,
)
);
// Return null if we don't have a record of the intrinsic mag
if (!isset($intrinsicMagnitudes[$this->tle->catnr])) {
return null;
}
$imag = $intrinsicMagnitudes[$this->tle->catnr];
// Convert the observer's geodetic info to radians and km so
// we can compare vectors
$observerGeo = new Predict_Geodetic();
$observerGeo->lat = Predict_Math::Radians($qth->lat);
$observerGeo->lon = Predict_Math::Radians($qth->lon);
$observerGeo->alt = $qth->alt * 1000;
// Now determine the sun and observer positions
$observerPos = new Predict_Vector();
$observerVel = new Predict_Vector();
$solarVector = new Predict_Vector();
Predict_Solar::Calculate_Solar_Position($time, $solarVector);
Predict_SGPObs::Calculate_User_PosVel($time, $observerGeo, $observerPos, $observerVel);
// Determine the solar phase and and thus the percent illumination
$observerSatPos = new Predict_Vector();
Predict_Math::Vec_Sub($this->pos, $observerPos, $observerSatPos);
$phaseAngle = Predict_Math::Degrees(Predict_Math::Angle($solarVector, $observerSatPos));
$illum = $phaseAngle / 180;
$illuminationChange = $illum / $imag['illum'];
$inverseSquareOfDistanceChange = pow(($imag['distance'] / $this->range), 2);
$changeInMagnitude = log(
$illuminationChange * $inverseSquareOfDistanceChange,
self::POGSONS_RATIO
);
return $imag['mag'] - $changeInMagnitude;
}
}

115
predict/Predict/Solar.php Normal file
View File

@@ -0,0 +1,115 @@
<?php
/*
* Ported from gpredict to PHP by Bill Shupp
*/
require_once realpath(__DIR__ . "/../../predict/Predict.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Math.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Time.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Vector.php");
require_once realpath(__DIR__ . "/../../predict/Predict/Geodetic.php");
require_once realpath(__DIR__ . "/../../predict/Predict/ObsSet.php");
require_once realpath(__DIR__ . "/../../predict/Predict/SGPObs.php");
/*
* Unit Solar
* Author: Dr TS Kelso
* Original Version: 1990 Jul 29
* Current Revision: 1999 Nov 27
* Version: 1.30
* Copyright: 1990-1999, All Rights Reserved
*
* Ported to C by: Neoklis Kyriazis April 1 2001
*/
class Predict_Solar
{
/* Calculates solar position vector */
public static function Calculate_Solar_Position($time, Predict_Vector $solar_vector)
{
$mjd = $time - 2415020.0;
$year = 1900 + $mjd / 365.25;
$T = ($mjd + Predict_Time::Delta_ET($year) / Predict::secday) / 36525.0;
$M = Predict_Math::Radians(Predict_Math::Modulus(358.47583 + Predict_Math::Modulus(35999.04975 * $T, 360.0)
- (0.000150 + 0.0000033 * $T) * ($T * $T), 360.0));
$L = Predict_Math::Radians(Predict_Math::Modulus(279.69668 + Predict_Math::Modulus(36000.76892 * $T, 360.0)
+ 0.0003025 * ($T * $T), 360.0));
$e = 0.01675104 - (0.0000418 + 0.000000126 * $T) * $T;
$C = Predict_Math::Radians((1.919460 - (0.004789 + 0.000014 * $T) * $T) * sin($M)
+ (0.020094 - 0.000100 * $T) * sin(2 * $M) + 0.000293 * sin(3 * $M));
$O = Predict_Math::Radians(Predict_Math::Modulus(259.18 - 1934.142 * $T, 360.0));
$Lsa = Predict_Math::Modulus($L + $C - Predict_Math::Radians(0.00569 - 0.00479 * sin($O)), Predict::twopi);
$nu = Predict_Math::Modulus($M + $C, Predict::twopi);
$R = 1.0000002 * (1 - ($e * $e)) / (1 + $e * cos($nu));
$eps = Predict_Math::Radians(23.452294 - (0.0130125 + (0.00000164 - 0.000000503 * $T) * $T) * $T + 0.00256 * cos($O));
$R = Predict::AU * $R;
$solar_vector->x = $R * cos($Lsa);
$solar_vector->y = $R * sin($Lsa) * cos($eps);
$solar_vector->z = $R * sin($Lsa) * sin($eps);
$solar_vector->w = $R;
}
/* Calculates stellite's eclipse status and depth */
public static function Sat_Eclipsed(Predict_Vector $pos, Predict_Vector $sol, &$depth)
{
$Rho = new Predict_Vector();
$earth = new Predict_Vector();
/* Determine partial eclipse */
$sd_earth = Predict_Math::ArcSin(Predict::xkmper / $pos->w);
Predict_Math::Vec_Sub($sol, $pos, $Rho);
$sd_sun = Predict_Math::ArcSin(Predict::__sr__ / $Rho->w);
Predict_Math::Scalar_Multiply(-1, $pos, $earth);
$delta = Predict_Math::Angle($sol, $earth);
$depth = $sd_earth - $sd_sun - $delta;
if ($sd_earth < $sd_sun) {
return 0;
} else if ($depth >= 0) {
return 1;
} else {
return 0;
}
}
/**
* Finds the current location of the sun based on the observer location
*
* @param Predict_QTH $qth The observer location
* @param int $daynum The daynum or null to use the current daynum
*
* @return Predict_ObsSet
*/
public static function FindSun(Predict_QTH $qth, $daynum = null)
{
if ($daynum === null) {
$daynum = Predict_Time::get_current_daynum();
}
$obs_geodetic = new Predict_Geodetic();
$obs_geodetic->lon = $qth->lon * Predict::de2ra;
$obs_geodetic->lat = $qth->lat * Predict::de2ra;
$obs_geodetic->alt = $qth->alt / 1000.0;
$obs_geodetic->theta = 0;
$solar_vector = new Predict_Vector();
$zero_vector = new Predict_Vector();
$solar_set = new Predict_ObsSet();
self::Calculate_Solar_Position($daynum, $solar_vector);
Predict_SGPObs::Calculate_Obs(
$daynum,
$solar_vector,
$zero_vector,
$obs_geodetic,
$solar_set
);
$solar_set->az = Predict_Math::Degrees($solar_set->az);
$solar_set->el = Predict_Math::Degrees($solar_set->el);
return $solar_set;
}
}

232
predict/Predict/TLE.php Normal file
View File

@@ -0,0 +1,232 @@
<?php
/**
* Ported from gpredict to PHP by Bill Shupp. License: GPL 2.
*/
require_once realpath(__DIR__ . "/../Predict/Exception.php");
/**
* Predict_TLE
*
* All routines for parsing and validating NORAD two line element sets
*/
class Predict_TLE
{
public $header; /* Header line of TLE file */
public $line1; /* Line 1 of TLE */
public $line2; /* Line 2 of TLE */
public $epoch; /*!< Epoch Time in NORAD TLE format YYDDD.FFFFFFFF */
public $epoch_year; /*!< Epoch: year */
public $epoch_day; /*!< Epoch: day of year */
public $epoch_fod; /*!< Epoch: Fraction of day. */
public $xndt2o; /*!< 1. time derivative of mean motion */
public $xndd6o; /*!< 2. time derivative of mean motion */
public $bstar; /*!< Bstar drag coefficient. */
public $xincl; /*!< Inclination */
public $xnodeo; /*!< R.A.A.N. */
public $eo; /*!< Eccentricity */
public $omegao; /*!< argument of perigee */
public $xmo; /*!< mean anomaly */
public $xno; /*!< mean motion */
public $catnr; /*!< Catalogue Number. */
public $elset; /*!< Element Set number. */
public $revnum; /*!< Revolution Number at epoch. */
public $sat_name; /*!< Satellite name string. */
public $idesg; /*!< International Designator. */
public $status; /*!< Operational status. */
/* values needed for squint calculations */
public $xincl1;
public $xnodeo1;
public $omegao1;
/* Converts the strings in a raw two-line element set */
/* to their intended numerical values. No processing */
/* of these values is done, e.g. from deg to rads etc. */
/* This is done in the select_ephemeris() function. */
public function __construct($header, $line1, $line2)
{
if (!$this->Good_Elements($line1, $line2)) {
throw new Predict_Exception('Invalid TLE contents');
}
$this->header = $header;
$this->line1 = $line1;
$this->line2 = $line2;
/** Decode Card 1 **/
/* Satellite's catalogue number */
$this->catnr = (int) substr($line1, 2, 5);
/* International Designator for satellite */
$this->idesg = substr($line1, 9, 8);
/* Epoch time; this is the complete, unconverted epoch. */
/* Replace spaces with 0 before casting, as leading spaces are allowed */
$this->epoch = (float) str_replace(' ', '0', substr($line1, 18, 14));
/* Now, convert the epoch time into year, day
and fraction of day, according to:
YYDDD.FFFFFFFF
*/
// Adjust for 2 digit year through 2056
$this->epoch_year = (int) substr($line1, 18, 2);
if ($this->epoch_year > 56) {
$this->epoch_year = $this->epoch_year + 1900;
} else {
$this->epoch_year = $this->epoch_year + 2000;
}
/* Epoch day */
$this->epoch_day = (int) substr($line1, 20, 3);
/* Epoch fraction of day */
$this->epoch_fod = (float) substr($line1, 23, 9);
/* Satellite's First Time Derivative */
$this->xndt2o = (float) substr($line1, 33, 10);
/* Satellite's Second Time Derivative */
$this->xndd6o = (float) (substr($line1, 44, 1) . '.' . substr($line1, 45, 5) . 'E' . substr($line1, 50, 2));
/* Satellite's bstar drag term
FIXME: How about buff[0] ????
*/
$this->bstar = (float) (substr($line1, 53, 1) . '.' . substr($line1, 54, 5) . 'E' . substr($line1, 59, 2));
/* Element Number */
$this->elset = (int) substr($line1, 64, 4);
/** Decode Card 2 **/
/* Satellite's Orbital Inclination (degrees) */
$this->xincl = (float) substr($line2, 8, 8);
/* Satellite's RAAN (degrees) */
$this->xnodeo = (float) substr($line2, 17, 8);
/* Satellite's Orbital Eccentricity */
$this->eo = (float) ('.' . substr($line2, 26, 7));
/* Satellite's Argument of Perigee (degrees) */
$this->omegao = (float) substr($line2, 34, 8);
/* Satellite's Mean Anomaly of Orbit (degrees) */
$this->xmo = (float) substr($line2, 43, 8);
/* Satellite's Mean Motion (rev/day) */
$this->xno = (float) substr($line2, 52, 11);
/* Satellite's Revolution number at epoch */
$this->revnum = (float) substr($line2, 63, 5);
}
/* Calculates the checksum mod 10 of a line from a TLE set and */
/* returns true if it compares with checksum in column 68, else false.*/
/* tle_set is a character string holding the two lines read */
/* from a text file containing NASA format Keplerian elements. */
/* NOTE!!! The stuff about two lines is not quite true.
The function assumes that tle_set[0] is the begining
of the line and that there are 68 elements - see the consumer
*/
public function Checksum_Good($tle_set)
{
if (strlen($tle_set) < 69) {
return false;
}
$checksum = 0;
for ($i = 0; $i < 68; $i++) {
if (($tle_set[$i] >= '0') && ($tle_set[$i] <= '9')) {
$value = $tle_set[$i] - '0';
} else if ($tle_set[$i] == '-' ) {
$value = 1;
} else {
$value = 0;
}
$checksum += $value;
}
$checksum %= 10;
$check_digit = $tle_set[68] - '0';
return $checksum == $check_digit;
}
/* Carries out various checks on a TLE set to verify its validity */
/* $line1 is the first line of the TLE, $line2 is the second line */
/* from a text file containing NASA format Keplerian elements. */
public function Good_Elements($line1, $line2)
{
/* Verify checksum of both lines of a TLE set */
if (!$this->Checksum_Good($line1) || !$this->Checksum_Good($line2)) {
return false;
}
/* Check the line number of each line */
if (($line1[0] != '1') || ($line2[0] != '2')) {
return false;
}
/* Verify that Satellite Number is same in both lines */
if (strncmp($line1[2], $line2[2], 5) != 0) {
return false;
}
/* Check that various elements are in the right place */
if (($line1[23] != '.') ||
($line1[34] != '.') ||
($line2[11] != '.') ||
($line2[20] != '.') ||
($line2[37] != '.') ||
($line2[46] != '.') ||
($line2[54] != '.') ||
(strncmp(substr($line1, 61), ' 0 ', 3) != 0)) {
return false;
}
return true;
}
/**
* A function to allow checksum creation of a line. This is driven by
* the fact that some TLEs from SpaceTrack are missing checksum numbers.
* You can use this to create a checksum for a line, but you should
* probably have confidence that the TLE data itself is good. YMMV.
*
* @throws Predict_Exception if the line is not exactly 68 chars
* @return string
*/
static public function createChecksum($line)
{
if (strlen($line) != 68) {
throw Predict_Exception('Invalid line, needs to e 68 chars');
}
$checksum = 0;
for ($i = 0; $i < 68; $i++) {
if (($line[$i] >= '0') && ($line[$i] <= '9')) {
$value = (int) $line[$i];
} else if ($line[$i] == '-' ) {
$value = 1;
} else {
$value = 0;
}
$checksum += $value;
}
$checksum %= 10;
return $checksum;
}
}

222
predict/Predict/Time.php Normal file
View File

@@ -0,0 +1,222 @@
<?php
/*
* Functions from sgp_time.c and time-tools.c (except where noted)
* ported to PHP by Bill Shupp
*/
require_once realpath(__DIR__ . "/../Predict.php");
require_once realpath(__DIR__ . "/../Predict/Math.php");
/*
* Unit SGP_Time
* Author: Dr TS Kelso
* Original Version: 1992 Jun 02
* Current Revision: 2000 Jan 22
* Modified for Y2K: 1999 Mar 07
* Version: 2.05
* Copyright: 1992-1999, All Rights Reserved
* Version 1.50 added Y2K support. Due to limitations in the current
* format of the NORAD two-line element sets, however, only dates
* through 2056 December 31/2359 UTC are valid.
* Version 1.60 modifies Calendar_Date to ensure date matches time
* resolution and modifies Time_of_Day to make it more robust.
* Version 2.00 adds Julian_Date, Date_Time, and Check_Date to support
* checking for valid date/times, permitting the use of Time_to_UTC and
* Time_from_UTC for UTC/local time conversions.
* Version 2.05 modifies UTC_offset to allow non-integer offsets.
*
* Ported to C by: Neoklis Kyriazis April 9 2001
*/
class Predict_Time
{
/* The function Julian_Date_of_Epoch returns the Julian Date of */
/* an epoch specified in the format used in the NORAD two-line */
/* element sets. It has been modified to support dates beyond */
/* the year 1999 assuming that two-digit years in the range 00-56 */
/* correspond to 2000-2056. Until the two-line element set format */
/* is changed, it is only valid for dates through 2056 December 31. */
public static function Julian_Date_of_Epoch($epoch)
{
$year = 0;
/* Modification to support Y2K */
/* Valid 1957 through 2056 */
$day = self::modf($epoch * 1E-3, $year) * 1E3;
if ($year < 57) {
$year = $year + 2000;
} else {
$year = $year + 1900;
}
/* End modification */
return self::Julian_Date_of_Year($year) + $day;
}
/* Equivalent to the C modf function */
public static function modf($x, &$ipart) {
$ipart = (int)$x;
return $x - $ipart;
}
/* The function Julian_Date_of_Year calculates the Julian Date */
/* of Day 0.0 of {year}. This function is used to calculate the */
/* Julian Date of any date by using Julian_Date_of_Year, DOY, */
/* and Fraction_of_Day. */
public static function Julian_Date_of_Year($year)
{
/* Astronomical Formulae for Calculators, Jean Meeus, */
/* pages 23-25. Calculate Julian Date of 0.0 Jan year */
$year = $year - 1;
$i = (int) ($year / 100);
$A = $i;
$i = (int) ($A / 4);
$B = (int) (2 - $A + $i);
$i = (int) (365.25 * $year);
$i += (int) (30.6001 * 14);
$jdoy = $i + 1720994.5 + $B;
return $jdoy;
}
/* The function ThetaG calculates the Greenwich Mean Sidereal Time */
/* for an epoch specified in the format used in the NORAD two-line */
/* element sets. It has now been adapted for dates beyond the year */
/* 1999, as described above. The function ThetaG_JD provides the */
/* same calculation except that it is based on an input in the */
/* form of a Julian Date. */
public static function ThetaG($epoch, Predict_DeepArg $deep_arg)
{
/* Reference: The 1992 Astronomical Almanac, page B6. */
// double year,day,UT,jd,TU,GMST,_ThetaG;
/* Modification to support Y2K */
/* Valid 1957 through 2056 */
$year = 0;
$day = self::modf($epoch * 1E-3, $year) * 1E3;
if ($year < 57) {
$year += 2000;
} else {
$year += 1900;
}
/* End modification */
$UT = fmod($day, $day);
$jd = self::Julian_Date_of_Year($year) + $day;
$TU = ($jd - 2451545.0) / 36525;
$GMST = 24110.54841 + $TU * (8640184.812866 + $TU * (0.093104 - $TU * 6.2E-6));
$GMST = Predict_Math::Modulus($GMST + Predict::secday * Predict::omega_E * $UT, Predict::secday);
$deep_arg->ds50 = $jd - 2433281.5 + $UT;
return Predict_Math::FMod2p(6.3003880987 * $deep_arg->ds50 + 1.72944494);
}
/* See the ThetaG doc block above */
public static function ThetaG_JD($jd)
{
/* Reference: The 1992 Astronomical Almanac, page B6. */
$UT = Predict_Math::Frac($jd + 0.5);
$jd = $jd - $UT;
$TU = ($jd - 2451545.0) / 36525;
$GMST = 24110.54841 + $TU * (8640184.812866 + $TU * (0.093104 - $TU * 6.2E-6));
$GMST = Predict_Math::Modulus($GMST + Predict::secday * Predict::omega_E * $UT, Predict::secday);
return Predict::twopi * $GMST / Predict::secday;
}
/**
* Read the system clock and return the current Julian day. From phpPredict
*
* @return float
*/
public static function get_current_daynum() {
// Gets the current decimal day number from microtime
list($usec, $sec) = explode(' ', microtime());
return self::unix2daynum($sec, $usec);
}
/**
* Converts a standard unix timestamp and optional
* milliseconds to a daynum
*
* @param int $sec Seconds from the unix epoch
* @param int $usec Optional milliseconds
*
* @return float
*/
public static function unix2daynum($sec, $usec = 0)
{
$time = ((($sec + $usec) / 86400.0) - 3651.0);
return $time + 2444238.5;
}
/* The function Delta_ET has been added to allow calculations on */
/* the position of the sun. It provides the difference between UT */
/* (approximately the same as UTC) and ET (now referred to as TDT).*/
/* This function is based on a least squares fit of data from 1950 */
/* to 1991 and will need to be updated periodically. */
public static function Delta_ET($year)
{
/* Values determined using data from 1950-1991 in the 1990
Astronomical Almanac. See DELTA_ET.WQ1 for details. */
$delta_et = 26.465 + 0.747622 * ($year - 1950) +
1.886913 * sin(Predict::twopi * ($year - 1975) / 33);
return $delta_et;
}
/**
* Converts a daynum to a unix timestamp. From phpPredict.
*
* @param float $dn Julian Daynum
*
* @return float
*/
public static function daynum2unix($dn) {
// Converts a daynum to a UNIX timestamp
return (86400.0 * ($dn - 2444238.5 + 3651.0));
}
/**
* Converts a daynum to a readable time format.
*
* @param float $dn The julian date
* @param string $zone The zone string, defaults to America/Los_Angeles
* @param string $format The date() function's format string. Defaults to m-d-Y H:i:s
*
* @return string
*/
public static function daynum2readable($dn, $zone = 'America/Los_Angeles', $format = 'm-d-Y H:i:s')
{
$unix = self::daynum2unix($dn);
$date = new DateTime("@" . round($unix));
$dateTimezone = new DateTimezone($zone);
$date->setTimezone($dateTimezone);
return $date->format($format);
}
/**
* Returns the unix timestamp of a TLE's epoch
*
* @param Predict_TLE $tle The TLE object
*
* @return int
*/
public static function getEpochTimeStamp(Predict_TLE $tle)
{
$year = $tle->epoch_year;
$day = $tle->epoch_day;
$sec = round(86400 * $tle->epoch_fod);
$zone = new DateTimeZone('GMT');
$date = new DateTime();
$date->setTimezone($zone);
$date->setDate($year, 1, 1);
$date->setTime(0, 0, 0);
return $date->format('U') + (86400 * $day) + $sec - 86400;
}
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Predict_Vector - General three-dimensional vector structure.
* Ported from sgp4sdp4.h.
*/
class Predict_Vector
{
public $x = 0;
public $y = 0;
public $z = 0;
public $w = 0;
}

View File

35
predict/README.md Normal file
View File

@@ -0,0 +1,35 @@
# Overview
Predict is a partial PHP port of the [Gpredict](http://gpredict.oz9aec.net/) program
that allows real-time tracking and orbit prediction of satellites from two line
element sets. It supports the SGP4 and SDP4 [models](http://en.wikipedia.org/wiki/Simplified_perturbations_models) for prediction.
# Installation
Just clone this repo and try out the tests and examples from the root of the checkout.
# Examples/Tests
The tests directory includes a port of the original sgpsdp test files from
Gpredict. They are pretty close.
Included in the examples directory is a sample iss.tle (with an update script, which you
should run first). There are two examples, the visible_passes.php script and the benchmark.php
script. The former is for generating visible pass predictions of the ISS, and its output is
similar to what you might get from the Heavens-Above website, and it is heavily commented.
The latter just does predictions for benchmarking with xhprof.
You can see an image of a Predict/Google Maps API mash-up I did for fun:
![Google Maps Mashup](https://raw.github.com/shupp/Predict/master/examples/google_maps_iss.png)
You can also see an example visible pass plotted from the polar view of the observer:
![Google Maps Mashup](https://raw.github.com/shupp/Predict/master/examples/pass_polar_plot.png)
# About this port
This port largely maintains the style and organization of the original C code, but
scopes methods into classes rather than leaving everything in the global scope.
The motivation for this is so that changes upstream can more easily be integrated over
time. Only the prediction routines have been ported.

19
predict/composer.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "predict/predict",
"type": "library",
"description": "Predict PHP",
"keywords": [
"sgp",
"sgp4",
"sdp4",
"satellite",
"iss"
],
"homepage": "https://github.com/shupp/Predict",
"authors": [
{
"name": "Bill Shupp",
"email": "bill@shupp.org"
}
]
}

View File

@@ -0,0 +1,63 @@
<?php
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
require_once('PEAR/PackageFileManager2.php');
PEAR::setErrorHandling(PEAR_ERROR_DIE);
$packagexml = new PEAR_PackageFileManager2;
$packagexml->setOptions(array(
'baseinstalldir' => '/',
'simpleoutput' => true,
'packagedirectory' => './',
'filelistgenerator' => 'file',
'ignore' => array('generatePackage.php', 'xhprof_lib/*'),
'dir_roles' => array(
'tests' => 'test',
'examples' => 'doc'
),
'exceptions' => array('README.md' => 'doc'),
));
$packagexml->setPackage('Predict');
$packagexml->setSummary('A partial port of the Gpredict program for satellite tracking');
$packagexml->setDescription(
'Predict is a partial PHP port of the Gpredict (http://gpredict.oz9aec.net/) program that '
. 'allows real-time tracking and orbit prediction of satellites from two line element sets. '
. 'It supports the SGP4 and SDP4 models for prediction.'
);
$packagexml->setChannel('shupp.github.com/pirum');
$packagexml->setAPIVersion('0.2.2');
$packagexml->setReleaseVersion('0.2.2');
$packagexml->setReleaseStability('alpha');
$packagexml->setAPIStability('alpha');
$packagexml->setNotes('
* Addec Predict_TLE::createChecksum()
* Updates to examples
');
$packagexml->setPackageType('php');
$packagexml->addRelease();
$packagexml->detectDependencies();
$packagexml->addMaintainer('lead',
'shupp',
'Bill Shupp',
'shupp@php.net');
$packagexml->setLicense('GPL v2.1',
'http://www.opensource.org/licenses/gpl-license.php');
$packagexml->setPhpDep('5.2.0');
$packagexml->setPearinstallerDep('1.4.0b1');
$packagexml->addExtensionDep('required', 'date');
$packagexml->generateContents();
$packagexml->writePackageFile();
?>

0
predict/index.html Normal file
View File

182
predict/package.xml Normal file
View File

@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.4" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>Predict</name>
<channel>shupp.github.com/pirum</channel>
<summary>A partial port of the Gpredict program for satellite tracking</summary>
<description>Predict is a partial PHP port of the Gpredict (http://gpredict.oz9aec.net/) program that allows real-time tracking and orbit prediction of satellites from two line element sets. It supports the SGP4 and SDP4 models for prediction.</description>
<lead>
<name>Bill Shupp</name>
<user>shupp</user>
<email>shupp@php.net</email>
<active>yes</active>
</lead>
<date>2014-03-06</date>
<time>13:28:41</time>
<version>
<release>0.2.2</release>
<api>0.2.2</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Addec Predict_TLE::createChecksum()
* Updates to examples
</notes>
<contents>
<dir baseinstalldir="/" name="/">
<dir name="examples">
<file name="benchmark.php" role="doc" />
<file name="findsun.php" role="doc" />
<file name="google_maps_iss.png" role="doc" />
<file name="iss.tle" role="doc" />
<file name="pass_polar_plot.png" role="doc" />
<file name="solar_position.php" role="doc" />
<file name="update_iss_tle.php" role="doc" />
<file name="visible_passes.php" role="doc" />
</dir> <!-- /examples -->
<dir name="Predict">
<file name="DeepArg.php" role="php" />
<file name="DeepStatic.php" role="php" />
<file name="Exception.php" role="php" />
<file name="Geodetic.php" role="php" />
<file name="Math.php" role="php" />
<file name="ObsSet.php" role="php" />
<file name="Pass.php" role="php" />
<file name="PassDetail.php" role="php" />
<file name="QTH.php" role="php" />
<file name="Sat.php" role="php" />
<file name="SGPObs.php" role="php" />
<file name="SGPSDP.php" role="php" />
<file name="SGSDPStatic.php" role="php" />
<file name="Solar.php" role="php" />
<file name="Time.php" role="php" />
<file name="TLE.php" role="php" />
<file name="Vector.php" role="php" />
</dir> <!-- /Predict -->
<dir name="tests">
<file name="Table.php" role="test" />
<file name="test-001.php" role="test" />
<file name="test-001.tle" role="test" />
<file name="test-002.php" role="test" />
<file name="test-002.tle" role="test" />
</dir> <!-- /tests -->
<file name="Predict.php" role="php" />
<file name="README.md" role="doc" />
</dir> <!-- / -->
</contents>
<dependencies>
<required>
<php>
<min>5.2.0</min>
</php>
<pearinstaller>
<min>1.4.0b1</min>
</pearinstaller>
<extension>
<name>date</name>
</extension>
</required>
</dependencies>
<phprelease />
<changelog>
<release>
<version>
<release>0.1.0</release>
<api>0.1.0</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<date>2011-08-22</date>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Initial release
</notes>
</release>
<release>
<version>
<release>0.1.1</release>
<api>0.1.1</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<date>2011-09-05</date>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Fixed minimum elevation bug in visible pass detection
* Updated precision of Pogson&apos;s Ratio, refactored calculate magnitude to be more readble, as well as added comments
</notes>
</release>
<release>
<version>
<release>0.1.2</release>
<api>0.1.2</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<date>2011-09-25</date>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Added Predict_Time::getEpochTimeStamp()
</notes>
</release>
<release>
<version>
<release>0.2.0</release>
<api>0.2.0</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<date>2012-05-21</date>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Fixed bug in Predict_Time::unix2daynum()
* Updated example iss.tle file
</notes>
</release>
<release>
<version>
<release>0.2.1</release>
<api>0.2.1</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<date>2012-07-04</date>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Corrected role of README.md
</notes>
</release>
<release>
<version>
<release>0.2.2</release>
<api>0.2.2</api>
</version>
<stability>
<release>alpha</release>
<api>alpha</api>
</stability>
<date>2014-03-06</date>
<license uri="http://www.opensource.org/licenses/gpl-license.php">GPL v2.1</license>
<notes>
* Addec Predict_TLE::createChecksum()
* Updates to examples
</notes>
</release>
</changelog>
</package>