├── LICENSE ├── MANIFEST.in ├── README.md ├── apps ├── basic_example.py ├── gfs │ └── gfs_files_end_up_here.txt ├── predict.py ├── sonde_predict.py ├── sonde_predict.sh ├── web │ ├── index.html │ └── static │ │ ├── Leaflet.Control.Custom.js │ │ └── leaflet.polylineDecorator.js └── wind_grabber.sh ├── cusfpredict ├── __init__.py ├── gfs.py ├── predict.py ├── reader.py └── utils.py ├── pyproject.toml ├── requirements.txt ├── setup.py └── src ├── .gitignore ├── CMakeLists.txt ├── altitude.c ├── altitude.h ├── cmake_install.cmake ├── ini ├── LICENSE ├── README ├── README.orig ├── dictionary.c ├── dictionary.h ├── iniparser.c └── iniparser.h ├── pred.c ├── pred.h ├── run_model.c ├── run_model.h ├── util ├── getdelim.c ├── getdelim.h ├── getline.c ├── getline.h ├── gopt.c ├── gopt.h ├── random.c └── random.h └── wind ├── wind_file.c ├── wind_file.h ├── wind_file_cache.c └── wind_file_cache.h /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE.txt 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CUSF Standalone Predictor - Python Wrapper 2 | This is a semi-fork of the [CUSF Standalone Predictor](https://github.com/jonsowman/cusf-standalone-predictor/), which provides a Python wrapper around the predictor binary, and provides a means of gathering the requisite wind data. 3 | 4 | 2018-02 Update: Wind downloader updated to use the [NOMADS GRIB Filter](http://nomads.ncep.noaa.gov/txt_descriptions/grib_filter_doc.shtml), as the OpenDAP interface stopped working. As such, we no longer require PyDAP, but we do now require GDAL to read in the GRIB2 files. 5 | 6 | 2021-03 Update: We have dropped GDAL in favour of cfgrib. 7 | 8 | ## 1. Dependencies 9 | On a Raspbian/Ubuntu/Debian system, you can get most of the required dependencies using: 10 | ``` 11 | $ sudo apt-get install git cmake build-essential libglib2.0-dev python3-numpy python3-requests python3-dateutil python3-pip libeccodes-data libeccodes0 libgeos-dev libatlas-base-dev 12 | ``` 13 | 14 | ## 2. Download this Repository 15 | Clone this repository with: 16 | ``` 17 | $ git clone https://github.com/darksidelemm/cusf_predictor_wrapper.git 18 | ``` 19 | 20 | ## 3. Install the Python Wrapper 21 | 22 | ### Using Pip (Preferred) 23 | The easiest way to install the python wrapper is via pip: 24 | ``` 25 | $ sudo pip3 install cusfpredict 26 | ``` 27 | (Replace pip3 with pip if necessary on your system, however note that Python 2 is *not supported!*) 28 | 29 | ### From Source (If Necessary) 30 | If you couldn't install from pip for whatever reason, then the cusfpredict python package can then be installed in the usual Python way: 31 | ``` 32 | $ cd cusf_predictor_wrapper 33 | $ sudo python3 setup.py install 34 | ``` 35 | 36 | This should grab the other required Python dependencies, but if not, they are available in requirements.txt and can be preinstalled using 37 | ``` 38 | $ sudo pip3 install -r requirements.txt 39 | ``` 40 | 41 | Note that as pip3 installes shapely, it may throw some errors about not finding `geos_c.h`. These can be ignored. 42 | 43 | 44 | ## 4. Building the Predictor Binary 45 | The predictor itself is a binary ('pred'), which we (currently) build seperately, using CMake. 46 | 47 | From within the cusf_predictor_wrapper directory, run the following to build the predictor binary: 48 | 49 | ``` 50 | $ cd src 51 | $ mkdir build 52 | $ cd build 53 | $ cmake ../ 54 | $ make 55 | ``` 56 | 57 | The `pred` binary then needs to be copied into the 'apps' directory, or somewhere else useful, i.e. 58 | ``` 59 | $ cp pred ../../apps/ 60 | ``` 61 | 62 | If you are building this utility for use with chasemapper, then you should copy `pred` into the chasemapper directory: 63 | ``` 64 | $ cp pred ~/chasemapper/ 65 | ``` 66 | 67 | A pre-compiled Windows binary of the predictor is available here: http://rfhead.net/horus/cusf_standalone_predictor.zip 68 | Use at your own risk! 69 | 70 | 71 | ## 5. Getting Wind Data 72 | The predictor binary uses a custom wind data format, extracted from NOAA's Global Forecast System wind models. The `cusfpredict.gfs` Python module pulls down and formats the relevant data from NOAA's [NOMADS](http://nomads.ncep.noaa.gov) server. 73 | 74 | If you are using this library with ChaseMapper, you will need to adjust the download command in the [chasemapper configuration file](https://github.com/projecthorus/chasemapper/blob/master/horusmapper.cfg.example#L135). 75 | 76 | An example of running it is as follows: 77 | ``` 78 | $ python3 -m cusfpredict.gfs --lat=-33 --lon=139 --latdelta=10 --londelta=10 -f 24 -m 0p50 -o gfs 79 | ``` 80 | 81 | The command line arguments are as follows: 82 | ``` 83 | Area of interest: 84 | --lat Latitude (Decimal Degrees) of the centre of the area to gather. 85 | --lon Longitude (Decimal Degrees) of the centre of the area to gather. 86 | --latdelta Gather data from lat+/-latdelta 87 | --londelta Gather data from lon+/-londelta 88 | 89 | Time of interest: 90 | -f X Gather data up to X hours into the future, from the start of the most recent model. (Note that this can be up to 8 hours in the past.) Make sure you get enough for the flight! 91 | 92 | GFS Model Choice: 93 | -m Choose between either: 94 | 0p50 - 0.5 Degree Spatial, 3-hour Time Resolution 95 | 0p25_1hr - 0.25 Degree Spatial, 1-hour Time Resolution (default) 96 | 97 | Other settings: 98 | -v Verbose output 99 | -o output_dir (Where to save the gfs data to, defaults to ./gfs/) 100 | ``` 101 | 102 | The higher resolution wind model you choose, the larger the amount of data to download, and the longer it will take. It also increases the prediction calculation time (though not significantly). 103 | 104 | `wind_grabber.sh` is an example script to automatically grab wind data first to a temporary directory, and then to the final gfs directory. This could be run from a cronjob to keep the wind data up-to-date. 105 | 106 | New wind models become available approximately every 6 hours, approximately 4 hours after the model's nominal time (i.e. the 00Z model becomes available around 04Z). Information on the status of the GFS model generation is available here: http://www.nco.ncep.noaa.gov/pmb/nwprod/prodstat_new/ 107 | 108 | ## 5. Using the Predictor 109 | (Note: This section is intended for users within to run predictions from within their own software. If you are just installing this library for use with chasemapper, you can skip all of this!) 110 | 111 | The basic usage of the predictor from within Python is as follows: 112 | ``` 113 | import datetime 114 | from cusfpredict.predict import Predictor 115 | 116 | pred = Predictor(bin_path='./pred', gfs_path='./gfs') 117 | 118 | flight_path = pred.predict( 119 | launch_lat=-34.9499, 120 | launch_lon=138.5194, 121 | launch_alt=0.0, 122 | ascent_rate=5.0, 123 | descent_rate=5.0, 124 | burst_alt=30000, 125 | launch_time=datetime.datetime.utcnow() 126 | ) 127 | 128 | ``` 129 | 130 | Note that the launch time is a datetime object interpreted as UTC, so make sure you convert your launch time as appropriate. 131 | 132 | The output is a list-of-lists, containing entries of [utctimestamp, lat, lon, alt], i.e.: 133 | 134 | ``` 135 | >>> flight_path 136 | [[1516702953, -34.9471, 138.517, 250.0], [1516703003, -34.9436, 138.514, 500.0], , [1516703053, -34.9415, 138.513, 750.0]] 137 | ``` 138 | 139 | There is also a command-line utility, `predict.py`, which allows performing predictions with launch parameter variations: 140 | ``` 141 | usage: predict.py [-h] [-a ASCENTRATE] [-d DESCENTRATE] [-b BURSTALT] 142 | [--launchalt LAUNCHALT] [--latitude LATITUDE] 143 | [--longitude LONGITUDE] [--time TIME] [-o OUTPUT] 144 | [--altitude_deltas ALTITUDE_DELTAS] 145 | [--time_deltas TIME_DELTAS] [--absolute] 146 | 147 | optional arguments: 148 | -h, --help show this help message and exit 149 | -a ASCENTRATE, --ascentrate ASCENTRATE 150 | Ascent Rate (m/s). Default 5m/s 151 | -d DESCENTRATE, --descentrate DESCENTRATE 152 | Descent Rate (m/s). Default 5m/s 153 | -b BURSTALT, --burstalt BURSTALT 154 | Burst Altitude (m). Default 30000m 155 | --launchalt LAUNCHALT 156 | Launch Altitude (m). Default 0m 157 | --latitude LATITUDE Launch Latitude (dd.dddd) 158 | --longitude LONGITUDE 159 | Launch Longitude (dd.dddd) 160 | --time TIME Launch Time (string, UTC). Default = Now 161 | -o OUTPUT, --output OUTPUT 162 | Output KML File. Default = prediction.kml 163 | --altitude_deltas ALTITUDE_DELTAS 164 | Comma-delimited list of altitude deltas. (metres). 165 | --time_deltas TIME_DELTAS 166 | Comma-delimited list of time deltas. (hours) 167 | --absolute Show absolute altitudes for tracks and placemarks. 168 | ``` 169 | 170 | For example, to predict a radiosonde launch from Adelaide Airport (5m/s ascent, 26km burst, 7.5m/s descent), but to look at what happens if the burst altitude is higher or lower than usual: 171 | ``` 172 | $ python3 predict.py --latitude=-34.9499 --longitude=138.5194 -a 5 -d 7.5 -b 26000 --time "2018-01-27 11:15Z" --altitude_deltas="-2000,0,2000" 173 | Running using GFS Model: gfs20180127-00z 174 | 2018-01-27T11:15:00+00:00 5.0/24000.0/7.5 - Landing: -34.8585, 138.9600 at 2018-01-27T13:03:33 175 | 2018-01-27T11:15:00+00:00 5.0/26000.0/7.5 - Landing: -34.8587, 138.8870 at 2018-01-27T13:11:01 176 | 2018-01-27T11:15:00+00:00 5.0/28000.0/7.5 - Landing: -34.8598, 138.7990 at 2018-01-27T13:18:22 177 | KML written to prediction.kml 178 | ``` 179 | 180 | A few other example scripts are located in the 'apps' directory: 181 | * basic_usage.py - Example showing how to write a predicted flight path out to a KML file 182 | * sonde_predict.py - A more complex example, where predictions for the next week's of radiosonde flights are run and written to a KML file. 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /apps/basic_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper - Basic Predictor Usage Example 5 | # Copyright 2017 Mark Jessop 6 | # 7 | # This is an example of running prediction, and outputting data to a KML file. 8 | # 9 | 10 | import datetime, sys 11 | from cusfpredict.predict import Predictor 12 | from cusfpredict.utils import * 13 | 14 | # Predictor Binary and GFS data location 15 | PRED_BINARY = "./pred" 16 | GFS_PATH = "./gfs" 17 | 18 | # Launch Parameters 19 | LAUNCH_TIME = datetime.datetime.utcnow() # Note that this time is interpreted as a UTC time 20 | 21 | LAUNCH_LAT = -34.9499 22 | LAUNCH_LON = 138.5194 23 | LAUNCH_ALT = 0.0 24 | 25 | ASCENT_RATE = 5.0 26 | DESCENT_RATE = 5.0 27 | BURST_ALT = 30000.0 28 | 29 | # Output file 30 | OUTPUT_KML = "prediction.kml" 31 | 32 | # Create the predictor object. 33 | pred = Predictor(bin_path=PRED_BINARY, gfs_path=GFS_PATH) 34 | 35 | # Run the prediction 36 | flight_path = pred.predict( 37 | launch_lat=LAUNCH_LAT, 38 | launch_lon=LAUNCH_LON, 39 | launch_alt=LAUNCH_ALT, 40 | ascent_rate=ASCENT_RATE, 41 | descent_rate=DESCENT_RATE, 42 | burst_alt=BURST_ALT, 43 | launch_time=LAUNCH_TIME) 44 | 45 | # Check the output makes sense 46 | if len(flight_path) == 1: 47 | print("No Wind Data available for this prediction scenario!") 48 | sys.exit(1) 49 | 50 | # Create a list of items to add into the KML output file 51 | geom_items = [] 52 | 53 | # Add the flight path track, and the landing location to the list 54 | geom_items.append(flight_path_to_geometry(flight_path, comment="Predicted Flight Path")) 55 | geom_items.append(flight_path_landing_placemark(flight_path, comment="Predicted Landing Location")) 56 | 57 | # Write everything in the list out to the KML file 58 | write_flight_path_kml(geom_items, filename=OUTPUT_KML, comment="Balloon Flight Prediction") 59 | 60 | # Print out some basic information about the prediction 61 | # Launch time: 62 | launch_position = flight_path[0] 63 | print("Launch Time: %s" % datetime.datetime.utcfromtimestamp(launch_position[0]).isoformat()) 64 | print("Launch Location: %.4f, %.4f" % (launch_position[1],launch_position[2])) 65 | 66 | # Landing position 67 | landing_position = flight_path[-1] 68 | print("Landing Time: %s" % datetime.datetime.utcfromtimestamp(landing_position[0]).isoformat()) 69 | print("Landing Location: %.4f, %.4f" % (landing_position[1],landing_position[2])) 70 | 71 | -------------------------------------------------------------------------------- /apps/gfs/gfs_files_end_up_here.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darksidelemm/cusf_predictor_wrapper/f4352834a037e3e2bf01a3fd7d5a25aa482e27c6/apps/gfs/gfs_files_end_up_here.txt -------------------------------------------------------------------------------- /apps/predict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper - Example Predictor Usage 5 | # Copyright 2017 Mark Jessop 6 | # 7 | # This is an example of running prediction, and outputting data to a KML file. 8 | # 9 | 10 | import fastkml 11 | import datetime 12 | import argparse 13 | from dateutil.parser import parse 14 | from cusfpredict.predict import Predictor 15 | from pygeoif.geometry import Point, LineString 16 | from cusfpredict.utils import * 17 | 18 | # Predictor Parameters 19 | PRED_BINARY = "./pred" 20 | GFS_PATH = "./gfs" 21 | 22 | # Launch Parameters 23 | LAUNCH_TIME = datetime.datetime.utcnow().isoformat() # This can be anything that dateutil can parse. The time *must* be in UTC. 24 | LAUNCH_LAT = -34.9499 25 | LAUNCH_LON = 138.5194 26 | LAUNCH_ALT = 0.0 27 | ASCENT_RATE = 5.0 28 | DESCENT_RATE = 5.0 29 | BURST_ALT = 30000.0 30 | 31 | # Read in command line arguments. 32 | parser = argparse.ArgumentParser() 33 | parser.add_argument('-a', '--ascentrate', type=float, default=ASCENT_RATE, help="Ascent Rate (m/s). Default 5m/s") 34 | parser.add_argument('-d', '--descentrate', type=float, default=DESCENT_RATE, help="Descent Rate (m/s). Default 5m/s") 35 | parser.add_argument('-b', '--burstalt', type=float, default=BURST_ALT, help="Burst Altitude (m). Default 30000m") 36 | parser.add_argument('--launchalt', type=float, default=LAUNCH_ALT, help="Launch Altitude (m). Default 0m") 37 | parser.add_argument('--latitude', type=float, default=LAUNCH_LAT, help="Launch Latitude (dd.dddd)") 38 | parser.add_argument('--longitude', type=float, default=LAUNCH_LON, help="Launch Longitude (dd.dddd)") 39 | parser.add_argument('--time', type=str, default=LAUNCH_TIME, help="Launch Time (string, UTC). Default = Now") 40 | parser.add_argument('-o', '--output', type=str, default='prediction.kml', help="Output KML File. Default = prediction.kml") 41 | parser.add_argument('--altitude_deltas', type=str, default='0', help="Comma-delimited list of altitude deltas. (metres).") 42 | parser.add_argument('--time_deltas', type=str, default='0', help="Comma-delimited list of time deltas. (hours)") 43 | parser.add_argument('--absolute', action="store_true", default=False, help="Show absolute altitudes for tracks and placemarks.") 44 | args = parser.parse_args() 45 | 46 | LAUNCH_TIME = args.time 47 | LAUNCH_LAT = args.latitude 48 | LAUNCH_LON = args.longitude 49 | LAUNCH_ALT = args.launchalt 50 | ASCENT_RATE = args.ascentrate 51 | DESCENT_RATE = args.descentrate 52 | BURST_ALT = args.burstalt 53 | 54 | altitude_mode = "absolute" if args.absolute else "clampToGround" 55 | burst_alt_variations = [float(item) for item in args.altitude_deltas.split(',')] 56 | launch_time_variations = [float(item) for item in args.time_deltas.split(',')] 57 | 58 | predictions = [] 59 | 60 | print("Running using GFS Model: %s" % gfs_model_age(GFS_PATH)) 61 | 62 | # Create the predictor object. 63 | pred = Predictor(bin_path=PRED_BINARY, gfs_path=GFS_PATH) 64 | 65 | for _delta_alt in burst_alt_variations: 66 | for _delta_time in launch_time_variations: 67 | 68 | _burst_alt = BURST_ALT + _delta_alt 69 | _launch_time = parse(LAUNCH_TIME) + datetime.timedelta(seconds=_delta_time*3600) 70 | 71 | flight_path = pred.predict( 72 | launch_lat=LAUNCH_LAT, 73 | launch_lon=LAUNCH_LON, 74 | launch_alt=LAUNCH_ALT, 75 | ascent_rate=ASCENT_RATE, 76 | descent_rate=DESCENT_RATE, 77 | burst_alt=_burst_alt, 78 | launch_time=_launch_time) 79 | 80 | if len(flight_path) == 1: 81 | continue 82 | 83 | pred_comment = "%s %.1f/%.1f/%.1f" % (_launch_time.isoformat(), ASCENT_RATE, _burst_alt, DESCENT_RATE) 84 | 85 | predictions.append(flight_path_to_geometry(flight_path, comment=pred_comment, altitude_mode=altitude_mode)) 86 | predictions.append(flight_path_burst_placemark(flight_path, comment="Burst (%dm)"%_burst_alt, altitude_mode=altitude_mode)) 87 | predictions.append(flight_path_landing_placemark(flight_path, comment=pred_comment)) 88 | 89 | print("%s - Landing: %.4f, %.4f at %s" % (pred_comment, flight_path[-1][1], flight_path[-1][2], datetime.datetime.utcfromtimestamp(flight_path[-1][0]).isoformat())) 90 | 91 | write_flight_path_kml(predictions, filename=args.output) 92 | print("KML written to %s" % args.output) 93 | 94 | -------------------------------------------------------------------------------- /apps/sonde_predict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper 5 | # Radiosonde Launch Predictor 6 | # Copyright 2017 Mark Jessop 7 | # 8 | # In this example we run predictions for a radiosonde launch from Adelaide Airport 9 | # for every launch for the next 7 days, and write the tracks out to a KML file. 10 | # 11 | # For this script to work correctly, we need a weeks worth of GFS data available within the gfs directory. 12 | # This can be gathered using get_wind_data.py (or scripted with wind_grabber.sh), using : 13 | # python get_wind_data.py --lat=-33 --lon=139 --latdelta=10 --londelta=10 -f 168 -m 0p25_1hr -o gfs 14 | # with lat/lon parameters chaged as appropriate. 15 | # 16 | import argparse 17 | import fastkml 18 | import datetime 19 | import json 20 | from dateutil.parser import parse 21 | from cusfpredict.predict import Predictor 22 | from pygeoif.geometry import Point, LineString 23 | from cusfpredict.utils import * 24 | 25 | # Predictor Parameters 26 | PRED_BINARY = "./pred" 27 | GFS_PATH = "./gfs/" 28 | 29 | # Launch Parameters 30 | LAUNCH_TIME = "11:15Z" # This can be anything that dateutil can parse. The time *must* be in UTC. 31 | LAUNCH_STEP = 12 # Time step, in hours, between launch predictions. 32 | LAUNCH_TIME_LIMIT = 168 # Predict out this many hours into the future 33 | LAUNCH_LAT = -34.9499 34 | LAUNCH_LON = 138.5194 35 | LAUNCH_ALT = 0.0 36 | ASCENT_RATE = 5.0 37 | DESCENT_RATE = 6.0 38 | BURST_ALT = 26000.0 39 | 40 | # Read in command line arguments. 41 | parser = argparse.ArgumentParser() 42 | parser.add_argument('-a', '--ascentrate', type=float, default=ASCENT_RATE, help="Ascent Rate (m/s). Default %.1fm/s" % ASCENT_RATE) 43 | parser.add_argument('-d', '--descentrate', type=float, default=DESCENT_RATE, help="Descent Rate (m/s). Default %.1dm/s" % DESCENT_RATE) 44 | parser.add_argument('-b', '--burstalt', type=float, default=BURST_ALT, help="Burst Altitude (m). Default %.1fm" % BURST_ALT) 45 | parser.add_argument('--launchalt', type=float, default=LAUNCH_ALT, help="Launch Altitude (m). Default 0m") 46 | parser.add_argument('--latitude', type=float, default=LAUNCH_LAT, help="Launch Latitude (dd.dddd)") 47 | parser.add_argument('--longitude', type=float, default=LAUNCH_LON, help="Launch Longitude (dd.dddd)") 48 | parser.add_argument('--time', type=str, default=LAUNCH_TIME, help="First Time of the day (string, UTC). Default = %s" % LAUNCH_TIME) 49 | parser.add_argument('--step', type=int, default=LAUNCH_STEP, help="Time step between predictions (Hours). Default = %d" % LAUNCH_STEP) 50 | parser.add_argument('--limit', type=int, default=LAUNCH_TIME_LIMIT, help="Predict up to this many hours into the future. Default = %d" % LAUNCH_TIME_LIMIT) 51 | parser.add_argument('-s', '--site', type=str, default="Radiosonde", help="Launch site name. Default: Radiosonde") 52 | parser.add_argument('-o', '--output', type=str, default='sonde_predictions', help="Output JSON File. .json will be appended. Default = sonde_predictions[.json]") 53 | args = parser.parse_args() 54 | 55 | 56 | OUTPUT_FILE = args.output 57 | LAUNCH_LAT = args.latitude 58 | LAUNCH_LON = args.longitude 59 | LAUNCH_ALT = args.launchalt 60 | ASCENT_RATE = args.ascentrate 61 | DESCENT_RATE = args.descentrate 62 | BURST_ALT = args.burstalt 63 | 64 | 65 | # Set the launch time to the current UTC day, but set the hours to the 12Z sonde 66 | current_day = datetime.datetime.utcnow() 67 | launch_hour = parse(args.time) 68 | LAUNCH_TIME = datetime.datetime(current_day.year, current_day.month, current_day.day, launch_hour.hour, launch_hour.minute) 69 | 70 | 71 | # Parameter Variations 72 | # These can all be left at zero, or you can add a range of delta values 73 | launch_time_variations = range(0,args.limit,args.step) 74 | 75 | # A list to store predicton results 76 | predictions = [] 77 | 78 | # Separate store for JSON output data. 79 | json_out = { 80 | 'dataset': gfs_model_age(GFS_PATH), 81 | 'predictions':{}, 82 | 'site': args.site, 83 | 'launch_lat': LAUNCH_LAT, 84 | 'launch_lon': LAUNCH_LON 85 | } 86 | 87 | # Create the predictor object. 88 | pred = Predictor(bin_path=PRED_BINARY, gfs_path=GFS_PATH) 89 | 90 | 91 | # Iterate through the range of launch times set above 92 | for _delta_time in launch_time_variations: 93 | 94 | # Calculate the launch time for the current prediction. 95 | _launch_time = LAUNCH_TIME + datetime.timedelta(seconds=_delta_time*3600) 96 | 97 | # Run the prediction 98 | flight_path = pred.predict( 99 | launch_lat=LAUNCH_LAT, 100 | launch_lon=LAUNCH_LON, 101 | launch_alt=LAUNCH_ALT, 102 | ascent_rate=ASCENT_RATE, 103 | descent_rate=DESCENT_RATE, 104 | burst_alt=BURST_ALT, 105 | launch_time=_launch_time) 106 | 107 | # If we only get a single entry in the output array, it means we don't have any wind data for this time 108 | # Continue on to the next launch time. 109 | if len(flight_path) == 1: 110 | continue 111 | 112 | # Generate a descriptive comment for the track and placemark. 113 | pred_time_string = _launch_time.strftime("%Y%m%d-%H%M") 114 | pred_comment = "%s %.1f/%.1f/%.1f" % (pred_time_string, ASCENT_RATE, BURST_ALT, DESCENT_RATE) 115 | 116 | # Add the track and placemark to our list of predictions 117 | predictions.append(flight_path_to_geometry(flight_path, comment=pred_comment)) 118 | predictions.append(flight_path_landing_placemark(flight_path, comment=pred_comment)) 119 | 120 | json_out['predictions'][pred_comment] = { 121 | 'timestamp': _launch_time.strftime("%Y-%m-%d %H:%M:%S"), 122 | 'path':flight_path_to_polyline(flight_path), 123 | 'burst_alt': BURST_ALT 124 | } 125 | 126 | print("Prediction Run: %s" % pred_comment) 127 | 128 | # Write out the prediction data to the KML file 129 | kml_comment = "Sonde Predictions - %s" % gfs_model_age() 130 | write_flight_path_kml(predictions, filename=OUTPUT_FILE+".kml", comment=kml_comment) 131 | 132 | # Write out the JSON blob. 133 | with open(OUTPUT_FILE+".json",'w') as f: 134 | f.write(json.dumps(json_out)) -------------------------------------------------------------------------------- /apps/sonde_predict.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Example script for grabbing the following week's wind data, 4 | # and then running predictions of radiosonde launches from a site. 5 | # 6 | # Mark Jessop 7 | # 8 | # For this script to work, you need 9 | # - cusf_predictor_wrapper installed and available on the current python path. 10 | # - get_wind_data.py in this directory (should already be the case) 11 | # - The 'pred' binary available in this directory. 12 | # 13 | # New GFS models are available every 6 hours, approximately 3-4 hours after the model 14 | # start time (00/06/12/18Z). 15 | # 16 | # I use the following crontab entry to run this script at a time where the model is either available, 17 | # or close to being available. 18 | # 19 | # 40 3,9,15,21 * * * /home/username/cusf_predictor_wrapper/apps/sonde_predict.sh 20 | 21 | # Home location latitude & longitude 22 | HOME_LAT=-34.0 23 | HOME_LON=138.0 24 | 25 | # Area to grab data for. +/- 10 degrees is usually plenty! 26 | LATLON_DELTA=10 27 | 28 | # How many hours to grab data for. 192 hours = 8 days, which is about the extent of the GFS model 29 | HOUR_RANGE=192 30 | 31 | 32 | # We assume this script is run from the cusf_predictor_wrapper/apps directory. 33 | # If this is not the case (e.g. if it is run via a cronjob), then you may need 34 | # to modify and uncomment the following line. 35 | #cd /home/username/cusf_predictor_wrapper/apps/ 36 | 37 | # Clear old GFS data. 38 | rm gfs/*.txt 39 | rm gfs/*.dat 40 | 41 | # Download the wind data. 42 | # Note that this will wait up to 3 hours for the latest wind data to become available. 43 | python3 -m cusfpredict.gfs --lat=$HOME_LAT --lon=$HOME_LON --latdelta=$LATLON_DELTA --londelta=$LATLON_DELTA -f $HOUR_RANGE -m 0p25_1hr --wait=180 2>&1 | tee sonde_predict.log 44 | 45 | # Run predictions 46 | python3 sonde_predict.py 47 | 48 | # Copy the resultant json file into the web interface directory. 49 | # If the web interface is being hosted elsewhere, you may need to replace this with a SCP 50 | # command to get the json file into the right place, e.g. 51 | # scp sonde_predictions.json mywebserver.com:~/www/sondes/ 52 | cp sonde_predictions.json web/ 53 | -------------------------------------------------------------------------------- /apps/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Radiosonde Predictions 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 | 150 | 151 | 152 | 153 |
154 | 155 | 156 | -------------------------------------------------------------------------------- /apps/web/static/Leaflet.Control.Custom.js: -------------------------------------------------------------------------------- 1 | (function (window, document, undefined) { 2 | L.Control.Custom = L.Control.extend({ 3 | version: '1.0.1', 4 | options: { 5 | position: 'topright', 6 | id: '', 7 | title: '', 8 | classes: '', 9 | content: '', 10 | style: {}, 11 | datas: {}, 12 | events: {}, 13 | }, 14 | container: null, 15 | onAdd: function (map) { 16 | this.container = L.DomUtil.create('div'); 17 | this.container.id = this.options.id; 18 | this.container.title = this.options.title; 19 | this.container.className = this.options.classes; 20 | this.container.innerHTML = this.options.content; 21 | 22 | for (var option in this.options.style) 23 | { 24 | this.container.style[option] = this.options.style[option]; 25 | } 26 | 27 | for (var data in this.options.datas) 28 | { 29 | this.container.dataset[data] = this.options.datas[data]; 30 | } 31 | 32 | 33 | /* Prevent click events propagation to map */ 34 | L.DomEvent.disableClickPropagation(this.container); 35 | 36 | /* Prevent right click event propagation to map */ 37 | L.DomEvent.on(this.container, 'contextmenu', function (ev) 38 | { 39 | L.DomEvent.stopPropagation(ev); 40 | }); 41 | 42 | /* Prevent scroll events propagation to map when cursor on the div */ 43 | L.DomEvent.disableScrollPropagation(this.container); 44 | 45 | for (var event in this.options.events) 46 | { 47 | L.DomEvent.on(this.container, event, this.options.events[event], this.container); 48 | } 49 | 50 | return this.container; 51 | }, 52 | 53 | onRemove: function (map) { 54 | for (var event in this.options.events) 55 | { 56 | L.DomEvent.off(this.container, event, this.options.events[event], this.container); 57 | } 58 | }, 59 | }); 60 | 61 | L.control.custom = function (options) { 62 | return new L.Control.Custom(options); 63 | }; 64 | 65 | }(window, document)); -------------------------------------------------------------------------------- /apps/web/static/leaflet.polylineDecorator.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('leaflet')) : 3 | typeof define === 'function' && define.amd ? define(['leaflet'], factory) : 4 | (factory(global.L)); 5 | }(this, (function (L$1) { 'use strict'; 6 | 7 | L$1 = L$1 && L$1.hasOwnProperty('default') ? L$1['default'] : L$1; 8 | 9 | // functional re-impl of L.Point.distanceTo, 10 | // with no dependency on Leaflet for easier testing 11 | function pointDistance(ptA, ptB) { 12 | var x = ptB.x - ptA.x; 13 | var y = ptB.y - ptA.y; 14 | return Math.sqrt(x * x + y * y); 15 | } 16 | 17 | var computeSegmentHeading = function computeSegmentHeading(a, b) { 18 | return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI + 90 + 360) % 360; 19 | }; 20 | 21 | var asRatioToPathLength = function asRatioToPathLength(_ref, totalPathLength) { 22 | var value = _ref.value, 23 | isInPixels = _ref.isInPixels; 24 | return isInPixels ? value / totalPathLength : value; 25 | }; 26 | 27 | function parseRelativeOrAbsoluteValue(value) { 28 | if (typeof value === 'string' && value.indexOf('%') !== -1) { 29 | return { 30 | value: parseFloat(value) / 100, 31 | isInPixels: false 32 | }; 33 | } 34 | var parsedValue = value ? parseFloat(value) : 0; 35 | return { 36 | value: parsedValue, 37 | isInPixels: parsedValue > 0 38 | }; 39 | } 40 | 41 | var pointsEqual = function pointsEqual(a, b) { 42 | return a.x === b.x && a.y === b.y; 43 | }; 44 | 45 | function pointsToSegments(pts) { 46 | return pts.reduce(function (segments, b, idx, points) { 47 | // this test skips same adjacent points 48 | if (idx > 0 && !pointsEqual(b, points[idx - 1])) { 49 | var a = points[idx - 1]; 50 | var distA = segments.length > 0 ? segments[segments.length - 1].distB : 0; 51 | var distAB = pointDistance(a, b); 52 | segments.push({ 53 | a: a, 54 | b: b, 55 | distA: distA, 56 | distB: distA + distAB, 57 | heading: computeSegmentHeading(a, b) 58 | }); 59 | } 60 | return segments; 61 | }, []); 62 | } 63 | 64 | function projectPatternOnPointPath(pts, pattern) { 65 | // 1. split the path into segment infos 66 | var segments = pointsToSegments(pts); 67 | var nbSegments = segments.length; 68 | if (nbSegments === 0) { 69 | return []; 70 | } 71 | 72 | var totalPathLength = segments[nbSegments - 1].distB; 73 | 74 | var offset = asRatioToPathLength(pattern.offset, totalPathLength); 75 | var endOffset = asRatioToPathLength(pattern.endOffset, totalPathLength); 76 | var repeat = asRatioToPathLength(pattern.repeat, totalPathLength); 77 | 78 | var repeatIntervalPixels = totalPathLength * repeat; 79 | var startOffsetPixels = offset > 0 ? totalPathLength * offset : 0; 80 | var endOffsetPixels = endOffset > 0 ? totalPathLength * endOffset : 0; 81 | 82 | // 2. generate the positions of the pattern as offsets from the path start 83 | var positionOffsets = []; 84 | var positionOffset = startOffsetPixels; 85 | do { 86 | positionOffsets.push(positionOffset); 87 | positionOffset += repeatIntervalPixels; 88 | } while (repeatIntervalPixels > 0 && positionOffset < totalPathLength - endOffsetPixels); 89 | 90 | // 3. projects offsets to segments 91 | var segmentIndex = 0; 92 | var segment = segments[0]; 93 | return positionOffsets.map(function (positionOffset) { 94 | // find the segment matching the offset, 95 | // starting from the previous one as offsets are ordered 96 | while (positionOffset > segment.distB && segmentIndex < nbSegments - 1) { 97 | segmentIndex++; 98 | segment = segments[segmentIndex]; 99 | } 100 | 101 | var segmentRatio = (positionOffset - segment.distA) / (segment.distB - segment.distA); 102 | return { 103 | pt: interpolateBetweenPoints(segment.a, segment.b, segmentRatio), 104 | heading: segment.heading 105 | }; 106 | }); 107 | } 108 | 109 | /** 110 | * Finds the point which lies on the segment defined by points A and B, 111 | * at the given ratio of the distance from A to B, by linear interpolation. 112 | */ 113 | function interpolateBetweenPoints(ptA, ptB, ratio) { 114 | if (ptB.x !== ptA.x) { 115 | return { 116 | x: ptA.x + ratio * (ptB.x - ptA.x), 117 | y: ptA.y + ratio * (ptB.y - ptA.y) 118 | }; 119 | } 120 | // special case where points lie on the same vertical axis 121 | return { 122 | x: ptA.x, 123 | y: ptA.y + (ptB.y - ptA.y) * ratio 124 | }; 125 | } 126 | 127 | (function() { 128 | // save these original methods before they are overwritten 129 | var proto_initIcon = L.Marker.prototype._initIcon; 130 | var proto_setPos = L.Marker.prototype._setPos; 131 | 132 | var oldIE = (L.DomUtil.TRANSFORM === 'msTransform'); 133 | 134 | L.Marker.addInitHook(function () { 135 | var iconOptions = this.options.icon && this.options.icon.options; 136 | var iconAnchor = iconOptions && this.options.icon.options.iconAnchor; 137 | if (iconAnchor) { 138 | iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px'); 139 | } 140 | this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom' ; 141 | this.options.rotationAngle = this.options.rotationAngle || 0; 142 | 143 | // Ensure marker keeps rotated during dragging 144 | this.on('drag', function(e) { e.target._applyRotation(); }); 145 | }); 146 | 147 | L.Marker.include({ 148 | _initIcon: function() { 149 | proto_initIcon.call(this); 150 | }, 151 | 152 | _setPos: function (pos) { 153 | proto_setPos.call(this, pos); 154 | this._applyRotation(); 155 | }, 156 | 157 | _applyRotation: function () { 158 | if(this.options.rotationAngle) { 159 | this._icon.style[L.DomUtil.TRANSFORM+'Origin'] = this.options.rotationOrigin; 160 | 161 | if(oldIE) { 162 | // for IE 9, use the 2D rotation 163 | this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)'; 164 | } else { 165 | // for modern browsers, prefer the 3D accelerated version 166 | this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)'; 167 | } 168 | } 169 | }, 170 | 171 | setRotationAngle: function(angle) { 172 | this.options.rotationAngle = angle; 173 | this.update(); 174 | return this; 175 | }, 176 | 177 | setRotationOrigin: function(origin) { 178 | this.options.rotationOrigin = origin; 179 | this.update(); 180 | return this; 181 | } 182 | }); 183 | })(); 184 | 185 | L$1.Symbol = L$1.Symbol || {}; 186 | 187 | /** 188 | * A simple dash symbol, drawn as a Polyline. 189 | * Can also be used for dots, if 'pixelSize' option is given the 0 value. 190 | */ 191 | L$1.Symbol.Dash = L$1.Class.extend({ 192 | options: { 193 | pixelSize: 10, 194 | pathOptions: {} 195 | }, 196 | 197 | initialize: function initialize(options) { 198 | L$1.Util.setOptions(this, options); 199 | this.options.pathOptions.clickable = false; 200 | }, 201 | 202 | buildSymbol: function buildSymbol(dirPoint, latLngs, map, index, total) { 203 | var opts = this.options; 204 | var d2r = Math.PI / 180; 205 | 206 | // for a dot, nothing more to compute 207 | if (opts.pixelSize <= 1) { 208 | return L$1.polyline([dirPoint.latLng, dirPoint.latLng], opts.pathOptions); 209 | } 210 | 211 | var midPoint = map.project(dirPoint.latLng); 212 | var angle = -(dirPoint.heading - 90) * d2r; 213 | var a = L$1.point(midPoint.x + opts.pixelSize * Math.cos(angle + Math.PI) / 2, midPoint.y + opts.pixelSize * Math.sin(angle) / 2); 214 | // compute second point by central symmetry to avoid unecessary cos/sin 215 | var b = midPoint.add(midPoint.subtract(a)); 216 | return L$1.polyline([map.unproject(a), map.unproject(b)], opts.pathOptions); 217 | } 218 | }); 219 | 220 | L$1.Symbol.dash = function (options) { 221 | return new L$1.Symbol.Dash(options); 222 | }; 223 | 224 | L$1.Symbol.ArrowHead = L$1.Class.extend({ 225 | options: { 226 | polygon: true, 227 | pixelSize: 10, 228 | headAngle: 60, 229 | pathOptions: { 230 | stroke: false, 231 | weight: 2 232 | } 233 | }, 234 | 235 | initialize: function initialize(options) { 236 | L$1.Util.setOptions(this, options); 237 | this.options.pathOptions.clickable = false; 238 | }, 239 | 240 | buildSymbol: function buildSymbol(dirPoint, latLngs, map, index, total) { 241 | return this.options.polygon ? L$1.polygon(this._buildArrowPath(dirPoint, map), this.options.pathOptions) : L$1.polyline(this._buildArrowPath(dirPoint, map), this.options.pathOptions); 242 | }, 243 | 244 | _buildArrowPath: function _buildArrowPath(dirPoint, map) { 245 | var d2r = Math.PI / 180; 246 | var tipPoint = map.project(dirPoint.latLng); 247 | var direction = -(dirPoint.heading - 90) * d2r; 248 | var radianArrowAngle = this.options.headAngle / 2 * d2r; 249 | 250 | var headAngle1 = direction + radianArrowAngle; 251 | var headAngle2 = direction - radianArrowAngle; 252 | var arrowHead1 = L$1.point(tipPoint.x - this.options.pixelSize * Math.cos(headAngle1), tipPoint.y + this.options.pixelSize * Math.sin(headAngle1)); 253 | var arrowHead2 = L$1.point(tipPoint.x - this.options.pixelSize * Math.cos(headAngle2), tipPoint.y + this.options.pixelSize * Math.sin(headAngle2)); 254 | 255 | return [map.unproject(arrowHead1), dirPoint.latLng, map.unproject(arrowHead2)]; 256 | } 257 | }); 258 | 259 | L$1.Symbol.arrowHead = function (options) { 260 | return new L$1.Symbol.ArrowHead(options); 261 | }; 262 | 263 | L$1.Symbol.Marker = L$1.Class.extend({ 264 | options: { 265 | markerOptions: {}, 266 | rotate: false 267 | }, 268 | 269 | initialize: function initialize(options) { 270 | L$1.Util.setOptions(this, options); 271 | this.options.markerOptions.clickable = false; 272 | this.options.markerOptions.draggable = false; 273 | }, 274 | 275 | buildSymbol: function buildSymbol(directionPoint, latLngs, map, index, total) { 276 | if (this.options.rotate) { 277 | this.options.markerOptions.rotationAngle = directionPoint.heading + (this.options.angleCorrection || 0); 278 | } 279 | return L$1.marker(directionPoint.latLng, this.options.markerOptions); 280 | } 281 | }); 282 | 283 | L$1.Symbol.marker = function (options) { 284 | return new L$1.Symbol.Marker(options); 285 | }; 286 | 287 | var isCoord = function isCoord(c) { 288 | return c instanceof L$1.LatLng || Array.isArray(c) && c.length === 2 && typeof c[0] === 'number'; 289 | }; 290 | 291 | var isCoordArray = function isCoordArray(ll) { 292 | return Array.isArray(ll) && isCoord(ll[0]); 293 | }; 294 | 295 | L$1.PolylineDecorator = L$1.FeatureGroup.extend({ 296 | options: { 297 | patterns: [] 298 | }, 299 | 300 | initialize: function initialize(paths, options) { 301 | L$1.FeatureGroup.prototype.initialize.call(this); 302 | L$1.Util.setOptions(this, options); 303 | this._map = null; 304 | this._paths = this._initPaths(paths); 305 | this._bounds = this._initBounds(); 306 | this._patterns = this._initPatterns(this.options.patterns); 307 | }, 308 | 309 | /** 310 | * Deals with all the different cases. input can be one of these types: 311 | * array of LatLng, array of 2-number arrays, Polyline, Polygon, 312 | * array of one of the previous. 313 | */ 314 | _initPaths: function _initPaths(input, isPolygon) { 315 | var _this = this; 316 | 317 | if (isCoordArray(input)) { 318 | // Leaflet Polygons don't need the first point to be repeated, but we do 319 | var coords = isPolygon ? input.concat([input[0]]) : input; 320 | return [coords]; 321 | } 322 | if (input instanceof L$1.Polyline) { 323 | // we need some recursivity to support multi-poly* 324 | return this._initPaths(input.getLatLngs(), input instanceof L$1.Polygon); 325 | } 326 | if (Array.isArray(input)) { 327 | // flatten everything, we just need coordinate lists to apply patterns 328 | return input.reduce(function (flatArray, p) { 329 | return flatArray.concat(_this._initPaths(p, isPolygon)); 330 | }, []); 331 | } 332 | return []; 333 | }, 334 | 335 | // parse pattern definitions and precompute some values 336 | _initPatterns: function _initPatterns(patternDefs) { 337 | return patternDefs.map(this._parsePatternDef); 338 | }, 339 | 340 | /** 341 | * Changes the patterns used by this decorator 342 | * and redraws the new one. 343 | */ 344 | setPatterns: function setPatterns(patterns) { 345 | this.options.patterns = patterns; 346 | this._patterns = this._initPatterns(this.options.patterns); 347 | this.redraw(); 348 | }, 349 | 350 | /** 351 | * Changes the patterns used by this decorator 352 | * and redraws the new one. 353 | */ 354 | setPaths: function setPaths(paths) { 355 | this._paths = this._initPaths(paths); 356 | this._bounds = this._initBounds(); 357 | this.redraw(); 358 | }, 359 | 360 | /** 361 | * Parse the pattern definition 362 | */ 363 | _parsePatternDef: function _parsePatternDef(patternDef, latLngs) { 364 | return { 365 | symbolFactory: patternDef.symbol, 366 | // Parse offset and repeat values, managing the two cases: 367 | // absolute (in pixels) or relative (in percentage of the polyline length) 368 | offset: parseRelativeOrAbsoluteValue(patternDef.offset), 369 | endOffset: parseRelativeOrAbsoluteValue(patternDef.endOffset), 370 | repeat: parseRelativeOrAbsoluteValue(patternDef.repeat) 371 | }; 372 | }, 373 | 374 | onAdd: function onAdd(map) { 375 | this._map = map; 376 | this._draw(); 377 | this._map.on('moveend', this.redraw, this); 378 | }, 379 | 380 | onRemove: function onRemove(map) { 381 | this._map.off('moveend', this.redraw, this); 382 | this._map = null; 383 | L$1.FeatureGroup.prototype.onRemove.call(this, map); 384 | }, 385 | 386 | /** 387 | * As real pattern bounds depends on map zoom and bounds, 388 | * we just compute the total bounds of all paths decorated by this instance. 389 | */ 390 | _initBounds: function _initBounds() { 391 | var allPathCoords = this._paths.reduce(function (acc, path) { 392 | return acc.concat(path); 393 | }, []); 394 | return L$1.latLngBounds(allPathCoords); 395 | }, 396 | 397 | getBounds: function getBounds() { 398 | return this._bounds; 399 | }, 400 | 401 | /** 402 | * Returns an array of ILayers object 403 | */ 404 | _buildSymbols: function _buildSymbols(latLngs, symbolFactory, directionPoints) { 405 | var _this2 = this; 406 | 407 | return directionPoints.map(function (directionPoint, i) { 408 | return symbolFactory.buildSymbol(directionPoint, latLngs, _this2._map, i, directionPoints.length); 409 | }); 410 | }, 411 | 412 | /** 413 | * Compute pairs of LatLng and heading angle, 414 | * that define positions and directions of the symbols on the path 415 | */ 416 | _getDirectionPoints: function _getDirectionPoints(latLngs, pattern) { 417 | var _this3 = this; 418 | 419 | if (latLngs.length < 2) { 420 | return []; 421 | } 422 | var pathAsPoints = latLngs.map(function (latLng) { 423 | return _this3._map.project(latLng); 424 | }); 425 | return projectPatternOnPointPath(pathAsPoints, pattern).map(function (point) { 426 | return { 427 | latLng: _this3._map.unproject(L$1.point(point.pt)), 428 | heading: point.heading 429 | }; 430 | }); 431 | }, 432 | 433 | redraw: function redraw() { 434 | if (!this._map) { 435 | return; 436 | } 437 | this.clearLayers(); 438 | this._draw(); 439 | }, 440 | 441 | /** 442 | * Returns all symbols for a given pattern as an array of FeatureGroup 443 | */ 444 | _getPatternLayers: function _getPatternLayers(pattern) { 445 | var _this4 = this; 446 | 447 | var mapBounds = this._map.getBounds().pad(0.1); 448 | return this._paths.map(function (path) { 449 | var directionPoints = _this4._getDirectionPoints(path, pattern) 450 | // filter out invisible points 451 | .filter(function (point) { 452 | return mapBounds.contains(point.latLng); 453 | }); 454 | return L$1.featureGroup(_this4._buildSymbols(path, pattern.symbolFactory, directionPoints)); 455 | }); 456 | }, 457 | 458 | /** 459 | * Draw all patterns 460 | */ 461 | _draw: function _draw() { 462 | var _this5 = this; 463 | 464 | this._patterns.map(function (pattern) { 465 | return _this5._getPatternLayers(pattern); 466 | }).forEach(function (layers) { 467 | _this5.addLayer(L$1.featureGroup(layers)); 468 | }); 469 | } 470 | }); 471 | /* 472 | * Allows compact syntax to be used 473 | */ 474 | L$1.polylineDecorator = function (paths, options) { 475 | return new L$1.PolylineDecorator(paths, options); 476 | }; 477 | 478 | }))); 479 | -------------------------------------------------------------------------------- /apps/wind_grabber.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Wind Grabber Script Example 4 | # 5 | # An example of how cusfpredict.gfs could be run as a cron-job, to keep a directory of wind data up to date. 6 | # 7 | 8 | # Run cusfpredict.gfs 9 | # Update arguments as required. 10 | python3 -m cusfpredict.gfs --lat=-33 --lon=139 --latdelta=10 --londelta=10 -f 24 -m 0p25_1hr -o gfs/ 11 | -------------------------------------------------------------------------------- /cusfpredict/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper 5 | # Copyright 2020 Mark Jessop 6 | # 7 | 8 | __version__ = "0.2.1" -------------------------------------------------------------------------------- /cusfpredict/gfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper - GRIB Downloader & Parser 5 | # Copyright 2020 Mark Jessop 6 | # 7 | # Download GRIB files and convert them to predict-compatible GFS files. 8 | # 9 | # TODO: 10 | # [ ] Use HTTP Range requests instead of using the GRIB filter. 11 | # 12 | import sys 13 | import glob 14 | import os.path 15 | from os import remove 16 | import shutil 17 | from tempfile import mkdtemp 18 | import traceback 19 | import requests 20 | import argparse 21 | import logging 22 | import datetime 23 | import time 24 | import numpy as np 25 | 26 | try: 27 | import xarray as xr 28 | import cfgrib 29 | except ImportError: 30 | print("xarray and/or cfgrib not installed! Check setup instructions...") 31 | sys.exit(1) 32 | 33 | # GRIB Filter URL 34 | GRIB_FILTER_URL = "http://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_%s.pl" 35 | # Temporary parallel FV3 Model URL 36 | #GRIB_FILTER_URL = "https://nomads.ncep.noaa.gov/cgi-bin/filter_fv3_gfs_%s.pl" 37 | 38 | # GFS Parameters we are interested in 39 | # Note: There is also a new 0.4mb pressure level which we are not using yet. 40 | GFS_LEVELS = [1000.0,975.0,950.0,925.0,900.0,850.0,800.0,750.0,700.0,650.0,600.0,550.0,500.0,450.0,400.0,350.0,300.0,250.0,200.0,150.0,100.0,70.0,50.0,30.0,20.0,10.0,7.0,5.0,3.0,2.0,1.0] 41 | GFS_PARAMS = ['HGT', 'UGRD', 'VGRD'] 42 | 43 | # Dictionary containg available times and other information for each supported model. 44 | # Times are an array of the available hours for the model. 45 | 46 | VALID_MODELS = { 47 | '0p25_1hr' : {'times': np.concatenate((np.arange(0,120,1),np.arange(120,240,3),np.arange(240,396,12))), 48 | 'model_file': "gfs.t%sz.pgrb2.%s.f%03d" 49 | }, 50 | '0p50' : {'times': np.concatenate((np.arange(0,240,3),np.arange(240,396,12))), 51 | 'model_file': "gfs.t%sz.pgrb2full.%s.f%03d"} 52 | } 53 | 54 | # Other Globals 55 | REQUEST_TIMEOUT = 60 # GRIB filter requests have been observed to take up to 60 seconds to complete... 56 | REQUEST_RETRIES = 10 # We often have to retry a LOT. 57 | 58 | # Functions to Generate the GRIB Filter URL 59 | 60 | def latest_model_name(age = 0): 61 | ''' Get the N-th latest GFS model time ''' 62 | 63 | # Get Current UTC time. 64 | _current_dt = datetime.datetime.utcnow() 65 | 66 | # Round to the last 6-hour interval 67 | _model_hour = _current_dt.hour - _current_dt.hour%6 68 | _last_model_dt = datetime.datetime(_current_dt.year, _current_dt.month, _current_dt.day, _model_hour, 0, 0) 69 | 70 | # If we have been requested to get an older model, subtract *6 hours from the current time 71 | _last_model_dt = _last_model_dt + datetime.timedelta(0,6*3600*age) 72 | 73 | return _last_model_dt 74 | 75 | 76 | def find_nearest(array, value): 77 | ''' Find nearest value in an array ''' 78 | idx = (np.abs(array-value)).argmin() 79 | return idx 80 | 81 | 82 | def generate_filter_request(model='0p25_1hr', 83 | forecast_time=0, 84 | model_dt=latest_model_name(0), 85 | lat=-34.0, 86 | lon=138.0, 87 | latdelta=10.0, 88 | londelta=10.0 89 | ): 90 | ''' Generate a URL and a dictionary of request parameters for use with the GRIB filter ''' 91 | 92 | if model not in VALID_MODELS.keys(): 93 | raise ValueError("Invalid GFS Model!") 94 | 95 | # Get latest model time 96 | _model_dt = model_dt 97 | _model_timestring = _model_dt.strftime("%Y%m%d/%H") 98 | _model_hour = _model_dt.strftime("%H") 99 | 100 | _filter_url = GRIB_FILTER_URL % model 101 | 102 | _filter_params = {} 103 | _filter_params['file'] = VALID_MODELS[model]['model_file'] % (_model_hour, model.split('_')[0], forecast_time) 104 | _filter_params['dir'] = "/gfs.%s/atmos" % (_model_timestring) 105 | _filter_params['subregion'] = '' 106 | # TODO: Fix this to handle borders at -180 degrees properly. 107 | _filter_params['leftlon'] = max(-180, int(lon - londelta)) 108 | _filter_params['rightlon'] = min(180, int(lon + londelta)) 109 | _filter_params['toplat'] = min(90, int(lat + latdelta)) 110 | _filter_params['bottomlat'] = max(-90, int(lat - latdelta)) 111 | 112 | # Add the parameters we want: 113 | for _param in GFS_PARAMS: 114 | _filter_params['var_%s'%_param] = 'on' 115 | 116 | # Add in the levels we want: 117 | for _level in GFS_LEVELS: 118 | if _level%1.0 == 0.0: 119 | _filter_params['lev_%d_mb' % int(_level)] = 'on' 120 | else: 121 | _filter_params['lev_%.1f_mb' % _level] = 'on' 122 | 123 | 124 | #logging.debug("Filter URL: %s" % _filter_url) 125 | #logging.debug("Filter Parameters: %s" % str(_filter_params)) 126 | 127 | return (_filter_url, _filter_params) 128 | 129 | 130 | def determine_latest_available_dataset(model='0p25_1hr', forecast_time=0): 131 | ''' Determine what the latest available dataset with hours of model available is ''' 132 | # NOTE: Not all models have all forecast hours available! 133 | # Clip the forecast time to the nearest available hour 134 | _times = VALID_MODELS[model]['times'] 135 | _forecast_time = _times[find_nearest(_times, forecast_time)] 136 | 137 | # Attempt to grab a small amount of data from the most recent model. 138 | # if that fails, go to the next most recent, and continue until either we have data, or have completely failed. 139 | for _model_age in range(0,-5,-1): 140 | _model_dt = latest_model_name(_model_age) 141 | _model_timestring = _model_dt.strftime("%Y%m%d/%H") 142 | logging.info("Testing Model: %s" % _model_timestring) 143 | (_url, _params) = generate_filter_request( 144 | model=model, 145 | forecast_time=_forecast_time, 146 | model_dt = _model_dt, 147 | lat=0.0, 148 | lon=0.0, 149 | latdelta=1.0, 150 | londelta=1.0) 151 | 152 | 153 | _retries = REQUEST_RETRIES 154 | while _retries > 0: 155 | try: 156 | _r = requests.get(_url, params=_params, timeout=REQUEST_TIMEOUT) 157 | if _r.status_code == requests.codes.ok: 158 | logging.info("Found valid data in model %s!" % _model_timestring) 159 | return _model_dt 160 | else: 161 | break 162 | except Exception as e: 163 | logging.error("Error when testing model, retrying: %s" % str(e)) 164 | _retries -= 1 165 | continue 166 | 167 | logging.error("Could not find a model with the required data.") 168 | return None 169 | 170 | 171 | def wait_for_newest_dataset(model='0p25_1hr', forecast_time=0, timeout=4*60): 172 | ''' Wait until enough data from the newest dataset is available. ''' 173 | 174 | # NOTE: Not all models have all forecast hours available! 175 | # Clip the forecast time to the nearest available hour 176 | _times = VALID_MODELS[model]['times'] 177 | _forecast_time = _times[find_nearest(_times, forecast_time)] 178 | 179 | # Attempt to grab a small amount of data from the most recent model. 180 | # if that fails, go to the next most recent, and continue until either we have data, or have completely failed. 181 | _start_time = time.time() 182 | while (time.time()-_start_time) < timeout*60: 183 | _model_dt = latest_model_name(0) 184 | _model_timestring = _model_dt.strftime("%Y%m%d/%H") 185 | logging.info("Testing Model: %s" % _model_timestring) 186 | (_url, _params) = generate_filter_request( 187 | model=model, 188 | forecast_time=_forecast_time, 189 | model_dt = _model_dt, 190 | lat=0.0, 191 | lon=0.0, 192 | latdelta=1.0, 193 | londelta=1.0) 194 | 195 | 196 | _retries = REQUEST_RETRIES 197 | while _retries > 0: 198 | try: 199 | _r = requests.get(_url, params=_params, timeout=REQUEST_TIMEOUT) 200 | if _r.status_code == requests.codes.ok: 201 | logging.info("Found valid data in model %s!" % _model_timestring) 202 | return _model_dt 203 | else: 204 | logging.info("Model does not exist, or does not contain the required data yet. Waiting...") 205 | time.sleep(120) 206 | break 207 | except Exception as e: 208 | logging.error("Error when testing model, retrying: %s" % str(e)) 209 | _retries -= 1 210 | continue 211 | 212 | 213 | logging.error("Could not find a model with the required data within timeout period.") 214 | return None 215 | 216 | 217 | # Functions to poll the GRIB filter, and download data. 218 | def download_grib(url, params, filename="temp.grib"): 219 | ''' Attempt to download a GRIB file to disk ''' 220 | _retries = REQUEST_RETRIES 221 | 222 | while _retries > 0: 223 | try: 224 | _start = time.time() 225 | _r = requests.get(url, params=params, timeout=REQUEST_TIMEOUT) 226 | 227 | # Return code is OK, write out to disk. 228 | if _r.status_code == requests.codes.ok: 229 | # Writeout to disk. 230 | _duration = time.time() - _start 231 | logging.info("GRIB request took %.1f seconds." % _duration) 232 | f = open(filename, 'wb') 233 | f.write(_r.content) 234 | f.close() 235 | return True 236 | # Return status is something else... 237 | else: 238 | logging.error("Request returned error code: %s" % str(_r.status_code)) 239 | _retries -= 1 240 | continue 241 | 242 | except Exception as e: 243 | logging.error("Request failed with error: %s" % str(e)) 244 | _retries -= 1 245 | continue 246 | 247 | logging.error("Attempt to download GRIB failed after %d retries." % retries) 248 | return False 249 | 250 | 251 | def parse_grib_to_dict(gribfile): 252 | ''' Parse a GRIB file into a python dictionary format ''' 253 | 254 | _grib = xr.open_dataset(gribfile, engine='cfgrib') 255 | 256 | output = {} 257 | 258 | try: 259 | # Extract coordinate scales. 260 | output['lon_scale'] = _grib['longitude'].data 261 | output['lat_scale'] = _grib['latitude'].data 262 | output['iso_scale'] = _grib['isobaricInhPa'].data 263 | output['lon_centre'] = output['lon_scale'][len(output['lon_scale'])//2] 264 | output['lat_centre'] = output['lat_scale'][len(output['lat_scale'])//2] 265 | output['lon_radius'] = (max(output['lon_scale']) - min(output['lon_scale']))/2.0 266 | output['lat_radius'] = (max(output['lat_scale']) - min(output['lat_scale']))/2.0 267 | # Extract time. 268 | output['valid_time'] = int(_grib['valid_time'].data)//1000000000 269 | 270 | except: 271 | traceback.print_exc() 272 | return None 273 | 274 | # Extract the rasters layers we need 275 | for _n in range(len(output['iso_scale'])): 276 | try: 277 | _level_int = int(output['iso_scale'][_n]) 278 | 279 | if _level_int not in output.keys(): 280 | output[_level_int] = {} 281 | 282 | output[_level_int]['HGT'] = _grib['gh'][_n].data 283 | output[_level_int]['VGRD'] = _grib['v'][_n].data 284 | output[_level_int]['UGRD'] = _grib['u'][_n].data 285 | except: 286 | traceback.print_exc() 287 | continue 288 | 289 | return output 290 | 291 | 292 | def wind_dict_to_cusf(data, output_dir='./gfs/'): 293 | ''' 294 | Export wind data to a cusf-standalone-predictor compatible file 295 | Note that the file-naming scheme is fixed, so only the output directory is user-selectable. 296 | ''' 297 | 298 | # Generate Output Filename: i.e. gfs_1506052799_-33.0_139.0_10.0_10.0.dat 299 | _output_filename = "gfs_%d_%.1f_%.1f_%.1f_%.1f.dat" % ( 300 | data['valid_time'], 301 | data['lat_centre'], 302 | data['lon_centre'], 303 | data['lat_radius'], 304 | data['lon_radius'] 305 | ) 306 | _output_filename = os.path.join(output_dir, _output_filename) 307 | 308 | output_text = "" 309 | 310 | # Get the list of pressures. This is essentially all the integer keys in the data dictionary. 311 | _pressures = [] 312 | for _key in data.keys(): 313 | if type(_key) == int: 314 | _pressures.append(_key) 315 | 316 | # Sort the list of pressures from highest to lowest 317 | _pressures = np.sort(_pressures)[::-1] 318 | 319 | 320 | # Build up the output file, section by section. 321 | 322 | # HEADER Block 323 | # Window coverage area, and timestamp 324 | output_text += "# window centre latitude, window latitude radius, window centre longitude, window longitude radius, POSIX timestamp\n" 325 | output_text += "%.1f,%.1f,%.1f,%.1f,%d\n" % ( 326 | data['lat_centre'], 327 | data['lat_radius'], 328 | data['lon_centre'], 329 | data['lon_radius'], 330 | data['valid_time']) 331 | 332 | # Number of axes in dataset - always 3 - pressure, latitude, longitude 333 | output_text += "# Number of axes\n3\n" 334 | 335 | # First Axis definition - Pressure 336 | output_text += "# axis 1: pressures\n" 337 | # Size of Axis 338 | output_text += "%d\n" % len(_pressures) 339 | # Values 340 | output_text += ",".join(["%.1f" % num for num in _pressures.tolist()]) + "\n" 341 | 342 | # Second Axis Definition - Latitude 343 | output_text += "# axis 2: latitudes\n" 344 | # Size of Axis 345 | output_text += "%d\n" % len(data['lat_scale']) 346 | # Values 347 | output_text += ",".join(["%.2f" % num for num in data['lat_scale']]) + "\n" 348 | 349 | # Third Axis Definition - Longitude 350 | output_text += "# axis 3: longitudes\n" 351 | # Size of Axis 352 | output_text += "%d\n" % len(data['lon_scale']) 353 | # Values 354 | output_text += ",".join(["%.2f" % num for num in data['lon_scale']]) + "\n" 355 | 356 | # DATA BLOCK 357 | # Number of lines of data 358 | output_text += "# number of lines of data\n" 359 | output_text += "%d\n" % (len(data['lat_scale']) * len(data['lon_scale']) * len(_pressures)) 360 | # Components of data (3) 361 | output_text += "# data line component count\n3\n" 362 | # Output Data header 363 | output_text += "# now the data in axis 3 major order\n# data is: geopotential height [gpm], u-component wind [m/s], v-component wind [m/s]\n" 364 | 365 | # Now we need to format our output values. 366 | _values = "" 367 | # TODO: Nice and fast Numpy way of doing this. 368 | for pressureidx, pressure in enumerate(_pressures): 369 | for latidx, latitude in enumerate(data['lat_scale']): 370 | for lonidx, longitude in enumerate(data['lon_scale']): 371 | _hgt_val = data[pressure]['HGT'][latidx,lonidx] 372 | _ugrd_val = data[pressure]['UGRD'][latidx,lonidx] 373 | _vgrd_val = data[pressure]['VGRD'][latidx,lonidx] 374 | 375 | output_text += "%.5f,%.5f,%.5f\n" % (_hgt_val,_ugrd_val,_vgrd_val) 376 | 377 | # Write out to file! 378 | f = open(_output_filename,'w') 379 | f.write(output_text) 380 | f.close() 381 | 382 | return (_output_filename, output_text) 383 | 384 | # Copy a directory over another existing directory ( https://stackoverflow.com/a/12514470 ) 385 | def copytree(src, dst, symlinks=False, ignore=None): 386 | for item in os.listdir(src): 387 | s = os.path.join(src, item) 388 | d = os.path.join(dst, item) 389 | if os.path.isdir(s): 390 | shutil.copytree(s, d, symlinks, ignore) 391 | else: 392 | shutil.copy2(s, d) 393 | 394 | # Remove directory contents ( https://stackoverflow.com/a/185941 ) 395 | def remove_dir_contents(_dir): 396 | for file in os.listdir(_dir): 397 | file_path = os.path.join(_dir, file) 398 | try: 399 | if os.path.isfile(file_path): 400 | os.unlink(file_path) 401 | elif os.path.isdir(file_path): 402 | shutil.rmtree(file_path) 403 | except Exception as e: 404 | print(e) 405 | 406 | def main(): 407 | parser = argparse.ArgumentParser() 408 | parser.add_argument('--age', type=int, default=0, help="Age of the model to grab, in blocks of 6 hours.") 409 | parser.add_argument('-f', '--future', type=int, default=48, help="window of time to save data is at most HOURS hours in future.") 410 | parser.add_argument('--lat', type=float, default=-34.0, help="tile centre latitude in range (-90,90) degrees north") 411 | parser.add_argument('--lon', type=float, default=138.0, help="tile centre longitude in range (-180,180) degrees north") 412 | parser.add_argument('--latdelta', type=float, default=10.0, help='tile radius in latitude in degrees') 413 | parser.add_argument('--londelta', type=float, default=10.0, help='tile radius in longitude in degrees') 414 | parser.add_argument('-m', '--model', type=str, default='0p25_1hr', help="GFS Model to use.") 415 | parser.add_argument('-v', '--verbose', action='store_true', default=False, help="Verbose output.") 416 | parser.add_argument('-o', '--output_dir', type=str, default='./gfs/', help='GFS data output directory.') 417 | parser.add_argument('--wait', type=int, default=0, help="Force use of the latest dataset, and wait up to X minutes for the data to become available.") 418 | parser.add_argument('--override', action='store_true', default=False, help="Re-download data, even if there is existing data.") 419 | args = parser.parse_args() 420 | 421 | if args.verbose: 422 | logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) 423 | else: 424 | logging.basicConfig(stream=sys.stdout, level=logging.INFO) 425 | 426 | if args.wait == 0: 427 | _model_dt = determine_latest_available_dataset(model=args.model, forecast_time=args.future) 428 | else: 429 | _model_dt = wait_for_newest_dataset(model=args.model, forecast_time=args.future, timeout=args.wait) 430 | 431 | if _model_dt == None: 432 | sys.exit(1) 433 | 434 | # Check for existing dataset 435 | if os.path.exists(os.path.join(args.output_dir, "dataset.txt")): 436 | with open(os.path.join(args.output_dir, "dataset.txt"), 'r') as f: 437 | f_data = f.read().replace('\n', '') 438 | logging.info("Found existing dataset %s", f_data) 439 | _existing_model_dt = datetime.datetime.strptime(f_data, "%Y%m%d%Hz") 440 | if( (_existing_model_dt >= _model_dt) and not args.override): 441 | logging.info("No new data available") 442 | sys.exit(0) 443 | else: 444 | logging.info("Downloading newer dataset %s" % _model_dt.strftime("%Y%m%d%Hz")) 445 | 446 | # Create temporary directory for download 447 | _temp_dir = mkdtemp() 448 | logging.info("Created temporary directory %s" % _temp_dir) 449 | logging.info("Starting download of wind data...") 450 | 451 | # Get a list of valid forecast times, up until the user-specified time. 452 | _times = VALID_MODELS[args.model]['times'] 453 | _forecast_times = _times[:find_nearest(_times, args.future)+1] 454 | # Iterate through all forecast times, download and parse. 455 | for forecast_time in _forecast_times: 456 | (url, params) = generate_filter_request( 457 | model=args.model, 458 | forecast_time=forecast_time, 459 | model_dt=_model_dt, 460 | lat=args.lat, 461 | lon=args.lon, 462 | latdelta=args.latdelta, 463 | londelta=args.londelta 464 | ) 465 | 466 | success = download_grib(url, params, filename=os.path.join(_temp_dir, 'temp.grib')) 467 | 468 | if success: 469 | logging.info("Downloaded data for T+%03d" % forecast_time) 470 | else: 471 | logging.error("Could not download data for T+%03d" % forecast_time) 472 | continue 473 | 474 | # Now process the 475 | logging.info("Processing GRIB file...") 476 | _wind = parse_grib_to_dict(os.path.join(_temp_dir, 'temp.grib')) 477 | # Remove GRIB and index file. 478 | remove(os.path.join(_temp_dir, 'temp.grib')) 479 | for _entry in glob.glob(os.path.join(_temp_dir, "*.idx")): 480 | remove(_entry) 481 | 482 | if _wind is not None: 483 | (_filename, _text) = wind_dict_to_cusf(_wind, output_dir=_temp_dir) 484 | logging.info("GFS data written to: %s" % _filename) 485 | else: 486 | logging.error("Error processing GRIB file.") 487 | 488 | # Clean out output directory if it already exists, create if it does not 489 | if os.path.exists(args.output_dir): 490 | remove_dir_contents(args.output_dir) 491 | else: 492 | os.mkdir(args.output_dir) 493 | 494 | # Write model name into dataset.txt 495 | logging.info("Writing out dataset info.") 496 | f = open(os.path.join(_temp_dir, "dataset.txt"), 'w') 497 | f.write("%s" % _model_dt.strftime("%Y%m%d%Hz")) 498 | f.close() 499 | 500 | # Copy temporary directory into output directory 501 | copytree(_temp_dir, args.output_dir) 502 | 503 | # Clean up temporary directory 504 | shutil.rmtree(_temp_dir) 505 | 506 | logging.info("Finished!") 507 | 508 | 509 | if __name__ == '__main__': 510 | main() -------------------------------------------------------------------------------- /cusfpredict/predict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper 5 | # Copyright 2017 Mark Jessop 6 | # 7 | 8 | import subprocess 9 | import glob 10 | import os 11 | import time 12 | import datetime 13 | import logging 14 | 15 | class Predictor: 16 | ''' CUSF Standalone Predictor Wrapper ''' 17 | def __init__(self, bin_path = "./pred", gfs_path = "./gfs", verbose=False): 18 | # Sanity check that binary exists. 19 | if not os.path.isfile(bin_path): 20 | raise Exception("Predictor Binary does not exist.") 21 | 22 | if not self.test_pred_bin(bin_path): 23 | raise Exception("Not a valid CUSF predictor binary.") 24 | 25 | # Check that the gfs directory exists 26 | if not os.path.isdir(gfs_path): 27 | raise Exception("GFS data directory does not exist.") 28 | 29 | # Check the gfs directory contains some gfs data files. 30 | gfs_list = glob.glob(os.path.join(gfs_path, "gfs_*.dat")) 31 | if len(gfs_list) == 0: 32 | raise Exception("No GFS data files in directory.") 33 | 34 | self.bin_path = bin_path 35 | self.gfs_path = gfs_path 36 | self.verbose = verbose 37 | 38 | def test_pred_bin(self, bin_path): 39 | ''' Test that a binary is a CUSF predictor binary. ''' 40 | try: 41 | pred_version = subprocess.check_output([bin_path, '--version']).decode('ascii') 42 | if 'Landing Prediction' in pred_version: 43 | return True 44 | else: 45 | return False 46 | except: 47 | return False 48 | 49 | def predict(self,launch_lat= -34.9499, 50 | launch_lon = 138.5194, 51 | launch_alt = 0, 52 | ascent_rate = 5.0, 53 | descent_rate = 8.0, 54 | burst_alt = 26000, 55 | launch_time = datetime.datetime.utcnow(), 56 | descent_mode = False): 57 | 58 | 59 | # Generate the 'scenario' input data (ini-like structure) 60 | scenario = "[launch-site]\n" 61 | scenario += "latitude = %.5f\n" % float(launch_lat) 62 | scenario += "longitude = %.5f\n" % float(launch_lon) 63 | scenario += "altitude = %d\n" % int(launch_alt) 64 | scenario += "[atmosphere]\nwind-error = 0\n" 65 | scenario += "[altitude-model]\n" 66 | scenario += "ascent-rate = %.1f\n" % float(ascent_rate) 67 | scenario += "descent-rate = %.1f\n" % float(descent_rate) 68 | scenario += "burst-altitude = %d\n" % (int(launch_alt) if descent_mode else int(burst_alt)) 69 | scenario += "[launch-time]\n" 70 | scenario += "hour = %d\n" % (launch_time.hour) 71 | scenario += "minute = %d\n" % launch_time.minute 72 | scenario += "second = %d\n" % launch_time.second 73 | scenario += "day = %d\n" % launch_time.day 74 | scenario += "month = %d\n" % launch_time.month 75 | scenario += "year = %d\n" % launch_time.year 76 | 77 | # Attempt to run predictor 78 | 79 | # Force the local timezone env-var to UTC. 80 | env = dict(os.environ) 81 | env['TZ'] = 'UTC' 82 | subprocess_params = [self.bin_path, '-i', self.gfs_path] 83 | # If we are using 'descent mode', we just flag this to the predictor, so it ignores the ascent rate and burst altitude params. 84 | if descent_mode: 85 | subprocess_params.append('-d') 86 | 87 | if self.verbose: 88 | subprocess_params.append('-vv') 89 | # Run! 90 | pred = subprocess.Popen(subprocess_params, stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=env) 91 | (pred_stdout, pred_stderr) = pred.communicate(scenario.encode('ascii')) 92 | 93 | # Parse stdout data into an array of floats. 94 | logging.debug("Errors:") 95 | logging.debug(pred_stderr) 96 | 97 | # Parse output into an array. 98 | output = [] 99 | for line in pred_stdout.decode('ascii').split('\n'): 100 | try: 101 | fields = line.split(',') 102 | timestamp = int(fields[0]) 103 | lat = float(fields[1]) 104 | lon = float(fields[2]) 105 | alt = float(fields[3]) 106 | output.append([timestamp,lat,lon,alt]) 107 | except ValueError: 108 | continue 109 | 110 | return output 111 | 112 | 113 | # Test Script. Run a prediction for Adelaide Airport and print the landing location. 114 | if __name__ == "__main__": 115 | import argparse 116 | from dateutil.parser import parse 117 | from .utils import * 118 | # Predictor Parameters 119 | PRED_BINARY = "./pred" 120 | GFS_PATH = "./gfs" 121 | 122 | # Launch Parameters 123 | LAUNCH_TIME = datetime.datetime.utcnow().isoformat() # This can be anything that dateutil can parse. The time *must* be in UTC. 124 | LAUNCH_LAT = -34.9499 125 | LAUNCH_LON = 138.5194 126 | LAUNCH_ALT = 0.0 127 | ASCENT_RATE = 5.0 128 | DESCENT_RATE = 5.0 129 | BURST_ALT = 30000.0 130 | 131 | # Read in command line arguments. 132 | parser = argparse.ArgumentParser() 133 | parser.add_argument('--pred', type=str, default=PRED_BINARY, help="Location of the pred binary. (Default: ./pred)") 134 | parser.add_argument('--gfs', type=str, default=GFS_PATH, help="Location of the GFS data store. (Default: ./gfs/)") 135 | parser.add_argument('-a', '--ascentrate', type=float, default=ASCENT_RATE, help="Ascent Rate (m/s). Default 5m/s") 136 | parser.add_argument('-d', '--descentrate', type=float, default=DESCENT_RATE, help="Descent Rate (m/s). Default 5m/s") 137 | parser.add_argument('-b', '--burstalt', type=float, default=BURST_ALT, help="Burst Altitude (m). Default 30000m") 138 | parser.add_argument('--launchalt', type=float, default=LAUNCH_ALT, help="Launch Altitude (m). Default 0m") 139 | parser.add_argument('--latitude', type=float, default=LAUNCH_LAT, help="Launch Latitude (dd.dddd)") 140 | parser.add_argument('--longitude', type=float, default=LAUNCH_LON, help="Launch Longitude (dd.dddd)") 141 | parser.add_argument('--time', type=str, default=LAUNCH_TIME, help="Launch Time (string, UTC). Default = Now") 142 | parser.add_argument('-o', '--output', type=str, default='prediction.kml', help="Output KML File. Default = prediction.kml") 143 | parser.add_argument('--altitude_deltas', type=str, default='0', help="Comma-delimited list of altitude deltas. (metres).") 144 | parser.add_argument('--time_deltas', type=str, default='0', help="Comma-delimited list of time deltas. (hours)") 145 | parser.add_argument('--absolute', action="store_true", default=False, help="Show absolute altitudes for tracks and placemarks.") 146 | args = parser.parse_args() 147 | 148 | LAUNCH_TIME = args.time 149 | LAUNCH_LAT = args.latitude 150 | LAUNCH_LON = args.longitude 151 | LAUNCH_ALT = args.launchalt 152 | ASCENT_RATE = args.ascentrate 153 | DESCENT_RATE = args.descentrate 154 | BURST_ALT = args.burstalt 155 | 156 | altitude_mode = "absolute" if args.absolute else "clampToGround" 157 | burst_alt_variations = [float(item) for item in args.altitude_deltas.split(',')] 158 | launch_time_variations = [float(item) for item in args.time_deltas.split(',')] 159 | 160 | predictions = [] 161 | 162 | print("Running using GFS Model: %s" % gfs_model_age(GFS_PATH)) 163 | 164 | # Create the predictor object. 165 | pred = Predictor(bin_path=args.pred, gfs_path=args.gfs) 166 | 167 | for _delta_alt in burst_alt_variations: 168 | for _delta_time in launch_time_variations: 169 | 170 | _burst_alt = BURST_ALT + _delta_alt 171 | _launch_time = parse(LAUNCH_TIME) + datetime.timedelta(seconds=_delta_time*3600) 172 | 173 | flight_path = pred.predict( 174 | launch_lat=LAUNCH_LAT, 175 | launch_lon=LAUNCH_LON, 176 | launch_alt=LAUNCH_ALT, 177 | ascent_rate=ASCENT_RATE, 178 | descent_rate=DESCENT_RATE, 179 | burst_alt=_burst_alt, 180 | launch_time=_launch_time) 181 | 182 | if len(flight_path) == 1: 183 | continue 184 | 185 | pred_comment = "%s %.1f/%.1f/%.1f" % (_launch_time.isoformat(), ASCENT_RATE, _burst_alt, DESCENT_RATE) 186 | 187 | predictions.append(flight_path_to_geometry(flight_path, comment=pred_comment, altitude_mode=altitude_mode)) 188 | predictions.append(flight_path_burst_placemark(flight_path, comment="Burst (%dm)"%_burst_alt, altitude_mode=altitude_mode)) 189 | predictions.append(flight_path_landing_placemark(flight_path, comment=pred_comment)) 190 | 191 | print("%s - Landing: %.4f, %.4f at %s" % (pred_comment, flight_path[-1][1], flight_path[-1][2], datetime.datetime.utcfromtimestamp(flight_path[-1][0]).isoformat())) 192 | 193 | write_flight_path_kml(predictions, filename=args.output) 194 | print("KML written to %s" % args.output) 195 | -------------------------------------------------------------------------------- /cusfpredict/reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper - CUSF GFS File Reader 5 | # Copyright 2019 Mark Jessop 6 | # 7 | import datetime 8 | import math 9 | import pytz 10 | import numpy as np 11 | 12 | 13 | 14 | def read_cusf_gfs(filename): 15 | """ Read in a CUSF-format GFS data file """ 16 | 17 | _output = {} 18 | 19 | with open(filename, 'r') as _f: 20 | # As this file has lots of comments, we just do the first few lines the hard way... 21 | 22 | # Check the first comment line exists. 23 | _line = _f.readline() 24 | if 'window centre latitude, window latitude radius' not in _line: 25 | raise ValueError('Not a CUSF GFS file.') 26 | 27 | # window centre latitude, window latitude radius, window centre longitude, window longitude radius, POSIX timestamp 28 | _line = _f.readline() 29 | _fields = _line.split(',') 30 | _output['window_centre_latitude'] = float(_fields[0]) 31 | _output['window_latitude_radius'] = float(_fields[1]) 32 | _output['window_centre_longitude'] = float(_fields[2]) 33 | _output['window_longitude_radius'] = float(_fields[3]) 34 | _output['posix_timestamp'] = int(_fields[4]) 35 | # Parse timestamp into a datetime object. 36 | _output['timestamp'] = pytz.utc.localize(datetime.datetime.utcfromtimestamp(_output['posix_timestamp'])) 37 | 38 | # Comment line 39 | _f.readline() 40 | # Number of axes 41 | _output['axes'] = int(_f.readline()) 42 | 43 | # Comment line 44 | _f.readline() 45 | # Number of pressure levels 46 | _output['pressure_level_count'] = int(_f.readline()) 47 | # Pressure levels 48 | _output['pressures'] = np.fromstring(_f.readline(), sep=',') 49 | 50 | 51 | # Comment line 52 | _f.readline() 53 | # Number of latitudes 54 | _output['latitude_count'] = int(_f.readline()) 55 | # Latitudes 56 | _output['latitudes'] = np.fromstring(_f.readline(), sep=',') 57 | 58 | # Comment line 59 | _f.readline() 60 | # Number of longitudes 61 | _output['longitude_count'] = int(_f.readline()) 62 | # Pressure levels 63 | _output['longitudes'] = np.fromstring(_f.readline(), sep=',') 64 | 65 | # Comment line 66 | _f.readline() 67 | # Number of lines of data 68 | _output['data_lines'] = int(_f.readline()) 69 | 70 | # Comment line 71 | _f.readline() 72 | # Components per data line 73 | _output['components'] = int(_f.readline()) 74 | 75 | # Two comment lines 76 | _f.readline() 77 | _f.readline() 78 | 79 | # Now read in the rest of the lines 80 | _output['raw_data'] = [] 81 | for _line in _f: 82 | _fields = _line.split(',') 83 | if len(_fields) == 3: 84 | _hgt = float(_fields[0]) 85 | _ugrd = float(_fields[1]) 86 | _vgrd = float(_fields[2]) 87 | _speed = math.sqrt(_ugrd*_ugrd + _vgrd*_vgrd) 88 | _dir = 57.29578*(math.atan2(_ugrd,_vgrd))+180.0 89 | _output['raw_data'].append([_hgt, _ugrd, _vgrd, _speed, _dir]) 90 | 91 | # Re-shape into a 3D Array, with dimensions [pressure, latitude, longitude] 92 | _output['data'] = np.reshape(_output['raw_data'], (_output['pressure_level_count'], _output['latitude_count'], _output['longitude_count'], _output['components']+2), order='C') 93 | 94 | return _output 95 | 96 | 97 | if __name__ == "__main__": 98 | import sys 99 | 100 | print(read_cusf_gfs(sys.argv[1])) 101 | -------------------------------------------------------------------------------- /cusfpredict/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Project Horus 4 | # CUSF Standalone Predictor Python Wrapper - Utilities 5 | # Copyright 2017 Mark Jessop 6 | # 7 | import fastkml 8 | import glob 9 | import datetime 10 | import os.path 11 | from shapely.geometry import Point, LineString 12 | 13 | def available_gfs(gfs_path='./gfs'): 14 | """ Determine the time extent of the GFS dataset """ 15 | 16 | gfs_files = glob.glob(os.path.join(gfs_path,"gfs_*.dat")) 17 | 18 | if len(gfs_files) == 0: 19 | return (None, None) 20 | 21 | # Pull out the timestamps from each filename. 22 | _timestamps = [] 23 | 24 | for _filename in gfs_files: 25 | try: 26 | _ts = int(_filename.split('_')[1]) 27 | _timestamps.append(_ts) 28 | except: 29 | pass 30 | 31 | _timestamps.sort() 32 | 33 | start_time = datetime.datetime.utcfromtimestamp(_timestamps[0]) 34 | end_time = datetime.datetime.utcfromtimestamp(_timestamps[-1]) 35 | 36 | return (start_time, end_time) 37 | 38 | 39 | def gfs_model_age(gfs_path="./gfs"): 40 | """ Reads the model URL in from the dataset.txt file written by get_wind_data.py """ 41 | 42 | dataset_file = os.path.join(gfs_path,"dataset.txt") 43 | 44 | try: 45 | _f = open(dataset_file,'r') 46 | dataset_time = _f.read() 47 | _f.close() 48 | 49 | return dataset_time 50 | except: 51 | return "Unknown" 52 | 53 | 54 | 55 | 56 | # Geometry and KML related stuff 57 | ns = '{http://www.opengis.net/kml/2.2}' 58 | 59 | def flight_path_to_linestring(flight_path): 60 | ''' Convert a predicted flight path to a LineString geometry object ''' 61 | 62 | track_points = [] 63 | for _point in flight_path: 64 | # Flight path array is in lat,lon,alt order, needs to be in lon,lat,alt 65 | track_points.append([_point[2],_point[1],_point[3]]) 66 | 67 | return LineString(track_points) 68 | 69 | 70 | def flight_path_to_polyline(flight_path): 71 | ''' Convert a flight path to an array suitable for use with leaflet's PolyLine ''' 72 | track_points = [] 73 | for _point in flight_path: 74 | # Flight path array is in lat,lon,alt order, needs to be in lon,lat,alt 75 | track_points.append([_point[1],_point[2],_point[3]]) 76 | 77 | return track_points 78 | 79 | 80 | def flight_path_to_geometry(flight_path, 81 | name="Flight Path", 82 | comment="Predicted Flight Path Data", 83 | track_color="ffff8000", 84 | poly_color="20000000", 85 | track_width=3.0, 86 | altitude_mode = 'absolute'): 87 | ''' Produce a fastkml geometry object from a flight path array ''' 88 | 89 | flight_track_line_style = fastkml.styles.LineStyle( 90 | ns=ns, 91 | color=track_color, 92 | width=track_width) 93 | 94 | flight_extrusion_style = fastkml.styles.PolyStyle( 95 | ns=ns, 96 | color=poly_color) 97 | 98 | flight_track_style = fastkml.styles.Style( 99 | ns=ns, 100 | styles=[flight_track_line_style, flight_extrusion_style]) 101 | 102 | flight_line = fastkml.kml.Placemark( 103 | ns=ns, 104 | id=name, 105 | name=comment, 106 | styles=[flight_track_style]) 107 | 108 | flight_line.geometry = fastkml.geometry.Geometry( 109 | ns=ns, 110 | geometry=flight_path_to_linestring(flight_path), 111 | altitude_mode=altitude_mode, 112 | extrude=True, 113 | tessellate=True) 114 | 115 | return flight_line 116 | 117 | 118 | def flight_path_landing_placemark(flight_path, 119 | name="Flight Path", 120 | comment="Landing"): 121 | """ Produce a placemark of the landing position of a flight """ 122 | 123 | flight_icon_style = fastkml.styles.IconStyle( 124 | ns=ns, 125 | icon_href="http://maps.google.com/mapfiles/kml/shapes/cross-hairs.png", 126 | scale=2.0) 127 | 128 | flight_style = fastkml.styles.Style( 129 | ns=ns, 130 | styles=[flight_icon_style]) 131 | 132 | flight_placemark = fastkml.kml.Placemark( 133 | ns=ns, 134 | id=name, 135 | name=comment, 136 | description="", 137 | styles=[flight_style]) 138 | 139 | flight_placemark.geometry = fastkml.geometry.Geometry( 140 | ns=ns, 141 | geometry=Point(flight_path[-1][2], flight_path[-1][1], flight_path[-1][3]), 142 | altitude_mode='clampToGround') 143 | 144 | return flight_placemark 145 | 146 | 147 | def flight_path_burst_placemark(flight_path, 148 | name="Flight Path", 149 | comment="Burst", 150 | altitude_mode = 'absolute'): 151 | """ Produce a placemark of the burst position of a flight """ 152 | 153 | flight_icon_style = fastkml.styles.IconStyle( 154 | ns=ns, 155 | icon_href="http://maps.google.com/mapfiles/kml/shapes/star.png", 156 | scale=2.0) 157 | 158 | flight_style = fastkml.styles.Style( 159 | ns=ns, 160 | styles=[flight_icon_style]) 161 | 162 | flight_placemark = fastkml.kml.Placemark( 163 | ns=ns, 164 | id=name, 165 | name=comment, 166 | description="", 167 | styles=[flight_style]) 168 | 169 | # Read through array and hunt for max altitude point. 170 | current_alt = 0.0 171 | current_index = 0 172 | for i in range(len(flight_path)): 173 | if flight_path[i][3] > current_alt: 174 | current_alt = flight_path[i][3] 175 | current_index = i 176 | 177 | 178 | flight_placemark.geometry = fastkml.geometry.Geometry( 179 | ns=ns, 180 | geometry=Point(flight_path[current_index][2], flight_path[current_index][1], flight_path[current_index][3]), 181 | altitude_mode=altitude_mode) 182 | 183 | return flight_placemark 184 | 185 | 186 | def write_flight_path_kml(flight_data, 187 | filename="prediction.kml", 188 | comment="HAB Prediction", 189 | kml_hack=True): 190 | """ Write out flight path geometry objects to a kml file. """ 191 | 192 | kml_root = fastkml.kml.KML() 193 | kml_doc = fastkml.kml.Document( 194 | ns=ns, 195 | name=comment) 196 | 197 | if type(flight_data) is not list: 198 | flight_data = [flight_data] 199 | 200 | for _flight in flight_data: 201 | kml_doc.append(_flight) 202 | 203 | with open(filename,'w') as kml_file: 204 | kml_str = kml_doc.to_string() 205 | if kml_hack: 206 | kml_str = kml_str.replace('kml:','').replace(':kml','') 207 | kml_file.write(kml_str) 208 | kml_file.close() 209 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "cusfpredict" 3 | version = "0.2.1" 4 | description = "CUSF Predictor Wrapper & GFS Downloader" 5 | authors = ["Mark Jessop"] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" # last actively maintained version of Python 9 | fastkml = "^1" 10 | requests = "^2.25.1" 11 | numpy = "^2.0.0" 12 | pytz = "^2025.1" 13 | shapely = "^2.0.0" 14 | python-dateutil = "^2.8.1" 15 | xarray = "*" 16 | cfgrib = "^0.9.8" 17 | 18 | [build-system] 19 | requires = ["setuptools", "poetry>=0.12"] 20 | build-backend = "poetry.masonry.api" 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastkml 2 | requests 3 | numpy 4 | pytz 5 | shapely 6 | python-dateutil 7 | xarray 8 | cfgrib>=0.9.8 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup, find_packages 4 | 5 | regexp = re.compile(r".*__version__ = [\'\"](.*?)[\'\"]", re.S) 6 | 7 | init_file = os.path.join(os.path.dirname(__file__), "cusfpredict", "__init__.py") 8 | with open(init_file, "r") as f: 9 | module_content = f.read() 10 | match = regexp.match(module_content) 11 | if match: 12 | version = match.group(1) 13 | else: 14 | raise RuntimeError(f"Cannot find __version__ in {init_file}") 15 | 16 | 17 | with open("README.md", "r") as f: 18 | readme = f.read() 19 | 20 | 21 | with open("requirements.txt", "r") as f: 22 | requirements = [] 23 | for line in f.read().split("\n"): 24 | line = line.strip() 25 | if line and not line.startswith("#"): 26 | requirements.append(line) 27 | 28 | 29 | if __name__ == "__main__": 30 | setup( 31 | name="cusfpredict", 32 | description="Python Wrapper for the CUSF High-Altitude Balloon Predictor", 33 | long_description=readme, 34 | version=version, 35 | install_requires=requirements, 36 | keywords=["horus balloon prediction gfs"], 37 | package_dir={"": "."}, 38 | packages=find_packages("."), 39 | classifiers=[ 40 | "Intended Audience :: Developers", 41 | "Programming Language :: Python :: 3.6", 42 | "Programming Language :: Python :: 3.7", 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | Makefile 3 | pred 4 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake build file for the CUSF landing prediction software 2 | # 3 | # Original version: Rich Wareham 4 | 5 | # Use PkgConfig to find the glib libraries 6 | find_package(PkgConfig) 7 | 8 | pkg_check_modules(GLIB REQUIRED glib-2.0) 9 | 10 | include_directories(${GLIB_INCLUDE_DIRS}) 11 | link_directories(${GLIB_LIBRARY_DIRS}) 12 | 13 | add_executable(pred 14 | util/gopt.c 15 | util/getdelim.c 16 | util/random.h 17 | util/gopt.h 18 | util/getline.h 19 | util/getline.c 20 | util/getdelim.h 21 | util/random.c 22 | altitude.h 23 | wind/wind_file_cache.c 24 | wind/wind_file_cache.h 25 | wind/wind_file.c 26 | wind/wind_file.h 27 | altitude.c 28 | pred.c 29 | run_model.c 30 | pred.h 31 | run_model.h 32 | ini/iniparser.c 33 | ini/iniparser.h 34 | ini/dictionary.h 35 | ini/dictionary.c 36 | ) 37 | 38 | target_link_libraries(pred ${GLIB_LIBRARIES} -lm) 39 | -------------------------------------------------------------------------------- /src/altitude.c: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rob Anderson 6 | // Modified by Fergus Noble 7 | // 8 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 11 | // PARTICULAR PURPOSE. 12 | // -------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "pred.h" 19 | #include "altitude.h" 20 | #include "run_model.h" 21 | 22 | // get density of atmosphere at a given altitude 23 | // uses NASA model from http://www.grc.nasa.gov/WWW/K-12/airplane/atmosmet.html 24 | // units of degrees celcius, metres, KPa and Kg/metre cubed 25 | static float get_density(float altitude); 26 | 27 | #define G 9.8 28 | 29 | struct altitude_model_s 30 | { 31 | float burst_altitude; 32 | float ascent_rate; 33 | float drag_coeff; 34 | int descent_mode; 35 | 36 | float initial_alt; 37 | int burst_time; 38 | }; 39 | 40 | altitude_model_t* 41 | altitude_model_new(int dec_mode, float burst_alt, float asc_rate, float drag_co) 42 | { 43 | altitude_model_t* self = (altitude_model_t*)malloc(sizeof(altitude_model_t)); 44 | 45 | // this function doesn't do anything much at the moment 46 | // but will prove useful in the future 47 | 48 | self->burst_altitude = burst_alt; 49 | self->ascent_rate = asc_rate; 50 | self->drag_coeff = drag_co; 51 | self->descent_mode = dec_mode; 52 | 53 | return self; 54 | } 55 | 56 | void 57 | altitude_model_free(altitude_model_t* self) 58 | { 59 | if(!self) 60 | return; 61 | 62 | free(self); 63 | } 64 | 65 | int 66 | altitude_model_get_altitude(altitude_model_t* self, int time_into_flight, float* alt) { 67 | // TODO: this section needs some work to make it more flexible 68 | 69 | // time == 0 so setup initial altitude stuff 70 | if (time_into_flight == 0) { 71 | self->initial_alt = *alt; 72 | self->burst_time = (self->burst_altitude - self->initial_alt) / self->ascent_rate; 73 | } 74 | 75 | // If we are not doing a descending mode sim then start going up 76 | // at out ascent rate. The ascent rate is constant to a good approximation. 77 | if (self->descent_mode == DESCENT_MODE_NORMAL) 78 | if (time_into_flight <= self->burst_time) { 79 | *alt = self->initial_alt + time_into_flight*self->ascent_rate; 80 | return 1; 81 | } 82 | 83 | // Descent - just assume its at terminal velocity (which varies with altitude) 84 | // this is a pretty darn good approximation for high-ish drag e.g. under parachute 85 | // still converges to T.V. quickly (i.e. less than a minute) for low drag. 86 | // terminal velocity = -drag_coeff/sqrt(get_density(*alt)); 87 | *alt += TIMESTEP * -self->drag_coeff/sqrt(get_density(*alt)); 88 | 89 | /* 90 | // Rob's method - is this just freefall until we reach terminal velocity? 91 | vertical_speed = -drag_coeff/sqrt(get_density(*alt)); 92 | // TODO: inaccurate if still accelerating 93 | if (vertical_speed < previous_vertical_speed - G*TIMESTEP) // still accelerating 94 | vertical_speed = previous_vertical_speed - G*TIMESTEP; 95 | if(vertical_speed > previous_vertical_speed + G*TIMESTEP) // still accelerating 96 | vertical_speed = previous_vertical_speed + G*TIMESTEP; 97 | previous_vertical_speed = vertical_speed; 98 | *alt += TIMESTEP * vertical_speed; 99 | */ 100 | 101 | if (*alt <= 0) 102 | return 0; 103 | 104 | return 1; 105 | 106 | } 107 | 108 | float get_density(float altitude) { 109 | 110 | float temp = 0.f, pressure = 0.f; 111 | 112 | if (altitude > 25000) { 113 | temp = -131.21 + 0.00299 * altitude; 114 | pressure = 2.488*pow((temp+273.1)/216.6,-11.388); 115 | } 116 | if (altitude <=25000 && altitude > 11000) { 117 | temp = -56.46; 118 | pressure = 22.65 * exp(1.73-0.000157*altitude); 119 | } 120 | if (altitude <=11000) { 121 | temp = 15.04 - 0.00649 * altitude; 122 | pressure = 101.29 * pow((temp + 273.1)/288.08,5.256); 123 | } 124 | 125 | return pressure/(0.2869*(temp+273.1)); 126 | } 127 | -------------------------------------------------------------------------------- /src/altitude.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rob Anderson 6 | // Modified by Fergus Noble 7 | // 8 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 11 | // PARTICULAR PURPOSE. 12 | // -------------------------------------------------------------- 13 | 14 | #ifndef __ALTITUDE_H__ 15 | #define __ALTITUDE_H__ 16 | 17 | typedef struct altitude_model_s altitude_model_t; 18 | 19 | // create an altitude/time model with given parameters, must be called before 20 | // calling run_model if descent_mode is DESCENT_MODE_DESCENDING then we start 21 | // off with the balloon descending i.e. after burst 22 | altitude_model_t *altitude_model_new (int descent_mode, 23 | float burst_alt, 24 | float ascent_rate, 25 | float drag_coeff); 26 | 27 | // free resources associated with the specified altitude model. 28 | void altitude_model_free (altitude_model_t *model); 29 | 30 | // returns the altitude corresponding to a certain time into the flight (in seconds) 31 | // the result it stored in the alt variable. 32 | // the contents of alt when the function is called with time_into_flight = 0 33 | // will be taken as the starting altitude returns 1 normally and 0 when the 34 | // flight has terminated 35 | int altitude_model_get_altitude 36 | (altitude_model_t *model, 37 | int time_into_flight, 38 | float *alt); 39 | 40 | 41 | 42 | // it seems like overkill to do it this way but it is in preparation for being able to load in 43 | // arbitrary altitude/time profiles from a file 44 | 45 | #define DESCENT_MODE_DESCENDING 1 46 | #define DESCENT_MODE_NORMAL 0 47 | 48 | #endif // __ALTITUDE_H__ 49 | 50 | -------------------------------------------------------------------------------- /src/cmake_install.cmake: -------------------------------------------------------------------------------- 1 | # Install script for directory: /var/www/html/habhub/cusf-standalone-predictor/pred_src 2 | 3 | # Set the install prefix 4 | IF(NOT DEFINED CMAKE_INSTALL_PREFIX) 5 | SET(CMAKE_INSTALL_PREFIX "/usr/local") 6 | ENDIF(NOT DEFINED CMAKE_INSTALL_PREFIX) 7 | STRING(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") 8 | 9 | # Set the install configuration name. 10 | IF(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) 11 | IF(BUILD_TYPE) 12 | STRING(REGEX REPLACE "^[^A-Za-z0-9_]+" "" 13 | CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") 14 | ELSE(BUILD_TYPE) 15 | SET(CMAKE_INSTALL_CONFIG_NAME "") 16 | ENDIF(BUILD_TYPE) 17 | MESSAGE(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") 18 | ENDIF(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) 19 | 20 | # Set the component getting installed. 21 | IF(NOT CMAKE_INSTALL_COMPONENT) 22 | IF(COMPONENT) 23 | MESSAGE(STATUS "Install component: \"${COMPONENT}\"") 24 | SET(CMAKE_INSTALL_COMPONENT "${COMPONENT}") 25 | ELSE(COMPONENT) 26 | SET(CMAKE_INSTALL_COMPONENT) 27 | ENDIF(COMPONENT) 28 | ENDIF(NOT CMAKE_INSTALL_COMPONENT) 29 | 30 | # Install shared libraries without execute permission? 31 | IF(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE) 32 | SET(CMAKE_INSTALL_SO_NO_EXE "0") 33 | ENDIF(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE) 34 | 35 | IF(CMAKE_INSTALL_COMPONENT) 36 | SET(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") 37 | ELSE(CMAKE_INSTALL_COMPONENT) 38 | SET(CMAKE_INSTALL_MANIFEST "install_manifest.txt") 39 | ENDIF(CMAKE_INSTALL_COMPONENT) 40 | 41 | FILE(WRITE "/var/www/html/habhub/cusf-standalone-predictor/pred_src/${CMAKE_INSTALL_MANIFEST}" "") 42 | FOREACH(file ${CMAKE_INSTALL_MANIFEST_FILES}) 43 | FILE(APPEND "/var/www/html/habhub/cusf-standalone-predictor/pred_src/${CMAKE_INSTALL_MANIFEST}" "${file}\n") 44 | ENDFOREACH(file) 45 | -------------------------------------------------------------------------------- /src/ini/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2000-2007 by Nicolas Devillard. 2 | MIT License 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a 5 | copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /src/ini/README: -------------------------------------------------------------------------------- 1 | These files from from the iniparser project[1] and are released under the MIT 2 | license which may be found in the associated LICENSE file. 3 | 4 | [1] http://ndevilla.free.fr/iniparser/ 5 | -------------------------------------------------------------------------------- /src/ini/README.orig: -------------------------------------------------------------------------------- 1 | 2 | Welcome to iniParser -- version 3.0b (beta) 3 | released 03 Jan 2008 4 | 5 | This modules offers parsing of ini files from the C level. 6 | See a complete documentation in HTML format, from this directory 7 | open the file html/index.html with any HTML-capable browser. 8 | 9 | Enjoy! 10 | 11 | N.Devillard 12 | Thu Jan 3 19:36:31 CET 2008 13 | -------------------------------------------------------------------------------- /src/ini/dictionary.c: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------*/ 2 | /** 3 | @file dictionary.c 4 | @author N. Devillard 5 | @date Sep 2007 6 | @version $Revision: 1.27 $ 7 | @brief Implements a dictionary for string variables. 8 | 9 | This module implements a simple dictionary object, i.e. a list 10 | of string/string associations. This object is useful to store e.g. 11 | informations retrieved from a configuration file (ini files). 12 | */ 13 | /*--------------------------------------------------------------------------*/ 14 | 15 | /* 16 | $Id: dictionary.c,v 1.27 2007-11-23 21:39:18 ndevilla Exp $ 17 | $Revision: 1.27 $ 18 | */ 19 | /*--------------------------------------------------------------------------- 20 | Includes 21 | ---------------------------------------------------------------------------*/ 22 | #include "dictionary.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | /** Maximum value size for integers and doubles. */ 30 | #define MAXVALSZ 1024 31 | 32 | /** Minimal allocated number of entries in a dictionary */ 33 | #define DICTMINSZ 128 34 | 35 | /** Invalid key token */ 36 | #define DICT_INVALID_KEY ((char*)-1) 37 | 38 | /*--------------------------------------------------------------------------- 39 | Private functions 40 | ---------------------------------------------------------------------------*/ 41 | 42 | /* Doubles the allocated size associated to a pointer */ 43 | /* 'size' is the current allocated size. */ 44 | static void * mem_double(void * ptr, int size) 45 | { 46 | void * newptr ; 47 | 48 | newptr = calloc(2*size, 1); 49 | if (newptr==NULL) { 50 | return NULL ; 51 | } 52 | memcpy(newptr, ptr, size); 53 | free(ptr); 54 | return newptr ; 55 | } 56 | 57 | /*-------------------------------------------------------------------------*/ 58 | /** 59 | @brief Duplicate a string 60 | @param s String to duplicate 61 | @return Pointer to a newly allocated string, to be freed with free() 62 | 63 | This is a replacement for strdup(). This implementation is provided 64 | for systems that do not have it. 65 | */ 66 | /*--------------------------------------------------------------------------*/ 67 | static char * xstrdup(char * s) 68 | { 69 | char * t ; 70 | if (!s) 71 | return NULL ; 72 | t = malloc(strlen(s)+1) ; 73 | if (t) { 74 | strcpy(t,s); 75 | } 76 | return t ; 77 | } 78 | 79 | /*--------------------------------------------------------------------------- 80 | Function codes 81 | ---------------------------------------------------------------------------*/ 82 | /*-------------------------------------------------------------------------*/ 83 | /** 84 | @brief Compute the hash key for a string. 85 | @param key Character string to use for key. 86 | @return 1 unsigned int on at least 32 bits. 87 | 88 | This hash function has been taken from an Article in Dr Dobbs Journal. 89 | This is normally a collision-free function, distributing keys evenly. 90 | The key is stored anyway in the struct so that collision can be avoided 91 | by comparing the key itself in last resort. 92 | */ 93 | /*--------------------------------------------------------------------------*/ 94 | unsigned dictionary_hash(char * key) 95 | { 96 | int len ; 97 | unsigned hash ; 98 | int i ; 99 | 100 | len = strlen(key); 101 | for (hash=0, i=0 ; i>6) ; 105 | } 106 | hash += (hash <<3); 107 | hash ^= (hash >>11); 108 | hash += (hash <<15); 109 | return hash ; 110 | } 111 | 112 | /*-------------------------------------------------------------------------*/ 113 | /** 114 | @brief Create a new dictionary object. 115 | @param size Optional initial size of the dictionary. 116 | @return 1 newly allocated dictionary objet. 117 | 118 | This function allocates a new dictionary object of given size and returns 119 | it. If you do not know in advance (roughly) the number of entries in the 120 | dictionary, give size=0. 121 | */ 122 | /*--------------------------------------------------------------------------*/ 123 | dictionary * dictionary_new(int size) 124 | { 125 | dictionary * d ; 126 | 127 | /* If no size was specified, allocate space for DICTMINSZ */ 128 | if (sizesize = size ; 134 | d->val = (char **)calloc(size, sizeof(char*)); 135 | d->key = (char **)calloc(size, sizeof(char*)); 136 | d->hash = (unsigned int *)calloc(size, sizeof(unsigned)); 137 | return d ; 138 | } 139 | 140 | /*-------------------------------------------------------------------------*/ 141 | /** 142 | @brief Delete a dictionary object 143 | @param d dictionary object to deallocate. 144 | @return void 145 | 146 | Deallocate a dictionary object and all memory associated to it. 147 | */ 148 | /*--------------------------------------------------------------------------*/ 149 | void dictionary_del(dictionary * d) 150 | { 151 | int i ; 152 | 153 | if (d==NULL) return ; 154 | for (i=0 ; isize ; i++) { 155 | if (d->key[i]!=NULL) 156 | free(d->key[i]); 157 | if (d->val[i]!=NULL) 158 | free(d->val[i]); 159 | } 160 | free(d->val); 161 | free(d->key); 162 | free(d->hash); 163 | free(d); 164 | return ; 165 | } 166 | 167 | /*-------------------------------------------------------------------------*/ 168 | /** 169 | @brief Get a value from a dictionary. 170 | @param d dictionary object to search. 171 | @param key Key to look for in the dictionary. 172 | @param def Default value to return if key not found. 173 | @return 1 pointer to internally allocated character string. 174 | 175 | This function locates a key in a dictionary and returns a pointer to its 176 | value, or the passed 'def' pointer if no such key can be found in 177 | dictionary. The returned character pointer points to data internal to the 178 | dictionary object, you should not try to free it or modify it. 179 | */ 180 | /*--------------------------------------------------------------------------*/ 181 | char * dictionary_get(dictionary * d, char * key, char * def) 182 | { 183 | unsigned hash ; 184 | int i ; 185 | 186 | hash = dictionary_hash(key); 187 | for (i=0 ; isize ; i++) { 188 | if (d->key[i]==NULL) 189 | continue ; 190 | /* Compare hash */ 191 | if (hash==d->hash[i]) { 192 | /* Compare string, to avoid hash collisions */ 193 | if (!strcmp(key, d->key[i])) { 194 | return d->val[i] ; 195 | } 196 | } 197 | } 198 | return def ; 199 | } 200 | 201 | /*-------------------------------------------------------------------------*/ 202 | /** 203 | @brief Set a value in a dictionary. 204 | @param d dictionary object to modify. 205 | @param key Key to modify or add. 206 | @param val Value to add. 207 | @return int 0 if Ok, anything else otherwise 208 | 209 | If the given key is found in the dictionary, the associated value is 210 | replaced by the provided one. If the key cannot be found in the 211 | dictionary, it is added to it. 212 | 213 | It is Ok to provide a NULL value for val, but NULL values for the dictionary 214 | or the key are considered as errors: the function will return immediately 215 | in such a case. 216 | 217 | Notice that if you dictionary_set a variable to NULL, a call to 218 | dictionary_get will return a NULL value: the variable will be found, and 219 | its value (NULL) is returned. In other words, setting the variable 220 | content to NULL is equivalent to deleting the variable from the 221 | dictionary. It is not possible (in this implementation) to have a key in 222 | the dictionary without value. 223 | 224 | This function returns non-zero in case of failure. 225 | */ 226 | /*--------------------------------------------------------------------------*/ 227 | int dictionary_set(dictionary * d, char * key, char * val) 228 | { 229 | int i ; 230 | unsigned hash ; 231 | 232 | if (d==NULL || key==NULL) return -1 ; 233 | 234 | /* Compute hash for this key */ 235 | hash = dictionary_hash(key) ; 236 | /* Find if value is already in dictionary */ 237 | if (d->n>0) { 238 | for (i=0 ; isize ; i++) { 239 | if (d->key[i]==NULL) 240 | continue ; 241 | if (hash==d->hash[i]) { /* Same hash value */ 242 | if (!strcmp(key, d->key[i])) { /* Same key */ 243 | /* Found a value: modify and return */ 244 | if (d->val[i]!=NULL) 245 | free(d->val[i]); 246 | d->val[i] = val ? xstrdup(val) : NULL ; 247 | /* Value has been modified: return */ 248 | return 0 ; 249 | } 250 | } 251 | } 252 | } 253 | /* Add a new value */ 254 | /* See if dictionary needs to grow */ 255 | if (d->n==d->size) { 256 | 257 | /* Reached maximum size: reallocate dictionary */ 258 | d->val = (char **)mem_double(d->val, d->size * sizeof(char*)) ; 259 | d->key = (char **)mem_double(d->key, d->size * sizeof(char*)) ; 260 | d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ; 261 | if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) { 262 | /* Cannot grow dictionary */ 263 | return -1 ; 264 | } 265 | /* Double size */ 266 | d->size *= 2 ; 267 | } 268 | 269 | /* Insert key in the first empty slot */ 270 | for (i=0 ; isize ; i++) { 271 | if (d->key[i]==NULL) { 272 | /* Add key here */ 273 | break ; 274 | } 275 | } 276 | /* Copy key */ 277 | d->key[i] = xstrdup(key); 278 | d->val[i] = val ? xstrdup(val) : NULL ; 279 | d->hash[i] = hash; 280 | d->n ++ ; 281 | return 0 ; 282 | } 283 | 284 | /*-------------------------------------------------------------------------*/ 285 | /** 286 | @brief Delete a key in a dictionary 287 | @param d dictionary object to modify. 288 | @param key Key to remove. 289 | @return void 290 | 291 | This function deletes a key in a dictionary. Nothing is done if the 292 | key cannot be found. 293 | */ 294 | /*--------------------------------------------------------------------------*/ 295 | void dictionary_unset(dictionary * d, char * key) 296 | { 297 | unsigned hash ; 298 | int i ; 299 | 300 | if (key == NULL) { 301 | return; 302 | } 303 | 304 | hash = dictionary_hash(key); 305 | for (i=0 ; isize ; i++) { 306 | if (d->key[i]==NULL) 307 | continue ; 308 | /* Compare hash */ 309 | if (hash==d->hash[i]) { 310 | /* Compare string, to avoid hash collisions */ 311 | if (!strcmp(key, d->key[i])) { 312 | /* Found key */ 313 | break ; 314 | } 315 | } 316 | } 317 | if (i>=d->size) 318 | /* Key not found */ 319 | return ; 320 | 321 | free(d->key[i]); 322 | d->key[i] = NULL ; 323 | if (d->val[i]!=NULL) { 324 | free(d->val[i]); 325 | d->val[i] = NULL ; 326 | } 327 | d->hash[i] = 0 ; 328 | d->n -- ; 329 | return ; 330 | } 331 | 332 | /*-------------------------------------------------------------------------*/ 333 | /** 334 | @brief Dump a dictionary to an opened file pointer. 335 | @param d Dictionary to dump 336 | @param f Opened file pointer. 337 | @return void 338 | 339 | Dumps a dictionary onto an opened file pointer. Key pairs are printed out 340 | as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as 341 | output file pointers. 342 | */ 343 | /*--------------------------------------------------------------------------*/ 344 | void dictionary_dump(dictionary * d, FILE * out) 345 | { 346 | int i ; 347 | 348 | if (d==NULL || out==NULL) return ; 349 | if (d->n<1) { 350 | fprintf(out, "empty dictionary\n"); 351 | return ; 352 | } 353 | for (i=0 ; isize ; i++) { 354 | if (d->key[i]) { 355 | fprintf(out, "%20s\t[%s]\n", 356 | d->key[i], 357 | d->val[i] ? d->val[i] : "UNDEF"); 358 | } 359 | } 360 | return ; 361 | } 362 | 363 | 364 | /* Test code */ 365 | #ifdef TESTDIC 366 | #define NVALS 20000 367 | int main(int argc, char *argv[]) 368 | { 369 | dictionary * d ; 370 | char * val ; 371 | int i ; 372 | char cval[90] ; 373 | 374 | /* Allocate dictionary */ 375 | printf("allocating...\n"); 376 | d = dictionary_new(0); 377 | 378 | /* Set values in dictionary */ 379 | printf("setting %d values...\n", NVALS); 380 | for (i=0 ; in != 0) { 398 | printf("error deleting values\n"); 399 | } 400 | printf("deallocating...\n"); 401 | dictionary_del(d); 402 | return 0 ; 403 | } 404 | #endif 405 | /* vim: set ts=4 et sw=4 tw=75 */ 406 | -------------------------------------------------------------------------------- /src/ini/dictionary.h: -------------------------------------------------------------------------------- 1 | 2 | /*-------------------------------------------------------------------------*/ 3 | /** 4 | @file dictionary.h 5 | @author N. Devillard 6 | @date Sep 2007 7 | @version $Revision: 1.12 $ 8 | @brief Implements a dictionary for string variables. 9 | 10 | This module implements a simple dictionary object, i.e. a list 11 | of string/string associations. This object is useful to store e.g. 12 | informations retrieved from a configuration file (ini files). 13 | */ 14 | /*--------------------------------------------------------------------------*/ 15 | 16 | /* 17 | $Id: dictionary.h,v 1.12 2007-11-23 21:37:00 ndevilla Exp $ 18 | $Author: ndevilla $ 19 | $Date: 2007-11-23 21:37:00 $ 20 | $Revision: 1.12 $ 21 | */ 22 | 23 | #ifndef _DICTIONARY_H_ 24 | #define _DICTIONARY_H_ 25 | 26 | /*--------------------------------------------------------------------------- 27 | Includes 28 | ---------------------------------------------------------------------------*/ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | /*--------------------------------------------------------------------------- 36 | New types 37 | ---------------------------------------------------------------------------*/ 38 | 39 | 40 | /*-------------------------------------------------------------------------*/ 41 | /** 42 | @brief Dictionary object 43 | 44 | This object contains a list of string/string associations. Each 45 | association is identified by a unique string key. Looking up values 46 | in the dictionary is speeded up by the use of a (hopefully collision-free) 47 | hash function. 48 | */ 49 | /*-------------------------------------------------------------------------*/ 50 | typedef struct _dictionary_ { 51 | int n ; /** Number of entries in dictionary */ 52 | int size ; /** Storage size */ 53 | char ** val ; /** List of string values */ 54 | char ** key ; /** List of string keys */ 55 | unsigned * hash ; /** List of hash values for keys */ 56 | } dictionary ; 57 | 58 | 59 | /*--------------------------------------------------------------------------- 60 | Function prototypes 61 | ---------------------------------------------------------------------------*/ 62 | 63 | /*-------------------------------------------------------------------------*/ 64 | /** 65 | @brief Compute the hash key for a string. 66 | @param key Character string to use for key. 67 | @return 1 unsigned int on at least 32 bits. 68 | 69 | This hash function has been taken from an Article in Dr Dobbs Journal. 70 | This is normally a collision-free function, distributing keys evenly. 71 | The key is stored anyway in the struct so that collision can be avoided 72 | by comparing the key itself in last resort. 73 | */ 74 | /*--------------------------------------------------------------------------*/ 75 | unsigned dictionary_hash(char * key); 76 | 77 | /*-------------------------------------------------------------------------*/ 78 | /** 79 | @brief Create a new dictionary object. 80 | @param size Optional initial size of the dictionary. 81 | @return 1 newly allocated dictionary objet. 82 | 83 | This function allocates a new dictionary object of given size and returns 84 | it. If you do not know in advance (roughly) the number of entries in the 85 | dictionary, give size=0. 86 | */ 87 | /*--------------------------------------------------------------------------*/ 88 | dictionary * dictionary_new(int size); 89 | 90 | /*-------------------------------------------------------------------------*/ 91 | /** 92 | @brief Delete a dictionary object 93 | @param d dictionary object to deallocate. 94 | @return void 95 | 96 | Deallocate a dictionary object and all memory associated to it. 97 | */ 98 | /*--------------------------------------------------------------------------*/ 99 | void dictionary_del(dictionary * vd); 100 | 101 | /*-------------------------------------------------------------------------*/ 102 | /** 103 | @brief Get a value from a dictionary. 104 | @param d dictionary object to search. 105 | @param key Key to look for in the dictionary. 106 | @param def Default value to return if key not found. 107 | @return 1 pointer to internally allocated character string. 108 | 109 | This function locates a key in a dictionary and returns a pointer to its 110 | value, or the passed 'def' pointer if no such key can be found in 111 | dictionary. The returned character pointer points to data internal to the 112 | dictionary object, you should not try to free it or modify it. 113 | */ 114 | /*--------------------------------------------------------------------------*/ 115 | char * dictionary_get(dictionary * d, char * key, char * def); 116 | 117 | 118 | /*-------------------------------------------------------------------------*/ 119 | /** 120 | @brief Set a value in a dictionary. 121 | @param d dictionary object to modify. 122 | @param key Key to modify or add. 123 | @param val Value to add. 124 | @return int 0 if Ok, anything else otherwise 125 | 126 | If the given key is found in the dictionary, the associated value is 127 | replaced by the provided one. If the key cannot be found in the 128 | dictionary, it is added to it. 129 | 130 | It is Ok to provide a NULL value for val, but NULL values for the dictionary 131 | or the key are considered as errors: the function will return immediately 132 | in such a case. 133 | 134 | Notice that if you dictionary_set a variable to NULL, a call to 135 | dictionary_get will return a NULL value: the variable will be found, and 136 | its value (NULL) is returned. In other words, setting the variable 137 | content to NULL is equivalent to deleting the variable from the 138 | dictionary. It is not possible (in this implementation) to have a key in 139 | the dictionary without value. 140 | 141 | This function returns non-zero in case of failure. 142 | */ 143 | /*--------------------------------------------------------------------------*/ 144 | int dictionary_set(dictionary * vd, char * key, char * val); 145 | 146 | /*-------------------------------------------------------------------------*/ 147 | /** 148 | @brief Delete a key in a dictionary 149 | @param d dictionary object to modify. 150 | @param key Key to remove. 151 | @return void 152 | 153 | This function deletes a key in a dictionary. Nothing is done if the 154 | key cannot be found. 155 | */ 156 | /*--------------------------------------------------------------------------*/ 157 | void dictionary_unset(dictionary * d, char * key); 158 | 159 | 160 | /*-------------------------------------------------------------------------*/ 161 | /** 162 | @brief Dump a dictionary to an opened file pointer. 163 | @param d Dictionary to dump 164 | @param f Opened file pointer. 165 | @return void 166 | 167 | Dumps a dictionary onto an opened file pointer. Key pairs are printed out 168 | as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as 169 | output file pointers. 170 | */ 171 | /*--------------------------------------------------------------------------*/ 172 | void dictionary_dump(dictionary * d, FILE * out); 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /src/ini/iniparser.c: -------------------------------------------------------------------------------- 1 | 2 | /*-------------------------------------------------------------------------*/ 3 | /** 4 | @file iniparser.c 5 | @author N. Devillard 6 | @date Sep 2007 7 | @version 3.0 8 | @brief Parser for ini files. 9 | */ 10 | /*--------------------------------------------------------------------------*/ 11 | /* 12 | $Id: iniparser.c,v 2.18 2008-01-03 18:35:39 ndevilla Exp $ 13 | $Revision: 2.18 $ 14 | $Date: 2008-01-03 18:35:39 $ 15 | */ 16 | /*---------------------------- Includes ------------------------------------*/ 17 | #include 18 | #include "iniparser.h" 19 | 20 | /*---------------------------- Defines -------------------------------------*/ 21 | #define ASCIILINESZ (1024) 22 | #define INI_INVALID_KEY ((char*)-1) 23 | 24 | /*--------------------------------------------------------------------------- 25 | Private to this module 26 | ---------------------------------------------------------------------------*/ 27 | /** 28 | * This enum stores the status for each parsed line (internal use only). 29 | */ 30 | typedef enum _line_status_ { 31 | LINE_UNPROCESSED, 32 | LINE_ERROR, 33 | LINE_EMPTY, 34 | LINE_COMMENT, 35 | LINE_SECTION, 36 | LINE_VALUE 37 | } line_status ; 38 | 39 | /*-------------------------------------------------------------------------*/ 40 | /** 41 | @brief Convert a string to lowercase. 42 | @param s String to convert. 43 | @return ptr to statically allocated string. 44 | 45 | This function returns a pointer to a statically allocated string 46 | containing a lowercased version of the input string. Do not free 47 | or modify the returned string! Since the returned string is statically 48 | allocated, it will be modified at each function call (not re-entrant). 49 | */ 50 | /*--------------------------------------------------------------------------*/ 51 | static char * strlwc(const char * s) 52 | { 53 | static char l[ASCIILINESZ+1]; 54 | int i ; 55 | 56 | if (s==NULL) return NULL ; 57 | memset(l, 0, ASCIILINESZ+1); 58 | i=0 ; 59 | while (s[i] && i l) { 93 | if (!isspace((int)*(last-1))) 94 | break ; 95 | last -- ; 96 | } 97 | *last = (char)0; 98 | return (char*)l ; 99 | } 100 | 101 | /*-------------------------------------------------------------------------*/ 102 | /** 103 | @brief Get number of sections in a dictionary 104 | @param d Dictionary to examine 105 | @return int Number of sections found in dictionary 106 | 107 | This function returns the number of sections found in a dictionary. 108 | The test to recognize sections is done on the string stored in the 109 | dictionary: a section name is given as "section" whereas a key is 110 | stored as "section:key", thus the test looks for entries that do not 111 | contain a colon. 112 | 113 | This clearly fails in the case a section name contains a colon, but 114 | this should simply be avoided. 115 | 116 | This function returns -1 in case of error. 117 | */ 118 | /*--------------------------------------------------------------------------*/ 119 | int iniparser_getnsec(dictionary * d) 120 | { 121 | int i ; 122 | int nsec ; 123 | 124 | if (d==NULL) return -1 ; 125 | nsec=0 ; 126 | for (i=0 ; isize ; i++) { 127 | if (d->key[i]==NULL) 128 | continue ; 129 | if (strchr(d->key[i], ':')==NULL) { 130 | nsec ++ ; 131 | } 132 | } 133 | return nsec ; 134 | } 135 | 136 | /*-------------------------------------------------------------------------*/ 137 | /** 138 | @brief Get name for section n in a dictionary. 139 | @param d Dictionary to examine 140 | @param n Section number (from 0 to nsec-1). 141 | @return Pointer to char string 142 | 143 | This function locates the n-th section in a dictionary and returns 144 | its name as a pointer to a string statically allocated inside the 145 | dictionary. Do not free or modify the returned string! 146 | 147 | This function returns NULL in case of error. 148 | */ 149 | /*--------------------------------------------------------------------------*/ 150 | char * iniparser_getsecname(dictionary * d, int n) 151 | { 152 | int i ; 153 | int foundsec ; 154 | 155 | if (d==NULL || n<0) return NULL ; 156 | foundsec=0 ; 157 | for (i=0 ; isize ; i++) { 158 | if (d->key[i]==NULL) 159 | continue ; 160 | if (strchr(d->key[i], ':')==NULL) { 161 | foundsec++ ; 162 | if (foundsec>n) 163 | break ; 164 | } 165 | } 166 | if (foundsec<=n) { 167 | return NULL ; 168 | } 169 | return d->key[i] ; 170 | } 171 | 172 | /*-------------------------------------------------------------------------*/ 173 | /** 174 | @brief Dump a dictionary to an opened file pointer. 175 | @param d Dictionary to dump. 176 | @param f Opened file pointer to dump to. 177 | @return void 178 | 179 | This function prints out the contents of a dictionary, one element by 180 | line, onto the provided file pointer. It is OK to specify @c stderr 181 | or @c stdout as output files. This function is meant for debugging 182 | purposes mostly. 183 | */ 184 | /*--------------------------------------------------------------------------*/ 185 | void iniparser_dump(dictionary * d, FILE * f) 186 | { 187 | int i ; 188 | 189 | if (d==NULL || f==NULL) return ; 190 | for (i=0 ; isize ; i++) { 191 | if (d->key[i]==NULL) 192 | continue ; 193 | if (d->val[i]!=NULL) { 194 | fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); 195 | } else { 196 | fprintf(f, "[%s]=UNDEF\n", d->key[i]); 197 | } 198 | } 199 | return ; 200 | } 201 | 202 | /*-------------------------------------------------------------------------*/ 203 | /** 204 | @brief Save a dictionary to a loadable ini file 205 | @param d Dictionary to dump 206 | @param f Opened file pointer to dump to 207 | @return void 208 | 209 | This function dumps a given dictionary into a loadable ini file. 210 | It is Ok to specify @c stderr or @c stdout as output files. 211 | */ 212 | /*--------------------------------------------------------------------------*/ 213 | void iniparser_dump_ini(dictionary * d, FILE * f) 214 | { 215 | int i, j ; 216 | char keym[ASCIILINESZ+1]; 217 | int nsec ; 218 | char * secname ; 219 | int seclen ; 220 | 221 | if (d==NULL || f==NULL) return ; 222 | 223 | nsec = iniparser_getnsec(d); 224 | if (nsec<1) { 225 | /* No section in file: dump all keys as they are */ 226 | for (i=0 ; isize ; i++) { 227 | if (d->key[i]==NULL) 228 | continue ; 229 | fprintf(f, "%s = %s\n", d->key[i], d->val[i]); 230 | } 231 | return ; 232 | } 233 | for (i=0 ; isize ; j++) { 239 | if (d->key[j]==NULL) 240 | continue ; 241 | if (!strncmp(d->key[j], keym, seclen+1)) { 242 | fprintf(f, 243 | "%-30s = %s\n", 244 | d->key[j]+seclen+1, 245 | d->val[j] ? d->val[j] : ""); 246 | } 247 | } 248 | } 249 | fprintf(f, "\n"); 250 | return ; 251 | } 252 | 253 | /*-------------------------------------------------------------------------*/ 254 | /** 255 | @brief Get the string associated to a key 256 | @param d Dictionary to search 257 | @param key Key string to look for 258 | @param def Default value to return if key not found. 259 | @return pointer to statically allocated character string 260 | 261 | This function queries a dictionary for a key. A key as read from an 262 | ini file is given as "section:key". If the key cannot be found, 263 | the pointer passed as 'def' is returned. 264 | The returned char pointer is pointing to a string allocated in 265 | the dictionary, do not free or modify it. 266 | */ 267 | /*--------------------------------------------------------------------------*/ 268 | char * iniparser_getstring(dictionary * d, const char * key, char * def) 269 | { 270 | char * lc_key ; 271 | char * sval ; 272 | 273 | if (d==NULL || key==NULL) 274 | return def ; 275 | 276 | lc_key = strlwc(key); 277 | sval = dictionary_get(d, lc_key, def); 278 | return sval ; 279 | } 280 | 281 | /*-------------------------------------------------------------------------*/ 282 | /** 283 | @brief Get the string associated to a key, convert to an int 284 | @param d Dictionary to search 285 | @param key Key string to look for 286 | @param notfound Value to return in case of error 287 | @return integer 288 | 289 | This function queries a dictionary for a key. A key as read from an 290 | ini file is given as "section:key". If the key cannot be found, 291 | the notfound value is returned. 292 | 293 | Supported values for integers include the usual C notation 294 | so decimal, octal (starting with 0) and hexadecimal (starting with 0x) 295 | are supported. Examples: 296 | 297 | "42" -> 42 298 | "042" -> 34 (octal -> decimal) 299 | "0x42" -> 66 (hexa -> decimal) 300 | 301 | Warning: the conversion may overflow in various ways. Conversion is 302 | totally outsourced to strtol(), see the associated man page for overflow 303 | handling. 304 | 305 | Credits: Thanks to A. Becker for suggesting strtol() 306 | */ 307 | /*--------------------------------------------------------------------------*/ 308 | int iniparser_getint(dictionary * d, const char * key, int notfound) 309 | { 310 | char * str ; 311 | 312 | str = iniparser_getstring(d, key, INI_INVALID_KEY); 313 | if (str==INI_INVALID_KEY) return notfound ; 314 | return (int)strtol(str, NULL, 0); 315 | } 316 | 317 | /*-------------------------------------------------------------------------*/ 318 | /** 319 | @brief Get the string associated to a key, convert to a double 320 | @param d Dictionary to search 321 | @param key Key string to look for 322 | @param notfound Value to return in case of error 323 | @return double 324 | 325 | This function queries a dictionary for a key. A key as read from an 326 | ini file is given as "section:key". If the key cannot be found, 327 | the notfound value is returned. 328 | */ 329 | /*--------------------------------------------------------------------------*/ 330 | double iniparser_getdouble(dictionary * d, char * key, double notfound) 331 | { 332 | char * str ; 333 | 334 | str = iniparser_getstring(d, key, INI_INVALID_KEY); 335 | if (str==INI_INVALID_KEY) return notfound ; 336 | return atof(str); 337 | } 338 | 339 | /*-------------------------------------------------------------------------*/ 340 | /** 341 | @brief Get the string associated to a key, convert to a boolean 342 | @param d Dictionary to search 343 | @param key Key string to look for 344 | @param notfound Value to return in case of error 345 | @return integer 346 | 347 | This function queries a dictionary for a key. A key as read from an 348 | ini file is given as "section:key". If the key cannot be found, 349 | the notfound value is returned. 350 | 351 | A true boolean is found if one of the following is matched: 352 | 353 | - A string starting with 'y' 354 | - A string starting with 'Y' 355 | - A string starting with 't' 356 | - A string starting with 'T' 357 | - A string starting with '1' 358 | 359 | A false boolean is found if one of the following is matched: 360 | 361 | - A string starting with 'n' 362 | - A string starting with 'N' 363 | - A string starting with 'f' 364 | - A string starting with 'F' 365 | - A string starting with '0' 366 | 367 | The notfound value returned if no boolean is identified, does not 368 | necessarily have to be 0 or 1. 369 | */ 370 | /*--------------------------------------------------------------------------*/ 371 | int iniparser_getboolean(dictionary * d, const char * key, int notfound) 372 | { 373 | char * c ; 374 | int ret ; 375 | 376 | c = iniparser_getstring(d, key, INI_INVALID_KEY); 377 | if (c==INI_INVALID_KEY) return notfound ; 378 | if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { 379 | ret = 1 ; 380 | } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { 381 | ret = 0 ; 382 | } else { 383 | ret = notfound ; 384 | } 385 | return ret; 386 | } 387 | 388 | /*-------------------------------------------------------------------------*/ 389 | /** 390 | @brief Finds out if a given entry exists in a dictionary 391 | @param ini Dictionary to search 392 | @param entry Name of the entry to look for 393 | @return integer 1 if entry exists, 0 otherwise 394 | 395 | Finds out if a given entry exists in the dictionary. Since sections 396 | are stored as keys with NULL associated values, this is the only way 397 | of querying for the presence of sections in a dictionary. 398 | */ 399 | /*--------------------------------------------------------------------------*/ 400 | int iniparser_find_entry( 401 | dictionary * ini, 402 | char * entry 403 | ) 404 | { 405 | int found=0 ; 406 | if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { 407 | found = 1 ; 408 | } 409 | return found ; 410 | } 411 | 412 | /*-------------------------------------------------------------------------*/ 413 | /** 414 | @brief Set an entry in a dictionary. 415 | @param ini Dictionary to modify. 416 | @param entry Entry to modify (entry name) 417 | @param val New value to associate to the entry. 418 | @return int 0 if Ok, -1 otherwise. 419 | 420 | If the given entry can be found in the dictionary, it is modified to 421 | contain the provided value. If it cannot be found, -1 is returned. 422 | It is Ok to set val to NULL. 423 | */ 424 | /*--------------------------------------------------------------------------*/ 425 | int iniparser_set(dictionary * ini, char * entry, char * val) 426 | { 427 | return dictionary_set(ini, strlwc(entry), val) ; 428 | } 429 | 430 | /*-------------------------------------------------------------------------*/ 431 | /** 432 | @brief Delete an entry in a dictionary 433 | @param ini Dictionary to modify 434 | @param entry Entry to delete (entry name) 435 | @return void 436 | 437 | If the given entry can be found, it is deleted from the dictionary. 438 | */ 439 | /*--------------------------------------------------------------------------*/ 440 | void iniparser_unset(dictionary * ini, char * entry) 441 | { 442 | dictionary_unset(ini, strlwc(entry)); 443 | } 444 | 445 | /*-------------------------------------------------------------------------*/ 446 | /** 447 | @brief Load a single line from an INI file 448 | @param input_line Input line, may be concatenated multi-line input 449 | @param section Output space to store section 450 | @param key Output space to store key 451 | @param value Output space to store value 452 | @return line_status value 453 | */ 454 | /*--------------------------------------------------------------------------*/ 455 | static line_status iniparser_line( 456 | char * input_line, 457 | char * section, 458 | char * key, 459 | char * value) 460 | { 461 | line_status sta ; 462 | char line[ASCIILINESZ+1]; 463 | int len ; 464 | 465 | strcpy(line, strstrip(input_line)); 466 | len = (int)strlen(line); 467 | 468 | sta = LINE_UNPROCESSED ; 469 | if (len<1) { 470 | /* Empty line */ 471 | sta = LINE_EMPTY ; 472 | } else if (line[0]=='#') { 473 | /* Comment line */ 474 | sta = LINE_COMMENT ; 475 | } else if (line[0]=='[' && line[len-1]==']') { 476 | /* Section name */ 477 | sscanf(line, "[%[^]]", section); 478 | strcpy(section, strstrip(section)); 479 | strcpy(section, strlwc(section)); 480 | sta = LINE_SECTION ; 481 | } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 482 | || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2 483 | || sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { 484 | /* Usual key=value, with or without comments */ 485 | strcpy(key, strstrip(key)); 486 | strcpy(key, strlwc(key)); 487 | strcpy(value, strstrip(value)); 488 | /* 489 | * sscanf cannot handle '' or "" as empty values 490 | * this is done here 491 | */ 492 | if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { 493 | value[0]=0 ; 494 | } 495 | sta = LINE_VALUE ; 496 | } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 497 | || sscanf(line, "%[^=] %[=]", key, value) == 2) { 498 | /* 499 | * Special cases: 500 | * key= 501 | * key=; 502 | * key=# 503 | */ 504 | strcpy(key, strstrip(key)); 505 | strcpy(key, strlwc(key)); 506 | value[0]=0 ; 507 | sta = LINE_VALUE ; 508 | } else { 509 | /* Generate syntax error */ 510 | sta = LINE_ERROR ; 511 | } 512 | return sta ; 513 | } 514 | 515 | /*-------------------------------------------------------------------------*/ 516 | /** 517 | @brief Parse an ini file and return an allocated dictionary object 518 | @param in FILE* to read INI from 519 | @return Pointer to newly allocated dictionary 520 | 521 | This is the parser for ini files. This function is called, providing 522 | the name of the file to be read. It returns a dictionary object that 523 | should not be accessed directly, but through accessor functions 524 | instead. 525 | 526 | The returned dictionary must be freed using iniparser_freedict(). 527 | */ 528 | /*--------------------------------------------------------------------------*/ 529 | dictionary * iniparser_loadfile(FILE * in) 530 | { 531 | char line [ASCIILINESZ+1] ; 532 | char section [ASCIILINESZ+1] ; 533 | char key [ASCIILINESZ+1] ; 534 | char tmp [ASCIILINESZ+1] ; 535 | char val [ASCIILINESZ+1] ; 536 | 537 | int last=0 ; 538 | int len ; 539 | int lineno=0 ; 540 | int errs=0; 541 | 542 | dictionary * dict ; 543 | 544 | if(in == NULL) { 545 | return NULL ; 546 | } 547 | 548 | dict = dictionary_new(0) ; 549 | if (!dict) { 550 | return NULL ; 551 | } 552 | 553 | memset(line, 0, ASCIILINESZ); 554 | memset(section, 0, ASCIILINESZ); 555 | memset(key, 0, ASCIILINESZ); 556 | memset(val, 0, ASCIILINESZ); 557 | last=0 ; 558 | 559 | while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { 560 | lineno++ ; 561 | len = (int)strlen(line)-1; 562 | /* Safety check against buffer overflows */ 563 | if (line[len]!='\n') { 564 | fprintf(stderr, 565 | "iniparser: input line too long in file (%d)\n", 566 | lineno); 567 | dictionary_del(dict); 568 | return NULL ; 569 | } 570 | /* Get rid of \n and spaces at end of line */ 571 | while ((len>=0) && 572 | ((line[len]=='\n') || (isspace(line[len])))) { 573 | line[len]=0 ; 574 | len-- ; 575 | } 576 | /* Detect multi-line */ 577 | if (line[len]=='\\') { 578 | /* Multi-line value */ 579 | last=len ; 580 | continue ; 581 | } else { 582 | last=0 ; 583 | } 584 | switch (iniparser_line(line, section, key, val)) { 585 | case LINE_EMPTY: 586 | case LINE_COMMENT: 587 | break ; 588 | 589 | case LINE_SECTION: 590 | errs = dictionary_set(dict, section, NULL); 591 | break ; 592 | 593 | case LINE_VALUE: 594 | sprintf(tmp, "%s:%s", section, key); 595 | errs = dictionary_set(dict, tmp, val) ; 596 | break ; 597 | 598 | case LINE_ERROR: 599 | fprintf(stderr, "iniparser: syntax error in file (%d):\n", 600 | lineno); 601 | fprintf(stderr, "-> %s\n", line); 602 | errs++ ; 603 | break; 604 | 605 | default: 606 | break ; 607 | } 608 | memset(line, 0, ASCIILINESZ); 609 | last=0; 610 | if (errs<0) { 611 | fprintf(stderr, "iniparser: memory allocation failure\n"); 612 | break ; 613 | } 614 | } 615 | if (errs) { 616 | dictionary_del(dict); 617 | dict = NULL ; 618 | } 619 | return dict ; 620 | } 621 | 622 | /*-------------------------------------------------------------------------*/ 623 | /** 624 | @brief Parse an ini file and return an allocated dictionary object 625 | @param ininame Name of the ini file to read. 626 | @return Pointer to newly allocated dictionary 627 | 628 | This is the parser for ini files. This function is called, providing 629 | the name of the file to be read. It returns a dictionary object that 630 | should not be accessed directly, but through accessor functions 631 | instead. 632 | 633 | The returned dictionary must be freed using iniparser_freedict(). 634 | */ 635 | /*--------------------------------------------------------------------------*/ 636 | dictionary * iniparser_load(const char * ininame) 637 | { 638 | FILE * in; 639 | dictionary * dict; 640 | 641 | if ((in=fopen(ininame, "r"))==NULL) { 642 | fprintf(stderr, "iniparser: cannot open %s\n", ininame); 643 | return NULL ; 644 | } 645 | 646 | dict = iniparser_loadfile(in); 647 | fclose(in); 648 | 649 | return dict ; 650 | } 651 | 652 | /*-------------------------------------------------------------------------*/ 653 | /** 654 | @brief Free all memory associated to an ini dictionary 655 | @param d Dictionary to free 656 | @return void 657 | 658 | Free all memory associated to an ini dictionary. 659 | It is mandatory to call this function before the dictionary object 660 | gets out of the current context. 661 | */ 662 | /*--------------------------------------------------------------------------*/ 663 | void iniparser_freedict(dictionary * d) 664 | { 665 | dictionary_del(d); 666 | } 667 | 668 | /* vim: set ts=4 et sw=4 tw=75 */ 669 | -------------------------------------------------------------------------------- /src/ini/iniparser.h: -------------------------------------------------------------------------------- 1 | 2 | /*-------------------------------------------------------------------------*/ 3 | /** 4 | @file iniparser.h 5 | @author N. Devillard 6 | @date Sep 2007 7 | @version 3.0 8 | @brief Parser for ini files. 9 | */ 10 | /*--------------------------------------------------------------------------*/ 11 | 12 | /* 13 | $Id: iniparser.h,v 1.24 2007-11-23 21:38:19 ndevilla Exp $ 14 | $Revision: 1.24 $ 15 | */ 16 | 17 | #ifndef _INIPARSER_H_ 18 | #define _INIPARSER_H_ 19 | 20 | /*--------------------------------------------------------------------------- 21 | Includes 22 | ---------------------------------------------------------------------------*/ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | /* 29 | * The following #include is necessary on many Unixes but not Linux. 30 | * It is not needed for Windows platforms. 31 | * Uncomment it if needed. 32 | */ 33 | /* #include */ 34 | 35 | #include "dictionary.h" 36 | 37 | /*--------------------------------------------------------------------------- 38 | Macros 39 | ---------------------------------------------------------------------------*/ 40 | /** For backwards compatibility only */ 41 | #define iniparser_getstr(d, k) iniparser_getstring(d, k, NULL) 42 | #define iniparser_setstr iniparser_setstring 43 | 44 | /*-------------------------------------------------------------------------*/ 45 | /** 46 | @brief Get number of sections in a dictionary 47 | @param d Dictionary to examine 48 | @return int Number of sections found in dictionary 49 | 50 | This function returns the number of sections found in a dictionary. 51 | The test to recognize sections is done on the string stored in the 52 | dictionary: a section name is given as "section" whereas a key is 53 | stored as "section:key", thus the test looks for entries that do not 54 | contain a colon. 55 | 56 | This clearly fails in the case a section name contains a colon, but 57 | this should simply be avoided. 58 | 59 | This function returns -1 in case of error. 60 | */ 61 | /*--------------------------------------------------------------------------*/ 62 | 63 | int iniparser_getnsec(dictionary * d); 64 | 65 | 66 | /*-------------------------------------------------------------------------*/ 67 | /** 68 | @brief Get name for section n in a dictionary. 69 | @param d Dictionary to examine 70 | @param n Section number (from 0 to nsec-1). 71 | @return Pointer to char string 72 | 73 | This function locates the n-th section in a dictionary and returns 74 | its name as a pointer to a string statically allocated inside the 75 | dictionary. Do not free or modify the returned string! 76 | 77 | This function returns NULL in case of error. 78 | */ 79 | /*--------------------------------------------------------------------------*/ 80 | 81 | char * iniparser_getsecname(dictionary * d, int n); 82 | 83 | 84 | /*-------------------------------------------------------------------------*/ 85 | /** 86 | @brief Save a dictionary to a loadable ini file 87 | @param d Dictionary to dump 88 | @param f Opened file pointer to dump to 89 | @return void 90 | 91 | This function dumps a given dictionary into a loadable ini file. 92 | It is Ok to specify @c stderr or @c stdout as output files. 93 | */ 94 | /*--------------------------------------------------------------------------*/ 95 | 96 | void iniparser_dump_ini(dictionary * d, FILE * f); 97 | 98 | /*-------------------------------------------------------------------------*/ 99 | /** 100 | @brief Dump a dictionary to an opened file pointer. 101 | @param d Dictionary to dump. 102 | @param f Opened file pointer to dump to. 103 | @return void 104 | 105 | This function prints out the contents of a dictionary, one element by 106 | line, onto the provided file pointer. It is OK to specify @c stderr 107 | or @c stdout as output files. This function is meant for debugging 108 | purposes mostly. 109 | */ 110 | /*--------------------------------------------------------------------------*/ 111 | void iniparser_dump(dictionary * d, FILE * f); 112 | 113 | /*-------------------------------------------------------------------------*/ 114 | /** 115 | @brief Get the string associated to a key 116 | @param d Dictionary to search 117 | @param key Key string to look for 118 | @param def Default value to return if key not found. 119 | @return pointer to statically allocated character string 120 | 121 | This function queries a dictionary for a key. A key as read from an 122 | ini file is given as "section:key". If the key cannot be found, 123 | the pointer passed as 'def' is returned. 124 | The returned char pointer is pointing to a string allocated in 125 | the dictionary, do not free or modify it. 126 | */ 127 | /*--------------------------------------------------------------------------*/ 128 | char * iniparser_getstring(dictionary * d, const char * key, char * def); 129 | 130 | /*-------------------------------------------------------------------------*/ 131 | /** 132 | @brief Get the string associated to a key, convert to an int 133 | @param d Dictionary to search 134 | @param key Key string to look for 135 | @param notfound Value to return in case of error 136 | @return integer 137 | 138 | This function queries a dictionary for a key. A key as read from an 139 | ini file is given as "section:key". If the key cannot be found, 140 | the notfound value is returned. 141 | 142 | Supported values for integers include the usual C notation 143 | so decimal, octal (starting with 0) and hexadecimal (starting with 0x) 144 | are supported. Examples: 145 | 146 | - "42" -> 42 147 | - "042" -> 34 (octal -> decimal) 148 | - "0x42" -> 66 (hexa -> decimal) 149 | 150 | Warning: the conversion may overflow in various ways. Conversion is 151 | totally outsourced to strtol(), see the associated man page for overflow 152 | handling. 153 | 154 | Credits: Thanks to A. Becker for suggesting strtol() 155 | */ 156 | /*--------------------------------------------------------------------------*/ 157 | int iniparser_getint(dictionary * d, const char * key, int notfound); 158 | 159 | /*-------------------------------------------------------------------------*/ 160 | /** 161 | @brief Get the string associated to a key, convert to a double 162 | @param d Dictionary to search 163 | @param key Key string to look for 164 | @param notfound Value to return in case of error 165 | @return double 166 | 167 | This function queries a dictionary for a key. A key as read from an 168 | ini file is given as "section:key". If the key cannot be found, 169 | the notfound value is returned. 170 | */ 171 | /*--------------------------------------------------------------------------*/ 172 | double iniparser_getdouble(dictionary * d, char * key, double notfound); 173 | 174 | /*-------------------------------------------------------------------------*/ 175 | /** 176 | @brief Get the string associated to a key, convert to a boolean 177 | @param d Dictionary to search 178 | @param key Key string to look for 179 | @param notfound Value to return in case of error 180 | @return integer 181 | 182 | This function queries a dictionary for a key. A key as read from an 183 | ini file is given as "section:key". If the key cannot be found, 184 | the notfound value is returned. 185 | 186 | A true boolean is found if one of the following is matched: 187 | 188 | - A string starting with 'y' 189 | - A string starting with 'Y' 190 | - A string starting with 't' 191 | - A string starting with 'T' 192 | - A string starting with '1' 193 | 194 | A false boolean is found if one of the following is matched: 195 | 196 | - A string starting with 'n' 197 | - A string starting with 'N' 198 | - A string starting with 'f' 199 | - A string starting with 'F' 200 | - A string starting with '0' 201 | 202 | The notfound value returned if no boolean is identified, does not 203 | necessarily have to be 0 or 1. 204 | */ 205 | /*--------------------------------------------------------------------------*/ 206 | int iniparser_getboolean(dictionary * d, const char * key, int notfound); 207 | 208 | 209 | /*-------------------------------------------------------------------------*/ 210 | /** 211 | @brief Set an entry in a dictionary. 212 | @param ini Dictionary to modify. 213 | @param entry Entry to modify (entry name) 214 | @param val New value to associate to the entry. 215 | @return int 0 if Ok, -1 otherwise. 216 | 217 | If the given entry can be found in the dictionary, it is modified to 218 | contain the provided value. If it cannot be found, -1 is returned. 219 | It is Ok to set val to NULL. 220 | */ 221 | /*--------------------------------------------------------------------------*/ 222 | int iniparser_setstring(dictionary * ini, char * entry, char * val); 223 | 224 | 225 | /*-------------------------------------------------------------------------*/ 226 | /** 227 | @brief Delete an entry in a dictionary 228 | @param ini Dictionary to modify 229 | @param entry Entry to delete (entry name) 230 | @return void 231 | 232 | If the given entry can be found, it is deleted from the dictionary. 233 | */ 234 | /*--------------------------------------------------------------------------*/ 235 | void iniparser_unset(dictionary * ini, char * entry); 236 | 237 | /*-------------------------------------------------------------------------*/ 238 | /** 239 | @brief Finds out if a given entry exists in a dictionary 240 | @param ini Dictionary to search 241 | @param entry Name of the entry to look for 242 | @return integer 1 if entry exists, 0 otherwise 243 | 244 | Finds out if a given entry exists in the dictionary. Since sections 245 | are stored as keys with NULL associated values, this is the only way 246 | of querying for the presence of sections in a dictionary. 247 | */ 248 | /*--------------------------------------------------------------------------*/ 249 | int iniparser_find_entry(dictionary * ini, char * entry) ; 250 | 251 | /*-------------------------------------------------------------------------*/ 252 | /** 253 | @brief Parse an ini file and return an allocated dictionary object 254 | @param ininame Name of the ini file to read. 255 | @return Pointer to newly allocated dictionary 256 | 257 | This is the parser for ini files. This function is called, providing 258 | the name of the file to be read. It returns a dictionary object that 259 | should not be accessed directly, but through accessor functions 260 | instead. 261 | 262 | The returned dictionary must be freed using iniparser_freedict(). 263 | */ 264 | /*--------------------------------------------------------------------------*/ 265 | dictionary * iniparser_load(const char * ininame); 266 | 267 | /*-------------------------------------------------------------------------*/ 268 | /** 269 | @brief Parse an ini file and return an allocated dictionary object 270 | @param file FILE* associated with file to read 271 | @return Pointer to newly allocated dictionary 272 | 273 | This is the parser for ini files. This function is called, providing 274 | the name of the file to be read. It returns a dictionary object that 275 | should not be accessed directly, but through accessor functions 276 | instead. 277 | 278 | The returned dictionary must be freed using iniparser_freedict(). 279 | */ 280 | /*--------------------------------------------------------------------------*/ 281 | dictionary * iniparser_loadfile(FILE * file); 282 | 283 | /*-------------------------------------------------------------------------*/ 284 | /** 285 | @brief Free all memory associated to an ini dictionary 286 | @param d Dictionary to free 287 | @return void 288 | 289 | Free all memory associated to an ini dictionary. 290 | It is mandatory to call this function before the dictionary object 291 | gets out of the current context. 292 | */ 293 | /*--------------------------------------------------------------------------*/ 294 | void iniparser_freedict(dictionary * d); 295 | 296 | #endif 297 | -------------------------------------------------------------------------------- /src/pred.c: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rob Anderson 6 | // Modified by Fergus Noble 7 | // 8 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 11 | // PARTICULAR PURPOSE. 12 | // -------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "ini/iniparser.h" 22 | #include "util/gopt.h" 23 | #include "wind/wind_file_cache.h" 24 | 25 | #include "run_model.h" 26 | #include "pred.h" 27 | #include "altitude.h" 28 | 29 | FILE* output; 30 | FILE* kml_file; 31 | const char* data_dir; 32 | int verbosity; 33 | 34 | int main(int argc, const char *argv[]) { 35 | 36 | const char* argument; 37 | 38 | long int initial_timestamp; 39 | float initial_lat, initial_lng, initial_alt; 40 | float burst_alt, ascent_rate, drag_coeff, rmswinderror; 41 | int descent_mode; 42 | int scenario_idx, n_scenarios; 43 | int alarm_time; 44 | char* endptr; // used to check for errors on strtod calls 45 | 46 | wind_file_cache_t* file_cache; 47 | dictionary* scenario = NULL; 48 | 49 | // configure command-line options parsing 50 | void *options = gopt_sort(&argc, argv, gopt_start( 51 | gopt_option('h', 0, gopt_shorts('h', '?'), gopt_longs("help")), 52 | gopt_option('z', 0, gopt_shorts(0), gopt_longs("version")), 53 | gopt_option('v', GOPT_REPEAT, gopt_shorts('v'), gopt_longs("verbose")), 54 | gopt_option('o', GOPT_ARG, gopt_shorts('o'), gopt_longs("output")), 55 | gopt_option('k', GOPT_ARG, gopt_shorts('k'), gopt_longs("kml")), 56 | gopt_option('t', GOPT_ARG, gopt_shorts('t'), gopt_longs("start_time")), 57 | gopt_option('i', GOPT_ARG, gopt_shorts('i'), gopt_longs("data_dir")), 58 | gopt_option('d', 0, gopt_shorts('d'), gopt_longs("descending")), 59 | gopt_option('e', GOPT_ARG, gopt_shorts('e'), gopt_longs("wind_error")), 60 | gopt_option('a', GOPT_ARG, gopt_shorts('a'), gopt_longs("alarm")) 61 | )); 62 | 63 | if (gopt(options, 'h')) { 64 | // Help! 65 | // Print usage information 66 | printf("Usage: %s [options] [scenario files]\n", argv[0]); 67 | printf("Options:\n\n"); 68 | printf(" -h --help Display this information.\n"); 69 | printf(" --version Display version information.\n"); 70 | printf(" -v --verbose Display more information while running,\n"); 71 | printf(" Use -vv, -vvv etc. for even more verbose output.\n"); 72 | printf(" -t --start_time Start time of model, defaults to current time.\n"); 73 | printf(" Should be a UNIX standard format timestamp.\n"); 74 | printf(" -o --output Output file for CSV data, defaults to stdout. Overrides scenario.\n"); 75 | printf(" -k --kml Output KML file.\n"); 76 | printf(" -d --descending We are in the descent phase of the flight, i.e. after\n"); 77 | printf(" burst or cutdown. burst_alt and ascent_rate ignored.\n"); 78 | printf(" -i --data_dir Input directory for wind data, defaults to current dir.\n\n"); 79 | printf(" -e --wind_error RMS windspeed error (m/s).\n"); 80 | printf(" -a --alarm Use alarm() to kill pred incase it hangs.\n"); 81 | printf("The scenario file is an INI-like file giving the launch scenario. If it is\n"); 82 | printf("omitted, the scenario is read from standard input.\n"); 83 | exit(0); 84 | } 85 | 86 | if (gopt(options, 'z')) { 87 | // Version information 88 | printf("Landing Prediction version: %s\nCopyright (c) CU Spaceflight 2009\n", VERSION); 89 | exit(0); 90 | } 91 | 92 | if (gopt_arg(options, 'a', &argument) && strcmp(argument, "-")) { 93 | alarm_time = strtol(argument, &endptr, 0); 94 | if (endptr == argument) { 95 | fprintf(stderr, "ERROR: %s: invalid alarm length\n", argument); 96 | exit(1); 97 | } 98 | alarm(alarm_time); 99 | } 100 | 101 | verbosity = gopt(options, 'v'); 102 | 103 | if (gopt(options, 'd')) 104 | descent_mode = DESCENT_MODE_DESCENDING; 105 | else 106 | descent_mode = DESCENT_MODE_NORMAL; 107 | 108 | if (gopt_arg(options, 'k', &argument) && strcmp(argument, "-")) { 109 | kml_file = fopen(argument, "wb"); 110 | if (!kml_file) { 111 | fprintf(stderr, "ERROR: %s: could not open KML file for output\n", argument); 112 | exit(1); 113 | } 114 | } 115 | else 116 | kml_file = NULL; 117 | 118 | if (gopt_arg(options, 't', &argument) && strcmp(argument, "-")) { 119 | initial_timestamp = strtol(argument, &endptr, 0); 120 | if (endptr == argument) { 121 | fprintf(stderr, "ERROR: %s: invalid start timestamp\n", argument); 122 | exit(1); 123 | } 124 | } else { 125 | initial_timestamp = time(NULL); 126 | } 127 | 128 | if (!(gopt_arg(options, 'i', &data_dir) && strcmp(data_dir, "-"))) 129 | data_dir = "./"; 130 | 131 | 132 | // populate wind data file cache 133 | file_cache = wind_file_cache_new(data_dir); 134 | 135 | // read in flight parameters 136 | n_scenarios = argc - 1; 137 | if(n_scenarios == 0) { 138 | // we'll parse from std in 139 | n_scenarios = 1; 140 | } 141 | 142 | for(scenario_idx = 0; scenario_idx < n_scenarios; ++scenario_idx) { 143 | char* scenario_output = NULL; 144 | 145 | if(argc > scenario_idx+1) { 146 | scenario = iniparser_load(argv[scenario_idx+1]); 147 | } else { 148 | scenario = iniparser_loadfile(stdin); 149 | } 150 | 151 | if(!scenario) { 152 | fprintf(stderr, "ERROR: could not parse scanario file.\n"); 153 | exit(1); 154 | } 155 | 156 | if(verbosity > 1) { 157 | fprintf(stderr, "INFO: Parsed scenario file:\n"); 158 | iniparser_dump_ini(scenario, stderr); 159 | } 160 | 161 | scenario_output = iniparser_getstring(scenario, "output:filename", NULL); 162 | 163 | if (gopt_arg(options, 'o', &argument) && strcmp(argument, "-")) { 164 | if(verbosity > 0) { 165 | fprintf(stderr, "INFO: Writing output to file specified on command line: %s\n", argument); 166 | } 167 | output = fopen(argument, "wb"); 168 | if (!output) { 169 | fprintf(stderr, "ERROR: %s: could not open CSV file for output\n", argument); 170 | exit(1); 171 | } 172 | } else if (scenario_output != NULL) { 173 | if(verbosity > 0) { 174 | fprintf(stderr, "INFO: Writing output to file specified in scenario: %s\n", scenario_output); 175 | } 176 | output = fopen(scenario_output, "wb"); 177 | if (!output) { 178 | fprintf(stderr, "ERROR: %s: could not open CSV file for output\n", scenario_output); 179 | exit(1); 180 | } 181 | } else { 182 | if(verbosity > 0) { 183 | fprintf(stderr, "INFO: Writing output to stdout.\n"); 184 | } 185 | output = stdout; 186 | } 187 | 188 | // write KML header 189 | if (kml_file) 190 | start_kml(); 191 | 192 | // The observant amongst you will notice that there are default values for 193 | // *all* keys. This information should not be spread around too well. 194 | // Unfortunately, this means we lack some error checking. 195 | 196 | initial_lat = iniparser_getdouble(scenario, "launch-site:latitude", 0.0); 197 | initial_lng = iniparser_getdouble(scenario, "launch-site:longitude", 0.0); 198 | initial_alt = iniparser_getdouble(scenario, "launch-site:altitude", 0.0); 199 | 200 | ascent_rate = iniparser_getdouble(scenario, "altitude-model:ascent-rate", 1.0); 201 | 202 | // The 1.1045 comes from a magic constant buried in 203 | // ~cuspaceflight/public_html/predict/index.php. 204 | // MJ: This is actually the square root of a rounded sea-level air density figure (1.22) 205 | // A more precise definition is sqrt(1.225) = 1.10679 206 | drag_coeff = iniparser_getdouble(scenario, "altitude-model:descent-rate", 1.0) * 1.10679; 207 | 208 | burst_alt = iniparser_getdouble(scenario, "altitude-model:burst-altitude", 1.0); 209 | 210 | rmswinderror = iniparser_getdouble(scenario, "atmosphere:wind-error", 0.0); 211 | if(gopt_arg(options, 'e', &argument) && strcmp(argument, "-")) { 212 | rmswinderror = strtod(argument, &endptr); 213 | if (endptr == argument) { 214 | fprintf(stderr, "ERROR: %s: invalid RMS wind speed error\n", argument); 215 | exit(1); 216 | } 217 | } 218 | 219 | { 220 | int year, month, day, hour, minute, second; 221 | year = iniparser_getint(scenario, "launch-time:year", -1); 222 | month = iniparser_getint(scenario, "launch-time:month", -1); 223 | day = iniparser_getint(scenario, "launch-time:day", -1); 224 | hour = iniparser_getint(scenario, "launch-time:hour", -1); 225 | minute = iniparser_getint(scenario, "launch-time:minute", -1); 226 | second = iniparser_getint(scenario, "launch-time:second", -1); 227 | 228 | if((year >= 0) && (month >= 0) && (day >= 0) && (hour >= 0) 229 | && (minute >= 0) && (second >= 0)) 230 | { 231 | struct tm timeval = { 0 }; 232 | time_t scenario_launch_time = -1; 233 | 234 | if(verbosity > 0) { 235 | fprintf(stderr, "INFO: Using launch time from scenario: " 236 | "%i/%i/%i %i:%i:%i\n", 237 | year, month, day, hour, minute, second); 238 | } 239 | 240 | timeval.tm_sec = second; 241 | timeval.tm_min = minute; 242 | timeval.tm_hour = hour; 243 | timeval.tm_mday = day; /* 1 - 31 */ 244 | timeval.tm_mon = month - 1; /* 0 - 11 */ 245 | timeval.tm_year = year - 1900; /* fuck you Millenium Bug! */ 246 | 247 | #ifndef _BSD_SOURCE 248 | # warning This version of mktime does not allow explicit setting of timezone. 249 | #else 250 | timeval.tm_zone = "UTC"; 251 | #endif 252 | 253 | scenario_launch_time = mktime(&timeval); 254 | if(scenario_launch_time <= 0) { 255 | fprintf(stderr, "ERROR: Launch time in scenario is invalid\n"); 256 | exit(1); 257 | } else { 258 | initial_timestamp = scenario_launch_time; 259 | } 260 | } 261 | } 262 | 263 | if(verbosity > 0) { 264 | fprintf(stderr, "INFO: Scenario loaded:\n"); 265 | fprintf(stderr, " - Initial latitude : %lf deg N\n", initial_lat); 266 | fprintf(stderr, " - Initial longitude : %lf deg E\n", initial_lng); 267 | fprintf(stderr, " - Initial altitude : %lf m above sea level\n", initial_alt); 268 | fprintf(stderr, " - Initial timestamp : %li\n", initial_timestamp); 269 | fprintf(stderr, " - Drag coeff. : %lf\n", drag_coeff); 270 | if(!descent_mode) { 271 | fprintf(stderr, " - Ascent rate : %lf m/s\n", ascent_rate); 272 | fprintf(stderr, " - Burst alt. : %lf m\n", burst_alt); 273 | } 274 | fprintf(stderr, " - Windspeed err. : %f m/s\n", rmswinderror); 275 | } 276 | 277 | { 278 | // do the actual stuff!! 279 | altitude_model_t* alt_model = altitude_model_new(descent_mode, burst_alt, 280 | ascent_rate, drag_coeff); 281 | if(!alt_model) { 282 | fprintf(stderr, "ERROR: error initialising altitude profile\n"); 283 | exit(1); 284 | } 285 | 286 | if (!run_model(file_cache, alt_model, 287 | initial_lat, initial_lng, initial_alt, initial_timestamp, 288 | rmswinderror)) { 289 | fprintf(stderr, "ERROR: error during model run!\n"); 290 | exit(1); 291 | } 292 | 293 | altitude_model_free(alt_model); 294 | } 295 | 296 | // release the scenario 297 | iniparser_freedict(scenario); 298 | 299 | // write footer to KML and close output files 300 | if (kml_file) { 301 | finish_kml(); 302 | fclose(kml_file); 303 | } 304 | 305 | if (output != stdout) { 306 | fclose(output); 307 | } 308 | } 309 | 310 | // release gopt data, 311 | gopt_free(options); 312 | 313 | // release the file cache resources. 314 | wind_file_cache_free(file_cache); 315 | 316 | return 0; 317 | } 318 | 319 | void write_position(float lat, float lng, float alt, int timestamp) { 320 | // the predictor uses 0<=lng<360; most other things expect -180 180) 322 | lng -= 360; 323 | 324 | if (kml_file) { 325 | fprintf(kml_file, "%g,%g,%g\n", lng, lat, alt); 326 | if (ferror(kml_file)) { 327 | fprintf(stderr, "ERROR: error writing to KML file\n"); 328 | exit(1); 329 | } 330 | } 331 | 332 | fprintf(output, "%d,%g,%g,%g\n", timestamp, lat, lng, alt); 333 | if (ferror(output)) { 334 | fprintf(stderr, "ERROR: error writing to CSV file\n"); 335 | exit(1); 336 | } 337 | } 338 | 339 | void start_kml() { 340 | FILE* kml_header; 341 | char c; 342 | 343 | kml_header = fopen("kml_header", "r"); 344 | 345 | while (!feof(kml_header)) { 346 | c = fgetc(kml_header); 347 | if (ferror(kml_header)) { 348 | fprintf(stderr, "ERROR: error reading KML header file\n"); 349 | exit(1); 350 | } 351 | if (!feof(kml_header)) fputc(c, kml_file); 352 | if (ferror(kml_file)) { 353 | fprintf(stderr, "ERROR: error writing header to KML file\n"); 354 | exit(1); 355 | } 356 | } 357 | 358 | fclose(kml_header); 359 | } 360 | 361 | void finish_kml() { 362 | FILE* kml_footer; 363 | char c; 364 | 365 | kml_footer = fopen("kml_footer", "r"); 366 | 367 | while (!feof(kml_footer)) { 368 | c = fgetc(kml_footer); 369 | if (ferror(kml_footer)) { 370 | fprintf(stderr, "ERROR: error reading KML footer file\n"); 371 | exit(1); 372 | } 373 | if (!feof(kml_footer)) fputc(c, kml_file); 374 | if (ferror(kml_file)) { 375 | fprintf(stderr, "ERROR: error writing footer to KML file\n"); 376 | exit(1); 377 | } 378 | } 379 | 380 | fclose(kml_footer); 381 | } 382 | 383 | -------------------------------------------------------------------------------- /src/pred.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rob Anderson 6 | // Modified by Fergus Noble 7 | // 8 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 11 | // PARTICULAR PURPOSE. 12 | // -------------------------------------------------------------- 13 | 14 | #ifndef __PRED_H__ 15 | #define __PRED_H__ 16 | 17 | #define VERSION "0.0.1" 18 | 19 | // write a position entry into the output files 20 | void write_position(float lat, float lng, float alt, int timestamp); 21 | 22 | // start and finish KML files, basically just write header and footer in 23 | void start_kml(); 24 | void finish_kml(); 25 | 26 | #endif // __PRED_H__ 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/run_model.c: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rob Anderson 6 | // Modified by Fergus Noble 7 | // 8 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 11 | // PARTICULAR PURPOSE. 12 | // -------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "wind/wind_file.h" 20 | #include "util/random.h" 21 | #include "run_model.h" 22 | #include "pred.h" 23 | #include "altitude.h" 24 | 25 | extern int verbosity; 26 | 27 | #define RADIUS_OF_EARTH 6371009.f 28 | 29 | typedef struct model_state_s model_state_t; 30 | struct model_state_s 31 | { 32 | float lat; 33 | float lng; 34 | float alt; 35 | altitude_model_t *alt_model; 36 | double loglik; 37 | }; 38 | 39 | // Get the distance (in metres) of one degree of latitude and one degree of 40 | // longitude. This varys with height (not much grant you). 41 | static void 42 | _get_frame(float lat, float lng, float alt, 43 | float *d_dlat, float *d_dlng) 44 | { 45 | float theta, r; 46 | 47 | theta = 2.f * M_PI * (90.f - lat) / 360.f; 48 | r = RADIUS_OF_EARTH + alt; 49 | 50 | // See the differentiation section of 51 | // http://en.wikipedia.org/wiki/Spherical_coordinate_system 52 | 53 | // d/dv = d/dlat = -d/dtheta 54 | *d_dlat = (2.f * M_PI) * r / 360.f; 55 | 56 | // d/du = d/dlong = d/dphi 57 | *d_dlng = (2.f * M_PI) * r * sinf(theta) / 360.f; 58 | } 59 | 60 | static int 61 | _advance_one_timestep(wind_file_cache_t* cache, 62 | unsigned long delta_t, 63 | unsigned long timestamp, unsigned long initial_timestamp, 64 | unsigned int n_states, model_state_t* states, 65 | float rmserror) 66 | { 67 | unsigned int i; 68 | 69 | for(i=0; ialt_model, 77 | timestamp - initial_timestamp, &state->alt)) 78 | return 0; // alt < 0; finished 79 | 80 | if(!get_wind(cache, state->lat, state->lng, state->alt, timestamp, 81 | &wind_v, &wind_u, &wind_var)) 82 | return -1; // error 83 | 84 | _get_frame(state->lat, state->lng, state->alt, &ddlat, &ddlng); 85 | 86 | // NOTE: it this really the right thing to be doing? - think about what 87 | // happens near the poles 88 | 89 | wind_var += rmserror * rmserror; 90 | 91 | assert(wind_var >= 0.f); 92 | 93 | //fprintf(stderr, "U: %f +/- %f, V: %f +/- %f\n", 94 | // wind_u, sqrtf(wind_u_var), 95 | // wind_v, sqrtf(wind_v_var)); 96 | 97 | u_samp = random_sample_normal(wind_u, wind_var, &u_lik); 98 | v_samp = random_sample_normal(wind_v, wind_var, &v_lik); 99 | 100 | //u_samp = wind_u; 101 | //v_samp = wind_v; 102 | 103 | state->lat += v_samp * delta_t / ddlat; 104 | state->lng += u_samp * delta_t / ddlng; 105 | 106 | state->loglik += (double)(u_lik + v_lik); 107 | } 108 | 109 | return 1; // OK, and continue 110 | } 111 | 112 | static int _state_compare_rev(const void* a, const void *b) 113 | { 114 | model_state_t* sa = (model_state_t*)a; 115 | model_state_t* sb = (model_state_t*)b; 116 | 117 | // this returns a value s.t. the states will be sorted so that 118 | // the maximum likelihood state is at position 0. 119 | return sb->loglik - sa->loglik; 120 | } 121 | 122 | int run_model(wind_file_cache_t* cache, altitude_model_t* alt_model, 123 | float initial_lat, float initial_lng, float initial_alt, 124 | long int initial_timestamp, float rmswinderror) 125 | { 126 | model_state_t* states; 127 | const unsigned int n_states = 1; 128 | unsigned int i; 129 | 130 | states = (model_state_t*) malloc( sizeof(model_state_t) * n_states ); 131 | 132 | for(i=0; ialt = initial_alt; 137 | state->lat = initial_lat; 138 | state->lng = initial_lng; 139 | state->alt_model = alt_model; 140 | state->loglik = 0.f; 141 | } 142 | 143 | long int timestamp = initial_timestamp; 144 | 145 | int log_counter = 0; // only write position to output files every LOG_DECIMATE timesteps 146 | int r, return_code = 1; 147 | 148 | while(1) 149 | { 150 | r = _advance_one_timestep(cache, TIMESTEP, timestamp, initial_timestamp, 151 | n_states, states, rmswinderror); 152 | if (r == -1) // error getting wind. Save prediction, but emit error messages 153 | return_code = 0; 154 | 155 | if (r != 1) // 1 = continue 156 | break; 157 | 158 | // Sort the array of models in order of log likelihood. 159 | qsort(states, n_states, sizeof(model_state_t), _state_compare_rev); 160 | 161 | // write the maximum likelihood state out. 162 | if (log_counter == LOG_DECIMATE) { 163 | write_position(states[0].lat, states[0].lng, states[0].alt, timestamp); 164 | log_counter = 0; 165 | } 166 | 167 | log_counter++; 168 | timestamp += TIMESTEP; 169 | } 170 | 171 | for(i=0; ilat, state->lng, state->alt, timestamp); 175 | } 176 | 177 | //fprintf(stderr, "INFO: Final maximum log lik: %f (=%f)\n", 178 | // states[0].loglik, exp(states[0].loglik)); 179 | 180 | free(states); 181 | 182 | return return_code; 183 | } 184 | 185 | int get_wind(wind_file_cache_t* cache, float lat, float lng, float alt, long int timestamp, 186 | float* wind_v, float* wind_u, float *wind_var) { 187 | int i, s; 188 | float lambda, wu_l, wv_l, wu_h, wv_h; 189 | float wuvar_l, wvvar_l, wuvar_h, wvvar_h; 190 | wind_file_cache_entry_t* found_entries[] = { NULL, NULL }; 191 | wind_file_t* found_files[] = { NULL, NULL }; 192 | unsigned int earlier_ts, later_ts; 193 | 194 | // look for a wind file which matches this latitude and longitude... 195 | wind_file_cache_find_entry(cache, lat, lng, timestamp, 196 | &(found_entries[0]), &(found_entries[1])); 197 | 198 | if(!found_entries[0] || !found_entries[1]) { 199 | fprintf(stderr, "ERROR: Do not have wind data for this (lat, lon, alt, time) (%.4f,%.4f,%.1f,%d).\n",lat,lng,alt,timestamp); 200 | return 0; 201 | } 202 | 203 | if(!wind_file_cache_entry_contains_point(found_entries[0], lat, lng) || 204 | !wind_file_cache_entry_contains_point(found_entries[1], lat, lng)) 205 | { 206 | fprintf(stderr, "ERROR: Could not locate appropriate wind data tile for location " 207 | "lat=%f, lon=%f.\n", lat, lng); 208 | return 0; 209 | } 210 | 211 | // Look in the cache for the files we need. 212 | for(i=0; i<2; ++i) 213 | { 214 | found_files[i] = wind_file_cache_entry_file(found_entries[i]); 215 | } 216 | 217 | earlier_ts = wind_file_cache_entry_timestamp(found_entries[0]); 218 | later_ts = wind_file_cache_entry_timestamp(found_entries[1]); 219 | 220 | if(earlier_ts > timestamp || later_ts < timestamp) 221 | { 222 | fprintf(stderr, "Error: found_entries have bad times.\n"); 223 | return 0; 224 | } 225 | 226 | if(earlier_ts != later_ts) 227 | lambda = ((float)timestamp - (float)earlier_ts) / 228 | ((float)later_ts - (float)earlier_ts); 229 | else 230 | lambda = 0.5f; 231 | 232 | s = wind_file_get_wind(found_files[0], lat, lng, alt, &wu_l, &wv_l, &wuvar_l, &wvvar_l); 233 | if (s == 0) return 0; // hard error 234 | s = wind_file_get_wind(found_files[1], lat, lng, alt, &wu_h, &wv_h, &wuvar_h, &wvvar_h); 235 | if (s == 0) return 0; 236 | 237 | *wind_u = lambda * wu_h + (1.f-lambda) * wu_l; 238 | *wind_v = lambda * wv_h + (1.f-lambda) * wv_l; 239 | 240 | // flatten the u and v variances into a single mean variance for the 241 | // magnitude. 242 | *wind_var = 0.5f * (wuvar_h + wuvar_l + wvvar_h + wvvar_l); 243 | 244 | return 1; 245 | } 246 | 247 | // vim:sw=4:ts=4:et:cindent 248 | -------------------------------------------------------------------------------- /src/run_model.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rob Anderson 6 | // Modified by Fergus Noble 7 | // 8 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 9 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 10 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 11 | // PARTICULAR PURPOSE. 12 | // -------------------------------------------------------------- 13 | 14 | #ifndef __RUN_MODEL_H__ 15 | #define __RUN_MODEL_H__ 16 | 17 | #include "wind/wind_file_cache.h" 18 | #include "altitude.h" 19 | 20 | // run the model 21 | int run_model(wind_file_cache_t* cache, altitude_model_t* alt_model, 22 | float initial_lat, float initial_lng, float initial_alt, 23 | long int initial_timestamp, float rmswinderror); 24 | 25 | #define TIMESTEP 1 // in seconds 26 | #define LOG_DECIMATE 50 // write entry to output files every x timesteps 27 | 28 | #define METRES_TO_DEGREES 0.00000899289281755 // one metre corresponds to this many degrees latitude 29 | #define DEGREES_TO_METRES 111198.92345 // one degree latitude corresponds to this many metres 30 | #define DEGREES_TO_RADIANS 0.0174532925 // 1 degree is this many radians 31 | 32 | // get the wind values in the u and v directions at a point in space and time from the dataset data 33 | // we interpolate lat, lng, alt and time. The GRIB data only contains pressure levels so we first 34 | // determine which pressure levels straddle to our desired altitude and then interpolate between them 35 | int get_wind(wind_file_cache_t* cache, float lat, float lng, float alt, long int timestamp, float* wind_v, float* wind_u, float *wind_var); 36 | // note: get_wind will likely call load_data and load a different tile into data, so just be careful that data could be pointing 37 | // somewhere else after running get_wind 38 | 39 | #endif // __RUN_MODEL_H__ 40 | 41 | -------------------------------------------------------------------------------- /src/util/getdelim.c: -------------------------------------------------------------------------------- 1 | /* getdelim.c --- Implementation of replacement getdelim function. 2 | Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005 Free 3 | Software Foundation, Inc. 4 | 5 | This program is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License as 7 | published by the Free Software Foundation; either version 2, or (at 8 | your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, but 11 | WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | 02110-1301, USA. */ 19 | 20 | /* Ported from glibc by Simon Josefsson. */ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #include "getdelim.h" 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #ifndef SIZE_MAX 33 | # define SIZE_MAX ((size_t) -1) 34 | #endif 35 | #ifndef SSIZE_MAX 36 | # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) 37 | #endif 38 | #if !HAVE_FLOCKFILE 39 | # undef flockfile 40 | # define flockfile(x) ((void) 0) 41 | #endif 42 | #if !HAVE_FUNLOCKFILE 43 | # undef funlockfile 44 | # define funlockfile(x) ((void) 0) 45 | #endif 46 | 47 | /* Read up to (and including) a DELIMITER from FP into *LINEPTR (and 48 | NUL-terminate it). *LINEPTR is a pointer returned from malloc (or 49 | NULL), pointing to *N characters of space. It is realloc'ed as 50 | necessary. Returns the number of characters read (not including 51 | the null terminator), or -1 on error or EOF. */ 52 | 53 | ssize_t 54 | getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp) 55 | { 56 | ssize_t result; 57 | size_t cur_len = 0; 58 | 59 | if (lineptr == NULL || n == NULL || fp == NULL) 60 | { 61 | errno = EINVAL; 62 | return -1; 63 | } 64 | 65 | flockfile (fp); 66 | 67 | if (*lineptr == NULL || *n == 0) 68 | { 69 | *n = 120; 70 | *lineptr = (char *) malloc (*n); 71 | if (*lineptr == NULL) 72 | { 73 | result = -1; 74 | goto unlock_return; 75 | } 76 | } 77 | 78 | for (;;) 79 | { 80 | int i; 81 | 82 | i = getc (fp); 83 | if (i == EOF) 84 | { 85 | result = -1; 86 | break; 87 | } 88 | 89 | /* Make enough space for len+1 (for final NUL) bytes. */ 90 | if (cur_len + 1 >= *n) 91 | { 92 | size_t needed_max = 93 | SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX; 94 | size_t needed = 2 * *n + 1; /* Be generous. */ 95 | char *new_lineptr; 96 | 97 | if (needed_max < needed) 98 | needed = needed_max; 99 | if (cur_len + 1 >= needed) 100 | { 101 | result = -1; 102 | goto unlock_return; 103 | } 104 | 105 | new_lineptr = (char *) realloc (*lineptr, needed); 106 | if (new_lineptr == NULL) 107 | { 108 | result = -1; 109 | goto unlock_return; 110 | } 111 | 112 | *lineptr = new_lineptr; 113 | *n = needed; 114 | } 115 | 116 | (*lineptr)[cur_len] = i; 117 | cur_len++; 118 | 119 | if (i == delimiter) 120 | break; 121 | } 122 | (*lineptr)[cur_len] = '\0'; 123 | result = cur_len ? cur_len : result; 124 | 125 | unlock_return: 126 | funlockfile (fp); 127 | return result; 128 | } 129 | -------------------------------------------------------------------------------- /src/util/getdelim.h: -------------------------------------------------------------------------------- 1 | /* getdelim.h --- Prototype for replacement getdelim function. 2 | Copyright (C) 2005 Free Software Foundation, Inc. 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as 6 | published by the Free Software Foundation; either version 2, or (at 7 | your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | 02110-1301, USA. */ 18 | 19 | /* Written by Simon Josefsson. */ 20 | 21 | /* Get size_t, FILE, ssize_t. And getdelim, if available. */ 22 | # include 23 | # include 24 | # include 25 | 26 | #if !HAVE_DECL_GETDELIM 27 | ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream); 28 | #endif /* !HAVE_GETDELIM */ 29 | -------------------------------------------------------------------------------- /src/util/getline.c: -------------------------------------------------------------------------------- 1 | /* getline.c --- Implementation of replacement getline function. 2 | Copyright (C) 2005 Free Software Foundation, Inc. 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as 6 | published by the Free Software Foundation; either version 2, or (at 7 | your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | 02110-1301, USA. */ 18 | 19 | /* Written by Simon Josefsson. */ 20 | 21 | #ifdef HAVE_CONFIG_H 22 | # include 23 | #endif 24 | 25 | #include "getdelim.h" 26 | #include "getline.h" 27 | 28 | ssize_t 29 | getline (char **lineptr, size_t *n, FILE *stream) 30 | { 31 | return getdelim (lineptr, n, '\n', stream); 32 | } 33 | -------------------------------------------------------------------------------- /src/util/getline.h: -------------------------------------------------------------------------------- 1 | /* getline.h --- Prototype for replacement getline function. 2 | Copyright (C) 2005 Free Software Foundation, Inc. 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License as 6 | published by the Free Software Foundation; either version 2, or (at 7 | your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | 02110-1301, USA. */ 18 | 19 | /* Written by Simon Josefsson. */ 20 | 21 | /* Get size_t, FILE, ssize_t. And getline, if available. */ 22 | # include 23 | # include 24 | # include 25 | 26 | #if !HAVE_DECL_GETLINE 27 | ssize_t getline (char **lineptr, size_t *n, FILE *stream); 28 | #endif /* !HAVE_GETLINE */ 29 | -------------------------------------------------------------------------------- /src/util/gopt.c: -------------------------------------------------------------------------------- 1 | /* gopt.c version 8.1: tom.viza@gmail.com PUBLIC DOMAIN 2003-8 */ 2 | /* 3 | I, Tom Vajzovic, am the author of this software and its documentation and 4 | permanently abandon all copyright and other intellectual property rights in 5 | them, including the right to be identified as the author. 6 | 7 | I am fairly certain that this software does what the documentation says it 8 | does, but I cannot guarantee that it does, or that it does what you think it 9 | should, and I cannot guarantee that it will not have undesirable side effects. 10 | 11 | You are free to use, modify and distribute this software as you please, but 12 | you do so at your own risk. If you remove or hide this warning then you are 13 | responsible for any problems encountered by people that you make the software 14 | available to. 15 | 16 | Before modifying or distributing this software I ask that you would please 17 | read http://www.purposeful.co.uk/tfl/ 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "gopt.h" 24 | 25 | #ifdef USE_SYSEXITS 26 | #include 27 | #else 28 | #define EX_OSERR EXIT_FAILURE 29 | #define EX_USAGE EXIT_FAILURE 30 | #endif 31 | 32 | struct opt_spec_s { 33 | int key; 34 | int flags; 35 | const char *shorts; 36 | const char* const *longs; 37 | }; 38 | typedef struct opt_spec_s opt_spec_t; 39 | 40 | struct opt_s { 41 | int key; 42 | const char *arg; 43 | }; 44 | typedef struct opt_s opt_t; 45 | 46 | void *gopt_sort( int *argc, const char **argv, const void *opt_specs ){ 47 | void *opts; 48 | {{{ 49 | const char* const *arg_p= argv + 1; 50 | size_t opt_count= 1; 51 | for( ; *arg_p; ++arg_p ) { 52 | if( '-' == (*arg_p)[0] && (*arg_p)[1] ) { 53 | if( '-' == (*arg_p)[1] ) { 54 | if( (*arg_p)[2] ) { 55 | ++opt_count; 56 | } else { 57 | break; 58 | } 59 | } else { 60 | const opt_spec_t *opt_spec_p= opt_specs; 61 | for( ; opt_spec_p-> key; ++opt_spec_p ) 62 | if( strchr( opt_spec_p-> shorts, (*arg_p)[1] )){ 63 | opt_count+= opt_spec_p-> flags & GOPT_ARG ? 1 : strlen( (*arg_p) + 1 ); 64 | break; 65 | } 66 | } 67 | } 68 | } 69 | opts= malloc( opt_count * sizeof(opt_t) ); 70 | }}} 71 | { 72 | const char **arg_p= argv + 1; 73 | const char **next_operand= arg_p; 74 | opt_t *next_option= opts; 75 | 76 | if( ! opts ){ 77 | perror( argv[0] ); 78 | exit( EX_OSERR ); 79 | } 80 | for( ; *arg_p; ++arg_p ) 81 | if( '-' == (*arg_p)[0] && (*arg_p)[1] ) 82 | if( '-' == (*arg_p)[1] ) 83 | if( (*arg_p)[2] ) 84 | {{{ 85 | const opt_spec_t *opt_spec_p= opt_specs; 86 | const char* const *longs= opt_spec_p-> longs; 87 | next_option-> key= 0; 88 | while( *longs ){ 89 | const char *option_cp= (*arg_p) + 2; 90 | const char *name_cp= *longs; 91 | while( *option_cp && *option_cp == *name_cp ){ 92 | ++option_cp; 93 | ++name_cp; 94 | } 95 | if( '=' == *option_cp || !*option_cp ){ 96 | if( *name_cp ){ 97 | if( next_option-> key ){ 98 | fprintf( stderr, "%s: --%.*s: abbreviated option is ambiguous\n", argv[0], (int)( option_cp -( (*arg_p) + 2 )), (*arg_p) + 2 ); 99 | free( opts ); 100 | exit( EX_USAGE ); 101 | } 102 | next_option-> key= opt_spec_p-> key; 103 | } 104 | else { 105 | next_option-> key= opt_spec_p-> key; 106 | goto found_long; 107 | } 108 | } 109 | if( !*++longs ){ 110 | ++opt_spec_p; 111 | if( opt_spec_p-> key ) 112 | longs= opt_spec_p-> longs; 113 | } 114 | } 115 | if( ! next_option-> key ){ 116 | fprintf( stderr, "%s: --%.*s: unknown option\n", argv[0], (int)strcspn( (*arg_p) + 2, "=" ), (*arg_p) + 2 ); 117 | free( opts ); 118 | exit( EX_USAGE ); 119 | } 120 | for( opt_spec_p= opt_specs; opt_spec_p-> key != next_option-> key; ++opt_spec_p ); 121 | found_long: 122 | 123 | if( !( opt_spec_p-> flags & GOPT_REPEAT )){ 124 | const opt_t *opt_p= opts; 125 | for( ; opt_p != next_option; ++opt_p ) 126 | if( opt_p-> key == opt_spec_p-> key ){ 127 | fprintf( stderr, "%s: --%.*s: option may not be repeated (in any long or short form)\n", argv[0], (int)strcspn( (*arg_p) + 2, "=" ), (*arg_p) + 2 ); 128 | free( opts ); 129 | exit( EX_USAGE ); 130 | } 131 | } 132 | if( opt_spec_p-> flags & GOPT_ARG ){ 133 | next_option-> arg= strchr( (*arg_p) + 2, '=' ) + 1; 134 | if( (char*)0 + 1 == next_option-> arg ){ 135 | ++arg_p; 136 | if( !*arg_p || ('-' == (*arg_p)[0] && (*arg_p)[1]) ){ 137 | fprintf( stderr, "%s: --%s: option requires an option argument\n", argv[0], (*(arg_p-1)) + 2 ); 138 | free( opts ); 139 | exit( EX_USAGE ); 140 | } 141 | next_option-> arg= *arg_p; 142 | } 143 | } 144 | else { 145 | if( strchr( (*arg_p) + 2, '=' )){ 146 | fprintf( stderr, "%s: --%.*s: option may not take an option argument\n", argv[0], (int)strcspn( (*arg_p) + 2, "=" ), (*arg_p) + 2 ); 147 | free( opts ); 148 | exit( EX_USAGE ); 149 | } 150 | next_option-> arg= NULL; 151 | } 152 | ++next_option; 153 | }}} 154 | else { 155 | for( ++arg_p; *arg_p; ++arg_p ) 156 | *next_operand++= *arg_p; 157 | break; 158 | } 159 | else 160 | {{{ 161 | const char *short_opt= (*arg_p) + 1; 162 | for( ;*short_opt; ++short_opt ){ 163 | const opt_spec_t *opt_spec_p= opt_specs; 164 | 165 | for( ; opt_spec_p-> key; ++opt_spec_p ) 166 | if( strchr( opt_spec_p-> shorts, *short_opt )){ 167 | if( !( opt_spec_p-> flags & GOPT_REPEAT )){ 168 | const opt_t *opt_p= opts; 169 | for( ; opt_p != next_option; ++opt_p ) 170 | if( opt_p-> key == opt_spec_p-> key ){ 171 | fprintf( stderr, "%s: -%c: option may not be repeated (in any long or short form)\n", argv[0], *short_opt ); 172 | free( opts ); 173 | exit( EX_USAGE ); 174 | } 175 | } 176 | next_option-> key= opt_spec_p-> key; 177 | 178 | if( opt_spec_p-> flags & GOPT_ARG ){ 179 | if( short_opt[1] ) 180 | next_option-> arg= short_opt + 1; 181 | 182 | else { 183 | ++arg_p; 184 | if( !*arg_p || ('-' == (*arg_p)[0] && (*arg_p)[1]) ){ 185 | fprintf( stderr, "%s: -%c: option requires an option argument\n", argv[0], *short_opt ); 186 | free( opts ); 187 | exit( EX_USAGE ); 188 | } 189 | next_option-> arg= *arg_p; 190 | } 191 | ++next_option; 192 | goto break_2; 193 | } 194 | next_option-> arg= NULL; 195 | ++next_option; 196 | goto continue_2; 197 | } 198 | fprintf( stderr, "%s: -%c: unknown option\n", argv[0], *short_opt ); 199 | free( opts ); 200 | exit( EX_USAGE ); 201 | continue_2: ; 202 | } 203 | break_2: ; 204 | }}} 205 | else 206 | *next_operand++= *arg_p; 207 | 208 | next_option-> key= 0; 209 | *next_operand= NULL; 210 | *argc= next_operand - argv; 211 | } 212 | return opts; 213 | } 214 | 215 | size_t gopt( const void *vptr_opts, int key ){ 216 | const opt_t *opts= vptr_opts; 217 | size_t count= 0; 218 | for( ; opts-> key; ++opts ) 219 | count+= opts-> key == key; 220 | 221 | return count; 222 | } 223 | 224 | size_t gopt_arg( const void *vptr_opts, int key, const char **arg ){ 225 | const opt_t *opts= vptr_opts; 226 | size_t count= 0; 227 | 228 | for( ; opts-> key; ++opts ) 229 | if( opts-> key == key ){ 230 | if( ! count ) 231 | *arg= opts-> arg; 232 | ++count; 233 | } 234 | return count; 235 | } 236 | 237 | const char *gopt_arg_i( const void *vptr_opts, int key, size_t i ){ 238 | const opt_t *opts= vptr_opts; 239 | 240 | for( ; opts-> key; ++opts ) 241 | if( opts-> key == key ){ 242 | if( ! i ) 243 | return opts-> arg; 244 | --i; 245 | } 246 | return NULL; 247 | } 248 | 249 | size_t gopt_args( const void *vptr_opts, int key, const char **args, size_t args_len ){ 250 | const char **args_stop= args + args_len; 251 | const char **args_ptr= args; 252 | const opt_t *opts= vptr_opts; 253 | 254 | for( ; opts-> key; ++opts ) 255 | if( opts-> key == key ){ 256 | if( args_stop == args_ptr ) 257 | return args_len + gopt( opts, key ); 258 | 259 | *args_ptr++= opts-> arg; 260 | } 261 | if( args_stop != args_ptr ) 262 | *args_ptr= NULL; 263 | return args_ptr - args; 264 | } 265 | 266 | void gopt_free( void *vptr_opts ){ 267 | free( vptr_opts ); 268 | } 269 | -------------------------------------------------------------------------------- /src/util/gopt.h: -------------------------------------------------------------------------------- 1 | /* gopt.h version 8.1: tom.viza@gmail.com PUBLIC DOMAIN 2003-8 */ 2 | /* 3 | I, Tom Vajzovic, am the author of this software and its documentation and 4 | permanently abandon all copyright and other intellectual property rights in 5 | them, including the right to be identified as the author. 6 | 7 | I am fairly certain that this software does what the documentation says it 8 | does, but I cannot guarantee that it does, or that it does what you think it 9 | should, and I cannot guarantee that it will not have undesirable side effects. 10 | 11 | You are free to use, modify and distribute this software as you please, but 12 | you do so at your own risk. If you remove or hide this warning then you are 13 | responsible for any problems encountered by people that you make the software 14 | available to. 15 | 16 | Before modifying or distributing this software I ask that you would please 17 | read http://www.purposeful.co.uk/tfl/ 18 | */ 19 | 20 | #ifndef GOPT_H_INCLUDED 21 | #define GOPT_H_INCLUDED 22 | 23 | #define GOPT_ONCE 0 24 | #define GOPT_REPEAT 1 25 | #define GOPT_NOARG 0 26 | #define GOPT_ARG 2 27 | 28 | typedef struct { int k; int f; const char *s; const char*const*l; } kfsl; 29 | #define gopt_start(...) (const void*)( const kfsl[]){ __VA_ARGS__, {0}} 30 | #define gopt_option(k,f,s,l) { k, f, s, l } 31 | #define gopt_shorts( ... ) (const char*)(const char[]){ __VA_ARGS__, 0 } 32 | #define gopt_longs( ... ) (const char**)(const char*[]){ __VA_ARGS__, NULL } 33 | 34 | 35 | void *gopt_sort( int *argc, const char **argv, const void *opt_specs ); 36 | /* returns a pointer for use in the following calls 37 | * prints to stderr and call exit() on error 38 | */ 39 | size_t gopt( const void *opts, int key ); 40 | /* returns the number of times the option was specified 41 | * which will be 0 or 1 unless GOPT_REPEAT was used 42 | */ 43 | size_t gopt_arg( const void *opts, int key, const char **arg ); 44 | /* returns the number of times the option was specified 45 | * writes a pointer to the option argument from the first (or only) occurance to *arg 46 | */ 47 | const char *gopt_arg_i( const void *opts, int key, size_t i ); 48 | /* returns a pointer to the ith (starting at zero) occurance 49 | * of the option, or NULL if it was not specified that many times 50 | */ 51 | size_t gopt_args( const void *opts, int key, const char **args, size_t args_len ); 52 | /* returns the number of times the option was specified 53 | * writes pointers to the option arguments in the order of occurance to args[]. 54 | * writes at most args_len pointers 55 | * if the return value is less than args_len, also writes a null pointer 56 | */ 57 | void gopt_free( void *opts ); 58 | /* releases memory allocated in the corresponding call to gopt_sort() 59 | * opts can no longer be used 60 | */ 61 | #endif /* GOPT_H_INCLUDED */ 62 | -------------------------------------------------------------------------------- /src/util/random.c: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rich Wareham 6 | // 7 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 8 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 9 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 10 | // PARTICULAR PURPOSE. 11 | // -------------------------------------------------------------- 12 | 13 | #include "random.h" 14 | 15 | #include 16 | #include 17 | 18 | // Sample from a normal distribution with zero mean and unit variance. 19 | // See http://en.wikipedia.org/wiki/Normal_distribution 20 | // #Generating_values_for_normal_random_variables 21 | static float _random_sample_normal_intl(float* loglik) 22 | { 23 | double u, v = 0.0; 24 | static const double k = 0.918938533204673; // = 0.5 * (log(2) + log(pi)), see below. 25 | 26 | u = g_random_double(); 27 | v = g_random_double(); 28 | v = sqrt(-2.0 * log(u)) * cos(2.0 * G_PI * v); 29 | 30 | // actual likelihood is 1/sqrt(2*pi) exp(-(x^2)) since mu = 0 and sigma^2 = 1. 31 | // log-likelihood is therefore: 32 | // \ell(x) = - (x^2) - 0.5 * (log(2) + log(pi)) = - (x^2) - k 33 | 34 | if(loglik) 35 | *loglik = (float) ( - (v*v) - k ); 36 | 37 | return v; 38 | } 39 | 40 | float random_sample_normal(float mu, float sigma2, float *loglik) 41 | { 42 | // Sample from our base case. 43 | float v = _random_sample_normal_intl(loglik); 44 | 45 | // Transform into appropriate range. 46 | v *= sqrt(sigma2); 47 | v += mu; 48 | 49 | return v; 50 | } 51 | 52 | // vim:sw=4:ts=4:et:cindent 53 | -------------------------------------------------------------------------------- /src/util/random.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rich Wareham 6 | // 7 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 8 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 9 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 10 | // PARTICULAR PURPOSE. 11 | // -------------------------------------------------------------- 12 | 13 | #ifndef __RANDOM_H__ 14 | #define __RANDOM_H__ 15 | 16 | // Return a random sample drawn from the normal distribution with mean mu and 17 | // variance sigma2. If loglik is non-NULL, *loglik is set to the log-likelihood 18 | // of drawing that sample. 19 | float random_sample_normal(float mu, float sigma2, float *loglik); 20 | 21 | #endif /* __RANDOM_H__ */ 22 | 23 | // vim:sw=4:ts=4:et:cindent 24 | -------------------------------------------------------------------------------- /src/wind/wind_file.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rich Wareham 6 | // 7 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 8 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 9 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 10 | // PARTICULAR PURPOSE. 11 | // -------------------------------------------------------------- 12 | 13 | #ifndef __WIND_FILE_H__ 14 | #define __WIND_FILE_H__ 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif // __cplusplus 19 | 20 | // An opaque type representing the cache itself. 21 | typedef struct wind_file_s wind_file_t; 22 | 23 | // An opaque type representing a cache entry. 24 | typedef struct wind_file_entry_s wind_file_entry_t; 25 | 26 | // Open 'file' and parse contents. Return NULL on failure. 27 | wind_file_t *wind_file_new (const char *file); 28 | 29 | // Free resources associated with 'file'. 30 | void wind_file_free (wind_file_t *file); 31 | 32 | int wind_file_get_wind (wind_file_t *file, 33 | float lat, 34 | float lon, 35 | float height, 36 | float *windu, 37 | float *windv, 38 | float *windusq, 39 | float *windvsq); 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif // __cplusplus 44 | 45 | #endif // __WIND_FILE_H__ 46 | 47 | // Data for God's own editor. 48 | // vim:sw=8:ts=8:et:cindent 49 | -------------------------------------------------------------------------------- /src/wind/wind_file_cache.c: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rich Wareham 6 | // 7 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 8 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 9 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 10 | // PARTICULAR PURPOSE. 11 | // -------------------------------------------------------------- 12 | 13 | #include "wind_file_cache.h" 14 | #include "wind_file.h" 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "../util/getline.h" 30 | 31 | extern int verbosity; 32 | 33 | struct wind_file_cache_entry_s 34 | { 35 | char *filepath; // Full path. 36 | unsigned long timestamp; // As POSIX timestamp. 37 | float lat, lon; // Window centre. 38 | float latrad, lonrad; // Window radius. 39 | wind_file_t *loaded_file; // Initially NULL. 40 | }; 41 | 42 | struct wind_file_cache_s 43 | { 44 | char *directory_name; 45 | unsigned int n_entries; 46 | struct wind_file_cache_entry_s **entries; // Matching directory entries. 47 | }; 48 | 49 | // Yuk! Needed to make use of scandir. Gotta love APIs designed in the 80s. 50 | static wind_file_cache_t* _scandir_current_cache; 51 | 52 | static int 53 | _parse_header(const char* filepath, 54 | float *lat, float *latrad, 55 | float *lon, float *lonrad, 56 | unsigned long* timestamp) 57 | { 58 | FILE* file; 59 | char* line; 60 | size_t line_len; 61 | 62 | // Can I open this file? 63 | file = fopen(filepath, "r"); 64 | if(!file) { 65 | // No, abort 66 | return 0; 67 | } 68 | 69 | // Look for first non-comment line. 70 | line = NULL; 71 | while((getline(&line, &line_len, file) >= 0) && (line[0] == '#')) 72 | { 73 | // line does not need to be free-ed since getline will realloc 74 | // it if it is too small. See getline(3). 75 | } 76 | 77 | if(feof(file)) 78 | { 79 | // Got to the end without finding non-comment. 80 | free(line); 81 | fclose(file); 82 | return 0; 83 | } 84 | 85 | 86 | // 'line' is first non-comment. Try to parse it. 87 | if(5 != sscanf(line, "%f,%f,%f,%f,%ld", lat, latrad, lon, lonrad, timestamp)) 88 | { 89 | // Failed to parse, it is invalid. 90 | free(line); 91 | fclose(file); 92 | return 0; 93 | } 94 | 95 | // File seems valid. 96 | free(line); 97 | fclose(file); 98 | 99 | return 1; 100 | } 101 | 102 | #ifdef __APPLE__ 103 | static int 104 | _file_filter(struct dirent *entry) 105 | #else 106 | static int 107 | _file_filter(const struct dirent *entry) 108 | #endif 109 | { 110 | int filepath_len; 111 | int rv; 112 | wind_file_cache_t* self = _scandir_current_cache; 113 | char* filepath = NULL; 114 | struct stat stat_buf; 115 | 116 | float lat, latrad, lon, lonrad; 117 | unsigned long timestamp; 118 | 119 | // This is using sprintf in C99 mode to create a buffer with 120 | // the full file path/ 121 | filepath_len = 1 + snprintf(NULL, 0, "%s/%s", self->directory_name, entry->d_name); 122 | filepath = (char*)malloc(filepath_len); 123 | snprintf(filepath, filepath_len, "%s/%s", self->directory_name, entry->d_name); 124 | 125 | // Stat the file. 126 | rv = stat(filepath, &stat_buf); 127 | if(rv < 0) 128 | { 129 | perror("Error scanning data dir"); 130 | free(filepath); 131 | return 0; 132 | } 133 | 134 | // Is this a regular file? 135 | if(!S_ISREG(stat_buf.st_mode)) 136 | { 137 | free(filepath); 138 | return 0; 139 | } 140 | 141 | // Can I parse out the header? 142 | if(!_parse_header(filepath, &lat, &latrad, &lon, &lonrad, ×tamp)) 143 | { 144 | free(filepath); 145 | return 0; 146 | } 147 | 148 | free(filepath); 149 | 150 | return 1; 151 | } 152 | 153 | wind_file_cache_t* 154 | wind_file_cache_new(const char *directory) 155 | { 156 | wind_file_cache_t* self; 157 | int rv, i; 158 | struct dirent **dir_entries; 159 | 160 | assert(directory); 161 | 162 | // Allocate memory for ourself 163 | self = (wind_file_cache_t*) malloc(sizeof(wind_file_cache_t)); 164 | self->n_entries = 0; 165 | self->directory_name = strdup(directory); 166 | 167 | if(verbosity > 0) 168 | fprintf(stderr, "INFO: Scanning directory '%s'.\n", directory); 169 | 170 | // Use scandir scan the directory looking for data files. 171 | _scandir_current_cache = self; // ew! 172 | rv = scandir(directory, &dir_entries, _file_filter, alphasort); 173 | if(rv < 0) { 174 | perror(NULL); 175 | wind_file_cache_free(self); 176 | return NULL; 177 | } 178 | if(verbosity > 0) 179 | fprintf(stderr, "INFO: Found %i data files.\n", rv); 180 | self->n_entries = rv; 181 | 182 | self->entries = (struct wind_file_cache_entry_s**)malloc(sizeof(struct wind_file_cache_entry_s*)*self->n_entries); 183 | for(i=0; in_entries; ++i) 184 | { 185 | struct dirent* entry = dir_entries[i]; 186 | int filepath_len, parse_rv; 187 | char* filepath = NULL; 188 | 189 | // allocate the entry. 190 | self->entries[i] = (struct wind_file_cache_entry_s*)malloc(sizeof(struct wind_file_cache_entry_s)); 191 | 192 | // This is using sprintf in C99 mode to create a buffer with 193 | // the full file path/ 194 | filepath_len = 1 + snprintf(NULL, 0, "%s/%s", self->directory_name, entry->d_name); 195 | filepath = (char*)malloc(filepath_len); 196 | snprintf(filepath, filepath_len, "%s/%s", self->directory_name, entry->d_name); 197 | 198 | // Fill in the file path. 199 | self->entries[i]->filepath = filepath; 200 | 201 | // Parse the file header 202 | parse_rv = _parse_header(filepath, 203 | &(self->entries[i]->lat), &(self->entries[i]->latrad), 204 | &(self->entries[i]->lon), &(self->entries[i]->lonrad), 205 | &(self->entries[i]->timestamp)); 206 | if(!parse_rv) 207 | { 208 | fprintf(stderr, "WARN: Hmm... some files appear to have " 209 | "changed under me!"); 210 | } 211 | 212 | if(verbosity > 1) { 213 | fprintf(stderr, "INFO: Found %s.\n", filepath); 214 | fprintf(stderr, "INFO: - Covers window (lat, long) = " 215 | "(%f +/-%f, %f +/-%f).\n", 216 | self->entries[i]->lat, self->entries[i]->latrad, 217 | self->entries[i]->lon, self->entries[i]->lonrad); 218 | } 219 | 220 | // initially, no file is loaded. 221 | self->entries[i]->loaded_file = NULL; 222 | 223 | // finished with this entry 224 | free(dir_entries[i]); 225 | } 226 | // finished with the dir entries. 227 | free(dir_entries); 228 | 229 | return self; 230 | } 231 | 232 | void 233 | wind_file_cache_free(wind_file_cache_t *cache) 234 | { 235 | if(!cache) 236 | return; 237 | 238 | free(cache->directory_name); 239 | 240 | if(cache->n_entries > 0) 241 | { 242 | unsigned int i; 243 | for(i=0; in_entries; ++i) 244 | { 245 | free(cache->entries[i]->filepath); 246 | free(cache->entries[i]); 247 | cache->entries[i] = NULL; 248 | } 249 | free(cache->entries); 250 | } 251 | 252 | free(cache); 253 | } 254 | 255 | static float 256 | _lon_dist(float a, float b) 257 | { 258 | float d1 = fabs(a-b); 259 | float d2 = 360.f - d1; 260 | return (d1 < d2) ? d1 : d2; 261 | } 262 | 263 | int 264 | wind_file_cache_entry_contains_point(wind_file_cache_entry_t* entry, float lat, float lon) 265 | { 266 | if(!entry) 267 | return 0; 268 | 269 | if(fabs(entry->lat - lat) > entry->latrad) 270 | return 0; 271 | 272 | if(_lon_dist(entry->lon, lon) > entry->lonrad) 273 | return 0; 274 | 275 | return 1; 276 | } 277 | 278 | void 279 | wind_file_cache_find_entry(wind_file_cache_t *cache, 280 | float lat, float lon, unsigned long timestamp, 281 | wind_file_cache_entry_t** earlier, 282 | wind_file_cache_entry_t** later) 283 | { 284 | assert(cache && earlier && later); 285 | 286 | *earlier = *later = NULL; 287 | 288 | // This is the best we can do if we have no entries. 289 | if(cache->n_entries == 0) 290 | return; 291 | 292 | // Search for earlier and later entries which match 293 | unsigned int i; 294 | for(i=0; in_entries; ++i) 295 | { 296 | wind_file_cache_entry_t* entry = cache->entries[i]; 297 | 298 | if(entry->timestamp <= timestamp) { 299 | // This is an earlier entry 300 | if(!(*earlier) || (entry->timestamp > (*earlier)->timestamp)) 301 | { 302 | if(wind_file_cache_entry_contains_point(entry,lat,lon)) 303 | *earlier = entry; 304 | } 305 | } else { 306 | // This is a later entry 307 | if(!(*later) || (entry->timestamp < (*later)->timestamp)) { 308 | if(wind_file_cache_entry_contains_point(entry,lat,lon)) 309 | *later = entry; 310 | } 311 | } 312 | } 313 | } 314 | 315 | const char* 316 | wind_file_cache_entry_file_path(wind_file_cache_entry_t* entry) 317 | { 318 | if(!entry) 319 | return NULL; 320 | return entry->filepath; 321 | } 322 | 323 | unsigned int 324 | wind_file_cache_entry_timestamp(wind_file_cache_entry_t* entry) 325 | { 326 | if(!entry) 327 | return 0; 328 | return entry->timestamp; 329 | } 330 | 331 | wind_file_t* 332 | wind_file_cache_entry_file(wind_file_cache_entry_t *entry) 333 | { 334 | const char* filepath; 335 | 336 | if(!entry) 337 | return NULL; 338 | 339 | if(entry->loaded_file) 340 | return entry->loaded_file; 341 | 342 | filepath = wind_file_cache_entry_file_path(entry); 343 | if(!filepath) 344 | return NULL; 345 | 346 | entry->loaded_file = wind_file_new(filepath); 347 | return entry->loaded_file; 348 | } 349 | 350 | // Data for God's own editor. 351 | // vim:sw=8:ts=8:et:cindent 352 | -------------------------------------------------------------------------------- /src/wind/wind_file_cache.h: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // CU Spaceflight Landing Prediction 3 | // Copyright (c) CU Spaceflight 2009, All Right Reserved 4 | // 5 | // Written by Rich Wareham 6 | // 7 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 8 | // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 9 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 10 | // PARTICULAR PURPOSE. 11 | // -------------------------------------------------------------- 12 | 13 | #ifndef __WIND_FILES_H__ 14 | #define __WIND_FILES_H__ 15 | 16 | #include "wind_file.h" 17 | 18 | // A cache which scans the wind data directory for data files, tries to read 19 | // the header and parse out their timestamp and window information. It then 20 | // allows one to query for files closest in time and space for a specified 21 | // latitude/longitude/time. 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif // __cplusplus 26 | 27 | // An opaque type representing the cache itself. 28 | typedef struct wind_file_cache_s wind_file_cache_t; 29 | 30 | // An opaque type representing a cache entry. 31 | typedef struct wind_file_cache_entry_s wind_file_cache_entry_t; 32 | 33 | // Scan 'directory' for wind files. Return a new cache. 34 | wind_file_cache_t *wind_file_cache_new (const char *directory); 35 | 36 | // Free resources associated with 'cache'. 37 | void wind_file_cache_free (wind_file_cache_t *cache); 38 | 39 | // Search for a cache entry closest to the specified lat, lon and time. 40 | // *earlier and *later are set to the nearest cache entries which are 41 | // (respectively) earlier and later. 42 | void wind_file_cache_find_entry 43 | (wind_file_cache_t *cache, 44 | float lat, 45 | float lon, 46 | unsigned long timestamp, 47 | wind_file_cache_entry_t **earlier, 48 | wind_file_cache_entry_t **later); 49 | 50 | // Return non-zero if the cache entry specifies contains the latitude 51 | // and longitude. 52 | int wind_file_cache_entry_contains_point 53 | (wind_file_cache_entry_t *entry, 54 | float lat, 55 | float lon); 56 | 57 | // Return a string which gives the full path to the wind file 58 | // corresponding to 'entry'. This should not be freed since it 59 | // is owned by the cache itself. 60 | const char* wind_file_cache_entry_file_path 61 | (wind_file_cache_entry_t *entry); 62 | 63 | // Return the timestamp of the specified cache entry. 64 | unsigned int wind_file_cache_entry_timestamp 65 | (wind_file_cache_entry_t *entry); 66 | 67 | // Return the file for of the specified cache entry loading it if 68 | // necessary. 69 | wind_file_t* wind_file_cache_entry_file 70 | (wind_file_cache_entry_t *entry); 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif // __cplusplus 75 | 76 | #endif // __WIND_FILES_H__ 77 | 78 | // Data for God's own editor. 79 | // vim:sw=8:ts=8:et:cindent 80 | --------------------------------------------------------------------------------