Repository: mie-lab/trackintel (python)

Issue:
generate_trips changes index name of triplegs
The problem seems to be the join in [line 226](https://github.com/henrymartin1/trackintel/blob/27d80e3320fa8046da56d9ebb4c3e6fd4a798c36/trackintel/preprocessing/triplegs.py#L226). 

Pull request:
BUG: Maintain index dtype for sp.
closes #292 

Minor bug in generating trips, when column contains None the dtype changes.
Added a line to revert that.

Patch:
diff --git a/docs/conf.py b/docs/conf.py
index e53793c..5946bc4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -74,7 +74,7 @@ def setup(app):
 # The full version, including alpha/beta/rc tags
 # release = version
 
-version = "1.1.3"
+version = "1.1.4"
 release = version
 
 
diff --git a/docs/modules/geogr.rst b/docs/modules/geogr.rst
index 023f7bd..5abd6ed 100644
--- a/docs/modules/geogr.rst
+++ b/docs/modules/geogr.rst
@@ -8,7 +8,7 @@ distance functions for points and trajectories
 
 .. autofunction:: trackintel.geogr.distances.meters_to_decimal_degrees
 
-.. autofunction:: trackintel.geogr.distances.check_gdf_crs
+.. autofunction:: trackintel.geogr.distances.check_gdf_planar
 
 .. autofunction:: trackintel.geogr.distances.calculate_haversine_length
 
diff --git a/trackintel/__init__.py b/trackintel/__init__.py
index a6fcd84..9df718b 100644
--- a/trackintel/__init__.py
+++ b/trackintel/__init__.py
@@ -10,4 +10,5 @@ from trackintel.io.file import read_locations_csv
 from trackintel.io.file import read_trips_csv
 
 #
+from trackintel.__version__ import __version__
 from .core import print_version
diff --git a/trackintel/__version__.py b/trackintel/__version__.py
index e7a04b3..3f1e6b5 100644
--- a/trackintel/__version__.py
+++ b/trackintel/__version__.py
@@ -1,3 +1,3 @@
-VERSION = (1, 1, 3)
+VERSION = (1, 1, 4)
 
 __version__ = ".".join(map(str, VERSION))
diff --git a/trackintel/analysis/labelling.py b/trackintel/analysis/labelling.py
index 6ee8516..4f5f08f 100644
--- a/trackintel/analysis/labelling.py
+++ b/trackintel/analysis/labelling.py
@@ -2,7 +2,7 @@ import datetime
 
 import numpy as np
 
-from trackintel.geogr.distances import check_gdf_crs, calculate_haversine_length
+from trackintel.geogr.distances import check_gdf_planar, calculate_haversine_length
 
 
 def create_activity_flag(staypoints, method="time_threshold", time_threshold=15.0, activity_column_name="activity"):
@@ -125,7 +125,7 @@ def _predict_transport_mode_simple_coarse(triplegs_in, categories):
         raise ValueError("the categories must be in increasing order")
 
     triplegs = triplegs_in.copy()
-    if_planer_crs = check_gdf_crs(triplegs)
+    if_planer_crs = check_gdf_planar(triplegs)
     #
     if not if_planer_crs:
         triplegs["distance"] = calculate_haversine_length(triplegs)
diff --git a/trackintel/analysis/modal_split.py b/trackintel/analysis/modal_split.py
index e297e13..a4e186c 100644
--- a/trackintel/analysis/modal_split.py
+++ b/trackintel/analysis/modal_split.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pandas as pd
 
-from trackintel.geogr.distances import check_gdf_crs, calculate_haversine_length
+from trackintel.geogr.distances import check_gdf_planar, calculate_haversine_length
 
 
 def calculate_modal_split(tpls_in, freq=None, metric="count", per_user=False, norm=False):
@@ -48,7 +48,7 @@ def calculate_modal_split(tpls_in, freq=None, metric="count", per_user=False, no
 
     # precalculate distance and duration if required
     if metric == "distance":
-        if_planer_crs = check_gdf_crs(tpls)
+        if_planer_crs = check_gdf_planar(tpls)
         if not if_planer_crs:
             tpls["distance"] = calculate_haversine_length(tpls)
         else:
diff --git a/trackintel/core.py b/trackintel/core.py
index 52f4182..76326e4 100644
--- a/trackintel/core.py
+++ b/trackintel/core.py
@@ -6,7 +6,7 @@ def print_version():
 
     print(
         "This is trackintel v"
-        + str(__version__.__version__)
+        + str(__version__)
         + ". You can find more information "
         + "under https://github.com/mie-lab/trackintel. Thank you for using it!"
     )
diff --git a/trackintel/geogr/distances.py b/trackintel/geogr/distances.py
index a61b3f4..58ee163 100644
--- a/trackintel/geogr/distances.py
+++ b/trackintel/geogr/distances.py
@@ -183,11 +183,11 @@ def meters_to_decimal_degrees(meters, latitude):
     return meters / (111.32 * 1000.0 * cos(latitude * (pi / 180.0)))
 
 
-def check_gdf_crs(gdf, transform=False):
+def check_gdf_planar(gdf, transform=False):
     """
-    Check if GeoDataFrame has CRS or is already in WGS84.
+    Check if a GeoDataFrame has a planar or projected coordinate system.
 
-    Additionally transform a GeoDataFrame into WGS84.
+    Optionally transform a GeoDataFrame into WGS84.
 
     Parameters
     ----------
@@ -208,33 +208,34 @@ def check_gdf_crs(gdf, transform=False):
 
     Examples
     --------
-    >>> from trackintel.geogr.distances import check_gdf_crs
-    >>> check_gdf_crs(triplegs, transform=False)
+    >>> from trackintel.geogr.distances import check_gdf_planar
+    >>> check_gdf_planar(triplegs, transform=False)
     """
-    if_planer = False
-    if gdf.crs is None:
-        # projection is not defined
-        if_planer = False
+    is_planar = False
+    if gdf.crs is None:  # projection is not defined
+        is_planar = False
         if transform:
             gdf.crs = "EPSG:4326"
         else:
-            warnings.warn("Your data is not projected.")
+            warnings.warn("The CRS of your data is not defined.")
 
-    elif gdf.crs == "EPSG:4326":
-        # if projection is defined as WGS84
-        if_planer = False
+    elif gdf.crs == "EPSG:4326":  # if projection is defined as WGS84
+        is_planar = False
+
+    else:  # if projection is defined but not as WGS84
+        if gdf.crs.is_geographic:  # if projection is a geographic crs
+            is_planar = False
+        else:
+            is_planar = True
 
-    else:
-        # if projection is defined but not as WGS84
-        if_planer = True
         if transform:
-            if_planer = False
+            is_planar = False
             gdf = gdf.to_crs("EPSG:4326")
 
     if transform:
-        return if_planer, gdf
+        return is_planar, gdf
     else:
-        return if_planer
+        return is_planar
 
 
 def calculate_haversine_length(gdf):
diff --git a/trackintel/preprocessing/triplegs.py b/trackintel/preprocessing/triplegs.py
index 12dfb90..4c56df4 100644
--- a/trackintel/preprocessing/triplegs.py
+++ b/trackintel/preprocessing/triplegs.py
@@ -217,6 +217,8 @@ def generate_trips(spts, tpls, gap_threshold=15, add_geometry=True):
     trips_with_act["next_trip_id"] = trips_with_act["trip_id"].shift(-1)
     activity_staypoints = trips_with_act[trips_with_act["type"] == "staypoint"].copy()
     activity_staypoints.index = activity_staypoints["spts_tpls_id"]
+    # containing None changes dtype -> revert to original dtype.
+    activity_staypoints.index = activity_staypoints.index.astype(spts.index.dtype)
     spts = spts.join(activity_staypoints[["prev_trip_id", "next_trip_id"]], how="left")
 
     # transform column to binary
diff --git a/trackintel/visualization/locations.py b/trackintel/visualization/locations.py
index de01b62..bcef293 100644
--- a/trackintel/visualization/locations.py
+++ b/trackintel/visualization/locations.py
@@ -5,7 +5,7 @@ import warnings
 from trackintel.geogr.distances import meters_to_decimal_degrees
 from trackintel.visualization.osm import plot_osm_streets
 from trackintel.visualization.util import regular_figure, save_fig
-from trackintel.geogr.distances import check_gdf_crs
+from trackintel.geogr.distances import check_gdf_planar
 
 
 def plot_locations(
@@ -57,7 +57,7 @@ def plot_locations(
         _, ax = regular_figure()
     else:
         ax = axis
-    _, locations = check_gdf_crs(locations, transform=True)
+    _, locations = check_gdf_planar(locations, transform=True)
 
     if staypoints is not None:
         staypoints.as_staypoints.plot(radius=staypoints_radius, positionfixes=positionfixes, plot_osm=plot_osm, axis=ax)
diff --git a/trackintel/visualization/positionfixes.py b/trackintel/visualization/positionfixes.py
index 724933e..7d60842 100644
--- a/trackintel/visualization/positionfixes.py
+++ b/trackintel/visualization/positionfixes.py
@@ -2,7 +2,7 @@ import matplotlib.pyplot as plt
 import warnings
 from trackintel.visualization.osm import plot_osm_streets
 from trackintel.visualization.util import regular_figure, save_fig
-from trackintel.geogr.distances import check_gdf_crs
+from trackintel.geogr.distances import check_gdf_planar
 
 
 def plot_positionfixes(positionfixes, out_filename=None, plot_osm=False, axis=None):
@@ -35,7 +35,7 @@ def plot_positionfixes(positionfixes, out_filename=None, plot_osm=False, axis=No
         _, ax = regular_figure()
     else:
         ax = axis
-    _, positionfixes = check_gdf_crs(positionfixes, transform=True)
+    _, positionfixes = check_gdf_planar(positionfixes, transform=True)
 
     if plot_osm:
         west = positionfixes.geometry.x.min()
diff --git a/trackintel/visualization/staypoints.py b/trackintel/visualization/staypoints.py
index 68e14d8..f80e7af 100644
--- a/trackintel/visualization/staypoints.py
+++ b/trackintel/visualization/staypoints.py
@@ -5,7 +5,7 @@ import warnings
 from trackintel.geogr.distances import meters_to_decimal_degrees
 from trackintel.visualization.osm import plot_osm_streets
 from trackintel.visualization.util import regular_figure, save_fig
-from trackintel.geogr.distances import check_gdf_crs
+from trackintel.geogr.distances import check_gdf_planar
 
 
 def plot_staypoints(staypoints, out_filename=None, radius=100, positionfixes=None, plot_osm=False, axis=None):
@@ -45,7 +45,7 @@ def plot_staypoints(staypoints, out_filename=None, radius=100, positionfixes=Non
     else:
         ax = axis
     name_geocol = staypoints.geometry.name
-    _, staypoints = check_gdf_crs(staypoints, transform=True)
+    _, staypoints = check_gdf_planar(staypoints, transform=True)
 
     if positionfixes is not None:
         positionfixes.as_positionfixes.plot(plot_osm=plot_osm, axis=ax)
diff --git a/trackintel/visualization/triplegs.py b/trackintel/visualization/triplegs.py
index ec0f4fa..e03a938 100644
--- a/trackintel/visualization/triplegs.py
+++ b/trackintel/visualization/triplegs.py
@@ -4,7 +4,7 @@ import warnings
 
 from trackintel.visualization.osm import plot_osm_streets
 from trackintel.visualization.util import regular_figure, save_fig
-from trackintel.geogr.distances import check_gdf_crs
+from trackintel.geogr.distances import check_gdf_planar
 
 
 def plot_triplegs(
@@ -48,7 +48,7 @@ def plot_triplegs(
         _, ax = regular_figure()
     else:
         ax = axis
-    _, triplegs = check_gdf_crs(triplegs, transform=True)
+    _, triplegs = check_gdf_planar(triplegs, transform=True)
 
     if staypoints is not None:
         staypoints.as_staypoints.plot(radius=staypoints_radius, positionfixes=positionfixes, plot_osm=plot_osm, axis=ax)


Test patch:
diff --git a/tests/analysis/test_label.py b/tests/analysis/test_label.py
index 01704eb..f974e98 100644
--- a/tests/analysis/test_label.py
+++ b/tests/analysis/test_label.py
@@ -71,7 +71,7 @@ class TestPredict_transport_mode:
 
         with pytest.warns(
             UserWarning,
-            match="Your data is not projected.",
+            match="The CRS of your data is not defined.",
         ):
             tpls_transport_mode = tpls.as_triplegs.predict_transport_mode(method="simple-coarse")
 
@@ -101,15 +101,6 @@ class TestPredict_transport_mode:
         assert tpls_transport_mode_3.iloc[1]["mode"] == "motorized_mobility"
         assert tpls_transport_mode_3.iloc[2]["mode"] == "fast_mobility"
 
-    def test_simple_coarse_identification_geographic(self):
-        """Asserts that a warning is thrown if data in non-WGS geographic coordinate systems."""
-        tpls_file = os.path.join("tests", "data", "triplegs_transport_mode_identification.csv")
-        tpls = ti.read_triplegs_csv(tpls_file, sep=";", index_col="id")
-        tpls_2 = tpls.set_crs(epsg=4326)
-        tpls_4 = tpls_2.to_crs(epsg=4269)
-        with pytest.warns(UserWarning):
-            tpls_4.as_triplegs.predict_transport_mode(method="simple-coarse")
-
     def test_check_categories(self):
         """Asserts the correct identification of valid category dictionaries."""
         tpls_file = os.path.join("tests", "data", "triplegs_transport_mode_identification.csv")
diff --git a/tests/geogr/test_distances.py b/tests/geogr/test_distances.py
index 0c67329..7e8351b 100644
--- a/tests/geogr/test_distances.py
+++ b/tests/geogr/test_distances.py
@@ -1,17 +1,22 @@
 import os
-from math import radians
+import datetime
 
 import geopandas as gpd
+import pandas as pd
 import numpy as np
 import pytest
+
+from math import radians
 from shapely import wkt
 from shapely.geometry import LineString, MultiLineString
 from sklearn.metrics import pairwise_distances
 from geopandas.testing import assert_geodataframe_equal
+from shapely.geometry import Point
+
 
 import trackintel as ti
 from trackintel.geogr.distances import (
-    check_gdf_crs,
+    check_gdf_planar,
     meters_to_decimal_degrees,
     calculate_distance_matrix,
     calculate_haversine_length,
@@ -173,15 +178,15 @@ class TestCalculate_distance_matrix:
             calculate_distance_matrix(X=gdf, dist_metric="dtw", n_jobs=1)
 
 
-class TestCheck_gdf_crs:
-    """Tests for check_gdf_crs() method."""
+class TestCheck_gdf_planar:
+    """Tests for check_gdf_planar() method."""
 
     def test_transformation(self):
         """Check if data gets transformed."""
         file = os.path.join("tests", "data", "positionfixes.csv")
         pfs = ti.read_positionfixes_csv(file, sep=";", crs="EPSG:4326", index_col=None)
         pfs_2056 = pfs.to_crs("EPSG:2056")
-        _, pfs_4326 = check_gdf_crs(pfs_2056, transform=True)
+        _, pfs_4326 = check_gdf_planar(pfs_2056, transform=True)
         assert_geodataframe_equal(pfs, pfs_4326, check_less_precise=True)
 
     def test_crs_warning(self):
@@ -189,7 +194,28 @@ class TestCheck_gdf_crs:
         file = os.path.join("tests", "data", "positionfixes.csv")
         pfs = ti.read_positionfixes_csv(file, sep=";", crs=None, index_col=None)
         with pytest.warns(UserWarning):
-            check_gdf_crs(pfs)
+            check_gdf_planar(pfs)
+
+    def test_if_planer(self):
+        """Check if planer crs is successfully checked."""
+        p1 = Point(8.5067847, 47.4)
+        t1 = pd.Timestamp("1971-01-01 00:00:00", tz="utc")
+
+        list_dict = [
+            {"user_id": 0, "started_at": t1, "finished_at": t1, "geom": p1},
+        ]
+        # a geographic crs different than wgs1984
+        sp = gpd.GeoDataFrame(data=list_dict, geometry="geom", crs="EPSG:4610")
+        assert check_gdf_planar(sp) == False
+
+        # wgs1984
+        sp = gpd.GeoDataFrame(data=list_dict, geometry="geom", crs="EPSG:4326")
+        assert check_gdf_planar(sp) == False
+
+        # wgs1984 to swiss planer
+        sp = gpd.GeoDataFrame(data=list_dict, geometry="geom", crs="EPSG:4326")
+        sp = sp.to_crs("EPSG:2056")
+        assert check_gdf_planar(sp) == True
 
 
 class TestMetersToDecimalDegrees:
diff --git a/tests/preprocessing/test_triplegs.py b/tests/preprocessing/test_triplegs.py
index b160d19..da6db49 100644
--- a/tests/preprocessing/test_triplegs.py
+++ b/tests/preprocessing/test_triplegs.py
@@ -6,7 +6,7 @@ import numpy as np
 import pandas as pd
 import pytest
 from geopandas.testing import assert_geodataframe_equal
-from pandas.testing import assert_frame_equal, assert_series_equal
+from pandas.testing import assert_frame_equal, assert_series_equal, assert_index_equal
 from shapely import geometry
 from shapely.geometry import LineString, Point
 
@@ -336,6 +336,31 @@ class TestGenerate_trips:
         assert (tpls_["trip_id"] == 0).all()
         assert len(trips) == 1
 
+    def test_spts_tpls_index(self):
+        """Test if staypoint and tripleg index are identical before and after generating trips."""
+        start = pd.Timestamp("2021-07-11 8:00:00")
+        h = pd.to_timedelta("1h")
+        spts_tpls = [
+            {"activity": True, "type": "staypoint"},
+            {"activity": False, "type": "tripleg"},
+            {"activity": False, "type": "staypoint"},
+            {"activity": False, "type": "tripleg"},
+            {"activity": True, "type": "staypoint"},
+        ]
+        for n, d in enumerate(spts_tpls):
+            d["user_id"] = 0
+            d["started_at"] = start + n * h
+            d["finished_at"] = d["started_at"] + h
+
+        spts_tpls = pd.DataFrame(spts_tpls)
+        spts = spts_tpls[spts_tpls["type"] == "staypoint"]
+        tpls = spts_tpls[spts_tpls["type"] != "staypoint"]
+        tpls.index.name = "something_long_and_obscure"
+        spts.index.name = "even_obscurer"
+        spts_, tpls_, _ = generate_trips(spts, tpls, add_geometry=False)
+        assert_index_equal(tpls.index, tpls_.index)
+        assert_index_equal(spts.index, spts_.index)
+
     def test_loop_linestring_case(self, example_triplegs):
         """Test corner case where a tripleg starts and ends at the same point"""
         # input data: preprocessed stps and tpls