From 451063dfc16c95eed0606e0a1a1c2b95ff749ebf Mon Sep 17 00:00:00 2001 From: Simon Rieger Date: Thu, 20 Mar 2025 16:33:31 +0100 Subject: [PATCH] add median support --- .dockerignore | 1 + init.sql | 1 + main.go | 670 ++++++++++++++++++++++++++------------------------ main.go.old | 164 ------------ 4 files changed, 349 insertions(+), 487 deletions(-) delete mode 100644 main.go.old diff --git a/.dockerignore b/.dockerignore index 19e0180..6686457 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ db/ +.git/ diff --git a/init.sql b/init.sql index fccc42d..f19e9b4 100644 --- a/init.sql +++ b/init.sql @@ -30,6 +30,7 @@ CREATE TABLE IF NOT EXISTS delay_stats ( total_trips INT, delayed_trips INT, avg_delay FLOAT, + median_delay FLOAT, last_updated DATETIME, INDEX idx_fahrt_nr (fahrt_nr) ); diff --git a/main.go b/main.go index c1d0dd0..193bb06 100644 --- a/main.go +++ b/main.go @@ -1,434 +1,458 @@ package main import ( - "database/sql" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "math" - "net/http" - "net/url" - "os" - "strconv" - "strings" - "time" + "database/sql" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math" + "net/http" + "net/url" + "os" + "sort" + "strconv" + "strings" + "time" - _ "github.com/go-sql-driver/mysql" - "github.com/google/uuid" + _ "github.com/go-sql-driver/mysql" + "github.com/google/uuid" ) type Departure struct { - TripId string `json:"tripId"` - When string `json:"when"` - PlannedWhen string `json:"plannedWhen"` - Delay int `json:"delay"` - Line struct { - Name string `json:"name"` - FahrtNr string `json:"fahrtNr"` - } `json:"line"` + TripId string `json:"tripId"` + When string `json:"when"` + PlannedWhen string `json:"plannedWhen"` + Delay int `json:"delay"` + Line struct { + Name string `json:"name"` + FahrtNr string `json:"fahrtNr"` + } `json:"line"` } type APIResponse struct { - Departures []Departure `json:"departures"` + Departures []Departure `json:"departures"` } type TripDetails struct { - Origin Station `json:"origin"` - Destination Station `json:"destination"` - Departure time.Time `json:"departure"` - Arrival time.Time `json:"arrival"` - Polyline Polyline `json:"polyline"` + Origin Station `json:"origin"` + Destination Station `json:"destination"` + Departure time.Time `json:"departure"` + Arrival time.Time `json:"arrival"` + Polyline Polyline `json:"polyline"` } type Station struct { - Name string `json:"name"` - Location Location `json:"location"` + Name string `json:"name"` + Location Location `json:"location"` } type Location struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` } type Polyline struct { - Features []Feature `json:"features"` + Features []Feature `json:"features"` } type Feature struct { - Geometry Geometry `json:"geometry"` + Geometry Geometry `json:"geometry"` } type Geometry struct { - Coordinates []float64 `json:"coordinates"` + Coordinates []float64 `json:"coordinates"` } func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) - log.Println("Anwendung gestartet") + log.SetOutput(os.Stdout) + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + log.Println("Anwendung gestartet") - dbDSN := os.Getenv("DB_DSN") - apiBaseURL := os.Getenv("API_BASE_URL") - duration, err := strconv.Atoi(os.Getenv("DURATION")) - if err != nil { - log.Fatalf("Ungültiger Wert für DURATION: %v", err) - } - deleteAfter, err := strconv.Atoi(os.Getenv("DELETE_AFTER_MINUTES")) - if err != nil { - log.Fatalf("Ungültiger Wert für DELETE_AFTER_MINUTES: %v", err) - } - stationIDs := strings.Split(os.Getenv("STATION_IDS"), ",") + dbDSN := os.Getenv("DB_DSN") + apiBaseURL := os.Getenv("API_BASE_URL") + duration, err := strconv.Atoi(os.Getenv("DURATION")) + if err != nil { + log.Fatalf("Ungültiger Wert für DURATION: %v", err) + } + deleteAfter, err := strconv.Atoi(os.Getenv("DELETE_AFTER_MINUTES")) + if err != nil { + log.Fatalf("Ungültiger Wert für DELETE_AFTER_MINUTES: %v", err) + } + stationIDs := strings.Split(os.Getenv("STATION_IDS"), ",") - updateInterval, err := strconv.Atoi(os.Getenv("UPDATE_INTERVAL_MINUTES")) - if err != nil || updateInterval <= 0 { - log.Println("Ungültiger oder fehlender Wert für UPDATE_INTERVAL_MINUTES, verwende Standardwert von 1 Minute") - updateInterval = 1 - } + updateInterval, err := strconv.Atoi(os.Getenv("UPDATE_INTERVAL_MINUTES")) + if err != nil || updateInterval <= 0 { + log.Println("Ungültiger oder fehlender Wert für UPDATE_INTERVAL_MINUTES, verwende Standardwert von 1 Minute") + updateInterval = 1 + } - transferTimeStr := os.Getenv("TRANSFER_TIME") - transferTime, err := time.Parse("15:04", transferTimeStr) - if err != nil { - log.Printf("Ungültiger Wert für TRANSFER_TIME, verwende Standardwert 23:00") - transferTime = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 0, 0, 0, time.Local) - } else { - now := time.Now() - transferTime = time.Date(now.Year(), now.Month(), now.Day(), transferTime.Hour(), transferTime.Minute(), 0, 0, time.Local) - } + transferTimeStr := os.Getenv("TRANSFER_TIME") + transferTime, err := time.Parse("15:04", transferTimeStr) + if err != nil { + log.Printf("Ungültiger Wert für TRANSFER_TIME, verwende Standardwert 23:00") + transferTime = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 0, 0, 0, time.Local) + } else { + now := time.Now() + transferTime = time.Date(now.Year(), now.Month(), now.Day(), transferTime.Hour(), transferTime.Minute(), 0, 0, time.Local) + } - db, err := sql.Open("mysql", dbDSN) - if err != nil { - log.Fatal("Fehler beim Verbinden mit der Datenbank: ", err) - } - defer db.Close() + db, err := sql.Open("mysql", dbDSN) + if err != nil { + log.Fatal("Fehler beim Verbinden mit der Datenbank: ", err) + } + defer db.Close() - ticker := time.NewTicker(5 * time.Minute) - updateTicker := time.NewTicker(time.Duration(updateInterval) * time.Minute) - defer ticker.Stop() - defer updateTicker.Stop() + ticker := time.NewTicker(5 * time.Minute) + updateTicker := time.NewTicker(time.Duration(updateInterval) * time.Minute) + defer ticker.Stop() + defer updateTicker.Stop() - for { - select { - case <-updateTicker.C: - for _, stationID := range stationIDs { - departures := fetchDepartures(apiBaseURL, stationID, duration) - // Füge einen 1-Sekunden-Sleeper hinzu - time.Sleep(1 * time.Second) - for _, dep := range departures { - savePosition(db, dep, apiBaseURL) - } - } - deleteOldEntries(db, deleteAfter) - case <-ticker.C: - logDatabaseStats(db) - default: - now := time.Now() - if now.After(transferTime) { - transferDailyDelayStats(db) - transferTime = time.Date(now.Year(), now.Month(), now.Day()+1, transferTime.Hour(), transferTime.Minute(), 0, 0, time.Local) - } - time.Sleep(1 * time.Minute) - } - } + for { + select { + case <-updateTicker.C: + for _, stationID := range stationIDs { + departures := fetchDepartures(apiBaseURL, stationID, duration) + // Füge einen 1-Sekunden-Sleeper hinzu + time.Sleep(1 * time.Second) + for _, dep := range departures { + savePosition(db, dep, apiBaseURL) + } + } + deleteOldEntries(db, deleteAfter) + case <-ticker.C: + logDatabaseStats(db) + default: + now := time.Now() + if now.After(transferTime) { + transferDailyDelayStats(db) + transferTime = time.Date(now.Year(), now.Month(), now.Day()+1, transferTime.Hour(), transferTime.Minute(), 0, 0, time.Local) + } + time.Sleep(1 * time.Minute) + } + } } func fetchDepartures(apiBaseURL, stationID string, duration int) []Departure { - url := fmt.Sprintf("%s/stops/%s/departures?duration=%d&linesOfStops=false&remarks=true&language=en&nationalExpress=true&national=true®ionalExpress=true®ional=true&suburban=true&bus=false&ferry=false&subway=false&tram=false&taxi=false&pretty=false", - apiBaseURL, stationID, duration) - resp, err := http.Get(url) - if err != nil { - log.Printf("Fehler beim Abrufen der Abfahrten für Station %s: %v\n", stationID, err) - return nil - } - defer resp.Body.Close() + url := fmt.Sprintf("%s/stops/%s/departures?duration=%d&linesOfStops=false&remarks=true&language=en&nationalExpress=true&national=true®ionalExpress=true®ional=true&suburban=true&bus=false&ferry=false&subway=false&tram=false&taxi=false&pretty=false", + apiBaseURL, stationID, duration) + resp, err := http.Get(url) + if err != nil { + log.Printf("Fehler beim Abrufen der Abfahrten für Station %s: %v\n", stationID, err) + return nil + } + defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Printf("Fehler beim Lesen der Antwort für Station %s: %v\n", stationID, err) - return nil - } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("Fehler beim Lesen der Antwort für Station %s: %v\n", stationID, err) + return nil + } - if len(body) == 0 { - log.Printf("Leere Antwort vom Server für Station %s erhalten\n", stationID) - return nil - } + if len(body) == 0 { + log.Printf("Leere Antwort vom Server für Station %s erhalten\n", stationID) + return nil + } - var response APIResponse - err = json.Unmarshal(body, &response) - if err != nil { - log.Printf("Fehler beim Dekodieren der Abfahrten für Station %s: %v\nAntwort-Body: %s\n", stationID, err, string(body)) - return nil - } + var response APIResponse + err = json.Unmarshal(body, &response) + if err != nil { + log.Printf("Fehler beim Dekodieren der Abfahrten für Station %s: %v\nAntwort-Body: %s\n", stationID, err, string(body)) + return nil + } - log.Printf("Erfolgreich %d Abfahrten für Station %s abgerufen\n", len(response.Departures), stationID) - return response.Departures + log.Printf("Erfolgreich %d Abfahrten für Station %s abgerufen\n", len(response.Departures), stationID) + return response.Departures } func fetchTripDetails(apiBaseURL, tripID string) (*TripDetails, error) { - escapedTripID := url.QueryEscape(tripID) - url := fmt.Sprintf("%s/trips/%s?stopovers=true&remarks=true&polyline=true&language=en&pretty=false", apiBaseURL, escapedTripID) - resp, err := http.Get(url) - if err != nil { - return nil, fmt.Errorf("Fehler beim Abrufen der Zugdetails: %v", err) - } - defer resp.Body.Close() + escapedTripID := url.QueryEscape(tripID) + url := fmt.Sprintf("%s/trips/%s?stopovers=true&remarks=true&polyline=true&language=en&pretty=false", apiBaseURL, escapedTripID) + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("Fehler beim Abrufen der Zugdetails: %v", err) + } + defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("Fehler beim Lesen der Antwort: %v", err) - } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("Fehler beim Lesen der Antwort: %v", err) + } - if len(body) == 0 { - return nil, fmt.Errorf("Leere Antwort vom Server erhalten") - } + if len(body) == 0 { + return nil, fmt.Errorf("Leere Antwort vom Server erhalten") + } - var tripResponse struct { - Trip TripDetails `json:"trip"` - } - if err := json.Unmarshal(body, &tripResponse); err != nil { - return nil, fmt.Errorf("Fehler beim Dekodieren der Zugdetails: %v", err) - } + var tripResponse struct { + Trip TripDetails `json:"trip"` + } + if err := json.Unmarshal(body, &tripResponse); err != nil { + return nil, fmt.Errorf("Fehler beim Dekodieren der Zugdetails: %v", err) + } - if tripResponse.Trip.Origin.Name == "" || tripResponse.Trip.Destination.Name == "" { - return nil, fmt.Errorf("Unvollständige Tripdaten erhalten") - } + if tripResponse.Trip.Origin.Name == "" || tripResponse.Trip.Destination.Name == "" { + return nil, fmt.Errorf("Unvollständige Tripdaten erhalten") + } - return &tripResponse.Trip, nil + return &tripResponse.Trip, nil } func savePosition(db *sql.DB, dep Departure, apiBaseURL string) { - // Füge einen 1-Sekunden-Sleeper hinzu - time.Sleep(1 * time.Second) + // Füge einen 1-Sekunden-Sleeper hinzu + time.Sleep(1 * time.Second) - tripDetails, err := fetchTripDetails(apiBaseURL, dep.TripId) - if err != nil { - log.Printf("Fehler beim Abrufen der Zugdetails für TripID %s: %v\n", dep.TripId, err) - return - } + tripDetails, err := fetchTripDetails(apiBaseURL, dep.TripId) + if err != nil { + log.Printf("Fehler beim Abrufen der Zugdetails für TripID %s: %v\n", dep.TripId, err) + return + } - currentTime := time.Now() - longitude, latitude := calculateCurrentPosition(tripDetails, currentTime) + currentTime := time.Now() + longitude, latitude := calculateCurrentPosition(tripDetails, currentTime) - if dep.When == "" { - log.Printf("Warnung: Leerer Zeitstempel für FahrtNr %s, überspringe Eintrag\n", dep.Line.FahrtNr) - return - } + if dep.When == "" { + log.Printf("Warnung: Leerer Zeitstempel für FahrtNr %s, überspringe Eintrag\n", dep.Line.FahrtNr) + return + } - whenTime, err := time.Parse(time.RFC3339, dep.When) - if err != nil { - log.Printf("Fehler beim Parsen der Zeit für TripID %s: %v\n", dep.TripId, err) - return - } + whenTime, err := time.Parse(time.RFC3339, dep.When) + if err != nil { + log.Printf("Fehler beim Parsen der Zeit für TripID %s: %v\n", dep.TripId, err) + return + } - plannedWhenTime, err := time.Parse(time.RFC3339, dep.PlannedWhen) - if err != nil { - log.Printf("Fehler beim Parsen der geplanten Zeit für TripID %s: %v\n", dep.TripId, err) - return - } + plannedWhenTime, err := time.Parse(time.RFC3339, dep.PlannedWhen) + if err != nil { + log.Printf("Fehler beim Parsen der geplanten Zeit für TripID %s: %v\n", dep.TripId, err) + return + } - var existingID string - err = db.QueryRow("SELECT id FROM trips WHERE fahrt_nr = ?", dep.Line.FahrtNr).Scan(&existingID) + var existingID string + err = db.QueryRow("SELECT id FROM trips WHERE fahrt_nr = ?", dep.Line.FahrtNr).Scan(&existingID) - if err == sql.ErrNoRows { - id := uuid.New().String() - _, err = db.Exec("INSERT INTO trips (id, timestamp, planned_timestamp, delay, train_name, fahrt_nr, trip_id, latitude, longitude, destination) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - id, whenTime, plannedWhenTime, dep.Delay, dep.Line.Name, dep.Line.FahrtNr, dep.TripId, latitude, longitude, tripDetails.Destination.Name) - if err != nil { - log.Printf("Fehler beim Speichern der neuen Position für TripID %s: %v\n", dep.TripId, err) - } else { - log.Printf("Neue Position gespeichert (ID: %s, Zug: %s, FahrtNr: %s, Lat: %f, Lon: %f, Verspätung: %d, Ziel: %s)\n", id, dep.Line.Name, dep.Line.FahrtNr, latitude, longitude, dep.Delay, tripDetails.Destination.Name) - } - } else if err == nil { - _, err = db.Exec("UPDATE trips SET timestamp = ?, planned_timestamp = ?, delay = ?, train_name = ?, trip_id = ?, latitude = ?, longitude = ?, destination = ? WHERE id = ?", - whenTime, plannedWhenTime, dep.Delay, dep.Line.Name, dep.TripId, latitude, longitude, tripDetails.Destination.Name, existingID) - if err != nil { - log.Printf("Fehler beim Aktualisieren der Position für TripID %s: %v\n", dep.TripId, err) - } else { - log.Printf("Position aktualisiert (ID: %s, Zug: %s, FahrtNr: %s, Lat: %f, Lon: %f, Verspätung: %d, Ziel: %s)\n", existingID, dep.Line.Name, dep.Line.FahrtNr, latitude, longitude, dep.Delay, tripDetails.Destination.Name) - } - } else { - log.Printf("Fehler bei der Überprüfung des existierenden Eintrags für TripID %s: %v\n", dep.TripId, err) - } + if err == sql.ErrNoRows { + id := uuid.New().String() + _, err = db.Exec("INSERT INTO trips (id, timestamp, planned_timestamp, delay, train_name, fahrt_nr, trip_id, latitude, longitude, destination) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + id, whenTime, plannedWhenTime, dep.Delay, dep.Line.Name, dep.Line.FahrtNr, dep.TripId, latitude, longitude, tripDetails.Destination.Name) + if err != nil { + log.Printf("Fehler beim Speichern der neuen Position für TripID %s: %v\n", dep.TripId, err) + } else { + log.Printf("Neue Position gespeichert (ID: %s, Zug: %s, FahrtNr: %s, Lat: %f, Lon: %f, Verspätung: %d, Ziel: %s)\n", id, dep.Line.Name, dep.Line.FahrtNr, latitude, longitude, dep.Delay, tripDetails.Destination.Name) + } + } else if err == nil { + _, err = db.Exec("UPDATE trips SET timestamp = ?, planned_timestamp = ?, delay = ?, train_name = ?, trip_id = ?, latitude = ?, longitude = ?, destination = ? WHERE id = ?", + whenTime, plannedWhenTime, dep.Delay, dep.Line.Name, dep.TripId, latitude, longitude, tripDetails.Destination.Name, existingID) + if err != nil { + log.Printf("Fehler beim Aktualisieren der Position für TripID %s: %v\n", dep.TripId, err) + } else { + log.Printf("Position aktualisiert (ID: %s, Zug: %s, FahrtNr: %s, Lat: %f, Lon: %f, Verspätung: %d, Ziel: %s)\n", existingID, dep.Line.Name, dep.Line.FahrtNr, latitude, longitude, dep.Delay, tripDetails.Destination.Name) + } + } else { + log.Printf("Fehler bei der Überprüfung des existierenden Eintrags für TripID %s: %v\n", dep.TripId, err) + } - updateTodayDelayStats(db, dep.Line.FahrtNr, dep.Line.Name, dep.Delay, whenTime) + updateTodayDelayStats(db, dep.Line.FahrtNr, dep.Line.Name, dep.Delay, whenTime) } func updateTodayDelayStats(db *sql.DB, fahrtNr, trainName string, delay int, timestamp time.Time) { - var existingID string - err := db.QueryRow("SELECT id FROM today_delay_stats WHERE fahrt_nr = ?", fahrtNr).Scan(&existingID) + var existingID string + err := db.QueryRow("SELECT id FROM today_delay_stats WHERE fahrt_nr = ?", fahrtNr).Scan(&existingID) - if err == sql.ErrNoRows { - // Kein existierender Eintrag, führe INSERT aus - _, err = db.Exec(` + if err == sql.ErrNoRows { + // Kein existierender Eintrag, führe INSERT aus + _, err = db.Exec(` INSERT INTO today_delay_stats (id, fahrt_nr, train_name, delay, timestamp) VALUES (UUID(), ?, ?, ?, ?) `, fahrtNr, trainName, delay, timestamp) - if err != nil { - log.Printf("Fehler beim Einfügen der heutigen Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) - } - } else if err == nil { - // Existierender Eintrag gefunden, führe UPDATE aus - _, err = db.Exec(` + if err != nil { + log.Printf("Fehler beim Einfügen der heutigen Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) + } + } else if err == nil { + // Existierender Eintrag gefunden, führe UPDATE aus + _, err = db.Exec(` UPDATE today_delay_stats SET train_name = ?, delay = ?, timestamp = ? WHERE id = ? `, trainName, delay, timestamp, existingID) - if err != nil { - log.Printf("Fehler beim Aktualisieren der heutigen Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) - } - } else { - log.Printf("Fehler beim Überprüfen der heutigen Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) - } + if err != nil { + log.Printf("Fehler beim Aktualisieren der heutigen Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) + } + } else { + log.Printf("Fehler beim Überprüfen der heutigen Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) + } +} + +func calculateMedian(delays []int) float64 { + sort.Ints(delays) + length := len(delays) + if length == 0 { + return 0 + } + if length%2 == 0 { + return float64(delays[length/2-1]+delays[length/2]) / 2 + } + return float64(delays[length/2]) } func transferDailyDelayStats(db *sql.DB) { - log.Println("Starte tägliche Übertragung der Verspätungsstatistiken") - - rows, err := db.Query("SELECT fahrt_nr, train_name, delay FROM today_delay_stats") - if err != nil { - log.Printf("Fehler beim Abrufen der heutigen Verspätungsstatistiken: %v\n", err) - return - } - defer rows.Close() + log.Println("Starte tägliche Übertragung der Verspätungsstatistiken") - var totalTransferred, totalUpdated, totalInserted int + rows, err := db.Query("SELECT fahrt_nr, train_name, delay FROM today_delay_stats") + if err != nil { + log.Printf("Fehler beim Abrufen der heutigen Verspätungsstatistiken: %v\n", err) + return + } + defer rows.Close() - for rows.Next() { - var fahrtNr, trainName string - var delay int - if err := rows.Scan(&fahrtNr, &trainName, &delay); err != nil { - log.Printf("Fehler beim Scannen der Verspätungsdaten: %v\n", err) - continue - } + delayMap := make(map[string][]int) + for rows.Next() { + var fahrtNr, trainName string + var delay int + if err := rows.Scan(&fahrtNr, &trainName, &delay); err != nil { + log.Printf("Fehler beim Scannen der Verspätungsdaten: %v\n", err) + continue + } + delayMap[fahrtNr] = append(delayMap[fahrtNr], delay) + } - var existingID string - err := db.QueryRow("SELECT id FROM delay_stats WHERE fahrt_nr = ?", fahrtNr).Scan(&existingID) + for fahrtNr, delays := range delayMap { + avgDelay := calculateAverage(delays) + medianDelay := calculateMedian(delays) + totalTrips := len(delays) + delayedTrips := countDelayedTrips(delays) - if err == sql.ErrNoRows { - // Kein existierender Eintrag, führe INSERT aus - _, err = db.Exec(` - INSERT INTO delay_stats (id, fahrt_nr, total_trips, delayed_trips, avg_delay, last_updated) - VALUES (UUID(), ?, 1, ?, ?, NOW()) - `, fahrtNr, delay > 300, delay) - if err != nil { - log.Printf("Fehler beim Einfügen der Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) - } else { - totalInserted++ - log.Printf("Neue Verspätungsstatistik eingefügt für FahrtNr %s (Zug: %s, Verspätung: %d)\n", fahrtNr, trainName, delay) - } - } else if err == nil { - // Existierender Eintrag gefunden, führe UPDATE aus - _, err = db.Exec(` + var existingID string + err := db.QueryRow("SELECT id FROM delay_stats WHERE fahrt_nr = ?", fahrtNr).Scan(&existingID) + + if err == sql.ErrNoRows { + _, err = db.Exec(` + INSERT INTO delay_stats (id, fahrt_nr, total_trips, delayed_trips, avg_delay, median_delay, last_updated) + VALUES (UUID(), ?, ?, ?, ?, ?, NOW()) + `, fahrtNr, totalTrips, delayedTrips, avgDelay, medianDelay) + } else if err == nil { + _, err = db.Exec(` UPDATE delay_stats - SET total_trips = total_trips + 1, + SET total_trips = total_trips + ?, delayed_trips = delayed_trips + ?, - avg_delay = ((avg_delay * total_trips) + ?) / (total_trips + 1), + avg_delay = ((avg_delay * total_trips) + ?) / (total_trips + ?), + median_delay = ?, last_updated = NOW() WHERE id = ? - `, delay > 300, delay, existingID) - if err != nil { - log.Printf("Fehler beim Aktualisieren der Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) - } else { - totalUpdated++ - log.Printf("Verspätungsstatistik aktualisiert für FahrtNr %s (Zug: %s, Verspätung: %d)\n", fahrtNr, trainName, delay) - } - } else { - log.Printf("Fehler beim Überprüfen der Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) - } + `, totalTrips, delayedTrips, avgDelay*float64(totalTrips), totalTrips, medianDelay, existingID) + } - totalTransferred++ - } + if err != nil { + log.Printf("Fehler beim Aktualisieren der Verspätungsstatistiken für FahrtNr %s: %v\n", fahrtNr, err) + } else { + log.Printf("Verspätungsstatistik aktualisiert für FahrtNr %s (Durchschnitt: %.2f, Median: %.2f)\n", fahrtNr, avgDelay, medianDelay) + } + } - // Löschen Sie die heutigen Statistiken nach der Übertragung - result, err := db.Exec("DELETE FROM today_delay_stats") - if err != nil { - log.Printf("Fehler beim Löschen der heutigen Verspätungsstatistiken: %v\n", err) - } else { - rowsAffected, _ := result.RowsAffected() - log.Printf("%d Einträge aus today_delay_stats gelöscht\n", rowsAffected) - } + // Löschen Sie die heutigen Statistiken nach der Übertragung + result, err := db.Exec("DELETE FROM today_delay_stats") + if err != nil { + log.Printf("Fehler beim Löschen der heutigen Verspätungsstatistiken: %v\n", err) + } else { + rowsAffected, _ := result.RowsAffected() + log.Printf("%d Einträge aus today_delay_stats gelöscht\n", rowsAffected) + } +} - log.Printf("Tägliche Übertragung abgeschlossen. Gesamt: %d, Eingefügt: %d, Aktualisiert: %d\n", totalTransferred, totalInserted, totalUpdated) +func calculateAverage(delays []int) float64 { + sum := 0 + for _, delay := range delays { + sum += delay + } + return float64(sum) / float64(len(delays)) +} + +func countDelayedTrips(delays []int) int { + count := 0 + for _, delay := range delays { + if delay > 300 { + count++ + } + } + return count } func calculateCurrentPosition(trip *TripDetails, currentTime time.Time) (float64, float64) { - totalDuration := trip.Arrival.Sub(trip.Departure) - elapsedDuration := currentTime.Sub(trip.Departure) - progress := elapsedDuration.Seconds() / totalDuration.Seconds() + totalDuration := trip.Arrival.Sub(trip.Departure) + elapsedDuration := currentTime.Sub(trip.Departure) + progress := elapsedDuration.Seconds() / totalDuration.Seconds() - if progress < 0 { - return trip.Origin.Location.Longitude, trip.Origin.Location.Latitude - } - if progress > 1 { - return trip.Destination.Location.Longitude, trip.Destination.Location.Latitude - } + if progress < 0 { + return trip.Origin.Location.Longitude, trip.Origin.Location.Latitude + } + if progress > 1 { + return trip.Destination.Location.Longitude, trip.Destination.Location.Latitude + } - polyline := trip.Polyline.Features - totalDistance := 0.0 - distances := make([]float64, len(polyline)-1) + polyline := trip.Polyline.Features + totalDistance := 0.0 + distances := make([]float64, len(polyline)-1) - for i := 0; i < len(polyline)-1; i++ { - dist := distance( - polyline[i].Geometry.Coordinates[1], polyline[i].Geometry.Coordinates[0], - polyline[i+1].Geometry.Coordinates[1], polyline[i+1].Geometry.Coordinates[0], - ) - distances[i] = dist - totalDistance += dist - } + for i := 0; i < len(polyline)-1; i++ { + dist := distance( + polyline[i].Geometry.Coordinates[1], polyline[i].Geometry.Coordinates[0], + polyline[i+1].Geometry.Coordinates[1], polyline[i+1].Geometry.Coordinates[0], + ) + distances[i] = dist + totalDistance += dist + } - targetDistance := totalDistance * progress - coveredDistance := 0.0 + targetDistance := totalDistance * progress + coveredDistance := 0.0 - for i, dist := range distances { - if coveredDistance+dist > targetDistance { - remainingDistance := targetDistance - coveredDistance - ratio := remainingDistance / dist - return interpolate( - polyline[i].Geometry.Coordinates[0], polyline[i].Geometry.Coordinates[1], - polyline[i+1].Geometry.Coordinates[0], polyline[i+1].Geometry.Coordinates[1], - ratio, - ) - } - coveredDistance += dist - } + for i, dist := range distances { + if coveredDistance+dist > targetDistance { + remainingDistance := targetDistance - coveredDistance + ratio := remainingDistance / dist + return interpolate( + polyline[i].Geometry.Coordinates[0], polyline[i].Geometry.Coordinates[1], + polyline[i+1].Geometry.Coordinates[0], polyline[i+1].Geometry.Coordinates[1], + ratio, + ) + } + coveredDistance += dist + } - return trip.Destination.Location.Longitude, trip.Destination.Location.Latitude + return trip.Destination.Location.Longitude, trip.Destination.Location.Latitude } func distance(lat1, lon1, lat2, lon2 float64) float64 { - const r = 6371 // Earth radius in kilometers + const r = 6371 // Earth radius in kilometers - dLat := (lat2 - lat1) * math.Pi / 180 - dLon := (lon2 - lon1) * math.Pi / 180 - a := math.Sin(dLat/2)*math.Sin(dLat/2) + - math.Cos(lat1*math.Pi/180)*math.Cos(lat2*math.Pi/180)* - math.Sin(dLon/2)*math.Sin(dLon/2) - c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) - return r * c + dLat := (lat2 - lat1) * math.Pi / 180 + dLon := (lon2 - lon1) * math.Pi / 180 + a := math.Sin(dLat/2)*math.Sin(dLat/2) + + math.Cos(lat1*math.Pi/180)*math.Cos(lat2*math.Pi/180)* + math.Sin(dLon/2)*math.Sin(dLon/2) + c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) + return r * c } func interpolate(lon1, lat1, lon2, lat2, ratio float64) (float64, float64) { - return lon1 + (lon2-lon1)*ratio, lat1 + (lat2-lat1)*ratio + return lon1 + (lon2-lon1)*ratio, lat1 + (lat2-lat1)*ratio } func deleteOldEntries(db *sql.DB, deleteAfterMinutes int) { - deleteTime := time.Now().Add(time.Duration(-deleteAfterMinutes) * time.Minute) - result, err := db.Exec("DELETE FROM trips WHERE timestamp < ?", deleteTime) - if err != nil { - log.Printf("Fehler beim Löschen alter Einträge: %v\n", err) - return - } - rowsAffected, _ := result.RowsAffected() - log.Printf("%d alte Einträge gelöscht\n", rowsAffected) + deleteTime := time.Now().Add(time.Duration(-deleteAfterMinutes) * time.Minute) + result, err := db.Exec("DELETE FROM trips WHERE timestamp < ?", deleteTime) + if err != nil { + log.Printf("Fehler beim Löschen alter Einträge: %v\n", err) + return + } + rowsAffected, _ := result.RowsAffected() + log.Printf("%d alte Einträge gelöscht\n", rowsAffected) } func logDatabaseStats(db *sql.DB) { - var count int - err := db.QueryRow("SELECT COUNT(*) FROM trips").Scan(&count) - if err != nil { - log.Printf("Fehler beim Abrufen der Datenbankstatistiken: %v\n", err) - return - } - log.Printf("Aktuelle Anzahl der Einträge in der Datenbank: %d\n", count) + var count int + err := db.QueryRow("SELECT COUNT(*) FROM trips").Scan(&count) + if err != nil { + log.Printf("Fehler beim Abrufen der Datenbankstatistiken: %v\n", err) + return + } + log.Printf("Aktuelle Anzahl der Einträge in der Datenbank: %d\n", count) } - diff --git a/main.go.old b/main.go.old deleted file mode 100644 index ad0c521..0000000 --- a/main.go.old +++ /dev/null @@ -1,164 +0,0 @@ -package main - -import ( - "database/sql" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "strconv" - "strings" - "time" - - _ "github.com/go-sql-driver/mysql" - "github.com/google/uuid" -) - -type Departure struct { - CurrentTripPosition struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - } `json:"currentTripPosition"` - When time.Time `json:"when"` - Line struct { - Name string `json:"name"` - FahrtNr string `json:"fahrtNr"` - } `json:"line"` -} - -type APIResponse struct { - Departures []Departure `json:"departures"` -} - -func main() { - log.SetOutput(os.Stdout) - log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) - log.Println("Anwendung gestartet") - - dbDSN := os.Getenv("DB_DSN") - apiBaseURL := os.Getenv("API_BASE_URL") - maxResults, err := strconv.Atoi(os.Getenv("MAX_RESULTS")) - if err != nil { - log.Fatalf("Ungültiger Wert für MAX_RESULTS: %v", err) - } - duration, err := strconv.Atoi(os.Getenv("DURATION")) - if err != nil { - log.Fatalf("Ungültiger Wert für DURATION: %v", err) - } - includeBus, err := strconv.ParseBool(os.Getenv("BUS")) - if err != nil { - log.Fatalf("Ungültiger Wert für BUS: %v", err) - } - includeFerry, err := strconv.ParseBool(os.Getenv("FERRY")) - if err != nil { - log.Fatalf("Ungültiger Wert für FERRY: %v", err) - } - includeTram, err := strconv.ParseBool(os.Getenv("TRAM")) - if err != nil { - log.Fatalf("Ungültiger Wert für TRAM: %v", err) - } - includeTaxi, err := strconv.ParseBool(os.Getenv("TAXI")) - if err != nil { - log.Fatalf("Ungültiger Wert für TAXI: %v", err) - } - deleteAfter, err := strconv.Atoi(os.Getenv("DELETE_AFTER_MINUTES")) - if err != nil { - log.Fatalf("Ungültiger Wert für DELETE_AFTER_MINUTES: %v", err) - } - stationIDs := strings.Split(os.Getenv("STATION_IDS"), ",") - - db, err := sql.Open("mysql", dbDSN) - if err != nil { - log.Fatal("Fehler beim Verbinden mit der Datenbank: ", err) - } - defer db.Close() - - for { - for _, stationID := range stationIDs { - departures := fetchDepartures(apiBaseURL, stationID, maxResults, duration, includeBus, includeFerry, includeTram, includeTaxi) - for _, dep := range departures { - savePosition(db, dep) - } - } - deleteOldEntries(db, deleteAfter) - time.Sleep(1 * time.Minute) - } -} - -func fetchDepartures(apiBaseURL, stationID string, maxResults, duration int, includeBus, includeFerry, includeTram, includeTaxi bool) []Departure { - url := fmt.Sprintf("%s/stops/%s/departures?results=%d&duration=%d&bus=%t&ferry=%t&tram=%t&taxi=%t", - apiBaseURL, stationID, maxResults, duration, includeBus, includeFerry, includeTram, includeTaxi) - resp, err := http.Get(url) - if err != nil { - log.Printf("Fehler beim Abrufen der Abfahrten für Station %s: %v\n", stationID, err) - return nil - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Printf("Fehler beim Lesen der Antwort für Station %s: %v\n", stationID, err) - return nil - } - - if len(body) == 0 { - log.Printf("Leere Antwort vom Server für Station %s erhalten\n", stationID) - return nil - } - - var response APIResponse - err = json.Unmarshal(body, &response) - if err != nil { - log.Printf("Fehler beim Dekodieren der Abfahrten für Station %s: %v\nAntwort-Body: %s\n", stationID, err, string(body)) - return nil - } - - log.Printf("Erfolgreich %d Abfahrten für Station %s abgerufen\n", len(response.Departures), stationID) - return response.Departures -} - -func savePosition(db *sql.DB, dep Departure) { - if dep.CurrentTripPosition.Latitude == 0 && dep.CurrentTripPosition.Longitude == 0 { - log.Println("Keine gültige Position verfügbar") - return - } - - today := time.Now().Format("2006-01-02") - - var existingID string - err := db.QueryRow("SELECT id FROM trips WHERE fahrt_nr = ? AND DATE(timestamp) = ?", dep.Line.FahrtNr, today).Scan(&existingID) - - if err == sql.ErrNoRows { - id := uuid.New().String() - _, err = db.Exec("INSERT INTO trips (id, latitude, longitude, timestamp, train_name, fahrt_nr) VALUES (?, ?, ?, ?, ?, ?)", - id, dep.CurrentTripPosition.Latitude, dep.CurrentTripPosition.Longitude, dep.When, dep.Line.Name, dep.Line.FahrtNr) - if err != nil { - log.Printf("Fehler beim Speichern der neuen Position: %v\n", err) - } else { - log.Printf("Neue Position gespeichert (ID: %s, Zug: %s, FahrtNr: %s)\n", id, dep.Line.Name, dep.Line.FahrtNr) - } - } else if err == nil { - _, err = db.Exec("UPDATE trips SET latitude = ?, longitude = ?, timestamp = ?, train_name = ? WHERE id = ?", - dep.CurrentTripPosition.Latitude, dep.CurrentTripPosition.Longitude, dep.When, dep.Line.Name, existingID) - if err != nil { - log.Printf("Fehler beim Aktualisieren der Position: %v\n", err) - } else { - log.Printf("Position aktualisiert (ID: %s, Zug: %s, FahrtNr: %s)\n", existingID, dep.Line.Name, dep.Line.FahrtNr) - } - } else { - log.Printf("Fehler bei der Überprüfung des existierenden Eintrags: %v\n", err) - } -} - -func deleteOldEntries(db *sql.DB, deleteAfterMinutes int) { - deleteTime := time.Now().Add(time.Duration(-deleteAfterMinutes) * time.Minute) - result, err := db.Exec("DELETE FROM trips WHERE timestamp < ?", deleteTime) - if err != nil { - log.Printf("Fehler beim Löschen alter Einträge: %v\n", err) - return - } - rowsAffected, _ := result.RowsAffected() - log.Printf("%d alte Einträge gelöscht\n", rowsAffected) -}