Ejemplo n.º 1
0
def test_delwpt(start):
    global starttime, timer, wp2reached
    timelimit = 240
    if start:
        # Create a waypoint which does not lie on the aircraft path
        # And create a next waypoint to reach after that
        stack.stack("CRE KLM10 B747 52 4 000 FL99 150")
        stack.stack("DEFWPT WPDEL1 52.0 4.1")
        stack.stack("DEFWPT WPDEL2 52.1 4.2")
        stack.stack("DEFWPT WPDEL3 52.0 4.3")
        stack.stack("KLM10 ADDWPT WPDEL1")
        stack.stack("KLM10 ADDWPT WPDEL2")
        stack.stack("KLM10 ADDWPT WPDEL3")
        stack.stack("KLM10 DELWPT WPDEL2")
        starttime = timer
        wp2reached = False
    else:
        _, d2 = geo.qdrdist(52.1 ,4.2 ,traf.lat[0],traf.lon[0])
        if d2 < 1:
            wp2reached = True
        # When the aircraft reaches waypoint 3 within the time limit return success
        _, d = geo.qdrdist(52.0 ,4.3 ,traf.lat[0],traf.lon[0])
        wp3reached = d < 1 # d is measured in nm
        if timer-starttime>timelimit or wp3reached:
            return wp3reached and not wp2reached
Ejemplo n.º 2
0
def test_direct(start):
    global starttime, timer, wp1reached
    timelimit = 350
    if start:
        # Create a waypoint which does not lie on the aircraft path
        # And create the waypoint to direct to
        stack.stack("CRE KLM10 B747 52 4 000 FL99 150")
        stack.stack("DEFWPT WPDIR1 52.2 4.1")
        stack.stack("DEFWPT WPDIR2 52.2 3.9")
        stack.stack("KLM10 ADDWPT WPDIR1")
        stack.stack("KLM10 ADDWPT WPDIR2")
        stack.stack("KLM10 DIRECT WPDIR2")
        starttime = timer
        wp1reached = False
    else:
        # If close to WP1, save that WP1 is reached
        _, d1 = geo.qdrdist(52.2 , 4.1 ,traf.lat[0],traf.lon[0])
        if d1 < 1:
            wp1reached = True

        # When the aircraft reaches waypoint 2 within the time limit, return success
        _, d2 = geo.qdrdist(52.2 , 3.9 ,traf.lat[0],traf.lon[0])
        closeenough = d2 < 1 # d is measured in nm
        if timer-starttime>timelimit or closeenough:
            return closeenough and not wp1reached
Ejemplo n.º 3
0
    def ll2xy(self, lat, lon):
        if not self.swnavdisp:
            # RADAR mode:
            # Convert lat/lon to pixel x,y

            # Normal case
            if self.lon1 > self.lon0:
                x = self.width * (lon - self.lon0) / (self.lon1 - self.lon0)

            # Wrap around:
            else:
                dellon = 180. - self.lon0 + self.lon1 + 180.
                xlon = lon + (lon < 0.) * 360.
                x = (xlon - self.lon0) / dellon * self.width

            y = self.height * (self.lat1 - lat) / (self.lat1 - self.lat0)
        else:
            # NAVDISP mode:
            qdr, dist = geo.qdrdist(self.ndlat, self.ndlon, lat, lon)
            alpha = np.radians(qdr - self.ndcrs)
            base = 30. * (self.lat1 - self.lat0)
            x = dist * np.sin(alpha) / base * self.height + self.width / 2
            y = -dist * np.cos(alpha) / base * self.height + self.height / 2

        return np.rint(x), np.rint(y)
Ejemplo n.º 4
0
    def calcfp(self):
        """Do flight plan calculations"""
#        self.delwpt("T/D")
#        self.delwpt("T/C")

        # Direction to waypoint
        self.nwp = len(self.wpname)

        # Create flight plan calculation table
        self.wpdirfrom   = self.nwp*[0.]
        self.wpdistto    = self.nwp*[0.]
        self.wpialt      = self.nwp*[-1]
        self.wptoalt     = self.nwp*[-999.]
        self.wpxtoalt    = self.nwp*[1.]

        # No waypoints: make empty variables to be safe and return: nothing to do
        if self.nwp==0:
            return

        # Calculate lateral leg data
        # LNAV: Calculate leg distances and directions

        for i in range(0, self.nwp - 1):
            qdr,dist = geo.qdrdist(self.wplat[i]  ,self.wplon[i],
                                self.wplat[i+1],self.wplon[i+1])
            self.wpdirfrom[i] = qdr
            self.wpdistto[i+1]  = dist #[nm]  distto is in nautical miles

        if self.nwp>1:
            self.wpdirfrom[-1] = self.wpdirfrom[-2]

        # Calclate longitudinal leg data
        # VNAV: calc next altitude constraint: index, altitude and distance to it
        ialt = -1
        toalt = -999.
        xtoalt = 0.
        for i in range(self.nwp-1,-1,-1):

            # waypoint with altitude constraint (dest of al specified)
            if self.wptype[i]==Route.dest:
                ialt   = i
                toalt  = 0.
                xtoalt = 0.                # [m]

            elif self.wpalt[i] >= 0:
                ialt   = i
                toalt  = self.wpalt[i]
                xtoalt = 0.                # [m]

            # waypoint with no altitude constraint:keep counting
            else:
                if i!=self.nwp-1:
                    xtoalt += self.wpdistto[i+1]*nm  # [m] xtoalt is in meters!
                else:
                    xtoalt = 0.0

            self.wpialt[i]   = ialt
            self.wptoalt[i]  = toalt   #[m]
            self.wpxtoalt[i] = xtoalt  #[m]
Ejemplo n.º 5
0
 def getnextqdr(self):
     # get qdr for next leg
     if -1 < self.iactwp < self.nwp - 1:
         nextqdr, dist = geo.qdrdist(\
                     self.wplat[self.iactwp],  self.wplon[self.iactwp],\
                     self.wplat[self.iactwp+1],self.wplon[self.iactwp+1])
     else:
         nextqdr = -999.
     return nextqdr
Ejemplo n.º 6
0
    def direct(self, idx, wpnam):
        """Set active point to a waypoint by name"""
        name = wpnam.upper().strip()
        if name != "" and self.wpname.count(name) > 0:
            wpidx = self.wpname.index(name)
            self.iactwp = wpidx

            bs.traf.actwp.lat[idx]   = self.wplat[wpidx]
            bs.traf.actwp.lon[idx]   = self.wplon[wpidx]
            bs.traf.actwp.flyby[idx] = self.wpflyby[wpidx]


            self.calcfp()
            bs.traf.ap.ComputeVNAV(idx, self.wptoalt[wpidx], self.wpxtoalt[wpidx])

            # If there is a speed specified, process it
            if self.wpspd[wpidx]>0.:
                # Set target speed for autopilot

                if self.wpalt[wpidx] < 0.0:
                    alt = bs.traf.alt[idx]
                else:
                    alt = self.wpalt[wpidx]

                # Check for valid Mach or CAS
                if self.wpspd[wpidx] <2.0:
                    cas = mach2cas(self.wpspd[wpidx], alt)
                else:
                    cas = self.wpspd[wpidx]

                # Save it for next leg
                bs.traf.actwp.spd[idx] = cas

                # When already in VNAV: fly it
                if bs.traf.swvnav[idx]:
                    bs.traf.selspd[idx]=cas

            # No speed specified for next leg
            else:
                 bs.traf.actwp.spd[idx] = -999.


            qdr, dist = geo.qdrdist(bs.traf.lat[idx], bs.traf.lon[idx],
                                bs.traf.actwp.lat[idx], bs.traf.actwp.lon[idx])

            turnrad = bs.traf.tas[idx]*bs.traf.tas[idx]/tan(radians(25.)) / g0 / nm  # [nm]default bank angle 25 deg

            bs.traf.actwp.turndist[idx] = (bs.traf.actwp.flyby[idx] > 0.5)  *   \
                     turnrad*abs(tan(0.5*radians(max(5., abs(degto180(qdr -
                        self.wpdirfrom[self.iactwp]))))))    # [nm]


            bs.traf.swlnav[idx] = True
            return True
        else:
            return False, "Waypoint " + wpnam + " not found"
Ejemplo n.º 7
0
def test_addwpt(start):
    global starttime, timer
    timelimit = 120
    if start:
        # Create a waypoint which does not lie on the aircraft path
        stack.stack("CRE KLM10 B747 52 4 000 FL99 150")
        stack.stack("KLM10 ADDWPT 52 4.1")
        starttime = timer
    else:
        # When the aircraft reaches the waypoint, return success
        _, d = geo.qdrdist(52,4.1,traf.lat[0],traf.lon[0])
        closeenough = d < 0.2 # d is measured in nm
        if timer-starttime>timelimit or closeenough:
            return closeenough
Ejemplo n.º 8
0
def test_wind(start):
    global starttime, timer
    timelimit = 20
    if start:
        # Create a very strong headwind
        cas = 250
        tas = vcas2tas(cas*kts,10000*ft)/kts
        stack.stack("CRE KLM10 B747 52 4 000 FL100 "+str(int(cas)))
        # Mind that wind is defined in the direction that it is coming from
        stack.stack("WIND 52 4 FL100 000 "+str(int(tas)))
        starttime = timer
    elif timer-starttime>timelimit:
        # If the aircraft did not move 
        _, d = geo.qdrdist(52,4,traf.lat[0],traf.lon[0])
        return d < 0.1 # d is measured in nm
Ejemplo n.º 9
0
def test_after(start):
    global starttime, timer
    timelimit = 240
    lowertimelimit = 120
    if start:
        # Create a waypoint which does not lie on the aircraft path
        # And create a next waypoint to reach after that
        stack.stack("CRE KLM10 B747 52 4 000 FL99 150")
        stack.stack("DEFWPT TESTWP 52 4.1")
        stack.stack("KLM10 ADDWPT TESTWP")
        stack.stack("KLM10 AFTER TESTWP ADDWPT 51.9 4.1")
        starttime = timer
    else:
        # When the aircraft reaches the waypoint, return success
        _, d = geo.qdrdist(51.9,4.1,traf.lat[0],traf.lon[0])
        closeenough = d < 0.2 # d is measured in nm
        if timer-starttime>timelimit or closeenough:
            return closeenough and timer-starttime>lowertimelimit
Ejemplo n.º 10
0
def test_before(start):
    global starttime, timer
    timelimit = 240
    if start:
        # Create a waypoint which does not lie on the aircraft path
        # And create a next waypoint to reach after that
        stack.stack("CRE KLM10 B747 52 4 000 FL99 150")
        stack.stack("DEFWPT WP1 52.0 4.1")
        stack.stack("DEFWPT WP2 52.1 4.2")
        stack.stack("DEFWPT WP3 52.0 4.3")
        stack.stack("KLM10 ADDWPT WP1")
        stack.stack("KLM10 AFTER WP1 ADDWPT WP3")
        stack.stack("KLM10 BEFORE WP3 ADDWPT WP2")
        starttime = timer
    else:
        # When the aircraft reaches waypoint 3 within the time limit return success
        _, d = geo.qdrdist(52.1 ,4.2 ,traf.lat[0],traf.lon[0])
        closeenough = d < 4 # d is measured in nm
        if timer-starttime>timelimit or closeenough:
            return closeenough
Ejemplo n.º 11
0
def minTLOS(asas, traf, i, i_other, x1, y1, x, y):
    """ This function calculates the aggregated TLOS for all resolution points """
    # Get speeds of other AC in range
    x_other = traf.gseast[i_other]
    y_other = traf.gsnorth[i_other]
    # Get relative bearing [deg] and distance [nm]
    qdr, dist = geo.qdrdist(traf.lat[i], traf.lon[i], traf.lat[i_other], traf.lon[i_other])
    # Convert to SI
    qdr = np.deg2rad(qdr)
    dist *= nm
    # For vectorization, store lengths as W and L
    W = np.shape(x)[0]
    L = np.shape(x_other)[0]
    # Relative speed-components
    du = np.dot(x_other.reshape((L,1)),np.ones((1,W))) - np.dot(np.ones((L,1)),x.reshape((1,W)))
    dv = np.dot(y_other.reshape((L,1)),np.ones((1,W))) - np.dot(np.ones((L,1)),y.reshape((1,W)))
    # Relative speed + zero check
    vrel2 = du * du + dv * dv
    vrel2 = np.where(np.abs(vrel2) < 1e-6, 1e-6, vrel2)  # limit lower absolute value
    # X and Y distance
    dx = np.dot(np.reshape(dist*np.sin(qdr),(L,1)),np.ones((1,W)))
    dy = np.dot(np.reshape(dist*np.cos(qdr),(L,1)),np.ones((1,W)))
    # Time to CPA
    tcpa = -(du * dx + dv * dy) / vrel2
    # CPA distance
    dcpa2 = np.square(np.dot(dist.reshape((L,1)),np.ones((1,W)))) - np.square(tcpa) * vrel2
    # Calculate time to LOS
    R2 = asas.R * asas.R
    swhorconf = dcpa2 < R2
    dxinhor = np.sqrt(np.maximum(0,R2-dcpa2))
    dtinhor = dxinhor / np.sqrt(vrel2)
    tinhor = np.where(swhorconf, tcpa-dtinhor, 0.)
    tinhor = np.where(tinhor > 0, tinhor, 1e6)
    # Get index of best solution
    idx = np.argmax(np.sum(tinhor,0))

    return idx
Ejemplo n.º 12
0
    def update(self):
        # FMS LNAV mode:
        # qdr[deg],distinnm[nm]
        qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat, bs.traf.actwp.lon)  # [deg][nm])
        self.qdr2wp  = qdr
        self.dist2wp = distinnm*nm  # Conversion to meters

        # FMS route update and possibly waypoint shift. Note: qdr, dist2wp will be updated accordingly in case of wp switch
        self.update_fms(qdr, self.dist2wp) # Updates self.qdr2wp when necessary

        #================= Continuous FMS guidance ========================

        # Waypoint switching in the loop above was scalar (per a/c with index i)
        # Code below is vectorized, with arrays for all aircraft

        # Do VNAV start of descent check
        #dy = (bs.traf.actwp.lat - bs.traf.lat)  #[deg lat = 60 nm]
        #dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat #[corrected deg lon = 60 nm]
        #self.dist2wp   = 60. * nm * np.sqrt(dx * dx + dy * dy) # [m]

        # VNAV logic: descend as late as possible, climb as soon as possible
        startdescent = (self.dist2wp < self.dist2vs) + (bs.traf.actwp.nextaltco > bs.traf.alt)

        # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
        #    (because there are no more waypoints). This is needed
        #    to continue descending when you get into a conflict
        #    while descending to the destination (the last waypoint)
        #    Use 0.1 nm (185.2 m) circle in case turndist might be zero
        self.swvnavvs = bs.traf.swvnav * np.where(bs.traf.swlnav, startdescent,
                                        self.dist2wp <= np.maximum(185.2,bs.traf.actwp.turndist))

        #Recalculate V/S based on current altitude and distance to next alt constraint
        # How much time do we have before we need to descend?

        t2go2alt = np.maximum(0.,(self.dist2wp + bs.traf.actwp.xtoalt - bs.traf.actwp.turndist)) \
                                    / np.maximum(0.5,bs.traf.gs)

        # use steepness to calculate V/S unless we need to descend faster
        bs.traf.actwp.vs = np.maximum(self.steepness*bs.traf.gs, \
                                np.abs((bs.traf.actwp.nextaltco-bs.traf.alt))  \
                                /np.maximum(1.0,t2go2alt))

        self.vnavvs  = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
        #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

        # self.vs = np.where(self.swvnavvs, self.vnavvs, self.vsdef * bs.traf.limvs_flag)
        selvs    = np.where(abs(bs.traf.selvs) > 0.1, bs.traf.selvs, self.vsdef) # m/s
        self.vs  = np.where(self.swvnavvs, self.vnavvs, selvs)
        self.alt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco, bs.traf.selalt)

        # When descending or climbing in VNAV also update altitude command of select/hold mode
        bs.traf.selalt = np.where(self.swvnavvs,bs.traf.actwp.nextaltco,bs.traf.selalt)

        # LNAV commanded track angle
        self.trk = np.where(bs.traf.swlnav, self.qdr2wp, self.trk)

        # FMS speed guidance: anticipate accel/decel distance for next leg or turn

        # Calculate actual distance it takes to decelerate/accelerate based on two cases: turning speed (decel)

        # Normally next leg speed (actwp.spd) but in case we fly turns with a specified turn speed
        # use the turn speed

        # Is turn speed specified and are we not already slow enough? We only decelerate for turns, not accel.
        turntas       = np.where(bs.traf.actwp.turnspd>0.0, vcas2tas(bs.traf.actwp.turnspd, bs.traf.alt),
                                 -1.0+0.*bs.traf.tas)
        swturnspd     = bs.traf.actwp.flyturn*(turntas>0.0)*(bs.traf.actwp.turnspd>0.0)
        turntasdiff   = np.maximum(0.,(bs.traf.tas - turntas)*(turntas>0.0))

        # t = (v1-v0)/a ; x = v0*t+1/2*a*t*t => dx = (v1*v1-v0*v0)/ (2a)
        dxturnspdchg = distaccel(turntas,bs.traf.tas, bs.traf.perf.axmax)
#        dxturnspdchg = 0.5*np.abs(turntas*turntas-bs.traf.tas*bs.traf.tas)/(np.sign(turntas-bs.traf.tas)*np.maximum(0.01,np.abs(ax)))
#        dxturnspdchg  = np.where(swturnspd, np.abs(turntasdiff)/np.maximum(0.01,ax)*(bs.traf.tas+0.5*np.abs(turntasdiff)),
#                                                                   0.0*bs.traf.tas)

        # Decelerate or accelerate for next required speed because of speed constraint or RTA speed
        # Note that because nextspd comes from the stack, and can be either a mach number or
        # a calibrated airspeed, it can only be converted from Mach / CAS [kts] to TAS [m/s]
        # once the altitude is known.
        nexttas = vcasormach2tas(bs.traf.actwp.nextspd, bs.traf.alt)

#        tasdiff   = (nexttas - bs.traf.tas)*(bs.traf.actwp.spd>=0.) # [m/s]


        # t = (v1-v0)/a ; x = v0*t+1/2*a*t*t => dx = (v1*v1-v0*v0)/ (2a)

        dxspdconchg = distaccel(bs.traf.tas, nexttas, bs.traf.perf.axmax)

        # Check also whether VNAVSPD is on, if not, SPD SEL has override for next leg
        # and same for turn logic
        usenextspdcon = (self.dist2wp < dxspdconchg)*(bs.traf.actwp.nextspd>-990.) * \
                            bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav
        useturnspd = np.logical_or(bs.traf.actwp.turntonextwp,\
                                   (self.dist2wp < dxturnspdchg+bs.traf.actwp.turndist) * \
                                        swturnspd*bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav)

        # Hold turn mode can only be switched on here, cannot be switched off here (happeps upon passing wp)
        bs.traf.actwp.turntonextwp = np.logical_or(bs.traf.actwp.turntonextwp,useturnspd)

        # Which CAS/Mach do we have to keep? VNAV, last turn or next turn?
        oncurrentleg = (abs(degto180(bs.traf.trk - qdr)) < 2.0) # [deg]
        inoldturn    = (bs.traf.actwp.oldturnspd > 0.) * np.logical_not(oncurrentleg)

        # Avoid using old turning speeds when turning of this leg to the next leg
        # by disabling (old) turningspd when on leg
        bs.traf.actwp.oldturnspd = np.where(oncurrentleg*(bs.traf.actwp.oldturnspd>0.), -998.,
                                            bs.traf.actwp.oldturnspd)

        # turnfromlastwp can only be switched off here, not on (latter happens upon passing wp)
        bs.traf.actwp.turnfromlastwp = np.logical_and(bs.traf.actwp.turnfromlastwp,inoldturn)

        # Select speed: turn sped, next speed constraint, or current speed constraint
        bs.traf.selspd = np.where(useturnspd,bs.traf.actwp.turnspd,
                                  np.where(usenextspdcon, bs.traf.actwp.nextspd,
                                           np.where((bs.traf.actwp.spdcon>=0)*bs.traf.swvnavspd,bs.traf.actwp.spd,
                                                                            bs.traf.selspd)))

        # Temporary override when still in old turn
        bs.traf.selspd = np.where(inoldturn*(bs.traf.actwp.oldturnspd>0.)*bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav,
                                  bs.traf.actwp.oldturnspd,bs.traf.selspd)


        #debug if inoldturn[0]:
        #debug     print("inoldturn bs.traf.trk =",bs.traf.trk[0],"qdr =",qdr)
        #debug elif usenextspdcon[0]:
        #debug     print("usenextspdcon")
        #debug elif useturnspd[0]:
        #debug     print("useturnspd")
        #debug elif bs.traf.actwp.spdcon>0:
        #debug     print("using current speed constraint")
        #debug else:
        #debug     print("no speed given")

        # Below crossover altitude: CAS=const, above crossover altitude: Mach = const
        self.tas = vcasormach2tas(bs.traf.selspd, bs.traf.alt)
Ejemplo n.º 13
0
    def update(self):
        ''' Periodic update function for metrics calculation. '''
        self.sectorsd = np.zeros(len(self.sectors))
        self.sectorconv = np.zeros(len(self.sectors))
        self.sectoreff = []
        if not traf.ntraf or not self.sectors:
            return

        # Check convergence using CD with large RPZ and tlook
        confpairs, lospairs, inconf, tcpamax, qdr, dist, dcpa, tcpa, tLOS = \
            traf.cd.detect(traf, traf, np.ones(traf.ntraf) * 20 * nm, traf.cd.hpz, np.ones(traf.ntraf) * 3600)

        if confpairs:
            own, intr = zip(*confpairs)
            ownidx = traf.id2idx(own)
            mask = traf.alt[ownidx] > 70 * ft
            ownidx = np.array(ownidx)[mask]
            dcpa = np.array(dcpa)[mask]
            tcpa = np.array(tcpa)[mask]
        else:
            ownidx = np.array([])

        sendeff = False
        for idx, (sector,
                  previnside) in enumerate(zip(self.sectors, self.acinside)):
            inside = areafilter.checkInside(sector, traf.lat, traf.lon,
                                            traf.alt)

            sectoreff = []
            # Detect aircraft leaving and entering the sector
            previds = set(previnside.acid)
            ids = set(np.array(traf.id)[inside])
            arrived = list(ids - previds)
            left = previds - ids

            # Split aircraft that left the sector in deleted and not deleted
            left_intraf = left.intersection(traf.id)
            left_del = list(
                left - left_intraf)  # Aircraft id's prev inside but deleted
            left_intraf = list(left_intraf)  # Aircraft id's that left sector

            # New a/c in sector arr listed by index in arridx
            arridx = traf.id2idx(arrived)
            leftidx = traf.id2idx(left_intraf)

            # Retrieve the current distance flown for arriving and leaving aircraft
            arrdist = traf.distflown[arridx]
            arrlat = traf.lat[arridx]
            arrlon = traf.lon[arridx]

            # Get all a/c ids that left from the set delac
            leftlat, leftlon, leftdist = self.delac.get(left_del)
            leftlat = np.append(leftlat, traf.lat[leftidx])
            leftlon = np.append(leftlon, traf.lon[leftidx])
            leftdist = np.append(leftdist, traf.distflown[leftidx])
            leftlat0, leftlon0, leftdist0 = previnside.get(left_del +
                                                           left_intraf)
            self.delac.delete(left_del)

            if len(left) > 0:

                # Exclude aircraft where origin = destination for sector efficiency,
                # so require that distance start-end > 10 nm
                q, d = geo.qdrdist(leftlat0, leftlon0, leftlat, leftlon)
                mask = d > 10

                sectoreff = list(
                    (leftdist[mask] - leftdist0[mask]) / d[mask] / nm)
                names = np.array(left_del + left_intraf)[mask]

                for name, eff in zip(names, sectoreff):
                    self.feff.write('{}, {}, {}\n'.format(sim.simt, name, eff))
                sendeff = True

                # print('{} aircraft left sector {}, distance flown (acid:dist):'.format(len(left), sector))
                # for a, d0, d1, e in zip(left, leftdist0, leftdist, sectoreff):
                #     print('Aircraft {} flew {} meters (eff = {})'.format(a, round(d1-d0), e))

            # Update inside data for this sector
            previnside.delete(left)
            previnside.extend(arrived, arrlat, arrlon, arrdist)

            self.sectoreff.append(sectoreff)

            self.sectorsd[idx] = np.count_nonzero(inside)
            insidx = np.where(np.logical_and(inside, inconf))
            pairsinside = np.isin(ownidx, insidx)
            if len(pairsinside):
                tnorm = np.array(tcpa)[pairsinside] / 300.0
                dcpanorm = np.array(dcpa)[pairsinside] / (5.0 * nm)
                self.sectorconv[idx] = np.sum(
                    np.sqrt(2.0 / tnorm * tnorm + dcpanorm * dcpanorm))
            else:
                self.sectorconv[idx] = 0

            self.fconv.write('{}, {}\n'.format(sim.simt, self.sectorconv[idx]))
            self.fsd.write('{}, {}\n'.format(sim.simt, self.sectorsd[idx]))
        if sendeff:
            self.effplot.send()
Ejemplo n.º 14
0
    def newcalcfp(self):
        """Do flight plan calculations"""

        # Remove old top of descents and old top of climbs
        while self.wpname.count("T/D") > 0:
            self.delwpt("T/D")

        while self.wpname.count("T/C") > 0:
            self.delwpt("T/C")

        # Remove old actual position waypoints
        while self.wpname.count("A/C") > 0:
            self.delwpt("A/C")

        # Insert actual position as A/C waypoint
        idx = self.iactwp
        self.insertcalcwp(idx, "A/C")
        self.wplat[idx] = bs.traf.lat[self.iac]  # deg
        self.wplon[idx] = bs.traf.lon[self.iac]  # deg
        self.wpalt[idx] = bs.traf.alt[self.iac]  # m
        self.wpspd[idx] = bs.traf.tas[self.iac]  # m/s

        # Calculate distance to last waypoint in route
        nwp = len(self.wpname)
        dist2go = [0.0]
        for i in range(nwp - 2, -1, -1):
            qdr, dist = geo.qdrdist(self.wplat[i], self.wplon[i],
                                    self.wplat[i + 1], self.wplon[i + 1])
            dist2go = [dist2go[0] + dist] + dist2go

        # Make VNAV WP list with only waypoints with altitude constraints
        # This list we will use to find where to insert t/c and t/d
        alt = []
        x = []
        name = []
        for i in range(nwp):
            if self.wpalt[i] > -1.:
                alt.append(self.wpalt[i])
                x.append(dist2go[i])
                name.append(self.wpname[i] +
                            " ")  # space for check first 2 chars later

        # Find where to insert cruise segment (if any)

        # Find longest segment without altitude constraints

        desslope = clbslope = 1.
        crzalt = bs.traf.crzalt[self.iac]
        if crzalt > 0.:
            ilong = -1
            dxlong = 0.0

            nvwp = len(alt)
            for i in range(nvwp - 1):
                if x[i] - x[i + 1] > dxlong:
                    ilong = i
                    dxlong = x[i] - x[i + 1]

            # VNAV parameters to insert T/Cs and T/Ds
            crzdist = 20. * nm  # minimally required distance at cruise level
            clbslope = 3000. * ft / (10. * nm)  # 1:3 rule for now
            desslope = clbslope  # 1:3 rule for now

            # Can we get a sufficient distance at cruise altitude?
            if max(alt[ilong], alt[ilong + 1]) < crzalt:
                dxclimb = (crzalt - alt[ilong]) * clbslope
                dxdesc = (crzalt - alt[ilong + 1]) * desslope
                if x[ilong] - x[ilong + 1] > dxclimb + crzdist + dxdesc:

                    # Insert T/C (top of climb) at cruise level
                    name.insert(ilong + 1, "T/C")
                    alt.insert(ilong + 1, crzalt)
                    x.insert(ilong + 1, x[ilong] + dxclimb)

                    # Insert T/D (top of descent) at cruise level
                    name.insert(ilong + 2, "T/D")
                    alt.insert(ilong + 2, crzalt)
                    x.insert(ilong + 2, x[ilong + 1] - dxdesc)

        # Compare angles to rates:
        epsh = 50. * ft  # Nothing to be done for small altitude changes
        epsx = 1. * nm  # [m] Nothing to be done at this short range
        i = 0
        while i < len(alt) - 1:
            if name[i][:2] == "T/":
                continue

            dy = alt[i + 1] - alt[i]  # alt change (pos = climb)
            dx = x[i] - x[i + 1]  # distance (positive)

            dxdes = abs(dy) / desslope
            dxclb = abs(dy) / clbslope

            if dy < epsh and dx + epsx > dxdes:  # insert T/D?

                name.insert(i + 1, "T/D")
                alt.insert(i + 1, alt[i])
                x.insert(i + 1, x[i + 1] - dxdes)
                i += 1

            elif dy > epsh and dx + epsx > dxclb:  # insert T/C?

                name.insert(i + 1, "T/C")
                alt.insert(i + 1, alt[i + 1])
                x.insert(i + 1, x[i] + dxclb)
                i += 2
            else:
                i += 1

        # Now insert T/Cs and T/Ds in actual flight plan
        nvwp = len(alt)
        for i in range(nvwp, -1, -1):

            # Copy all new waypoints (which are all named T/C or T/D)
            if name[i][:2] == "T/":

                # Find place in flight plan to insert T/C or T/D
                j = nvwp - 1
                while dist2go[j] < x[i] and j > 1:
                    j = j - 1

                # Interpolation factor for position on leg
                f = (x[i] - dist2go[j + 1]) / (dist2go[j] - dist2go[j + 1])

                lat = f * self.wplat[j] + (1. - f) * self.wplat[j + 1]
                lon = f * self.wplon[j] + (1. - f) * self.wplon[j + 1]

                self.wpname.insert(j, name[i])
                self.wptype.insert(j, Route.calcwp)
                self.wplat.insert(j, lat)
                self.wplon.insert(j, lon)
                self.wpalt.insert(j, alt[i])
                self.wpspd.insert(j, -999.)
Ejemplo n.º 15
0
    def __init__(self,name,cmd,cmdargs):
        self.name    = name.upper()
        self.tupdate = sim.simt-999.
        self.flow    = 0
        self.incircle = True
        self.segdir  = None  # Segment direction in degrees
        self.hdg     = None # When runway is used as separate airport

        # Is location a circle segment?
        if swcircle and self.name[:4]=="SEGM":
            self.type = "seg"
            self.lat,self.lon,brg = getseg(self.name)
            pass

        else:
            success, posobj = txt2pos(name,ctrlat,ctrlon)
            if success:
                # for runway type, get heading as default optional argument for command line
                if posobj.type == "rwy":
                    aptname, rwyname = name.split('/RW')
                    rwyname = rwyname.lstrip('Y')
                    try:
                        self.hdg = navdb.rwythresholds[aptname][rwyname][2]
                    except:
                        self.hdg = None
                        pass
                else:
                    rwyhdg = None


                self.lat,self.lon = posobj.lat, posobj.lon
                self.type = posobj.type

                # If outside circle change lat,lon to edge of circle
                self.incircle = incircle(self.lat,self.lon)
                if not self.incircle:
                    self.type = "seg"
                    self.segdir,dist = qdrdist(ctrlat,ctrlon,posobj.lat,posobj.lon)
                    segname = "SEGM"+str(int(round(self.segdir)))
                    self.lat,self.lon,hdg = getseg(segname)

            else:
                print("ERROR: trafgen class Source called for "+name+". Position not found")
                self.lat,self.lon = 0.0,0.0


        # Aircraft types
        self.actypes = ["*"]

        # Runways
        self.runways    = [] # name
        self.rwylat     = []
        self.rwylon     = []
        self.rwyhdg     = []
        self.rwyline    = [] # number of aircraft waiting in line
        self.rwytotime  = [] # time of last takeoff

        self.dtakeoff = 90. # sec take-off interval on one runway, default 90 sec

        # Destinations
        self.dest        = []
        self.destlat     = []
        self.destlon     = []
        self.desttype    = [] # "apt","wpt","seg"
        self.desthdg     = []  # if dest is a runway (for flights within FIR) or circle segment (source outside FIR)
        self.destactypes = []   # Types for this destinations ([]=use defaults for this source)

        #Names of drawing objects runways
        self.polys       = []    # Names of current runway polygons to remove when runways change

        # Start values
        self.startaltmin = -999. # [ft] minimum starting altitude
        self.startaltmax = -999. # [ft] maximum starting altitude
        self.startspdmin = -999. # [m/s] minimum starting speed
        self.startspdmax = -999. # [m/s] maximum starting speed
        self.starthdgmin = -999. # [deg] Valid values -360 - 360 degrees (to also have an interval possible around 360)
        self.starthdgmax = -999. # [deg] Valid values -360 - 360 degrees (to also have an interval possible around 360)


        return
Ejemplo n.º 16
0
    def calcfp(
        self
    ):  # Current Flight Plan calculations, which actualize based on flight condition
        """Do flight plan calculations"""
        #        self.delwpt("T/D")
        #        self.delwpt("T/C")

        # Direction to waypoint
        self.nwp = len(self.wpname)

        # Create flight plan calculation table
        self.wpdirfrom = self.nwp * [0.]
        self.wpdistto = self.nwp * [0.]
        self.wpialt = self.nwp * [-1]
        self.wptoalt = self.nwp * [-999.]
        self.wpxtoalt = self.nwp * [1.]  # Avoid division by zero
        self.wpirta = self.nwp * [-1]
        self.wptorta = self.nwp * [-999.]
        self.wpxtorta = self.nwp * [1.]  #[m] Avoid division by zero

        # No waypoints: make empty variables to be safe and return: nothing to do
        if self.nwp == 0:
            return

        # Calculate lateral leg data
        # LNAV: Calculate leg distances and directions

        for i in range(0, self.nwp - 1):
            qdr, dist = geo.qdrdist(self.wplat[i], self.wplon[i],
                                    self.wplat[i + 1], self.wplon[i + 1])
            self.wpdirfrom[i] = qdr
            self.wpdistto[i + 1] = dist  #[nm]  distto is in nautical miles

        if self.nwp > 1:
            self.wpdirfrom[-1] = self.wpdirfrom[-2]

        # Calculate longitudinal leg data
        # VNAV: calc next altitude constraint: index, altitude and distance to it
        ialt = -1  # index to waypoint with next altitude constraint
        toalt = -999.  # value of next altitude constraint
        xtoalt = 0.  # distance to next altitude constraint from this wp
        for i in range(self.nwp - 1, -1, -1):

            # waypoint with altitude constraint (dest of al specified)
            if self.wptype[i] == Route.dest:
                ialt = i
                toalt = 0.
                xtoalt = 0.  # [m]

            elif self.wpalt[i] >= 0:
                ialt = i
                toalt = self.wpalt[i]
                xtoalt = 0.  # [m]

            # waypoint with no altitude constraint:keep counting
            else:
                if i != self.nwp - 1:
                    xtoalt = xtoalt + self.wpdistto[
                        i + 1] * nm  # [m] xtoalt is in meters!
                else:
                    xtoalt = 0.0

            self.wpialt[i] = ialt
            self.wptoalt[i] = toalt  #[m]
            self.wpxtoalt[i] = xtoalt  #[m]

        # RTA: calc next rta constraint: index, altitude and distance to it
        # If any RTA.
        if any(array(self.wprta) >= 0.0):
            #print("Yes, I found RTAs")
            irta = -1  # index of wp
            torta = -999.  # next rta value
            xtorta = 0.  # distance to next rta
            for i in range(self.nwp - 1, -1, -1):

                # waypoint with rta: reset counter, update rts
                if self.wprta[i] >= 0:
                    irta = i
                    torta = self.wprta[i]
                    xtorta = 0.  # [m]

                # waypoint with no altitude constraint:keep counting
                else:
                    if i != self.nwp - 1:
                        # No speed or rta constraint: add to xtorta
                        if self.wpspd[i] <= 0.0:
                            xtorta = xtorta + self.wpdistto[
                                i + 1] * nm  # [m] xtoalt is in meters!
                        else:
                            # speed constraint on this leg: shift torta to account for this
                            # altitude unknown
                            if self.wptoalt[i] > 0.:
                                alt = toalt
                            else:
                                # TODO: current a/c altitude would be better guess, but not accessible here
                                # as we do not know aircraft index for this route
                                alt = 10000. * ft  # default to minimize errors, when no alt constraints are present
                            legtas = casormach2tas(self.wpspd[i], alt)
                            #TODO: account for wind at this position vy adding wind vectors to waypoints?

                            # xtorta stays the same! This leg will not be available for RTA scheduling, so distance
                            # is not in xtorta. Therefore we need to subtract legtime to ignore this leg for the RTA
                            # scheduling
                            legtime = self.wpdistto[i + 1] / legtas
                            torta = torta - legtime
                    else:
                        xtorta = 0.0
                        torta = -999.0

                self.wpirta[i] = irta
                self.wptorta[i] = torta  # [s]
                self.wpxtorta[i] = xtorta  # [m]
Ejemplo n.º 17
0
    def update_fms(self, qdr, dist, dt=bs.settings.fms_dt):
        # Shift waypoints for aircraft i where necessary
        for i in bs.traf.actwp.Reached(qdr, dist, bs.traf.actwp.flyby):
            # Save current wp speed for use on next leg when we pass this waypoint
            # VNAV speeds are always FROM-speed, so we accelerate/decellerate at the waypoint
            # where this speed is specified, so we need to save it for use now
            # before getting the new data for the next waypoint

            # Save speed as specified for the waypoint we pass
            oldspd = bs.traf.actwp.spd[i]

            # Get next wp (lnavon = False if no more waypoints)
            lat, lon, alt, spd, bs.traf.actwp.xtoalt[i], toalt, \
                lnavon, flyby, bs.traf.actwp.next_qdr[i] =  \
                self.route[i].getnextwp()  # note: xtoalt,toalt in [m]

            # End of route/no more waypoints: switch off LNAV
            bs.traf.swlnav[i] = bs.traf.swlnav[i] and lnavon

            # In case of no LNAV, do not allow VNAV mode on its own
            bs.traf.swvnav[i] = bs.traf.swvnav[i] and bs.traf.swlnav[i]

            bs.traf.actwp.lat[i] = lat  # [deg]
            bs.traf.actwp.lon[i] = lon  # [deg]
            # 1.0 in case of fly by, else fly over
            bs.traf.actwp.flyby[i] = int(flyby)

            # User has entered an altitude for this waypoint
            if alt >= -0.01:
                bs.traf.actwp.nextaltco[i] = alt  # [m]

            if spd > -990. and bs.traf.swlnav[i] and bs.traf.swvnav[i]:

                # Valid speed and LNAV and VNAV ap modes are on
                # Depending on crossover altitude we fix CAS or Mach
                if bs.traf.abco[i] and spd > 1.0:
                    bs.traf.actwp.spd[i] = cas2mach(spd, bs.traf.alt[i])

                elif bs.traf.belco[i] and 0. < spd <= 1.0:
                    bs.traf.actwp.spd[i] = mach2cas(spd, bs.traf.alt[i])

                else:
                    bs.traf.actwp.spd[i] = spd

            else:
                bs.traf.actwp.spd[i] = -999.

            # VNAV spd mode: use speed of this waypoint as commanded speed
            # while passing waypoint and save next speed for passing next wp
            # Speed is now from speed! Next speed is ready in wpdata
            if bs.traf.swvnav[i] and oldspd > 0.0:
                    bs.traf.selspd[i] = oldspd

            # Update qdr and turndist for this new waypoint for ComputeVNAV
            qdr[i], dummy = geo.qdrdist(bs.traf.lat[i], bs.traf.lon[i],
                                        bs.traf.actwp.lat[i], bs.traf.actwp.lon[i])

            # Update turndist so ComputeVNAV wokrs, is there a next leg direction or not?
            if bs.traf.actwp.next_qdr[i] < -900.:
                local_next_qdr = qdr[i]
            else:
                local_next_qdr = bs.traf.actwp.next_qdr[i]

            # Calculate turn dist 9and radius which we do not use) now for scalar variable [i]
            bs.traf.actwp.turndist[i], dummy = \
                bs.traf.actwp.calcturn(bs.traf.tas[i], bs.traf.bank[i],
                                        qdr[i], local_next_qdr)  # update turn distance for VNAV

            # VNAV = FMS ALT/SPD mode
            self.ComputeVNAV(i, toalt, bs.traf.actwp.xtoalt[i])
Ejemplo n.º 18
0
    def update(self):
        if self.ncond==0:
            return

        # Update indices based on list of id's
        acidxlst = np.array(bs.traf.id2idx(self.id))
        if len(acidxlst)>0:
            idelcond = sorted(list(np.where(acidxlst<0)[0]))
            for i in idelcond[::-1]:
                del (self.id[i])
                self.condtype = np.delete(self.condtype, i)
                self.target = np.delete(self.target, i)
                self.lastdif = np.delete(self.lastdif, i)
                del self.posdata[i]
                del self.cmd[i]

            self.ncond = len(self.id)
            if self.ncond==0:
                return
            acidxlst = np.array(bs.traf.id2idx(self.id))

        # Check condition types
        actdist = np.ones(self.ncond)*999e9  # Invalid number which never triggers anything is extremely large
        for j in range(self.ncond):
            if self.condtype[j] == postype:
                qdr,dist = qdrdist(bs.traf.lat[acidxlst[j]],bs.traf.lon[acidxlst[j]],self.posdata[j][0],self.posdata[j][1])
                actdist[j] = dist # [nm]

        # Get relevant actual value using index list as index to numpy arrays
        self.actual = (self.condtype == alttype) * bs.traf.alt[acidxlst] + \
                      (self.condtype == spdtype) * bs.traf.cas[acidxlst] + \
                      (self.condtype == postype) * actdist

        # Compare sign of actual difference with sign of last difference
        actdif       = self.target - self.actual

        # Make sorted arrya of indices of true conditions and their conditional commands
        idxtrue      = sorted(list(np.where(actdif*self.lastdif <= 0.0)[0]))# Sign changed
        self.lastdif = actdif
        if idxtrue==None or len(idxtrue)==0:
            return


        # Execute commands found to have true condition
        for i in idxtrue:
            if i>=0:
                stack.stack(self.cmd[i])
                # debug
                # stack.stack(" ECHO Conditional command issued: "+self.cmd[i])

        # Delete executed commands to clean up arrays and lists
        # from highest index to lowest for consistency
        for i in idxtrue[::-1]:
            if i>=0:
                del self.id[i]
                self.condtype = np.delete(self.condtype,i)
                self.target   = np.delete(self.target,i)
                self.lastdif  = np.delete(self.lastdif,i)
                del self.posdata[i]
                del self.cmd[i]

        # Adjust number of conditions
        self.ncond = len(self.id)

        if self.ncond!=len(self.cmd):
            print ("self.ncond=",self.ncond)
            print ("self.cmd=",self.cmd)
            print ("traffic/conditional.py: self.delcondition: invalid condition array size")
        return
Ejemplo n.º 19
0
    def calcfp(self):
        """Do flight plan calculations"""
        #        self.delwpt("T/D")
        #        self.delwpt("T/C")

        # Direction to waypoint
        self.nwp = len(self.wpname)

        # Create flight plan calculation table
        self.wpdirfrom = self.nwp * [0.]
        self.wpdistto = self.nwp * [0.]
        self.wpialt = self.nwp * [-1]
        self.wptoalt = self.nwp * [-999.]
        self.wpxtoalt = self.nwp * [1.]

        # No waypoints: make empty variables to be safe and return: nothing to do
        if self.nwp == 0:
            return

        # Calculate lateral leg data
        # LNAV: Calculate leg distances and directions

        for i in range(0, self.nwp - 1):
            qdr,dist = geo.qdrdist(self.wplat[i]  ,self.wplon[i], \
                                self.wplat[i+1],self.wplon[i+1])
            self.wpdirfrom[i] = qdr
            self.wpdistto[i + 1] = dist  #[nm]  distto is in nautical miles

        if self.nwp > 1:
            self.wpdirfrom[-1] = self.wpdirfrom[-2]

        # Calclate longitudinal leg data
        # VNAV: calc next altitude constraint: index, altitude and distance to it
        ialt = -1
        toalt = -999.
        xtoalt = 0.
        for i in range(self.nwp - 1, -1, -1):

            # waypoint with altitude constraint (dest of al specified)
            if self.wptype[i] == self.dest:
                ialt = i
                toalt = 0.
                xtoalt = 0.  # [m]

            elif self.wpalt[i] >= 0:
                ialt = i
                toalt = self.wpalt[i]
                xtoalt = 0.  # [m]

            # waypoint with no altitude constraint:keep counting
            else:
                if i != self.nwp - 1:
                    xtoalt = xtoalt + self.wpdistto[
                        i + 1] * nm  # [m] xtoalt is in meters!
                else:
                    xtoalt = 0.0

            self.wpialt[i] = ialt
            self.wptoalt[i] = toalt  #[m]
            self.wpxtoalt[i] = xtoalt  #[m]

        return
Ejemplo n.º 20
0
    def update(self):
        # FMS LNAV mode:
        # qdr[deg],distinnm[nm]
        qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat,
                                    bs.traf.actwp.lon)  # [deg][nm])
        dist = distinnm * nm  # Conversion to meters

        # FMS route update
        self.update_fms(qdr, dist)

        #================= Continuous FMS guidance ========================

        # Waypoint switching in the loop above was scalar (per a/c with index i)
        # Code below is vectorized, with arrays for all aircraft

        # Do VNAV start of descent check
        dy = (bs.traf.actwp.lat - bs.traf.lat)  #[deg lat = 60 nm]
        dx = (bs.traf.actwp.lon -
              bs.traf.lon) * bs.traf.coslat  #[corrected deg lon = 60 nm]
        dist2wp = 60. * nm * np.sqrt(dx * dx + dy * dy)  # [m]
        #print("dist2wp =",dist2wp,"   self.dist2vs =",self.dist2vs)

        # VNAV logic: descend as late as possible, climb as soon as possible
        startdescent = (dist2wp < self.dist2vs) + (bs.traf.actwp.nextaltco >
                                                   bs.traf.alt)

        # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
        #    (because there are no more waypoints). This is needed
        #    to continue descending when you get into a conflict
        #    while descending to the destination (the last waypoint)
        #    Use 0.1 nm (185.2 m) circle in case turndist might be zero
        self.swvnavvs = bs.traf.swvnav * np.where(
            bs.traf.swlnav, startdescent,
            dist <= np.maximum(185.2, bs.traf.actwp.turndist))

        #Recalculate V/S based on current altitude and distance to next alt constraint
        # How much time do we have before we need to descend?

        t2go2alt = np.maximum(0.,(dist2wp + bs.traf.actwp.xtoalt - bs.traf.actwp.turndist)) \
                                    / np.maximum(0.5,bs.traf.gs)

        # use steepness to calculate V/S unless we need to descend faster
        bs.traf.actwp.vs = np.maximum(self.steepness*bs.traf.gs, \
                                np.abs((bs.traf.actwp.nextaltco-bs.traf.alt))  \
                                /np.maximum(1.0,t2go2alt))

        self.vnavvs = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
        #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

        # self.vs = np.where(self.swvnavvs, self.vnavvs, bs.traf.apvsdef * bs.traf.limvs_flag)
        selvs = np.where(
            abs(bs.traf.selvs) > 0.1, bs.traf.selvs, bs.traf.apvsdef)  # m/s
        self.vs = np.where(self.swvnavvs, self.vnavvs, selvs)
        self.alt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco,
                            bs.traf.selalt)

        # When descending or climbing in VNAV also update altitude command of select/hold mode
        bs.traf.selalt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco,
                                  bs.traf.selalt)

        # LNAV commanded track angle
        self.trk = np.where(bs.traf.swlnav, qdr, self.trk)

        # FMS speed guidance: anticipate accel distance

        # Actual distance it takes to decelerate
        nexttas = vcasormach2tas(bs.traf.actwp.spd, bs.traf.alt)
        tasdiff = nexttas - bs.traf.tas  # [m/s]
        dtspdchg = np.abs(tasdiff) / np.maximum(0.01, np.abs(bs.traf.ax))  #[s]
        dxspdchg = 0.5 * np.sign(tasdiff) * np.abs(
            bs.traf.ax) * dtspdchg * dtspdchg + bs.traf.tas * dtspdchg  #[m]

        # Chekc also whether VNAVSPD is on, if not, SPD SEL has override
        usespdcon = (dist2wp < dxspdchg) * (
            bs.traf.actwp.spd > -990.) * bs.traf.swvnavspd * bs.traf.swvnav

        bs.traf.selspd = np.where(usespdcon, bs.traf.actwp.spd, bs.traf.selspd)

        # Below crossover altitude: CAS=const, above crossover altitude: Mach = const
        self.tas = vcasormach2tas(bs.traf.selspd, bs.traf.alt)
Ejemplo n.º 21
0
    def update(self,gain):
        # Time step update of drain

        # Get time step
        dt = sim.simt - self.tupdate
        self.tupdate = sim.simt

        # Time for a new aircraft?
        if dt>0.0:

            # Calculate probability of a geberate occurring with flow
            chances = 1.0-gain*self.flow*dt/3600. #flow is in a/c per hour=360 seconds
            if random.random() >= chances:
                # Calculate starting position using origin
                if len(self.orig)>0:
                    # Add origin
                    iorig = int(random.random() * len(self.orig))
                else:
                    iorig = -1

                if iorig>=0:
                    incirc = self.origincirc[iorig]
                    lat,lon = self.origlat[iorig],self.origlon[iorig]
                    hdg,dist = qdrdist(lat,lon,self.lat,self.lon)
                else:
                    print("Warning update drain",self.name,"called with no origins present!")
                    hdg = random.random()*360.
                    print("using random segment",int(hdg+180)%360)

                    incirc = False

                if not incirc:
                    lat,lon = kwikpos(ctrlat,ctrlon,(hdg+180)%360,radius)
                elif self.origtype=="seg":
                    lat,lon,brg = getseg(self.name)
                    hdg = (brg+180)%360
                else:
                    hdg = random.random()*360.

                if incirc and (self.origtype[iorig]=="apt" or self.origtype[iorig]=="rwy"):
                    alttxt,spdtxt = str(0),str(0)
                else:
                    alttxt,spdtxt = "FL"+str(random.randint(200,300)), str(random.randint(250,350))

                if iorig>=0:
                    acid = randacname(self.orig[iorig], self.name)
                else:
                    acid = randacname("LFPG", self.name)

                if len(self.origactypes)>0:
                    actype = random.choice(self.origactypes[iorig])
                else:
                    actype = random.choice(self.actypes)

                stack.stack("CRE " + ",".join([acid,actype,str(lat), str(lon),
                                               str(int(hdg%360)),alttxt,spdtxt]))
                if iorig>=0:
                    if self.orig[iorig][:4]!="SEGM":
                        stack.stack(acid + " ORIG " + self.orig[iorig])
                    else:
                        stack.stack(acid + " ORIG " + str(self.origlat[iorig]) + " " +\
                                     str(self.origlat[iorig]))
                if not (self.name[:4]=="SEGM"):
                    stack.stack(acid + " DEST " + self.name)
                else:
                    stack.stack(acid + " ADDWPT " + str(self.lat) + " " + str(self.lon))

                if alttxt=="0" and spdtxt =="0":
                    stack.stack(acid+" SPD 250")
                    stack.stack(acid+" ALT FL100")
                    #stack.stack(acid+" LNAV ON")
                else:
                    stack.stack(acid + " LNAV ON")
Ejemplo n.º 22
0
    def __init__(self,name,cmd,cmdargs):
        self.name    = name.upper()
        self.tupdate = sim.simt-999.
        self.flow    = 0
        self.incircle = True
        self.segdir  = None  # Segment direction in degrees

        # Is location a circle segment?
        if self.name[:4]=="SEGM":
            self.type = "seg"
            self.lat,self.lon,brg = getseg(self.name) # For SEGMnnn to SEGMnnn for crossing flights optional
            pass

        else:
            success, posobj = txt2pos(name,ctrlat,ctrlon)
            if success:
                # for runway type, get heading as default optional argument for command line
                if posobj.type == "rwy":
                    aptname, rwyname = name.split('/RW')
                    rwyname = rwyname.lstrip('Y')
                    try:
                        rwyhdg = navdb.rwythresholds[aptname][rwyname][2]
                    except:
                        rwyhdg = None
                        pass
                else:
                    rwyhdg = None


                self.lat,self.lon = posobj.lat, posobj.lon
                self.type = posobj.type

                # If outside circle change lat,lon to edge of circle
                self.incircle = incircle(self.lat,self.lon)
                if not self.incircle:
                    self.type = "seg"
                    self.segdir,dist = qdrdist(ctrlat,ctrlon,posobj.lat,posobj.lon)
                    segname = "SEGM"+str(int(round(self.segdir)))
                    self.lat,self.lon,hdg = getseg(segname)

            else:
                print("ERROR: trafgen class Drain called for "+name+". Position not found")
                self.lat,self.lon = 0.0,0.0


        # Aircraft types
        self.actypes = ["*"]

        # Runways
        self.runways    = [] # name
        self.rwylat     = []
        self.rwylon     = []
        self.rwyhdg     = []
        self.rwyline    = [] # number of aircraft waiting in line

        #Origins
        self.orig        = []
        self.origlat     = []
        self.origlon     = []
        self.origtype    = [] # "apt","wpt","seg"
        self.orighdg     = []  # if orig is a runway (for flights within FIR) or circle segment (drain outside FIR)
        self.origactypes = []   # Types for this originations ([]=use defaults for this drain)
        self.origincirc  = []

        #Names of drawing objects runways
        self.polys       = []

        return
Ejemplo n.º 23
0
    def update_fms(self, qdr, dist):
        # Check which aircraft i have reached their active waypoint
        # Shift waypoints for aircraft i where necessary
        # Reached function return list of indices where reached logic is True
        for i in bs.traf.actwp.Reached(qdr, dist, bs.traf.actwp.flyby,
                                       bs.traf.actwp.flyturn,bs.traf.actwp.turnrad,bs.traf.actwp.swlastwp):

            # Save current wp speed for use on next leg when we pass this waypoint
            # VNAV speeds are always FROM-speeds, so we accelerate/decellerate at the waypoint
            # where this speed is specified, so we need to save it for use now
            # before getting the new data for the next waypoint

            # Get speed for next leg from the waypoint we pass now
            bs.traf.actwp.spd[i]    = bs.traf.actwp.nextspd[i]
            bs.traf.actwp.spdcon[i] = bs.traf.actwp.nextspd[i]

            # Execute stack commands for the still active waypoint, which we pass
            self.route[i].runactwpstack()

            # If specified, use the given turn radius of passing wp for bank angle
            if bs.traf.actwp.flyturn[i]:
                if bs.traf.actwp.turnspd[i]>=0.:
                    turnspd = bs.traf.actwp.turnspd[i]
                else:
                    turnspd = bs.traf.tas[i]

                if bs.traf.actwp.turnrad[i] > 0.:
                    self.turnphi[i] = atan(turnspd*turnspd/(bs.traf.actwp.turnrad[i]*nm*g0)) # [rad]
                else:
                    self.turnphi[i] = 0.0  # [rad] or leave untouched???

            else:
                self.turnphi[i] = 0.0  #[rad] or leave untouched???

            # Get next wp, if there still is one
            if not bs.traf.actwp.swlastwp[i]:
                lat, lon, alt, bs.traf.actwp.nextspd[i], bs.traf.actwp.xtoalt[i], toalt, \
                    bs.traf.actwp.xtorta[i], bs.traf.actwp.torta[i], \
                    lnavon, flyby, flyturn, turnrad, turnspd,\
                    bs.traf.actwp.next_qdr[i], bs.traf.actwp.swlastwp[i] =      \
                    self.route[i].getnextwp()  # note: xtoalt,toalt in [m]

            # Prevent trying to activate the next waypoint when it was already the last waypoint
            else:
                bs.traf.swlnav[i] = False
                bs.traf.swvnav[i] = False
                bs.traf.swvnavspd[i] = False
                continue # Go to next a/c which reached its active waypoint

            # End of route/no more waypoints: switch off LNAV using the lnavon
            # switch returned by getnextwp
            if not lnavon and bs.traf.swlnav[i]:
                bs.traf.swlnav[i] = False
                # Last wp: copy last wp values for alt and speed in autopilot
                if bs.traf.swvnavspd[i] and bs.traf.actwp.nextspd[i]>= 0.0:
                    bs.traf.selspd[i] = bs.traf.actwp.nextspd[i]

            # In case of no LNAV, do not allow VNAV mode on its own
            bs.traf.swvnav[i] = bs.traf.swvnav[i] and bs.traf.swlnav[i]

            bs.traf.actwp.lat[i] = lat  # [deg]
            bs.traf.actwp.lon[i] = lon  # [deg]
            # 1.0 in case of fly by, else fly over
            bs.traf.actwp.flyby[i] = int(flyby)

            # User has entered an altitude for this waypoint
            if alt >= -0.01:
                bs.traf.actwp.nextaltco[i] = alt  # [m]

            #if not bs.traf.swlnav[i]:
            #    bs.traf.actwp.spd[i] = -997.

            # VNAV spd mode: use speed of this waypoint as commanded speed
            # while passing waypoint and save next speed for passing next wp
            # Speed is now from speed! Next speed is ready in wpdata
            if bs.traf.swvnavspd[i] and bs.traf.actwp.spd[i]>= 0.0:
                    bs.traf.selspd[i] = bs.traf.actwp.spd[i]

            # Update qdr and turndist for this new waypoint for ComputeVNAV
            qdr[i], distnmi = geo.qdrdist(bs.traf.lat[i], bs.traf.lon[i],
                                        bs.traf.actwp.lat[i], bs.traf.actwp.lon[i])

            dist[i] = distnmi*nm
            self.dist2wp[i] = distnmi

            bs.traf.actwp.curlegdir[i] = qdr[i]
            bs.traf.actwp.curleglen[i] = distnmi

            # Update turndist so ComputeVNAV works, is there a next leg direction or not?
            if bs.traf.actwp.next_qdr[i] < -900.:
                local_next_qdr = qdr[i]
            else:
                local_next_qdr = bs.traf.actwp.next_qdr[i]

            # Get flyturn switches and data
            bs.traf.actwp.flyturn[i]     = flyturn
            bs.traf.actwp.turnrad[i]     = turnrad

            # Pass on whether currently flyturn mode:
            # at beginning of leg,c copy tonextwp to lastwp
            # set next turn False
            bs.traf.actwp.turnfromlastwp[i] = bs.traf.actwp.turntonextwp[i]
            bs.traf.actwp.turntonextwp[i]   = False

            # Keep both turning speeds: turn to leg and turn from leg
            bs.traf.actwp.oldturnspd[i]  = bs.traf.actwp.turnspd[i] # old turnspd, turning by this waypoint
            if bs.traf.actwp.flyturn[i]:
                bs.traf.actwp.turnspd[i] = turnspd                  # new turnspd, turning by next waypoint
            else:
                bs.traf.actwp.turnspd[i] = -990.

            # Calculate turn dist (and radius which we do not use) now for scalar variable [i]
            bs.traf.actwp.turndist[i], dummy = \
                bs.traf.actwp.calcturn(bs.traf.tas[i], self.bankdef[i],
                                        qdr[i], local_next_qdr,turnrad)  # update turn distance for VNAV

            # Reduce turn dist for reduced turnspd
            if bs.traf.actwp.flyturn[i] and bs.traf.actwp.turnrad[i]<0.0 and bs.traf.actwp.turnspd[i]>=0.:
                turntas = cas2tas(bs.traf.actwp.turnspd[i], bs.traf.alt[i])
                bs.traf.actwp.turndist[i] = bs.traf.actwp.turndist[i]*turntas*turntas/(bs.traf.tas[i]*bs.traf.tas[i])

            # VNAV = FMS ALT/SPD mode incl. RTA
            self.ComputeVNAV(i, toalt, bs.traf.actwp.xtoalt[i], bs.traf.actwp.torta[i],
                             bs.traf.actwp.xtorta[i])

        

        # End of per waypoint i switching loop
        # Update qdr2wp with up-to-date qdr, now that we have checked passing wp
        self.qdr2wp = qdr%360.

        # Continuous guidance when speed constraint on active leg is in update-method

        # If still an RTA in the route and currently no speed constraint
        for iac in np.where((bs.traf.actwp.torta > -99.)*(bs.traf.actwp.spdcon<0.0))[0]:
            iwp = bs.traf.ap.route[iac].iactwp
            if bs.traf.ap.route[iac].wprta[iwp]>-99.:

                 # For all a/c flying to an RTA waypoint, recalculate speed more often
                dist2go4rta = geo.kwikdist(bs.traf.lat[iac],bs.traf.lon[iac], \
                                           bs.traf.actwp.lat[iac],bs.traf.actwp.lon[iac])*nm \
                               + bs.traf.ap.route[iac].wpxtorta[iwp] # last term zero for active wp rta

                # Set bs.traf.actwp.spd to rta speed, if necessary
                self.setspeedforRTA(iac,bs.traf.actwp.torta[iac],dist2go4rta)

                # If VNAV speed is on (by default coupled to VNAV), use it for speed guidance
                if bs.traf.swvnavspd[iac] and bs.traf.actwp.spd[iac]>=0.0:
                     bs.traf.selspd[iac] = bs.traf.actwp.spd[iac]
Ejemplo n.º 24
0
    def update(self):
        # FMS LNAV mode:
        # qdr[deg],distinnm[nm]
        qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat, bs.traf.actwp.lon)  # [deg][nm])
        dist = distinnm*nm  # Conversion to meters

        # FMS route update
        self.update_fms(qdr, dist)

        #================= Continuous FMS guidance ========================

        # Waypoint switching in the loop above was scalar (per a/c with index i)
        # Code below is vectorized, with arrays for all aircraft

        # Do VNAV start of descent check
        dy = (bs.traf.actwp.lat - bs.traf.lat)  #[deg lat = 60 nm]
        dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat #[corrected deg lon = 60 nm]
        dist2wp   = 60. * nm * np.sqrt(dx * dx + dy * dy) # [m]
        #print("dist2wp =",dist2wp,"   self.dist2vs =",self.dist2vs)


        # VNAV logic: descend as late as possible, climb as soon as possible
        startdescent = (dist2wp < self.dist2vs) + (bs.traf.actwp.nextaltco > bs.traf.alt)

        # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
        #    (because there are no more waypoints). This is needed
        #    to continue descending when you get into a conflict
        #    while descending to the destination (the last waypoint)
        #    Use 0.1 nm (185.2 m) circle in case turndist might be zero
        self.swvnavvs = bs.traf.swvnav * np.where(bs.traf.swlnav, startdescent,
                                        dist <= np.maximum(185.2,bs.traf.actwp.turndist))

        #Recalculate V/S based on current altitude and distance to next alt constraint
        # How much time do we have before we need to descend?

        t2go2alt = np.maximum(0.,(dist2wp + bs.traf.actwp.xtoalt - bs.traf.actwp.turndist)) \
                                    / np.maximum(0.5,bs.traf.gs)

        # use steepness to calculate V/S unless we need to descend faster
        bs.traf.actwp.vs = np.maximum(self.steepness*bs.traf.gs, \
                                np.abs((bs.traf.actwp.nextaltco-bs.traf.alt))  \
                                /np.maximum(1.0,t2go2alt))


        self.vnavvs  = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
        #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

        # self.vs = np.where(self.swvnavvs, self.vnavvs, bs.traf.apvsdef * bs.traf.limvs_flag)
        selvs    = np.where(abs(bs.traf.selvs) > 0.1, bs.traf.selvs, bs.traf.apvsdef) # m/s
        self.vs  = np.where(self.swvnavvs, self.vnavvs, selvs)
        self.alt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco, bs.traf.selalt)

        # When descending or climbing in VNAV also update altitude command of select/hold mode
        bs.traf.selalt = np.where(self.swvnavvs,bs.traf.actwp.nextaltco,bs.traf.selalt)

        # LNAV commanded track angle
        self.trk = np.where(bs.traf.swlnav, qdr, self.trk)

        # FMS speed guidance: anticipate accel distance

        # Actual distance it takes to decelerate
        nexttas  = vcasormach2tas(bs.traf.actwp.spd,bs.traf.alt)
        tasdiff  = nexttas - bs.traf.tas # [m/s]
        dtspdchg = np.abs(tasdiff)/np.maximum(0.01,np.abs(bs.traf.ax)) #[s]
        dxspdchg = 0.5*np.sign(tasdiff)*np.abs(bs.traf.ax)*dtspdchg*dtspdchg + bs.traf.tas*dtspdchg #[m]

        # Chekc also whether VNAVSPD is on, if not, SPD SEL has override
        usespdcon      = (dist2wp < dxspdchg)*(bs.traf.actwp.spd > -990.)*bs.traf.swvnavspd*bs.traf.swvnav

        bs.traf.selspd = np.where(usespdcon, bs.traf.actwp.spd, bs.traf.selspd)

        # Below crossover altitude: CAS=const, above crossover altitude: Mach = const
        self.tas = vcasormach2tas(bs.traf.selspd, bs.traf.alt)
Ejemplo n.º 25
0
 def atdistcmd(self, acidx, lat, lon, targdist, cmdtxt):
     qdr, actdist = qdrdist(bs.traf.lat[acidx], bs.traf.lon[acidx], lat, lon)
     self.addcondition(acidx, postype, targdist, actdist, cmdtxt, (lat,lon))
     return True
Ejemplo n.º 26
0
def ETA(acidx):
    # Assuming no wind.
    # Assume complete route is available including orig --> destination

#    print(np.where(traf.ap.route[acidx].wptype))
    # Acceleration value depends on flight phase and aircraft type in BADA
    # The longitudinal acceleration is constant for all airborne flight
    # phases in the bada model.
    ax = traf.perf.acceleration()
    iactwp = traf.ap.route[acidx].iactwp
    nwp = len(traf.ap.route[acidx].wpname)


    # Construct a tables with relevant route parameters before the 4D prediction
    dist2vs =   [0]*nwp
    Vtas =      [0]*nwp
    Vcas =      traf.ap.route[acidx].wpspd.copy()
    dsturn =    [0]*nwp
    qdr =       [0]*nwp
    s =         [0]*nwp
    turndist =  [0]*nwp
    lat =       traf.ap.route[acidx].wplat.copy()
    lon =       traf.ap.route[acidx].wplon.copy()
    altatwp =   traf.ap.route[acidx].wpalt.copy()
    wpialt =    traf.ap.route[acidx].wpialt.copy()

    for wpidx in range(iactwp-1, nwp):
        # If the next waypoint is the active waypoint, use aircraft data
        # Else use previous waypoint data
        if wpidx == iactwp-1:
            Vtas[wpidx] = traf.gs[acidx]       # [m/s]
            Vcas[wpidx] = traf.cas[acidx]
            lat[wpidx] = traf.lat[acidx]    # [deg]
            lon[wpidx] = traf.lon[acidx]    # [deg]
            altatwp[wpidx] = traf.alt[acidx] # [m]

        else:
            lat[wpidx] = traf.ap.route[acidx].wplat[wpidx]          # [deg]
            lon[wpidx] = traf.ap.route[acidx].wplon[wpidx]          #[deg]

            qdr, s[wpidx] = qdrdist(lat[wpidx-1], lon[wpidx-1], lat[wpidx], lon[wpidx]) # [nm]
            s[wpidx] *= nm # [m]

            # check for valid speed, if no speed is given take the speed
            # already in memory from previous waypoint.
            if traf.ap.route[acidx].wpspd[wpidx] < 0:
                Vtas[wpidx] = Vtas[wpidx-1]
                Vcas[wpidx] = Vcas[wpidx-1]
            else:

                Vtas[wpidx] = vcas2tas(traf.ap.route[acidx].wpspd[wpidx],
                          traf.ap.route[acidx].wpalt[wpidx]).item() # [m/s]


            # Compute distance correction for flyby turns.
            if wpidx < nwp - 2:
                #bearing of leg --> Second leg.
                nextqdr, nexts = qdrdist(lat[wpidx], lon[wpidx], lat[wpidx+1], lon[wpidx+1]) #[deg, nm]
                nexts *= nm # [m]
                dist2turn,  turnrad = traf.actwp.calcturn(Vtas[wpidx], traf.bank[acidx], qdr, nextqdr) # [m], [m]
                delhdg = np.abs(degto180(qdr%360-nextqdr%360)) # [deg] Heading change
                distofturn = np.pi*turnrad*delhdg/360 # [m] half of distance on turn radius

                dsturn[wpidx] = dist2turn - distofturn
                turndist[wpidx] = dist2turn


    # Now loop to fill the VNAV constraints. A second loop is necessary
    # Because the complete speed profile and turn profile is required.
    curalt = traf.alt[acidx]
    for wpidx in range(iactwp, nwp):
        # If Next altitude is not the altitude constraint, check if the
        # aircraft should already decent. Otherwise aircraft stays at
        # current altitude. Otherwise expected altitude is filled in.
        if wpidx < wpialt[wpidx]:
        # Compute whether decent should start or speed is still same
            dist2vs[wpidx] = turndist[wpidx] + \
                               np.abs(curalt - traf.ap.route[acidx].wptoalt[wpidx]) / traf.ap.steepness

            # If the dist2vs is smaller everything should be fine
            if traf.ap.route[acidx].wpxtoalt[wpidx] > dist2vs[wpidx]:
                dist2vs[wpidx] = 0
                altatwp[wpidx] = curalt
            else:
                dist2vs[wpidx] = dist2vs[wpidx] - traf.ap.route[acidx].wpxtoalt[wpidx]
                if curalt > traf.ap.route[acidx].wptoalt[wpidx]:
                    sign = -1
                else:
                    sign = 1
                curalt = curalt + dist2vs[wpidx] * traf.ap.steepness * sign
                altatwp[wpidx] = curalt

        # If there is an altitude constraint on the current waypoint compute
        # dist2vs for that waypoint.
        else:
            dist2vs[wpidx] = np.abs(curalt - traf.ap.route[acidx].wptoalt[wpidx]) / traf.ap.steepness
            if dist2vs[wpidx] > traf.ap.route[acidx].wpxtoalt[wpidx]:
                dist2vs[wpidx] = traf.ap.route[acidx].wpxtoalt[wpidx]


    # Start computing the actual 4D trajectory
    t_total = 0
    for wpidx in range(iactwp, nwp-1):
        # If V1 != V2 the aircraft will first immediately accelerate
        # according to the performance model. To compute the time for the
        # leg, constant acceleration is assumed
        Vcas1 = Vcas[wpidx-1]
        Vtas1 = Vtas[wpidx-1]
        Vcas2 = Vcas[wpidx]
        Vtas2 = Vtas[wpidx]
        s_leg = s[wpidx] - dsturn[wpidx-1] - dsturn[wpidx]

        if Vcas1!=Vcas2:
            axsign = 1 + -2*(Vcas1>Vcas2)     # [-]
            t_acc = (Vcas2-Vcas1)/ax * axsign    # [s]
            s_ax = t_acc * (Vtas2+Vtas1)/2         # [m] Constant acceleration
        else:
            s_ax = 0 # [m]
            t_acc = 0

        s_nom = s_leg - s_ax - dist2vs[wpidx] # [m] Distance of normal flight
        # Check if acceleration distance and start of descent overlap
        if s_nom < 0:
            t_vs = 0
            t_nom = 0
            if dist2vs[wpidx] <= s_leg:
                s1 = 0
                s2 = s_ax
                s3 = s_leg - s_ax
            else:
                s1 = s_leg - dist2vs[wpidx]
                s2 = dist2vs[wpidx] + s_ax - s_leg
                s3 = s_leg - s_ax


            t1 = abc(0.5*vcas2tas(ax, altatwp[wpidx-1]), Vtas1, s1)
            Vcas_vs = Vcas1 - t1 * ax
            t2 = (Vcas2-Vcas_vs)/ax
            alt2 = altatwp[wpidx-1] - s2 * traf.ap.steepness
            Vtas_vs = vcas2tas(Vcas_vs, alt2)
            t3 = s3/((Vtas_vs + Vtas2)/2)
            t_leg = t1 + t2 + t3

        else:
            Vtas_nom = vcas2tas(Vcas2, altatwp[wpidx-1])
            t_nom = s_nom/Vtas_nom

            #Assume same speed for now.
            t_vs = dist2vs[wpidx]/(Vtas_nom + Vtas2)/2

            if dist2vs[wpidx] < 1:
                t_vs = 0

            t_leg = t_nom + t_acc + t_vs
        t_total+=t_leg


    # Debug print statements
#    print('wptype ', traf.ap.route[acidx].wptype[traf.ap.route[acidx].iactwp])
#    print('DEBUG')
#    print('Vtas ', Vtas)
#    print('Vcas ', Vcas)
#    print('dsturn ', dsturn)
#    print('s ', s)
#    print('turndist ', turndist)
#    print('lat ', lat)
#    print('lon ', lon)
#    print('altatwp ', altatwp)
#    print('wpialt ', traf.ap.route[acidx].wpialt)
#    print('wptoalt ', traf.ap.route[acidx].wptoalt)
#    print('wpxtoalt ', traf.ap.route[acidx].wpxtoalt)
#    print('altatwp ', altatwp)
#    print('dist2vs ', dist2vs)
#    print('eta', sim.simt + t_total)
#    print(' ')
#    print(t_total)
    return t_total[0] + sim.simt
Ejemplo n.º 27
0
    def update(self, simt):
        # Scheduling: when dt has passed or restart
        if self.t0 + self.dt < simt or simt < self.t0:
            self.t0 = simt

            # FMS LNAV mode:
            qdr, dist = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat, bs.traf.actwp.lon)  # [deg][nm])

            # Shift waypoints for aircraft i where necessary
            for i in bs.traf.actwp.Reached(qdr, dist):
                # Save current wp speed
                oldspd = bs.traf.actwp.spd[i]

                # Get next wp (lnavon = False if no more waypoints)
                lat, lon, alt, spd, xtoalt, toalt, lnavon, flyby, bs.traf.actwp.next_qdr[i] =  \
                       self.route[i].getnextwp()  # note: xtoalt,toalt in [m]

                # End of route/no more waypoints: switch off LNAV
                bs.traf.swlnav[i] = bs.traf.swlnav[i] and lnavon

                # In case of no LNAV, do not allow VNAV mode on its own
                bs.traf.swvnav[i] = bs.traf.swvnav[i] and bs.traf.swlnav[i]

                bs.traf.actwp.lat[i]   = lat
                bs.traf.actwp.lon[i]   = lon
                bs.traf.actwp.flyby[i] = int(flyby)  # 1.0 in case of fly by, else fly over

                # User has entered an altitude for this waypoint
                if alt >= 0.:
                    bs.traf.actwp.alt[i] = alt

                if spd > 0. and bs.traf.swlnav[i] and bs.traf.swvnav[i]:
                    # Valid speed and LNAV and VNAV ap modes are on
                    bs.traf.actwp.spd[i] = spd
                else:
                    bs.traf.actwp.spd[i] = -999.

                # VNAV spd mode: use speed of this waypoint as commanded speed
                # while passing waypoint and save next speed for passing next wp
                if bs.traf.swvnav[i] and oldspd > 0.0:
                    destalt = alt if alt > 0.0 else bs.traf.alt[i]
                    if oldspd<2.0:
                        bs.traf.aspd[i] = mach2cas(oldspd, destalt)
                        bs.traf.ama[i]  = oldspd
                    else:
                        bs.traf.aspd[i] = oldspd
                        bs.traf.ama[i]  = cas2mach(oldspd, destalt)

                # VNAV = FMS ALT/SPD mode
                self.ComputeVNAV(i, toalt, xtoalt)

            #=============== End of Waypoint switching loop ===================

            #================= Continuous FMS guidance ========================
            # Do VNAV start of descent check
            dy = (bs.traf.actwp.lat - bs.traf.lat)
            dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat
            dist2wp   = 60. * nm * np.sqrt(dx * dx + dy * dy)

            # VNAV logic: descend as late as possible, climb as soon as possible
            startdescent = bs.traf.swvnav * ((dist2wp < self.dist2vs)+(bs.traf.actwp.alt > bs.traf.alt))

            # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
            #    (because there are no more waypoints). This is needed
            #    to continue descending when you get into a conflict
            #    while descending to the destination (the last waypoint)
            #    Use 100 nm (185.2 m) circle in case turndist might be zero
            self.swvnavvs = np.where(bs.traf.swlnav, startdescent, dist <= np.maximum(185.2,bs.traf.actwp.turndist))

            #Recalculate V/S based on current altitude and distance
            t2go = dist2wp/np.maximum(0.5,bs.traf.gs)
            bs.traf.actwp.vs = (bs.traf.actwp.alt-bs.traf.alt)/np.maximum(1.0,t2go)

            self.vnavvs  = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
            #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

            # self.vs = np.where(self.swvnavvs, self.vnavvs, bs.traf.avsdef * bs.traf.limvs_flag)
            avs = np.where(abs(bs.traf.avs) > 0.1, bs.traf.avs, bs.traf.avsdef) # m/s
            self.vs = np.where(self.swvnavvs, self.vnavvs, avs * bs.traf.limvs_flag)

            self.alt = np.where(self.swvnavvs, bs.traf.actwp.alt, bs.traf.apalt)

            # When descending or climbing in VNAV also update altitude command of select/hold mode
            bs.traf.apalt = np.where(self.swvnavvs,bs.traf.actwp.alt,bs.traf.apalt)

            # LNAV commanded track angle
            self.trk = np.where(bs.traf.swlnav, qdr, self.trk)

        # Below crossover altitude: CAS=const, above crossover altitude: MA = const
        self.tas = vcas2tas(bs.traf.aspd, bs.traf.alt) * bs.traf.belco + vmach2tas(bs.traf.ama, bs.traf.alt) * bs.traf.abco
Ejemplo n.º 28
0
    def direct(self, idx, wpnam):
        #print("Hello from direct")
        """Set active point to a waypoint by name"""
        name = wpnam.upper().strip()
        if name != "" and self.wpname.count(name) > 0:
            wpidx = self.wpname.index(name)

            self.iactwp = wpidx

            bs.traf.actwp.lat[idx] = self.wplat[wpidx]
            bs.traf.actwp.lon[idx] = self.wplon[wpidx]
            bs.traf.actwp.flyby[idx] = self.wpflyby[wpidx]
            bs.traf.actwp.flyturn[idx] = self.wpflyturn[wpidx]
            bs.traf.actwp.turnrad[idx] = self.wpturnrad[wpidx]
            bs.traf.actwp.turnspd[idx] = self.wpturnspd[wpidx]

            # Do calculation for VNAV
            self.calcfp()

            bs.traf.actwp.xtoalt[idx] = self.wpxtoalt[wpidx]
            bs.traf.actwp.nextaltco[idx] = self.wptoalt[wpidx]

            bs.traf.actwp.torta[idx] = self.wptorta[
                wpidx]  # available for active RTA-guidance
            bs.traf.actwp.xtorta[idx] = self.wpxtorta[
                wpidx]  # available for active RTA-guidance

            #VNAV calculations like V/S and speed for RTA
            bs.traf.ap.ComputeVNAV(idx, self.wptoalt[wpidx], self.wpxtoalt[wpidx],\
                                        self.wptorta[wpidx],self.wpxtorta[wpidx])

            # If there is a speed specified, process it
            if self.wpspd[wpidx] > 0.:
                # Set target speed for autopilot

                if self.wpalt[wpidx] < 0.0:
                    alt = bs.traf.alt[idx]
                else:
                    alt = self.wpalt[wpidx]

                # Check for valid Mach or CAS
                if self.wpspd[wpidx] < 2.0:
                    cas = mach2cas(self.wpspd[wpidx], alt)
                else:
                    cas = self.wpspd[wpidx]

                # Save it for next leg
                bs.traf.actwp.nextspd[idx] = cas

            # No speed specified for next leg
            else:
                bs.traf.actwp.nextspd[idx] = -999.

            qdr, dist = geo.qdrdist(bs.traf.lat[idx], bs.traf.lon[idx],
                                    bs.traf.actwp.lat[idx],
                                    bs.traf.actwp.lon[idx])

            if self.wpflyturn[wpidx] or self.wpturnrad[wpidx] < 0.:
                turnrad = self.wpturnrad[wpidx]
            else:
                turnrad = bs.traf.tas[idx] * bs.traf.tas[idx] / tan(
                    radians(25.)) / g0 / nm  # [nm]default bank angle 25 deg


            bs.traf.actwp.turndist[idx] = (bs.traf.actwp.flyby[idx] > 0.5)  *   \
                     turnrad*abs(tan(0.5*radians(max(5., abs(degto180(qdr -
                        self.wpdirfrom[self.iactwp]))))))    # [nm]

            bs.traf.swlnav[idx] = True
            return True
        else:
            return False, "Waypoint " + wpnam + " not found"
Ejemplo n.º 29
0
    def update_fms(self, qdr, dist):
        # Shift waypoints for aircraft i where necessary
        for i in bs.traf.actwp.Reached(qdr, dist, bs.traf.actwp.flyby,
                                       bs.traf.actwp.flyturn,bs.traf.actwp.turnrad):
            # Save current wp speed for use on next leg when we pass this waypoint
            # VNAV speeds are always FROM-speed, so we accelerate/decellerate at the waypoint
            # where this speed is specified, so we need to save it for use now
            # before getting the new data for the next waypoint

            # Get speed for next leg from the waypoint we pass now
            bs.traf.actwp.spd[i]    = bs.traf.actwp.nextspd[i]
            bs.traf.actwp.spdcon[i] = bs.traf.actwp.nextspd[i]

            # Use turnradius of passing wp for bank angle
            if bs.traf.actwp.flyturn[i] and bs.traf.actwp.turnrad[i]>0.:
                if bs.traf.actwp.turnspd[i]>0.:
                    turnspd = bs.traf.actwp.turnspd[i]
                else:
                    turnspd = bs.traf.tas[i]

                bs.traf.aphi[i] = atan(turnspd*turnspd/(bs.traf.actwp.turnrad[i]*nm*g0)) # [rad]
            else:
                bs.traf.aphi[i] = 0.0  #[rad] or leave untouched???

            # Get next wp (lnavon = False if no more waypoints)
            lat, lon, alt, bs.traf.actwp.nextspd[i], bs.traf.actwp.xtoalt[i], toalt, \
                bs.traf.actwp.xtorta[i], bs.traf.actwp.torta[i], \
                lnavon, flyby, flyturn, turnrad, turnspd,\
                bs.traf.actwp.next_qdr[i] =      \
                self.route[i].getnextwp()  # note: xtoalt,toalt in [m]

            # End of route/no more waypoints: switch off LNAV using the lnavon
            # switch returned by getnextwp
            if not lnavon and bs.traf.swlnav[i]:
                bs.traf.swlnav[i] = False
                # Last wp: copy last wp values for alt and speed in autopilot
                if bs.traf.swvnavspd[i] and bs.traf.actwp.nextspd[i]>= 0.0:
                    bs.traf.selspd[i] = bs.traf.actwp.nextspd[i]

            # In case of no LNAV, do not allow VNAV mode on its own
            bs.traf.swvnav[i] = bs.traf.swvnav[i] and bs.traf.swlnav[i]

            bs.traf.actwp.lat[i] = lat  # [deg]
            bs.traf.actwp.lon[i] = lon  # [deg]
            # 1.0 in case of fly by, else fly over
            bs.traf.actwp.flyby[i] = int(flyby)

            # User has entered an altitude for this waypoint
            if alt >= -0.01:
                bs.traf.actwp.nextaltco[i] = alt  # [m]

            if not bs.traf.swlnav[i]:
                bs.traf.actwp.spd[i] = -999.

            # VNAV spd mode: use speed of this waypoint as commanded speed
            # while passing waypoint and save next speed for passing next wp
            # Speed is now from speed! Next speed is ready in wpdata
            if bs.traf.swvnavspd[i] and bs.traf.actwp.spd[i]> 0.0:
                    bs.traf.selspd[i] = bs.traf.actwp.spd[i]

            # Update qdr and turndist for this new waypoint for ComputeVNAV
            qdr[i], distnmi = geo.qdrdist(bs.traf.lat[i], bs.traf.lon[i],
                                        bs.traf.actwp.lat[i], bs.traf.actwp.lon[i])

            dist[i] = distnmi*nm

            # Update turndist so ComputeVNAV works, is there a next leg direction or not?
            if bs.traf.actwp.next_qdr[i] < -900.:
                local_next_qdr = qdr[i]
            else:
                local_next_qdr = bs.traf.actwp.next_qdr[i]

            bs.traf.actwp.flyturn[i]     = flyturn
            bs.traf.actwp.turnrad[i]     = turnrad

            # Keep both turning speeds: turn to leg and turn from leg
            bs.traf.actwp.oldturnspd[i]  = bs.traf.actwp.turnspd[i] # old turnspd, turning by this waypoint
            bs.traf.actwp.turnspd[i]     = turnspd                  # new turnspd, turning by next waypoint

            # Calculate turn dist (and radius which we do not use) now for scalar variable [i]
            bs.traf.actwp.turndist[i], dummy = \
                bs.traf.actwp.calcturn(bs.traf.tas[i], bs.traf.bank[i],
                                        qdr[i], local_next_qdr,turnrad)  # update turn distance for VNAV


            # VNAV = FMS ALT/SPD mode incl. RTA
            self.ComputeVNAV(i, toalt, bs.traf.actwp.xtoalt[i], bs.traf.actwp.torta[i],
                             bs.traf.actwp.xtorta[i])

        # End of per waypoint i switching loop

        # Continuous guidance when speed constraint on active leg is in update-method

        # If still an RTA in the route and currently no speed constraint
        for iac in np.where((bs.traf.actwp.torta > -99.)*(bs.traf.actwp.spdcon<0.0))[0]:
            iwp = bs.traf.ap.route[iac].iactwp
            if bs.traf.ap.route[iac].wprta[iwp]>-99.:

                 # For all a/c flying to an RTA waypoint, recalculate speed more often
                dist2go4rta = geo.kwikdist(bs.traf.lat[iac],bs.traf.lon[iac], \
                                           bs.traf.actwp.lat[iac],bs.traf.actwp.lon[iac])*nm \
                               + bs.traf.ap.route[iac].wpxtorta[iwp] # last term zero for active wp rta

                # Set bs.traf.actwp.spd to rta speed, if necessary
                self.setspeedforRTA(iac,bs.traf.actwp.torta[iac],dist2go4rta)

                # If VNAV speed is on (by default coupled to VNAV), use it for speed guidance
                if bs.traf.swvnavspd[iac]:
                     bs.traf.selspd[iac] = bs.traf.actwp.spd[iac]

        return
Ejemplo n.º 30
0
def distcalc(lat0, lon0, lat1, lon1):
    try:
        qdr, dist = geo.qdrdist(lat0, lon0, lat1, lon1)
        return True, "QDR = %.2f deg, Dist = %.3f nm" % (qdr % 360.0, dist)
    except:
        return False, "Error in dist calculation."
Ejemplo n.º 31
0
    def resolve(self, conf, ownship, intruder):
        ''' Main resolution module '''
        # --> Resolution setting options (through METHOD stack command):
        #       1 - MVP, GEO, vertical              = METHOD BOTH 3D
        #       2 - MVP, GEO, no vertical           = METHOD BOTH 2D
        #       3 - MVP, no GEO, vertical           = METHOD MVP 3D
        #       4 - MVP, no GEO, no vertical        = METHOD MVP 2D
        #       5 - no MVP, GEO, vertical           = METHOD GEO 3D
        #       6 - no MVP, GEO, no vertical        = METHOD GEO 2D
        # ------------------------------------------------------------------

        # resolve() process:
        # 1 -> if mvp is active, compute pairwise summed horizontal MVP solution
        #           (this will give newtrk, newgs, newvs, newalt for all aircraft)
        # 2 -> check which aircraft will breach given the solution (from 1)
        #           (if mvp is inactive, return True for all aircraft)
        # 3 -> reset newtrk, newgs, newvs, newalt to current aircraft state for aircraft with breach = True
        # 4 -> loop through all pairwise conflicts where ownship breach == True (from 2)
        #   4.a -> if mvp is active, compute dv_mvp for pairwise conflict
        #             - if mvp does not breach, proceed directly to (4.d)
        #   4.b -> if geo is active, compute dv_geo for pairwise conflict
        #   4.c -> using a decision criterion, choose mvp or geo solution
        #           (non-existing maneuvers will be automatically dismissed)
        #   4.d -> if vertical is active, compute dv_ver
        #             - if vertical is not active, proceed directly to (4.f)
        #   4.e -> using a decision criterion, choose horizontal (from 6) or vertical (from 7)
        #           (not computed maneuvers will be automatically dismissed)
        #   4.f -> add chosen dv to already existing dv for ownship (pairwise summed)
        # 5 -> compute average of horizontal maneuver: pairwise-summed average
        # 6 -> add dv of all aircraft to newtrk, newgs, newvs, newalt from (1)
        #           (dv for breaching aircraft is zero by default)
        # 7 -> cap velocities and return newtrk, newgs, newvs, newalt

        # Initialize resomethod log for all conflicts in the current timestep
        self.resomethod = np.full(len(conf.confpairs), 'MVP')

        # Reset number of conflicts
        traf_nconf = np.zeros(traf.ntraf, dtype=int)

        # Create newvs, newalt
        newvs = np.zeros(ownship.ntraf)
        if np.any(self.layers):
            newalt = np.array(self.layers[self.layidx])
        else:
            newalt = np.array(ownship.alt)

        # Initialize switch to keep track of vertical maneuvers
        traf_swalt = np.zeros(ownship.ntraf, dtype=np.bool)

        # Get array containing active limits for each aircraft
        gsmin, gsmax, trkmin, trkmax, vsmin, vsmax = ap_geo.get_limits()

        # Create empty dv array for all aircraft
        dv_traf = np.zeros((ownship.ntraf, 3))

        # STEP 1: If MVP is active, determine pairwise summed horizontal solution and check for breaches =====
        if self.mvp_active:
            newtrk, newgs, _, _ = super().resolve(conf, ownship, intruder)

            # STEP 2: Check for breaches =========================================================================
            eps = 1e-10  # small increment for limit to prevent floating point errors leading to wrong result
            breachgs = np.logical_or(newgs < (gsmin - eps), newgs >
                                     (gsmax + eps))
            breachtrk = np.logical_or(
                degto180(newtrk - trkmin) < 0,
                degto180(newtrk - trkmax) > 0)

            # Bool array indicating which aircraft will breach a geovector limit through a CR maneuver
            swbreach = (breachgs | breachtrk) & conf.inconf

            # STEP 3: Reset newtrk, newgs, newvs, newalt for the breaching aircraft ==============================
            newtrk[swbreach] = np.array(ownship.trk[swbreach])
            newgs[swbreach] = np.array(ownship.gs[swbreach])

        # If MVP is not active, set each aircraft to be breaching
        # This ensures that all conflicts get a solution from the GEO module
        # Also create newtrk, newgs
        else:
            newtrk = np.array(ownship.trk)
            newgs = np.array(ownship.gs)
            swbreach = np.ones(ownship.ntraf, dtype=np.bool)

# STEP 4: Loop through all conflicts in the current timestep =========================================
        nconf = 0  # keeping track of conflict number to store resolution method
        for ((ac1, ac2), qdr, dist, tcpa, tLOS) in zip(conf.confpairs, conf.qdr, \
                                            conf.dist, conf.tcpa, conf.tLOS):

            # Retrieve ownship and intruder index
            idx1 = ownship.id.index(ac1)
            idx2 = intruder.id.index(ac2)

            # CR is switched off for aircraft in a geofence conflict: do not resolve
            try:
                if ap_geo.APgeo().geoconf[idx1]:
                    self.resomethod[nconf] = 'FCE'
                    nconf += 1
                    continue
            except AttributeError:  # if geofence autopilot is not implemented
                pass

            # Check if horizontal pairwise summed MVP solution will breach at all
            if not swbreach[idx1]:
                nconf += 1  # update conflict counter
                continue  # go to next iteration in for loop

            # Check if the aircraft is already performing a vertical maneuver
            if traf_swalt[idx1]:
                self.resomethod[nconf] = 'IND'
                nconf += 1
                continue

            # Retrieve horizontal geovector limits for the ownship
            gsl = gsmin[idx1]
            gsu = gsmax[idx1]
            trkl = trkmin[idx1]
            trku = trkmax[idx1]

            # STEP 4a: MVP  ======================================================================================
            if self.mvp_active:
                dv_mvp, _ = super().MVP(ownship, intruder, conf, qdr, dist,
                                        tcpa, tLOS, idx1, idx2)

                # Set mvp vs to zero
                dv_mvp[2] = 0.

                #Check if MVP solution leads to a geovector breach
                gs_mvp = ownship.gs[idx1] + np.sqrt(dv_mvp[0]**2 +
                                                    dv_mvp[1]**2)
                trk_mvp = ownship.trk[idx1] + (
                    np.rad2deg(np.arctan2(dv_mvp[0], dv_mvp[1])) % 360)
                mvp_breach = True if gs_mvp < gsl or gs_mvp > gsu or \
                        degto180(trk_mvp - trkl) < 0 or degto180(trk_mvp - trku) > 0 else False
            else:  #if MVP not active
                dv_mvp, mvp_breach = np.full(3, np.nan), True

# STEP 4b: GEO  ======================================================================================
# Only compute the alternative maneuver if the MVP solution will breach
            if mvp_breach:
                if self.geo_active:
                    # Retrieve intruder rpz and horizontal ownship geovector limits
                    rpz = conf.rpz[idx2] * self.resofach

                    # GEO maneuver only exists if the pair is not in LoS
                    if dist > rpz:
                        dv_geo, type_geo = self.GEO(ownship, intruder, gsl, gsu, trkl, trku, qdr, \
                                                                                dist, rpz, idx1, idx2)

                        # Check if GEO solution leads to a geovector breach
                        geo_breach = True if type_geo == 'INS' else False

                    elif not self.mvp_active:  # if already in LoS and MVP not active, do nothing
                        dv_geo, geo_breach, type_geo = np.zeros(
                            3), False, 'LOS'
                    else:  # if already in LoS but MVP maneuver can be used to escape
                        dv_geo, geo_breach, type_geo = np.full(
                            3, np.nan), False, 'LOS'

                else:  #if GEO not active
                    dv_geo, geo_breach, type_geo = np.full(
                        3, np.nan), True, 'undefined'

# STEP 4c: Choose horizontal maneuver ================================================================
                p_mvp = p_horz if mvp_breach else 1  #penalty factor for mvp maneuver
                p_geo = p_horz if geo_breach else 1  #penalty factor for geo maneuver

                criterion_mvp = p_mvp * np.sqrt(dv_mvp[0]**2 + dv_mvp[1]**2)
                criterion_geo = p_geo * np.sqrt(dv_geo[0]**2 + dv_geo[1]**2)
                criteria = np.array([criterion_mvp, criterion_geo])

                # The maneuver with the smallest existing horizontal state change magnitude will be chosen
                do_geo = True if np.nanargmin(criteria) == 1 else False
                if do_geo:
                    dv, self.resomethod[nconf] = dv_geo, type_geo
                    p_hor = p_vert if geo_breach else 1
                else:
                    dv, self.resomethod[nconf], p_hor = dv_mvp, 'MVP', p_vert

            # If the MVP solution for this pairwise conflict will not breach, choose MVP
            else:
                dv, self.resomethod[nconf], p_hor = dv_mvp, 'MVP', 1

# STEP 4d: vertical  =================================================================================
# Compute vertical maneuver if active and choose between horizontal and vertical
            alt_cur = newalt[idx1]
            if self.ver_active:
                # Vertical maneuver is only allowed if the aircraft has priority
                if self.row_priority(ownship, intruder, idx1, idx2):
                    dv_ver, alt_res, layidx = self.vertical(
                        ownship, intruder, idx1, idx2, conf.hpz[idx2], tLOS)

                    # Retrieve vertical geovector limits for the ownship
                    vsl, vsu = vsmin[idx1], vsmax[idx1]

                    # Check if vertical solution leads to a geovector breach and set penalty
                    ver_breach = True if dv_ver[2] < vsl or dv_ver[
                        2] > vsu else False
                    p_ver = p_vert if ver_breach else 1

                    # Determine if traffic is in lookahead disc on current (_cur) and resolution (_res) alt
                    # Lookahead disc equals ownship ground speed * lookahead time with height = hpz
                    # Furthermore, number of instant LoS will be determined for both altitudes
                    traf_qdr, traf_dist = qdrdist(ownship.lat[idx1],
                                                  ownship.lon[idx1],
                                                  intruder.lat, intruder.lon)
                    traf_qdr, traf_dist = np.radians(traf_qdr), traf_dist * nm
                    traf_dh_cur = alt_cur - intruder.alt
                    traf_dh_res = alt_res - intruder.alt

                    # inside horizontally for both the lookahead disc and the PZ
                    ins_hor = traf_dist < (ownship.gs[idx1] *
                                           conf.dtlookahead[idx1])
                    los_hor = traf_dist < conf.rpz[
                        idx1]  # inside ownship PZ = LoS
                    los_hor[
                        idx1] = False  # for LoS count -> exclude the ownship

                    # inside vertically on current and resolution altitude
                    ins_ver_cur = np.abs(traf_dh_cur) < conf.hpz[idx1]
                    ins_ver_res = np.abs(traf_dh_res) < conf.hpz[idx1]

                    # if both are True: traffic is inside
                    ins_cur = np.logical_and(ins_hor, ins_ver_cur)
                    ins_res = np.logical_and(ins_hor, ins_ver_res)

                    los_cur = np.logical_and(los_hor, ins_ver_cur)
                    los_res = np.logical_and(los_hor, ins_ver_res)

                    # number of instant losses of separation
                    nlos_cur = los_cur.sum()
                    nlos_res = los_res.sum()

                    # Determine if traffic is converging with ownship
                    # Aircraft are converging if distance between them will get smaller in the future
                    drel = np.array([
                        np.sin(traf_qdr) * traf_dist,
                        np.cos(traf_qdr) * traf_dist, traf_dh_cur
                    ]).T
                    vall = np.array(
                        [intruder.gseast, intruder.gsnorth, intruder.vs]).T
                    vown = np.array([
                        ownship.gseast[idx1], ownship.gsnorth[idx1],
                        ownship.vs[idx1]
                    ]).T

                    vrel = vall - vown
                    dnew = drel + vrel  # look one second ahead
                    traf_newdist = np.sqrt(dnew[:, 0]**2 + dnew[:, 1]**2 +
                                           dnew[:, 2]**2)

                    conv_cur = traf_newdist[ins_cur] < traf_dist[ins_cur]
                    conv_res = traf_newdist[ins_res] < traf_dist[ins_res]

                    # Count number of converging traffic
                    # (works since ownship is not converging with itself)
                    nconv_cur = conv_cur.sum()
                    nconv_res = conv_res.sum()

                    # Compute decision criterion for both current and resolution altitude
                    criterion_cur = p_hor * (nconv_cur + 100 * nlos_cur
                                             )  # x100 penalty for instant LoS
                    criterion_res = p_ver * (nconv_res + 100 * nlos_res
                                             )  #        "         "

                    # STEP 4e: Choose between horizontal and vertical maneuver and set dv accordingly ====================
                    do_ver = True if criterion_res < criterion_cur else False
                    if do_ver:
                        dv, self.resomethod[nconf] = dv_ver, 'VER'
                        newalt[idx1] = alt_res
                        self.layidx[idx1] = layidx
                        traf_swalt[idx1] = True

                        # Reset previously computed horizontal maneuvers
                        # These are not necessary any more
                        dv_traf[idx1, 0:2] = 0.
                        for n in range(
                                nconf
                        ):  # loop through previously solved pairwise conflicts
                            if ownship.id.index(
                                    conf.confpairs[n]
                                [0]) == idx1:  # check if ownship = idx1
                                self.resomethod[n] = 'IND'

# STEP 4f: Finally append chosen dv to complete dv array =============================================
            dv_traf[idx1] += dv
            traf_nconf[idx1] += 1

            # Update conflict counter
            nconf += 1

# STEP 5: Compute average of horizontal pairwise-summed state changes =================================
        dv_traf[:,
                0] = np.where(dv_traf[:, 0] == 0., 0., dv_traf[:, 0] /
                              traf_nconf)  # average eartherly gs state-change
        dv_traf[:,
                1] = np.where(dv_traf[:, 1] == 0., 0., dv_traf[:, 1] /
                              traf_nconf)  # average northerly gs state-change

        # STEP 6: Update velocity vector of all aircraft =====================================================
        newtrk = np.radians(newtrk)
        newgseast = newgs * np.sin(newtrk)
        newgsnorth = newgs * np.cos(newtrk)
        v_traf = np.array([newgseast, newgsnorth, newvs])
        v_traf += dv_traf.T

        # Convert velocity vector to cyclindrical coordinate system
        newtrk = (np.arctan2(v_traf[0, :], v_traf[1, :]) * 180 / np.pi) % 360
        newgs = (np.sqrt(v_traf[0, :]**2 + v_traf[1, :]**2))
        newvs = v_traf[2, :]

        # STEP 7: Cap the velocity and vertical speed and return =============================================
        newgscapped = np.maximum(ownship.perf.vmin,
                                 np.minimum(ownship.perf.vmax, newgs))
        newvscapped = np.maximum(ownship.perf.vsmin,
                                 np.minimum(ownship.perf.vsmax, newvs))

        return newtrk, newgscapped, newvscapped, newalt
Ejemplo n.º 32
0
    def update(self):
        self.sectorsd = np.zeros(len(self.sectors))
        self.sectorconv = np.zeros(len(self.sectors))
        self.sectoreff = []
        if not traf.ntraf or not self.sectors:
            return

        # Check convergence using CD with large RPZ and tlook
        confpairs, lospairs, inconf, tcpamax, qdr, dist, dcpa, tcpa, tLOS = \
            traf.asas.cd.detect(traf, traf, 20 * nm, traf.asas.dh, 3600)

        if confpairs:
            own, intr = zip(*confpairs)
            ownidx = traf.id2idx(own)
            mask = traf.alt[ownidx] > 70 * ft
            ownidx = np.array(ownidx)[mask]
            dcpa = np.array(dcpa)[mask]
            tcpa = np.array(tcpa)[mask]
        else:
            ownidx = np.array([])
    
        sendeff = False
        for idx, (sector, previnside) in enumerate(zip(self.sectors, self.acinside)):
            inside = areafilter.checkInside(sector, traf.lat, traf.lon, traf.alt)
            sectoreff = []
            # Detect aircraft leaving and entering the sector
            previds = set(previnside.acid)
            ids = set(np.array(traf.id)[inside])
            arrived = list(ids - previds)
            left = previds - ids

            # Split left aircraft in deleted and not deleted
            left_intraf = left.intersection(traf.id)
            left_del = list(left - left_intraf)
            left_intraf = list(left_intraf)

            arridx = traf.id2idx(arrived)
            leftidx = traf.id2idx(left_intraf)
            # Retrieve the current distance flown for arriving and leaving aircraft

            arrdist = traf.distflown[arridx]
            arrlat = traf.lat[arridx]
            arrlon = traf.lon[arridx]
            leftlat, leftlon, leftdist = self.delac.get(left_del)
            leftlat = np.append(leftlat, traf.lat[leftidx])
            leftlon = np.append(leftlon, traf.lon[leftidx])
            leftdist = np.append(leftdist, traf.distflown[leftidx])
            leftlat0, leftlon0, leftdist0 = previnside.get(left_del + left_intraf)
            self.delac.delete(left_del)

            if len(left) > 0:
                q, d = geo.qdrdist(leftlat0, leftlon0, leftlat, leftlon)
                
                # Exclude aircraft where origin = destination
                mask = d > 10

                sectoreff = list((leftdist[mask] - leftdist0[mask]) / d[mask] / nm)

                names = np.array(left_del + left_intraf)[mask]
                for name, eff in zip(names, sectoreff):
                    self.feff.write('{}, {}, {}\n'.format(sim.simt, name, eff))
                sendeff = True
                # print('{} aircraft left sector {}, distance flown (acid:dist):'.format(len(left), sector))
                # for a, d0, d1, e in zip(left, leftdist0, leftdist, sectoreff):
                #     print('Aircraft {} flew {} meters (eff = {})'.format(a, round(d1-d0), e))

            # Update inside data for this sector
            previnside.delete(left)
            previnside.extend(arrived, arrlat, arrlon, arrdist)

            self.sectoreff.append(sectoreff)

            self.sectorsd[idx] = np.count_nonzero(inside)
            insidx = np.where(np.logical_and(inside, inconf))
            pairsinside = np.isin(ownidx, insidx)
            if len(pairsinside):
                tnorm = np.array(tcpa)[pairsinside] / 300.0
                dcpanorm = np.array(dcpa)[pairsinside] / (5.0 * nm)
                self.sectorconv[idx] = np.sum(
                    np.sqrt(2.0 / tnorm * tnorm + dcpanorm * dcpanorm))
            else:
                self.sectorconv[idx] = 0

            self.fconv.write('{}, {}\n'.format(sim.simt, self.sectorconv[idx]))
            self.fsd.write('{}, {}\n'.format(sim.simt, self.sectorsd[idx]))
        if sendeff:
            self.effplot.send()
Ejemplo n.º 33
0
    def __init__(self,name,cmd,cmdargs):
        self.name    = name.upper()
        self.tupdate = sim.simt-999.
        self.flow    = 0
        self.incircle = True
        self.segdir  = None  # Segment direction in degrees
        self.hdg     = None # When runway is used as separate airport

        # Is location a circle segment?
        if swcircle and self.name[:4]=="SEGM":
            self.type = "seg"
            self.lat,self.lon,brg = getseg(self.name)
            pass

        else:
            success, posobj = txt2pos(name,ctrlat,ctrlon)
            if success:
                # for runway type, get heading as default optional argument for command line
                if posobj.type == "rwy":
                    aptname, rwyname = name.split('/RW')
                    rwyname = rwyname.lstrip('Y')
                    try:
                        self.hdg = navdb.rwythresholds[aptname][rwyname][2]
                    except:
                        self.hdg = None
                        pass
                else:
                    rwyhdg = None


                self.lat,self.lon = posobj.lat, posobj.lon
                self.type = posobj.type

                # If outside circle change lat,lon to edge of circle
                self.incircle = incircle(self.lat,self.lon)
                if not self.incircle:
                    self.type = "seg"
                    self.segdir,dist = qdrdist(ctrlat,ctrlon,posobj.lat,posobj.lon)
                    segname = "SEGM"+str(int(round(self.segdir)))
                    self.lat,self.lon,hdg = getseg(segname)

            else:
                print("ERROR: trafgen class Source called for "+name+". Position not found")
                self.lat,self.lon = 0.0,0.0


        # Aircraft types
        self.actypes = ["*"]

        # Runways
        self.runways    = [] # name
        self.rwylat     = []
        self.rwylon     = []
        self.rwyhdg     = []
        self.rwyline    = [] # number of aircraft waiting in line
        self.rwytotime  = [] # time of last takeoff

        # If source type is a runway, add it as the only runway
        if self.type=="rwy":
            self.runways = [rwyname]
            self.rwylat     = [self.lat]
            self.rwylon     = [self.lon]
            self.rwyhdg     = [int(rwyname.rstrip("LCR").lstrip("0"))*10.]
            self.rwyline    = [] # number of aircraft waiting in line
            self.rwytotime  = [-999] # time of last takeoff


        self.dtakeoff = 90. # sec take-off interval on one runway, default 90 sec

        # Destinations
        self.dest        = []
        self.destlat     = []
        self.destlon     = []
        self.desttype    = [] # "apt","wpt","seg"
        self.desthdg     = []  # if dest is a runway (for flights within FIR) or circle segment (source outside FIR)
        self.destactypes = []   # Types for this destinations ([]=use defaults for this source)

        #Names of drawing objects runways
        self.polys       = []    # Names of current runway polygons to remove when runways change

        # Start values
        self.startaltmin = -999. # [ft] minimum starting altitude
        self.startaltmax = -999. # [ft] maximum starting altitude
        self.startspdmin = -999. # [m/s] minimum starting speed
        self.startspdmax = -999. # [m/s] maximum starting speed
        self.starthdgmin = -999. # [deg] Valid values -360 - 360 degrees (to also have an interval possible around 360)
        self.starthdgmax = -999. # [deg] Valid values -360 - 360 degrees (to also have an interval possible around 360)


        return
Ejemplo n.º 34
0
    def __init__(self,name,cmd,cmdargs):
        self.name    = name.upper()
        self.tupdate = sim.simt-999.
        self.flow    = 0
        self.incircle = True
        self.segdir  = None  # Segment direction in degrees

        # Is location a circle segment?
        if self.name[:4]=="SEGM":
            self.type = "seg"
            self.lat,self.lon,brg = getseg(self.name) # For SEGMnnn to SEGMnnn for crossing flights optional
            pass

        else:
            success, posobj = txt2pos(name,ctrlat,ctrlon)
            if success:
                # for runway type, get heading as default optional argument for command line
                if posobj.type == "rwy":
                    aptname, rwyname = name.split('/RW')
                    rwyname = rwyname.lstrip('Y')
                    try:
                        rwyhdg = navdb.rwythresholds[aptname][rwyname][2]
                    except:
                        rwyhdg = None
                        pass
                else:
                    rwyhdg = None


                self.lat,self.lon = posobj.lat, posobj.lon
                self.type = posobj.type

                # If outside circle change lat,lon to edge of circle
                self.incircle = incircle(self.lat,self.lon)
                if not self.incircle:
                    self.type = "seg"
                    self.segdir,dist = qdrdist(ctrlat,ctrlon,posobj.lat,posobj.lon)
                    segname = "SEGM"+str(int(round(self.segdir)))
                    self.lat,self.lon,hdg = getseg(segname)

            else:
                print("ERROR: trafgen class Drain called for "+name+". Position not found")
                self.lat,self.lon = 0.0,0.0


        # Aircraft types
        self.actypes = ["*"]

        # Runways
        self.runways    = [] # name
        self.rwylat     = []
        self.rwylon     = []
        self.rwyhdg     = []
        self.rwyline    = [] # number of aircraft waiting in line

        #Origins
        self.orig        = []
        self.origlat     = []
        self.origlon     = []
        self.origtype    = [] # "apt","wpt","seg"
        self.orighdg     = []  # if orig is a runway (for flights within FIR) or circle segment (drain outside FIR)
        self.origactypes = []   # Types for this originations ([]=use defaults for this drain)
        self.origincirc  = []

        #Names of drawing objects runways
        self.polys       = []

        return
Ejemplo n.º 35
0
    def update(self,gain):
        # Time step update of drain

        # Get time step
        dt = sim.simt - self.tupdate
        self.tupdate = sim.simt

        # Time for a new aircraft?
        if dt>0.0:

            # Calculate probability of a geberate occurring with flow
            chances = 1.0-gain*self.flow*dt/3600. #flow is in a/c per hour=360 seconds
            if random.random() >= chances:
                # Calculate starting position using origin
                if len(self.orig)>0:
                    # Add origin
                    iorig = int(random.random() * len(self.orig))
                else:
                    iorig = -1

                if iorig>=0:
                    incirc = self.origincirc[iorig]
                    lat,lon = self.origlat[iorig],self.origlon[iorig]
                    hdg,dist = qdrdist(lat,lon,self.lat,self.lon)
                else:
                    print("Warning update drain",self.name,"called with no origins present!")
                    hdg = random.random()*360.
                    print("using random segment",int(hdg+180)%360)

                    incirc = False

                if not incirc:
                    lat,lon = kwikpos(ctrlat,ctrlon,(hdg+180)%360,radius)
                elif self.origtype=="seg":
                    lat,lon,brg = getseg(self.name)
                    hdg = (brg+180)%360
                else:
                    hdg = random.random()*360.

                if incirc and (self.origtype[iorig]=="apt" or self.origtype[iorig]=="rwy"):
                    alttxt,spdtxt = str(0),str(0)
                else:
                    alttxt,spdtxt = "FL"+str(random.randint(200,300)), str(random.randint(250,350))

                if iorig>=0:
                    acid = randacname(self.orig[iorig], self.name)
                else:
                    acid = randacname("LFPG", self.name)

                if len(self.origactypes)>0:
                    actype = random.choice(self.origactypes[iorig])
                else:
                    actype = random.choice(self.actypes)

                stack.stack("CRE " + ",".join([acid,actype,str(lat), str(lon),
                                               str(int(hdg%360)),alttxt,spdtxt]))
                if iorig>=0:
                    if self.orig[iorig][:4]!="SEGM":
                        stack.stack(acid + " ORIG " + self.orig[iorig])
                    else:
                        stack.stack(acid + " ORIG " + str(self.origlat[iorig]) + " " +\
                                     str(self.origlon[iorig]))
                if not (self.name[:4]=="SEGM"):
                    stack.stack(acid + " DEST " + self.name)
                else:
                    stack.stack(acid + " ADDWPT " + str(self.lat) + " " + str(self.lon))

                if alttxt=="0" and spdtxt =="0":
                    stack.stack(acid+" SPD 250")
                    stack.stack(acid+" ALT FL100")
                    #stack.stack(acid+" LNAV ON")
                else:
                    stack.stack(acid + " LNAV ON")
Ejemplo n.º 36
0
    def newcalcfp(self):
        """Do flight plan calculations"""

        # Remove old top of descents and old top of climbs
        while self.wpname.count("T/D")>0:
            self.delwpt("T/D")

        while self.wpname.count("T/C")>0:
            self.delwpt("T/C")

        # Remove old actual position waypoints
        while self.wpname.count("A/C")>0:
            self.delwpt("A/C")

        # Insert actual position as A/C waypoint
        idx = self.iactwp
        self.insertcalcwp(idx,"A/C")
        self.wplat[idx] = bs.traf.lat[self.iac] # deg
        self.wplon[idx] = bs.traf.lon[self.iac] # deg
        self.wpalt[idx] = bs.traf.alt[self.iac] # m
        self.wpspd[idx] = bs.traf.tas[self.iac] # m/s

        # Calculate distance to last waypoint in route
        nwp = len(self.wpname)
        dist2go = [0.0]
        for i in range(nwp - 2, -1, -1):
            qdr, dist = geo.qdrdist(self.wplat[i], self.wplon[i],
                                    self.wplat[i + 1], self.wplon[i + 1])
            dist2go = [dist2go[0] + dist] + dist2go

        # Make VNAV WP list with only waypoints with altitude constraints
        # This list we will use to find where to insert t/c and t/d
        alt = []
        x   = []
        name = []
        for i in range(nwp):
            if self.wpalt[i]>-1.:
                alt.append(self.wpalt[i])
                x.append(dist2go[i])
                name.append(self.wpname[i]+" ")    # space for check first 2 chars later

        # Find where to insert cruise segment (if any)

        # Find longest segment without altitude constraints

        desslope = clbslope = 1.
        crzalt = bs.traf.crzalt[self.iac]
        if crzalt>0.:
            ilong  = -1
            dxlong = 0.0

            nvwp = len(alt)
            for i in range(nvwp-1):
                if x[i]-x[i+1]> dxlong:
                    ilong  = i
                    dxlong = x[i]-x[i+1]

            # VNAV parameters to insert T/Cs and T/Ds
            crzdist  = 20.*nm   # minimally required distance at cruise level
            clbslope = 3000.*ft/(10.*nm)    # 1:3 rule for now
            desslope = clbslope             # 1:3 rule for now

            # Can we get a sufficient distance at cruise altitude?
            if max(alt[ilong],alt[ilong+1]) < crzalt :
                dxclimb = (crzalt-alt[ilong])*clbslope
                dxdesc  = (crzalt-alt[ilong+1])*desslope
                if x[ilong] - x[ilong+1] > dxclimb + crzdist + dxdesc:

                    # Insert T/C (top of climb) at cruise level
                   name.insert(ilong+1,"T/C")
                   alt.insert(ilong+1,crzalt)
                   x.insert(ilong+1,x[ilong]+dxclimb)

                    # Insert T/D (top of descent) at cruise level
                   name.insert(ilong+2,"T/D")
                   alt.insert(ilong+2,crzalt)
                   x.insert(ilong+2,x[ilong+1]-dxdesc)

        # Compare angles to rates:
        epsh = 50.*ft   # Nothing to be done for small altitude changes
        epsx = 1.*nm    # [m] Nothing to be done at this short range
        i = 0
        while i<len(alt)-1:
            if name[i][:2]=="T/":
                continue

            dy = alt[i+1]-alt[i]   # alt change (pos = climb)
            dx = x[i]-x[i+1]       # distance (positive)

            dxdes = abs(dy)/desslope
            dxclb = abs(dy)/clbslope

            if dy<epsh and  dx + epsx > dxdes:   # insert T/D?

               name.insert(i+1,"T/D")
               alt.insert(i+1,alt[i])
               x.insert(i+1,x[i+1]-dxdes)
               i += 1

            elif dy>epsh and  dx + epsx > dxclb:  # insert T/C?

               name.insert(i+1,"T/C")
               alt.insert(i+1,alt[i+1])
               x.insert(i+1,x[i]+dxclb)
               i += 2
            else:
                i += 1

        # Now insert T/Cs and T/Ds in actual flight plan
        nvwp = len(alt)
        for i in range(nvwp,-1,-1):

            # Copy all new waypoints (which are all named T/C or T/D)
            if name[i][:2]=="T/":

                # Find place in flight plan to insert T/C or T/D
                j = nvwp-1
                while dist2go[j]<x[i] and j>1:
                    j=j-1

                # Interpolation factor for position on leg
                f   = (x[i]-dist2go[j+1])/(dist2go[j]-dist2go[j+1])

                lat = f*self.wplat[j]+(1.-f)*self.wplat[j+1]
                lon = f*self.wplon[j]+(1.-f)*self.wplon[j+1]

                self.wpname.insert(j,name[i])
                self.wptype.insert(j,Route.calcwp)
                self.wplat.insert(j,lat)
                self.wplon.insert(j,lon)
                self.wpalt.insert(j,alt[i])
                self.wpspd.insert(j,-999.)
Ejemplo n.º 37
0
    def update(self, simt):
        # Scheduling: when dt has passed or restart
        if self.t0 + self.dt < simt or simt < self.t0 or simt<self.dt:
            self.t0 = simt

            # FMS LNAV mode:
            # qdr[deg],distinnm[nm]
            qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat, bs.traf.actwp.lon)  # [deg][nm])
            dist = distinnm*nm # Conversion to meters

            # Shift waypoints for aircraft i where necessary
            for i in bs.traf.actwp.Reached(qdr,dist,bs.traf.actwp.flyby):
                # Save current wp speed
                oldspd = bs.traf.actwp.spd[i]

                # Get next wp (lnavon = False if no more waypoints)
                lat, lon, alt, spd, bs.traf.actwp.xtoalt[i], toalt, \
                          lnavon, flyby, bs.traf.actwp.next_qdr[i] =  \
                       self.route[i].getnextwp()  # note: xtoalt,toalt in [m]

                # End of route/no more waypoints: switch off LNAV
                bs.traf.swlnav[i] = bs.traf.swlnav[i] and lnavon

                # In case of no LNAV, do not allow VNAV mode on its own
                bs.traf.swvnav[i] = bs.traf.swvnav[i] and bs.traf.swlnav[i]

                bs.traf.actwp.lat[i]   = lat  # [deg]
                bs.traf.actwp.lon[i]   = lon  # [deg]
                bs.traf.actwp.flyby[i] = int(flyby)  # 1.0 in case of fly by, else fly over

                # User has entered an altitude for this waypoint
                if alt >= -0.01:
                    bs.traf.actwp.nextaltco[i] = alt #[m]

                if spd > 0. and bs.traf.swlnav[i] and bs.traf.swvnav[i]:

                    # Valid speed and LNAV and VNAV ap modes are on
                    # Depending on crossover altitude we fix CAS or Mach
                    if bs.traf.abco[i] and spd>2.0:
                        bs.traf.actwp.spd[i] = cas2mach(spd,bs.traf.alt[i])

                    elif bs.traf.belco[i] and spd<=2.0:
                        bs.traf.actwp.spd[i] = mach2cas(spd,bs.traf.alt[i])

                    else:
                        bs.traf.actwp.spd[i] = spd

                else:
                    bs.traf.actwp.spd[i] = -999.

                # VNAV spd mode: use speed of this waypoint as commanded speed
                # while passing waypoint and save next speed for passing next wp
                # Speed is now from speed! Next speed is ready in wpdata
                if bs.traf.swvnav[i] and oldspd > 0.0:
                        bs.traf.selspd[i] = oldspd

                # VNAV = FMS ALT/SPD mode
                self.ComputeVNAV(i, toalt, bs.traf.actwp.xtoalt[i])

            #=============== End of Waypoint switching loop ===================

            #================= Continuous FMS guidance ========================

            # Waypoint switching in the loop above was scalar (per a/c with index i)
            # Code below is vectorized, with arrays for all aircraft

            # Do VNAV start of descent check
            dy = (bs.traf.actwp.lat - bs.traf.lat)  #[deg lat = 60 nm]
            dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat #[corrected deg lon = 60 nm]
            dist2wp   = 60. * nm * np.sqrt(dx * dx + dy * dy) # [m]

            # VNAV logic: descend as late as possible, climb as soon as possible
            startdescent = (dist2wp < self.dist2vs) + (bs.traf.actwp.nextaltco > bs.traf.alt)

            # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
            #    (because there are no more waypoints). This is needed
            #    to continue descending when you get into a conflict
            #    while descending to the destination (the last waypoint)
            #    Use 0.1 nm (185.2 m) circle in case turndist might be zero
            self.swvnavvs = bs.traf.swvnav * np.where(bs.traf.swlnav, startdescent,
                                         dist <= np.maximum(185.2,bs.traf.actwp.turndist))

            #Recalculate V/S based on current altitude and distance to next alt constraint
            # How much time do we have before we need to descend?

            t2go2alt = np.maximum(0.,(dist2wp + bs.traf.actwp.xtoalt - bs.traf.actwp.turndist)) \
                                        / np.maximum(0.5,bs.traf.gs)

            # use steepness to calculate V/S unless we need to descend faster
            bs.traf.actwp.vs = np.maximum(self.steepness*bs.traf.gs, \
                                   np.abs((bs.traf.actwp.nextaltco-bs.traf.alt))  \
                                   /np.maximum(1.0,t2go2alt))


            self.vnavvs  = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
            #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

            # self.vs = np.where(self.swvnavvs, self.vnavvs, bs.traf.apvsdef * bs.traf.limvs_flag)
            selvs = np.where(abs(bs.traf.selvs) > 0.1, bs.traf.selvs, bs.traf.apvsdef) # m/s
            self.vs = np.where(self.swvnavvs, self.vnavvs, selvs)
            self.alt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco, bs.traf.selalt)

            # When descending or climbing in VNAV also update altitude command of select/hold mode
            bs.traf.selalt = np.where(self.swvnavvs,bs.traf.actwp.nextaltco,bs.traf.selalt)

            # LNAV commanded track angle
            self.trk = np.where(bs.traf.swlnav, qdr, self.trk)

            # FMS speed guidance: anticipate accel distance

            # Actual distance it takes to decelerate
            nexttas  = vcasormach2tas(bs.traf.actwp.spd,bs.traf.alt)
            tasdiff  = nexttas - bs.traf.tas # [m/s]
            dtspdchg = np.abs(tasdiff)/np.maximum(0.01,np.abs(bs.traf.ax)) #[s]
            dxspdchg = 0.5*np.sign(tasdiff)*np.abs(bs.traf.ax)*dtspdchg*dtspdchg + bs.traf.tas*dtspdchg #[m]

            usespdcon      = (dist2wp < dxspdchg)*(bs.traf.actwp.spd > 0.)*bs.traf.swvnav
            bs.traf.selspd = np.where(usespdcon, bs.traf.actwp.spd, bs.traf.selspd)

        # Below crossover altitude: CAS=const, above crossover altitude: Mach = const
        self.tas = vcasormach2tas(bs.traf.selspd, bs.traf.alt)
Ejemplo n.º 38
0
    def update(self, simt):
        # Scheduling: when dt has passed or restart
        if self.t0 + self.dt < simt or simt < self.t0 or simt<self.dt:
            self.t0 = simt

            # FMS LNAV mode:
            # qdr[deg],distinnm[nm]
            qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat, bs.traf.actwp.lon)  # [deg][nm])
            dist = distinnm*nm # Conversion to meters

            # Shift waypoints for aircraft i where necessary
            for i in bs.traf.actwp.Reached(qdr,dist,bs.traf.actwp.flyby):
                # Save current wp speed
                oldspd = bs.traf.actwp.spd[i]

                # Get next wp (lnavon = False if no more waypoints)
                lat, lon, alt, spd, bs.traf.actwp.xtoalt[i], toalt, \
                          lnavon, flyby, bs.traf.actwp.next_qdr[i] =  \
                       self.route[i].getnextwp()  # note: xtoalt,toalt in [m]

                # End of route/no more waypoints: switch off LNAV
                bs.traf.swlnav[i] = bs.traf.swlnav[i] and lnavon

                # In case of no LNAV, do not allow VNAV mode on its own
                bs.traf.swvnav[i] = bs.traf.swvnav[i] and bs.traf.swlnav[i]

                bs.traf.actwp.lat[i]   = lat  # [deg]
                bs.traf.actwp.lon[i]   = lon  # [deg]
                bs.traf.actwp.flyby[i] = int(flyby)  # 1.0 in case of fly by, else fly over

                # User has entered an altitude for this waypoint
                if alt >= -0.01:
                    bs.traf.actwp.nextaltco[i] = alt #[m]

                if spd > -990. and bs.traf.swlnav[i] and bs.traf.swvnav[i]:

                    # Valid speed and LNAV and VNAV ap modes are on
                    # Depending on crossover altitude we fix CAS or Mach
                    if bs.traf.abco[i] and spd>1.0:
                        bs.traf.actwp.spd[i] = cas2mach(spd,bs.traf.alt[i])

                    elif bs.traf.belco[i] and 0. < spd<=1.0:
                        bs.traf.actwp.spd[i] = mach2cas(spd,bs.traf.alt[i])

                    else:
                        bs.traf.actwp.spd[i] = spd
                        
                else:
                    bs.traf.actwp.spd[i] = -999.

                # VNAV spd mode: use speed of this waypoint as commanded speed
                # while passing waypoint and save next speed for passing next wp
                # Speed is now from speed! Next speed is ready in wpdata
                if bs.traf.swvnav[i] and oldspd > 0.0:
                        bs.traf.selspd[i] = oldspd


                # VNAV = FMS ALT/SPD mode
                self.ComputeVNAV(i, toalt, bs.traf.actwp.xtoalt[i])

            #=============== End of Waypoint switching loop ===================

            #================= Continuous FMS guidance ========================

            # Waypoint switching in the loop above was scalar (per a/c with index i)
            # Code below is vectorized, with arrays for all aircraft

            # Do VNAV start of descent check
            dy = (bs.traf.actwp.lat - bs.traf.lat)  #[deg lat = 60 nm]
            dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat #[corrected deg lon = 60 nm]
            dist2wp   = 60. * nm * np.sqrt(dx * dx + dy * dy) # [m]

            # VNAV logic: descend as late as possible, climb as soon as possible
            startdescent = (dist2wp < self.dist2vs) + (bs.traf.actwp.nextaltco > bs.traf.alt)

            # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
            #    (because there are no more waypoints). This is needed
            #    to continue descending when you get into a conflict
            #    while descending to the destination (the last waypoint)
            #    Use 0.1 nm (185.2 m) circle in case turndist might be zero
            self.swvnavvs = bs.traf.swvnav * np.where(bs.traf.swlnav, startdescent,
                                         dist <= np.maximum(185.2,bs.traf.actwp.turndist))

            #Recalculate V/S based on current altitude and distance to next alt constraint
            # How much time do we have before we need to descend?

            t2go2alt = np.maximum(0.,(dist2wp + bs.traf.actwp.xtoalt - bs.traf.actwp.turndist)) \
                                        / np.maximum(0.5,bs.traf.gs)

            # use steepness to calculate V/S unless we need to descend faster
            bs.traf.actwp.vs = np.maximum(self.steepness*bs.traf.gs, \
                                   np.abs((bs.traf.actwp.nextaltco-bs.traf.alt))  \
                                   /np.maximum(1.0,t2go2alt))


            self.vnavvs  = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
            #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

            # self.vs = np.where(self.swvnavvs, self.vnavvs, bs.traf.apvsdef * bs.traf.limvs_flag)
            selvs = np.where(abs(bs.traf.selvs) > 0.1, bs.traf.selvs, bs.traf.apvsdef) # m/s
            self.vs = np.where(self.swvnavvs, self.vnavvs, selvs)
            self.alt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco, bs.traf.selalt)

            # When descending or climbing in VNAV also update altitude command of select/hold mode
            bs.traf.selalt = np.where(self.swvnavvs,bs.traf.actwp.nextaltco,bs.traf.selalt)

            # LNAV commanded track angle
            self.trk = np.where(bs.traf.swlnav, qdr, self.trk)

            # FMS speed guidance: anticipate accel distance

            # Actual distance it takes to decelerate
            nexttas  = vcasormach2tas(bs.traf.actwp.spd,bs.traf.alt)
            tasdiff  = nexttas - bs.traf.tas # [m/s]
            dtspdchg = np.abs(tasdiff)/np.maximum(0.01,np.abs(bs.traf.ax)) #[s]
            dxspdchg = 0.5*np.sign(tasdiff)*np.abs(bs.traf.ax)*dtspdchg*dtspdchg + bs.traf.tas*dtspdchg #[m]

            usespdcon      = (dist2wp < dxspdchg)*(bs.traf.actwp.spd > -990.)*bs.traf.swvnav
            bs.traf.selspd = np.where(usespdcon, bs.traf.actwp.spd, bs.traf.selspd)
            

        # Below crossover altitude: CAS=const, above crossover altitude: Mach = const
        self.tas = vcasormach2tas(bs.traf.selspd, bs.traf.alt)
Ejemplo n.º 39
0
    def update(self):
        # FMS LNAV mode:
        # qdr[deg],distinnm[nm]
        qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon,
                                    bs.traf.actwp.lat, bs.traf.actwp.lon)  # [deg][nm])
        dist2wp = distinnm*nm  # Conversion to meters


        # FMS route update and possibly waypoint shift. Note: qdr, dist2wp will be updated accordingly in case of wp switch
        self.update_fms(qdr, dist2wp)

        #================= Continuous FMS guidance ========================

        # Waypoint switching in the loop above was scalar (per a/c with index i)
        # Code below is vectorized, with arrays for all aircraft

        # Do VNAV start of descent check
        #dy = (bs.traf.actwp.lat - bs.traf.lat)  #[deg lat = 60 nm]
        #dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat #[corrected deg lon = 60 nm]
        #dist2wp   = 60. * nm * np.sqrt(dx * dx + dy * dy) # [m]

        # VNAV logic: descend as late as possible, climb as soon as possible
        startdescent = (dist2wp < self.dist2vs) + (bs.traf.actwp.nextaltco > bs.traf.alt)

        # If not lnav:Climb/descend if doing so before lnav/vnav was switched off
        #    (because there are no more waypoints). This is needed
        #    to continue descending when you get into a conflict
        #    while descending to the destination (the last waypoint)
        #    Use 0.1 nm (185.2 m) circle in case turndist might be zero
        self.swvnavvs = bs.traf.swvnav * np.where(bs.traf.swlnav, startdescent,
                                        dist2wp <= np.maximum(185.2,bs.traf.actwp.turndist))

        #Recalculate V/S based on current altitude and distance to next alt constraint
        # How much time do we have before we need to descend?

        t2go2alt = np.maximum(0.,(dist2wp + bs.traf.actwp.xtoalt - bs.traf.actwp.turndist)) \
                                    / np.maximum(0.5,bs.traf.gs)

        # use steepness to calculate V/S unless we need to descend faster
        bs.traf.actwp.vs = np.maximum(self.steepness*bs.traf.gs, \
                                np.abs((bs.traf.actwp.nextaltco-bs.traf.alt))  \
                                /np.maximum(1.0,t2go2alt))

        self.vnavvs  = np.where(self.swvnavvs, bs.traf.actwp.vs, self.vnavvs)
        #was: self.vnavvs  = np.where(self.swvnavvs, self.steepness * bs.traf.gs, self.vnavvs)

        # self.vs = np.where(self.swvnavvs, self.vnavvs, bs.traf.apvsdef * bs.traf.limvs_flag)
        selvs    = np.where(abs(bs.traf.selvs) > 0.1, bs.traf.selvs, bs.traf.apvsdef) # m/s
        self.vs  = np.where(self.swvnavvs, self.vnavvs, selvs)
        self.alt = np.where(self.swvnavvs, bs.traf.actwp.nextaltco, bs.traf.selalt)

        # When descending or climbing in VNAV also update altitude command of select/hold mode
        bs.traf.selalt = np.where(self.swvnavvs,bs.traf.actwp.nextaltco,bs.traf.selalt)

        # LNAV commanded track angle
        self.trk = np.where(bs.traf.swlnav, qdr, self.trk)

        # FMS speed guidance: anticipate accel/decel distance for next leg or turn

        # Calculate actual distance it takes to decelerate/accelerate based on two cases: turning speed (decel)

        # Normally next leg speed (actwp.spd) but in case we fly turns with a specified turn speed
        # use the turn speed

        # Is turn speed specified and are we not already slow enough? We only decelerate for turns, not accel.
        turntas       = np.where(bs.traf.actwp.turnspd>0.0, vcas2tas(bs.traf.actwp.turnspd, bs.traf.alt),
                                 -1.0+0.*bs.traf.tas)
        swturnspd     = bs.traf.actwp.flyturn*(turntas>0.0)*(bs.traf.tas>turntas)*(bs.traf.actwp.turnspd>0.0)
        turntasdiff   = (bs.traf.tas - turntas)*(turntas>0.0)

        # dt = dv/a ;  dx = v*dt + 0.5*a*dt2 => dx = dv/a * (v + 0.5*dv)
        dxturnspdchg  = np.where(swturnspd, turntasdiff/np.maximum(0.01,np.abs(bs.traf.ax)*(bs.traf.tas+0.5*np.abs(turntasdiff))),
                                                                   0.0*bs.traf.tas)

        # Decelerate or accelerate for next required speed because of speed constraint or RTA speed
        nexttas   = vcasormach2tas(bs.traf.actwp.spd,bs.traf.alt)
        tasdiff   = nexttas - bs.traf.tas # [m/s]

        # dt = dv/a   dx = v*dt + 0.5*a*dt2 => dx = dv/a * (v + 0.5*dv)
        dxspdconchg = np.abs(tasdiff)/np.maximum(0.01, np.abs(bs.traf.ax)) * (bs.traf.tas+0.5*np.abs(tasdiff))


        # Check also whether VNAVSPD is on, if not, SPD SEL has override for next leg
        # and same for turn logic
        usespdcon = (dist2wp < dxspdconchg)*(bs.traf.actwp.spdcon> -990.) * \
                            bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav

        useturnspd = (dist2wp < dxturnspdchg)*swturnspd * \
                            bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav

        # Which CAS/Mach do we have to keep? VNAV, last turn or next turn?
        oncurrentleg = (abs(bs.traf.trk - qdr) < 2.0)
        inoldturn    = (bs.traf.actwp.oldturnspd > 0.) * np.logical_not(oncurrentleg)   # [deg]

        # Avoid using old turning speeds when turning of this leg ot the next leg
        # by disabling (old) turningspd when on leg
        bs.traf.actwp.oldturnspd = np.where(oncurrentleg*bs.traf.actwp.oldturnspd, -999.,
                                            bs.traf.actwp.oldturnspd)

        # Select speed
        bs.traf.selspd = np.where(useturnspd,bs.traf.actwp.turnspd,np.where(usespdcon, bs.traf.actwp.spd, bs.traf.selspd))

        # Temporary override when still in old turn
        bs.traf.selspd = np.where(inoldturn*(bs.traf.actwp.oldturnspd)*bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav,
                                  bs.traf.actwp.oldturnspd,bs.traf.selspd)

        # Below crossover altitude: CAS=const, above crossover altitude: Mach = const
        self.tas = vcasormach2tas(bs.traf.selspd, bs.traf.alt)

        return