use std::sync::Arc;
use arrow_array::Array as _;
use arrow_array::ArrayRef as ArrowArrayRef;
use arrow_array::BinaryArray;
use arrow_array::BinaryViewArray;
use arrow_array::LargeBinaryArray;
use arrow_array::cast::AsArray;
use arrow_schema::DataType;
use arrow_schema::Field;
use arrow_schema::extension::ExtensionType as _;
use geo_traits::to_geo::ToGeoGeometry;
use geo_types::Coord;
use geo_types::Geometry;
use geo_types::LineString;
use geo_types::Polygon;
use geoarrow::datatypes::Crs;
use geoarrow::datatypes::Metadata;
use geoarrow::datatypes::WkbType;
use rstest::rstest;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::arrays::ExtensionArray;
use vortex_array::arrays::varbin::builder::VarBinBuilder;
use vortex_array::arrow::ArrowSessionExt;
use vortex_array::dtype::DType;
use vortex_array::dtype::Nullability;
use vortex_array::dtype::extension::ExtDType;
use vortex_array::dtype::extension::ExtVTable;
use vortex_error::VortexResult;
use vortex_error::vortex_err;
use wkb::writer::WriteOptions;
use super::SESSION;
use crate::extension::GeoMetadata;
use crate::extension::WellKnownBinary;
fn test_polygon() -> Geometry {
Geometry::Polygon(Polygon::new(
LineString::new(vec![
Coord::zero(),
Coord { x: 100.0, y: 0.0 },
Coord { x: 100.0, y: 100.0 },
Coord { x: 0.0, y: 100.0 },
Coord::zero(),
]),
vec![],
))
}
fn wkb_extension_array() -> VortexResult<(Vec<u8>, vortex_array::ArrayRef)> {
let mut buf = Vec::new();
wkb::writer::write_geometry(&mut buf, &test_polygon(), &WriteOptions::default())
.map_err(|e| vortex_err!("writing WKB failed: {e}"))?;
let mut builder = VarBinBuilder::<i32>::with_capacity(3);
builder.append_value(&buf);
builder.append_value(&buf);
builder.append_value(&buf);
let dtype = ExtDType::<WellKnownBinary>::try_new(
GeoMetadata {
crs: Some("EPSG:4326".to_string()),
},
DType::Binary(Nullability::NonNullable),
)?;
let storage = builder.finish(DType::Binary(Nullability::NonNullable));
let array = ExtensionArray::new(dtype.erased(), storage.into_array()).into_array();
Ok((buf, array))
}
fn wkb_field(name: &str, data_type: DataType, nullable: bool, crs: Option<&str>) -> Field {
let crs = crs
.map(|crs| Crs::from_unknown_crs_type(crs.to_string()))
.unwrap_or_default();
let wkb_type = WkbType::new(Arc::new(Metadata::new(crs, None)));
Field::new(name, data_type, nullable).with_extension_type(wkb_type)
}
fn assert_imported_wkb_dtype(dtype: &DType, expected_crs: Option<&str>, nullable: bool) {
let DType::Extension(ext) = dtype else {
panic!("expected Extension dtype, got {dtype}");
};
assert!(ext.is::<WellKnownBinary>());
assert_eq!(ext.storage_dtype(), &DType::Binary(nullable.into()));
let geo = ext.metadata::<WellKnownBinary>();
assert_eq!(geo.crs.as_deref(), expected_crs);
}
#[test]
fn scalar_unpacks_to_geometry() -> VortexResult<()> {
let mut ctx = SESSION.create_execution_ctx();
let (_, array) = wkb_extension_array()?;
let dtype = ExtDType::<WellKnownBinary>::try_new(
GeoMetadata {
crs: Some("EPSG:4326".to_string()),
},
DType::Binary(Nullability::NonNullable),
)?;
for idx in 0..3 {
let geom = array.execute_scalar(idx, &mut ctx)?;
let wkb = WellKnownBinary::unpack_native(&dtype, geom.value().unwrap())?;
assert_eq!(wkb.to_geometry(), test_polygon());
}
Ok(())
}
#[test]
fn export_field_carries_extension() -> VortexResult<()> {
let (_, array) = wkb_extension_array()?;
let field = SESSION.arrow().to_arrow_field("geom", array.dtype())?;
assert_eq!(field.extension_type_name(), Some(WkbType::NAME));
assert_eq!(field.data_type(), &DataType::BinaryView);
Ok(())
}
#[rstest]
#[case::binary(DataType::Binary)]
#[case::large_binary(DataType::LargeBinary)]
#[case::binary_view(DataType::BinaryView)]
fn exports_to_binary_family(#[case] data_type: DataType) -> VortexResult<()> {
let mut ctx = SESSION.create_execution_ctx();
let (wkb_bytes, array) = wkb_extension_array()?;
let field = Field::new("geom", data_type.clone(), false)
.with_extension_type(WkbType::new(Default::default()));
let exported = SESSION
.arrow()
.execute_arrow(array, Some(&field), &mut ctx)?;
assert_eq!(exported.data_type(), &data_type);
let values: Vec<&[u8]> = match &data_type {
DataType::Binary => exported.as_binary::<i32>().iter().flatten().collect(),
DataType::LargeBinary => exported.as_binary::<i64>().iter().flatten().collect(),
DataType::BinaryView => exported.as_binary_view().iter().flatten().collect(),
_ => unreachable!("cases cover only the binary family"),
};
assert_eq!(values.len(), 3);
for value in values {
assert_eq!(value, wkb_bytes.as_slice());
}
Ok(())
}
#[test]
fn import_field_recovers_extension() -> VortexResult<()> {
let field = wkb_field("geom", DataType::Binary, false, Some("EPSG:4326"));
let dtype = SESSION.arrow().from_arrow_field(&field)?;
assert_imported_wkb_dtype(&dtype, Some("EPSG:4326"), false);
Ok(())
}
#[test]
fn import_field_without_crs() -> VortexResult<()> {
let field = wkb_field("geom", DataType::BinaryView, true, None);
let dtype = SESSION.arrow().from_arrow_field(&field)?;
assert_imported_wkb_dtype(&dtype, None, true);
Ok(())
}
#[test]
fn imports_from_binary() -> VortexResult<()> {
let mut ctx = SESSION.create_execution_ctx();
let (wkb_bytes, _) = wkb_extension_array()?;
let arrow: ArrowArrayRef = Arc::new(BinaryArray::from_iter_values([
wkb_bytes.as_slice(),
wkb_bytes.as_slice(),
wkb_bytes.as_slice(),
]));
let field = wkb_field("geom", DataType::Binary, false, Some("EPSG:4326"));
let imported = SESSION.arrow().from_arrow_array(arrow, &field)?;
assert_imported_wkb_dtype(imported.dtype(), Some("EPSG:4326"), false);
for idx in 0..3 {
let geom = imported.execute_scalar(idx, &mut ctx)?;
assert_eq!(geom.value().unwrap().as_binary().as_slice(), wkb_bytes);
}
Ok(())
}
#[test]
fn imports_from_large_binary() -> VortexResult<()> {
let (wkb_bytes, _) = wkb_extension_array()?;
let arrow: ArrowArrayRef = Arc::new(LargeBinaryArray::from_iter_values([
wkb_bytes.as_slice(),
wkb_bytes.as_slice(),
]));
let field = wkb_field("geom", DataType::LargeBinary, false, Some("EPSG:4326"));
let imported = SESSION.arrow().from_arrow_array(arrow, &field)?;
assert_imported_wkb_dtype(imported.dtype(), Some("EPSG:4326"), false);
assert_eq!(imported.len(), 2);
Ok(())
}
#[test]
fn imports_from_binary_view() -> VortexResult<()> {
let (wkb_bytes, _) = wkb_extension_array()?;
let arrow: ArrowArrayRef = Arc::new(BinaryViewArray::from_iter_values([
wkb_bytes.as_slice(),
wkb_bytes.as_slice(),
wkb_bytes.as_slice(),
wkb_bytes.as_slice(),
]));
let field = wkb_field("geom", DataType::BinaryView, false, None);
let imported = SESSION.arrow().from_arrow_array(arrow, &field)?;
assert_imported_wkb_dtype(imported.dtype(), None, false);
assert_eq!(imported.len(), 4);
Ok(())
}
#[test]
fn roundtrips_through_arrow() -> VortexResult<()> {
let mut ctx = SESSION.create_execution_ctx();
let (wkb_bytes, original) = wkb_extension_array()?;
let field = Field::new("geom", DataType::BinaryView, false)
.with_extension_type(WkbType::new(Default::default()));
let exported = SESSION
.arrow()
.execute_arrow(original, Some(&field), &mut ctx)?;
let arrow_field = wkb_field("geom", DataType::BinaryView, false, Some("EPSG:4326"));
let reimported = SESSION.arrow().from_arrow_array(exported, &arrow_field)?;
assert_imported_wkb_dtype(reimported.dtype(), Some("EPSG:4326"), false);
for idx in 0..3 {
let geom = reimported.execute_scalar(idx, &mut ctx)?;
assert_eq!(geom.value().unwrap().as_binary().as_slice(), wkb_bytes);
}
Ok(())
}