Example #1
0
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)
	}
}
Example #2
0
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
}
Example #3
0
// 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
}
Example #4
0
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))
}
Example #5
0
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 ?")
}
Example #6
0
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
}
Example #7
0
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
}
Example #8
0
File: sfo.go Project: skypies/geo
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},
		},
	}
)
Example #9
0
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
}