Quick Start

Basic usage

from vibeproj import Transformer

# Create a transformer between two coordinate systems
t = Transformer.from_crs("EPSG:4326", "EPSG:32631")

# Transform a single point (scalar)
x, y = t.transform(49.0, 2.0)

# Transform arrays
import numpy as np
lat = np.array([49.0, 48.8566, 40.7128])
lon = np.array([2.0, 2.3522, -74.006])
x, y = t.transform(lat, lon)

# Inverse transform
lat2, lon2 = t.transform(x, y, direction="INVERSE")

CRS input formats

from_crs() accepts several formats:

# EPSG integer
t = Transformer.from_crs(4326, 32631)

# Authority string
t = Transformer.from_crs("EPSG:4326", "EPSG:32631")

# Tuple
t = Transformer.from_crs(("EPSG", 4326), ("EPSG", 32631))

# Plain string
t = Transformer.from_crs("4326", "32631")

GPU acceleration

When CuPy is available and input arrays are CuPy arrays, transforms run on the GPU automatically using fused NVRTC kernels:

import cupy as cp

lat = cp.array([49.0, 48.8566, 40.7128], dtype=cp.float64)
lon = cp.array([2.0, 2.3522, -74.006], dtype=cp.float64)

t = Transformer.from_crs("EPSG:4326", "EPSG:32631")
x, y = t.transform(lat, lon)  # runs on GPU

NumPy arrays always use the CPU path. CuPy arrays always use the GPU path. There is no explicit device selection – the array type determines the backend.

Zero-copy transforms

For high-throughput pipelines, transform_buffers() avoids all intermediate allocations by writing directly into pre-allocated output arrays:

import cupy as cp

t = Transformer.from_crs("EPSG:4326", "EPSG:32631")

# Input arrays (already on GPU)
lat = cp.asarray(lat_data, dtype=cp.float64)
lon = cp.asarray(lon_data, dtype=cp.float64)

# Pre-allocate output
out_x = cp.empty_like(lat)
out_y = cp.empty_like(lon)

# Transform in-place -- no intermediate allocation
t.transform_buffers(lat, lon, out_x=out_x, out_y=out_y)

The returned arrays are the same objects as out_x and out_y.

Projected-to-projected transforms

vibeProj handles projected-to-projected transforms by chaining through a geographic intermediate:

# UTM Zone 31N -> Web Mercator (no need to go through EPSG:4326 manually)
t = Transformer.from_crs("EPSG:32631", "EPSG:3857")
x_webmerc, y_webmerc = t.transform(x_utm, y_utm)

On GPU, this executes as two fused kernel calls (inverse + forward) with no host round-trip between them.