├── data ├── frequencies.txt └── sites.txt ├── tests ├── data │ ├── empty.tle │ ├── alpha5.tle │ └── catalog.tle ├── tests_rftles.h ├── tests_rffft_internal.h ├── tests.c ├── tests_rffft_internal.c └── tests_rftles.c ├── rftime.h ├── zscale.h ├── contrib ├── requirements.txt ├── plot_spectrum.py ├── settings.py ├── download_satnogs_tle.py ├── spectrum_plot.py ├── find_good_satnogs_artifacts.py ├── spectrum_parser.py ├── README.md ├── download_satnogs_artifact.py ├── analyze_artifact.py └── satnogs_waterfall_tabulation_helper.py ├── rfsites.h ├── .gitignore ├── rfio.h ├── .github └── workflows │ └── github-actions.yml ├── env-dist ├── rftrace.h ├── ferror.c ├── rftles.h ├── rffft_internal.h ├── simplex.c ├── satutl.h ├── rfinfo.c ├── rfsites.c ├── rftime.c ├── tleupdate ├── Makefile.linux ├── rfdop.c ├── GUIDE.md ├── makefile ├── dsmin.c ├── Makefile.osx ├── rfedit.c ├── rftles.c ├── versafit.c ├── rffind.c ├── zscale.c ├── README.md ├── rfio.c ├── rffft_internal.c ├── satutl.c ├── sgdp4h.h ├── rffft.c ├── rftrace.c └── rfpng.c /data/frequencies.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/data/empty.tle: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rftime.h: -------------------------------------------------------------------------------- 1 | double nfd2mjd(char *date); 2 | double date2mjd(int year,int month,double day); 3 | void mjd2nfd(double mjd,char *nfd); 4 | -------------------------------------------------------------------------------- /zscale.h: -------------------------------------------------------------------------------- 1 | #ifndef ZSCALE_H 2 | #define ZSCALE_H 3 | 4 | #include "rfio.h" 5 | 6 | void zscale(struct spectrogram *image, int nsamples, double contrast, double *z1, double *z2); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /contrib/requirements.txt: -------------------------------------------------------------------------------- 1 | astropy 2 | matplotlib 3 | numpy 4 | Pillow 5 | python-decouple 6 | requests 7 | git+https://gitlab.com/kerel-fs/python-satnogs-api.git@0.2 8 | h5py~=3.6.0 9 | pandas~=1.3.4 10 | ephem~=4.1.4 11 | -------------------------------------------------------------------------------- /tests/tests_rftles.h: -------------------------------------------------------------------------------- 1 | #ifndef _TESTS_RFTLES_H 2 | #define _TESTS_RFTLES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int run_tle_tests(); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | 14 | #endif /* _TESTS_RFTLES_H */ 15 | -------------------------------------------------------------------------------- /tests/tests_rffft_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef _TESTS_RFFFT_INTERNAL_H 2 | #define _TESTS_RFFFT_INTERNAL_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int run_rffft_internal_tests(); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | 14 | #endif /* _TESTS_RFFFT_INTERNAL_H */ 15 | -------------------------------------------------------------------------------- /tests/data/alpha5.tle: -------------------------------------------------------------------------------- 1 | 0 ISS (ZARYA) 2 | 1 B5544U 98067A 24356.58519896 .00014389 00000-0 25222-3 0 9992 3 | 2 B5544 51.6403 106.8969 0007877 6.1421 113.2479 15.50801739487615 4 | 0 ISS (ZARYA) 5 | 1 Z9999U 98067A 24356.58519896 .00014389 00000-0 25222-3 0 9992 6 | 2 Z9999 51.6403 106.8969 0007877 6.1421 113.2479 15.50801739487615 7 | -------------------------------------------------------------------------------- /rfsites.h: -------------------------------------------------------------------------------- 1 | #ifndef _RFSITES_H 2 | #define _RFSITES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | typedef struct site { 9 | int id; 10 | double lat,lng; 11 | float alt; 12 | char observer[64]; 13 | } site_t; 14 | 15 | site_t get_site(int site_id); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif /* _RFSITES_H */ 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | 9 | # Shared objects (inc. Windows DLLs) 10 | *.dll 11 | *.so 12 | *.so.* 13 | *.dylib 14 | 15 | # Executables 16 | *.exe 17 | *.out 18 | *.app 19 | 20 | /rfdop 21 | /rfedit 22 | /rffft 23 | /rffind 24 | /rffit 25 | /rfinfo 26 | /rfplot 27 | /rfpng 28 | /rfinfo 29 | /tests/tests 30 | 31 | .env 32 | -------------------------------------------------------------------------------- /tests/tests.c: -------------------------------------------------------------------------------- 1 | #include "tests_rffft_internal.h" 2 | #include "tests_rftles.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main(void) { 11 | int failures = 0; 12 | 13 | failures += run_rffft_internal_tests(); 14 | failures += run_tle_tests(); 15 | 16 | return failures; 17 | } 18 | -------------------------------------------------------------------------------- /rfio.h: -------------------------------------------------------------------------------- 1 | #ifndef RFIO_H 2 | #define RFIO_H 3 | struct spectrogram { 4 | int nsub,nchan,msub,isub; 5 | double *mjd; 6 | double freq,samp_rate; 7 | float *length; 8 | float *z,*zavg,*zstd; 9 | float zmin,zmax; 10 | char nfd0[32]; 11 | }; 12 | struct spectrogram read_spectrogram(char *prefix,int isub,int nsub,double f0,double df0,int nbin,double foff); 13 | void write_spectrogram(struct spectrogram s,char *prefix); 14 | void free_spectrogram(struct spectrogram s); 15 | #endif 16 | -------------------------------------------------------------------------------- /.github/workflows/github-actions.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions 2 | run-name: Building and testing STRF 3 | on: [push] 4 | jobs: 5 | GitHub-Actions: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - run: sudo apt install --no-install-recommends make gcc pgplot5 gfortran libpng-dev libx11-dev libgsl-dev libfftw3-dev libsox-dev libcmocka-dev libcmocka0 9 | - name: Check out repository code 10 | uses: actions/checkout@v3 11 | - run: cd ${{ github.workspace }} 12 | - run: make 13 | - run: make tests 14 | -------------------------------------------------------------------------------- /env-dist: -------------------------------------------------------------------------------- 1 | # "New" Python STRF environment variables 2 | 3 | SATNOGS_DIR=/home/pi/satnogs_data 4 | 5 | # SatNOGS Waterfall Tabulation Helper 6 | export SATNOGS_TLE_DIR=$SATNOGS_DIR/tles/satnogs 7 | export SATNOGS_ARTIFACTS_DIR=$SATNOGS_DIR/artifacts 8 | export SATNOGS_DOPPLER_OBS_DIR=$SATNOGS_DIR/doppler_obs 9 | SATNOGS_SITES_TXT=$SATNOGS_DIR/sites.txt 10 | 11 | 12 | # SatNOGS Artifacts Helpers 13 | SATNOGS_NETWORK_API_URL=https://network.satnogs.org/api/ 14 | SATNOGS_DB_API_URL=https://db.satnogs.org/api/ 15 | 16 | SATNOGS_DB_API_TOKEN=your-db-api-token 17 | -------------------------------------------------------------------------------- /contrib/plot_spectrum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | 4 | from spectrum_parser import read_spectrum 5 | from spectrum_plot import plot_spectrum 6 | 7 | 8 | if __name__ == '__main__': 9 | parser = argparse.ArgumentParser(description='Plot RF observations') 10 | parser.add_argument('path', 11 | type=str, 12 | help='Path to the directory with the input files $PATH/x_???.bin') 13 | args = parser.parse_args() 14 | 15 | z, headers = read_spectrum(args.path) 16 | plot_spectrum(z, headers) 17 | -------------------------------------------------------------------------------- /rftrace.h: -------------------------------------------------------------------------------- 1 | struct trace { 2 | char satname[25]; 3 | int satno,n,site,classfd,graves; 4 | double *mjd; 5 | double *freq,freq0; 6 | float *za; 7 | }; 8 | struct trace *compute_trace(char *tlefile,double *mjd,int n,int site_id,float fmin,float fmax,int *nsat,int graves,char *freqlist); 9 | void identify_trace(char *tlefile,struct trace t,int satno,char *freqlist); 10 | void identify_trace_graves(char *tlefile,struct trace t,int satno,char *freqlist); 11 | void compute_doppler(char *tlefile,double *mjd,int n,int site_id,int satno,int graves, int skiphigh,char *outfname); 12 | int fgetline(FILE *file,char *s,int lim); 13 | -------------------------------------------------------------------------------- /ferror.c: -------------------------------------------------------------------------------- 1 | /* > satutl.c 2 | * 3 | */ 4 | 5 | 6 | #include "sgdp4h.h" 7 | 8 | #include 9 | 10 | void fatal_error(const char *format, ...) 11 | { 12 | va_list arg_ptr; 13 | 14 | fflush(stdout); 15 | 16 | fprintf(stderr, "\nDundee Satellite Lab fatal run-time error:\n"); 17 | 18 | va_start(arg_ptr, format); 19 | vfprintf(stderr, format, arg_ptr); 20 | va_end(arg_ptr); 21 | 22 | // fprintf(stderr, "\nNow terminating the program...\n"); 23 | // fflush(stderr); 24 | 25 | // exit(5); 26 | return; 27 | } 28 | 29 | /* ===================================================================== */ 30 | -------------------------------------------------------------------------------- /rftles.h: -------------------------------------------------------------------------------- 1 | #ifndef _RFTLES_H 2 | #define _RFTLES_H 3 | 4 | #include "sgdp4h.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct tle { 11 | orbit_t orbit; 12 | char *name; 13 | } tle_t; 14 | 15 | typedef struct tle_array { 16 | long number_of_elements; 17 | tle_t *tles; 18 | } tle_array_t; 19 | 20 | tle_array_t *load_tles(char *tlefile); 21 | void free_tles(tle_array_t *tle_array); 22 | tle_t *get_tle_by_index(tle_array_t *tle_array, long index); 23 | tle_t *get_tle_by_catalog_id(tle_array_t *tle_array, long satno); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif /* _RFTLES_H */ 30 | -------------------------------------------------------------------------------- /contrib/settings.py: -------------------------------------------------------------------------------- 1 | from decouple import config 2 | 3 | # Path to TLE 4 | # Filename convention: {TLE_DIR}/{observation_id}.txt 5 | TLE_DIR = config('SATNOGS_TLE_DIR') 6 | 7 | # absulute frequency measurement storage 8 | # Filename convention: {DOPPLER_OBS_DIR}/{observation_id}.dat 9 | DOPPLER_OBS_DIR = config('SATNOGS_DOPPLER_OBS_DIR') 10 | # legacy name: 11 | OBS_DIR = DOPPLER_OBS_DIR 12 | 13 | # SATTOOLS/STRF/STVID sites.txt file 14 | SITES_TXT = config('SATNOGS_SITES_TXT') 15 | 16 | 17 | SATNOGS_NETWORK_API_URL = config('SATNOGS_NETWORK_API_URL') 18 | SATNOGS_DB_API_URL = config('SATNOGS_DB_API_URL') 19 | SATNOGS_DB_API_TOKEN = config('SATNOGS_DB_API_TOKEN') 20 | -------------------------------------------------------------------------------- /rffft_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef _RFFFT_INTERNAL_H 2 | #define _RFFFT_INTERNAL_H 3 | 4 | #include "sgdp4h.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | // input: 11 | // filename: filename string to parse 12 | // output: 13 | // samplerate: parsed samplerate 14 | // frequency: parsed frequency 15 | // format: parsed sample format: char: 'c', int: 'i', float: 'f', wav: 'w' 16 | // starttime: parsed start time string formatted YYYY-MM-DDTHH:MM:SS.sss 17 | int rffft_params_from_filename(char * filename, double * samplerate, double * frequency, char * format, char * starttime); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif /* _RFFFT_INTERNAL_H */ 24 | -------------------------------------------------------------------------------- /simplex.c: -------------------------------------------------------------------------------- 1 | // Creates Simplex 2 | #include 3 | #include 4 | 5 | double **simplex(int n,double *a,double *da) 6 | { 7 | int i,j; 8 | double **p; 9 | 10 | // Allocate pointers to rows 11 | p=(double **) malloc(sizeof(double *) * (n+1)); 12 | 13 | // Allocate rows and set pointers 14 | for (i=0;i<=n;i++) 15 | p[i]=(double *) malloc(sizeof(double) * (n+1)*n); 16 | 17 | // Fill simplex 18 | for (i=0;i<=n;i++) { 19 | for (j=0;jj) p[i][j]=a[j]-da[j]; 23 | } 24 | } 25 | 26 | return p; 27 | } 28 | 29 | void simplex_free(double **p,int n) 30 | { 31 | int i; 32 | 33 | if(p==NULL) 34 | return; 35 | for(i=0;i<=n;i++) 36 | free(p[i]); 37 | free(p); 38 | } 39 | -------------------------------------------------------------------------------- /contrib/download_satnogs_tle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import os 5 | 6 | from satnogs_api_client import fetch_observation_data 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Download TLE from a specific Observation in SatNOGS Network.') 11 | parser.add_argument('observation_id', type=int, 12 | help='SatNOGS Observation ID') 13 | args = parser.parse_args() 14 | 15 | obs = fetch_observation_data([args.observation_id])[0] 16 | 17 | filename = '{}/{}.txt'.format(os.getenv('SATNOGS_TLE_DIR'), args.observation_id) 18 | 19 | with open(filename, 'w') as f: 20 | f.write(obs['tle0']) 21 | f.write('\n') 22 | f.write(obs['tle1']) 23 | f.write('\n') 24 | f.write(obs['tle2']) 25 | f.write('\n') 26 | print("TLE saved in {}".format(filename)) 27 | -------------------------------------------------------------------------------- /satutl.h: -------------------------------------------------------------------------------- 1 | /* > satutl.h 2 | * 3 | */ 4 | 5 | #ifndef _SATUTL_H 6 | #define _SATUTL_H 7 | 8 | #define ST_SIZE 256 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | /** satutl.c **/ 15 | void read_kb(char *buf); 16 | void conditional_copy_satname(char * satname, char * current_line); 17 | int read_twoline(FILE *fp, long satno, orbit_t *orb, char *satname); 18 | void *vector(size_t num, size_t size); 19 | void print_orb(orbit_t *orb); 20 | int alpha5_to_number(const char *s); 21 | void number_to_alpha5(int number, char *result); 22 | void zero_pad(const char *s, char *result); 23 | void strip_leading_spaces(const char *s, char *result); 24 | void strip_trailing_spaces(char *s); 25 | 26 | /** aries.c **/ 27 | double gha_aries(double jd); 28 | 29 | /** ferror.c **/ 30 | void fatal_error(const char *format, ...); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | 36 | #endif /* _SATUTL_H */ 37 | -------------------------------------------------------------------------------- /rfinfo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc,char *argv[]) 6 | { 7 | int i,firstfile=1,status; 8 | FILE *file; 9 | char header[256],filename[128],nfd[32],fileroot[128]; 10 | double freq,samp_rate; 11 | float length; 12 | int nchan; 13 | 14 | if (strchr(argv[1],'_')!=NULL) { 15 | strncpy(fileroot,argv[1],strlen(argv[1])-11); 16 | fileroot[strlen(argv[1])-11]='\0'; 17 | } else { 18 | strcpy(fileroot,argv[1]); 19 | } 20 | 21 | // Open first file 22 | for (i=0;;i++) { 23 | sprintf(filename,"%s_%06d.bin",fileroot,i); 24 | file=fopen(filename,"r"); 25 | 26 | // Break if file does not exist 27 | if (file==NULL) 28 | break; 29 | 30 | // Read header 31 | if (firstfile==1) { 32 | // Read header 33 | status=fread(header,sizeof(char),256,file); 34 | status=sscanf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\n",nfd,&freq,&samp_rate,&length,&nchan); 35 | firstfile=0; 36 | } 37 | 38 | fclose(file); 39 | } 40 | 41 | printf("%s %8.3lf %8.3lf %d %d\n",argv[1],freq*1e-6,samp_rate*1e-6,nchan,i); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /contrib/spectrum_plot.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import matplotlib.dates as mdates 6 | 7 | 8 | def plot_spectrum(z, headers): 9 | z_mean = np.mean(z) 10 | z_std = np.std(z) 11 | zmin = z_mean - 2 * z_std 12 | zmax = z_mean + 6 * z_std 13 | 14 | f_min = -headers[0]['bw']*1e-3 15 | f_max = +headers[0]['bw']*1e-3 16 | 17 | utc_start = headers[0]['utc_start'] 18 | t_min = mdates.date2num(utc_start + timedelta(seconds=0)) 19 | t_max = mdates.date2num(utc_start + timedelta(seconds=z.shape[1])) 20 | 21 | fig, ax = plt.subplots(figsize=(12, 6)) 22 | 23 | ax.imshow(z, 24 | origin='lower', 25 | aspect='auto', 26 | extent=[t_min, t_max, f_min, f_max], 27 | vmin=zmin, 28 | vmax=zmax, 29 | interpolation='None', 30 | cmap='viridis') 31 | 32 | ax.xaxis_date() 33 | date_format = mdates.DateFormatter('%H:%M:%S') 34 | ax.xaxis.set_major_formatter(date_format) 35 | 36 | # diagonal xaxis labels 37 | fig.autofmt_xdate() 38 | 39 | plt.xlabel('Time; start: {:%Y-%m-%d %H:%M:%S}Z'.format(headers[0]['utc_start'])) 40 | plt.ylabel('Freqeuncy / kHz; center: {} MHz'.format(headers[0]['freq'] * 1e-6)) 41 | 42 | plt.tight_layout() 43 | 44 | plt.show() 45 | -------------------------------------------------------------------------------- /contrib/find_good_satnogs_artifacts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import logging 5 | import requests 6 | import settings 7 | 8 | from urllib.parse import urljoin 9 | from pprint import pprint 10 | 11 | from satnogs_api_client import fetch_observation_data, fetch_tle_of_observation 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | def fetch_latest_artifacts_metadata(): 17 | url = urljoin(settings.SATNOGS_DB_API_URL, 'artifacts/',) 18 | params = {} 19 | headers = {'Authorization': 'Token {0}'.format(settings.SATNOGS_DB_API_TOKEN)} 20 | 21 | try: 22 | response = requests.get(url, 23 | params=params, 24 | headers=headers, 25 | timeout=10) 26 | response.raise_for_status() 27 | except (requests.ConnectionError, requests.Timeout, requests.TooManyRedirects): 28 | logger.exception('An error occurred trying to GET artifact metadata from db') 29 | 30 | artifacts_metadata = response.json() 31 | 32 | if not len(artifacts_metadata): 33 | logger.info('No artifacts found in db') 34 | sys.exit(-1) 35 | 36 | return artifacts_metadata 37 | 38 | if __name__ == "__main__": 39 | logging.basicConfig(level=logging.INFO) 40 | 41 | # Fetch list of artifacts 42 | artifacts_metadata = fetch_latest_artifacts_metadata() 43 | 44 | observation_ids = [artifact['network_obs_id'] for artifact in artifacts_metadata] 45 | 46 | # Load corresponding obs from network 47 | observations = fetch_observation_data(sorted(observation_ids, reverse=True)) 48 | 49 | # Filter by good status 50 | for observation in observations: 51 | if not observation['vetted_status'] == 'good': 52 | pass 53 | 54 | print("{}/observations/{}/".format(settings.SATNOGS_NETWORK_API_URL[:-5], observation['id'])) 55 | -------------------------------------------------------------------------------- /rfsites.c: -------------------------------------------------------------------------------- 1 | #include "rfsites.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define LIM 80 8 | 9 | // Get observing site 10 | site_t get_site(int site_id) { 11 | int i=0,status; 12 | int count = 0; 13 | char line[LIM]; 14 | FILE *file; 15 | int id; 16 | double lat,lng; 17 | float alt; 18 | char abbrev[3],observer[64]; 19 | site_t s; 20 | char *env_datadir,*env_sites_txt,filename[LIM]; 21 | 22 | env_datadir = getenv("ST_DATADIR"); 23 | if (env_datadir == NULL || strlen(env_datadir) == 0) { 24 | env_datadir = "."; 25 | } 26 | 27 | env_sites_txt = getenv("ST_SITES_TXT"); 28 | if (env_sites_txt == NULL || strlen(env_sites_txt) == 0) { 29 | sprintf(filename, "%s/data/sites.txt", env_datadir); 30 | } else { 31 | sprintf(filename, "%s", env_sites_txt); 32 | } 33 | 34 | file=fopen(filename,"r"); 35 | if (file==NULL) { 36 | printf("File with site information not count!\n"); 37 | exit(0); 38 | } 39 | while (fgets(line,LIM,file)!=NULL) { 40 | // Skip 41 | if (strstr(line,"#")!=NULL) 42 | continue; 43 | 44 | // Strip newline 45 | line[strlen(line)-1]='\0'; 46 | 47 | // Read data 48 | status=sscanf(line,"%4d %2s %lf %lf %f", 49 | &id,abbrev,&lat,&lng,&alt); 50 | strcpy(observer,line+38); 51 | 52 | // Change to km 53 | alt/=1000.0; 54 | 55 | // Copy site 56 | if (id==site_id) { 57 | count += 1; 58 | s.lat=lat; 59 | s.lng=lng; 60 | s.alt=alt; 61 | s.id=id; 62 | strcpy(s.observer,observer); 63 | } 64 | 65 | } 66 | fclose(file); 67 | 68 | if (count == 0) { 69 | printf("Error: Site %d was not found in %s!\n", site_id, filename); 70 | exit(-1); 71 | } else if (count > 1) { 72 | printf("Site %d was found multiple times in %s, use last occurence.\n", site_id, filename); 73 | } 74 | 75 | return s; 76 | } 77 | -------------------------------------------------------------------------------- /rftime.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "rftime.h" 4 | 5 | // nfd2mjd 6 | double nfd2mjd(char *date) 7 | { 8 | int year,month,day,hour,min; 9 | double mjd,dday; 10 | float sec; 11 | 12 | sscanf(date,"%04d-%02d-%02dT%02d:%02d:%f",&year,&month,&day,&hour,&min,&sec); 13 | dday=day+hour/24.0+min/1440.0+sec/86400.0; 14 | mjd=date2mjd(year,month,dday); 15 | 16 | return mjd; 17 | } 18 | 19 | // Compute Julian Day from Date 20 | double date2mjd(int year,int month,double day) 21 | { 22 | int a,b; 23 | double jd; 24 | 25 | if (month<3) { 26 | year--; 27 | month+=12; 28 | } 29 | 30 | a=floor(year/100.); 31 | b=2.-a+floor(a/4.); 32 | 33 | if (year<1582) b=0; 34 | if (year==1582 && month<10) b=0; 35 | if (year==1852 && month==10 && day<=4) b=0; 36 | 37 | jd=floor(365.25*(year+4716))+floor(30.6001*(month+1))+day+b-1524.5; 38 | 39 | return jd-2400000.5; 40 | } 41 | 42 | // Compute Date from Julian Day 43 | void mjd2nfd(double mjd,char *nfd) 44 | { 45 | double f,jd,dday; 46 | int z,alpha,a,b,c,d,e; 47 | int year,month,day,hour,min; 48 | float sec,x; 49 | 50 | jd=mjd+2400000.5; 51 | jd+=0.5; 52 | 53 | z=floor(jd); 54 | f=fmod(jd,1.); 55 | 56 | if (z<2299161) 57 | a=z; 58 | else { 59 | alpha=floor((z-1867216.25)/36524.25); 60 | a=z+1+alpha-floor(alpha/4.); 61 | } 62 | b=a+1524; 63 | c=floor((b-122.1)/365.25); 64 | d=floor(365.25*c); 65 | e=floor((b-d)/30.6001); 66 | 67 | dday=b-d-floor(30.6001*e)+f; 68 | if (e<14) 69 | month=e-1; 70 | else 71 | month=e-13; 72 | 73 | if (month>2) 74 | year=c-4716; 75 | else 76 | year=c-4715; 77 | 78 | day=(int) floor(dday); 79 | x=24.0*(dday-day); 80 | x=3600.*fabs(x); 81 | sec=fmod(x,60.); 82 | x=(x-sec)/60.; 83 | min=fmod(x,60.); 84 | x=(x-min)/60.; 85 | hour=x; 86 | sec=floor(1000.0*sec)/1000.0; 87 | 88 | sprintf(nfd,"%04d-%02d-%02dT%02d:%02d:%06.3f",year,month,day,hour,min,sec); 89 | 90 | return; 91 | } 92 | -------------------------------------------------------------------------------- /tleupdate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if TLE directory exists 4 | if [ ! -d $ST_TLEDIR ]; then 5 | mkdir -p $ST_TLEDIR 6 | fi 7 | 8 | # Goto TLE dir 9 | cd $ST_TLEDIR 10 | 11 | # Get date 12 | DATE=`date +%Y%m%d_%H%M%S` 13 | 14 | # Get space-track catalog 15 | if [[ ! -z "${ST_LOGIN}" ]]; then 16 | # Get cookie 17 | wget --post-data=$ST_LOGIN --cookies=on --keep-session-cookies --save-cookies=/tmp/cookies.txt 'https://www.space-track.org/ajaxauth/login' -o /tmp/stget.log 18 | 19 | # Get data 20 | wget --keep-session-cookies --load-cookies=/tmp/cookies.txt 'https://www.space-track.org/basicspacedata/query/class/gp/EPOCH/>now-30/orderby/NORAD_CAT_ID,EPOCH/format/3le' -O catalog.tle 21 | dos2unix catalog.tle 22 | 23 | # Fix missing leading zeros 24 | sed -i -e "s/^1 /1 0000/g" -e "s/^2 /2 0000/g" -e "s/^1 /1 000/g" -e "s/^2 /2 000/g" -e "s/^1 /1 00/g" -e "s/^2 /2 00/g" -e "s/^1 /1 0/g" -e "s/^2 /2 0/g" catalog.tle 25 | cp catalog.tle ${DATE}_catalog.txt 26 | rm login /tmp/cookies.txt /tmp/stget.log 27 | fi 28 | 29 | # Get classfd 30 | wget https://mmccants.org/tles/classfd.zip -O classfd.zip 31 | unzip -o classfd.zip 32 | dos2unix classfd.tle 33 | cp classfd.tle ${DATE}_classfd.txt 34 | rm classfd.zip 35 | 36 | # Get inttles 37 | wget https://mmccants.org/tles/inttles.zip -O inttles.zip 38 | unzip -o inttles.zip 39 | dos2unix inttles.tle 40 | cp inttles.tle ${DATE}_inttles.txt 41 | rm inttles.zip 42 | 43 | # Get CALPOLY tles 44 | wget http://mstl.atl.calpoly.edu/~ops/keps/kepler.txt -O kepler.tle 45 | dos2unix kepler.tle 46 | sed -i -e "s/^1 /1 0000/g" -e "s/^2 /2 0000/g" -e "s/^1 /1 000/g" -e "s/^2 /2 000/g" -e "s/^1 /1 00/g" -e "s/^2 /2 00/g" -e "s/^1 /1 0/g" -e "s/^2 /2 0/g" kepler.tle 47 | 48 | # Create TLE bulk file 49 | cat classfd.tle kepler.tle catalog.tle >bulk.tle 50 | #cat classfd.tle catalog.tle >bulk.tle 51 | cat bulk.tle | grep -e "^1 " | awk '{if ($2<80000 || $2>99000) printf("%05d %s\n",$2,$3)}' | sort | uniq >$ST_DATADIR/data/desig.txt 52 | -------------------------------------------------------------------------------- /contrib/spectrum_parser.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import numpy as np 3 | import glob 4 | import os 5 | import re 6 | from pathlib import Path 7 | 8 | 9 | def read_spectrum(path): 10 | # Get the number of files 11 | filenames = glob.glob(os.path.join(path, '*.bin')) 12 | datestr = Path(filenames[0]).stem.split('_')[0] 13 | 14 | # Read first file to get the number of channels and number of "samples" 15 | filename = os.path.join(path, '{:s}_{:06}.bin'.format(datestr, 0)) 16 | with open(filename, 'rb') as f: 17 | header = parse_header(f.read(256)) 18 | 19 | zs = [] 20 | headers = [] 21 | for i_file in range(len(filenames)): 22 | filename = os.path.join(path, '{:s}_{:06}.bin'.format(datestr, i_file)) 23 | # i_sub = 0 24 | with open(filename, 'rb') as f: 25 | next_header = f.read(256) 26 | while(next_header): 27 | headers.append(parse_header(next_header)) 28 | zs.append(np.fromfile(f, dtype=np.float32, count=header["nchan"])) 29 | next_header = f.read(256) 30 | return np.transpose(np.vstack(zs)), headers 31 | 32 | 33 | def parse_header(header_b): 34 | # TODO. Support files with the additional fields 35 | # - NBITS 36 | # - MEAN 37 | # - RMS 38 | # "HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\n" 39 | 40 | header_s = header_b.decode('ASCII').strip('\x00') 41 | 42 | regex = r"^HEADER\nUTC_START (.*)\nFREQ (.*) Hz\nBW (.*) Hz\nLENGTH (.*) s\nNCHAN (.*)\nNSUB (.*)\nEND\n$" 43 | match = re.fullmatch(regex, header_s, re.MULTILINE) 44 | 45 | utc_start = datetime.strptime(match.group(1), '%Y-%m-%dT%H:%M:%S.%f') 46 | 47 | return {'utc_start': utc_start, 48 | 'freq': float(match.group(2)), 49 | 'bw': float(match.group(3)), 50 | 'length': float(match.group(4)), 51 | 'nchan': int(match.group(5)), 52 | 'nsub': int(match.group(6))} 53 | -------------------------------------------------------------------------------- /contrib/README.md: -------------------------------------------------------------------------------- 1 | # STRF contrib Scripts 2 | 3 | ## Installation 4 | 5 | - Create Pyhton virtual environment and install dependencies via pip: 6 | ``` 7 | mkvirtualenv strf 8 | pip install -r contrib/requirements.txt 9 | ``` 10 | 11 | - Create initial configuration from `env-dist` 12 | ``` 13 | cp env-dist .env 14 | ``` 15 | - Configure the data paths (they must already exist!) and add your SatNOGS DB API Token in `.env` 16 | 17 | ## Find good observations with SatNOGS artifacts 18 | ``` 19 | $ ./contrib/find_good_satnogs_artifacts.py 20 | ``` 21 | 22 | ## Download SatNOGS Artifacts 23 | 24 | ``` 25 | $ ./contrib/download_satnogs_artifact.py 4950356 26 | Artifact Metadata for Observation #4950356 found. 27 | Artifact saved in /home/pi/data/artifacts/4950356.h5 28 | ``` 29 | 30 | ## Download SatNOGS Observation TLEs 31 | 32 | To add the TLE used in a specific SatNOGS Observation to your catalog, the following command can be used: 33 | ``` 34 | $ ./contrib/download_satnogs_tle.py 4950356 35 | TLE saved in /home/pi/data/tles/satnogs/4950356.txt 36 | ``` 37 | 38 | ## Plot SatNOGS Artifacts 39 | 40 | This can be done using [spectranalysis](https://github.com/kerel-fs/spectranalysis). 41 | 42 | ## Ultra-Short Analysis Guide 43 | 44 | ``` 45 | source .env 46 | 47 | OBSERVATION_ID=4950356 48 | ./contrib/download_satnogs_tle.py $OBSERVATION_ID 49 | ./contrib/download_satnogs_artifact.py $OBSERVATION_ID 50 | ./contrib/analyze_artifact.py --observation_id $OBSERVATION_ID --site_id 977 51 | rffit -d "$SATNOGS_DOPPLER_OBS_DIR/$OBSERVATION_ID.dat" -c "$SATNOGS_TLE_DIR/$OBSERVATION_ID.txt" -i 27844 -s 977 52 | ``` 53 | 54 | ``` 55 | $ ./contrib/download_satnogs_tle.py 4950356 56 | TLE saved in /mnt/old_home/kerel/c4/satnogs/data/tles/satnogs/4950356.txt 57 | $ ./contrib/download_satnogs_artifact.py 4950356 58 | Artifact Metadata for Observation #4950356 found. 59 | Artifact saved in /mnt/old_home/kerel/c4/satnogs/data/artifacts/4950356.h5 60 | $ ./contrib/analyze_artifact.py --observation_id 4950356 --site_id 977 61 | Load /mnt/old_home/kerel/c4/satnogs/data/artifacts/4950356.h5 62 | Extract measurements... 63 | Data written in /mnt/old_home/kerel/c4/satnogs/data/doppler_obs/4950356.dat 64 | $ rffit -d ${SATNOGS_DOPPLER_OBS_DIR}/4950356.dat -c ${SATNOGS_TLE_DIR}/4950356.txt -i 27844 -s 977 65 | ``` 66 | -------------------------------------------------------------------------------- /Makefile.linux: -------------------------------------------------------------------------------- 1 | # Compiling flags 2 | CFLAGS = -O3 3 | 4 | # Linking flags 5 | LFLAGS = -lcpgplot -lpgplot -lX11 -lpng -lm -lgsl -lgslcblas 6 | 7 | # Compiler 8 | CC = gcc 9 | 10 | # Installation 11 | INSTALL_PROGRAM = install -m 775 12 | prefix = /usr/local 13 | exec_prefix = $(prefix) 14 | bindir = $(exec_prefix)/bin 15 | 16 | all: 17 | make rfedit rfplot rffft rfpng rffit rffind 18 | 19 | rffit: rffit.o sgdp4.o satutl.o deep.o ferror.o dsmin.o simplex.o versafit.o rfsites.o rftles.o 20 | gfortran -o rffit rffit.o sgdp4.o satutl.o deep.o ferror.o dsmin.o simplex.o versafit.o rfsites.o rftles.o $(LFLAGS) 21 | 22 | rfpng: rfpng.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o 23 | gfortran -o rfpng rfpng.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o $(LFLAGS) 24 | 25 | rfedit: zscale.o rfedit.o rfio.o rftime.o 26 | $(CC) -o rfedit rfedit.o zscale.o rfio.o rftime.o -lm 27 | 28 | rffind: rffind.o rfio.o rftime.o zscale.o 29 | $(CC) -o rffind rffind.o rfio.o rftime.o zscale.o -lm 30 | 31 | rftrack: rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o zscale.o 32 | $(CC) -o rftrack rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o zscale.o -lm 33 | 34 | rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o 35 | gfortran -o rfplot rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o $(LFLAGS) 36 | 37 | rffft: rffft.o rffft_internal.o rftime.o 38 | $(CC) -o rffft rffft.o rffft_internal.o rftime.o -lfftw3f -lm -lsox 39 | 40 | tests/tests: tests/tests.o tests/tests_rffft_internal.o tests/tests_rftles.o rffft_internal.o rftles.o satutl.o ferror.o 41 | $(CC) -Wall -o $@ $^ -lcmocka -lm 42 | 43 | tests: tests/tests 44 | ./tests/tests 45 | 46 | .PHONY: clean install uninstall tests 47 | 48 | clean: 49 | rm -f *.o tests/*.o 50 | rm -f *~ 51 | 52 | install: 53 | $(INSTALL_PROGRAM) rffit $(DESTDIR)$(bindir)/rffit 54 | $(INSTALL_PROGRAM) rfpng $(DESTDIR)$(bindir)/rfpng 55 | $(INSTALL_PROGRAM) rfedit $(DESTDIR)$(bindir)/rfedit 56 | $(INSTALL_PROGRAM) rffind $(DESTDIR)$(bindir)/rffind 57 | $(INSTALL_PROGRAM) rfplot $(DESTDIR)$(bindir)/rfplot 58 | $(INSTALL_PROGRAM) rffft $(DESTDIR)$(bindir)/rffft 59 | $(INSTALL_PROGRAM) rffft $(DESTDIR)$(bindir)/tleupdate 60 | 61 | uninstall: 62 | $(RM) $(DESTDIR)$(bindir)/rffit 63 | $(RM) $(DESTDIR)$(bindir)/rfpng 64 | $(RM) $(DESTDIR)$(bindir)/rfedit 65 | $(RM) $(DESTDIR)$(bindir)/rffind 66 | $(RM) $(DESTDIR)$(bindir)/rfplot 67 | $(RM) $(DESTDIR)$(bindir)/rffft 68 | $(RM) $(DESTDIR)$(bindir)/tleupdate 69 | -------------------------------------------------------------------------------- /rfdop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "rfio.h" 7 | #include "rftime.h" 8 | #include "rftrace.h" 9 | 10 | void usage(void) 11 | { 12 | printf("rfdop m:t:c:l:d:g:s:o:hi:\n\n"); 13 | printf("m date/time (MJD)\n"); 14 | printf("t date/time (yyyy-mm-ddThh:mm:ss.sss)\n"); 15 | printf("i NORAD number\n"); 16 | printf("c TLE catalog file\n"); 17 | printf("s site (COSPAR)\n"); 18 | printf("d time step [default: 10s]\n"); 19 | printf("l trail length [default: 900s]\n"); 20 | printf("g enable GRAVES\n"); 21 | printf("H skip high orbits (<10 revs/day)\n"); 22 | printf("o output file name\n"); 23 | printf("h this help\n"); 24 | 25 | return; 26 | } 27 | 28 | int main(int argc, char *argv[]) 29 | { 30 | int i, n, satno=0; 31 | double mjd0, *mjd, t; 32 | float length=900.0, dt=10.0; 33 | int site_id; 34 | int graves=0,skiphigh=0; 35 | char *env,tlefile[256],nfd[64],outfname[256]="out.dat"; 36 | int arg=0; 37 | 38 | // Get defaults 39 | env=getenv("ST_COSPAR"); 40 | if (env!=NULL) { 41 | site_id=atoi(env); 42 | } 43 | 44 | if (argc>1) { 45 | while ((arg=getopt(argc,argv,"m:t:c:i:l:d:gs:o:hH"))!=-1) { 46 | switch (arg) { 47 | case 't': 48 | strcpy(nfd,optarg); 49 | mjd0=nfd2mjd(nfd); 50 | break; 51 | 52 | case 'm': 53 | mjd0=(double) atof(optarg); 54 | break; 55 | 56 | case 'c': 57 | strcpy(tlefile,optarg); 58 | break; 59 | 60 | case 'i': 61 | satno=atoi(optarg); 62 | break; 63 | 64 | case 'l': 65 | length=atof(optarg); 66 | break; 67 | 68 | case 'd': 69 | dt=atof(optarg); 70 | break; 71 | 72 | case 's': 73 | site_id=atoi(optarg); 74 | break; 75 | 76 | case 'g': 77 | graves=1; 78 | break; 79 | 80 | case 'H': 81 | skiphigh=1; 82 | break; 83 | 84 | case 'o': 85 | strcpy(outfname,optarg); 86 | break; 87 | 88 | case 'h': 89 | usage(); 90 | return 0; 91 | 92 | default: 93 | usage(); 94 | return 0; 95 | } 96 | } 97 | } else { 98 | usage(); 99 | return 0; 100 | } 101 | 102 | // Generate MJDs 103 | n = (int) floor(length/dt); 104 | mjd = (double *) malloc(sizeof(double)*n); 105 | for (i=0;i. 8 | 9 | ## Installation 10 | Install dependencies 11 | ``` 12 | pip install -r contrib/requirements.txt 13 | ``` 14 | 15 | # First Setup 16 | Choose a folder where TLEs are stored and 17 | a folder where RF doppler observatons (`.dat`-files) are stored. 18 | 19 | For now the paths are configured via environment variables, 20 | so make sure to set them correctly before each usage. 21 | 22 | Example: 23 | ``` 24 | # Filename convention: {TLE_DIR}/{observation_id}.txt 25 | SATNOGS_TLE_DIR="./data/tles" 26 | 27 | # absulute frequency measurement storage 28 | # Filename convention: {DOPPLER_OBS_DIR}/{observation_id}.txt 29 | SATNOGS_DOPPLER_OBS_DIR="./data/obs" 30 | 31 | # SATTOOLS/STRF/STVID sites.txt file 32 | SATNOGS_SITES_TXT="./data/sites.txt" 33 | 34 | mkdir -p $SATNOGS_TLE_DIR 35 | mkdir -p $SATNOGS_DOPPLER_OBS_DIR 36 | ``` 37 | 38 | ## Usage 39 | 0. Make sure the (3) env variables are set. 40 | 1. Choose SatNOGS Observation ID from network and run the tabulation helper 41 | 42 | ``` 43 | ./contrib/satnogs_waterfall_tabulation_helper.py 1102230 44 | ``` 45 | 46 | An interactive plot will show up. 47 | Clicking inside the plot will add a signal marker. 48 | If you are finished with adding signal markers, 49 | save the signal markers using the keyboard shortcut `f`. 50 | 51 | Custom keyboard shortcuts: 52 | 53 | - u - undo last signal marker 54 | - f - save the signal markers in an strf-compatible file 55 | 56 | Useful Matplotlib navigation keyboard shortcuts (documentation): 57 | 58 | p - toggle 'Pan/Zoom' modus 59 | o - toggle 'Zoom-to-rect' modus 60 | h - Home/Reset (view) 61 | c - Back (view) 62 | 63 | 2. Run rffit for orbit fitting, e.g. 64 | ``` 65 | ./rffit -d $SATNOGS_DOPPLER_OBS_DIR/1102230.dat -i 44356 -c $SATNOGS_TLE_DIR/1102230.txt -s 7669 66 | ``` 67 | 68 | ## Known issues 69 | - A site id of the form `7{station_id}` is automatically assigned and written to 70 | the `sites.txt` (e.g. station 669 should get `7669` assigned). 71 | Only SatNOGS stations <999 are supported, as the strf sites.txt parse only allows 72 | 4-digit site ids. In case of problems, choose a free site id and manually correct the 73 | doppler obs (`.dat`-files, last colum). 74 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Compiling flags 2 | CFLAGS = -O3 3 | 4 | # Linking flags 5 | LFLAGS = -lcpgplot -lpgplot -lX11 -lpng -lm -lgsl -lgslcblas 6 | 7 | # Compiler 8 | CC = gcc 9 | 10 | # Installation 11 | INSTALL_PROGRAM = install -m 755 12 | prefix = /usr/local 13 | exec_prefix = $(prefix) 14 | bindir = $(exec_prefix)/bin 15 | 16 | all: 17 | make rfedit rfplot rffft rfpng rffit rffind rfdop 18 | 19 | rffit: rffit.o sgdp4.o satutl.o deep.o ferror.o dsmin.o simplex.o versafit.o rfsites.o rftles.o 20 | gfortran -o rffit rffit.o sgdp4.o satutl.o deep.o ferror.o dsmin.o simplex.o versafit.o rfsites.o rftles.o $(LFLAGS) 21 | 22 | rfpng: rfpng.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o 23 | gfortran -o rfpng rfpng.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o $(LFLAGS) 24 | 25 | rfdop: rfdop.o rftrace.o rfio.o rftime.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o 26 | $(CC) -o rfdop rfdop.o rftrace.o rfio.o rftime.o sgdp4.o satutl.o deep.o ferror.o rftles.o zscale.o -lm 27 | 28 | rfedit: rfedit.o rfio.o rftime.o zscale.o 29 | $(CC) -o rfedit rfedit.o rfio.o rftime.o zscale.o -lm 30 | 31 | rffind: rffind.o rfio.o rftime.o zscale.o 32 | $(CC) -o rffind rffind.o rfio.o rftime.o zscale.o -lm 33 | 34 | rftrack: rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o zscale.o 35 | $(CC) -o rftrack rftrack.o rfio.o rftime.o rftrace.o sgdp4.o satutl.o deep.o ferror.o zscale.o -lm 36 | 37 | rfplot: rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o versafit.o dsmin.o simplex.o rftles.o zscale.o 38 | gfortran -o rfplot rfplot.o rftime.o rfio.o rftrace.o sgdp4.o satutl.o deep.o ferror.o versafit.o dsmin.o simplex.o rftles.o zscale.o $(LFLAGS) 39 | 40 | rffft: rffft.o rffft_internal.o rftime.o 41 | $(CC) -o rffft rffft.o rffft_internal.o rftime.o -lfftw3f -lm -lsox 42 | 43 | tests/tests: tests/tests.o tests/tests_rffft_internal.o tests/tests_rftles.o rffft_internal.o rftles.o satutl.o ferror.o 44 | $(CC) -Wall -o $@ $^ -lcmocka -lm 45 | 46 | tests: tests/tests 47 | ./tests/tests 48 | 49 | .PHONY: clean install uninstall tests 50 | 51 | clean: 52 | rm -f *.o tests/*.o 53 | rm -f *~ 54 | 55 | install: 56 | $(INSTALL_PROGRAM) rffit $(DESTDIR)$(bindir)/rffit 57 | $(INSTALL_PROGRAM) rfpng $(DESTDIR)$(bindir)/rfpng 58 | $(INSTALL_PROGRAM) rfedit $(DESTDIR)$(bindir)/rfedit 59 | $(INSTALL_PROGRAM) rffind $(DESTDIR)$(bindir)/rffind 60 | $(INSTALL_PROGRAM) rfplot $(DESTDIR)$(bindir)/rfplot 61 | $(INSTALL_PROGRAM) rffft $(DESTDIR)$(bindir)/rffft 62 | $(INSTALL_PROGRAM) tleupdate $(DESTDIR)$(bindir)/tleupdate 63 | 64 | uninstall: 65 | $(RM) $(DESTDIR)$(bindir)/rffit 66 | $(RM) $(DESTDIR)$(bindir)/rfpng 67 | $(RM) $(DESTDIR)$(bindir)/rfedit 68 | $(RM) $(DESTDIR)$(bindir)/rffind 69 | $(RM) $(DESTDIR)$(bindir)/rfplot 70 | $(RM) $(DESTDIR)$(bindir)/rffft 71 | $(RM) $(DESTDIR)$(bindir)/tleupdate 72 | -------------------------------------------------------------------------------- /contrib/download_satnogs_artifact.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import logging 5 | import requests 6 | import settings 7 | import shutil 8 | import sys 9 | import tempfile 10 | import os 11 | 12 | from urllib.parse import urljoin 13 | from pprint import pprint 14 | 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | def fetch_artifact_metadata(network_obs_id): 19 | url = urljoin(settings.SATNOGS_DB_API_URL, 'artifacts/',) 20 | params = {'network_obs_id': network_obs_id} 21 | headers = {'Authorization': 'Token {0}'.format(settings.SATNOGS_DB_API_TOKEN)} 22 | 23 | response = requests.get(url, 24 | params=params, 25 | headers=headers, 26 | timeout=10) 27 | response.raise_for_status() 28 | 29 | return response.json() 30 | 31 | 32 | def fetch_artifact(url, artifact_filename): 33 | headers = {'Authorization': 'Token {0}'.format(settings.SATNOGS_DB_API_TOKEN)} 34 | 35 | response = requests.get(url, 36 | headers=headers, 37 | stream=True, 38 | timeout=10) 39 | response.raise_for_status() 40 | 41 | with open(artifact_filename, 'wb') as fname: 42 | for chunk in response.iter_content(chunk_size=1024): 43 | fname.write(chunk) 44 | 45 | 46 | def download_artifact(observation_id, filename): 47 | try: 48 | artifact_metadata = fetch_artifact_metadata(network_obs_id=observation_id) 49 | except requests.HTTPError: 50 | print('An error occurred trying to GET artifact metadata from db') 51 | return 52 | 53 | if not len(artifact_metadata): 54 | print('No artifact found in db for network_obs_id {}'.format(network_obs_id)) 55 | return 56 | 57 | print("Artifact Metadata for Observation #{} found.".format(observation_id)) 58 | 59 | try: 60 | artifact_file_url = artifact_metadata[0]['artifact_file'] 61 | artifact_file = tempfile.NamedTemporaryFile(delete=False) 62 | 63 | fetch_artifact(artifact_file_url, artifact_file.name) 64 | 65 | artifact_file.close() 66 | shutil.copy(artifact_file.name, filename) 67 | os.remove(artifact_file.name) 68 | print("Artifact saved in {}".format(filename)) 69 | except requests.HTTPError: 70 | print('Download failed for {}'.format(artifact_file_url)) 71 | return 72 | 73 | 74 | if __name__ == "__main__": 75 | parser = argparse.ArgumentParser(description='Download SatNOGS Artifacts from SatNOGS DB.') 76 | parser.add_argument('observation_ids', metavar='ID', type=int, nargs='+', 77 | help='SatNOGS Observation ID') 78 | 79 | args = parser.parse_args() 80 | 81 | logging.basicConfig(level=logging.INFO) 82 | 83 | for observation_id in args.observation_ids: 84 | download_artifact(observation_id, 85 | '{}/{}.h5'.format(os.getenv('SATNOGS_ARTIFACTS_DIR'), observation_id)) 86 | -------------------------------------------------------------------------------- /dsmin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define TINY 1.0e-10 6 | #define NMAX 100000 7 | #define SWAP(a,b) {(a)+=(b);(b)=(a)-(b);(a)-=(b);} 8 | 9 | // Downhill Simplex Minimization 10 | int dsmin(double **p,double *y,int n,double ftol,double (*func)(double *)) 11 | { 12 | int i,j,nfunk=0; 13 | int ihi,ilo,ise; 14 | double *ptry,*pmid,*psum; 15 | double tol,ytry,rtol,ysave; 16 | double *vector_sum(double **,int); 17 | double dsmod(double **,double *,double *,int,double (*func)(double *),int,double); 18 | 19 | // Get function values 20 | for (i=0;i<=n;i++) 21 | y[i]=func(p[i]); 22 | 23 | // Sum vectors 24 | psum=vector_sum(p,n); 25 | 26 | // Start forever loop 27 | for (;;) { 28 | // Find high and low point 29 | ilo=0; 30 | ihi = (y[0]>y[1]) ? (ise=1,0) : (ise=0,1); 31 | for (i=0;i<=n;i++) { 32 | if (y[i]<=y[ilo]) ilo=i; 33 | if (y[i]>y[ihi]) { 34 | ise=ihi; 35 | ihi=i; 36 | } else if (y[i]>y[ise] && i!=ihi) ise=i; 37 | } 38 | 39 | // Compute fractional range from highest to lowest point 40 | rtol=2.0*fabs(y[ihi]-y[ilo])/(fabs(y[ihi])+fabs(y[ilo])+TINY); 41 | 42 | // Return if fractional tolerance is acceptable 43 | if (rtol=NMAX) { 47 | printf("dsmin: NMAX exceeded!\n"); 48 | return -1; 49 | } 50 | nfunk+=2; 51 | 52 | // Reflect simplex 53 | ytry=dsmod(p,y,psum,n,func,ihi,-1.0); 54 | 55 | if (ytry<=y[ilo]) // Goes right direction, extrapolate by factor 2 56 | ytry=dsmod(p,y,psum,n,func,ihi,2.0); 57 | else if (ytry>=y[ise]) { // 1D contraction 58 | ysave=y[ihi]; 59 | ytry=dsmod(p,y,psum,n,func,ihi,0.5); 60 | if (ytry>=ysave) { 61 | for (i=0;i<=n;i++) { 62 | if (i!=ilo) { 63 | for (j=0;j 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "rfio.h" 8 | 9 | #define LIM 128 10 | 11 | void dec2sex(double x,char *s,int f,int len); 12 | 13 | void usage(void) 14 | { 15 | printf("rfplot: plot RF observations\n\n"); 16 | printf("-p Path to file prefix /a/b/c_??????.bin\n"); 17 | printf("-O Output file name [test_000000.bin]\n"); 18 | printf("-s Number of starting subintegration [0]\n"); 19 | printf("-l Number of subintegrations to plot [3600]\n"); 20 | printf("-o Frequency offset to apply (Hz) [0.0]\n"); 21 | printf("-b Number of subintegrations to bin [1]\n"); 22 | printf("-f Frequency to zoom into (Hz)\n"); 23 | printf("-w Bandwidth to zoom into (Hz)\n"); 24 | printf("-h This help\n"); 25 | 26 | return; 27 | } 28 | 29 | int main(int argc,char *argv[]) 30 | { 31 | struct spectrogram s; 32 | char path[128],outfile[128]="test"; 33 | int arg=0,nsub=3600,nbin=1,isub=0; 34 | double f0=0.0,df0=0.0,foff=0.0; 35 | 36 | // Read arguments 37 | if (argc>1) { 38 | while ((arg=getopt(argc,argv,"p:o:O:f:w:s:l:b:h"))!=-1) { 39 | switch (arg) { 40 | 41 | case 'p': 42 | strcpy(path,optarg); 43 | break; 44 | 45 | case 'O': 46 | strcpy(outfile,optarg); 47 | break; 48 | 49 | case 'o': 50 | foff=(double) atof(optarg); 51 | break; 52 | 53 | case 's': 54 | isub=atoi(optarg); 55 | break; 56 | 57 | case 'l': 58 | nsub=atoi(optarg); 59 | break; 60 | 61 | case 'b': 62 | nbin=atoi(optarg); 63 | break; 64 | 65 | case 'f': 66 | f0=(double) atof(optarg); 67 | break; 68 | 69 | case 'w': 70 | df0=(double) atof(optarg); 71 | break; 72 | 73 | case 'h': 74 | usage(); 75 | break; 76 | 77 | default: 78 | usage(); 79 | return 0; 80 | } 81 | } 82 | } else { 83 | usage(); 84 | return 0; 85 | } 86 | 87 | // Read data 88 | s=read_spectrogram(path,isub,nsub,f0,df0,nbin,foff); 89 | 90 | // Write data 91 | write_spectrogram(s,outfile); 92 | 93 | // Free 94 | // free(s.z); 95 | // free(s.mjd); 96 | // free(s.length); 97 | 98 | return 0; 99 | } 100 | 101 | 102 | // Convert Decimal into Sexagesimal 103 | void dec2sex(double x,char *s,int f,int len) 104 | { 105 | int i; 106 | double sec,deg,min; 107 | char sign; 108 | char *form[]={"::",",,","hms"," "}; 109 | 110 | sign=(x<0 ? '-' : ' '); 111 | x=3600.*fabs(x); 112 | 113 | sec=fmod(x,60.); 114 | x=(x-sec)/60.; 115 | min=fmod(x,60.); 116 | x=(x-min)/60.; 117 | // deg=fmod(x,60.); 118 | deg=x; 119 | 120 | if (len==7) sprintf(s,"%c%02i%c%02i%c%07.4f%c",sign,(int) deg,form[f][0],(int) min,form[f][1],sec,form[f][2]); 121 | if (len==6) sprintf(s,"%c%02i%c%02i%c%06.3f%c",sign,(int) deg,form[f][0],(int) min,form[f][1],sec,form[f][2]); 122 | if (len==5) sprintf(s,"%c%02i%c%02i%c%05.2f%c",sign,(int) deg,form[f][0],(int) min,form[f][1],sec,form[f][2]); 123 | if (len==4) sprintf(s,"%c%02i%c%02i%c%04.1f%c",sign,(int) deg,form[f][0],(int) min,form[f][1],sec,form[f][2]); 124 | if (len==2) sprintf(s,"%c%02i%c%02i%c%02i%c",sign,(int) deg,form[f][0],(int) min,form[f][1],(int) floor(sec),form[f][2]); 125 | 126 | return; 127 | } 128 | 129 | -------------------------------------------------------------------------------- /data/sites.txt: -------------------------------------------------------------------------------- 1 | # No ID Latitude Longitude Elev Observer 2 | 1111 RL 38.9478 -104.5614 2073 Ron Lee 3 | 4171 CB 52.8344 6.3785 10 Cees Bassa 4 | 4172 LB 52.3713 5.2580 -3 Leo Barhorst 5 | 4553 CB 53.3210 -2.2330 86 Cees Bassa 6 | 0001 MM 30.3340 -97.7610 160 Mike McCants 7 | 0002 MM 30.3138 -97.8661 280 Mike McCants 8 | 0030 IA 47.4857 18.9727 400 Ivan Artner 9 | 0031 IA 47.5612 18.7366 149 Ivan Artner 10 | 0070 BC 53.2233 -0.6003 30 Bob Christy 11 | 0100 SG 59.4627 17.9138 0 Sven Grahn 12 | 0110 LK 32.5408 -96.8906 200 Lyn Kennedy 13 | 0433 GR -33.9406 18.5129 10 Greg Roberts 14 | 0434 IR -26.1030 27.9288 1646 Ian Roberts 15 | 0435 GR -33.9369 18.5101 25 Greg Roberts 16 | 0710 LS 52.3261 10.6756 85 Lutz Schindler 17 | 0899 FM -34.8961 -56.1227 30 Fernando Mederos 18 | 1056 MK 57.0122 23.9833 4 Martins Keruss 19 | 1086 NK 46.4778 30.7556 56 Nikolay Koshkin 20 | 1244 AM 44.3932 33.9701 69 Andriy Makeyev 21 | 1747 DD 45.7275 -72.3526 191 Daniel Deak 22 | 1775 KF 44.6062 -75.6910 200 Kevin Fetter 23 | 2018 PW 51.0945 -1.1188 124 Peter Wakelin 24 | 2115 MW 51.3286 -0.7950 75 Mike Waterman 25 | 2414 DH 50.7468 -1.8800 34 David Hopkins 26 | 2420 RE 55.9486 -3.1386 40 Russell Eberst 27 | 2563 PN 51.0524 2.4043 10 Pierre Nierinck 28 | 2675 DB 52.1358 -2.3264 70 David Brierley 29 | 2701 TM 43.6876 -79.3924 230 Ted Molczan 30 | 2751 BM 51.3440 -1.9849 125 Bruce MacDonald 31 | 2756 AK 56.0907 -3.1623 25 Andy Kirkham 32 | 4353 ML 52.1541 4.4908 0 Marco Langbroek 33 | 4354 ML 52.1168 4.5602 -2 Marco Langbroek 34 | 4355 ML 52.1388 4.4994 -2 Marco Langbroek 35 | 4541 AR 41.9639 12.4531 80 Alberto Rango 36 | 4542 AR 41.9683 12.4545 80 Alberto Rango 37 | 4641 AR 41.1060 16.9010 70 Alberto Rango 38 | 5555 SG 56.1019 94.5533 154 Sergey Guryanov 39 | 5918 BG 59.2985 18.1045 40 Bjorn Gimle 40 | 5919 BG 59.2615 18.6206 30 Bjorn Gimle 41 | 6226 SC 28.4861 -97.8194 110 Scott Campbell 42 | 7777 BY 38.1656 -2.3267 1608 Brad Young remote 43 | 7778 BY -31.2733 149.0644 1122 Brad Young SSO 44 | 7779 BY 32.9204 -105.5283 2225 Brad Young NM 45 | 8048 ST 49.4175 -123.6420 1. Scott Tilley 46 | 8049 ST 49.4348 -123.6685 40. Scott Tilley 47 | 8305 PG 26.2431 -98.2163 30 Paul Gabriel 48 | 8335 BY 36.1397 -95.9838 205 Brad Young 49 | 8336 BY 36.1397 -95.9838 205 Brad Young 50 | 8438 SS 38.8108 -119.7750 1545 Sierra Stars 51 | 8536 TL 36.8479 -76.5010 4 Tim Luton 52 | 8539 SN 39.4707 -79.3388 839 Steve Newcomb 53 | 8597 TB -34.9638 138.6333 100 Tony Beresford 54 | 8650 QI -34.7207 138.6928 80 Mark Jessop 55 | 8600 PC -32.9770 151.6477 18 Paul Camilleri 56 | 0699 PC -14.4733 132.2369 108 Paul Camilleri 57 | 8730 EC 30.3086 -97.7279 150 Ed Cannon 58 | 8739 DB 37.1133 -121.7028 282 Derek Breit 59 | 9461 KO 47.9175 19.89365 938 Konkoly Obs 60 | 9633 JN 33.0206 -96.9982 153 Jim Nix 61 | 9730 MM 30.3150 -97.8660 280 Mike McCants 62 | 6242 JM 42.9453 -2.8284 623 Jon Mikel 63 | 6241 JM 42.9565 -2.8203 619 Jon Mikel 64 | 4160 BD 51.2793 5.4768 35 Bram Dorreman 65 | 9999 GR 47.348 5.5151 100 Graves 66 | -------------------------------------------------------------------------------- /rftles.c: -------------------------------------------------------------------------------- 1 | #include "rftles.h" 2 | 3 | #include "satutl.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | tle_array_t *load_tles(char *tlefile) { 10 | tle_array_t *tle_array; 11 | 12 | tle_array = (tle_array_t *)malloc(sizeof(tle_array_t)); 13 | 14 | if (tle_array == NULL) { 15 | return NULL; 16 | } 17 | 18 | tle_array->tles = NULL; 19 | tle_array->number_of_elements = 0; 20 | 21 | char filename[1024]; 22 | 23 | if (tlefile) { 24 | strncpy(filename, tlefile, sizeof(filename)); 25 | } else { 26 | char * env = getenv("ST_TLEDIR"); 27 | 28 | if (env == NULL || strlen(env) == 0) { 29 | env="."; 30 | } 31 | 32 | sprintf(filename, "%s/bulk.tle", env); 33 | } 34 | 35 | FILE * file = fopen(filename, "r"); 36 | 37 | if (file == NULL) { 38 | fprintf(stderr, "TLE file %s not found\n", filename); 39 | 40 | return tle_array; 41 | } 42 | 43 | size_t linesize = 256; 44 | char * line = malloc(linesize); 45 | ssize_t read; 46 | 47 | // Count number of entries 48 | long num_elements = 0; 49 | 50 | while ((read = getline(&line, &linesize, file)) != -1) { 51 | if (read > 0 && strncmp(line, "1 ", 2) == 0) { 52 | num_elements++; 53 | } 54 | } 55 | 56 | // Don't allocate anything if no entry found in file 57 | if (num_elements == 0) { 58 | fclose(file); 59 | free(line); 60 | 61 | return tle_array; 62 | } 63 | 64 | tle_array->tles = (tle_t *)calloc(num_elements, sizeof(tle_t)); 65 | 66 | if (tle_array->tles == NULL) { 67 | fclose(file); 68 | free(line); 69 | free(tle_array); 70 | 71 | return NULL; 72 | } 73 | 74 | // Rewind and parse file 75 | rewind(file); 76 | 77 | char satname[linesize]; 78 | while (read_twoline(file, 0, &(tle_array->tles[tle_array->number_of_elements].orbit), satname) == 0) { 79 | if (satname[0] != '\0') { 80 | int satname_len = strlen(satname) + 1; 81 | tle_array->tles[tle_array->number_of_elements].name = malloc(satname_len); 82 | strncpy(tle_array->tles[tle_array->number_of_elements].name, satname, satname_len); 83 | } else { 84 | tle_array->tles[tle_array->number_of_elements].name = NULL; 85 | } 86 | 87 | tle_array->number_of_elements++; 88 | } 89 | 90 | free(line); 91 | fclose(file); 92 | 93 | printf("Loaded %ld orbits\n", tle_array->number_of_elements); 94 | 95 | return tle_array; 96 | } 97 | 98 | void free_tles(tle_array_t *tle_array) { 99 | if (tle_array) { 100 | for (long i = 0; i < tle_array->number_of_elements; i++) { 101 | if (tle_array->tles[i].name != NULL) { 102 | free(tle_array->tles[i].name); 103 | } 104 | } 105 | 106 | free(tle_array->tles); 107 | free(tle_array); 108 | } 109 | } 110 | 111 | tle_t *get_tle_by_index(tle_array_t *tle_array, long index) { 112 | if (tle_array && (index < tle_array->number_of_elements)) { 113 | return &(tle_array->tles[index]); 114 | } 115 | 116 | return NULL; 117 | } 118 | 119 | tle_t *get_tle_by_catalog_id(tle_array_t *tle_array, long satno) { 120 | if (tle_array) { 121 | for (long i = 0; i < tle_array->number_of_elements; i++) { 122 | if (tle_array->tles[i].orbit.satno == satno) { 123 | return &(tle_array->tles[i]); 124 | } 125 | } 126 | } 127 | 128 | return NULL; 129 | } 130 | -------------------------------------------------------------------------------- /versafit.c: -------------------------------------------------------------------------------- 1 | // Versatile Fitting Routine 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int OUTPUT=1; // Print output on screen (1 = yes; 0 = no) 8 | int ERRCOMP=0; // Set reduced Chi-Squared to unity (1 = yes; 0 = no) 9 | 10 | int dsmin(double **,double *,int,double,double (*func)(double *)); 11 | double **simplex(int,double *,double *); 12 | void simplex_free(double **,int); 13 | double parabolic_root(double,double,double,double); 14 | 15 | // Versafit fitting routine 16 | // 17 | // Inputs: 18 | // m: number of datapoints 19 | // n: number of parameters 20 | // a: parameters 21 | // da: expected spread in parameters 22 | // func: function to fit (Chi-squared function) 23 | // dchisq difference in Chi-squared 24 | // tol: tolerance 25 | // opt: options 26 | // - n: no output 27 | void versafit(int m,int n,double *a,double *da,double (*func)(double *),double dchisq,double tol,char *opt) 28 | { 29 | int i,j,k,l,nfunk,kmax=50; 30 | double chisqmin; 31 | double *b,*db; 32 | double **p,*y; 33 | double d[2],errcomp; 34 | 35 | // Decode options 36 | if (strchr(opt,'n')!=NULL) OUTPUT=0; 37 | if (strchr(opt,'e')!=NULL) ERRCOMP=1; 38 | 39 | // Intialize y 40 | y=(double *) malloc(sizeof(double) * (n+1)); 41 | 42 | if (dchisq>=0.) { 43 | // Compute simplex and minimize function 44 | p=simplex(n,a,da); 45 | nfunk=dsmin(p,y,n,tol,func); 46 | 47 | // Average parameters 48 | for (i=0;i 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../rffft_internal.h" 10 | 11 | // Tests 12 | 13 | // Test SatDump filenames 14 | void rffft_internal_parse_satdump_filenames(void **state) { 15 | double samplerate = 0; 16 | double frequency = 0; 17 | char format = '\0'; 18 | char starttime[] = "YYYY-mm-ddTHH:MM:SS.sss"; 19 | char ref_format = '\0'; 20 | 21 | // s8 file without milliseconds 22 | ref_format = 'c'; 23 | assert_int_equal(0, rffft_params_from_filename("2023-08-05_08-02-00_16000000SPS_2274000000Hz.s8", &samplerate, &frequency, &format, starttime)); 24 | // assert_double_equal has been introduced in cmocka 1.1.6 not available on most distribs yet 25 | assert_float_equal(16e6, samplerate, 1e-12); 26 | assert_float_equal(2.274e9, frequency, 1e-12); 27 | assert_memory_equal(&ref_format, &format, 1); 28 | assert_string_equal("2023-08-05T08:02:00", starttime); 29 | 30 | // f32 file with milliseconds 31 | ref_format = 'f'; 32 | assert_int_equal(0, rffft_params_from_filename("2023-08-17_11-41-14-373_1000000SPS_100000000Hz.f32", &samplerate, &frequency, &format, starttime)); 33 | assert_float_equal(1e6, samplerate, 1e-12); 34 | assert_float_equal(100e6, frequency, 1e-12); 35 | assert_memory_equal(&ref_format, &format, 1); 36 | assert_string_equal("2023-08-17T11:41:14.373", starttime); 37 | 38 | // s16 file with broken milliseconds format 39 | ref_format = 'i'; 40 | assert_int_equal(0, rffft_params_from_filename("2023-08-05_18-02-45-1691258565.534000_8000000SPS_2284000000Hz.s16", &samplerate, &frequency, &format, starttime)); 41 | assert_float_equal(8e6, samplerate, 1e-12); 42 | assert_float_equal(2.284e9, frequency, 1e-12); 43 | assert_memory_equal(&ref_format, &format, 1); 44 | assert_string_equal("2023-08-05T18:02:45.534", starttime); 45 | 46 | // wav file with milliseconds 47 | ref_format = 'w'; 48 | assert_int_equal(0, rffft_params_from_filename("2023-08-07_16-36-47-749_2400000SPS_100000000Hz.wav", &samplerate, &frequency, &format, starttime)); 49 | assert_float_equal(2.4e6, samplerate, 1e-12); 50 | assert_float_equal(100e6, frequency, 1e-12); 51 | assert_memory_equal(&ref_format, &format, 1); 52 | assert_string_equal("2023-08-07T16:36:47.749", starttime); 53 | 54 | assert_int_equal(-1, rffft_params_from_filename("2023-08-05-19:59:30_16000000SPS_402000000Hz.f32", &samplerate, &frequency, &format, starttime)); 55 | } 56 | 57 | // Test GQRX filenames 58 | void rffft_internal_parse_gqrx_filenames(void **state) { 59 | double samplerate = 0; 60 | double frequency = 0; 61 | char format = '\0'; 62 | char starttime[] = "YYYY-mm-ddTHH:MM:SS.sss"; 63 | char ref_format = '\0'; 64 | 65 | ref_format = 'f'; 66 | assert_int_equal(0, rffft_params_from_filename("gqrx_20230806_151838_428000000_200000_fc.raw", &samplerate, &frequency, &format, starttime)); 67 | // assert_double_equal has been introduced in cmocka 1.1.6 not available on most distribs yet 68 | assert_float_equal(200e3, samplerate, 1e-12); 69 | assert_float_equal(428e6, frequency, 1e-12); 70 | assert_memory_equal(&ref_format, &format, 1); 71 | assert_string_equal("2023-08-06T15:18:38", starttime); 72 | 73 | assert_int_equal(-1, rffft_params_from_filename("gqrx_2023-08-06_15:18:38_428000000_200000_fc.raw", &samplerate, &frequency, &format, starttime)); 74 | } 75 | 76 | // Test SDR Console filenames 77 | void rffft_internal_parse_sdrconsole_filenames(void **state) { 78 | double samplerate = 0; 79 | double frequency = 0; 80 | char format = '\0'; 81 | char starttime[] = "YYYY-mm-ddTHH:MM:SS.sss"; 82 | char ref_format = '\0'; 83 | 84 | ref_format = 'w'; 85 | assert_int_equal(0, rffft_params_from_filename("07-Aug-2023 181711.798 401.774MHz.wav", &samplerate, &frequency, &format, starttime)); 86 | // assert_double_equal has been introduced in cmocka 1.1.6 not available on most distribs yet 87 | assert_float_equal(0, samplerate, 1e-12); 88 | assert_float_equal(401.774e6, frequency, 1e-12); 89 | assert_memory_equal(&ref_format, &format, 1); 90 | assert_string_equal("2023-08-07T18:17:11.798", starttime); 91 | 92 | assert_int_equal(-1, rffft_params_from_filename("07-Yol-2023 181711.798 401.774MHz.wav", &samplerate, &frequency, &format, starttime)); 93 | } 94 | 95 | // Entry point to run all tests 96 | int run_rffft_internal_tests() { 97 | const struct CMUnitTest tests[] = { 98 | cmocka_unit_test(rffft_internal_parse_satdump_filenames), 99 | cmocka_unit_test(rffft_internal_parse_gqrx_filenames), 100 | cmocka_unit_test(rffft_internal_parse_sdrconsole_filenames), 101 | }; 102 | 103 | return cmocka_run_group_tests_name("rffft internal", tests, NULL, NULL); 104 | } 105 | -------------------------------------------------------------------------------- /rffind.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "rftime.h" 7 | #include "rfio.h" 8 | 9 | #define LIM 128 10 | #define NMAX 64 11 | 12 | 13 | 14 | void filter(struct spectrogram s,int site_id,float sigma,char *filename,int graves) 15 | { 16 | int i,j,k,l; 17 | float s1,s2,avg,std,dz; 18 | FILE *file; 19 | double f; 20 | int *mask; 21 | float *sig; 22 | 23 | mask=(int *) malloc(sizeof(int)*s.nchan); 24 | sig=(float *) malloc(sizeof(float)*s.nchan); 25 | 26 | // Open file 27 | file=fopen(filename,"a"); 28 | 29 | // Loop over subints 30 | for (i=0;isigma*std) { 60 | mask[j]=0; 61 | l++; 62 | } 63 | } 64 | } 65 | // Reset mask 66 | for (j=0;jsigma) 69 | mask[j]=1; 70 | else 71 | mask[j]=0; 72 | } 73 | 74 | // Find maximum when points are adjacent 75 | for (j=0;j=0;j--) { 82 | if (mask[j]==1 && mask[j-1]==1) { 83 | if (s.z[i+s.nsub*j]1.0) { 93 | if (graves==0) 94 | fprintf(file,"%lf %lf %f %d\n",s.mjd[i],f,sig[j],site_id); 95 | else 96 | fprintf(file,"%lf %lf %f %d 9999\n",s.mjd[i],f,sig[j],site_id); 97 | } 98 | } 99 | } 100 | } 101 | 102 | fclose(file); 103 | 104 | free(mask); 105 | free(sig); 106 | 107 | return; 108 | } 109 | 110 | void usage(void) 111 | { 112 | printf("rffind: Find signals RF observations\n\n"); 113 | printf("-p Input path to file /a/b/c_??????.bin\n"); 114 | printf("-s Number of starting subintegration [0]\n"); 115 | printf("-l Number of subintegrations to plot [3600]\n"); 116 | printf("-f Frequency to zoom into (Hz)\n"); 117 | printf("-w Bandwidth to zoom into (Hz)\n"); 118 | printf("-o Frequency offset to apply\n"); 119 | printf("-C Site ID\n"); 120 | printf("-g GRAVES data\n"); 121 | printf("-S Sigma limit [default: 5.0]\n"); 122 | printf("-h This help\n"); 123 | } 124 | 125 | int main(int argc,char *argv[]) 126 | { 127 | int i,j,k,l,j0,j1,m=2,n; 128 | struct spectrogram s; 129 | char path[128]; 130 | int isub=0,nsub=0; 131 | char *env; 132 | int site_id=0,graves=0; 133 | float avg,std; 134 | int arg=0; 135 | float sigma=5.0; 136 | FILE *file; 137 | double f,f0=0.0,df0=0.0; 138 | char filename[128]="find.dat"; 139 | 140 | // Get site 141 | env=getenv("ST_COSPAR"); 142 | if (env!=NULL) { 143 | site_id=atoi(env); 144 | } else { 145 | printf("ST_COSPAR environment variable not found.\n"); 146 | } 147 | 148 | // Read arguments 149 | if (argc>1) { 150 | while ((arg=getopt(argc,argv,"p:f:w:s:l:hC:o:S:g"))!=-1) { 151 | switch (arg) { 152 | 153 | case 'p': 154 | strcpy(path,optarg); 155 | break; 156 | 157 | case 's': 158 | isub=atoi(optarg); 159 | break; 160 | 161 | case 'C': 162 | site_id=atoi(optarg); 163 | break; 164 | 165 | case 'l': 166 | nsub=atoi(optarg); 167 | break; 168 | 169 | case 'o': 170 | strcpy(filename,optarg); 171 | break; 172 | 173 | case 'f': 174 | f0=(double) atof(optarg); 175 | break; 176 | 177 | case 'g': 178 | graves=1; 179 | break; 180 | 181 | case 'S': 182 | sigma=atof(optarg); 183 | break; 184 | 185 | case 'w': 186 | df0=(double) atof(optarg); 187 | break; 188 | 189 | case 'h': 190 | usage(); 191 | return 0; 192 | 193 | default: 194 | usage(); 195 | return 0; 196 | } 197 | } 198 | } else { 199 | usage(); 200 | return 0; 201 | } 202 | 203 | if (nsub==0) { 204 | // Read data 205 | for (i=isub;;i++) { 206 | s=read_spectrogram(path,i,nsub,f0,df0,1,0.0); 207 | 208 | // Exit on emtpy file 209 | if (s.nsub==0) 210 | break; 211 | 212 | printf("Read spectrogram\n%d channels, %d subints\nFrequency: %g MHz\nBandwidth: %g MHz\n",s.nchan,s.nsub,s.freq*1e-6,s.samp_rate*1e-6); 213 | 214 | // Filter 215 | filter(s,site_id,sigma,filename,graves); 216 | 217 | // Free 218 | free_spectrogram(s); 219 | } 220 | } else { 221 | // Read data 222 | s=read_spectrogram(path,isub,nsub,f0,df0,1,0.0); 223 | 224 | // Exit on emtpy file 225 | if (s.nsub>0) { 226 | printf("Read spectrogram\n%d channels, %d subints\nFrequency: %g MHz\nBandwidth: %g MHz\n",s.nchan,s.nsub,s.freq*1e-6,s.samp_rate*1e-6); 227 | 228 | // Filter 229 | filter(s,site_id,sigma,filename,graves); 230 | } 231 | 232 | // Free 233 | free_spectrogram(s); 234 | } 235 | 236 | return 0; 237 | } 238 | 239 | -------------------------------------------------------------------------------- /zscale.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "rftime.h" 6 | #include "rfio.h" 7 | 8 | #define MAX_REJECT 0.5 9 | #define MIN_NPIXELS 5 10 | #define GOOD_PIXEL 0 11 | #define BAD_PIXEL 1 12 | #define KREJ 2.5 13 | #define MAX_ITERATIONS 5 14 | 15 | void zsc_sample(struct spectrogram *image, int maxpix, double *samples, int *nsamples) { 16 | int nc = image->nchan; 17 | int nl = image->nsub; 18 | double stride = fmax(1.0, sqrt((nc - 1) * (nl - 1) / (double)maxpix)); 19 | int istride = (int)stride; 20 | 21 | int count = 0; 22 | for (int i = 0; i < nc; i += istride) { 23 | for (int j = 0; j < nl; j += istride) { 24 | if (count >= maxpix) { 25 | *nsamples = count; 26 | return; 27 | } 28 | samples[count++] = image->z[i * nl + j]; 29 | } 30 | } 31 | *nsamples = count; 32 | } 33 | 34 | void zsc_compute_sigma(double *flat, int *badpix, int npix, int *ngoodpix, double *mean, double *sigma) { 35 | double sumz = 0.0; 36 | double sumsq = 0.0; 37 | *ngoodpix = 0; 38 | 39 | for (int i = 0; i < npix; i++) { 40 | if (badpix[i] == GOOD_PIXEL) { 41 | sumz += flat[i]; 42 | sumsq += flat[i] * flat[i]; 43 | (*ngoodpix)++; 44 | } 45 | } 46 | 47 | if (*ngoodpix == 0) { 48 | *mean = NAN; 49 | *sigma = NAN; 50 | } else if (*ngoodpix == 1) { 51 | *mean = sumz; 52 | *sigma = NAN; 53 | } else { 54 | *mean = sumz / *ngoodpix; 55 | double temp = sumsq / (*ngoodpix - 1) - (sumz * sumz) / (*ngoodpix * (*ngoodpix - 1)); 56 | *sigma = temp < 0.0 ? 0.0 : sqrt(temp); 57 | } 58 | } 59 | 60 | void zsc_fit_line(double *samples, int npix, double krej, int ngrow, int maxiter, 61 | int *ngoodpix_out, double *zstart, double *zslope) { 62 | double xscale = 2.0 / (npix - 1); 63 | double *xnorm = (double *)malloc(npix * sizeof(double)); 64 | int *badpix = (int *)calloc(npix, sizeof(int)); 65 | int *conv = (int *)calloc(npix, sizeof(int)); 66 | 67 | for (int i = 0; i < npix; i++) { 68 | xnorm[i] = i * xscale - 1.0; 69 | } 70 | 71 | int ngoodpix = npix; 72 | int minpix = fmax(MIN_NPIXELS, (int)(npix * MAX_REJECT)); 73 | int last_ngoodpix = npix + 1; 74 | 75 | double intercept = 0.0; 76 | double slope = 0.0; 77 | 78 | for (int niter = 0; niter < maxiter; niter++) { 79 | if (ngoodpix >= last_ngoodpix || ngoodpix < minpix) { 80 | break; 81 | } 82 | 83 | double sumx = 0.0, sumxx = 0.0, sumxy = 0.0, sumy = 0.0; 84 | int count = 0; 85 | for (int i = 0; i < npix; i++) { 86 | if (badpix[i] == GOOD_PIXEL) { 87 | sumx += xnorm[i]; 88 | sumxx += xnorm[i] * xnorm[i]; 89 | sumxy += xnorm[i] * samples[i]; 90 | sumy += samples[i]; 91 | count++; 92 | } 93 | } 94 | 95 | double delta = count * sumxx - sumx * sumx; 96 | intercept = (sumxx * sumy - sumx * sumxy) / delta; 97 | slope = (count * sumxy - sumx * sumy) / delta; 98 | 99 | double *fitted = (double *)malloc(npix * sizeof(double)); 100 | double *flat = (double *)malloc(npix * sizeof(double)); 101 | for (int i = 0; i < npix; i++) { 102 | fitted[i] = xnorm[i] * slope + intercept; 103 | flat[i] = samples[i] - fitted[i]; 104 | } 105 | 106 | double mean, sigma; 107 | zsc_compute_sigma(flat, badpix, npix, &ngoodpix, &mean, &sigma); 108 | 109 | double threshold = sigma * krej; 110 | double lcut = -threshold; 111 | double hcut = threshold; 112 | 113 | for (int i = 0; i < npix; i++) { 114 | if (flat[i] < lcut || flat[i] > hcut) { 115 | badpix[i] = BAD_PIXEL; 116 | } 117 | } 118 | // temp = np.convolve(badpix, np.ones(ngrow), mode='same') 119 | // fixed convolution computation 120 | for (int i = 0; i < npix; i++) { 121 | int sum = 0; 122 | for (int j = -ngrow/2; j < ngrow/2; j++) { 123 | int idx = i + j; 124 | if (idx >= 0 && idx < npix) { 125 | sum+= badpix[idx]; 126 | } 127 | } 128 | conv[i] = sum; 129 | } 130 | 131 | free(fitted); 132 | free(flat); 133 | 134 | last_ngoodpix = ngoodpix; 135 | ngoodpix = 0; 136 | for (int i = 0; i < npix; i++) { 137 | badpix[i] = conv[i]; 138 | if (conv[i] == GOOD_PIXEL) { 139 | ngoodpix++; 140 | } 141 | } 142 | } 143 | 144 | *zstart = intercept - slope; 145 | *zslope = slope * xscale; 146 | *ngoodpix_out = ngoodpix; 147 | free(xnorm); 148 | free(badpix); 149 | free(conv); 150 | } 151 | 152 | int compare_doubles(const void *a, const void *b) { 153 | double diff = (*(double *)a - *(double *)b); 154 | return (diff > 0) - (diff < 0); 155 | } 156 | 157 | void zscale(struct spectrogram *image, int nsamples, double contrast, double *z1, double *z2) { 158 | double *samples = (double *)malloc(nsamples * sizeof(double)); 159 | int npix; 160 | 161 | zsc_sample(image, nsamples, samples, &npix); 162 | 163 | qsort(samples, npix, sizeof(double), compare_doubles); 164 | 165 | double zmin = samples[0]; 166 | double zmax = samples[npix - 1]; 167 | double median; 168 | int center_pixel = (npix - 1) / 2; 169 | if (npix % 2 == 1) { 170 | median = samples[center_pixel]; 171 | } else { 172 | median = 0.5 * (samples[center_pixel] + samples[center_pixel + 1]); 173 | } 174 | 175 | int ngoodpix; 176 | double zstart, zslope; 177 | zsc_fit_line(samples, npix, KREJ, fmax(1, npix * 0.01), MAX_ITERATIONS, 178 | &ngoodpix, &zstart, &zslope); 179 | 180 | 181 | if (ngoodpix < fmax(MIN_NPIXELS, npix * MAX_REJECT)) { 182 | *z1 = zmin; 183 | *z2 = zmax; 184 | } else { 185 | if (contrast > 0) zslope /= contrast; 186 | *z1 = fmax(zmin, median - (center_pixel - 1) * zslope); 187 | *z2 = fmin(zmax, median + (npix - center_pixel) * zslope); 188 | } 189 | 190 | free(samples); 191 | } 192 | -------------------------------------------------------------------------------- /contrib/analyze_artifact.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from astropy.time import Time 4 | 5 | from datetime import datetime, timedelta 6 | 7 | import argparse 8 | import ephem 9 | import h5py 10 | import json 11 | import os 12 | import sys 13 | 14 | import matplotlib.pyplot as plt 15 | import numpy as np 16 | import pandas as pd 17 | 18 | 19 | def load_artifact(filename): 20 | hdf5_file = h5py.File(filename, 'r') 21 | if hdf5_file.attrs['artifact_version'] != 2: 22 | print("unsupported artifact version {}".format(hdf5_file.attrs['artifact_version'])) 23 | # return 24 | wf = hdf5_file.get('waterfall') 25 | data = (np.array(wf['data']) * np.array(wf['scale']) + np.array(wf['offset'])) 26 | metadata = json.loads(hdf5_file.attrs['metadata']) 27 | 28 | return hdf5_file, wf, data, metadata 29 | 30 | def extract_peaks(data): 31 | """ 32 | Returns 33 | ------- 34 | points: (time_index, freq_index) 35 | measurements: measurement tuples of relative time in seconds and relative frequency in hertz 36 | """ 37 | 38 | snr = (np.max(data, axis=1) - np.mean(data, axis=1)) / np.std(data, axis=1) 39 | 40 | # Integrate each channel along the whole observation, 41 | # This filter helps under the assumption of minimal Doppler deviaion 42 | snr_integrated = np.zeros(data[0].shape) 43 | for row in data: 44 | snr_integrated += (row - np.mean(row)) / np.std(row) 45 | snr_integrated = pd.Series(snr_integrated/len(snr_integrated)) 46 | 47 | SNR_CUTOFF = 4 48 | CHANNEL_WINDOW_SIZE = 4 49 | channel_mask = (snr_integrated.diff().abs() / snr_integrated.diff().std() > SNR_CUTOFF).rolling(CHANNEL_WINDOW_SIZE, min_periods=1).max() 50 | rows=[] 51 | for i, row in enumerate(channel_mask): 52 | if not row: 53 | continue 54 | rows.append(i) 55 | 56 | points_all = [] 57 | points_filtered = [] 58 | for i,x in enumerate(np.argmax(data, axis=1)): 59 | points_all.append([i, x]) 60 | if x in rows: 61 | points_filtered.append([i, x]) 62 | 63 | points_all = np.array(points_all) 64 | points_filtered = np.array(points_filtered) 65 | 66 | if len(points_filtered) == 0: 67 | print("WARNING: No measurement passed filter, loosening requirements now.") 68 | points_filtered = points_all 69 | measurements = None 70 | 71 | measurements = np.vstack((wf['relative_time'][points_filtered[:,0]], 72 | [wf['frequency'][x] for x in points_filtered[:,1]])) 73 | return snr, measurements, points_filtered 74 | 75 | def plot_measurements_all(wf, data): 76 | plt.plot(wf['relative_time'][:], 77 | [wf['frequency'][x] for x in np.argmax(data[:], axis=1)], 78 | '.', 79 | label='all') 80 | 81 | def plot_measurements_selected(measurements): 82 | plt.plot(measurements[0,:], 83 | measurements[1,:], 84 | '.', 85 | label='filtered') 86 | 87 | def plot_legend(observation_id): 88 | plt.legend() 89 | plt.title("Observation #{} - Maximum Values".format(observation_id)) 90 | plt.grid() 91 | plt.xlabel('Elapsed Time / s') 92 | plt.ylabel('rel. Frequency / kHz') 93 | plt.show() 94 | 95 | 96 | 97 | def dedoppler(measurements, metadata): 98 | tle = metadata['tle'].split('\n') 99 | start_time = datetime.strptime(wf.attrs['start_time'].decode('ascii'), 100 | '%Y-%m-%dT%H:%M:%S.%fZ') 101 | f_center = float(metadata['frequency']) 102 | 103 | # Initialize SGP4 propagator / pyephem 104 | satellite = ephem.readtle('sat', tle[1], tle[2]) 105 | observer = ephem.Observer() 106 | observer.lat = str(metadata['location']['latitude']) 107 | observer.lon = str(metadata['location']['longitude']) 108 | observer.elevation = metadata['location']['altitude'] 109 | 110 | def remove_doppler_correction(t, freq): 111 | """ 112 | Arguments 113 | --------- 114 | t - float: Time in seconds 115 | freq - float: Relative Frequency in herz 116 | """ 117 | observer.date = t 118 | satellite.compute(observer) 119 | v = satellite.range_velocity 120 | df = f_center * v / ephem.c 121 | return f_center + freq - df*2 122 | 123 | output = [] 124 | for dt,df in measurements.T: 125 | t = start_time + timedelta(seconds=dt) 126 | f = f_center + df 127 | freq_recv = remove_doppler_correction(t, f) 128 | output.append((t, freq_recv)) 129 | return output 130 | 131 | def save_rffit_data(filename, measurements, site_id): 132 | with open(filename, 'w') as file_out: 133 | for time, freq in measurements: 134 | line = '{:.6f}\t{:.2f}\t1.0\t{}\n'.format(Time(time).mjd, freq, site_id) 135 | file_out.write(line) 136 | 137 | if __name__ == '__main__': 138 | parser = argparse.ArgumentParser(description='Analyze SatNOGS Artifact.') 139 | parser.add_argument('--observation_id', type=str, 140 | help='SatNOGS Observation ID') 141 | parser.add_argument('--filename', type=str, 142 | help='SatNOGS Artifacts File') 143 | parser.add_argument('--output_file', type=str, 144 | help='STRF-compatible output file') 145 | parser.add_argument('--site_id', type=int, required=True, 146 | help='STRF Site ID') 147 | args = parser.parse_args() 148 | 149 | if args.observation_id: 150 | # Use canonic file paths, ignoring `--filename` and `--output_path` 151 | filename = '{}/{}.h5'.format(os.getenv('SATNOGS_ARTIFACTS_DIR'), args.observation_id) 152 | output_file = '{}/{}.dat'.format(os.getenv('SATNOGS_DOPPLER_OBS_DIR'), args.observation_id) 153 | else: 154 | if not any([args.filename, args.output_file]): 155 | print('ERROR: Missing arguments') 156 | filename = args.filename 157 | output_file = args.output_file 158 | 159 | print('Load {}'.format(filename)) 160 | hdf5_file, wf, data, metadata = load_artifact(filename) 161 | 162 | print('Extract measurements...') 163 | snr, measurements, points = extract_peaks(data) 164 | plot_measurements_all(wf, data) 165 | plot_measurements_selected(measurements) 166 | plot_legend(args.observation_id) 167 | 168 | m2 = dedoppler(measurements, metadata) 169 | save_rffit_data(output_file, m2, site_id=args.site_id) 170 | print('Data written in {}'.format(output_file)) 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STRF 2 | 3 | **strf** is the satellite tracking toolkit for radio observations (RF). The software is designed to allow tracking of satellites from radio observations, using Doppler curves to identify satellites and/or determine their orbits. 4 | 5 | The software is designed for *linux* operating systems, and will work with most software defined radios (SDRs), certainly those that are supported by http://www.gnuradio.org. The software comes with tools for data acquisition, performing FFTs to generate timestamped spectrograms (waterfall plots), and analysis, to extract and analyse Doppler curves. 6 | 7 | Install 8 | ------ 9 | 10 | * For Ubuntu systems or similar. 11 | * Install dependencies: `sudo apt install git make gcc pgplot5 gfortran libpng-dev libx11-dev libgsl-dev libfftw3-dev libsox-dev dos2unix` 12 | * On recent systems (starting with Debian 13 Trixie and Ubuntu 24.10 Oracular), you'll need to install `sudo apt install pgplot5-dev` 13 | * Clone repository: `git clone https://github.com/cbassa/strf.git` 14 | * Compile: `cd strf; make` 15 | * Install (in `/usr/local`): `sudo make install` 16 | * To build and run the unit tests, also install the cmocka dependencies: `sudo apt install libcmocka-dev libcmocka0` and then run `make tests` 17 | 18 | Configure 19 | --------- 20 | * You will need to set the following environment variables in your login file to run **strf**. 21 | * `ST_DATADIR` path to **strf** directory (e.g. `$HOME/software/strf`, default: './') 22 | * `ST_TLEDIR` path to TLE directory (e.g. `$HOME/tle`) 23 | * `ST_COSPAR` COSPAR site number (add to site location to `$ST_DATADIR/data/sites.txt`) 24 | * `ST_LOGIN` space-track.org login info (of the form `ST_LOGIN="identity=username&password=password"`) 25 | * `ST_SITES_TXT` path to sites.txt (optional, default: `$ST_DATADIR/data/sites.txt`) 26 | * Run `tleupdate` to download latest TLEs. 27 | * You should install NTP support on the system and configure time/date to automatically 28 | synchronize to time servers. 29 | 30 | Operation 31 | --------- 32 | The main use of **strf** is to acquire IQ data from SDRs and produce time stamped spectrograms with the `rffft` application. `rffft` will perform Fast Fourier Transforms on the input data to a user defined number of spectral channels (via the `-c` command line option), and integrate/average these to a user defined integration length (via the `-t` command line option). The output will be a `*.bin` file which contains a 256 byte human readable header (which can be inspected with `head -c256`), followed by a binary array of floating point numbers representing the power in the spectral channels. This is an example of the 256 byte header: 33 | 34 | HEADER 35 | UTC_START 2018-01-12T15:59:13.524 36 | FREQ 2244000000.000000 Hz 37 | BW 4000000.000000 Hz 38 | LENGTH 0.998922 s 39 | NCHAN 40000 40 | NSUB 60 41 | END 42 | 43 | The header keywords are mostly self explanatory, though the `NSUB` keyword specifies that this single `bin` file contains 60 spectra. 44 | 45 | `rffft` can read from a previously recorded IQ recording, but is usually operated in realtime mode by reading IQ data from a so-called named pipe or fifo (first in, first out). Here, the SDR writes IQ data to a fifo (instead of a file), and `rffft` reads the samples from the fifo. Using an **airspy** as an example, it could be configured as follows: 46 | 47 | mkfifo fifo 48 | rffft -i fifo -f 101e6 -s 2.5e6 & 49 | airspy_rx -a 1 -f 101 -t 2 -r fifo 50 | 51 | Here, we first make the fifo `mkfifo fifo`, then start `rffft` to read from the fifo (`-i` option), with a 101MHz center frequency (`-f` option) and a 2.5MHz sample rate (`-s` option). The `&` puts this command in the background. Finally, we start obtaining IQ data from the **airspy** with `airspy_rx` in the 2.5MHz sampling mode (`-a 1`) at the same frequency (`-f 101`, in MHz), with the 2.5MHz sample rate (`-t 2`) and writing the samples to the fifo (`-r fifo`). Similar scripts can be made with other SDRs, and otherwise with **gnuradio** flow graphs where the output file sink is a fifo. 52 | 53 | Alternatively, when no input filename is given (with the `-i` option), `rffft` will read from stdin so it is possible to directly pipe an SDR receiver's application into `rffft`. 54 | 55 | With an RTL-SDR: 56 | 57 | rtl_sdr -g 29 -f 97400000 -s 2048000 - | ./rffft -f 97400000 -s 2048000 -F char 58 | 59 | Here we use the **RTL-SDR** receiver with `rtl_sdr` with a gain of 29dB (`-g 29`), a center frequency of 97.4MHz (`-f 97400000`, in Hz) and a samplerate of 2.048MS/s (`-s 2048000`, in S/s). Note the trailing dash (`-`) in the `rtl_sdr` command to tell it to write to stdout instead of a file so it can be piped (`|`) through `rffft`. The same center frequency and samplerate are given to `rffft`. As `rtl_sdr` outputs data as 8 bits, `-F char` is required to tell `rffft` the format of the data. 60 | 61 | With a HackRF: 62 | 63 | hackrf_transfer -l 24 -g 32 -f 97400000 -s 8000000 -r - | ./rffft -f 97400000 -s 8000000 -F char -c 100 64 | 65 | Here we use the **HackRF** receiver with `hackrf_transfer` with a lna gain of 24dB (`-l 24`), an IF gain of 32dB (`-g 32`), a center frequency of 97.4MHz (`-f 97400000`, in Hz) and a samplerate of 8MS/s (`-s 8000000`, in S/s). The output file is given as stdout (`-r -`). Again the same frequency and samplerate are given to `rffft` and as `hackrf_transfer` also outputs 8 bit data `-F char` is also required for `rffft`. 66 | 67 | With a Adalm Pluto: 68 | 69 | iio_attr -u usb:x.y.z -c ad9361-phy RX_LO frequency 97400000 70 | iio_attr -u usb:x.y.z -c ad9361-phy voltage0 rf_port_select A_BALANCED 71 | iio_attr -u usb:x.y.z -c ad9361-phy voltage0 rf_bandwidth 2000000 72 | iio_attr -u usb:x.y.z -c ad9361-phy voltage0 sampling_frequency 2000000 73 | iio_attr -u usb:x.y.z -c ad9361-phy voltage0 gain_control_mode manual 74 | iio_attr -u usb:x.y.z -c ad9361-phy voltage0 hardwaregain 60 75 | iio_readdev -u usb:x.y.z -b 4194304 cf-ad9361-lpc | ./rffft -f 97400000 -s 2000000 -F int 76 | 77 | Here we use the **Adalm Pluto** transceiver with `iio_readdev`. The transceiver is connected via USB. Replace `x.y.z` by the USB bus ID of your transceiver. You can retreive the USB IDs from the output of `iio_info -s`. Connection via Ethernet is possible as well. Before receiving we have to set center frequency, samplerate, gain, ... by a bunch of `iio_attr` commands. 78 | 79 | With I/Q recordings obtained from Gqrx: 80 | 81 | ./rffft -i gqrx_YYYYMMDD_HHMMSS_97400000_2000000_fc.raw -f 97400000 -s 2000000 -F float -T "YYYY-MM-DDTHH:MM:SS" 82 | 83 | **Gqrx** records complex samples into `raw` files. The filename contains date, time, center frequency and samplerate separated by underscores. Replace `YYYYMMDD` and `HHMMSS` by your actual time and respectively. Pay attention to insert an uppercase `T` between date and time in the time stamp parameter of the `rffft` command. 84 | 85 | Alternatively, with I/Q recordings from GQRX and SatDump, the `-P` option can be used to automatically extract the timestamp, format, frequency and samplerate from the filename: 86 | 87 | ./rffft -P -i gqrx_YYYYMMDD_HHMMSS_97400000_2000000_fc.raw 88 | 89 | Reading WAV files: 90 | 91 | It is also possible to read WAV files. This is compatible with any WAV file, 16 bit int or 32 bit float for example as well as RF64 (WAV64) files. 92 | 93 | ./rffft -i recording.wav -f 97400000 -s 48000 -F wav -T "YYYY-MM-DDTHH:MM:SS" 94 | 95 | For WAV files exported from SatDump and SDR Console the `-P` options will automatically extract the correct parameters from the filename. 96 | 97 | The output spectrograms can be viewed and analysed using `rfplot`. 98 | -------------------------------------------------------------------------------- /rfio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "rftime.h" 6 | #include "rfio.h" 7 | #include "zscale.h" 8 | 9 | struct spectrogram read_spectrogram(char *prefix,int isub,int nsub,double f0,double df0,int nbin,double foff) 10 | { 11 | int i,j,k,l,flag=0,status,msub,ibin,nadd,nbits=-32; 12 | char filename[128],header[256],nfd[32]; 13 | FILE *file; 14 | struct spectrogram s; 15 | float *z,zavg,zstd; 16 | char *cz; 17 | int nch,j0,j1; 18 | double freq,samp_rate; 19 | float length; 20 | int nchan,dummy; 21 | float s1,s2; 22 | 23 | // Open first file to get number of channels 24 | sprintf(filename,"%s_%06d.bin",prefix,isub); 25 | 26 | // Open file 27 | file=fopen(filename,"r"); 28 | if (file==NULL) { 29 | printf("%s does not exist\n",filename); 30 | s.nsub=0; 31 | return s; 32 | } 33 | 34 | // Read header 35 | status=fread(header,sizeof(char),256,file); 36 | if (strstr(header,"NBITS 8")==NULL) { 37 | status=sscanf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\n",s.nfd0,&s.freq,&s.samp_rate,&length,&nch,&msub); 38 | } else { 39 | status=sscanf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\nNBITS 8\nMEAN %f\nRMS %f",s.nfd0,&s.freq,&s.samp_rate,&length,&nch,&msub,&zavg,&zstd); 40 | nbits=8; 41 | } 42 | s.freq+=foff; 43 | 44 | // Close file 45 | fclose(file); 46 | 47 | // Compute plotting channel 48 | if (f0>0.0 && df0>0.0) { 49 | s.nchan=(int) (df0/s.samp_rate*(float) nch); 50 | 51 | j0=(int) ((f0-0.5*df0-s.freq+0.5*s.samp_rate)*(float) nch/s.samp_rate); 52 | j1=(int) ((f0+0.5*df0-s.freq+0.5*s.samp_rate)*(float) nch/s.samp_rate); 53 | 54 | if (j0<0 || j1>nch) { 55 | fprintf(stderr,"Requested frequency range out of limits\n"); 56 | s.nsub=0; 57 | s.nchan=0; 58 | return s; 59 | } 60 | } else { 61 | s.nchan=nch; 62 | j0=0; 63 | j1=s.nchan; 64 | } 65 | 66 | // Read whole file if not specified 67 | if (nsub==0 && msub>0) 68 | nsub=msub; 69 | 70 | // Number of subints 71 | s.nsub=nsub/nbin; 72 | s.msub=msub; 73 | s.isub=isub; 74 | 75 | printf("Allocating %.2f MB of memory\n",(4* (float) s.nchan * (float) s.nsub)/(1024 * 1024)); 76 | 77 | // Allocate 78 | s.z=(float *) malloc(sizeof(float)*s.nchan*s.nsub); 79 | s.zavg=(float *) malloc(sizeof(float)*s.nsub); 80 | s.zstd=(float *) malloc(sizeof(float)*s.nsub); 81 | z=(float *) malloc(sizeof(float)*nch); 82 | cz=(char *) malloc(sizeof(char)*nch); 83 | s.mjd=(double *) malloc(sizeof(double)*s.nsub); 84 | s.length=(float *) malloc(sizeof(float)*s.nsub); 85 | 86 | // Initialize 87 | for (j=0;j0) 156 | { 157 | s.mjd[i]/=(float) nadd; 158 | 159 | for (j=0;j0.0 && df0>0.0) { 165 | s.freq=f0; 166 | s.samp_rate=df0; 167 | } 168 | 169 | // Compute averages 170 | for (i=0;is.zmax) s.zmax=s.zavg[i]+1.0*s.zstd[i]; 195 | } 196 | } 197 | 198 | double z1,z2; 199 | zscale(&s, s.nsub, 0.25,&z1, &z2); 200 | printf("z1 = %f, z2 = %f\n", z1, z2); 201 | s.zmin = z1; 202 | s.zmax = z2; 203 | // Free 204 | free(z); 205 | free(cz); 206 | 207 | return s; 208 | } 209 | 210 | void write_spectrogram(struct spectrogram s,char *prefix) 211 | { 212 | int i,j; 213 | FILE *file; 214 | char header[256]="",filename[256],nfd[32]; 215 | float *z; 216 | double mjd; 217 | 218 | // Allocate 219 | z=(float *) malloc(sizeof(float)*s.nchan); 220 | 221 | // Generate filename 222 | sprintf(filename,"%s_%06d.bin",prefix,0); 223 | 224 | // Open file 225 | file=fopen(filename,"w"); 226 | 227 | // Loop over subints 228 | for (i=0;i 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | // Filename formats: 10 | // - SatDump 11 | // - 2023-08-05_08-02-00_16000000SPS_2274000000Hz.s8 12 | // - 2023-08-05_18-02-45-534_16000000SPS_2284000000Hz.s16 13 | // - 2023-08-05_18-02-45-1691258565.534000_16000000SPS_2284000000Hz.f32 14 | // - 2023-08-07_16-36-47-1691426207.749000_2400000SPS_100000000Hz.wav 15 | // s8: char, s16 short int, f32 float. 16 | // SatDump also supports a compressed versions of s8/s16/f32 with .ziq 17 | // extension. Those are not yet supported 18 | // timestamp can have an added milliseconds field, configurable. This feature 19 | // was broken during some time so files with this convention still exists. 20 | // - GQRX: 21 | // - gqrx_20230806_151838_428000000_200000_fc.raw 22 | // format always float32 23 | // - SDR Console: 24 | // - 07-Aug-2023 181711.798 401.774MHz.wav 25 | int rffft_params_from_filename(char * filename, double * samplerate, double * frequency, char * format, char * starttime) { 26 | // Temp vars to hold parsed values 27 | int p_year, p_month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds; 28 | int p_dummy_int; 29 | double p_samplerate, p_frequency; 30 | char p_month_string[16], p_format[16]; 31 | char p_dummy_string[128]; 32 | int parsed_tokens; 33 | 34 | char * base_filename = basename(filename); 35 | 36 | // Broken SatDump string with milliseconds activated 37 | parsed_tokens = sscanf( 38 | base_filename, 39 | "%04d-%02d-%02d_%02d-%02d-%02d-%d.%06d_%lfSPS_%lfHz.%s", 40 | &p_year, 41 | &p_month, 42 | &p_day, 43 | &p_hours, 44 | &p_minutes, 45 | &p_seconds, 46 | &p_dummy_int, 47 | &p_fractal_seconds, 48 | &p_samplerate, 49 | &p_frequency, 50 | p_format); 51 | 52 | if (parsed_tokens == 11) { 53 | *samplerate = p_samplerate; 54 | *frequency = p_frequency; 55 | 56 | if ((strlen(p_format) == 2) && (strncmp("s8", p_format, 2) == 0)) { 57 | *format = 'c'; 58 | } else if ((strlen(p_format) == 3) && (strncmp("s16", p_format, 3) == 0)) { 59 | *format = 'i'; 60 | } else if ((strlen(p_format) == 3) && (strncmp("f32", p_format, 3) == 0)) { 61 | *format = 'f'; 62 | } else if ((strlen(p_format) == 3) && (strncmp("wav", p_format, 3) == 0)) { 63 | *format = 'w'; 64 | } else { 65 | printf("Unsupported SatDump format %s\n", p_format); 66 | return -1; 67 | } 68 | 69 | snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds / 1000); 70 | 71 | return 0; 72 | } 73 | 74 | // SatDump string with milliseconds activated 75 | parsed_tokens = sscanf( 76 | base_filename, 77 | "%04d-%02d-%02d_%02d-%02d-%02d-%03d_%lfSPS_%lfHz.%s", 78 | &p_year, 79 | &p_month, 80 | &p_day, 81 | &p_hours, 82 | &p_minutes, 83 | &p_seconds, 84 | &p_fractal_seconds, 85 | &p_samplerate, 86 | &p_frequency, 87 | p_format); 88 | 89 | if (parsed_tokens == 10) { 90 | *samplerate = p_samplerate; 91 | *frequency = p_frequency; 92 | 93 | if ((strlen(p_format) == 2) && (strncmp("s8", p_format, 2) == 0)) { 94 | *format = 'c'; 95 | } else if ((strlen(p_format) == 3) && (strncmp("s16", p_format, 3) == 0)) { 96 | *format = 'i'; 97 | } else if ((strlen(p_format) == 3) && (strncmp("f32", p_format, 3) == 0)) { 98 | *format = 'f'; 99 | } else if ((strlen(p_format) == 3) && (strncmp("wav", p_format, 3) == 0)) { 100 | *format = 'w'; 101 | } else { 102 | printf("Unsupported SatDump format %s\n", p_format); 103 | return -1; 104 | } 105 | 106 | snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds); 107 | 108 | return 0; 109 | } 110 | 111 | // SatDump string without milliseconds activated 112 | parsed_tokens = sscanf( 113 | base_filename, 114 | "%04d-%02d-%02d_%02d-%02d-%02d_%lfSPS_%lfHz.%s", 115 | &p_year, 116 | &p_month, 117 | &p_day, 118 | &p_hours, 119 | &p_minutes, 120 | &p_seconds, 121 | &p_samplerate, 122 | &p_frequency, 123 | p_format); 124 | 125 | if (parsed_tokens == 9) { 126 | *samplerate = p_samplerate; 127 | *frequency = p_frequency; 128 | 129 | if ((strlen(p_format) == 2) && (strncmp("s8", p_format, 2) == 0)) { 130 | *format = 'c'; 131 | } else if ((strlen(p_format) == 3) && (strncmp("s16", p_format, 3) == 0)) { 132 | *format = 'i'; 133 | } else if ((strlen(p_format) == 3) && (strncmp("f32", p_format, 3) == 0)) { 134 | *format = 'f'; 135 | } else if ((strlen(p_format) == 3) && (strncmp("wav", p_format, 3) == 0)) { 136 | *format = 'w'; 137 | } else { 138 | printf("Unsupported SatDump format %s\n", p_format); 139 | return -1; 140 | } 141 | 142 | snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds); 143 | 144 | return 0; 145 | } 146 | 147 | // GQRX 148 | parsed_tokens = sscanf( 149 | base_filename, 150 | "gqrx_%04d%02d%02d_%02d%02d%02d_%lf_%lf_%s.raw", 151 | &p_year, 152 | &p_month, 153 | &p_day, 154 | &p_hours, 155 | &p_minutes, 156 | &p_seconds, 157 | &p_frequency, 158 | &p_samplerate, 159 | p_dummy_string); 160 | 161 | if (parsed_tokens == 9) { 162 | *samplerate = p_samplerate; 163 | *frequency = p_frequency; 164 | *format = 'f'; 165 | 166 | snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d", p_year, p_month, p_day, p_hours, p_minutes, p_seconds); 167 | 168 | return 0; 169 | } 170 | 171 | // SDR Console 172 | parsed_tokens = sscanf( 173 | base_filename, 174 | "%02d-%3s-%04d %02d%02d%02d.%03d %lfMHz.wav", 175 | &p_day, 176 | p_month_string, 177 | &p_year, 178 | &p_hours, 179 | &p_minutes, 180 | &p_seconds, 181 | &p_fractal_seconds, 182 | &p_frequency); 183 | 184 | if (parsed_tokens == 8) { 185 | *samplerate = 0; // Will be set when opening the wav 186 | *frequency = p_frequency * 1e6; 187 | *format = 'w'; 188 | 189 | int month; 190 | 191 | if ((strlen(p_month_string) == 3) && (strncmp("Jan", p_month_string, 3) == 0)) { 192 | month = 1; 193 | } else if ((strlen(p_month_string) == 3) && (strncmp("Feb", p_month_string, 3) == 0)) { 194 | month = 2; 195 | } else if ((strlen(p_month_string) == 3) && (strncmp("Mar", p_month_string, 3) == 0)) { 196 | month = 3; 197 | } else if ((strlen(p_month_string) == 3) && (strncmp("Apr", p_month_string, 3) == 0)) { 198 | month = 4; 199 | } else if ((strlen(p_month_string) == 3) && (strncmp("May", p_month_string, 3) == 0)) { 200 | month = 5; 201 | } else if ((strlen(p_month_string) == 3) && (strncmp("Jun", p_month_string, 3) == 0)) { 202 | month = 6; 203 | } else if ((strlen(p_month_string) == 3) && (strncmp("Jul", p_month_string, 3) == 0)) { 204 | month = 7; 205 | } else if ((strlen(p_month_string) == 3) && (strncmp("Aug", p_month_string, 3) == 0)) { 206 | month = 8; 207 | } else if ((strlen(p_month_string) == 3) && (strncmp("Sep", p_month_string, 3) == 0)) { 208 | month = 9; 209 | } else if ((strlen(p_month_string) == 3) && (strncmp("Oct", p_month_string, 3) == 0)) { 210 | month = 10; 211 | } else if ((strlen(p_month_string) == 3) && (strncmp("Nov", p_month_string, 3) == 0)) { 212 | month = 11; 213 | } else if ((strlen(p_month_string) == 3) && (strncmp("Dec", p_month_string, 3) == 0)) { 214 | month = 12; 215 | } else { 216 | printf("Unparsable month in SDR Console format %s\n", p_format); 217 | return -1; 218 | } 219 | 220 | snprintf(starttime, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", p_year, month, p_day, p_hours, p_minutes, p_seconds, p_fractal_seconds); 221 | 222 | return 0; 223 | } 224 | 225 | return -1; 226 | } 227 | -------------------------------------------------------------------------------- /tests/data/catalog.tle: -------------------------------------------------------------------------------- 1 | 0 LEMUR 2 KAREN_B 2 | 1 52736U 22057E 23036.86389985 .00017304 00000-0 82793-3 0 9996 3 | 2 52736 97.5231 153.7414 0006358 83.3132 276.8826 15.19025879 38769 4 | 0 GHGSAT-C3 5 | 1 52737U 22057F 23037.09204999 .00003459 00000-0 19726-3 0 9996 6 | 2 52737 97.5315 153.4851 0010090 90.2911 83.7555 15.13397267 38744 7 | 0 PLANETUM1 8 | 1 52738U 22057G 23037.05354110 .00012014 00000-0 63489-3 0 9996 9 | 2 52738 97.5267 153.6138 0008282 84.3113 275.9064 15.15611464 38761 10 | 0 CONNECTA T1-1 11 | 1 52739U 22057H 23036.88769218 .00006966 00000-0 38624-3 0 9998 12 | 2 52739 97.5358 153.5145 0010190 89.9510 270.2889 15.14050840 38726 13 | 0 LEMUR 2 HANCOM-1 14 | 1 52740U 22057J 23037.08570718 .00017932 00000-0 90182-3 0 9990 15 | 2 52740 97.5413 154.4138 0010408 91.3570 268.8856 15.17248891 38788 16 | 0 OP15 FLT1 (TYVAK-0820) 17 | 1 52741U 22057K 23036.85086563 .00013598 00000-0 69859-3 0 9996 18 | 2 52741 97.5364 153.9211 0009814 88.4347 271.8010 15.16566713 38745 19 | 0 CENTAURI 5 (TYVAK-0212) 20 | 1 52742U 22057L 23037.09912698 .00020032 00000-0 98481-3 0 9995 21 | 2 52742 97.5279 154.1026 0007435 85.2751 274.9332 15.18041987 38798 22 | 1 52743U 22057M 23037.04954473 .00011781 00000-0 61944-3 0 9993 23 | 2 52743 97.5265 153.6940 0008594 82.9904 31.3082 15.15793680 38769 24 | 0 GHGSAT-C5 25 | 1 52744U 22057N 23037.07272071 .00003967 00000-0 22555-3 0 9996 26 | 2 52744 97.5377 153.7350 0011367 88.3414 86.3529 15.13411014 38748 27 | 0 AMS 28 | 1 52745U 22057P 23036.90397027 .00042985 00000-0 15803-2 0 9997 29 | 2 52745 97.5123 155.7248 0022421 114.7010 245.6566 15.27525761 38904 30 | 0 GHGSAT-C4 31 | 1 52746U 22057Q 23037.10750856 .00003860 00000-0 21930-3 0 9992 32 | 2 52746 97.5251 153.3935 0010354 79.3126 23.4440 15.13459557 38750 33 | 0 NUSAT-28 (ALICE LEE) 34 | 1 52747U 22057R 23037.22603551 .00011634 00000-0 53078-3 0 9991 35 | 2 52747 97.4775 154.1756 0010454 54.6631 305.5580 15.20730797 38901 36 | 0 NUSAT-30 (MARGHERITA) 37 | 1 52748U 22057S 23037.06296123 .00014923 00000-0 56759-3 0 9990 38 | 2 52748 97.3595 153.1456 0008136 29.6606 330.5093 15.26838154 39039 39 | 0 ICEYE-X18 40 | 1 52749U 22057T 23037.11421501 .00016269 00000-0 85173-3 0 9994 41 | 2 52749 97.5277 153.8566 0009622 50.8929 75.3110 15.15861349 38777 42 | 0 FOSSASAT2E11 43 | 1 52750U 22057U 23037.09183814 .00042572 00000-0 17460-2 0 9994 44 | 2 52750 97.5329 155.3908 0008343 63.2717 296.9373 15.24088376 38869 45 | 0 OBJECT V 46 | 1 52751U 22057V 23037.14037215 .00004553 00000-0 25511-3 0 9998 47 | 2 52751 97.5273 153.5975 0010597 75.1988 46.5731 15.13861159 38760 48 | 0 NUSAT-31 (RUBY PAYNE-S) 49 | 1 52752U 22057W 23037.21796854 .00009140 00000-0 48828-3 0 9991 50 | 2 52752 97.5329 154.0609 0010266 74.5046 285.7319 15.15284975 38793 51 | 0 UMBRA-03 52 | 1 52753U 22057X 23036.89099654 .00024550 00000-0 13714-2 0 9996 53 | 2 52753 97.5273 152.8545 0003373 356.2312 3.8893 15.13497837 38690 54 | 0 HAWK-5C 55 | 1 52754U 22057Y 23037.12809845 .00006319 00000-0 34779-3 0 9993 56 | 2 52754 97.5417 154.1811 0010579 77.8956 282.3461 15.14348679 38772 57 | 0 ICEYE-X24 58 | 1 52755U 22057Z 23037.10645816 .00017128 00000-0 85326-3 0 9992 59 | 2 52755 97.5296 154.2550 0008875 73.5191 98.9679 15.17603716 38792 60 | 0 HAWK-5B 61 | 1 52756U 22057AA 23036.86364832 .00005988 00000-0 32977-3 0 9996 62 | 2 52756 97.5264 153.3553 0011256 73.7736 286.4733 15.14345711 38739 63 | 0 HAWK-5A 64 | 1 52757U 22057AB 23037.12774664 .00006166 00000-0 33934-3 0 9998 65 | 2 52757 97.5340 153.8973 0011616 76.3222 283.9302 15.14350099 38778 66 | 0 ICEYE-X19 67 | 1 52758U 22057AC 23037.14353186 .00015866 00000-0 83148-3 0 9992 68 | 2 52758 97.5303 154.0439 0013188 54.2839 45.1844 15.15795109 38789 69 | 0 ICEYE-X20 70 | 1 52759U 22057AD 23037.12140059 .00016162 00000-0 80321-3 0 9994 71 | 2 52759 97.5299 154.3640 0010407 71.3635 31.3468 15.17687465 38805 72 | 0 VIGORIDE-3 73 | 1 52760U 22057AE 23036.88124168 .00003100 00000-0 17516-3 0 9995 74 | 2 52760 97.5324 153.5507 0012882 73.3749 286.8896 15.13781195 38731 75 | 0 ION SCV-006 76 | 1 52761U 22057AF 23037.04836934 .00004178 00000-0 23221-3 0 9992 77 | 2 52761 97.5336 153.8286 0013112 72.9702 287.2965 15.14173716 38763 78 | 0 ICEYE-X17 79 | 1 52762U 22057AG 23037.11094679 .00015553 00000-0 77289-3 0 9995 80 | 2 52762 97.5302 154.3803 0010731 72.0978 30.7629 15.17695768 38800 81 | 0 SHERPA-AC1 82 | 1 52763U 22057AH 23036.89429988 .00003765 00000-0 20894-3 0 9997 83 | 2 52763 97.5338 153.7409 0013220 71.3804 288.8862 15.14290249 38741 84 | 0 NUSAT-29 (EDITH CLARKE) 85 | 1 52764U 22057AJ 23037.25786775 .00012139 00000-0 55246-3 0 9993 86 | 2 52764 97.4805 154.3300 0012460 44.8157 315.4082 15.20784362 38919 87 | 0 ARMSAT1 URDANETA 88 | 1 52765U 22057AK 23037.14532730 .00009541 00000-0 49292-3 0 9999 89 | 2 52765 97.5348 154.4763 0012917 64.8353 56.2978 15.16433317 38802 90 | 0 CELESTIS 21 - VARISAT 1C 91 | 1 52766U 22057AL 23037.03165535 .00008255 00000-0 43133-3 0 9992 92 | 2 52766 97.5363 154.3387 0013689 67.6542 50.0596 15.16067844 38782 93 | 0 CNCE4 94 | 1 52767U 22057AM 23036.85668304 .00020245 00000-0 95027-3 0 9996 95 | 2 52767 97.5277 154.4023 0011115 62.0389 298.1970 15.19619463 38790 96 | 0 CNCE5 97 | 1 52768U 22057AN 23036.72497729 .00019899 00000-0 93485-3 0 9993 98 | 2 52768 97.5280 154.2725 0011004 63.3387 296.8973 15.19593235 38775 99 | 0 LEMUR 2 MIMI1307 100 | 1 52769U 22057AP 23036.89557315 .00006598 00000-0 32449-3 0 9995 101 | 2 52769 97.5403 154.6093 0013610 69.1358 291.1332 15.18277619 38784 102 | 0 PLATFORM-1 103 | 1 52770U 22057AQ 23036.83446388 .00005396 00000-0 28018-3 0 9990 104 | 2 52770 97.5303 154.0147 0012921 58.9624 301.2876 15.16466316 38761 105 | 0 OMNI-L2 106 | 1 52771U 22057AR 23036.60749533 .00008836 00000-0 59024-3 0 9997 107 | 2 52771 97.5247 153.3085 0011966 66.5366 293.7118 15.07172161 38696 108 | 0 CPOD FLT1 (TYVAK-0032) 109 | 1 52772U 22057AS 23037.06888833 .00019927 00000-0 97173-3 0 9996 110 | 2 52772 97.5287 154.5002 0012822 54.2533 305.9891 15.18278587 38814 111 | 0 FOSSASAT2E12 112 | 1 52773U 22057AT 23036.82423510 .00030322 00000-0 12979-2 0 9999 113 | 2 52773 97.5324 154.9702 0009451 65.8451 294.3773 15.22710138 38817 114 | 0 SBUDNIC 115 | 1 52774U 22057AU 23036.87572409 .00049210 00000-0 19067-2 0 9998 116 | 2 52774 97.5283 155.3625 0004254 48.3756 311.7846 15.25980096 38844 117 | 0 GUARDIAN 118 | 1 52775U 22057AV 23037.13644854 .00007718 00000-0 42378-3 0 9992 119 | 2 52775 97.5302 153.7201 0010532 88.8794 33.4406 15.14361229 38755 120 | 0 FOSSASAT2E13 121 | 1 52776U 22057AW 23036.85686868 .00018369 00000-0 86958-3 0 9998 122 | 2 52776 97.5320 154.3372 0012965 61.4900 298.7638 15.19324828 38785 123 | 0 VEERY-FS1 124 | 1 52777U 22057AX 23037.05822551 .00020001 00000-0 94866-3 0 9996 125 | 2 52777 97.5315 154.5562 0013712 58.7801 301.4775 15.19231505 38810 126 | 0 FOSSASAT2E7 127 | 1 52778U 22057AY 23036.88096332 .00040630 00000-0 17406-2 0 9991 128 | 2 52778 97.5284 154.4416 0009517 69.0081 291.2172 15.22613958 38778 129 | 0 FOSSASAT2E8 130 | 1 52779U 22057AZ 23036.83569229 .00029862 00000-0 13576-2 0 9995 131 | 2 52779 97.5298 154.2065 0010443 72.4751 287.7624 15.20644843 38760 132 | 0 CPOD FLT2 (TYVAK-0033) 133 | 1 52780U 22057BB 23036.86744141 .00018086 00000-0 87869-3 0 9991 134 | 2 52780 97.5313 154.3283 0011660 53.1934 307.0368 15.18441019 16465 135 | 1998-067WV 136 | 1 60955U 98067WV 24295.33823779 .06453473 12009-4 26290-2 0 9998 137 | 2 60955 51.6166 43.0490 0010894 336.3668 23.6849 16.22453324 8315 138 | 2 PATHFINDER 139 | 1 45727U 20037E 24323.73967089 .00003818 00000+0 31595-3 0 9995 140 | 2 45727 97.7798 139.6782 0011624 329.2427 30.8113 14.99451155239085 141 | 0 SHINSEI (MS-F2) 142 | 1 5485U 71080A 24324.43728894 .00000099 00000-0 13784-3 0 9992 143 | 2 5485 32.0564 70.0187 0639723 198.9447 158.6281 12.74214074476065 144 | OSCAR 7 (AO-7) 145 | 1 07530U 74089B 24323.87818483 -.00000039 00000+0 47934-4 0 9997 146 | 2 07530 101.9893 320.0351 0012269 147.9195 274.9996 12.53682684288423 147 | -------------------------------------------------------------------------------- /contrib/satnogs_waterfall_tabulation_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import requests 5 | import ephem 6 | import csv 7 | 8 | 9 | from astropy.time import Time 10 | from astropy import constants as const 11 | from datetime import datetime, timedelta 12 | from io import BytesIO 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | from PIL import Image 16 | 17 | import settings 18 | from satnogs_api_client import fetch_observation_data, fetch_tle_of_observation 19 | 20 | 21 | def tabulation_helper_dialog(lower_left, upper_right, 22 | bandwidth, duration, 23 | f_center, filename_out, 24 | waterfall_matrix, 25 | epoch_start, site_id, correction_method=None): 26 | # Assumptions: x-axis - freqency, y-axis - time; up-time advance, right-freq advance 27 | 28 | # Derive conversion parameters 29 | x_range = upper_right[0] - lower_left[0] 30 | y_range = lower_left[1] - upper_right[1] 31 | x_offset = int(lower_left[0] + 0.5 * x_range) 32 | y_offset = lower_left[1] 33 | 34 | t_step = duration / y_range 35 | f_step = bandwidth / x_range 36 | 37 | # Highlight center and calibration lines 38 | waterfall_matrix[:,x_offset] = (255,0,0,255) 39 | waterfall_matrix[y_offset,:] = (255,0,0,255) 40 | waterfall_matrix[:,upper_right[0]] = (255,0,0,255) 41 | waterfall_matrix[upper_right[1],:] = (255,0,0,255) 42 | 43 | 44 | markers = {'x': [], 'y': [], 't': [], 'f': []} 45 | 46 | fig, ax = plt.subplots() 47 | ax.imshow(waterfall_matrix) 48 | marker_line, = ax.plot([0], [0], marker='.', c='k', zorder=100) 49 | def on_mouse_press(event): 50 | tb = plt.get_current_fig_manager().toolbar 51 | if not (event.button==1 and event.inaxes and tb.mode == ''): 52 | return 53 | 54 | markers['x'].append(event.xdata) 55 | markers['y'].append(event.ydata) 56 | markers['t'].append(epoch_start - (event.ydata - y_offset) * t_step) 57 | markers['f'].append(f_step * (event.xdata - x_offset)) 58 | print('x:{:.2f} y:{:.2f} --> {:7.0f} --> {} {:7.0f}'.format(markers['x'][-1], 59 | markers['y'][-1], 60 | markers['f'][-1] - f_center, 61 | markers['t'][-1], 62 | markers['f'][-1])) 63 | update_plot_markers() 64 | 65 | def update_plot_markers(): 66 | marker_line.set_xdata(markers['x']) 67 | marker_line.set_ydata(markers['y']) 68 | # ax.scatter(x=[int(event.xdata)], y=[int(event.ydata)], marker='.', c='k', zorder=100) 69 | ax.figure.canvas.draw() 70 | 71 | def undo_track_point_selection(): 72 | markers['x'] = markers['x'][:-1] 73 | markers['y'] = markers['y'][:-1] 74 | markers['t'] = markers['t'][:-1] 75 | markers['f'] = markers['f'][:-1] 76 | update_plot_markers() 77 | 78 | def write_strf_spectrum(filename, site_id, markers): 79 | with open(filename, 'w') as f: 80 | for t,freq in list(sorted(zip(markers['t'],markers['f']), key=lambda x: x[0])): 81 | 82 | if correction_method: 83 | freq_recv = correction_method(t, freq) 84 | else: 85 | freq_recv = freq 86 | line = '{:.6f}\t{:.2f}\t1.0\t{}\n'.format(Time(t).mjd, freq_recv, site_id) 87 | print(line, end='') 88 | f.write(line) 89 | 90 | def save_selected_track_points(): 91 | write_strf_spectrum(filename_out, site_id, markers) 92 | print('Stored {} selected track points in {}.'.format(len(markers['t']), filename_out)) 93 | 94 | def on_key(event): 95 | if event.key == 'u': 96 | undo_track_point_selection() 97 | elif event.key == 'f': 98 | save_selected_track_points() 99 | 100 | fig.canvas.mpl_connect('button_press_event', on_mouse_press) 101 | fig.canvas.mpl_connect('key_press_event', on_key) 102 | plt.tight_layout() 103 | plt.show() 104 | 105 | 106 | def read_sitestxt(path): 107 | sites = {} 108 | with open(path) as fp: 109 | d = csv.DictReader([row for row in fp if not row.startswith('#')], 110 | delimiter=' ', 111 | fieldnames=["no", "id", "lat", "lon", "alt"], 112 | restkey="observer", 113 | skipinitialspace=True) 114 | for line in d: 115 | print(line) 116 | sites[line["no"]] = {'lat': line["lat"], 117 | 'lon': line["lon"], 118 | 'sn': line["id"], 119 | 'alt': line["alt"], 120 | 'name': ' '.join(line["observer"])} 121 | return sites 122 | 123 | 124 | def add_station_to_sitestxt(station): 125 | # Read the sites.txt 126 | sites = read_sitestxt(settings.SITES_TXT) 127 | if '7{}'.format(station['id']) in sites: 128 | # Station is already available 129 | return 130 | else: 131 | with open(settings.SITES_TXT, 'a') as f: 132 | f.write('7{} {} {} {} {} {}\n'.format(station['id'], 133 | 'SN', 134 | station['lat'], 135 | station['lng'], 136 | station['alt'], 137 | station['name'])) 138 | 139 | 140 | def tabulation_helper(observation_id): 141 | # Fetch waterfall image and observation data 142 | observation = fetch_observation_data([observation_id])[0] 143 | tle = fetch_tle_of_observation(observation_id) 144 | result = requests.get(observation['waterfall']) 145 | image = Image.open(BytesIO(result.content)) 146 | waterfall_matrix = np.array(image) 147 | 148 | epoch_start = datetime.strptime(observation['start'], '%Y-%m-%dT%H:%M:%SZ') 149 | epoch_end = datetime.strptime(observation['end'], '%Y-%m-%dT%H:%M:%SZ') 150 | site_id = int("7" + str(observation['ground_station'])) 151 | 152 | station = {'lat': observation['station_lat'], 153 | 'lng': observation['station_lng'], 154 | 'alt': observation['station_alt'], 155 | 'name': "SatNOGS No.{}".format(observation['ground_station']), 156 | 'id': observation['ground_station']} 157 | 158 | # Store TLE to file 159 | with open('{}/{}.txt'.format(settings.TLE_DIR, observation_id), 'w') as f: 160 | f.write('0 OBJECT\n') 161 | for line in tle: 162 | f.write(f'{line}\n') 163 | 164 | # Add SatNOGS station to sites.txt 165 | add_station_to_sitestxt(station) 166 | 167 | # Set image parameters 168 | lower_left = (66, 1553) 169 | upper_right = (686, 13) 170 | 171 | # Set data parameters 172 | bandwidth = 48e3 # Hz 173 | duration = epoch_end - epoch_start 174 | f = observation['transmitter_downlink_low'] 175 | drift = observation['transmitter_downlink_drift'] 176 | if drift: 177 | f_center = f * (1 + drift / 1e9) 178 | else: 179 | f_center = f 180 | filename_out = '{}/{}.dat'.format(settings.OBS_DIR, observation_id) 181 | 182 | # Initialize SGP4 propagator / pyephem 183 | satellite = ephem.readtle('sat', tle[0], tle[1]) 184 | observer = ephem.Observer() 185 | observer.lat = str(station['lat']) 186 | observer.lon = str(station['lng']) 187 | observer.elevation = station['alt'] 188 | 189 | 190 | def remove_doppler_correction(t, freq): 191 | # Remove the doppler correction 192 | observer.date = t 193 | satellite.compute(observer) 194 | v = satellite.range_velocity 195 | df = f_center * v / const.c.value 196 | return f_center + freq - df 197 | 198 | tabulation_helper_dialog(lower_left, upper_right, 199 | bandwidth, duration, 200 | f_center, filename_out, 201 | waterfall_matrix, 202 | epoch_start, site_id, correction_method=remove_doppler_correction) 203 | 204 | 205 | if __name__ == '__main__': 206 | parser = argparse.ArgumentParser(description='Interactive helper to tabulate signals from SatNOGS waterfalls.') 207 | parser.add_argument('observation_ids', metavar='ID', type=int, nargs='+', 208 | help='SatNOGS Observation ID') 209 | 210 | args = parser.parse_args() 211 | 212 | for observation_id in args.observation_ids: 213 | tabulation_helper(observation_id) 214 | -------------------------------------------------------------------------------- /tests/tests_rftles.c: -------------------------------------------------------------------------------- 1 | #include "tests_rftles.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../rftles.h" 10 | 11 | // Setup and teardown functions for tests 12 | int setup_nonexistent(void **state) { 13 | tle_array_t * tle_array = load_tles("tests/data/nonexistent.tle"); 14 | 15 | if (tle_array == NULL) { 16 | return -1; 17 | } 18 | 19 | *state = tle_array; 20 | 21 | return 0; 22 | } 23 | 24 | int setup_empty(void **state) { 25 | tle_array_t * tle_array = load_tles("tests/data/empty.tle"); 26 | 27 | if (tle_array == NULL) { 28 | return -1; 29 | } 30 | 31 | *state = tle_array; 32 | 33 | return 0; 34 | } 35 | 36 | int setup(void **state) { 37 | tle_array_t * tle_array = load_tles("tests/data/catalog.tle"); 38 | 39 | if (tle_array == NULL) { 40 | return -1; 41 | } 42 | 43 | *state = tle_array; 44 | 45 | return 0; 46 | } 47 | 48 | int setup_alpha5(void **state) { 49 | tle_array_t * tle_array = load_tles("tests/data/alpha5.tle"); 50 | 51 | if (tle_array == NULL) { 52 | return -1; 53 | } 54 | 55 | *state = tle_array; 56 | 57 | return 0; 58 | } 59 | 60 | int teardown(void **state) { 61 | free_tles(*state); 62 | 63 | return 0; 64 | } 65 | 66 | // Tests 67 | void TLE_load_nonexistent_file(void **state) { 68 | tle_array_t tle_array = **(tle_array_t **)state; 69 | 70 | assert_null(tle_array.tles); 71 | assert_int_equal(tle_array.number_of_elements, 0); 72 | } 73 | 74 | void TLE_load_empty_file(void **state) { 75 | tle_array_t tle_array = **(tle_array_t **)state; 76 | 77 | assert_null(tle_array.tles); 78 | assert_int_equal(tle_array.number_of_elements, 0); 79 | } 80 | 81 | void TLE_load_invalid_index_from_file(void **state) { 82 | tle_array_t tle_array = **(tle_array_t **)state; 83 | 84 | assert_non_null(tle_array.tles); 85 | assert_int_equal(tle_array.number_of_elements, 49); 86 | 87 | tle_t * tle = get_tle_by_index(&tle_array, 50); 88 | assert_null(tle); 89 | } 90 | 91 | void TLE_load_index_from_file(void **state) { 92 | tle_array_t tle_array = **(tle_array_t **)state; 93 | 94 | assert_non_null(tle_array.tles); 95 | assert_int_equal(tle_array.number_of_elements, 49); 96 | 97 | // 9th element, 52745 - AMS 98 | tle_t * tle = get_tle_by_index(&tle_array, 9); 99 | 100 | assert_non_null(tle->name); 101 | assert_string_equal(tle->name, "AMS"); 102 | 103 | assert_int_equal(tle->orbit.ep_year, 2023); 104 | assert_float_equal(tle->orbit.ep_day, 36.90397027, 1e-9); 105 | assert_float_equal(tle->orbit.rev, 15.27525761, 1e-9); 106 | assert_float_equal(tle->orbit.bstar, 0.0015803, 1e-9); 107 | assert_float_equal(tle->orbit.eqinc, 1.701910696, 1e-9); 108 | assert_float_equal(tle->orbit.ecc, 0.0022421, 1e-9); 109 | assert_float_equal(tle->orbit.mnan, 4.287516499, 1e-9); 110 | assert_float_equal(tle->orbit.argp, 2.001910105, 1e-9); 111 | assert_float_equal(tle->orbit.ascn, 2.717910487, 1e-9); 112 | // smjaxs is not set by sgdp4h 113 | assert_float_equal(tle->orbit.smjaxs, 0., 1e-9); 114 | assert_float_equal(tle->orbit.ndot2, 0.00042985, 1e-9); 115 | assert_float_equal(tle->orbit.nddot6, 0., 1e-9); 116 | assert_string_equal(tle->orbit.desig, "22057P"); 117 | assert_int_equal(tle->orbit.norb, 3890); 118 | assert_int_equal(tle->orbit.satno, 52745); 119 | 120 | // 7th element, 52743 - OBJECT M, no name 121 | tle = get_tle_by_index(&tle_array, 7); 122 | assert_null(tle->name); 123 | 124 | // 0th element, 52736 - LEMUR 2 KAREN_B 125 | tle = get_tle_by_index(&tle_array, 0); 126 | assert_non_null(tle->name); 127 | assert_string_equal(tle->name, "LEMUR 2 KAREN_B"); 128 | 129 | // 45th element, 60955 - 1998-067WV 130 | tle = get_tle_by_index(&tle_array, 45); 131 | assert_int_equal(tle->orbit.satno, 60955); 132 | assert_non_null(tle->name); 133 | assert_string_equal(tle->name, "1998-067WV"); 134 | 135 | // 46th element, 45727 - 2 PATHFINDER 136 | tle = get_tle_by_index(&tle_array, 46); 137 | assert_int_equal(tle->orbit.satno, 45727); 138 | assert_non_null(tle->name); 139 | assert_string_equal(tle->name, "2 PATHFINDER"); 140 | 141 | // 47th element, 5485 - SHINSEI (MS-F2) 142 | tle = get_tle_by_index(&tle_array, 47); 143 | assert_int_equal(tle->orbit.satno, 5485); 144 | assert_non_null(tle->name); 145 | assert_string_equal(tle->name, "SHINSEI (MS-F2)"); 146 | 147 | // 48th element, 07530 - OSCAR 7 (AO-7) 148 | tle = get_tle_by_index(&tle_array, 48); 149 | assert_int_equal(tle->orbit.satno, 7530); 150 | assert_non_null(tle->name); 151 | assert_string_equal(tle->name, "OSCAR 7 (AO-7)"); 152 | } 153 | 154 | void TLE_load_invalid_catalog_id_from_file(void **state) { 155 | tle_array_t tle_array = **(tle_array_t **)state; 156 | 157 | assert_non_null(tle_array.tles); 158 | assert_int_equal(tle_array.number_of_elements, 49); 159 | 160 | tle_t * tle = get_tle_by_catalog_id(&tle_array, 12000); 161 | assert_null(tle); 162 | } 163 | 164 | void TLE_load_catalog_id_from_file(void **state) { 165 | tle_array_t tle_array = **(tle_array_t **)state; 166 | 167 | assert_non_null(tle_array.tles); 168 | assert_int_equal(tle_array.number_of_elements, 49); 169 | 170 | // 13th element, 52749, - ICEYE-X18 171 | tle_t * tle = get_tle_by_catalog_id(&tle_array, 52749); 172 | 173 | assert_non_null(tle->name); 174 | assert_string_equal(tle->name, "ICEYE-X18"); 175 | 176 | assert_int_equal(tle->orbit.ep_year, 2023); 177 | assert_float_equal(tle->orbit.ep_day, 37.11421501, 1e-9); 178 | assert_float_equal(tle->orbit.rev, 15.15861349, 1e-9); 179 | assert_float_equal(tle->orbit.bstar, 0.00085173, 1e-9); 180 | assert_float_equal(tle->orbit.eqinc, 1.702179477, 1e-9); 181 | assert_float_equal(tle->orbit.ecc, 0.0009622, 1e-9); 182 | assert_float_equal(tle->orbit.mnan, 1.314424913, 1e-9); 183 | assert_float_equal(tle->orbit.argp, 0.888248671, 1e-9); 184 | assert_float_equal(tle->orbit.ascn, 2.685304246, 1e-9); 185 | // smjaxs is not set by sgdp4h 186 | assert_float_equal(tle->orbit.smjaxs, 0., 1e-9); 187 | assert_float_equal(tle->orbit.ndot2, 0.00016269, 1e-9); 188 | assert_float_equal(tle->orbit.nddot6, 0., 1e-9); 189 | assert_string_equal(tle->orbit.desig, "22057T"); 190 | assert_int_equal(tle->orbit.norb, 3877); 191 | assert_int_equal(tle->orbit.satno, 52749); 192 | 193 | // 7th element, 52743 - OBJECT M, no name 194 | tle = get_tle_by_catalog_id(&tle_array, 52743); 195 | assert_null(tle->name); 196 | 197 | // 0th element, 52736 - LEMUR 2 KAREN_B 198 | tle = get_tle_by_catalog_id(&tle_array, 52736); 199 | assert_non_null(tle->name); 200 | assert_string_equal(tle->name, "LEMUR 2 KAREN_B"); 201 | 202 | // 45th element, 60955 - 1998-067WV 203 | tle = get_tle_by_catalog_id(&tle_array, 60955); 204 | assert_int_equal(tle->orbit.satno, 60955); 205 | assert_non_null(tle->name); 206 | assert_string_equal(tle->name, "1998-067WV"); 207 | 208 | // 46th element, 45727 - 2 PATHFINDER 209 | tle = get_tle_by_catalog_id(&tle_array, 45727); 210 | assert_int_equal(tle->orbit.satno, 45727); 211 | assert_non_null(tle->name); 212 | assert_string_equal(tle->name, "2 PATHFINDER"); 213 | 214 | // 47th element, 5485 - SHINSEI (MS-F2) 215 | tle = get_tle_by_catalog_id(&tle_array, 5485); 216 | assert_int_equal(tle->orbit.satno, 5485); 217 | assert_non_null(tle->name); 218 | assert_string_equal(tle->name, "SHINSEI (MS-F2)"); 219 | 220 | // 48th element, 07530 - OSCAR 7 (AO-7) 221 | tle = get_tle_by_catalog_id(&tle_array, 7530); 222 | assert_int_equal(tle->orbit.satno, 7530); 223 | assert_non_null(tle->name); 224 | assert_string_equal(tle->name, "OSCAR 7 (AO-7)"); 225 | } 226 | 227 | void TLE_decode_alpha5_designation(void **state) { 228 | tle_array_t tle_array = **(tle_array_t **)state; 229 | 230 | assert_non_null(tle_array.tles); 231 | assert_int_equal(tle_array.number_of_elements, 2); 232 | 233 | // 1st element, B5544 - ISS 234 | tle_t * tle = get_tle_by_catalog_id(&tle_array, 115544); 235 | 236 | assert_non_null(tle->name); 237 | assert_string_equal(tle->name, "ISS (ZARYA)"); 238 | 239 | assert_string_equal(tle->orbit.desig, "98067A"); 240 | assert_int_equal(tle->orbit.satno, 115544); 241 | 242 | // 2nd element, 339999 - ISS 243 | tle = get_tle_by_catalog_id(&tle_array, 339999); 244 | 245 | assert_non_null(tle->name); 246 | assert_string_equal(tle->name, "ISS (ZARYA)"); 247 | 248 | assert_string_equal(tle->orbit.desig, "98067A"); 249 | assert_int_equal(tle->orbit.satno, 339999); 250 | 251 | } 252 | 253 | // Entry point to run all tests 254 | int run_tle_tests() { 255 | const struct CMUnitTest tests[] = { 256 | cmocka_unit_test_setup_teardown(TLE_load_nonexistent_file, setup_nonexistent, teardown), 257 | cmocka_unit_test_setup_teardown(TLE_load_empty_file, setup_empty, teardown), 258 | cmocka_unit_test_setup_teardown(TLE_load_invalid_index_from_file, setup, teardown), 259 | cmocka_unit_test_setup_teardown(TLE_load_index_from_file, setup, teardown), 260 | cmocka_unit_test_setup_teardown(TLE_load_invalid_catalog_id_from_file, setup, teardown), 261 | cmocka_unit_test_setup_teardown(TLE_load_catalog_id_from_file, setup, teardown), 262 | cmocka_unit_test_setup_teardown(TLE_decode_alpha5_designation, setup_alpha5, teardown), 263 | }; 264 | 265 | return cmocka_run_group_tests_name("TLE", tests, NULL, NULL); 266 | } 267 | -------------------------------------------------------------------------------- /satutl.c: -------------------------------------------------------------------------------- 1 | /* > satutl.c 2 | * 3 | */ 4 | 5 | 6 | #include "sgdp4h.h" 7 | #include 8 | 9 | static char *st_start(char *buf); 10 | static long i_read(char *str, int start, int stop); 11 | static double d_read(char *str, int start, int stop); 12 | 13 | // Fixed mapping 14 | // Only capital letters and numbers are used in Alpha-5. The letters “I” and “O” are omitted to avoid confusion with the numbers “1” and “0”. 15 | const char mapping[] = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ"; 16 | 17 | // Function to convert alpha5 format to number 18 | int alpha5_to_number(const char *s) 19 | { 20 | // Split components 21 | char first_char = s[0]; 22 | int digits = 0; 23 | sscanf(s + 1, "%4d", &digits); // Get the last four digits 24 | 25 | // Decode first character 26 | int first_digit = 0; 27 | if (first_char == ' ') { 28 | first_digit = 0; 29 | } else if (isdigit(first_char)) { 30 | first_digit = first_char - '0'; 31 | } else { 32 | for (int i = 0; mapping[i] != '\0'; i++) { 33 | if (mapping[i] == first_char) { 34 | first_digit = i; 35 | break; 36 | } 37 | } 38 | } 39 | 40 | // Create and return number 41 | return first_digit * 10000 + digits; 42 | } 43 | 44 | // Function to convert number to alpha5 format 45 | void number_to_alpha5(int number, char *result) 46 | { 47 | // Split components 48 | int first_digit = number / 10000; 49 | int digits = number % 10000; 50 | 51 | // Get char 52 | char first_char = mapping[first_digit]; 53 | 54 | // Create string 55 | sprintf(result, "%c%04d", first_char, digits); 56 | 57 | return; 58 | } 59 | 60 | // Function to strip leading spaces 61 | void strip_leading_spaces(const char *s, char *result) 62 | { 63 | while (*s == ' ') { 64 | s++; // Skip leading spaces 65 | } 66 | strcpy(result, s); // Copy the remainder of the string 67 | 68 | return; 69 | } 70 | 71 | // Function to remove trailing spaces from a string 72 | void strip_trailing_spaces(char *s) 73 | { 74 | int i; 75 | 76 | // Loop from the end of the string to find the first non-space character 77 | for (i=strlen(s)-1;i>=0;i--) 78 | if (s[i]!=' ') 79 | break; 80 | s[i+1]='\0'; 81 | } 82 | 83 | // Function to zero pad a string 84 | void zero_pad(const char *s, char *result) 85 | { 86 | char stripped[6]; // Temporarily store the string without leading spaces 87 | strip_leading_spaces(s, stripped); 88 | 89 | if (isdigit(stripped[0])) { 90 | int value = atoi(stripped); 91 | sprintf(result, "%05d", value); 92 | } else { 93 | strcpy(result, stripped); // No zero-padding for non-digit strings 94 | } 95 | } 96 | 97 | 98 | /* ==================================================================== 99 | Read a string from key board, remove CR/LF etc. 100 | ==================================================================== */ 101 | 102 | void read_kb(char *buf) 103 | { 104 | int ii; 105 | 106 | fgets(buf, ST_SIZE-1, stdin); 107 | 108 | /* Remove the CR/LF etc. */ 109 | for(ii = 0; ii < ST_SIZE; ii++) 110 | { 111 | if(buf[ii] == '\r' || buf[ii] == '\n') 112 | { 113 | buf[ii] = '\0'; 114 | break; 115 | } 116 | } 117 | } 118 | 119 | // If current_line not lenght of 70 or doesn't start with "1 " or "2 ",copy it 120 | // to satname, stripping a potential leading "0 ", leading whitespaces and 121 | // trailing whitespaces and newline 122 | void conditional_copy_satname(char *satname, char *current_line) { 123 | if ((strlen(current_line) != 70) || 124 | ((current_line[0] != '1' || current_line[1] != ' ') && 125 | (current_line[0] != '2' || current_line[1] != ' '))) { 126 | // Name line found 127 | // st_start will strip the leading whitespaces 128 | if (current_line[0] == '0' && current_line[1] == ' ') { 129 | strncpy(satname, st_start(current_line + 2), ST_SIZE); 130 | } else { 131 | strncpy(satname, st_start(current_line), ST_SIZE); 132 | } 133 | } 134 | 135 | // Strip the trailing whitespaces and newline 136 | for (int i = strlen(satname); i >= 0; i--) { 137 | if (!isprint(satname[i])) { 138 | satname[i] = '\0'; 139 | } else { 140 | break; 141 | } 142 | } 143 | 144 | return; 145 | } 146 | 147 | /* ==================================================================== 148 | Read orbit parameters for "satno" in file "filename", return -1 if 149 | failed to find the corresponding data. Call with satno = 0 to get the 150 | next elements of whatever sort. 151 | 152 | When calling with satno != 0 and the corresponding sat doesn't have a 153 | name in the tle file, depending on the current file position, it can 154 | return the name of the previous satellite. This won't happen when 155 | called with satno == 0 or through the rftles wrapper which is 156 | currently used everywhere, there is no direct call to read_twoline. 157 | ==================================================================== */ 158 | 159 | int read_twoline(FILE *fp, long search_satno, orbit_t *orb, char *satname) 160 | { 161 | char tmp_satname[ST_SIZE] = ""; 162 | static char search[ST_SIZE]; 163 | static char line1[ST_SIZE]; 164 | static char line2[ST_SIZE]; 165 | char search_satstr[6]; 166 | char *st1, *st2; 167 | int found = 0; 168 | double bm, bx; 169 | 170 | st1 = line1; 171 | st2 = line2; 172 | 173 | // alpha5 designation to search 174 | number_to_alpha5(search_satno, search_satstr); 175 | 176 | do { 177 | if(fgets(line1, ST_SIZE-1, fp) == NULL) return -1; 178 | st1 = st_start(line1); 179 | 180 | conditional_copy_satname(tmp_satname, line1); 181 | } while((st1[0] != '1') || (st1[1] != ' ')); 182 | 183 | if (search_satno != 0) { 184 | sprintf(search, "1 %5s", search_satstr); 185 | } else { 186 | // If no search_satno given, set it to the currently read one 187 | // so next do/while loop will find it 188 | //search_satno = atol(st1+2); 189 | strncpy(search_satstr, st1+2, 5); 190 | search_satstr[5]='\0'; 191 | search_satno=alpha5_to_number(search_satstr); 192 | strncpy(search, line1, 7); 193 | search[7] = '\0'; 194 | } 195 | 196 | do { 197 | st1 = st_start(line1); 198 | if(strncmp(st1, search, 7) == 0) 199 | { 200 | found = 1; 201 | break; 202 | } 203 | 204 | conditional_copy_satname(tmp_satname, line1); 205 | } while(fgets(line1, ST_SIZE-1, fp) != NULL); 206 | 207 | search[0] = '2'; 208 | 209 | if(found) 210 | { 211 | fgets(line2, ST_SIZE-1, fp); 212 | st2 = st_start(line2); 213 | } 214 | 215 | if(!found || strncmp(st2, search, 7) != 0) 216 | { 217 | return -1; 218 | } 219 | 220 | orb->ep_year = (int)i_read(st1, 19, 20); 221 | 222 | if(orb->ep_year < 57) orb->ep_year += 2000; 223 | else orb->ep_year += 1900; 224 | 225 | orb->ep_day = d_read(st1, 21, 32); 226 | 227 | orb->ndot2 = d_read(st1, 34, 43); 228 | bm = d_read(st1, 45, 50) * 1.0e-5; 229 | bx = d_read(st1, 51, 52); 230 | orb->nddot6 = bm * pow(10.0, bx); 231 | bm = d_read(st1, 54, 59) * 1.0e-5; 232 | bx = d_read(st1, 60, 61); 233 | orb->bstar = bm * pow(10.0, bx); 234 | 235 | orb->eqinc = RAD(d_read(st2, 9, 16)); 236 | orb->ascn = RAD(d_read(st2, 18, 25)); 237 | orb->ecc = d_read(st2, 27, 33) * 1.0e-7; 238 | orb->argp = RAD(d_read(st2, 35, 42)); 239 | orb->mnan = RAD(d_read(st2, 44, 51)); 240 | orb->rev = d_read(st2, 53, 63); 241 | orb->norb = i_read(st2, 64, 68); 242 | 243 | orb->satno = search_satno; 244 | 245 | strncpy(orb->desig,st1+9,8); 246 | orb->desig[8]='\0'; 247 | strip_trailing_spaces(orb->desig); 248 | 249 | if (satname != NULL) { 250 | strncpy(satname, tmp_satname, ST_SIZE); 251 | } 252 | 253 | return 0; 254 | } 255 | 256 | /* ================================================================== 257 | Locate the first non-white space character, return location. 258 | ================================================================== */ 259 | 260 | static char *st_start(char *buf) 261 | { 262 | if(buf == NULL) return buf; 263 | 264 | while(*buf != '\0' && isspace(*buf)) buf++; 265 | 266 | return buf; 267 | } 268 | 269 | /* ================================================================== 270 | Mimick the FORTRAN formatted read (assumes array starts at 1), copy 271 | characters to buffer then convert. 272 | ================================================================== */ 273 | 274 | static long i_read(char *str, int start, int stop) 275 | { 276 | long itmp=0; 277 | char *buf, *tmp; 278 | int ii; 279 | 280 | start--; /* 'C' arrays start at 0 */ 281 | stop--; 282 | 283 | tmp = buf = (char *)vector(stop-start+2, sizeof(char)); 284 | 285 | for(ii = start; ii <= stop; ii++) 286 | { 287 | *tmp++ = str[ii]; /* Copy the characters. */ 288 | } 289 | *tmp = '\0'; /* NUL terminate */ 290 | 291 | itmp = atol(buf); /* Convert to long integer. */ 292 | free(buf); 293 | 294 | return itmp; 295 | } 296 | 297 | /* ================================================================== 298 | Mimick the FORTRAN formatted read (assumes array starts at 1), copy 299 | characters to buffer then convert. 300 | ================================================================== */ 301 | 302 | static double d_read(char *str, int start, int stop) 303 | { 304 | double dtmp=0; 305 | char *buf, *tmp; 306 | int ii; 307 | 308 | start--; 309 | stop--; 310 | 311 | tmp = buf = (char *)vector(stop-start+2, sizeof(char)); 312 | 313 | for(ii = start; ii <= stop; ii++) 314 | { 315 | *tmp++ = str[ii]; /* Copy the characters. */ 316 | } 317 | *tmp = '\0'; /* NUL terminate */ 318 | 319 | dtmp = atof(buf); /* Convert to long integer. */ 320 | free(buf); 321 | 322 | return dtmp; 323 | } 324 | 325 | /* ================================================================== 326 | Allocate and check an all-zero array of memory (storage vector). 327 | ================================================================== */ 328 | 329 | void *vector(size_t num, size_t size) 330 | { 331 | void *ptr; 332 | 333 | ptr = calloc(num, size); 334 | if(ptr == NULL) 335 | { 336 | fatal_error("vector: Allocation failed %u * %u", num, size); 337 | } 338 | 339 | return ptr; 340 | } 341 | 342 | /* ================================================================== 343 | Print out orbital parameters. 344 | ================================================================== */ 345 | 346 | void print_orb(orbit_t *orb) 347 | { 348 | printf("# Satellite ID = %ld\n", (long)orb->satno); 349 | printf("# Satellite designation = %s\n",orb->desig); 350 | printf("# Epoch year = %d day = %.8f\n", orb->ep_year, orb->ep_day); 351 | printf("# Eccentricity = %.7f\n", orb->ecc); 352 | printf("# Equatorial inclination = %.4f deg\n", DEG(orb->eqinc)); 353 | printf("# Argument of perigee = %.4f deg\n", DEG(orb->argp)); 354 | printf("# Mean anomaly = %.4f deg\n", DEG(orb->mnan)); 355 | printf("# Right Ascension of Ascending Node = %.4f deg\n", DEG(orb->ascn)); 356 | printf("# Mean Motion (number of rev/day) = %.8f\n", orb->rev); 357 | printf("# First derivative of mean motion = %e\n",orb->ndot2); 358 | printf("# Second derivative of mean motion = %e\n",orb->nddot6); 359 | printf("# BSTAR drag = %.4e\n", orb->bstar); 360 | printf("# Orbit number = %ld\n", orb->norb); 361 | } 362 | 363 | /* ====================================================================== */ 364 | -------------------------------------------------------------------------------- /sgdp4h.h: -------------------------------------------------------------------------------- 1 | /* > sgdp4h.h 2 | * 3 | * 4 | * Paul S. Crawford and Andrew R. Brooks 5 | * Dundee University 6 | * 7 | * NOTE ! 8 | * This code is supplied "as is" and without warranty of any sort. 9 | * 10 | * (c) 1994-2004, Paul Crawford, Andrew Brooks 11 | * 12 | * 13 | * 2.00 psc Sun May 28 1995 - Modifed for non-Dundee use. 14 | * 15 | */ 16 | 17 | #ifndef _SGDP4H_H 18 | #define _SGDP4H_H 19 | 20 | /* 21 | * Set up standard system-dependent names UNIX, LINUX, RISCOS, MSDOS, WIN32 22 | */ 23 | 24 | #if defined( unix ) 25 | # define UNIX 26 | # if defined( linux ) && !defined( LINUX ) 27 | # define LINUX 28 | # endif 29 | #elif defined( __riscos ) && !defined( RISCOS ) 30 | # define RISCOS 31 | #elif !defined( MSDOS ) && !defined( WIN32 ) && !defined( __CYGWIN__ ) 32 | # define MSDOS 33 | #endif 34 | 35 | /* 36 | * Include files 37 | */ 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #ifdef UNIX 46 | #include 47 | #endif 48 | 49 | #ifdef SUN4 50 | #include 51 | #endif 52 | 53 | #ifdef sun 54 | #include /* solaris 7 has struct timeval in here */ 55 | #include /* for sincos() which is in libsunmath */ 56 | #endif 57 | 58 | #ifdef linux 59 | #include 60 | void sincos(double x, double *s, double *c); /* declared where? */ 61 | #endif 62 | 63 | /* 64 | * ================= SYSTEM SPECIFIC DEFINITIONS ===================== 65 | */ 66 | 67 | /* Use INLINE keyword when declaring inline functions */ 68 | #ifdef WIN32 69 | #define INLINE __inline 70 | #elif defined( MSDOS ) 71 | #define INLINE 72 | #else 73 | /*UNIX?*/ 74 | #define INLINE inline 75 | #endif 76 | 77 | /* Sun C compiler has automatic inline and doesn't understand inline keyword */ 78 | #ifdef __SUNPRO_C 79 | #undef INLINE 80 | #define INLINE 81 | #define MACROS_ARE_SAFE 82 | #endif 83 | 84 | /* Some very common constants. */ 85 | 86 | #ifndef M_PI 87 | #define M_PI 3.141592653589793 88 | #endif /* MSDOS */ 89 | 90 | #ifndef PI 91 | #define PI M_PI 92 | #endif 93 | 94 | #define TWOPI (2.0*PI) /* Optimising compiler will deal with this! */ 95 | #define PB2 (0.5*PI) 96 | #define PI180 (PI/180.0) 97 | 98 | #define SOLAR_DAY (1440.0) /* Minutes per 24 hours */ 99 | #define SIDERIAL_DAY (23.0*60.0 + 56.0 + 4.09054/60.0) /* Against stars */ 100 | 101 | #define EQRAD (6378.137) /* Earth radius at equator, km */ 102 | #define LATCON (1.0/298.257) /* Latitude radius constant */ 103 | #define ECON ((1.0-LATCON)*(1.0-LATCON)) 104 | 105 | #define JD1900 2415020.5 /* Julian day number for Jan 1st, 00:00 hours 1900 */ 106 | 107 | 108 | /* 109 | * =============================== MACROS ============================ 110 | * 111 | * 112 | * Define macro for sign transfer, double to nearest (long) integer, 113 | * to square an expression (not nested), and A "safe" square, uses test 114 | * to force correct sequence of evaluation when the macro is nested. 115 | */ 116 | 117 | /* 118 | * These macros are safe since they make no assignments. 119 | */ 120 | #define SIGN(a, b) ((b) >= 0 ? fabs(a) : -fabs(a)) 121 | /* Coordinate conversion macros */ 122 | #define DEG(x) ((x)/PI180) 123 | #define RAD(x) ((x)*PI180) 124 | #define GEOC(x) (atan(ECON*tan(x))) /* Geographic to geocentric. */ 125 | #define GEOG(x) (atan(tan(x)/ECON)) 126 | 127 | /* 128 | * All other compilers can have static inline functions. 129 | * (SQR is used badly here: do_cal.c, glat2lat.c, satpos.c, vmath.h). 130 | */ 131 | static INLINE int NINT(double a) { return (int)(a > 0 ? a+0.5 : a-0.5); } 132 | static INLINE long NLONG(double a) { return (long)(a > 0 ? a+0.5 : a-0.5); } 133 | 134 | static INLINE double DSQR(double a) { return(a*a); } 135 | static INLINE float FSQR(float a) { return(a*a); } 136 | static INLINE int ISQR(int a) { return(a*a); } 137 | 138 | static INLINE double DCUBE(double a) { return(a*a*a); } 139 | static INLINE float FCUBE(float a) { return(a*a*a); } 140 | static INLINE int ICUBE(int a) { return(a*a*a); } 141 | 142 | static INLINE double DPOW4(double a) { a*=a; return(a*a); } 143 | static INLINE float FPOW4(float a) { a*=a; return(a*a); } 144 | static INLINE int IPOW4(int a) { a*=a; return(a*a); } 145 | 146 | static INLINE double DMAX(double a,double b) { if (a>b) return a; else return b; } 147 | static INLINE float FMAX(float a, float b) { if (a>b) return a; else return b; } 148 | static INLINE int IMAX(int a, int b) { if (a>b) return a; else return b; } 149 | 150 | static INLINE double DMIN(double a,double b) { if (a 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "rftime.h" 10 | 11 | #include 12 | 13 | #include "rffft_internal.h" 14 | 15 | void usage(void) 16 | { 17 | printf("rffft: FFT RF observations\n\n"); 18 | printf("-i Input file (can be fifo) [stdin]\n"); 19 | printf("-p Output path, directory into which to puth the files\n"); 20 | printf("-o Output filename [default: YYYY-MM-DDTHH:MM:SS.sss_XXXXXX.bin]\n"); 21 | printf("-f Center frequency (Hz)\n"); 22 | printf("-s Sample rate (Hz)\n"); 23 | printf("-c Channel size [100Hz]\n"); 24 | printf("-t Integration time [1s]\n"); 25 | printf("-n Number of integrations per file [60]\n"); 26 | printf("-m Use every mth integration [1]\n"); 27 | printf("-F Input format char, int, float, wav [int]\n"); 28 | printf("-T YYYY-MM-DDTHH:MM:SSS.sss\n"); 29 | printf("-R Frequency range to store (Hz)\n"); 30 | printf("-S Starting index [int]\n"); 31 | printf("-2 Square signal before processing (to detect BPSK signals\n"); 32 | printf("-4 Square-square signal before processing (to detect QPSK signals\n"); 33 | printf("-I Invert frequencies\n"); 34 | printf("-b Digitize output to bytes [off]\n"); 35 | printf("-q Quiet mode, no output [off]\n"); 36 | printf("-P Parse frequency, samplerate, format and start time from filename\n"); 37 | printf("-h This help\n"); 38 | 39 | return; 40 | } 41 | 42 | int main(int argc,char *argv[]) 43 | { 44 | int i,j,k,l,nchan,m=0,nint=1,arg=0,nbytes,nsub=60,flag,nuse=1,realtime=1,quiet=0,imin,imax,partial=0,useoutput=0; 45 | fftwf_complex *c,*d,ct; 46 | fftwf_plan fft; 47 | FILE *infile,*outfile; 48 | char infname[128]="",outfname[128]="",path[64]=".",prefix[32]="",output[128]=""; 49 | char informat='i',outformat='f'; 50 | int16_t *ibuf; 51 | char *cbuf; 52 | float *fbuf; 53 | int32_t *wbuf; 54 | float *z,length,fchan=100.0,tint=1.0,zavg,zstd,*zw; 55 | char *cz; 56 | double freq,samp_rate,mjd,freqmin=-1,freqmax=-1; 57 | struct timeval start,end; 58 | char tbuf[30],nfd[32],header[256]=""; 59 | int sign=1; 60 | int parse_params_from_filename = 0; 61 | sox_format_t * wav_reader = NULL; 62 | int flag_x2=0,flag_x4=0,fac=1; 63 | 64 | // Read arguments 65 | if (argc>1) { 66 | while ((arg=getopt(argc,argv,"i:f:s:c:t:p:n:hm:F:T:bqR:o:IS:P24"))!=-1) { 67 | switch(arg) { 68 | 69 | case 'i': 70 | strcpy(infname,optarg); 71 | break; 72 | 73 | case 'p': 74 | strcpy(path,optarg); 75 | break; 76 | 77 | case 'o': 78 | strcpy(output,optarg); 79 | useoutput=1; 80 | break; 81 | 82 | case 'f': 83 | freq=(double) atof(optarg); 84 | break; 85 | 86 | case 's': 87 | samp_rate=(double) atof(optarg); 88 | break; 89 | 90 | case 'c': 91 | fchan=atof(optarg); 92 | break; 93 | 94 | case 'F': 95 | if (strcmp(optarg,"char")==0) 96 | informat='c'; 97 | else if (strcmp(optarg,"int")==0) 98 | informat='i'; 99 | else if (strcmp(optarg,"float")==0) 100 | informat='f'; 101 | else if (strcmp(optarg, "wav") == 0) 102 | informat='w'; 103 | break; 104 | 105 | case 'R': 106 | sscanf(optarg,"%lf,%lf",&freqmin,&freqmax); 107 | break; 108 | 109 | case 'b': 110 | outformat='c'; 111 | break; 112 | 113 | case 'n': 114 | nsub=atoi(optarg); 115 | break; 116 | 117 | case 'S': 118 | m=atoi(optarg); 119 | break; 120 | 121 | case 'q': 122 | quiet=1; 123 | break; 124 | 125 | case '2': 126 | flag_x2=1; 127 | fac=2; 128 | break; 129 | 130 | case '4': 131 | flag_x4=1; 132 | fac=4; 133 | break; 134 | 135 | case 'm': 136 | nuse=atoi(optarg); 137 | break; 138 | 139 | case 't': 140 | tint=atof(optarg); 141 | break; 142 | 143 | case 'T': 144 | strcpy(nfd,optarg); 145 | realtime=0; 146 | break; 147 | 148 | case 'I': 149 | sign=-1; 150 | break; 151 | 152 | case 'P': 153 | parse_params_from_filename = 1; 154 | realtime=0; 155 | break; 156 | 157 | case 'h': 158 | usage(); 159 | return 0; 160 | 161 | default: 162 | usage(); 163 | return 0; 164 | } 165 | } 166 | } else { 167 | usage(); 168 | return 0; 169 | } 170 | 171 | if (parse_params_from_filename != 0) { 172 | if (rffft_params_from_filename(infname, &samp_rate, &freq, &informat, nfd) != 0) { 173 | fprintf(stderr, "Error parsing parameters from filename\n"); 174 | exit(-1); 175 | }; 176 | } 177 | 178 | if (informat == 'w') { 179 | if (sox_init() != SOX_SUCCESS) { 180 | fprintf(stderr, "Error initalizing sox"); 181 | exit(-1); 182 | } 183 | if (sox_format_init() != SOX_SUCCESS) { 184 | fprintf(stderr, "Error initalizing sox"); 185 | exit(-1); 186 | } 187 | 188 | wav_reader = sox_open_read(infname, NULL, NULL, NULL); 189 | 190 | if (wav_reader == NULL) { 191 | fprintf(stderr, "Error opening file %s", infname); 192 | exit(-1); 193 | } 194 | 195 | if (wav_reader->signal.channels != 2) { 196 | fprintf(stderr, "Error: Only wav files with 2 channels supported."); 197 | exit(-1); 198 | } 199 | 200 | samp_rate = wav_reader->signal.rate; 201 | } 202 | 203 | // Ensure integer number of spectra per subintegration 204 | tint=ceil(fchan*tint)/fchan; 205 | 206 | // Number of channels 207 | nchan=(int) (samp_rate/fchan); 208 | 209 | // Number of integrations 210 | nint=(int) (tint*(float) samp_rate/(float) nchan); 211 | 212 | // Get channel range 213 | if (freqmin>0.0 && freqmax>0.0) { 214 | imin=(int) ((freqmin-freq+0.5*samp_rate)/fchan); 215 | imax=(int) ((freqmax-freq+0.5*samp_rate)/fchan); 216 | if (imin<0 || imin>=nchan || imax<0 || imax>=nchan || imax<=imin) { 217 | fprintf(stderr,"Output frequency range (%.3lf MHz -> %.3lf MHz) incompatible with\ninput settings (%.3lf MHz center frequency, %.3lf MHz sample rate)!\n",freqmin*1e-6,freqmax*1e-6,freq*1e-6,samp_rate*1e-6); 218 | return -1; 219 | } 220 | partial=1; 221 | } 222 | 223 | // Dump statistics 224 | printf("Filename: %s\n", (strlen(infname) ? infname : "stdin")); 225 | printf("Frequency: %f MHz\n",freq*1e-6); 226 | printf("Bandwidth: %f MHz\n",samp_rate*1e-6); 227 | printf("Sampling time: %f us\n",1e6/samp_rate); 228 | printf("Number of channels: %d\n",nchan); 229 | printf("Channel size: %f Hz\n",samp_rate/(float) nchan); 230 | printf("Integration time: %f s\n",tint); 231 | printf("Number of averaged spectra: %d\n",nint); 232 | printf("Number of subints per file: %d\n",nsub); 233 | printf("Starting index: %d\n",m); 234 | 235 | // Allocate 236 | c=fftwf_malloc(sizeof(fftwf_complex)*nchan); 237 | d=fftwf_malloc(sizeof(fftwf_complex)*nchan); 238 | ibuf=(int16_t *) malloc(sizeof(int16_t)*2*nchan); 239 | cbuf=(char *) malloc(sizeof(char)*2*nchan); 240 | fbuf=(float *) malloc(sizeof(float)*2*nchan); 241 | wbuf = (int32_t *)malloc(sizeof(int32_t) * 2 * nchan); 242 | z=(float *) malloc(sizeof(float)*nchan); 243 | cz=(char *) malloc(sizeof(char)*nchan); 244 | zw=(float *) malloc(sizeof(float)*nchan); 245 | 246 | // Compute window 247 | for (i=0;i127.0) 397 | z[i]=127.0; 398 | cz[i]=(char) z[i]; 399 | } 400 | } 401 | 402 | // Format start time 403 | if (realtime==1) { 404 | strftime(tbuf,30,"%Y-%m-%dT%T",gmtime(&start.tv_sec)); 405 | sprintf(nfd,"%s.%03ld",tbuf,start.tv_usec/1000); 406 | } else { 407 | mjd2nfd(mjd+(m*nsub+k)*tint/86400.0,nfd); 408 | length=tint; 409 | } 410 | 411 | // Header 412 | if (partial==0) { 413 | if (outformat=='f') 414 | sprintf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\nEND\n",nfd,freq,samp_rate/fac,length,nchan,nsub); 415 | else if (outformat=='c') 416 | sprintf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\nNBITS 8\nMEAN %e\nRMS %e\nEND\n",nfd,freq,samp_rate/fac,length,nchan,nsub,zavg,zstd); 417 | } else if (partial==1) { 418 | if (outformat=='f') 419 | sprintf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\nEND\n",nfd,0.5*(freqmax+freqmin),(freqmax-freqmin)/fac,length,imax-imin,nsub); 420 | else if (outformat=='c') 421 | sprintf(header,"HEADER\nUTC_START %s\nFREQ %lf Hz\nBW %lf Hz\nLENGTH %f s\nNCHAN %d\nNSUB %d\nNBITS 8\nMEAN %e\nRMS %e\nEND\n",nfd,0.5*(freqmax+freqmin),(freqmax-freqmin)/fac,length,imax-imin,nsub,zavg,zstd); 422 | } 423 | // Limit output 424 | if (!quiet) 425 | printf("%s %s %f %d\n",outfname,nfd,length,j); 426 | 427 | // Dump file 428 | fwrite(header,sizeof(char),256,outfile); 429 | if (partial==0) { 430 | if (outformat=='f') 431 | fwrite(z,sizeof(float),nchan,outfile); 432 | else if (outformat=='c') 433 | fwrite(cz,sizeof(char),nchan,outfile); 434 | } else if (partial==1) { 435 | if (outformat=='f') 436 | fwrite(&z[imin],sizeof(float),imax-imin,outfile); 437 | else if (outformat=='c') 438 | fwrite(&cz[imin],sizeof(char),imax-imin,outfile); 439 | } 440 | // Break; 441 | if (nbytes==0) 442 | break; 443 | } 444 | 445 | // Break; 446 | if (nbytes==0) 447 | break; 448 | 449 | // Close file 450 | fclose(outfile); 451 | } 452 | 453 | if (informat != 'w') { 454 | fclose(infile); 455 | } 456 | 457 | if (informat == 'w') { 458 | sox_close(wav_reader); 459 | sox_format_quit(); 460 | sox_quit(); 461 | } 462 | 463 | // Destroy plan 464 | fftwf_destroy_plan(fft); 465 | 466 | // Deallocate 467 | free(ibuf); 468 | free(cbuf); 469 | free(fbuf); 470 | free(wbuf); 471 | fftwf_free(c); 472 | fftwf_free(d); 473 | free(z); 474 | free(cz); 475 | free(zw); 476 | 477 | return 0; 478 | } 479 | 480 | -------------------------------------------------------------------------------- /rftrace.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "sgdp4h.h" 7 | #include "satutl.h" 8 | #include "rftime.h" 9 | #include "rftrace.h" 10 | #include 11 | #include 12 | 13 | 14 | #include "rftles.h" 15 | 16 | #define LIM 80 17 | #define D2R M_PI/180.0 18 | #define R2D 180.0/M_PI 19 | #define XKMPER 6378.135 // Earth radius in km 20 | #define XKMPAU 149597879.691 // AU in km 21 | #define FLAT (1.0/298.257) 22 | #define C 299792.458 // Speed of light in km/s 23 | 24 | struct point { 25 | xyz_t obspos,obsvel; 26 | xyz_t grpos,grvel; 27 | }; 28 | struct site { 29 | int id; 30 | double lng,lat; 31 | float alt; 32 | char observer[64]; 33 | }; 34 | 35 | // Return x modulo y [0,y) 36 | double modulo(double x,double y) 37 | { 38 | x=fmod(x,y); 39 | if (x<0.0) x+=y; 40 | 41 | return x; 42 | } 43 | 44 | // Read a line of maximum length int lim from file FILE into string s 45 | int fgetline(FILE *file,char *s,int lim) 46 | { 47 | int c,i=0; 48 | 49 | while (--lim > 0 && (c=fgetc(file)) != EOF && c != '\n') 50 | s[i++] = c; 51 | // if (c == '\n') 52 | // s[i++] = c; 53 | s[i] = '\0'; 54 | return i; 55 | } 56 | 57 | // Greenwich Mean Sidereal Time 58 | double gmst(double mjd) 59 | { 60 | double t,gmst; 61 | 62 | t=(mjd-51544.5)/36525.0; 63 | 64 | gmst=modulo(280.46061837+360.98564736629*(mjd-51544.5)+t*t*(0.000387933-t/38710000),360.0); 65 | 66 | return gmst; 67 | } 68 | 69 | // Greenwich Mean Sidereal Time 70 | double dgmst(double mjd) 71 | { 72 | double t,dgmst; 73 | 74 | t=(mjd-51544.5)/36525.0; 75 | 76 | dgmst=360.98564736629+t*(0.000387933-t/38710000); 77 | 78 | return dgmst; 79 | } 80 | 81 | // Observer position 82 | void obspos_xyz(double mjd,double lng,double lat,float alt,xyz_t *pos,xyz_t *vel) 83 | { 84 | double ff,gc,gs,theta,s,dtheta; 85 | 86 | s=sin(lat*D2R); 87 | ff=sqrt(1.0-FLAT*(2.0-FLAT)*s*s); 88 | gc=1.0/ff+alt/XKMPER; 89 | gs=(1.0-FLAT)*(1.0-FLAT)/ff+alt/XKMPER; 90 | 91 | theta=gmst(mjd)+lng; 92 | dtheta=dgmst(mjd)*D2R/86400; 93 | 94 | pos->x=gc*cos(lat*D2R)*cos(theta*D2R)*XKMPER; 95 | pos->y=gc*cos(lat*D2R)*sin(theta*D2R)*XKMPER; 96 | pos->z=gs*sin(lat*D2R)*XKMPER; 97 | vel->x=-gc*cos(lat*D2R)*sin(theta*D2R)*XKMPER*dtheta; 98 | vel->y=gc*cos(lat*D2R)*cos(theta*D2R)*XKMPER*dtheta; 99 | vel->z=0.0; 100 | 101 | return; 102 | } 103 | 104 | // Convert equatorial into horizontal coordinates 105 | void equatorial2horizontal(double mjd,double ra,double de,double lng,double lat,double *azi,double *alt) 106 | { 107 | double h; 108 | 109 | h=gmst(mjd)+lng-ra; 110 | 111 | *azi=modulo(atan2(sin(h*D2R),cos(h*D2R)*sin(lat*D2R)-tan(de*D2R)*cos(lat*D2R))*R2D,360.0); 112 | *alt=asin(sin(lat*D2R)*sin(de*D2R)+cos(lat*D2R)*cos(de*D2R)*cos(h*D2R))*R2D; 113 | 114 | return; 115 | } 116 | 117 | // Get observing site 118 | struct site get_site(int site_id) 119 | { 120 | int i=0,status; 121 | char line[LIM]; 122 | FILE *file; 123 | int id; 124 | double lat,lng; 125 | float alt; 126 | char abbrev[3],observer[64]; 127 | struct site s; 128 | char *env,filename[LIM]; 129 | 130 | env=getenv("ST_DATADIR"); 131 | if(env==NULL||strlen(env)==0) 132 | env="."; 133 | sprintf(filename,"%s/data/sites.txt",env); 134 | 135 | file=fopen(filename,"r"); 136 | if (file==NULL) { 137 | printf("File with site information not found!\n"); 138 | return s; 139 | } 140 | while (fgets(line,LIM,file)!=NULL) { 141 | // Skip 142 | if (strstr(line,"#")!=NULL) 143 | continue; 144 | 145 | // Strip newline 146 | line[strlen(line)-1]='\0'; 147 | 148 | // Read data 149 | status=sscanf(line,"%4d %2s %lf %lf %f", 150 | &id,abbrev,&lat,&lng,&alt); 151 | strcpy(observer,line+38); 152 | 153 | // Change to km 154 | alt/=1000.0; 155 | 156 | // Copy site 157 | if (id==site_id) { 158 | s.lat=lat; 159 | s.lng=lng; 160 | s.alt=alt; 161 | s.id=id; 162 | strcpy(s.observer,observer); 163 | } 164 | 165 | } 166 | fclose(file); 167 | 168 | return s; 169 | } 170 | 171 | // Identify trace 172 | void identify_trace_graves(char *tlefile,struct trace t,int satno,char *freqlist) 173 | { 174 | int i,imode,flag=0,status,imid; 175 | struct point *p; 176 | struct site s,sg; 177 | double *v,*vg; 178 | tle_t *tle; 179 | xyz_t satpos,satvel; 180 | FILE *file; 181 | double dx,dy,dz,dvx,dvy,dvz,r,za; 182 | double sum1,sum2,beta,freq0,rms,mjd0; 183 | char nfd[32],nfdmin[32],text[16]; 184 | char * satnamemin = NULL; 185 | int satnomin; 186 | double rmsmin,freqmin,altmin,azimin; 187 | double ra,de,azi,alt; 188 | 189 | // Reloop stderr 190 | if (freopen("/tmp/stderr.txt","w",stderr)==NULL) 191 | fprintf(stderr,"Failed to redirect stderr\n"); 192 | 193 | // Get sites 194 | s=get_site(t.site); 195 | sg=get_site(9999); 196 | 197 | // Allocate 198 | p=(struct point *) malloc(sizeof(struct point)*t.n); 199 | v=(double *) malloc(sizeof(double)*t.n); 200 | vg=(double *) malloc(sizeof(double)*t.n); 201 | 202 | // Get observer position 203 | for (i=0;inumber_of_elements == 0) { 216 | fprintf(stderr,"TLE file %s not found or empty\n", tlefile); 217 | return; 218 | } 219 | 220 | for (long elem = 0; elem < tle_array->number_of_elements; elem++) { 221 | // Get TLE 222 | tle = get_tle_by_index(tle_array, elem); 223 | 224 | // Initialize 225 | imode=init_sgdp4(&(tle->orbit)); 226 | if (imode==SGDP4_ERROR) { 227 | printf("Error with %d, skipping\n", tle->orbit.satno); 228 | continue; 229 | } 230 | 231 | // Loop over points 232 | for (i=0,sum1=0.0,sum2=0.0;i270.0) && alt>15.0 && alt<40.0)) 262 | // t[j].za[i]=100.0; 263 | } 264 | freq0=143050000.0; 265 | 266 | // Compute residuals 267 | for (i=0,rms=0.0;i0.0) 277 | mjd2nfd(mjd0,nfd); 278 | else 279 | strcpy(nfd,"0000-00-00T00:00:00"); 280 | 281 | if (rms<1000) { 282 | if (rms<50.0) { 283 | if (tle->name) { 284 | printf("%05d %s %8.1f Hz (%.1f,%.1f) | %s\n", tle->orbit.satno, nfd, rms, modulo(azi+180.0,360.0), alt, tle->name); 285 | } else { 286 | printf("%05d %s %8.1f Hz (%.1f,%.1f)\n", tle->orbit.satno, nfd, rms, modulo(azi+180.0,360.0), alt); 287 | } 288 | } 289 | // printf("%05d: %s %8.3f MHz %8.3f kHz\n",tle->orbit.satno,nfd,1e-6*freq0,1e-3*rms); 290 | if (flag==0 || rmsorbit.satno; 292 | satnamemin = tle->name; 293 | strcpy(nfdmin,nfd); 294 | freqmin=freq0; 295 | rmsmin=rms; 296 | altmin=alt; 297 | azimin=azi; 298 | flag=1; 299 | } 300 | } 301 | } 302 | fclose(stderr); 303 | 304 | if (flag==1) { 305 | printf("\nBest fitting object:\n"); 306 | if (satnamemin) { 307 | printf("%05d - %s: %s %8.1f Hz (%.1f,%.1f)\n", satnomin, satnamemin, nfdmin, rmsmin, modulo(azimin+180.0,360.0), altmin); 308 | } else { 309 | printf("%05d: %s %8.1f Hz (%.1f,%.1f)\n", satnomin, nfdmin, rmsmin, modulo(azimin+180.0,360.0), altmin); 310 | } 311 | printf("Store frequency? [y/n]\n"); 312 | status=scanf("%s",text); 313 | if (text[0]=='y') { 314 | file=fopen(freqlist,"a"); 315 | fprintf(file,"%05d %lf\n",satnomin,1e-6*freqmin); 316 | fclose(file); 317 | file=fopen("log.txt","a"); 318 | fprintf(file,"%05d %lf %.3f %.19s\n",satnomin,1e-6*freqmin,1e-3*rmsmin,nfdmin); 319 | fclose(file); 320 | printf("Frequency stored\n\n"); 321 | } 322 | } else { 323 | printf("\nTrace not identified..\n"); 324 | } 325 | 326 | // Free 327 | free_tles(tle_array); 328 | free(p); 329 | free(v); 330 | free(vg); 331 | 332 | return; 333 | } 334 | 335 | // Identify trace 336 | void identify_trace(char *tlefile,struct trace t,int satno,char *freqlist) 337 | { 338 | int i,imode,flag=0,status; 339 | struct point *p; 340 | struct site s; 341 | double *v; 342 | tle_t *tle; 343 | xyz_t satpos,satvel; 344 | FILE *file; 345 | double dx,dy,dz,dvx,dvy,dvz,r,za; 346 | double sum1,sum2,beta,freq0,rms,mjd0; 347 | char nfd[32],nfdmin[32],text[16]; 348 | char * satnamemin = NULL; 349 | int satnomin; 350 | double rmsmin,freqmin; 351 | struct timeval tv; 352 | char tbuf[30]; 353 | 354 | // Reloop stderr 355 | if (freopen("/tmp/stderr.txt","w",stderr)==NULL) 356 | fprintf(stderr,"Failed to redirect stderr\n"); 357 | 358 | // Get site 359 | s=get_site(t.site); 360 | 361 | // Allocate 362 | p=(struct point *) malloc(sizeof(struct point)*t.n); 363 | v=(double *) malloc(sizeof(double)*t.n); 364 | 365 | // Get observer position 366 | for (i=0;inumber_of_elements == 0) { 375 | fprintf(stderr,"TLE file %s not found or empty\n", tlefile); 376 | return; 377 | } 378 | 379 | for (long elem = 0; elem < tle_array->number_of_elements; elem++) { 380 | // Get TLE 381 | tle = get_tle_by_index(tle_array, elem); 382 | 383 | // Initialize 384 | imode=init_sgdp4(&(tle->orbit)); 385 | if (imode==SGDP4_ERROR) { 386 | printf("Error with %d, skipping\n", tle->orbit.satno); 387 | continue; 388 | } 389 | 390 | // Loop over points 391 | for (i=0,sum1=0.0,sum2=0.0;i0.0) 422 | mjd2nfd(mjd0,nfd); 423 | else 424 | strcpy(nfd,"0000-00-00T00:00:00"); 425 | 426 | if (rms<1000) { 427 | if (tle->name) { 428 | printf("%05d %s %8.3f MHz %8.3f kHz | %s\n", tle->orbit.satno, nfd, 1e-6*freq0, 1e-3*rms, tle->name); 429 | } else { 430 | printf("%05d %s %8.3f MHz %8.3f kHz\n", tle->orbit.satno, nfd, 1e-6*freq0, 1e-3*rms); 431 | } 432 | if (flag==0 || rmsorbit.satno; 434 | satnamemin = tle->name; 435 | strcpy(nfdmin,nfd); 436 | freqmin=freq0; 437 | rmsmin=rms; 438 | flag=1; 439 | } 440 | } 441 | } 442 | fclose(stderr); 443 | 444 | if (flag==1) { 445 | printf("\nBest fitting object:\n"); 446 | if (satnamemin) { 447 | printf("%05d %s %8.3f MHz %8.3f kHz | %s\n", satnomin, nfdmin, 1e-6*freqmin, 1e-3*rmsmin, satnamemin); 448 | } else { 449 | printf("%05d %s %8.3f MHz %8.3f kHz\n", satnomin, nfdmin, 1e-6*freqmin, 1e-3*rmsmin); 450 | } 451 | printf("Store frequency? [y/n]\n"); 452 | status=scanf("%s",text); 453 | if (text[0]=='y') { 454 | gettimeofday(&tv,0); 455 | strftime(tbuf,30,"%Y-%m-%dT%T",gmtime(&tv.tv_sec)); 456 | file=fopen(freqlist,"a"); 457 | fprintf(file,"%05d %lf %.19s %04d\n",satnomin,1e-6*freqmin,tbuf,s.id); 458 | fclose(file); 459 | file=fopen("log.txt","a"); 460 | fprintf(file,"%05d %lf %.3f %.19s\n",satnomin,1e-6*freqmin,1e-3*rmsmin,nfdmin); 461 | fclose(file); 462 | printf("Frequency stored\n\n"); 463 | } 464 | } else { 465 | printf("\nTrace not identified..\n"); 466 | } 467 | 468 | // Free 469 | free_tles(tle_array); 470 | free(p); 471 | free(v); 472 | 473 | return; 474 | } 475 | 476 | // Is it a classified satellite 477 | int is_classified(int satno) 478 | { 479 | int flag=0,no; 480 | char *env,tlefile[128],line[LIM]; 481 | FILE *file; 482 | 483 | // Get classfd.tle path 484 | env=getenv("ST_TLEDIR"); 485 | if(env==NULL||strlen(env)==0) 486 | env="."; 487 | sprintf(tlefile,"%s/classfd.tle",env); 488 | 489 | // Does it exist 490 | file=fopen(tlefile,"r"); 491 | if (file==NULL) { 492 | printf("%s not found\n",tlefile); 493 | flag=0; 494 | } else { 495 | // Loop over TLEs 496 | while (fgetline(file,line,LIM)>0) { 497 | // Use 1st TLE line 498 | if (line[0]=='1') { 499 | sscanf(line+2,"%d",&no); 500 | if (no==satno) flag=1; 501 | } 502 | } 503 | fclose(file); 504 | } 505 | 506 | return flag; 507 | } 508 | 509 | // Compute trace 510 | struct trace *compute_trace(char *tlefile,double *mjd,int n,int site_id,float freq,float bw,int *nsat,int graves,char *freqlist) 511 | { 512 | int i,j,imode,flag,satno,tflag,m,status,hastle; 513 | struct point *p; 514 | struct site s,sg; 515 | FILE *file,*infile; 516 | tle_t *tle; 517 | xyz_t satpos,satvel; 518 | double dx,dy,dz,dvx,dvy,dvz,r,v,za,vg; 519 | double freq0,dfreq; 520 | char * line = NULL; 521 | size_t line_size = 0; 522 | struct trace *t; 523 | float fmin,fmax; 524 | double ra,de,azi,alt; 525 | 526 | // Maximum doppler offset (assumes max 20km/s velocity) 527 | dfreq=20.0/299792.458*freq; 528 | 529 | // Frequency limits 530 | fmin=freq-0.5*bw-dfreq; 531 | fmax=freq+0.5*bw+dfreq; 532 | 533 | // Reloop stderr 534 | if (freopen("/tmp/stderr.txt","w",stderr)==NULL) 535 | fprintf(stderr,"Failed to redirect stderr\n"); 536 | 537 | // Find number of satellites in frequency range 538 | infile=fopen(freqlist,"r"); 539 | if (infile==NULL) { 540 | printf("%s not found\n",freqlist); 541 | *nsat=0; 542 | return NULL; 543 | } else { 544 | for (i=0;;) { 545 | if (getline(&line, &line_size, infile) < 0) 546 | break; 547 | 548 | if ((line[0] == '\n') || (line[0] == '#')) 549 | continue; 550 | 551 | status=sscanf(line,"%d %lf",&satno,&freq0); 552 | 553 | if (status != 2) { 554 | printf("Frequency line not parsable, skipping:\n%s\n", line); 555 | continue; 556 | } 557 | 558 | if (graves==1 && fabs(freq0-143.050)<1e-3) 559 | i++; 560 | else if (freq0>=fmin && freq0<=fmax && graves==0) 561 | i++; 562 | 563 | } 564 | fclose(infile); 565 | *nsat=i; 566 | } 567 | 568 | // Free allocated buffer by getline() as we might return NULL 569 | // before next call to to getline() 570 | free(line); 571 | line = NULL; 572 | 573 | // Break out 574 | if (i==0) 575 | { 576 | *nsat=0; 577 | return NULL; 578 | } 579 | 580 | // Valid MJDs 581 | for (i=0;inumber_of_elements == 0) { 610 | fprintf(stderr,"TLE file %s not found or empty\n", tlefile); 611 | return NULL; 612 | } 613 | 614 | infile=fopen(freqlist,"r"); 615 | for (j=0;;) { 616 | if (getline(&line, &line_size, infile) < 0) 617 | break; 618 | 619 | if ((line[0] == '\n') || (line[0] == '#')) 620 | continue; 621 | 622 | status=sscanf(line,"%d %lf",&satno,&freq0); 623 | 624 | if (status != 2) { 625 | // Error about not being able to parse line already reported 626 | // while counting lines, don't repeat ourselves 627 | continue; 628 | } 629 | 630 | flag=0; 631 | if (graves==1 && fabs(freq0-143.050)<1e-3) 632 | flag=1; 633 | else if (freq0>=fmin && freq0<=fmax && graves==0) 634 | flag=1; 635 | 636 | if (flag==0) 637 | continue; 638 | 639 | // Allocate 640 | t[j].satname[0] = '\0'; 641 | t[j].satno=satno; 642 | t[j].site=site_id; 643 | t[j].n=m; 644 | t[j].freq0=freq0; 645 | t[j].mjd=(double *) malloc(sizeof(double)*m); 646 | t[j].freq=(double *) malloc(sizeof(double)*m); 647 | t[j].za=(float *) malloc(sizeof(float)*m); 648 | t[j].classfd=is_classified(t[j].satno); 649 | t[j].graves=graves; 650 | 651 | // Get TLE 652 | tle = get_tle_by_catalog_id(tle_array, satno); 653 | 654 | if (tle) { 655 | // Initialize 656 | imode=init_sgdp4(&(tle->orbit)); 657 | if (imode==SGDP4_ERROR) { 658 | printf("Error with %d, skipping\n", tle->orbit.satno); 659 | continue; 660 | } 661 | 662 | // Copy sat name into trace 663 | if (tle->name != NULL) { 664 | strncpy(t[j].satname, tle->name, 25); 665 | } 666 | 667 | // Loop over points 668 | for (i=0,flag=0,tflag=0;i270.0) && alt>15.0 && alt<40.0)) 703 | t[j].za[i]=100.0; 704 | } 705 | } 706 | 707 | // Increment 708 | j++; 709 | } 710 | } 711 | fclose(infile); 712 | fclose(stderr); 713 | 714 | // Free 715 | free(line); 716 | line = NULL; 717 | 718 | free_tles(tle_array); 719 | free(p); 720 | 721 | // Update counter 722 | *nsat=j; 723 | 724 | return t; 725 | } 726 | 727 | // Compute trace 728 | void compute_doppler(char *tlefile,double *mjd,int n,int site_id,int satno,int graves, int skiphigh, char *outfname) 729 | { 730 | int i,j,imode,flag,tflag,m,status; 731 | struct point *p; 732 | struct site s,sg; 733 | FILE *outfile; 734 | tle_t *tle; 735 | xyz_t satpos,satvel; 736 | double dx,dy,dz,dvx,dvy,dvz,r,v,rg,vg; 737 | double freq0; 738 | char line[LIM],text[8]; 739 | struct trace *t; 740 | float fmin,fmax; 741 | double ra,de,azi,alt; 742 | double rag,deg,azig,altg; 743 | 744 | // Reloop stderr 745 | if (freopen("/tmp/stderr.txt","w",stderr)==NULL) 746 | fprintf(stderr,"Failed to redirect stderr\n"); 747 | 748 | // Get site 749 | s=get_site(site_id); 750 | 751 | // Allocate 752 | p=(struct point *) malloc(sizeof(struct point)*n); 753 | 754 | // Get observer position 755 | for (i=0;inumber_of_elements == 0) { 778 | fprintf(stderr,"TLE file %s not found or empty\n", tlefile); 779 | return; 780 | } 781 | 782 | // Get TLE 783 | tle = get_tle_by_catalog_id(tle_array, satno); 784 | 785 | // Skip high satellites 786 | if (tle && !(skiphigh == 1 && tle->orbit.rev < 10.0)) { 787 | // Initialize 788 | imode=init_sgdp4(&(tle->orbit)); 789 | if (imode==SGDP4_ERROR) { 790 | printf("Error with %d, skipping\n", tle->orbit.satno); 791 | } 792 | 793 | // Loop over points 794 | for (i=0,flag=0,tflag=0;iorbit.satno,mjd[i],r,v,azi,alt,rg,vg,azig,altg); 824 | } else { 825 | fprintf(outfile,"%05d %14.8lf %f %f %f %f\n", tle->orbit.satno,mjd[i],r,v,azi,alt); 826 | } 827 | } 828 | } 829 | fclose(outfile); 830 | 831 | fclose(stderr); 832 | 833 | // Free 834 | free_tles(tle_array); 835 | free(p); 836 | 837 | return; 838 | } 839 | -------------------------------------------------------------------------------- /rfpng.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "rftime.h" 8 | #include "rfio.h" 9 | #include "rftrace.h" 10 | 11 | #define LIM 128 12 | #define NMAX 64 13 | 14 | struct select { 15 | int flag,n; 16 | float x[NMAX],y[NMAX],w; 17 | }; 18 | struct point { 19 | double mjd; 20 | double freq; 21 | }; 22 | 23 | void dec2sex(double x,char *s,int f,int len); 24 | void time_axis(double *mjd,int n,float xmin,float xmax,float ymin,float ymax); 25 | void usage(void); 26 | void plot_traces(struct trace *t,int nsat,float foff,int type,int isci); 27 | void filter(struct spectrogram s,int site_id,float sigma,char *filename,int graves); 28 | 29 | int main(int argc,char *argv[]) 30 | { 31 | struct spectrogram s; 32 | float tr[]={-0.5,1.0,0.0,-0.5,0.0,1.0}; 33 | float cool_l[]={-0.5,0.0,0.17,0.33,0.50,0.67,0.83,1.0,1.7}; 34 | float cool_r[]={0.0,0.0,0.0,0.0,0.6,1.0,1.0,1.0,1.0}; 35 | float cool_g[]={0.0,0.0,0.0,1.0,1.0,1.0,0.6,0.0,1.0}; 36 | float cool_b[]={0.0,0.3,0.8,1.0,0.3,0.0,0.0,0.0,1.0}; 37 | float viridis_l[]={0.000000,0.003922,0.007843,0.011765,0.015686,0.019608,0.023529,0.027451,0.031373,0.035294,0.039216,0.043137,0.047059,0.050980,0.054902,0.058824,0.062745,0.066667,0.070588,0.074510,0.078431,0.082353,0.086275,0.090196,0.094118,0.098039,0.101961,0.105882,0.109804,0.113725,0.117647,0.121569,0.125490,0.129412,0.133333,0.137255,0.141176,0.145098,0.149020,0.152941,0.156863,0.160784,0.164706,0.168627,0.172549,0.176471,0.180392,0.184314,0.188235,0.192157,0.196078,0.200000,0.203922,0.207843,0.211765,0.215686,0.219608,0.223529,0.227451,0.231373,0.235294,0.239216,0.243137,0.247059,0.250980,0.254902,0.258824,0.262745,0.266667,0.270588,0.274510,0.278431,0.282353,0.286275,0.290196,0.294118,0.298039,0.301961,0.305882,0.309804,0.313725,0.317647,0.321569,0.325490,0.329412,0.333333,0.337255,0.341176,0.345098,0.349020,0.352941,0.356863,0.360784,0.364706,0.368627,0.372549,0.376471,0.380392,0.384314,0.388235,0.392157,0.396078,0.400000,0.403922,0.407843,0.411765,0.415686,0.419608,0.423529,0.427451,0.431373,0.435294,0.439216,0.443137,0.447059,0.450980,0.454902,0.458824,0.462745,0.466667,0.470588,0.474510,0.478431,0.482353,0.486275,0.490196,0.494118,0.498039,0.501961,0.505882,0.509804,0.513725,0.517647,0.521569,0.525490,0.529412,0.533333,0.537255,0.541176,0.545098,0.549020,0.552941,0.556863,0.560784,0.564706,0.568627,0.572549,0.576471,0.580392,0.584314,0.588235,0.592157,0.596078,0.600000,0.603922,0.607843,0.611765,0.615686,0.619608,0.623529,0.627451,0.631373,0.635294,0.639216,0.643137,0.647059,0.650980,0.654902,0.658824,0.662745,0.666667,0.670588,0.674510,0.678431,0.682353,0.686275,0.690196,0.694118,0.698039,0.701961,0.705882,0.709804,0.713725,0.717647,0.721569,0.725490,0.729412,0.733333,0.737255,0.741176,0.745098,0.749020,0.752941,0.756863,0.760784,0.764706,0.768627,0.772549,0.776471,0.780392,0.784314,0.788235,0.792157,0.796078,0.800000,0.803922,0.807843,0.811765,0.815686,0.819608,0.823529,0.827451,0.831373,0.835294,0.839216,0.843137,0.847059,0.850980,0.854902,0.858824,0.862745,0.866667,0.870588,0.874510,0.878431,0.882353,0.886275,0.890196,0.894118,0.898039,0.901961,0.905882,0.909804,0.913725,0.917647,0.921569,0.925490,0.929412,0.933333,0.937255,0.941176,0.945098,0.949020,0.952941,0.956863,0.960784,0.964706,0.968627,0.972549,0.976471,0.980392,0.984314,0.988235,0.992157,0.996078,1.000000}; 38 | float viridis_r[]={0.267004,0.268510,0.269944,0.271305,0.272594,0.273809,0.274952,0.276022,0.277018,0.277941,0.278791,0.279566,0.280267,0.280894,0.281446,0.281924,0.282327,0.282656,0.282910,0.283091,0.283197,0.283229,0.283187,0.283072,0.282884,0.282623,0.282290,0.281887,0.281412,0.280868,0.280255,0.279574,0.278826,0.278012,0.277134,0.276194,0.275191,0.274128,0.273006,0.271828,0.270595,0.269308,0.267968,0.266580,0.265145,0.263663,0.262138,0.260571,0.258965,0.257322,0.255645,0.253935,0.252194,0.250425,0.248629,0.246811,0.244972,0.243113,0.241237,0.239346,0.237441,0.235526,0.233603,0.231674,0.229739,0.227802,0.225863,0.223925,0.221989,0.220057,0.218130,0.216210,0.214298,0.212395,0.210503,0.208623,0.206756,0.204903,0.203063,0.201239,0.199430,0.197636,0.195860,0.194100,0.192357,0.190631,0.188923,0.187231,0.185556,0.183898,0.182256,0.180629,0.179019,0.177423,0.175841,0.174274,0.172719,0.171176,0.169646,0.168126,0.166617,0.165117,0.163625,0.162142,0.160665,0.159194,0.157729,0.156270,0.154815,0.153364,0.151918,0.150476,0.149039,0.147607,0.146180,0.144759,0.143343,0.141935,0.140536,0.139147,0.137770,0.136408,0.135066,0.133743,0.132444,0.131172,0.129933,0.128729,0.127568,0.126453,0.125394,0.124395,0.123463,0.122606,0.121831,0.121148,0.120565,0.120092,0.119738,0.119512,0.119423,0.119483,0.119699,0.120081,0.120638,0.121380,0.122312,0.123444,0.124780,0.126326,0.128087,0.130067,0.132268,0.134692,0.137339,0.140210,0.143303,0.146616,0.150148,0.153894,0.157851,0.162016,0.166383,0.170948,0.175707,0.180653,0.185783,0.191090,0.196571,0.202219,0.208030,0.214000,0.220124,0.226397,0.232815,0.239374,0.246070,0.252899,0.259857,0.266941,0.274149,0.281477,0.288921,0.296479,0.304148,0.311925,0.319809,0.327796,0.335885,0.344074,0.352360,0.360741,0.369214,0.377779,0.386433,0.395174,0.404001,0.412913,0.421908,0.430983,0.440137,0.449368,0.458674,0.468053,0.477504,0.487026,0.496615,0.506271,0.515992,0.525776,0.535621,0.545524,0.555484,0.565498,0.575563,0.585678,0.595839,0.606045,0.616293,0.626579,0.636902,0.647257,0.657642,0.668054,0.678489,0.688944,0.699415,0.709898,0.720391,0.730889,0.741388,0.751884,0.762373,0.772852,0.783315,0.793760,0.804182,0.814576,0.824940,0.835270,0.845561,0.855810,0.866013,0.876168,0.886271,0.896320,0.906311,0.916242,0.926106,0.935904,0.945636,0.955300,0.964894,0.974417,0.983868,0.993248}; 39 | float viridis_g[]={0.004874,0.009605,0.014625,0.019942,0.025563,0.031497,0.037752,0.044167,0.050344,0.056324,0.062145,0.067836,0.073417,0.078907,0.084320,0.089666,0.094955,0.100196,0.105393,0.110553,0.115680,0.120777,0.125848,0.130895,0.135920,0.140926,0.145912,0.150881,0.155834,0.160771,0.165693,0.170599,0.175490,0.180367,0.185228,0.190074,0.194905,0.199721,0.204520,0.209303,0.214069,0.218818,0.223549,0.228262,0.232956,0.237631,0.242286,0.246922,0.251537,0.256130,0.260703,0.265254,0.269783,0.274290,0.278775,0.283237,0.287675,0.292092,0.296485,0.300855,0.305202,0.309527,0.313828,0.318106,0.322361,0.326594,0.330805,0.334994,0.339161,0.343307,0.347432,0.351535,0.355619,0.359683,0.363727,0.367752,0.371758,0.375746,0.379716,0.383670,0.387607,0.391528,0.395433,0.399323,0.403199,0.407061,0.410910,0.414746,0.418570,0.422383,0.426184,0.429975,0.433756,0.437527,0.441290,0.445044,0.448791,0.452530,0.456262,0.459988,0.463708,0.467423,0.471133,0.474838,0.478540,0.482237,0.485932,0.489624,0.493313,0.497000,0.500685,0.504369,0.508051,0.511733,0.515413,0.519093,0.522773,0.526453,0.530132,0.533812,0.537492,0.541173,0.544853,0.548535,0.552216,0.555899,0.559582,0.563265,0.566949,0.570633,0.574318,0.578002,0.581687,0.585371,0.589055,0.592739,0.596422,0.600104,0.603785,0.607464,0.611141,0.614817,0.618490,0.622161,0.625828,0.629492,0.633153,0.636809,0.640461,0.644107,0.647749,0.651384,0.655014,0.658636,0.662252,0.665859,0.669459,0.673050,0.676631,0.680203,0.683765,0.687316,0.690856,0.694384,0.697900,0.701402,0.704891,0.708366,0.711827,0.715272,0.718701,0.722114,0.725509,0.728888,0.732247,0.735588,0.738910,0.742211,0.745492,0.748751,0.751988,0.755203,0.758394,0.761561,0.764704,0.767822,0.770914,0.773980,0.777018,0.780029,0.783011,0.785964,0.788888,0.791781,0.794644,0.797475,0.800275,0.803041,0.805774,0.808473,0.811138,0.813768,0.816363,0.818921,0.821444,0.823929,0.826376,0.828786,0.831158,0.833491,0.835785,0.838039,0.840254,0.842430,0.844566,0.846661,0.848717,0.850733,0.852709,0.854645,0.856542,0.858400,0.860219,0.861999,0.863742,0.865448,0.867117,0.868751,0.870350,0.871916,0.873449,0.874951,0.876424,0.877868,0.879285,0.880678,0.882046,0.883393,0.884720,0.886029,0.887322,0.888601,0.889868,0.891125,0.892374,0.893616,0.894855,0.896091,0.897330,0.898570,0.899815,0.901065,0.902323,0.903590,0.904867,0.906157}; 40 | float viridis_b[]={0.329415,0.335427,0.341379,0.347269,0.353093,0.358853,0.364543,0.370164,0.375715,0.381191,0.386592,0.391917,0.397163,0.402329,0.407414,0.412415,0.417331,0.422160,0.426902,0.431554,0.436115,0.440584,0.444960,0.449241,0.453427,0.457517,0.461510,0.465405,0.469201,0.472899,0.476498,0.479997,0.483397,0.486697,0.489898,0.493001,0.496005,0.498911,0.501721,0.504434,0.507052,0.509577,0.512008,0.514349,0.516599,0.518762,0.520837,0.522828,0.524736,0.526563,0.528312,0.529983,0.531579,0.533103,0.534556,0.535941,0.537260,0.538516,0.539709,0.540844,0.541921,0.542944,0.543914,0.544834,0.545706,0.546532,0.547314,0.548053,0.548752,0.549413,0.550038,0.550627,0.551184,0.551710,0.552206,0.552675,0.553117,0.553533,0.553925,0.554294,0.554642,0.554969,0.555276,0.555565,0.555836,0.556089,0.556326,0.556547,0.556753,0.556944,0.557120,0.557282,0.557430,0.557565,0.557685,0.557792,0.557885,0.557965,0.558030,0.558082,0.558119,0.558141,0.558148,0.558140,0.558115,0.558073,0.558013,0.557936,0.557840,0.557724,0.557587,0.557430,0.557250,0.557049,0.556823,0.556572,0.556295,0.555991,0.555659,0.555298,0.554906,0.554483,0.554029,0.553541,0.553018,0.552459,0.551864,0.551229,0.550556,0.549841,0.549086,0.548287,0.547445,0.546557,0.545623,0.544641,0.543611,0.542530,0.541400,0.540218,0.538982,0.537692,0.536347,0.534946,0.533488,0.531973,0.530398,0.528763,0.527068,0.525311,0.523491,0.521608,0.519661,0.517649,0.515571,0.513427,0.511215,0.508936,0.506589,0.504172,0.501686,0.499129,0.496502,0.493803,0.491033,0.488189,0.485273,0.482284,0.479221,0.476084,0.472873,0.469588,0.466226,0.462789,0.459277,0.455688,0.452024,0.448284,0.444467,0.440573,0.436601,0.432552,0.428426,0.424223,0.419943,0.415586,0.411152,0.406640,0.402049,0.397381,0.392636,0.387814,0.382914,0.377939,0.372886,0.367757,0.362552,0.357269,0.351910,0.346476,0.340967,0.335384,0.329727,0.323998,0.318195,0.312321,0.306377,0.300362,0.294279,0.288127,0.281908,0.275626,0.269281,0.262877,0.256415,0.249897,0.243329,0.236712,0.230052,0.223353,0.216620,0.209861,0.203082,0.196293,0.189503,0.182725,0.175971,0.169257,0.162603,0.156029,0.149561,0.143228,0.137064,0.131109,0.125405,0.120005,0.114965,0.110347,0.106217,0.102646,0.099702,0.097452,0.095953,0.095250,0.095374,0.096335,0.098125,0.100717,0.104071,0.108131,0.112838,0.118128,0.123941,0.130215,0.136897,0.143936}; 41 | float heat_l[] = {0.0, 0.2, 0.4, 0.6, 1.0}; 42 | float heat_r[] = {0.0, 0.5, 1.0, 1.0, 1.0}; 43 | float heat_g[] = {0.0, 0.0, 0.5, 1.0, 1.0}; 44 | float heat_b[] = {0.0, 0.0, 0.0, 0.3, 1.0}; 45 | float xmin,xmax,ymin,ymax,zmin,zmax=8.0; 46 | int i,j,k; 47 | float dt,zzmax,s1,s2; 48 | int ix=0,iy=0,isub=0; 49 | int i0,j0,i1,j1,jmax; 50 | float width=14.0,sigma=5.0,foff=0.0,aspect=1.0; 51 | float x,y,x0,y0; 52 | char c; 53 | char path[128],xlabel[64],ylabel[64],filename[32],tlefile[128],pngfile[128],datfile[128],freqlist[128]; 54 | int sec,lsec,ssec; 55 | char stime[16]; 56 | double fmin,fmax,fcen,f; 57 | FILE *file; 58 | int arg=0,nsub=1800,nbin=1; 59 | double f0=0.0,df0=0.0,dy=2500; 60 | int foverlay=1; 61 | struct trace *t,tf; 62 | int nsat,satno; 63 | struct select sel; 64 | char *env; 65 | int site_id=0,cmap=2,graves=0,create_dat=1,fname_flag=1; 66 | 67 | // Get site 68 | env=getenv("ST_COSPAR"); 69 | if (env!=NULL) { 70 | site_id=atoi(env); 71 | } else { 72 | printf("ST_COSPAR environment variable not found.\n"); 73 | } 74 | env=getenv("ST_TLEDIR"); 75 | if(env==NULL||strlen(env)==0) 76 | env="."; 77 | sprintf(tlefile,"%s/bulk.tle",env); 78 | env=getenv("ST_DATADIR"); 79 | if(env==NULL||strlen(env)==0) 80 | env="."; 81 | sprintf(freqlist,"%s/data/frequencies.txt",env); 82 | 83 | // Read arguments 84 | if (argc>1) { 85 | while ((arg=getopt(argc,argv,"p:f:w:s:l:b:z:hc:C:m:gS:qo:O:F:W:A:"))!=-1) { 86 | switch (arg) { 87 | 88 | case 'p': 89 | strcpy(path,optarg); 90 | break; 91 | 92 | case 'o': 93 | strcpy(pngfile,optarg); 94 | fname_flag=0; 95 | break; 96 | 97 | case 'O': 98 | foff=atof(optarg); 99 | break; 100 | 101 | case 's': 102 | isub=atoi(optarg); 103 | break; 104 | 105 | case 'f': 106 | f0=(double) atof(optarg); 107 | break; 108 | 109 | case 'l': 110 | nsub=atoi(optarg); 111 | break; 112 | 113 | case 'F': 114 | strcpy(freqlist,optarg); 115 | break; 116 | 117 | case 'w': 118 | df0=(double) atof(optarg); 119 | break; 120 | 121 | case 'z': 122 | zmax=atof(optarg); 123 | break; 124 | 125 | case 'c': 126 | strcpy(tlefile,optarg); 127 | break; 128 | 129 | case 'g': 130 | graves=1; 131 | break; 132 | 133 | case 'S': 134 | sigma=atof(optarg); 135 | break; 136 | 137 | case 'q': 138 | create_dat=0; 139 | break; 140 | 141 | case 'C': 142 | site_id=atoi(optarg); 143 | break; 144 | 145 | case 'W': 146 | width=atof(optarg); 147 | break; 148 | 149 | case 'A': 150 | aspect=atof(optarg); 151 | break; 152 | 153 | case 'm': 154 | cmap=atoi(optarg); 155 | if (cmap>3) 156 | cmap=0; 157 | break; 158 | 159 | case 'h': 160 | usage(); 161 | return 0; 162 | 163 | default: 164 | usage(); 165 | return 0; 166 | } 167 | } 168 | } else { 169 | usage(); 170 | return 0; 171 | } 172 | 173 | // Read data 174 | s=read_spectrogram(path,isub,nsub,f0,df0,nbin,foff); 175 | if (s.mjd[0]<54000) 176 | return 0; 177 | 178 | // Output filename 179 | if (fname_flag==1) 180 | sprintf(pngfile,"%.19s_%08.3f.png/png",s.nfd0,s.freq*1e-6); 181 | 182 | if (create_dat==1) 183 | sprintf(datfile,"%.19s_%08.3f.dat",s.nfd0,s.freq*1e-6); 184 | 185 | printf("Read spectrogram\n%d channels, %d subints\nFrequency: %g MHz\nBandwidth: %g MHz\n",s.nchan,s.nsub,s.freq*1e-6,s.samp_rate*1e-6); 186 | 187 | // Compute traces 188 | t=compute_trace(tlefile,s.mjd,s.nsub,site_id,s.freq*1e-6,s.samp_rate*1e-6,&nsat,graves,freqlist); 189 | printf("Traces for %d objects for location %d\n",nsat,site_id); 190 | 191 | cpgopen(pngfile); 192 | // cpgctab(cool_l,cool_r,cool_g,cool_b,9,1.0,0.5); 193 | cpgpap(width, aspect); 194 | cpgsch(0.6); 195 | cpgask(1); 196 | 197 | // Default limits 198 | xmin=0.0; 199 | xmax=(float) s.nsub; 200 | ymin=0; 201 | ymax=(float) s.nchan; 202 | zmin=0.0; 203 | 204 | cpgsci(1); 205 | cpgpage(); 206 | cpgsvp(0.1,0.95,0.1,0.95); 207 | cpgswin(xmin,xmax,ymin,ymax); 208 | 209 | if (cmap==3) { 210 | cpggray(s.z,s.nsub,s.nchan,1,s.nsub,1,s.nchan,zmax,zmin,tr); 211 | } else { 212 | if (cmap==0) 213 | cpgctab(cool_l,cool_r,cool_g,cool_b,9,1.0,0.5); 214 | else if (cmap==1) 215 | cpgctab(heat_l,heat_r,heat_g,heat_b,9,1.0,0.5); 216 | else if (cmap==2) 217 | cpgctab(viridis_l,viridis_r,viridis_g,viridis_b,256,1.0,0.5); 218 | cpgimag(s.z,s.nsub,s.nchan,1,s.nsub,1,s.nchan,zmin,zmax,tr); 219 | } 220 | 221 | cpgimag(s.z,s.nsub,s.nchan,1,s.nsub,1,s.nchan,zmin,zmax,tr); 222 | 223 | // Pixel axis 224 | cpgbox("CTSM1",0.,0,"CTSM1",0.,0); 225 | 226 | // Time axis 227 | cpgbox("B",0.,0,"",0.,0); 228 | 229 | cpgswin(xmin,xmax,ymin,ymax); 230 | 231 | // Filter points 232 | if (create_dat) 233 | filter(s,site_id,sigma,datfile,graves); 234 | 235 | time_axis(s.mjd,s.nsub,xmin,xmax,ymin,ymax); 236 | 237 | // Freq axis 238 | fmin=s.freq-0.5*s.samp_rate+ymin*s.samp_rate/(float) s.nchan; 239 | fmax=s.freq-0.5*s.samp_rate+ymax*s.samp_rate/(float) s.nchan; 240 | fmin*=1e-6; 241 | fmax*=1e-6; 242 | 243 | // Plot traces 244 | cpgswin(xmin,xmax,fmin,fmax); 245 | cpgsch(0.5); 246 | plot_traces(t,nsat,0.0,0,3); 247 | plot_traces(t,nsat,0.0,1,8); 248 | cpgsch(0.6); 249 | 250 | // Human readable frequency axis 251 | fcen=0.5*(fmax+fmin); 252 | fcen=floor(1000*fcen)/1000.0; 253 | if (fabs(foff)<1e-3) 254 | sprintf(ylabel,"Frequency - %.3f MHz",fcen); 255 | else 256 | sprintf(ylabel,"Frequency - %.3f MHz (%.3f Hz)",fcen, foff); 257 | fmin-=fcen; 258 | fmax-=fcen; 259 | cpgswin(xmin,xmax,fmin,fmax); 260 | cpgbox("",0.,0,"BTSN",0.,0); 261 | 262 | sprintf(xlabel,"UT Date: %.10s",s.nfd0); 263 | cpglab(xlabel,ylabel," "); 264 | 265 | 266 | cpgend(); 267 | 268 | // Free 269 | free(s.z); 270 | free(s.mjd); 271 | 272 | for (i=0;imjdmax) mjdmax=mjd[i]; 323 | } 324 | } 325 | dt=(float) 86400*(mjdmax-mjdmin); 326 | 327 | // Choose tickmarks 328 | if (dt>43000) { 329 | lsec=10800; 330 | ssec=3600; 331 | } else if (dt>21600) { 332 | lsec=10800; 333 | ssec=3600; 334 | } else if (dt>7200) { 335 | lsec=1800; 336 | ssec=300; 337 | } else if (dt>3600) { 338 | lsec=600; 339 | ssec=120; 340 | } else if (dt>900) { 341 | lsec=300; 342 | ssec=60; 343 | } else { 344 | lsec=60; 345 | ssec=10; 346 | } 347 | // Override 348 | // lsec=60; 349 | // ssec=10; 350 | 351 | // Extrema 352 | tmin=86400.0*(mjdmin-floor(mjdmin)); 353 | tmax=tmin+dt; 354 | tmin=lsec*floor(tmin/lsec); 355 | tmax=lsec*ceil(tmax/lsec); 356 | 357 | // Large tickmarks 358 | for (t=tmin;t<=tmax;t+=lsec) { 359 | mjdt=floor(mjdmin)+t/86400.0; 360 | if (mjdt>=mjdmin && mjdt=mjd[i] && mjdt=mjdmin && mjdt=mjd[i] && mjdt Input path to file /a/b/c_??????.bin\n"); 390 | printf("-o Output PGPLOT device [