diff --git a/python/tests/test_transform_to_pickle.py b/python/tests/test_transform_to_pickle.py index aa16897..aa5de89 100644 --- a/python/tests/test_transform_to_pickle.py +++ b/python/tests/test_transform_to_pickle.py @@ -4,6 +4,7 @@ import unittest from zoneinfo import ZoneInfo +import pandas as pd import pytz from ..transform_to_pickle import TransformToPickle @@ -78,5 +79,28 @@ def test_it_should_write_a_gzip_compressed_pickle(self): actual = written.read(2) self.assertEqual(b'\x1f\x8b', actual) + def test_it_should_write_trajectories_with_point_locations(self): + # Readers access `trajectory.df.geometry` directly: movingpandas < 0.22 does it in + # `TrajectoryStopDetector`, Apps do it in their own code. A geometry movingpandas >= 0.22 + # left to be built on demand arrives there as None. + # arrange + with tempfile.TemporaryDirectory() as tmp: + output = os.path.join(tmp, 'output_file') + + # act + self.sut.convert( + input_data_file_name='./python/sample/input4/link.csv', + input_meta_file_name='./python/sample/input4/meta.csv', + output_file_name=output + ) + actual = pd.read_pickle(output, compression='gzip') + + # assert + for trajectory in actual.trajectories: + self.assertEqual(0, trajectory.df.geometry.isna().sum(), trajectory.id) + # csv: earliest fix of 'X742..deploy_id.56853924.' at 2013-08-08 06:47:31 + first = actual.trajectories[0].df.geometry.iloc[0] + self.assertEqual((48.761833, 69.128117), (first.x, first.y)) + if __name__ == '__main__': unittest.main() diff --git a/python/transform_to_pickle.py b/python/transform_to_pickle.py index b5ff518..12eeb5c 100644 --- a/python/transform_to_pickle.py +++ b/python/transform_to_pickle.py @@ -1,5 +1,6 @@ import os +import geopandas as gpd import movingpandas as mpd import pandas as pd from dataclasses import dataclass @@ -47,13 +48,19 @@ def adjust_timestamps(self, data, timezone, time_col_name): return data def create_moving_pandas(self, data, projection, track_id_col_name): + # Build the points here, not via movingpandas' `x`/`y`: since 0.22 that path leaves the + # geometry None and builds it on demand, which a reader on movingpandas < 0.22 - or one + # reading `trajectory.df.geometry` directly - never does. Dropping x/y keeps the columns + # movingpandas 0.17.2 handed over. + points = gpd.GeoDataFrame( + data.drop(columns=['coords_x', 'coords_y']), + geometry=gpd.points_from_xy(data['coords_x'], data['coords_y']), + crs=projection + ) move = mpd.TrajectoryCollection( - data, + points, traj_id_col=track_id_col_name, - crs=projection, - t='timestamp_utc', # use our converted timezone column (UTC) - x='coords_x', - y='coords_y' + t='timestamp_utc' # use our converted timezone column (UTC) ) print(move) return move