From 541251dcd5d537d6915ace41e8e6c737899a3871 Mon Sep 17 00:00:00 2001 From: Vladimir Simakhin Date: Tue, 2 Jul 2024 22:14:13 +0200 Subject: [PATCH] fix night time calculation inside polar circle --- CHANGELOG.md | 4 +++ README.md | 10 +++--- internal/nighttime/nighttime.go | 63 +++++++++++++++++++++++---------- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bae6697..70d70af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Unreleased] + +- Fix: Incorrect night time calculation when flying inside the polar circle + ## [2.38.0] - 25.06.2024 - Fix: Bug with columns for the extended PDF format, both A4 and A5. diff --git a/README.md b/README.md index 45ec05e..927dbf6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ You also can easily export all flight records into EASA style pdf format, print # Changelog +## [Unreleased] + +- Fix: Incorrect night time calculation when flying inside the polar circle + ## [2.38.0] - 25.06.2024 - Fix: Bug with columns for the extended PDF format, both A4 and A5. @@ -39,12 +43,6 @@ You also can easily export all flight records into EASA style pdf format, print - Fix: Add dynamic resizing for the charts on Stats pages - Fix: Instructor's hours for the Total Stats table were mixed up for the last 90 days and the last 12 months -## [2.35.1] - 20.05.2024 - -- Fix: Correct using styles and classes for the sidebar when switching between the pages -- Update: Code cleanup, removing code for synchronization with mobile client (will not continue working on it) -- Update: Update golang version (1.21.10) and go packages. - The full changelog is [here](https://github.com/vsimakhin/web-logbook/blob/main/CHANGELOG.md) # Usage diff --git a/internal/nighttime/nighttime.go b/internal/nighttime/nighttime.go index 928f80f..1665911 100644 --- a/internal/nighttime/nighttime.go +++ b/internal/nighttime/nighttime.go @@ -77,25 +77,40 @@ func (route *Route) FlightSpeed() float64 { return route.RouteDistance() / route.FlightTime().Hours() } -// SunriseSunset returns sunrise and sunset times -func (place *Place) SunriseSunset() (time.Time, time.Time) { - sunrise, sunset := sunrise.SunriseSunset(place.Lat, place.Lon, place.Time.UTC().Year(), place.Time.UTC().Month(), place.Time.UTC().Day()) +// SunriseSunset returns sunrise and sunset times or time.Time{} if there is no night +func (place *Place) SunriseSunset() (time.Time, time.Time, float64) { + sunElevation := sunrise.Elevation(place.Lat, place.Lon, place.Time) + sunRise, sunSet := sunrise.SunriseSunset(place.Lat, place.Lon, place.Time.UTC().Year(), place.Time.UTC().Month(), place.Time.UTC().Day()) + + noNight := time.Time{} + if sunRise == noNight || sunSet == noNight { + return noNight, noNight, sunElevation + } + + aviationSunRise := sunRise.Add(time.Duration(-30) * time.Minute) + aviationSunSet := sunSet.Add(time.Duration(30) * time.Minute) - return sunrise.UTC().Add(time.Duration(-30) * time.Minute), sunset.UTC().Add(time.Duration(30) * time.Minute) + return aviationSunRise, aviationSunSet, sunElevation } // Sunrise returns aviation sunrise time func (place *Place) Sunrise() time.Time { - s, _ := place.SunriseSunset() + s, _, _ := place.SunriseSunset() return s } // Sunset returns aviation sunset time (+30 minutes from apparent sunset) func (place *Place) Sunset() time.Time { - _, s := place.SunriseSunset() + _, s, _ := place.SunriseSunset() return s } +// Elevation returns sun elevation at the place +func (place *Place) Elevation() float64 { + _, _, e := place.SunriseSunset() + return e +} + func (route *Route) NightTime() time.Duration { speed := route.FlightSpeed() speedPerMinute := speed / 60 @@ -107,9 +122,9 @@ func (route *Route) NightTime() time.Duration { } func nightSegment(start Place, end Place, maxDistance float64, speedPerMinute float64) time.Duration { - d := time.Duration(0) distance := distance(start, end) + if distance > maxDistance { // too long, let's split it again mid := midpoint(start, end) @@ -117,19 +132,29 @@ func nightSegment(start Place, end Place, maxDistance float64, speedPerMinute fl flightTime := distance / 2 / speedPerMinute mid.Time = start.Time.Add(time.Duration(flightTime) * time.Minute) - d = nightSegment(start, mid, maxDistance, speedPerMinute) + nightSegment(mid, end, maxDistance, speedPerMinute) - } else { - // get sunrise and sunset for the end point - // it could be calculated for the middle point again to be more precise, - // but it will add few more calculations and the error is not so high - sr, ss := end.SunriseSunset() - - if end.Time.After(sr) && end.Time.Before(ss) { - d = time.Duration(0) - } else { - d = time.Duration(distance / speedPerMinute * float64(time.Minute)) + return nightSegment(start, mid, maxDistance, speedPerMinute) + nightSegment(mid, end, maxDistance, speedPerMinute) + } + + // get sunrise and sunset for the end point + // it could be calculated for the middle point again to be more precise, + // but it will add few more calculations and the error is not so high + sr, ss, elevation := end.SunriseSunset() + + nightTime := time.Duration(distance / speedPerMinute * float64(time.Minute)) + + if sr.Year() == 1 && ss.Year() == 1 { + if elevation > 0 { + // Polar day, no night time + return 0 } + // Polar night, all time is night + return nightTime + } + + // day time + if end.Time.After(sr) && end.Time.Before(ss) { + return 0 } - return d + return nightTime }