Repository: mie-lab/trackintel
Pull request #571 (2023-11-08 18:37:35)

DOC: fix docstrings
`generate_trips` method pointed to the wrong function.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/mie-lab/trackintel/pull/571?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`2b2cd36`)](https://app.codecov.io/gh/mie-lab/trackintel/commit/2b2cd36a2ca2b442d9d4678b15ad6f50ed44842c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) 93.10% compared to head [(`070f4b9`)](https://app.codecov.io/gh/mie-lab/trackintel/pull/571?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) 93.10%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #571   +/-   ##
=======================================
  Coverage   93.10%   93.10%           
=======================================
  Files          33       33           
  Lines        2088     2088           
  Branches      373      373           
=======================================
  Hits         1944     1944           
  Misses        130      130           
  Partials       14       14           
```


| [Files](https://app.codecov.io/gh/mie-lab/trackintel/pull/571?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) | Coverage Δ | |
|---|---|---|
| [trackintel/model/staypoints.py](https://app.codecov.io/gh/mie-lab/trackintel/pull/571?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC9zdGF5cG9pbnRzLnB5) | `100.00% <ø> (ø)` | |
| [trackintel/model/triplegs.py](https://app.codecov.io/gh/mie-lab/trackintel/pull/571?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9tb2RlbC90cmlwbGVncy5weQ==) | `100.00% <ø> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mie-lab/trackintel/pull/571?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab).


Patch:
diff --git a/docs/modules/io.rst b/docs/modules/io.rst
index a9dab6e..a6656cc 100644
--- a/docs/modules/io.rst
+++ b/docs/modules/io.rst
@@ -104,3 +104,10 @@ We support easy parsing of the Geolife dataset including available mode labels.
 .. autofunction:: trackintel.io.read_geolife
 
 .. autofunction:: trackintel.io.geolife_add_modes_to_triplegs
+
+
+GPX
+-----------
+Load multiple tracks of the same user with this function.
+
+.. autofunction:: trackintel.io.read_gpx
diff --git a/trackintel/io/__init__.py b/trackintel/io/__init__.py
index 0ab916c..517ea8a 100644
--- a/trackintel/io/__init__.py
+++ b/trackintel/io/__init__.py
@@ -37,6 +37,7 @@ from .from_geopandas import read_tours_gpd
 from .dataset_reader import read_geolife
 from .dataset_reader import read_mzmv
 from .dataset_reader import geolife_add_modes_to_triplegs
+from .dataset_reader import read_gpx
 
 __all__ = [
     # positionfixes
@@ -79,4 +80,5 @@ __all__ = [
     "read_geolife",
     "read_mzmv",
     "geolife_add_modes_to_triplegs",
+    "read_gpx",
 ]
diff --git a/trackintel/io/dataset_reader.py b/trackintel/io/dataset_reader.py
index b1839bf..25c4cc3 100644
--- a/trackintel/io/dataset_reader.py
+++ b/trackintel/io/dataset_reader.py
@@ -13,6 +13,7 @@ from tqdm import tqdm
 
 from trackintel.preprocessing.util import calc_temp_overlap
 from trackintel import Positionfixes, Staypoints, Triplegs
+from trackintel.io import read_positionfixes_gpd
 
 FEET2METER = 0.3048
 CRS_WGS84 = 4326
@@ -683,3 +684,50 @@ def _mzmv_generate_sp(tpls, zf):
     added_cols = [b + c for b in ("S_", "Z_") for c in col[-7:]]
     tpls.drop(columns=added_cols, inplace=True)
     return sp
+
+
+def read_gpx(path):
+    """
+    Read gpx data and return it as Positionfixes of a single user
+
+    Parameters
+    ----------
+    path : str
+        Path to directory of gpx files. Non gpx files are ignored.
+
+    Returns
+    -------
+    Positionfixes
+    """
+    pattern = os.path.join(path, "*.gpx")
+    pfs_list = []
+    track_fid_offset = 0
+    # sorted to make result deterministic
+    for file in sorted(glob.glob(pattern)):
+        pfs = _read_single_gpx_file(file)
+        # give each track an unique ID
+        pfs["track_fid"] += track_fid_offset
+        track_fid_offset = pfs["track_fid"].max() + 1
+        pfs_list.append(pfs)
+    return pd.concat(pfs_list, ignore_index=True)
+
+
+def _read_single_gpx_file(path):
+    """
+    Read track points out from single gpx file
+
+    Extension types are not supported by fiona and therefore dropped.
+
+    Parameters
+    ----------
+    path : str
+        Path to `.gpx` file with track
+
+    Returns
+    -------
+    Positionfixes
+    """
+    gdf = gpd.read_file(path, layer="track_points")
+    gdf = gdf.dropna(axis="columns", how="all")  # drop empty columns
+    gdf["user_id"] = 0  # maybe we have a smarter way for this
+    return read_positionfixes_gpd(gdf, tracked_at="time")
diff --git a/trackintel/model/staypoints.py b/trackintel/model/staypoints.py
index a45a22a..9cbc312 100644
--- a/trackintel/model/staypoints.py
+++ b/trackintel/model/staypoints.py
@@ -156,7 +156,7 @@ class Staypoints(TrackintelBase, TrackintelGeoDataFrame):
         """
         Generate trips based on staypoints and triplegs.
 
-        See :func:`trackintel.preprocessing.generate_triplegs` for full documentation.
+        See :func:`trackintel.preprocessing.generate_trips` for full documentation.
         """
         return ti.preprocessing.generate_trips(self, triplegs, gap_threshold=gap_threshold, add_geometry=add_geometry)
 
diff --git a/trackintel/model/triplegs.py b/trackintel/model/triplegs.py
index dea6df9..f54d6cb 100644
--- a/trackintel/model/triplegs.py
+++ b/trackintel/model/triplegs.py
@@ -101,7 +101,7 @@ class Triplegs(TrackintelBase, TrackintelGeoDataFrame):
         """
         Generate trips based on staypoints and triplegs.
 
-        See :func:`trackintel.preprocessing.generate_triplegs` for full documentation.
+        See :func:`trackintel.preprocessing.generate_trips` for full documentation.
         """
         return ti.preprocessing.generate_trips(staypoints, self, gap_threshold=gap_threshold, add_geometry=add_geometry)
 


Test patch:
diff --git a/tests/data/gpx_data/track1.gpx b/tests/data/gpx_data/track1.gpx
new file mode 100644
index 0000000..3e69c51
--- /dev/null
+++ b/tests/data/gpx_data/track1.gpx
@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<gpx version="1.1" creator="HiWi" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
+<metadata>
+    <name>2023-11-08_15-56_Tue</name>
+    <time>2023-11-08T15:00:00Z</time>
+  </metadata>
+<trk>
+    <trkseg>
+      <trkpt lat="47.0" lon="8.0">
+        <ele>1000.0</ele>
+        <time>2023-11-08T10:00:00Z</time>
+        <hdop>10.0</hdop>
+      </trkpt>
+    </trkseg>
+  </trk>
+</gpx>
\ No newline at end of file
diff --git a/tests/data/gpx_data/track2.gpx b/tests/data/gpx_data/track2.gpx
new file mode 100644
index 0000000..ba45316
--- /dev/null
+++ b/tests/data/gpx_data/track2.gpx
@@ -0,0 +1,24 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<gpx version="1.1" creator="HiWi" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
+<metadata>
+    <name>2023-11-08_15-56_Tue</name>
+    <time>2023-11-08T15:00:00Z</time>
+  </metadata>
+<trk>
+    <trkseg>
+      <trkpt lat="48.0" lon="9.0">
+        <ele>1001.0</ele>
+        <time>2023-11-08T11:00:00Z</time>
+        <hdop>11.0</hdop>
+        <extensions> <!-- cannot be read by fiora -->
+          <osmand:speed>0.2</osmand:speed>
+        </extensions>
+      </trkpt>
+      <trkpt lat="49.0" lon="10.0">
+        <ele>1002.0</ele>
+        <time>2023-11-08T12:00:00Z</time>
+        <hdop>12.0</hdop>
+      </trkpt>
+    </trkseg>
+  </trk>
+</gpx>
\ No newline at end of file
diff --git a/tests/io/test_dataset_reader.py b/tests/io/test_dataset_reader.py
index 7e5f4b9..d963554 100644
--- a/tests/io/test_dataset_reader.py
+++ b/tests/io/test_dataset_reader.py
@@ -7,7 +7,8 @@ from geopandas.testing import assert_geodataframe_equal
 from shapely.geometry import Point
 
 import trackintel as ti
-from trackintel.io.dataset_reader import _get_df, _get_labels, geolife_add_modes_to_triplegs, read_geolife
+from trackintel.io.dataset_reader import _get_df, _get_labels, geolife_add_modes_to_triplegs, read_geolife, read_gpx
+from trackintel import Positionfixes
 
 
 @pytest.fixture
@@ -244,3 +245,26 @@ class TestGeolife_add_modes_to_triplegs:
 
         tpls = geolife_add_modes_to_triplegs(tpls, labels)
         assert pd.isna(tpls.iloc[0]["mode"])
+
+
+class TestGpx_reader:
+    def test_simple_input(self):
+        """Test if gpx_reader works on simple input"""
+        pfs = read_gpx(os.path.join("tests", "data", "gpx_data"))
+        elev = 1000.0
+        h = pd.Timedelta(hours=1)
+        time = pd.Timestamp("2023-11-08 10:00:00+00:00")
+        hdop = 10.0
+        x, y = 8, 47
+        data = {
+            "track_fid": [0, 1, 1],  # track 0 with 1 point, track 1 with 2 points
+            "track_seg_id": [0, 0, 0],  # each track has 1 segment
+            "track_seg_point_id": [0, 0, 1],
+            "ele": [elev, elev + 1, elev + 2],
+            "tracked_at": [time, time + h, time + 2 * h],
+            "hdop": [hdop, hdop + 1, hdop + 2],
+            "geometry": [Point(x, y), Point(x + 1, y + 1), Point(x + 2, y + 2)],
+            "user_id": [0, 0, 0],
+        }
+        pfs_test = Positionfixes(data, geometry="geometry", crs=4326)
+        assert_geodataframe_equal(pfs, pfs_test)