├── README.md └── RinexObsToCSV.py /README.md: -------------------------------------------------------------------------------- 1 | # Rinex Parser 2 | ### Tools for converting RINEX files to more easily managed formats 3 | 4 | RINEX (receiver independent exchange format) is a standard GNSS file format for 5 | sharing GNSS receiver information; 6 | while many GNSS receivers will offer a 7 | variety of output formats for storage and post processing, RINEX logs are almost 8 | universally available from commercial receivers and online datasets. 9 | This collection of scripts is designed to convert RINEX files into more 10 | accessible CSV formats that can be more easily read into matrix or spreadsheet 11 | software. 12 | 13 | This software was designed to comply with the RINEX 2.11 standard available 14 | [here](https://igscb.jpl.nasa.gov/igscb/data/format/rinex211.txt). The software 15 | has only been tested on GPS observation files with the `.??O` file extension. 16 | Support for GLONASS or Galileo systems may require modifications to the entry 17 | parsing algorithm. 18 | GPS navigation (ephemeris) file support has not been researched and may require 19 | larger changes to the script. 20 | 21 | ## Sample Data Files 22 | The two files `cat20010.16o` and `cat20010.16n` are provided as sample 23 | observation and navigation files respectively. GNSS measurements, such as 24 | these, are publicaly available and can be downloaded from the 25 | [NOAA CORS](https://geodesy.noaa.gov/CORS/data.shtml) website. 26 | The `cat2` site is located in Avalon, California. 27 | 28 | ## Usage 29 | #### Observation RINEX to CSV 30 | `RinexObsToCSV.py` converts a RINEX observation to a comma separated value 31 | format composed of user specified fields. 32 | Available fields are time in GPS week-seconds (`time`), PRN/satellite number 33 | (`prn`), code measurements in meters, carrier phase measurements in meters, 34 | Doppler measurements Hertz, and SNR. The measurements for each frequency band 35 | are specified using the two character codes (`C1`, `S5`, etc) specified in 36 | section 10.1.1 and Table A.1 of the 37 | [RINEX 2.11 specification](https://igscb.jpl.nasa.gov/igscb/data/format/rinex211.txt). 38 | This script requires Python 2.7. 39 | 40 | `python RinexObsToCSV.py [Rinex_File [CSV_File]] [-al] [-f Fields]` 41 | - `Rinex_File`: RINEX observation file (`.??o` extension) 42 | - `CSV_File`: Output file name, the output name is generated from `Rinex_File` 43 | by default if this argument is absent. 44 | - `-a`: Append the program output to the output file, overwrites by default 45 | - `-l`: Parse only legacy L1 and L2 C/A and P measurements, overrides `-f` 46 | - `-f Fields`: User specified fields, parses all available if this or `-l` is 47 | absent 48 | 49 | If no arguments are given the program is set to parse the sample file by 50 | default. 51 | The following will parse the sample observation file for the code measurements 52 | of the C/A and P signals. 53 | ``` 54 | python RinexObsToCSV.py cat20010.16o pseudoranges.csv -f time prn C1 P1 55 | ``` 56 | -------------------------------------------------------------------------------- /RinexObsToCSV.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | # Python Script for converting a RINEX file to a generic csv according to given 4 | # format specifiers 5 | 6 | import sys 7 | import datetime 8 | 9 | # Set up the dictionary structure for possible measurements. 10 | # C => C/A code measurement 11 | # P => P code measurement 12 | # L => Carrier phase 13 | # D => Doppler 14 | # S => Carrier noise density (dB-Hz) 15 | # 16 | # Numbers indicate the signal band (1 => L1, 2 => L2, 3 => L5) 17 | # P2 is the P code measurment on the L2 band in compliance with the RINEX 18 | # 2.11 speccification. 19 | 20 | gpsRecord = dict( 21 | time = '0', 22 | prn = '0', 23 | C1 = '0', 24 | P1 = '0', 25 | L1 = '0', 26 | D1 = '0', 27 | S1 = '0', 28 | C2 = '0', 29 | P2 = '0', 30 | L2 = '0', 31 | D2 = '0', 32 | S2 = '0', 33 | C5 = '0', 34 | L5 = '0', 35 | D5 = '0', 36 | S5 = '0' 37 | ) 38 | 39 | rinFileName = "cat20010.16n" 40 | csvFileName = "cat20010.csv" 41 | writeMode = "w" 42 | 43 | csvFields = ["time", "prn", "C1", "P1", "L1", "D1", "S1", "C2", "P2", "L2", 44 | "D2", "S2", "C5", "L5", "D5", "S5"] 45 | rinFields = [] 46 | 47 | numFields = 0 48 | 49 | # Parse the commnad line arguments 50 | argc = len(sys.argv) 51 | if argc < 2: 52 | print "No arguments given, using default values" 53 | 54 | else: 55 | if sys.argv[1] == "-h" or sys.argv[1] == "--help": # help 56 | print "RinexParser.py: Python utility for converting RINEX to CSV" 57 | print "Usage:" 58 | print "RinexParser.py [RINEX_File [CSV_File]] [-al] [-f Fields]" 59 | print " RINEX_File : Name of Rinex file" 60 | print " CSV_File : Name of csv file, built from RINEX name if absent" 61 | print " -a : Append to csv file" 62 | print " -l : Legacy data only, supercedes -f" 63 | print " -f Fields : List of rinex fields as specified" 64 | print " C => C/A code measurement" 65 | print " P => P code measurement" 66 | print " L => Carrier phase" 67 | print " D => Doppler" 68 | print " S => Carrier noise density (dB-Hz)" 69 | print "" 70 | print " Numbers indicate the signal band (1 => L1, 2 => L2, 3 => L5)" 71 | print " P2 is the P code measurment on the L2 band in compliance with the RINEX" 72 | print " 2.11 speccification." 73 | exit(0) 74 | 75 | rinFileName = sys.argv[1] 76 | csvFileName = rinFileName[:-3] + "csv" 77 | 78 | i = 2 79 | while i < argc: 80 | if i == 2 and sys.argv[i][0] != "-": 81 | csvFileName = sys.argv[2] 82 | i += 1 83 | continue 84 | 85 | if sys.argv[i] == "-f": # Use the user specified format 86 | i += 1 87 | csvFields = [] 88 | while i < argc and sys.argv[i][0] != "-": 89 | csvFields.append(sys.argv[i]) 90 | i += 1 91 | 92 | elif sys.argv[i] == "-l": # ``Legacy'' fields 93 | csvFields = ["time", "prn", "C1", "P1", "L1", "D1", "S1", "S2", 94 | "C2", "L2", "D2"] 95 | 96 | elif sys.argv[i] == "-a": # append mode 97 | writeMode = "a" 98 | 99 | else: 100 | print "Unknown argument: {}".format(sys.argv[i]) 101 | exit(0) 102 | i += 1 103 | 104 | # Open the rinex file and extract header data 105 | rinFile = open(rinFileName, "r") 106 | csvFile = open(csvFileName, writeMode) 107 | 108 | currentLine = rinFile.readline() 109 | while currentLine[60:].rstrip() != "END OF HEADER": 110 | if currentLine[60:].rstrip() == "# / TYPES OF OBSERV": # Extract header data 111 | numFields = int(currentLine[1:6]) 112 | for j in range(numFields/9): 113 | for i in range(9): 114 | rinFields.append(currentLine[(7+i*6):(12+i*6)].lstrip()) 115 | currentLine = rinFile.readline() 116 | for i in range(numFields%9): 117 | rinFields.append(currentLine[(7+i*6):(12+i*6)].lstrip()) 118 | currentLine = rinFile.readline() 119 | 120 | # Scroll through looking for valid epochs 121 | currentLine = rinFile.readline() 122 | while currentLine != "": 123 | if currentLine[27:30] == " 0 ": 124 | year = int(currentLine[1:3]) 125 | if year < 80: 126 | year = 2000 + year 127 | else: 128 | 1900 + year 129 | month = int(currentLine[4:6]) 130 | day = int(currentLine[7:9]) 131 | hour = int(currentLine[10:12]) 132 | mm = int(currentLine[13:15]) 133 | sec = float(currentLine[16:26]) 134 | 135 | gpsRecord["time"] = str(datetime.date(year, month, day).isoweekday() \ 136 | % 7 * 24 * 3600 + hour * 3600 + mm * 60 + sec) 137 | 138 | # Parse the PRN values for this epoch 139 | numSats = int(currentLine[30:32]) 140 | epochPRN = [] 141 | for j in range(numSats / 12): 142 | for i in range(12): 143 | epochPRN.append(currentLine[33+i*3:35+i*3]) 144 | for i in range(numSats % 12): 145 | epochPRN.append(currentLine[33+i*3:35+i*3]) 146 | 147 | for prn in epochPRN: 148 | gpsRecord["prn"] = prn 149 | for j in range(numFields / 5): 150 | currentLine = rinFile.readline() 151 | for i in range(5): 152 | record = currentLine[i*16+1:i*16+14].lstrip() 153 | if record == "": 154 | gpsRecord[rinFields[j*5+i]] = "0" 155 | else: 156 | gpsRecord[rinFields[j*5+i]] = record 157 | currentLine = rinFile.readline() 158 | for i in range(numFields % 5): 159 | record = currentLine[i*16+1:i*16+14].lstrip() 160 | if record == "": 161 | gpsRecord[rinFields[numFields/5*5+i]] = "0" 162 | else: 163 | gpsRecord[rinFields[numFields/5*5+i]] = record 164 | 165 | csvLine = "" 166 | for field in csvFields: 167 | csvLine += gpsRecord[field] + "," 168 | csvFile.write(csvLine[:-1]+"\r\n") 169 | 170 | currentLine = rinFile.readline() 171 | 172 | # Close files and clean up 173 | rinFile.close() 174 | csvFile.close() 175 | --------------------------------------------------------------------------------