CoordinatePair class is class to check if input coordinates are valid, and keeps relevant information such as: if input is valid, error message and decimal degrees format. The purpose of CoordinatePair class is to easy access to validation coordinates in further scripts and plugins.
Basically, checking if coordinate is valid is split into two parts: check if coordinate is in compacted format and is in separated (or delimited) format.
Let's create new python file: aviation_coordinate_tools.py and start populate it with the code.
First, definition of constant:
Types of coordinates:
C_LAT = 'C_LAT' C_LON = 'C_LON'
Hemisphere indicators:
H_LAT = ['N', 'S'] H_LON = ['E', 'W'] H_NEGATIVE = ['-', 'S', 'W'] H_ALL = ['-', '+', 'N', 'S', 'E', 'W']
Types of separators:
S_SPACE = ' ' S_HYPHEN = '-' S_DEG_WORD = 'DEG' S_DEG_LETTER = 'D' S_MIN_WORD = 'MIN' S_MIN_LETTER = 'M' S_SEC_WORD = 'SEC' S_ALL = [S_SPACE, S_HYPHEN, S_DEG_WORD, S_DEG_LETTER, S_MIN_WORD, S_MIN_LETTER, S_SEC_WORD]
Types of compacted formats:
F_HDMS_COMP = 'F_HDMS_COMP' # Hemisphere prefix DMS compacted F_DMSH_COMP = 'F_DMSH_COMP' # Hemisphere suffix DMS compacted F_HDM_COMP = 'F_HDM_COMP' # Hemisphere prefix DMS compacted F_DMH_COMP = 'F_DMH_COMP' # Hemisphere suffix DMS compacted
Regular expressions for compacted formats:
coord_regex = {F_HDMS_COMP: re.compile(r'''(?P<hem>^[NSEW]) (?P<deg>\d{2,3}) # Degrees (?P<min>\d{2}) # Minutes (?P<sec>\d{2}(\.\d+)?$) # Seconds ''', re.VERBOSE), F_DMSH_COMP: re.compile(r'''(?P<deg>^\d{2,3}) # Degrees (?P<min>\d{2}) # Minutes (?P<sec>\d{2}(\.\d+)?) # Seconds (?P<hem>[NSEW]$) ''', re.VERBOSE), F_HDM_COMP: re.compile(r'''(?P<hem>^[NSEW]) (?P<deg>\d{2,3}) # Degrees (?P<min>\d{2}(\.\d+)?$) # Minutes ''', re.VERBOSE), F_DMH_COMP: re.compile(r'''(?P<deg>^\d{2,3}) # Degrees (?P<min>\d{2}(\.\d+)?) # Minutes (?P<hem>[NSEW]$) ''', re.VERBOSE) }
CoordinatesPair class constructor:
class CoordinatesPair: def __init__(self, src_lat, src_lon): self.src_lat = src_lat self.src_lon = src_lon self._is_valid = None self._err_msg = '' self._dd_lat = None self._dd_lon = None self.parse_src_coordinates()
Method that 'normalizes' input (source) coordinates:
@staticmethod def normalize_src_input(src_input): norm_input = str(src_input) norm_input = norm_input.strip() # Trim leading and trailing space norm_input = norm_input.upper() # Make all letters capitals norm_input = norm_input.replace(',', '.') # Make sure that decimal separator is dot not comma return norm_input
Method that checks if input coordinate is in compacted format:
@staticmethod def parse_regex(regex_patterns, dms, c_type): dd = None for pattern in regex_patterns: # Check if input matches any pattern if regex_patterns.get(pattern).match(dms): if pattern in [F_DMSH_COMP, F_HDMS_COMP]: # If input matches to pattern get hemisphere, degrees, minutes and seconds values groups = regex_patterns.get(pattern).search(dms) h = groups.group('hem') d = float(groups.group('deg')) m = float(groups.group('min')) s = float(groups.group('sec')) if (h in H_LAT and c_type == C_LAT) or (h in H_LON and c_type == C_LON): if h in ['N', 'S']: if d > 90: # Latitude is in range <-90, 90> dd = None elif d == 90 and (m > 0 or s > 0): dd = None else: if m >= 60 or s >= 60: dd = None else: dd = d + m / 60 + s / 3600 if h == 'S': dd = -dd elif h in ['E', 'W']: if d > 180: # Longitude is in range <-180, 180> dd = None elif d == 180 and (m > 0 or s > 0): dd = None else: if m >= 60 or s >= 60: dd = None else: dd = d + m / 60 + s / 3600 if h == 'W': dd = -dd return dd
Method that checks if coordinate is in separated format:
@staticmethod def parse_coordinate(coord_norm, c_type): dd = None # First, check if input is in DD format try: dd = float(coord_norm) except ValueError: # Assume that coordinate is in DMS, DMSH, DM, DMH, HDD, DDH # Check first and last character h = coord_norm[0] if h in H_ALL: # DMS, DM signed or HDMS, HDM, HDD, coord_norm = coord_norm[1:] else: # Check last character h = coord_norm[-1] if h in H_ALL: coord_norm = coord_norm[:-1] else: h = coord_norm[0] if h.isdigit(): if c_type == C_LAT: h = 'N' elif c_type == C_LON: h = 'E' # Check if hemisphere letter matches coordinate type (c_type) if (h in H_LAT and c_type == C_LAT) or (h in H_LON and c_type == C_LON): # Trim spaces again coord_norm = coord_norm.strip() # Replace separators (delimiters) with blank (space) for sep in S_ALL: coord_norm = re.sub(sep, ' ', coord_norm) # Replace multiple spaces into single spaces coord_norm = re.sub('\s+', ' ', coord_norm) c_parts = coord_norm.split(' ') if len(c_parts) == 3: # Assume format DMS separated try: d = int(c_parts[0]) if d < 0: return None except ValueError: return None try: m = int(c_parts[1]) if m < 0 or m >= 60: return None except ValueError: return None try: s = float(c_parts[2]) if s < 0 or s >= 60: return None except ValueError: return None try: dd = float(d) + float(m) / 60 + s / 3600 if h in H_NEGATIVE: dd = - dd except ValueError: return None elif len(c_parts) == 2: # Assume format DM separated try: d = int(c_parts[0]) if d < 0: return None except ValueError: return None try: m = float(c_parts[1]) if m < 0 or m >= 60: return None except ValueError: return None try: dd = float(d) + m / 60 if h in H_NEGATIVE: dd = - dd except ValueError: return None elif len(c_parts) == 1: # Assume format DMS, DM compacted or DD try: dd = float(c_parts[0]) if h in H_NEGATIVE: dd = -dd except ValueError: return None else: return None # If we get dd - check is is withing range if dd is not None: if c_type == C_LAT: if -90 <= dd <= dd: return dd else: return None elif c_type == C_LON: if -180 <= dd <= 180: return dd else: return None
Method that parses input coordinates:
def parse_src_coordinates(self): if self.src_lat == '': # Blank input self._err_msg += 'Enter latitude value!\n' else: norm_lat = self.normalize_src_input(self.src_lat) self.dd_lat = self.parse_regex(coord_regex, norm_lat, C_LAT) if self.dd_lat is None: self.dd_lat = self.parse_coordinate(norm_lat, C_LAT) if self.dd_lat is None: self.err_msg += 'Latitude value wrong value!\n' if self.src_lon == '': # Blank input self.err_msg += 'Enter longitude value!\n' else: norm_lon = self.normalize_src_input(self.src_lon) self.dd_lon = self.parse_regex(coord_regex, norm_lon, C_LON) if self.dd_lon is None: self.dd_lon = self.parse_coordinate(norm_lon, C_LON) if self.dd_lon is None: self.err_msg += 'Longitude value wrong value!\n' if self.dd_lat is not None and self.dd_lon is not None: self.is_valid = True else: self.is_valid = False
No comments:
Post a Comment