Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/spatial/modules/geos/geos_geometry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ inline GeosGeometry GeosGeometry::get_transformed(const double matrix[6]) const
}

inline GeosGeometry GeosGeometry::get_gridded(double grid_size) const {
return GeosGeometry(handle, GEOSGeom_setPrecision_r(handle, geom, grid_size, GEOS_PREC_NO_TOPO));
return GeosGeometry(handle, GEOSGeom_setPrecision_r(handle, geom, grid_size, GEOS_PREC_VALID_OUTPUT));
}

inline GeosGeometry GeosGeometry::get_maximum_inscribed_circle() const {
Expand Down
16 changes: 10 additions & 6 deletions src/spatial/modules/geos/geos_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,6 @@ struct ST_AsMVTGeom {
const auto &blob = geom_data[geom_idx];
auto geom = lstate.Deserialize(blob);

// Orient polygons in place
geom.orient_polygons(true);

// Compute bounds
const auto extent = bind_data.extent;

Expand Down Expand Up @@ -363,10 +360,14 @@ struct ST_AsMVTGeom {
const auto transformed = geom.get_transformed(affine_matrix);

// Snap to grid (round coordinates to integers)
const auto snapped = transformed.get_gridded(1.0);
auto snapped = transformed.get_gridded(1.0);

// Should we clip? if not, return the snapped geometry
if (!bind_data.clip) {

// But first orient in place
snapped.orient_polygons(true);

res_data[out_idx] = lstate.Serialize(result, snapped);
continue;
}
Expand All @@ -385,7 +386,10 @@ struct ST_AsMVTGeom {
}

// Snap again to clean up any potential issues from clipping
const auto cleaned_clipped = clipped.get_gridded(1.0);
auto cleaned_clipped = clipped.get_gridded(1.0);

// Also orient the polygons in place
cleaned_clipped.orient_polygons(true);

res_data[out_idx] = lstate.Serialize(result, cleaned_clipped);
}
Expand Down Expand Up @@ -1286,7 +1290,7 @@ struct ST_DistanceWithin {
});

func.SetDescription(R"(
Returns if two geometries are within a target distance of each-other
Returns true if two geometries are within a target distance of each-other
)");

func.SetTag("ext", "spatial");
Expand Down
47 changes: 35 additions & 12 deletions src/spatial/modules/main/spatial_functions_scalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4804,7 +4804,7 @@ struct ST_GeomFromWKB {
y_data[i] = vertex.y;
}

if (args.AllConstant()) {
if (args.AllConstant() || args.size() == 1) {
result.SetVectorType(VectorType::CONSTANT_VECTOR);
}
}
Expand Down Expand Up @@ -4872,7 +4872,7 @@ struct ST_GeomFromWKB {

ListVector::SetListSize(result, total_size);

if (args.AllConstant()) {
if (args.AllConstant() || args.size() == 1) {
result.SetVectorType(VectorType::CONSTANT_VECTOR);
}
}
Expand Down Expand Up @@ -4967,7 +4967,7 @@ struct ST_GeomFromWKB {
ListVector::SetListSize(result, total_ring_count);
ListVector::SetListSize(ring_vec, total_point_count);

if (count == 1) {
if (args.AllConstant() || args.size() == 1) {
result.SetVectorType(VectorType::CONSTANT_VECTOR);
}
}
Expand All @@ -4986,8 +4986,16 @@ struct ST_GeomFromWKB {
static void Register(ExtensionLoader &loader) {
FunctionBuilder::RegisterScalar(loader, "ST_Point2DFromWKB", [](ScalarFunctionBuilder &builder) {
builder.AddVariant([](ScalarFunctionVariantBuilder &variant) {
variant.AddParameter("point", GeoTypes::POINT_2D());
variant.SetReturnType(GeoTypes::GEOMETRY());
variant.AddParameter("wkb", GeoTypes::WKB_BLOB());
variant.SetReturnType(GeoTypes::POINT_2D());

variant.SetInit(LocalState::Init);
variant.SetFunction(ExecutePoint);
});

builder.AddVariant([](ScalarFunctionVariantBuilder &variant) {
variant.AddParameter("blob", LogicalType::BLOB);
variant.SetReturnType(GeoTypes::POINT_2D());

variant.SetInit(LocalState::Init);
variant.SetFunction(ExecutePoint);
Expand All @@ -5001,8 +5009,16 @@ struct ST_GeomFromWKB {

FunctionBuilder::RegisterScalar(loader, "ST_LineString2DFromWKB", [](ScalarFunctionBuilder &builder) {
builder.AddVariant([](ScalarFunctionVariantBuilder &variant) {
variant.AddParameter("linestring", GeoTypes::LINESTRING_2D());
variant.SetReturnType(GeoTypes::GEOMETRY());
variant.AddParameter("wkb", GeoTypes::WKB_BLOB());
variant.SetReturnType(GeoTypes::LINESTRING_2D());

variant.SetInit(LocalState::Init);
variant.SetFunction(ExecuteLineString);
});

builder.AddVariant([](ScalarFunctionVariantBuilder &variant) {
variant.AddParameter("blob", LogicalType::BLOB);
variant.SetReturnType(GeoTypes::LINESTRING_2D());

variant.SetInit(LocalState::Init);
variant.SetFunction(ExecuteLineString);
Expand All @@ -5016,8 +5032,15 @@ struct ST_GeomFromWKB {

FunctionBuilder::RegisterScalar(loader, "ST_Polygon2DFromWKB", [](ScalarFunctionBuilder &builder) {
builder.AddVariant([](ScalarFunctionVariantBuilder &variant) {
variant.AddParameter("polygon", GeoTypes::POLYGON_2D());
variant.SetReturnType(GeoTypes::GEOMETRY());
variant.AddParameter("wkb", GeoTypes::WKB_BLOB());
variant.SetReturnType(GeoTypes::POLYGON_2D());

variant.SetInit(LocalState::Init);
variant.SetFunction(ExecutePolygon);
});
builder.AddVariant([](ScalarFunctionVariantBuilder &variant) {
variant.AddParameter("blob", LogicalType::BLOB);
variant.SetReturnType(GeoTypes::POLYGON_2D());

variant.SetInit(LocalState::Init);
variant.SetFunction(ExecutePolygon);
Expand Down Expand Up @@ -5252,7 +5275,7 @@ struct ST_LineInterpolatePoint {
auto &lstate = LocalState::ResetAndGet(state);

BinaryExecutor::Execute<string_t, double, string_t>(
args.data[0], args.data[1], result, args.size(), [&](const string_t &blob, const double faction) {
args.data[0], args.data[1], result, args.size(), [&](const string_t &blob, const double fraction) {
sgl::geometry geom;
lstate.Deserialize(blob, geom);

Expand All @@ -5261,7 +5284,7 @@ struct ST_LineInterpolatePoint {
}

sgl::vertex_xyzm out_vertex = {0, 0, 0, 0};
if (sgl::linestring::interpolate(geom, faction, out_vertex)) {
if (sgl::linestring::interpolate(geom, fraction, out_vertex)) {
sgl::geometry point(sgl::geometry_type::POINT, geom.has_z(), geom.has_m());
point.set_vertex_array(&out_vertex, 1);
return lstate.Serialize(result, point);
Expand Down Expand Up @@ -6092,7 +6115,7 @@ struct ST_Hilbert {
static constexpr auto DESCRIPTION = R"(
Encodes the X and Y values as the hilbert curve index for a curve covering the given bounding box.
If a geometry is provided, the center of the approximate bounding box is used as the point to encode.
If no bounding box is provided, the hilbert curve index is mapped to the full range of a single-presicion float.
If no bounding box is provided, the hilbert curve index is mapped to the full range of a single-precision float.
For the BOX_2D and BOX_2DF variants, the center of the box is used as the point to encode.
)";

Expand Down
2 changes: 1 addition & 1 deletion src/spatial/modules/mvt/mvt_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ class MVTFeatureBuilder {
geometry.push_back((1 & 0x7) | (1 << 3)); // MoveTo, 1 part
geometry.push_back(protozero::encode_zigzag32(x - cursor_x));
geometry.push_back(protozero::encode_zigzag32(y - cursor_y));
geometry.push_back((2 & 0x7) | ((vertex_count - 2) << 3)); // LineTo, part count
geometry.push_back((2 & 0x7) | ((vertex_count - 1) << 3)); // LineTo, part count
} else {
geometry.push_back(protozero::encode_zigzag32(x - cursor_x));
geometry.push_back(protozero::encode_zigzag32(y - cursor_y));
Expand Down
19 changes: 19 additions & 0 deletions test/sql/geometry/st_2d_fromwkb.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# name: test/sql/geometry/st_2d_fromwkb.test
# group: [geometry]

require spatial

query I
select ST_Point2DFromWKB(ST_AsWKB(ST_Point(1, 2)));
----
POINT (1 2)

query I
SELECT ST_Linestring2DFromWKB(ST_AsWKB(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 2)')));
----
LINESTRING (0 0, 1 1, 2 2)

query I
SELECT ST_Polygon2DFromWKB(ST_AsWKB(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')));
----
POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))
157 changes: 157 additions & 0 deletions test/sql/mvt/st_asmvt_linestring.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# name: test/sql/mvt/st_asmvt_linestring.test
# group: [mvt]

require spatial

# Test LINESTRING encoding
statement ok
COPY (
SELECT st_asmvt(
{"geom": geom},
'lines'
) as mvt
FROM (
SELECT
st_geomfromtext('LINESTRING(0 0, 100 100, 200 0)') as geom
)
) TO '__TEST_DIR__/test_linestring.mvt' (FORMAT BLOB);

query I
select count(*) from st_read('__TEST_DIR__/test_linestring.mvt');
----
1

# Test MULTI_LINESTRING encoding
statement ok
COPY (
SELECT st_asmvt(
{"geom": geom},
'multilines'
) as mvt
FROM (
SELECT
st_geomfromtext('MULTILINESTRING((0 0, 100 100, 200 0), (300 0, 400 100, 500 0))') as geom
)
) TO '__TEST_DIR__/test_multilinestring.mvt' (FORMAT BLOB);

query I
select count(*) from st_read('__TEST_DIR__/test_multilinestring.mvt');
----
1

# Test LINESTRING with ST_AsMVTGeom (clipping can produce MULTI_LINESTRING)
statement ok
COPY (
SELECT st_asmvt(
{"geom": ST_AsMVTGeom(
geom,
ST_Extent(ST_MakeEnvelope(0, 0, 1000, 1000)),
4096,
256,
true
)},
'clipped_lines'
) as mvt
FROM (
SELECT
st_geomfromtext('LINESTRING(100 100, 500 500, 900 100)') as geom
)
) TO '__TEST_DIR__/test_clipped_linestring.mvt' (FORMAT BLOB);

query I
select count(*) from st_read('__TEST_DIR__/test_clipped_linestring.mvt');
----
1

# Test LINESTRING crossing tile boundary (produces MULTI_LINESTRING after clipping)
statement ok
COPY (
SELECT st_asmvt(
{"geom": ST_AsMVTGeom(
geom,
ST_Extent(ST_MakeEnvelope(0, 0, 1000, 1000)),
4096,
256,
true
)},
'crossing_lines'
) as mvt
FROM (
SELECT
st_geomfromtext('LINESTRING(-500 500, 500 500, 1500 500)') as geom
)
) TO '__TEST_DIR__/test_crossing_linestring.mvt' (FORMAT BLOB);

query I
select count(*) from st_read('__TEST_DIR__/test_crossing_linestring.mvt');
----
1

# Test multiple LINESTRINGs with various lengths
statement ok
COPY (
SELECT st_asmvt(
{"geom": geom, "id": id},
'various_lines',
4096,
'geom',
'id'
) as mvt
FROM (
SELECT
row_number() over () as id,
st_geomfromtext('LINESTRING(' || (x*100) || ' ' || (y*100) || ', ' || (x*100+50) || ' ' || (y*100+50) || ', ' || (x*100+100) || ' ' || (y*100) || ')') as geom
FROM range(0, 10) as r(x),
range(0, 10) as rr(y)
)
) TO '__TEST_DIR__/test_various_linestrings.mvt' (FORMAT BLOB);

query I
select count(*) from st_read('__TEST_DIR__/test_various_linestrings.mvt');
----
100

# Test global scale dataset scenario (like Natural Earth roads)
# This simulates the case where geometries at low zoom levels span large areas
statement ok
COPY (
SELECT st_asmvt(
{"geom": ST_AsMVTGeom(
geom,
ST_Extent(ST_TileEnvelope(2, 1, 1)),
4096,
256,
false
)},
'global_lines'
) as mvt
FROM (
SELECT
st_geomfromtext('LINESTRING(-10000000 5000000, 0 0, 10000000 -5000000)') as geom
)
) TO '__TEST_DIR__/test_global_linestring.mvt' (FORMAT BLOB);

query I
select count(*) from st_read('__TEST_DIR__/test_global_linestring.mvt');
----
1

# Test that LINESTRING with attributes can be read back
statement ok
COPY (
SELECT st_asmvt(
{"geom": geom, "name": name},
'roads'
) as mvt
FROM (
VALUES
(st_geomfromtext('MULTILINESTRING((100 100, 500 500), (600 600, 900 900))'), 'road1'),
(st_geomfromtext('LINESTRING(200 200, 800 800)'), 'road2')
) t(geom, name)
) TO '__TEST_DIR__/test_roads.mvt' (FORMAT BLOB);

query II
select count(*), count(name) from st_read('__TEST_DIR__/test_roads.mvt');
----
2 2

Loading