Example #1
0
func addtrackHandler(w http.ResponseWriter, r *http.Request) {
	ctx := req2ctx(r)
	db := fdb.NewDB(r)

	icaoId := r.FormValue("icaoid")
	callsign := strings.TrimSpace(r.FormValue("callsign"))
	tStr := r.FormValue("track")

	// Validate it works before persisting
	t := ftype.Track{}
	if err := t.Base64Decode(tStr); err != nil {
		log.Errorf(ctx, " /mdb/addtrack: decode failed: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	ftf := ftype.FrozenTrackFragment{
		TrackBase64: tStr,
		Callsign:    callsign,
		Icao24:      icaoId,
	}
	if err := db.AddTrackFrgament(ftf); err != nil {
		log.Errorf(ctx, " /mdb/addtrack: db.AddTrackFragment failed: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	// 3. Routine to merge track fragments ? Extra credit ?

	// c.Infof(" /mdb/addtrack: added %d points for [%s][%s]", len(t), icaoId, callsign)
	w.Write([]byte(fmt.Sprintf("Added %d for %s\n", len(t), icaoId)))
}
Example #2
0
func jobRetagHandler(r *http.Request, f *oldfdb.Flight) (string, error) {
	ctx := req2ctx(r)
	str := ""

	oldtags := f.TagList()

	f.Tags = map[string]bool{}

	f.AnalyseFlightPath()
	f.Analyse()

	if taglistsEqual(oldtags, f.TagList()) {
		return fmt.Sprintf("* no change to tags: %v", f.TagList()), nil
	}

	db := oldfgae.NewDB(r)
	if err := db.UpdateFlight(*f); err != nil {
		log.Errorf(ctx, "Persist Flight %s: %v", f, err)
		return str, err
	}
	log.Infof(ctx, "Updated flight %s", f)
	str += fmt.Sprintf("--\nFlight was updated\n")

	return str, nil
}
Example #3
0
func jobV2adsbHandler(r *http.Request, f *oldfdb.Flight) (string, error) {
	ctx := req2ctx(r)
	str := ""

	// Allow overwrite, for the (36,0) disastery
	// if f.HasTrack("ADSB") { return "", nil } // Already has one

	err, deb := f.GetV2ADSBTrack(urlfetch.Client(ctx))
	str += fmt.Sprintf("*getv2ADSB [%v]:-\n", err, deb)
	if err != nil {
		return str, err
	}

	if !f.HasTrack("ADSB") {
		return "", nil
	} // Didn't find one

	f.Analyse() // Retrigger Class-B stuff

	db := oldfgae.NewDB(r)
	if err := db.UpdateFlight(*f); err != nil {
		log.Errorf(ctx, "Persist Flight %s: %v", f, err)
		return str, err
	}
	log.Infof(ctx, "Updated flight %s", f)
	str += fmt.Sprintf("--\nFlight was updated\n")

	return str, nil
}
Example #4
0
func jobTrackTimezoneHandler(r *http.Request, f *oldfdb.Flight) (string, error) {
	ctx := req2ctx(r)

	defaultTP := f.Track.ClosestTrackpoint(sfo.KFixes["EPICK"])
	adsbTP := f.Tracks["ADSB"].ClosestTrackpoint(sfo.KFixes["EPICK"])
	trackTimeDelta := defaultTP.TimestampUTC.Sub(adsbTP.TimestampUTC)

	str := fmt.Sprintf("OK, looked up %s\n Default: %s\n ADSB   : %s\n delta: %s\n",
		f, defaultTP, adsbTP, trackTimeDelta)

	if trackTimeDelta < -4*time.Hour || trackTimeDelta > 4*time.Hour {
		str += fmt.Sprintf("* recoding\n* before: %s\n", f.Tracks["ADSB"])

		for i, _ := range f.Tracks["ADSB"] {
			f.Tracks["ADSB"][i].TimestampUTC = f.Tracks["ADSB"][i].TimestampUTC.Add(time.Hour * -8)
		}
		str += fmt.Sprintf("* after : %s\n", f.Tracks["ADSB"])

		db := oldfgae.NewDB(r)
		if err := db.UpdateFlight(*f); err != nil {
			log.Errorf(ctx, "Persist Flight %s: %v", f, err)
			return str, err
		}
		log.Infof(ctx, "Updated flight %s", f)
		str += fmt.Sprintf("--\nFlight was updated\n")

	} else {
		log.Debugf(ctx, "Skipped flight %s, delta=%s", f, trackTimeDelta)
		str += "--\nFlight was OK, left untouched\n"
	}

	return str, nil
}
Example #5
0
// Dequeue a single day, and enqueue a job for each flight on that day
func batchFlightDayHandler(w http.ResponseWriter, r *http.Request) {
	ctx := req2ctx(r)

	tags := []string{} //"ADSB"} // Maybe make this configurable ...

	n := 0
	str := ""
	job := r.FormValue("job")
	if job == "" {
		http.Error(w, "Missing argument: &job=foo", http.StatusInternalServerError)
	}

	day := date.ArbitraryDatestring2MidnightPdt(r.FormValue("day"), "2006/01/02")

	fdb := oldfgae.NewDB(r)

	dStart, dEnd := date.WindowForTime(day)
	dEnd = dEnd.Add(-1 * time.Second)
	keys, err := fdb.KeysInTimeRangeByTags(tags, dStart, dEnd)
	if err != nil {
		log.Errorf(ctx, "upgradeHandler: enqueue: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	singleFlightUrl := "/backend/fdb-batch/flight"
	for _, key := range keys {
		str += fmt.Sprintf("Enqueing day=%s: %s?job=%s&key=%s\n",
			day.Format("2006.01.02"), singleFlightUrl, job, key.Encode())

		if r.FormValue("dryrun") == "" {
			t := taskqueue.NewPOSTTask(singleFlightUrl, map[string][]string{
				// "date": {day.Format("2006.01.02")},
				"key": {key.Encode()},
				"job": {job},
			})

			if _, err := taskqueue.Add(ctx, t, "batch"); err != nil {
				log.Errorf(ctx, "upgradeHandler: enqueue: %v", err)
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		}

		n++
	}

	log.Infof(ctx, "enqueued %d batch items for '%s'", n, job)

	w.Header().Set("Content-Type", "text/plain")
	w.Write([]byte(fmt.Sprintf("OK, batch, enqueued %d tasks for %s\n%s", n, job, str)))
}
Example #6
0
// A super widget, for all the batch jobs
func formValueFlightByKey(r *http.Request) (*oldfdb.Flight, error) {
	fdb := oldfgae.NewDB(r)

	key, err := olddatastore.DecodeKey(r.FormValue("key"))
	if err != nil {
		return nil, fmt.Errorf("fdb-batch: %v", err)
	}
	f, err := fdb.KeyToFlight(key)
	if err != nil {
		return nil, fmt.Errorf("fdb-batch: %v", err)
	}
	return f, nil
}
Example #7
0
func debugHandler(w http.ResponseWriter, r *http.Request) {
	db := fdb.NewDB(r)
	id := r.FormValue("id")
	blob, f, err := db.GetBlobById(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	} else if blob == nil {
		http.Error(w, fmt.Sprintf("id=%s not found", id), http.StatusInternalServerError)
		return
	}

	blob.Flight = []byte{} // Zero this out

	s, e := f.Track.TimesInBox(sfo.KBoxSnarfingCatchment)
	log := blob.GestationLog
	blob.GestationLog = ""
	_ = log

	str := fmt.Sprintf("OK\n* Flight found: %s\n* Tracks: %q\n", f, f.TrackList())
	str += fmt.Sprintf("* Points in default track: %d\n", len(f.Track))
	str += fmt.Sprintf("* Default's start/end: %s, %s\n", s, e)
	// str += fmt.Sprintf("\n** Gestation log:-\n%s\n", log)
	// str += fmt.Sprintf("** Blob:-\n* Id=%s, Icao24=%s\n* Enter/Leave: %s -> %s\n* Tags: %v\n",
	//	blob.Id, blob.Icao24, blob.EnterUTC, blob.LeaveUTC, blob.Tags)

	str += "** v2 snarf URL: " + f.GetV2JsonUrl() + "\n"

	for tName, t := range f.Tracks {
		str += fmt.Sprintf("\n---- Track %s ----\n", tName)
		for i, tp := range t {
			str += fmt.Sprintf("  - %03d %s\n", i, tp)
		}
	}

	if true {
		f2, _ := f.V2()

		str += fmt.Sprintf("** Ids\n* %s\n* %s\n* %#v\n",
			f2.FullString(), f2.IdentString(), f2.Identity)

		str += fmt.Sprintf("\n *** /backend/fdb-batch/flight?key=%s&job=retag\n", f.DatastoreKey())
	}

	w.Header().Set("Content-Type", "text/plain")
	w.Write([]byte(str))
}
Example #8
0
// We examine the tags CGI arg, which should be a pipe-delimited set of flight tags.
func flightListHandler(w http.ResponseWriter, r *http.Request) {
	ctx := req2ctx(r)
	db := fdb.NewDB(r)

	tags := []string{}
	if r.FormValue("tags") != "" {
		tags = append(tags, strings.Split(r.FormValue("tags"), "|")...)
	}

	timeRange := regexp.MustCompile("/fdb/(.+)$").ReplaceAllString(r.URL.Path, "$1")
	var flights []ftype.Flight
	var err error

	switch timeRange {
	case "recent":
		flights, err = db.LookupRecentByTags(tags, 400)
	case "today":
		s, e := date.WindowForToday()
		flights, err = db.LookupTimeRangeByTags(tags, s, e)
	case "yesterday":
		s, e := date.WindowForYesterday()
		flights, err = db.LookupTimeRangeByTags(tags, s, e)
	}

	if err != nil {
		log.Errorf(ctx, " %s: %v", r.URL.Path, err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var params = map[string]interface{}{
		"Tags":      tags,
		"TimeRange": timeRange,
		"Flights":   flights2params(flights),
	}
	if err := templates.ExecuteTemplate(w, "fdb-recentlist", params); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
Example #9
0
func decodetrackHandler(w http.ResponseWriter, r *http.Request) {
	db := fdb.NewDB(r)

	icao := r.FormValue("icaoid")
	callsign := strings.TrimSpace(r.FormValue("callsign"))
	if icao == "" || callsign == "" {
		http.Error(w, "need args {icaoid,callsign}", http.StatusInternalServerError)
		return
	}

	if tracks, err := db.ReadTrackFragments(icao, callsign); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	} else {
		var params = map[string]interface{}{
			"Tracks":   tracks,
			"Callsign": callsign,
			"Icao24":   icao,
		}
		if err := templates.ExecuteTemplate(w, "fdb-decodetrack", params); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}
Example #10
0
func queryHandler(w http.ResponseWriter, r *http.Request) {
	if r.FormValue("date") == "" && r.FormValue("epoch") == "" {
		var params = map[string]interface{}{
			"TwoHoursAgo": date.NowInPdt().Add(-2 * time.Hour),
		}
		if err := templates.ExecuteTemplate(w, "fdb-queryform", params); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
		return
	}

	db := fdb.NewDB(r)
	db.Memcache = true

	var t time.Time
	if r.FormValue("epoch") != "" {
		if epoch, err := strconv.ParseInt(r.FormValue("epoch"), 10, 64); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		} else {
			t = time.Unix(epoch, 0)
		}
	} else {
		var err2 error
		t, err2 = date.ParseInPdt("2006/01/02 15:04:05", r.FormValue("date")+" "+r.FormValue("time"))
		if err2 != nil {
			http.Error(w, err2.Error(), http.StatusInternalServerError)
			return
		}
	}

	var refPoint *geo.Latlong = nil
	if r.FormValue("lat") != "" {
		refPoint = &geo.Latlong{}
		var err error
		if refPoint.Lat, err = strconv.ParseFloat(r.FormValue("lat"), 64); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if refPoint.Long, err = strconv.ParseFloat(r.FormValue("long"), 64); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	if snapshots, err := db.LookupSnapshotsAtTimestampUTC(t.UTC(), refPoint, 1000); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	} else {
		var params = map[string]interface{}{
			"Legend":        buildLegend(t),
			"SearchTimeUTC": t.UTC(),
			"SearchTime":    date.InPdt(t),
			"Flights":       snapshots2params(snapshots),

			"FlightsJS":  ftype.FlightSnapshotSet(snapshots).ToJSVar(),
			"MapsAPIKey": kGoogleMapsAPIKey,
			"Center":     sfo.KLatlongSERFR1,
			"Zoom":       9,
			// "CaptureArea": fdb.KBoxSnarfingCatchment,  // comment out, as we don't want it in this view
		}

		if r.FormValue("resultformat") == "json" {
			for i, _ := range snapshots {
				snapshots[i].F.Track = nil
				snapshots[i].F.Tracks = nil
			}
			js, err := json.Marshal(snapshots)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			w.Write(js)

		} else {
			templateName := "fdb-queryresults-map"
			if r.FormValue("resultformat") == "list" {
				templateName = "fdb-queryresults-list"
			}

			if err := templates.ExecuteTemplate(w, templateName, params); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
		}
	}
}
Example #11
0
func lookupHandler(w http.ResponseWriter, r *http.Request) {
	db := fdb.NewDB(r)
	id := r.FormValue("id")

	if f, err2 := db.LookupById(id); err2 != nil {
		http.Error(w, err2.Error(), http.StatusInternalServerError)

	} else if f == nil {
		http.Error(w, fmt.Sprintf("id=%s not found", id), http.StatusInternalServerError)

	} else {
		_, classBTrack := f.SFOClassB("", nil)

		f.Analyse()           // Repopulate the flight tags; useful when debugging new analysis stuff
		f.AnalyseFlightPath() // Repopulate the flight tags; useful when debugging new analysis stuff

		// Todo: collapse all these separate tracks down into the single point/line list thing
		fr24TrackJSVar := classBTrack.ToJSVar()

		// For Flightaware tracks
		//faClassBTrack := fdb.ClassBTrack{}
		faTrackJSVar := template.JS("{}")
		if _, exists := f.Tracks["FA"]; exists == true {
			_, faClassBTrack := f.SFOClassB("FA", nil)
			faTrackJSVar = faClassBTrack.ToJSVar()
			//fr24TrackJSVar = template.JS("{}")
		}

		// For ADS-B tracks !
		adsbTrackJSVar := template.JS("{}")
		if _, exists := f.Tracks["ADSB"]; exists == true {
			_, adsbClassBTrack := f.SFOClassB("ADSB", nil)
			adsbTrackJSVar = adsbClassBTrack.ToJSVar()
		}

		skimTrackJSVar := template.JS("{}")
		if r.FormValue("skim") != "" {
			alttol, _ := strconv.ParseFloat(r.FormValue("alttol"), 64)
			mindist, _ := strconv.ParseFloat(r.FormValue("mindist"), 64)
			skimTrack, _ := f.BestTrack().SkimsToSFO(alttol, mindist, 15.0, 40.0)
			skimTrackJSVar = skimTrack.ToJSVar()
			fr24TrackJSVar = template.JS("{}")
			faTrackJSVar = template.JS("{}")
			adsbTrackJSVar = template.JS("{}")
		}

		mapPoints := []MapPoint{}
		mapLines := []MapLine{}

		// &waypoint=EPICK
		if waypoint := r.FormValue("waypoint"); waypoint != "" {
			//pos := geo.Latlong{37.060312, -121.990814}
			pos := sfo.KFixes[waypoint]
			if itp, err := f.BestTrack().PointOfClosestApproach(pos); err != nil {
				//log.Infof(" ** Error: %v", err)
			} else {
				mapPoints = append(mapPoints, MapPoint{Icon: "red", ITP: &itp})
				mapPoints = append(mapPoints, MapPoint{Pos: &itp.Ref, Text: "** Reference point"})
				mapLines = append(mapLines, MapLine{Line: &itp.Line, Color: "#ff8822"})
				mapLines = append(mapLines, MapLine{Line: &itp.Perp, Color: "#ff2288"})
			}
		}

		// &boxes=1
		if r.FormValue("boxes") != "" {
			if true {
				// fr24
				for _, box := range f.Track.AsContiguousBoxes() {
					mapLines = append(mapLines, LatlongTimeBoxToMapLines(box, "#118811")...)
				}
			}
			if t, exists := f.Tracks["FA"]; exists == true {
				for _, box := range t.AsContiguousBoxes() {
					mapLines = append(mapLines, LatlongTimeBoxToMapLines(box, "#1111aa")...)
				}
			}
			if t, exists := f.Tracks["ADSB"]; exists == true {
				for _, box := range t.AsContiguousBoxes() {
					mapLines = append(mapLines, LatlongTimeBoxToMapLines(box, "#aaaa11")...)
				}
			}
		}

		pointsStr := "{\n"
		for i, mp := range mapPoints {
			pointsStr += fmt.Sprintf("    %d: {%s},\n", i, mp.ToJSStr(""))
		}
		pointsJS := template.JS(pointsStr + "  }\n")
		linesStr := "{\n"
		for i, ml := range mapLines {
			linesStr += fmt.Sprintf("    %d: {%s},\n", i, ml.ToJSStr(""))
		}
		linesJS := template.JS(linesStr + "  }\n")

		box := sfo.KBoxSnarfingCatchment
		if r.FormValue("report") == "level" {
			box = sfo.KBoxPaloAlto20K
		} else if r.FormValue("report") == "stack" {
			box = sfo.KBoxSFO10K
		}

		var params = map[string]interface{}{
			"F":       f,
			"Legend":  f.Legend(),
			"Oneline": f.String(),
			"Text":    fmt.Sprintf("%#v", f.Id),
			"ClassB":  "",

			"MapsAPIKey":  kGoogleMapsAPIKey,
			"Center":      sfo.KLatlongSERFR1,
			"Zoom":        10,
			"CaptureArea": box,

			"MapsTrack":        fr24TrackJSVar,
			"FlightawareTrack": faTrackJSVar,
			"ADSBTrack":        adsbTrackJSVar,
			"SkimTrack":        skimTrackJSVar,

			"Points": pointsJS,
			"Lines":  linesJS,
		}

		templateName := "fdb-lookup"
		if r.FormValue("map") != "" {
			templateName = "fdb-lookup-map"
		}
		if err7 := templates.ExecuteTemplate(w, templateName, params); err7 != nil {
			http.Error(w, err7.Error(), http.StatusInternalServerError)
		}
	}
}
Example #12
0
func addflightHandler(w http.ResponseWriter, r *http.Request) {
	ctx := req2ctx(r)
	client := req2client(r)

	logstr := fmt.Sprintf("* addFlightHandler invoked: %s\n", time.Now().UTC())

	fsStr := r.FormValue("flightsnapshot")
	fs := ftype.FlightSnapshot{}
	if err := fs.Base64Decode(fsStr); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	fr24Id := fs.F.Id.ForeignKeys["fr24"] // This is the only field we take from the
	logstr += fmt.Sprintf("* fr24 key: %s\n", fr24Id)

	db := fdb.NewDB(r)
	fr24db, err := fdb24.NewFlightDBFr24(client)
	if err != nil {
		log.Errorf(ctx, " /mdb/addflight: newdb: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Be idempotent - check to see if this flight has already been recorded
	/* Can't do this check now; the fs.F.Id we cached might be different from the
	   * f.Id we get back from LookupPlayback(), because fr24 reuse their keys.
	   *
		if exists,err := db.FlightExists(fs.F.Id.UniqueIdentifier()); err != nil {
			c.Errorf(" /mdb/addflight: FlightExists check failed: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		} else if exists {
			c.Infof(" /mdb/addflight: already exists %s", fs)
			w.Write([]byte(fmt.Sprintf("Skipped %s\n", fs)))
			return
		}
		logstr += fmt.Sprintf("* FlightExists('%s') -> false\n", fs.F.Id.UniqueIdentifier())
	*/

	// Now grab an initial flight (with track), from fr24.
	var f *ftype.Flight
	if f, err = fr24db.LookupPlayback(fr24Id); err != nil {
		// c.Errorf(" /mdb/addflight: lookup: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	logstr += fmt.Sprintf("* the fr24/default track has %d points\n", len(f.Track))

	// Kludge: fr24 keys get reused, so the flight fr24 thinks it refers to might be
	// different than when we cached it. So we do the uniqueness check here, to avoid
	// dupes in the DB. Need a better solution to this.
	if exists, err := db.FlightExists(f.Id.UniqueIdentifier()); err != nil {
		log.Errorf(ctx, " /mdb/addflight: FlightExists check failed: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	} else if exists {
		log.Infof(ctx, " /mdb/addflight: already exists %s", *f)
		w.Write([]byte(fmt.Sprintf("Skipped %s\n", *f)))
		return
	}
	logstr += fmt.Sprintf("* FlightExists('%s') -> false\n", f.Id.UniqueIdentifier())

	// Fetch ADSB (and MLAT!) tracks from Skypi, via the new DB
	err, deb := f.GetV2ADSBTrack(client)
	logstr += "* fetchV2ADSB\n" + deb
	if err != nil {
		log.Errorf(ctx, "ADSB fetch err: %v", err)
	}

	// If we have any locally received ADSB fragments for this flight, add them in
	//if err := db.MaybeAddTrackFragmentsToFlight(f); err != nil {
	//	c.Errorf(" /mdb/addflight: addTrackFrags(%s): %v", f.Id, err)
	//}

	f.AnalyseFlightPath() // Takes a coarse look at the flight path
	logstr += fmt.Sprintf("* Initial tags: %v\n", f.TagList())

	// For flights on the SERFR1 or BRIXX1 approaches, fetch a flightaware track
	if f.HasTag(ftype.KTagSERFR1) || f.HasTag(ftype.KTagBRIXX) {
		u, p := kFlightawareAPIUsername, kFlightawareAPIKey
		if err := fdbfa.AddFlightAwareTrack(client, f, u, p); err != nil {
			log.Errorf(ctx, " /mdb/addflight: addflightaware: %v", err)
		}
	}

	f.Analyse()

	if err := db.PersistFlight(*f, logstr); err != nil {
		log.Errorf(ctx, " /mdb/addflight: persist: %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// Success !
	w.Write([]byte(fmt.Sprintf("Added %s\n", f)))
}
Example #13
0
func report3Handler(w http.ResponseWriter, r *http.Request) {
	if r.FormValue("rep") == "" {
		var params = map[string]interface{}{
			"Yesterday": date.NowInPdt().AddDate(0, 0, -1),
			"Reports":   report.ListReports(),
			"FormUrl":   "/report/",
			"Waypoints": sfo.ListWaypoints(),
			"Title":     "Reports (DB v1)",
		}
		if err := templates.ExecuteTemplate(w, "report3-form", params); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
		return
	}

	rep, err := report.SetupReport(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if r.FormValue("debug") != "" {
		str := fmt.Sprintf("Report Options\n\n%s\n", rep.Options)
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(fmt.Sprintf("OK\n\n%s\n", str)))
		return
	}

	//airframes := ref.NewAirframeCache(c)
	metars, err := metar.LookupArchive(req2ctx(r), "KSFO",
		rep.Start.AddDate(0, 0, -1), rep.End.AddDate(0, 0, 1))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	v1idspecs := []string{}
	v2idspecs := []string{}
	v1RejectByRestrict := []string{}
	v1RejectByReport := []string{}

	reportFunc := func(oldF *oldfdb.Flight) {
		newF, err := oldF.V2()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		newF.ComputeIndicatedAltitudes(metars)

		outcome, err := rep.Process(newF)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		switch outcome {
		case report.RejectedByGeoRestriction:
			v1RejectByRestrict = append(v1RejectByRestrict, oldF.UniqueIdentifier())
		case report.RejectedByReport:
			v1RejectByReport = append(v1RejectByReport, oldF.UniqueIdentifier())
		case report.Accepted:
			v1idspecs = append(v1idspecs, oldF.UniqueIdentifier())
			v2idspecs = append(v2idspecs, newF.IdSpec().String())
		}
	}

	tags := rep.Options.Tags

	for _, wp := range rep.Waypoints {
		tags = append(tags, fmt.Sprintf("%s%s", oldfdb.KWaypointTagPrefix, wp))
	}

	db := oldfgae.NewDB(r)
	s, e := rep.Start, rep.End
	if err := db.LongIterWith(db.QueryTimeRangeByTags(tags, s, e), reportFunc); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	rep.FinishSummary()

	if rep.ResultsFormat == "csv" {
		rep.OutputAsCSV(w)
		return
	}

	postButtons := ""
	url := fmt.Sprintf("/fdb/trackset2?%s", rep.ToCGIArgs())
	postButtons += maybeButtonPOST(v1RejectByRestrict, "Restriction Rejects as VectorMap", url)
	postButtons += maybeButtonPOST(v1RejectByReport, "Report Rejects as VectorMap", url)

	url = fmt.Sprintf("/fdb/descent2?%s", rep.ToCGIArgs())
	postButtons += maybeButtonPOST(v1RejectByRestrict, "Restriction Rejects as DescentGraph", url)
	postButtons += maybeButtonPOST(v1RejectByReport, "Report Rejects DescentGraph", url)

	if rep.Name == "sfoclassb" {
		url = fmt.Sprintf("/fdb/approach2?%s", rep.ToCGIArgs())
		postButtons += maybeButtonPOST(v1idspecs, "Matches as ClassB", url)
		postButtons += maybeButtonPOST(v1RejectByReport, "Report Rejects as ClassB", url)
	}

	// The only way to get embedded CGI args without them getting escaped is to submit a whole tag
	vizFormURL := "http://stop.jetnoise.net/fdb/visualize2?" + rep.ToCGIArgs()
	vizFormTag := "<form action=\"" + vizFormURL + "\" method=\"post\" target=\"_blank\">"

	var params = map[string]interface{}{
		"R":                    rep,
		"Metadata":             rep.MetadataTable(),
		"PostButtons":          template.HTML(postButtons),
		"OptStr":               template.HTML(fmt.Sprintf("<pre>%s</pre>\n", rep.Options)),
		"IdSpecs":              template.HTML(strings.Join(v1idspecs, ",")),
		"VisualizationFormTag": template.HTML(vizFormTag),
	}
	if err := templates.ExecuteTemplate(w, "report3-results", params); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

}