fastgpx 0.2.1__tar.gz → 0.3.0__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 (62) hide show
  1. {fastgpx-0.2.1 → fastgpx-0.3.0}/CMakeLists.txt +8 -0
  2. {fastgpx-0.2.1 → fastgpx-0.3.0}/Development.md +4 -0
  3. {fastgpx-0.2.1 → fastgpx-0.3.0}/PKG-INFO +14 -1
  4. {fastgpx-0.2.1 → fastgpx-0.3.0}/README.md +13 -0
  5. {fastgpx-0.2.1 → fastgpx-0.3.0}/pyproject.toml +1 -1
  6. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/CMakeLists.txt +11 -3
  7. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/fastgpx.cpp +5 -1
  8. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/fastgpx.hpp +1 -0
  9. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/polyline.cpp +8 -8
  10. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/python_fastgpx.cpp +7 -1
  11. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/python_utc_chrono.hpp +13 -1
  12. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/__init__.pyi +1 -1
  13. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/fastgpx/__init__.pyi +8 -0
  14. {fastgpx-0.2.1 → fastgpx-0.3.0}/tests/test_fastgpx.py +0 -1
  15. fastgpx-0.3.0/tests/test_time_bounds.py +73 -0
  16. {fastgpx-0.2.1 → fastgpx-0.3.0}/uv.lock +1 -1
  17. {fastgpx-0.2.1 → fastgpx-0.3.0}/.clang-format +0 -0
  18. {fastgpx-0.2.1 → fastgpx-0.3.0}/.gitignore +0 -0
  19. {fastgpx-0.2.1 → fastgpx-0.3.0}/.python-version +0 -0
  20. {fastgpx-0.2.1 → fastgpx-0.3.0}/CMakeSettings.json +0 -0
  21. {fastgpx-0.2.1 → fastgpx-0.3.0}/GPX.md +0 -0
  22. {fastgpx-0.2.1 → fastgpx-0.3.0}/LICENSE.md +0 -0
  23. {fastgpx-0.2.1 → fastgpx-0.3.0}/benchmarks/benchmark_gpx.py +0 -0
  24. {fastgpx-0.2.1 → fastgpx-0.3.0}/benchmarks/benchmark_polyline.py +0 -0
  25. {fastgpx-0.2.1 → fastgpx-0.3.0}/benchmarks/gpx_parse.md +0 -0
  26. {fastgpx-0.2.1 → fastgpx-0.3.0}/catch2.py +0 -0
  27. {fastgpx-0.2.1 → fastgpx-0.3.0}/coverage.bat +0 -0
  28. {fastgpx-0.2.1 → fastgpx-0.3.0}/datetime.md +0 -0
  29. {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/Makefile +0 -0
  30. {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/make.bat +0 -0
  31. {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/api.rst +0 -0
  32. {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/conf.py +0 -0
  33. {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/index.rst +0 -0
  34. {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/overview.rst +0 -0
  35. {fastgpx-0.2.1 → fastgpx-0.3.0}/profiling/profile_polyline.py +0 -0
  36. {fastgpx-0.2.1 → fastgpx-0.3.0}/pytest.ini +0 -0
  37. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/app.cpp +0 -0
  38. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/expected_gpx_data.json +0 -0
  39. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/datetime.cpp +0 -0
  40. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/datetime.hpp +0 -0
  41. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/datetime_test.cpp +0 -0
  42. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/errors.cpp +0 -0
  43. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/errors.hpp +0 -0
  44. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/errors_test.cpp +0 -0
  45. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/fastgpx_test.cpp +0 -0
  46. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/filesystem.cpp +0 -0
  47. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/filesystem.hpp +0 -0
  48. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/filesystem_test.cpp +0 -0
  49. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/geom.cpp +0 -0
  50. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/geom.hpp +0 -0
  51. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/geom_test.cpp +0 -0
  52. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/polyline.hpp +0 -0
  53. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data.cpp +0 -0
  54. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data.hpp +0 -0
  55. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data.json +0 -0
  56. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data_test.cpp +0 -0
  57. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/__init__.py +0 -0
  58. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/fastgpx/geo.pyi +0 -0
  59. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/fastgpx/polyline.pyi +0 -0
  60. {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/py.typed +0 -0
  61. {fastgpx-0.2.1 → fastgpx-0.3.0}/tests/test_bounds.py +0 -0
  62. {fastgpx-0.2.1 → fastgpx-0.3.0}/tests/test_polyline.py +0 -0
@@ -2,6 +2,14 @@ cmake_minimum_required(VERSION 3.30.2)
2
2
 
3
3
  project(fastgpx)
4
4
 
5
+ if(NOT DEFINED FASTGPX_WARNINGS_AS_ERRORS)
6
+ set(FASTGPX_WARNINGS_AS_ERRORS "$ENV{FASTGPX_WARNINGS_AS_ERRORS}")
7
+ endif()
8
+
9
+ option(FASTGPX_WARNINGS_AS_ERRORS "Treat C++ warnings as errors" ${FASTGPX_WARNINGS_AS_ERRORS})
10
+
11
+ message(STATUS "FASTGPX_WARNINGS_AS_ERRORS: ${FASTGPX_WARNINGS_AS_ERRORS}")
12
+
5
13
  # > The CTest module defines a BUILD_TESTING cache variable which defaults to true.
6
14
  # > It is used to decide whether the module calls enable_testing() or not, so the
7
15
  # > project does not have to make its own explicit call to enable_testing(). The
@@ -150,3 +150,7 @@ build\src\cpp\RelWithDebInfo\fastgpx_test.exe
150
150
  ```sh
151
151
  build\src\cpp\RelWithDebInfo\fastgpx_test.exe [!benchmark]
152
152
  ```
153
+
154
+ ## VSCode / CMake
155
+
156
+ Building directly with CMake require pybind11 installed. Currently this is defined as a build tool dependency in `pyproject.toml` and will therefore have to be manually installed into the `.venv` using `pip install pybind11`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastgpx
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: An experimental Python library for parsing GPX files fast.
5
5
  Keywords: gpx,parser,fast
6
6
  Author: Thomas Thomassen
@@ -45,6 +45,19 @@ for track in gpx.tracks:
45
45
 
46
46
  [Documentation](https://thomthom.github.io/fastgpx/)
47
47
 
48
+ ## Requirements
49
+
50
+ * Python 3.11+ (Tested with 3.11, 3.12)
51
+ * C++23 Compiler
52
+
53
+ ### Windows
54
+
55
+ * Tested with MSVC 17.12.4+ and Clang-cl 19+.
56
+
57
+ ### Linux (Tested on Ubuntu)
58
+
59
+ * C++23 compatible runtime (GCC libstdc++ 14+ or Clang libc++ 18.1+)
60
+
48
61
  ## GPX/XML Performance (Background)
49
62
 
50
63
  `gpxpy` appear to be the most popular GPX library for Python.
@@ -28,6 +28,19 @@ for track in gpx.tracks:
28
28
 
29
29
  [Documentation](https://thomthom.github.io/fastgpx/)
30
30
 
31
+ ## Requirements
32
+
33
+ * Python 3.11+ (Tested with 3.11, 3.12)
34
+ * C++23 Compiler
35
+
36
+ ### Windows
37
+
38
+ * Tested with MSVC 17.12.4+ and Clang-cl 19+.
39
+
40
+ ### Linux (Tested on Ubuntu)
41
+
42
+ * C++23 compatible runtime (GCC libstdc++ 14+ or Clang libc++ 18.1+)
43
+
31
44
  ## GPX/XML Performance (Background)
32
45
 
33
46
  `gpxpy` appear to be the most popular GPX library for Python.
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "fastgpx"
7
- version = "0.2.1"
7
+ version = "0.3.0"
8
8
  requires-python = ">=3.11"
9
9
 
10
10
  description = "An experimental Python library for parsing GPX files fast."
@@ -40,12 +40,16 @@ function(set_common_properties TARGET)
40
40
  # Elevate warning level.
41
41
  # Ignore unknown pragmas (mac).
42
42
  target_compile_options(${TARGET} PRIVATE
43
- /W4 /WX
43
+ /W4
44
44
  /wd4068
45
45
  )
46
+ if(FASTGPX_WARNINGS_AS_ERRORS)
47
+ target_compile_options(${TARGET} PRIVATE /WX)
48
+ endif()
46
49
 
47
50
  if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
48
- # Clang pretending to be MSVC – add Clang-specific warnings
51
+ # Clang pretending to be MSVC – add Clang-specific warnings what /W4 does
52
+ # not cover.
49
53
  target_compile_options(${TARGET} PRIVATE
50
54
  -Wshadow -Wconversion
51
55
  -Wno-unknown-pragmas
@@ -59,14 +63,18 @@ function(set_common_properties TARGET)
59
63
  # Ensure MSVC report up to date version for __cplusplus macro.
60
64
  # https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-160
61
65
  target_compile_options(${TARGET} PRIVATE /Zc:__cplusplus)
66
+
62
67
  else()
63
68
  # Elevate warning level.
64
69
  # Ignore unknown pragmas (win).
65
70
  target_compile_options(${TARGET} PRIVATE
66
- -Wall -Wextra -pedantic -Werror
71
+ -Wall -Wextra -pedantic
67
72
  -Wshadow -Wconversion
68
73
  -Wno-unknown-pragmas
69
74
  )
75
+ if(FASTGPX_WARNINGS_AS_ERRORS)
76
+ target_compile_options(${TARGET} PRIVATE -Werror)
77
+ endif()
70
78
  endif()
71
79
  endfunction()
72
80
 
@@ -43,10 +43,14 @@ std::chrono::system_clock::time_point TimePoint::value() const
43
43
 
44
44
  bool TimeBounds::IsEmpty() const
45
45
  {
46
- assert(start_time.has_value() == end_time.has_value());
47
46
  return !start_time.has_value() && !end_time.has_value();
48
47
  }
49
48
 
49
+ bool TimeBounds::IsRange() const
50
+ {
51
+ return start_time.has_value() && end_time.has_value();
52
+ }
53
+
50
54
  void TimeBounds::Add(const std::chrono::system_clock::time_point time_point)
51
55
  {
52
56
  if (start_time.has_value())
@@ -28,6 +28,7 @@ struct TimeBounds
28
28
  std::optional<std::chrono::system_clock::time_point> end_time = std::nullopt;
29
29
 
30
30
  bool IsEmpty() const;
31
+ bool IsRange() const;
31
32
 
32
33
  void Add(std::chrono::system_clock::time_point time_point);
33
34
  void Add(const TimeBounds& time_bounds);
@@ -12,7 +12,7 @@ namespace polyline {
12
12
  std::string encode(std::span<const LatLong> locations, Precision precision)
13
13
  {
14
14
  const int factor = (precision == Precision::Six) ? 1'000'000 : 100'000;
15
- std::string encoded;
15
+ std::string encoded_polyline;
16
16
  int last_lat = 0;
17
17
  int last_lng = 0;
18
18
 
@@ -25,25 +25,25 @@ std::string encode(std::span<const LatLong> locations, Precision precision)
25
25
  const int delta_lng = lng - last_lng;
26
26
 
27
27
  auto encode_value = [](int value) -> std::string {
28
- std::string encoded;
28
+ std::string encoded_latlong;
29
29
  value = (value < 0) ? ~(value << 1) : (value << 1);
30
30
  while (value >= 0x20)
31
31
  {
32
- encoded += static_cast<char>((0x20 | (value & 0x1f)) + 63);
32
+ encoded_latlong += static_cast<char>((0x20 | (value & 0x1f)) + 63);
33
33
  value >>= 5;
34
34
  }
35
- encoded += static_cast<char>(value + 63);
36
- return encoded;
35
+ encoded_latlong += static_cast<char>(value + 63);
36
+ return encoded_latlong;
37
37
  };
38
38
 
39
- encoded += encode_value(delta_lat);
40
- encoded += encode_value(delta_lng);
39
+ encoded_polyline += encode_value(delta_lat);
40
+ encoded_polyline += encode_value(delta_lng);
41
41
 
42
42
  last_lat = lat;
43
43
  last_lng = lng;
44
44
  }
45
45
 
46
- return encoded;
46
+ return encoded_polyline;
47
47
  }
48
48
 
49
49
  std::vector<LatLong> decode(std::string_view encoded, Precision precision)
@@ -53,7 +53,13 @@ PYBIND11_MODULE(fastgpx, m)
53
53
  py::arg("end_time"))
54
54
  .def_readwrite("start_time", &fastgpx::TimeBounds::start_time)
55
55
  .def_readwrite("end_time", &fastgpx::TimeBounds::end_time)
56
- .def("is_empty", &fastgpx::TimeBounds::IsEmpty);
56
+ .def("is_empty", &fastgpx::TimeBounds::IsEmpty)
57
+ .def("is_range", &fastgpx::TimeBounds::IsRange)
58
+ .def("add",
59
+ py::overload_cast<std::chrono::system_clock::time_point>(&fastgpx::TimeBounds::Add),
60
+ py::arg("datetime"))
61
+ .def("add", py::overload_cast<const fastgpx::TimeBounds&>(&fastgpx::TimeBounds::Add),
62
+ py::arg("timebounds"));
57
63
 
58
64
  py::class_<fastgpx::LatLong>(m, "LatLong")
59
65
  .def(py::init<>())
@@ -27,6 +27,15 @@
27
27
  namespace PYBIND11_NAMESPACE {
28
28
  namespace detail {
29
29
 
30
+ inline std::time_t to_utc_time_t(std::tm* tm)
31
+ {
32
+ #if defined(_WIN32)
33
+ return _mkgmtime(tm);
34
+ #else
35
+ return timegm(tm);
36
+ #endif
37
+ }
38
+
30
39
  template<typename type>
31
40
  class duration_caster
32
41
  {
@@ -195,7 +204,10 @@ public:
195
204
  return false;
196
205
  }
197
206
 
198
- value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
207
+ // HACK: Assume datetime.datetime objects are in UTC, so we use timegm instead of mktime.
208
+ // value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
209
+ std::time_t tt = to_utc_time_t(&cal);
210
+ value = time_point_cast<Duration>(system_clock::from_time_t(tt) + msecs);
199
211
  return true;
200
212
  }
201
213
 
@@ -9,4 +9,4 @@ from fastgpx.fastgpx import geo
9
9
  from fastgpx.fastgpx import parse
10
10
  from fastgpx.fastgpx import polyline
11
11
  from . import fastgpx
12
- __all__ = ['Bounds', 'Gpx', 'LatLong', 'Segment', 'TimeBounds', 'Track', 'fastgpx', 'geo', 'parse', 'polyline']
12
+ __all__: list = ['Bounds', 'Gpx', 'LatLong', 'Segment', 'TimeBounds', 'Track', 'geo', 'parse', 'polyline']
@@ -101,8 +101,16 @@ class TimeBounds:
101
101
  @typing.overload
102
102
  def __init__(self, start_time: datetime.datetime | None, end_time: datetime.datetime | None) -> None:
103
103
  ...
104
+ @typing.overload
105
+ def add(self, datetime: datetime.datetime) -> None:
106
+ ...
107
+ @typing.overload
108
+ def add(self, timebounds: TimeBounds) -> None:
109
+ ...
104
110
  def is_empty(self) -> bool:
105
111
  ...
112
+ def is_range(self) -> bool:
113
+ ...
106
114
  class Track:
107
115
  comment: str | None
108
116
  description: str | None
@@ -1,7 +1,6 @@
1
1
  import datetime
2
2
 
3
3
  import gpxpy
4
- import gpxpy.gpx
5
4
  import pytest
6
5
 
7
6
  import fastgpx
@@ -0,0 +1,73 @@
1
+ from datetime import datetime
2
+
3
+ import fastgpx
4
+
5
+
6
+ class TestTimeBounds:
7
+
8
+ def test_init_defaults(self):
9
+ bounds = fastgpx.TimeBounds()
10
+ assert bounds.is_empty()
11
+ assert not bounds.is_range()
12
+ assert bounds.start_time is None
13
+ assert bounds.end_time is None
14
+
15
+ def test_init_with_start_time(self):
16
+ start_time = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
17
+ bounds = fastgpx.TimeBounds(start_time=start_time, end_time=None)
18
+ assert not bounds.is_empty()
19
+ assert not bounds.is_range()
20
+ assert bounds.start_time == start_time
21
+ assert bounds.end_time is None
22
+
23
+ def test_init_with_end_time(self):
24
+ end_time = datetime.fromisoformat("2025-06-27 13:37:00+00:00")
25
+ bounds = fastgpx.TimeBounds(start_time=None, end_time=end_time)
26
+ assert not bounds.is_empty()
27
+ assert not bounds.is_range()
28
+ assert bounds.start_time is None
29
+ assert bounds.end_time == end_time
30
+
31
+ def test_init_with_start_and_end_time(self):
32
+ start_time = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
33
+ end_time = datetime.fromisoformat("2025-06-27 13:37:00+00:00")
34
+ bounds = fastgpx.TimeBounds(start_time=start_time, end_time=end_time)
35
+ assert not bounds.is_empty()
36
+ assert bounds.is_range()
37
+ assert bounds.start_time == start_time
38
+ assert bounds.end_time == end_time
39
+
40
+ def test_add_single_time_point(self):
41
+ bounds = fastgpx.TimeBounds()
42
+ time_point = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
43
+ bounds.add(time_point)
44
+ assert not bounds.is_empty()
45
+ assert bounds.is_range()
46
+ assert bounds.start_time == time_point
47
+ assert bounds.end_time == time_point
48
+
49
+ def test_add_second_time_point(self):
50
+ bounds = fastgpx.TimeBounds()
51
+ time_point1 = datetime.fromisoformat("2025-06-20 08:07:28+00:00")
52
+ time_point2 = datetime.fromisoformat("2025-06-27 13:37:00+00:00")
53
+ bounds.add(time_point1)
54
+ bounds.add(time_point2)
55
+ assert not bounds.is_empty()
56
+ assert bounds.is_range()
57
+ assert bounds.start_time == time_point1
58
+ assert bounds.end_time == time_point2
59
+
60
+ def test_add_time_bounds(self):
61
+ bounds1 = fastgpx.TimeBounds(
62
+ start_time=datetime.fromisoformat("2025-06-20 08:07:28+00:00"),
63
+ end_time=datetime.fromisoformat("2025-06-27 13:37:00+00:00")
64
+ )
65
+ bounds2 = fastgpx.TimeBounds(
66
+ start_time=datetime.fromisoformat("2025-06-25 10:00:00+00:00"),
67
+ end_time=datetime.fromisoformat("2025-06-30 15:00:00+00:00")
68
+ )
69
+ bounds1.add(bounds2)
70
+ assert not bounds1.is_empty()
71
+ assert bounds1.is_range()
72
+ assert bounds1.start_time == bounds1.start_time
73
+ assert bounds1.end_time == bounds2.end_time
@@ -97,7 +97,7 @@ wheels = [
97
97
 
98
98
  [[package]]
99
99
  name = "fastgpx"
100
- version = "0.0.1.dev0"
100
+ version = "0.3.0"
101
101
  source = { editable = "." }
102
102
 
103
103
  [package.dev-dependencies]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes