#!/usr/bin/env python #**************************************************************************** # route.py, provides non-GUI base classes for route data # # 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. #***************************************************************************** from waypoint import Waypoint, Airport, Navaid, Fixpoint from plandata import PlanData from searchstr import SearchStr from units import Angle, Time, TimeSpan import math class Route: """Container class for waypoints and legs""" def __init__(self, option): self.option = option self.wpList = [] self.wpErrorList = [] self.legList = [] self.fltPlan = None self.mapData = None def findWp(self, keyWords): """Find waypoint by ID then search, return list""" srchStr = keyWords.upper().strip() results = [] # try to find by ID only for simple search string: if ' ' not in srchStr and '*' not in srchStr: if 3 <= len(srchStr) <= 4: results.append(Airport().setId(srchStr)) if len(srchStr) <= 3: results.append(Navaid().setId(srchStr)) # fix set id not req'd - will be found in search results = [wp for wp in results if wp] if results and len(srchStr) == 3: # return id matches only for common id length return results results = [] # avoid duplicate matches results.extend(Airport().search(srchStr)) results.extend(Navaid().search(srchStr)) results.extend(Fixpoint().search(srchStr)) return results def readFile(self, fileName): """Read file contents into route, return 1/0 on success/fail""" try: f = open(fileName, 'r') lines = [line.strip() for line in f.readlines()] f.close() except IOError: print 'Error - can not read file', fileName return 0 try: self.fltPlan = None brkPos = lines.index('____') fltPlnList = lines[brkPos + 1:] self.fltPlan = PlanData(self.option, fltPlnList) lines = [line for line in lines[:brkPos] if line] except ValueError: pass typeDict = {'A': Airport, 'N': Navaid, 'F': Fixpoint} self.wpList = [] self.wpErrorList = [] for line in lines: data = line.split(',') if len(data) < 3: data.append('') type, id, state = data wp = typeDict.get(type, Airport)().setId(id, state) if wp: self.wpList.append(wp) else: self.wpErrorList.append(id) return 1 def writeFile(self, fileName): """Write route contents to file, return 1/0 on success/fail""" try: f = open(fileName, 'w') f.writelines(['%s,%s%s\n' % (wp.wpType, wp.ident(), wp.stateRef()) \ for wp in self.wpList]) if self.fltPlan: f.write('____\n') f.writelines(self.fltPlan.writeFile()) f.close() except IOError: print 'Error - can not write to file', fileName return 0 return 1 def updateLegs(self): """Replace legs for route""" self.legList = [] for i in range(len(self.wpList) - 1): self.legList.append(RouteLeg(self.wpList[i], self.wpList[i + 1], \ self.option, self.legList and \ self.legList[-1] or None)) def isWithinDistance(self, refWp, distance): """Return 1 if refWp is at least as close as distance to a route leg""" if len(self.wpList) == 1 and \ wpDistance(refWp, self.wpList[0])[0] <= distance: return 1 for leg in self.legList: if leg.distanceToCourse(refWp) <= distance: return 1 return 0 def sortWpByRoute(self, wpList): """Return list sorted by ratio along route length""" tmpList = [(self.ratioAlongRoute(wp), wp) for wp in wpList] tmpList.sort() return [tmp[1] for tmp in tmpList] def ratioAlongRoute(self, wp): """Return a ratio of relative distance along route length""" if len(self.wpList) < 2: return 0 dist1 = wpDistance(wp, self.wpList[0])[0] dist2 = wpDistance(wp, self.wpList[-1])[0] if dist1 + dist2 == 0: return 0 return dist1 / (dist1 + dist2) class RouteLeg: """Calculates parameters between two waypoints""" nmLat = 60.00721 nmLng = 60.10793 maxAirspeed = 9999 maxWindSpeed = 999 def __init__(self, fromWp, toWp, option, prevLeg=None): self.fromWp = fromWp self.toWp = toWp self.distance, xDistance, yDistance = wpDistance(fromWp, toWp) if yDistance != 0: self.absCourse = Angle(math.atan(xDistance/yDistance)*180/math.pi) if yDistance < 0: self.absCourse = Angle(self.absCourse - 180.0) elif xDistance >= 0: self.absCourse = Angle(90.0) else: self.absCourse = Angle(-90.0) airspeed = option.intData('Airspeed', 1, RouteLeg.maxAirspeed) windSpeed = option.intData('WindSpeed', 0, RouteLeg.maxWindSpeed) windDir = Angle(option.intData('WindDir', 0, 360)) self.is12hrClock = option.boolData('12hrClock') absHeading = Angle(self.absCourse - (180/math.pi) \ * math.asin(float(windSpeed) / airspeed) \ * math.sin((self.absCourse - windDir) \ * math.pi/180)) self.groundSpeed = math.cos((absHeading - self.absCourse) \ * math.pi/180) * airspeed \ + windSpeed * math.cos((windDir - self.absCourse \ + 180.0) * math.pi/180) magVar = (fromWp.magVarValue() + toWp.magVarValue()) / 2 self.course = Angle(self.absCourse + magVar) self.heading = Angle(absHeading + magVar) self.ete = TimeSpan(self.distance / self.groundSpeed) if prevLeg: self.eta = Time(prevLeg.eta + self.ete) self.totalDist = prevLeg.totalDist + self.distance self.totalTime = TimeSpan(prevLeg.totalTime + self.ete) else: self.eta = Time(option.intData('DepartHour', 0, 23) \ + option.intData('DepartMinute', 0, 59) / 60.0 \ + self.ete) self.totalDist = self.distance self.totalTime = self.ete def output(self): """Return tuple of 6 result strings""" return (self.course.angleStr(), self.heading.angleStr(), \ '%3.0f nmi' % self.distance, '%3.0f kts' % self.groundSpeed, \ self.ete.timeStr(), self.eta.timeStr(not self.is12hrClock)) def outputTotals(self): """Return tuple of 6 strings with total distance and time""" return ('', '', '%3.0f nmi' % self.totalDist, '', \ self.totalTime.timeStr(), '') def distanceToCourse(self, refWp): """Returns the min distance from refWp to this leg's course line""" distEnd1 = wpDistance(refWp, self.fromWp)[0] distEnd2 = wpDistance(refWp, self.toWp)[0] distEnd1, distEnd2 = min(distEnd1, distEnd2), max(distEnd1, distEnd2) # check for endpoint being closest point: if distEnd2**2 >= distEnd1**2 + self.distance**2: return distEnd1 sqrDist = distEnd1**2 - \ ((distEnd1**2 - distEnd2**2 + self.distance**2) / \ (2 * self.distance))**2 return math.sqrt(abs(sqrDist)) #################### Utility Fynctions ############### def wpDistance(fromWp, toWp): """Calculates distance between waypoints. Returns tuple of (distance, xDistance, yDistance)""" yDistance = (toWp.latitudeValue() - fromWp.latitudeValue()) * \ RouteLeg.nmLat xDistance = (math.cos(fromWp.latitudeValue() * math.pi/180) \ + math.cos(toWp.latitudeValue() * math.pi/180)) \ * (toWp.longitudeValue() - fromWp.longitudeValue()) \ * (RouteLeg.nmLng / 2) distance = math.sqrt(yDistance**2 + xDistance**2) return (distance, xDistance, yDistance)