Metadata-Version: 2.4
Name: pyvalhalla
Version: 3.7.0
Summary: High-level bindings to the Valhalla C++ library
Author: Nils Nolde, Kevin Kreiser
License-Expression: MIT
License-File: COPYING
Project-URL: Homepage, https://github.com/valhalla/valhalla
Project-URL: Documentation, https://valhalla.github.io/valhalla/
Project-URL: Repository, https://github.com/valhalla/valhalla
Project-URL: Issues, https://github.com/valhalla/valhalla/issues
Project-URL: Changelog, https://github.com/valhalla/valhalla/blob/master/CHANGELOG.md
Requires-Python: >=3.9.0
Provides-Extra: typing
Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "typing"
Description-Content-Type: text/markdown

## Valhalla Python bindings

[![pyvalhalla version](https://img.shields.io/pypi/v/pyvalhalla?label=pyvalhalla)](https://pypi.org/project/pyvalhalla/)

This folder ([`src/bindings/python`](https://github.com/valhalla/valhalla/tree/master/src/bindings/python)) contains the Python bindings for the [Valhalla routing engine](https://github.com/valhalla/valhalla).

> [!NOTE]
> `pyvalhalla` packages are currently only published for:
> - `linux-x86_x64`
> - `linux-aarch64`
> - `win-amd64`
> - `macos-arm64`

On top of the (very) high-level Python bindings, we package some data-building Valhalla executables to ease the process of graph creation or run Valhalla as a service, see [below](#valhalla-executables).

### Installation

We publish CPython packages as **binary wheels** for Win (`amd64`), MacOS (`arm64`) and Linux (`x86_64`/`aarch64`) distributions with `glibc>=2.28`. To decrease disk footprint of the PyPI releases, we only publish a single `abi3` wheel per platform, which **requires Python >= 3.12**. To install on Python < 3.12, make sure to install the system dependencies as described in [the docs](https://valhalla.github.io/valhalla/building/#platform-specific-builds) before trying a `pip install pyvalhalla`.

Or manually in the current Python environment with e.g.

```shell
git clone https://github.com/valhalla/valhalla
cd valhalla
pip install .
```

In case you need to do a source installation (from `sdist`), follow the [build instructions](https://valhalla.github.io/valhalla/building/) for your platform to install the needed dependencies. Then a simple `pip install pyvalhalla` should work fine for Linux/OSX. On Windows one needs to install C++ developer tools, see also below in the developer notes for external `vcpkg` usage to resolve dependencies.

> [!TIP]
> **For developers**: `pip install -e` (editable build) will by default build into a temp directory, so everytime it's invoked it'll rebuild all of libvalhalla. Use the following command to enable real incremental builds:
> 
> ```shell
> pip install -e . --no-build-isolation \
>   -Cbuild-dir=build_python (or other build dir) \
>   -Ccmake.build-type=Release \
>   -Ccmake.define.VALHALLA_VERSION_MODIFIER="$(git rev-parse --short HEAD)"
>   # optionally for vcpkg package management
>   -Ccmake.define.CMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
>   -Ccmake.define.VCPKG_TARGET_TRIPLET=x64-windows
>   -Ccmake.define.VCPKG_OVERLAY_PORTS=overlay-ports-vcpkg
> ```
> 
> Similarly for building a wheel:
> 
> ```shell
> pip wheel . -w dist --no-build-isolation \
>   -Cbuild-dir=build_python (or other build dir) \
>   -Ccmake.build-type=Release \
>   -Ccmake.define.VALHALLA_VERSION_MODIFIER="$(git rev-parse --short HEAD)"
>   # optionally for vcpkg package management
>   -Ccmake.define.CMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
>   -Ccmake.define.VCPKG_TARGET_TRIPLET=x64-windows
>   -Ccmake.define.VCPKG_OVERLAY_PORTS=overlay-ports-vcpkg
> ```
>
> Both commands have to repeated for each build.

#### Typing support

We (try to) include full typing support for pyvalhalla. If you're using `mypy` with strict typing policies, you'll need to install pyvalhalla appropriately with `pip install pyvalhalla[typing]`.

### Usage

#### Bindings

Find a more extended notebook in `./examples`, e.g. how to [use the actor](https://github.com/valhalla/valhalla/blob/master/src/bindings/python/examples/actor_examples.ipynb).

Before using the Python bindings you need to have access to a routable Valhalla graph. Once you installed the `pyvalhalla` package you can create one with

```shell
wget https://download.geofabrik.de/europe/andorra-latest.osm.pbf
python -m valhalla valhalla_build_tiles -c <valhalla.json> andorra-latest.osm.pbf
```

Once you have created a graph locally, you can use it like this:

```python
from valhalla import Actor, get_config, get_help

# generate configuration
config = get_config(tile_extract='./custom_files/valhalla_tiles.tar', verbose=True)

# print the help for specific config items (has the same structure as the output of get_config()
print(get_help()["service_limits"]["auto"]["max_distance"])

# instantiate Actor to load graph and call actions
actor = Actor(config)
route = actor.route({"locations": [...]})
```

#### Error Handling

When a routing operation fails, a `ValhallaError` is raised (a subclass of `RuntimeError`) with structured fields from Valhalla's internal error codes:

```python
from valhalla import Actor, ValhallaError, get_config

actor = Actor(get_config(tile_extract='./valhalla_tiles.tar'))

try:
    actor.route({"locations": [{"lat": 0.0, "lon": 0.0}, {"lat": 0.1, "lon": 0.1}], "costing": "auto"})
except ValhallaError as e:
    print(e.code)          # 171
    print(e.message)       # "No suitable edges near location"
    print(e.http_code)     # 400
    print(e.http_message)  # "Bad Request"
```

#### Graph Utilities

Access to low-level graph data structures for advanced use cases:

```python
from valhalla.utils.graph_utils import GraphId, GraphUtils

# Create a GraphId from its string representation or numeric value
edge_id = GraphId("2/421920/20")  # format: "level/tileid/id"
# or
edge_id = GraphId(674464020)

# Initialize GraphUtils with config (reuse for multiple queries)
# Accepts: file path (str/Path), JSON string, or dict
config = "/path/to/valhalla.json"
# or dict config
config = {"mjolnir": {"tile_extract": "/path/to/tiles.tar"}}
graph = GraphUtils(config)

# Get the polyline geometry for an edge
shape = graph.get_edge_shape(edge_id)

# shape is a list of (lon, lat) tuples
print(f"Edge has {len(shape)} coordinate points")
for lon, lat in shape:
    print(f"  ({lon:.6f}, {lat:.6f})")

# Convert to GeoJSON LineString
geojson = {
    "type": "Feature",
    "geometry": {
        "type": "LineString",
        "coordinates": [[lon, lat] for lon, lat in shape]
    }
}
```

#### Speed Compression Utilities

Valhalla uses DCT-2 (Discrete Cosine Transform) to compress historical speed profiles. These utilities allow you to work with compressed speed data:

```python
import numpy as np
from valhalla.utils.predicted_speeds import (
    compress_speed_buckets,
    decompress_speed_bucket,
    encode_compressed_speeds,
    decode_compressed_speeds,
    BUCKETS_PER_WEEK,
    COEFFICIENT_COUNT,
)

# Compress 2016 speed buckets (7 days × 288 five-minute intervals)
speeds = np.full(BUCKETS_PER_WEEK, 50.0, dtype=np.float32)  # 50 KPH constant
coefficients = compress_speed_buckets(speeds)

# Decompress a specific bucket (e.g., Monday 10:00 AM)
bucket_idx = 120  # (24 hours × 12 buckets/hour) × 0 days + 10 × 12
speed = decompress_speed_bucket(coefficients, bucket_idx)

# Encode coefficients for storage/transmission
encoded = encode_compressed_speeds(coefficients)
print(f"Compressed to {len(encoded)} characters")

# Decode from string
coefficients_restored = decode_compressed_speeds(encoded)
```

#### Valhalla executables

##### C++ executables

To access the C++ (native) executables, there are 2 options:

- (recommended) execute the module, e.g. `python -m valhalla valhalla_build_tiles -h`
- execute the Python wrapper scripts directly, e.g. `valhalla_build_tiles -h`

> [!NOTE]
> For the latter option to work, the Python environment's `bin/` folder has to be in the `$PATH`. Inside virtual environments, that's always the case.

Executing the scripts directly might also not work properly if there's a system-wide Valhalla installation, unless the Python environment's `bin/` folder has higher priority than system folders in `$PATH`. The module execution uses an explicit Python executable which should be preferred.

There are also some additional commands we added:

- `--help`: print the help for `python -m valhalla` explicitly
- `--quiet`: redirect `stdout` of the C++ executables to `/dev/null`; can be added **once** anywhere in the command, will not be forwarded to a C++ executable
- `print_bin_path`: simply prints the absolute path to the package-internal `bin/` directory where the C++ executables are; useful if the executables should be accessed directly in some script

To find out which Valhalla executables are currently included, run `python -m valhalla --help`. We limit the number of executables to control the wheel size. However, we're open to include any other executable if there's a good reason.

##### Pure Python scripts

The following tools are implemented in pure Python and installed as console scripts:

- `valhalla_build_config`: Generate or merge Valhalla configuration JSON files
- `valhalla_build_elevation`: Download elevation (DEM) tiles for a given region
- `valhalla_build_extract`: Create tar extracts from routing tiles

These are invoked directly, e.g. `valhalla_build_config -h` or `valhalla_build_extract -h`. They do **not** go through the `python -m valhalla` module mechanism.

### Building from source

Note, building the bindings from source is usually best done by building Valhalla with `cmake -B build -DENABLE_PYTHON_BINDING=ON ...`. However, if you want to package your own `pyvalhalla` bindings for some reason (e.g. fork in a bigger team), you can follow the below instructions, which are also executed by our CI.

The Python build respects a few CMake configuration variables:

- `VALHALLA_VERSION_MODIFIER` (optional): Will append a string to the actual Valhalla version string, e.g. `$(git rev-parse --short HEAD)` will append the current branch's commit hash.

#### `cibuildwheel`

On our CI, this orchestrates the packaging of all `pyvalhalla` wheels for every supported, minor Python version and every platform. It can also be run locally (obviously only being able to build wheels for _your_ platform), e.g.

```shell
python -m pip install cibuildwheel
cibuildwheel --print-build-identifiers
cibuildwheel --only cp313-manylinux_x86_64

# for windows you'll have to set an env var to the vcpkg win root
VCPKG_ARCH_ROOT="build/vcpkg_installed/custom-x64-windows" cibuildwheel --only cp313-win_amd64
```

The build looks at a few environment variables:

- `VCPKG_ARCH_ROOT` (required for Win): The relative/absolute directory of the `vcpkg` root.

In the end, you'll find the wheel in `./wheelhouse`.

#### Linux

To package arch-dependent Linux bindings we use a `manylinux` fork, where we install all dependencies into the `manylinux_2_28` image, based on AlmaLinux 8. This is necessary to have a broad `glibc` compatibility with many semi-recent Linux distros.

Either pull the `manylinux` image, or build it locally for testing:

```shell
docker pull ghcr.io/valhalla/manylinux:2_28_valhalla_python

# or pull the image from ghcr.io
git clone https://github.com/valhalla/manylinux
cd manylinux
POLICY=manylinux_2_28 PLATFORM=x86_64 COMMIT_SHA=$(git rev-parse --verify HEAD) BUILDX_BUILDER=builder-docker-container ./build.sh
docker tag quay.io/pypa/manylinux_2_28_x86_64:$(git rev-parse --verify HEAD) ghcr.io/valhalla/manylinux:2_28_valhalla_python
```

Once built, start a container to actually build Valhalla using AlmaLinux 8:

```shell
cd valhalla
docker run -dt -v $PWD:/valhalla-py --name valhalla-py --workdir /valhalla-py ghcr.io/valhalla/manylinux:2_28_valhalla_python
docker exec -t valhalla-py /valhalla-py/src/bindings/python/scripts/build_manylinux.sh build_manylinux 3.13
```

This will also build & install `libvalhalla` before building the bindings. At this point there should be a `wheelhouse` folder with the fixed python wheel, ready to be installed or distributed to arbitrary python 3.13 installations.

### Testing (**`linux` only**)

We have a small [test script](https://github.com/valhalla/valhalla/blob/master/src/bindings/python/test/test_pyvalhalla_package.sh) which makes sure that all the executables are working properly. If run locally for some reason, install a `pyvalhalla` wheel first. We run this in CI in a fresh Docker container with no dependencies installed, mostly to verify dynamic linking of the vendored dependencies.
