pysnapping 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {pysnapping-0.2.1 → pysnapping-0.2.2}/.gitlab-ci.yml +1 -1
  2. {pysnapping-0.2.1 → pysnapping-0.2.2}/.pre-commit-config.yaml +11 -14
  3. {pysnapping-0.2.1 → pysnapping-0.2.2}/PKG-INFO +9 -4
  4. {pysnapping-0.2.1 → pysnapping-0.2.2}/README.md +6 -1
  5. {pysnapping-0.2.1 → pysnapping-0.2.2}/dev-requirements.txt +11 -15
  6. {pysnapping-0.2.1 → pysnapping-0.2.2}/pyproject.toml +2 -2
  7. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/linear_referencing.py +6 -6
  8. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/snap.py +19 -7
  9. pysnapping-0.2.2/pysnapping/stats.py +132 -0
  10. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/test/test_snap.py +39 -19
  11. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping.egg-info/PKG-INFO +9 -4
  12. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping.egg-info/SOURCES.txt +1 -0
  13. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping.egg-info/requires.txt +1 -1
  14. pysnapping-0.2.2/requirements.txt +12 -0
  15. pysnapping-0.2.1/requirements.txt +0 -16
  16. {pysnapping-0.2.1 → pysnapping-0.2.2}/.editorconfig +0 -0
  17. {pysnapping-0.2.1 → pysnapping-0.2.2}/.gitignore +0 -0
  18. {pysnapping-0.2.1 → pysnapping-0.2.2}/LICENSE +0 -0
  19. {pysnapping-0.2.1 → pysnapping-0.2.2}/MANIFEST.in +0 -0
  20. {pysnapping-0.2.1 → pysnapping-0.2.2}/dev-requirements.in +0 -0
  21. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/__init__.py +0 -0
  22. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/py.typed +0 -0
  23. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/test/__init__.py +0 -0
  24. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/test/complex_trip.geojson +0 -0
  25. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/test/conftest.py +0 -0
  26. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/test/test_linear_referencing.py +0 -0
  27. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/test/test_util.py +0 -0
  28. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping/util.py +0 -0
  29. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping.egg-info/dependency_links.txt +0 -0
  30. {pysnapping-0.2.1 → pysnapping-0.2.2}/pysnapping.egg-info/top_level.txt +0 -0
  31. {pysnapping-0.2.1 → pysnapping-0.2.2}/setup.cfg +0 -0
@@ -1,4 +1,4 @@
1
- image: registry.geops.de/build/bullseye-python:latest
1
+ image: registry.geops.com/geops/gitlab-ci-dockerfiles/bullseye-python:latest
2
2
 
3
3
  variables:
4
4
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.pip-cache"
@@ -1,7 +1,7 @@
1
1
  fail_fast: false
2
2
  repos:
3
3
  - repo: https://github.com/pre-commit/pre-commit-hooks
4
- rev: v4.5.0
4
+ rev: v5.0.0
5
5
  hooks:
6
6
  - id: no-commit-to-branch
7
7
  - id: check-merge-conflict
@@ -13,11 +13,20 @@ repos:
13
13
  - id: check-case-conflict
14
14
  - id: check-docstring-first
15
15
  - repo: https://github.com/astral-sh/ruff-pre-commit
16
- rev: v0.1.14
16
+ rev: v0.9.9
17
17
  hooks:
18
18
  - id: ruff
19
19
  args: [ --fix ]
20
20
  - id: ruff-format
21
+ - repo: https://github.com/astral-sh/uv-pre-commit
22
+ rev: 0.6.3
23
+ hooks:
24
+ - id: pip-compile
25
+ args: [pyproject.toml, -o, requirements.txt, -p, "3.9"]
26
+ files: ^(pyproject.toml|requirements.txt)$
27
+ - id: pip-compile
28
+ args: [dev-requirements.in, -o, dev-requirements.txt, -p, "3.9"]
29
+ files: ^(dev\-)?requirements.(txt|in)$
21
30
  - repo: local
22
31
  hooks:
23
32
  - id: mypy
@@ -27,15 +36,3 @@ repos:
27
36
  pass_filenames: false
28
37
  always_run: true
29
38
  args: ["-p", "pysnapping"]
30
- - id: pip-compile
31
- name: pip-compile
32
- entry: pip-compile pyproject.toml
33
- language: python
34
- pass_filenames: false
35
- files: 'pyproject\.toml|requirements\.txt'
36
- - id: pip-compile-dev-requirements
37
- name: pip-compile-dev-requirements
38
- entry: pip-compile dev-requirements.in
39
- language: python
40
- pass_filenames: false
41
- files: '(dev-)?requirements\.(in|txt)'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pysnapping
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Snap points to a line string keeping a given order or spacing intact
5
5
  Author-email: Alexander Held | geOps <alexander.held@geops.com>
6
6
  Maintainer-email: Alexander Held | geOps <alexander.held@geops.com>
@@ -40,7 +40,7 @@ Requires-Python: >=3.9
40
40
  Description-Content-Type: text/markdown
41
41
  License-File: LICENSE
42
42
  Requires-Dist: shapely
43
- Requires-Dist: numpy
43
+ Requires-Dist: numpy<2
44
44
  Requires-Dist: pyproj>=3.2.0
45
45
 
46
46
  # PySnapping
@@ -85,9 +85,14 @@ The `pysnapping.util` module contains common helper functions used in other part
85
85
 
86
86
  ### Snapping
87
87
 
88
- The `pysanpping.snap` module is the main entry point for users of the `pysnapping` library and
88
+ The `pysnapping.snap` module is the main entry point for users of the `pysnapping` library and
89
89
  provides the classes needed to use the library.
90
90
 
91
+ ### Statistics
92
+
93
+ The `pysnapping.stats` module provides tools to aggregate statistics about snapping
94
+ failures and the methods used in case of success.
95
+
91
96
  ## Usage
92
97
 
93
98
  The typical usage pattern is to create a `pysnapping.snap.DubiousTrajectory` instance which represents
@@ -40,9 +40,14 @@ The `pysnapping.util` module contains common helper functions used in other part
40
40
 
41
41
  ### Snapping
42
42
 
43
- The `pysanpping.snap` module is the main entry point for users of the `pysnapping` library and
43
+ The `pysnapping.snap` module is the main entry point for users of the `pysnapping` library and
44
44
  provides the classes needed to use the library.
45
45
 
46
+ ### Statistics
47
+
48
+ The `pysnapping.stats` module provides tools to aggregate statistics about snapping
49
+ failures and the methods used in case of success.
50
+
46
51
  ## Usage
47
52
 
48
53
  The typical usage pattern is to create a `pysnapping.snap.DubiousTrajectory` instance which represents
@@ -1,19 +1,13 @@
1
- #
2
- # This file is autogenerated by pip-compile with Python 3.9
3
- # by the following command:
4
- #
5
- # pip-compile dev-requirements.in
6
- #
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile dev-requirements.in -o dev-requirements.txt -p 3.9
7
3
  build==1.0.3
8
4
  # via pip-tools
9
5
  cfgv==3.4.0
10
6
  # via pre-commit
11
7
  click==8.1.7
12
8
  # via pip-tools
13
- coverage[toml]==7.4.1
14
- # via
15
- # coverage
16
- # pytest-cov
9
+ coverage==7.4.1
10
+ # via pytest-cov
17
11
  distlib==0.3.8
18
12
  # via virtualenv
19
13
  exceptiongroup==1.2.0
@@ -32,7 +26,7 @@ mypy-extensions==1.0.0
32
26
  # via mypy
33
27
  nodeenv==1.8.0
34
28
  # via pre-commit
35
- numpy==1.26.3
29
+ numpy==1.26.4
36
30
  # via
37
31
  # -c requirements.txt
38
32
  # -r dev-requirements.in
@@ -40,6 +34,8 @@ packaging==23.2
40
34
  # via
41
35
  # build
42
36
  # pytest
37
+ pip==25.0.1
38
+ # via pip-tools
43
39
  pip-tools==7.3.0
44
40
  # via -r dev-requirements.in
45
41
  platformdirs==4.1.0
@@ -58,6 +54,10 @@ pytest-cov==4.1.0
58
54
  # via -r dev-requirements.in
59
55
  pyyaml==6.0.1
60
56
  # via pre-commit
57
+ setuptools==75.8.2
58
+ # via
59
+ # nodeenv
60
+ # pip-tools
61
61
  tomli==2.0.1
62
62
  # via
63
63
  # build
@@ -74,7 +74,3 @@ wheel==0.42.0
74
74
  # via pip-tools
75
75
  zipp==3.17.0
76
76
  # via importlib-metadata
77
-
78
- # The following packages are considered to be unsafe in a requirements file:
79
- # pip
80
- # setuptools
@@ -4,7 +4,7 @@ description = "Snap points to a line string keeping a given order or spacing int
4
4
  readme = "README.md"
5
5
  dependencies = [
6
6
  "shapely",
7
- "numpy",
7
+ "numpy<2",
8
8
  "pyproj>=3.2.0", # for defining custom CRS
9
9
  ]
10
10
  requires-python = ">=3.9"
@@ -47,7 +47,7 @@ build-backend = "setuptools.build_meta"
47
47
  [tool.setuptools.packages.find]
48
48
  include = ["pysnapping", "pysnapping.*"]
49
49
 
50
- [tool.ruff]
50
+ [tool.ruff.lint]
51
51
  select = [
52
52
  "F", # pyflakes
53
53
  "E", # pycodestyle
@@ -622,15 +622,15 @@ class SlicedLineFractions:
622
622
  right_fractions = np.linspace(
623
623
  self.closest_fractions[i], self.max_fractions[i], n_right_samples[i]
624
624
  )
625
- locations.from_vertices[
626
- index_offset : index_offset + n_samples[i]
627
- ] = self.segment_indices[i]
625
+ locations.from_vertices[index_offset : index_offset + n_samples[i]] = (
626
+ self.segment_indices[i]
627
+ )
628
628
  locations.to_vertices[index_offset : index_offset + n_samples[i]] = (
629
629
  self.segment_indices[i] + 1
630
630
  )
631
- locations.fractions[
632
- index_offset : index_offset + n_left_samples[i]
633
- ] = left_fractions
631
+ locations.fractions[index_offset : index_offset + n_left_samples[i]] = (
632
+ left_fractions
633
+ )
634
634
  # 1 sample overlap thus - 1
635
635
  locations.fractions[
636
636
  index_offset + n_left_samples[i] - 1 : index_offset + n_samples[i]
@@ -140,21 +140,26 @@ class SnappingParams(typing.NamedTuple):
140
140
 
141
141
  values_arr = np.asarray(values, dtype=float)
142
142
 
143
- min_spacing = self.min_spacing
143
+ nan_min_spacing = self.min_spacing
144
144
  if consider_sampling_accuracy:
145
- min_spacing += 2.01 * self.sampling_step
145
+ # extra spacing for numerical solution for untrusted points
146
+ nan_min_spacing += 2.01 * self.sampling_step
146
147
 
147
148
  # padding to treat NaNs at the boundary, d_min, d_max and min_spacing in an
148
149
  # elegant way
149
150
  padded_values = np.empty(len(values_arr) + 2)
150
- padded_values[0] = d_min - min_spacing
151
+ padded_values[0] = d_min
151
152
  padded_values[1:-1] = values_arr
152
- padded_values[-1] = d_max + min_spacing
153
+ padded_values[-1] = d_max
153
154
  del values_arr # prevent accidental usage
154
155
 
155
156
  non_nan_indices = np.nonzero(np.logical_not(np.isnan(padded_values)))[0]
156
- min_spacing_arr = np.diff(non_nan_indices).astype(float)
157
- min_spacing_arr *= min_spacing
157
+ index_diffs = np.diff(non_nan_indices)
158
+ index_diffs[0] -= 1
159
+ index_diffs[-1] -= 1
160
+ min_spacing_arr = index_diffs.astype(float) * np.where(
161
+ index_diffs > 1, nan_min_spacing, self.min_spacing
162
+ )
158
163
 
159
164
  # bool to ensure builtin bool (and not numpy bool)
160
165
  return bool(np.all(np.diff(padded_values[non_nan_indices]) >= min_spacing_arr))
@@ -578,7 +583,14 @@ class TrajectoryTrip(XYZDMixin):
578
583
  )
579
584
  ppoints_list[i_global] = ppoints
580
585
  else:
581
- logger.debug("spacing of distances is not OK, untrusting all distances")
586
+ logger.debug(
587
+ "spacing of distances %s is not OK (d_min=%s, d_max=%s, "
588
+ "min_spacing=%s), untrusting all distances",
589
+ self.dists,
590
+ self.trajectory.d_min,
591
+ self.trajectory.d_max,
592
+ params.min_spacing,
593
+ )
582
594
  # if the spacing was not ok, it can still be bad after untrusting all points
583
595
  # since the total length can be too short for the points
584
596
  untrusted_dists = np.full_like(self.dists, np.NaN)
@@ -0,0 +1,132 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum
3
+ import typing
4
+ from collections import Counter
5
+ import logging
6
+
7
+ from pysnapping import SnappingError, SnappingMethod
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from pysnapping.snap import SnappedTripPoints, TrajectoryTrip
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class AggregatedSnappingMethod(Enum):
17
+ failed = "failed"
18
+ all_trusted = "all_trusted"
19
+ all_routed = "all_routed"
20
+ all_fallback = "all_fallback"
21
+ mixed = "mixed"
22
+ total = "total"
23
+
24
+
25
+ @dataclass
26
+ class SnappingStats:
27
+ method_counters: list[Counter[SnappingMethod]] = field(default_factory=list)
28
+ aggregated_counter: Counter[AggregatedSnappingMethod] = field(
29
+ default_factory=Counter
30
+ )
31
+
32
+ def reset(self) -> None:
33
+ self.method_counters.clear()
34
+ self.aggregated_counter.clear()
35
+
36
+ def process_result(
37
+ self, result: "SnappedTripPoints"
38
+ ) -> tuple[Counter[SnappingMethod], AggregatedSnappingMethod]:
39
+ counter = Counter(result.methods)
40
+ self.method_counters.append(counter)
41
+ agg_method = self.aggregate_methods(counter)
42
+ self.aggregated_counter[agg_method] += 1
43
+ self.aggregated_counter[AggregatedSnappingMethod.total] += 1
44
+ return counter, agg_method
45
+
46
+ def aggregate_methods(
47
+ self, counter: Counter[SnappingMethod]
48
+ ) -> AggregatedSnappingMethod:
49
+ n_total = sum(v for v in counter.values())
50
+ if counter[SnappingMethod.trusted] == n_total:
51
+ return AggregatedSnappingMethod.all_trusted
52
+ elif counter[SnappingMethod.routed] == n_total:
53
+ return AggregatedSnappingMethod.all_routed
54
+ elif counter[SnappingMethod.fallback] == n_total:
55
+ return AggregatedSnappingMethod.all_fallback
56
+ else:
57
+ return AggregatedSnappingMethod.mixed
58
+
59
+ def process_failure(self) -> None:
60
+ agg_method = AggregatedSnappingMethod.failed
61
+ self.aggregated_counter[agg_method] += 1
62
+ self.aggregated_counter[AggregatedSnappingMethod.total] += 1
63
+ logger.debug("snapping failed")
64
+
65
+ @typing.overload
66
+ def snap_trip_points(
67
+ self,
68
+ ttrip: "TrajectoryTrip",
69
+ *args,
70
+ log_failed: bool = False,
71
+ raise_failed: typing.Literal[True] = ...,
72
+ **kwargs,
73
+ ) -> "SnappedTripPoints": ...
74
+
75
+ @typing.overload
76
+ def snap_trip_points(
77
+ self,
78
+ ttrip: "TrajectoryTrip",
79
+ *args,
80
+ log_failed: bool = False,
81
+ raise_failed: typing.Literal[False],
82
+ **kwargs,
83
+ ) -> typing.Optional["SnappedTripPoints"]: ...
84
+
85
+ def snap_trip_points(
86
+ self,
87
+ ttrip: "TrajectoryTrip",
88
+ *args,
89
+ log_failed: bool = False,
90
+ raise_failed: bool = True,
91
+ **kwargs,
92
+ ) -> typing.Optional["SnappedTripPoints"]:
93
+ """Wrapper for `TrajectoryTrip.snap_trip_points` that updates the statistics.
94
+
95
+ Snapping statistics and failures are aggregated and logged at level DEBUG. Apart
96
+ from that, by default, snapping errors are raised and not logged. By setting
97
+ `raise_failed` and/or `log_failed` you can choose whether to raise and/or log
98
+ failures at level ERROR (with traceback).
99
+
100
+ If you need more custom logging, you should either raise, catch and log or not
101
+ use this wrapper and use `process_result` and `process_failure` instead for even
102
+ more control.
103
+ """
104
+ try:
105
+ result = ttrip.snap_trip_points(*args, **kwargs)
106
+ except SnappingError as error:
107
+ self.process_failure()
108
+ (logger.exception if log_failed else logger.debug)(
109
+ "snapping %r failed: %s",
110
+ ttrip,
111
+ error,
112
+ )
113
+ if raise_failed:
114
+ raise
115
+ else:
116
+ return None
117
+ else:
118
+ counter, agg_method = self.process_result(result)
119
+ logger.debug(
120
+ "snapping %r succeeded with methods %s aggregated to %s",
121
+ ttrip,
122
+ {k.value: v for k, v in counter.items()},
123
+ agg_method.value,
124
+ )
125
+ return result
126
+
127
+ def log_aggregated(self, prefix: str = "") -> None:
128
+ logger.info(
129
+ "%s%s",
130
+ prefix,
131
+ {k.value: v for k, v in self.aggregated_counter.items()},
132
+ )
@@ -15,6 +15,7 @@ from pysnapping.snap import (
15
15
  SnappingParams,
16
16
  )
17
17
  from pysnapping.linear_referencing import locate, interpolate, resample
18
+ from pysnapping.stats import SnappingStats
18
19
  from pysnapping.util import get_trafo, transform_coords
19
20
  from pysnapping import EPSG4326, EPSG4978
20
21
 
@@ -52,27 +53,40 @@ def segment_lengths_to_dists(segment_lengths, start=0):
52
53
 
53
54
 
54
55
  @pytest.mark.parametrize(
55
- "values,d_min,d_max,min_spacing,ok",
56
+ "values,d_min,d_max,min_spacing,consider_sampling_accuracy,ok",
56
57
  [
57
- ([], 0, 0, 10, True),
58
- ([np.NaN], 0, 0, 10, True),
59
- ([0], 0, 0, 10, True),
60
- ([1], 0, 1, 10, True),
61
- ([1], 0, 1 - 1e-6, 10, False),
62
- ([np.NaN, 1, np.NaN], 0, 2, 1 - 1e-6, True),
63
- ([np.NaN, 1, np.NaN], 0, 1.9, 1, False),
64
- ([-1, np.NaN, np.NaN, 0], -10, 10, 1, False),
65
- ([-1, np.NaN, np.NaN, 0], -10, 10, 0.5, False),
66
- ([-1, np.NaN, np.NaN, 0], -10, 10, 1 / 3 - 1e-6, True),
67
- ([np.NaN, np.NaN, np.NaN], -100, 0, 50 - 1e-6, True),
68
- ([np.NaN, np.NaN, np.NaN], -100, 0, 50 + 1e-6, False),
58
+ ([], 0, 0, 10, False, True),
59
+ ([np.NaN], 0, 0, 10, False, True),
60
+ ([0], 0, 0, 10, False, True),
61
+ ([1], 0, 1, 10, False, True),
62
+ ([1], 0, 1 - 1e-6, 10, False, False),
63
+ ([np.NaN, 1, np.NaN], 0, 2, 1 - 1e-6, False, True),
64
+ ([np.NaN, 1, np.NaN], 0, 1.9, 1, False, False),
65
+ ([-1, np.NaN, np.NaN, 0], -10, 10, 1, False, False),
66
+ ([-1, np.NaN, np.NaN, 0], -10, 10, 0.5, False, False),
67
+ ([-1, np.NaN, np.NaN, 0], -10, 10, 1 / 3 - 1e-6, False, True),
68
+ ([np.NaN, np.NaN, np.NaN], -100, 0, 50 - 1e-6, False, True),
69
+ ([np.NaN, np.NaN, np.NaN], -100, 0, 50 + 1e-6, False, False),
70
+ ([0, 0, 1, 1, 1.001, 1.001, 10, 10], 0, 10, 0, False, True),
71
+ ([0, 0, 1, 1, 1.001, 1.001, 10, 10], 0, 10, 0, True, True),
72
+ ([0, 0, 1, np.NaN, 1, 1.001, 1.001, 10, 10], 0, 10, 0, False, True),
73
+ ([0, 0, 1, np.NaN, 1, 1.001, 1.001, 10, 10], 0, 10, 0, True, False),
74
+ ([-1e9, 1e9], -1e9, 1e9, 0, False, True),
75
+ ([-1e9, 1e9], -1e9, 1e9, 0, True, True),
76
+ ([-1e9, 1e9], -1e9, 1e9, 2.01 * 5.0, False, True),
77
+ ([-1e9, 1e9], -1e9, 1e9, 2.01 * 5.0, True, True),
69
78
  ],
70
79
  )
71
80
  def test_spacing_ok(
72
- values: "ArrayLike", d_min: float, d_max: float, min_spacing: float, ok: bool
81
+ values: "ArrayLike",
82
+ d_min: float,
83
+ d_max: float,
84
+ min_spacing: float,
85
+ consider_sampling_accuracy: bool,
86
+ ok: bool,
73
87
  ) -> None:
74
88
  params = SnappingParams(min_spacing=min_spacing)
75
- assert params.spacing_ok(values, d_min, d_max) == ok
89
+ assert params.spacing_ok(values, d_min, d_max, consider_sampling_accuracy) == ok
76
90
 
77
91
 
78
92
  # test around the entire earth with different orientations
@@ -276,6 +290,9 @@ def test_short_trajectory_and_random_garbage(seed):
276
290
  atol_snap=float("inf"),
277
291
  )
278
292
 
293
+ # just to get code coverage of stats module
294
+ stats = SnappingStats()
295
+
279
296
  rng = np.random.default_rng(seed=seed)
280
297
 
281
298
  if seed == 0:
@@ -312,7 +329,9 @@ def test_short_trajectory_and_random_garbage(seed):
312
329
 
313
330
  dtrip = DubiousTrajectoryTrip(dtraj, trip_xyzd)
314
331
  trip = dtrip.to_trajectory_trip()
315
- snapped = trip.snap_trip_points(params)
332
+ snapped = stats.snap_trip_points(trip, params)
333
+ stats.log_aggregated(prefix=f"seed {seed}: ")
334
+ stats.reset()
316
335
  min_spacing = min(params.min_spacing, trip.trajectory.length / (n_points - 1))
317
336
  for split_segment in snapped.get_inter_point_ls_lon_lat_in_travel_direction():
318
337
  assert (
@@ -350,11 +369,12 @@ def test_complex_trip():
350
369
 
351
370
 
352
371
  # 25 is default min_spacing and 5 is default sampling_step.
353
- # The cutoff is at 25 + 2.01 * 5 thus 35 should be too close but 36 should be good.
372
+ # The cutoff is at 25 thus 24.9 should be too close but 25.1 should be good.
354
373
  @pytest.mark.parametrize(
355
- "spacing,method", ((35, SnappingMethod.routed), (36, SnappingMethod.trusted))
374
+ "spacing,method", ((24.9, SnappingMethod.routed), (25.1, SnappingMethod.trusted))
356
375
  )
357
376
  def test_untrust_bad_spacing(spacing: float, method: SnappingMethod) -> None:
377
+ params = SnappingParams(atol_trusted=1e3)
358
378
  length = 50
359
379
  dtraj = make_dubious_traj(
360
380
  [length],
@@ -370,7 +390,7 @@ def test_untrust_bad_spacing(spacing: float, method: SnappingMethod) -> None:
370
390
  xyzd=trip_xyzd,
371
391
  )
372
392
  trip = dtrip.to_trajectory_trip()
373
- snapped = trip.snap_trip_points()
393
+ snapped = trip.snap_trip_points(params)
374
394
  assert not snapped.reverse_order
375
395
  assert np.all(snapped.methods == method)
376
396
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pysnapping
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Snap points to a line string keeping a given order or spacing intact
5
5
  Author-email: Alexander Held | geOps <alexander.held@geops.com>
6
6
  Maintainer-email: Alexander Held | geOps <alexander.held@geops.com>
@@ -40,7 +40,7 @@ Requires-Python: >=3.9
40
40
  Description-Content-Type: text/markdown
41
41
  License-File: LICENSE
42
42
  Requires-Dist: shapely
43
- Requires-Dist: numpy
43
+ Requires-Dist: numpy<2
44
44
  Requires-Dist: pyproj>=3.2.0
45
45
 
46
46
  # PySnapping
@@ -85,9 +85,14 @@ The `pysnapping.util` module contains common helper functions used in other part
85
85
 
86
86
  ### Snapping
87
87
 
88
- The `pysanpping.snap` module is the main entry point for users of the `pysnapping` library and
88
+ The `pysnapping.snap` module is the main entry point for users of the `pysnapping` library and
89
89
  provides the classes needed to use the library.
90
90
 
91
+ ### Statistics
92
+
93
+ The `pysnapping.stats` module provides tools to aggregate statistics about snapping
94
+ failures and the methods used in case of success.
95
+
91
96
  ## Usage
92
97
 
93
98
  The typical usage pattern is to create a `pysnapping.snap.DubiousTrajectory` instance which represents
@@ -13,6 +13,7 @@ pysnapping/__init__.py
13
13
  pysnapping/linear_referencing.py
14
14
  pysnapping/py.typed
15
15
  pysnapping/snap.py
16
+ pysnapping/stats.py
16
17
  pysnapping/util.py
17
18
  pysnapping.egg-info/PKG-INFO
18
19
  pysnapping.egg-info/SOURCES.txt
@@ -1,3 +1,3 @@
1
1
  shapely
2
- numpy
2
+ numpy<2
3
3
  pyproj>=3.2.0
@@ -0,0 +1,12 @@
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml -o requirements.txt -p 3.9
3
+ certifi==2025.1.31
4
+ # via pyproj
5
+ numpy==1.26.4
6
+ # via
7
+ # pysnapping (pyproject.toml)
8
+ # shapely
9
+ pyproj==3.6.1
10
+ # via pysnapping (pyproject.toml)
11
+ shapely==2.0.7
12
+ # via pysnapping (pyproject.toml)
@@ -1,16 +0,0 @@
1
- #
2
- # This file is autogenerated by pip-compile with Python 3.9
3
- # by the following command:
4
- #
5
- # pip-compile pyproject.toml
6
- #
7
- certifi==2023.11.17
8
- # via pyproj
9
- numpy==1.26.3
10
- # via
11
- # pysnapping (pyproject.toml)
12
- # shapely
13
- pyproj==3.6.1
14
- # via pysnapping (pyproject.toml)
15
- shapely==2.0.2
16
- # via pysnapping (pyproject.toml)
File without changes
File without changes
File without changes
File without changes
File without changes