From e32e07e1c3f52a23453c94259cf3971ea1b4699a Mon Sep 17 00:00:00 2001 From: Max Gabrielsson Date: Fri, 24 Oct 2025 11:14:26 +0200 Subject: [PATCH 1/2] update CI --- .github/workflows/MainDistributionPipeline.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/MainDistributionPipeline.yml b/.github/workflows/MainDistributionPipeline.yml index 8e1b18c1..bdeba8e0 100644 --- a/.github/workflows/MainDistributionPipeline.yml +++ b/.github/workflows/MainDistributionPipeline.yml @@ -5,13 +5,13 @@ name: Main Extension Distribution Pipeline on: pull_request: branches: - - main + - v1.4-andium paths-ignore: - '**/README.md' - 'doc/**' push: branches: - - main + - v1.4-andium paths-ignore: - '**/README.md' - 'doc/**' @@ -27,7 +27,7 @@ jobs: name: Build extension binaries uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main with: - duckdb_version: main + duckdb_version: v1.4-andium extension_name: spatial ci_tools_version: main vcpkg_commit: ce613c41372b23b1f51333815feb3edd87ef8a8b @@ -38,7 +38,7 @@ jobs: uses: duckdb/extension-ci-tools/.github/workflows/_extension_deploy.yml@main secrets: inherit with: - duckdb_version: main + duckdb_version: v1.4-andium ci_tools_version: main extension_name: spatial deploy_latest: ${{ startsWith(github.ref, 'refs/heads/v') || github.ref == 'refs/heads/main' }} From 456d9f968db466690b9596dd1d58faef8153fcf1 Mon Sep 17 00:00:00 2001 From: Max Gabrielsson Date: Fri, 24 Oct 2025 11:20:35 +0200 Subject: [PATCH 2/2] pin to duckdb v1.4.1, fix bug in st_distance when processing zero-length segments in polygon rings --- .../workflows/MainDistributionPipeline.yml | 4 +-- duckdb | 2 +- src/sgl/sgl.cpp | 33 +++++++++++++++++++ test/sql/geometry/st_distance.test | 12 +++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/MainDistributionPipeline.yml b/.github/workflows/MainDistributionPipeline.yml index bdeba8e0..91f0f865 100644 --- a/.github/workflows/MainDistributionPipeline.yml +++ b/.github/workflows/MainDistributionPipeline.yml @@ -27,7 +27,7 @@ jobs: name: Build extension binaries uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@main with: - duckdb_version: v1.4-andium + duckdb_version: v1.4.1 extension_name: spatial ci_tools_version: main vcpkg_commit: ce613c41372b23b1f51333815feb3edd87ef8a8b @@ -38,7 +38,7 @@ jobs: uses: duckdb/extension-ci-tools/.github/workflows/_extension_deploy.yml@main secrets: inherit with: - duckdb_version: v1.4-andium + duckdb_version: v1.4.1 ci_tools_version: main extension_name: spatial deploy_latest: ${{ startsWith(github.ref, 'refs/heads/v') || github.ref == 'refs/heads/main' }} diff --git a/duckdb b/duckdb index 47993080..9069f536 160000 --- a/duckdb +++ b/duckdb @@ -1 +1 @@ -Subproject commit 4799308087583835a7731a266262ba0fcac9af08 +Subproject commit 9069f5363cac06a1172dfeb36f5f2cd2a97bf37b diff --git a/src/sgl/sgl.cpp b/src/sgl/sgl.cpp index 8a985f80..c9612e5d 100644 --- a/src/sgl/sgl.cpp +++ b/src/sgl/sgl.cpp @@ -3130,6 +3130,7 @@ static double point_segment_dist_sq(const vertex_xy &p, const vertex_xy &a, cons return diff.norm_sq(); } +// Check if point P is on segment QR static bool point_on_segment(const vertex_xy &p, const vertex_xy &q, const vertex_xy &r) { return q.x >= std::min(p.x, r.x) && q.x <= std::max(p.x, r.x) && q.y >= std::min(p.y, r.y) && q.y <= std::max(p.y, r.y); @@ -3137,6 +3138,22 @@ static bool point_on_segment(const vertex_xy &p, const vertex_xy &q, const verte static bool segment_intersects(const vertex_xy &a1, const vertex_xy &a2, const vertex_xy &b1, const vertex_xy &b2) { // Check if two segments intersect using the orientation method + // Handle degenerate cases where a segment is actually a single point + const bool a_is_point = (a1.x == a2.x && a1.y == a2.y); + const bool b_is_point = (b1.x == b2.x && b1.y == b2.y); + + if (a_is_point && b_is_point) { + // Both are points: intersect only if identical + return (a1.x == b1.x && a1.y == b1.y); + } + if (a_is_point) { + // A is a point: check if A lies on segment B + return point_on_segment(a1, b1, b2); + } + if (b_is_point) { + // B is a point: check if B lies on segment A + return point_on_segment(b1, a1, a2); + } const auto o1 = orient2d_fast(a1, a2, b1); const auto o2 = orient2d_fast(a1, a2, b2); @@ -3250,6 +3267,15 @@ static bool try_get_prepared_distance_lines(const prepared_geometry &lhs, const for (uint32_t i = lhs_beg_idx + 1; i < lhs_end_idx; i++) { memcpy(&lhs_next, lhs_vertex_array + i * lhs_vertex_width, sizeof(vertex_xy)); + // If this is a zero-length segment, skip it + // LINESTRINGs must have at least two distinct vertices to be valid, so this is safe. Even if we skip + // this vertex now, we must eventually reach a non-zero-length segment that includes this vertex as + // its start point. It will therefore still contribute to the distance calculation once we process that + // segment. + if (lhs_prev.x == lhs_next.x && lhs_prev.y == lhs_next.y) { + continue; + } + // Quick check: If the distance between the segment and the box (all the segments) // is greater than min_dist, we can skip the exact distance check @@ -3268,6 +3294,13 @@ static bool try_get_prepared_distance_lines(const prepared_geometry &lhs, const for (uint32_t j = rhs_beg_idx + 1; j < rhs_end_idx; j++) { memcpy(&rhs_next, rhs_vertex_array + j * rhs_vertex_width, sizeof(vertex_xy)); + // If this is a zero-length segment, skip it + // LINESTRINGs must have at least two distinct points to be valid, so this is safe. + // (see comment above) + if (rhs_prev.x == rhs_next.x && rhs_prev.y == rhs_next.y) { + continue; + } + // Quick check: If the distance between the segment bounds are greater than min_dist, // we can skip the exact distance check extent_xy rhs_seg; diff --git a/test/sql/geometry/st_distance.test b/test/sql/geometry/st_distance.test index 2763e75d..4b14520f 100644 --- a/test/sql/geometry/st_distance.test +++ b/test/sql/geometry/st_distance.test @@ -26,3 +26,15 @@ SELECT ST_Distance( ---- 5 + +# This test also comes from a bug report, where ST_Distance would incorrectly handle line segments with zero length. +# The issue is that we were not properly considering segments where both endpoints are the same point, and conclude +# that a zero length segment always intersects with any other segment, which is not correct. We now skip zero-length +# segments when performing intersection tests between indexed polygon rings, and also handle zero-length segments as +# points when performing regular segment-segment intersection tests. + +query I +select st_distance('POLYGON((0 0, 1 1, 1 1, 10 10, 0 0))'::GEOMETRY, 'POLYGON((4 0, 4 1, 5 1, 5 0, 4 0))'::GEOMETRY) != 0; +---- +true +