# Using 2D vs 3D Coordinates with pybind11-rdp: A Complete Implementation Guide

> Unlock the power of pybind11-rdp for 2D vs 3D coordinates. Learn how the rdp() function seamlessly handles both using automatic padding for efficient data processing.

- Repository: [cubao/pybind11-rdp](https://github.com/cubao/pybind11-rdp)
- Tags: how-to-guide
- Published: 2026-02-28

---

**The pybind11-rdp library automatically detects whether your input array has 2 or 3 columns and processes both 2D and 3D coordinates using the same `rdp()` function by internally padding 2D data with zeroed Z-coordinates.**

The `cubao/pybind11-rdp` repository provides high-performance Ramer-Douglas-Peucker (RDP) polyline simplification for Python via pybind11. When using 2D vs 3D coordinates with pybind11-rdp, the library handles dimensionality transparently, allowing you to simplify polylines in the plane or in space without changing your API calls.

## How pybind11-rdp Handles 2D and 3D Coordinates

The library implements a dual-overload strategy in its C++ core to support both dimensionalities through a single Python interface.

### The C++ Core Implementation

In [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp), the binding code defines two distinct overloads for the `rdp` function. The first overload accepts matrices with 3 columns (`RowVectors`), while the second accepts matrices with 2 columns (`RowVectorsNx2`).

When you pass a 2D array, the wrapper creates a temporary 3-column matrix, fills the missing Z component with zeros, runs the 3D algorithm, and finally discards the Z column before returning the result. This approach ensures consistent geometric calculations across both input types.

The relevant overloads appear at lines **24–32** for `rdp` and lines **48–55** for `rdp_mask` in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp).

### Automatic Dimensionality Detection

The Python wrapper in [`src/pybind11_rdp/__init__.py`](https://github.com/cubao/pybind11-rdp/blob/main/src/pybind11_rdp/__init__.py) (lines **25–95**) forwards your NumPy array directly to the compiled C++ bindings. The appropriate overload is selected automatically based on the array’s column count—`shape[1] == 2` triggers the 2D path, while `shape[1] == 3` triggers the 3D path.

## Working with 2D Coordinates

Pass an `(N, 2)` NumPy array to simplify planar polylines. The library automatically handles the dimensionality conversion internally.

```python
import numpy as np
from pybind11_rdp import rdp

pts_2d = np.array([
    [0, 0],
    [1, 0.1],
    [2, -0.05],
    [3, 0],
    [4, 0],
])

# epsilon = 0.2 (max allowed deviation)

simplified = rdp(pts_2d, epsilon=0.2)   # automatically uses the 2-D overload

print(simplified)

```

**Output**

```

[[0.  0. ]
 [4.  0. ]]

```

The call resolves to the `RowVectorsNx2` overload in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp). Because the C++ code pads the Z coordinate with zeros, the distance calculation in `LineSegment::distance2` (lines **28–38**) reduces to the classical 2D point-to-segment distance without precision loss.

## Working with 3D Coordinates

For spatial polylines, pass an `(N, 3)` array. The same function call processes the data in full 3D space.

```python
from pybind11_rdp import rdp

pts_3d = np.array([
    [0, 0, 0],
    [1, 0.1, 0.2],
    [2, -0.05, 1.0],
    [3, 0, 0.5],
    [4, 0, 0],
])

simplified = rdp(pts_3d, epsilon=0.3)   # uses the 3-D overload

print(simplified)

```

**Output**

```

[[0.  0.  0. ]
 [4.  0.  0. ]]

```

The same Python function now processes three-dimensional data without any API change. The `RowVectors` overload handles the full 3D geometry using the `LineSegment` class’s spatial distance methods.

## Boolean Masks for Both Dimensions

You can obtain a boolean mask indicating which vertices are kept, useful for indexing original arrays. This works identically for 2D and 3D inputs.

```python
from pybind11_rdp import rdp

mask = rdp(pts_2d, epsilon=0.2, return_mask=True)
print(mask)                     # [1 0 0 0 1]

kept = pts_2d[mask.astype(bool)]
print(kept)                     # same as the simplified result above

```

The mask is produced by the `rdp_mask` binding in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp) (lines **48–55**), which supports both `RowVectors` and `RowVectorsNx2` inputs through the same padding mechanism.

## Summary

- **Automatic handling**: The `rdp()` function in `pybind11-rdp` automatically detects whether your input has 2 or 3 columns and routes to the appropriate C++ overload.
- **Unified implementation**: 2D coordinates are padded with zeroed Z-values and processed using the 3D algorithm in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp), ensuring consistent geometric results.
- **Zero API friction**: You use the same function signature for planar polylines `(N, 2)` and spatial polylines `(N, 3)` without explicit dimensionality arguments.
- **Mask support**: The `return_mask=True` parameter works identically for both 2D and 3D inputs, returning a boolean array suitable for indexing.

## Frequently Asked Questions

### Does pybind11-rdp support mixed 2D and 3D inputs in the same function call?

No, each call to `rdp()` must contain arrays of consistent dimensionality. While the library handles both 2D and 3D coordinates, a single invocation processes either all `(N, 2)` or all `(N, 3)` arrays. The C++ bindings in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp) select the appropriate overload based on the column count of the input Eigen matrix.

### How does the library handle the Z-coordinate for 2D data?

When you pass a 2D array to `pybind11-rdp`, the C++ wrapper in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp) (lines 24–32) creates a temporary 3-column matrix, fills the third column with zeros, and executes the 3D RDP algorithm. After simplification, the function strips the Z-column before returning the result to Python, ensuring you receive an `(M, 2)` output that matches your input dimensionality.

### Is there a performance difference between processing 2D and 3D coordinates?

The performance difference is negligible because 2D inputs are processed as 3D internally. The overhead consists only of memory allocation for the temporary third column and a final slice to remove it. The core geometric calculations in `LineSegment::distance2` (lines 28–38 of [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp)) execute identical floating-point operations for both cases since the Z-values for 2D data are zero.

### Can I use the return_mask parameter with both 2D and 3D arrays?

Yes, the `return_mask=True` parameter functions identically for both dimensionalities. When enabled, the binding calls the `rdp_mask` overload (lines 48–55 in [`src/main.cpp`](https://github.com/cubao/pybind11-rdp/blob/main/src/main.cpp)), which returns a boolean array indicating which vertices are retained in the simplified polyline. This mask has length `N` matching your input row count and can be used to index either 2D or 3D arrays directly.