pysnapping 0.2.2__tar.gz → 0.2.4__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 (36) hide show
  1. {pysnapping-0.2.2 → pysnapping-0.2.4}/.gitignore +0 -3
  2. pysnapping-0.2.4/.gitlab-ci.yml +71 -0
  3. pysnapping-0.2.4/.pre-commit-config.yaml +43 -0
  4. pysnapping-0.2.4/.python-version +1 -0
  5. {pysnapping-0.2.2 → pysnapping-0.2.4}/PKG-INFO +28 -17
  6. {pysnapping-0.2.2 → pysnapping-0.2.4}/README.md +23 -13
  7. {pysnapping-0.2.2 → pysnapping-0.2.4}/pyproject.toml +30 -8
  8. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/linear_referencing.py +10 -14
  9. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/snap.py +32 -37
  10. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/test/test_snap.py +19 -19
  11. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/test/test_util.py +1 -1
  12. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/util.py +8 -10
  13. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping.egg-info/PKG-INFO +28 -17
  14. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping.egg-info/SOURCES.txt +4 -3
  15. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping.egg-info/requires.txt +1 -1
  16. pysnapping-0.2.4/pysnapping.egg-info/scm_file_list.json +26 -0
  17. pysnapping-0.2.4/pysnapping.egg-info/scm_version.json +8 -0
  18. pysnapping-0.2.4/uv.lock +721 -0
  19. pysnapping-0.2.2/.gitlab-ci.yml +0 -66
  20. pysnapping-0.2.2/.pre-commit-config.yaml +0 -38
  21. pysnapping-0.2.2/dev-requirements.in +0 -7
  22. pysnapping-0.2.2/dev-requirements.txt +0 -76
  23. pysnapping-0.2.2/requirements.txt +0 -12
  24. {pysnapping-0.2.2 → pysnapping-0.2.4}/.editorconfig +0 -0
  25. {pysnapping-0.2.2 → pysnapping-0.2.4}/LICENSE +0 -0
  26. {pysnapping-0.2.2 → pysnapping-0.2.4}/MANIFEST.in +0 -0
  27. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/__init__.py +1 -1
  28. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/py.typed +0 -0
  29. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/stats.py +3 -3
  30. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/test/__init__.py +0 -0
  31. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/test/complex_trip.geojson +0 -0
  32. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/test/conftest.py +1 -1
  33. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping/test/test_linear_referencing.py +5 -5
  34. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping.egg-info/dependency_links.txt +0 -0
  35. {pysnapping-0.2.2 → pysnapping-0.2.4}/pysnapping.egg-info/top_level.txt +0 -0
  36. {pysnapping-0.2.2 → pysnapping-0.2.4}/setup.cfg +0 -0
@@ -74,9 +74,6 @@ target/
74
74
  # Jupyter Notebook
75
75
  .ipynb_checkpoints
76
76
 
77
- # pyenv
78
- .python-version
79
-
80
77
  # celery beat schedule file
81
78
  celerybeat-schedule
82
79
 
@@ -0,0 +1,71 @@
1
+ image: registry.geops.com/geops/gitlab-ci-dockerfiles/trixie-python:latest
2
+
3
+ stages:
4
+ - test
5
+
6
+ variables:
7
+ # Fail instead of updating uv.lock if it is out of sync with pyproject.toml.
8
+ UV_LOCKED: "1"
9
+ UV_NO_EDITABLE: "1"
10
+ # use uv python to get more recent patch releases than from the debian image
11
+ UV_PYTHON_PREFERENCE: "only-managed"
12
+ XDG_CACHE_HOME: "${CI_PROJECT_DIR}/.cache/"
13
+ # keep managed pythons in the cached directory (uv stores them outside its cache by default)
14
+ UV_PYTHON_INSTALL_DIR: "${XDG_CACHE_HOME}/uv-python"
15
+ LC_ALL: "C.UTF-8"
16
+ LANG: "C.UTF-8"
17
+ # for printing version
18
+ PACKAGE_NAME: pysnapping
19
+
20
+ cache:
21
+ # key just that cache does not grow unbounded
22
+ key:
23
+ files:
24
+ - uv.lock
25
+ - .pre-commit-config.yaml
26
+ paths:
27
+ - .cache
28
+
29
+ default:
30
+ before_script:
31
+ - eval $(ssh-agent -s)
32
+ - mkdir -p ~/.ssh
33
+ - echo -e "Host *\nStrictHostKeyChecking no\n\n" > ~/.ssh/config
34
+ - echo "$GITLAB_PIPELINES_KEY" | tr -d '\r' | ssh-add - > /dev/null
35
+ - uv --version
36
+ - uv cache dir
37
+ - uv --preview-features=cache-size cache size --human
38
+ # upgrade cached python to latest patch release (if not cached, other uv commands
39
+ # will install the latest patch release and cache it)
40
+ - uv python upgrade
41
+ # install package and print the version (as seen be the CI) that is actually used in
42
+ # the job (might come from uv cache) for debugging possible cache issues
43
+ - uv run python -c "from importlib.metadata import version; print('${PACKAGE_NAME} version as seen by CI (might miss tags that have not been pulled):', version('${PACKAGE_NAME}'))"
44
+
45
+ after_script:
46
+ - eval "$(ssh-agent -k)" # to stop ssh-agent from running forever
47
+
48
+ run_tests:
49
+ stage: test
50
+ needs: []
51
+ script:
52
+ - uv run pytest -s -vvv --log-cli-level INFO --cov=pysnapping --cov-report term --cov-report xml:cobertura_coverage.xml --cov-fail-under=85 --junitxml=junit_coverage.xml pysnapping
53
+ coverage: '/^TOTAL.+?(\d+\%)$/'
54
+ artifacts:
55
+ reports:
56
+ coverage_report:
57
+ coverage_format: cobertura
58
+ path: cobertura_coverage.xml
59
+ junit: junit_coverage.xml
60
+
61
+ run_pre_commit_checks:
62
+ stage: test
63
+ needs: []
64
+ script:
65
+ # We have to uv run here since we still have a system mypy hook that needs installed
66
+ # dependencies. Could be fixed by using ty and calling it without installing
67
+ # anything since ty can handle dependencies on its own (mypy and pyright can't do
68
+ # that).
69
+ - uv run pre-commit run --all-files
70
+ except:
71
+ - master
@@ -0,0 +1,43 @@
1
+ fail_fast: true
2
+ repos:
3
+ - repo: https://github.com/pre-commit/pre-commit-hooks
4
+ rev: v6.0.0
5
+ hooks:
6
+ - id: no-commit-to-branch
7
+ - id: check-merge-conflict
8
+ - id: check-symlinks
9
+ - id: mixed-line-ending
10
+ args: ['--fix=no']
11
+ - id: check-ast
12
+ - id: check-builtin-literals
13
+ - id: check-case-conflict
14
+ - id: check-docstring-first
15
+ - repo: https://github.com/astral-sh/ruff-pre-commit
16
+ rev: v0.17.4
17
+ hooks:
18
+ - id: ruff
19
+ args: [ --fix ]
20
+ - id: ruff-format
21
+ - repo: https://github.com/astral-sh/uv-pre-commit
22
+ rev: 0.13.7
23
+ hooks:
24
+ - id: uv-lock
25
+ files: ^pyproject.toml|uv.lock$
26
+ - repo: local
27
+ hooks:
28
+ - id: mypy
29
+ name: mypy
30
+ # gitlab job syncs explicitly without building extensions
31
+ # and for local dev we assume synced
32
+ entry: uv run --no-sync mypy
33
+ # We need installed dependencies for mypy, so we can't use an isolated env. This
34
+ # could be fixed by using ty instead which is capable of resolving dependencies on
35
+ # its own (mypy and pyright can't do that).
36
+ language: system
37
+ # don't pass file names, so we detect errors in B when only A changed but B
38
+ # imports A
39
+ pass_filenames: false
40
+ # always run instead of a python file filter, so we detect errors in B when A gets
41
+ # deleted but B imports A
42
+ always_run: true
43
+ args: ["-p", "pysnapping"]
@@ -0,0 +1 @@
1
+ 3.13
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pysnapping
3
- Version: 0.2.2
3
+ Version: 0.2.4
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>
@@ -36,12 +36,13 @@ Classifier: License :: OSI Approved :: MIT License
36
36
  Classifier: Topic :: Scientific/Engineering :: GIS
37
37
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
38
38
  Classifier: Typing :: Typed
39
- Requires-Python: >=3.9
39
+ Requires-Python: >=3.11
40
40
  Description-Content-Type: text/markdown
41
41
  License-File: LICENSE
42
42
  Requires-Dist: shapely
43
- Requires-Dist: numpy<2
43
+ Requires-Dist: numpy~=2.0
44
44
  Requires-Dist: pyproj>=3.2.0
45
+ Dynamic: license-file
45
46
 
46
47
  # PySnapping
47
48
 
@@ -65,10 +66,10 @@ those that respect the order and minimum spacing between points.
65
66
 
66
67
  ## Installation
67
68
 
68
- Install the latest stable release from PyPI with pip:
69
+ Add the latest stable release from PyPI to your project with uv:
69
70
 
70
71
  ```bash
71
- pip install pysnapping
72
+ uv add pysnapping
72
73
  ```
73
74
 
74
75
  ## Modules
@@ -125,31 +126,25 @@ See [our GitHub page](https://github.com/geops) for contact details.
125
126
 
126
127
  ### Editable Installation
127
128
 
128
- Clone this repo and enter the corresponding directory.
129
- Create a virtual environment, then install frozen requirements, dev-requirements
130
- and this library in editable mode:
129
+ Clone this repo and enter the corresponding directory. Then run:
131
130
 
132
131
  ```bash
133
- python3.9 -m venv env
134
- . env/bin/activate
135
- pip install -U pip
136
- pip install -r requirements.txt -r dev-requirements.txt -e .
132
+ uv sync
137
133
  ```
138
134
 
139
- Keep env activated for all following instructions.
140
135
 
141
136
  ### Pre-Commit Hooks
142
137
 
143
138
  Enable pre-commit hooks:
144
139
 
145
140
  ```bash
146
- pre-commit install
141
+ uv run pre-commit install
147
142
  ```
148
143
 
149
144
  From time to time (not automated yet) run
150
145
 
151
146
  ```bash
152
- pre-commit autoupdate
147
+ uv run pre-commit autoupdate
153
148
  ```
154
149
 
155
150
  to update frozen revs.
@@ -159,7 +154,23 @@ to update frozen revs.
159
154
  Run tests and analyze code coverage:
160
155
 
161
156
  ```bash
162
- pytest --cov=pysnapping --cov-report term --cov-fail-under=85 pysnapping
157
+ uv run pytest --cov=pysnapping --cov-report term --cov-fail-under=85 pysnapping
158
+ ```
159
+
160
+ ### Build release for PyPI
161
+
162
+ Make sure to add a git tag first and push it to geOps gitlab and github.
163
+
164
+ Install `build` once:
165
+
166
+ ```bash
167
+ uv tool install build
168
+ ```
169
+
170
+ Then run
171
+
172
+ ```bash
173
+ pyproject-build
163
174
  ```
164
175
 
165
176
  ## Changelog
@@ -20,10 +20,10 @@ those that respect the order and minimum spacing between points.
20
20
 
21
21
  ## Installation
22
22
 
23
- Install the latest stable release from PyPI with pip:
23
+ Add the latest stable release from PyPI to your project with uv:
24
24
 
25
25
  ```bash
26
- pip install pysnapping
26
+ uv add pysnapping
27
27
  ```
28
28
 
29
29
  ## Modules
@@ -80,31 +80,25 @@ See [our GitHub page](https://github.com/geops) for contact details.
80
80
 
81
81
  ### Editable Installation
82
82
 
83
- Clone this repo and enter the corresponding directory.
84
- Create a virtual environment, then install frozen requirements, dev-requirements
85
- and this library in editable mode:
83
+ Clone this repo and enter the corresponding directory. Then run:
86
84
 
87
85
  ```bash
88
- python3.9 -m venv env
89
- . env/bin/activate
90
- pip install -U pip
91
- pip install -r requirements.txt -r dev-requirements.txt -e .
86
+ uv sync
92
87
  ```
93
88
 
94
- Keep env activated for all following instructions.
95
89
 
96
90
  ### Pre-Commit Hooks
97
91
 
98
92
  Enable pre-commit hooks:
99
93
 
100
94
  ```bash
101
- pre-commit install
95
+ uv run pre-commit install
102
96
  ```
103
97
 
104
98
  From time to time (not automated yet) run
105
99
 
106
100
  ```bash
107
- pre-commit autoupdate
101
+ uv run pre-commit autoupdate
108
102
  ```
109
103
 
110
104
  to update frozen revs.
@@ -114,7 +108,23 @@ to update frozen revs.
114
108
  Run tests and analyze code coverage:
115
109
 
116
110
  ```bash
117
- pytest --cov=pysnapping --cov-report term --cov-fail-under=85 pysnapping
111
+ uv run pytest --cov=pysnapping --cov-report term --cov-fail-under=85 pysnapping
112
+ ```
113
+
114
+ ### Build release for PyPI
115
+
116
+ Make sure to add a git tag first and push it to geOps gitlab and github.
117
+
118
+ Install `build` once:
119
+
120
+ ```bash
121
+ uv tool install build
122
+ ```
123
+
124
+ Then run
125
+
126
+ ```bash
127
+ pyproject-build
118
128
  ```
119
129
 
120
130
  ## Changelog
@@ -4,10 +4,10 @@ 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<2",
7
+ "numpy~=2.0", # for vectorized operations
8
8
  "pyproj>=3.2.0", # for defining custom CRS
9
9
  ]
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.11"
11
11
  license = {file = "LICENSE"}
12
12
  authors = [{name = "Alexander Held | geOps", email = "alexander.held@geops.com"}]
13
13
  maintainers = [{name = "Alexander Held | geOps", email = "alexander.held@geops.com"}]
@@ -34,6 +34,14 @@ classifiers = [
34
34
  ]
35
35
  dynamic = ["version"]
36
36
 
37
+ [dependency-groups]
38
+ dev = [
39
+ "pytest",
40
+ "pytest-cov",
41
+ "pre-commit",
42
+ "mypy",
43
+ ]
44
+
37
45
  [project.urls]
38
46
  homepage = "https://github.com/geops/pysnapping"
39
47
  geOps = "https://geops.com/en"
@@ -47,6 +55,15 @@ build-backend = "setuptools.build_meta"
47
55
  [tool.setuptools.packages.find]
48
56
  include = ["pysnapping", "pysnapping.*"]
49
57
 
58
+ [tool.uv]
59
+ cache-keys = [
60
+ # default
61
+ { file = "pyproject.toml"},
62
+ # for non-editable installs (dynamic setuptools_scm versioning)
63
+ { git = { commit = true } },
64
+ ]
65
+ environments = ["sys_platform == 'linux'"]
66
+
50
67
  [tool.ruff.lint]
51
68
  select = [
52
69
  "F", # pyflakes
@@ -60,10 +77,13 @@ select = [
60
77
  "PIE", # unnecessary statements
61
78
  "SLOT", # str, tuple, namedtuple subclasses need __slots__
62
79
  "FIX003", # line contains XXX
80
+ "NPY201", # numpy 2.0 deprecations
81
+ "SIM", # flake8-simplify
82
+ "I", # sort imports
83
+ "T100", # breakpoints
63
84
  ]
64
85
  ignore = [
65
86
  "E501", # line too long
66
- "UP038", # see https://github.com/astral-sh/ruff/issues/7871
67
87
  ]
68
88
 
69
89
  [tool.ruff.lint.mccabe]
@@ -71,11 +91,13 @@ max-complexity = 12
71
91
 
72
92
  [tool.mypy]
73
93
  ignore_missing_imports = true
74
- plugins = [
75
- "numpy.typing.mypy_plugin",
76
- ]
77
- install_types = true
78
- non_interactive = true
94
+ # If we allow to install types, mypy will use pip to alter the venv with unpinned extra
95
+ # type stubs. We are on a pure uv setup and don't have pip. Also we want everything
96
+ # pinned. Thus all missing type stubs have to go to dev dependencies.
97
+ install_types = false
98
+ non_interactive = false
99
+
100
+ [tool.pytest]
79
101
 
80
102
  [tool.coverage.run]
81
103
  branch = true
@@ -9,14 +9,10 @@ Consider using np.moveaxis if your data is shaped differently.
9
9
  This module is rather low-level without sanity checks and conversion of array-like to array.
10
10
  """
11
11
 
12
- import typing
13
12
  from math import floor
14
13
 
15
14
  import numpy as np
16
15
 
17
- # faster than np.clip
18
- from numpy.core.umath import clip # type: ignore
19
-
20
16
  from . import ExtrapolationError
21
17
 
22
18
 
@@ -321,7 +317,7 @@ class LineFractions:
321
317
  head_fraction: float = 0.0,
322
318
  tail_segment: int = -1,
323
319
  tail_fraction: float = 1.0,
324
- out: typing.Optional[ProjectedPoints] = None,
320
+ out: ProjectedPoints | None = None,
325
321
  ) -> ProjectedPoints:
326
322
  """Project points to the linestring or a substring thereof.
327
323
 
@@ -375,8 +371,8 @@ class LineFractions:
375
371
  segment_starts = self.target.segment_starts[np.newaxis, segment_slice, :]
376
372
  del self # protection against using unsliced data
377
373
 
378
- low: typing.Union[float, np.ndarray]
379
- high: typing.Union[float, np.ndarray]
374
+ low: float | np.ndarray
375
+ high: float | np.ndarray
380
376
  # different cases only for speeding up the default
381
377
  if head_fraction != 0.0:
382
378
  low = np.zeros((1, fractions.shape[1], 1))
@@ -389,7 +385,7 @@ class LineFractions:
389
385
  else:
390
386
  high = 1.0
391
387
 
392
- fractions = clip(fractions, low, high)
388
+ fractions = np.clip(fractions, low, high)
393
389
 
394
390
  projected_points = fractions * segment_dirs
395
391
  projected_points += segment_starts
@@ -433,7 +429,7 @@ class LineFractions:
433
429
  d_to: float,
434
430
  distances: np.ndarray,
435
431
  extrapolate: bool = False,
436
- out: typing.Optional[ProjectedPoints] = None,
432
+ out: ProjectedPoints | None = None,
437
433
  ) -> ProjectedPoints:
438
434
  """Convenience method to project between two distances.
439
435
 
@@ -527,9 +523,9 @@ class LineFractions:
527
523
  i,
528
524
  segment_indices[keep],
529
525
  # clip to segments
530
- clip(min_fractions[keep], 0, 1),
531
- clip(closest_fractions[keep], 0, 1),
532
- clip(max_fractions[keep], 0, 1),
526
+ np.clip(min_fractions[keep], 0, 1),
527
+ np.clip(closest_fractions[keep], 0, 1),
528
+ np.clip(max_fractions[keep], 0, 1),
533
529
  )
534
530
  ball_slices.append(slfs)
535
531
  return ball_slices
@@ -726,14 +722,14 @@ def substrings(
726
722
  frac_start_ceil = np.ceil(frac_start)
727
723
  first = frac_start_ceil.astype(int)
728
724
  first[frac_start == frac_start_ceil] += 1
729
- clip(first, starts.from_vertices, starts.to_vertices + 1, out=first)
725
+ np.clip(first, starts.from_vertices, starts.to_vertices + 1, out=first)
730
726
 
731
727
  # the last indices that are not convered by end interpolation/extrapolation
732
728
  frac_end = ends.get_fractional_indices()
733
729
  frac_end_floor = np.floor(frac_end)
734
730
  last = frac_end_floor.astype(int)
735
731
  last[frac_end == frac_end_floor] -= 1
736
- clip(last, ends.from_vertices - 1, ends.to_vertices, out=last)
732
+ np.clip(last, ends.from_vertices - 1, ends.to_vertices, out=last)
737
733
 
738
734
  data_length = len(data)
739
735
  start_data = interpolate(data, starts)
@@ -1,40 +1,39 @@
1
+ import logging
1
2
  import typing
2
- from functools import partial
3
3
  from bisect import bisect_right
4
+ from functools import partial
4
5
  from operator import itemgetter
5
- import logging
6
6
 
7
- import pyproj
8
7
  import numpy as np
8
+ import pyproj
9
9
  from numpy.typing import ArrayLike
10
10
 
11
+ from . import (
12
+ EPSG4326,
13
+ EPSG4978,
14
+ BadShortestDistances,
15
+ ExtrapolationError,
16
+ SnappingError,
17
+ SnappingMethod,
18
+ )
11
19
  from .linear_referencing import (
20
+ LineFractions,
12
21
  Locations,
13
- substrings,
14
- locate,
15
- interpolate,
16
- ProjectionTarget,
17
22
  ProjectedPoints,
18
- LineFractions,
23
+ ProjectionTarget,
24
+ interpolate,
25
+ locate,
19
26
  resample,
27
+ substrings,
20
28
  )
21
29
  from .util import (
22
- get_trafo,
23
- transform_coords,
24
30
  array_chk,
25
- simplify_2d_keep_rest,
26
31
  cumulative_min_and_argmin,
27
- )
28
- from . import (
29
- SnappingError,
30
- BadShortestDistances,
31
- ExtrapolationError,
32
- EPSG4326,
33
- EPSG4978,
34
- SnappingMethod,
32
+ get_trafo,
33
+ simplify_2d_keep_rest,
34
+ transform_coords,
35
35
  )
36
36
 
37
-
38
37
  logger = logging.getLogger(__name__)
39
38
 
40
39
 
@@ -207,7 +206,7 @@ class DubiousTrajectory(XYZDMixin):
207
206
  xyzd: ArrayLike,
208
207
  crs: pyproj.CRS = EPSG4326,
209
208
  strict_axis_order: bool = False,
210
- simplify_tolerance: typing.Optional[float] = None,
209
+ simplify_tolerance: float | None = None,
211
210
  ) -> None:
212
211
  """Initialize DubiousTrajectory.
213
212
 
@@ -223,9 +222,7 @@ class DubiousTrajectory(XYZDMixin):
223
222
  raise ValueError("coords have to be finite")
224
223
 
225
224
  @classmethod
226
- def from_xyd_and_z(
227
- cls, xyd: ArrayLike, z: typing.Union[float, ArrayLike] = 0.0, **kwargs
228
- ):
225
+ def from_xyd_and_z(cls, xyd: ArrayLike, z: float | ArrayLike = 0.0, **kwargs):
229
226
  xyd_arr = np.asarray(xyd, dtype=float)
230
227
  xyzd = np.empty((xyd_arr.shape[0], 4))
231
228
  xyzd[:, :2] = xyd_arr[:, :2]
@@ -329,7 +326,7 @@ class Trajectory(XYZDMixin):
329
326
  __slots__ = ("_target",)
330
327
 
331
328
  # lazy data calculated when first needed
332
- _target: typing.Optional[ProjectionTarget]
329
+ _target: ProjectionTarget | None
333
330
 
334
331
  def __init__(self, xyz: ArrayLike) -> None:
335
332
  xyz = array_chk(xyz, ((2, None), 3), chk_finite=True, dtype=float, copy=False)
@@ -410,8 +407,8 @@ class TrajectoryTrip(XYZDMixin):
410
407
  trajectory: Trajectory
411
408
 
412
409
  # lazy data calculated when first needed
413
- _line_fractions: typing.Optional[LineFractions]
414
- _projected_points: typing.Optional[ProjectedPoints]
410
+ _line_fractions: LineFractions | None
411
+ _projected_points: ProjectedPoints | None
415
412
 
416
413
  def __init__(self, trajectory: Trajectory, xyzd: ArrayLike) -> None:
417
414
  self.trajectory = trajectory
@@ -490,7 +487,7 @@ class TrajectoryTrip(XYZDMixin):
490
487
  # list of (ppoints, cost, reverse_order) tuples for forward and backward
491
488
  # solution
492
489
  results: list[tuple[ProjectedPoints, float, bool]] = []
493
- forward_error: typing.Optional[SnappingError] = None
490
+ forward_error: SnappingError | None = None
494
491
  try:
495
492
  forward_result = self.route_candidates(params, ppoints_list) + (False,)
496
493
  except SnappingError as error:
@@ -531,9 +528,7 @@ class TrajectoryTrip(XYZDMixin):
531
528
  reverse_order=reverse_order,
532
529
  )
533
530
 
534
- def snap_trusted(
535
- self, params: SnappingParams
536
- ) -> list[typing.Optional[ProjectedPoints]]:
531
+ def snap_trusted(self, params: SnappingParams) -> list[ProjectedPoints | None]:
537
532
  """Determine which points are trusted and snap them to the trajectory.
538
533
 
539
534
  See `SnappingParams` for an explanation of trusted/untrusted. The list contains
@@ -546,7 +541,7 @@ class TrajectoryTrip(XYZDMixin):
546
541
  d_max=self.trajectory.d_max,
547
542
  consider_sampling_accuracy=True,
548
543
  )
549
- ppoints_list: list[typing.Optional[ProjectedPoints]] = [None] * len(self)
544
+ ppoints_list: list[ProjectedPoints | None] = [None] * len(self)
550
545
  if traj_spacing_ok(self.dists):
551
546
  trusted_mask = np.logical_not(np.isnan(self.dists))
552
547
  n_non_nan = trusted_mask.sum()
@@ -593,7 +588,7 @@ class TrajectoryTrip(XYZDMixin):
593
588
  )
594
589
  # if the spacing was not ok, it can still be bad after untrusting all points
595
590
  # since the total length can be too short for the points
596
- untrusted_dists = np.full_like(self.dists, np.NaN)
591
+ untrusted_dists = np.full_like(self.dists, np.nan)
597
592
  if not traj_spacing_ok(untrusted_dists):
598
593
  raise SnappingError(
599
594
  f"trajectory of length {self.trajectory.length:g} is too short "
@@ -606,7 +601,7 @@ class TrajectoryTrip(XYZDMixin):
606
601
  def set_untrusted_candidates_inplace(
607
602
  self,
608
603
  params: SnappingParams,
609
- ppoints_list: list[typing.Optional[ProjectedPoints]],
604
+ ppoints_list: list[ProjectedPoints | None],
610
605
  ) -> list[ProjectedPoints]:
611
606
  """Set untrusted snapping candidates inplace.
612
607
 
@@ -707,7 +702,7 @@ class TrajectoryTrip(XYZDMixin):
707
702
  reachable_costs = [[0.0]]
708
703
  # index into the lists from the previous layer for every entry in the current
709
704
  # layer for shortest path reconstruction
710
- reachable_predecessors: list[list[typing.Optional[int]]] = [[None]]
705
+ reachable_predecessors: list[list[int | None]] = [[None]]
711
706
 
712
707
  # artificial end layer connected to all reachable candidates of the real last
713
708
  # layer
@@ -767,7 +762,7 @@ class TrajectoryTrip(XYZDMixin):
767
762
  self,
768
763
  ppoints_list: list[ProjectedPoints],
769
764
  reachable_candidates: list[list[int]],
770
- reachable_predecessors: list[list[typing.Optional[int]]],
765
+ reachable_predecessors: list[list[int | None]],
771
766
  ) -> ProjectedPoints:
772
767
  winner_indices: list[int] = [-1] * len(ppoints_list)
773
768
  predecessor = 0
@@ -799,7 +794,7 @@ class SnappedTripPoints:
799
794
  snapped_points: ProjectedPoints,
800
795
  methods: ArrayLike,
801
796
  reverse_order: bool,
802
- distances: typing.Optional[np.ndarray] = None,
797
+ distances: np.ndarray | None = None,
803
798
  ):
804
799
  if len(snapped_points) != len(trip):
805
800
  raise ValueError("wrong number of snapped points for trip")