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.
- {fastgpx-0.2.1 → fastgpx-0.3.0}/CMakeLists.txt +8 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/Development.md +4 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/PKG-INFO +14 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/README.md +13 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/pyproject.toml +1 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/CMakeLists.txt +11 -3
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/fastgpx.cpp +5 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/fastgpx.hpp +1 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/polyline.cpp +8 -8
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/python_fastgpx.cpp +7 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/python_utc_chrono.hpp +13 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/__init__.pyi +1 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/fastgpx/__init__.pyi +8 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/tests/test_fastgpx.py +0 -1
- fastgpx-0.3.0/tests/test_time_bounds.py +73 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/uv.lock +1 -1
- {fastgpx-0.2.1 → fastgpx-0.3.0}/.clang-format +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/.gitignore +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/.python-version +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/CMakeSettings.json +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/GPX.md +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/LICENSE.md +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/benchmarks/benchmark_gpx.py +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/benchmarks/benchmark_polyline.py +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/benchmarks/gpx_parse.md +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/catch2.py +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/coverage.bat +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/datetime.md +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/Makefile +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/make.bat +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/api.rst +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/conf.py +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/index.rst +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/docs/source/overview.rst +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/profiling/profile_polyline.py +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/pytest.ini +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/app.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/expected_gpx_data.json +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/datetime.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/datetime.hpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/datetime_test.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/errors.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/errors.hpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/errors_test.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/fastgpx_test.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/filesystem.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/filesystem.hpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/filesystem_test.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/geom.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/geom.hpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/geom_test.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/polyline.hpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data.hpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data.json +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/cpp/fastgpx/test_data_test.cpp +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/__init__.py +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/fastgpx/geo.pyi +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/fastgpx/polyline.pyi +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/src/fastgpx/py.typed +0 -0
- {fastgpx-0.2.1 → fastgpx-0.3.0}/tests/test_bounds.py +0 -0
- {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.
|
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.
|
@@ -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
|
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
|
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())
|
@@ -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
|
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
|
28
|
+
std::string encoded_latlong;
|
29
29
|
value = (value < 0) ? ~(value << 1) : (value << 1);
|
30
30
|
while (value >= 0x20)
|
31
31
|
{
|
32
|
-
|
32
|
+
encoded_latlong += static_cast<char>((0x20 | (value & 0x1f)) + 63);
|
33
33
|
value >>= 5;
|
34
34
|
}
|
35
|
-
|
36
|
-
return
|
35
|
+
encoded_latlong += static_cast<char>(value + 63);
|
36
|
+
return encoded_latlong;
|
37
37
|
};
|
38
38
|
|
39
|
-
|
40
|
-
|
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
|
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
|
-
|
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', '
|
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
|
@@ -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
|
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
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|