Repository: mie-lab/trackintel

Issue:
Why is imageio in environment.yml?
I don't think that this is currently in use. Is it a dependency for something?

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mie-lab/trackintel/pull/246?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) Report
> Merging [#246](https://codecov.io/gh/mie-lab/trackintel/pull/246?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) (8c7b77a) into [master](https://codecov.io/gh/mie-lab/trackintel/commit/deb78cafe8d4f8e7ea74ef1d5d71b7b0e185080a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) (deb78ca) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/mie-lab/trackintel/pull/246/graphs/tree.svg?width=650&height=150&src=pr&token=ITDZEEE1MO&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab)](https://codecov.io/gh/mie-lab/trackintel/pull/246?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab)

```diff
@@           Coverage Diff           @@
##           master     #246   +/-   ##
=======================================
  Coverage   94.77%   94.77%           
=======================================
  Files          37       37           
  Lines        1550     1550           
  Branches      260      260           
=======================================
  Hits         1469     1469           
  Misses         42       42           
  Partials       39       39           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/mie-lab/trackintel/pull/246?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mie-lab/trackintel/pull/246?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab). Last update [deb78ca...8c7b77a](https://codecov.io/gh/mie-lab/trackintel/pull/246?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab).


Patch:
diff --git a/docs/environment.yml b/docs/environment.yml
index 48537bc..4750c75 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -15,7 +15,6 @@ dependencies:
 - geoalchemy2
 - osmnx
 - psycopg2
-- imageio
 - sphinx
 - sphinx_rtd_theme
 - tqdm
diff --git a/environment.yml b/environment.yml
index 1739723..775c6bd 100644
--- a/environment.yml
+++ b/environment.yml
@@ -15,6 +15,5 @@ dependencies:
 - geoalchemy2 
 - osmnx 
 - psycopg2
-- imageio
 - tqdm
 - similaritymeasures
diff --git a/requirements.txt b/requirements.txt
index 9dff552..3297999 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,5 @@ pint
 geoalchemy2 
 osmnx 
 psycopg2
-imageio
 tqdm
 similaritymeasures
\ No newline at end of file
diff --git a/trackintel/analysis/tracking_quality.py b/trackintel/analysis/tracking_quality.py
index 67085b8..62c2607 100644
--- a/trackintel/analysis/tracking_quality.py
+++ b/trackintel/analysis/tracking_quality.py
@@ -227,26 +227,29 @@ def _split_overlaps(source, granularity="day"):
     # Iteratively split one day/hour from multi day/hour entries until no entry spans over multiple days/hours
     while change_flag.sum() > 0:
 
-        # calculate new finished_at timestamp (1sec before midnight)
+        # calculate new finished_at timestamp (00:00 midnight)
         finished_at_temp = df.loc[change_flag, "finished_at"].copy()
         if granularity == "day":
             df.loc[change_flag, "finished_at"] = df.loc[change_flag, "started_at"].apply(
-                lambda x: x.replace(hour=23, minute=59, second=59)
+                lambda x: x.replace(hour=23, minute=59, second=59) + datetime.timedelta(seconds=1)
             )
         elif granularity == "hour":
             df.loc[change_flag, "finished_at"] = df.loc[change_flag, "started_at"].apply(
-                lambda x: x.replace(minute=59, second=59)
+                lambda x: x.replace(minute=59, second=59) + datetime.timedelta(seconds=1)
             )
 
         # create new entries with remaining timestamp
         new_df = df.loc[change_flag].copy()
-        new_df.loc[change_flag, "started_at"] = df.loc[change_flag, "finished_at"] + datetime.timedelta(seconds=1)
+        new_df.loc[change_flag, "started_at"] = df.loc[change_flag, "finished_at"]
         new_df.loc[change_flag, "finished_at"] = finished_at_temp
 
         df = df.append(new_df, ignore_index=True, sort=True)
 
         change_flag = __get_split_index(df, granularity=granularity)
 
+    if "duration" in df.columns:
+        df["duration"] = df["finished_at"] - df["started_at"]
+
     return df
 
 
@@ -268,9 +271,9 @@ def __get_split_index(df, granularity="day"):
     change_flag: pd.Series
         Boolean index indicating which records needs to be splitted
     """
-    change_flag = df["started_at"].dt.date != df["finished_at"].dt.date
+    change_flag = df["started_at"].dt.date != (df["finished_at"] - pd.to_timedelta("1s")).dt.date
     if granularity == "hour":
-        hour_flag = df["started_at"].dt.hour != df["finished_at"].dt.hour
+        hour_flag = df["started_at"].dt.hour != (df["finished_at"] - pd.to_timedelta("1s")).dt.hour
         # union of day and hour change flag
         change_flag = change_flag | hour_flag
 


Test patch:
diff --git a/ci/envs/37-latest-conda-forge.yaml b/ci/envs/37-latest-conda-forge.yaml
index 90e4c16..66f5129 100644
--- a/ci/envs/37-latest-conda-forge.yaml
+++ b/ci/envs/37-latest-conda-forge.yaml
@@ -15,7 +15,6 @@ dependencies:
 - geoalchemy2 
 - osmnx 
 - psycopg2
-- imageio
 - pytest
 - pytest-cov
 - pytest-xdist
diff --git a/ci/envs/38-latest-conda-forge.yaml b/ci/envs/38-latest-conda-forge.yaml
index 8ab82f2..81dddb0 100644
--- a/ci/envs/38-latest-conda-forge.yaml
+++ b/ci/envs/38-latest-conda-forge.yaml
@@ -15,7 +15,6 @@ dependencies:
 - geoalchemy2 
 - osmnx 
 - psycopg2
-- imageio
 - pytest
 - pytest-cov
 - pytest-xdist
diff --git a/ci/envs/39-latest-conda-forge.yaml b/ci/envs/39-latest-conda-forge.yaml
index a9ae0da..da5a726 100644
--- a/ci/envs/39-latest-conda-forge.yaml
+++ b/ci/envs/39-latest-conda-forge.yaml
@@ -15,7 +15,6 @@ dependencies:
 - geoalchemy2 
 - osmnx 
 - psycopg2
-- imageio
 - pytest
 - pytest-cov
 - pytest-xdist
diff --git a/tests/analysis/test_tracking_quality.py b/tests/analysis/test_tracking_quality.py
index 94ecdbf..c3e5574 100644
--- a/tests/analysis/test_tracking_quality.py
+++ b/tests/analysis/test_tracking_quality.py
@@ -18,8 +18,20 @@ def testdata_stps_tpls_geolife_long():
     return stps_tpls
 
 
+@pytest.fixture
+def testdata_all_geolife_long():
+    """Generate stps, tpls and trips of the original pfs for subsequent testing."""
+    pfs, _ = ti.io.dataset_reader.read_geolife(os.path.join("tests", "data", "geolife_long"))
+    pfs, stps = pfs.as_positionfixes.generate_staypoints(method="sliding", dist_threshold=25, time_threshold=5)
+    stps = stps.as_staypoints.create_activity_flag(time_threshold=15)
+    pfs, tpls = pfs.as_positionfixes.generate_triplegs(stps, method="between_staypoints")
+    stps, tpls, trips = ti.preprocessing.triplegs.generate_trips(stps, tpls, gap_threshold=15)
+
+    return stps, tpls, trips
+
+
 class TestTemporal_tracking_quality:
-    """Tests for the temporal_tracking_quality() function and its subfunctions."""
+    """Tests for the temporal_tracking_quality() function."""
 
     def test_tracking_quality_all(self, testdata_stps_tpls_geolife_long):
         """Test if the calculated total tracking quality is correct."""
@@ -124,7 +136,7 @@ class TestTemporal_tracking_quality:
         quality = ti.analysis.tracking_quality.temporal_tracking_quality(stps_tpls, granularity="hour")
 
         assert quality_manual == quality.loc[(quality["user_id"] == 0) & (quality["hour"] == 2), "quality"].values[0]
-        assert (quality["quality"] < 1).all()
+        assert (quality["quality"] <= 1).all()
 
     def test_tracking_quality_error(self, testdata_stps_tpls_geolife_long):
         """Test if the an error is raised when passing unknown 'granularity' to temporal_tracking_quality()."""
@@ -161,6 +173,37 @@ class TestTemporal_tracking_quality:
         with pytest.raises(AttributeError):
             ti.analysis.tracking_quality._get_tracking_quality_user(user_0, granularity="random")
 
+    def test_staypoints_accessors(self, testdata_all_geolife_long):
+        """Test tracking_quality calculation from staypoints accessor."""
+        stps, _, _ = testdata_all_geolife_long
+
+        # for staypoints
+        stps_quality_accessor = stps.as_staypoints.temporal_tracking_quality()
+        stps_quality_method = ti.analysis.tracking_quality.temporal_tracking_quality(stps)
+        pd.testing.assert_frame_equal(stps_quality_accessor, stps_quality_method)
+
+    def test_triplegs_accessors(self, testdata_all_geolife_long):
+        """Test tracking_quality calculation from triplegs accessor."""
+        _, tpls, _ = testdata_all_geolife_long
+
+        # for triplegs
+        tpls_quality_accessor = tpls.as_triplegs.temporal_tracking_quality()
+        tpls_quality_method = ti.analysis.tracking_quality.temporal_tracking_quality(tpls)
+        pd.testing.assert_frame_equal(tpls_quality_accessor, tpls_quality_method)
+
+    def test_trips_accessors(self, testdata_all_geolife_long):
+        """Test tracking_quality calculation from trips accessor."""
+        _, _, trips = testdata_all_geolife_long
+
+        # for trips
+        trips_quality_accessor = trips.as_trips.temporal_tracking_quality()
+        trips_quality_method = ti.analysis.tracking_quality.temporal_tracking_quality(trips)
+        pd.testing.assert_frame_equal(trips_quality_accessor, trips_quality_method)
+
+
+class TestSplit_overlaps:
+    """Tests for the _split_overlaps() function."""
+
     def test_split_overlaps_days(self, testdata_stps_tpls_geolife_long):
         """Test if _split_overlaps() function can split records that span several days."""
         stps_tpls = testdata_stps_tpls_geolife_long
@@ -174,7 +217,7 @@ class TestTemporal_tracking_quality:
         splitted = ti.analysis.tracking_quality._split_overlaps(stps_tpls, granularity="day")
 
         # no record spans several days after the split
-        multi_day_records = splitted["finished_at"].dt.day - splitted["started_at"].dt.day
+        multi_day_records = (splitted["finished_at"] - pd.to_timedelta("1s")).dt.day - splitted["started_at"].dt.day
         assert (multi_day_records == 0).all()
 
     def test_split_overlaps_hours(self, testdata_stps_tpls_geolife_long):
@@ -190,7 +233,7 @@ class TestTemporal_tracking_quality:
         splitted = ti.analysis.tracking_quality._split_overlaps(stps_tpls, granularity="hour")
 
         # no record spans several hours after the split
-        hour_diff = splitted["finished_at"].dt.hour - splitted["started_at"].dt.hour
+        hour_diff = (splitted["finished_at"] - pd.to_timedelta("1s")).dt.hour - splitted["started_at"].dt.hour
         assert (hour_diff == 0).all()
 
     def test_split_overlaps_hours_case2(self, testdata_stps_tpls_geolife_long):
@@ -203,10 +246,10 @@ class TestTemporal_tracking_quality:
         head2["finished_at"] = head2.apply(lambda x: x["started_at"].replace(day=x["started_at"].day + 1), axis=1)
 
         # the records have the same hour
-        hour_diff = head2["finished_at"].dt.hour - head2["started_at"].dt.hour
+        hour_diff = (head2["finished_at"] - pd.to_timedelta("1s")).dt.hour - head2["started_at"].dt.hour
         assert (hour_diff == 0).all()
         # but have different days
-        day_diff = head2["finished_at"].dt.day - head2["started_at"].dt.day
+        day_diff = (head2["finished_at"] - pd.to_timedelta("1s")).dt.day - head2["started_at"].dt.day
         assert (day_diff > 0).all()
 
         # split the records according to hour
@@ -214,28 +257,22 @@ class TestTemporal_tracking_quality:
         splitted = ti.analysis.tracking_quality._split_overlaps(head2, granularity="hour")
 
         # no record has different days after the split
-        day_diff = splitted["finished_at"].dt.day - splitted["started_at"].dt.day
+        day_diff = (splitted["finished_at"] - pd.to_timedelta("1s")).dt.day - splitted["started_at"].dt.day
         assert (day_diff == 0).all()
 
-    def test_accessors(self):
-        """The result obtained from model accessors should be the same as calling the function."""
-        pfs, _ = ti.io.dataset_reader.read_geolife(os.path.join("tests", "data", "geolife_long"))
-        pfs, stps = pfs.as_positionfixes.generate_staypoints(method="sliding", dist_threshold=25, time_threshold=5)
-        stps = stps.as_staypoints.create_activity_flag(time_threshold=15)
-        pfs, tpls = pfs.as_positionfixes.generate_triplegs(stps, method="between_staypoints")
-        stps, tpls, trips = ti.preprocessing.triplegs.generate_trips(stps, tpls, gap_threshold=15)
+    def test_split_overlaps_duration(self, testdata_stps_tpls_geolife_long):
+        """Test if the column 'duration' gets updated after using the _split_overlaps() function."""
+        stps_tpls = testdata_stps_tpls_geolife_long
+        # initiate the duration column
+        stps_tpls["duration"] = stps_tpls["finished_at"] - stps_tpls["started_at"]
+        stps_tpls.reset_index(inplace=True)
 
-        # for staypoints
-        stps_quality_accessor = stps.as_staypoints.temporal_tracking_quality()
-        stps_quality_method = ti.analysis.tracking_quality.temporal_tracking_quality(stps)
-        pd.testing.assert_frame_equal(stps_quality_accessor, stps_quality_method)
+        # split the records according to day
+        splitted_day = ti.analysis.tracking_quality._split_overlaps(stps_tpls, granularity="day")
 
-        # for triplegs
-        tpls_quality_accessor = tpls.as_triplegs.temporal_tracking_quality()
-        tpls_quality_method = ti.analysis.tracking_quality.temporal_tracking_quality(tpls)
-        pd.testing.assert_frame_equal(tpls_quality_accessor, tpls_quality_method)
+        # split the records according to hour
+        splitted_hour = ti.analysis.tracking_quality._split_overlaps(stps_tpls, granularity="hour")
 
-        # for trips
-        trips_quality_accessor = trips.as_trips.temporal_tracking_quality()
-        trips_quality_method = ti.analysis.tracking_quality.temporal_tracking_quality(trips)
-        pd.testing.assert_frame_equal(trips_quality_accessor, trips_quality_method)
+        # test "duration" is recalculated after the split
+        assert splitted_day["duration"].sum() == stps_tpls["duration"].sum()
+        assert splitted_hour["duration"].sum() == stps_tpls["duration"].sum()