Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NPI-3683 SP3 consistency check streamlining #70

Merged
merged 13 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
NPI-3683 added a function for removing satellites from the internal h…
…eader representation, to help maintain consistency of that header data. This is useful for example when removing offline sats.
  • Loading branch information
treefern committed Jan 10, 2025
commit 05b0c93db422f35dde2af5eed1a8d6d8bde3f2ff
26 changes: 26 additions & 0 deletions gnssanalysis/gn_io/sp3.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,32 @@ def sp3_clock_nodata_to_nan(sp3_df: _pd.DataFrame) -> None:
sp3_df.loc[nan_mask, ("EST", "CLK")] = _np.nan


def remove_svs_from_header(sp3_df: _pd.DataFrame, sats_to_remove: set[str]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing output type hint

"""
Utility function to update the internal representation of an SP3 header, when SVs are removed from the SP3
DataFrame. This is useful e.g. when removing offline satellites.
:param _pd.DataFrame sp3_df: SP3 DataFrame on which to update the header (in place).
:param list[str] sats_to_remove: list of SV names to remove from the header.
"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc string missing output parameter

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is none; this function operates on the DataFrame passed in, modifying it in place.

num_to_remove: int = len(sats_to_remove)

# Update header SV count (bunch of type conversion because header is stored as strings)
sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED = str(int(sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED) - num_to_remove)

# Remove sats from the multi-index which contains SV_INFO and HEAD. This does both SV list and accuracy code list.
sp3_df.attrs["HEADER"].drop(level=1, labels=sats_to_remove, inplace=True)

# Notes on the multi-index update:

# The hierarchy here is:
# dict (HEADER) > series (unnamed) > HEAD (part of multi-index) > str (SV_COUNT_STATED)
# So we operate on the overall Series in the dict.

# In the above statement, Level 1 gets us to the 'column' names (like G02), not the 'section' names (like SV_INFO).
# Labels will apply to anything at that level (including things in HEAD - but those columns should never share a
# name with an SV).


def remove_offline_sats(sp3_df: _pd.DataFrame, df_friendly_name: str = ""):
"""
Remove any satellites that have "0.0" or NaN for all three position coordinates - this indicates satellite offline.
Expand Down
62 changes: 62 additions & 0 deletions tests/test_sp3.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,44 @@ def test_velinterpolation(self, mock_file):
@patch("builtins.open", new_callable=mock_open, read_data=offline_sat_test_data)
def test_sp3_offline_sat_removal(self, mock_file):
sp3_df = sp3.read_sp3("mock_path", pOnly=False)

# Confirm starting state of content
self.assertEqual(
sp3_df.index.get_level_values(1).unique().array.tolist(),
["G02", "G03", "G19"],
"Should be three SVs in test file before removing offline ones",
)

# Confirm header matches (this is doubling up on header update test)
self.assertEqual(
sp3_df.attrs["HEADER"].SV_INFO.index.array.tolist(),
["G02", "G03", "G19"],
"Should be three SVs in parsed header before removing offline ones",
)
self.assertEqual(
sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED, "3", "Header should have 2 SVs before removing offline"
)

# Now make the changes - this should also update the header
sp3_df = sp3.remove_offline_sats(sp3_df)

# Check contents
self.assertEqual(
sp3_df.index.get_level_values(1).unique().array.tolist(),
["G02", "G03"],
"Should be two SVs after removing offline ones",
)

# Check header
self.assertEqual(
sp3_df.attrs["HEADER"].SV_INFO.index.array.tolist(),
["G02", "G03"],
"Should be two SVs in parsed header after removing offline ones",
)
self.assertEqual(
sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED, "2", "Header should have 2 SVs after removing offline"
)

# sp3_test_data_truncated_cod_final is input_data2
@patch("builtins.open", new_callable=mock_open, read_data=input_data2)
def test_filter_by_svs(self, mock_file):
Expand Down Expand Up @@ -317,6 +342,43 @@ def test_trim_df(self, mock_file):
)


class TestSP3Utils(TestCase):

@patch("builtins.open", new_callable=mock_open, read_data=input_data)
def test_get_unique_svs(self, mock_file):
sp3_df = sp3.read_sp3("mock_path", pOnly=True)

unique_svs = set(sp3.get_unique_svs(sp3_df).values)
self.assertEqual(unique_svs, set(["G01", "G02"]))

@patch("builtins.open", new_callable=mock_open, read_data=input_data)
def test_get_unique_epochs(self, mock_file):
sp3_df = sp3.read_sp3("mock_path", pOnly=True)

unique_epochs = set(sp3.get_unique_epochs(sp3_df).values)
self.assertEqual(unique_epochs, set([229608000, 229608900, 229609800]))

@patch("builtins.open", new_callable=mock_open, read_data=sp3c_example2_data)
def test_remove_svs_from_header(self, mock_file):
sp3_df = sp3.read_sp3("mock_path", pOnly=True)
self.assertEqual(sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED, "5", "Header should have 5 SVs to start with")
self.assertEqual(
set(sp3_df.attrs["HEADER"].SV_INFO.index.values),
set(["G01", "G02", "G03", "G04", "G05"]),
"Header SV list should have the 5 SVs expected to start with",
)

# Remove two specific SVs
sp3.remove_svs_from_header(sp3_df, set(["G02", "G04"]))

self.assertEqual(sp3_df.attrs["HEADER"].HEAD.SV_COUNT_STATED, "3", "Header should have 3 SVs after removal")
self.assertEqual(
set(sp3_df.attrs["HEADER"].SV_INFO.index.values),
set(["G01", "G03", "G05"]),
"Header SV list should have the 3 SVs expected",
)


class TestMergeSP3(TestCase):
def setUp(self):
self.setUpPyfakefs()
Expand Down