1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
|
/*
sat_id.cpp 8 March 2003, with updates as listed below
An example 'main' function illustrating how to find which satellite(s)
are within a given radius of a given RA/dec, as seen from a given
point. The code reads in a file of observations in MPC format (name
provided as the first command-line argument). For example:
sat_id mpc_obs.txt
would hunt through the file 'mpc_obs.txt' for MPC-formatted
observations. It would then read the file 'alldat.tle', looking
for corresponding satellites within .2 degrees of said observations.
It then spits out the original file, with satellite IDs added
(when found) after each observation line. For each IDed satellite,
the international and NORAD designations are given, along with
its angular distance from the search point, position angle of
motion, and apparent angular rate of motion in arcminutes/second
(or, equivalently, degrees/minute). */
/* 2 July 2003: fixed the day/month/year to JD part of 'get_mpc_data()'
so it will work for all years >= 0 (previously, it worked for years
2000 to 2099... plenty for the practical purpose of ID'ing recently-found
satellites, but this is also an 'example' program.) */
/* 3 July 2005: revised the check on the return value for parse_elements().
Now elements with bad checksums won't be rejected. */
/* 23 June 2006: after comment from Eric Christensen, revised to use
names 'ObsCodes.html' or 'ObsCodes.htm', with 'stations.txt' being a
third choice. Also added the '-a' command line switch to cause the program
to show all lines from input (default is now that only MPC astrometric
input gets echoed.) */
/* 30 June 2006: further comment from Eric Christensen: when computing
object motion from two consecutive observations, if the second one has
a date/time preceding the first, you get a negative rate of motion that's
off by 180 degrees. Fixed this. */
/* 17 Nov 2006: artificial satellite data is now being provided in a
file named 'ALL_TLE.TXT'. I've modified the default TLE to match. */
/* 22 Oct 2012: minor cosmetic changes, such as making constant variables
of type 'const', updating URL for the MPC station code file, adding a
comment or two. */
/* 7 Jan 2013: revised output to show satellite name if available, plus
the eccentricity, orbital period, and inclination. */
/* 2013 Dec 8: revised to pay attention to "# MJD" and "#Ephem start"
lines, for files that contain many TLEs covering different time spans
for the same object. I sometimes create such files; when that happens,
for each observation, only the TLE(s) covering that observation's time
should be used, and the others are suppressed. */
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>
#if defined( _WIN32) || defined( __WATCOMC__)
#include <malloc.h> /* for alloca() prototype */
#endif
#include "norad.h"
#include "observe.h"
#ifdef FRENCH_REPUBLICAN_CLOCK
#define hours_per_day 10
#define minutes_per_hour 100
#define seconds_per_minute 100
#else
#define hours_per_day 24
#define minutes_per_hour 60
#define seconds_per_minute 60
#endif
#define seconds_per_hour (seconds_per_minute * minutes_per_hour)
#define seconds_per_day (seconds_per_hour * hours_per_day)
#define minutes_per_day (minutes_per_hour * hours_per_day)
#define OBSERVATION struct observation
OBSERVATION
{
char text[81];
double jd, ra, dec;
double lon, rho_cos_phi, rho_sin_phi;
};
#define PI 3.1415926535897932384626433832795028841971693993751058209749445923
#define TIME_EPSILON (1./86400.)
static char *fgets_trimmed( char *buff, const int buffsize, FILE *ifile)
{
char *rval = fgets( buff, buffsize, ifile);
if( rval)
{
size_t i = 0;
while( rval[i] != 10 && rval[i] != 13 && rval[i])
i++;
rval[i] = '\0';
}
return( rval);
}
static double dmy_to_jd( const int year, const int month, const double day)
{
double rval = 1721059.5
+ (double)( year * 365 + year / 4 - year / 100 + year / 400) + day;
int i;
static const char month_len[12] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
assert( month >= 1 && month <= 12);
assert( day >= 1. && day < 32.);
assert( year > 1956 && year < 2050);
for( i = 0; i < month - 1; i++)
rval += (double)month_len[i];
if( month < 3 && !(year % 4)) /* leap years, January and February */
if( !(year % 400) || (year % 100))
rval--;
return( rval);
}
static int get_mpc_data( OBSERVATION *obs, const char *buff)
{
int i1, i2, year, month;
double tval, day;
if( strlen( buff) != 80)
return( -1);
if( sscanf( buff + 32, "%d %d %lf", &i1, &i2, &tval) != 3)
return( -2);
obs->ra = ((double)i1 + (double)i2 / 60. + tval / 3600.) * (PI / 12.);
if( sscanf( buff + 45, "%d %d %lf", &i1, &i2, &tval) != 3)
return( -3);
obs->dec = ((double)i1 + (double)i2 / 60. + tval / 3600.) * (PI / 180.);
if( buff[44] == '-')
obs->dec = -obs->dec;
else if( buff[44] != '+')
return( -4);
/* Read in the day/month/year from the record... */
if( sscanf( buff + 15, "%d %d %lf", &year, &month, &day) != 3)
return( -5);
if( year < 1957)
return( -6);
obs->jd = dmy_to_jd( year, month, day);
strcpy( obs->text, buff);
return( 0);
}
void make_config_dir_name( char *oname, const char *iname)
{
strcpy( oname, getenv( "HOME"));
strcat( oname, "/.find_orb/");
strcat( oname, iname);
}
/* This loads up the file 'ObsCodes.html' into memory on its first call.
Then, given an MPC code, it finds the corresponding line and copies
it into 'station_code_data'. It looks in several places for the file;
if you've installed Find_Orb, it should be able to get it from the
~/.find_orb directory. It also checks for the truncated 'ObsCodes.htm'
version of the file. */
int verbose = 0;
static int get_station_code_data( char *station_code_data,
const char *mpc_code)
{
static char *cached_data, *cached_ptr;
if( !mpc_code) /* freeing memory */
{
if( cached_data)
free( cached_data);
cached_data = cached_ptr = NULL;
return( 0);
}
*station_code_data = '\0';
if( !cached_data)
{
const char *filenames[2] = { "ObsCodes.html", "ObsCodes.htm" };
FILE *ifile = NULL;
size_t size;
int i;
for( i = 0; !ifile && i < 2; i++)
ifile = fopen( filenames[i], "rb");
for( i = 0; !ifile && i < 2; i++)
{
char filename[255];
make_config_dir_name( filename, filenames[i]);
ifile = fopen( filename, "rb");
}
if( !ifile)
{
printf( "Failed to find MPC station list 'ObsCodes.html'\n");
printf( "This can be downloaded at:\n\n");
printf( "http://www.minorplanetcenter.org/iau/lists/ObsCodes.html\n");
exit( -3);
}
fseek( ifile, 0L, SEEK_END);
size = (size_t)ftell( ifile);
fseek( ifile, 0L, SEEK_SET);
cached_data = (char *)malloc( size + 1);
if( fread( cached_data, 1, size, ifile) != size)
{
printf( "Failed to read station file\n");
exit( -4);
}
fclose( ifile);
cached_data[size] = '\0';
if( verbose)
printf( "Station codes: %u bytes read\n", (unsigned)size);
}
if( !cached_ptr || memcmp( cached_ptr, mpc_code, 3))
{
char search_buff[5];
sprintf( search_buff, "\n%.3s", mpc_code);
cached_ptr = strstr( cached_data, search_buff);
if( cached_ptr)
cached_ptr++;
}
if( cached_ptr)
{
size_t i;
for( i = 0; cached_ptr[i] >= ' '; i++)
station_code_data[i] = cached_ptr[i];
station_code_data[i] = '\0';
}
else
{
printf( "Station code '%s' not found.\n", mpc_code);
#ifdef ON_LINE_VERSION
printf( "If this is a new MPC code, it could be that this service needs to be\n");
printf( "updated to know about it. Please contact pluto at projectpluto.com so\n");
printf( "I can fix this.\n");
#else
printf( "If this is a new MPC code, you may need to get this file:\n");
printf( "http://www.minorplanetcenter.org/iau/lists/ObsCodes.html\n");
printf( "and replace the existing ObsCodes.html.\n");
#endif
}
return( cached_ptr ? 0 : -1);
}
/* Loads up MPC-formatted 80-column observations from a file. Makes
a pass to find out how many observations there are, allocates space
for them, then reads them again to actually load the observations. */
static OBSERVATION *get_observations_from_file( FILE *ifile, size_t *n_found,
const double t_low, const double t_high)
{
int pass;
OBSERVATION *rval = NULL, obs;
memset( &obs, 0, sizeof( OBSERVATION));
for( pass = 0; pass < 2; pass++)
{
char buff[100];
size_t count = 0;
fseek( ifile, 0L, SEEK_SET);
while( fgets_trimmed( buff, sizeof( buff), ifile))
if( !get_mpc_data( &obs, buff) && obs.jd > t_low
&& obs.jd < t_high)
{
if( rval)
{
char station_data[100];
j2000_to_epoch_of_date( obs.jd, &obs.ra, &obs.dec);
if( get_station_code_data( station_data, obs.text + 77))
printf( "FAILED to find MPC code %s\n", obs.text + 77);
sscanf( station_data + 3, "%lf %lf %lf", &obs.lon,
&obs.rho_cos_phi, &obs.rho_sin_phi);
obs.lon *= PI / 180.;
rval[count] = obs;
}
count++;
}
if( !pass && count)
rval = (OBSERVATION *)calloc( count, sizeof( OBSERVATION));
*n_found = count;
}
return( rval);
}
static int id_compare( const OBSERVATION *a, const OBSERVATION *b)
{
return( memcmp( a->text, b->text, 12));
}
static int compare_obs( const void *a, const void *b, void *context)
{
const OBSERVATION *aptr = (const OBSERVATION *)a;
const OBSERVATION *bptr = (const OBSERVATION *)b;
int rval = id_compare( aptr, bptr);
if( !rval) /* same IDs? Then sort by JD of observation */
rval = (aptr->jd > bptr->jd ? 1 : -1);
return( rval);
}
/* Copied straight from 'mpc_obs.cpp' in Find_Orb. See comments there. */
void shellsort_r( void *base, const size_t n_elements, const size_t esize,
int (*compare)(const void *, const void *, void *), void *context)
{
#if (defined _GNU_SOURCE || defined __GNU__ || defined __linux)
qsort_r( base, n_elements, esize, compare, context);
#else
size_t gap = 1;
char *data = (char *)base;
char *pivot = (char *)alloca( esize);
while( gap < n_elements)
gap = gap * 3 + 1;
while( gap /= 3)
{
size_t j;
const size_t spacing = esize * gap;
for( j = gap; j < n_elements; j++)
{
char *tptr = data + j * esize;
char *tptr2 = tptr - spacing;
if( (compare)( tptr2, tptr, context) > 0)
{
memcpy( pivot, tptr, esize);
memcpy( tptr, tptr2, esize);
tptr = tptr2;
tptr2 -= spacing;
while( tptr2 >= base && (compare)( tptr2, pivot, context) > 0)
{
memcpy( tptr, tptr2, esize);
tptr = tptr2;
tptr2 -= spacing;
}
memcpy( tptr, pivot, esize);
}
}
}
#endif
}
static void compute_offsets( double *dx, double *dy,
double delta_ra, const double dec1, const double dec2)
{
while( delta_ra > PI)
delta_ra -= PI + PI;
while( delta_ra < -PI)
delta_ra += PI + PI;
*dy = dec2 - dec1;
*dx = delta_ra * cos( (dec2 + dec1) / 2.);
}
static double angular_sep( const double delta_ra, const double dec1,
const double dec2)
{
double dx, dy;
compute_offsets( &dx, &dy, delta_ra, dec1, dec2);
return( sqrt( dx * dx + dy * dy));
}
/* Out of all observations for a given object, this function will pick
two that "best" describe the object's motion. For that purpose, we look
for a pair closest to 'optimal_dist' apart. We also limit the separation
in time to 'max_time_sep'; that's to avoid a situation where the observations
are really close to the optimal distance apart, but are actually from
different orbits.
This code thinks in terms of pairs of observations. If somebody insists
on providing a single observation, we duplicate it.
We also drop objects if they're moving slower than 'speed_cutoff',
set to help us ignore the slow guys that are almost certainly rocks. */
static size_t drop_extra_obs( OBSERVATION *obs, const size_t n_obs,
const double speed_cutoff)
{
size_t i = 0, rval = 0;
while( i < n_obs)
{
size_t j = 0;
OBSERVATION *optr = obs + i;
while( j < n_obs - i && !id_compare( optr, optr + j))
j++;
if( j == 1) /* singleton observation */
{
obs[rval++] = *optr;
obs[rval++] = *optr;
}
else /* two or more obs: pick two best */
{
size_t a, b, best_a = 1, best_b = 0;
double speed = 0.;
double best_score = 1e+30;
for( a = 1; a < j; a++)
for( b = 0; b < a; b++)
{
const double max_time_sep = 0.1; /* .1 day = 2.4 hr */
const double optimal_dist = PI / 180.; /* one degree */
const double dist = angular_sep( optr[b].ra - optr[a].ra,
optr[b].dec, optr[a].dec);
const double score = fabs( dist - optimal_dist);
const double dt = optr[a].jd - optr[b].jd;
assert( dt >= .0);
if( best_score > score
&& dt < max_time_sep && dt > 0.
&& !memcmp( &optr[a].text[77], &optr[b].text[77], 3))
{
best_score = score;
best_a = a;
best_b = b;
speed = dist / dt;
}
}
speed *= 180. / PI; /* cvt speed from radians/day to deg/day */
speed /= minutes_per_day; /* ...then to deg/hour = arcmin/minute */
if( speed > speed_cutoff) /* omit slow objects */
{
obs[rval++] = optr[best_b];
obs[rval++] = optr[best_a];
}
}
i += j;
}
return( rval);
}
static void error_exit( const int exit_code)
{
printf(
"sat_id takes the name of an input file of MPC-formatted (80-column)\n\
astrometry as a command-line argument. It searches for matches between\n\
the observation data and satellites in 'ALL_TLE.TXT'. By default, matches\n\
within .2 degrees are shown.\n\n\
Additional command-line arguments are:\n\
-a YYYYMMDD Only use observations after this time\n\
-b YYYYMMDD Only use observations before this time\n\
-m (nrevs) Only consider objects with fewer # revs/day (default=6)\n\
-n (NORAD) Only consider objects with this NORAD identifier\n\
-r (radius) Only show matches within this radius in degrees (default=4)\n\
-t (fname) Get TLEs from this filename\n\
-v Verbose output. '-v2' gets still more verboseness.\n\
-z (rate) Only consider observations above 'rate' deg/hr (default=.001)\n");
exit( exit_code);
}
static int compute_artsat_ra_dec( double *ra, double *dec, double *dist,
const OBSERVATION *optr, tle_t *tle,
const double *sat_params)
{
double observer_loc[3];
double pos[3]; /* Satellite position vector */
double t_since = (optr->jd - tle->epoch) * minutes_per_day;
int sxpx_rval;
observer_cartesian_coords( optr->jd, optr->lon,
optr->rho_cos_phi, optr->rho_sin_phi, observer_loc);
if( select_ephemeris( tle))
sxpx_rval = SDP4( t_since, tle, sat_params, pos, NULL);
else
sxpx_rval = SGP4( t_since, tle, sat_params, pos, NULL);
if( verbose > 2 && sxpx_rval)
printf( "TLE failed: %d\n", sxpx_rval);
get_satellite_ra_dec_delta( observer_loc, pos, ra, dec, dist);
return( sxpx_rval);
}
static bool is_in_range( const double jd, const double tle_start,
const double tle_range)
{
return( !tle_range || !tle_start ||
(jd >= tle_start && jd <= tle_start + tle_range));
}
static bool got_obs_in_range( const OBSERVATION *obs, size_t n_obs,
const double jd_start, const double jd_end)
{
// if( obs[0].jd < jd_end && obs[n_obs - 1].jd > jd_start)
while( n_obs--)
{
if( obs->jd > jd_start && obs->jd < jd_end)
return( true);
obs++;
}
return( false);
}
/* The computed and observed motions should match, but (obviously)
only to some tolerance. A tolerance of 0.001'/s seems to work. */
double motion_mismatch_limit = .001;
/* Given a set of MPC observations and a TLE file, this function looks at
each TLE in the file and checks to see if that satellite came close to any
of the observations. The function is called for each TLE file.
*/
int norad_id = 0;
double tle_start = 0., tle_range = 1e+9;
static int add_tle_to_obs( OBSERVATION *obs, const size_t n_obs,
const char *tle_file_name, const double search_radius,
const double max_revs_per_day)
{
char line0[100], line1[100], line2[100];
FILE *tle_file = fopen( tle_file_name, "rb");
int rval = 0, n_tles_found = 0;
bool check_updates = true;
if( !tle_file)
{
printf( "Couldn't open TLE file %s\n", tle_file_name);
return( -1);
}
if( verbose)
printf( "Looking through TLE file '%s', %u obs, radius %f, max %f revs/day\n",
tle_file_name, (unsigned)n_obs, search_radius, max_revs_per_day);
*line0 = *line1 = '\0';
while( fgets_trimmed( line2, sizeof( line2), tle_file))
{
tle_t tle; /* Structure for two-line elements set for satellite */
const double mins_per_day = 24. * 60.;
if( got_obs_in_range( obs, n_obs, tle_start, tle_start + tle_range)
&& parse_elements( line1, line2, &tle) >= 0
&& (tle.ephemeris_type == 'H'
|| tle.xno < 2. * PI * max_revs_per_day / mins_per_day)
&& (!norad_id || norad_id == tle.norad_number))
{ /* hey! we got a TLE! */
double sat_params[N_SAT_PARAMS];
if( verbose > 1)
printf( "TLE found:\n%s\n%s\n", line1, line2);
n_tles_found++;
if( select_ephemeris( &tle))
SDP4_init( sat_params, &tle);
else
SGP4_init( sat_params, &tle);
for( size_t idx = 0; idx < n_obs; idx += 2)
if( is_in_range( obs[idx].jd, tle_start, tle_range))
{
OBSERVATION *optr = obs + idx;
double dx, dy;
double radius;
double ra, dec, dist_to_satellite;
int sxpx_rval;
sxpx_rval = compute_artsat_ra_dec( &ra, &dec, &dist_to_satellite,
optr, &tle, sat_params);
compute_offsets( &dx, &dy, ra - optr->ra, dec, optr->dec);
radius = sqrt( dx * dx + dy * dy) * 180. / PI;
if( !sxpx_rval && radius < search_radius) /* good enough for us! */
{
double dx1, dy1;
const double dt = optr[1].jd - optr[0].jd;
double motion_diff;
compute_artsat_ra_dec( &ra, &dec, &dist_to_satellite,
optr + 1, &tle, sat_params);
compute_offsets( &dx1, &dy1, ra - optr[1].ra, dec, optr[1].dec);
dx1 -= dx;
dy1 -= dy;
motion_diff = sqrt( dx1 * dx1 + dy1 * dy1);
if( dt) /* convert separations/dist into speeds */
{
dx /= dt;
dy /= dt;
motion_diff /= dt;
}
assert( dt >= 0.);
motion_diff *= 180. / PI; /* now in degrees/day */
motion_diff /= minutes_per_day; /* now in arcmin/second */
if( motion_diff < motion_mismatch_limit)
{
char obuff[200];
char full_intl_desig[20];
double xvel, yvel;
double motion_rate = 0., motion_pa = 0.;
compute_offsets( &xvel, &yvel, optr[1].ra - optr[0].ra,
optr[1].dec, optr[0].dec);
if( dt)
{
motion_pa = atan2( yvel, xvel) * 180. / PI + 90.;
if( motion_pa < 0.)
motion_pa += 180.;
motion_rate = sqrt( xvel * xvel + yvel * yvel);
motion_rate /= dt;
motion_rate *= 180. / PI; /* now in degrees/day */
motion_rate /= 24.; /* now in degrees/hr = arcsec/second */
}
line1[8] = line1[16] = '\0';
memcpy( line1 + 30, line1 + 11, 6);
line1[11] = '\0';
sprintf( full_intl_desig, "%s%.2s-%s",
(tle.intl_desig[0] < '5' ? "20" : "19"),
tle.intl_desig, tle.intl_desig + 2);
sprintf( obuff, " %5dU = %-9s",
tle.norad_number, full_intl_desig);
sprintf( obuff + strlen( obuff),
"e=%.2f; P=%.1f min; i=%.1f",
tle.eo, 2. * PI / tle.xno,
tle.xincl * 180. / PI);
if( tle_checksum( line0)) /* object name given... */
sprintf( obuff + strlen( obuff), ": %s", line0);
obuff[79] = '\0'; /* avoid buffer overrun */
// sprintf( obuff + strlen( obuff), " motion %f", motion_diff);
strcat( obuff, "\n");
sprintf( obuff + strlen( obuff),
" motion %5.2f\"/sec at PA %.1f; dist=%8.1f km; offset=%5.2f deg\n",
motion_rate, motion_pa,
dist_to_satellite, radius);
/* "Speed" is displayed in arcminutes/second,
or in degrees/minute */
printf( "%s\n", obs[idx].text);
printf( "%s\n", obuff);
}
}
}
}
else if( !memcmp( line2, "# No updates", 12))
check_updates = false;
else if( !memcmp( line2, "# Ephem range:", 14))
{
const double mjd_1970 = 40587.; /* MJD for 1970 Jan 1 */
double mjd_start, mjd_end;
double curr_mjd = mjd_1970 + (double)time( NULL) / 86400.;
sscanf( line2 + 14, "%lf %lf %lf\n", &mjd_start, &mjd_end, &tle_range);
if( check_updates && mjd_end < curr_mjd + 7.)
printf( "WARNING: Update TLEs in '%s'\n", tle_file_name);
if( !got_obs_in_range( obs, n_obs, mjd_start + 2400000.5,
mjd_end + 2400000.5))
{
if( verbose)
printf( "'%s' contains no TLEs for our time range\n", tle_file_name);
fclose( tle_file);
return( 0);
}
}
else if( !memcmp( line2, "# Range: ", 9))
{
int year1, year2, month1, month2;
double day1, day2;
int n_read;
n_read = sscanf( line2 + 9, "%d-%d-%lf %d-%d-%lf",
&year1, &month1, &day1,
&year2, &month2, &day2);
assert( n_read == 6);
tle_start = dmy_to_jd( year1, month1, day1);
tle_range = dmy_to_jd( year2, month2, day2) - tle_start;
}
else if( !memcmp( line2, "# MJD ", 6))
tle_start = atof( line2 + 6) + 2400000.5;
else if( !memcmp( line2, "# Include ", 10))
{
if( got_obs_in_range( obs, n_obs, tle_start, tle_start + tle_range))
{
char iname[255];
size_t i = strlen( tle_file_name);
while( i && tle_file_name[i - 1] != '/')
i--;
memcpy( iname, tle_file_name, i);
strcpy( iname + i, line2 + 10);
rval = add_tle_to_obs( obs, n_obs, iname, search_radius,
max_revs_per_day);
}
tle_start = 0.;
tle_range = 1e+9;
}
strcpy( line0, line1);
strcpy( line1, line2);
}
if( verbose)
printf( "%d TLEs read from '%s'\n", n_tles_found, tle_file_name);
fclose( tle_file);
return( rval);
}
/* The "on-line version", sat_id2, gathers data from a CGI multipart form,
puts it into a file, possibly adds in some options, puts together the
command-line arguments, and then calls sat_id_main. See 'sat_id2.cpp'. */
static double get_time( const char *buff)
{
long year = atol( buff) / 10000L;
const long month = (atol( buff) / 100L) % 100L;
const int day = atol( buff) % 100L;
if( year < 40) /* before 2040 */
year += 2000;
if( year < 100) /* 1940-1999 */
year += 1900;
return( dmy_to_jd( (int)year, (int)month, (double)day));
}
#ifdef ON_LINE_VERSION
int sat_id_main( const int argc, const char **argv)
#else
int main( const int argc, const char **argv)
#endif
{
const char *tle_file_name = "tle_list.txt";
FILE *ifile = fopen( argv[1], "rb");
OBSERVATION *obs;
size_t n_obs;
double search_radius = 4; /* default to 4-degree search */
/* Asteroid searchers _sometimes_ report Molniyas to me,
which make two revolutions a day. This limit could safely
be set to three, but few artsats are between 6 and 3 revs/day
(i.e., in four to eight-hour orbits). So this doesn't result
in much extra checking. */
double max_revs_per_day = 6.;
/* Anything slower than 0.001'/sec is almost certainly not an
artsat. We don't even bother to check those (unless the -z
option is used to reset this limit). */
double speed_cutoff = 0.001;
double t_low = 2435839.5; /* no satellites before 1957 Jan 1 */
double t_high = 3000000.5;
int rval;
if( argc == 1)
{
printf( "No input file of astrometry specified on command line\n\n");
error_exit( -2);
}
for( int i = 1; i < argc; i++)
if( argv[i][0] == '-')
{
const char *param = argv[i] + 2;
if( !*param && i < argc - 1)
param = argv[i + 1];
switch( argv[i][1])
{
case 'a':
t_low = get_time( param);
break;
case 'b':
t_high = get_time( param);
break;
case 'r':
search_radius = atof( param);
break;
case 'y':
motion_mismatch_limit = atof( param);
break;
case 'm':
max_revs_per_day = atof( param);
break;
case 'n':
norad_id = atoi( param);
break;
case 't':
tle_file_name = param;
break;
case 'v':
verbose = atoi( param) + 1;
break;
// case 'd':
// debug_level = atoi( param);
// break;
case 'z':
speed_cutoff = atof( param);
break;
default:
printf( "Unrecognized command-line option '%s'\n", argv[i]);
error_exit( -3);
break;
}
}
if( verbose)
for( int i = 0; i < argc; i++)
printf( "Arg %d: '%s'\n", i, argv[i]);
if( !ifile)
{
printf( "Couldn't open input file %s\n", argv[1]);
return( -1);
}
obs = get_observations_from_file( ifile, &n_obs, t_low, t_high);
fclose( ifile);
printf( "%d observations found\n", (int)n_obs);
shellsort_r( obs, n_obs, sizeof( obs[0]), compare_obs, NULL);
n_obs = drop_extra_obs( obs, n_obs, speed_cutoff);
printf( "%d observations left after dropping extras\n", (int)n_obs);
if( !obs || !n_obs)
return( -2);
rval = add_tle_to_obs( obs, n_obs, tle_file_name, search_radius,
max_revs_per_day);
free( obs);
get_station_code_data( NULL, NULL);
printf( "%.1f seconds elapsed\n", (double)clock( ) / (double)CLOCKS_PER_SEC);
return( rval);
} /* End of main() */
|