Skip to content

Commit

Permalink
change all times into timezone aware datetimes to cover timezone prob…
Browse files Browse the repository at this point in the history
…lems
  • Loading branch information
GliderGeek committed Aug 29, 2024
1 parent b8d955a commit 9d750f8
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 136 deletions.
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
OpenSoar
========

busy with making everything timezone aware
- (done) changed start-time and fixes to timezone aware datetimes
- (done) removed unnecessary helper functionality
- check .seconds on delta. should it be 'total_seconds'?
- run tests on opensoar, does it still work?
- run test with pysoar, same results?
- how to keep backwards compatible? add flag?

.. image:: https://img.shields.io/pypi/v/opensoar.svg
:target: https://pypi.org/project/opensoar/
:alt: pypi version and link
Expand Down
39 changes: 34 additions & 5 deletions opensoar/competition/soaringspot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from opensoar.task.race_task import RaceTask
from opensoar.task.task import Task
from opensoar.task.waypoint import Waypoint
from opensoar.utilities.helper_functions import dm2dd, subtract_times
from opensoar.utilities.helper_functions import dm2dd
from opensoar.competition.daily_results_page import DailyResultsPage


Expand Down Expand Up @@ -46,7 +46,7 @@ def get_task_rules(lseeyou_tsk_line: str) -> Tuple[datetime.time, datetime.timed
return start_opening, t_min, multi_start


def get_info_from_comment_lines(parsed_igc_file: dict, start_time_buffer: int=0) -> Tuple[Optional[Task], dict, dict]:
def get_info_from_comment_lines(parsed_igc_file: dict, date: datetime.date, start_time_buffer: int=0) -> Tuple[Optional[Task], dict, dict]:
"""
There is specific contest information stored in the comment lines of the IGC files.
This function extracts this information
Expand Down Expand Up @@ -84,8 +84,16 @@ def get_info_from_comment_lines(parsed_igc_file: dict, start_time_buffer: int=0)
timezone = int(line.split(':')[3])

if start_opening is not None:
# convert start opening to UTC time
start_opening = subtract_times(start_opening, datetime.timedelta(hours=timezone))
# make timezone aware datetime object
start_opening = datetime.datetime(
year=date.year,
month=date.month,
day=date.day,
hour=start_opening.hour,
minute=start_opening.minute,
second=start_opening.second,
tzinfo=datetime.tzinfo.utcoffset(timezone)
)

if len(lcu_lines) == 0 or len(lseeyou_lines) == 0:
# somehow some IGC files do not contain the LCU or LSEEYOU lines with task information
Expand Down Expand Up @@ -359,10 +367,31 @@ def generate_competition_day(self, target_directory: str, download_progress=None
continue

# get info from file
task, contest_information, competitor_information = get_info_from_comment_lines(parsed_igc_file, start_time_buffer)
task, contest_information, competitor_information = get_info_from_comment_lines(parsed_igc_file, date, start_time_buffer)
plane_model = competitor_information.get('plane_model', None)
pilot_name = competitor_information.get('pilot_name', None)

# convert trace into timezone aware fixes
# first fix (specified in UTC) should be on specified date (local)
hour_local_time = trace[0].hour + task.timezone
if hour_local_time < 0:
day_diff = 1 # UTC is one day later than local
elif hour_local_time >= 24:
day_diff = -1 # UTC is one day prior to local
else:
day_diff = 0 # UTC is same day

for fix in trace:
fix['time'] = datetime.datetime(
year=date.year,
month=date.month,
day=date.day + day_diff,
hour= fix['time'].hour,
hour= fix['time'].minute,
second= fix['time'].second,
tzinfo=datetime.tzinfo.utcoffset(0),
)

competitor = Competitor(trace, competition_id, plane_model, ranking, pilot_name)

competitors.append(competitor)
Expand Down
9 changes: 4 additions & 5 deletions opensoar/task/aat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from copy import deepcopy

from opensoar.task.task import Task
from opensoar.utilities.helper_functions import double_iterator, calculate_distance_bearing, calculate_destination, \
seconds_time_difference_fixes, add_times
from opensoar.utilities.helper_functions import double_iterator, calculate_distance_bearing, calculate_destination


class AAT(Task):
Expand Down Expand Up @@ -51,10 +50,10 @@ def apply_rules(self, trace):
return fixes, start_time, outlanding_fix, distances, finish_time, sector_fixes

def _determine_finish_time(self, fixes, outlanding_fix):
total_trip_time = seconds_time_difference_fixes(fixes[0], fixes[-1])
total_trip_time = (fixes[-1]['time'] - fixes[0]['time']).seconds
minimum_trip_time = self._t_min.total_seconds()
if outlanding_fix is None and total_trip_time < minimum_trip_time:
finish_time = add_times(fixes[0]['time'], self._t_min)
finish_time = fixes[0]['time'] + self._t_min
else:
finish_time = fixes[-1]['time']
return finish_time
Expand Down Expand Up @@ -118,7 +117,7 @@ def _get_sector_fixes(self, trace):
if enl_first_fix is None:
enl_first_fix = fix

enl_time = seconds_time_difference_fixes(enl_first_fix, fix)
enl_time = (fix['time'] - enl_first_fix['time']).seconds
if self.enl_time_exceeded(enl_time):
enl_registered = True
if current_leg > 0:
Expand Down
10 changes: 5 additions & 5 deletions opensoar/task/race_task.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from opensoar.task.task import Task
from opensoar.utilities.helper_functions import calculate_distance_bearing, double_iterator, \
seconds_time_difference_fixes, add_seconds
import datetime

from opensoar.task.task import Task
from opensoar.utilities.helper_functions import calculate_distance_bearing, double_iterator

class RaceTask(Task):
"""
Expand Down Expand Up @@ -100,15 +100,15 @@ def determine_trip_fixes(self, trace):
if enl_first_fix is None:
enl_first_fix = fix_minus1

enl_time = seconds_time_difference_fixes(enl_first_fix, fix)
enl_time = (fix['time'] - enl_first_fix['time']).seconds
enl_registered = enl_registered or self.enl_time_exceeded(enl_time)
elif not enl_registered:
enl_first_fix = None

if self.start_opening is None:
after_start_opening = True
else:
after_start_opening = add_seconds(fix['time'], self.start_time_buffer) > self.start_opening
after_start_opening = self.start_opening + datetime.timedelta(seconds=self.start_time_buffer) < fix['time']

if leg == -1 and after_start_opening:
if self.started(fix_minus1, fix):
Expand Down
7 changes: 2 additions & 5 deletions opensoar/task/trip.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from opensoar.utilities.helper_functions import seconds_time_difference


class Trip:
"""
Realised
Expand Down Expand Up @@ -47,11 +44,11 @@ def fix_on_leg(self, fix, leg):
return larger_than_minimum and smaller_than_maximum

def fix_before_leg(self, fix, leg):
return seconds_time_difference(fix['time'], self.fixes[leg]['time']) >= 0
return (self.fixes[leg]['time'] - fix['time']).seconds >= 0

def fix_after_leg(self, fix, leg):
if leg + 1 <= self.completed_legs():
return seconds_time_difference(self.fixes[leg + 1]['time'], fix['time']) >= 0
return (fix['time'] - self.fixes[leg + 1]['time']).seconds >= 0
elif self.outlanded() and leg == self.outlanding_leg():
return False
else: # leg > self.completed_legs() + 1
Expand Down
9 changes: 4 additions & 5 deletions opensoar/thermals/pysoar_thermal_detector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from opensoar.utilities.helper_functions import triple_iterator, calculate_bearing_change, calculate_distance_bearing, \
seconds_time_difference
from opensoar.utilities.helper_functions import triple_iterator, calculate_bearing_change, calculate_distance_bearing


class PySoarThermalDetector:
Expand Down Expand Up @@ -39,8 +38,8 @@ def analyse(self, trace):
time = fix['time']

bearing_change = calculate_bearing_change(fix_minus2, fix_minus1, fix)
delta_t = (0.5 * seconds_time_difference(time_minus1, time) +
0.5 * seconds_time_difference(time_minus2, time))
delta_t = (0.5 * (time - time_minus1).seconds +
0.5 * (time - time_minus2).seconds)
bearing_change_rate = bearing_change / delta_t

if cruise:
Expand Down Expand Up @@ -101,7 +100,7 @@ def analyse(self, trace):
possible_cruise_fixes.append(fix)
total_bearing_change += bearing_change

delta_t = seconds_time_difference(possible_cruise_fixes[0]['time'], time)
delta_t = (time - possible_cruise_fixes[0]['time']).seconds
cruise_distance, _ = calculate_distance_bearing(possible_cruise_fixes[0], fix)
temp_bearing_rate_avg = 0 if delta_t == 0 else total_bearing_change / delta_t

Expand Down
64 changes: 2 additions & 62 deletions opensoar/utilities/helper_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,6 @@ def altitude_gain_and_loss(fixes: List[dict], gps_altitude=True):
return gain, loss


def seconds_time_difference_fixes(fix1, fix2):
return seconds_time_difference(fix1['time'], fix2['time'])


def total_distance_travelled(fixes: List[dict]):
"""Calculates the total distance, summing over the inter fix distances"""
distance = 0
Expand All @@ -138,61 +134,6 @@ def total_distance_travelled(fixes: List[dict]):
return distance


def seconds_time_difference(time1: datetime.time, time2: datetime.time):
"""
Determines the time difference between to datetime.time instances, mocking the operation time2 - time1
It is assumed that both take place at the same day.
:param time1:
:param time2:
:return: time difference in seconds
"""

today = datetime.date.today()
time_diff = datetime.datetime.combine(today, time2) - datetime.datetime.combine(today, time1)
return time_diff.total_seconds()


def add_times(start_time: datetime.time, delta_time: datetime.timedelta):
"""
Helper to circumvent problem that normal datetime.time instances can not be added.
:param start_time:
:param delta_time:
:return:
"""
full_datetime_start = datetime.datetime.combine(datetime.date.today(), start_time)

full_datetime_result = full_datetime_start + delta_time
return full_datetime_result.time()


def subtract_times(start_time: datetime.time, delta_time: datetime.timedelta):
full_datetime_start = datetime.datetime.combine(datetime.date.today(), start_time)
full_datetime_result = full_datetime_start - delta_time
return full_datetime_result.time()


def add_seconds(time: datetime.time, seconds: int) -> datetime.time:
"""
Add seconds to datetime.time object and return resulting datetime.time object.
:param time:
:param seconds: not limited to 0-59.
:return:
"""

additional_seconds = seconds

additional_hours = additional_seconds // 3600
additional_seconds -= additional_hours * 3600

additional_minutes = additional_seconds // 60
additional_seconds -= additional_minutes * 60

return add_times(time, datetime.timedelta(hours=additional_hours,
minutes=additional_minutes,
seconds=additional_seconds))


def range_with_bounds(start: int, stop: int, interval: int) -> List[int]:
"""Return list"""
result = [int(val) for val in range(start, stop, interval)]
Expand All @@ -202,7 +143,7 @@ def range_with_bounds(start: int, stop: int, interval: int) -> List[int]:


def calculate_time_differences(time1, time2, interval):
total_difference = int(seconds_time_difference(time1, time2))
total_difference = (time2 - time1).seconds
differences = range_with_bounds(0, total_difference, interval)
return differences

Expand All @@ -225,8 +166,7 @@ def interpolate_fixes(fix1, fix2, interval=1):

lat = fix1['lat'] + fraction * (fix2['lat'] - fix1['lat'])
lon = fix1['lon'] + fraction * (fix2['lon'] - fix1['lon'])
time = add_seconds(fix1['time'], difference)

time = fix1['time'] + datetime.timedelta(seconds=difference)
fixes.append(dict(time=time, lat=lat, lon=lon))

return fixes
Expand Down
8 changes: 5 additions & 3 deletions tests/competition/test_strepla.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from opensoar.competition.competitor import Competitor
from opensoar.competition.strepla import get_waypoint_name_lat_long, get_waypoints, get_waypoint, get_task_and_competitor_info, get_info_from_comment_lines
from opensoar.task.aat import AAT
from opensoar.utilities.helper_functions import seconds_time_difference


class TestStrepla(unittest.TestCase):
Expand Down Expand Up @@ -107,8 +106,11 @@ def test_aat_from_file(self):
competitor = Competitor(trace, 'CX', 'Discus2b', 1, 'Karsten Leucker')
competitor.analyse(task, 'pysoar')

time_diff = seconds_time_difference(competitor.trip.refined_start_time, datetime.time(13, 22, 40))
self.assertEqual(competitor.trip.refined_start_time.hour, 13)
self.assertEqual(competitor.trip.refined_start_time.minute, 22)
seconds = competitor.trip.refined_start_time.second

dist_diff = sum(competitor.trip.distances) - 283500
self.assertLessEqual(abs(time_diff), 1)
self.assertLessEqual(abs(seconds-40), 1)
self.assertEqual(len(competitor.trip.fixes), len(expected_waypoints))
self.assertLessEqual(abs(dist_diff), 1000)
14 changes: 8 additions & 6 deletions tests/thermals/test_flight_phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from opensoar.task.trip import Trip
from opensoar.thermals.flight_phases import FlightPhases
from opensoar.utilities.helper_functions import double_iterator, seconds_time_difference
from opensoar.utilities.helper_functions import double_iterator

from tests.task.helper_functions import get_trace, get_task

Expand Down Expand Up @@ -47,7 +47,7 @@ def test_all_phases(self):

# check if start times of phases are within 2 seconds
for phase, pysoar_phase_start_time in zip(all_phases, self.pysoar_phase_start_times):
time_diff = seconds_time_difference(phase.fixes[0]['time'], pysoar_phase_start_time)
time_diff = (pysoar_phase_start_time - phase.fixes[0]['time']).seconds
self.assertLessEqual(abs(time_diff), 2)

def test_thermals(self):
Expand All @@ -60,7 +60,7 @@ def test_thermals(self):

# check if correct phases are classified as thermals
for thermal, pysoar_start_time in zip(thermals, self.pysoar_phase_start_times[1::2]):
time_diff = seconds_time_difference(thermal.fixes[0]['time'], pysoar_start_time)
time_diff = (pysoar_start_time - thermal.fixes[0]['time']).seconds
self.assertLessEqual(abs(time_diff), 2)

def test_cruises(self):
Expand All @@ -73,7 +73,7 @@ def test_cruises(self):

# check if correct phases are classified as cruises
for cruise, pysoar_start_time in zip(cruises, self.pysoar_phase_start_times[0::2]):
time_diff = seconds_time_difference(cruise.fixes[0]['time'], pysoar_start_time)
time_diff = (pysoar_start_time - cruise.fixes[0]['time']).seconds
self.assertLessEqual(abs(time_diff), 2)

def test_thermals_on_leg(self):
Expand All @@ -92,11 +92,13 @@ def test_thermals_on_leg(self):

# check starttime of first thermal
start_time = thermals_leg2[0].fixes[0]['time']
self.assertEqual(seconds_time_difference(start_time, leg_start_time), 0)
diff = (leg_start_time - start_time).seconds
self.assertEqual(diff, 0)

# check endtime of last thermal
end_time = thermals_leg2[-1].fixes[-1]['time']
self.assertEqual(seconds_time_difference(end_time, leg_end_time), 0)
diff = (leg_end_time - end_time).seconds
self.assertEqual(diff, 0)

def test_cruises_on_leg(self):

Expand Down
Loading

0 comments on commit 9d750f8

Please sign in to comment.