├── Calculations ├── A.png ├── W.png ├── b.png ├── l.png ├── Cd.png ├── DSl.png ├── sl.png ├── Xhat.png └── Elevations:Variance.png ├── Matrices ├── ATWA Matrix.png ├── (ATWA)^-1 Matrix.png ├── Design (A) Matrix.png ├── Weight (Wd) Matrix.png ├── Double Differencing (D) Matrix.png ├── Single Differencing (S) Matrix.png ├── Covariance matrix of observations (cl) Matrix.png └── covariance matrix of the double differences (Cd) Matrix.png ├── Diagrams ├── DD_Diagram_aid.png ├── Method_Flowchart.png ├── Satellite_Angles.png └── Satellite_Elevation.png ├── Vectors ├── Degrees Vector.png ├── Radians Vector.png ├── Variances Vector.png ├── Observed - Computed Vector.png ├── Double differences (Dsl) Vector.png ├── Vector of observations (l) Vector.png └── Vector of single differences (sl) Vector.png ├── Graphs └── Z Scores of Observed - Computed.png ├── Todo.py ├── Quality_Assessment.py ├── Original_Data └── GNSS_Data_(text).txt ├── Computations.py ├── Epoch2.py ├── Epoch3.py ├── main.py └── README.md /Calculations/A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/A.png -------------------------------------------------------------------------------- /Calculations/W.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/W.png -------------------------------------------------------------------------------- /Calculations/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/b.png -------------------------------------------------------------------------------- /Calculations/l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/l.png -------------------------------------------------------------------------------- /Calculations/Cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/Cd.png -------------------------------------------------------------------------------- /Calculations/DSl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/DSl.png -------------------------------------------------------------------------------- /Calculations/sl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/sl.png -------------------------------------------------------------------------------- /Calculations/Xhat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/Xhat.png -------------------------------------------------------------------------------- /Matrices/ATWA Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/ATWA Matrix.png -------------------------------------------------------------------------------- /Diagrams/DD_Diagram_aid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Diagrams/DD_Diagram_aid.png -------------------------------------------------------------------------------- /Vectors/Degrees Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Degrees Vector.png -------------------------------------------------------------------------------- /Vectors/Radians Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Radians Vector.png -------------------------------------------------------------------------------- /Vectors/Variances Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Variances Vector.png -------------------------------------------------------------------------------- /Diagrams/Method_Flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Diagrams/Method_Flowchart.png -------------------------------------------------------------------------------- /Diagrams/Satellite_Angles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Diagrams/Satellite_Angles.png -------------------------------------------------------------------------------- /Matrices/(ATWA)^-1 Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/(ATWA)^-1 Matrix.png -------------------------------------------------------------------------------- /Matrices/Design (A) Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/Design (A) Matrix.png -------------------------------------------------------------------------------- /Diagrams/Satellite_Elevation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Diagrams/Satellite_Elevation.png -------------------------------------------------------------------------------- /Matrices/Weight (Wd) Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/Weight (Wd) Matrix.png -------------------------------------------------------------------------------- /Calculations/Elevations:Variance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Calculations/Elevations:Variance.png -------------------------------------------------------------------------------- /Vectors/Observed - Computed Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Observed - Computed Vector.png -------------------------------------------------------------------------------- /Graphs/Z Scores of Observed - Computed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Graphs/Z Scores of Observed - Computed.png -------------------------------------------------------------------------------- /Matrices/Double Differencing (D) Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/Double Differencing (D) Matrix.png -------------------------------------------------------------------------------- /Matrices/Single Differencing (S) Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/Single Differencing (S) Matrix.png -------------------------------------------------------------------------------- /Vectors/Double differences (Dsl) Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Double differences (Dsl) Vector.png -------------------------------------------------------------------------------- /Vectors/Vector of observations (l) Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Vector of observations (l) Vector.png -------------------------------------------------------------------------------- /Vectors/Vector of single differences (sl) Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Vectors/Vector of single differences (sl) Vector.png -------------------------------------------------------------------------------- /Matrices/Covariance matrix of observations (cl) Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/Covariance matrix of observations (cl) Matrix.png -------------------------------------------------------------------------------- /Todo.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # TODO: Label all units 4 | # TODO: Redraw elevations drawing 5 | # TODO Fix Cl Matrix - Needs to be wider 6 | # TODO: Give the vectors clearer labels - maybe some hand calculations -------------------------------------------------------------------------------- /Matrices/covariance matrix of the double differences (Cd) Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThomasJames/GNSS_Double_Differencing/HEAD/Matrices/covariance matrix of the double differences (Cd) Matrix.png -------------------------------------------------------------------------------- /Quality_Assessment.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from statistics import mean 3 | from scipy.stats import zscore 4 | import seaborn as sns 5 | 6 | 7 | x = [4929605.54130538, 4929605.5410819, 4929605.53911663] 8 | y = [-29123.82730074, -29123.82767789, -29123.82739046] 9 | z = [4033603.93206799, 4033603.93099312, 4033603.93271809] 10 | 11 | 12 | # Get the mean of the 3 epochs. 13 | x = mean(x) 14 | y = mean(y) 15 | z = mean(z) 16 | 17 | 18 | # collect the residuals from the three epochs 19 | b = [9.29757378e-01, 2.52782310e-04, 9.89184036e-01, 2.90583952e-01, 9.64209316e-01, -1.13994574e-01,7.92398563e-02, 20 | 0.94013581, 0.00273867, 0.97883069, 0.28919151, 0.97179478, -0.10708197, 0.08768272, 0.93938436, 0.01976735, 21 | 0.98053831, 0.29998728, 0.97263572, -0.10390881, 0.06579685] 22 | 23 | # Get the Z-Scores of the b vector 24 | b_z = zscore(b) 25 | 26 | # Plot the Z-Scores 27 | sns.distplot(b_z, bins=200, kde=False) 28 | plt.xlabel("Z Scores") 29 | plt.ylabel("Frequency") 30 | plt.title("Z Scores of Observed - Computed") 31 | plt.savefig("Graphs/Z Scores of Observed - Computed") 32 | plt.show() 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Original_Data/GNSS_Data_(text).txt: -------------------------------------------------------------------------------- 1 | Data from Leica receivers and choke-ring antennas placed ~94.4m apart. 2 | Polytechnic University of Valencia (UPV) calibration baseline. 3 | 4 | 5 | ECEF BASE (Pillar 1A) POSITION (m): (assumed as phase centre) 6 | ----------------------------------- 7 | 4929635.440 8 | -29041.877 9 | 4033567.846 10 | 11 | 12 | ECEF ROVER (Pillar 3A) POSITION (m): (a good approximation of phase centre) 13 | ------------------------------------ 14 | 4929605.542 15 | -29123.828 16 | 4033603.932 17 | 18 | BASE: PL1A3201.16o (Pillar 1A) 19 | ROVER: PL3A3201.16o (Pillar 3A) 20 | 21 | 22 | BASE OBSERVATIONS (Pillar 1A) C1C (metres), L1C (L1 cycles) 23 | ----------------------------- 24 | > 2016 11 15 22 19 5 25 | G10 23726969.123 124686036.295 26 | G12 20647534.024 108503516.027 27 | G13 23087780.798 121327099.499 28 | G15 21346539.664 112176830.803 29 | G17 23379753.757 122861442.012 30 | G18 23217805.737 122010370.583 31 | G19 22181729.713 116565751.296 32 | G24 20436699.926 107395596.426 33 | > 2016 11 15 22 19 6 34 | G10 23726938.331 124685874.438 35 | G12 20647192.450 108501721.043 36 | G13 23088426.022 121330490.145 37 | G15 21346983.784 112179164.661 38 | G17 23380013.060 122862804.668 39 | G18 23218019.358 122011493.172 40 | G19 22181749.153 116565853.440 41 | G24 20436639.939 107395281.178 42 | > 2016 11 15 22 19 7 43 | G10 23726907.612 124685712.984 44 | G12 20646850.932 108499926.342 45 | G13 23089071.250 121333880.840 46 | G15 21347427.948 112181498.752 47 | G17 23380272.423 122864167.659 48 | G18 23218233.033 122012615.974 49 | G19 22181768.684 116565956.073 50 | G24 20436579.966 107394966.011 51 | 52 | ROVER OBSERVATIONS (Pillar 3A) C1C (metres), L1C (L1 cycles) 53 | ------------------------------ 54 | > 2016 11 15 22 19 5 55 | G10 23726881.094 124685588.685 56 | G12 20647514.655 108503447.644 57 | G13 23087860.345 121327512.345 58 | G15 21346576.786 112177022.660 59 | G17 23379790.820 122861635.973 60 | G18 23217736.821 122010019.631 61 | G19 22181785.598 116566080.299 62 | G24 20436682.002 107395502.123 63 | > 2016 11 15 22 19 6 64 | G10 23726850.239 124685426.535 65 | G12 20647173.023 108501652.350 66 | G13 23088505.515 121330902.710 67 | G15 21347020.856 112179356.269 68 | G17 23380050.064 122862998.307 69 | G18 23217950.391 122011141.955 70 | G19 22181804.968 116566182.098 71 | G24 20436621.970 107395186.650 72 | > 2016 11 15 22 19 7 73 | G10 23726819.469 124685264.860 74 | G12 20646831.462 108499857.433 75 | G13 23089150.708 121334293.217 76 | G15 21347464.989 112181690.196 77 | G17 23380309.383 122864361.034 78 | G18 23218164.027 122012264.591 79 | G19 22181824.450 116566284.483 80 | G24 20436561.968 107394871.335 81 | 82 | 83 | ECEF SATELLITE POSITIONS X, Y, Z (m) (already corrected for earth rotation during signal travel time) 84 | ---------------------------- 85 | 2016 11 15 22 19 5 86 | G10 4634093.207 -19899701.050 16933747.321 87 | G12 22559170.178 -8979632.676 10377257.530 88 | G13 23277536.897 12575815.276 -2029027.200 89 | G15 25950462.808 2443858.353 5881092.070 90 | G17 5785091.956 16827408.400 20125597.869 91 | G18 13564948.214 -21357948.777 8232124.013 92 | G19 12262838.101 17165601.305 15682863.092 93 | G24 15569244.807 -1039249.482 21443791.252 94 | 95 | 2016 11 15 22 19 6 96 | G10 4635830.310 -19900984.252 16931753.611 97 | G12 22558227.890 -8978660.026 10380129.659 98 | G13 23277208.631 12575940.142 -2032215.535 99 | G15 25951110.653 2444379.287 5878128.296 100 | G17 5782765.465 16826578.194 20126966.693 101 | G18 13565919.388 -21358354.010 8229256.854 102 | G19 12260892.186 17164934.342 15685108.806 103 | G24 15569832.291 -1036572.061 21443516.954 104 | 105 | 2016 11 15 22 19 7 106 | G10 4637567.153 -19902267.390 16929759.540 107 | G12 22557285.375 -8977687.092 10383001.564 108 | G13 23276880.010 12576064.854 -2035403.827 109 | G15 25951758.170 2444900.089 5875164.399 110 | G17 5780438.765 16825748.070 20128335.104 111 | G18 13566890.289 -21358759.047 8226389.520 112 | G19 12258945.969 17164267.376 15687354.176 113 | G24 15570419.916 -1033894.709 21443242.199 114 | 115 | 116 | STANDARD DEVIATIONS 117 | ----------------------------------------------------- 118 | 119 | L1C standard deviation (s): 0.003 120 | L1C variance for satellite at elevation angle E: s*s/sin(E) 121 | 122 | ---------------------- 123 | NOTE: Satellite G24 has the highest elevation: 71 degrees. 124 | 125 | Phase ambiguities for each epoch and each phase measurement: 126 | 127 | 2016 11 15 22 19 5 128 | 129 | Before ambig resolution: 130 | 4929605.364 131 | -29123.817 132 | 4033603.867 133 | 12.564 (G24-G10) 134 | 34.873 (G24-G12) 135 | -3.838 (G24-G13) 136 | -4.170 (G24-G15) 137 | 1.538 (G24-G17) 138 | 11.324 (G24-G18) 139 | 34.352 (G24-G19) 140 | 141 | After ambig resolution (LAMBDA): 142 | 4929605.542 143 | -29123.828 144 | 4033603.932 145 | 12.000 (G24-G10) 146 | 35.000 (G24-G12) 147 | -4.000 (G24-G13) 148 | -4.000 (G24-G15) 149 | 1.000 (G24-G17) 150 | 11.000 (G24-G18) 151 | 34.000 (G24-G19) 152 | 153 | 2016 11 15 22 19 6 154 | 155 | Before ambig resolution: 156 | 4929605.363 157 | -29123.817 158 | 4033603.866 159 | 12.560 (G24-G10) 160 | 34.866 (G24-G12) 161 | -3.842 (G24-G13) 162 | -4.168 (G24-G15) 163 | 1.553 (G24-G17) 164 | 11.325 (G24-G18) 165 | 34.345 (G24-G19) 166 | 167 | After ambig resolution (LAMBDA): 168 | 4929605.541 169 | -29123.828 170 | 4033603.931 171 | 12.000 (G24-G10) 172 | 35.000 (G24-G12) 173 | -4.000 (G24-G13) 174 | -4.000 (G24-G15) 175 | 1.000 (G24-G17) 176 | 11.000 (G24-G18) 177 | 34.000 (G24-G19) 178 | 179 | 2016 11 15 22 19 7 180 | 181 | Before ambig resolution: 182 | 4929605.358 183 | -29123.821 184 | 4033603.865 185 | 12.580 (G24-G10) 186 | 34.879 (G24-G12) 187 | -3.850 (G24-G13) 188 | -4.172 (G24-G15) 189 | 1.541 (G24-G17) 190 | 11.371 (G24-G18) 191 | 34.342 (G24-G19) 192 | 193 | After ambig resolution (LAMBDA): 194 | 4929605.540 195 | -29123.828 196 | 4033603.933 197 | 12.000 (G24-G10) 198 | 35.000 (G24-G12) 199 | -4.000 (G24-G13) 200 | -4.000 (G24-G15) 201 | 1.000 (G24-G17) 202 | 11.000 (G24-G18) 203 | 34.000 (G24-G19) -------------------------------------------------------------------------------- /Computations.py: -------------------------------------------------------------------------------- 1 | from numpy import transpose, linalg 2 | import matplotlib.pyplot as plt 3 | import seaborn as sns 4 | from math import sqrt, sin, degrees, acos, radians 5 | from typing import List 6 | 7 | 8 | class DD: 9 | """" 10 | ref_station - Earth centric Cartesian Coordinates of the reference station [X, Y, Z] 11 | corresponding_sat - Earth centric cartesian Coordinates of the corresponding station [X, Y, Z] 12 | sat_ref - Satellite reference of cartesian Coordinates of the reference station [X, Y, Z] 13 | brrs - Base receiver to reference satellite 14 | rrrs - Reference receiver to reference satellite 15 | brcs - Base receiver to corresponding satellite 16 | rrcs - Reference receiver to corresponding satellite 17 | c = speed of light in vacuum (299792458.0 ms-1) - Set to default 18 | f = signal frequency (L1: 1575.42MHz, L2: 1227.6MHz) either L1 or L2 can be True 19 | λ=𝑐/𝑓 - Wavelength calculated from c and f 20 | """"" 21 | 22 | def __init__(self, ref_station: List[float] = None, 23 | rov_station: List[float] = None, 24 | corresponding_sat: List[float] = None, 25 | sat_ref: List[float] = None, 26 | L1: bool = True, 27 | L2: bool = False, 28 | observed: float = None): 29 | 30 | # Speed of light m/s 31 | c: float = 299792458.0 32 | 33 | # Signal frequency of L1 (MHz) 34 | L1_f: float = 1575.42 * 1000000 35 | 36 | # Signal frequency of L2 37 | L2_f: float = 1227.6 * 1000000 38 | 39 | # Set to True by default 40 | if L1: 41 | wl = c / L1_f 42 | 43 | if L2: 44 | wl = c / L2_f 45 | 46 | # Error check the arguments 47 | assert len(ref_station) == len(rov_station) == len(corresponding_sat) == len(sat_ref) 48 | assert L1 != L2 49 | 50 | # Compute ranges from satellite coordinates 51 | brrs: float = self.distance(ref_station, sat_ref) 52 | rrrs: float = self.distance(rov_station, sat_ref) 53 | brcs: float = self.distance(ref_station, corresponding_sat) 54 | rrcs: float = self.distance(rov_station, corresponding_sat) 55 | 56 | # Initialise the class variables 57 | self.x_1A = ref_station[0] 58 | self.y_1A = ref_station[1] 59 | self.z_1A = ref_station[2] 60 | self.x_3A = rov_station[0] 61 | self.y_3A = rov_station[1] 62 | self.z_3A = rov_station[2] 63 | self.x_s = corresponding_sat[0] 64 | self.y_s = corresponding_sat[1] 65 | self.z_s = corresponding_sat[2] 66 | self.x_s_ref = sat_ref[0] 67 | self.y_s_ref = sat_ref[1] 68 | self.z_s_ref = sat_ref[2] 69 | self.wl = wl 70 | self.brrs = brrs 71 | self.rrrs = rrrs 72 | self.brcs = brcs 73 | self.rrcs = rrcs 74 | self.observed = observed 75 | 76 | def x_diff(self) -> float: 77 | return float(1 / self.wl * \ 78 | ( 79 | (self.x_3A - self.x_s) / 80 | (sqrt(((self.x_s - self.x_3A) ** 2) + 81 | ((self.y_s - self.y_3A) ** 2) + 82 | ((self.z_s - self.z_3A) ** 2))) 83 | - 84 | (self.x_3A - self.x_s_ref) / 85 | (sqrt(((self.x_s_ref - self.x_3A) ** 2) + 86 | ((self.y_s_ref - self.y_3A) ** 2) + 87 | ((self.z_s_ref - self.z_3A) ** 2))))) 88 | 89 | def y_diff(self) -> float: 90 | return float((1 / self.wl * \ 91 | ( 92 | (self.y_3A - self.y_s) / 93 | (sqrt(((self.x_s - self.x_3A) ** 2) + 94 | ((self.y_s - self.y_3A) ** 2) + 95 | ((self.z_s - self.z_3A) ** 2))) 96 | - 97 | (self.y_3A - self.y_s_ref) / 98 | (sqrt(((self.x_s_ref - self.x_3A) ** 2) + 99 | ((self.y_s_ref - self.y_3A) ** 2) + 100 | ((self.z_s_ref - self.z_3A) ** 2)))))) 101 | 102 | def z_diff(self) -> float: 103 | return float(1 / self.wl * \ 104 | ( 105 | (self.z_3A - self.z_s) / 106 | (sqrt(((self.x_s - self.x_3A) ** 2) + 107 | ((self.y_s - self.y_3A) ** 2) + 108 | ((self.z_s - self.z_3A) ** 2))) 109 | - 110 | (self.z_3A - self.z_s_ref) / 111 | (sqrt(((self.x_s_ref - self.x_3A) ** 2) + 112 | ((self.y_s_ref - self.y_3A) ** 2) + 113 | ((self.z_s_ref - self.z_3A) ** 2))))) 114 | 115 | def calc_b_vector(self) -> float: 116 | # observed - The vector of measured quantities 117 | o = self.observed 118 | 119 | # Computed 120 | c = (1 / self.wl) * (self.brrs - self.rrrs - self.brcs + self.rrcs) 121 | return o - c 122 | 123 | def distance(self, point_1: List[float], point_2: List[float]) -> float: 124 | """"" 125 | Find the difference between two points given sets of [X, Y, Z] coordinates. 126 | """"" 127 | return sqrt((point_2[0] - point_1[0]) ** 2 + 128 | (point_2[1] - point_1[1]) ** 2 + 129 | (point_2[2] - point_1[2]) ** 2) 130 | 131 | 132 | class Variance: 133 | 134 | def __init__(self, sat_coords: List[float], 135 | receiver_coords: List[float], 136 | L1: bool == True): 137 | if L1: 138 | l1_std = 0.003 139 | 140 | assert len(sat_coords) == len(receiver_coords) 141 | 142 | self.l1_std = l1_std 143 | self.sat_coords = sat_coords 144 | self.receiver_coords = receiver_coords 145 | 146 | def elevation_calculator(self) -> float: 147 | """" 148 | This method calculates the satellite angle of elevation in the following stages: 149 | Calculates the distance of receiver to the satellite (m) using pythagoras theorem. 150 | Calculates the distance between the earth center and the satellite (m) using pythagoras theorem. 151 | Calculates the distance between the earth center and the receiver (m) using pythagoras theorem. 152 | These ranges make up a scalene triangle, where all ranges are known. 153 | The low of cosines is used to calculate the angle about the receiver in degrees. 154 | 90 is subtracted from this angle to get the local elevation angle. 155 | """"" 156 | 157 | # Extract ECEF distances (m) 158 | x_s, y_s, z_s = self.sat_coords[0], self.sat_coords[1], self.sat_coords[2] 159 | x_r, y_r, z_r = self.receiver_coords[0], self.receiver_coords[1], self.receiver_coords[2] 160 | 161 | # Distance from receiver to satellite (m) 162 | r_s = sqrt((x_s - x_r) ** 2 + (y_s - y_r) ** 2 + (z_s - z_r) ** 2) 163 | 164 | # Distance from earth center to satellite (m) 165 | ec_s = sqrt((sqrt(x_s ** 2 + y_s ** 2)) ** 2 + z_s ** 2) 166 | 167 | # Distance from earth center to receiver (m) 168 | ec_r = sqrt((sqrt(x_r ** 2 + y_r ** 2)) ** 2 + z_r ** 2) 169 | 170 | # Angle from the local horizontal to the satellite (m) 171 | angle_radians = radians((degrees(acos((ec_r ** 2 + r_s ** 2 - ec_s ** 2) / (2 * ec_r * r_s)))) - 90) 172 | 173 | return angle_radians 174 | 175 | def variance(self): 176 | """"" 177 | This method then calculates the variance of the satellite at the calculated angle. 178 | returns the variance as a float 179 | """"" 180 | # Variance (uncertainty associated with the satellite) (m) 181 | variance = (self.l1_std ** 2) / (sin(self.elevation_calculator())) 182 | 183 | return variance 184 | 185 | 186 | class MatrixOperations: 187 | """" 188 | Matrix Operations 189 | 190 | D - Double differencing matrix 191 | S - Single differencing matrix 192 | Cl - Covariance matrix of observations 193 | A - Design matrix 194 | W - Weight matrix 195 | b - B (innovation vector?) 196 | """"" 197 | 198 | def __init__(self, D=None, S=None, Cl=None, A=None, W=None, b=None): 199 | self.D = D 200 | self.S = S 201 | self.Cl = Cl 202 | self.A = A 203 | self.W = W 204 | self.b = b 205 | 206 | def Cd_calculator(self): 207 | try: 208 | return (((self.D.dot(self.S)).dot(self.Cl)).dot(transpose(self.S))).dot(transpose(self.D)) 209 | except IOError: 210 | print("Cd_calculator failed") 211 | 212 | def calculate_x_hat(self): 213 | try: 214 | return \ 215 | ((linalg.inv((transpose(self.A).dot(self.W)).dot(self.A))).dot(transpose(self.A)) 216 | .dot(self.W)).dot(self.b) 217 | except IOError: 218 | print("Calculate_x_hat failed") 219 | 220 | def ATWA(self): 221 | try: 222 | return ((transpose(self.A)).dot(self.W)).dot(self.A) 223 | except IOError: 224 | print("ATWA failed") 225 | 226 | 227 | def matrix_heatmap(matrix, name: str) -> None: 228 | sns.heatmap(matrix, 229 | annot=True, 230 | xticklabels=False, 231 | yticklabels=False, 232 | cmap="Blues", 233 | cbar=False, ) 234 | plt.title(f"{name} Matrix") 235 | plt.savefig(f"Matrices/{name} Matrix") 236 | plt.show() 237 | 238 | 239 | def vector_heatmap(matrix, name: str): 240 | sns.heatmap(matrix, 241 | annot=True, 242 | xticklabels=False, 243 | yticklabels=False, 244 | cmap="Blues", 245 | cbar=False, ) 246 | plt.title(f"{name} Matrix") 247 | plt.savefig(f"Vectors/{name} Vector") 248 | plt.show() 249 | 250 | 251 | def flipped_vector_heatmap(data, name: str): 252 | vec2 = data.reshape(data.shape[0], 1) 253 | ax = sns.heatmap((vec2), 254 | annot=True, 255 | xticklabels=False, 256 | yticklabels=False, 257 | cmap="Blues", 258 | cbar=False, 259 | fmt='g') 260 | plt.title(f"{name} Vector") 261 | plt.savefig(f"Vectors/{name} Vector") 262 | plt.show() 263 | -------------------------------------------------------------------------------- /Epoch2.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, cos, sin, degrees, acos 2 | import numpy as np 3 | from numpy import transpose, linalg 4 | from Computations import DD, Variance, matrix_heatmap, vector_heatmap, flipped_vector_heatmap, MatrixOperations 5 | import matplotlib.pyplot as plt 6 | import seaborn as sns 7 | from typing import List 8 | 9 | """ 10 | GOAL: Calculate the coordinates of the reference antenna (ARP) of the roving receiver 11 | 12 | Try to get your answer close to the figures for 3A. The nominal coordinates given mean you do not need to iterate the 13 | least squares solution, you should converge on the answer with on round of matrix inversion 14 | The data contains 1 of the three epochs of phase and pseudorange observations measured on a calibration baseline in 15 | valencia, spain. 16 | The sensors used are geodetic quality receivers using choke ring antennas. 17 | Pillar 1A is treated as the reference receiver. 18 | Pillar 3A is treated as the monument. 19 | """ 20 | 21 | # X, Y, Z ECEF coordinates for the phase center of the receiver 22 | pillar_1A_base = np.array([[4929635.440], [-29041.877], [4033567.846]]) # Reference receiver 23 | 24 | # Trying to reproduce these coordinates 25 | pillar_3A_rover = np.array([[4929605.400], [-29123.700], [4033603.800]]) # Monument 26 | distance_between_receivers = 94.4 # Measured in meters / Approximate 27 | l1_SD = 0.003 28 | 29 | """ 30 | ECEF coordinates (m) of the satellite phase centers when they transmitted the signals measured at each epoch. 31 | """ 32 | 33 | # Make a note of the L1 Wl 34 | wl = 0.19029367 35 | 36 | # ECEF SATELLITE POSITIONS X, Y, Z (m) (already corrected for earth rotation during signal travel time) 37 | # 2016 11 15 22 19 6 38 | 39 | G10 = [4635830.310, -19900984.252, 16931753.611] 40 | G12 = [22558227.890, -8978660.026, 10380129.659] 41 | G13 = [23277208.631, 12575940.142, -2032215.535] 42 | G15 = [25951110.653, 2444379.287, 5878128.296] 43 | G17 = [ 5782765.465, 16826578.194, 20126966.693] 44 | G18 = [13565919.388, -21358354.010, 8229256.854] 45 | G19 = [12260892.186, 17164934.342, 15685108.806] 46 | G24 = [15569832.291, -1036572.061, 21443516.954] 47 | 48 | """ 49 | Use double differenced phase measurements, from the first epoch of data only 2016_11_15_22_19_5 50 | TO compute the precise coordinates of the pillar 3A sensor phase center. 51 | These are pseudo range measurements 52 | """ 53 | 54 | # BASE OBSERVATIONS (Pillar 1A) C1C (metres), L1C (L1 cycles) 55 | # 2016_11_15_22_19_5 56 | 57 | G24_base_obs = [20436639.939, 107395281.178] 58 | G19_base_obs = [22181749.153, 116565853.440] 59 | G18_base_obs = [23218019.358, 122011493.172] 60 | G17_base_obs = [23380013.060, 122862804.668] 61 | G15_base_obs = [21346983.784, 112179164.661] 62 | G13_base_obs = [23088426.022, 121330490.145] 63 | G12_base_obs = [20647192.450, 108501721.043] 64 | G10_base_obs = [23726938.331, 124685874.438] 65 | 66 | # ROVER OBSERVATIONS (Pillar 3A) C1C (metres), L1C (L1 cycles) 67 | # 2016 11 15 22 19 6 (Second Epoch) 68 | # meters, cycles 69 | 70 | G24_rover_obs = [20436621.970, 107395186.650] 71 | G19_rover_obs = [22181804.968, 116566182.098] 72 | G18_rover_obs = [23217950.391, 122011141.955] 73 | G17_rover_obs = [23380050.064, 122862998.307] 74 | G15_rover_obs = [21347020.856, 112179356.269] 75 | G13_rover_obs = [23088505.515, 121330902.710] 76 | G12_rover_obs = [20647173.023, 108501652.350] 77 | G10_rover_obs = [23726850.239, 124685426.535] 78 | 79 | # At the first epoch we have 16 raw phase observations in cycles. 80 | l = np.transpose([ 81 | G24_base_obs[1], 82 | G24_rover_obs[1], 83 | G19_base_obs[1], 84 | G19_rover_obs[1], 85 | G18_base_obs[1], 86 | G18_rover_obs[1], 87 | G17_base_obs[1], 88 | G17_rover_obs[1], 89 | G15_base_obs[1], 90 | G15_rover_obs[1], 91 | G13_base_obs[1], 92 | G13_rover_obs[1], 93 | G12_base_obs[1], 94 | G12_rover_obs[1], 95 | G10_base_obs[1], 96 | G10_rover_obs[1]]) 97 | 98 | """ 99 | Phase Ambiguity terms (N) for each measurement, before and after ambiguity resolution 100 | Use integer terms in computations 101 | 2016 11 15 22 19 5 102 | G24 is a reference satellite - and has the highest 103 | """ 104 | 105 | # Phase ambiguities for each epoch and each phase measurement: 106 | before_ambiguity_resolution = np.array([[4929605.364], [-29123.817], [4033603.867]]) 107 | G24toG19_before = 34.345 108 | G24toG18_before = 1.325 109 | G24toG17_before = 1.553 110 | G24toG15_before = -4.168 111 | G24toG13_before = -3.842 112 | G24toG12_before = 34.866 113 | G24toG10_before = 12.560 114 | 115 | after_ambiguity_resolution = np.array([[4929605.542], [-29123.828], [4033603.932]]) 116 | G24toG19_after = 34.000 117 | G24toG18_after = 11.000 118 | G24toG17_after = 1.000 119 | G24toG15_after = -4.000 120 | G24toG13_after = -4.000 121 | G24toG12_after = 35.000 122 | G24toG10_after = 12.000 123 | 124 | a_a_r = [G24toG19_after, 125 | G24toG18_after, 126 | G24toG17_after, 127 | G24toG15_after, 128 | G24toG13_after, 129 | G24toG12_after, 130 | G24toG10_after] 131 | 132 | G24_base_var = Variance(sat_coords=G24, receiver_coords=pillar_1A_base, L1=True) 133 | G24_rover_var = Variance(sat_coords=G24, receiver_coords=pillar_3A_rover, L1=True) 134 | G19_base_var = Variance(sat_coords=G19, receiver_coords=pillar_1A_base, L1=True) 135 | G19_rover_var = Variance(sat_coords=G19, receiver_coords=pillar_3A_rover, L1=True) 136 | G18_base_var = Variance(sat_coords=G18, receiver_coords=pillar_1A_base, L1=True) 137 | G18_rover_var = Variance(sat_coords=G18, receiver_coords=pillar_3A_rover, L1=True) 138 | G17_base_var = Variance(sat_coords=G17, receiver_coords=pillar_1A_base, L1=True) 139 | G17_rover_var = Variance(sat_coords=G17, receiver_coords=pillar_3A_rover, L1=True) 140 | G15_base_var = Variance(sat_coords=G15, receiver_coords=pillar_1A_base, L1=True) 141 | G15_rover_var = Variance(sat_coords=G15, receiver_coords=pillar_3A_rover, L1=True) 142 | G13_base_var = Variance(sat_coords=G13, receiver_coords=pillar_1A_base, L1=True) 143 | G13_rover_var = Variance(sat_coords=G13, receiver_coords=pillar_3A_rover, L1=True) 144 | G12_base_var = Variance(sat_coords=G12, receiver_coords=pillar_1A_base, L1=True) 145 | G12_rover_var = Variance(sat_coords=G12, receiver_coords=pillar_3A_rover, L1=True) 146 | G10_base_var = Variance(sat_coords=G10, receiver_coords=pillar_1A_base, L1=True) 147 | G10_rover_var = Variance(sat_coords=G10, receiver_coords=pillar_3A_rover, L1=True) 148 | 149 | elevations_radians = np.array([ 150 | [G24_base_var.elevation_calculator()], 151 | [G24_rover_var.elevation_calculator()], 152 | [G19_base_var.elevation_calculator()], 153 | [G19_rover_var.elevation_calculator()], 154 | [G18_base_var.elevation_calculator()], 155 | [G18_rover_var.elevation_calculator()], 156 | [G17_base_var.elevation_calculator()], 157 | [G17_rover_var.elevation_calculator()], 158 | [G15_base_var.elevation_calculator()], 159 | [G15_rover_var.elevation_calculator()], 160 | [G13_base_var.elevation_calculator()], 161 | [G13_rover_var.elevation_calculator()], 162 | [G12_base_var.elevation_calculator()], 163 | [G12_rover_var.elevation_calculator()], 164 | [G10_base_var.elevation_calculator()], 165 | [G10_rover_var.elevation_calculator()]]) 166 | 167 | satelltie_names = np.array([["Base to G19"], 168 | ["Rover to G19"], 169 | ["Base to G18"], 170 | ["Rover to G18"], 171 | ["Base to G17"], 172 | ["Rover to G17"], 173 | ["Base to G15"], 174 | ["Rover to G15"], 175 | ["Base to G13"], 176 | ["Rover to G13"], 177 | ["Base to G12"], 178 | ["Rover to G12"], 179 | ["Base to G10"], 180 | ["Rover to G10"], 181 | ["Base to G24"], 182 | ["Rover to G24"]]) 183 | 184 | # Elevations in degrees 185 | elevations_degrees = np.array([degrees(x) for x in elevations_radians]) 186 | 187 | variance_vector = np.array([ 188 | [G24_base_var.variance()], 189 | [G24_rover_var.variance()], 190 | [G19_base_var.variance()], 191 | [G19_rover_var.variance()], 192 | [G18_base_var.variance()], 193 | [G18_rover_var.variance()], 194 | [G17_base_var.variance()], 195 | [G17_rover_var.variance()], 196 | [G15_base_var.variance()], 197 | [G15_rover_var.variance()], 198 | [G13_base_var.variance()], 199 | [G13_rover_var.variance()], 200 | [G12_base_var.variance()], 201 | [G12_rover_var.variance()], 202 | [G10_base_var.variance()], 203 | [G10_rover_var.variance()]]) 204 | 205 | # 16 x 8: Differencing matrix 206 | S = np.array([[1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 207 | [0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 208 | [0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 209 | [0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0], 210 | [0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0], 211 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0], 212 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0], 213 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1]]) 214 | 215 | D = np.array([[1, -1, 0, 0, 0, 0, 0, 0], 216 | [1, 0, -1, 0, 0, 0, 0, 0], 217 | [1, 0, 0, -1, 0, 0, 0, 0], 218 | [1, 0, 0, 0, -1, 0, 0, 0], 219 | [1, 0, 0, 0, 0, -1, 0, 0], 220 | [1, 0, 0, 0, 0, 0, -1, 0], 221 | [1, 0, 0, 0, 0, 0, 0, -1]]) 222 | 223 | if __name__ == "__main__": 224 | 225 | flipped_vector_heatmap(variance_vector, "Variances") 226 | flipped_vector_heatmap(elevations_radians, "Radians") 227 | flipped_vector_heatmap(elevations_degrees, "Degrees") 228 | 229 | """ 230 | l vector - The vector of raw observations 231 | """ 232 | 233 | flipped_vector_heatmap(l, "Vector of observations (l)") 234 | 235 | """ 236 | For purposes of single differencing and double differencing. 237 | The D and S are created. 238 | D DIM: 7 x 8 239 | S DIM: 8 X 16 240 | """ 241 | 242 | matrix_heatmap(S, "Single Differencing (S)") 243 | 244 | matrix_heatmap(D, "Double Differencing (D)") 245 | 246 | """ 247 | SINGLE DIFFERENCING 248 | As receiver 1A and 3A are relatively close together (9.4m) 249 | The ionspheric and tropospheric terms cancel out 250 | Therefore we can calculate receiver-receiver single difference (RRSD) 251 | sl = vector of receiver-receiver single differences 252 | DIM: 8 x 1 253 | """ 254 | 255 | sl = S.dot(l) 256 | 257 | flipped_vector_heatmap(sl, "Vector of single differences (sl)") 258 | 259 | """ 260 | DOUBLE DIFFERENCING 261 | 262 | We choose the satellite with the highest elevation as the reference satellite. 263 | This will be G24 - This satellite will have the least noise and multipath 264 | 265 | Dsl = vector of double differences 266 | DIM: 7 x 1 267 | """ 268 | 269 | Dsl = D.dot(sl) 270 | 271 | """ 272 | Subtract the corresponding ambiguity resolved phase ambiguity term. 273 | """ 274 | DD_s_p_a = [] 275 | for i in range(len(Dsl)): 276 | DD_s_p_a.append(Dsl[i] - a_a_r[i]) 277 | 278 | flipped_vector_heatmap(Dsl, "Double differences (Dsl)") 279 | 280 | """ 281 | Covariance matrix of the observation vector. 282 | This is an identity matrix scaled by the variance. 283 | It is assumed that the variance of each raw phase observation has the same l1 standard deviation is 0.003. 284 | Therefore variance is this value squared. 285 | UNITS: Cycles 286 | DIM: 16 x 16 287 | """ 288 | 289 | cl = np.eye(16, 16) * (1 / wl * variance_vector) # back to cycles 290 | matrix_heatmap(cl, "Covariance matrix of observations (cl)") 291 | 292 | """ 293 | Obtain the covariance matrix of the double differences. 294 | This is because we require Wd - The weight matrix of the double difference vector. (The inverse of cd) 295 | Apply gauss's propagation of error law: y = Qx, Cx to work out Cd 296 | Where Q is the matrix operator - No uncertainties associated 297 | Where x is stochastic quantity 298 | Where y is stochastic 299 | Where Cx is the covariance matrix of x and is known 300 | 301 | d = DSl 302 | Cl Exists and is known. 303 | 304 | Use the formula: Cd = DSCl (DS)^T 305 | 306 | DIM: 7 x 7 307 | """ 308 | 309 | Cd = MatrixOperations(D=D, S=S, Cl=cl) 310 | Cd = Cd.Cd_calculator() 311 | matrix_heatmap(Cd, "covariance matrix of the double differences (Cd)") 312 | 313 | """ 314 | Calculate the weight matrix of the double differences. 315 | 316 | Wd = Cd^-1 317 | DIM: 7 x 7 318 | """ 319 | 320 | Wd = linalg.inv(Cd) 321 | matrix_heatmap(Wd, "Weight (Wd)") 322 | 323 | """ 324 | 325 | """ 326 | G24G19 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G19, sat_ref=G24, 327 | observed=DD_s_p_a[0]) 328 | 329 | G24G18 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G18, sat_ref=G24, 330 | observed=DD_s_p_a[1]) 331 | 332 | G24G17 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G17, sat_ref=G24, 333 | observed=DD_s_p_a[2]) 334 | 335 | G24G15 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G15, sat_ref=G24, 336 | observed=DD_s_p_a[3]) 337 | 338 | G24G13 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G13, sat_ref=G24, 339 | observed=DD_s_p_a[4]) 340 | 341 | G24G12 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G12, sat_ref=G24, 342 | observed=DD_s_p_a[5]) 343 | 344 | G24G10 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G10, sat_ref=G24, 345 | observed=DD_s_p_a[6]) 346 | 347 | """ 348 | Calculate the b vector: 349 | This is the observed double differencing measurements - the computed. 350 | """ 351 | b = np.array([[G24G19.calc_b_vector()], 352 | [G24G18.calc_b_vector()], 353 | [G24G17.calc_b_vector()], 354 | [G24G15.calc_b_vector()], 355 | [G24G13.calc_b_vector()], 356 | [G24G12.calc_b_vector()], 357 | [G24G10.calc_b_vector()]]) 358 | 359 | vector_heatmap(b, "Observed - Computed") 360 | 361 | # Populate the design matrix 362 | A = np.array([[G24G19.x_diff(), G24G19.y_diff(), G24G19.z_diff()], 363 | [G24G18.x_diff(), G24G18.y_diff(), G24G18.z_diff()], 364 | [G24G17.x_diff(), G24G17.y_diff(), G24G17.z_diff()], 365 | [G24G15.x_diff(), G24G15.y_diff(), G24G15.z_diff()], 366 | [G24G13.x_diff(), G24G13.y_diff(), G24G13.z_diff()], 367 | [G24G12.x_diff(), G24G12.y_diff(), G24G12.z_diff()], 368 | [G24G10.x_diff(), G24G10.y_diff(), G24G10.z_diff()]]) 369 | 370 | matrix_heatmap(A, "Design (A)") 371 | 372 | """ 373 | Output the ATWA matrix 374 | """ 375 | atwa = MatrixOperations(A=A, W=Wd) 376 | atwa = atwa.ATWA() 377 | matrix_heatmap(atwa, "ATWA") 378 | 379 | """ 380 | Output the (ATWA)^-1 matrix 381 | """ 382 | inverse_atwa = linalg.inv(atwa) 383 | 384 | matrix_heatmap(inverse_atwa, "(ATWA)^-1") 385 | 386 | ATWb = (transpose(A).dot(Wd)).dot(b) 387 | 388 | x_hat = inverse_atwa.dot(ATWb) 389 | 390 | x, y, z = x_hat 391 | print(x) 392 | print(y) 393 | print(z) 394 | 395 | print(f"Updated Cooridnates: {pillar_3A_rover[0] + x} actual coordinates: {after_ambiguity_resolution[0]}") 396 | print(f"Updated Cooridnates: {pillar_3A_rover[1] + y} actual coordinates: {after_ambiguity_resolution[1]}") 397 | print(f"Updated Cooridnates: {pillar_3A_rover[2] + z} actual coordinates: {after_ambiguity_resolution[2]}") 398 | 399 | 400 | # Quality Assessment 401 | # Distance between nominal reciever and reference receiver 402 | def distance(point_1: List[float], point_2: List[float]) -> float: 403 | """"" 404 | Find the difference between two points given sets of [X, Y, Z] coordinates. 405 | """"" 406 | return sqrt((point_2[0] - point_1[0]) ** 2 + 407 | (point_2[1] - point_1[1]) ** 2 + 408 | (point_2[2] - point_1[2]) ** 2) 409 | 410 | 411 | print(distance(pillar_1A_base, pillar_3A_rover)) 412 | print(distance(pillar_1A_base, pillar_3A_rover + x_hat)) 413 | 414 | 415 | 416 | -------------------------------------------------------------------------------- /Epoch3.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, cos, sin, degrees, acos 2 | import numpy as np 3 | from numpy import transpose, linalg 4 | from Computations import DD, Variance, matrix_heatmap, vector_heatmap, flipped_vector_heatmap, MatrixOperations 5 | import matplotlib.pyplot as plt 6 | import seaborn as sns 7 | from typing import List 8 | 9 | """ 10 | GOAL: Calculate the coordinates of the reference antenna (ARP) of the roving receiver 11 | 12 | Try to get your answer close to the figures for 3A. The nominal coordinates given mean you do not need to iterate the 13 | least squares solution, you should converge on the answer with on round of matrix inversion 14 | The data contains 1 of the three epochs of phase and pseudorange observations measured on a calibration baseline in 15 | valencia, spain. 16 | The sensors used are geodetic quality receivers using choke ring antennas. 17 | Pillar 1A is treated as the reference receiver. 18 | Pillar 3A is treated as the monument. 19 | """ 20 | 21 | # X, Y, Z ECEF coordinates for the phase center of the receiver 22 | pillar_1A_base = np.array([[4929635.440], [-29041.877], [4033567.846]]) # Reference receiver 23 | 24 | # Trying to reproduce these coordinates 25 | pillar_3A_rover = np.array([[4929605.400], [-29123.700], [4033603.800]]) # Monument 26 | distance_between_receivers = 94.4 # Measured in meters / Approximate 27 | l1_SD = 0.003 28 | 29 | """ 30 | ECEF coordinates (m) of the satellite phase centers when they transmitted the signals measured at each epoch. 31 | """ 32 | 33 | # Make a note of the L1 Wl 34 | wl = 0.19029367 35 | 36 | # ECEF SATELLITE POSITIONS X, Y, Z (m) (already corrected for earth rotation during signal travel time) 37 | # 2016 11 15 22 19 6 38 | 39 | G10 = [4637567.153, -19902267.390, 16929759.540] 40 | G12 = [22557285.375 , -8977687.092 , 10383001.564] 41 | G13 = [23276880.010 , 12576064.854 , -2035403.827] 42 | G15 = [25951758.170 , 2444900.089 , 5875164.399] 43 | G17 = [5780438.765, 16825748.070, 20128335.104] 44 | G18 = [13566890.289 ,-21358759.047 , 8226389.520] 45 | G19 = [12258945.969 , 17164267.376 , 15687354.176] 46 | G24 = [15570419.916 , -1033894.709 , 21443242.199] 47 | 48 | """ 49 | Use double differenced phase measurements, from the first epoch of data only 2016_11_15_22_19_5 50 | TO compute the precise coordinates of the pillar 3A sensor phase center. 51 | These are pseudo range measurements 52 | """ 53 | 54 | # BASE OBSERVATIONS (Pillar 1A) C1C (metres), L1C (L1 cycles) 55 | # 2016_11_15_22_19_7 56 | 57 | G24_base_obs = [20436579.966, 107394966.011] 58 | G19_base_obs = [22181768.684, 116565956.073] 59 | G18_base_obs = [23218233.033, 122012615.974] 60 | G17_base_obs = [23380272.423, 122864167.659] 61 | G15_base_obs = [21347427.948, 112181498.752] 62 | G13_base_obs = [23089071.250, 121333880.840] 63 | G12_base_obs = [20646850.932, 108499926.342] 64 | G10_base_obs = [23726907.612, 124685712.984] 65 | 66 | # ROVER OBSERVATIONS (Pillar 3A) C1C (metres), L1C (L1 cycles) 67 | # 2016 11 15 22 19 7 (Third Epoch) 68 | # meters, cycles 69 | 70 | G24_rover_obs = [20436561.968, 107394871.335] 71 | G19_rover_obs = [22181824.450, 116566284.483] 72 | G18_rover_obs = [23218164.027, 122012264.591] 73 | G17_rover_obs = [23380309.383, 122864361.034] 74 | G15_rover_obs = [21347464.989, 112181690.196] 75 | G13_rover_obs = [23089150.708, 121334293.217] 76 | G12_rover_obs = [20646831.462, 108499857.433] 77 | G10_rover_obs = [23726819.469, 124685264.860] 78 | 79 | # At the first epoch we have 16 raw phase observations in cycles. 80 | l = np.transpose([ 81 | G24_base_obs[1], 82 | G24_rover_obs[1], 83 | G19_base_obs[1], 84 | G19_rover_obs[1], 85 | G18_base_obs[1], 86 | G18_rover_obs[1], 87 | G17_base_obs[1], 88 | G17_rover_obs[1], 89 | G15_base_obs[1], 90 | G15_rover_obs[1], 91 | G13_base_obs[1], 92 | G13_rover_obs[1], 93 | G12_base_obs[1], 94 | G12_rover_obs[1], 95 | G10_base_obs[1], 96 | G10_rover_obs[1]]) 97 | 98 | """ 99 | Phase Ambiguity terms (N) for each measurement, before and after ambiguity resolution 100 | Use integer terms in computations 101 | 2016 11 15 22 19 5 102 | G24 is a reference satellite - and has the highest 103 | """ 104 | 105 | # Phase ambiguities for each epoch and each phase measurement: 106 | before_ambiguity_resolution = np.array([[4929605.364], [-29123.817], [4033603.867]]) 107 | G24toG19_before = 34.342 108 | G24toG18_before = 1.371 109 | G24toG17_before = 1.541 110 | G24toG15_before = -4.172 111 | G24toG13_before = -3.842 112 | G24toG12_before = 34.879 113 | G24toG10_before = 12.580 114 | 115 | after_ambiguity_resolution = np.array([[4929605.542], [-29123.828], [4033603.932]]) 116 | G24toG19_after = 34.000 117 | G24toG18_after = 11.000 118 | G24toG17_after = 1.000 119 | G24toG15_after = -4.000 120 | G24toG13_after = -4.000 121 | G24toG12_after = 35.000 122 | G24toG10_after = 12.000 123 | 124 | a_a_r = [G24toG19_after, 125 | G24toG18_after, 126 | G24toG17_after, 127 | G24toG15_after, 128 | G24toG13_after, 129 | G24toG12_after, 130 | G24toG10_after] 131 | 132 | G24_base_var = Variance(sat_coords=G24, receiver_coords=pillar_1A_base, L1=True) 133 | G24_rover_var = Variance(sat_coords=G24, receiver_coords=pillar_3A_rover, L1=True) 134 | G19_base_var = Variance(sat_coords=G19, receiver_coords=pillar_1A_base, L1=True) 135 | G19_rover_var = Variance(sat_coords=G19, receiver_coords=pillar_3A_rover, L1=True) 136 | G18_base_var = Variance(sat_coords=G18, receiver_coords=pillar_1A_base, L1=True) 137 | G18_rover_var = Variance(sat_coords=G18, receiver_coords=pillar_3A_rover, L1=True) 138 | G17_base_var = Variance(sat_coords=G17, receiver_coords=pillar_1A_base, L1=True) 139 | G17_rover_var = Variance(sat_coords=G17, receiver_coords=pillar_3A_rover, L1=True) 140 | G15_base_var = Variance(sat_coords=G15, receiver_coords=pillar_1A_base, L1=True) 141 | G15_rover_var = Variance(sat_coords=G15, receiver_coords=pillar_3A_rover, L1=True) 142 | G13_base_var = Variance(sat_coords=G13, receiver_coords=pillar_1A_base, L1=True) 143 | G13_rover_var = Variance(sat_coords=G13, receiver_coords=pillar_3A_rover, L1=True) 144 | G12_base_var = Variance(sat_coords=G12, receiver_coords=pillar_1A_base, L1=True) 145 | G12_rover_var = Variance(sat_coords=G12, receiver_coords=pillar_3A_rover, L1=True) 146 | G10_base_var = Variance(sat_coords=G10, receiver_coords=pillar_1A_base, L1=True) 147 | G10_rover_var = Variance(sat_coords=G10, receiver_coords=pillar_3A_rover, L1=True) 148 | 149 | elevations_radians = np.array([ 150 | [G24_base_var.elevation_calculator()], 151 | [G24_rover_var.elevation_calculator()], 152 | [G19_base_var.elevation_calculator()], 153 | [G19_rover_var.elevation_calculator()], 154 | [G18_base_var.elevation_calculator()], 155 | [G18_rover_var.elevation_calculator()], 156 | [G17_base_var.elevation_calculator()], 157 | [G17_rover_var.elevation_calculator()], 158 | [G15_base_var.elevation_calculator()], 159 | [G15_rover_var.elevation_calculator()], 160 | [G13_base_var.elevation_calculator()], 161 | [G13_rover_var.elevation_calculator()], 162 | [G12_base_var.elevation_calculator()], 163 | [G12_rover_var.elevation_calculator()], 164 | [G10_base_var.elevation_calculator()], 165 | [G10_rover_var.elevation_calculator()]]) 166 | 167 | satelltie_names = np.array([["Base to G19"], 168 | ["Rover to G19"], 169 | ["Base to G18"], 170 | ["Rover to G18"], 171 | ["Base to G17"], 172 | ["Rover to G17"], 173 | ["Base to G15"], 174 | ["Rover to G15"], 175 | ["Base to G13"], 176 | ["Rover to G13"], 177 | ["Base to G12"], 178 | ["Rover to G12"], 179 | ["Base to G10"], 180 | ["Rover to G10"], 181 | ["Base to G24"], 182 | ["Rover to G24"]]) 183 | 184 | # Elevations in degrees 185 | elevations_degrees = np.array([degrees(x) for x in elevations_radians]) 186 | 187 | variance_vector = np.array([ 188 | [G24_base_var.variance()], 189 | [G24_rover_var.variance()], 190 | [G19_base_var.variance()], 191 | [G19_rover_var.variance()], 192 | [G18_base_var.variance()], 193 | [G18_rover_var.variance()], 194 | [G17_base_var.variance()], 195 | [G17_rover_var.variance()], 196 | [G15_base_var.variance()], 197 | [G15_rover_var.variance()], 198 | [G13_base_var.variance()], 199 | [G13_rover_var.variance()], 200 | [G12_base_var.variance()], 201 | [G12_rover_var.variance()], 202 | [G10_base_var.variance()], 203 | [G10_rover_var.variance()]]) 204 | 205 | # 16 x 8: Differencing matrix 206 | S = np.array([[1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 207 | [0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 208 | [0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 209 | [0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0], 210 | [0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0], 211 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0], 212 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0], 213 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1]]) 214 | 215 | D = np.array([[1, -1, 0, 0, 0, 0, 0, 0], 216 | [1, 0, -1, 0, 0, 0, 0, 0], 217 | [1, 0, 0, -1, 0, 0, 0, 0], 218 | [1, 0, 0, 0, -1, 0, 0, 0], 219 | [1, 0, 0, 0, 0, -1, 0, 0], 220 | [1, 0, 0, 0, 0, 0, -1, 0], 221 | [1, 0, 0, 0, 0, 0, 0, -1]]) 222 | 223 | if __name__ == "__main__": 224 | 225 | flipped_vector_heatmap(variance_vector, "Variances") 226 | flipped_vector_heatmap(elevations_radians, "Radians") 227 | flipped_vector_heatmap(elevations_degrees, "Degrees") 228 | 229 | """ 230 | l vector - The vector of raw observations 231 | """ 232 | 233 | flipped_vector_heatmap(l, "Vector of observations (l)") 234 | 235 | """ 236 | For purposes of single differencing and double differencing. 237 | The D and S are created. 238 | D DIM: 7 x 8 239 | S DIM: 8 X 16 240 | """ 241 | 242 | matrix_heatmap(S, "Single Differencing (S)") 243 | 244 | matrix_heatmap(D, "Double Differencing (D)") 245 | 246 | """ 247 | SINGLE DIFFERENCING 248 | As receiver 1A and 3A are relatively close together (9.4m) 249 | The ionspheric and tropospheric terms cancel out 250 | Therefore we can calculate receiver-receiver single difference (RRSD) 251 | sl = vector of receiver-receiver single differences 252 | DIM: 8 x 1 253 | """ 254 | 255 | sl = S.dot(l) 256 | 257 | flipped_vector_heatmap(sl, "Vector of single differences (sl)") 258 | 259 | """ 260 | DOUBLE DIFFERENCING 261 | 262 | We choose the satellite with the highest elevation as the reference satellite. 263 | This will be G24 - This satellite will have the least noise and multipath 264 | 265 | Dsl = vector of double differences 266 | DIM: 7 x 1 267 | """ 268 | 269 | Dsl = D.dot(sl) 270 | 271 | """ 272 | Subtract the corresponding ambiguity resolved phase ambiguity term. 273 | """ 274 | DD_s_p_a = [] 275 | for i in range(len(Dsl)): 276 | DD_s_p_a.append(Dsl[i] - a_a_r[i]) 277 | 278 | flipped_vector_heatmap(Dsl, "Double differences (Dsl)") 279 | 280 | """ 281 | Covariance matrix of the observation vector. 282 | This is an identity matrix scaled by the variance. 283 | It is assumed that the variance of each raw phase observation has the same l1 standard deviation is 0.003. 284 | Therefore variance is this value squared. 285 | UNITS: Cycles 286 | DIM: 16 x 16 287 | """ 288 | 289 | cl = np.eye(16, 16) * (1 / wl * variance_vector) # back to cycles 290 | matrix_heatmap(cl, "Covariance matrix of observations (cl)") 291 | 292 | """ 293 | Obtain the covariance matrix of the double differences. 294 | This is because we require Wd - The weight matrix of the double difference vector. (The inverse of cd) 295 | Apply gauss's propagation of error law: y = Qx, Cx to work out Cd 296 | Where Q is the matrix operator - No uncertainties associated 297 | Where x is stochastic quantity 298 | Where y is stochastic 299 | Where Cx is the covariance matrix of x and is known 300 | 301 | d = DSl 302 | Cl Exists and is known. 303 | 304 | Use the formula: Cd = DSCl (DS)^T 305 | 306 | DIM: 7 x 7 307 | """ 308 | 309 | Cd = MatrixOperations(D=D, S=S, Cl=cl) 310 | Cd = Cd.Cd_calculator() 311 | matrix_heatmap(Cd, "covariance matrix of the double differences (Cd)") 312 | 313 | """ 314 | Calculate the weight matrix of the double differences. 315 | 316 | Wd = Cd^-1 317 | DIM: 7 x 7 318 | """ 319 | 320 | Wd = linalg.inv(Cd) 321 | matrix_heatmap(Wd, "Weight (Wd)") 322 | 323 | """ 324 | 325 | """ 326 | G24G19 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G19, sat_ref=G24, 327 | observed=DD_s_p_a[0]) 328 | 329 | G24G18 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G18, sat_ref=G24, 330 | observed=DD_s_p_a[1]) 331 | 332 | G24G17 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G17, sat_ref=G24, 333 | observed=DD_s_p_a[2]) 334 | 335 | G24G15 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G15, sat_ref=G24, 336 | observed=DD_s_p_a[3]) 337 | 338 | G24G13 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G13, sat_ref=G24, 339 | observed=DD_s_p_a[4]) 340 | 341 | G24G12 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G12, sat_ref=G24, 342 | observed=DD_s_p_a[5]) 343 | 344 | G24G10 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G10, sat_ref=G24, 345 | observed=DD_s_p_a[6]) 346 | 347 | """ 348 | Calculate the b vector: 349 | This is the observed double differencing measurements - the computed. 350 | """ 351 | b = np.array([[G24G19.calc_b_vector()], 352 | [G24G18.calc_b_vector()], 353 | [G24G17.calc_b_vector()], 354 | [G24G15.calc_b_vector()], 355 | [G24G13.calc_b_vector()], 356 | [G24G12.calc_b_vector()], 357 | [G24G10.calc_b_vector()]]) 358 | 359 | vector_heatmap(b, "Observed - Computed") 360 | 361 | # Populate the design matrix 362 | A = np.array([[G24G19.x_diff(), G24G19.y_diff(), G24G19.z_diff()], 363 | [G24G18.x_diff(), G24G18.y_diff(), G24G18.z_diff()], 364 | [G24G17.x_diff(), G24G17.y_diff(), G24G17.z_diff()], 365 | [G24G15.x_diff(), G24G15.y_diff(), G24G15.z_diff()], 366 | [G24G13.x_diff(), G24G13.y_diff(), G24G13.z_diff()], 367 | [G24G12.x_diff(), G24G12.y_diff(), G24G12.z_diff()], 368 | [G24G10.x_diff(), G24G10.y_diff(), G24G10.z_diff()]]) 369 | 370 | matrix_heatmap(A, "Design (A)") 371 | 372 | """ 373 | Output the ATWA matrix 374 | """ 375 | atwa = MatrixOperations(A=A, W=Wd) 376 | atwa = atwa.ATWA() 377 | matrix_heatmap(atwa, "ATWA") 378 | 379 | """ 380 | Output the (ATWA)^-1 matrix 381 | """ 382 | inverse_atwa = linalg.inv(atwa) 383 | 384 | matrix_heatmap(inverse_atwa, "(ATWA)^-1") 385 | 386 | ATWb = (transpose(A).dot(Wd)).dot(b) 387 | 388 | x_hat = inverse_atwa.dot(ATWb) 389 | 390 | x, y, z = x_hat 391 | print(x) 392 | print(y) 393 | print(z) 394 | 395 | print(f"Updated Cooridnates: {pillar_3A_rover[0] + x} actual coordinates: {after_ambiguity_resolution[0]}") 396 | print(f"Updated Cooridnates: {pillar_3A_rover[1] + y} actual coordinates: {after_ambiguity_resolution[1]}") 397 | print(f"Updated Cooridnates: {pillar_3A_rover[2] + z} actual coordinates: {after_ambiguity_resolution[2]}") 398 | 399 | 400 | # Quality Assessment 401 | # Distance between nominal reciever and reference receiver 402 | def distance(point_1: List[float], point_2: List[float]) -> float: 403 | """"" 404 | Find the difference between two points given sets of [X, Y, Z] coordinates. 405 | """"" 406 | return sqrt((point_2[0] - point_1[0]) ** 2 + 407 | (point_2[1] - point_1[1]) ** 2 + 408 | (point_2[2] - point_1[2]) ** 2) 409 | 410 | 411 | print(distance(pillar_1A_base, pillar_3A_rover)) 412 | print(distance(pillar_1A_base, pillar_3A_rover + x_hat)) 413 | 414 | 415 | 416 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, cos, sin, degrees, acos 2 | import numpy as np 3 | from numpy import transpose, linalg 4 | from Computations import DD, Variance, matrix_heatmap, vector_heatmap, flipped_vector_heatmap, MatrixOperations 5 | import matplotlib.pyplot as plt 6 | import seaborn as sns 7 | from typing import List 8 | 9 | """ 10 | GOAL: Calculate the coordinates of the reference antenna (ARP) of the roving receiver 11 | 12 | Try to get your answer close to the figures for 3A. The nominal coordinates given mean you do not need to iterate the 13 | least squares solution, you should converge on the answer with on round of matrix inversion 14 | The data contains 1 of the three epochs of phase and pseudorange observations measured on a calibration baseline in 15 | valencia, spain. 16 | The sensors used are geodetic quality receivers using choke ring antennas. 17 | Pillar 1A is treated as the reference receiver. 18 | Pillar 3A is treated as the monument. 19 | """ 20 | 21 | # X, Y, Z ECEF coordinates for the phase center of the receiver 22 | pillar_1A_base = np.array([[4929635.440], [-29041.877], [4033567.846]]) # Reference receiver 23 | 24 | # Trying to reproduce these coordinates 25 | pillar_3A_rover = np.array([[4929605.400], [-29123.700], [4033603.800]]) # Monument 26 | distance_between_receivers = 94.4 # Measured in meters / Approximate 27 | l1_SD = 0.003 28 | 29 | """ 30 | ECEF coordinates (m) of the satellite phase centers when they transmitted the signals measured at each epoch. 31 | """ 32 | 33 | # Make a note of the L1 Wl 34 | wl = 0.19029367 35 | 36 | # ECEF SATELLITE POSITIONS X, Y, Z (m) (already corrected for earth rotation during signal travel time) 37 | # 2016 11 15 22 19 5 38 | 39 | G10 = [4634093.207, -19899701.050, 16933747.321] 40 | G12 = [22559170.178, -8979632.676, 10377257.530] 41 | G13 = [23277536.897, 12575815.276, -2029027.200] 42 | G15 = [25950462.808, 2443858.353, 5881092.070] 43 | G17 = [5785091.956, 16827408.400, 20125597.869] 44 | G18 = [13564948.214, -21357948.777, 8232124.013] 45 | G19 = [12262838.101, 17165601.305, 15682863.092] 46 | G24 = [15569244.807, -1039249.482, 21443791.252] 47 | 48 | """ 49 | Use double differenced phase measurements, from the first epoch of data only 2016_11_15_22_19_5 50 | TO compute the precise coordinates of the pillar 3A sensor phase center. 51 | These are pseudo range measurements 52 | """ 53 | 54 | # BASE OBSERVATIONS (Pillar 1A) C1C (metres), L1C (L1 cycles) 55 | # 2016_11_15_22_19_5 56 | 57 | G24_base_obs = [20436699.926, 107395596.426] 58 | G19_base_obs = [22181729.713, 116565751.296] 59 | G18_base_obs = [23217805.737, 122010370.583] 60 | G17_base_obs = [23379753.757, 122861442.012] 61 | G15_base_obs = [21346539.664, 112176830.803] 62 | G13_base_obs = [23087780.798, 121327099.499] 63 | G12_base_obs = [20647534.024, 108503516.027] 64 | G10_base_obs = [23726969.123, 124686036.295] 65 | 66 | # ROVER OBSERVATIONS (Pillar 3A) C1C (metres), L1C (L1 cycles) 67 | # 2016 11 15 22 19 5 (First Epoch) 68 | # meters, cycles 69 | 70 | G24_rover_obs = [20436682.002, 107395502.123] 71 | G19_rover_obs = [22181785.598, 116566080.299] 72 | G18_rover_obs = [23217736.821, 122010019.631] 73 | G17_rover_obs = [23379790.820, 122861635.973] 74 | G15_rover_obs = [21346576.786, 112177022.660] 75 | G13_rover_obs = [23087860.345, 121327512.345] 76 | G12_rover_obs = [20647514.655, 108503447.644] 77 | G10_rover_obs = [23726881.094, 124685588.685] 78 | 79 | # At the first epoch we have 16 raw phase observations in cycles. 80 | l = np.transpose([ 81 | G24_base_obs[1], 82 | G24_rover_obs[1], 83 | G19_base_obs[1], 84 | G19_rover_obs[1], 85 | G18_base_obs[1], 86 | G18_rover_obs[1], 87 | G17_base_obs[1], 88 | G17_rover_obs[1], 89 | G15_base_obs[1], 90 | G15_rover_obs[1], 91 | G13_base_obs[1], 92 | G13_rover_obs[1], 93 | G12_base_obs[1], 94 | G12_rover_obs[1], 95 | G10_base_obs[1], 96 | G10_rover_obs[1]]) 97 | 98 | """ 99 | Phase Ambiguity terms (N) for each measurement, before and after ambiguity resolution 100 | Use integer terms in computations 101 | 2016 11 15 22 19 5 102 | G24 is a reference satellite - and has the highest 103 | """ 104 | 105 | # Phase ambiguities for each epoch and each phase measurement: 106 | before_ambiguity_resolution = np.array([[4929605.364], [-29123.817], [4033603.867]]) 107 | G24toG19_before = 34.352 108 | G24toG18_before = 11.324 109 | G24toG17_before = 1.538 110 | G24toG15_before = -4.170 111 | G24toG13_before = -3.838 112 | G24toG12_before = 34.873 113 | G24toG10_before = 12.564 114 | 115 | after_ambiguity_resolution = np.array([[4929605.542], [-29123.828], [4033603.932]]) 116 | G24toG19_after = 34.000 117 | G24toG18_after = 11.000 118 | G24toG17_after = 1.000 119 | G24toG15_after = -4.000 120 | G24toG13_after = -4.000 121 | G24toG12_after = 35.000 122 | G24toG10_after = 12.000 123 | 124 | a_a_r = [G24toG19_after, 125 | G24toG18_after, 126 | G24toG17_after, 127 | G24toG15_after, 128 | G24toG13_after, 129 | G24toG12_after, 130 | G24toG10_after] 131 | 132 | G24_base_var = Variance(sat_coords=G24, receiver_coords=pillar_1A_base, L1=True) 133 | G24_rover_var = Variance(sat_coords=G24, receiver_coords=pillar_3A_rover, L1=True) 134 | G19_base_var = Variance(sat_coords=G19, receiver_coords=pillar_1A_base, L1=True) 135 | G19_rover_var = Variance(sat_coords=G19, receiver_coords=pillar_3A_rover, L1=True) 136 | G18_base_var = Variance(sat_coords=G18, receiver_coords=pillar_1A_base, L1=True) 137 | G18_rover_var = Variance(sat_coords=G18, receiver_coords=pillar_3A_rover, L1=True) 138 | G17_base_var = Variance(sat_coords=G17, receiver_coords=pillar_1A_base, L1=True) 139 | G17_rover_var = Variance(sat_coords=G17, receiver_coords=pillar_3A_rover, L1=True) 140 | G15_base_var = Variance(sat_coords=G15, receiver_coords=pillar_1A_base, L1=True) 141 | G15_rover_var = Variance(sat_coords=G15, receiver_coords=pillar_3A_rover, L1=True) 142 | G13_base_var = Variance(sat_coords=G13, receiver_coords=pillar_1A_base, L1=True) 143 | G13_rover_var = Variance(sat_coords=G13, receiver_coords=pillar_3A_rover, L1=True) 144 | G12_base_var = Variance(sat_coords=G12, receiver_coords=pillar_1A_base, L1=True) 145 | G12_rover_var = Variance(sat_coords=G12, receiver_coords=pillar_3A_rover, L1=True) 146 | G10_base_var = Variance(sat_coords=G10, receiver_coords=pillar_1A_base, L1=True) 147 | G10_rover_var = Variance(sat_coords=G10, receiver_coords=pillar_3A_rover, L1=True) 148 | 149 | elevations_radians = np.array([ 150 | [G24_base_var.elevation_calculator()], 151 | [G24_rover_var.elevation_calculator()], 152 | [G19_base_var.elevation_calculator()], 153 | [G19_rover_var.elevation_calculator()], 154 | [G18_base_var.elevation_calculator()], 155 | [G18_rover_var.elevation_calculator()], 156 | [G17_base_var.elevation_calculator()], 157 | [G17_rover_var.elevation_calculator()], 158 | [G15_base_var.elevation_calculator()], 159 | [G15_rover_var.elevation_calculator()], 160 | [G13_base_var.elevation_calculator()], 161 | [G13_rover_var.elevation_calculator()], 162 | [G12_base_var.elevation_calculator()], 163 | [G12_rover_var.elevation_calculator()], 164 | [G10_base_var.elevation_calculator()], 165 | [G10_rover_var.elevation_calculator()]]) 166 | 167 | satelltie_names = np.array([["Base to G19"], 168 | ["Rover to G19"], 169 | ["Base to G18"], 170 | ["Rover to G18"], 171 | ["Base to G17"], 172 | ["Rover to G17"], 173 | ["Base to G15"], 174 | ["Rover to G15"], 175 | ["Base to G13"], 176 | ["Rover to G13"], 177 | ["Base to G12"], 178 | ["Rover to G12"], 179 | ["Base to G10"], 180 | ["Rover to G10"], 181 | ["Base to G24"], 182 | ["Rover to G24"]]) 183 | 184 | # Elevations in degrees 185 | elevations_degrees = np.array([degrees(x) for x in elevations_radians]) 186 | 187 | variance_vector = np.array([ 188 | [G24_base_var.variance()], 189 | [G24_rover_var.variance()], 190 | [G19_base_var.variance()], 191 | [G19_rover_var.variance()], 192 | [G18_base_var.variance()], 193 | [G18_rover_var.variance()], 194 | [G17_base_var.variance()], 195 | [G17_rover_var.variance()], 196 | [G15_base_var.variance()], 197 | [G15_rover_var.variance()], 198 | [G13_base_var.variance()], 199 | [G13_rover_var.variance()], 200 | [G12_base_var.variance()], 201 | [G12_rover_var.variance()], 202 | [G10_base_var.variance()], 203 | [G10_rover_var.variance()]]) 204 | 205 | # 16 x 8: Differencing matrix 206 | S = np.array([[1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 207 | [0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 208 | [0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 209 | [0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0], 210 | [0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0], 211 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0], 212 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0], 213 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1]]) 214 | 215 | D = np.array([[1, -1, 0, 0, 0, 0, 0, 0], 216 | [1, 0, -1, 0, 0, 0, 0, 0], 217 | [1, 0, 0, -1, 0, 0, 0, 0], 218 | [1, 0, 0, 0, -1, 0, 0, 0], 219 | [1, 0, 0, 0, 0, -1, 0, 0], 220 | [1, 0, 0, 0, 0, 0, -1, 0], 221 | [1, 0, 0, 0, 0, 0, 0, -1]]) 222 | 223 | 224 | if __name__ == "__main__": 225 | 226 | flipped_vector_heatmap(variance_vector, "Variances") 227 | flipped_vector_heatmap(elevations_radians, "Radians") 228 | flipped_vector_heatmap(elevations_degrees, "Degrees") 229 | 230 | """ 231 | l vector - The vector of raw observations 232 | """ 233 | 234 | flipped_vector_heatmap(l, "Vector of observations (l)") 235 | 236 | """ 237 | For purposes of single differencing and double differencing. 238 | The D and S are created. 239 | D DIM: 7 x 8 240 | S DIM: 8 X 16 241 | """ 242 | 243 | matrix_heatmap(S, "Single Differencing (S)") 244 | 245 | matrix_heatmap(D, "Double Differencing (D)") 246 | 247 | """ 248 | SINGLE DIFFERENCING 249 | As receiver 1A and 3A are relatively close together (9.4m) 250 | The ionspheric and tropospheric terms cancel out 251 | Therefore we can calculate receiver-receiver single difference (RRSD) 252 | sl = vector of receiver-receiver single differences 253 | DIM: 8 x 1 254 | """ 255 | 256 | sl = S.dot(l) 257 | 258 | flipped_vector_heatmap(sl, "Vector of single differences (sl)") 259 | 260 | """ 261 | DOUBLE DIFFERENCING 262 | 263 | We choose the satellite with the highest elevation as the reference satellite. 264 | This will be G24 - This satellite will have the least noise and multipath 265 | 266 | Dsl = vector of double differences 267 | DIM: 7 x 1 268 | """ 269 | 270 | Dsl = D.dot(sl) 271 | 272 | """ 273 | Subtract the corresponding ambiguity resolved phase ambiguity term. 274 | """ 275 | DD_s_p_a = [] 276 | for i in range(len(Dsl)): 277 | DD_s_p_a.append(Dsl[i] - a_a_r[i]) 278 | 279 | flipped_vector_heatmap(Dsl, "Double differences (Dsl)") 280 | 281 | """ 282 | Covariance matrix of the observation vector. 283 | This is an identity matrix scaled by the variance. 284 | It is assumed that the variance of each raw phase observation has the same l1 standard deviation is 0.003. 285 | Therefore variance is this value squared. 286 | UNITS: Cycles 287 | DIM: 16 x 16 288 | """ 289 | 290 | cl = np.eye(16, 16) * (1 / wl * variance_vector) # (1/wl to convert from cycles to meters) 291 | matrix_heatmap(cl, "Covariance matrix of observations (cl)") 292 | 293 | """ 294 | Obtain the covariance matrix of the double differences. 295 | This is because we require Wd - The weight matrix of the double difference vector. (The inverse of cd) 296 | Apply gauss's propagation of error law: y = Qx, Cx to work out Cd 297 | Where Q is the matrix operator - No uncertainties associated 298 | Where x is stochastic quantity 299 | Where y is stochastic 300 | Where Cx is the covariance matrix of x and is known 301 | 302 | d = DSl 303 | Cl Exists and is known. 304 | 305 | Use the formula: Cd = DSCl (DS)^T 306 | 307 | DIM: 7 x 7 308 | """ 309 | 310 | Cd = MatrixOperations(D=D, S=S, Cl=cl) 311 | Cd = Cd.Cd_calculator() 312 | matrix_heatmap(Cd, "covariance matrix of the double differences (Cd)") 313 | 314 | """ 315 | Calculate the weight matrix of the double differences. 316 | 317 | Wd = Cd^-1 318 | DIM: 7 x 7 319 | """ 320 | 321 | Wd = linalg.inv(Cd) 322 | matrix_heatmap(Wd, "Weight (Wd)") 323 | 324 | """ 325 | 326 | """ 327 | G24G19 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G19, sat_ref=G24, 328 | observed=DD_s_p_a[0]) 329 | 330 | G24G18 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G18, sat_ref=G24, 331 | observed=DD_s_p_a[1]) 332 | 333 | G24G17 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G17, sat_ref=G24, 334 | observed=DD_s_p_a[2]) 335 | 336 | G24G15 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G15, sat_ref=G24, 337 | observed=DD_s_p_a[3]) 338 | 339 | G24G13 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G13, sat_ref=G24, 340 | observed=DD_s_p_a[4]) 341 | 342 | G24G12 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G12, sat_ref=G24, 343 | observed=DD_s_p_a[5]) 344 | 345 | G24G10 = DD(ref_station=pillar_1A_base, rov_station=pillar_3A_rover, corresponding_sat=G10, sat_ref=G24, 346 | observed=DD_s_p_a[6]) 347 | 348 | """ 349 | Calculate the b vector: 350 | This is the observed double differencing measurements - the computed. 351 | """ 352 | b = np.array([[G24G19.calc_b_vector()], 353 | [G24G18.calc_b_vector()], 354 | [G24G17.calc_b_vector()], 355 | [G24G15.calc_b_vector()], 356 | [G24G13.calc_b_vector()], 357 | [G24G12.calc_b_vector()], 358 | [G24G10.calc_b_vector()]]) 359 | 360 | vector_heatmap(b, "Observed - Computed") 361 | 362 | # Populate the design matrix 363 | A = np.array([[G24G19.x_diff(), G24G19.y_diff(), G24G19.z_diff()], 364 | [G24G18.x_diff(), G24G18.y_diff(), G24G18.z_diff()], 365 | [G24G17.x_diff(), G24G17.y_diff(), G24G17.z_diff()], 366 | [G24G15.x_diff(), G24G15.y_diff(), G24G15.z_diff()], 367 | [G24G13.x_diff(), G24G13.y_diff(), G24G13.z_diff()], 368 | [G24G12.x_diff(), G24G12.y_diff(), G24G12.z_diff()], 369 | [G24G10.x_diff(), G24G10.y_diff(), G24G10.z_diff()]]) 370 | 371 | matrix_heatmap(A, "Design (A)") 372 | 373 | """ 374 | Output the ATWA matrix 375 | """ 376 | atwa = MatrixOperations(A=A, W=Wd) 377 | atwa = atwa.ATWA() 378 | matrix_heatmap(atwa, "ATWA") 379 | 380 | """ 381 | Output the (ATWA)^-1 matrix 382 | """ 383 | inverse_atwa = linalg.inv(atwa) 384 | 385 | matrix_heatmap(inverse_atwa, "(ATWA)^-1") 386 | 387 | ATWb = (transpose(A).dot(Wd)).dot(b) 388 | 389 | x_hat = inverse_atwa.dot(ATWb) 390 | 391 | x, y, z = x_hat 392 | print(x) 393 | print(y) 394 | print(z) 395 | 396 | print(f"Updated Cooridnates: {pillar_3A_rover[0] + x} actual coordinates: {after_ambiguity_resolution[0]}") 397 | print(f"Updated Cooridnates: {pillar_3A_rover[1] + y} actual coordinates: {after_ambiguity_resolution[1]}") 398 | print(f"Updated Cooridnates: {pillar_3A_rover[2] + z} actual coordinates: {after_ambiguity_resolution[2]}") 399 | 400 | 401 | # Quality Assessment 402 | # Distance between nominal reciever and reference receiver 403 | def distance(point_1: List[float], point_2: List[float]) -> float: 404 | """"" 405 | Find the difference between two points given sets of [X, Y, Z] coordinates. 406 | """"" 407 | return sqrt((point_2[0] - point_1[0]) ** 2 + 408 | (point_2[1] - point_1[1]) ** 2 + 409 | (point_2[2] - point_1[2]) ** 2) 410 | 411 | 412 | print(distance(pillar_1A_base, pillar_3A_rover)) 413 | print(distance(pillar_1A_base, pillar_3A_rover + x_hat)) 414 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📡 GNSS Double Differencing 📡 2 | 3 | Double Differencing: Main method of high precision commercial positioning. This method requires two high grade GNSS receivers. In this case a reference, and a (static or kinematic) rover receiver. 4 | 5 | ## Scenario 6 | 7 | Phase and pseudorange observations measured on a calibration baseline in Valencia, Spain. The sensors used 8 | are geodetic quality Leica receivers using choke ring antennas. The receiver on pillar 1A is treated as 9 | the reference receiver, with the following known ECEF coordinates in metres (XYZ)T 10 | for the phase 11 | 12 | centre: 13 | 4929635.440 14 | -29041.877 15 | 4033567.846 16 | 17 | Use the following nominal coordinates for the phase centre of the sensor on pillar 3A: 18 | 4929605.400 19 | -29123.700 20 | 4033603.800 21 | 22 | Use double differenced phase measurements, from the first epoch of data only (2016 11 15 22 19 23 | 5), to compute the precise coordinates of the pillar 3A sensor phase centre 24 | 25 | 26 | 27 | ## Prerequisites 28 | 29 | ``` Python 3``` 30 | 31 | The following libraries must be installed: 32 | 33 | ``` 34 | pip install numpy 35 | pip install matplotlib 36 | pip install seaborn 37 | ``` 38 | 39 | You can find the computations [here](https://github.com/ThomasJames/GNSS_Double_Differencing/blob/master/Computations.py) 40 | 41 | ### Clone this repository: 42 | 43 | ``` 44 | $ git clone https://github.com/ThomasJames/GNSS_Double_Differencing 45 | ``` 46 | 47 | ## Data 48 | 49 | The original [text file](https://github.com/ThomasJames/GNSS_Data_(text).txt) is in this repository, however the data has been organised and processed into [python](https://github.com/ThomasJames/GNSS_Double_Differencing/Data.py) variables. 50 | 51 | ## Method 52 | 53 | 54 | 55 | Satellite G24 has the highest elevation of apprixmatley 71 degrees. This satellite is used as the reference satellite. 56 | 57 | It is important to initially calculate the elevation angles of each satelite. The error of a satellite is inversely proportial to elevation with respect to a local horizon. Low elevation satellites produce less reliable results, and this needs to be taken into account when formulating a weight matrix. 58 | 59 | 60 | 61 | 62 | ### Computations of elevations and variances: 63 | 64 | This method calculates the satellite angle of elevation in the following stages: 65 | Calculates the distance of receiver to the satellite (m) using pythagoras theorem. 66 | Calculates the distance between the earth center and the satellite (m) using pythagoras theorem. 67 | Calculates the distance between the earth center and the receiver (m) using pythagoras theorem. 68 | These ranges make up a scalene triangle, where all ranges/sides are known. 69 | The low of cosines is used to calculate the angle about the receiver in degrees. 70 | 90 is subtracted from this angle to get the local elevation angle. 71 | The method then calculates the variance of the satellite at the calculated angle. 72 | returns the variance as a float 73 | 74 | 75 | 76 | The code to achieve this is here: 77 | ``` 78 | def elevation_variance_calculator(self) -> float: 79 | 80 | """" 81 | This method calculates the satellite angle of elevation in the following stages: 82 | Calculates the distance of receiver to the satellite (m) using pythagoras theorem. 83 | Calculates the distance between the earth center and the satellite (m) using pythagoras theorem. 84 | Calculates the distance between the earth center and the receiver (m) using pythagoras theorem. 85 | These ranges make up a scalene triangle, where all ranges are known. 86 | The low of cosines is used to calculate the angle about the receiver in degrees. 87 | 90 is subtracted from this angle to get the local elevation angle. 88 | """"" 89 | 90 | # Extract ECEF distances (m) 91 | x_s, y_s, z_s = self.sat_coords[0], self.sat_coords[1], self.sat_coords[2] 92 | x_r, y_r, z_r = self.receiver_coords[0], self.receiver_coords[1], self.receiver_coords[2] 93 | 94 | # Distance from receiver to satellite (m) 95 | r_s = sqrt((x_s - x_r)**2 + (y_s - y_r)**2 + (z_s - z_r)**2) 96 | 97 | # Distance from earth center to satellite (m) 98 | ec_s = sqrt((sqrt(x_s ** 2 + y_s ** 2)) ** 2 + z_s ** 2) 99 | 100 | # Distance from earth center to receiver (m) 101 | ec_r = sqrt((sqrt(x_r ** 2 + y_r ** 2)) ** 2 + z_r ** 2) 102 | 103 | # Angle from the local horizontal to the satellite (m) 104 | angle = radians((degrees(acos((ec_r ** 2 + r_s ** 2 - ec_s ** 2) / (2 * ec_r * r_s)))) - 90) 105 | 106 | return angle 107 | 108 | def variance(self): 109 | """"" 110 | This method then calculates the variance of the satellite at the calculated angle. 111 | returns the variance as a float 112 | """"" 113 | # Variance (uncertainty associated with the satellite) (m) 114 | variance = (self.l1_std ** 2) / (sin(self.elevation_variance_calculator())) 115 | 116 | return variance 117 | 118 | 119 | ``` 120 | Satellites ordered top to bottom: G24, G19, G18, G17, G15, G13, G12, G10 121 | 122 | 123 | 124 | (Elevation angles are representative) 125 | 126 | 127 | 128 | ### l (Observations) vector 129 | 130 | This is the vector of raw observations. 131 | UNITS: L1C (L1 cycles) 132 | 133 | 134 | 135 | 136 | 137 | 138 | ### S (Single differencing) Matrix 139 | 140 | This matrix is used to generate a vector of single differences. 141 | 142 | 143 | 144 | ### Sl (Vector of single differences) 145 | 146 | The dot product of the differencing matrix (S) and the vector of observations (l) generates the vector of single differences. 147 | 148 | 149 | 150 | The following code was used compute this: 151 | ``` 152 | sl = S.dot(l) 153 | ``` 154 | UNITS: L1C (L1 cycles) 155 | 156 | 157 | ### D (Doube differencing) Matrix 158 | 159 | This matrix is used to generate values for the double differences of the observations matrix. 160 | 161 | 162 | 163 | ### DSl (Vector of Double Differences of Observations) 164 | 165 | The dot product of the double differencing matrix (D) and the vector of single differences generates the vector of double differences of the observations 166 | 167 | 168 | 169 | The following code was used to compute this. 170 | 171 | UNITS: L1C (L1 cycles) 172 | 173 | 174 | 175 | Code: 176 | ``` 177 | Dsl = D.dot(sl) 178 | ``` 179 | 180 | 181 | 182 | ### b (Observed - Computed) 183 | 184 | The observed - computed (b) matrix was calculated in the following steps: 185 | 186 | 1. The phase ambiguity term (N) was subtracted from the double differenced vector of double differences (DSl): 187 | In the code, this has been stored into the variable ``` DD_s_p_a``` This value is expressed in cycles 188 | 189 | 2. The range terms are computed from the satellite coordinates. These are then used to generate a value of the computed measurement. These are multiplied by 1/wavelength to convert from meters into cycles. 190 | 191 | 3. The observed measurement is subtracted from the computed measurement. 192 | 193 | 194 | 195 | The code to achieve this is here: 196 | ``` 197 | # This function is used to compute satelite - receiver ranges. 198 | def distance(point_1: List[float], point_2: List[float]) -> float: 199 | return sqrt((point_2[0] - point_1[0])**2 + 200 | (point_2[1] - point_1[1])**2 + 201 | (point_2[2] - point_1[2])**2) 202 | 203 | """ 204 | wl - Wavelength 205 | brrs - Base receiver to reference satellite 206 | rrrs - Reference receiver to reference satellite 207 | brcs - Base receiver to corresponding satellite 208 | rrcs - Reference receiver to corresponding satellite 209 | DD_s_p_a - Vector of double differences, after phase ambiguity term subtracted. 210 | """ 211 | brrs: floa = distance(ref_station, sat_ref) 212 | rrrs = distance(rov_station, sat_ref) 213 | brcs = distance(ref_station, corresponding_sat) 214 | rrcs = distance(rov_station, corresponding_sat) 215 | 216 | def calc_b_vector(wl: float, DD_s_p_a: float, brrs: float, rrrs: float, brcs: float, rrcs: float) -> float: 217 | # observed - The vector of measured quantities 218 | o = DD_s_p_a 219 | 220 | # Computed 221 | c = (1 / wl) * (brrs - rrrs - brcs + rrcs) 222 | return o - c 223 | 224 | ``` 225 | 226 | UNITS: meters 227 | 228 | 229 | 230 | ### Cl (Observations covariance) Matrix 231 | 232 | The covarince matrix of observations is calculated in the following steps: 233 | 234 | Step 1: A 16 x 16 Identity matrix is generated. 235 | 236 | Step 2: This matrix is multiplied by the vector of variances (Computed from satellite elevations) 237 | 238 | This following code was used to make this computations: 239 | ``` 240 | cl = np.eye(16, 16) * (1 / wl * variance_vector) # Convert to meters 241 | ``` 242 | 243 | UNITS: Meters 244 | 245 | 246 | 247 | ### Cd (Covariance) Matrix 248 | 249 | 250 | 251 | ``` 252 | def Cd_calculator(D, S, Cl): 253 | return (((D.dot(S)).dot(Cl)).dot(transpose(S))).dot(transpose(D)) 254 | ``` 255 | UNITS: Meters 256 | 257 | 258 | 259 | ### Wd (Weight) Matrix 260 | 261 | 262 | 263 | The code to compute this is here: 264 | ``` 265 | Wd = linalg.inv(Cd) 266 | ``` 267 | 268 | UNITS: Meters 269 | 270 | 271 | 272 | ### A (Design) Martix 273 | 274 | The design matrix was populated with the partial derivitives of the double difference observation equations with respect to the unknowns. 275 | 276 | UNITS: Meters 277 | 278 | 279 | 280 | 281 | NOTE: Phase ambiguity terms are not included. 282 | 283 | ### The following code was used to perform these computations. 284 | 285 | ``` 286 | class DD: 287 | """" 288 | ref_station - Earth centric Cartesian Coordinates of the reference station [X, Y, Z] 289 | corresponding_sat - Earth centric cartesian Coordinates of the corresponding station [X, Y, Z] 290 | sat_ref - Satellite reference of cartesian Coordinates of the reference station [X, Y, Z] 291 | c = speed of light in vacuum (299792458.0 ms-1) - Set to default 292 | f = signal frequency (L1: 1575.42MHz, L2: 1227.6MHz) either L1 or L2 can be True 293 | λ=𝑐/𝑓 - Wavelength calculated from c and f 294 | """"" 295 | 296 | def __init__(self, ref_station: List[float] = None, 297 | rov_station: List[float] = None, 298 | corresponding_sat: List[float] = None, 299 | sat_ref: List[float] = None, 300 | L1: bool = True, 301 | L2: bool = False, 302 | observed: float = None): 303 | 304 | # Speed of light m/s 305 | c: float = 299792458.0 306 | 307 | # Signal frequency of L1 (MHz) 308 | L1_f: float = 1575.42 * 1000000 309 | 310 | # Signal frequency of L2 311 | L2_f: float = 1227.6 * 1000000 312 | 313 | # Set to True by default 314 | if L1: 315 | wl = c / L1_f 316 | 317 | if L2: 318 | wl = c / L2_f 319 | 320 | # Error check the arguments 321 | assert len(ref_station) == len(rov_station) == len(corresponding_sat) == len(sat_ref) 322 | assert L1 != L2 323 | 324 | # Initialise the class variables 325 | self.x_1A = ref_station[0] 326 | self.y_1A = ref_station[1] 327 | self.z_1A = ref_station[2] 328 | self.x_3A = rov_station[0] 329 | self.y_3A = rov_station[1] 330 | self.z_3A = rov_station[2] 331 | self.x_s = corresponding_sat[0] 332 | self.y_s = corresponding_sat[1] 333 | self.z_s = corresponding_sat[2] 334 | self.x_s_ref = sat_ref[0] 335 | self.y_s_ref = sat_ref[1] 336 | self.z_s_ref = sat_ref[2] 337 | self.wl = wl 338 | self.observed = observed 339 | 340 | def x_diff(self) -> float: 341 | return float(1 / self.wl * \ 342 | ( 343 | (self.x_3A - self.x_s) / 344 | (sqrt(((self.x_s - self.x_3A) ** 2) + 345 | ((self.y_s - self.y_3A) ** 2) + 346 | ((self.z_s - self.z_3A) ** 2))) 347 | - 348 | (self.x_3A - self.x_s_ref) / 349 | (sqrt(((self.x_s_ref - self.x_3A) ** 2) + 350 | ((self.y_s_ref - self.y_3A) ** 2) + 351 | ((self.z_s_ref - self.z_3A) ** 2))))) 352 | 353 | def y_diff(self) -> float: 354 | return float((1 / self.wl * \ 355 | ( 356 | (self.y_3A - self.y_s) / 357 | (sqrt(((self.x_s - self.x_3A) ** 2) + 358 | ((self.y_s - self.y_3A) ** 2) + 359 | ((self.z_s - self.z_3A) ** 2))) 360 | - 361 | (self.y_3A - self.y_s_ref) / 362 | (sqrt(((self.x_s_ref - self.x_3A) ** 2) + 363 | ((self.y_s_ref - self.y_3A) ** 2) + 364 | ((self.z_s_ref - self.z_3A) ** 2)))))) 365 | 366 | def z_diff(self) -> float: 367 | return float(1 / self.wl * \ 368 | ( 369 | (self.z_3A - self.z_s) / 370 | (sqrt(((self.x_s - self.x_3A) ** 2) + 371 | ((self.y_s - self.y_3A) ** 2) + 372 | ((self.z_s - self.z_3A) ** 2))) 373 | - 374 | (self.z_3A - self.z_s_ref) / 375 | (sqrt(((self.x_s_ref - self.x_3A) ** 2) + 376 | ((self.y_s_ref - self.y_3A) ** 2) + 377 | ((self.z_s_ref - self.z_3A) ** 2))))) 378 | 379 | 380 | ``` 381 | 382 | 383 | 384 | ### Calculating the updates: 385 | 386 | 387 | 388 | ### ATWA Matrix 389 | 390 | UNITS: Meters 391 | 392 | ``` 393 | def ATWA(A, W): 394 | return ((transpose(A)).dot(W)).dot(A) 395 | ``` 396 | 397 | 398 | 399 | ### (ATWA)^-1 Matrix 400 | 401 | UNITS: Meters 402 | 403 | ``` 404 | linalg.inv(atwa) 405 | ``` 406 | 407 | 408 | 409 | ## RESULT 410 | 411 | ### EPOCH 1 412 | ##### X update = 0.14130538m 413 | ##### Y update = -0.12730074m 414 | ##### Z update = 0.13206799m 415 | 416 | ##### Updated X Cooridnates: 4929605.54130538 417 | ##### Updated Y Cooridnates: -29123.82730074 418 | ##### Updated Z Cooridnates: 4033603.93206799 419 | 420 | ### EPOCH 2 421 | ##### X update = 0.1410819m 422 | ##### Y update = -0.12767789m 423 | ##### Z update = 0.13099312m 424 | 425 | ##### Updated X Cooridnates: 4929605.5410819 426 | ##### Updated Y Cooridnates: -29123.82767789 427 | ##### Updated Z Cooridnates: 4033603.93099312 428 | 429 | ### EPOCH 3 430 | ##### X update = 0.13911663m 431 | ##### Y update = -0.12739046m 432 | ##### Z update = 0.13271809m 433 | 434 | ##### Updated X Cooridnates: 4929605.53911663 435 | ##### Updated Y Cooridnates: -29123.82739046 436 | ##### Updated Z Cooridnates: 4033603.93271809 437 | 438 | ## Position Solution as an average of 3 Epochs. 439 | 440 | ##### Averaged X Cooridnates: 4929605.540501303 441 | ##### Averaged Y Cooridnates: -29123.82745636333 442 | ##### Averaged Z Cooridnates: 4033603.9319264 443 | 444 | 445 | ## Quality Assessment: 446 | 447 | The distance between the two pillars is approximatley 94.4m. 448 | 449 | Distance between Pillar 1A and Nominal coordinates for Pillar 3A: 450 | 94.287m 451 | 452 | Distance between Pillar 1A and updated coordinates for Pillar 3A 453 | 94.403m 454 | 455 | This shows a 0.116 change in range. 456 | 457 | #### Residuals: 458 | 459 | Observed - Computed residuals are all within 99% confidence intervals for all three epochs. 460 | 461 | 462 | 463 | 464 | 465 | 466 | --------------------------------------------------------------------------------