Many times in aeronautical publications you will see:
- position of points are defined as magnetic bearing from specified point (e. g. aerodrome reference point), and magnetic variation for this point is known, we are interesting in geographic position of obstacle not magnetic
- boundary of airspace (a good example that comes to my mind are airspaces on minimum radar vectoring chart) are defined as arcs and 'strait' lines by points, which are defined by radials and distances against point with specified position and magnetic variation; by definition radial are expressed in magnetic bearing - and again we would like to know geographic position of vertices that create the shape of airspace not magnetic.
Because bearing and magnetic variation are types of angles (as well as latitude and longitude) and might be given in various formats let's create basic class Angle.
Angle class will cover common tasks as validating input, range and so on.
Class definition will start with constructor:
class Angle: def __init__(self, src_value): self.src_value = src_value self._is_valid = None self._dd_value = None self._err_msg = ''
where src_value is value of angle (bearing, magnetic variation) form source (NOTAM, AIP etc.)
Attribute _is_valid will take value True if input src_value will be valid angle and False otherwise.
Because for further calculations we need value in decimal degrees, attribute _dd_value will keep DD
for passed value from source - only if value is valid for specified angle type, such as bearing, or magnetic variation.
Angle class will be basic class for other classes (to deal with bearings and magnetic variation) so validation of angle will be covered in appropriate subclasses.
You might ask now: Where are latitude and longitude types?
Angle class will contain methods to validate various formats of angle (DD, DMS etc.) that are common for all types of angles (bearing, magnetic variation, coordinates). Specific formats for specified types of angle will be implemented in appropriate subclasses.
I assume that src_value might be passed by 'copy and paste' methos, hence first of all I need to normalize input, e. g. trim leading and trailing blanks characters and etc. I have crated method
normalize_src_input do deal with that:
@staticmethod def normalize_src_input(src_input): """ Normalizes source (input) angle for further processing :param src_input: str, input angle string to normalize :return: norm_angle: str, normalized angle string """ norm_input = str(src_input) norm_input = norm_input.replace(',', '.') norm_input = norm_input.upper() return norm_input
Now we write function that will check if input data is correct. Let's start with very basic formats: so far the only angle format that is valid is decimal degrees. Function to validate if input is in decimal degrees:
@staticmethod def check_angle_dd(angle): """ Checks if angle is in DD format. :param angle: float, str: angle to check :return: float, vale of angle if angle is integer of float, const NOT_VALID otherwise """ try: a = float(angle) return a except ValueError: return None
Next step is the function that will check if the angle in DD format is within appropriate range, witch depends on type of angle (e.g. absolute bearing <0, 360>, latitude <-90, 90):
@staticmethod def check_angle_range(angle, min_value, max_value): if min_value <= angle <= max_value: return True, angle else: return False, None
Magnetic variation
For the time being this we will cover following formats of magnetic variation:
- DD format (e. g. -3.55, 0.7)
- DD format with magnetic variation letter prefix or suffix (e. g. W3.55, E0.7)
class MagVar(Angle): def __init__(self, mag_var_src): Angle.__init__(self, mag_var_src) self.parse_mag_var()
First case (DD) is covered by Angle class, so we need only write method that checks if magnetic variation is in second format:
def check_magvar_vletter_dd(self, mag_var): """ Check if magnetic variation is in decimal degrees with variation letter suffix or prefix format. e. g.: E3.55, 0.77W :return: float - magnetic variation in decimal degrees, or bool - False if input outside the range """ if REGEX_MAG_VAR_VLDD.match(mag_var): h = mag_var[0] mv = self.check_angle_dd(mag_var[1:]) if mv != NOT_VALID: if h == 'W': mv = -mv return mv else: return None elif REGEX_MAG_VAR_DDVL.match(mag_var): h = mag_var[-1] mv = self.check_angle_dd(mag_var[:-1]) if mv != NOT_VALID: if h == 'W': mv = -mv return mv else: return None else: return None
And I used following regular expressions:
REGEX_MAG_VAR_VLDD = re.compile(r'^[WE]\d+\.\d+$|^[WE]\d+$') REGEX_MAG_VAR_DDVL = re.compile(r'^\d+\.\d+[WE]$|^\d+[WE]$')
And finally, function that parse input (src_value) and ties convert it into DD format:
def parse_mag_var(self): """ Parse source value to convert it into decimal degrees value""" if self.src_value == '': # If no value given - by default magnetic variation is 0.0 self.dd_value = 0.0 self.is_valid = True return else: norm_src = self.normalize_src_input(self.src_value) mv_dd = self.check_angle_dd(norm_src) # Check if magnetic variation is in DD format if mv_dd is None: mv_dd = self.check_magvar_vletter_dd(norm_src) # Check if it is in HDD or DDH format if mv_dd is None: self.is_valid = False self.err_msg = 'Magnetic variation error!\n' if mv_dd is not None: # Managed to get DD format of magnetic variation - check if it is within range self.is_valid, self.dd_value = self.check_angle_range(mv_dd, -120, 120) if self.is_valid is False: self.err_msg = 'Magnetic variation error!\n'
Bearing class
Constructor:
class Bearing(Angle): def __init__(self, brng_src): Angle.__init__(self, brng_src) self.dd_tbrng = None self.parse_brng()
Check if src_value of bearing is correct:
def parse_brng(self): """ Parse source value to convert it into decimal degrees value""" if self.src_value == '': # No value self.is_valid = False self.err_msg = 'Enter bearing!\n' else: norm_src = self.normalize_src_input(self.src_value) brng = self.check_angle_dd(norm_src) # Check if bearing is given in decimal degrees format if brng is None: self.is_valid = False self.err_msg = 'Bearing error!\n' if brng is not None: # Managed to get DD format of bearing - check if it is within range self.is_valid, self.dd_value = self.check_angle_range(brng, 0, 360) if self.is_valid is False: self.err_msg = 'Bearing error!\n'
Calculate true bearing is done by method:
def calc_tbrng(self, dd_mag_var): """ Calculates true bearing. :param: dd_mag_var: float, magnetic variation value """ if dd_mag_var == 0: self.dd_tbrng = self.dd_value else: self.dd_tbrng = self.dd_value + dd_mag_var if self.dd_tbrng > 360: self.dd_tbrng -= 360 elif self.dd_tbrng < 360: self.dd_tbrng += 360
No comments:
Post a Comment