Repository: mie-lab/trackintel

Issue:
BUG: crs not set for trips or locations
When I generate trips from staypoints and triplegs, the resulting Geodataframe has no crs defined, even though the crs was set for both staypoints and triplegs. 
e.g.:
```
>> print(sp.crs, legs.crs)
EPSG:2056 EPSG:2056
>> sp, legs, trips = ti.preprocessing.triplegs.generate_trips(act_sp, legs, gap_threshold=15, add_geometry=True)
>> print(trips.crs)
None
```
The same happens for generating locations. This is not intended, is it? For further processing of the data, I'd expect that the crs is adopted. 

Comments:

Patch:
diff --git a/trackintel/preprocessing/triplegs.py b/trackintel/preprocessing/triplegs.py
index d87343f..495d4a5 100644
--- a/trackintel/preprocessing/triplegs.py
+++ b/trackintel/preprocessing/triplegs.py
@@ -198,9 +198,10 @@ def generate_trips(staypoints, triplegs, gap_threshold=15, add_geometry=True):
             # from tpls table, get the last point of the last tripleg on the trip
             lambda x: Point(tpls.loc[x[-1], tpls.geometry.name].coords[-1])
         )
-        # convert to GeoDataFrame with MultiPoint column
+        # convert to GeoDataFrame with MultiPoint column and crs (not-None if possible)
         trips["geom"] = [MultiPoint([x, y]) for x, y in zip(trips.origin_geom, trips.destination_geom)]
-        trips = gpd.GeoDataFrame(trips, geometry="geom")
+        crs_trips = sp.crs if sp.crs else tpls.crs
+        trips = gpd.GeoDataFrame(trips, geometry="geom", crs=crs_trips)
         # cleanup
         trips.drop(["origin_geom", "destination_geom"], inplace=True, axis=1)
 
@@ -269,6 +270,14 @@ def _concat_staypoints_triplegs(staypoints, triplegs, add_geometry):
     sp_tpls["is_activity"].fillna(False, inplace=True)
     sp_tpls["sp_tpls_id"] = sp_tpls.index  # store id for later reassignment
     if add_geometry:
+        # Check if crs is set. Warn if None
+        if sp.crs is None:
+            warnings.warn("Staypoint crs is not set. Assuming same as for triplegs.")
+        if tpls.crs is None:
+            warnings.warn("Tripleg crs is not set. Assuming same as for staypoints.")
+        assert (
+            sp.crs == tpls.crs or sp.crs is None or tpls.crs is None
+        ), "CRS of staypoints and triplegs differ. Geometry cannot be joined safely."
         sp_tpls["geom"] = pd.concat([sp.geometry, tpls.geometry])
 
     sp_tpls.sort_values(by=["user_id", "started_at"], inplace=True)


Test patch:
diff --git a/tests/preprocessing/test_staypoints.py b/tests/preprocessing/test_staypoints.py
index e0cdce0..5f50f4a 100644
--- a/tests/preprocessing/test_staypoints.py
+++ b/tests/preprocessing/test_staypoints.py
@@ -245,6 +245,12 @@ class TestGenerate_locations:
         assert loc_dataset_num == 1
         assert loc_user_num == 2
 
+    def test_crs(self, example_staypoints):
+        """Test whether the crs of the output locations is set correctly."""
+        sp = example_staypoints
+        sp, locs = sp.as_staypoints.generate_locations(method="dbscan", epsilon=20, num_samples=1)
+        assert locs.crs == sp.crs
+
     def test_dbscan_min(self):
         """Test with small epsilon parameter."""
         pfs_file = os.path.join("tests", "data", "positionfixes.csv")
diff --git a/tests/preprocessing/test_triplegs.py b/tests/preprocessing/test_triplegs.py
index 534b72b..341eda2 100644
--- a/tests/preprocessing/test_triplegs.py
+++ b/tests/preprocessing/test_triplegs.py
@@ -60,7 +60,7 @@ class TestGenerate_trips:
         """Test if we can generate the example trips based on example data."""
         # load pregenerated trips
         path = os.path.join("tests", "data", "geolife_long", "trips.csv")
-        trips_loaded = ti.read_trips_csv(path, index_col="id", geom_col="geom", crs=None)
+        trips_loaded = ti.read_trips_csv(path, index_col="id", geom_col="geom", crs="EPSG:4326")
 
         # create trips from geolife (based on positionfixes) - with gap_threshold 1e6
         sp, tpls = example_triplegs_higher_gap_threshold
@@ -373,6 +373,35 @@ class TestGenerate_trips:
         with pytest.raises(AttributeError, match=error_msg):
             generate_trips(sp, tpls)
 
+    def test_crs(self, example_triplegs):
+        """Test that the resulting GeoDataFrame has the correct crs or a warning or error is thrown if not set"""
+        sp, tpls = example_triplegs
+        # Case 1: sp crs None --> throw warning and set to tpls crs
+        sp.crs = None
+        with pytest.warns(UserWarning):
+            _, _, trips = generate_trips(sp, tpls)
+            assert trips.crs == tpls.crs
+        # Case 2: Both crs None --> warn and set to None
+        tpls.crs = None
+        with pytest.warns(UserWarning):
+            _, _, trips = generate_trips(sp, tpls)
+            assert trips.crs is None
+        # Case 3: tpls crs is None --> throw warning and set to sp crs
+        sp.crs = "EPSG:4326"
+        with pytest.warns(UserWarning):
+            _, _, trips = generate_trips(sp, tpls)
+            assert trips.crs == "EPSG:4326"
+        # Case 4: Both crs set and correspond
+        tpls.crs = "EPSG:2056"
+        sp.crs = "EPSG:2056"
+        _, _, trips = generate_trips(sp, tpls)
+        assert trips.crs == "EPSG:2056"
+        # Case 5: Both crs set but differ --> throw error
+        sp.crs = "EPSG:4326"
+        error_msg = "CRS of staypoints and triplegs differ. Geometry cannot be joined safely."
+        with pytest.raises(AssertionError, match=error_msg):
+            generate_trips(sp, tpls)
+
 
 def _create_debug_sp_tpls_data(sp, tpls, gap_threshold):
     """Preprocess sp and tpls for "test_generate_trips_*."""