Repository: mie-lab/trackintel

Issue:
pygeos for trackintel
pygeos is a package that vectorizes the geometry in GeoPandas using Cython. It is maintained basically by the same people that maintain geopandas. As such they've implemented a lot of functions in geopandas to run faster if pygeos is installed. That would give us a free speedup and further allow us to use vectorized functions on geometryArrays similar to ufunc on numpy arrays. 

Further informations:
- https://jorisvandenbossche.github.io/blog/2017/09/19/geopandas-cython/
- https://github.com/pygeos/pygeos

One use case would be the generation of points out of longitude and latitude. Instead of using the relatively slow `.apply(Points)` we can use directly on arrays `pygeos.points(x, y)`. 
This way we get the following times on my laptop.

Preparation
````python
import numpy as np
import pandas as pd
from shapely.geometry import Point

a = np.arange(100_000)
b = np.arange(100_000, 200_000)
df = pd.DataFrame({"a": a, "b": b})
````

`pygeos`
````python
%timeit pygeos.points(df["a"], df["b"])
57.4 ms ± 4.71 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
````

Our way so far
````python
%%timeit
df["geom"] = list(zip(df["a"], df["b"]))
df["geom"] = df["geom"].apply(Point)
1.08 s ± 31 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
````


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mie-lab/trackintel/pull/355?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) Report
> Merging [#355](https://codecov.io/gh/mie-lab/trackintel/pull/355?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) (dea1dc4) into [master](https://codecov.io/gh/mie-lab/trackintel/commit/6fc03be9c7fda4d6e99313c3863e0148cd32d7ec?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) (6fc03be) will **decrease** coverage by `2.25%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/mie-lab/trackintel/pull/355/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/355?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     #355      +/-   ##
==========================================
- Coverage   96.37%   94.12%   -2.26%     
==========================================
  Files          39       39              
  Lines        1905     1905              
  Branches      311      311              
==========================================
- Hits         1836     1793      -43     
- Misses         31       75      +44     
+ Partials       38       37       -1     
```


| [Impacted Files](https://codecov.io/gh/mie-lab/trackintel/pull/355?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) | Coverage Δ | |
|---|---|---|
| [trackintel/io/postgis.py](https://codecov.io/gh/mie-lab/trackintel/pull/355/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9pby9wb3N0Z2lzLnB5) | `49.36% <0.00%> (-48.11%)` | :arrow_down: |
| [trackintel/model/locations.py](https://codecov.io/gh/mie-lab/trackintel/pull/355/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC9sb2NhdGlvbnMucHk=) | `96.87% <0.00%> (-3.13%)` | :arrow_down: |
| [trackintel/model/trips.py](https://codecov.io/gh/mie-lab/trackintel/pull/355/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC90cmlwcy5weQ==) | `97.22% <0.00%> (-2.78%)` | :arrow_down: |
| [trackintel/model/positionfixes.py](https://codecov.io/gh/mie-lab/trackintel/pull/355/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC9wb3NpdGlvbmZpeGVzLnB5) | `97.82% <0.00%> (-2.18%)` | :arrow_down: |
| [trackintel/model/staypoints.py](https://codecov.io/gh/mie-lab/trackintel/pull/355/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC9zdGF5cG9pbnRzLnB5) | `98.14% <0.00%> (-1.86%)` | :arrow_down: |
| [trackintel/model/triplegs.py](https://codecov.io/gh/mie-lab/trackintel/pull/355/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC90cmlwbGVncy5weQ==) | `98.27% <0.00%> (-1.73%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mie-lab/trackintel/pull/355?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/355?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab). Last update [6fc03be...dea1dc4](https://codecov.io/gh/mie-lab/trackintel/pull/355?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/README.md b/README.md
index b1f8b60..25d3f4b 100644
--- a/README.md
+++ b/README.md
@@ -116,6 +116,7 @@ ti.print_version()
 * tqdm
 * OSMnx
 * similaritymeasures
+* pygeos
 
 ## Development
 You can find the development roadmap under `ROADMAP.md` and further development guidelines under `CONTRIBUTING.md`.
diff --git a/docs/conf.py b/docs/conf.py
index 5946bc4..8ec6909 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -50,6 +50,7 @@ autodoc_mock_imports = [
     "geoalchemy2",
     "tqdm",
     "similaritymeasures",
+    "pygeos",
 ]
 
 
diff --git a/docs/environment.yml b/docs/environment.yml
index 1392606..9957364 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -19,3 +19,4 @@ dependencies:
   - sphinx_rtd_theme
   - tqdm
   - similaritymeasures
+  - pygeos
diff --git a/docs/modules/io.rst b/docs/modules/io.rst
index 1483fd6..09083d7 100644
--- a/docs/modules/io.rst
+++ b/docs/modules/io.rst
@@ -45,6 +45,8 @@ GeoDataFrame Import
 
 .. autofunction:: trackintel.io.from_geopandas.read_trips_gpd
 
+.. autofunction:: trackintel.io.from_geopandas.read_tours_gpd
+
 
 PostGIS Import
 ==============
diff --git a/environment.yml b/environment.yml
index 775c6bd..e13e8f7 100644
--- a/environment.yml
+++ b/environment.yml
@@ -17,3 +17,4 @@ dependencies:
 - psycopg2
 - tqdm
 - similaritymeasures
+- pygeos
diff --git a/requirements.txt b/requirements.txt
index 3297999..fe00f6d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,4 +9,5 @@ geoalchemy2
 osmnx 
 psycopg2
 tqdm
-similaritymeasures
\ No newline at end of file
+similaritymeasures
+pygeos
\ No newline at end of file
diff --git a/setup.py b/setup.py
index bc20126..98a920f 100644
--- a/setup.py
+++ b/setup.py
@@ -33,6 +33,7 @@ REQUIRED = [
     "scikit-learn",
     "tqdm",
     "similaritymeasures",
+    "pygeos",
 ]
 
 install_requires = [
@@ -48,6 +49,7 @@ install_requires = [
     "tqdm",
     "geopandas",
     "similaritymeasures",
+    "pygeos",
 ]
 
 # What packages are optional?
diff --git a/trackintel/io/file.py b/trackintel/io/file.py
index 879b322..6443763 100644
--- a/trackintel/io/file.py
+++ b/trackintel/io/file.py
@@ -141,7 +141,7 @@ def write_positionfixes_csv(positionfixes, filename, *args, **kwargs):
     df.to_csv(filename, index=True, *args, **kwargs)
 
 
-def read_triplegs_csv(*args, columns=None, tz=None, index_col=object(), crs=None, **kwargs):
+def read_triplegs_csv(*args, columns=None, tz=None, index_col=object(), geom_col="geom", crs=None, **kwargs):
     """
     Read triplegs from csv file.
 
@@ -163,9 +163,12 @@ def read_triplegs_csv(*args, columns=None, tz=None, index_col=object(), crs=None
         pytz compatible timezone string. If None UTC is assumed.
 
     index_col : str, optional
-        column name to be used as index. If None the default index is assumed
+        Column name to be used as index. If None the default index is assumed
         as unique identifier.
 
+    geom_col : str, default "geom"
+        Name of the column containing the geometry as WKT.
+
     crs : pyproj.crs or str, optional
         Set coordinate reference system. The value can be anything accepted
         by pyproj.CRS.from_user_input(), such as an authority string
@@ -203,7 +206,7 @@ def read_triplegs_csv(*args, columns=None, tz=None, index_col=object(), crs=None
     df = df.rename(columns=columns)
 
     # construct geom column
-    df["geom"] = df["geom"].apply(wkt.loads)
+    df[geom_col] = df[geom_col].apply(wkt.loads)
 
     # transform to datatime
     df["started_at"] = pd.to_datetime(df["started_at"])
@@ -254,7 +257,7 @@ def write_triplegs_csv(triplegs, filename, *args, **kwargs):
     df.to_csv(filename, index=True, *args, **kwargs)
 
 
-def read_staypoints_csv(*args, columns=None, tz=None, index_col=object(), crs=None, **kwargs):
+def read_staypoints_csv(*args, columns=None, tz=None, index_col=object(), geom_col="geom", crs=None, **kwargs):
     """
     Read staypoints from csv file.
 
@@ -280,6 +283,9 @@ def read_staypoints_csv(*args, columns=None, tz=None, index_col=object(), crs=No
         column name to be used as index. If None the default index is assumed
         as unique identifier.
 
+    geom_col : str, default "geom"
+        Name of the column containing the geometry as WKT.
+
     crs : pyproj.crs or str, optional
         Set coordinate reference system. The value can be anything accepted
         by pyproj.CRS.from_user_input(), such as an authority string
@@ -317,7 +323,7 @@ def read_staypoints_csv(*args, columns=None, tz=None, index_col=object(), crs=No
     df = df.rename(columns=columns)
 
     # construct geom column
-    df["geom"] = df["geom"].apply(wkt.loads)
+    df[geom_col] = df[geom_col].apply(wkt.loads)
 
     # transform to datatime
     df["started_at"] = pd.to_datetime(df["started_at"])
@@ -368,7 +374,7 @@ def write_staypoints_csv(staypoints, filename, *args, **kwargs):
     df.to_csv(filename, index=True, *args, **kwargs)
 
 
-def read_locations_csv(*args, columns=None, index_col=object(), crs=None, **kwargs):
+def read_locations_csv(*args, columns=None, index_col=object(), geom_col="geom", crs=None, **kwargs):
     """
     Read locations from csv file.
 
@@ -472,7 +478,7 @@ def write_locations_csv(locations, filename, *args, **kwargs):
     df.to_csv(filename, index=True, *args, **kwargs)
 
 
-def read_trips_csv(*args, columns=None, tz=None, index_col=object(), **kwargs):
+def read_trips_csv(*args, columns=None, tz=None, index_col=object(), geom_col=None, crs=None, **kwargs):
     """
     Read trips from csv file.
 
@@ -498,6 +504,15 @@ def read_trips_csv(*args, columns=None, tz=None, index_col=object(), **kwargs):
         column name to be used as index. If None the default index is assumed
         as unique identifier.
 
+    geom_col : str, default None
+        Name of the column containing the geometry as WKT.
+        If None no geometry gets added.
+
+    crs : pyproj.crs or str, optional
+        Set coordinate reference system. The value can be anything accepted
+        by pyproj.CRS.from_user_input(), such as an authority string
+        (eg “EPSG:4326”) or a WKT string. Ignored if geom_col is None.
+
     kwargs
         Additional keyword arguments passed to pd.read_csv().
 
@@ -508,7 +523,7 @@ def read_trips_csv(*args, columns=None, tz=None, index_col=object(), **kwargs):
 
     Notes
     -----
-    No geometry is required for trackintel trips.
+    Geometry is not mandatory for trackintel trips.
 
     Examples
     --------
@@ -546,9 +561,9 @@ def read_trips_csv(*args, columns=None, tz=None, index_col=object(), **kwargs):
             trips[col] = _localize_timestamp(dt_series=trips[col], pytz_tzinfo=tz, col_name=col)
 
     # convert to geodataframe
-    if "geom" in trips.columns:
-        trips["geom"] = trips["geom"].apply(wkt.loads)
-        trips = gpd.GeoDataFrame(trips, geometry="geom")
+    if geom_col is not None:
+        trips[geom_col] = trips[geom_col].apply(wkt.loads)
+        trips = gpd.GeoDataFrame(trips, geometry=geom_col, crs=crs)
 
     # assert validity of trips
     trips.as_trips
diff --git a/trackintel/io/from_geopandas.py b/trackintel/io/from_geopandas.py
index 2d0bf34..5e101c1 100644
--- a/trackintel/io/from_geopandas.py
+++ b/trackintel/io/from_geopandas.py
@@ -251,14 +251,7 @@ def read_trips_gpd(
     if mapper is not None:
         columns.update(mapper)
 
-    if geom_col is not None:  # handle (Geo)DataFrame modes.
-        trips = _trackintel_model(gdf, columns, geom_col, crs, ["started_at", "finished_at"], tz)
-    else:
-        trips = gdf.rename(columns=columns)
-        # check and/or set timezone
-        for col in ["started_at", "finished_at"]:
-            if not pd.api.types.is_datetime64tz_dtype(trips[col]):
-                trips[col] = _localize_timestamp(dt_series=trips[col], pytz_tzinfo=tz, col_name=col)
+    trips = _trackintel_model(gdf, columns, geom_col, crs, ["started_at", "finished_at"], tz)
 
     # assert validity of trips
     trips.as_trips
@@ -323,10 +316,8 @@ def read_tours_gpd(
     user_id="user_id",
     started_at="started_at",
     finished_at="finished_at",
-    location_id="location_id",
-    journey="journey",
     tz=None,
-    mapper={},
+    mapper=None,
 ):
     """
     Read tours from GeoDataFrames.
@@ -336,48 +327,42 @@ def read_tours_gpd(
     Parameters
     ----------
     gdf : GeoDataFrame
-        GeoDataFrame with valid point geometry, containing the locations to import.
+        GeoDataFrame containing the tours to import.
 
     user_id : str, default 'user_id'
-        name of the column storing the user_id.
+        Name of the column storing the user_id.
 
     started_at : str, default 'started_at'
-        name of the column storing the starttime of the staypoints.
+        Name of the column storing the start time of the tours.
 
     finished_at : str, default 'finished_at'
-        name of the column storing the endtime of the staypoints.
+        Name of the column storing the end time of the tours.
 
-    location_id : str, default 'location_id'
-        the name of the column storing the id of the location where the tour starts and ends.
-
-    journey : str, default 'journey'
-        name of the column storing the information (bool) if the tour is a journey.
+    tz : str, optional
+        pytz compatible timezone string. If None UTC is assumed.
 
     mapper : dict, optional
-        further columns that should be renamed.
+        Further columns that should be renamed.
 
     Returns
     -------
-    trs : GeoDataFrame (as trackintel tours)
+    tours : GeoDataFrame (as trackintel tours)
         A GeoDataFrame containing the tours
     """
-    # columns = {user_id: 'user_id',
-    #            started_at: 'tracked_at',
-    #            finished_at: 'finished_at',
-    #            location_id: 'location_id',
-    #            journey: 'journey'}
-    # columns.update(mapper)
+    columns = {
+        user_id: "user_id",
+        started_at: "started_at",
+        finished_at: "finished_at",
+    }
+    if mapper is not None:
+        columns.update(mapper)
 
-    # trs = gdf.rename(columns=columns)
+    tours = _trackintel_model(gdf, set_names=columns, tz_cols=["started_at", "finished_at"], tz=tz)
 
-    # # check and/or set timezone
-    # for col in ['started_at', 'finished_at']:
-    #     if not pd.api.types.is_datetime64tz_dtype(trs[col]):
-    #         trs[col] = localize_timestamp(dt_series=trs[col], pytz_tzinfo=tz, col_name=col)
+    # assert validity of tours
+    tours.as_tours
 
-    # trs.as_tours
-    # return trs
-    pass
+    return tours
 
 
 def _trackintel_model(gdf, set_names=None, geom_col=None, crs=None, tz_cols=None, tz=None):
@@ -413,6 +398,16 @@ def _trackintel_model(gdf, set_names=None, geom_col=None, crs=None, tz_cols=None
     if set_names is not None:
         gdf = gdf.rename(columns=set_names)
 
+    if tz_cols is not None:
+        for col in tz_cols:
+            if not pd.api.types.is_datetime64tz_dtype(gdf[col]):
+                gdf[col] = _localize_timestamp(dt_series=gdf[col], pytz_tzinfo=tz, col_name=col)
+
+    # If is not GeoDataFrame and no geom_col is set end early.
+    # That allows us to handle DataFrames and GeoDataFrames in one function.
+    if not isinstance(gdf, gpd.GeoDataFrame) and geom_col is None:
+        return gdf
+
     if geom_col is not None:
         gdf = gdf.set_geometry(geom_col)
     else:
@@ -424,9 +419,4 @@ def _trackintel_model(gdf, set_names=None, geom_col=None, crs=None, tz_cols=None
     if crs is not None:
         gdf = gdf.set_crs(crs)
 
-    if tz_cols is not None:
-        for col in tz_cols:
-            if not pd.api.types.is_datetime64tz_dtype(gdf[col]):
-                gdf[col] = _localize_timestamp(dt_series=gdf[col], pytz_tzinfo=tz, col_name=col)
-
     return gdf


Test patch:
diff --git a/ci/envs/37-latest-conda-forge.yaml b/ci/envs/37-latest-conda-forge.yaml
index 016279d..5a64500 100644
--- a/ci/envs/37-latest-conda-forge.yaml
+++ b/ci/envs/37-latest-conda-forge.yaml
@@ -20,4 +20,5 @@ dependencies:
 - pytest-xdist
 - tqdm
 - similaritymeasures
+- pygeos
 - jupyter
diff --git a/ci/envs/38-latest-conda-forge.yaml b/ci/envs/38-latest-conda-forge.yaml
index 40c036e..e3896e5 100644
--- a/ci/envs/38-latest-conda-forge.yaml
+++ b/ci/envs/38-latest-conda-forge.yaml
@@ -20,4 +20,5 @@ dependencies:
 - pytest-xdist
 - tqdm
 - similaritymeasures
+- pygeos
 - jupyter
diff --git a/ci/envs/39-latest-conda-forge.yaml b/ci/envs/39-latest-conda-forge.yaml
index 814ac7c..488da4e 100644
--- a/ci/envs/39-latest-conda-forge.yaml
+++ b/ci/envs/39-latest-conda-forge.yaml
@@ -20,4 +20,5 @@ dependencies:
 - pytest-xdist
 - tqdm
 - similaritymeasures
+- pygeos
 - jupyter
diff --git a/tests/data/tours.csv b/tests/data/tours.csv
new file mode 100644
index 0000000..7613a17
--- /dev/null
+++ b/tests/data/tours.csv
@@ -0,0 +1,3 @@
+id;user_id;started_at;finished_at;
+0;1;2015-11-27T08:00:00Z;2015-11-27T10:00:00Z;
+1;1;2015-11-27T12:00:00Z;2015-11-27T14:00:00Z;
diff --git a/tests/io/test_from_geopandas.py b/tests/io/test_from_geopandas.py
index cabf841..c04a724 100644
--- a/tests/io/test_from_geopandas.py
+++ b/tests/io/test_from_geopandas.py
@@ -259,7 +259,7 @@ def example_trips():
 class TestRead_Trips_Gpd:
     """Test `read_trips_gpd()` function."""
 
-    def test_cvs(self):
+    def test_csv(self):
         """Test if the results of reading from gpd and csv agrees."""
         df = pd.read_csv(os.path.join("tests", "data", "trips.csv"), sep=";")
         df.set_index("id", inplace=True)
@@ -297,9 +297,40 @@ class TestRead_Trips_Gpd:
         assert_geodataframe_equal(trips, example_trips)
 
 
+@pytest.fixture
+def example_tours():
+    """Model conform tours to test with."""
+    start = pd.Timestamp("1971-01-01 00:00:00", tz="utc")
+    h = pd.Timedelta("1h")
+
+    list_dict = [
+        {"user_id": 0, "started_at": start + 0 * h, "finished_at": start + 1 * h},
+        {"user_id": 0, "started_at": start + 2 * h, "finished_at": start + 3 * h},
+        {"user_id": 1, "started_at": start + 4 * h, "finished_at": start + 5 * h},
+    ]
+    tours = pd.DataFrame(data=list_dict)
+    tours.index.name = "id"
+    tours.as_tours
+    return tours
+
+
 class TestRead_Tours_Gpd:
     """Test `read_trips_gpd()` function."""
 
-    def test_read_tours_gpd(self):
-        # TODO: implement tests for reading tours from Geopandas
-        pass
+    def test_csv(self):
+        """Test if the results of reading from gpd and csv agrees."""
+        tours_file = os.path.join("tests", "data", "tours.csv")
+        df = pd.read_csv(tours_file, sep=";")
+        df.set_index("id", inplace=True)
+        tours_from_gpd = read_tours_gpd(df, tz="utc")
+
+        tours_from_csv = ti.read_tours_csv(tours_file, sep=";", tz="utc", index_col="id")
+
+        assert_frame_equal(tours_from_gpd, tours_from_csv, check_exact=False)
+
+    def test_mapper(self, example_tours):
+        example_tours["additional_col"] = [11, 22, 33]
+        mapper = {"additional_col": "additional_col_renamed"}
+        tours = read_tours_gpd(example_tours, mapper=mapper)
+        example_tours.rename(columns=mapper, inplace=True)
+        assert_frame_equal(tours, example_tours)
diff --git a/tests/model/test_trips.py b/tests/model/test_trips.py
index f2b87ab..a418894 100644
--- a/tests/model/test_trips.py
+++ b/tests/model/test_trips.py
@@ -8,7 +8,8 @@ import trackintel as ti
 @pytest.fixture
 def testdata_trips():
     """Read trips test data from file."""
-    test_trips = ti.read_trips_csv(os.path.join("tests", "data", "geolife_long", "trips.csv"), index_col="id")
+    path = os.path.join("tests", "data", "geolife_long", "trips.csv")
+    test_trips = ti.read_trips_csv(path, index_col="id", geom_col="geom")
     return test_trips
 
 
diff --git a/tests/preprocessing/test_positionfixes.py b/tests/preprocessing/test_positionfixes.py
index 95d75ff..df9af75 100644
--- a/tests/preprocessing/test_positionfixes.py
+++ b/tests/preprocessing/test_positionfixes.py
@@ -172,17 +172,21 @@ class TestGenerate_staypoints:
     def test_sliding_max(self):
         """Test if using large thresholds, stp extraction yield no pfs."""
         pfs, _ = ti.io.dataset_reader.read_geolife(os.path.join("tests", "data", "geolife"))
-        _, sp = pfs.as_positionfixes.generate_staypoints(
-            method="sliding", dist_threshold=sys.maxsize, time_threshold=sys.maxsize
-        )
+        warn_string = "No staypoints can be generated, returning empty sp."
+        with pytest.warns(UserWarning, match=warn_string):
+            _, sp = pfs.as_positionfixes.generate_staypoints(
+                method="sliding", dist_threshold=sys.maxsize, time_threshold=sys.maxsize
+            )
         assert len(sp) == 0
 
     def test_missing_link(self):
         """Test nan is assigned for missing link between pfs and sp."""
         pfs, _ = ti.io.dataset_reader.read_geolife(os.path.join("tests", "data", "geolife"))
-        pfs, _ = pfs.as_positionfixes.generate_staypoints(
-            method="sliding", dist_threshold=sys.maxsize, time_threshold=sys.maxsize
-        )
+        warn_string = "No staypoints can be generated, returning empty sp."
+        with pytest.warns(UserWarning, match=warn_string):
+            pfs, _ = pfs.as_positionfixes.generate_staypoints(
+                method="sliding", dist_threshold=sys.maxsize, time_threshold=sys.maxsize
+            )
 
         assert pd.isna(pfs["staypoint_id"]).any()
 
diff --git a/tests/preprocessing/test_staypoints.py b/tests/preprocessing/test_staypoints.py
index 10c4425..1cfab19 100644
--- a/tests/preprocessing/test_staypoints.py
+++ b/tests/preprocessing/test_staypoints.py
@@ -115,7 +115,7 @@ def example_triplegs_merge(example_staypoints_merge):
         {"id": 1, "user_id": 0, "started_at": t21, "finished_at": t22},
     ]
     # geometry is not required for the merge operation, so we leave it away
-    tpls = gpd.GeoDataFrame(data=list_dict, crs="EPSG:4326")
+    tpls = gpd.GeoDataFrame(data=list_dict)
     tpls = tpls.set_index("id")
     return sp, tpls
 
@@ -269,12 +269,14 @@ class TestGenerate_locations:
         _, sp = pfs.as_positionfixes.generate_staypoints(
             method="sliding", gap_threshold=1e6, dist_threshold=0, time_threshold=0
         )
-        _, locs_user = sp.as_staypoints.generate_locations(
-            method="dbscan", epsilon=1e18, num_samples=1000, agg_level="user"
-        )
-        _, locs_data = sp.as_staypoints.generate_locations(
-            method="dbscan", epsilon=1e18, num_samples=1000, agg_level="dataset"
-        )
+        warn_string = "No locations can be generated, returning empty locs."
+        with pytest.warns(UserWarning, match=warn_string):
+            _, locs_user = sp.as_staypoints.generate_locations(
+                method="dbscan", epsilon=1e18, num_samples=1000, agg_level="user"
+            )
+            _, locs_data = sp.as_staypoints.generate_locations(
+                method="dbscan", epsilon=1e18, num_samples=1000, agg_level="dataset"
+            )
         # "With large epsilon, every user location is an outlier"
         assert len(locs_user) == 0
         assert len(locs_data) == 0
@@ -286,7 +288,11 @@ class TestGenerate_locations:
         _, sp = pfs.as_positionfixes.generate_staypoints(
             method="sliding", gap_threshold=1e6, dist_threshold=0, time_threshold=0
         )
-        sp, _ = sp.as_staypoints.generate_locations(method="dbscan", epsilon=1e18, num_samples=1000, agg_level="user")
+        warn_string = "No locations can be generated, returning empty locs."
+        with pytest.warns(UserWarning, match=warn_string):
+            sp, _ = sp.as_staypoints.generate_locations(
+                method="dbscan", epsilon=1e18, num_samples=1000, agg_level="user"
+            )
 
         assert pd.isna(sp["location_id"]).any()
 
diff --git a/tests/preprocessing/test_triplegs.py b/tests/preprocessing/test_triplegs.py
index 11f8e8e..fc7ad42 100644
--- a/tests/preprocessing/test_triplegs.py
+++ b/tests/preprocessing/test_triplegs.py
@@ -78,7 +78,8 @@ class TestGenerate_trips:
     def test_generate_trips(self, example_triplegs_higher_gap_threshold):
         """Test if we can generate the example trips based on example data."""
         # load pregenerated trips
-        trips_loaded = ti.read_trips_csv(os.path.join("tests", "data", "geolife_long", "trips.csv"), index_col="id")
+        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)
 
         # create trips from geolife (based on positionfixes) - with gap_threshold 1e6
         sp, tpls = example_triplegs_higher_gap_threshold
diff --git a/tests/preprocessing/test_trips.py b/tests/preprocessing/test_trips.py
index bf3c86d..0aa69f4 100644
--- a/tests/preprocessing/test_trips.py
+++ b/tests/preprocessing/test_trips.py
@@ -234,12 +234,12 @@ class TestGenerate_tours:
 
     def test_generate_tours_geolife(self):
         """Test tour generation also on the geolife dataset"""
-        trips = ti.io.file.read_trips_csv(os.path.join("tests", "data", "geolife_long", "trips.csv"), index_col="id")
+        path_trips = os.path.join("tests", "data", "geolife_long", "trips.csv")
+        trips = ti.io.file.read_trips_csv(path_trips, index_col="id", geom_col="geom", crs="EPSG:4326")
         _, tours = ti.preprocessing.trips.generate_tours(trips, max_dist=100)
 
-        tours_loaded = ti.io.file.read_tours_csv(
-            os.path.join("tests", "data", "geolife_long", "tours.csv"), index_col="id"
-        )
+        path_tours = os.path.join("tests", "data", "geolife_long", "tours.csv")
+        tours_loaded = ti.io.file.read_tours_csv(path_tours, index_col="id")
         # check only first five columns because location ID is empty in this case
         pd.testing.assert_frame_equal(tours_loaded.iloc[:, :5], tours.iloc[:, :5])