pyroutingkit 0.1.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.
- pyroutingkit-0.1.0/.github/workflows/publish.yml +71 -0
- pyroutingkit-0.1.0/.gitignore +8 -0
- pyroutingkit-0.1.0/.gitmodules +3 -0
- pyroutingkit-0.1.0/CMakeLists.txt +40 -0
- pyroutingkit-0.1.0/PKG-INFO +103 -0
- pyroutingkit-0.1.0/README.md +87 -0
- pyroutingkit-0.1.0/pyproject.toml +36 -0
- pyroutingkit-0.1.0/src/pyroutingkit/__init__.py +17 -0
- pyroutingkit-0.1.0/src/routingkit_module.cpp +263 -0
- pyroutingkit-0.1.0/tests/test_cch.py +85 -0
- pyroutingkit-0.1.0/uv.lock +312 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/.gitignore +3 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/.travis.yml +98 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/LICENSE +22 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/Makefile +486 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/README.md +99 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/doc/ContractionHierarchy.md +439 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/doc/CoordinatesToNodeID.md +36 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/doc/CustomizableContractionHierarchy.md +139 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/doc/OpenStreetMap.md +371 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/doc/Setup.md +102 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/doc/SupportFunctions.md +274 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/generate_make_file +316 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/all.h +32 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/bit_vector.h +154 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/constants.h +14 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/contraction_hierarchy.h +759 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/customizable_contraction_hierarchy.h +180 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/dijkstra.h +176 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/filter.h +56 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/geo_dist.h +128 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/geo_position_to_node.h +43 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/google_polyline.h +76 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/graph_util.h +68 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/id_mapper.h +65 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/id_queue.h +181 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/id_set_queue.h +234 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/inverse_vector.h +58 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/min_max.h +70 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/nested_dissection.h +122 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/osm_decoder.h +50 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/osm_graph_builder.h +122 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/osm_profile.h +29 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/osm_simple.h +94 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/permutation.h +165 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/sort.h +380 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/strongly_connected_component.h +25 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/tag_map.h +127 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/timer.h +10 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/timestamp_flag.h +42 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/include/routingkit/vector_io.h +177 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/bit_select.cpp +101 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/bit_select.h +17 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/bit_vector.cpp +529 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/buffered_asynchronous_reader.cpp +186 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/buffered_asynchronous_reader.h +35 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/compare_vector.cpp +137 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/compute_contraction_hierarchy.cpp +71 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/compute_geographic_distance_weights.cpp +74 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/compute_nested_dissection_order.cpp +61 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/contraction_hierarchy.cpp +2217 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/convert_road_dimacs_coordinates.cpp +105 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/convert_road_dimacs_graph.cpp +114 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/customizable_contraction_hierarchy.cpp +1920 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/decode_vector.cpp +87 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/emulate_gcc_builtin.h +62 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/encode_vector.cpp +115 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/examine_ch.cpp +46 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/expect.cpp +5 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/expect.h +38 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/export_road_dimacs_graph.cpp +77 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/file_data_source.cpp +188 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/file_data_source.h +53 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/generate_constant_vector.cpp +43 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/generate_dijkstra_rank_test_queries.cpp +110 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/generate_random_node_list.cpp +54 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/generate_random_source_times.cpp +54 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/generate_test_queries.cpp +56 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/geo_position_to_node.cpp +232 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/google_polyline.cpp +88 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/graph_to_dot.cpp +59 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/graph_to_svg.cpp +85 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/graph_util.cpp +167 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/id_mapper.cpp +129 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/nested_dissection.cpp +883 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/osm_decoder.cpp +765 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/osm_extract.cpp +144 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/osm_graph_builder.cpp +834 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/osm_profile.cpp +803 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/osm_simple.cpp +155 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/osmpbfformat.proto +78 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/protobuf.cpp +37 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/protobuf.h +54 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/randomly_permute_nodes.cpp +98 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/run_contraction_hierarchy_query.cpp +78 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/run_dijkstra.cpp +117 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/show_path.cpp +76 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/strongly_connected_component.cpp +99 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_basic_features.cpp +783 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_bit_vector.cpp +484 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_buffered_asynchronous_reader.cpp +116 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_contraction_hierarchy_extra_weight.cpp +862 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_contraction_hierarchy_path_query.cpp +165 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_contraction_hierarchy_pinned_query.cpp +169 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_customizable_contraction_hierarchy.cpp +60 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_customization.cpp +148 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_path_query.cpp +171 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_perfect_customization.cpp +172 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_pinned_query.cpp +204 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_customizable_contraction_hierarchy_reset.cpp +316 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_dijkstra.cpp +170 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_geo_dist.cpp +138 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_google_polyline.cpp +100 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_id_mapper.cpp +335 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_id_set_queue.cpp +63 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_inverse_vector.cpp +42 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_nearest_neighbor.cpp +167 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_nested_dissection.cpp +421 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_osm_simple.cpp +74 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_permutation.cpp +73 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_protobuf.cpp +117 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_sort.cpp +453 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_strongly_connected_component.cpp +73 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/test_tag_map.cpp +49 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/timer.cpp +18 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/vector_io.cpp +64 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/verify.cpp +95 -0
- pyroutingkit-0.1.0/vendor/RoutingKit/src/verify.h +29 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: Build & Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
wheels:
|
|
10
|
+
name: Build wheels (${{ matrix.os }})
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-14]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
submodules: recursive
|
|
20
|
+
|
|
21
|
+
- name: Build wheels
|
|
22
|
+
uses: pypa/cibuildwheel@v2.22
|
|
23
|
+
env:
|
|
24
|
+
CIBW_BUILD: cp310-* cp311-* cp312-* cp313-*
|
|
25
|
+
CIBW_SKIP: "*-win32 *-manylinux_i686 *musllinux* pp*"
|
|
26
|
+
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
|
|
27
|
+
CIBW_TEST_COMMAND: python -c "from pyroutingkit import CCH, INF_WEIGHT; print('OK')"
|
|
28
|
+
|
|
29
|
+
- uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: wheels-${{ matrix.os }}
|
|
32
|
+
path: wheelhouse/*.whl
|
|
33
|
+
|
|
34
|
+
sdist:
|
|
35
|
+
name: Build sdist
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
with:
|
|
40
|
+
submodules: recursive
|
|
41
|
+
|
|
42
|
+
- name: Install uv
|
|
43
|
+
uses: astral-sh/setup-uv@v4
|
|
44
|
+
|
|
45
|
+
- name: Build sdist
|
|
46
|
+
run: uv build --sdist
|
|
47
|
+
|
|
48
|
+
- uses: actions/upload-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: sdist
|
|
51
|
+
path: dist/*.tar.gz
|
|
52
|
+
|
|
53
|
+
publish:
|
|
54
|
+
name: Publish to PyPI
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
needs: [wheels, sdist]
|
|
57
|
+
permissions:
|
|
58
|
+
id-token: write # Required for trusted publishing
|
|
59
|
+
|
|
60
|
+
steps:
|
|
61
|
+
- name: Download all artifacts
|
|
62
|
+
uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
path: dist
|
|
65
|
+
merge-multiple: true
|
|
66
|
+
|
|
67
|
+
- name: Publish to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
69
|
+
# Uses trusted publishing (OIDC) — no API token needed.
|
|
70
|
+
# Configure at: https://pypi.org/manage/account/publishing/
|
|
71
|
+
# Publisher: GitHub, repo: nullbutt/pyroutingkit, workflow: publish.yml
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.20)
|
|
2
|
+
project(pyroutingkit LANGUAGES CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
7
|
+
|
|
8
|
+
find_package(pybind11 REQUIRED)
|
|
9
|
+
|
|
10
|
+
# RoutingKit source files (only what we need for CCH)
|
|
11
|
+
set(ROUTINGKIT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/RoutingKit")
|
|
12
|
+
|
|
13
|
+
set(ROUTINGKIT_SOURCES
|
|
14
|
+
${ROUTINGKIT_DIR}/src/bit_select.cpp
|
|
15
|
+
${ROUTINGKIT_DIR}/src/bit_vector.cpp
|
|
16
|
+
${ROUTINGKIT_DIR}/src/customizable_contraction_hierarchy.cpp
|
|
17
|
+
${ROUTINGKIT_DIR}/src/graph_util.cpp
|
|
18
|
+
${ROUTINGKIT_DIR}/src/id_mapper.cpp
|
|
19
|
+
${ROUTINGKIT_DIR}/src/nested_dissection.cpp
|
|
20
|
+
${ROUTINGKIT_DIR}/src/contraction_hierarchy.cpp
|
|
21
|
+
${ROUTINGKIT_DIR}/src/strongly_connected_component.cpp
|
|
22
|
+
${ROUTINGKIT_DIR}/src/timer.cpp
|
|
23
|
+
${ROUTINGKIT_DIR}/src/geo_position_to_node.cpp
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Build RoutingKit as a static library
|
|
27
|
+
add_library(routingkit_static STATIC ${ROUTINGKIT_SOURCES})
|
|
28
|
+
target_include_directories(routingkit_static PUBLIC ${ROUTINGKIT_DIR}/include)
|
|
29
|
+
target_compile_options(routingkit_static PRIVATE -O3)
|
|
30
|
+
|
|
31
|
+
if(APPLE)
|
|
32
|
+
target_compile_options(routingkit_static PRIVATE -Wno-deprecated -Wno-unused-parameter)
|
|
33
|
+
endif()
|
|
34
|
+
|
|
35
|
+
# pybind11 module
|
|
36
|
+
pybind11_add_module(_core src/routingkit_module.cpp)
|
|
37
|
+
target_link_libraries(_core PRIVATE routingkit_static)
|
|
38
|
+
target_include_directories(_core PRIVATE ${ROUTINGKIT_DIR}/include)
|
|
39
|
+
|
|
40
|
+
install(TARGETS _core DESTINATION pyroutingkit)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyroutingkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python bindings for RoutingKit Customizable Contraction Hierarchies (CCH)
|
|
5
|
+
Author: Ryan Fisk
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: C++
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
12
|
+
Project-URL: Repository, https://github.com/nullbutt/pyroutingkit
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: numpy>=1.24
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# pyroutingkit
|
|
18
|
+
|
|
19
|
+
Python bindings for [RoutingKit](https://github.com/RoutingKit/RoutingKit) Customizable Contraction Hierarchies (CCH).
|
|
20
|
+
|
|
21
|
+
Provides **microsecond-scale shortest-path queries** on large road networks (millions of nodes). Pre-compiled wheels for Linux (manylinux) and macOS — no C++ toolchain needed at install time.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pyroutingkit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import numpy as np
|
|
33
|
+
from pyroutingkit import CCH, INF_WEIGHT
|
|
34
|
+
|
|
35
|
+
# Build topology (one-time, expensive — can be saved/loaded)
|
|
36
|
+
cch = CCH()
|
|
37
|
+
tail = np.array([0, 1, 2], dtype=np.uint32)
|
|
38
|
+
head = np.array([1, 2, 0], dtype=np.uint32)
|
|
39
|
+
lat = np.array([9.0, 9.5, 9.2], dtype=np.float32)
|
|
40
|
+
lon = np.array([38.7, 39.0, 38.5], dtype=np.float32)
|
|
41
|
+
cch.build_topology(tail, head, lat, lon, node_count=3)
|
|
42
|
+
|
|
43
|
+
# Customize with weights (fast, can be called multiple times)
|
|
44
|
+
weights = np.array([100, 150, 120], dtype=np.uint32)
|
|
45
|
+
cch.customize_weights(weights)
|
|
46
|
+
|
|
47
|
+
# Query (microseconds)
|
|
48
|
+
distance, path = cch.query(0, 2)
|
|
49
|
+
print(f"Distance: {distance}, Path: {path}")
|
|
50
|
+
|
|
51
|
+
# Many-to-many matrix
|
|
52
|
+
sources = np.array([0, 1], dtype=np.uint32)
|
|
53
|
+
targets = np.array([1, 2], dtype=np.uint32)
|
|
54
|
+
matrix = cch.distances_many_to_many(sources, targets)
|
|
55
|
+
|
|
56
|
+
# Save/load topology (skip expensive build on next run)
|
|
57
|
+
cch.save_topology("/tmp/cch_cache")
|
|
58
|
+
cch2 = CCH()
|
|
59
|
+
cch2.load_topology("/tmp/cch_cache")
|
|
60
|
+
cch2.customize_weights(weights) # Must re-customize after load
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
### `CCH` class
|
|
66
|
+
|
|
67
|
+
| Method | Description |
|
|
68
|
+
|--------|-------------|
|
|
69
|
+
| `build_topology(tail, head, lat, lon, node_count)` | Build CCH from graph arrays. Uses inertial flow for node ordering. |
|
|
70
|
+
| `customize_weights(weights)` | Set edge weights (uint32). Can be called multiple times. |
|
|
71
|
+
| `query(source, target)` | Point-to-point query. Returns `(distance, path)`. |
|
|
72
|
+
| `distances_many_to_many(sources, targets)` | NxM distance matrix. |
|
|
73
|
+
| `save_topology(dir_path)` | Save topology to disk (binary format). |
|
|
74
|
+
| `load_topology(dir_path)` | Load topology from disk (skips build). |
|
|
75
|
+
|
|
76
|
+
### Properties
|
|
77
|
+
|
|
78
|
+
| Property | Type | Description |
|
|
79
|
+
|----------|------|-------------|
|
|
80
|
+
| `node_count` | `int` | Number of nodes |
|
|
81
|
+
| `arc_count` | `int` | Number of directed edges |
|
|
82
|
+
| `is_built` | `bool` | Topology has been built or loaded |
|
|
83
|
+
| `is_customized` | `bool` | Weights have been set |
|
|
84
|
+
|
|
85
|
+
### Constants
|
|
86
|
+
|
|
87
|
+
| Constant | Description |
|
|
88
|
+
|----------|-------------|
|
|
89
|
+
| `INF_WEIGHT` | Sentinel value for unreachable pairs |
|
|
90
|
+
|
|
91
|
+
## Building from source
|
|
92
|
+
|
|
93
|
+
Requires CMake 3.20+ and a C++17 compiler.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git clone --recurse-submodules https://github.com/nullbutt/pyroutingkit
|
|
97
|
+
cd pyroutingkit
|
|
98
|
+
uv build
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT. RoutingKit is also MIT-licensed.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# pyroutingkit
|
|
2
|
+
|
|
3
|
+
Python bindings for [RoutingKit](https://github.com/RoutingKit/RoutingKit) Customizable Contraction Hierarchies (CCH).
|
|
4
|
+
|
|
5
|
+
Provides **microsecond-scale shortest-path queries** on large road networks (millions of nodes). Pre-compiled wheels for Linux (manylinux) and macOS — no C++ toolchain needed at install time.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pyroutingkit
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import numpy as np
|
|
17
|
+
from pyroutingkit import CCH, INF_WEIGHT
|
|
18
|
+
|
|
19
|
+
# Build topology (one-time, expensive — can be saved/loaded)
|
|
20
|
+
cch = CCH()
|
|
21
|
+
tail = np.array([0, 1, 2], dtype=np.uint32)
|
|
22
|
+
head = np.array([1, 2, 0], dtype=np.uint32)
|
|
23
|
+
lat = np.array([9.0, 9.5, 9.2], dtype=np.float32)
|
|
24
|
+
lon = np.array([38.7, 39.0, 38.5], dtype=np.float32)
|
|
25
|
+
cch.build_topology(tail, head, lat, lon, node_count=3)
|
|
26
|
+
|
|
27
|
+
# Customize with weights (fast, can be called multiple times)
|
|
28
|
+
weights = np.array([100, 150, 120], dtype=np.uint32)
|
|
29
|
+
cch.customize_weights(weights)
|
|
30
|
+
|
|
31
|
+
# Query (microseconds)
|
|
32
|
+
distance, path = cch.query(0, 2)
|
|
33
|
+
print(f"Distance: {distance}, Path: {path}")
|
|
34
|
+
|
|
35
|
+
# Many-to-many matrix
|
|
36
|
+
sources = np.array([0, 1], dtype=np.uint32)
|
|
37
|
+
targets = np.array([1, 2], dtype=np.uint32)
|
|
38
|
+
matrix = cch.distances_many_to_many(sources, targets)
|
|
39
|
+
|
|
40
|
+
# Save/load topology (skip expensive build on next run)
|
|
41
|
+
cch.save_topology("/tmp/cch_cache")
|
|
42
|
+
cch2 = CCH()
|
|
43
|
+
cch2.load_topology("/tmp/cch_cache")
|
|
44
|
+
cch2.customize_weights(weights) # Must re-customize after load
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### `CCH` class
|
|
50
|
+
|
|
51
|
+
| Method | Description |
|
|
52
|
+
|--------|-------------|
|
|
53
|
+
| `build_topology(tail, head, lat, lon, node_count)` | Build CCH from graph arrays. Uses inertial flow for node ordering. |
|
|
54
|
+
| `customize_weights(weights)` | Set edge weights (uint32). Can be called multiple times. |
|
|
55
|
+
| `query(source, target)` | Point-to-point query. Returns `(distance, path)`. |
|
|
56
|
+
| `distances_many_to_many(sources, targets)` | NxM distance matrix. |
|
|
57
|
+
| `save_topology(dir_path)` | Save topology to disk (binary format). |
|
|
58
|
+
| `load_topology(dir_path)` | Load topology from disk (skips build). |
|
|
59
|
+
|
|
60
|
+
### Properties
|
|
61
|
+
|
|
62
|
+
| Property | Type | Description |
|
|
63
|
+
|----------|------|-------------|
|
|
64
|
+
| `node_count` | `int` | Number of nodes |
|
|
65
|
+
| `arc_count` | `int` | Number of directed edges |
|
|
66
|
+
| `is_built` | `bool` | Topology has been built or loaded |
|
|
67
|
+
| `is_customized` | `bool` | Weights have been set |
|
|
68
|
+
|
|
69
|
+
### Constants
|
|
70
|
+
|
|
71
|
+
| Constant | Description |
|
|
72
|
+
|----------|-------------|
|
|
73
|
+
| `INF_WEIGHT` | Sentinel value for unreachable pairs |
|
|
74
|
+
|
|
75
|
+
## Building from source
|
|
76
|
+
|
|
77
|
+
Requires CMake 3.20+ and a C++17 compiler.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git clone --recurse-submodules https://github.com/nullbutt/pyroutingkit
|
|
81
|
+
cd pyroutingkit
|
|
82
|
+
uv build
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT. RoutingKit is also MIT-licensed.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyroutingkit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python bindings for RoutingKit Customizable Contraction Hierarchies (CCH)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Ryan Fisk" },
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: C++",
|
|
15
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
16
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"numpy>=1.24",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Repository = "https://github.com/nullbutt/pyroutingkit"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["scikit-build-core>=0.10", "pybind11>=2.13"]
|
|
27
|
+
build-backend = "scikit_build_core.build"
|
|
28
|
+
|
|
29
|
+
[tool.scikit-build]
|
|
30
|
+
cmake.source-dir = "."
|
|
31
|
+
wheel.packages = ["src/pyroutingkit"]
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=9.0.2",
|
|
36
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyroutingkit: Python bindings for RoutingKit Customizable Contraction Hierarchies.
|
|
3
|
+
|
|
4
|
+
Provides microsecond-scale shortest-path queries on large road networks.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from pyroutingkit import CCH, INF_WEIGHT
|
|
8
|
+
|
|
9
|
+
cch = CCH()
|
|
10
|
+
cch.build_topology(tail, head, latitude, longitude, node_count)
|
|
11
|
+
cch.customize_weights(weights_uint32)
|
|
12
|
+
distance, path = cch.query(source, target)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pyroutingkit._core import CCH, INF_WEIGHT
|
|
16
|
+
|
|
17
|
+
__all__ = ["CCH", "INF_WEIGHT"]
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pybind11 wrapper for RoutingKit's Customizable Contraction Hierarchy (CCH).
|
|
3
|
+
*
|
|
4
|
+
* Exposes:
|
|
5
|
+
* - CCH topology construction (compute nested dissection order + build CCH)
|
|
6
|
+
* - Metric customization (uint32 weights)
|
|
7
|
+
* - Point-to-point query (distance + node path)
|
|
8
|
+
* - Many-to-many distance matrix
|
|
9
|
+
* - Topology serialization (save/load as binary vectors)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include <pybind11/pybind11.h>
|
|
13
|
+
#include <pybind11/numpy.h>
|
|
14
|
+
#include <pybind11/stl.h>
|
|
15
|
+
|
|
16
|
+
#include <routingkit/customizable_contraction_hierarchy.h>
|
|
17
|
+
#include <routingkit/nested_dissection.h>
|
|
18
|
+
#include <routingkit/constants.h>
|
|
19
|
+
#include <routingkit/vector_io.h>
|
|
20
|
+
|
|
21
|
+
#include <vector>
|
|
22
|
+
#include <string>
|
|
23
|
+
#include <stdexcept>
|
|
24
|
+
#include <fstream>
|
|
25
|
+
#include <cstdint>
|
|
26
|
+
|
|
27
|
+
namespace py = pybind11;
|
|
28
|
+
|
|
29
|
+
class CCHWrapper {
|
|
30
|
+
public:
|
|
31
|
+
CCHWrapper() : node_count_(0), built_(false), customized_(false) {}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build CCH topology from graph arrays.
|
|
35
|
+
*
|
|
36
|
+
* Uses compute_nested_node_dissection_order_using_inertial_flow for the
|
|
37
|
+
* elimination order (requires node coordinates).
|
|
38
|
+
*/
|
|
39
|
+
void build_topology(
|
|
40
|
+
py::array_t<uint32_t> tail_arr,
|
|
41
|
+
py::array_t<uint32_t> head_arr,
|
|
42
|
+
py::array_t<float> latitude_arr,
|
|
43
|
+
py::array_t<float> longitude_arr,
|
|
44
|
+
uint32_t node_count
|
|
45
|
+
) {
|
|
46
|
+
auto tail_buf = tail_arr.request();
|
|
47
|
+
auto head_buf = head_arr.request();
|
|
48
|
+
auto lat_buf = latitude_arr.request();
|
|
49
|
+
auto lon_buf = longitude_arr.request();
|
|
50
|
+
|
|
51
|
+
if (tail_buf.size != head_buf.size) {
|
|
52
|
+
throw std::invalid_argument("tail and head must have same length");
|
|
53
|
+
}
|
|
54
|
+
if (lat_buf.size != (ssize_t)node_count || lon_buf.size != (ssize_t)node_count) {
|
|
55
|
+
throw std::invalid_argument("latitude/longitude must have length == node_count");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
node_count_ = node_count;
|
|
59
|
+
|
|
60
|
+
// Copy into std::vector
|
|
61
|
+
auto* tail_ptr = static_cast<uint32_t*>(tail_buf.ptr);
|
|
62
|
+
auto* head_ptr = static_cast<uint32_t*>(head_buf.ptr);
|
|
63
|
+
auto* lat_ptr = static_cast<float*>(lat_buf.ptr);
|
|
64
|
+
auto* lon_ptr = static_cast<float*>(lon_buf.ptr);
|
|
65
|
+
|
|
66
|
+
tail_.assign(tail_ptr, tail_ptr + tail_buf.size);
|
|
67
|
+
head_.assign(head_ptr, head_ptr + head_buf.size);
|
|
68
|
+
std::vector<float> latitude(lat_ptr, lat_ptr + lat_buf.size);
|
|
69
|
+
std::vector<float> longitude(lon_ptr, lon_ptr + lon_buf.size);
|
|
70
|
+
|
|
71
|
+
// Compute nested dissection order using inertial flow
|
|
72
|
+
auto order = RoutingKit::compute_nested_node_dissection_order_using_inertial_flow(
|
|
73
|
+
node_count, tail_, head_, latitude, longitude
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Build CCH
|
|
77
|
+
cch_ = RoutingKit::CustomizableContractionHierarchy(order, tail_, head_);
|
|
78
|
+
built_ = true;
|
|
79
|
+
customized_ = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Customize weights (quantized uint32).
|
|
84
|
+
* Must be called after build_topology and before query.
|
|
85
|
+
*/
|
|
86
|
+
void customize_weights(py::array_t<uint32_t> weights_arr) {
|
|
87
|
+
if (!built_) {
|
|
88
|
+
throw std::runtime_error("Must call build_topology before customize_weights");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
auto buf = weights_arr.request();
|
|
92
|
+
if (buf.size != (ssize_t)tail_.size()) {
|
|
93
|
+
throw std::invalid_argument(
|
|
94
|
+
"weights length (" + std::to_string(buf.size) +
|
|
95
|
+
") must equal arc count (" + std::to_string(tail_.size()) + ")"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
auto* ptr = static_cast<uint32_t*>(buf.ptr);
|
|
100
|
+
weights_.assign(ptr, ptr + buf.size);
|
|
101
|
+
|
|
102
|
+
metric_ = RoutingKit::CustomizableContractionHierarchyMetric(cch_, weights_);
|
|
103
|
+
metric_.customize();
|
|
104
|
+
query_ = RoutingKit::CustomizableContractionHierarchyQuery(metric_);
|
|
105
|
+
customized_ = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Point-to-point query.
|
|
110
|
+
* Returns (distance, node_path) where distance is uint32
|
|
111
|
+
* and node_path is the list of original node indices.
|
|
112
|
+
*/
|
|
113
|
+
py::tuple query(uint32_t source, uint32_t target) {
|
|
114
|
+
if (!customized_) {
|
|
115
|
+
throw std::runtime_error("Must call customize_weights before query");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
query_.reset().add_source(source).add_target(target).run();
|
|
119
|
+
unsigned dist = query_.get_distance();
|
|
120
|
+
|
|
121
|
+
if (dist == RoutingKit::inf_weight) {
|
|
122
|
+
// Unreachable
|
|
123
|
+
return py::make_tuple(dist, py::list());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
auto path = query_.get_node_path();
|
|
127
|
+
return py::make_tuple(dist, py::cast(path));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Many-to-many distance matrix.
|
|
132
|
+
* Uses one-to-many queries for each source.
|
|
133
|
+
* Returns a flattened uint32 array of shape (len(sources) * len(targets)).
|
|
134
|
+
*/
|
|
135
|
+
py::array_t<uint32_t> distances_many_to_many(
|
|
136
|
+
py::array_t<uint32_t> sources_arr,
|
|
137
|
+
py::array_t<uint32_t> targets_arr
|
|
138
|
+
) {
|
|
139
|
+
if (!customized_) {
|
|
140
|
+
throw std::runtime_error("Must call customize_weights before distances_many_to_many");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
auto src_buf = sources_arr.request();
|
|
144
|
+
auto tgt_buf = targets_arr.request();
|
|
145
|
+
auto* src_ptr = static_cast<uint32_t*>(src_buf.ptr);
|
|
146
|
+
auto* tgt_ptr = static_cast<uint32_t*>(tgt_buf.ptr);
|
|
147
|
+
|
|
148
|
+
size_t n_src = src_buf.size;
|
|
149
|
+
size_t n_tgt = tgt_buf.size;
|
|
150
|
+
|
|
151
|
+
std::vector<uint32_t> targets(tgt_ptr, tgt_ptr + n_tgt);
|
|
152
|
+
|
|
153
|
+
// Result matrix
|
|
154
|
+
std::vector<uint32_t> result(n_src * n_tgt);
|
|
155
|
+
|
|
156
|
+
// Pin targets once, then iterate sources
|
|
157
|
+
query_.reset().pin_targets(targets);
|
|
158
|
+
|
|
159
|
+
for (size_t i = 0; i < n_src; i++) {
|
|
160
|
+
query_.reset_source().add_source(src_ptr[i]).run_to_pinned_targets();
|
|
161
|
+
auto dists = query_.get_distances_to_targets();
|
|
162
|
+
for (size_t j = 0; j < n_tgt; j++) {
|
|
163
|
+
result[i * n_tgt + j] = dists[j];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Return as numpy array with shape (n_src, n_tgt)
|
|
168
|
+
auto result_arr = py::array_t<uint32_t>({(ssize_t)n_src, (ssize_t)n_tgt});
|
|
169
|
+
auto result_buf = result_arr.request();
|
|
170
|
+
std::memcpy(result_buf.ptr, result.data(), result.size() * sizeof(uint32_t));
|
|
171
|
+
return result_arr;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Save CCH topology + edge arrays to a directory.
|
|
176
|
+
*/
|
|
177
|
+
void save_topology(const std::string& dir_path) {
|
|
178
|
+
if (!built_) {
|
|
179
|
+
throw std::runtime_error("Must call build_topology before save_topology");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
RoutingKit::save_vector(dir_path + "/tail", tail_);
|
|
183
|
+
RoutingKit::save_vector(dir_path + "/head", head_);
|
|
184
|
+
RoutingKit::save_vector(dir_path + "/order", cch_.order);
|
|
185
|
+
RoutingKit::save_vector(dir_path + "/rank", cch_.rank);
|
|
186
|
+
|
|
187
|
+
// Save node count as a single-element vector
|
|
188
|
+
std::vector<uint32_t> nc = {node_count_};
|
|
189
|
+
RoutingKit::save_vector(dir_path + "/node_count", nc);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Load CCH topology from a directory (skips the expensive nested dissection).
|
|
194
|
+
*/
|
|
195
|
+
void load_topology(const std::string& dir_path) {
|
|
196
|
+
tail_ = RoutingKit::load_vector<unsigned>(dir_path + "/tail");
|
|
197
|
+
head_ = RoutingKit::load_vector<unsigned>(dir_path + "/head");
|
|
198
|
+
auto order = RoutingKit::load_vector<unsigned>(dir_path + "/order");
|
|
199
|
+
auto nc = RoutingKit::load_vector<unsigned>(dir_path + "/node_count");
|
|
200
|
+
|
|
201
|
+
if (nc.empty()) {
|
|
202
|
+
throw std::runtime_error("Invalid topology: missing node_count");
|
|
203
|
+
}
|
|
204
|
+
node_count_ = nc[0];
|
|
205
|
+
|
|
206
|
+
cch_ = RoutingKit::CustomizableContractionHierarchy(order, tail_, head_);
|
|
207
|
+
built_ = true;
|
|
208
|
+
customized_ = false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
uint32_t get_node_count() const { return node_count_; }
|
|
212
|
+
size_t get_arc_count() const { return tail_.size(); }
|
|
213
|
+
bool is_built() const { return built_; }
|
|
214
|
+
bool is_customized() const { return customized_; }
|
|
215
|
+
|
|
216
|
+
private:
|
|
217
|
+
uint32_t node_count_;
|
|
218
|
+
bool built_;
|
|
219
|
+
bool customized_;
|
|
220
|
+
|
|
221
|
+
std::vector<unsigned> tail_;
|
|
222
|
+
std::vector<unsigned> head_;
|
|
223
|
+
std::vector<unsigned> weights_;
|
|
224
|
+
|
|
225
|
+
RoutingKit::CustomizableContractionHierarchy cch_;
|
|
226
|
+
RoutingKit::CustomizableContractionHierarchyMetric metric_;
|
|
227
|
+
RoutingKit::CustomizableContractionHierarchyQuery query_;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
PYBIND11_MODULE(_core, m) {
|
|
232
|
+
m.doc() = "RoutingKit CCH wrapper for Python";
|
|
233
|
+
|
|
234
|
+
py::class_<CCHWrapper>(m, "CCH")
|
|
235
|
+
.def(py::init<>())
|
|
236
|
+
.def("build_topology", &CCHWrapper::build_topology,
|
|
237
|
+
py::arg("tail"), py::arg("head"),
|
|
238
|
+
py::arg("latitude"), py::arg("longitude"),
|
|
239
|
+
py::arg("node_count"),
|
|
240
|
+
"Build CCH topology from graph arrays with inertial flow ordering")
|
|
241
|
+
.def("customize_weights", &CCHWrapper::customize_weights,
|
|
242
|
+
py::arg("weights"),
|
|
243
|
+
"Customize CCH with uint32 weights")
|
|
244
|
+
.def("query", &CCHWrapper::query,
|
|
245
|
+
py::arg("source"), py::arg("target"),
|
|
246
|
+
"Point-to-point query. Returns (distance, node_path)")
|
|
247
|
+
.def("distances_many_to_many", &CCHWrapper::distances_many_to_many,
|
|
248
|
+
py::arg("sources"), py::arg("targets"),
|
|
249
|
+
"Many-to-many distance matrix")
|
|
250
|
+
.def("save_topology", &CCHWrapper::save_topology,
|
|
251
|
+
py::arg("dir_path"),
|
|
252
|
+
"Save CCH topology to directory")
|
|
253
|
+
.def("load_topology", &CCHWrapper::load_topology,
|
|
254
|
+
py::arg("dir_path"),
|
|
255
|
+
"Load CCH topology from directory")
|
|
256
|
+
.def_property_readonly("node_count", &CCHWrapper::get_node_count)
|
|
257
|
+
.def_property_readonly("arc_count", &CCHWrapper::get_arc_count)
|
|
258
|
+
.def_property_readonly("is_built", &CCHWrapper::is_built)
|
|
259
|
+
.def_property_readonly("is_customized", &CCHWrapper::is_customized);
|
|
260
|
+
|
|
261
|
+
// Expose inf_weight constant
|
|
262
|
+
m.attr("INF_WEIGHT") = RoutingKit::inf_weight;
|
|
263
|
+
}
|