diff --git a/geoarrow-pyarrow/pyproject.toml b/geoarrow-pyarrow/pyproject.toml index a8ae9dd..e11ab4d 100644 --- a/geoarrow-pyarrow/pyproject.toml +++ b/geoarrow-pyarrow/pyproject.toml @@ -23,7 +23,7 @@ description = "" authors = [{name = "Dewey Dunnington", email = "dewey@dunnington.ca"}] license = {text = "Apache-2.0"} requires-python = ">=3.9" -dependencies = ["pyarrow >= 14.0.2", "geoarrow-types >= 0.3.0", "geoarrow-c >= 0.3.0"] +dependencies = ["pyarrow >= 14.0.2", "geoarrow-types >= 0.3.0", "geoarrow-c >= 0.3.1"] [project.optional-dependencies] test = ["pytest", "pandas", "numpy", "geopandas", "pyogrio", "pyproj"] diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py index 3b3441d..f5229b2 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_array.py @@ -1,4 +1,4 @@ -from geoarrow.pyarrow._kernel import Kernel, _geoarrow_c +from geoarrow.pyarrow._kernel import Kernel from geoarrow.pyarrow._type import ( GeometryExtensionType, large_wkb, @@ -8,22 +8,12 @@ ) import pyarrow as pa +from pyarrow import types as pa_types class GeometryExtensionArray(pa.ExtensionArray): def geobuffers(self): - import numpy as np - - lib = _geoarrow_c() - - cschema = lib.SchemaHolder() - self.type._export_to_c(cschema._addr()) - carray = lib.ArrayHolder() - self._export_to_c(carray._addr()) - - array_view = lib.CArrayView(carray, cschema) - buffers = array_view.buffers() - return [np.array(b) if b is not None else None for b in buffers] + return _geobuffers(self.storage) def __repr__(self): # Pretty WKT printing needs geoarrow-c @@ -161,3 +151,49 @@ def array(obj, type_=None, *args, **kwargs) -> GeometryExtensionArray: # Eventually we will be able to handle more types (e.g., parse wkt or wkb # into a geoarrow type) raise TypeError(f"Can't create geoarrow.array for type {type_}") + + +def _geobuffers(array, include_validity=True): + """Collect the physical buffers of ``array`` in depth-first order.""" + import numpy as np + + # While we may be able to be slightly smarter about not copying for slices + # where we can salvage pieces of the original buffer without copying, this + # line ensures correctness in the case of a sliced array (where buffers + # require taking into account the parent or multiple parents' offsets) + if array.offset != 0: + array = pa.concat_arrays([array]) + + type_ = array.type + buffers = array.buffers() + out = [] + + if include_validity: + out.append(_buffer_to_numpy(buffers[0], np.uint8)) + + if pa_types.is_struct(type_): + for i in range(type_.num_fields): + out.extend(_geobuffers(array.field(i), include_validity=False)) + elif pa_types.is_list(type_) or pa_types.is_large_list(type_): + offset_dtype = np.int64 if pa_types.is_large_list(type_) else np.int32 + out.append(_buffer_to_numpy(buffers[1], offset_dtype)) + out.extend(_geobuffers(array.values, include_validity=False)) + elif pa_types.is_fixed_size_list(type_): + out.extend(_geobuffers(array.values, include_validity=False)) + elif pa_types.is_float32(type_): + out.append(_buffer_to_numpy(buffers[1], np.float32)) + elif pa_types.is_float64(type_): + out.append(_buffer_to_numpy(buffers[1], np.float64)) + else: + raise TypeError(f"Unsupported GeoArrow storage type for geobuffers(): {type_}") + + return out + + +def _buffer_to_numpy(buffer, dtype): + import numpy as np + + if buffer is None: + return None + + return np.frombuffer(buffer, dtype=dtype) diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py index 98cfb65..d018058 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py @@ -1,6 +1,6 @@ from geoarrow.pyarrow import _type from geoarrow.pyarrow._array import array -from geoarrow.pyarrow._kernel import Kernel, _geoarrow_c +from geoarrow.pyarrow._kernel import Kernel from geoarrow.types import ( CoordType, Dimensions, @@ -215,7 +215,7 @@ def as_wkb(obj, strict_iso_wkb=False): obj = as_geoarrow(obj, _type.wkb()) if check_wkb and strict_iso_wkb and _any_ewkb(obj): - return push_all(Kernel.as_geoarrow, obj, args={"type_id": 100001}) + return push_all(Kernel.as_geoarrow, obj, args={"type_out": _type.wkb()}) else: return obj @@ -262,13 +262,7 @@ def as_geoarrow(obj, type=None, coord_type=None, promote_multi=False): if obj.type.spec == type.spec: return obj - lib = _geoarrow_c() - - cschema = lib.SchemaHolder() - type._export_to_c(cschema._addr()) - ctype = lib.CVectorType.FromExtension(cschema) - - return push_all(Kernel.as_geoarrow, obj, args={"type_id": ctype.id}) + return push_all(Kernel.as_geoarrow, obj, args={"type_out": type}) def format_wkt(obj, precision=None, max_element_size_bytes=None): diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py index e39929c..4078ed2 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/_kernel.py @@ -1,16 +1,17 @@ import sys from geoarrow.pyarrow._type import GeometryExtensionType -from geoarrow.types import box as box_spec +from geoarrow.types import CoordType, Dimensions, Encoding, GeometryType +from geoarrow.types import wkb as wkb_spec +from geoarrow.types import wkt as wkt_spec import pyarrow as pa _lazy_lib = None -_geoarrow_c_version = None def _geoarrow_c(): - global _lazy_lib, _geoarrow_c_version + global _lazy_lib if _lazy_lib is None: try: import geoarrow.c @@ -19,10 +20,6 @@ def _geoarrow_c(): raise ImportError("Requested operation requires geoarrow-c") from e _lazy_lib = geoarrow.c.lib - if hasattr(geoarrow.c, "__version_tuple__"): - _geoarrow_c_version = geoarrow.c.__version_tuple__ - else: - _geoarrow_c_version = (0, 1, 0) return _lazy_lib @@ -91,11 +88,11 @@ def visit_void_agg(type_in): @staticmethod def as_wkt(type_in): - return Kernel.as_geoarrow(type_in, 100003) + return Kernel.as_geoarrow(type_in, wkt_spec().to_pyarrow()) @staticmethod def as_wkb(type_in): - return Kernel.as_geoarrow(type_in, 100001) + return Kernel.as_geoarrow(type_in, wkb_spec().to_pyarrow()) @staticmethod def format_wkt(type_in, precision=None, max_element_size_bytes=None): @@ -107,8 +104,8 @@ def format_wkt(type_in, precision=None, max_element_size_bytes=None): ) @staticmethod - def as_geoarrow(type_in, type_id): - return Kernel("as_geoarrow", type_in, type=int(type_id)) + def as_geoarrow(type_in, type_out): + return Kernel("as_geoarrow", type_in, type=_type_id(type_out)) @staticmethod def unique_geometry_types_agg(type_in): @@ -116,19 +113,11 @@ def unique_geometry_types_agg(type_in): @staticmethod def box(type_in): - kernel = Kernel("box", type_in) - if _geoarrow_c_version <= (0, 1, 3): - return BoxKernelCompat(kernel) - else: - return kernel + return Kernel("box", type_in) @staticmethod def box_agg(type_in): - kernel = Kernel("box_agg", type_in) - if _geoarrow_c_version <= (0, 1, 3): - return BoxKernelCompat(kernel) - else: - return kernel + return Kernel("box_agg", type_in) @staticmethod def _pack_options(options): @@ -149,27 +138,56 @@ def _pack_options(options): return bytes -class BoxKernelCompat: - """A wrapper around the "box" kernel that works for geoarrow-c 0.1. - This is mostly to ease the transition for geoarrow-python CI while - all the packages are being updated.""" +def _type_id(type_out): + """Previous versions used geoarrow-c's exposed CVectorType for this; however, + the CVectorType has been deprecated in favour of geoarrow.types. This keeps + geoarrow-pyarrow working against previous and future versions of geoarrow-c + while freeing up geoarrow-c to remove code that was used only here.""" + spec = type_out.spec - def __init__(self, parent: Kernel): - self.parent = parent - self.type_out = box_spec().to_pyarrow().with_crs(parent._type_in.crs) + if spec.encoding in _SERIALIZED_TYPE_IDS: + return _SERIALIZED_TYPE_IDS[spec.encoding] - def push(self, arr): - parent_result = self.parent.push(arr) - return ( - None if parent_result is None else self._old_box_to_new_box(parent_result) - ) - - def finish(self): - return self._old_box_to_new_box(self.parent.finish()) + if spec.encoding != Encoding.GEOARROW: + raise ValueError(f"Unsupported GeoArrow encoding: {spec.encoding}") - def _old_box_to_new_box(self, array): - xmin, xmax, ymin, ymax = array.flatten() - storage = pa.StructArray.from_arrays( - [xmin, ymin, xmax, ymax], names=["xmin", "ymin", "xmax", "ymax"] + try: + return ( + _GEOMETRY_TYPE_IDS[spec.geometry_type] + + _DIMENSION_TYPE_ID_OFFSETS[spec.dimensions] + + _COORD_TYPE_ID_OFFSETS[spec.coord_type] ) - return self.type_out.wrap_array(storage) + except KeyError as e: + raise ValueError(f"Unsupported GeoArrow type: {spec}") from e + + +_SERIALIZED_TYPE_IDS = { + Encoding.WKB: 100001, + Encoding.LARGE_WKB: 100002, + Encoding.WKT: 100003, + Encoding.LARGE_WKT: 100004, + Encoding.WKB_VIEW: 100005, + Encoding.WKT_VIEW: 100006, +} + +_GEOMETRY_TYPE_IDS = { + GeometryType.POINT: 1, + GeometryType.LINESTRING: 2, + GeometryType.POLYGON: 3, + GeometryType.MULTIPOINT: 4, + GeometryType.MULTILINESTRING: 5, + GeometryType.MULTIPOLYGON: 6, + GeometryType.BOX: 990, +} + +_DIMENSION_TYPE_ID_OFFSETS = { + Dimensions.XY: 0, + Dimensions.XYZ: 1000, + Dimensions.XYM: 2000, + Dimensions.XYZM: 3000, +} + +_COORD_TYPE_ID_OFFSETS = { + CoordType.SEPARATED: 0, + CoordType.INTERLEAVED: 10000, +} diff --git a/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py b/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py index c0fed01..3eba5d1 100644 --- a/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py +++ b/geoarrow-pyarrow/src/geoarrow/pyarrow/dataset.py @@ -6,9 +6,9 @@ from concurrent.futures import ThreadPoolExecutor, wait -from geoarrow.c.lib import CoordType from geoarrow.pyarrow._kernel import Kernel from geoarrow.pyarrow._type import GeometryExtensionType, wkb, wkt +from geoarrow.types import CoordType import pyarrow as _pa import pyarrow.compute as _compute @@ -385,7 +385,7 @@ def _wrap_parent(self, filtered_parent, fragment_indices): def _build_index(self, geometry_columns, geometry_types, num_threads=None): can_use_statistics = [ - type.coord_type == CoordType.SEPARATE for type in self.geometry_types + type.coord_type == CoordType.SEPARATED for type in self.geometry_types ] if not self._use_column_statistics or not any(can_use_statistics): @@ -454,16 +454,18 @@ def _parquet_field_boxes(self, parquet_indices): boxes[i].append( { "xmin": stats_x.min, - "xmax": stats_x.max, "ymin": stats_y.min, + "xmax": stats_x.max, "ymax": stats_y.max, } ) last_row_group = row_group - type_field_names = ["xmin", "xmax", "ymin", "ymax"] - type_fields = [_pa.field(name, _pa.float64()) for name in type_field_names] + type_field_names = ["xmin", "ymin", "xmax", "ymax"] + type_fields = [ + _pa.field(name, _pa.float64(), nullable=False) for name in type_field_names + ] type = _pa.struct(type_fields) return [_pa.array(box, type=type) for box in boxes] diff --git a/geoarrow-pyarrow/tests/test_pyarrow.py b/geoarrow-pyarrow/tests/test_pyarrow.py index fd116f1..f737ce6 100644 --- a/geoarrow-pyarrow/tests/test_pyarrow.py +++ b/geoarrow-pyarrow/tests/test_pyarrow.py @@ -7,9 +7,8 @@ import pyarrow as pa import pytest from geoarrow import types -from geoarrow.c import lib from geoarrow.pyarrow import _array, _type -from geoarrow.pyarrow._kernel import Kernel +from geoarrow.pyarrow._kernel import Kernel, _type_id def test_version(): @@ -260,13 +259,78 @@ def test_kernel_as(): assert out[0].as_py() == wkb_item array = ga.array(["POINT (30 10)"], ga.wkt().with_crs(types.OGC_CRS84)) - kernel = Kernel.as_geoarrow(array.type, 1) + kernel = Kernel.as_geoarrow(array.type, ga.point()) out = kernel.push(array) assert out.type.extension_name == "geoarrow.point" assert out.type.crs.to_json_dict() == types.OGC_CRS84.to_json_dict() assert isinstance(out, _array.GeometryExtensionArray) +@pytest.mark.parametrize( + "coord_type", [ga.CoordType.SEPARATED, ga.CoordType.INTERLEAVED] +) +@pytest.mark.parametrize( + "geometry_type", + [ + ga.GeometryType.POINT, + ga.GeometryType.LINESTRING, + ga.GeometryType.POLYGON, + ga.GeometryType.MULTIPOINT, + ga.GeometryType.MULTILINESTRING, + ga.GeometryType.MULTIPOLYGON, + ], +) +@pytest.mark.parametrize( + "dimensions", + [ + ga.Dimensions.XY, + ga.Dimensions.XYZ, + ga.Dimensions.XYM, + ga.Dimensions.XYZM, + ], +) +def test_type_id_geoarrow(coord_type, geometry_type, dimensions): + type_out = types.type_spec( + encoding=ga.Encoding.GEOARROW, + coord_type=coord_type, + geometry_type=geometry_type, + dimensions=dimensions, + ).to_pyarrow() + + kernel = Kernel.as_geoarrow(ga.wkt(), type_out) + assert kernel._type_out == type_out + + +@pytest.mark.parametrize( + ("type_out", "expected"), + [ + (ga.wkb(), 100001), + (ga.large_wkb(), 100002), + (ga.wkt(), 100003), + (ga.large_wkt(), 100004), + (ga.wkb_view(), 100005), + (ga.wkt_view(), 100006), + (types.box().to_pyarrow(), 990), + (types.box(dimensions=types.Dimensions.XYZ).to_pyarrow(), 1990), + (types.box(dimensions=types.Dimensions.XYM).to_pyarrow(), 2990), + (types.box(dimensions=types.Dimensions.XYZM).to_pyarrow(), 3990), + ], +) +def test_type_id_serialized(type_out, expected): + import geoarrow.c + + assert _type_id(type_out) == expected + + # Double check against the CVectorType for versions where we + # can check (e.g., the time of this writing) + if geoarrow.c.__version_tuple__ <= (0, 3, 1): + cschema = geoarrow.c.lib.SchemaHolder() + type_out._export_to_c(cschema._addr()) + assert ( + _type_id(type_out) == geoarrow.c.lib.CVectorType.FromExtension(cschema).id + ) + + def test_kernel_format(): array = ga.array(["POINT (30.12345 10.12345)"]) kernel = Kernel.format_wkt(array.type, precision=3, max_element_size_bytes=15) @@ -307,6 +371,8 @@ def test_kernel_box_agg(): def test_kernel_visit_void(): + from geoarrow.c import lib + array = ga.array(["POINT (30 10)"], ga.wkt()) kernel = Kernel.visit_void_agg(array.type) assert kernel.push(array) is None @@ -333,72 +399,117 @@ def test_array_geobuffers(): np.testing.assert_array_equal(bufs[4], np.array([0.0, 0.0, 1.0, 0.0])) +def _assert_geobuffers_roundtrip(arr, expected_geobuffers): + actual_geobuffers = arr.geobuffers() + assert len(actual_geobuffers) == len(expected_geobuffers) + + for actual, expected in zip(actual_geobuffers, expected_geobuffers): + if expected is None: + assert actual is None + else: + np.testing.assert_array_equal(actual, expected) + assert np.may_share_memory(actual, expected) + + roundtripped = arr.type.from_geobuffers(*actual_geobuffers) + assert roundtripped.equals(arr) + + def test_point_array_from_geobuffers(): - arr = ga.point().from_geobuffers( - b"\xff", + geobuffers = ( + np.frombuffer(b"\xff", dtype=np.uint8), np.array([1.0, 2.0, 3.0]), np.array([4.0, 5.0, 6.0]), ) + arr = ga.point().from_geobuffers(*geobuffers) assert len(arr) == 3 assert ga.as_wkt(arr)[2].as_py() == "POINT (3 6)" + _assert_geobuffers_roundtrip(arr, geobuffers) - arr = ( - ga.point() - .with_coord_type(ga.CoordType.INTERLEAVED) - .from_geobuffers(None, np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) - ) + geobuffers = (None, np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])) + type_ = ga.point().with_coord_type(ga.CoordType.INTERLEAVED) + arr = type_.from_geobuffers(*geobuffers) assert len(arr) == 3 assert ga.as_wkt(arr)[2].as_py() == "POINT (5 6)" + _assert_geobuffers_roundtrip(arr, geobuffers) def test_linestring_array_from_geobuffers(): - arr = ga.linestring().from_geobuffers( + geobuffers = ( None, np.array([0, 3], dtype=np.int32), np.array([1.0, 2.0, 3.0]), np.array([4.0, 5.0, 6.0]), ) + arr = ga.linestring().from_geobuffers(*geobuffers) assert len(arr) == 1 assert ga.as_wkt(arr)[0].as_py() == "LINESTRING (1 4, 2 5, 3 6)" + _assert_geobuffers_roundtrip(arr, geobuffers) + + +def test_linestring_array_geobuffers_slice(): + arr = ga.linestring().from_geobuffers( + np.frombuffer(b"\x05", dtype=np.uint8), + np.array([0, 2, 5, 6], dtype=np.int32), + np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), + np.array([11.0, 12.0, 13.0, 14.0, 15.0, 16.0]), + ) + sliced = arr[1:3] + assert sliced.offset == 1 + assert sliced.null_count == 1 + + geobuffers = sliced.geobuffers() + np.testing.assert_array_equal(geobuffers[0], np.array([2], dtype=np.uint8)) + np.testing.assert_array_equal(geobuffers[1], np.array([0, 3, 4], dtype=np.int32)) + np.testing.assert_array_equal(geobuffers[2], np.array([3.0, 4.0, 5.0, 6.0])) + np.testing.assert_array_equal(geobuffers[3], np.array([13.0, 14.0, 15.0, 16.0])) + + roundtripped = sliced.type.from_geobuffers(*geobuffers) + assert roundtripped.equals(sliced) def test_polygon_array_from_geobuffers(): - arr = ga.polygon().from_geobuffers( + geobuffers = ( None, np.array([0, 1], dtype=np.int32), np.array([0, 4], dtype=np.int32), np.array([1.0, 2.0, 3.0, 1.0]), np.array([4.0, 5.0, 6.0, 4.0]), ) + arr = ga.polygon().from_geobuffers(*geobuffers) assert len(arr) == 1 assert ga.as_wkt(arr)[0].as_py() == "POLYGON ((1 4, 2 5, 3 6, 1 4))" + _assert_geobuffers_roundtrip(arr, geobuffers) def test_multipoint_array_from_geobuffers(): - arr = ga.multipoint().from_geobuffers( + geobuffers = ( None, np.array([0, 3], dtype=np.int32), np.array([1.0, 2.0, 3.0]), np.array([4.0, 5.0, 6.0]), ) + arr = ga.multipoint().from_geobuffers(*geobuffers) assert len(arr) == 1 assert ga.as_wkt(arr)[0].as_py() == "MULTIPOINT (1 4, 2 5, 3 6)" + _assert_geobuffers_roundtrip(arr, geobuffers) def test_multilinestring_array_from_geobuffers(): - arr = ga.multilinestring().from_geobuffers( + geobuffers = ( None, np.array([0, 1], dtype=np.int32), np.array([0, 4], dtype=np.int32), np.array([1.0, 2.0, 3.0, 1.0]), np.array([4.0, 5.0, 6.0, 4.0]), ) + arr = ga.multilinestring().from_geobuffers(*geobuffers) assert len(arr) == 1 assert ga.as_wkt(arr)[0].as_py() == "MULTILINESTRING ((1 4, 2 5, 3 6, 1 4))" + _assert_geobuffers_roundtrip(arr, geobuffers) def test_multipolygon_array_from_geobuffers(): - arr = ga.multipolygon().from_geobuffers( + geobuffers = ( None, np.array([0, 1], dtype=np.int32), np.array([0, 1], dtype=np.int32), @@ -406,91 +517,23 @@ def test_multipolygon_array_from_geobuffers(): np.array([1.0, 2.0, 3.0, 1.0]), np.array([4.0, 5.0, 6.0, 4.0]), ) + arr = ga.multipolygon().from_geobuffers(*geobuffers) assert len(arr) == 1 assert ga.as_wkt(arr)[0].as_py() == "MULTIPOLYGON (((1 4, 2 5, 3 6, 1 4)))" + _assert_geobuffers_roundtrip(arr, geobuffers) def test_box_array_from_geobuffers(): - arr = ( - types.box() - .to_pyarrow() - .from_geobuffers( - b"\xff", - np.array([1.0, 2.0, 3.0]), - np.array([4.0, 5.0, 6.0]), - np.array([7.0, 8.0, 9.0]), - np.array([10.0, 11.0, 12.0]), - ) + geobuffers = ( + np.frombuffer(b"\xff", dtype=np.uint8), + np.array([1.0, 2.0, 3.0]), + np.array([4.0, 5.0, 6.0]), + np.array([7.0, 8.0, 9.0]), + np.array([10.0, 11.0, 12.0]), ) + arr = types.box().to_pyarrow().from_geobuffers(*geobuffers) assert len(arr) == 3 assert arr[2].bounds == {"xmin": 3.0, "ymin": 6.0, "xmax": 9.0, "ymax": 12.0} assert "BoxArray" in repr(arr) assert "'xmin': 3.0" in repr(arr) - - -# Easier to test here because we have actual geoarrow arrays to parse -def test_c_array_view(): - arr = ga.as_geoarrow(["POLYGON ((0 0, 1 0, 0 1, 0 0))"]) - - cschema = lib.SchemaHolder() - arr.type._export_to_c(cschema._addr()) - carray = lib.ArrayHolder() - arr._export_to_c(carray._addr()) - - array_view = lib.CArrayView(carray, cschema) - buffers = array_view.buffers() - assert len(buffers) == 5 - - buffer_arrays = [np.array(b) for b in buffers] - - assert buffers[0] is None - - assert buffer_arrays[1].shape == (2,) - assert buffer_arrays[1][0] == 0 - assert buffer_arrays[1][1] == 1 - - assert buffer_arrays[2].shape == (2,) - assert buffer_arrays[2][0] == 0 - assert buffer_arrays[2][1] == 4 - - assert buffer_arrays[3].shape == (4,) - assert buffer_arrays[3][1] == 1 - assert buffer_arrays[3][3] == 0 - - assert buffer_arrays[4].shape == (4,) - assert buffer_arrays[4][1] == 0 - assert buffer_arrays[4][3] == 0 - - -def test_c_array_view_interleaved(): - arr = ga.array(["POLYGON ((0 0, 1 0, 0 1, 0 0))"]) - arr = ga.as_geoarrow(arr, ga.polygon().with_coord_type(ga.CoordType.INTERLEAVED)) - - cschema = lib.SchemaHolder() - arr.type._export_to_c(cschema._addr()) - carray = lib.ArrayHolder() - arr._export_to_c(carray._addr()) - - array_view = lib.CArrayView(carray, cschema) - buffers = array_view.buffers() - assert len(buffers) == 4 - - buffer_arrays = [np.array(b) for b in buffers] - - assert buffers[0] is None - - assert buffer_arrays[1].shape == (2,) - assert buffer_arrays[1][0] == 0 - assert buffer_arrays[1][1] == 1 - - assert buffer_arrays[2].shape == (2,) - assert buffer_arrays[2][0] == 0 - assert buffer_arrays[2][1] == 4 - - assert buffer_arrays[3].shape == (8,) - assert buffer_arrays[3][0] == 0 - assert buffer_arrays[3][1] == 0 - assert buffer_arrays[3][2] == 1 - assert buffer_arrays[3][3] == 0 - assert buffer_arrays[3][6] == 0 - assert buffer_arrays[3][7] == 0 + _assert_geobuffers_roundtrip(arr, geobuffers) diff --git a/geoarrow-types/src/geoarrow/types/type_pyarrow.py b/geoarrow-types/src/geoarrow/types/type_pyarrow.py index 74b50cd..2b77f70 100644 --- a/geoarrow-types/src/geoarrow/types/type_pyarrow.py +++ b/geoarrow-types/src/geoarrow/types/type_pyarrow.py @@ -708,7 +708,9 @@ def _from_buffer_ordinate(x): if mv.format != "d": mv = mv.cast("d") - return pa.array(mv, pa.float64()) + return pa.Array.from_buffers( + pa.float64(), len(mv), buffers=[None, pa.py_buffer(mv)] + ) def _pybuffer_offset(x): @@ -793,7 +795,7 @@ def _from_buffers_multipolygon( return pa.Array.from_buffers( type_, n_offsets - 1, - buffers=[validity, pa.py_buffer(ring_offsets)], + buffers=[validity, pa.py_buffer(polygon_offsets)], children=[polygons], )