#!/usr/bin/env python #**************************************************************************** # waypoint.py, provides non-GUI base classes for airports, navaids & fixes # # FlyWay, a VFR/IFR Route Planner for Pilots # Copyright (C) 2002, Douglas W. Bell # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, Version 2. This program is # distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY. #***************************************************************************** try: from __main__ import dataFilePath except ImportError: dataFilePath = None from searchstr import SearchStr import sys, os.path class Waypoint: """Parent class for airport, navaid and fix data""" id = 0 latitude = 1 longitude = 2 magVar = 3 stateName = 4 stateAbbr = 5 name = 6 # Airport & Navaid only city = 7 # Airport & Navaid only elevation = 8 # Airport & Navaid only aptFuelAvail = navType = 9 aptAwos = navFreq = 10 aptCtaf = 11 aptTower = 12 aptGround = 13 aptClnc = 14 aptWthr = 15 rwy1dir1 = 16 rwy1dir2 = 17 rwy1length = 18 rwy1width = 19 rwy1surface = 20 rwyToNext = 5 aptFreqTypes = (('Tower', aptTower), ('Ground', aptGround), \ ('CTAF/UNIC', aptCtaf), ('ATIS/AWOS', aptAwos), \ ('Clnc. Del.', aptClnc)) def __init__(self, dataLine=None): self.dataList = [] if dataLine: self.dataList = dataLine.replace('\n', '').split('\t') for field in self.__class__.lowCaseFields: self.dataList[field] = self.dataList[field].title() def setId(self, id, state=''): """Read data from buffer for ID, return self/None on pass/fail""" if self.loadDataBuffer(): srchStr = id + '\t' for line in self.__class__.dataBuffer: if line.startswith(srchStr): self.dataList = self.__class__(line).dataList if not state or state == self.dataList[Waypoint.stateAbbr]: return self return None def search(self, keyWords): """Search waypoint type for key words, return list""" if self.loadDataBuffer(): srch = SearchStr(keyWords) results = [self.__class__(line) for line in \ self.__class__.dataBuffer if \ srch.isMatch(line)] # take only matches to search fields results = [wp for wp in results if \ srch.isMatch('\t'.join([wp.dataList[field] for \ field in \ self.__class__.searchFields])\ .upper())] if results: self.dataList = results[0].dataList return results return [] def findDataFile(self, pathList, fileName): """Search paths for file, return line list or None""" for path in pathList: try: f = open(os.path.join(path, fileName), 'r') lineList = f.readlines() f.close() return lineList except IOError: pass return None def loadDataBuffer(self): """Load data into buffer if reqd, return 1 on success""" if self.__class__.dataBuffer: return 1 modPath = os.path.abspath(sys.path[0]) pathList = [dataFilePath, os.path.join(modPath, '../data/'), \ modPath] lineList = self.findDataFile(filter(None, pathList), \ self.__class__.fileName) if not lineList: print 'Error - can not read data file', self.fileName return 0 self.__class__.dataBuffer = lineList[1:] # remove title line return 1 def getAllWaypoints(self): """Return a long list of all waypoints of this type""" if self.loadDataBuffer(): return [self.__class__(line) for line in \ self.__class__.dataBuffer if line.strip()] return [] def latitudeValue(self): """Return latitude as a float""" return float(self.dataList[Waypoint.latitude]) def longitudeValue(self): """Return longitude as a float""" return float(self.dataList[Waypoint.longitude]) def magVarValue(self): """Return magnetic variation as a float""" return float(self.dataList[Waypoint.magVar]) def wpTypeChar(self): """Return first letter of type name""" return self.__class__.wpType def elevationString(self): """Return elevation string""" elev = self.dataList[Waypoint.elevation] if elev and elev != 'U': return elev + ' ft' return 'N/A' def magVarString(self): """Return magnetic variation string""" if self.magVarValue() >= 0: return '%02.0f%s W' % (self.magVarValue(), chr(176)) return '%02.0f%s E' % (-self.magVarValue(), chr(176)) def mainFrequency(self): """Return primary frequency, overridden by types""" return '' def ident(self): """Return id string""" return self.dataList[Waypoint.id] def stateRef(self): """Return state abbrev in ',XX' format if req'd for unique storage""" return '' def freqDescription(self): """Return string with frequency and wp description""" freq = self.mainFrequency() if freq: return '(%s) %s' % (freq, self.description()) return self.description() def formatFreq(self, freqStr, unit='mHz'): """Return formatted string from number string""" if freqStr: # freqStr = '%g' % float(freqStr) # if len(freqStr) < 4: # freqStr += '.0' # return '%-7s %s' % (freqStr, unit) return '%.3f %s' % (float(freqStr), unit) return '' def detailNumLines(self): """Return num lines in detail list, 2 used for wp, descr, elev, magvar""" return self.__class__.detailLines class Airport(Waypoint): """Waypoint class for airport data""" fileName = 'apt.dat' wpType = 'A' dataBuffer = [] searchFields = [Waypoint.id, Waypoint.stateName, Waypoint.stateAbbr, \ Waypoint.name, Waypoint.city] lowCaseFields = [Waypoint.stateName, Waypoint.name, Waypoint.city] rwySurfDict = {'ASPH': 'Asphalt', 'CONC': 'Concrete', 'SNOW': 'Snow', \ 'ICE': 'Ice', 'MATS': 'Planks', 'TREATED': 'Misc Soft', \ 'GRAVEL': 'Gravel', 'TURF': 'Grass', 'DIRT': 'Soil', \ 'WATER': 'Water'} def __init__(self, dataLine=None): Waypoint.__init__(self, dataLine) def description(self): """Return string description based on several fields""" result = self.dataList[Waypoint.name] # if self.dataList[Waypoint.aptAltName]: # result = '%s / %s' % (result, self.dataList[Waypoint.aptAltName]) result += ' Airport' if self.dataList[Waypoint.city]: result = '%s, %s' % (result, self.dataList[Waypoint.city]) return '%s, %s' % (result, self.dataList[Waypoint.stateName]) def mainFrequency(self): """Return primary frequency""" if self.dataList[Waypoint.aptTower]: return self.freqAtPos(Waypoint.aptTower) return self.freqAtPos(Waypoint.aptCtaf) def freqAtPos(self, pos): """Return given frequency""" if pos == Waypoint.aptCtaf and \ self.dataList[pos] == self.dataList[Waypoint.aptTower]: return '' if self.dataList[pos]: return self.formatFreq(self.dataList[pos]) return '' def fuelAvail(self): """Return fuel string""" if self.dataList[Waypoint.aptFuelAvail] == 'Y': return 'Fuel is Available' return 'Fuel Not Available' def rwyName(self, num): """Return string with ID numbers for runway num 0-7""" delta = num * Waypoint.rwyToNext if len(self.dataList) > Waypoint.rwy1dir1 + delta and \ self.dataList[Waypoint.rwy1dir1 + delta]: return 'Rwy %s/%s' % (self.dataList[Waypoint.rwy1dir1 + delta], \ self.dataList[Waypoint.rwy1dir2 + delta]) return '' def rwyDesc(self, num): """Return string with info for runway num 0-7""" delta = num * Waypoint.rwyToNext return '%sx%s ft %s' % (self.dataList[Waypoint.rwy1length + delta], \ self.dataList[Waypoint.rwy1width + delta], \ Airport.rwySurfDict.get(self.dataList\ [Waypoint.rwy1surface + delta], '')) def detailList(self): """Return list of fields with all airport details""" freqList = ['%s %s' % (self.freqAtPos(type[1]), type[0]) for type \ in Waypoint.aptFreqTypes if self.freqAtPos(type[1])] rwyList = ['%s: %s' % (self.rwyName(num), self.rwyDesc(num)) for \ num in range(8) if self.rwyName(num)] list = ['%s Elevation' % self.elevationString(), \ '%s Magn. Var.' % self.magVarString(), self.fuelAvail()] for couple in map(None, freqList, rwyList): list.extend(couple) return [item and item or '' for item in list] # remove None's def detailNumLines(self): """Return num lines in detail list, 2 used for wp, descr, elev, magvar""" freqList = [type for type in Waypoint.aptFreqTypes if \ self.freqAtPos(type[1])] rwyList = [num for num in range(8) if self.rwyName(num)] return max(len(freqList), len(rwyList)) + 2 class Navaid(Waypoint): """Waypoint class for navaid data""" fileName = 'nav.dat' wpType = 'N' dataBuffer = [] detailLines = 3 searchFields = [Waypoint.id, Waypoint.stateName, Waypoint.stateAbbr, \ Waypoint.name, Waypoint.city] lowCaseFields = [Waypoint.stateName, Waypoint.name, Waypoint.city] def __init__(self, dataLine=None): Waypoint.__init__(self, dataLine) def description(self): """Return string description based on several fields""" result = '%s %s' % (self.dataList[Waypoint.name], \ self.dataList[Waypoint.navType]) if self.dataList[Waypoint.city]: result = '%s, %s' % (result, self.dataList[Waypoint.city]) return '%s, %s' % (result, self.dataList[Waypoint.stateName]) def mainFrequency(self): """Return primary frequency""" if self.dataList[Waypoint.navType].find('NDB') >= 0: return self.formatFreq(self.dataList[Waypoint.navFreq], 'kHz') return self.formatFreq(self.dataList[Waypoint.navFreq]) def detailList(self): """Return list of fields with all navaid details""" return ['%s Elevation' % self.elevationString(), \ '%s Magn. Var.' % self.magVarString(), '', \ '%s Frequency' % self.mainFrequency(), ''] def stateRef(self): """Return state abbrev in ',XX' format if req'd for unique storage""" if self.dataList[Waypoint.navType] == 'NDB': return ',%s' % self.dataList[Waypoint.stateAbbr] return '' class Fixpoint(Waypoint): """Waypoint class for fix data""" fileName = 'fix.dat' wpType = 'F' dataBuffer = [] detailLines = 2 searchFields = [Waypoint.id, Waypoint.stateName, Waypoint.stateAbbr] lowCaseFields = [Waypoint.stateName] def __init__(self, dataLine=None): Waypoint.__init__(self, dataLine) def description(self): """Return string description based on several fields""" return 'Intersection, %s' % self.dataList[Waypoint.stateName] def detailList(self): """Return list of fields with all fixpoint details""" return ['', '%s Magn. Var.' % self.magVarString(), '']