├── .gitignore ├── README.md ├── data ├── images │ ├── flightview.png │ ├── membership.png │ └── phase.png ├── sample_adsb_decoded.csv └── test_segment.pkl ├── flightextract.py ├── flightphase.py ├── flightview.py └── test_phases.py /.gitignore: -------------------------------------------------------------------------------- 1 | data/tmp 2 | !empty 3 | 4 | .DS_Store 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flight Data Processor 2 | 3 | This is a python library to process and analyze flight data (e.g. from decoded ADS-B messages). Following functions and algorithms are implemented: 4 | 5 | - Extract continuous full or partial flight path data 6 | - Unsupervised Machine Learning, Clustering using DBSCAN 7 | - Smoothing, filtering, and interpolating flight data 8 | - Spline filtering 9 | - Weighted average filtering 10 | - Time-based weighted average filtering 11 | - Segmenting flight into different phases: 12 | - using Fuzzy Logic with data interpolation methods 13 | - supporting phases: ground, climb, descend, cruise, and level flight 14 | 15 | # Paper and citation 16 | 17 | The source code of this repository complements the following publication: 18 | 19 | https://arc.aiaa.org/doi/10.2514/1.I010520 20 | 21 | If you use the code for your research, please cite: 22 | ``` 23 | @article{sun2017flight, 24 | title={Flight Extraction and Phase Identification for Large Automatic Dependent Surveillance--Broadcast Datasets}, 25 | author={Sun, Junzi and Ellerbroek, Joost and Hoekstra, Jacco}, 26 | journal={Journal of Aerospace Information Systems}, 27 | pages={1--6}, 28 | year={2017}, 29 | publisher={American Institute of Aeronautics and Astronautics} 30 | } 31 | ``` 32 | 33 | # Required software 34 | - Python 3.x 35 | - MongoDB 3 36 | - Dependent Python libraries 37 | - scipy 38 | - scikit-learn 39 | - skfuzzy 40 | - pymongo 41 | 42 | # Code examples 43 | 44 | ## 1. Flight clustering 45 | 46 | 1. install MongoDB 47 | 48 | 2. extract flight from ADS-B positions 49 | 50 | ```bash 51 | $ python flightextract.py --csv data/sample_adsb_decoded.csv --db test_db --coll flights 52 | ``` 53 | 54 | ## 2. Fuzzy segmentation 55 | You can use previously created collection in MongoDB. Or, using provided pickled data, run: 56 | 57 | ```bash 58 | $ python test_phases.py 59 | ``` 60 | 61 | The essential code to identify the flight phases is: 62 | 63 | ```python 64 | import flightphase 65 | flightphase.fuzzylabels(times, alts, spds, rocs) 66 | ``` 67 | 68 | ## 3. View flights 69 | 70 | Use the same previously created MongoDB collection: 71 | 72 | ```bash 73 | $ python flightview.py --db test_db --coll flights 74 | ``` 75 | 76 | 77 | ## Screen shots 78 | ### example flight phase identification 79 | ![flight phases](data/images/phase.png?raw=true) 80 | 81 | ### example fuzzy logic membership functions 82 | ![fuzzy logic membership](data/images/membership.png?raw=true) 83 | 84 | ### example flight viewer 85 | ![flight viewer](data/images/flightview.png?raw=true) 86 | -------------------------------------------------------------------------------- /data/images/flightview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junzis/flight-data-processor/12ed7b73d202b1d3c836323026725120501a40e5/data/images/flightview.png -------------------------------------------------------------------------------- /data/images/membership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junzis/flight-data-processor/12ed7b73d202b1d3c836323026725120501a40e5/data/images/membership.png -------------------------------------------------------------------------------- /data/images/phase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junzis/flight-data-processor/12ed7b73d202b1d3c836323026725120501a40e5/data/images/phase.png -------------------------------------------------------------------------------- /flightextract.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import pandas as pd 4 | from itertools import cycle 5 | from matplotlib import pyplot as plt 6 | from sklearn import preprocessing 7 | from pymongo import MongoClient 8 | from collections import OrderedDict 9 | from sklearn.cluster import DBSCAN 10 | 11 | # Constants 12 | HOST = "localhost" # MongoDB host 13 | PORT = 27017 # MongoDB port 14 | MIN_DATA_SIZA = 100 # minimal number of data in a flight 15 | CHUNK_SIZE = 50 # number of icaos to be processed in chunks 16 | TEST_FLAG = True # if this is a test run 17 | 18 | # get script arguments 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument( 21 | "--csv", dest="input_csv", required=True, help="decoded adsb csv file" 22 | ) 23 | parser.add_argument("--db", dest="db", required=True) 24 | parser.add_argument("--coll", dest="output_coll", help="Flight collection name") 25 | args = parser.parse_args() 26 | 27 | mdb = args.db 28 | pos_csv = args.input_csv 29 | flights_coll = args.output_coll 30 | 31 | mongo_client = MongoClient(HOST, PORT) 32 | 33 | if flights_coll: 34 | TEST_FLAG = False 35 | mcollflights = mongo_client[mdb][flights_coll] 36 | mcollflights.drop() # clear the segment collection first 37 | 38 | print("[1] Querying database.") 39 | 40 | df = pd.read_csv(pos_csv) 41 | df.drop_duplicates(subset=["ts"], inplace=True) 42 | 43 | # Find all ICAO IDs in the dataset 44 | dfcount = df.groupby("icao").size().reset_index(name="counts") 45 | icaos = dfcount[dfcount.counts > 100].icao.tolist() 46 | 47 | print("[2] %d number of valid ICAOs." % len(icaos)) 48 | 49 | for i in range(0, len(icaos), CHUNK_SIZE): 50 | 51 | print("[3][%d-%d of %d] ICAOs beening processed." % (i, i + CHUNK_SIZE, len(icaos))) 52 | 53 | chunk = icaos[i : i + CHUNK_SIZE] 54 | 55 | print(" [a] fetching records") 56 | 57 | dfchunk = df[df.icao.isin(chunk)] 58 | ids = dfchunk["icao"].as_matrix() 59 | lats = dfchunk["lat"].as_matrix() 60 | lons = dfchunk["lon"].as_matrix() 61 | alts = dfchunk["alt"].as_matrix() 62 | spds = dfchunk["spd"].as_matrix() 63 | hdgs = dfchunk["hdg"].as_matrix() 64 | rocs = dfchunk["roc"].as_matrix() 65 | times = dfchunk["ts"].as_matrix() 66 | 67 | ##################################################################### 68 | # Continous fligh path extraction using machine learning algorithms 69 | ##################################################################### 70 | 71 | print(" [b] data scaling") 72 | 73 | # transform the text ids into numbers 74 | # ------------------------------------ 75 | le = preprocessing.LabelEncoder() 76 | encoded_ids = le.fit_transform(ids) 77 | 78 | # Apply feature scaling - on altitude, spds, and times 79 | # ------------------------------------------------------ 80 | mms = preprocessing.MinMaxScaler(feature_range=(0, 1000)) 81 | times_norm = mms.fit_transform(times.reshape((-1, 1))) 82 | dt = mms.scale_ * 0.5 * 60 * 60 # time interval of 30 mins 83 | 84 | mms = preprocessing.MinMaxScaler(feature_range=(0, 100)) 85 | alts_norm = mms.fit_transform(alts.reshape((-1, 1))) 86 | 87 | print(" [c] creating machine learning dataset.") 88 | 89 | # aggregate data by there ids 90 | # ---------------------------- 91 | acs = {} 92 | for i in range(len(ids)): 93 | if ids[i] not in list(acs.keys()): 94 | acs[ids[i]] = [] 95 | 96 | acs[ids[i]].append( 97 | [ 98 | times_norm[i], 99 | alts_norm[i], 100 | times[i], 101 | lats[i], 102 | lons[i], 103 | int(alts[i]), 104 | spds[i], 105 | hdgs[i], 106 | rocs[i], 107 | ] 108 | ) 109 | 110 | print(" [d] start clustering, and saving results to DB") 111 | 112 | # Apply clustering method 113 | # ------------------------ 114 | cluster = DBSCAN(eps=dt, min_samples=MIN_DATA_SIZA) 115 | 116 | acsegs = {} 117 | total = len(list(acs.keys())) 118 | count = 0 119 | print(" * processing ", end=" ") 120 | for k in list(acs.keys()): 121 | count += 1 122 | print(".", end=" ") 123 | 124 | data = np.asarray(acs[k]) 125 | 126 | tdata = np.copy(data) 127 | tdata[:, 1:] = 0 128 | cluster.fit(tdata) 129 | labels = cluster.labels_ 130 | n_clusters = np.unique(labels).size 131 | # print("n_clusters : %d" % n_clusters) 132 | 133 | if not TEST_FLAG: 134 | for i in range(n_clusters): 135 | mask = labels == i 136 | c = data[mask, 2:] 137 | c = c[c[:, 0].argsort()] # sort by ts 138 | 139 | if len(c) > MIN_DATA_SIZA: 140 | mcollflights.insert_one( 141 | OrderedDict( 142 | [ 143 | ("icao", k), 144 | ("ts", c[:, 0].tolist()), 145 | ("lat", c[:, 1].tolist()), 146 | ("lon", c[:, 2].tolist()), 147 | ("alt", c[:, 3].tolist()), 148 | ("spd", c[:, 4].tolist()), 149 | ("hdg", c[:, 5].tolist()), 150 | ("roc", c[:, 6].tolist()), 151 | ] 152 | ) 153 | ) 154 | 155 | # Plot result 156 | if TEST_FLAG: 157 | colorset = cycle(["purple", "green", "red", "blue", "orange"]) 158 | for i, c in zip(list(range(n_clusters)), colorset): 159 | mask = labels == i 160 | ts = data[mask, 0].tolist() 161 | alts = data[mask, 5].tolist() 162 | if len(alts) > MIN_DATA_SIZA: 163 | plt.plot(ts, alts, "w", color=c, marker=".", alpha=1.0) 164 | 165 | plt.xlim([0, 1000]) 166 | plt.draw() 167 | plt.waitforbuttonpress(-1) 168 | plt.clf() 169 | print("") 170 | 171 | print() 172 | print("[4] All completed") 173 | -------------------------------------------------------------------------------- /flightphase.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import skfuzzy as fuzz 3 | from matplotlib import pyplot as plt 4 | from scipy.interpolate import UnivariateSpline 5 | 6 | # margin of outputs rules determin if a state is a valid state 7 | STATE_DIFF_MARGIN = 0.2 8 | 9 | # logic states 10 | alt_range = np.arange(0, 40000, 1) 11 | roc_range = np.arange(-4000, 4000, 0.1) 12 | spd_range = np.arange(0, 600, 1) 13 | states = np.arange(0, 6, 0.01) 14 | 15 | alt_gnd = fuzz.zmf(alt_range, 0, 200) 16 | alt_lo = fuzz.gaussmf(alt_range, 10000, 5000) 17 | alt_hi = fuzz.gaussmf(alt_range, 35000, 20000) 18 | 19 | roc_zero = fuzz.gaussmf(roc_range, 0, 100) 20 | roc_plus = fuzz.smf(roc_range, 10, 1000) 21 | roc_minus = fuzz.zmf(roc_range, -1000, -10) 22 | 23 | spd_hi = fuzz.gaussmf(spd_range, 600, 100) 24 | spd_md = fuzz.gaussmf(spd_range, 200, 100) 25 | spd_lo = fuzz.gaussmf(spd_range, 0, 50) 26 | 27 | state_ground = fuzz.gaussmf(states, 1, 0.1) 28 | state_climb = fuzz.gaussmf(states, 2, 0.1) 29 | state_descent = fuzz.gaussmf(states, 3, 0.1) 30 | state_cruise = fuzz.gaussmf(states, 4, 0.1) 31 | state_level = fuzz.gaussmf(states, 5, 0.1) 32 | 33 | state_label_map = {1: "GND", 2: "CL", 3: "DE", 4: "CR", 5: "LVL", 6: "NA"} 34 | 35 | 36 | # Visualize these universes and membership functions 37 | def plot_logics(): 38 | plt.figure(figsize=(6, 6)) 39 | 40 | plt.subplot(411) 41 | plt.plot(alt_range, alt_gnd, lw=2, label="Ground") 42 | plt.plot(alt_range, alt_lo, lw=2, label="Low") 43 | plt.plot(alt_range, alt_hi, lw=2, label="High") 44 | plt.ylim([-0.05, 1.05]) 45 | plt.ylabel("Altitude (ft)") 46 | plt.yticks([0, 1]) 47 | plt.legend(prop={"size": 7}) 48 | 49 | plt.subplot(412) 50 | plt.plot(roc_range, roc_zero, lw=2, label="Zero") 51 | plt.plot(roc_range, roc_plus, lw=2, label="Positive") 52 | plt.plot(roc_range, roc_minus, lw=2, label="Negative") 53 | plt.ylim([-0.05, 1.05]) 54 | plt.ylabel("RoC (ft/m)") 55 | plt.yticks([0, 1]) 56 | plt.legend(prop={"size": 7}) 57 | 58 | plt.subplot(413) 59 | plt.plot(spd_range, spd_hi, lw=2, label="High") 60 | plt.plot(spd_range, spd_md, lw=2, label="Midium") 61 | plt.plot(spd_range, spd_lo, lw=2, label="Low") 62 | plt.ylim([-0.05, 1.05]) 63 | plt.ylabel("Speed (kt)") 64 | plt.yticks([0, 1]) 65 | plt.legend(prop={"size": 7}) 66 | 67 | plt.subplot(414) 68 | plt.plot(states, state_ground, lw=2, label="ground") 69 | plt.plot(states, state_climb, lw=2, label="climb") 70 | plt.plot(states, state_descent, lw=2, label="descent") 71 | plt.plot(states, state_cruise, lw=2, label="cruise") 72 | plt.plot(states, state_level, lw=2, label="level flight") 73 | plt.ylim([-0.05, 1.05]) 74 | plt.ylabel("Flight Phases") 75 | plt.yticks([0, 1]) 76 | plt.legend(prop={"size": 7}) 77 | 78 | plt.tight_layout() 79 | plt.show() 80 | 81 | 82 | def fuzzylabels(ts, alts, spds, rocs, twindow=60): 83 | """ 84 | Fuzzy logic to determine the segments of the flight data 85 | segments are: ground [GND], climb [CL], descent [DE], cruise [CR]. 86 | 87 | Default time window is 60 second. 88 | """ 89 | 90 | if len(set([len(ts), len(alts), len(spds), len(rocs)])) > 1: 91 | raise RuntimeError("input ts and alts must have same length") 92 | 93 | n = len(ts) 94 | 95 | ts = np.array(ts) 96 | ts = ts - ts[0] 97 | idxs = np.arange(0, n) 98 | 99 | alts = UnivariateSpline(ts, alts)(ts) 100 | spds = UnivariateSpline(ts, spds)(ts) 101 | rocs = UnivariateSpline(ts, rocs)(ts) 102 | 103 | labels = ["NA"] * n 104 | 105 | twindows = ts // twindow 106 | print 107 | 108 | for tw in range(0, int(max(twindows))): 109 | if tw not in twindows: 110 | continue 111 | 112 | mask = twindows == tw 113 | 114 | idxchk = idxs[mask] 115 | altchk = alts[mask] 116 | spdchk = spds[mask] 117 | rocchk = rocs[mask] 118 | 119 | # mean value or extream value as range 120 | alt = max(min(np.mean(altchk), alt_range[-1]), alt_range[0]) 121 | spd = max(min(np.mean(spdchk), spd_range[-1]), spd_range[0]) 122 | roc = max(min(np.mean(rocchk), roc_range[-1]), roc_range[0]) 123 | 124 | # make sure values are within the boundaries 125 | alt = max(min(alt, alt_range[-1]), alt_range[0]) 126 | spd = max(min(spd, spd_range[-1]), spd_range[0]) 127 | roc = max(min(roc, roc_range[-1]), roc_range[0]) 128 | 129 | alt_level_gnd = fuzz.interp_membership(alt_range, alt_gnd, alt) 130 | alt_level_lo = fuzz.interp_membership(alt_range, alt_lo, alt) 131 | alt_level_hi = fuzz.interp_membership(alt_range, alt_hi, alt) 132 | 133 | spd_level_hi = fuzz.interp_membership(spd_range, spd_hi, spd) 134 | spd_level_md = fuzz.interp_membership(spd_range, spd_md, spd) 135 | spd_level_lo = fuzz.interp_membership(spd_range, spd_lo, spd) 136 | 137 | roc_level_zero = fuzz.interp_membership(roc_range, roc_zero, roc) 138 | roc_level_plus = fuzz.interp_membership(roc_range, roc_plus, roc) 139 | roc_level_minus = fuzz.interp_membership(roc_range, roc_minus, roc) 140 | 141 | rule_ground = min(alt_level_gnd, roc_level_zero, spd_level_lo) 142 | state_activate_ground = np.fmin(rule_ground, state_ground) 143 | 144 | rule_climb = min(alt_level_lo, roc_level_plus, spd_level_md) 145 | state_activate_climb = np.fmin(rule_climb, state_climb) 146 | 147 | rule_descent = min(alt_level_lo, roc_level_minus, spd_level_md) 148 | state_activate_descent = np.fmin(rule_descent, state_descent) 149 | 150 | rule_cruise = min(alt_level_hi, roc_level_zero, spd_level_hi) 151 | state_activate_cruise = np.fmin(rule_cruise, state_cruise) 152 | 153 | rule_level = min(alt_level_lo, roc_level_zero, spd_level_md) 154 | state_activate_level = np.fmin(rule_level, state_level) 155 | 156 | aggregated = np.max( 157 | np.vstack( 158 | [ 159 | state_activate_ground, 160 | state_activate_climb, 161 | state_activate_descent, 162 | state_activate_cruise, 163 | state_activate_level, 164 | ] 165 | ), 166 | axis=0, 167 | ) 168 | 169 | state_raw = fuzz.defuzz(states, aggregated, "lom") 170 | state = int(round(state_raw)) 171 | if state > 6: 172 | state = 6 173 | if state < 1: 174 | state = 1 175 | 176 | if len(idxchk) > 0: 177 | label = state_label_map[state] 178 | labels[idxchk[0] : (idxchk[-1] + 1)] = [label] * len(idxchk) 179 | 180 | return labels 181 | -------------------------------------------------------------------------------- /flightview.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import pandas as pd 4 | from scipy import interpolate 5 | from matplotlib import pyplot as plt 6 | from pymongo import MongoClient 7 | from mpl_toolkits.basemap import Basemap 8 | import flightphase 9 | 10 | 11 | def fill_nan(A): 12 | # interpolate to fill nan values 13 | inds = np.arange(A.shape[0]) 14 | good = np.where(np.isfinite(A)) 15 | f = interpolate.interp1d(inds[good], A[good], fill_value="extrapolate") 16 | B = np.where(np.isfinite(A), A, f(inds)) 17 | return B 18 | 19 | 20 | # get script arguments 21 | parser = argparse.ArgumentParser() 22 | 23 | parser.add_argument("--db", dest="db", required=True) 24 | parser.add_argument( 25 | "--coll", dest="coll", required=True, help="Flights data collection name" 26 | ) 27 | args = parser.parse_args() 28 | 29 | DB = args.db 30 | COLL = args.coll 31 | 32 | # Configuration for the database 33 | mongo_client = MongoClient("localhost", 27017) 34 | mcoll = mongo_client[DB][COLL] 35 | 36 | plt.figure(figsize=(10, 4)) 37 | 38 | res = mcoll.find() 39 | for r in res: 40 | icao = r["icao"] 41 | print(r["_id"], icao) 42 | 43 | df = pd.DataFrame(r) 44 | df.drop_duplicates(subset=["ts"], inplace=True) 45 | 46 | times = np.array(df["ts"]) 47 | times = times - times[0] 48 | lats = np.array(df["lat"]) 49 | lons = np.array(df["lon"]) 50 | 51 | if "alt" in r: 52 | alts = np.array(df["alt"]) 53 | spds = np.array(df["spd"]) 54 | rocs = np.array(df["roc"]) 55 | elif "H" in r: 56 | Hs = np.array(df["H"]) 57 | vgxs = np.array(df["vgx"]) 58 | vgys = np.array(df["vgy"]) 59 | vhs = np.array(df["vh"]) 60 | alts = Hs / 0.3048 61 | spds = np.sqrt(vgxs ** 2 + vgys ** 2) / 0.5144 62 | rocs = vhs / 0.00508 63 | 64 | try: 65 | labels = flightphase.fuzzylabels(times, alts, spds, rocs) 66 | except: 67 | continue 68 | 69 | colormap = { 70 | "GND": "black", 71 | "CL": "green", 72 | "CR": "blue", 73 | "DE": "orange", 74 | "LVL": "purple", 75 | "NA": "red", 76 | } 77 | 78 | colors = [colormap[l] for l in labels] 79 | 80 | # setup mercator map projection. 81 | plt.suptitle("press any key to continue to next example...") 82 | 83 | plt.subplot(121) 84 | m = Basemap( 85 | llcrnrlon=min(lons) - 2, 86 | llcrnrlat=min(lats) - 2, 87 | urcrnrlon=max(lons) + 2, 88 | urcrnrlat=max(lats) + 2, 89 | resolution="l", 90 | projection="merc", 91 | ) 92 | m.fillcontinents() 93 | # plot SIL as a fix point 94 | latSIL = 51.989884 95 | lonSIL = 4.375374 96 | m.plot(lonSIL, latSIL, latlon=True, marker="o", c="red", zorder=9) 97 | m.scatter(lons, lats, latlon=True, marker=".", c=colors, lw=0, zorder=10) 98 | 99 | plt.subplot(122) 100 | plt.scatter(times, alts, marker=".", c=colors, lw=0) 101 | plt.ylabel("altitude (ft)") 102 | 103 | # plt.tight_layout() 104 | plt.draw() 105 | plt.waitforbuttonpress(-1) 106 | plt.clf() 107 | -------------------------------------------------------------------------------- /test_phases.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pickle 4 | import matplotlib.pyplot as plt 5 | import flightphase 6 | from scipy.interpolate import UnivariateSpline 7 | 8 | flightphase.plot_logics() 9 | 10 | # get a sample data 11 | datadir = os.path.dirname(os.path.realpath(__file__)) 12 | dataset = pickle.load(open(datadir + "/data/test_segment.pkl", "rb"), encoding="bytes") 13 | 14 | for data in dataset: 15 | times = np.array(data["ts"]) 16 | times = times - times[0] 17 | alts = np.array(data["H"]) / 0.3048 18 | vgx = np.array(data["vgx"]) 19 | vgy = np.array(data["vgy"]) 20 | spds = np.sqrt(vgx ** 2 + vgy ** 2) / 0.514444 21 | rocs = np.array(data["vh"]) / 0.00508 22 | 23 | labels = flightphase.fuzzylabels(times, alts, spds, rocs) 24 | 25 | colormap = { 26 | "GND": "black", 27 | "CL": "green", 28 | "CR": "blue", 29 | "DE": "orange", 30 | "LVL": "cyan", 31 | "NA": "red", 32 | } 33 | 34 | colors = [colormap[l] for l in labels] 35 | 36 | altspl = UnivariateSpline(times, alts)(times) 37 | spdspl = UnivariateSpline(times, spds)(times) 38 | rocspl = UnivariateSpline(times, rocs)(times) 39 | 40 | plt.subplot(311) 41 | plt.title("press any key to continue to next example...") 42 | plt.plot(times, altspl, "-", color="k", alpha=0.5) 43 | plt.scatter(times, alts, marker=".", c=colors, lw=0) 44 | plt.ylabel("altitude (ft)") 45 | 46 | plt.subplot(312) 47 | plt.plot(times, spdspl, "-", color="k", alpha=0.5) 48 | plt.scatter(times, spds, marker=".", c=colors, lw=0) 49 | plt.ylabel("speed (kt)") 50 | 51 | plt.subplot(313) 52 | plt.plot(times, rocspl, "-", color="k", alpha=0.5) 53 | plt.scatter(times, rocs, marker=".", c=colors, lw=0) 54 | plt.ylabel("roc (fpm)") 55 | 56 | plt.tight_layout() 57 | plt.draw() 58 | plt.waitforbuttonpress(-1) 59 | plt.clf() 60 | --------------------------------------------------------------------------------