func FixupComplaint(c *types.Complaint, key *datastore.Key) { // 0. Snag the key, so we can refer to this object later c.DatastoreKey = key.Encode() // 1. GAE datastore helpfully converts timezones to UTC upon storage; fix that c.Timestamp = date.InPdt(c.Timestamp) // 2. Compute the flight details URL, if within 24 days age := date.NowInPdt().Sub(c.Timestamp) if age < time.Hour*24 { // c.AircraftOverhead.Fr24Url = c.AircraftOverhead.PlaybackUrl() c.AircraftOverhead.Fr24Url = "http://flightaware.com/live/flight/" + c.AircraftOverhead.FlightNumber // Or: http://flightaware.com/live/flight/UAL337/history/20151215/ [0655Z/KLAX/KSFO] // date is UTC of departure time; might be tricky to guess :/ } // 3. Compute distances, if we have an aircraft if c.AircraftOverhead.FlightNumber != "" { a := c.AircraftOverhead aircraftPos := geo.Latlong{a.Lat, a.Long} observerPos := geo.Latlong{c.Profile.Lat, c.Profile.Long} c.Dist2KM = observerPos.Dist(aircraftPos) c.Dist3KM = observerPos.Dist3(aircraftPos, a.Altitude) } }
func AirspaceToLocalizedAircraft(as *airspace.Airspace, pos geo.Latlong, elev float64) []Aircraft { ret := []Aircraft{} if as == nil { return ret } for _, ad := range as.Aircraft { tp := fdb.TrackpointFromADSB(ad.Msg) altitudeDelta := tp.Altitude - elev icaoid := string(ad.Msg.Icao24) icaoid = strings.TrimPrefix(icaoid, "EE") // fr24 airspaces use this prefix icaoid = strings.TrimPrefix(icaoid, "FF") // fa airspaces use this prefix a := Aircraft{ Dist: pos.DistKM(tp.Latlong), Dist3: pos.Dist3(tp.Latlong, altitudeDelta), BearingFromObserver: tp.Latlong.BearingTowards(pos), //Id: "someid", // This is set via an IdSpec string below Id2: icaoid, Lat: tp.Lat, Long: tp.Long, Track: tp.Heading, Altitude: tp.Altitude, Speed: tp.GroundSpeed, // Squawk string Radar: tp.ReceiverName, EquipType: ad.Airframe.EquipmentType, Registration: ad.Airframe.Registration, Epoch: float64(tp.TimestampUTC.Unix()), Origin: ad.Schedule.Origin, Destination: ad.Schedule.Destination, FlightNumber: ad.Schedule.IataFlight(), // Unknown float64 VerticalSpeed: tp.VerticalRate, Callsign: ad.Msg.Callsign, //"CAL123", //snap.Flight.Callsign, // Unknown2 float64 } // Even though we may be parsing an fr24 airspace, generate a skypi idspec (which may break) if icaoid != "" { a.Id = fdb.IdSpec{IcaoId: icaoid, Time: tp.TimestampUTC}.String() } // Hack for Surf Air; promote their callsigns into flightnumbers. // The goal is to allow them to get past the filter, and be printable etc. // There is probably a much better place for this kind of munging. But it needs to happen // regardless of the source of the airspace, so it can't be pushed upstream. (...?) if a.FlightNumber == "" && regexp.MustCompile("^URF\\d+$").MatchString(a.Callsign) { a.FlightNumber = a.Callsign } ret = append(ret, a) } sort.Sort(AircraftByDist3(ret)) return ret }
// Leave icaoid as empty string to get all complaints; else limits to that aircraft func (cdb ComplaintDB) GetComplaintPositionsInSpanByIcao(start, end time.Time, icaoid string) ([]geo.Latlong, error) { ret := []geo.Latlong{} q := datastore. NewQuery(kComplaintKind). Project("Profile.Lat", "Profile.Long"). Filter("Timestamp >= ", start). Filter("Timestamp < ", end) if icaoid != "" { cdb.Infof("Oho, adding id2=%s", icaoid) q = q.Filter("AircraftOverhead.Id2 = ", icaoid) } q = q.Limit(-1) var data = []types.Complaint{} if _, err := q.GetAll(cdb.Ctx(), &data); err != nil { return ret, err } for _, c := range data { // Round off the position data to a certain level of precision lat, long := toFixed(c.Profile.Lat, 2), toFixed(c.Profile.Long, 2) pos := geo.Latlong{Lat: lat, Long: long} if !pos.IsNil() { ret = append(ret, pos) } } return ret, nil }
func airspaceHandler(w http.ResponseWriter, r *http.Request) { client := urlfetch.Client(appengine.NewContext(r)) pos := geo.Latlong{37.060312, -121.990814} str := "" if as, err := fr24.FetchAirspace(client, pos.Box(100, 100)); err != nil { http.Error(w, fmt.Sprintf("FetchAirspace: %v", err), http.StatusInternalServerError) return } else { oh, debstr := IdentifyOverhead(as, pos, 0.0, AlgoConservativeNoCongestion) str += fmt.Sprintf("--{ IdentifyOverhead }--\n -{ OH: %s }-\n\n%s\n", oh, debstr) } w.Header().Set("Content-Type", "text/plain") w.Write([]byte("OK\n\n" + str)) }
func (f Flight) SnapshotAt(t time.Time, pos *geo.Latlong) (*FlightSnapshot, error) { if len(f.Track) == 0 { return nil, fmt.Errorf("SnapshotAt: found no trackpoints") } t = t.UTC() start, end := f.Track[0].TimestampUTC, f.Track[len(f.Track)-1].TimestampUTC if t.Before(start) { return nil, fmt.Errorf("SnapshotAt: t<start (t=%s, start=%s)", t, start) } else if t.After(end) { return nil, fmt.Errorf("SnapshotAt: t>end (t=%s, end=%s)", t, end) } for i, tp := range f.Track { if i == len(f.Track)-1 { break } if tp.TimestampUTC.Before(t) && f.Track[i+1].TimestampUTC.After(t) { // Grab the two trackpoints that bracket our time snap := &FlightSnapshot{ F: f, Pos: f.Track[i], NextPos: f.Track[i+1], } if pos != nil { snap.Reference = *pos snap.DistToReferenceKM = pos.Dist(f.Track[i].Latlong) snap.BearingToReference = float64(int64(pos.BearingTowards(f.Track[i].Latlong))) } return snap, nil } } return nil, fmt.Errorf("SnapshotAt: fell off the end, bad track ?") }
func (fr *Fr24) FindOverhead(observerPos geo.Latlong, overhead *Aircraft, grabAnything bool) (debug string, err error) { debug = fmt.Sprintf("*** FindOverhead for %s, at %s\n", observerPos, date.NowInPdt()) // Create a bounding box that's ~40m square, centred on the input lat,long // This is a grievous fudge http://www.movable-type.co.uk/scripts/latlong.html lat_20miles := 0.3 long_20miles := 0.35 nearby := []Aircraft{} if err = fr.ListBbox(observerPos.Lat-lat_20miles, observerPos.Long-long_20miles, observerPos.Lat+lat_20miles, observerPos.Long+long_20miles, &nearby); err != nil { debug += fmt.Sprintf("Lookup error: %s\n", err) return } for i, a := range nearby { aircraftPos := geo.Latlong{a.Lat, a.Long} nearby[i].Dist = observerPos.Dist(aircraftPos) nearby[i].Dist3 = observerPos.Dist3(aircraftPos, a.Altitude) nearby[i].BearingFromObserver = observerPos.BearingTowards(aircraftPos) } sort.Sort(byDist3(nearby)) debug += "** nearby list:-\n" + DebugFlightList(nearby) filtered := filterAircraft(nearby) if len(filtered) == 0 { debug += "** all empty after filtering\n" return } debug += "** filtered:-\n" + DebugFlightList(filtered) if grabAnything { *overhead = filtered[0] debug += "** grabbed 1st\n" } else { // closest plane has to be within 12 km to be 'overhead', and it has // to be 4km away from the next-closest if filtered[0].Dist3 < 12.0 { if (len(filtered) == 1) || (filtered[1].Dist3-filtered[0].Dist3) > 4.0 { *overhead = filtered[0] debug += "** selected 1st\n" } else { debug += "** 2nd was too close to 1st\n" } } else { debug += "** 1st was too far away\n" } } return }
func (cdb ComplaintDB) complainByProfile(cp types.ComplainerProfile, c *types.Complaint) error { client := cdb.HTTPClient() overhead := flightid.Aircraft{} // Check we're not over a daily cap for this user cdb.Debugf("cbe_010", "doing rate limit check") s, e := date.WindowForToday() if prevKeys, err := cdb.GetComplaintKeysInSpanByEmailAddress(s, e, cp.EmailAddress); err != nil { return err } else if len(prevKeys) >= KMaxComplaintsPerDay { return fmt.Errorf("Too many complaints filed today") } else { cdb.Debugf("cbe_011", "rate limit check passed (%d); calling FindOverhead", len(prevKeys)) } elev := 0.0 pos := geo.Latlong{cp.Lat, cp.Long} algo := flightid.AlgoConservativeNoCongestion if c.Description == "ANYANY" { algo = flightid.AlgoGrabClosest } if as, err := fr24.FetchAirspace(client, pos.Box(64, 64)); err != nil { cdb.Errorf("FindOverhead failed for %s: %v", cp.EmailAddress, err) } else { oh, deb := flightid.IdentifyOverhead(as, pos, elev, algo) c.Debug = deb if oh != nil { overhead = *oh c.AircraftOverhead = overhead } } cdb.Debugf("cbe_020", "FindOverhead returned") // Contrast with the skypi pathway if cp.CallerCode == "WOR004" || cp.CallerCode == "WOR005" { asFdb, _ := airspace.Fetch(client, "", pos.Box(60, 60)) oh3, deb3 := flightid.IdentifyOverhead(asFdb, pos, elev, algo) if oh3 == nil { oh3 = &flightid.Aircraft{} } newdebug := c.Debug + "\n*** v2 / fdb testing\n" + deb3 + "\n" headline := "" if overhead.FlightNumber != oh3.FlightNumber { headline = fmt.Sprintf("** * * DIFFERS * * **\n") } else { // Agree ! Copy over the Fdb IdSpec, and pretend we're living in the future headline = fmt.Sprintf("**---- Agrees ! ----**\n") c.AircraftOverhead.Id = oh3.Id } headline += fmt.Sprintf(" * skypi: %s\n * orig : %s\n", oh3, overhead) c.Debug = headline + newdebug } c.Version = kComplaintVersion // Kill this off ? c.Profile = cp // Copy the profile fields into every complaint // Too much like the last complaint by this user ? Just update that one. cdb.Debugf("cbe_030", "retrieving prev complaint") if prev, err := cdb.GetNewestComplaintByEmailAddress(cp.EmailAddress); err != nil { cdb.Errorf("complainByProfile/GetNewest: %v", err) } else if prev != nil && ComplaintsAreEquivalent(*prev, *c) { cdb.Debugf("cbe_031", "returned, equiv; about to UpdateComlaint()") // The two complaints are in fact one complaint. Overwrite the old one with data from new one. Overwrite(prev, c) err := cdb.UpdateComplaint(*prev, cp.EmailAddress) cdb.Debugf("cbe_032", "updated in place (all done)") return err } cdb.Debugf("cbe_033", "returned, distinct/first; about to put()") key := datastore.NewIncompleteKey(cdb.Ctx(), kComplaintKind, cdb.emailToRootKey(cp.EmailAddress)) _, err := datastore.Put(cdb.Ctx(), key, c) cdb.Debugf("cbe_034", "new entity added (all done)") // TEMP /* if debug,err := bksv.PostComplaint(client, cp, *c); err != nil { cdb.Infof("BKSV Debug\n------\n%s\n------\n", debug) cdb.Infof("BKSV posting error: %v", err) } else { cdb.Infof("BKSV Debug\n------\n%s\n------\n", debug) } */ return err }
var ( // Retire these three. KLatlongSFO = geo.Latlong{37.6188172, -122.3754281} KLatlongSJC = geo.Latlong{37.3639472, -121.9289375} KLatlongSERFR1 = geo.Latlong{37.221516, -121.992987} // This is the centerpoint for maps viewport KBoxSnarfingCatchment = KLatlongSFO.Box(125, 125) // The box in which we look for new flights // Boxes used in a few reports KBoxSFO10K = KLatlongSFO.Box(12, 12) KBoxPaloAlto20K = geo.Latlong{37.433536, -122.1310187}.Box(6, 7) KAirports = map[string]geo.Latlong{ "KSFO": geo.Latlong{37.6188172, -122.3754281}, "KSJC": geo.Latlong{37.3639472, -121.9289375}, "KOAK": geo.Latlong{37.7212597, -122.2211489}, } // http://www.myaviationinfo.com/FixState.php?FixState=CALIFORNIA KFixes = map[string]geo.Latlong{ // SERFR2 & WWAVS1 "SERFR": geo.Latlong{36.0683056, -121.3646639}, "NRRLI": geo.Latlong{36.4956000, -121.6994000}, "WWAVS": geo.Latlong{36.7415306, -121.8942333}, "EPICK": geo.Latlong{36.9508222, -121.9526722}, "EDDYY": geo.Latlong{37.3264500, -122.0997083}, "SWELS": geo.Latlong{37.3681556, -122.1160806}, "MENLO": geo.Latlong{37.4636861, -122.1536583}, "WPOUT": geo.Latlong{37.1194861, -122.2927417}, "THEEZ": geo.Latlong{37.5034694, -122.4247528}, "WESLA": geo.Latlong{37.6643722, -122.4802917}, "MVRKK": geo.Latlong{37.7369722, -122.4544500}, // BRIXX "CORKK": geo.Latlong{37.7335889, -122.4975500}, "BRIXX": geo.Latlong{37.6178444, -122.3745278}, "LUYTA": geo.Latlong{37.2948889, -122.2045528}, "JILNA": geo.Latlong{37.2488056, -122.1495000}, "YADUT": geo.Latlong{37.2039889, -122.0232778}, // BIGSURTWO "CARME": geo.Latlong{36.4551833, -121.8797139}, "ANJEE": geo.Latlong{36.7462861, -121.9648917}, "SKUNK": geo.Latlong{37.0075944, -122.0332278}, "BOLDR": geo.Latlong{37.1708861, -122.0761667}, // BDEGA2 "LOZIT": geo.Latlong{37.899325, -122.673194}, "BGGLO": geo.Latlong{38.224589, -122.767506}, "GEEHH": geo.Latlong{38.453333, -122.428650}, "MSCAT": geo.Latlong{38.566697, -122.671667}, "JONNE": geo.Latlong{38.551042, -122.863275}, "AMAKR": geo.Latlong{39.000000, -123.750000}, "DEEAN": geo.Latlong{38.349164, -123.302289}, "MRRLO": geo.Latlong{38.897547, -122.578233}, "MLBEC": geo.Latlong{38.874772, -122.958989}, // Things for SFO arrivals "HEMAN": geo.Latlong{37.5338500, -122.1733333}, "NEPIC": geo.Latlong{37.5858944, -122.2968833}, // Things for SFO departures "PORTE": geo.Latlong{37.4897861, -122.4745778}, "SSTIK": geo.Latlong{37.6783444, -122.3616583}, // Things for Oceanic "PPEGS": geo.Latlong{37.3920722, -122.2817222}, "ALLBE": geo.Latlong{37.5063889, -127.0000000}, "ALCOA": geo.Latlong{37.8332528, -125.8345250}, "CINNY": geo.Latlong{36.1816667, -124.7600000}, "PAINT": geo.Latlong{38.0000000, -125.5000000}, "OSI": geo.Latlong{37.3925000, -122.2813000}, "PIRAT": geo.Latlong{37.2576500, -122.8633528}, "PYE": geo.Latlong{38.0797567, -122.8678275}, "STINS": geo.Latlong{37.8236111, -122.7566667}, "HADLY": geo.Latlong{37.4022222, -122.5755556}, "PONKE": geo.Latlong{37.4588167, -121.9960528}, "WETOR": geo.Latlong{37.4847194, -122.0571417}, // Things for SJC/SILCN3 "VLLEY": geo.Latlong{36.5091667, -121.4402778}, "GUUYY": geo.Latlong{36.7394444, -121.5411111}, "SSEBB": geo.Latlong{36.9788889, -121.6425000}, "GSTEE": geo.Latlong{37.0708333, -121.6716667}, "KLIDE": geo.Latlong{37.1641667, -121.7130556}, "BAXBE": geo.Latlong{36.7730556, -121.6263889}, "APLLE": geo.Latlong{37.0338889, -121.8050000}, // Randoms "PARIY": geo.Latlong{37.3560056, -121.9231222}, // SJC ? "ZORSA": geo.Latlong{37.3627583, -122.0500306}, // Things for East Bay "HOPTA": geo.Latlong{37.78501944, -122.154}, "BOYSS": geo.Latlong{38.02001944, -122.3778639}, "WNDSR": geo.Latlong{38.681808, -122.478747}, "WEBRR": geo.Latlong{38.243881, -122.412142}, "SPAMY": geo.Latlong{39.200661, -122.591042}, "HUBRT": geo.Latlong{39.040228, -122.568314}, "DRAXE": geo.Latlong{38.759, -122.389047}, "BMBOO": geo.Latlong{38.892972, -122.233019}, "RBUCL": geo.Latlong{39.070053, -122.02615}, "GRTFL": geo.Latlong{38.35216944, -122.2314694}, "TRUKN": geo.Latlong{37.71755833, -122.2145889}, "DEDHD": geo.Latlong{38.33551666, -122.1128083}, "HYPEE": geo.Latlong{37.88024444, -122.0674833}, "COSMC": geo.Latlong{37.82606111, -122.0049}, "TYDYE": geo.Latlong{37.689319, -122.268944}, "ORRCA": geo.Latlong{38.610325, -121.551622}, "MOGEE": geo.Latlong{38.336111, -121.389722}, "TIPRE": geo.Latlong{38.205833, -121.035833}, "SYRAH": geo.Latlong{37.99105, -121.103089}, "RAIDR": geo.Latlong{38.0325, -122.5575}, "CRESN": geo.Latlong{37.697475, -122.012019}, "AAAME": geo.Latlong{37.770908, -122.082811}, "ALLXX": geo.Latlong{37.729606, -122.064283}, "HIRMO": geo.Latlong{37.92765, -122.14835}, "CEXUR": geo.Latlong{37.934161, -122.252928}, "WOULD": geo.Latlong{37.774508, -122.058064}, "FINSH": geo.Latlong{37.651203, -122.257161}, "HUSHH": geo.Latlong{37.7495, -122.338592}, "AANET": geo.Latlong{38.530769, -122.497194}, // Foster city "ROKME": geo.Latlong{37.5177778, -122.1191667}, "DONGG": geo.Latlong{37.5891667, -122.2525000}, "GUTTS": geo.Latlong{37.5552778, -122.1597222}, // This GOBEC is wrong ... see the one below. // "GOBEC": geo.Latlong{37.5869444, -122.2547222}, "WASOP": geo.Latlong{37.5391667, -122.1247222}, "DUYET": geo.Latlong{37.5680556, -122.2547222}, // DYAMD and YOSEM "ARCHI": geo.Latlong{37.490833, -121.875500}, "FRELY": geo.Latlong{37.510667, -121.793167}, "CEDES": geo.Latlong{37.550822, -121.624586}, "FLOWZ": geo.Latlong{37.592500, -121.264833}, "ALWYS": geo.Latlong{37.633500, -120.959333}, "LAANE": geo.Latlong{37.659000, -120.747333}, "DYAMD": geo.Latlong{37.699167, -120.404500}, "FAITH": geo.Latlong{37.401217, -121.861900}, "SOOIE": geo.Latlong{37.428500, -121.607667}, "FRIGG": geo.Latlong{37.465500, -121.257333}, "ZOMER": geo.Latlong{37.545333, -120.631500}, "SNORA": geo.Latlong{37.645500, -119.806333}, "YOSEM": geo.Latlong{37.762667, -118.766667}, // Final approaches into SFO (28L, 28R) "GOBEC": geo.Latlong{37.578833, -122.252833}, "JOSUF": geo.Latlong{37.592167, -122.285500}, "DARNE": geo.Latlong{37.593333, -122.292333}, "FABLA": geo.Latlong{37.597500, -122.318833}, "AXMUL": geo.Latlong{37.571500, -122.257167}, "WIBNI": geo.Latlong{37.516667, -122.031333}, "ANETE": geo.Latlong{37.463667, -121.942667}, "FATUS": geo.Latlong{37.486000, -122.002333}, "HEGOT": geo.Latlong{37.508000, -122.061833}, "MIUKE": geo.Latlong{37.552333, -122.181167}, "DIVEC": geo.Latlong{37.432833, -121.935000}, "CEPIN": geo.Latlong{37.536000, -122.172833}, "DUMBA": geo.Latlong{37.503500, -122.096167}, "GIRRR": geo.Latlong{37.495852, -122.027167}, "ZILED": geo.Latlong{37.495667, -121.958167}, // Personal entries "X_RSH": geo.Latlong{36.868582, -121.691934}, "X_BLH": geo.Latlong{37.2199471, -122.0425108}, "X_HBR": geo.Latlong{37.309564, -122.112378}, "X_WSD": geo.Latlong{37.420995, -122.268237}, // Woodside "X_PVY": geo.Latlong{37.38087, -122.23319}, // Portola Valley } SFOClassBMap = geo.ClassBMap{ Name: "SFO", Center: KLatlongSFO, Sectors: []geo.ClassBSector{ // Magnetic declination at SFO: 13.68 geo.ClassBSector{ StartBearing: 0, EndBearing: 360, Steps: []geo.Cylinder{ {7, 0, 100}, // from origin to 7NM : 100/00 (no floor) {10, 15, 100}, // from 7NM to 10NM : 100/15 {15, 30, 100}, // from 10NM to 15NM : 100/30 {20, 40, 100}, // from 15NM to 20NM : 100/40 {25, 60, 100}, // from 20NM to 25NM : 100/60 {30, 80, 100}, // from 25NM to 30NM : 100/80 }, }, // ... more sectors go here ! }, } // http://flightaware.com/resources/airport/SFO/STAR/SERFR+TWO+(RNAV)/pdf Serfr1 = geo.Procedure{ Name: "SERFR2", Departure: false, Airport: "SFO", Waypoints: []geo.Waypoint{ {"SERFR", geo.Latlong{}, 0, 0, 0, false}, // Many aircraft skip SERFR {"NNRLI", geo.Latlong{}, 20000, 20000, 280, true}, {"WWAVS", geo.Latlong{}, 15000, 19000, 280, true}, {"EPICK", geo.Latlong{}, 10000, 15000, 280, true}, {"EDDYY", geo.Latlong{}, 6000, 6000, 240, true}, // Delay vectoring inside EPICK-EDDYY {"SWELS", geo.Latlong{}, 4700, 4700, 240, false}, {"MENLO", geo.Latlong{}, 4000, 4000, 230, false}, }, } )
func (fr *Fr24) FindAllOverhead(observerPos geo.Latlong, overhead *flightid.Aircraft, grabAnything bool) (outAll []*Aircraft, outFilt []*Aircraft, debug string, err error) { outAll = []*Aircraft{} outFilt = []*Aircraft{} debug = fmt.Sprintf("*** FindOverhead for %s, at %s\n", observerPos, date.NowInPdt()) debug += fmt.Sprintf("* url: http://%s%s?array=1&bounds=%s\n", fr.host, kListUrlPath, "...") // Create a bounding box that's ~40m square, centred on the input lat,long // This is a grievous fudge http://www.movable-type.co.uk/scripts/latlong.html lat_20miles := 0.3 long_20miles := 0.35 nearby := []Aircraft{} if err = fr.ListBbox(observerPos.Lat-lat_20miles, observerPos.Long-long_20miles, observerPos.Lat+lat_20miles, observerPos.Long+long_20miles, &nearby); err != nil { debug += fmt.Sprintf("Lookup error: %s\n", err) return } for i, a := range nearby { // Hack for Surf Air; promote callsigns into (invalid_ flightnumbers, so they don't get stripped if a.FlightNumber == "" && regexp.MustCompile("^URF\\d+$").MatchString(a.Callsign) { nearby[i].FlightNumber = a.Callsign } else if a.FlightNumber != "" { if _, _, err := fdb.ParseIata(a.FlightNumber); err != nil { debug += "** saw bad flightnumber '" + a.FlightNumber + "' from fr24\n" nearby[i].FlightNumber = "" } } aircraftPos := geo.Latlong{a.Lat, a.Long} nearby[i].Dist = observerPos.Dist(aircraftPos) nearby[i].Dist3 = observerPos.Dist3(aircraftPos, a.Altitude) nearby[i].BearingFromObserver = observerPos.BearingTowards(aircraftPos) } sort.Sort(byDist3(nearby)) debug += "** nearby list:-\n" + DebugFlightList(nearby) filtered := filterAircraft(nearby) if len(filtered) == 0 { debug += "** all empty after filtering\n" return } for i, _ := range nearby { outAll = append(outAll, &nearby[i]) } for i, _ := range filtered { outFilt = append(outFilt, &filtered[i]) } debug += "** filtered:-\n" + DebugFlightList(filtered) if grabAnything { filtered[0].IntoFlightIdAircraft(overhead) debug += "** grabbed 1st\n" } else { // closest plane has to be within 12 km to be 'overhead', and it has // to be 4km away from the next-closest if filtered[0].Dist3 < 12.0 { if (len(filtered) == 1) || (filtered[1].Dist3-filtered[0].Dist3) > 4.0 { filtered[0].IntoFlightIdAircraft(overhead) debug += "** selected 1st\n" } else { debug += "** 2nd was too close to 1st\n" } } else { debug += "** 1st was too far away\n" } } return }