├── .gitignore ├── CHANGELOG.txt ├── DESCRIPTION.rst ├── LINZ ├── Geodetic │ ├── Ellipsoid.py │ ├── ITRF.py │ ├── Sinex.py │ └── __init__.py └── __init__.py ├── MANIFEST.in ├── README.rst ├── debian ├── changelog ├── clean ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── setup.cfg ├── setup.py └── tests ├── fileunittest.py ├── test.snx ├── testEllipsoid.py ├── testEllipsoid.results ├── testITRF.py ├── testITRF.results ├── testSinex.py └── testSinex.results /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | dist 4 | linz_geodetic.egg-info 5 | debian/python-linz* 6 | debian/debhelper.log 7 | debian/substvars 8 | debian/files 9 | tests/testSinex.results.new 10 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 1.7.0-1 (15 Mar 2016) 2 | --------------------- 3 | * Adding ITRF2014 4 | * Adding function for calculating coordinates at epoch 5 | * Adding dxyz function and tidying up solutions 6 | * Adding support for upper triangular covariance 7 | * Fixing up errors from refactoring 8 | 9 | 10 | 1.6.0-1 (29 Sep 2015) 11 | --------------------- 12 | * Adding properties, adding unit tests 13 | 14 | 15 | 1.5.1-2 (18 Sep 2015) 16 | --------------------- 17 | * Fixing precision of ITRF lat/lon coordinates 18 | * Changing to use pybuild 19 | 20 | 21 | 1.5.1-1 (27 Aug 2015) 22 | --------------------- 23 | * Making python3 ready 24 | 25 | 26 | 1.5.0-1 (27 Aug 2015) 27 | --------------------- 28 | * Moving out GDB to seperate module, making python3 compatible 29 | 30 | 31 | 1.4.0-1 (25 Aug 2015) 32 | --------------------- 33 | * Adding Sinex.Writer 34 | 35 | 36 | 1.3.3-1 (19 Aug 2015) 37 | --------------------- 38 | * Adding Sinex.py 39 | * Improving docs 40 | * Adding option for returning multiple solutions 41 | * More Sinex.py updates! 42 | * Fixing bug in ITRF transformation calculation 43 | 44 | 45 | 1.3.2-1 (03 Jul 2015) 46 | --------------------- 47 | * Fixing name of ellipsoid entry point 48 | 49 | 50 | 1.3.1-1 (02 Jul 2015) 51 | --------------------- 52 | * Fixing capitalisation in setup file 53 | 54 | 55 | 1.3.0-1 (02 Jul 2015) 56 | --------------------- 57 | * Tidying up ITRF module 58 | 59 | 60 | 1.2.0-1 (02 Jul 2015) 61 | --------------------- 62 | * More fixes of class case to be a bit more consistent 63 | 64 | 65 | 1.1.1-1 (02 Jul 2015) 66 | --------------------- 67 | * Renaming LINZ.geodetic to LINZ.Geodetic 68 | 69 | 70 | 1.1.0-1 (01 Jul 2015) 71 | --------------------- 72 | * Adding gdb module for rretrieving station information from the geodetic database 73 | 74 | 75 | 1.0.0-1 (27 Jun 2015) 76 | --------------------- 77 | * source package automatically created by stdeb 0.6.0+git 78 | -------------------------------------------------------------------------------- /DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | LINZ.geodetic package 2 | ===================== 3 | 4 | This provides the generic geodetic functions used by other LINZ modules. 5 | 6 | Currently includes: 7 | ellipsoid 8 | ITRF_transformation 9 | Sinex 10 | 11 | -------------------------------------------------------------------------------- /LINZ/Geodetic/Ellipsoid.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/python3 2 | 3 | import math 4 | 5 | import numpy as np 6 | 7 | 8 | class Ellipsoid: 9 | 10 | convergence = 1.0e-10 11 | 12 | @staticmethod 13 | def _cossin(angle): 14 | angle = np.radians(angle) 15 | return np.cos(angle), np.sin(angle) 16 | 17 | @staticmethod 18 | def enu_axes(lon, lat): 19 | """ 20 | Returns an array defining the east, north, and up unit vectors 21 | at a specified latitude and longitude 22 | 23 | To convert an xyz offset to an enu offset, use as an example 24 | 25 | enu_axes=GRS80.enu_axes(lon,lat) 26 | denu=enu_axes.dot(dxyz) 27 | dxyz=enu_axes.T.dot(denu) 28 | 29 | """ 30 | cln, sln = Ellipsoid._cossin(lon) 31 | clt, slt = Ellipsoid._cossin(lat) 32 | ve = np.array([-sln, cln, 0]) 33 | vn = np.array([-cln * slt, -sln * slt, clt]) 34 | vu = np.array([clt * cln, clt * sln, slt]) 35 | return np.vstack((ve, vn, vu)) 36 | 37 | def __init__(self, a, rf): 38 | """ 39 | Initiallize an ellipsoid based on semi major axis and inverse flattening 40 | """ 41 | self._setParams(a, rf) 42 | 43 | @property 44 | def a(self): 45 | return self._a 46 | 47 | @a.setter 48 | def a(self, a): 49 | self._setParams(a, self._rf) 50 | 51 | @property 52 | def rf(self): 53 | return self._rf 54 | 55 | @rf.setter 56 | def rf(self, rf): 57 | self._setParams(self._a, rf) 58 | 59 | @property 60 | def b(self): 61 | return self._b 62 | 63 | def _setParams(self, a, rf): 64 | self._a = float(a) 65 | self._rf = float(rf) 66 | self._b = a - a / rf if rf else a 67 | self._a2 = a * a 68 | self._b2 = self._b * self._b 69 | self._a2b2 = self._a2 - self._b2 70 | 71 | def xyz(self, lon, lat=None, hgt=None): 72 | """ 73 | Calculate the geocentric X,Y,Z coordinates at a longitude 74 | and latitude 75 | 76 | Input is one of 77 | lon, lat Single values or lists of values 78 | lon, lat, hgt 79 | llh Array of [lon,lat,hgt] 80 | """ 81 | single = True 82 | if lat is None: 83 | if not isinstance(lon, np.ndarray): 84 | lon = np.array(lon) 85 | single = len(lon.shape) == 1 86 | if single: 87 | lon = lon.reshape((1, lon.size)) 88 | lat = lon[:, 1] 89 | hgt = lon[:, 2] if lon.shape[1] > 2 else 0 90 | lon = lon[:, 0] 91 | if hgt is None: 92 | hgt = 0 93 | 94 | cln, sln = Ellipsoid._cossin(lon) 95 | clt, slt = Ellipsoid._cossin(lat) 96 | bsac = np.hypot(self._b * slt, self._a * clt) 97 | p = self._a2 * clt / bsac + hgt * clt 98 | 99 | xyz = [p * cln, p * sln, self._b2 * slt / bsac + hgt * slt] 100 | xyz = np.vstack(xyz).transpose() 101 | if single: 102 | xyz = xyz[0] 103 | return xyz 104 | 105 | def metres_per_degree(self, lon, lat, hgt=0): 106 | """ 107 | Calculate the number of metres per degree east and 108 | north 109 | """ 110 | clt, slt = Ellipsoid._cossin(lat) 111 | bsac = np.hypot(self._b * slt, self._a * clt) 112 | p = self._a2 * clt / bsac + hgt * clt 113 | dedln = np.radians(p) 114 | dndlt = np.radians((self._a2 * self._b2) / (bsac * bsac * bsac) + hgt) 115 | return dedln, dndlt 116 | 117 | def geodetic(self, xyz): 118 | """ 119 | Calculate the longitude, latitude, and height corresponding 120 | to a geocentric XYZ coordinate 121 | 122 | Input is one of 123 | xyz Single [x,y,z] 124 | xyz Array of [x,y,z] 125 | """ 126 | if not isinstance(xyz, np.ndarray): 127 | xyz = np.array(xyz) 128 | single = len(xyz.shape) == 1 129 | if single: 130 | xyz = xyz.reshape((1, xyz.size)) 131 | x, y, z = xyz[:, 0], xyz[:, 1], xyz[:, 2] 132 | ln = np.arctan2(y, x) 133 | p = np.hypot(x, y) 134 | lt = np.arctan2(self._a2 * z, self._b2 * p) 135 | for i in range(10): 136 | lt0 = lt 137 | slt = np.sin(lt) 138 | clt = np.cos(lt) 139 | bsac = np.hypot(self._b * slt, self._a * clt) 140 | lt = np.arctan2(z + slt * self._a2b2 / bsac, p) 141 | if np.all(abs(lt - lt0) < self.convergence): 142 | break 143 | h = p * clt + z * slt - bsac 144 | result = np.degrees(ln), np.degrees(lt), h 145 | result = np.vstack(result).transpose() 146 | return result[0] if single else result 147 | 148 | def radii_of_curvature(self, latitude): 149 | """ 150 | Returns the radii of curvature along the meridional and prime 151 | vertical normal sections. 152 | """ 153 | clt, slt = Ellipsoid._cossin(latitude) 154 | den = math.sqrt(self._a2 * clt * clt + self._b2 * slt * slt) 155 | rm = self._a2 * self._b2 / (den * den * den) 156 | rn = self._a2 / den 157 | return rm, rn 158 | 159 | 160 | GRS80 = Ellipsoid(6378137.0, 298.257222101) 161 | 162 | 163 | def main(): 164 | import argparse 165 | import sys 166 | 167 | parser = argparse.ArgumentParser(description="Convert Cartesian coordinates <=> Geodetic coordinates") 168 | parser.add_argument( 169 | "-e", 170 | "--ellipsoid", 171 | type=float, 172 | nargs=2, 173 | metavar="A RF", 174 | help="Ellipsoid semi-major axis and inverse flattening (default GRS80)", 175 | ) 176 | parser.add_argument( 177 | "-x", 178 | "--xyz", 179 | nargs=3, 180 | type=float, 181 | metavar=("X", "Y", "Z"), 182 | help="XYZ coordinates to transform", 183 | ) 184 | parser.add_argument( 185 | "-g", 186 | "--geodetic", 187 | nargs=3, 188 | type=float, 189 | metavar=("LON", "LAT", "EHGT"), 190 | help="Geodetic coordinates to transform", 191 | ) 192 | parser.add_argument( 193 | "-r", 194 | "--calc_geodetic", 195 | action="store_true", 196 | help="Calculate geodetic from XYZ (default is calculate XYZ)", 197 | ) 198 | parser.add_argument( 199 | "-c", 200 | "--csv", 201 | action="store_true", 202 | help="File format CSV - default whitespace delimited", 203 | ) 204 | parser.add_argument( 205 | "-zg", 206 | "--geodetic-column-names", 207 | metavar=("LON_COL", "LAT_COL", "HGT_COL"), 208 | nargs=3, 209 | help="Column names of X,Y,Z fields - default first three columns", 210 | ) 211 | parser.add_argument( 212 | "-zx", 213 | "--xyz-column-names", 214 | metavar=("X_COL", "Y_COL", "Z_COL"), 215 | nargs=3, 216 | help="Column names of X,Y,Z fields - default first three columns", 217 | ) 218 | parser.add_argument("input_file", nargs="?", help="Input file of XYZ coordinates") 219 | parser.add_argument("output_file", nargs="?", help="Output file of XYZ coordinates") 220 | 221 | args = parser.parse_args() 222 | input_file = args.input_file 223 | output_file = args.output_file 224 | 225 | if args.xyz is not None and input_file is not None: 226 | print("Cannot have xyz and input file arguments") 227 | sys.exit() 228 | if args.geodetic is not None and input_file is not None: 229 | print("Cannot have geodetic and input file arguments") 230 | sys.exit() 231 | if args.xyz is not None and args.geodetic is not None: 232 | print("Cannot have xyz and geodetic arguments") 233 | 234 | sys.exit() 235 | if args.geodetic is None and args.xyz is None and not input_file: 236 | print("No coordinate input specified - need xyz, geodetic, or input file") 237 | sys.exit() 238 | if input_file is not None and output_file is None: 239 | print("Need the name of an output file") 240 | sys.exit() 241 | 242 | ell = GRS80 243 | if args.ellipsoid: 244 | ell = Ellipsoid(args[0], args[1]) 245 | 246 | if args.xyz: 247 | llh = ell.geodetic(args.xyz) 248 | print(f"{llh[0]:.9f} {llh[1]:.9f} {llh[2]:.4f}") 249 | sys.exit() 250 | 251 | if args.geodetic: 252 | xyz = ell.xyz(args.geodetic) 253 | print("{xyz[0]:.4f} {1:.4f} {2:.4f}") 254 | sys.exit() 255 | 256 | tfm = ell.geodetic if args.calc_geodetic else ell.xyz 257 | gcol = args.geodetic_column_names 258 | xcol = args.xyz_column_names 259 | if gcol and not xcol: 260 | xcol = ["X", "Y", "Z"] 261 | if xcol and not gcol: 262 | gcol = ["Lon", "Lat", "Hgt"] 263 | 264 | incol = xcol if args.calc_geodetic else gcol 265 | outcol = gcol if args.calc_geodetic else xcol 266 | colfmt = ["{0:.9f}", "{0:.9f}", "{0:.4f}"] if args.calc_geodetic else ["{0:.4f}"] * 3 267 | 268 | if args.input_file: 269 | cols = [0, 1, 2] 270 | reqlen = 3 271 | with sys.stdin if input_file == "-" else open(input_file, "r", encoding="utf8") as fin: 272 | import csv 273 | 274 | if args.csv: 275 | freader = csv.reader(fin) 276 | else: 277 | 278 | def wsreader(f): 279 | for l in f: 280 | yield l.split() 281 | 282 | freader = wsreader(fin) 283 | with sys.stdout if output_file == "-" else open(output_file, "w", encoding="utf8") as fout: 284 | if args.csv: 285 | writerow = csv.writer(fout).writerow 286 | else: 287 | 288 | def writerow(row): 289 | fout.write("\t".join(row)) 290 | fout.write("\n") 291 | 292 | if incol: 293 | header = next(freader) 294 | header = [x.upper() for x in header] 295 | cols = [] 296 | for c in incol: 297 | c = c.upper() 298 | if c not in header: 299 | print("Column", c, "is missing from input file header") 300 | sys.exit() 301 | nc = header.index(c) 302 | cols.append(nc) 303 | cols.append(header.index(c)) 304 | reqlen = max(cols) 305 | row = list(header) 306 | for i, c in zip(cols, outcol): 307 | row[i] = c 308 | writerow(row) 309 | for row in freader: 310 | if len(row) < reqlen: 311 | continue 312 | xyz = [float(row[i]) for i in cols] 313 | xyzt = tfm(xyz) 314 | for c, x, f in zip(cols, xyzt, colfmt): 315 | row[c] = f.format(x) 316 | writerow(row) 317 | 318 | 319 | if __name__ == "__main__": 320 | main() 321 | -------------------------------------------------------------------------------- /LINZ/Geodetic/ITRF.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import math 4 | from datetime import date, datetime 5 | 6 | import numpy as np 7 | 8 | # IERS parameters: 9 | # 10 | # Data from http://itrf.ensg.ign.fr/doc_ITRF/Transfo-ITRF2008_ITRFs.txt 11 | # 12 | # Note : These parameters are derived from those already published in the IERS 13 | # Technical Notes and Annual Reports. The transformation parameters should be 14 | # used with the standard model (1) given below and are valid at the indicated 15 | # epoch. 16 | # 17 | # 18 | # : XS : : X : : Tx : : D -Rz Ry : : X : 19 | # : : : : : : : : : : 20 | # : YS : = : Y : + : Ty : + : Rz D -Rx : : Y : (1) 21 | # : : : : : : : : : : 22 | # : ZS : : Z : : Tz : : -Ry Rx D : : Z : 23 | # 24 | # 25 | # Where X,Y,Z are the coordinates in ITRF2008 and XS,YS,ZS are the coordinates in 26 | # the other frames. 27 | 28 | # IERS parameters relative to ITRF2008 29 | # 30 | # Tx Ty Tz S Rx Ry Rz date 31 | # ITRF2005 -2.0 -0.9 -4.7 0.94 0.00 0.00 0.00 2000.0 32 | # rates 0.3 0.0 0.0 0.00 0.00 0.00 0.00 33 | # ITRF2000 -1.9 -1.7 -10.5 1.34 0.00 0.00 0.00 2000.0 34 | # rates 0.1 0.1 -1.8 0.08 0.00 0.00 0.00 35 | # ITRF97 4.8 2.6 -33.2 2.92 0.00 0.00 0.06 2000.0 36 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 37 | # ITRF96 4.8 2.6 -33.2 2.92 0.00 0.00 0.06 2000.0 38 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 39 | # ITRF94 4.8 2.6 -33.2 2.92 0.00 0.00 0.06 2000.0 40 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 41 | # ITRF93 -24.0 2.4 -38.6 3.41 -1.71 -1.48 -0.30 2000.0 42 | # rates -2.8 -0.1 -2.4 0.09 -0.11 -0.19 0.07 43 | # ITRF92 12.8 4.6 -41.2 2.21 0.00 0.00 0.06 2000.0 44 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 45 | # ITRF91 24.8 18.6 -47.2 3.61 0.00 0.00 0.06 2000.0 46 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 47 | # ITRF90 22.8 14.6 -63.2 3.91 0.00 0.00 0.06 2000.0 48 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 49 | # ITRF89 27.8 38.6 -101.2 7.31 0.00 0.00 0.06 2000.0 50 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 51 | # ITRF88 22.8 2.6 -125.2 10.41 0.10 0.00 0.06 2000.0 52 | # rates 0.1 -0.5 -3.2 0.09 0.00 0.00 0.02 53 | # 54 | # IGS parameters 55 | # 56 | # From 57 | # Transforming Positions and Velocities between the International Terrestrial Reference Frame of 2000 58 | # and North American Datum of 1983 59 | # Tomas Soler, M.ASCE, and Richard A. Snay 60 | # JOURNAL OF SURVEYING ENGINEERING (c) ASCE / MAY 2004 / P49 61 | # 62 | # Referenced to 63 | # Springer, T. A., Kouba, J., and Mireault, Y. 2000. "1999 analysis coor- 64 | # dinator report." 1999 Tech. Rep., International GPS Service for Geo- 65 | # dynamics, Jet Propulsion Laboratory, Pasadena, Calif., 15-55. 66 | # 67 | # IGS parameters relative to ITRF97 68 | # 69 | # ITRF96 -2.07 -0.21 9.95 -0.93496 +0.12467 -0.22355 -0.06065 1997.0 70 | # rates 0.69 -0.10 1.86 -0.19201 +0.01347 -0.01514 +0.00027 71 | # 72 | # Combined to generate following parameters relative to ITRF96 at epoch 2000.0 73 | # 74 | # Note: IGS parameters use opposite sign convention for rotations to IERS 75 | # 76 | # ITRF2014 parameters from http://itrf.ensg.ign.fr/ITRF_solutions/2014/tp_14-08.php 77 | # 78 | # Transformation Parameters from ITRF2014 to ITRF2008 79 | # 80 | # 14 transformation parameters from ITRF2014 to ITRF2008 have been estimated using 127 stations listed in Table 2 and located at 125 sites shown on Figure 2. 81 | # 82 | # 83 | # T1 T2 T3 D R1 R2 R3 84 | # mm mm mm 10-9 mas mas mas 85 | # 1.6 1.9 2.4 -0.02 0.000 0.000 0.000 86 | # +/- 0.2 0.1 0.1 0.02 0.006 0.006 0.006 87 | # 88 | # Rates 0.0 0.0 -0.1 0.03 0.000 0.000 0.000 89 | # +/- 0.2 0.1 0.1 0.02 0.006 0.006 0.006 90 | # 91 | # Table 1: Transformation parameters at epoch 2010.0 and their rates from ITRF2014 to ITRF2008 (ITRF2008 minus ITRF2014 92 | # 93 | # (After conversion to epoch 2000) 94 | 95 | ITRF_params = ( 96 | ( 97 | "ITRF2020", 98 | (-5.0, -4.59, 15.87, -0.66901, 0.16508, -0.26897, -0.11984), 99 | (-0.79, 0.7, 1.24, 0.07201, 0.01347, -0.01514, -0.01973), 100 | ), 101 | ( 102 | "ITRF2014", 103 | (-6.4, -3.99, 14.27, -1.08901, 0.16508, -0.26897, -0.11984), 104 | (-0.79, 0.6, 1.44, 0.07201, 0.01347, -0.01514, -0.01973), 105 | ), 106 | ( 107 | "ITRF2008", 108 | (-4.8, -2.09, 17.67, -1.40901, 0.16508, -0.26897, -0.11984), 109 | (-0.79, 0.6, 1.34, 0.10201, 0.01347, -0.01514, -0.01973), 110 | ), 111 | ( 112 | "ITRF2005", 113 | (-6.8, -2.99, 12.97, -0.46901, 0.16508, -0.26897, -0.11984), 114 | (-0.49, 0.6, 1.34, 0.10201, 0.01347, -0.01514, -0.01973), 115 | ), 116 | ( 117 | "ITRF2000", 118 | (-6.7, -3.79, 7.17, -0.06901, 0.16508, -0.26897, -0.11984), 119 | (-0.69, 0.7, -0.46, 0.18201, 0.01347, -0.01514, -0.01973), 120 | ), 121 | ( 122 | "ITRF97", 123 | (0, 0.51, -15.53, 1.51099, 0.16508, -0.26897, -0.05984), 124 | (-0.69, 0.1, -1.86, 0.19201, 0.01347, -0.01514, 0.00027), 125 | ), 126 | ("ITRF96", (0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0)), 127 | ) 128 | 129 | ITRF_ref = "ITRF96" 130 | refdate = 2000.0 131 | 132 | secstorads = math.radians(1.0 / 3600.0) 133 | scalefactors = ( 134 | 0.001, 135 | 0.001, 136 | 0.001, 137 | 1.0e-9, 138 | secstorads * 0.001, 139 | secstorads * 0.001, 140 | secstorads * 0.001, 141 | ) 142 | 143 | 144 | def dateAsYear(dvalue): 145 | """ 146 | Convert a date or datetime to a floating point number of years 147 | Leaves floating or integer values unchanged. 148 | """ 149 | if type(dvalue) == float: 150 | return dvalue 151 | if type(dvalue) == int: 152 | return float(dvalue) 153 | if type(dvalue) == datetime: 154 | year = dvalue.year 155 | dt = dvalue - datetime(year, 1, 1) 156 | elif type(dvalue) == date: 157 | year = dvalue.year 158 | dt = dvalue - date(year, 1, 1) 159 | else: 160 | raise RuntimeError(type(dvalue).__name__ + " is not a valid type for a date") 161 | dty = float((date(year + 1, 1, 1) - date(year, 1, 1)).days) 162 | return year + (dt.days + dt.seconds / (24.0 * 60 * 60)) / dty 163 | 164 | 165 | class BursaWolf14Transformation(object): 166 | def __init__(self, rffrom, rfto, params, rates=None, refdate=refdate, source=None): 167 | self.rffrom = rffrom 168 | self.rfto = rfto 169 | self.params = None if params is None else list(params) 170 | self.rates = None if rates is None else list(rates) 171 | self.refdate = refdate 172 | self.source = source 173 | self._tf = None 174 | 175 | def __str__(self): 176 | return ( 177 | "Transformation from " 178 | + self.rffrom 179 | + " to " 180 | + self.rfto 181 | + "\n" 182 | + ( 183 | " Reference date {0:.1f}\n".format(self.refdate) 184 | if self.rates and self.refdate 185 | else "" 186 | ) 187 | + " Translations {0:.2f} {1:.2f} {2:.2f} mm\n".format(*self.params[0:3]) 188 | + ( 189 | " rates {0:.2f} {1:.2f} {2:.2f} mm/yr\n".format( 190 | *self.rates[0:3] 191 | ) 192 | if self.rates and self.refdate 193 | else "" 194 | ) 195 | + " Rotations {0:.5f} {1:.5f} {2:.5f} mas\n".format( 196 | *self.params[4:7] 197 | ) 198 | + ( 199 | " rates {0:.5f} {1:.5f} {2:.5f} mas/yr\n".format( 200 | *self.rates[4:7] 201 | ) 202 | if self.rates and self.refdate 203 | else "" 204 | ) 205 | + " Scale {0:.5f} ppb\n".format(self.params[3]) 206 | + ( 207 | " rates {0:.5f} ppb/yr\n".format(self.rates[3]) 208 | if self.rates and self.refdate 209 | else "" 210 | ) 211 | ) 212 | 213 | def reversed(self): 214 | return BursaWolf14Transformation( 215 | self.rfto, 216 | self.rffrom, 217 | [-p for p in self.params], 218 | None if self.rates is None else [-r for r in self.rates], 219 | self.refdate, 220 | self.source, 221 | ) 222 | 223 | def atDate(self, date): 224 | p = list(self.params) 225 | refdate = None 226 | date = dateAsYear(date) 227 | if self.rates and self.refdate: 228 | diff = date - self.refdate 229 | for i, r in enumerate(self.rates): 230 | p[i] = self.params[i] + r * diff 231 | refdate = date 232 | return BursaWolf14Transformation( 233 | self.rffrom, self.rfto, p, self.rates, refdate, self.source 234 | ) 235 | 236 | def add(self, other): 237 | if self.rffrom == other.rfto: 238 | rffrom = other.rffrom 239 | rfto = self.rfto 240 | elif self.rfto == other.rffrom: 241 | rffrom = self.rffrom 242 | rfto = other.rfto 243 | else: 244 | raise RuntimeError( 245 | "Cannot join incompatible transformations (must have common start/end reference frame" 246 | ) 247 | 248 | refdate = self.refdate if self.refdate is not None else other.refdate 249 | if refdate and refdate != other.refdate: 250 | other = other.atDate(refdate) 251 | 252 | return BursaWolf14Transformation( 253 | rffrom, 254 | rfto, 255 | [p1 + p2 for p1, p2 in zip(self.params, other.params)], 256 | ( 257 | self.rates 258 | if other.rates is None 259 | else ( 260 | other.rates 261 | if self.rates is None 262 | else [p1 + p2 for p1, p2 in zip(self.rates, other.rates)] 263 | ) 264 | ), 265 | refdate, 266 | self.source if self.source == other.source else None, 267 | ) 268 | 269 | def subtract(self, other): 270 | return self.add(other.reversed()) 271 | 272 | def transFunc(self, date): 273 | """ 274 | Generates a transformation function between the two ITRF 275 | realisations at a specific date. The function generated takes 276 | as parameters a single coordinate or an array of coordinates. 277 | 278 | Uses numpy for efficient processing of arrays of coordinates 279 | """ 280 | if self.rates is not None and self.refdate is not None and date is not None: 281 | diff = dateAsYear(date) - self.refdate 282 | params = [ 283 | (p + r * diff) * s 284 | for p, r, s in zip(self.params, self.rates, scalefactors) 285 | ] 286 | else: 287 | params = [p * s for p, s in zip(self.params, scalefactors)] 288 | txyz = np.array([params[0:3]]) 289 | scale = params[3] 290 | (rx, ry, rz) = params[4:7] 291 | rxyz = np.transpose( 292 | np.array([[scale, -rz, ry], [rz, scale, -rx], [-ry, rx, scale]]) 293 | ) 294 | 295 | def tf(coords): 296 | if not isinstance(coords, np.ndarray): 297 | coords = np.array(coords) 298 | single = len(coords.shape) == 1 299 | if single: 300 | coords = coords.reshape((1, coords.size)) 301 | coords = coords + txyz + coords.dot(rxyz) 302 | if single: 303 | coords = coords.reshape((coords.size)) 304 | return coords 305 | 306 | return tf 307 | 308 | def transform(self, xyz, date=None): 309 | """ 310 | Transforms a single coordinate [X,Y,Z] or an array of 311 | coordinates [[X1,Y1,Z1],[X2,Y2,Z2],..] from the source to the 312 | target ITRF. Transformation is applied at the specified date, 313 | or at the reference date of the transformation if none is 314 | defined. 315 | 316 | Uses numpy for efficient processing of arrays of coordinates 317 | """ 318 | if self._tf is None or date != self._tfdate: 319 | self._tf = self.transFunc(date) 320 | self._tfdate = date 321 | return self._tf(xyz) 322 | 323 | def transformLonLat(self, lon, lat=None, hgt=None, date=None): 324 | """ 325 | Transforms a single coordinate [Lon,Lat,Hgt] or an array of 326 | coordinates [[lon1,lat1,hgt1],[lon2,lat2,hgt2],..] from the 327 | source to the target ITRF. Transformation is applied at the specified date, 328 | or at the reference date of the transformation if none is 329 | defined. 330 | 331 | Uses numpy for efficient processing of arrays of coordinates 332 | """ 333 | from .Ellipsoid import GRS80 334 | 335 | xyz = GRS80.xyz(lon, lat, hgt) 336 | xyz = self.transform(xyz, date=date) 337 | return GRS80.geodetic(xyz) 338 | 339 | 340 | def Transformation(to_itrf=ITRF_ref, from_itrf=ITRF_ref): 341 | """ 342 | Determine the transformation from one ITRF realisation to another, 343 | returns an Transformation object. 344 | """ 345 | rffrom = None 346 | rfto = None 347 | for p in ITRF_params: 348 | if p[0] == to_itrf: 349 | rfto = BursaWolf14Transformation(ITRF_ref, p[0], p[1], p[2], refdate) 350 | if from_itrf == ITRF_ref: 351 | return rfto 352 | if p[0] == from_itrf: 353 | rffrom = BursaWolf14Transformation( 354 | ITRF_ref, p[0], p[1], p[2], refdate 355 | ).reversed() 356 | if to_itrf == ITRF_ref: 357 | return rffrom 358 | if not rffrom: 359 | raise RuntimeError(from_itrf + " is not a recognized ITRF") 360 | if not rfto: 361 | raise RuntimeError(to_itrf + " is not a recognized ITRF") 362 | return rffrom.add(rfto) 363 | 364 | 365 | # itrf2008_nzgd2000=Transformation.transformation(from_itrf='ITRF2008') 366 | # nzgd2000_itrf2008=Transformation.transformation(to_itrf='ITRF2008') 367 | 368 | 369 | def main(): 370 | import argparse 371 | import re 372 | import sys 373 | 374 | parser = argparse.ArgumentParser( 375 | description="Convert Cartesian coordinates between ITRF systems" 376 | ) 377 | parser.add_argument( 378 | "-f", "--from-itrf", default="ITRF2008", help="Source ITRF - default ITRF2008" 379 | ) 380 | parser.add_argument( 381 | "-t", "--to-itrf", default="ITRF96", help="Target ITRF - default ITRF96" 382 | ) 383 | parser.add_argument( 384 | "-d", 385 | "--date", 386 | help="Transformation date (yyyymmdd or yyyy.yyy) - default today", 387 | ) 388 | parser.add_argument( 389 | "-l", "--list", action="store_true", help="List transformation parameters" 390 | ) 391 | parser.add_argument( 392 | "-x", 393 | "--xyz", 394 | nargs=3, 395 | type=float, 396 | metavar=("X", "Y", "Z"), 397 | help="XYZ coordinates to transform (input/output files ignored)", 398 | ) 399 | parser.add_argument( 400 | "-c", 401 | "--csv", 402 | action="store_true", 403 | help="File format CSV - default whitespace delimited", 404 | ) 405 | parser.add_argument( 406 | "-z", 407 | "--column-names", 408 | metavar=("X_COL", "Y_COL", "Z_COL"), 409 | nargs=3, 410 | help="Column names of X,Y,Z fields - default first three columns", 411 | ) 412 | parser.add_argument( 413 | "-g", "--geodetic", action="store_true", help="Coordinates are lon,lat,hgt" 414 | ) 415 | parser.add_argument( 416 | "-v", "--verbose", action="store_true", help="More verbose output" 417 | ) 418 | parser.add_argument("input_file", nargs="?", help="Input file of XYZ coordinates") 419 | parser.add_argument("output_file", nargs="?", help="Output file of XYZ coordinates") 420 | 421 | args = parser.parse_args() 422 | input_file = args.input_file 423 | output_file = args.output_file 424 | if args.xyz is not None and input_file is not None: 425 | print("Cannot have xyz and input file arguments") 426 | sys.exit() 427 | if not args.list and args.xyz is None and not input_file: 428 | print("No coordinate input specified - need xyz or input file") 429 | sys.exit() 430 | if input_file is not None and output_file is None: 431 | print("Need the name of an output file") 432 | sys.exit() 433 | 434 | itrfs = [x[0] for x in ITRF_params] 435 | 436 | from_itrf = args.from_itrf.upper() 437 | if from_itrf not in itrfs: 438 | if "ITRF" + from_itrf in itrfs: 439 | from_itrf = "ITRF" + from_itrf 440 | else: 441 | print(from_itrf, "is not a valid ITRF") 442 | print("Options are:", ", ".join(itrfs)) 443 | sys.exit() 444 | 445 | to_itrf = args.to_itrf.upper() 446 | if to_itrf not in itrfs: 447 | if "ITRF" + to_itrf in itrfs: 448 | to_itrf = "ITRF" + to_itrf 449 | else: 450 | print(to_itrf, "is not a valid ITRF") 451 | print("Options are:", ", ".join(itrfs)) 452 | sys.exit() 453 | 454 | dt = datetime.now() 455 | if args.date is not None: 456 | m = re.match(r"(\d\d\d\d)(\d\d)(\d\d)$", args.date) 457 | if not m: 458 | m = re.match(r"(\d\d\d\d)\-(\d\d)\-(\d\d)$", args.date) 459 | if m: 460 | dt = datetime(int(m.group(1)), int(m.group(2)), int(m.group(3))) 461 | elif re.match(r"\d\d\d\d(?:\.\d+)?$", args.date): 462 | dt = float(args.date) 463 | year = dateAsYear(dt) 464 | 465 | tfm = Transformation(from_itrf=from_itrf, to_itrf=to_itrf).atDate(year) 466 | 467 | if args.list: 468 | print(tfm) 469 | elif args.verbose: 470 | print( 471 | "Transforming from {0} to {1} at {2:.2f}".format(from_itrf, to_itrf, year) 472 | ) 473 | 474 | if args.geodetic: 475 | transfunc = tfm.transformLonLat 476 | crdfmt = ["{0:.9f}", "{0:.9f}", "{0:.4f}"] 477 | else: 478 | transfunc = tfm.transform 479 | crdfmt = ["{0:.4f}"] * 3 480 | 481 | if args.xyz: 482 | xyzt = transfunc(args.xyz) 483 | xyzs = [f.format(x) for f, x in zip(crdfmt, xyzt)] 484 | print("{0} {1} {2}".format(*xyzs)) 485 | sys.exit() 486 | 487 | if args.input_file: 488 | cols = [0, 1, 2] 489 | reqlen = 3 490 | with sys.stdin if input_file == "-" else open(input_file, "r") as fin: 491 | if args.verbose: 492 | print("Reading coordinates from", input_file) 493 | import csv 494 | 495 | if args.csv: 496 | freader = csv.reader(fin) 497 | else: 498 | 499 | def wsreader(f): 500 | for l in f: 501 | yield l.split() 502 | 503 | freader = wsreader(fin) 504 | with sys.stdout if output_file == "-" else open(output_file, "w") as fout: 505 | if args.verbose: 506 | print("Writing coordinates to", output_file) 507 | if args.csv: 508 | writerow = csv.writer(fout).writerow 509 | else: 510 | 511 | def writerow(row): 512 | fout.write("\t".join(row)) 513 | fout.write("\n") 514 | 515 | if args.column_names: 516 | header = next(freader) 517 | writerow(header) 518 | header = [x.upper() for x in header] 519 | cols = [] 520 | for c in args.column_names: 521 | c = c.upper() 522 | if c not in header: 523 | print("Column", c, "is missing from input file header") 524 | sys.exit() 525 | cols.append(header.index(c)) 526 | reqlen = max(cols) 527 | for row in freader: 528 | if len(row) < reqlen: 529 | continue 530 | xyz = [float(row[i]) for i in cols] 531 | xyzt = transfunc(xyz) 532 | for f, c, x in zip(crdfmt, cols, xyzt): 533 | row[c] = f.format(x) 534 | writerow(row) 535 | 536 | 537 | if __name__ == "__main__": 538 | main() 539 | -------------------------------------------------------------------------------- /LINZ/Geodetic/Sinex.py: -------------------------------------------------------------------------------- 1 | # Module for reading SINEX files. 2 | 3 | import gzip 4 | import math 5 | import re 6 | import sys 7 | from collections import namedtuple 8 | from datetime import datetime, timedelta 9 | 10 | import numpy as np 11 | 12 | from .Ellipsoid import GRS80 13 | 14 | try: 15 | stringtype = str # for Python2 16 | except NameError: 17 | stringtype = str # for Python3 18 | 19 | COVAR_FULL = 2 20 | COVAR_STATION = 1 21 | COVAR_NONE = 0 22 | 23 | EXTRAPOLATE_NONE = 0 24 | EXTRAPOLATE_FIRST = 1 25 | EXTRAPOLATE_LAST = 2 26 | EXTRAPOLATE_AFTER = 4 27 | EXTRAPOLATE_ALL = 7 28 | 29 | _years = {} 30 | 31 | 32 | def _decimalYear(date): 33 | global _years 34 | year = date.year 35 | if year not in _years: 36 | y0 = datetime(year, 1, 1) 37 | y1 = datetime(year + 1, 1, 1) 38 | factor = 1.0 / (y1 - y0).total_seconds() 39 | _years[year] = (y0, factor) 40 | return year + (date - _years[year][0]).total_seconds() * _years[year][1] 41 | 42 | 43 | class NoSolution(RuntimeError): 44 | pass 45 | 46 | 47 | class AmbiguousSolution(RuntimeError): 48 | pass 49 | 50 | 51 | class InvalidParameters(RuntimeError): 52 | pass 53 | 54 | 55 | class CovarianceMissing(RuntimeError): 56 | pass 57 | 58 | 59 | class SinexFileError(RuntimeError): 60 | pass 61 | 62 | 63 | class Reader(object): 64 | 65 | _scanners = {} 66 | _epochre = re.compile(r"^(\d\d)\:(\d\d\d)\:(\d\d\d\d\d)$") 67 | 68 | Site = namedtuple("Site", "id code monument description llh") 69 | Epoch = namedtuple("Epoch", "id code soln start end mean") 70 | Coordinate = namedtuple("Coordinate", "id code soln crddate xyz vxyz prmids") 71 | SolutionId = namedtuple("SolutionId", "id code soln") 72 | Solution = namedtuple( 73 | "Solution", 74 | "id code soln monument description llh startdate enddate crddate crddate_year xyz vxyz prmids covariance", 75 | ) 76 | 77 | def __init__(self, filename, **options): 78 | """ 79 | Load a SINEX file. Options are: 80 | 81 | selectCodes a list of codes to load (default is all) 82 | velocities read velocities as well as xyz (boolean, default is True) 83 | covariance covariance option, one of COVAR_FULL, COVAR_STATION, COVAR_NONE, 84 | default is COVAR_NONE 85 | 86 | """ 87 | self._filename = filename 88 | self._fh = None 89 | self._lineno = 0 90 | self._version = None 91 | self._options = options 92 | self._selectCodes = options.get("selectCodes") 93 | covarOption = options.get("covariance") or COVAR_NONE 94 | if covarOption and covarOption != COVAR_STATION: 95 | covarOption = COVAR_FULL 96 | self._covarianceOption = covarOption 97 | self._sites = {} 98 | self._stats = {} 99 | self._epochs = {} 100 | self._coords = {} 101 | self._prmlookup = {} 102 | self._covariances = [] 103 | self._solutionIndex = {} 104 | self._scan() 105 | self._buildSolutionIndex() 106 | 107 | def solutions(self): 108 | """ 109 | Return a list of solutions as tuples (point id, point code, solution id) 110 | """ 111 | return sorted(self._coords.keys()) 112 | 113 | def _buildSolutionIndex(self): 114 | solutions = {} 115 | monuments = {} 116 | for key in self._coords: 117 | ptid, ptcode, solnid = key 118 | if ptid not in solutions: 119 | solutions[ptid] = [] 120 | solutions[ptid].append((ptid, ptcode, solnid)) 121 | site = self._sites.get((ptid, ptcode)) 122 | if site is not None: 123 | monument = site.monument 124 | if monument not in monuments: 125 | monuments[monument] = [] 126 | monuments[monument].append(Reader.SolutionId(ptid, ptcode, solnid)) 127 | self._solutions = solutions 128 | self._monuments = monuments 129 | 130 | def getSolutionIds(self, ptid=None, ptcode=None, solnid=None, monument=None, solution=None): 131 | """ 132 | Get a list of solution ids matching the point id, point code, and solution id, 133 | or monument 134 | or solution tuple 135 | 136 | First parameter may be interpreted as a ptid (stringtype), or ':' separated 137 | point id, point code, solution id, or solution id tuple (if a tuple), 138 | """ 139 | 140 | if isinstance(ptid, tuple): 141 | solution = ptid 142 | ptid = None 143 | 144 | if isinstance(solution, stringtype): 145 | ptid = solution 146 | solution = None 147 | 148 | if ptid is not None and ":" in ptid: 149 | solution = ((ptid + "::").split(":"))[:3] 150 | ptid = solution[0] 151 | 152 | if solution is not None: 153 | ptid = ptid or solution[0] 154 | ptcode = ptcode or solution[1] 155 | solnid = solnid or solution[2] 156 | 157 | solutions = [] 158 | if monument is not None: 159 | if ptid is not None or ptcode is not None: 160 | raise InvalidParameters("Sinex.Reader cannot specify monument as well as ptid or ptcode") 161 | for solution in self._monuments.get(monument, []): 162 | if solnid is not None and solution[2] != solnid: 163 | continue 164 | key = (solution[0], solution[1]) 165 | solutions.append(solution) 166 | elif ptid is not None: 167 | for solution in self._solutions.get(ptid, []): 168 | if ptcode is not None and solution[1] != ptcode: 169 | continue 170 | if solnid is not None and solution[2] != solnid: 171 | continue 172 | solutions.append(solution) 173 | else: 174 | raise InvalidParameters("Sinex.Reader requires a solution, monument, ptid parameter") 175 | return solutions 176 | 177 | def get( 178 | self, 179 | ptid=None, 180 | ptcode=None, 181 | solnid=None, 182 | monument=None, 183 | solution=None, 184 | date=None, 185 | allSolutions=False, 186 | extrapolate=EXTRAPOLATE_NONE, 187 | ): 188 | """ 189 | Get a specific solution. Can either specify a ptid or a monument. 190 | If this is ambiguous then a ptcode and solnid may also be supplied). 191 | Alternatively can specify a solution such as returned by solutions(). 192 | 193 | If allSolutions is true then a list of matching solutions will be returned 194 | 195 | If a date is specified then the solution applicable at that date is returned 196 | 197 | The extrapolate option affects extrapolation beyond the solution dates. Options are: 198 | EXTRAPOLATE_NONE only return solutions matching the date 199 | EXTRAPOLATE_FIRST use the first solution for dates before it 200 | EXTRAPOLATE_LAST use the last solution for dates after it 201 | EXTRAPOLATE_AFTER allow extrapolation each solution forwards till the next 202 | (implies EXTRAPOLATE_LAST) 203 | EXTRAPOLATE_ALL use all these options 204 | Note that extrapolation here refers to the selection of solutions, not the 205 | coordinates calculated from them. Use .xyz to calculate/extrapolate coordinates. 206 | """ 207 | 208 | solutions = self.getSolutionIds( 209 | ptid=ptid, 210 | ptcode=ptcode, 211 | solnid=solnid, 212 | monument=monument, 213 | solution=solution, 214 | ) 215 | if date is not None: 216 | 217 | extrapolateFirst = extrapolate & EXTRAPOLATE_FIRST 218 | extrapolateAfter = extrapolate & EXTRAPOLATE_AFTER 219 | extrapolateLast = (extrapolate & EXTRAPOLATE_LAST) or extrapolateAfter 220 | 221 | # Group solution by monument 222 | msolutions = {} 223 | for s in solutions: 224 | key = s[:2] 225 | if key not in msolutions: 226 | msolutions[key] = [] 227 | msolutions[key].append(s) 228 | 229 | # Order by epoch for each monument 230 | epochs = self._epochs 231 | for s in msolutions: 232 | msolutions[s].sort(key=lambda soln: epochs[soln].start if soln in epochs else datetime.min) 233 | 234 | # Select by date 235 | solutions = [] 236 | for solnset in list(msolutions.values()): 237 | # If a date is defined then only return one solution for each monument 238 | chosen = solnset[0] if extrapolateFirst else None 239 | for s in solnset: 240 | epoch = self._epochs.get(s) 241 | if epoch is not None: 242 | if not extrapolateAfter: 243 | chosen = None 244 | if epoch.start > date: 245 | break 246 | if epoch.start <= date: 247 | if epoch.end >= date: 248 | chosen = s 249 | break 250 | if extrapolateLast: # implies extrapolateAfter 251 | chosen = s 252 | if chosen: 253 | solutions.append(chosen) 254 | 255 | if len(solutions) == 0: 256 | raise NoSolution("No Sinex solution found matching request") 257 | return None 258 | if len(solutions) > 1 and not allSolutions: 259 | raise AmbiguousSolution("Ambiguous Sinex solution requested") 260 | 261 | results = self.getSolutions(solutions) 262 | return results if allSolutions else results[0] 263 | 264 | def getSolutions(self, solutions): 265 | """ 266 | Returns the full solution data for a list of solutions, each supplied 267 | as a tuple of point id, point code, and solution id (eg as returned by 268 | solutions, or get). 269 | 270 | If the covariance was loaded (see covariance option of __init__) then 271 | the solutions will include a covariance matrix. If the full covariance 272 | was loaded each solution will contain the same full covariance matrix. 273 | Each solution contains prmids array mapping x,y,z and vx, vy, vz into 274 | the corresponding elements of the array 275 | """ 276 | results = [] 277 | for solution in solutions: 278 | ptid, ptcode, solnid = solution[:3] 279 | site = self._sites.get((ptid, ptcode)) 280 | epoch = self._epochs.get((ptid, ptcode, solnid)) 281 | coord = self._coords[ptid, ptcode, solnid] 282 | ncvr, prmids = coord.prmids 283 | covar = None 284 | if ncvr < len(self._covariances): 285 | covar = self._covariances[ncvr] 286 | else: 287 | ncvr = None 288 | prmids = None 289 | results.append( 290 | Reader.Solution( 291 | ptid, 292 | ptcode, 293 | solnid, 294 | site.monument if site is not None else ptid, 295 | site.description if site is not None else "", 296 | site.llh if site is not None else None, 297 | epoch.start if epoch is not None else None, 298 | epoch.end if epoch is not None else None, 299 | coord.crddate, 300 | _decimalYear(coord.crddate), 301 | coord.xyz, 302 | coord.vxyz, 303 | prmids, 304 | covar, 305 | ) 306 | ) 307 | return results 308 | 309 | def xyz( 310 | self, 311 | solution=None, 312 | date=None, 313 | covariance=False, 314 | _covarInfo=False, 315 | extrapolate=EXTRAPOLATE_NONE, 316 | ): 317 | """ 318 | Return the xyz coordinate at an epoch based on the point id and date. 319 | Solution can be (ptid,ptcode,solnid) tuple, or "ptid:ptcode:solnid string". 320 | ptcode can be omitted if ptid is not ambiguous. solnid can be omitted - 321 | will be defined by date. 322 | 323 | If covariance is True returns a tuple of xyz, vxyz 324 | """ 325 | solutions = self.get(solution=solution, date=date, extrapolate=extrapolate, allSolutions=True) 326 | 327 | if not solutions: 328 | return 329 | if len(solutions) > 1: 330 | raise AmbiguousSolution("Ambiguous solution in SINEX file") 331 | solution = solutions[0] 332 | if (covariance or _covarInfo) and solution.covariance is None: 333 | raise CovarianceMissing("Sinex covariances not read") 334 | 335 | if solution.vxyz is not None and date is not None: 336 | ydiff = _decimalYear(date) - solution.crddate_year 337 | xyz = np.array(solution.xyz) + ydiff * np.array(solution.vxyz) 338 | nprm = 6 339 | else: 340 | ydiff = 0.0 341 | xyz = solution.xyz.copy() 342 | nprm = 3 343 | 344 | if _covarInfo: 345 | return xyz, ydiff, solution.prmids, nprm, solution.covariance 346 | 347 | if not covariance: 348 | return xyz 349 | 350 | prmids = solution.prmids 351 | ptcovar = solution.covariance[np.ix_(prmids, prmids)] 352 | mult = np.zeros((3, nprm)) 353 | mult[:, 0:3] = np.identity(3) 354 | if nprm > 3: 355 | mult[:, 3:] = np.identity(3) * ydiff 356 | xyzcovar = mult.dot(ptcovar.dot(mult.T)) 357 | return xyz, xyzcovar 358 | 359 | def dxyz( 360 | self, 361 | solutionFrom, 362 | solutionTo, 363 | date=None, 364 | enu=False, 365 | extrapolate=EXTRAPOLATE_NONE, 366 | covariance=False, 367 | ): 368 | """ 369 | Return the difference in xyz coordinates at an epoch based on the point id and date. 370 | Solutions can be supplied as solution tuples or ':' delimited strings of ptid, ptcode, solnid. 371 | """ 372 | 373 | xyz1 = self.xyz(solutionFrom, date, extrapolate=extrapolate, _covarInfo=covariance) 374 | xyz2 = self.xyz(solutionTo, date, extrapolate=extrapolate, _covarInfo=covariance) 375 | if covariance: 376 | xyz1, ydiff1, prmids1, nprm1, covar1 = xyz1 377 | xyz2, ydiff2, prmids2, nprm2, covar2 = xyz2 378 | if covar1 is not covar2 or nprm1 != nprm2: 379 | raise CovarianceMissing("Need full Sinex covariance to calculate vector difference covariance") 380 | obseq = np.zeros((3, covar1.shape[0])) 381 | rows = [0, 1, 2, 0, 2, 1][:nprm1] 382 | mult = np.ones((nprm1,)) 383 | if nprm1 > 3: 384 | mult[3:] = ydiff2 385 | obseq[rows, prmids2[:nprm1]] = mult 386 | if nprm1 > 3: 387 | mult[3:] = ydiff1 388 | obseq[rows, prmids1[:nprm1]] -= mult 389 | covar = obseq.dot(covar1.dot(obseq.T)) 390 | 391 | dxyz = xyz2 - xyz1 392 | if enu: 393 | xyz = (xyz1 + xyz2) / 2.0 394 | llh = GRS80.geodetic(xyz) 395 | enu_axes = GRS80.enu_axes(llh[0], llh[1]) 396 | 397 | dxyz = enu_axes.dot(dxyz) 398 | if covariance: 399 | covar = enu_axes.dot(covar.dot(enu_axes.T)) 400 | if covariance: 401 | return dxyz, covar 402 | return dxyz 403 | 404 | def _open(self): 405 | if self._fh: 406 | raise SinexFileError("Reopening already open SINEX file handle") 407 | try: 408 | if self._filename.endswith(".gz"): 409 | self._fh = gzip.GzipFile(self._filename) 410 | else: 411 | self._fh = open(self._filename) 412 | except: 413 | raise SinexFileError("Cannot open SINEX file " + self._filename) 414 | self._lineno = 0 415 | self._scanHeader() 416 | 417 | def _close(self): 418 | self._fh.close() 419 | self._fh = None 420 | self._lineno = 0 421 | 422 | def _scan(self): 423 | self._open() 424 | sections = [] 425 | try: 426 | while True: 427 | section = self._nextSection() 428 | if section is None: 429 | break 430 | sections.append(section) 431 | if section in Reader._scanners: 432 | Reader._scanners[section](self, section) 433 | if section == "SOLUTION/ESTIMATE" and self._covarianceOption == COVAR_NONE: 434 | break 435 | else: 436 | self._skipSection(section) 437 | finally: 438 | self._close() 439 | 440 | def _readline(self): 441 | if self._fh is None: 442 | raise SinexFileError("Cannot read from unopened SINEX file " + self._filename) 443 | line = self._fh.readline() 444 | if line == "": 445 | return None 446 | self._lineno += 1 447 | return line.rstrip() 448 | 449 | def _readError(self, message): 450 | message = message + " at line " + str(self._lineno) + " of SINEX file " + self._filename 451 | raise SinexFileError(message) 452 | 453 | def _scanHeader(self): 454 | header = self._readline() 455 | match = re.match(r"^\%\=SNX\s(\d\.\d\d)", header) 456 | if match is None: 457 | raise SinexFileError("Invalid SINEX file " + self._filename + " - missing %=SNX in header") 458 | 459 | def _sinexEpoch(self, epochstr): 460 | match = self._epochre.match(epochstr) 461 | if not match: 462 | self._readError("Invalid epoch string " + epochstr) 463 | y, d, s = (int(d) for d in match.groups()) 464 | if y == 0 and d == 0 and s == 0: 465 | return None 466 | year = y + 1900 if y > 50 else y + 2000 467 | return datetime(year, 1, 1) + timedelta(days=d - 1, seconds=s) 468 | 469 | def _scanSection(self, section, recordre): 470 | """ 471 | Iterates over a section 472 | """ 473 | while True: 474 | line = self._readline() 475 | if line is None: 476 | self._readError(section + " not terminated") 477 | if line == "": 478 | continue 479 | cmdchar = line[0] 480 | if cmdchar == "*": 481 | continue 482 | if cmdchar == "-": 483 | if line[1:].strip() != section: 484 | self._readError(section + " terminated incorrectly") 485 | break 486 | if cmdchar != " ": 487 | self._readError(section + " invalid line") 488 | if recordre is not None: 489 | match = recordre.match(line) 490 | if match is None: 491 | self._readError(section + " badly formatted line: " + line) 492 | yield match 493 | 494 | def _skipSection(self, section): 495 | for match in self._scanSection(section, None): 496 | pass 497 | 498 | def _nextSection(self): 499 | section = None 500 | while True: 501 | line = self._readline() 502 | if line is None: 503 | break 504 | if line == "%ENDSNX": 505 | break 506 | if line == "": 507 | continue 508 | cmdchar = line[0] 509 | if cmdchar == "*": 510 | continue 511 | if cmdchar == " ": 512 | continue 513 | if cmdchar != "+": 514 | self._readError("Unexpected character " + cmdchar + " looking for section") 515 | section = line[1:] 516 | break 517 | return section 518 | 519 | def _wantId(self, ptid): 520 | return self._selectCodes is None or ptid in self._selectCodes 521 | 522 | def _latlon(self, llstring): 523 | negative = "-" in llstring 524 | llstring = llstring.replace("-", " ") 525 | d, m, s = (float(x) for x in llstring.split()) 526 | angle = d + m / 60.0 + s / 3600.0 527 | if negative: 528 | angle = -angle 529 | return angle 530 | 531 | def _scanSiteId(self, section): 532 | recordre = re.compile( 533 | r"""^ 534 | \s([\s\w]{4}) # point id 535 | \s([\s\w]{2}) # point code 536 | \s(.{9}) # monument id 537 | \s[\w\s] # Observation techniquesl 538 | \s(.{22}) # description 539 | \s([\s\-\d]{3}[\-\s\d]{3}[\-\s\d\.]{5}) # longitude DMS 540 | \s([\s\-\d]{3}[\-\s\d]{3}[\-\s\d\.]{5}) # latitude DMS 541 | \s([\s\d\.\-]{7}) # height 542 | \s*$""", 543 | re.IGNORECASE | re.VERBOSE, 544 | ) 545 | 546 | sites = {} 547 | for match in self._scanSection(section, recordre): 548 | ptid, ptcode, ptname, description, lon, lat, hgt = (x.strip() for x in match.groups()) 549 | if self._wantId(ptid): 550 | llh = (self._latlon(lon), self._latlon(lat), float(hgt)) 551 | sites[ptid, ptcode] = Reader.Site(ptid, ptcode, ptname, description, llh) 552 | self._sites = sites 553 | 554 | def _scanSolutionStatistics(self, section): 555 | recordre = re.compile( 556 | r"""^ 557 | \s(.{0,30}) # Statisics item 558 | \s(.{0,22}) # Statisics value 559 | \s*$""", 560 | re.IGNORECASE | re.VERBOSE, 561 | ) 562 | 563 | stats = {} 564 | for match in self._scanSection(section, recordre): 565 | stats[match.group(1).strip()] = match.group(2).strip() 566 | self._stats = stats 567 | 568 | def _scanSolutionEpoch(self, section): 569 | recordre = re.compile( 570 | r"""^ 571 | \s([\s\w]{4}) # point id 572 | \s([\s\w]{2}) # point code 573 | \s([\s\w\-]{4}) # solution id 574 | \s([\w|\s]) # could be blank 575 | \s(\d\d\:\d\d\d\:\d\d\d\d\d) # start epoch 576 | \s(\d\d\:\d\d\d\:\d\d\d\d\d) # end epoch 577 | \s(\d\d\:\d\d\d\:\d\d\d\d\d) # mean epoch 578 | \s*$""", 579 | re.IGNORECASE | re.VERBOSE, 580 | ) 581 | 582 | epochs = {} 583 | for match in self._scanSection(section, recordre): 584 | ptid, ptcode, solnid, skip, start, end, mean = (x.strip() for x in match.groups()) 585 | starttime = self._sinexEpoch(start) 586 | endtime = self._sinexEpoch(end) 587 | meantime = self._sinexEpoch(mean) 588 | epochs[ptid, ptcode, solnid] = Reader.Epoch(ptid, ptcode, solnid, starttime, endtime, meantime) 589 | self._epochs = epochs 590 | 591 | def _scanSolutionEstimate(self, section): 592 | recordre = re.compile( 593 | r"""^ 594 | \s([\s\d]{5}) # param id 595 | \s([\s\w]{6}) # param type 596 | \s([\s\w]{4}|[\s\-]{4}) # point id 597 | \s([\s\w]{2}|[\s\-]{2}) # point code 598 | \s([\s\w]{4}|[\s\-]{4}) # solution id 599 | \s(\d\d\:\d\d\d\:\d\d\d\d\d) #parameter epoch 600 | \s([\s\w\/]{4}) # param units 601 | \s([\s\w]{1}) # param constraints 602 | \s([\s\dE\+\-\.]{21}) # param value 603 | \s([\s\dE\+\-\.]{11}) # param stddev 604 | \s*$""", 605 | re.IGNORECASE | re.VERBOSE, 606 | ) 607 | 608 | coords = {} 609 | prmlookup = {} 610 | 611 | usevel = self._options.get("velocities", True) 612 | useprms = ("STAX", "STAY", "STAZ", "VELX", "VELY", "VELZ") if usevel else ("STAX", "STAY", "STAZ") 613 | nprm = 6 if usevel else 3 614 | covarOption = self._covarianceOption 615 | nextprm = 0 616 | nextcvr = 0 617 | 618 | for match in self._scanSection(section, recordre): 619 | ( 620 | prmid, 621 | prmtype, 622 | ptid, 623 | ptcode, 624 | solnid, 625 | epoch, 626 | units, 627 | constraint, 628 | value, 629 | stddev, 630 | ) = (x.strip() for x in match.groups()) 631 | if not self._wantId(ptid): 632 | continue 633 | if prmtype not in useprms: 634 | continue 635 | prmno = useprms.index(prmtype) 636 | prmtime = self._sinexEpoch(epoch) 637 | prmid = int(prmid) 638 | solnid = Reader.SolutionId(ptid, ptcode, solnid) 639 | if solnid not in coords: 640 | prmids = (nextcvr, (list(range(nextprm, nextprm + nprm)))) 641 | if covarOption == COVAR_FULL: 642 | nextprm += nprm 643 | elif covarOption == COVAR_STATION: 644 | nextcvr += 1 645 | vxyz = np.zeros((3,)) if usevel else None 646 | coord = Reader.Coordinate(ptid, ptcode, solnid, prmtime, np.zeros((3,)), vxyz, prmids) 647 | coords[solnid] = coord 648 | coord = coords[solnid] 649 | if prmtime != coord.crddate: 650 | self._readError("Inconsistent parameter epoch") 651 | prmlookup[prmid] = (coord.prmids[0], coord.prmids[1][prmno]) 652 | if prmno < 3: 653 | coord.xyz[prmno] = float(value) 654 | else: 655 | coord.vxyz[prmno - 3] = float(value) 656 | 657 | self._coords = coords 658 | self._prmlookup = prmlookup 659 | 660 | def _scanSolutionMatrixEstimate(self, section): 661 | recordre = re.compile( 662 | r"""^ 663 | \s([\s\d]{5}) 664 | \s([\s\d]{5}) 665 | \s([\s\dE\+\-\.]{21}) 666 | (?:\s([\s\dE\+\-\.]{21}))? 667 | (?:\s([\s\dE\+\-\.]{21}))? 668 | \s*$""", 669 | re.IGNORECASE | re.VERBOSE, 670 | ) 671 | 672 | if self._covarianceOption == COVAR_NONE: 673 | self._skipSection(section) 674 | return 675 | 676 | ncvr = 0 677 | nprm = 0 678 | for cvr, prm in list(self._prmlookup.values()): 679 | if cvr > ncvr: 680 | ncvr = cvr 681 | if prm > nprm: 682 | nprm = prm 683 | ncvr += 1 684 | nprm += 1 685 | covariances = [] 686 | for i in range(ncvr): 687 | covariances.append(np.zeros((nprm, nprm))) 688 | prmlookup = self._prmlookup 689 | 690 | for match in self._scanSection(section, recordre): 691 | prmid1 = int(match.group(1).strip()) 692 | if prmid1 not in prmlookup: 693 | continue 694 | prms1 = prmlookup[prmid1] 695 | ncvr = prms1[0] 696 | nprm1 = prms1[1] 697 | 698 | prmid2 = int(match.group(2).strip()) 699 | for i in range(3): 700 | cvrval = match.group(3 + i) 701 | if cvrval is None: 702 | break 703 | cvrval = cvrval.strip() 704 | if cvrval == "": 705 | break 706 | if prmid2 in prmlookup: 707 | prms2 = prmlookup[prmid2] 708 | if prms2[0] == ncvr: 709 | nprm2 = prms2[1] 710 | cvrval = float(cvrval) 711 | covariances[ncvr][nprm1, nprm2] = cvrval 712 | if nprm1 != nprm2: 713 | covariances[ncvr][nprm2, nprm1] = cvrval 714 | prmid2 += 1 715 | 716 | self._covariances = covariances 717 | 718 | 719 | Reader._scanners = { 720 | "SITE/ID": Reader._scanSiteId, 721 | "SOLUTION/STATISTICS": Reader._scanSolutionStatistics, 722 | "SOLUTION/EPOCHS": Reader._scanSolutionEpoch, 723 | "SOLUTION/ESTIMATE": Reader._scanSolutionEstimate, 724 | "SOLUTION/MATRIX_ESTIMATE L COVA": Reader._scanSolutionMatrixEstimate, 725 | "SOLUTION/MATRIX_ESTIMATE U COVA": Reader._scanSolutionMatrixEstimate, 726 | } 727 | 728 | 729 | class Writer(object): 730 | """ 731 | The Sinex.Writer class is most simply used as a context manager. The usage is 732 | with Sinex.Writer(filename) as snx: 733 | snx.addFileInfo(...) 734 | snx.setObsDateRange(startdate,enddate) 735 | snx.setSolutionStatistics(varianceFactor,sumSquaredResiduals,nObs,nUnknowns ) 736 | for m in marks: 737 | snx.addMark(m.code, ... ) 738 | snx.addSolution(m,xyz,xyzprms) 739 | snx.setCovariance(covar) 740 | Alternatively can be used as an ordinary object, in which case a final call 741 | to snx.write() is required to create the file. 742 | """ 743 | 744 | # For the future - make consistent with Reader, make a Sinex object that can be 745 | # processed after reading or constructing... 746 | 747 | Mark = namedtuple("Mark", "mark id code monument description solutions") 748 | Solution = namedtuple("Solution", "solnid xyz params vxyz vparams startdate enddate crddate") 749 | 750 | def __init__(self, filename, **info): 751 | self._filename = filename 752 | self._fileRefInfo = {} 753 | self._comments = [] 754 | self._written = False 755 | self._agency = "---" 756 | self._version = "2.10" 757 | self._constraint = "2" 758 | self._stats = None 759 | self._startdate = None 760 | self._enddate = None 761 | self._marks = {} 762 | self._covariance = None 763 | self._varfactor = 1.0 764 | self.addFileInfo(**info) 765 | 766 | def __enter__(self): 767 | return self 768 | 769 | def __exit__(self, exctype, value, traceback): 770 | if exctype is None: 771 | if not self._written: 772 | self.write() 773 | 774 | def addFileInfo(self, **info): 775 | """ 776 | Add file header information, any of: 777 | description 778 | output 779 | contact 780 | software 781 | hardware 782 | input 783 | agency 784 | comment 785 | constraint 786 | """ 787 | for item, value in info.items(): 788 | item = item.upper() 789 | if item in ( 790 | "DESCRIPTION", 791 | "OUTPUT", 792 | "CONTACT", 793 | "SOFTWARE", 794 | "HARDWARE", 795 | "INPUT", 796 | ): 797 | self._fileRefInfo[item] = value 798 | elif item == "COMMENT": 799 | self._comments.extend(value.split("\n")) 800 | elif item == "AGENCY": 801 | self._agency = value.upper()[:3] 802 | elif item == "CONSTRAINT": 803 | if value not in ("0", "1", "2"): 804 | raise SinexFileError("Invalid SINEX constraint code " + value + " specified") 805 | self._constraint = value 806 | 807 | def setSolutionStatistics(self, varianceFactor, sumSquaredResiduals, nObs, nUnknowns): 808 | """ 809 | Set the solution statistics values 810 | variance factor 811 | sum of squared residuals 812 | number of observations 813 | number of unknowns 814 | """ 815 | self.stats = [varianceFactor, sumSquaredResiduals, nObs, nUnknowns] 816 | 817 | def setObsDateRange(self, startdate, enddate): 818 | """ 819 | Set the date range of the observations 820 | """ 821 | self._startdate = startdate 822 | self._enddate = enddate 823 | 824 | def addMark(self, mark, id=None, code=None, monument=None, description=None): 825 | """ 826 | Add a mark to the solution. Can specify the id, code, monument name, and 827 | description used to identify the mark 828 | """ 829 | id = (id or mark).upper()[:4] 830 | code = (code or "A")[:2] 831 | monument = (monument or mark).upper()[:9] 832 | description = description or "" 833 | self._marks[mark] = Writer.Mark(mark, id, code, monument, description, []) 834 | 835 | def addSolution( 836 | self, 837 | mark, 838 | xyz, 839 | params, 840 | vxyz=None, 841 | vparams=None, 842 | startdate=None, 843 | enddate=None, 844 | crddate=None, 845 | ): 846 | """ 847 | Add a coordinate solution to the sinex file. Specifies: 848 | mark (which must have been added with addMark), 849 | xyz coordinate components [x,y,z], 850 | params efining the row numbers of each ordinate in the covariance matrix (px,py,pz), 851 | vxyz (optional) velocity components 852 | vparams (optional) parameter numbers of velocity components 853 | startdate (optional) start date of applicability of the solution 854 | enddate (optional) end date of applicability of the solution 855 | """ 856 | if mark not in self._marks: 857 | raise SinexFileError("Cannot add solution for " + mark + " to SINEX file: mark not defined") 858 | if len(xyz) != 3 or len(params) != 3: 859 | raise SinexFileError("Invalid coordinate xyz or params specified for mark " + mark) 860 | params = list(params) 861 | if vxyz is not None: 862 | if len(vxyz) != 3 or len(vparams) != 3: 863 | raise SinexFileError("Invalid coordinate xyz or params specified for mark " + mark) 864 | vparams = list(vparams) 865 | else: 866 | vparams = None 867 | 868 | solnid = "{0:04d}".format(len(self._marks[mark].solutions) + 1) 869 | self._marks[mark].solutions.append(Writer.Solution(solnid, xyz, params, vxyz, vparams, startdate, enddate, crddate)) 870 | 871 | def setCovariance(self, covariance, varianceFactor=1.0): 872 | """ 873 | Define the covariance matrix for the ordinates. (numpy nxn array) 874 | """ 875 | self._covariance = np.array(covariance) 876 | self._varfactor = varianceFactor 877 | 878 | def _sinexEpoch(self, date=None): 879 | if date is None: 880 | date = datetime.now() 881 | diff = date - datetime(date.year, 1, 1) 882 | year = date.year - 1900 883 | if year > 99: 884 | year -= 100 885 | return "{0:02d}:{1:03d}:{2:05d}".format(year, diff.days + 1, diff.seconds) 886 | 887 | def _sinexDms(self, angle): 888 | sign = -1 if angle < 0 else 1 889 | angle = abs(angle) 890 | angle += 1.0 / (3600 * 10) # For rounding seconds to 1dp 891 | deg = int(angle) 892 | angle = (angle - deg) * 60 893 | min = int(angle) 894 | angle = (angle - min) * 60 895 | angle -= 0.1 # Finish rounding fix 896 | if angle < 0: 897 | angle = 0.0 898 | if deg > 0: 899 | deg *= sign 900 | elif min > 0: 901 | min *= sign 902 | else: 903 | angle *= sign 904 | return "{0:3d}{1:3d}{2:5.1f}".format(deg, min, angle) 905 | 906 | def write(self): 907 | """ 908 | Write the file - can only be called once. If Writer is used as a context manager 909 | this is called when the context closes. 910 | """ 911 | if self._written: 912 | raise SinexFileError("Cannot write already written SINEX file") 913 | if len(self._covariance) is None: 914 | raise SinexFileError("Cannot write SINEX file: no covariance matrix defined") 915 | # After this point supplied data may be modified! 916 | self._written = True 917 | marks = [] 918 | solutions = [] 919 | covarprms = [-1] 920 | marklist = {} 921 | prmid = 0 922 | for m in sorted(list(self._marks.values()), key=lambda m: (m.id, m.code, m.monument)): 923 | if len(m.solutions) == 0: 924 | continue 925 | key = (m.id, m.code, m.monument) 926 | if key in marklist: 927 | raise SinexFileError("Duplicate mark ({0} {1} {2} in SINEX file".format(*key)) 928 | marklist[key] = 1 929 | marks.append(m) 930 | for s in m.solutions: 931 | for i, p in enumerate(s.params): 932 | covarprms.append(p) 933 | prmid += 1 934 | s.params[i] = prmid 935 | if s.vparams is not None: 936 | for i, p in enumerate(s.params): 937 | covarprms.append(p) 938 | prmid += 1 939 | s.params[i] = prmid 940 | 941 | if prmid == 0: 942 | raise SinexFileError("Cannot write SINEX file: no solution coordinates defined") 943 | 944 | startdate = self._startdate or datetime.now() 945 | enddate = self._enddate or datetime.now() 946 | varf = self._varfactor 947 | 948 | with open(self._filename, "w") as sf: 949 | sf.write( 950 | "%=SNX {0:4.4s} {1:3.3s} {2} {1:3.3s} {3} {4} C {5:5d} {6} S\n".format( 951 | self._version, 952 | self._agency, 953 | self._sinexEpoch(), 954 | self._sinexEpoch(startdate), 955 | self._sinexEpoch(enddate), 956 | prmid, 957 | self._constraint, 958 | ) 959 | ) 960 | 961 | sf.write("+FILE/REFERENCE\n") 962 | for item in ( 963 | "DESCRIPTION", 964 | "OUTPUT", 965 | "CONTACT", 966 | "SOFTWARE", 967 | "HARDWARE", 968 | "INPUT", 969 | ): 970 | sf.write(" {0:18.18s} {1:.60s}\n".format(item, self._fileRefInfo.get(item, "") or "N/A")) 971 | sf.write("-FILE/REFERENCE\n") 972 | 973 | if len(self._comments) > 0: 974 | sf.write("+FILE/COMMENT\n") 975 | for c in self.comments: 976 | sf.write(" {0:.79s}\n".format(c)) 977 | sf.write("-FILE/COMMENT\n") 978 | 979 | sf.write("+SITE/ID\n") 980 | for m in marks: 981 | xyz = m.solutions[0].xyz 982 | lon, lat, hgt = GRS80.geodetic(xyz) 983 | if lon < 0: 984 | lon += 360 985 | sf.write( 986 | " {0:4.4s} {1:2.2s} {2:9.9s} C {3:22.22s} {4} {5} {6:7.1f}\n".format( 987 | m.id, 988 | m.code, 989 | m.monument, 990 | m.description, 991 | self._sinexDms(lon), 992 | self._sinexDms(lat), 993 | hgt, 994 | ) 995 | ) 996 | 997 | sf.write("-SITE/ID\n") 998 | 999 | # sf.write("+SITE/DATA\n") 1000 | # sf.write("-SITE/DATA\n") 1001 | 1002 | sf.write("+SOLUTION/EPOCHS\n") 1003 | for m in marks: 1004 | for s in m.solutions: 1005 | startdate = s.startdate or startdate 1006 | enddate = s.enddate or enddate 1007 | diff = enddate - startdate 1008 | middate = startdate + timedelta(seconds=diff.total_seconds() / 2) 1009 | sf.write( 1010 | " {0:4.4s} {1:2.2s} {2:4.4s} C {3} {4} {5}\n".format( 1011 | m.id, 1012 | m.code, 1013 | s.solnid, 1014 | self._sinexEpoch(startdate), 1015 | self._sinexEpoch(enddate), 1016 | self._sinexEpoch(middate), 1017 | ) 1018 | ) 1019 | sf.write("-SOLUTION/EPOCHS\n") 1020 | 1021 | if self.stats is not None: 1022 | sf.write("+SOLUTION/STATISTICS\n") 1023 | sf.write(" {0:30.30s} {1:22.15e}\n".format("VARIANCE FACTOR", self.stats[0])) 1024 | sf.write(" {0:30.30s} {1:22.15e}\n".format("SUM OR SQUARED RESIDUALS", self.stats[1])) 1025 | sf.write(" {0:30.30s} {1:22d}\n".format("NUMBER OF OBSERVATIONS", self.stats[2])) 1026 | sf.write(" {0:30.30s} {1:22d}\n".format("NUMBER OF UNKNOWNS", self.stats[3])) 1027 | sf.write("-SOLUTION/STATISTICS\n") 1028 | 1029 | sf.write("+SOLUTION/ESTIMATE\n") 1030 | crds = ("X", "Y", "Z") 1031 | for m in marks: 1032 | for s in m.solutions: 1033 | startdate = s.startdate or startdate 1034 | enddate = s.enddate or enddate 1035 | diff = enddate - startdate 1036 | crddate = s.crddate or startdate + timedelta(seconds=diff.total_seconds() / 2) 1037 | for crd, value, prmno in zip(crds, s.xyz, s.params): 1038 | cvrprm = covarprms[prmno] 1039 | stderr = math.sqrt(varf * self._covariance[cvrprm, cvrprm]) 1040 | sf.write( 1041 | " {0:5d} STA{1:1.1s} {2:4.4s} {3:2.2s} {4:4.4s} {5} {6:4.4s} {7:1.1s} {8:21.14e} {9:11.5e}\n".format( 1042 | prmno, 1043 | crd, 1044 | m.id, 1045 | m.code, 1046 | s.solnid, 1047 | self._sinexEpoch(crddate), 1048 | "m", 1049 | "2", 1050 | value, 1051 | stderr, 1052 | ) 1053 | ) 1054 | 1055 | sf.write("-SOLUTION/ESTIMATE\n") 1056 | 1057 | sf.write("+SOLUTION/MATRIX_ESTIMATE L COVA\n") 1058 | for i in range(1, prmid + 1): 1059 | ci = covarprms[i] 1060 | for j in range(1, i + 1): 1061 | if j % 3 == 1: 1062 | if j > 1: 1063 | sf.write("\n") 1064 | sf.write(" {0:5d} {1:5d}".format(i, j)) 1065 | cj = covarprms[j] 1066 | sf.write(" {0:21.14e}".format(varf * self._covariance[ci, cj])) 1067 | sf.write("\n") 1068 | sf.write("-SOLUTION/MATRIX_ESTIMATE L COVA\n") 1069 | 1070 | sf.write("%ENDSNX\n") 1071 | -------------------------------------------------------------------------------- /LINZ/Geodetic/__init__.py: -------------------------------------------------------------------------------- 1 | # Namespace package - __init__.py cannot contain anything else 2 | __import__("pkg_resources").declare_namespace(__name__) 3 | -------------------------------------------------------------------------------- /LINZ/__init__.py: -------------------------------------------------------------------------------- 1 | # Namespace package - __init__.py cannot contain anything else 2 | __import__("pkg_resources").declare_namespace(__name__) 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include DESCRIPTION.rst 2 | include CHANGELOG.txt 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | LINZ.geodetic package 2 | ======================= 3 | 4 | Generic geodetic functions used by other LINZ modules. 5 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-linz-geodetic (1.9.0-1linz~trusty1) trusty; urgency=medium 2 | 3 | * Fixing error ITRF2014 parameters 4 | * Adding ITRF2020 parameters 5 | 6 | -- Chris Crook Tue, 28 May 2024 09:00:00 +1300 7 | 8 | python-linz-geodetic (1.8.1-1linz~trusty1) trusty; urgency=medium 9 | 10 | * Fixing error in Sinex.Writer end date 11 | 12 | -- Chris Crook Fri, 29 Apr 2021 13:00:00 +1300 13 | 14 | python-linz-geodetic (1.7.1-1linz~trusty1) trusty; urgency=medium 15 | 16 | * Updating to Debian 3.0 packaging format 17 | 18 | -- Ivan Mincik Thu, 14 Apr 2016 13:57:11 +1200 19 | -------------------------------------------------------------------------------- /debian/clean: -------------------------------------------------------------------------------- 1 | linz_geodetic.egg-info/* 2 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-linz-geodetic 2 | Maintainer: Chris Crook 3 | Section: python 4 | X-Python-Version: >= 3.5 5 | Priority: optional 6 | Build-Depends: 7 | python3-all, 8 | python3-setuptools, 9 | debhelper (>= 9) 10 | Standards-Version: 3.9.1 11 | 12 | Package: python3-linz-geodetic 13 | Architecture: all 14 | Depends: ${misc:Depends}, ${python3:Depends} 15 | Description: Python LINZ geodetic 16 | Provides some base geodetic functions. 17 | . 18 | This package installs the library for Python 3. 19 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright: 2 | 3 | Copyright 2011 Crown copyright (c) Land Information New Zealand and the New 4 | Zealand Government. All rights reserved 5 | 6 | License: 7 | 8 | This software is provided as a free download under the 3-clause BSD License 9 | as follows: 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 3. Neither the name of Land Information New Zealand nor any of its contributors 20 | may be used to endorse or promote products derived from this software 21 | without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY LAND INFORMATION NEW ZEALAND AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | ARE DISCLAIMED. IN NO EVENT SHALL LAND INFORMATION NEW ZEALAND OR THE NEW 27 | ZEALAND GOVERNMENT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 32 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 33 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # This file was automatically generated by stdeb 0.6.0+git at 4 | # Sat, 27 Jun 2015 07:45:15 +1200 5 | 6 | %: 7 | dh $@ --with python3 --buildsystem=pybuild 8 | 9 | 10 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | # This flag says that the code is written to work on both Python 2 and Python 3 | # 3. If at all possible, it is good practice to do this. If you cannot, you 4 | # will need to generate wheels for each Python version that you support. 5 | universal=1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """linz-geodetic setup module 2 | """ 3 | 4 | # Comments from samply python packaging script 5 | 6 | # Always prefer setuptools over distutils 7 | from setuptools import setup, find_packages 8 | 9 | # To use a consistent encoding 10 | from codecs import open 11 | from os import path 12 | 13 | here = path.abspath(path.dirname(__file__)) 14 | 15 | # Get the long description from the relevant file 16 | with open(path.join(here, "DESCRIPTION.rst"), encoding="utf-8") as f: 17 | long_description = f.read() 18 | 19 | setup( 20 | name="linz-geodetic", 21 | # Versions should comply with PEP440. For a discussion on single-sourcing 22 | # the version across setup.py and the project code, see 23 | # https://packaging.python.org/en/latest/single_source_version.html 24 | version="1.0.0", 25 | description="LINZ.geodetic module - generic geodetic functions", 26 | long_description=long_description, 27 | # The project's main homepage. 28 | url="https://github.com/linz/python-linz-geodetic", 29 | # Author details 30 | author="Chris Crook", 31 | author_email="ccrook@linz.govt.nz", 32 | # Choose your license 33 | license="MIT", 34 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 35 | classifiers=[ 36 | # How mature is this project? Common values are 37 | # 3 - Alpha 38 | # 4 - Beta 39 | # 5 - Production/Stable 40 | "Development Status :: 5 - Stable", 41 | # Indicate who your project is intended for 42 | "Intended Audience :: LINZ coders", 43 | "Topic :: Geodetic", 44 | # Pick your license as you wish (should match "license" above) 45 | "License :: OSI Approved :: GPL License", 46 | # Specify the Python versions you support here. In particular, ensure 47 | # that you indicate whether you support Python 2, Python 3 or both. 48 | "Programming Language :: Python :: 2", 49 | "Programming Language :: Python :: 2.7", 50 | ], 51 | # What does your project relate to? 52 | keywords="geodesy", 53 | # You can just specify the packages manually here if your project is 54 | # simple. Or you can use find_packages(). 55 | packages=find_packages(), 56 | # List run-time dependencies here. These will be installed by pip when 57 | # your project is installed. For an analysis of "install_requires" vs pip's 58 | # requirements files see: 59 | # https://packaging.python.org/en/latest/requirements.html 60 | install_requires=["numpy"], 61 | # List additional groups of dependencies here (e.g. development 62 | # dependencies). You can install these using the following syntax, 63 | # for example: 64 | # $ pip install -e .[dev,test] 65 | extras_require={}, 66 | # If there are data files included in your packages that need to be 67 | # installed, specify them here. If using Python 2.6 or less, then these 68 | # have to be included in MANIFEST.in as well. 69 | package_data={}, 70 | # Namespace package - other modules may include into these packages 71 | namespace_packages=["LINZ", "LINZ.Geodetic"], 72 | # To provide executable scripts, use entry points in preference to the 73 | # "scripts" keyword. Entry points provide cross-platform support and allow 74 | # pip to create the appropriate form of executable for the target platform. 75 | entry_points={ 76 | "console_scripts": [ 77 | "ellipsoid=LINZ.Geodetic.Ellipsoid:main", 78 | ], 79 | }, 80 | ) 81 | -------------------------------------------------------------------------------- /tests/fileunittest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import os.path 4 | import re 5 | import unittest 6 | import numpy as np 7 | from numpy import array 8 | 9 | # Set output options for dumping numpy data 10 | np.set_printoptions(precision=10, suppress=True) 11 | 12 | # Floating point test tolerance 13 | defaultDelta = 1.0e-8 14 | 15 | # Set True to write the results from all tests to stdout 16 | dumpResults = False 17 | dumpFileReset = False 18 | try: 19 | stringtype = str 20 | except NameError: 21 | stringtype = str 22 | 23 | 24 | class TestCase(unittest.TestCase): 25 | """ 26 | Subclass of unittest.TestCase to support generation and use of a file 27 | of test results. Basic structure of a test module using this is: 28 | 29 | from LINZ import fileunittest 30 | 31 | class MyTestCase( fileunittest.TestCase ): 32 | 33 | def test_001( self ): 34 | result=calc_result() 35 | self.check('test 001 result',result) 36 | 37 | def test002( self ): 38 | if notgood(): 39 | self.reportFail('Test 2 failed') 40 | 41 | if __name__ == "__main__": 42 | fileunittest.main() 43 | 44 | Run with the --dump option to create a file with the same name as the 45 | test module but extension .results.new Run without --dump to test against 46 | file .results. 47 | """ 48 | 49 | resultsFile = None 50 | dumpFile = None 51 | testResults = {} 52 | 53 | @classmethod 54 | def setUpClass(cls): 55 | import inspect 56 | 57 | clsfile = inspect.getfile(cls) 58 | TestCase.resultsFile = os.path.splitext(clsfile)[0] + ".results" 59 | TestCase.dumpFile = os.path.splitext(clsfile)[0] + ".results.new" 60 | data = "" 61 | try: 62 | with open(TestCase.resultsFile) as rf: 63 | data = rf.read() 64 | except: 65 | pass 66 | resultRe = re.compile( 67 | r"^\>\>\>\>\s+(\w+)\s*(.*?)(?=^\>\>\>\>)", re.MULTILINE | re.DOTALL 68 | ) 69 | for test, result in resultRe.findall(data): 70 | TestCase.testResults[test] = result.strip() 71 | 72 | def setUp(self): 73 | global dumpResults 74 | global dumpFileReset 75 | self.dumpfh = None 76 | if dumpResults: 77 | if not dumpFileReset: 78 | if os.path.exists(self.dumpFile): 79 | os.unlink(self.dumpFile) 80 | dumpFileReset = True 81 | self.dumpfh = open(self.dumpFile, "a") 82 | self.dumpfh.write(("=" * 70) + "\n") 83 | 84 | def tearDown(self): 85 | if self.dumpfh: 86 | self.dumpfh.write(">>>>\n") 87 | self.dumpfh.close() 88 | 89 | def check(self, testname, output, message=None, delta=None): 90 | """ 91 | Call to check that output matches the value for testname in the .results 92 | file. message is the error message if it fails. delta is a numerical 93 | tolerance for floating point tests. delta is used in the unittest.assertAlmostEqual 94 | call. 95 | """ 96 | global dumpResults 97 | testcode = testname.lower() 98 | testcode = re.sub(r"\s+", "_", testcode) 99 | testcode = re.sub(r"\W", "_", testcode) 100 | if dumpResults: 101 | self.dumpfh.write(">>>> " + testcode + " ") 102 | self.dumpfh.write( 103 | output if isinstance(output, stringtype) else repr(output) 104 | ) 105 | self.dumpfh.write("\n") 106 | else: 107 | message = message or testname + " incorrect" 108 | expected = self.testResults.get(testcode) 109 | if not isinstance(output, str): 110 | try: 111 | expected = eval(expected) 112 | except: 113 | output = repr(output) 114 | self.checkEqual(output, expected, message, delta) 115 | 116 | def checkRun(self, testname, function, message=None, delta=None): 117 | # Run a function with no parameters and either check the output 118 | # or check the error generated. 119 | try: 120 | self.check(testname, function(), message, delta) 121 | except Exception as ex: 122 | self.check(testname, ex, message, delta) 123 | 124 | def checkEqual(self, output, expected, message, delta): 125 | # Equality check with tolerance on floating point numbers and handling 126 | # of numpy arrays 127 | global defaultDelta 128 | delta = delta or defaultDelta 129 | if isinstance(output, np.ndarray): 130 | error = np.max(np.abs(output - expected)) 131 | if error > delta: 132 | self.fail(message + " max diff {0:.3e}".format(error)) 133 | elif isinstance(output, float): 134 | message = message + " ({0} != {1})".format(output, expected) 135 | self.assertAlmostEqual(output, expected, msg=message, delta=delta) 136 | elif isinstance(output, str): 137 | output = output.strip() 138 | if isinstance(expected, str): 139 | expected = expected.strip() 140 | if output != expected: 141 | self.fail(message + " ({0} != {1})".format(output, expected)) 142 | elif isinstance(output, dict): 143 | if not message.endswith("]"): 144 | message = message + " " 145 | for k in list(expected.keys()): 146 | self.checkEqual( 147 | output[k], expected[k], message + "[{0}]".format(k), delta 148 | ) 149 | elif hasattr(output, "__getitem__"): 150 | if not message.endswith("]"): 151 | message = message + " " 152 | for i, e in enumerate(expected): 153 | o = output[i] 154 | self.checkEqual(o, e, message + "[{0}]".format(i), delta) 155 | else: 156 | self.assertEqual(output, expected, msg=message) 157 | 158 | def reportFail(self, message): 159 | """ 160 | Function to directly report a failed test 161 | """ 162 | global dumpResults 163 | if not dumpResults: 164 | self.fail(message) 165 | 166 | 167 | def main(): 168 | """ 169 | Main function called from sublclasses to run the tests directly 170 | """ 171 | global dumpResults 172 | global dumpFileReset 173 | if "--dump" in sys.argv: 174 | sys.argv.remove("--dump") 175 | dumpResults = True 176 | dumpFileReset = False 177 | print("**** Dumping output - not running tests!") 178 | unittest.main() 179 | -------------------------------------------------------------------------------- /tests/test.snx: -------------------------------------------------------------------------------- 1 | %=SNX 2.01 LNZ 16:336:81780 IGS 16:331:00000 16:332:00000 P 00012 1 S 2 | *------------------------------------------------------------------------------- 3 | +FILE/REFERENCE 4 | *INFO_TYPE_________ INFO________________________________________________________ 5 | DESCRIPTION Land Information New Zealand 6 | OUTPUT Solution generated by LINZ PositioNZ-PP service 7 | CONTACT positionz@linz.govt.nz 8 | SOFTWARE Bernese GNSS Software Version 5.2 9 | HARDWARE PositioNZ-PP processor 10 | INPUT IGS/IGLOS GNSS tracking data, PositioNZ station data 11 | -FILE/REFERENCE 12 | *------------------------------------------------------------------------------- 13 | +INPUT/ACKNOWLEDGMENTS 14 | *AGY DESCRIPTION________________________________________________________________ 15 | LNZ Land Information New Zealand (positionz@linz.govt.nz) 16 | IGS International GNSS Service 17 | -INPUT/ACKNOWLEDGMENTS 18 | *------------------------------------------------------------------------------- 19 | +SOLUTION/STATISTICS 20 | *_STATISTICAL PARAMETER________ __VALUE(S)____________ 21 | NUMBER OF OBSERVATIONS 64432 22 | NUMBER OF UNKNOWNS 104 23 | NUMBER OF DEGREES OF FREEDOM 64328 24 | PHASE MEASUREMENTS SIGMA 0.00100 25 | SAMPLING INTERVAL (SECONDS) 30 26 | VARIANCE FACTOR 2.531262866845353 27 | -SOLUTION/STATISTICS 28 | *------------------------------------------------------------------------------- 29 | +SITE/ID 30 | *CODE PT __DOMES__ T _STATION DESCRIPTION__ APPROX_LON_ APPROX_LAT_ _APP_H_ 31 | 1163 A M P 173 41 48.8 -42 25 13.8 122.6 32 | KAIK A M P 173 32 1.2 -42 25 31.7 315.5 33 | NLSN A M P 173 26 1.4 -41 11 0.6 302.1 34 | WGTN A M P 174 48 21.2 -41 19 24.4 26.0 35 | -SITE/ID 36 | *------------------------------------------------------------------------------- 37 | +SITE/RECEIVER 38 | *SITE PT SOLN T DATA_START__ DATA_END____ DESCRIPTION_________ S/N__ FIRMWARE___ 39 | 1163 A 1 P 16:331:00000 16:331:86370 TRIMBLE NETR9 ----- ----------- 40 | KAIK A 1 P 16:331:00000 16:331:86370 TRIMBLE NETR9 ----- ----------- 41 | NLSN A 1 P 16:331:00000 16:331:86370 TRIMBLE NETR9 ----- ----------- 42 | WGTN A 1 P 16:331:00000 16:331:86370 TRIMBLE NETR9 ----- ----------- 43 | -SITE/RECEIVER 44 | *------------------------------------------------------------------------------- 45 | +SITE/ANTENNA 46 | *SITE PT SOLN T DATA_START__ DATA_END____ DESCRIPTION_________ S/N__ 47 | 1163 A 1 P 16:331:00000 16:331:86370 TRM57971.00 NONE ----- 48 | KAIK A 1 P 16:331:00000 16:331:86370 TRM57971.00 NONE ----- 49 | NLSN A 1 P 16:331:00000 16:331:86370 TRM57971.00 NONE ----- 50 | WGTN A 1 P 16:331:00000 16:331:86370 TRM57971.00 NONE ----- 51 | -SITE/ANTENNA 52 | *------------------------------------------------------------------------------- 53 | +SITE/GPS_PHASE_CENTER 54 | * UP____ NORTH_ EAST__ UP____ NORTH_ EAST__ 55 | *DESCRIPTION_________ S/N__ L1->ARP(M)__________ L2->ARP(M)__________ 56 | TRM57971.00 NONE ----- 0.0668 0.0011 -.0003 0.0578 0.0001 0.0007 IGS08_1924 57 | -SITE/GPS_PHASE_CENTER 58 | *------------------------------------------------------------------------------- 59 | +SITE/ECCENTRICITY 60 | * UP______ NORTH___ EAST____ 61 | *SITE PT SOLN T DATA_START__ DATA_END____ AXE ARP->BENCHMARK(M)_________ 62 | 1163 A 1 P 16:331:00000 16:331:86370 UNE 1.3260 0.0000 0.0000 63 | KAIK A 1 P 16:331:00000 16:331:86370 UNE 0.0550 0.0000 0.0000 64 | NLSN A 1 P 16:331:00000 16:331:86370 UNE 0.0550 0.0000 0.0000 65 | WGTN A 1 P 16:331:00000 16:331:86370 UNE 0.0550 0.0000 0.0000 66 | -SITE/ECCENTRICITY 67 | *------------------------------------------------------------------------------- 68 | +SOLUTION/EPOCHS 69 | *CODE PT SOLN T _DATA_START_ __DATA_END__ _MEAN_EPOCH_ 70 | 1163 A 1 P 16:331:00000 16:331:86370 16:331:43185 71 | KAIK A 1 P 16:331:00000 16:331:86370 16:331:43185 72 | NLSN A 1 P 16:331:00000 16:331:86370 16:331:43185 73 | WGTN A 1 P 16:331:00000 16:331:86370 16:331:43185 74 | -SOLUTION/EPOCHS 75 | *------------------------------------------------------------------------------- 76 | +SOLUTION/ESTIMATE 77 | *INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __ESTIMATED VALUE____ _STD_DEV___ 78 | 1 STAX 1163 A 1 16:331:43200 m 2 -.468720175682924E+07 .547952E-03 79 | 2 STAY 1163 A 1 16:331:43200 m 2 0.517729903966416E+06 .126090E-03 80 | 3 STAZ 1163 A 1 16:331:43200 m 2 -.428028031635972E+07 .472169E-03 81 | 4 STAX KAIK A 1 16:331:43200 m 1 -.468548036895222E+07 .399815E-03 82 | 5 STAY KAIK A 1 16:331:43200 m 1 0.531054576640439E+06 .917545E-04 83 | 6 STAZ KAIK A 1 16:331:43200 m 1 -.428081916946820E+07 .351802E-03 84 | 7 STAX NLSN A 1 16:331:43200 m 1 -.477588851915855E+07 .401660E-03 85 | 8 STAY NLSN A 1 16:331:43200 m 1 0.549740165694525E+06 .926504E-04 86 | 9 STAZ NLSN A 1 16:331:43200 m 1 -.417798089363501E+07 .346824E-03 87 | 10 STAX WGTN A 1 16:331:43200 m 1 -.477726974195999E+07 .410335E-03 88 | 11 STAY WGTN A 1 16:331:43200 m 1 0.434270504414540E+06 .914974E-04 89 | 12 STAZ WGTN A 1 16:331:43200 m 1 -.418948403886692E+07 .353501E-03 90 | -SOLUTION/ESTIMATE 91 | *------------------------------------------------------------------------------- 92 | +SOLUTION/APRIORI 93 | *INDEX TYPE__ CODE PT SOLN _REF_EPOCH__ UNIT S __APRIORI VALUE______ _STD_DEV___ 94 | 1 STAX 1163 A 1 16:331:43200 m 2 -.468720291390000E+07 .000000E+00 95 | 2 STAY 1163 A 1 16:331:43200 m 2 0.517730335600000E+06 .000000E+00 96 | 3 STAZ 1163 A 1 16:331:43200 m 2 -.428028141990000E+07 .000000E+00 97 | 4 STAX KAIK A 1 16:331:43200 m 1 -.468548035983000E+07 .000000E+00 98 | 5 STAY KAIK A 1 16:331:43200 m 1 0.531054577100000E+06 .000000E+00 99 | 6 STAZ KAIK A 1 16:331:43200 m 1 -.428081916638000E+07 .000000E+00 100 | 7 STAX NLSN A 1 16:331:43200 m 1 -.477588852141000E+07 .000000E+00 101 | 8 STAY NLSN A 1 16:331:43200 m 1 0.549740169550000E+06 .000000E+00 102 | 9 STAZ NLSN A 1 16:331:43200 m 1 -.417798089139000E+07 .000000E+00 103 | 10 STAX WGTN A 1 16:331:43200 m 1 -.477726974883000E+07 .000000E+00 104 | 11 STAY WGTN A 1 16:331:43200 m 1 0.434270500100000E+06 .000000E+00 105 | 12 STAZ WGTN A 1 16:331:43200 m 1 -.418948404420000E+07 .000000E+00 106 | -SOLUTION/APRIORI 107 | *------------------------------------------------------------------------------- 108 | +SOLUTION/MATRIX_ESTIMATE L COVA 109 | *PARA1 PARA2 ____PARA2+0__________ ____PARA2+1__________ ____PARA2+2__________ 110 | 1 1 0.30025164040403E-06 111 | 2 1 -0.26373032080051E-07 0.15898607531225E-07 112 | 3 1 0.23494840164016E-06 -0.21828138955247E-07 0.22294354570634E-06 113 | 4 1 0.94362354902460E-08 -0.63090639972866E-09 0.62075475807490E-08 114 | 4 4 0.15985178301900E-06 115 | 5 1 0.11750927959214E-08 0.12909887707950E-09 0.10631903758248E-08 116 | 5 4 -0.13990126833790E-07 0.84188827948102E-08 117 | 6 1 -0.39629390601241E-09 0.16098122476862E-09 -0.16036900520868E-08 118 | 6 4 0.12826024122824E-06 -0.11898536815774E-07 0.12376484736459E-06 119 | 7 1 -0.58658778290664E-09 0.96683829333709E-09 -0.52146047338665E-10 120 | 7 4 -0.76024828469865E-07 0.77690823690040E-08 -0.61172960701856E-07 121 | 7 7 0.16133110992556E-06 122 | 8 1 0.32245372417145E-08 -0.10682999408949E-09 0.26929179908122E-08 123 | 8 4 0.77640390159394E-08 -0.39357664329360E-08 0.64586528615398E-08 124 | 8 7 -0.14885783332527E-07 0.85840934216674E-08 125 | 9 1 0.36167105436275E-08 0.43639573026820E-09 0.38666713219658E-08 126 | 9 4 -0.61264339033944E-07 0.66072417221473E-08 -0.59164551764524E-07 127 | 9 7 0.12703034249557E-06 -0.12376586849153E-07 0.12028665955429E-06 128 | 10 1 -0.80899753386633E-08 -0.33570522733173E-09 -0.61575629686644E-08 129 | 10 4 -0.83067741941855E-07 0.62215152460202E-08 -0.67089878080752E-07 130 | 10 7 -0.84547055532810E-07 0.71224297018373E-08 -0.65764764364390E-07 131 | 10 10 0.16837449537991E-06 132 | 11 1 -0.44006084818292E-08 0.73695461466453E-09 -0.37566105444561E-08 133 | 11 4 0.62246112430570E-08 -0.37238482697795E-08 0.54390285465800E-08 134 | 11 7 0.71148540626535E-08 -0.38886939789704E-08 0.57678767624239E-08 135 | 11 10 -0.13340621506369E-07 0.83717774558205E-08 136 | 12 1 -0.32183786657429E-08 -0.59727572074248E-09 -0.15030018581821E-08 137 | 12 4 -0.66994099064673E-07 0.52916656846192E-08 -0.63840569327368E-07 138 | 12 7 -0.65858329916982E-07 0.59187473832403E-08 -0.60362870340584E-07 139 | 12 10 0.13285378749634E-06 -0.11208089283967E-07 0.12496261235055E-06 140 | -SOLUTION/MATRIX_ESTIMATE L COVA 141 | *------------------------------------------------------------------------------- 142 | +SOLUTION/MATRIX_APRIORI L COVA 143 | *PARA1 PARA2 ____PARA2+0__________ ____PARA2+1__________ ____PARA2+2__________ 144 | 1 1 0.25312628668454E+02 145 | 2 1 0.00000000000000E+00 0.25312628668454E+02 146 | 3 1 0.00000000000000E+00 0.00000000000000E+00 0.25312628668454E+02 147 | 4 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 148 | 4 4 0.16875021931078E+02 149 | 5 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 150 | 5 4 0.00000000000000E+00 0.16875021931078E+02 151 | 6 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 152 | 6 4 0.00000000000000E+00 0.00000000000000E+00 0.16875021931078E+02 153 | 7 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 154 | 7 4 -0.84375109655392E+01 0.00000000000000E+00 0.00000000000000E+00 155 | 7 7 0.16875021931078E+02 156 | 8 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 157 | 8 4 0.00000000000000E+00 -0.84375109655392E+01 0.00000000000000E+00 158 | 8 7 0.00000000000000E+00 0.16875021931078E+02 159 | 9 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 160 | 9 4 0.00000000000000E+00 0.00000000000000E+00 -0.84375109655392E+01 161 | 9 7 0.00000000000000E+00 0.00000000000000E+00 0.16875021931078E+02 162 | 10 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 163 | 10 4 -0.84375109647798E+01 0.00000000000000E+00 0.00000000000000E+00 164 | 10 7 -0.84375109647798E+01 0.00000000000000E+00 0.00000000000000E+00 165 | 10 10 0.16875021930319E+02 166 | 11 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 167 | 11 4 0.00000000000000E+00 -0.84375109647798E+01 0.00000000000000E+00 168 | 11 7 0.00000000000000E+00 -0.84375109647798E+01 0.00000000000000E+00 169 | 11 10 0.00000000000000E+00 0.16875021930319E+02 170 | 12 1 0.00000000000000E+00 0.00000000000000E+00 0.00000000000000E+00 171 | 12 4 0.00000000000000E+00 0.00000000000000E+00 -0.84375109647798E+01 172 | 12 7 0.00000000000000E+00 0.00000000000000E+00 -0.84375109647798E+01 173 | 12 10 0.00000000000000E+00 0.00000000000000E+00 0.16875021930319E+02 174 | -SOLUTION/MATRIX_APRIORI L COVA 175 | %ENDSNX 176 | -------------------------------------------------------------------------------- /tests/testEllipsoid.py: -------------------------------------------------------------------------------- 1 | # Imports to support python 3 compatibility 2 | 3 | import sys 4 | import os.path 5 | 6 | sys.path.insert( 7 | 0, 8 | os.path.join( 9 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "LINZ", "Geodetic" 10 | ), 11 | ) 12 | 13 | import fileunittest 14 | import Ellipsoid 15 | 16 | 17 | class EllipsoidTestCase(fileunittest.TestCase): 18 | def test001_Params(self): 19 | """ 20 | Test handling of ellipsoid parameters 21 | """ 22 | ell = Ellipsoid.Ellipsoid(6378101, 297.23) 23 | self.check("001: Semi major axis", ell.a) 24 | self.check("001: Semi minor axis", ell.b) 25 | self.check("001: Flattening", ell.rf) 26 | 27 | def test002_Convert(self): 28 | """ 29 | Conversion geodetic <-> XYZ 30 | """ 31 | ell = Ellipsoid.Ellipsoid(6378101, 297.23) 32 | xyz = ell.xyz(172.0, -41.0) 33 | self.check("002: XYZ from lat/lon", xyz) 34 | xyz = ell.xyz(172.0, -41.0, 238.0) 35 | self.check("002: XYZ from lat/lon/hgt", xyz) 36 | xyz = ell.xyz([172.0, -41.0, 238.0]) 37 | self.check("002: XYZ from [lat,lon,hgt] list", xyz) 38 | llh = ell.geodetic(xyz) 39 | self.check("002: LLH from XYZ", llh) 40 | llh = ell.geodetic([xyz, xyz]) 41 | self.check("002: LLH from multiple XYZ", llh) 42 | 43 | def test003_Calcs(self): 44 | ell = Ellipsoid.Ellipsoid(6378101, 297.23) 45 | enu = ell.enu_axes(165.0, -23.0) 46 | self.check("003: enu_axes", enu.tolist()) 47 | mpd = ell.metres_per_degree(-165.0, -23) 48 | self.check("003: metres_per_degree lat/lon", mpd) 49 | mpd = ell.metres_per_degree(165.0, -23.0, 1234.0) 50 | self.check("003: metres_per_degree lat/lon/hgt", mpd) 51 | 52 | def test004_Grs80(self): 53 | ell = Ellipsoid.GRS80 54 | self.check("004: GRS80 Semi major axis", ell.a) 55 | self.check("004: GRS80 Semi minor axis", ell.b) 56 | self.check("004: GRS80 Flattening", ell.rf) 57 | 58 | 59 | if __name__ == "__main__": 60 | fileunittest.main() 61 | -------------------------------------------------------------------------------- /tests/testEllipsoid.results: -------------------------------------------------------------------------------- 1 | ====================================================================== 2 | >>>> 001__semi_major_axis 6378101.0 3 | >>>> 001__semi_minor_axis 6356642.530128184 4 | >>>> 001__flattening 297.23 5 | >>>> 6 | ====================================================================== 7 | >>>> 002__xyz_from_lat_lon array([-4773674.2077239919, 670896.1577508064, -4162323.6700582835]) 8 | >>>> 002__xyz_from_lat_lon_hgt array([-4773852.080546028 , 670921.1561456862, -4162479.812107183 ]) 9 | >>>> 002__xyz_from__lat_lon_hgt__list array([-4773852.080546028 , 670921.1561456862, -4162479.812107183 ]) 10 | >>>> 002__llh_from_xyz array([ 172., -41., 238.]) 11 | >>>> 002__llh_from_multiple_xyz array([[ 172., -41., 238.], 12 | [ 172., -41., 238.]]) 13 | >>>> 14 | ====================================================================== 15 | >>>> 003__enu_axes [[-0.258819045102521, -0.9659258262890682, 0.0], [-0.37741728814286185, 0.10112865756742428, 0.9205048534524404], [-0.8891394111741461, 0.23824418718279666, -0.39073112848927377]] 16 | >>>> 003__metres_per_degree_lat_lon (102522.13811197459, 110741.39528002527) 17 | >>>> 003__metres_per_degree_lat_lon_hgt (102541.96335911867, 110762.93264299489) 18 | >>>> 19 | ====================================================================== 20 | >>>> 004__grs80_semi_major_axis 6378137.0 21 | >>>> 004__grs80_semi_minor_axis 6356752.314140356 22 | >>>> 004__grs80_flattening 298.257222101 23 | >>>> 24 | -------------------------------------------------------------------------------- /tests/testITRF.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | from datetime import datetime 4 | 5 | testdir = os.path.dirname(os.path.abspath(__file__)) 6 | sys.path.insert(0, os.path.join(os.path.dirname(testdir), "LINZ")) 7 | 8 | import fileunittest 9 | from Geodetic import ITRF 10 | 11 | 12 | class ITRFTestCase(fileunittest.TestCase): 13 | def test001_ITRF_parameters(self): 14 | """ 15 | Test ITRF parameters 16 | """ 17 | params = {p[0]: p[1] for p in ITRF.ITRF_params} 18 | for r in ("ITRF96", "ITRF97", "ITRF2000", "ITRF2005", "ITRF2008", "ITRF2014"): 19 | self.check("test001: {0}".format(r), params.get(r)) 20 | 21 | def test002_BWtransform(self): 22 | tf = ITRF.Transformation("ITRF96", "ITRF2008") 23 | self.check("test002 BW transform 2000", str(tf)) 24 | tf = ITRF.Transformation("ITRF96", "ITRF2008") 25 | self.check("test002 BW transform 2020", str(tf)) 26 | tf2 = tf.atDate(2020.0) 27 | self.check("test002 BW transform 2020 at 2020", str(tf)) 28 | self.check( 29 | "test002 BW apply1", tf2.transform([-4761241.541, 754106.577, -4162423.201]) 30 | ) 31 | self.check( 32 | "test002 BW apply2", 33 | tf.transform([-4761241.541, 754106.577, -4162423.201], 2020.0), 34 | ) 35 | self.check( 36 | "test002 BW apply3", tf.transform([-4761241.541, 754106.577, -4162423.201]) 37 | ) 38 | self.check("test002 BW apply4", tf.transformLonLat([171.28, -41.54, 34.0])) 39 | 40 | 41 | if __name__ == "__main__": 42 | fileunittest.main() 43 | -------------------------------------------------------------------------------- /tests/testITRF.results: -------------------------------------------------------------------------------- 1 | ====================================================================== 2 | >>>> test001__itrf96 (0, 0, 0, 0, 0, 0, 0) 3 | >>>> test001__itrf97 (0, 0.51, -15.53, 1.51099, 0.16508, -0.26897, -0.05984) 4 | >>>> test001__itrf2000 (-6.7, -3.79, 7.17, -0.06901, 0.16508, -0.26897, -0.11984) 5 | >>>> test001__itrf2005 (-6.8, -2.99, 12.97, -0.46901, 0.16508, -0.26897, -0.11984) 6 | >>>> test001__itrf2008 (-4.8, -2.09, 17.67, -1.40901, 0.16508, -0.26897, -0.11984) 7 | >>>> test001__itrf2014 (-3.2, -0.19, 21.07, -1.72901, 0.16508, -0.26897, -0.11984) 8 | >>>> 9 | ====================================================================== 10 | >>>> test002_bw_transform_2000 Transformation from ITRF2008 to ITRF96 11 | Reference date 2000.0 12 | Translations 4.80 2.09 -17.67 mm 13 | rates 0.79 -0.60 -1.34 mm/yr 14 | Rotations -0.16508 0.26897 0.11984 mas 15 | rates -0.01347 0.01514 0.01973 mas/yr 16 | Scale 1.40901 ppb 17 | rates -0.10201 ppb/yr 18 | 19 | >>>> test002_bw_transform_2020 Transformation from ITRF2008 to ITRF96 20 | Reference date 2000.0 21 | Translations 4.80 2.09 -17.67 mm 22 | rates 0.79 -0.60 -1.34 mm/yr 23 | Rotations -0.16508 0.26897 0.11984 mas 24 | rates -0.01347 0.01514 0.01973 mas/yr 25 | Scale 1.40901 ppb 26 | rates -0.10201 ppb/yr 27 | 28 | >>>> test002_bw_transform_2020_at_2020 Transformation from ITRF2008 to ITRF96 29 | Reference date 2000.0 30 | Translations 4.80 2.09 -17.67 mm 31 | rates 0.79 -0.60 -1.34 mm/yr 32 | Rotations -0.16508 0.26897 0.11984 mas 33 | rates -0.01347 0.01514 0.01973 mas/yr 34 | Scale 1.40901 ppb 35 | rates -0.10201 ppb/yr 36 | 37 | >>>> test002_bw_apply1 array([-4761241.5308138672, 754106.5459713144, -4162423.2312329314]) 38 | >>>> test002_bw_apply2 array([-4761241.5308138672, 754106.5459713144, -4162423.2312329314]) 39 | >>>> test002_bw_apply3 array([-4761241.548774587, 754106.574054945, -4162423.218929755]) 40 | >>>> test002_bw_apply4 array([ 171.2800000497, -41.5400000783, 34.0173525456]) 41 | >>>> 42 | -------------------------------------------------------------------------------- /tests/testSinex.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | from datetime import datetime 4 | 5 | testdir = os.path.dirname(os.path.abspath(__file__)) 6 | sys.path.insert(0, os.path.join(os.path.dirname(testdir), "LINZ")) 7 | 8 | import fileunittest 9 | from Geodetic import Sinex 10 | 11 | 12 | class SinexTestCase(fileunittest.TestCase): 13 | 14 | sinexfile1 = os.path.join(testdir, "test.snx") 15 | 16 | def test001_ReaderSolutionId(self): 17 | """ 18 | Test basic sinex reading 19 | """ 20 | 21 | snx = Sinex.Reader(SinexTestCase.sinexfile1, covariance=Sinex.COVAR_FULL) 22 | self.check("test001: solutions", snx.solutions()) 23 | self.check("test001: getSolutionId(KAIK)", snx.getSolutionIds(ptid="KAIK")) 24 | self.check( 25 | "test001: getSolutionsIds(KAIK,A)", 26 | snx.getSolutionIds(ptid="KAIK", ptcode="A"), 27 | ) 28 | self.check( 29 | "test001: getSolutionsIds(KAIK,B)", 30 | snx.getSolutionIds(ptid="KAIK", ptcode="B"), 31 | ) 32 | # self.check("test001: getSolutionIds(A)",snx.getSolutionIds(ptcode='A')) 33 | self.check( 34 | "test001: getSolutionIds(KAIK,1)", 35 | snx.getSolutionIds(ptid="KAIK", solnid="1"), 36 | ) 37 | self.check( 38 | "test001: getSolutionIds(KAIK,2)", 39 | snx.getSolutionIds(ptid="KAIK", solnid="2"), 40 | ) 41 | self.check( 42 | "test001: getSolutionIds(KAIK:A) single", snx.getSolutionIds("KAIK:A") 43 | ) 44 | self.check( 45 | "test001: getSolutionIds(KAIK:B) single", snx.getSolutionIds("KAIK:B") 46 | ) 47 | self.check( 48 | "test001: getSolutionIds(KAIK:A:1) single", snx.getSolutionIds("KAIK:A:1") 49 | ) 50 | self.check( 51 | "test001: getSolutionIds(KAIK:A:2) single", snx.getSolutionIds("KAIK:A:2") 52 | ) 53 | 54 | def test002_ReaderGet(self): 55 | """ 56 | Test get (no velocities) 57 | """ 58 | 59 | snx = Sinex.Reader( 60 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_FULL, velocities=False 61 | ) 62 | self.check("test002: get(KAIK)", snx.get("KAIK")) 63 | self.check("test002: get(solutions(0))", snx.get(snx.solutions()[0])) 64 | self.check("test002: get(solution(KAIK A))", snx.get("KAIK", ptcode="A")) 65 | 66 | def test003_ReaderGetSolutions(self): 67 | """ 68 | Test getSolutions (no velocities) 69 | """ 70 | snx = Sinex.Reader( 71 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_FULL, velocities=False 72 | ) 73 | solutions = snx.solutions() 74 | self.check("test003: getsolutions(0)", snx.getSolutions(solutions[0:1])) 75 | self.check("test003: getsolutions(1:3))", snx.getSolutions(solutions[1:3])) 76 | snx = Sinex.Reader( 77 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_STATION, velocities=False 78 | ) 79 | self.check( 80 | "test003: getsolutions(0) stn covar", snx.getSolutions(solutions[0:1]) 81 | ) 82 | self.check( 83 | "test003: getsolutions(1:3)) stn covar", snx.getSolutions(solutions[1:3]) 84 | ) 85 | snx = Sinex.Reader( 86 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_NONE, velocities=False 87 | ) 88 | self.check( 89 | "test003: getsolutions(0) stn none", snx.getSolutions(solutions[0:1]) 90 | ) 91 | self.check( 92 | "test003: getsolutions(1:3)) stn none", snx.getSolutions(solutions[1:3]) 93 | ) 94 | 95 | def test004_ReaderXYZ(self): 96 | """ 97 | Test xyz function 98 | """ 99 | snx = Sinex.Reader( 100 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_FULL, velocities=False 101 | ) 102 | self.check("test004: xyz KAIK", snx.xyz("KAIK")) 103 | self.check("test004: xyz KAIK tuple", snx.xyz(("KAIK", "A", "1"))) 104 | self.check("test004: xyz KAIK A 1", snx.xyz("KAIK:A:1")) 105 | self.check("test004: xyz KAIK with covar", snx.xyz("KAIK", covariance=True)) 106 | self.checkRun("test004: xyz ABCD", lambda: snx.xyz("ABCD")) 107 | snx = Sinex.Reader( 108 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_STATION, velocities=False 109 | ) 110 | self.check("test004: stn covar xyz KAIK", snx.xyz("KAIK")) 111 | self.check( 112 | "test004: stn covar xyz KAIK with covar", snx.xyz("KAIK", covariance=True) 113 | ) 114 | snx = Sinex.Reader( 115 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_NONE, velocities=False 116 | ) 117 | self.check("test004: no covar xyz KAIK", snx.xyz("KAIK")) 118 | self.checkRun( 119 | "test004: no covar xyz KAIK with covar", 120 | lambda: snx.xyz("KAIK", covariance=True), 121 | ) 122 | 123 | def test005_ReaderDXYZ(self): 124 | """ 125 | Test dxyz function 126 | """ 127 | snx = Sinex.Reader( 128 | SinexTestCase.sinexfile1, covariance=Sinex.COVAR_FULL, velocities=False 129 | ) 130 | self.check("test005: dxyz 1163 KAIK", snx.dxyz("1163", "KAIK")) 131 | self.check("test005: dxyz KAIK 1163", snx.dxyz("KAIK", "1163")) 132 | self.checkRun("test005: dxyz KAIK KAIK", lambda: snx.dxyz("KAIK", "KAIK")) 133 | self.checkRun("test005: dxyz KAIK ABCD", lambda: snx.dxyz("KAIK", "ABCD")) 134 | 135 | def test006_WriteSindex(self): 136 | filename = os.path.join(testdir, "test006_output.snx") 137 | with Sinex.Writer(filename) as snxf: 138 | snxf.addFileInfo(description="Test SINEX.Writer", agency="Land Info NZ") 139 | snxf.setSolutionStatistics(2.4, 23.8, 180, 6) 140 | snxf.setObsDateRange(datetime(2010, 2, 5), datetime(2010, 2, 8)) 141 | snxf.addMark("12cd", description="Useful mark for some") 142 | snxf.addMark("ax1d", monument="Second mark", description=None) 143 | snxf.addSolution( 144 | "12cd", 145 | [-4685480.3689522203, 531054.576640439, -4280819.1694681998], 146 | [0, 1, 2], 147 | ) 148 | snxf.addSolution( 149 | "ax1d", 150 | [-46854280.3689522203, 531354.576640439, -4280849.1694681998], 151 | [3, 4, 5], 152 | ) 153 | snxf.setCovariance( 154 | [ 155 | [ 156 | 0.0000003003, 157 | -0.0000000264, 158 | 0.0000002349, 159 | 0.0000000094, 160 | 0.0000000012, 161 | -0.0000000004, 162 | -0.0000000006, 163 | 0.0000000032, 164 | ], 165 | [ 166 | -0.0000000264, 167 | 0.0000000159, 168 | -0.0000000218, 169 | -0.0000000006, 170 | 0.0000000001, 171 | 0.0000000002, 172 | 0.000000001, 173 | -0.0000000001, 174 | ], 175 | [ 176 | 0.0000002349, 177 | -0.0000000218, 178 | 0.0000002229, 179 | 0.0000000062, 180 | 0.0000000011, 181 | -0.0000000016, 182 | -0.0000000001, 183 | 0.0000000027, 184 | ], 185 | [ 186 | 0.0000000094, 187 | -0.0000000006, 188 | 0.0000000062, 189 | 0.0000001599, 190 | -0.000000014, 191 | 0.0000001283, 192 | -0.000000076, 193 | 0.0000000078, 194 | ], 195 | [ 196 | 0.0000000012, 197 | 0.0000000001, 198 | 0.0000000011, 199 | -0.000000014, 200 | 0.0000000084, 201 | -0.0000000119, 202 | 0.0000000078, 203 | -0.0000000039, 204 | ], 205 | [ 206 | -0.0000000004, 207 | 0.0000000002, 208 | -0.0000000016, 209 | 0.0000001283, 210 | -0.0000000119, 211 | 0.0000001238, 212 | -0.0000000612, 213 | 0.0000000065, 214 | ], 215 | ] 216 | ) 217 | with open(filename) as sf: 218 | sinexdata = sf.read() 219 | sinexdata = sinexdata[:15] + "00:001:12345" + sinexdata[27:] 220 | self.check("test006: write sinex", sinexdata) 221 | os.remove(filename) 222 | 223 | 224 | if __name__ == "__main__": 225 | fileunittest.main() 226 | -------------------------------------------------------------------------------- /tests/testSinex.results: -------------------------------------------------------------------------------- 1 | ====================================================================== 2 | >>>> test001__solutions [SolutionId(id='1163', code='A', soln='1'), SolutionId(id='KAIK', code='A', soln='1'), SolutionId(id='NLSN', code='A', soln='1'), SolutionId(id='WGTN', code='A', soln='1')] 3 | >>>> test001__getsolutionid_kaik_ [('KAIK', 'A', '1')] 4 | >>>> test001__getsolutionsids_kaik_a_ [('KAIK', 'A', '1')] 5 | >>>> test001__getsolutionsids_kaik_b_ [] 6 | >>>> test001__getsolutionids_kaik_1_ [('KAIK', 'A', '1')] 7 | >>>> test001__getsolutionids_kaik_2_ [] 8 | >>>> test001__getsolutionids_kaik_a__single [] 9 | >>>> test001__getsolutionids_kaik_b__single [] 10 | >>>> test001__getsolutionids_kaik_a_1__single [('KAIK', 'A', '1')] 11 | >>>> test001__getsolutionids_kaik_a_2__single [] 12 | >>>> 13 | ====================================================================== 14 | >>>> test002__get_kaik_ Solution(id='KAIK', code='A', soln='1', monument='M', description='', llh=(173.53366666666668, -42.42547222222222, 315.5), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), vxyz=None, prmids=[3, 4, 5], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349, 0.0000000094, 15 | 0.0000000012, -0.0000000004, -0.0000000006, 0.0000000032, 16 | 0.0000000036, -0.0000000081, -0.0000000044, -0.0000000032], 17 | [-0.0000000264, 0.0000000159, -0.0000000218, -0.0000000006, 18 | 0.0000000001, 0.0000000002, 0.000000001 , -0.0000000001, 19 | 0.0000000004, -0.0000000003, 0.0000000007, -0.0000000006], 20 | [ 0.0000002349, -0.0000000218, 0.0000002229, 0.0000000062, 21 | 0.0000000011, -0.0000000016, -0.0000000001, 0.0000000027, 22 | 0.0000000039, -0.0000000062, -0.0000000038, -0.0000000015], 23 | [ 0.0000000094, -0.0000000006, 0.0000000062, 0.0000001599, 24 | -0.000000014 , 0.0000001283, -0.000000076 , 0.0000000078, 25 | -0.0000000613, -0.0000000831, 0.0000000062, -0.000000067 ], 26 | [ 0.0000000012, 0.0000000001, 0.0000000011, -0.000000014 , 27 | 0.0000000084, -0.0000000119, 0.0000000078, -0.0000000039, 28 | 0.0000000066, 0.0000000062, -0.0000000037, 0.0000000053], 29 | [-0.0000000004, 0.0000000002, -0.0000000016, 0.0000001283, 30 | -0.0000000119, 0.0000001238, -0.0000000612, 0.0000000065, 31 | -0.0000000592, -0.0000000671, 0.0000000054, -0.0000000638], 32 | [-0.0000000006, 0.000000001 , -0.0000000001, -0.000000076 , 33 | 0.0000000078, -0.0000000612, 0.0000001613, -0.0000000149, 34 | 0.000000127 , -0.0000000845, 0.0000000071, -0.0000000659], 35 | [ 0.0000000032, -0.0000000001, 0.0000000027, 0.0000000078, 36 | -0.0000000039, 0.0000000065, -0.0000000149, 0.0000000086, 37 | -0.0000000124, 0.0000000071, -0.0000000039, 0.0000000059], 38 | [ 0.0000000036, 0.0000000004, 0.0000000039, -0.0000000613, 39 | 0.0000000066, -0.0000000592, 0.000000127 , -0.0000000124, 40 | 0.0000001203, -0.0000000658, 0.0000000058, -0.0000000604], 41 | [-0.0000000081, -0.0000000003, -0.0000000062, -0.0000000831, 42 | 0.0000000062, -0.0000000671, -0.0000000845, 0.0000000071, 43 | -0.0000000658, 0.0000001684, -0.0000000133, 0.0000001329], 44 | [-0.0000000044, 0.0000000007, -0.0000000038, 0.0000000062, 45 | -0.0000000037, 0.0000000054, 0.0000000071, -0.0000000039, 46 | 0.0000000058, -0.0000000133, 0.0000000084, -0.0000000112], 47 | [-0.0000000032, -0.0000000006, -0.0000000015, -0.000000067 , 48 | 0.0000000053, -0.0000000638, -0.0000000659, 0.0000000059, 49 | -0.0000000604, 0.0000001329, -0.0000000112, 0.000000125 ]])) 50 | >>>> test002__get_solutions_0__ Solution(id='1163', code='A', soln='1', monument='M', description='', llh=(173.6968888888889, -42.4205, 122.6), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4687201.75682924 , 517729.903966416, -4280280.31635972 ]), vxyz=None, prmids=[0, 1, 2], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349, 0.0000000094, 51 | 0.0000000012, -0.0000000004, -0.0000000006, 0.0000000032, 52 | 0.0000000036, -0.0000000081, -0.0000000044, -0.0000000032], 53 | [-0.0000000264, 0.0000000159, -0.0000000218, -0.0000000006, 54 | 0.0000000001, 0.0000000002, 0.000000001 , -0.0000000001, 55 | 0.0000000004, -0.0000000003, 0.0000000007, -0.0000000006], 56 | [ 0.0000002349, -0.0000000218, 0.0000002229, 0.0000000062, 57 | 0.0000000011, -0.0000000016, -0.0000000001, 0.0000000027, 58 | 0.0000000039, -0.0000000062, -0.0000000038, -0.0000000015], 59 | [ 0.0000000094, -0.0000000006, 0.0000000062, 0.0000001599, 60 | -0.000000014 , 0.0000001283, -0.000000076 , 0.0000000078, 61 | -0.0000000613, -0.0000000831, 0.0000000062, -0.000000067 ], 62 | [ 0.0000000012, 0.0000000001, 0.0000000011, -0.000000014 , 63 | 0.0000000084, -0.0000000119, 0.0000000078, -0.0000000039, 64 | 0.0000000066, 0.0000000062, -0.0000000037, 0.0000000053], 65 | [-0.0000000004, 0.0000000002, -0.0000000016, 0.0000001283, 66 | -0.0000000119, 0.0000001238, -0.0000000612, 0.0000000065, 67 | -0.0000000592, -0.0000000671, 0.0000000054, -0.0000000638], 68 | [-0.0000000006, 0.000000001 , -0.0000000001, -0.000000076 , 69 | 0.0000000078, -0.0000000612, 0.0000001613, -0.0000000149, 70 | 0.000000127 , -0.0000000845, 0.0000000071, -0.0000000659], 71 | [ 0.0000000032, -0.0000000001, 0.0000000027, 0.0000000078, 72 | -0.0000000039, 0.0000000065, -0.0000000149, 0.0000000086, 73 | -0.0000000124, 0.0000000071, -0.0000000039, 0.0000000059], 74 | [ 0.0000000036, 0.0000000004, 0.0000000039, -0.0000000613, 75 | 0.0000000066, -0.0000000592, 0.000000127 , -0.0000000124, 76 | 0.0000001203, -0.0000000658, 0.0000000058, -0.0000000604], 77 | [-0.0000000081, -0.0000000003, -0.0000000062, -0.0000000831, 78 | 0.0000000062, -0.0000000671, -0.0000000845, 0.0000000071, 79 | -0.0000000658, 0.0000001684, -0.0000000133, 0.0000001329], 80 | [-0.0000000044, 0.0000000007, -0.0000000038, 0.0000000062, 81 | -0.0000000037, 0.0000000054, 0.0000000071, -0.0000000039, 82 | 0.0000000058, -0.0000000133, 0.0000000084, -0.0000000112], 83 | [-0.0000000032, -0.0000000006, -0.0000000015, -0.000000067 , 84 | 0.0000000053, -0.0000000638, -0.0000000659, 0.0000000059, 85 | -0.0000000604, 0.0000001329, -0.0000000112, 0.000000125 ]])) 86 | >>>> test002__get_solution_kaik_a__ Solution(id='KAIK', code='A', soln='1', monument='M', description='', llh=(173.53366666666668, -42.42547222222222, 315.5), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), vxyz=None, prmids=[3, 4, 5], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349, 0.0000000094, 87 | 0.0000000012, -0.0000000004, -0.0000000006, 0.0000000032, 88 | 0.0000000036, -0.0000000081, -0.0000000044, -0.0000000032], 89 | [-0.0000000264, 0.0000000159, -0.0000000218, -0.0000000006, 90 | 0.0000000001, 0.0000000002, 0.000000001 , -0.0000000001, 91 | 0.0000000004, -0.0000000003, 0.0000000007, -0.0000000006], 92 | [ 0.0000002349, -0.0000000218, 0.0000002229, 0.0000000062, 93 | 0.0000000011, -0.0000000016, -0.0000000001, 0.0000000027, 94 | 0.0000000039, -0.0000000062, -0.0000000038, -0.0000000015], 95 | [ 0.0000000094, -0.0000000006, 0.0000000062, 0.0000001599, 96 | -0.000000014 , 0.0000001283, -0.000000076 , 0.0000000078, 97 | -0.0000000613, -0.0000000831, 0.0000000062, -0.000000067 ], 98 | [ 0.0000000012, 0.0000000001, 0.0000000011, -0.000000014 , 99 | 0.0000000084, -0.0000000119, 0.0000000078, -0.0000000039, 100 | 0.0000000066, 0.0000000062, -0.0000000037, 0.0000000053], 101 | [-0.0000000004, 0.0000000002, -0.0000000016, 0.0000001283, 102 | -0.0000000119, 0.0000001238, -0.0000000612, 0.0000000065, 103 | -0.0000000592, -0.0000000671, 0.0000000054, -0.0000000638], 104 | [-0.0000000006, 0.000000001 , -0.0000000001, -0.000000076 , 105 | 0.0000000078, -0.0000000612, 0.0000001613, -0.0000000149, 106 | 0.000000127 , -0.0000000845, 0.0000000071, -0.0000000659], 107 | [ 0.0000000032, -0.0000000001, 0.0000000027, 0.0000000078, 108 | -0.0000000039, 0.0000000065, -0.0000000149, 0.0000000086, 109 | -0.0000000124, 0.0000000071, -0.0000000039, 0.0000000059], 110 | [ 0.0000000036, 0.0000000004, 0.0000000039, -0.0000000613, 111 | 0.0000000066, -0.0000000592, 0.000000127 , -0.0000000124, 112 | 0.0000001203, -0.0000000658, 0.0000000058, -0.0000000604], 113 | [-0.0000000081, -0.0000000003, -0.0000000062, -0.0000000831, 114 | 0.0000000062, -0.0000000671, -0.0000000845, 0.0000000071, 115 | -0.0000000658, 0.0000001684, -0.0000000133, 0.0000001329], 116 | [-0.0000000044, 0.0000000007, -0.0000000038, 0.0000000062, 117 | -0.0000000037, 0.0000000054, 0.0000000071, -0.0000000039, 118 | 0.0000000058, -0.0000000133, 0.0000000084, -0.0000000112], 119 | [-0.0000000032, -0.0000000006, -0.0000000015, -0.000000067 , 120 | 0.0000000053, -0.0000000638, -0.0000000659, 0.0000000059, 121 | -0.0000000604, 0.0000001329, -0.0000000112, 0.000000125 ]])) 122 | >>>> 123 | ====================================================================== 124 | >>>> test003__getsolutions_0_ [Solution(id='1163', code='A', soln='1', monument='M', description='', llh=(173.6968888888889, -42.4205, 122.6), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4687201.75682924 , 517729.903966416, -4280280.31635972 ]), vxyz=None, prmids=[0, 1, 2], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349, 0.0000000094, 125 | 0.0000000012, -0.0000000004, -0.0000000006, 0.0000000032, 126 | 0.0000000036, -0.0000000081, -0.0000000044, -0.0000000032], 127 | [-0.0000000264, 0.0000000159, -0.0000000218, -0.0000000006, 128 | 0.0000000001, 0.0000000002, 0.000000001 , -0.0000000001, 129 | 0.0000000004, -0.0000000003, 0.0000000007, -0.0000000006], 130 | [ 0.0000002349, -0.0000000218, 0.0000002229, 0.0000000062, 131 | 0.0000000011, -0.0000000016, -0.0000000001, 0.0000000027, 132 | 0.0000000039, -0.0000000062, -0.0000000038, -0.0000000015], 133 | [ 0.0000000094, -0.0000000006, 0.0000000062, 0.0000001599, 134 | -0.000000014 , 0.0000001283, -0.000000076 , 0.0000000078, 135 | -0.0000000613, -0.0000000831, 0.0000000062, -0.000000067 ], 136 | [ 0.0000000012, 0.0000000001, 0.0000000011, -0.000000014 , 137 | 0.0000000084, -0.0000000119, 0.0000000078, -0.0000000039, 138 | 0.0000000066, 0.0000000062, -0.0000000037, 0.0000000053], 139 | [-0.0000000004, 0.0000000002, -0.0000000016, 0.0000001283, 140 | -0.0000000119, 0.0000001238, -0.0000000612, 0.0000000065, 141 | -0.0000000592, -0.0000000671, 0.0000000054, -0.0000000638], 142 | [-0.0000000006, 0.000000001 , -0.0000000001, -0.000000076 , 143 | 0.0000000078, -0.0000000612, 0.0000001613, -0.0000000149, 144 | 0.000000127 , -0.0000000845, 0.0000000071, -0.0000000659], 145 | [ 0.0000000032, -0.0000000001, 0.0000000027, 0.0000000078, 146 | -0.0000000039, 0.0000000065, -0.0000000149, 0.0000000086, 147 | -0.0000000124, 0.0000000071, -0.0000000039, 0.0000000059], 148 | [ 0.0000000036, 0.0000000004, 0.0000000039, -0.0000000613, 149 | 0.0000000066, -0.0000000592, 0.000000127 , -0.0000000124, 150 | 0.0000001203, -0.0000000658, 0.0000000058, -0.0000000604], 151 | [-0.0000000081, -0.0000000003, -0.0000000062, -0.0000000831, 152 | 0.0000000062, -0.0000000671, -0.0000000845, 0.0000000071, 153 | -0.0000000658, 0.0000001684, -0.0000000133, 0.0000001329], 154 | [-0.0000000044, 0.0000000007, -0.0000000038, 0.0000000062, 155 | -0.0000000037, 0.0000000054, 0.0000000071, -0.0000000039, 156 | 0.0000000058, -0.0000000133, 0.0000000084, -0.0000000112], 157 | [-0.0000000032, -0.0000000006, -0.0000000015, -0.000000067 , 158 | 0.0000000053, -0.0000000638, -0.0000000659, 0.0000000059, 159 | -0.0000000604, 0.0000001329, -0.0000000112, 0.000000125 ]]))] 160 | >>>> test003__getsolutions_1_3__ [Solution(id='KAIK', code='A', soln='1', monument='M', description='', llh=(173.53366666666668, -42.42547222222222, 315.5), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), vxyz=None, prmids=[3, 4, 5], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349, 0.0000000094, 161 | 0.0000000012, -0.0000000004, -0.0000000006, 0.0000000032, 162 | 0.0000000036, -0.0000000081, -0.0000000044, -0.0000000032], 163 | [-0.0000000264, 0.0000000159, -0.0000000218, -0.0000000006, 164 | 0.0000000001, 0.0000000002, 0.000000001 , -0.0000000001, 165 | 0.0000000004, -0.0000000003, 0.0000000007, -0.0000000006], 166 | [ 0.0000002349, -0.0000000218, 0.0000002229, 0.0000000062, 167 | 0.0000000011, -0.0000000016, -0.0000000001, 0.0000000027, 168 | 0.0000000039, -0.0000000062, -0.0000000038, -0.0000000015], 169 | [ 0.0000000094, -0.0000000006, 0.0000000062, 0.0000001599, 170 | -0.000000014 , 0.0000001283, -0.000000076 , 0.0000000078, 171 | -0.0000000613, -0.0000000831, 0.0000000062, -0.000000067 ], 172 | [ 0.0000000012, 0.0000000001, 0.0000000011, -0.000000014 , 173 | 0.0000000084, -0.0000000119, 0.0000000078, -0.0000000039, 174 | 0.0000000066, 0.0000000062, -0.0000000037, 0.0000000053], 175 | [-0.0000000004, 0.0000000002, -0.0000000016, 0.0000001283, 176 | -0.0000000119, 0.0000001238, -0.0000000612, 0.0000000065, 177 | -0.0000000592, -0.0000000671, 0.0000000054, -0.0000000638], 178 | [-0.0000000006, 0.000000001 , -0.0000000001, -0.000000076 , 179 | 0.0000000078, -0.0000000612, 0.0000001613, -0.0000000149, 180 | 0.000000127 , -0.0000000845, 0.0000000071, -0.0000000659], 181 | [ 0.0000000032, -0.0000000001, 0.0000000027, 0.0000000078, 182 | -0.0000000039, 0.0000000065, -0.0000000149, 0.0000000086, 183 | -0.0000000124, 0.0000000071, -0.0000000039, 0.0000000059], 184 | [ 0.0000000036, 0.0000000004, 0.0000000039, -0.0000000613, 185 | 0.0000000066, -0.0000000592, 0.000000127 , -0.0000000124, 186 | 0.0000001203, -0.0000000658, 0.0000000058, -0.0000000604], 187 | [-0.0000000081, -0.0000000003, -0.0000000062, -0.0000000831, 188 | 0.0000000062, -0.0000000671, -0.0000000845, 0.0000000071, 189 | -0.0000000658, 0.0000001684, -0.0000000133, 0.0000001329], 190 | [-0.0000000044, 0.0000000007, -0.0000000038, 0.0000000062, 191 | -0.0000000037, 0.0000000054, 0.0000000071, -0.0000000039, 192 | 0.0000000058, -0.0000000133, 0.0000000084, -0.0000000112], 193 | [-0.0000000032, -0.0000000006, -0.0000000015, -0.000000067 , 194 | 0.0000000053, -0.0000000638, -0.0000000659, 0.0000000059, 195 | -0.0000000604, 0.0000001329, -0.0000000112, 0.000000125 ]])), Solution(id='NLSN', code='A', soln='1', monument='M', description='', llh=(173.43372222222223, -41.183499999999995, 302.1), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4775888.51915855 , 549740.165694525, -4177980.89363501 ]), vxyz=None, prmids=[6, 7, 8], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349, 0.0000000094, 196 | 0.0000000012, -0.0000000004, -0.0000000006, 0.0000000032, 197 | 0.0000000036, -0.0000000081, -0.0000000044, -0.0000000032], 198 | [-0.0000000264, 0.0000000159, -0.0000000218, -0.0000000006, 199 | 0.0000000001, 0.0000000002, 0.000000001 , -0.0000000001, 200 | 0.0000000004, -0.0000000003, 0.0000000007, -0.0000000006], 201 | [ 0.0000002349, -0.0000000218, 0.0000002229, 0.0000000062, 202 | 0.0000000011, -0.0000000016, -0.0000000001, 0.0000000027, 203 | 0.0000000039, -0.0000000062, -0.0000000038, -0.0000000015], 204 | [ 0.0000000094, -0.0000000006, 0.0000000062, 0.0000001599, 205 | -0.000000014 , 0.0000001283, -0.000000076 , 0.0000000078, 206 | -0.0000000613, -0.0000000831, 0.0000000062, -0.000000067 ], 207 | [ 0.0000000012, 0.0000000001, 0.0000000011, -0.000000014 , 208 | 0.0000000084, -0.0000000119, 0.0000000078, -0.0000000039, 209 | 0.0000000066, 0.0000000062, -0.0000000037, 0.0000000053], 210 | [-0.0000000004, 0.0000000002, -0.0000000016, 0.0000001283, 211 | -0.0000000119, 0.0000001238, -0.0000000612, 0.0000000065, 212 | -0.0000000592, -0.0000000671, 0.0000000054, -0.0000000638], 213 | [-0.0000000006, 0.000000001 , -0.0000000001, -0.000000076 , 214 | 0.0000000078, -0.0000000612, 0.0000001613, -0.0000000149, 215 | 0.000000127 , -0.0000000845, 0.0000000071, -0.0000000659], 216 | [ 0.0000000032, -0.0000000001, 0.0000000027, 0.0000000078, 217 | -0.0000000039, 0.0000000065, -0.0000000149, 0.0000000086, 218 | -0.0000000124, 0.0000000071, -0.0000000039, 0.0000000059], 219 | [ 0.0000000036, 0.0000000004, 0.0000000039, -0.0000000613, 220 | 0.0000000066, -0.0000000592, 0.000000127 , -0.0000000124, 221 | 0.0000001203, -0.0000000658, 0.0000000058, -0.0000000604], 222 | [-0.0000000081, -0.0000000003, -0.0000000062, -0.0000000831, 223 | 0.0000000062, -0.0000000671, -0.0000000845, 0.0000000071, 224 | -0.0000000658, 0.0000001684, -0.0000000133, 0.0000001329], 225 | [-0.0000000044, 0.0000000007, -0.0000000038, 0.0000000062, 226 | -0.0000000037, 0.0000000054, 0.0000000071, -0.0000000039, 227 | 0.0000000058, -0.0000000133, 0.0000000084, -0.0000000112], 228 | [-0.0000000032, -0.0000000006, -0.0000000015, -0.000000067 , 229 | 0.0000000053, -0.0000000638, -0.0000000659, 0.0000000059, 230 | -0.0000000604, 0.0000001329, -0.0000000112, 0.000000125 ]]))] 231 | >>>> test003__getsolutions_0__stn_covar [Solution(id='1163', code='A', soln='1', monument='M', description='', llh=(173.6968888888889, -42.4205, 122.6), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4687201.75682924 , 517729.903966416, -4280280.31635972 ]), vxyz=None, prmids=[0, 1, 2], covariance=array([[ 0.0000003003, -0.0000000264, 0.0000002349], 232 | [-0.0000000264, 0.0000000159, -0.0000000218], 233 | [ 0.0000002349, -0.0000000218, 0.0000002229]]))] 234 | >>>> test003__getsolutions_1_3___stn_covar [Solution(id='KAIK', code='A', soln='1', monument='M', description='', llh=(173.53366666666668, -42.42547222222222, 315.5), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), vxyz=None, prmids=[0, 1, 2], covariance=array([[ 0.0000001599, -0.000000014 , 0.0000001283], 235 | [-0.000000014 , 0.0000000084, -0.0000000119], 236 | [ 0.0000001283, -0.0000000119, 0.0000001238]])), Solution(id='NLSN', code='A', soln='1', monument='M', description='', llh=(173.43372222222223, -41.183499999999995, 302.1), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4775888.51915855 , 549740.165694525, -4177980.89363501 ]), vxyz=None, prmids=[0, 1, 2], covariance=array([[ 0.0000001613, -0.0000000149, 0.000000127 ], 237 | [-0.0000000149, 0.0000000086, -0.0000000124], 238 | [ 0.000000127 , -0.0000000124, 0.0000001203]]))] 239 | >>>> test003__getsolutions_0__stn_none [Solution(id='1163', code='A', soln='1', monument='M', description='', llh=(173.6968888888889, -42.4205, 122.6), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4687201.75682924 , 517729.903966416, -4280280.31635972 ]), vxyz=None, prmids=None, covariance=None)] 240 | >>>> test003__getsolutions_1_3___stn_none [Solution(id='KAIK', code='A', soln='1', monument='M', description='', llh=(173.53366666666668, -42.42547222222222, 315.5), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), vxyz=None, prmids=None, covariance=None), Solution(id='NLSN', code='A', soln='1', monument='M', description='', llh=(173.43372222222223, -41.183499999999995, 302.1), startdate=datetime.datetime(2016, 11, 26, 0, 0), enddate=datetime.datetime(2016, 11, 26, 23, 59, 30), crddate=datetime.datetime(2016, 11, 26, 12, 0), crddate_year=2016.9030054644809, xyz=array([-4775888.51915855 , 549740.165694525, -4177980.89363501 ]), vxyz=None, prmids=None, covariance=None)] 241 | >>>> 242 | ====================================================================== 243 | >>>> test004__xyz_kaik array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]) 244 | >>>> test004__xyz_kaik_tuple array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]) 245 | >>>> test004__xyz_kaik_a_1 array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]) 246 | >>>> test004__xyz_kaik_with_covar (array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), array([[ 0.0000001599, -0.000000014 , 0.0000001283], 247 | [-0.000000014 , 0.0000000084, -0.0000000119], 248 | [ 0.0000001283, -0.0000000119, 0.0000001238]])) 249 | >>>> test004__xyz_abcd NoSolution('No Sinex solution found matching request') 250 | >>>> test004__stn_covar_xyz_kaik array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]) 251 | >>>> test004__stn_covar_xyz_kaik_with_covar (array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]), array([[ 0.0000001599, -0.000000014 , 0.0000001283], 252 | [-0.000000014 , 0.0000000084, -0.0000000119], 253 | [ 0.0000001283, -0.0000000119, 0.0000001238]])) 254 | >>>> test004__no_covar_xyz_kaik array([-4685480.36895222 , 531054.576640439, -4280819.1694682 ]) 255 | >>>> test004__no_covar_xyz_kaik_with_covar CovarianceMissing('Sinex covariances not read') 256 | >>>> 257 | ====================================================================== 258 | >>>> test005__dxyz_1163_kaik array([ 1721.3878770201, 13324.672674023 , -538.8531084796]) 259 | >>>> test005__dxyz_kaik_1163 array([ -1721.3878770201, -13324.672674023 , 538.8531084796]) 260 | >>>> test005__dxyz_kaik_kaik array([ 0., 0., 0.]) 261 | >>>> test005__dxyz_kaik_abcd NoSolution('No Sinex solution found matching request') 262 | >>>> 263 | ====================================================================== 264 | >>>> test006__write_sinex %=SNX 2.10 LAN 00:001:12345 LAN 10:036:00000 10:039:00000 C 6 2 S 265 | +FILE/REFERENCE 266 | DESCRIPTION Test SINEX.Writer 267 | OUTPUT N/A 268 | CONTACT N/A 269 | SOFTWARE N/A 270 | HARDWARE N/A 271 | INPUT N/A 272 | -FILE/REFERENCE 273 | +SITE/ID 274 | 12CD A 12CD C Useful mark for some 173 32 1.2 -42 25 31.7 315.5 275 | AX1D A SECOND MA C 179 21 0.9 -5 13 29.0 40674474.4 276 | -SITE/ID 277 | +SOLUTION/EPOCHS 278 | 12CD A 0001 C 10:036:00000 10:039:00000 10:037:43200 279 | AX1D A 0001 C 10:036:00000 10:039:00000 10:037:43200 280 | -SOLUTION/EPOCHS 281 | +SOLUTION/STATISTICS 282 | VARIANCE FACTOR 2.400000000000000e+00 283 | SUM OR SQUARED RESIDUALS 2.380000000000000e+01 284 | NUMBER OF OBSERVATIONS 180 285 | NUMBER OF UNKNOWNS 6 286 | -SOLUTION/STATISTICS 287 | +SOLUTION/ESTIMATE 288 | 1 STAX 12CD A 0001 10:037:43200 m 2 -4.68548036895222e+06 5.47996e-04 289 | 2 STAY 12CD A 0001 10:037:43200 m 2 5.31054576640439e+05 1.26095e-04 290 | 3 STAZ 12CD A 0001 10:037:43200 m 2 -4.28081916946820e+06 4.72123e-04 291 | 4 STAX AX1D A 0001 10:037:43200 m 2 -4.68542803689522e+07 3.99875e-04 292 | 5 STAY AX1D A 0001 10:037:43200 m 2 5.31354576640439e+05 9.16515e-05 293 | 6 STAZ AX1D A 0001 10:037:43200 m 2 -4.28084916946820e+06 3.51852e-04 294 | -SOLUTION/ESTIMATE 295 | +SOLUTION/MATRIX_ESTIMATE L COVA 296 | 1 1 3.00300000000000e-07 297 | 2 1 -2.64000000000000e-08 1.59000000000000e-08 298 | 3 1 2.34900000000000e-07 -2.18000000000000e-08 2.22900000000000e-07 299 | 4 1 9.40000000000000e-09 -6.00000000000000e-10 6.20000000000000e-09 300 | 4 4 1.59900000000000e-07 301 | 5 1 1.20000000000000e-09 1.00000000000000e-10 1.10000000000000e-09 302 | 5 4 -1.40000000000000e-08 8.40000000000000e-09 303 | 6 1 -4.00000000000000e-10 2.00000000000000e-10 -1.60000000000000e-09 304 | 6 4 1.28300000000000e-07 -1.19000000000000e-08 1.23800000000000e-07 305 | -SOLUTION/MATRIX_ESTIMATE L COVA 306 | %ENDSNX 307 | 308 | >>>> 309 | --------------------------------------------------------------------------------