| 1 | #!/usr/bin/python3
|
|---|
| 2 |
|
|---|
| 3 | import sys
|
|---|
| 4 | import datetime
|
|---|
| 5 | import matplotlib.pyplot as plt
|
|---|
| 6 | import matplotlib.dates as mdates
|
|---|
| 7 | import matplotlib.ticker as mticker
|
|---|
| 8 | import numpy as np
|
|---|
| 9 | import pathlib as pth
|
|---|
| 10 |
|
|---|
| 11 | import utils
|
|---|
| 12 |
|
|---|
| 13 | sys.dont_write_bytecode = True
|
|---|
| 14 |
|
|---|
| 15 | # Data Class
|
|---|
| 16 | ##########################################################################################
|
|---|
| 17 | class Data:
|
|---|
| 18 | def __init__(self):
|
|---|
| 19 | self.epo0 = None
|
|---|
| 20 | self.epo = []
|
|---|
| 21 | self.dNfix = []
|
|---|
| 22 | self.dEfix = []
|
|---|
| 23 | self.dUfix = []
|
|---|
| 24 | self.dNflt = []
|
|---|
| 25 | self.dEflt = []
|
|---|
| 26 | self.dUflt = []
|
|---|
| 27 | self.nFix = []
|
|---|
| 28 | self.conv2D = {}
|
|---|
| 29 |
|
|---|
| 30 | def append(self, epo, dN, dE, dU, isFix, nFix, flgReset):
|
|---|
| 31 | self.epo.append(epo)
|
|---|
| 32 | self.nFix.append(nFix)
|
|---|
| 33 | if isFix:
|
|---|
| 34 | self.dNfix.append(dN)
|
|---|
| 35 | self.dEfix.append(dE)
|
|---|
| 36 | self.dUfix.append(dU)
|
|---|
| 37 | self.dNflt.append(np.nan)
|
|---|
| 38 | self.dEflt.append(np.nan)
|
|---|
| 39 | self.dUflt.append(np.nan)
|
|---|
| 40 | else:
|
|---|
| 41 | self.dNflt.append(dN)
|
|---|
| 42 | self.dEflt.append(dE)
|
|---|
| 43 | self.dUflt.append(dU)
|
|---|
| 44 | self.dNfix.append(np.nan)
|
|---|
| 45 | self.dEfix.append(np.nan)
|
|---|
| 46 | self.dUfix.append(np.nan)
|
|---|
| 47 |
|
|---|
| 48 | if flgReset or self.epo0 is None:
|
|---|
| 49 | self.epo0 = epo
|
|---|
| 50 |
|
|---|
| 51 | nSec = int((epo - self.epo0).total_seconds())
|
|---|
| 52 | if nSec not in self.conv2D:
|
|---|
| 53 | self.conv2D[nSec] = []
|
|---|
| 54 | self.conv2D[nSec].append(np.sqrt(dN*dN + dE*dE))
|
|---|
| 55 |
|
|---|
| 56 | # Function to generate the plot
|
|---|
| 57 | ##########################################################################################
|
|---|
| 58 | def run_plot_client(staName, data, title, timeStr, pngFile, statFile):
|
|---|
| 59 | cm = 1/2.54
|
|---|
| 60 | fig, (ax1, ax2, ax3, dummy, ax4) = plt.subplots(5, 1, figsize=(20*cm,20*cm),
|
|---|
| 61 | gridspec_kw={'height_ratios': [1, 1, 1, 0.3, 1.5]})
|
|---|
| 62 | dummy.axis("off")
|
|---|
| 63 |
|
|---|
| 64 | ax1.set_title(title)
|
|---|
| 65 | ax1.set_ylabel("North [m]")
|
|---|
| 66 | ax1.tick_params(labelbottom=False)
|
|---|
| 67 | ax1.plot(data.epo, data.dNflt, color = "crimson", linewidth = 1.0)
|
|---|
| 68 | ax1.plot(data.epo, data.dNfix, color = "forestgreen", label = "fixed", linewidth = 1.0)
|
|---|
| 69 |
|
|---|
| 70 | ax2.set_ylabel("East [m]")
|
|---|
| 71 | ax2.tick_params(labelbottom=False)
|
|---|
| 72 | ax2.plot(data.epo, data.dEflt, color = "crimson", label = "float", linewidth = 1.0)
|
|---|
| 73 | ax2.plot(data.epo, data.dEfix, color = "forestgreen", linewidth = 1.0)
|
|---|
| 74 |
|
|---|
| 75 | ax3.set_xlabel(timeStr)
|
|---|
| 76 | ax3.set_ylabel("Height [m]")
|
|---|
| 77 | ax3.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
|---|
| 78 | ax3.plot(data.epo, data.dUflt, color = "crimson", linewidth = 1.0)
|
|---|
| 79 | ax3.plot(data.epo, data.dUfix, color = "forestgreen", linewidth = 1.0)
|
|---|
| 80 |
|
|---|
| 81 | ax32 = ax3.twinx()
|
|---|
| 82 | ax32.plot(data.epo, data.nFix, color = "lightskyblue", label = "% fix", linewidth = 0.4)
|
|---|
| 83 | ax32.set_ylim(0,300)
|
|---|
| 84 | ax32.set_yticks([0,50,100])
|
|---|
| 85 | ax32.legend(loc="upper right")
|
|---|
| 86 |
|
|---|
| 87 | for ax in [ax1, ax2, ax3]:
|
|---|
| 88 | ax.set_ylim(-0.4, 0.4)
|
|---|
| 89 | hh, ll = ax.get_legend_handles_labels()
|
|---|
| 90 | if len(hh) > 0:
|
|---|
| 91 | ax.legend(loc="upper right")
|
|---|
| 92 | ax.grid(axis = 'y', which = 'both', color = 'grey', linestyle = '--', linewidth = 0.3)
|
|---|
| 93 | ax.set_yticks([x / 10 for x in range(-4, 5, 2)])
|
|---|
| 94 | ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(2))
|
|---|
| 95 |
|
|---|
| 96 | maxLen = 0
|
|---|
| 97 | perc = {68: [], 95: []}
|
|---|
| 98 | percMin = {10: {68 : 0.0, 95 : 0.0}, 20: {68 : 0.0, 95 : 0.0}, 30: {68 : 0.0, 95 : 0.0}}
|
|---|
| 99 | keyMin = {10: {68 : 0.0, 95 : 0.0}, 20: {68 : 0.0, 95 : 0.0}, 30: {68 : 0.0, 95 : 0.0}}
|
|---|
| 100 |
|
|---|
| 101 | for key in data.conv2D.keys():
|
|---|
| 102 | if len(data.conv2D[key]) > maxLen:
|
|---|
| 103 | maxLen = len(data.conv2D[key])
|
|---|
| 104 |
|
|---|
| 105 | for pKey in (68, 95):
|
|---|
| 106 | pp = np.percentile(data.conv2D[key], pKey)
|
|---|
| 107 | perc[pKey].append(pp)
|
|---|
| 108 | for minute in (10, 20, 30):
|
|---|
| 109 | tMin = 60*minute
|
|---|
| 110 | if key in range(60*minute-10, 60*minute+1):
|
|---|
| 111 | if abs(key - tMin) < abs(keyMin[minute][pKey] - tMin):
|
|---|
| 112 | keyMin[minute][pKey] = key
|
|---|
| 113 | percMin[minute][pKey] = pp
|
|---|
| 114 |
|
|---|
| 115 | print("%8s 68: %6.3f %6.3f %6.3f 95: %6.3f %6.3f %6.3f" %
|
|---|
| 116 | (staName, percMin[10][68], percMin[20][68], percMin[30][68], percMin[10][95], percMin[20][95], percMin[30][95]))
|
|---|
| 117 |
|
|---|
| 118 | for ii in range(0, maxLen):
|
|---|
| 119 | dPos = []
|
|---|
| 120 | for key in data.conv2D.keys():
|
|---|
| 121 | if len(data.conv2D[key]) > ii:
|
|---|
| 122 | dPos.append(data.conv2D[key][ii])
|
|---|
| 123 | else:
|
|---|
| 124 | dPos.append(np.nan)
|
|---|
| 125 | ax4.plot(data.conv2D.keys(), dPos, linewidth = 0.5)
|
|---|
| 126 |
|
|---|
| 127 | ax4.set_title("Convergence")
|
|---|
| 128 | ax4.plot(data.conv2D.keys(), perc[68], linewidth = 1.0, color = "black", label = "68 %")
|
|---|
| 129 | ax4.plot(data.conv2D.keys(), perc[95], linewidth = 2.0, color = "black", label = "95 %")
|
|---|
| 130 | ax4.legend(loc="upper right")
|
|---|
| 131 | ax4.set_ylim(0, 1.0)
|
|---|
| 132 | ax4.set_xlabel("Seconds after Reset")
|
|---|
| 133 | ax4.set_ylabel("Meters")
|
|---|
| 134 | ax4.text( 0.1, 0.7,
|
|---|
| 135 | "percentile after 10 min:\n68%% : %8.3f m\n95%% : %8.3f m" % (percMin[10][68], percMin[10][95]),
|
|---|
| 136 | transform=ax4.transAxes)
|
|---|
| 137 | ax4.text( 0.4, 0.5,
|
|---|
| 138 | "percentile after 20 min:\n68%% : %8.3f m\n95%% : %8.3f m" % (percMin[20][68], percMin[20][95]),
|
|---|
| 139 | transform=ax4.transAxes)
|
|---|
| 140 | ax4.text( 0.7, 0.3,
|
|---|
| 141 | "percentile after 30 min:\n68%% : %8.3f m\n95%% : %8.3f m" % (percMin[30][68], percMin[30][95]),
|
|---|
| 142 | transform=ax4.transAxes)
|
|---|
| 143 |
|
|---|
| 144 | ### plt.show()
|
|---|
| 145 | plt.savefig(pngFile)
|
|---|
| 146 | plt.close()
|
|---|
| 147 |
|
|---|
| 148 | # Output Statistics
|
|---|
| 149 | # -----------------
|
|---|
| 150 | if statFile:
|
|---|
| 151 | with open(statFile, 'w') as outStat:
|
|---|
| 152 | print("%s %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f" %
|
|---|
| 153 | (fileName,
|
|---|
| 154 | percMin[10][68], percMin[10][95],
|
|---|
| 155 | percMin[20][68], percMin[20][95],
|
|---|
| 156 | percMin[30][68], percMin[30][95]), file = outStat)
|
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 | # Main Program
|
|---|
| 160 | ##########################################################################################
|
|---|
| 161 | if __name__ == '__main__':
|
|---|
| 162 | import sys
|
|---|
| 163 | import argparse
|
|---|
| 164 |
|
|---|
| 165 | parser = argparse.ArgumentParser()
|
|---|
| 166 | parser.add_argument("crdFile")
|
|---|
| 167 | parser.add_argument("fileName")
|
|---|
| 168 | parser.add_argument("--pngFile", type=str)
|
|---|
| 169 | parser.add_argument("--statFile", type=str)
|
|---|
| 170 | parser.add_argument("--title", type=str)
|
|---|
| 171 | args = parser.parse_args()
|
|---|
| 172 |
|
|---|
| 173 | crdFile = args.crdFile
|
|---|
| 174 | fileName = args.fileName
|
|---|
| 175 | if args.pngFile is None:
|
|---|
| 176 | pngFile = pth.Path(fileName).with_suffix(".png")
|
|---|
| 177 | else :
|
|---|
| 178 | pngFile = args.pngFile
|
|---|
| 179 | if args.title is None:
|
|---|
| 180 | title = fileName
|
|---|
| 181 | else:
|
|---|
| 182 | title = args.title
|
|---|
| 183 | statFile = args.statFile
|
|---|
| 184 |
|
|---|
| 185 | dateFmt = "%Y-%m-%d_%H:%M:%S.%f"
|
|---|
| 186 | dfCrd = utils.readcrdfile(crdFile)
|
|---|
| 187 | station = None
|
|---|
| 188 |
|
|---|
| 189 | # Read Data
|
|---|
| 190 | # ---------
|
|---|
| 191 | data = Data()
|
|---|
| 192 | flgReset = True
|
|---|
| 193 | timeStr = None
|
|---|
| 194 | with open(fileName, 'r') as inFile:
|
|---|
| 195 | for line in inFile:
|
|---|
| 196 |
|
|---|
| 197 | if line.find("RESET FILTER") == 0:
|
|---|
| 198 | flgReset = True
|
|---|
| 199 |
|
|---|
| 200 | elif line.find("X =") >= 0:
|
|---|
| 201 | fields = line.split()
|
|---|
| 202 |
|
|---|
| 203 | dateStr = fields[0]
|
|---|
| 204 | staName = fields[1]
|
|---|
| 205 | xx = float(fields[4])
|
|---|
| 206 | yy = float(fields[9])
|
|---|
| 207 | zz = float(fields[14])
|
|---|
| 208 | isFix = (fields[32] == "fix")
|
|---|
| 209 | if isFix:
|
|---|
| 210 | numFix = int(fields[33])
|
|---|
| 211 | else:
|
|---|
| 212 | numFix = 0
|
|---|
| 213 |
|
|---|
| 214 | if station is None:
|
|---|
| 215 | station = utils.getsta(dfCrd, staName)
|
|---|
| 216 | if station is None:
|
|---|
| 217 | raise Exception("Station %s not found" % staName)
|
|---|
| 218 | ell = utils.xyz2ell((xx, yy, zz))
|
|---|
| 219 |
|
|---|
| 220 | xyz = (xx - station.xx,
|
|---|
| 221 | yy - station.yy,
|
|---|
| 222 | zz - station.zz)
|
|---|
| 223 |
|
|---|
| 224 | neu = utils.xyz2neu(ell, xyz)
|
|---|
| 225 |
|
|---|
| 226 | time = datetime.datetime.strptime(dateStr, dateFmt)
|
|---|
| 227 | data.append(time, neu[0], neu[1], neu[2], isFix, numFix, flgReset)
|
|---|
| 228 | flgReset = False
|
|---|
| 229 |
|
|---|
| 230 | if timeStr is None:
|
|---|
| 231 | timeStr = time.strftime("%Y-%m-%d")
|
|---|
| 232 |
|
|---|
| 233 | run_plot_client(staName, data, title, timeStr, pngFile, statFile)
|
|---|