blob: 8499a021929bc4f119ae9dd741ce1f17685af262 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import json
import pyarrow as pa
import pyproj
import pytest
from tests.test_base import TestBase
from pyspark.sql.types import StringType, StructType
from sedona.spark.geoarrow.geoarrow import (
dataframe_to_arrow,
unique_srid_from_ewkb,
wrap_geoarrow_field,
wrap_geoarrow_extension,
)
class TestGeoArrow(TestBase):
def test_to_geoarrow_no_geometry(self):
schema = StructType().add("wkt", StringType())
wkt_df = TestGeoArrow.spark.createDataFrame(zip(TEST_WKT), schema)
wkt_table = dataframe_to_arrow(wkt_df)
assert wkt_table == pa.table({"wkt": TEST_WKT})
def test_to_geoarrow_zero_rows(self):
schema = StructType().add("wkt", StringType())
wkt_df = TestGeoArrow.spark.createDataFrame(zip(TEST_WKT), schema).limit(0)
wkt_table = dataframe_to_arrow(wkt_df)
assert wkt_table == pa.table({"wkt": pa.array([], pa.utf8())})
def test_to_geoarrow_with_geometry(self):
schema = StructType().add("wkt", StringType())
wkt_df = TestGeoArrow.spark.createDataFrame(zip(TEST_WKT), schema)
geo_df = wkt_df.selectExpr("wkt", "ST_GeomFromText(wkt) AS geom")
geo_table = dataframe_to_arrow(geo_df)
assert geo_table.column_names == ["wkt", "geom"]
geom = geo_table["geom"]
if isinstance(geom.type, pa.ExtensionType):
assert geom.type.extension_name == "geoarrow.wkb"
assert geom.type.crs is None
else:
field = geo_table.field("geom")
assert field.metadata is not None
assert b"ARROW:extension:name" in field.metadata
assert field.metadata[b"ARROW:extension:name"] == b"geoarrow.wkb"
assert field.metadata[b"ARROW:extension:metadata"] == b"{}"
def test_to_geoarrow_with_geometry_with_srid(self):
schema = StructType().add("wkt", StringType())
wkt_df = TestGeoArrow.spark.createDataFrame(zip(TEST_WKT), schema)
geo_df = wkt_df.selectExpr("ST_SetSRID(ST_GeomFromText(wkt), 4326) AS geom")
geo_table = dataframe_to_arrow(geo_df)
geom = geo_table["geom"]
if isinstance(geom.type, pa.ExtensionType):
assert geom.type.extension_name == "geoarrow.wkb"
# CRS handling in geoarrow-types was updated in 0.2, but this should work for both
assert "EPSG:4326" in repr(geom.type.crs)
else:
field = geo_table.field("geom")
assert field.metadata is not None
assert b"ARROW:extension:name" in field.metadata
assert field.metadata[b"ARROW:extension:name"] == b"geoarrow.wkb"
metadata = json.loads(field.metadata[b"ARROW:extension:metadata"])
assert "crs" in metadata
assert "id" in metadata["crs"]
assert metadata["crs"]["id"] == {"authority": "EPSG", "code": 4326}
def test_wrap_field(self):
col_empty = pa.array([], pa.binary())
field = pa.field("foofy", col_empty.type)
# With pyproj object crs override
wrapped = wrap_geoarrow_field(field, col_empty, pyproj.CRS("OGC:CRS84"))
assert wrapped.metadata[b"ARROW:extension:name"] == b"geoarrow.wkb"
assert b"WGS 84 (CRS84)" in wrapped.metadata[b"ARROW:extension:metadata"]
# With arbitrary string override
wrapped = wrap_geoarrow_field(field, col_empty, "OGC:CRS84")
assert wrapped.metadata[b"ARROW:extension:name"] == b"geoarrow.wkb"
assert b"WGS 84 (CRS84)" in wrapped.metadata[b"ARROW:extension:metadata"]
# With no output CRS
wrapped = wrap_geoarrow_field(field, col_empty, None)
assert wrapped.metadata[b"ARROW:extension:name"] == b"geoarrow.wkb"
assert wrapped.metadata[b"ARROW:extension:metadata"] == b"{}"
# With inferred crs
col = pa.array(TEST_WKB["ewkb_srid_little_endian"])
wrapped = wrap_geoarrow_field(field, col, None)
assert wrapped.metadata[b"ARROW:extension:name"] == b"geoarrow.wkb"
assert b"WGS 84" in wrapped.metadata[b"ARROW:extension:metadata"]
def test_wrap_extension(self):
gat = pytest.importorskip("geoarrow.types")
col_empty = pa.array([], pa.binary())
spec = gat.wkb()
# With pyproj object crs override
wrapped = wrap_geoarrow_extension(col_empty, spec, pyproj.CRS("OGC:CRS84"))
assert wrapped.type.encoding == gat.Encoding.WKB
assert "WGS 84 (CRS84)" in wrapped.type.crs.to_json()
# With no output CRS
wrapped = wrap_geoarrow_extension(col_empty, spec, None)
assert wrapped.type.encoding == gat.Encoding.WKB
assert wrapped.type.crs is None
# With arbitrary string override
wrapped = wrap_geoarrow_extension(col_empty, spec, "OGC:CRS84")
assert wrapped.type.encoding == gat.Encoding.WKB
assert "WGS 84 (CRS84)" in wrapped.type.crs.to_json()
# With inferred crs
col = pa.array(TEST_WKB["ewkb_srid_little_endian"])
wrapped = wrap_geoarrow_extension(col, spec, None)
assert wrapped.type.encoding == gat.Encoding.WKB
assert "WGS 84" in wrapped.type.crs.to_json()
def test_unique_srid(self):
# Zero size should return None
assert unique_srid_from_ewkb(pa.array([], pa.binary())) is None
# EWKB with no SRID should return None here
assert unique_srid_from_ewkb(pa.array(TEST_WKB["ewkb_little_endian"])) is None
assert unique_srid_from_ewkb(pa.array(TEST_WKB["ewkb_big_endian"])) is None
# EWKB with SRID
assert (
unique_srid_from_ewkb(pa.array(TEST_WKB["ewkb_srid_little_endian"])) == 4326
)
assert unique_srid_from_ewkb(pa.array(TEST_WKB["ewkb_srid_big_endian"])) == 4326
# In the presence of geometries with SRID and without SRID, the geometries
# without SRID are not counted
assert (
unique_srid_from_ewkb(
pa.array(
TEST_WKB["ewkb_little_endian"] + TEST_WKB["ewkb_srid_little_endian"]
)
)
== 4326
)
# If there is more than one SRID present, we return None
# "SRID=1234;POINT (10 20)"
ewkb_other_srid = b"\x01\x01\x00\x00 \xd2\x04\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@"
assert (
unique_srid_from_ewkb(
pa.array(TEST_WKB["ewkb_srid_little_endian"] + [ewkb_other_srid])
)
is None
)
# Mixed endian (unlikely) should return None
assert (
unique_srid_from_ewkb(
pa.array(
TEST_WKB["ewkb_srid_big_endian"]
+ TEST_WKB["ewkb_srid_little_endian"]
)
)
is None
)
TEST_WKT = [
"POINT (10 20)",
"POINT (10 20 30)",
"LINESTRING (10 20, 30 40)",
"LINESTRING (10 20 30, 40 50 60)",
"POLYGON ((10 10, 20 20, 20 10, 10 10))",
"POLYGON ((10 10 10, 20 20 10, 20 10 10, 10 10 10))",
"POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))",
]
# A little tricky to generate EWKB test data because shapely/GEOS
# does not support M values. In R we have two options that can do this
# (sf and wk), so the test data was generated there. We need test data
# with all dimensions because this affects the high bytes used to detect
# the presence of an SRID; we need both endians because this affects the
# decoding of the high bits and the SRID value.
#
# ewkt <- c(
# "POINT (10 20)",
# "POINT Z (10 20 2)",
# "POINT M (10 20 2)",
# "POINT ZM (10 20 2 3)",
# "POINT (10 20)",
# "LINESTRING (10 20, 30 40)",
# "POLYGON ((10 10, 20 20, 20 10, 10 10))",
# "MULTIPOINT ((10 20))",
# "MULTILINESTRING ((10 20, 30 40))",
# "MULTIPOLYGON (((10 10, 20 20, 20 10, 10 10)))",
# "GEOMETRYCOLLECTION (POINT (10 20))"
# )
#
# ewkt_srid <- paste0("SRID=4326;", ewkt)
#
# ewkb_test <- list(
# ewkb_little_endian = wk::wkt_translate_wkb(ewkt, endian = 1),
# ewkb_big_endian = wk::wkt_translate_wkb(ewkt, endian = 0),
# ewkb_srid_little_endian = wk::wkt_translate_wkb(ewkt_srid, endian = 1),
# ewkb_srid_big_endian = wk::wkt_translate_wkb(ewkt_srid, endian = 0)
# )
#
# # Generate a version of this we can paste into Python, with a bit of
# # indirection because reticulate converts raw vectors as bytearray
# py <- reticulate::py
# py$ekwb <- ewkb_test
# py_ewkb <- reticulate::py_eval(
# "{k: [bytes(i) for i in v] for k, v in ekwb.items()}",
# convert = FALSE
# )
# print(py_ewkb)
TEST_WKB = {
"ewkb_little_endian": [
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
b"\x01\x01\x00\x00\x80\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x00@",
b"\x01\x01\x00\x00@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x00@",
b"\x01\x01\x00\x00\xc0\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@",
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
b"\x01\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00D@",
b"\x01\x03\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@",
b"\x01\x04\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
b"\x01\x05\x00\x00\x00\x01\x00\x00\x00\x01\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00D@",
b"\x01\x06\x00\x00\x00\x01\x00\x00\x00\x01\x03\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@",
b"\x01\x07\x00\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
],
"ewkb_big_endian": [
b"\x00\x00\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
b"\x00\x80\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00",
b"\x00@\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00",
b"\x00\xc0\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00@\x08\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x02\x00\x00\x00\x02@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x04@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x00\x02\x00\x00\x00\x02@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x06\x00\x00\x00\x01\x00\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x04@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00",
b"\x00\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
],
"ewkb_srid_little_endian": [
b"\x01\x01\x00\x00 \xe6\x10\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
b"\x01\x01\x00\x00\xa0\xe6\x10\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x00@",
b"\x01\x01\x00\x00`\xe6\x10\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x00@",
b"\x01\x01\x00\x00\xe0\xe6\x10\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@",
b"\x01\x01\x00\x00 \xe6\x10\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
b"\x01\x02\x00\x00 \xe6\x10\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00D@",
b"\x01\x03\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@",
b"\x01\x04\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
b"\x01\x05\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x01\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00D@",
b"\x01\x06\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x01\x03\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00$@",
b"\x01\x07\x00\x00 \xe6\x10\x00\x00\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x004@",
],
"ewkb_srid_big_endian": [
b"\x00 \x00\x00\x01\x00\x00\x10\xe6@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
b"\x00\xa0\x00\x00\x01\x00\x00\x10\xe6@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00",
b"\x00`\x00\x00\x01\x00\x00\x10\xe6@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00",
b"\x00\xe0\x00\x00\x01\x00\x00\x10\xe6@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00@\x08\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x01\x00\x00\x10\xe6@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x02\x00\x00\x10\xe6\x00\x00\x00\x02@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x03\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x04@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x04\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x05\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x00\x02\x00\x00\x00\x02@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x06\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x04@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00",
b"\x00 \x00\x00\x07\x00\x00\x10\xe6\x00\x00\x00\x01\x00\x00\x00\x00\x01@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00",
],
}