Added interpolator and implemented correct parsing and output for JavaProp to JSBsim CP and CT tables converter
This commit is contained in:
parent
82c5f6fc29
commit
3cd5fce3b7
22
README.md
22
README.md
@ -1,2 +1,20 @@
|
||||
# fgscenery-tools
|
||||
Tools for creating, managing and editing FlightGear scenery
|
||||
# fgtools
|
||||
Tools for creating, managing and editing FlightGear scenery, aircraft, …
|
||||
|
||||
## Installation
|
||||
To run these scripts you need Python 3, Python 2 won't work. Recommended version is 3.8 as I only have that installed so couldn't test with any other versions - earlier 3.x versions should work, but no guarantee !
|
||||
|
||||
### Linux
|
||||
Download / `git clone` this repo and put it in a place of your choice, say `/home/user/fgtools`. With `git clone`, you would use this command:
|
||||
```sh
|
||||
/home/user$ git clone https://github.com/TheFGFSEagle/fgtools
|
||||
```
|
||||
Before you run the scripts you have to make sure that the folder containing this repository on your local disk (here `/home/user`) is inside your `PYTHONPATH` environment variable, or you must run the scripts from inside the `fgtools` folder. To add the folder to your `PYTHONPATH`, use this command:
|
||||
```sh
|
||||
export PYTHONPATH="${PYTHONPATH}:/home/user"
|
||||
```
|
||||
Note: this is lost when you close the terminal / console, so you have to run this command every time you open a new console and run the scripts from it. To make the change persistent, add the command to the end of the `.profile` file in your home folder.
|
||||
|
||||
### Windows
|
||||
_I don't have Windows so cannot provide any instructions - contributions by Windows users welcome !_
|
||||
|
||||
|
0
__init__.py
Normal file
0
__init__.py
Normal file
@ -3,24 +3,51 @@
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from fgtools.utils.interpolator import Interpolator
|
||||
|
||||
|
||||
def parse_data_files(input_files, blade_angles):
|
||||
blade_angles = list(map(float, blade_angles))
|
||||
data = {}
|
||||
for file, angle in zip(input_files, blade_angles):
|
||||
data[angle] = {
|
||||
"Cp": Interpolator(),
|
||||
"Ct": Interpolator()
|
||||
}
|
||||
with open(file) as f:
|
||||
content = f.readlines()
|
||||
|
||||
content = list(map(lambda s: s.strip().split("\t"), content))[2:]
|
||||
data[angle] = {}
|
||||
for line in content:
|
||||
data[angle][float(line[0])] = {"Ct": float(line[2]), "Cp": float(line[3])}
|
||||
av, cp, ct = float(line[0]), float(line[2]), float(line[3])
|
||||
data[angle]["Cp"].add_value(av, cp)
|
||||
data[angle]["Ct"].add_value(av, ct)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def make_tables(data, maximum, indentation="\t", resolution=0.05):
|
||||
Cp = Ct = indentation * 4 + (indentation * 2).join(map(str, data)) + "\n"
|
||||
av = 0
|
||||
while av <= maximum:
|
||||
av = round(av, 6)
|
||||
Cp += indentation * 2 + indentation + str(av) + indentation
|
||||
Ct += indentation * 2 + indentation + str(av) + indentation
|
||||
cps = []
|
||||
cts = []
|
||||
for angle in data:
|
||||
cps.append("%.6f" % round(data[angle]["Cp"].interpolate(av, sort=False), 6))
|
||||
cts.append("%.6f" % round(data[angle]["Ct"].interpolate(av, sort=False), 6))
|
||||
|
||||
Cp += indentation.join(cps) + "\n"
|
||||
Ct += indentation.join(cts) + "\n"
|
||||
av += resolution
|
||||
return {"Cp":Cp, "Ct": Ct}
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argp = argparse.ArgumentParser(description="javaprop2jsbcpct.py - converts JavaProp propeller data into Cp and Ct tables for a JSBsim propelller")
|
||||
argp = argparse.ArgumentParser(description="javaprop2jsbcpct.py - converts JavaProp propeller data into Cp and Ct tables for a JSBsim propeller")
|
||||
|
||||
argp.add_argument(
|
||||
"-i", "--input-file",
|
||||
@ -38,6 +65,13 @@ if __name__ == "__main__":
|
||||
dest="blade_angles"
|
||||
)
|
||||
|
||||
argp.add_argument(
|
||||
"-m", "--max",
|
||||
help="Maximum advance ratio to output data for",
|
||||
required=True,
|
||||
type=float
|
||||
)
|
||||
|
||||
argp.add_argument(
|
||||
"--interactive",
|
||||
action="store_true",
|
||||
@ -50,6 +84,13 @@ if __name__ == "__main__":
|
||||
default="\t"
|
||||
)
|
||||
|
||||
argp.add_argument(
|
||||
"-r", "--resolution",
|
||||
help="Advance ratio resolution to generate (default: 0.05)",
|
||||
type=float,
|
||||
default=0.05
|
||||
)
|
||||
|
||||
args = argp.parse_args()
|
||||
|
||||
for path in args.input_files:
|
||||
@ -60,8 +101,22 @@ if __name__ == "__main__":
|
||||
if len(args.blade_angles) < len(args.input_files):
|
||||
print("Error: less blade angles than input files")
|
||||
elif len(args.blade_angles) > len(args.input_files):
|
||||
args.blade_angles, rest = args.blade_angles[:len(args.input_files)]
|
||||
args.blade_angles, rest = args.blade_angles[:len(args.input_files) + 1]
|
||||
print(f"Warning: skipping {len(rest)} blade angles because no corresponding data file was specified")
|
||||
|
||||
data = parse_data_files(args.input_files, args.blade_angles)
|
||||
output = make_tables(data, args.max, args.indentation, args.resolution)
|
||||
|
||||
|
||||
print(args.indentation + "<table name=\"C_THRUST\" type=\"internal\">")
|
||||
print(args.indentation * 2 + "<tableData>")
|
||||
print(output["Ct"])
|
||||
print(args.indentation * 2 + "</tableData>")
|
||||
print(args.indentation + "</table>")
|
||||
print(args.indentation)
|
||||
print(args.indentation + "<table name=\"C_POWER\" type=\"internal\">")
|
||||
print(args.indentation * 2 + "<tableData>")
|
||||
print(output["Cp"])
|
||||
print(args.indentation * 2 + "</tableData>")
|
||||
print(args.indentation + "</table>")
|
||||
|
||||
|
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
82
utils/interpolator.py
Executable file
82
utils/interpolator.py
Executable file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
#-*- coding:utf-8 -*-
|
||||
|
||||
class Interpolator:
|
||||
def __init__(self):
|
||||
self._indexes = []
|
||||
self._values = []
|
||||
self._sorted = False
|
||||
|
||||
self.methods = {"linear": self._interpolate_linear}
|
||||
|
||||
def add_value(self, index, value):
|
||||
if type(index) not in (int, float) or type(value) not in (int, float):
|
||||
try:
|
||||
index = float(index)
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
raise TypeError(f"Interpolator.add_value: index '{index}' or value '{value}' not a number")
|
||||
|
||||
self._indexes.append(index)
|
||||
self._values.append(value)
|
||||
self._sorted = False
|
||||
|
||||
def add_values(self, indexes, values):
|
||||
for i, v in zip(indexes, values):
|
||||
self.add_value(i, v)
|
||||
|
||||
def interpolate(self, index, extrapolate=True, method="linear", sort=True):
|
||||
if not method in self.methods:
|
||||
raise NotImplementedError(f"Interpolator.interpolate: interpolation method '{method}' not yet supported")
|
||||
|
||||
if len(self._indexes) < 2:
|
||||
raise ValueError(f"Interpolator.interpolate: cannot interpolate on a table with less than two data points")
|
||||
|
||||
# only sort if not already sorted to increase performance for large tables
|
||||
if not self._sorted and sort:
|
||||
self._indexes.sort()
|
||||
self._values.sort()
|
||||
self._sorted = True
|
||||
|
||||
return self.methods[method](index, extrapolate)
|
||||
|
||||
def _find_neighbours(self, index):
|
||||
lower = upper = 0
|
||||
last = self._indexes[0]
|
||||
for it, _index in enumerate(self._indexes):
|
||||
lower = last
|
||||
last = it
|
||||
if _index > index:
|
||||
upper = it
|
||||
break
|
||||
|
||||
return lower, upper
|
||||
|
||||
def _interpolate_linear(self, index, extrapolate=True):
|
||||
if index in self._indexes:
|
||||
return self._values[self._indexes.index(index)]
|
||||
|
||||
if self._indexes[0] < index < self._indexes[-1]:
|
||||
lower, upper = self._find_neighbours(index)
|
||||
return self._values[lower] + (self._values[upper] - self._values[lower]) * (index - self._indexes[lower]) / (self._indexes[upper] - self._indexes[lower])
|
||||
else:
|
||||
if not extrapolate:
|
||||
if index < self._indexes[0]:
|
||||
return self._values[0]
|
||||
else:
|
||||
return self._values[-1]
|
||||
else:
|
||||
if index < self._indexes[0]:
|
||||
return self._values[1] + (index - self._indexes[1]) / (self._indexes[0] - self._indexes[1]) * (self._values[0] - self._values[1])
|
||||
else:
|
||||
return self._values[-2] + (index - self._indexes[-2]) / (self._indexes[-1] - self._indexes[-2]) * (self._values[-1] - self._values[-2])
|
||||
|
||||
# run test if run directly
|
||||
if __name__ == "__main__":
|
||||
print("Test results")
|
||||
i = Interpolator()
|
||||
i.add_values((0, 10, 20), (0, 20, 30))
|
||||
for test_val in (-5, 0, 1, 2, 3.5, 5.55555, 9, 10, 15, 100):
|
||||
print(test_val, i.interpolate(test_val))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user