vibespatial.geometry.device_array

DeviceGeometryArray – pandas ExtensionArray backed by device-resident OwnedGeometryArray.

This module inverts the storage model: OwnedGeometryArray is the source of truth (device-resident when GPU is available), and Shapely objects are materialized lazily on demand. The goal is to eliminate D->H->Shapely->H->D roundtrips when GPU-decoded geometry flows through GeoDataFrame into GPU consumers.

See epic vibeSpatial-o17.9.13 and ADR-0005 for design rationale.

Attributes

Classes

DeviceGeometryDtype

pandas dtype for device-resident geometry arrays.

DeviceGeometryArray

pandas ExtensionArray backed by a device-resident OwnedGeometryArray.

Module Contents

vibespatial.geometry.device_array.TAG_FAMILIES
class vibespatial.geometry.device_array.DeviceGeometryDtype

pandas dtype for device-resident geometry arrays.

type

The scalar type for the array, e.g. int

It’s expected ExtensionArray[item] returns an instance of ExtensionDtype.type for scalar item, assuming that value is valid (not NA). NA values do not need to be instances of type.

name = 'device_geometry'

A string identifying the data type.

Will be used for display in, e.g. Series.dtype

na_value = None

Default NA value to use for this type.

This is used in e.g. ExtensionArray.take. This should be the user-facing “boxed” version of the NA value, not the physical NA value for storage. e.g. for JSONArray, this is an empty dictionary.

classmethod construct_from_string(string: str) DeviceGeometryDtype

Construct this type from a string.

This is useful mainly for data types that accept parameters. For example, a period dtype accepts a frequency parameter that can be set as period[h] (where H means hourly frequency).

By default, in the abstract class, just the name of the type is expected. But subclasses can overwrite this method to accept parameters.

Parameters

stringstr

The name of the type, for example category.

Returns

ExtensionDtype

Instance of the dtype.

Raises

TypeError

If a class cannot be constructed from this ‘string’.

Examples

For extension dtypes with arguments the following may be an adequate implementation.

>>> import re
>>> @classmethod
... def construct_from_string(cls, string):
...     pattern = re.compile(r"^my_type\[(?P<arg_name>.+)\]$")
...     match = pattern.match(string)
...     if match:
...         return cls(**match.groupdict())
...     else:
...         raise TypeError(
...             f"Cannot construct a '{cls.__name__}' from '{string}'"
...         )
classmethod construct_array_type() type[DeviceGeometryArray]

Return the array type associated with this dtype.

Returns

type

class vibespatial.geometry.device_array.DeviceGeometryArray(owned: vibespatial.geometry.owned.OwnedGeometryArray, *, crs: Any | None = None)

pandas ExtensionArray backed by a device-resident OwnedGeometryArray.

Source of truth is _owned (an OwnedGeometryArray). Shapely objects are materialized lazily into _shapely_cache only when pandas or user code requires individual geometry objects.

Key properties: - take operates on owned buffers without Shapely round-trip. - copy duplicates owned buffers on the current residency side. - _concat_same_type merges owned buffers. - Diagnostic events are emitted for every materialization (per ADR-0005).

property dtype: DeviceGeometryDtype

An instance of ExtensionDtype.

See Also

api.extensions.ExtensionDtype : Base class for extension dtypes. api.extensions.ExtensionArray : Base class for extension array types. api.extensions.ExtensionArray.dtype : The dtype of an ExtensionArray. Series.dtype : The dtype of a Series. DataFrame.dtype : The dtype of a DataFrame.

Examples

>>> pd.array([1, 2, 3]).dtype
Int64Dtype()
property owned: vibespatial.geometry.owned.OwnedGeometryArray

The underlying OwnedGeometryArray (source of truth).

property crs: Any | None
property nbytes: int

The number of bytes needed to store this object in memory.

See Also

ExtensionArray.shape: Return a tuple of the array dimensions. ExtensionArray.size: The number of elements in the array.

Examples

>>> pd.array([1, 2, 3]).nbytes
27
isna() numpy.ndarray

A 1-D array indicating if each value is missing.

Returns

numpy.ndarray or pandas.api.extensions.ExtensionArray

In most cases, this should return a NumPy ndarray. For exceptional cases like SparseArray, where returning an ndarray would be expensive, an ExtensionArray may be returned.

See Also

ExtensionArray.dropna: Return ExtensionArray without NA values. ExtensionArray.fillna: Fill NA/NaN values using the specified method.

Notes

If returning an ExtensionArray, then

  • na_values._is_boolean should be True

  • na_values should implement ExtensionArray._reduce()

  • na_values should implement ExtensionArray._accumulate()

  • na_values.any and na_values.all should be implemented

Examples

>>> arr = pd.array([1, 2, np.nan, np.nan])
>>> arr.isna()
array([False, False,  True,  True])
property geom_type: numpy.ndarray

Geometry type names from owned tags — no Shapely materialization.

property is_empty: numpy.ndarray

Per-geometry emptiness from owned empty_mask — no Shapely materialization.

property bounds: numpy.ndarray

Per-geometry bounds from owned coordinate buffers — no Shapely materialization.

Returns (N, 4) float64 array of [minx, miny, maxx, maxy].

property total_bounds: numpy.ndarray

Aggregate bounds — no Shapely materialization.

estimate_utm_crs(datum_name: str = 'WGS 84')

Estimate UTM CRS from bounds – no Shapely materialization.

to_crs(crs=None, epsg=None)

Reproject via vibeProj transform_buffers – stays on device.

check_geographic_crs(stacklevel: int) None

Warn if CRS is geographic.

property area: numpy.ndarray

Area — GPU-accelerated from owned coordinate buffers, no Shapely.

property length: numpy.ndarray

Length — GPU-accelerated from owned coordinate buffers, no Shapely.

property is_valid: numpy.ndarray

OGC validity — GPU-accelerated from owned coordinate buffers, no Shapely.

is_valid_owned() covers full OGC validity: ring closure, min coords, ring self-intersection, hole-in-shell containment, ring-ring crossing, collinear overlap, and interior connectedness (multi-touch detection). Zero-copy: reads device buffers directly, returns boolean mask to host.

property is_simple: numpy.ndarray

Simplicity — GPU-accelerated from owned coordinate buffers, no Shapely.

property is_ring: numpy.ndarray
property is_closed: numpy.ndarray
property has_z: numpy.ndarray

has_z from owned metadata — OwnedGeometryArray is 2D (x/y only).

property has_m: numpy.ndarray

has_m from owned metadata — OwnedGeometryArray is 2D (x/y only).

is_valid_reason() numpy.ndarray
property boundary

Boundary — GPU-accelerated via owned path, no Shapely.

property centroid
property convex_hull

Convex hull — GPU-accelerated via owned path, no Shapely.

property envelope
property exterior
property unary_union
union_all(method='unary', grid_size=None)
buffer(distance, resolution=16, **kwargs)

Buffer – routes through owned dispatch with GPU kernel when possible.

simplify(tolerance, preserve_topology=True)
normalize(precision='auto')
offset_curve(distance, quad_segs=8, join_style='round', mitre_limit=5.0)
make_valid(method='linework', keep_collapsed=True)
representative_point()
affine_transform(matrix)
translate(xoff=0.0, yoff=0.0, zoff=0.0)
rotate(angle, origin='center', use_radians=False)
scale(xfact=1.0, yfact=1.0, zfact=1.0, origin='center')
skew(xs=0.0, ys=0.0, origin='center', use_radians=False)
count_coordinates()
count_geometries()
count_interior_rings()
to_wkb(**kwargs)
to_wkt(**kwargs)
to_owned() vibespatial.geometry.owned.OwnedGeometryArray

Return the underlying OwnedGeometryArray — no materialization.

supports_owned_spatial_input() bool

Device-resident geometries always support the owned query path.

owned_flat_sindex()

Return (owned, flat_index) without Shapely materialization.

The flat spatial index is built directly from the OwnedGeometryArray coordinate buffers and cached for reuse.

property sindex

Spatial index that routes through the owned query engine.

Construction does NOT trigger Shapely materialization. The returned SpatialIndex is backed by a lazy STRtree (built on first non-owned query) and eagerly caches the owned flat index.

property has_sindex: bool

Check existence of the spatial index without generating it.

intersects(other, *args, **kwargs)
contains(other, *args, **kwargs)
within(other, *args, **kwargs)
touches(other, *args, **kwargs)
crosses(other, *args, **kwargs)
overlaps(other, *args, **kwargs)
covers(other, *args, **kwargs)
covered_by(other, *args, **kwargs)
disjoint(other, *args, **kwargs)
contains_properly(other, *args, **kwargs)
equals(other, *args, **kwargs)

Return if another array is equivalent to this array.

Equivalent means that both arrays have the same shape and dtype, and all values compare equal. Missing values in the same location are considered equal (in contrast with normal equality).

Parameters

otherExtensionArray

Array to compare to this Array.

Returns

boolean

Whether the arrays are equivalent.

See Also

numpy.array_equal : Equivalent method for numpy array. Series.equals : Equivalent method for Series. DataFrame.equals : Equivalent method for DataFrame.

Examples

>>> arr1 = pd.array([1, 2, np.nan])
>>> arr2 = pd.array([1, 2, np.nan])
>>> arr1.equals(arr2)
True
>>> arr1 = pd.array([1, 3, np.nan])
>>> arr2 = pd.array([1, 2, np.nan])
>>> arr1.equals(arr2)
False
geom_equals(other)
geom_equals_exact(other, tolerance)
distance(other, *args, **kwargs)
dwithin(other, distance)
hausdorff_distance(other, densify=None)
frechet_distance(other, densify=None)
clip_by_rect(xmin, ymin, xmax, ymax)
intersection(other, *args, **kwargs)
union(other, *args, **kwargs)
difference(other, *args, **kwargs)
symmetric_difference(other, *args, **kwargs)
view(dtype=None) DeviceGeometryArray

Return a shallow view sharing the same owned array.

pandas’ ExtensionArray.view() calls self[:] which triggers a full take; override to share the backing OwnedGeometryArray directly (semantically identical for immutable-in-practice DGA).

take(indices: numpy.ndarray | collections.abc.Sequence[int], *, allow_fill: bool = False, fill_value: Any = None) DeviceGeometryArray

Take elements from an array.

Parameters

indicessequence of int or one-dimensional np.ndarray of int

Indices to be taken.

allow_fillbool, default False

How to handle negative values in indices.

  • False: negative values in indices indicate positional indices from the right (the default). This is similar to numpy.take().

  • True: negative values in indices indicate missing values. These values are set to fill_value. Any other other negative values raise a ValueError.

fill_valueany, optional

Fill value to use for NA-indices when allow_fill is True. This may be None, in which case the default NA value for the type, self.dtype.na_value, is used.

For many ExtensionArrays, there will be two representations of fill_value: a user-facing “boxed” scalar, and a low-level physical NA value. fill_value should be the user-facing version, and the implementation should handle translating that to the physical version for processing the take if necessary.

Returns

ExtensionArray

An array formed with selected indices.

Raises

IndexError

When the indices are out of bounds for the array.

ValueError

When indices contains negative values other than -1 and allow_fill is True.

See Also

numpy.take : Take elements from an array along an axis. api.extensions.take : Take elements from an array.

Notes

ExtensionArray.take is called by Series.__getitem__, .loc, iloc, when indices is a sequence of values. Additionally, it’s called by Series.reindex(), or any other method that causes realignment, with a fill_value.

Examples

Here’s an example implementation, which relies on casting the extension array to object dtype. This uses the helper method pandas.api.extensions.take().

def take(self, indices, allow_fill=False, fill_value=None):
    from pandas.api.extensions import take

    # If the ExtensionArray is backed by an ndarray, then
    # just pass that here instead of coercing to object.
    data = self.astype(object)

    if allow_fill and fill_value is None:
        fill_value = self.dtype.na_value

    # fill value should always be translated from the scalar
    # type for the array, to the physical storage type for
    # the data, before passing to take.

    result = take(
        data, indices, fill_value=fill_value, allow_fill=allow_fill
    )
    return self._from_sequence(result, dtype=self.dtype)
copy() DeviceGeometryArray

Return a copy of the array.

This method creates a copy of the ExtensionArray where modifying the data in the copy will not affect the original array. This is useful when you want to manipulate data without altering the original dataset.

Returns

ExtensionArray

A new ExtensionArray object that is a copy of the current instance.

See Also

DataFrame.copy : Return a copy of the DataFrame. Series.copy : Return a copy of the Series.

Examples

>>> arr = pd.array([1, 2, 3])
>>> arr2 = arr.copy()
>>> arr[0] = 2
>>> arr2
<IntegerArray>
[1, 2, 3]
Length: 3, dtype: Int64
property diagnostics: list[vibespatial.geometry.owned.DiagnosticEvent]