Repository: mie-lab/trackintel

Issue:
ENH: warning for not setting the crs of trackintel objects
The current `read` functions in `io` would proceed without an error or warning when not providing a `crs` as input; the resulting trackintel class object thus has no `crs` set (e.g., running `print(pfs.crs)` will return `None`). This might cause problems later when running some analysis functions that require a `crs`, e.g., `predict_transport_mode` raises a warning: `UserWarning: The CRS of your data is not defined.`.

Shall we add a warning statement if no crs is provided?
BUG: radius_gyration raises a error if method="duration" and no duration column is provided
As described in the title. As trackintel enforces started_at and finished_at columns as time objects, the default behavior shall be recalculating the duration column, similar to in other analysis functions (e.g., [calculate_modal_split](https://github.com/mie-lab/trackintel/blob/b43c225d071d38170a14e5f6cd1d9be565eb59c4/trackintel/analysis/modal_split.py#L52C12-L52C12))

Discussion:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c816663..ed07d36 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,7 +13,7 @@ ci:
 
 repos: 
 - repo: https://github.com/psf/black 
-  rev: 23.10.1 
+  rev: 23.11.0 
   hooks:
   - id: black
     args: [
diff --git a/trackintel/analysis/metrics.py b/trackintel/analysis/metrics.py
index 51c8e6a..6683b92 100644
--- a/trackintel/analysis/metrics.py
+++ b/trackintel/analysis/metrics.py
@@ -18,7 +18,6 @@ def radius_gyration(sp, method="count", print_progress=False):
 
         - `count`: assigns each Point the same weight of 1.
         - `duration`: assigns each Point a weight based on duration.
-          Additionally needs Timedelta column "duration" in sp.
 
     print_progress: bool, default False
         Show per-user progress if set to True.
@@ -65,7 +64,8 @@ def _radius_gyration_user(sp, method):
     y = sp.geometry.y
 
     if method == "duration":
-        w = sp["duration"].dt.total_seconds()
+        duration = sp["finished_at"] - sp["started_at"]
+        w = duration.dt.total_seconds()
     else:  # method == count (check is done in upper level)
         w = np.ones_like(x)
 
diff --git a/trackintel/io/dataset_reader.py b/trackintel/io/dataset_reader.py
index 25c4cc3..93a90f9 100644
--- a/trackintel/io/dataset_reader.py
+++ b/trackintel/io/dataset_reader.py
@@ -52,7 +52,7 @@ def read_geolife(geolife_path, print_progress=False):
     For some users, travel mode labels are provided as .txt file. These labels are read and returned as label dictionary.
     The label dictionary contains the user ids as keys and DataFrames with the available labels as values.
     Labels can be added to each user at the tripleg level, see
-    :func:`trackintel.io.dataset_reader.geolife_add_modes_to_triplegs` for more details.
+    :func:`trackintel.io.geolife_add_modes_to_triplegs` for more details.
 
     The folder structure within the geolife directory needs to be identical with the folder structure
     available from the official download. The means that the top level folder (provided with 'geolife_path')
@@ -700,10 +700,13 @@ def read_gpx(path):
     Positionfixes
     """
     pattern = os.path.join(path, "*.gpx")
+    # sorted to make result deterministic
+    files = sorted(glob.glob(pattern))
+    if not files:
+        raise FileNotFoundError(f'Found no gpx files in path "{path}"')
     pfs_list = []
     track_fid_offset = 0
-    # sorted to make result deterministic
-    for file in sorted(glob.glob(pattern)):
+    for file in files:
         pfs = _read_single_gpx_file(file)
         # give each track an unique ID
         pfs["track_fid"] += track_fid_offset


Test patch:
diff --git a/tests/analysis/test_metrics.py b/tests/analysis/test_metrics.py
index 3a16806..051f4d7 100644
--- a/tests/analysis/test_metrics.py
+++ b/tests/analysis/test_metrics.py
@@ -51,7 +51,6 @@ class TestRadius_gyration:
 
     def test_duration(self, staypoints):
         """Test duration-method with a planar crs."""
-        staypoints["duration"] = staypoints["finished_at"] - staypoints["started_at"]
         s = radius_gyration(staypoints, method="duration")
         v1 = np.sqrt(4 * 3**2 / 4)  # center is (3, 12) weight is 4 due to duration
         v2 = 0  # center lies on p1 and only p1 remains -> 0 variance
diff --git a/tests/io/test_dataset_reader.py b/tests/io/test_dataset_reader.py
index d963554..577d7c8 100644
--- a/tests/io/test_dataset_reader.py
+++ b/tests/io/test_dataset_reader.py
@@ -268,3 +268,8 @@ class TestGpx_reader:
         }
         pfs_test = Positionfixes(data, geometry="geometry", crs=4326)
         assert_geodataframe_equal(pfs, pfs_test)
+
+    def test_missing_files(self):
+        """Test if useful message is output if directory has no gpx files"""
+        with pytest.raises(FileNotFoundError, match="Found no gpx files"):
+            read_gpx(os.path.join("tests", "data"))