Skip to content

Commit fbd5681

Browse files
authored
Merge pull request #726 from pinkerltm/feature/st_interiorringn
Thanks!
2 parents 2f2668d + 535a3dd commit fbd5681

File tree

2 files changed

+267
-2
lines changed

2 files changed

+267
-2
lines changed

src/spatial/modules/main/spatial_functions_scalar.cpp

Lines changed: 226 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3463,8 +3463,9 @@ struct ST_ExteriorRing {
34633463
}
34643464

34653465
if (geom.is_empty()) {
3466-
const sgl::geometry empty(sgl::geometry_type::LINESTRING, geom.has_z(), geom.has_m());
3467-
return lstate.Serialize(result, empty);
3466+
// Polygon empty -> return empty linestring
3467+
sgl::geometry empty_linestring(sgl::geometry_type::LINESTRING, geom.has_z(), geom.has_m());
3468+
return lstate.Serialize(result, empty_linestring);
34683469
}
34693470

34703471
const auto shell = geom.get_first_part();
@@ -6256,6 +6257,228 @@ struct ST_Hilbert {
62566257
}
62576258
};
62586259

6260+
//======================================================================================================================
6261+
// ST_InteriorRingN
6262+
//======================================================================================================================
6263+
6264+
struct ST_InteriorRingN {
6265+
6266+
//------------------------------------------------------------------------------------------------------------------
6267+
// GEOMETRY
6268+
//------------------------------------------------------------------------------------------------------------------
6269+
static void ExecuteGeometry(DataChunk &args, ExpressionState &state, Vector &result) {
6270+
auto &lstate = LocalState::ResetAndGet(state);
6271+
6272+
BinaryExecutor::ExecuteWithNulls<string_t, int64_t, string_t>(
6273+
args.data[0], args.data[1], result, args.size(),
6274+
[&](const string_t &blob, const int64_t &n, ValidityMask &mask, idx_t idx) {
6275+
sgl::geometry geom;
6276+
lstate.Deserialize(blob, geom);
6277+
6278+
// ---- validate geometry ----
6279+
if (geom.get_type() != sgl::geometry_type::POLYGON) {
6280+
mask.SetInvalid(idx);
6281+
return string_t {};
6282+
}
6283+
6284+
if (geom.is_empty()) {
6285+
// empty polygon → NULL because ring index must be always out of bounds then
6286+
mask.SetInvalid(idx);
6287+
return string_t {};
6288+
}
6289+
6290+
if (n < 1) {
6291+
// invalid index → NULL
6292+
mask.SetInvalid(idx);
6293+
return string_t {};
6294+
}
6295+
6296+
const idx_t num_parts = geom.get_part_count(); // includes shell
6297+
// parts: 0 = exterior, 1..n = interior rings
6298+
const idx_t num_interior = num_parts > 0 ? num_parts - 1 : 0;
6299+
6300+
if (static_cast<idx_t>(n) > num_interior) {
6301+
// ring doesn't exist → NULL
6302+
mask.SetInvalid(idx);
6303+
return string_t {};
6304+
}
6305+
6306+
// interior ring N = part N (because part 0 = shell)
6307+
const auto *ring = geom.get_first_part();
6308+
for (idx_t i = 0; i < (idx_t)n; i++) {
6309+
ring = ring->get_next();
6310+
}
6311+
6312+
D_ASSERT(ring != nullptr);
6313+
return lstate.Serialize(result, *ring);
6314+
}
6315+
);
6316+
}
6317+
6318+
//------------------------------------------------------------------------------------------------------------------
6319+
// POLYGON_2D
6320+
//------------------------------------------------------------------------------------------------------------------
6321+
static void ExecutePolygon(DataChunk &args, ExpressionState &state, Vector &result) {
6322+
D_ASSERT(args.data.size() == 2);
6323+
auto &poly_vec = args.data[0];
6324+
auto &n_vec = args.data[1];
6325+
6326+
// same layout as ST_ExteriorRing::ExecutePolygon
6327+
auto poly_entries = ListVector::GetData(poly_vec);
6328+
auto &ring_vec = ListVector::GetEntry(poly_vec);
6329+
auto ring_entries = ListVector::GetData(ring_vec);
6330+
auto &vertex_vec = ListVector::GetEntry(ring_vec);
6331+
auto &vertex_vec_children = StructVector::GetEntries(vertex_vec);
6332+
auto poly_x_data = FlatVector::GetData<double>(*vertex_vec_children[0]);
6333+
auto poly_y_data = FlatVector::GetData<double>(*vertex_vec_children[1]);
6334+
6335+
auto count = args.size();
6336+
UnifiedVectorFormat poly_format;
6337+
poly_vec.ToUnifiedFormat(count, poly_format);
6338+
6339+
// We'll need to build the result list length: sum of selected interior ring lengths
6340+
idx_t total_vertex_count = 0;
6341+
6342+
// To inspect n per-row, extract unified format for n (it might be constant)
6343+
UnifiedVectorFormat n_format;
6344+
n_vec.ToUnifiedFormat(count, n_format);
6345+
auto n_data = FlatVector::GetData<int64_t>(n_vec);
6346+
6347+
for (idx_t i = 0; i < count; i++) {
6348+
auto row_idx = poly_format.sel->get_index(i);
6349+
if (!poly_format.validity.RowIsValid(row_idx)) {
6350+
continue;
6351+
}
6352+
auto poly = poly_entries[row_idx];
6353+
if (poly.length == 0) {
6354+
// empty polygon -> nothing to add
6355+
continue;
6356+
}
6357+
6358+
// read requested n for this row
6359+
int64_t nr = 0;
6360+
// handle constant / flat
6361+
if (n_format.validity.RowIsValid(n_format.sel->get_index(i))) {
6362+
nr = n_data[n_format.sel->get_index(i)];
6363+
} else {
6364+
// n is null -> will produce NULL result later
6365+
continue;
6366+
}
6367+
6368+
// polygon has poly.length rings: first is exterior, rest are interior
6369+
const idx_t ring_count = poly.length; // >=1 normally
6370+
const idx_t interior_count = (ring_count > 0 ? ring_count - 1 : 0);
6371+
6372+
if (nr < 1 || nr > static_cast<int64_t>(interior_count)) {
6373+
// out of range or invalid -> no vertices added (result will be NULL or empty)
6374+
continue;
6375+
}
6376+
6377+
// interior ring index in ring_entries: poly.offset + nr (since 1 -> first interior at offset+1)
6378+
auto &ring = ring_entries[poly.offset + static_cast<idx_t>(nr)];
6379+
total_vertex_count += ring.length;
6380+
}
6381+
6382+
// Allocate result
6383+
auto &line_vec = result;
6384+
ListVector::Reserve(line_vec, total_vertex_count);
6385+
ListVector::SetListSize(line_vec, total_vertex_count);
6386+
6387+
auto line_entries = ListVector::GetData(line_vec);
6388+
auto &line_coord_vec = StructVector::GetEntries(ListVector::GetEntry(line_vec));
6389+
auto line_data_x = FlatVector::GetData<double>(*line_coord_vec[0]);
6390+
auto line_data_y = FlatVector::GetData<double>(*line_coord_vec[1]);
6391+
6392+
// Fill results
6393+
idx_t line_data_offset = 0;
6394+
for (idx_t i = 0; i < count; i++) {
6395+
auto row_idx = poly_format.sel->get_index(i);
6396+
if (!poly_format.validity.RowIsValid(row_idx)) {
6397+
FlatVector::SetNull(line_vec, i, true);
6398+
continue;
6399+
}
6400+
6401+
auto poly = poly_entries[row_idx];
6402+
6403+
// read requested n for this row
6404+
const auto n_idx = n_format.sel->get_index(i);
6405+
if(!n_format.validity.RowIsValid(n_idx)) {
6406+
FlatVector::SetNull(line_vec, i, true);
6407+
continue;
6408+
}
6409+
6410+
const auto nr = n_data[n_idx];
6411+
6412+
const idx_t ring_count = poly.length;
6413+
const idx_t interior_count = (ring_count > 0 ? ring_count - 1 : 0);
6414+
6415+
if (nr < 1 || nr > static_cast<int64_t>(interior_count)) {
6416+
// out of range -> NULL result
6417+
FlatVector::SetNull(line_vec, i, true);
6418+
continue;
6419+
}
6420+
6421+
auto &ring = ring_entries[poly.offset + static_cast<idx_t>(nr)]; // offset + 1..N -> interior rings
6422+
auto &line_entry = line_entries[i];
6423+
line_entry.offset = line_data_offset;
6424+
line_entry.length = ring.length;
6425+
6426+
for (idx_t coord_idx = 0; coord_idx < ring.length; coord_idx++) {
6427+
line_data_x[line_entry.offset + coord_idx] = poly_x_data[ring.offset + coord_idx];
6428+
line_data_y[line_entry.offset + coord_idx] = poly_y_data[ring.offset + coord_idx];
6429+
}
6430+
6431+
line_data_offset += ring.length;
6432+
}
6433+
6434+
if (count == 1) {
6435+
result.SetVectorType(VectorType::CONSTANT_VECTOR);
6436+
}
6437+
}
6438+
6439+
//------------------------------------------------------------------------------------------------------------------
6440+
// Documentation
6441+
//------------------------------------------------------------------------------------------------------------------
6442+
static constexpr auto DESCRIPTION =
6443+
"Returns the N-th interior ring (hole) of a POLYGON as a LINESTRING. Indexing is 1-based (n = 1 returns the first interior ring). "
6444+
"Returns NULL if the polygon is empty or has fewer than N interior rings.";
6445+
6446+
static constexpr auto EXAMPLE = R"(
6447+
SELECT ST_AsText(ST_InteriorRingN(ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),(2 2,4 2,4 4,2 4,2 2))'), 1));
6448+
)";
6449+
6450+
//------------------------------------------------------------------------------------------------------------------
6451+
// Register
6452+
//------------------------------------------------------------------------------------------------------------------
6453+
static void Register(ExtensionLoader &loader) {
6454+
FunctionBuilder::RegisterScalar(loader, "ST_InteriorRingN", [](ScalarFunctionBuilder &func) {
6455+
func.AddVariant([](ScalarFunctionVariantBuilder &variant) {
6456+
variant.AddParameter("geom", GeoTypes::GEOMETRY());
6457+
variant.AddParameter("n", LogicalType::BIGINT);
6458+
variant.SetReturnType(GeoTypes::GEOMETRY());
6459+
6460+
variant.SetInit(LocalState::Init);
6461+
variant.SetFunction(ExecuteGeometry);
6462+
});
6463+
6464+
func.AddVariant([](ScalarFunctionVariantBuilder &variant) {
6465+
variant.AddParameter("polygon", GeoTypes::POLYGON_2D());
6466+
variant.AddParameter("n", LogicalType::BIGINT);
6467+
variant.SetReturnType(GeoTypes::LINESTRING_2D());
6468+
6469+
variant.SetFunction(ExecutePolygon);
6470+
});
6471+
6472+
func.SetDescription(DESCRIPTION);
6473+
func.SetExample(EXAMPLE);
6474+
6475+
func.SetTag("ext", "spatial");
6476+
func.SetTag("category", "property");
6477+
});
6478+
}
6479+
};
6480+
6481+
62596482
//======================================================================================================================
62606483
// ST_InterpolatePoint
62616484
//======================================================================================================================
@@ -9404,6 +9627,7 @@ void RegisterSpatialScalarFunctions(ExtensionLoader &loader) {
94049627
ST_ZMFlag::Register(loader);
94059628
ST_Distance_Sphere::Register(loader);
94069629
ST_Hilbert::Register(loader);
9630+
ST_InteriorRingN::Register(loader);
94079631
ST_InterpolatePoint::Register(loader);
94089632
ST_Intersects::Register(loader);
94099633
ST_IntersectsExtent::Register(loader);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require spatial
2+
3+
foreach TYPE GEOMETRY POLYGON_2D
4+
5+
query I
6+
select st_interiorringn(
7+
st_geomfromtext('POLYGON((0 0,10 0,10 10,0 10,0 0),
8+
(2 2,4 2,4 4,2 4,2 2),
9+
(6 6,8 6,8 8,6 8,6 6))')::${TYPE},
10+
2
11+
);
12+
----
13+
LINESTRING (6 6, 8 6, 8 8, 6 8, 6 6)
14+
15+
query I
16+
select st_interiorringn(
17+
st_geomfromtext('POLYGON((0 0,10 0,10 10,0 10,0 0),
18+
(2 2,4 2,4 4,2 4,2 2),
19+
(6 6,8 6,8 8,6 8,6 6))')::${TYPE},
20+
3
21+
);
22+
----
23+
NULL
24+
25+
query I
26+
select st_interiorringn(
27+
st_geomfromtext('POLYGON EMPTY')::${TYPE},
28+
3
29+
);
30+
----
31+
NULL
32+
33+
query I
34+
select st_interiorringn(
35+
st_geomfromtext('POLYGON EMPTY')::${TYPE},
36+
0
37+
);
38+
----
39+
NULL
40+
41+
endloop

0 commit comments

Comments
 (0)