From git at osgeo.org Thu Oct 2 10:13:23 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 2 Oct 2025 10:13:23 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.12 updated. 5b6bd8e5483ea23937b6bded95899166b79c3b75 Message-ID: <20251002171326.DEAC816C589@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.12 has been updated via 5b6bd8e5483ea23937b6bded95899166b79c3b75 (commit) from 7172d20d992f6344c1dd3297c65dddd72d6df505 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 5b6bd8e5483ea23937b6bded95899166b79c3b75 Author: Paul Ramsey Date: Thu Oct 2 10:12:52 2025 -0700 Remove deprecated macos-13 runner diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a57616746..4408f33a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,22 +321,14 @@ jobs: strategy: matrix: include: -# - xcode: 13.2.1 -# cxxstd: 14 -# build_type: ASAN -# runs_on: macos-12 - - xcode: 14.3.1 - cxxstd: 17 - build_type: ASAN - runs_on: macos-13 - xcode: 15.4 cxxstd: 20 build_type: Release - runs_on: macOS-14 - - xcode: 16.0 + runs_on: macos-14 + - xcode: 16.4 cxxstd: 20 build_type: Release - runs_on: macOS-15 + runs_on: macos-15 runs-on: ${{ matrix.runs_on }} steps: ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 2 10:14:00 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 2 Oct 2025 10:14:00 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.13 updated. 2eda0a8fc2de92e1545f7e62fe7649aa8a83981e Message-ID: <20251002171401.2DFEE16C4A0@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.13 has been updated via 2eda0a8fc2de92e1545f7e62fe7649aa8a83981e (commit) from 05681c210b727655da463feba904e08e8d84c61c (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 2eda0a8fc2de92e1545f7e62fe7649aa8a83981e Author: Paul Ramsey Date: Thu Oct 2 10:13:33 2025 -0700 Remove deprecated macos-13 runner diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96d1ba3bb..deea2b503 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -375,22 +375,14 @@ jobs: fail-fast: false matrix: include: -# - xcode: 13.2.1 -# cxxstd: 14 -# build_type: ASAN -# runs_on: macos-12 - - xcode: 14.3.1 - cxxstd: 17 - build_type: ASAN - runs_on: macos-13 - xcode: 15.4 cxxstd: 20 build_type: Release - runs_on: macOS-14 - - xcode: 16.0 + runs_on: macos-14 + - xcode: 16.4 cxxstd: 20 build_type: Release - runs_on: macOS-15 + runs_on: macos-15 runs-on: ${{ matrix.runs_on }} steps: ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 2 10:14:27 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 2 Oct 2025 10:14:27 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. 115d442fd496235716583263727bf069a77efb43 Message-ID: <20251002171428.313EE16C191@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.14 has been updated via 115d442fd496235716583263727bf069a77efb43 (commit) from e79760289d817a33268a8e8e8f6bcc1b3a3e2b90 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 115d442fd496235716583263727bf069a77efb43 Author: Paul Ramsey Date: Thu Oct 2 10:13:59 2025 -0700 Remove deprecated macos-13 runner diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11e1e9a62..113f55e61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -421,15 +421,11 @@ jobs: fail-fast: false matrix: include: - - xcode: 14.3.1 - cxxstd: 17 - build_type: ASAN - runs_on: macos-13 - xcode: 15.4 cxxstd: 20 build_type: Release - runs_on: macOS-14 - - xcode: 16.0 + runs_on: macos-14 + - xcode: 16.4 cxxstd: 23 build_type: ASAN runs_on: macos-15 ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 2 10:15:12 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 2 Oct 2025 10:15:12 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 936f621c4e84499f5c3b0b8c4bb222b29273a51d Message-ID: <20251002171512.6AEE016B7E4@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 936f621c4e84499f5c3b0b8c4bb222b29273a51d (commit) from 555c572874bbe44fdc4400da282faaa012c5f3ea (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 936f621c4e84499f5c3b0b8c4bb222b29273a51d Author: Paul Ramsey Date: Thu Oct 2 10:14:49 2025 -0700 Remove deprecated macos-13 runner diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11e1e9a62..113f55e61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -421,15 +421,11 @@ jobs: fail-fast: false matrix: include: - - xcode: 14.3.1 - cxxstd: 17 - build_type: ASAN - runs_on: macos-13 - xcode: 15.4 cxxstd: 20 build_type: Release - runs_on: macOS-14 - - xcode: 16.0 + runs_on: macos-14 + - xcode: 16.4 cxxstd: 23 build_type: ASAN runs_on: macos-15 ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 2 10:18:25 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 2 Oct 2025 10:18:25 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. db9a00da18c3ac6451a4d8bce3195fc8ab06d54f Message-ID: <20251002171825.C82F016C64B@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via db9a00da18c3ac6451a4d8bce3195fc8ab06d54f (commit) from 936f621c4e84499f5c3b0b8c4bb222b29273a51d (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit db9a00da18c3ac6451a4d8bce3195fc8ab06d54f Author: Paul Ramsey Date: Thu Oct 2 10:18:02 2025 -0700 Add macos-26 runner diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 113f55e61..2846d20d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -429,6 +429,10 @@ jobs: cxxstd: 23 build_type: ASAN runs_on: macos-15 + - xcode: 26.0 + cxxstd: 23 + build_type: ASAN + runs_on: macos-26 runs-on: ${{ matrix.runs_on }} steps: ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Fri Oct 3 10:32:44 2025 From: git at osgeo.org (git at osgeo.org) Date: Fri, 3 Oct 2025 10:32:44 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 883f237d1ecbf49f8efd09905df05814783c5b50 Message-ID: <20251003173247.805311923DC@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 883f237d1ecbf49f8efd09905df05814783c5b50 (commit) from db9a00da18c3ac6451a4d8bce3195fc8ab06d54f (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 883f237d1ecbf49f8efd09905df05814783c5b50 Author: Paul Ramsey Date: Fri Oct 3 10:06:16 2025 -0700 Remove undefined behaviour in CoordinateSequence::closeRing. Closes #1309 diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp index 99af270f7..1292c2bd1 100644 --- a/src/geom/CoordinateSequence.cpp +++ b/src/geom/CoordinateSequence.cpp @@ -261,9 +261,10 @@ void CoordinateSequence::closeRing(bool allowRepeated) { if(!isEmpty() && (allowRepeated || front() != back())) { - m_vect.insert(m_vect.end(), - m_vect.begin(), - std::next(m_vect.begin(), stride())); + const std::size_t n = stride(); + const std::size_t old = m_vect.size(); + m_vect.resize(old + n); + std::copy_n(m_vect.data(), n, m_vect.data() + old); } } ----------------------------------------------------------------------- Summary of changes: src/geom/CoordinateSequence.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Oct 3 13:13:32 2025 From: git at osgeo.org (git at osgeo.org) Date: Fri, 3 Oct 2025 13:13:32 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.12 updated. 3d1d86b43f25c60400e491de64d7579b534747a3 Message-ID: <20251003201332.DBA85195CF1@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.12 has been updated via 3d1d86b43f25c60400e491de64d7579b534747a3 (commit) from 5b6bd8e5483ea23937b6bded95899166b79c3b75 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 3d1d86b43f25c60400e491de64d7579b534747a3 Author: Paul Ramsey Date: Fri Oct 3 10:06:16 2025 -0700 Remove undefined behaviour in CoordinateSequence::closeRing. Closes #1309 diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp index fc36b4bb3..856e0fc33 100644 --- a/src/geom/CoordinateSequence.cpp +++ b/src/geom/CoordinateSequence.cpp @@ -256,9 +256,10 @@ void CoordinateSequence::closeRing(bool allowRepeated) { if(!isEmpty() && (allowRepeated || front() != back())) { - m_vect.insert(m_vect.end(), - m_vect.begin(), - std::next(m_vect.begin(), stride())); + const std::size_t n = stride(); + const std::size_t old = m_vect.size(); + m_vect.resize(old + n); + std::copy_n(m_vect.data(), n, m_vect.data() + old); } } ----------------------------------------------------------------------- Summary of changes: src/geom/CoordinateSequence.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Oct 3 13:13:36 2025 From: git at osgeo.org (git at osgeo.org) Date: Fri, 3 Oct 2025 13:13:36 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.13 updated. 32325937373d4f6e14be5cb40bb841188f5c0475 Message-ID: <20251003201336.5C852195C42@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.13 has been updated via 32325937373d4f6e14be5cb40bb841188f5c0475 (commit) from 2eda0a8fc2de92e1545f7e62fe7649aa8a83981e (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 32325937373d4f6e14be5cb40bb841188f5c0475 Author: Paul Ramsey Date: Fri Oct 3 10:06:16 2025 -0700 Remove undefined behaviour in CoordinateSequence::closeRing. Closes #1309 diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp index 599ca6ffb..0345fe2d4 100644 --- a/src/geom/CoordinateSequence.cpp +++ b/src/geom/CoordinateSequence.cpp @@ -256,9 +256,10 @@ void CoordinateSequence::closeRing(bool allowRepeated) { if(!isEmpty() && (allowRepeated || front() != back())) { - m_vect.insert(m_vect.end(), - m_vect.begin(), - std::next(m_vect.begin(), stride())); + const std::size_t n = stride(); + const std::size_t old = m_vect.size(); + m_vect.resize(old + n); + std::copy_n(m_vect.data(), n, m_vect.data() + old); } } ----------------------------------------------------------------------- Summary of changes: src/geom/CoordinateSequence.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Oct 3 13:13:37 2025 From: git at osgeo.org (git at osgeo.org) Date: Fri, 3 Oct 2025 13:13:37 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. 86ba6f9aa0d694ce87637ab5ecd264c304074606 Message-ID: <20251003201337.D99E01960AD@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.14 has been updated via 86ba6f9aa0d694ce87637ab5ecd264c304074606 (commit) from 115d442fd496235716583263727bf069a77efb43 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 86ba6f9aa0d694ce87637ab5ecd264c304074606 Author: Paul Ramsey Date: Fri Oct 3 10:06:16 2025 -0700 Remove undefined behaviour in CoordinateSequence::closeRing. Closes #1309 diff --git a/src/geom/CoordinateSequence.cpp b/src/geom/CoordinateSequence.cpp index 99af270f7..1292c2bd1 100644 --- a/src/geom/CoordinateSequence.cpp +++ b/src/geom/CoordinateSequence.cpp @@ -261,9 +261,10 @@ void CoordinateSequence::closeRing(bool allowRepeated) { if(!isEmpty() && (allowRepeated || front() != back())) { - m_vect.insert(m_vect.end(), - m_vect.begin(), - std::next(m_vect.begin(), stride())); + const std::size_t n = stride(); + const std::size_t old = m_vect.size(); + m_vect.resize(old + n); + std::copy_n(m_vect.data(), n, m_vect.data() + old); } } ----------------------------------------------------------------------- Summary of changes: src/geom/CoordinateSequence.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Oct 7 10:17:33 2025 From: git at osgeo.org (git at osgeo.org) Date: Tue, 7 Oct 2025 10:17:33 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. cc613dafe7f165876009a681dde33852a967def3 Message-ID: <20251007171733.D775C133C4D@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via cc613dafe7f165876009a681dde33852a967def3 (commit) from 883f237d1ecbf49f8efd09905df05814783c5b50 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit cc613dafe7f165876009a681dde33852a967def3 Author: Martin Davis Date: Tue Oct 7 10:17:09 2025 -0700 Fix doc typo diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index e52e2cac4..6caf8b89a 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -4433,7 +4433,7 @@ extern GEOSGeometry GEOS_DLL * GEOSCoverageSimplifyVW( /** * Create a default GEOSCoverageCleanParams object for controlling -* the way invalid polygon interactions are repaird by \ref GEOSCoverageCleanWithParams. +* the way invalid polygon interactions are repaired by \ref GEOSCoverageCleanWithParams. * \return A newly allocated GEOSCoverageCleanParams. NULL on exception. * Caller is responsible for freeing with GEOSCoverageCleanParams_destroy(). * ----------------------------------------------------------------------- Summary of changes: capi/geos_c.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Oct 7 15:54:48 2025 From: git at osgeo.org (git at osgeo.org) Date: Tue, 7 Oct 2025 15:54:48 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. cb0909d11523d8e3d86f679e7cfaac09cb912028 Message-ID: <20251007225452.B462816123D@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via cb0909d11523d8e3d86f679e7cfaac09cb912028 (commit) from cc613dafe7f165876009a681dde33852a967def3 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit cb0909d11523d8e3d86f679e7cfaac09cb912028 Author: Mike Taves Date: Wed Oct 8 11:54:09 2025 +1300 Fix doc typo diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index 6caf8b89a..b5d5ada78 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -6707,7 +6707,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeomFromWKB_buf(const unsigned char *wkb, size extern unsigned char GEOS_DLL *GEOSGeomToWKB_buf(const GEOSGeometry* g, size_t *size); /** -* \deprecated use \ref GEOSWKBReader and GEOSWKBWriter_readHEX() +* \deprecated use \ref GEOSWKBReader and GEOSWKBReader_readHEX() */ extern GEOSGeometry GEOS_DLL *GEOSGeomFromHEX_buf(const unsigned char *hex, size_t size); ----------------------------------------------------------------------- Summary of changes: capi/geos_c.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Oct 7 16:54:23 2025 From: git at osgeo.org (git at osgeo.org) Date: Tue, 7 Oct 2025 16:54:23 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 16f53573f6bef38551801c2d449d4782775267bc Message-ID: <20251007235423.D7451161A59@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 16f53573f6bef38551801c2d449d4782775267bc (commit) from cb0909d11523d8e3d86f679e7cfaac09cb912028 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 16f53573f6bef38551801c2d449d4782775267bc Author: Mike Taves Date: Wed Oct 8 12:53:58 2025 +1300 Fix xcode versions by specifying YAML str (not float) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2846d20d9..69b0b8601 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -421,15 +421,15 @@ jobs: fail-fast: false matrix: include: - - xcode: 15.4 + - xcode: "15.4" cxxstd: 20 build_type: Release runs_on: macos-14 - - xcode: 16.4 + - xcode: "16.4" cxxstd: 23 build_type: ASAN runs_on: macos-15 - - xcode: 26.0 + - xcode: "26.0" cxxstd: 23 build_type: ASAN runs_on: macos-26 ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 9 13:58:52 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 9 Oct 2025 13:58:52 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 47ae3352f7a96001b07690010406afff1ef177ff Message-ID: <20251009205852.5E8C4161E2F@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 47ae3352f7a96001b07690010406afff1ef177ff (commit) from 16f53573f6bef38551801c2d449d4782775267bc (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 47ae3352f7a96001b07690010406afff1ef177ff Author: Paul Ramsey Date: Thu Oct 9 13:58:25 2025 -0700 Add some ClipByRect tests to memorialize point/multipoint behaviour diff --git a/tests/unit/capi/GEOSClipByRectTest.cpp b/tests/unit/capi/GEOSClipByRectTest.cpp index 9ff33d336..c1c77aace 100644 --- a/tests/unit/capi/GEOSClipByRectTest.cpp +++ b/tests/unit/capi/GEOSClipByRectTest.cpp @@ -361,6 +361,43 @@ void object::test<22>() ); } +/// Multipoint overlapping +template<> +template<> +void object::test<25>() +{ + checkClipByRectIdentical( + "MULTIPOINT((1 1),(6 4),(5 4))", + 0, 0, 5, 5, + "POINT(1 1)" + ); +} + + +/// Multipoint overlapping +template<> +template<> +void object::test<26>() +{ + checkClipByRectIdentical( + "MULTIPOINT((1 1),(3 4),(5 4))", + 0, 0, 5, 5, + "MULTIPOINT((1 1),(3 4))" + ); +} + +/// Linestring overlapping +template<> +template<> +void object::test<27>() +{ + checkClipByRectIdentical( + "LINESTRING(0 0,1 1,3 4,5 4)", + 0, 0, 5, 5, + "LINESTRING(0 0,1 1,3 4,5 4)" + ); +} + } // namespace tut ----------------------------------------------------------------------- Summary of changes: tests/unit/capi/GEOSClipByRectTest.cpp | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 9 14:18:04 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 9 Oct 2025 14:18:04 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 556cf8c664366c7ffef39190581a0b55f4729a75 Message-ID: <20251009211804.8F745161FB7@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 556cf8c664366c7ffef39190581a0b55f4729a75 (commit) from 47ae3352f7a96001b07690010406afff1ef177ff (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 556cf8c664366c7ffef39190581a0b55f4729a75 Author: Paul Ramsey Date: Thu Oct 9 14:17:11 2025 -0700 Use GeometryFixer instead of Buffer(0) to validate output of Densifier References #1294 diff --git a/src/geom/util/Densifier.cpp b/src/geom/util/Densifier.cpp index b4ee7b2a5..9f4e27967 100644 --- a/src/geom/util/Densifier.cpp +++ b/src/geom/util/Densifier.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -95,7 +96,7 @@ Densifier::DensifyTransformer::createValidArea(const Geometry* roughAreaGeom) { if (roughAreaGeom->isValid()) return Geometry::Ptr(roughAreaGeom->clone()); - return roughAreaGeom->buffer(0.0); + return GeometryFixer::fix(roughAreaGeom); } /* util::Densifier */ ----------------------------------------------------------------------- Summary of changes: src/geom/util/Densifier.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 9 14:24:39 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 9 Oct 2025 14:24:39 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 0db400d33d14bdbb0ab3b6d749b886996d02c67a Message-ID: <20251009212439.9CA12161E39@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 0db400d33d14bdbb0ab3b6d749b886996d02c67a (commit) from 556cf8c664366c7ffef39190581a0b55f4729a75 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 0db400d33d14bdbb0ab3b6d749b886996d02c67a Author: Paul Ramsey Date: Thu Oct 9 14:24:16 2025 -0700 Remove unused GeometryPrecisionReducer::fixPolygonalTopology method diff --git a/include/geos/precision/GeometryPrecisionReducer.h b/include/geos/precision/GeometryPrecisionReducer.h index cb0cb9a82..de8afb120 100644 --- a/include/geos/precision/GeometryPrecisionReducer.h +++ b/include/geos/precision/GeometryPrecisionReducer.h @@ -68,8 +68,6 @@ private: bool useAreaReducer; bool isPointwise; - std::unique_ptr fixPolygonalTopology(const geom::Geometry& geom); - geom::GeometryFactory::Ptr createFactory( const geom::GeometryFactory& oldGF, const geom::PrecisionModel& newPM); diff --git a/src/precision/GeometryPrecisionReducer.cpp b/src/precision/GeometryPrecisionReducer.cpp index 3a5480a81..6b5f07918 100644 --- a/src/precision/GeometryPrecisionReducer.cpp +++ b/src/precision/GeometryPrecisionReducer.cpp @@ -83,35 +83,6 @@ GeometryPrecisionReducer::reduce(const geom::Geometry& g, const geom::PrecisionM } -/* private */ -std::unique_ptr -GeometryPrecisionReducer::fixPolygonalTopology(const Geometry& geom) -{ - /* - * If precision model was *not* changed, need to flip - * geometry to targetPM, buffer in that model, then flip back - */ - std::unique_ptr tmp; - GeometryFactory::Ptr tmpFactory; - - const Geometry* geomToBuffer = &geom; - - if(! newFactory) { - tmpFactory = createFactory(*geom.getFactory(), targetPM); - tmp = tmpFactory->createGeometry(&geom); - geomToBuffer = tmp.get(); - } - - std::unique_ptr bufGeom(geomToBuffer->buffer(0)); - - if(! newFactory) { - // a slick way to copy the geometry with the original precision factory - bufGeom = geom.getFactory()->createGeometry(bufGeom.get()); - } - - return bufGeom; -} - /* private */ GeometryFactory::Ptr GeometryPrecisionReducer::createFactory(const GeometryFactory& oldGF, ----------------------------------------------------------------------- Summary of changes: include/geos/precision/GeometryPrecisionReducer.h | 2 -- src/precision/GeometryPrecisionReducer.cpp | 29 ----------------------- 2 files changed, 31 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 9 14:36:49 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 9 Oct 2025 14:36:49 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 2e12fa312d8ed12b36839ef20c193b49d695f1d4 Message-ID: <20251009213649.6731D16164D@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 2e12fa312d8ed12b36839ef20c193b49d695f1d4 (commit) from 0db400d33d14bdbb0ab3b6d749b886996d02c67a (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 2e12fa312d8ed12b36839ef20c193b49d695f1d4 Author: Paul Ramsey Date: Thu Oct 9 14:36:26 2025 -0700 Use GeometryFixer in place of buffer(0) in GeometrySnapper::snapToSelf diff --git a/src/operation/overlay/snap/GeometrySnapper.cpp b/src/operation/overlay/snap/GeometrySnapper.cpp index a2aed6eaa..7a13005f5 100644 --- a/src/operation/overlay/snap/GeometrySnapper.cpp +++ b/src/operation/overlay/snap/GeometrySnapper.cpp @@ -20,6 +20,7 @@ #include #include #include // inherit. of SnapTransformer +#include #include #include #include @@ -130,12 +131,11 @@ GeometrySnapper::snapToSelf(double snapTolerance, bool cleanResult) // (we need a pointer for dynamic polymorphism) std::unique_ptr snapTrans(new SnapTransformer(snapTolerance, *snapPts)); - GeomPtr result = snapTrans->transform(&srcGeom); + std::unique_ptr result = snapTrans->transform(&srcGeom); - if(cleanResult && (dynamic_cast(result.get()) || - dynamic_cast(result.get()))) { - // TODO: use better cleaning approach - result = result->buffer(0); + if (cleanResult && (result->getGeometryTypeId() == GEOS_POLYGON || + result->getGeometryTypeId() == GEOS_MULTIPOLYGON)) { + result = geom::util::GeometryFixer::fix(result.get()); } return result; ----------------------------------------------------------------------- Summary of changes: src/operation/overlay/snap/GeometrySnapper.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Wed Oct 15 14:17:59 2025 From: git at osgeo.org (git at osgeo.org) Date: Wed, 15 Oct 2025 14:17:59 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 15ef9178e57dcc9adc3d84ebe26d9c4b1a86cc73 Message-ID: <20251015211800.33F5519FA97@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 15ef9178e57dcc9adc3d84ebe26d9c4b1a86cc73 (commit) from 2e12fa312d8ed12b36839ef20c193b49d695f1d4 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 15ef9178e57dcc9adc3d84ebe26d9c4b1a86cc73 Author: Paul Ramsey Date: Wed Oct 15 13:59:48 2025 -0700 Prepared geometry DE9IM test from postgis-users list, broken in Windows, not in MacOS? diff --git a/tests/unit/operation/relateng/RelateNGTest.cpp b/tests/unit/operation/relateng/RelateNGTest.cpp index 2a4a53156..ec4a757f6 100644 --- a/tests/unit/operation/relateng/RelateNGTest.cpp +++ b/tests/unit/operation/relateng/RelateNGTest.cpp @@ -914,5 +914,15 @@ void object::test<63> () checkRelate(a, b, "212101212"); } +// Prepared test +template<> +template<> +void object::test<64> () +{ + std::string a = "LINESTRING(120 215, 176 197)"; + std::string b = "POLYGON ((100 200, 140 230, 180 310, 280 310, 390 270, 400 210, 320 140, 215 141, 150 170, 100 200))"; + checkRelate(a, b, "1FF00F212"); +} + } // namespace tut diff --git a/tests/unit/operation/relateng/RelateNGTest.h b/tests/unit/operation/relateng/RelateNGTest.h index a1f4f7d07..0a6bff3ac 100644 --- a/tests/unit/operation/relateng/RelateNGTest.h +++ b/tests/unit/operation/relateng/RelateNGTest.h @@ -48,7 +48,10 @@ struct test_relateng_support { ensure_equals("preparedContains", prep_a->contains(b), a->contains(b)); ensure_equals("preparedCrosses", prep_a->crosses(b), a->crosses(b)); ensure_equals("preparedTouches", prep_a->touches(b), a->touches(b)); - ensure_equals("preparedRelate", prep_a->relate(b)->toString(), a->relate(b)->toString()); + ensure_equals("preparedRelate", prep_a->relate(b)->toString(), + a->relate(b)->toString()); + ensure_equals("preparedRelateBool", prep_a->relate(b, a->relate(b)->toString()), + a->relate(b, a->relate(b)->toString())); } void checkIntersectsDisjoint(const std::string& wkta, const std::string& wktb, bool expectedValue) ----------------------------------------------------------------------- Summary of changes: tests/unit/operation/relateng/RelateNGTest.cpp | 10 ++++++++++ tests/unit/operation/relateng/RelateNGTest.h | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Wed Oct 15 15:15:33 2025 From: git at osgeo.org (git at osgeo.org) Date: Wed, 15 Oct 2025 15:15:33 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 32c23d1fe658c575e187579ae49dc821df2651b6 Message-ID: <20251015221533.71DF019FAD3@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 32c23d1fe658c575e187579ae49dc821df2651b6 (commit) from 15ef9178e57dcc9adc3d84ebe26d9c4b1a86cc73 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 32c23d1fe658c575e187579ae49dc821df2651b6 Author: Dave Allured Date: Wed Oct 15 16:15:07 2025 -0600 CoverageRingEdges.cpp: Fix include map (#1312) #include was accidentally deleted in the previous commit, but it is referenced in several places within. This commit restores the now-missing include. diff --git a/src/coverage/CoverageRingEdges.cpp b/src/coverage/CoverageRingEdges.cpp index 8fc54a91f..4d1495a47 100644 --- a/src/coverage/CoverageRingEdges.cpp +++ b/src/coverage/CoverageRingEdges.cpp @@ -13,6 +13,7 @@ * **********************************************************************/ +#include #include #include ----------------------------------------------------------------------- Summary of changes: src/coverage/CoverageRingEdges.cpp | 1 + 1 file changed, 1 insertion(+) hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 23 09:32:22 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 23 Oct 2025 09:32:22 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 47435e4f5d4163d01e77116b289de5b5c57778c6 Message-ID: <20251023163222.E238C19239A@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via 47435e4f5d4163d01e77116b289de5b5c57778c6 (commit) from 32c23d1fe658c575e187579ae49dc821df2651b6 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 47435e4f5d4163d01e77116b289de5b5c57778c6 Author: Daniel Baston Date: Thu Oct 23 12:31:58 2025 -0400 CircularArcs: Fix incorrect Envelope calculation (#1314) diff --git a/include/geos/geom/Quadrant.h b/include/geos/geom/Quadrant.h index 6705f1f2e..d7a9e9519 100644 --- a/include/geos/geom/Quadrant.h +++ b/include/geos/geom/Quadrant.h @@ -119,6 +119,24 @@ public: } }; + /** Return a measure that increases monotonically with counterclockwise + * angle and avoids trigonometric calculations. Values are consistent + * with the numeric quadrant codes + * + * @param p0 circle center coordinate + * @param p1 coordinate for which pseudoangle should be calculated + */ + static double pseudoAngle(const CoordinateXY& p0, const CoordinateXY& p1) + { + const double dx = p1.x - p0.x; + const double dy = p1.y - p0.y; + + const double k = dx / (std::abs(dx) + std::abs(dy)); + const double w = 2 + (dy > 0 ? 3 - k : 1 + k); + + return w >= 4 ? w - 4 : w; + } + /** * Returns true if the quadrants are 1 and 3, or 2 and 4 */ diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index 949f18509..068dfd38c 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -57,6 +57,8 @@ void CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2) { + using geom::Quadrant; + e.expandToInclude(p0); e.expandToInclude(p1); e.expandToInclude(p2); @@ -73,22 +75,24 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co return; } - auto orientation = Orientation::index(center, p0, p1); - //* 1 | 0 //* --+-- //* 2 | 3 + const auto pa0 = Quadrant::pseudoAngle(center, p0); + const auto pa1 = Quadrant::pseudoAngle(center, p1); + const auto pa2 = Quadrant::pseudoAngle(center, p2); - using geom::Quadrant; - - auto q0 = geom::Quadrant::quadrant(center, p0); - auto q2 = geom::Quadrant::quadrant(center, p2); + auto q0 = static_cast(pa0); + auto q2 = static_cast(pa2); double R = center.distance(p1); if (q0 == q2) { - // Start and end quadrants are the same. Either the arc crosses all of + // Start and end quadrants are the same. Either the arc crosses all // the axes, or none of the axes. - if (Orientation::index(center, p1, p2) != orientation) { + + const bool isBetween = pa1 > std::min(pa0, pa2) && pa1 < std::max(pa0, pa2); + + if (!isBetween) { e.expandToInclude({center.x, center.y + R}); e.expandToInclude({center.x - R, center.y}); e.expandToInclude({center.x, center.y - R}); @@ -98,6 +102,8 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co return; } + auto orientation = Orientation::index(p0, p1, p2); + if (orientation == Orientation::CLOCKWISE) { std::swap(q0, q2); } diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp index 475261d4f..e40aa3435 100644 --- a/tests/unit/algorithm/CircularArcsTest.cpp +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -215,5 +215,19 @@ void object::test<13>() 3, 4, 3, 4); } +template<> +template<> +void object::test<14>() +{ + set_test_name("envelope: GH #1313"); + + CoordinateXY p0{2, 0}; + CoordinateXY p1{4, 2}; + CoordinateXY p2{2, 1}; + + checkEnvelope(p0, p1, p2, + 2, -1.0811388300841898, 5.08113883008419,2.08113883008419); +} + } diff --git a/tests/unit/geom/QuadrantTest.cpp b/tests/unit/geom/QuadrantTest.cpp new file mode 100644 index 000000000..bc23d1094 --- /dev/null +++ b/tests/unit/geom/QuadrantTest.cpp @@ -0,0 +1,83 @@ +#include + +#include + +using geos::geom::Quadrant; + +namespace tut { + +struct test_quadrant_data { + + static std::string quadrantName(int quadrant) { + switch (quadrant) { + case Quadrant::NE: return "NE"; + case Quadrant::NW: return "NW"; + case Quadrant::SE: return "SE"; + case Quadrant::SW: return "SW"; + } + return "unknown"; + } + + static void checkQuadrant(double dx, double dy, int expected) { + int quadrant = Quadrant::quadrant({0, 0}, {dx, dy}); + ensure_equals(quadrant, expected); + } + + static void checkPseudoAngleMatchesQuadrant(double dx, double dy) { + int quadrant = Quadrant::quadrant({0, 0}, {dx, dy}); + double pa = Quadrant::pseudoAngle({0, 0}, {dx, dy}); + + if (quadrant != static_cast(pa)) { + std::stringstream ss; + ss << "relative coordinate (" << dx << ", " << dy << ") expected to be in quadrant " << quadrantName(quadrant) << " but pseudoAngle " << pa << " corresponds to quadrant " << quadrantName(static_cast(pa)); + fail(ss.str()); + } + + ensure_equals(quadrant, static_cast(pa)); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_quadrant_group("geos::geom::Quadrant"); + +template<> +template<> +void object::test<1>() +{ + checkQuadrant(1, 1, Quadrant::NE); + checkQuadrant(1, -1, Quadrant::SE); + checkQuadrant(-1, -1, Quadrant::SW); + checkQuadrant(-1, 1, Quadrant::NW); +} + +template<> +template<> +void object::test<2>() +{ + static constexpr double eps = 1e-8; + + // center of each quadrant + checkPseudoAngleMatchesQuadrant(1, 1); + checkPseudoAngleMatchesQuadrant(1, -1); + checkPseudoAngleMatchesQuadrant(-1, -1); + checkPseudoAngleMatchesQuadrant(-1, 1); + + // near axes + checkPseudoAngleMatchesQuadrant(1, eps); // +X + checkPseudoAngleMatchesQuadrant(1, -eps); // +X + checkPseudoAngleMatchesQuadrant(eps, 1); // +Y + checkPseudoAngleMatchesQuadrant(-eps, 1); // +Y + checkPseudoAngleMatchesQuadrant(-1, eps); // +X + checkPseudoAngleMatchesQuadrant(-1, -eps); // +X + checkPseudoAngleMatchesQuadrant(eps, -1); // -Y + checkPseudoAngleMatchesQuadrant(-eps, -1); // -Y + + // axes + checkPseudoAngleMatchesQuadrant(1, 0); // +X + //checkPseudoAngleMatchesQuadrant(0, 1); // +Y + //checkPseudoAngleMatchesQuadrant(-1, 0); // +X + //checkPseudoAngleMatchesQuadrant(0, -1); // -Y +} +} ----------------------------------------------------------------------- Summary of changes: include/geos/geom/Quadrant.h | 18 +++++++ src/algorithm/CircularArcs.cpp | 22 +++++--- tests/unit/algorithm/CircularArcsTest.cpp | 14 ++++++ tests/unit/geom/QuadrantTest.cpp | 83 +++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 tests/unit/geom/QuadrantTest.cpp hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 23 09:36:22 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 23 Oct 2025 09:36:22 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. 08e7d5412fb5bca73046252b42f0586aaab6017d Message-ID: <20251023163622.E7A181925B7@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.14 has been updated via 08e7d5412fb5bca73046252b42f0586aaab6017d (commit) from 86ba6f9aa0d694ce87637ab5ecd264c304074606 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 08e7d5412fb5bca73046252b42f0586aaab6017d Author: Daniel Baston Date: Thu Oct 23 12:35:54 2025 -0400 CircularArcs: Fix incorrect Envelope calculation (#1314) diff --git a/NEWS.md b/NEWS.md index 5555b42ff..6d871d531 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ - Fixes/Improvements: - Make floating-point exceptions optional for geosop (GH-1305, Maxim Kochetkov) - GridIntersection: Fix crash for certain polygons outside grid extent (Dan Baston) + - Fix incorrect envelope calculation for arcs (GH-1314, Dan Baston) ## Changes in 3.14.0 diff --git a/include/geos/geom/Quadrant.h b/include/geos/geom/Quadrant.h index 6705f1f2e..d7a9e9519 100644 --- a/include/geos/geom/Quadrant.h +++ b/include/geos/geom/Quadrant.h @@ -119,6 +119,24 @@ public: } }; + /** Return a measure that increases monotonically with counterclockwise + * angle and avoids trigonometric calculations. Values are consistent + * with the numeric quadrant codes + * + * @param p0 circle center coordinate + * @param p1 coordinate for which pseudoangle should be calculated + */ + static double pseudoAngle(const CoordinateXY& p0, const CoordinateXY& p1) + { + const double dx = p1.x - p0.x; + const double dy = p1.y - p0.y; + + const double k = dx / (std::abs(dx) + std::abs(dy)); + const double w = 2 + (dy > 0 ? 3 - k : 1 + k); + + return w >= 4 ? w - 4 : w; + } + /** * Returns true if the quadrants are 1 and 3, or 2 and 4 */ diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index 949f18509..068dfd38c 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -57,6 +57,8 @@ void CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2) { + using geom::Quadrant; + e.expandToInclude(p0); e.expandToInclude(p1); e.expandToInclude(p2); @@ -73,22 +75,24 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co return; } - auto orientation = Orientation::index(center, p0, p1); - //* 1 | 0 //* --+-- //* 2 | 3 + const auto pa0 = Quadrant::pseudoAngle(center, p0); + const auto pa1 = Quadrant::pseudoAngle(center, p1); + const auto pa2 = Quadrant::pseudoAngle(center, p2); - using geom::Quadrant; - - auto q0 = geom::Quadrant::quadrant(center, p0); - auto q2 = geom::Quadrant::quadrant(center, p2); + auto q0 = static_cast(pa0); + auto q2 = static_cast(pa2); double R = center.distance(p1); if (q0 == q2) { - // Start and end quadrants are the same. Either the arc crosses all of + // Start and end quadrants are the same. Either the arc crosses all // the axes, or none of the axes. - if (Orientation::index(center, p1, p2) != orientation) { + + const bool isBetween = pa1 > std::min(pa0, pa2) && pa1 < std::max(pa0, pa2); + + if (!isBetween) { e.expandToInclude({center.x, center.y + R}); e.expandToInclude({center.x - R, center.y}); e.expandToInclude({center.x, center.y - R}); @@ -98,6 +102,8 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co return; } + auto orientation = Orientation::index(p0, p1, p2); + if (orientation == Orientation::CLOCKWISE) { std::swap(q0, q2); } diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp index 475261d4f..e40aa3435 100644 --- a/tests/unit/algorithm/CircularArcsTest.cpp +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -215,5 +215,19 @@ void object::test<13>() 3, 4, 3, 4); } +template<> +template<> +void object::test<14>() +{ + set_test_name("envelope: GH #1313"); + + CoordinateXY p0{2, 0}; + CoordinateXY p1{4, 2}; + CoordinateXY p2{2, 1}; + + checkEnvelope(p0, p1, p2, + 2, -1.0811388300841898, 5.08113883008419,2.08113883008419); +} + } diff --git a/tests/unit/geom/QuadrantTest.cpp b/tests/unit/geom/QuadrantTest.cpp new file mode 100644 index 000000000..bc23d1094 --- /dev/null +++ b/tests/unit/geom/QuadrantTest.cpp @@ -0,0 +1,83 @@ +#include + +#include + +using geos::geom::Quadrant; + +namespace tut { + +struct test_quadrant_data { + + static std::string quadrantName(int quadrant) { + switch (quadrant) { + case Quadrant::NE: return "NE"; + case Quadrant::NW: return "NW"; + case Quadrant::SE: return "SE"; + case Quadrant::SW: return "SW"; + } + return "unknown"; + } + + static void checkQuadrant(double dx, double dy, int expected) { + int quadrant = Quadrant::quadrant({0, 0}, {dx, dy}); + ensure_equals(quadrant, expected); + } + + static void checkPseudoAngleMatchesQuadrant(double dx, double dy) { + int quadrant = Quadrant::quadrant({0, 0}, {dx, dy}); + double pa = Quadrant::pseudoAngle({0, 0}, {dx, dy}); + + if (quadrant != static_cast(pa)) { + std::stringstream ss; + ss << "relative coordinate (" << dx << ", " << dy << ") expected to be in quadrant " << quadrantName(quadrant) << " but pseudoAngle " << pa << " corresponds to quadrant " << quadrantName(static_cast(pa)); + fail(ss.str()); + } + + ensure_equals(quadrant, static_cast(pa)); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_quadrant_group("geos::geom::Quadrant"); + +template<> +template<> +void object::test<1>() +{ + checkQuadrant(1, 1, Quadrant::NE); + checkQuadrant(1, -1, Quadrant::SE); + checkQuadrant(-1, -1, Quadrant::SW); + checkQuadrant(-1, 1, Quadrant::NW); +} + +template<> +template<> +void object::test<2>() +{ + static constexpr double eps = 1e-8; + + // center of each quadrant + checkPseudoAngleMatchesQuadrant(1, 1); + checkPseudoAngleMatchesQuadrant(1, -1); + checkPseudoAngleMatchesQuadrant(-1, -1); + checkPseudoAngleMatchesQuadrant(-1, 1); + + // near axes + checkPseudoAngleMatchesQuadrant(1, eps); // +X + checkPseudoAngleMatchesQuadrant(1, -eps); // +X + checkPseudoAngleMatchesQuadrant(eps, 1); // +Y + checkPseudoAngleMatchesQuadrant(-eps, 1); // +Y + checkPseudoAngleMatchesQuadrant(-1, eps); // +X + checkPseudoAngleMatchesQuadrant(-1, -eps); // +X + checkPseudoAngleMatchesQuadrant(eps, -1); // -Y + checkPseudoAngleMatchesQuadrant(-eps, -1); // -Y + + // axes + checkPseudoAngleMatchesQuadrant(1, 0); // +X + //checkPseudoAngleMatchesQuadrant(0, 1); // +Y + //checkPseudoAngleMatchesQuadrant(-1, 0); // +X + //checkPseudoAngleMatchesQuadrant(0, -1); // -Y +} +} ----------------------------------------------------------------------- Summary of changes: NEWS.md | 1 + include/geos/geom/Quadrant.h | 18 +++++++ src/algorithm/CircularArcs.cpp | 22 +++++--- tests/unit/algorithm/CircularArcsTest.cpp | 14 ++++++ tests/unit/geom/QuadrantTest.cpp | 83 +++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 tests/unit/geom/QuadrantTest.cpp hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 23 09:41:49 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 23 Oct 2025 09:41:49 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.13 updated. 28f2e42a6c40910fda9f08fddec28f43c148583b Message-ID: <20251023164150.1CA7A19239C@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.13 has been updated via 28f2e42a6c40910fda9f08fddec28f43c148583b (commit) from 32325937373d4f6e14be5cb40bb841188f5c0475 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 28f2e42a6c40910fda9f08fddec28f43c148583b Author: Daniel Baston Date: Thu Oct 23 12:40:40 2025 -0400 CircularArcs: Fix incorrect Envelope calculation (#1314) diff --git a/NEWS.md b/NEWS.md index 0a6228f90..5bcee20e8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ - Fix OverlayNG coordinate dimemsion handling for EMPTY geometries (GH-1258, Martin Davis) - Fix DepthSegment comparison logic (really this time) (GH-1266, Martin Davis) - Change CoverageGapFinder to return polygons (Martin Davis) + - Fix incorrect envelope calculation for arcs (GH-1314, Dan Baston) ## Changes in 3.13.1 2025-03-03 diff --git a/include/geos/geom/Quadrant.h b/include/geos/geom/Quadrant.h index 6705f1f2e..d7a9e9519 100644 --- a/include/geos/geom/Quadrant.h +++ b/include/geos/geom/Quadrant.h @@ -119,6 +119,24 @@ public: } }; + /** Return a measure that increases monotonically with counterclockwise + * angle and avoids trigonometric calculations. Values are consistent + * with the numeric quadrant codes + * + * @param p0 circle center coordinate + * @param p1 coordinate for which pseudoangle should be calculated + */ + static double pseudoAngle(const CoordinateXY& p0, const CoordinateXY& p1) + { + const double dx = p1.x - p0.x; + const double dy = p1.y - p0.y; + + const double k = dx / (std::abs(dx) + std::abs(dy)); + const double w = 2 + (dy > 0 ? 3 - k : 1 + k); + + return w >= 4 ? w - 4 : w; + } + /** * Returns true if the quadrants are 1 and 3, or 2 and 4 */ diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index 949f18509..068dfd38c 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -57,6 +57,8 @@ void CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2) { + using geom::Quadrant; + e.expandToInclude(p0); e.expandToInclude(p1); e.expandToInclude(p2); @@ -73,22 +75,24 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co return; } - auto orientation = Orientation::index(center, p0, p1); - //* 1 | 0 //* --+-- //* 2 | 3 + const auto pa0 = Quadrant::pseudoAngle(center, p0); + const auto pa1 = Quadrant::pseudoAngle(center, p1); + const auto pa2 = Quadrant::pseudoAngle(center, p2); - using geom::Quadrant; - - auto q0 = geom::Quadrant::quadrant(center, p0); - auto q2 = geom::Quadrant::quadrant(center, p2); + auto q0 = static_cast(pa0); + auto q2 = static_cast(pa2); double R = center.distance(p1); if (q0 == q2) { - // Start and end quadrants are the same. Either the arc crosses all of + // Start and end quadrants are the same. Either the arc crosses all // the axes, or none of the axes. - if (Orientation::index(center, p1, p2) != orientation) { + + const bool isBetween = pa1 > std::min(pa0, pa2) && pa1 < std::max(pa0, pa2); + + if (!isBetween) { e.expandToInclude({center.x, center.y + R}); e.expandToInclude({center.x - R, center.y}); e.expandToInclude({center.x, center.y - R}); @@ -98,6 +102,8 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co return; } + auto orientation = Orientation::index(p0, p1, p2); + if (orientation == Orientation::CLOCKWISE) { std::swap(q0, q2); } diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp index 475261d4f..e40aa3435 100644 --- a/tests/unit/algorithm/CircularArcsTest.cpp +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -215,5 +215,19 @@ void object::test<13>() 3, 4, 3, 4); } +template<> +template<> +void object::test<14>() +{ + set_test_name("envelope: GH #1313"); + + CoordinateXY p0{2, 0}; + CoordinateXY p1{4, 2}; + CoordinateXY p2{2, 1}; + + checkEnvelope(p0, p1, p2, + 2, -1.0811388300841898, 5.08113883008419,2.08113883008419); +} + } diff --git a/tests/unit/geom/QuadrantTest.cpp b/tests/unit/geom/QuadrantTest.cpp new file mode 100644 index 000000000..bc23d1094 --- /dev/null +++ b/tests/unit/geom/QuadrantTest.cpp @@ -0,0 +1,83 @@ +#include + +#include + +using geos::geom::Quadrant; + +namespace tut { + +struct test_quadrant_data { + + static std::string quadrantName(int quadrant) { + switch (quadrant) { + case Quadrant::NE: return "NE"; + case Quadrant::NW: return "NW"; + case Quadrant::SE: return "SE"; + case Quadrant::SW: return "SW"; + } + return "unknown"; + } + + static void checkQuadrant(double dx, double dy, int expected) { + int quadrant = Quadrant::quadrant({0, 0}, {dx, dy}); + ensure_equals(quadrant, expected); + } + + static void checkPseudoAngleMatchesQuadrant(double dx, double dy) { + int quadrant = Quadrant::quadrant({0, 0}, {dx, dy}); + double pa = Quadrant::pseudoAngle({0, 0}, {dx, dy}); + + if (quadrant != static_cast(pa)) { + std::stringstream ss; + ss << "relative coordinate (" << dx << ", " << dy << ") expected to be in quadrant " << quadrantName(quadrant) << " but pseudoAngle " << pa << " corresponds to quadrant " << quadrantName(static_cast(pa)); + fail(ss.str()); + } + + ensure_equals(quadrant, static_cast(pa)); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_quadrant_group("geos::geom::Quadrant"); + +template<> +template<> +void object::test<1>() +{ + checkQuadrant(1, 1, Quadrant::NE); + checkQuadrant(1, -1, Quadrant::SE); + checkQuadrant(-1, -1, Quadrant::SW); + checkQuadrant(-1, 1, Quadrant::NW); +} + +template<> +template<> +void object::test<2>() +{ + static constexpr double eps = 1e-8; + + // center of each quadrant + checkPseudoAngleMatchesQuadrant(1, 1); + checkPseudoAngleMatchesQuadrant(1, -1); + checkPseudoAngleMatchesQuadrant(-1, -1); + checkPseudoAngleMatchesQuadrant(-1, 1); + + // near axes + checkPseudoAngleMatchesQuadrant(1, eps); // +X + checkPseudoAngleMatchesQuadrant(1, -eps); // +X + checkPseudoAngleMatchesQuadrant(eps, 1); // +Y + checkPseudoAngleMatchesQuadrant(-eps, 1); // +Y + checkPseudoAngleMatchesQuadrant(-1, eps); // +X + checkPseudoAngleMatchesQuadrant(-1, -eps); // +X + checkPseudoAngleMatchesQuadrant(eps, -1); // -Y + checkPseudoAngleMatchesQuadrant(-eps, -1); // -Y + + // axes + checkPseudoAngleMatchesQuadrant(1, 0); // +X + //checkPseudoAngleMatchesQuadrant(0, 1); // +Y + //checkPseudoAngleMatchesQuadrant(-1, 0); // +X + //checkPseudoAngleMatchesQuadrant(0, -1); // -Y +} +} ----------------------------------------------------------------------- Summary of changes: NEWS.md | 1 + include/geos/geom/Quadrant.h | 18 +++++++ src/algorithm/CircularArcs.cpp | 22 +++++--- tests/unit/algorithm/CircularArcsTest.cpp | 14 ++++++ tests/unit/geom/QuadrantTest.cpp | 83 +++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 tests/unit/geom/QuadrantTest.cpp hooks/post-receive -- GEOS From git at osgeo.org Thu Oct 23 10:27:05 2025 From: git at osgeo.org (git at osgeo.org) Date: Thu, 23 Oct 2025 10:27:05 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. c0d97ffcdfd2f5f175053ff1aa9b44df6baf49be Message-ID: <20251023172705.7D29A1925E8@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via c0d97ffcdfd2f5f175053ff1aa9b44df6baf49be (commit) from 47435e4f5d4163d01e77116b289de5b5c57778c6 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit c0d97ffcdfd2f5f175053ff1aa9b44df6baf49be Author: Daniel Baston Date: Thu Oct 23 13:26:44 2025 -0400 FAQ: Remove outdated content about M ordinates (#1318) diff --git a/web/content/usage/faq/index.md b/web/content/usage/faq/index.md index 5481744af..5ecc2fcd4 100644 --- a/web/content/usage/faq/index.md +++ b/web/content/usage/faq/index.md @@ -37,11 +37,6 @@ One way to perform geodetic computations is to project data to an appropriate pl using a transformation library such as [PROJ](https://proj.org/). The desired geometric operations can be computed in planar space, and reprojected back to geodetic. -### Does GEOS support coordinates with measures (M)? - -No, the GEOS coordinate model only supports X,Y and Z ordinates. -We hope to add support for M, and also a more efficient XY coordinate storage representation. - ## Robustness ### Why does `GEOSIntersects(GEOSIntersection(A, B), A) == false`? @@ -50,7 +45,7 @@ GEOS represents geometry coordinates using IEEE-754 double-precision floating po This is a finite representation, whereas the implicit lines between vertices have infinite precision. In general it is highly unlikely that a coordinate computed via an arithmetic operation (such as a line intersection) is reported by an `intersects` test as lying exactly on the (theoretical) lines. -For example, the diagram below shows how the computed intersection point of two lines in general does not lie exactly on either line (scale exaggerrated for clarity): +For example, the diagram below shows how the computed intersection point of two lines in general does not lie exactly on either line (scale exaggerated for clarity): ![GEOS computed intersection point for two lines](geos-line-intersect-precision.png) @@ -61,6 +56,6 @@ As explained in the previous question, GEOS uses finite-precision floating point because in the general case it is not possible to represent points along a line exactly using finite-precision numbers. The diagram below shows how points interpolated along a line rarely lie exactly on the line -(scale exaggerrated for clarity): +(scale exaggerated for clarity): ![GEOS computed points interpolated along line](geos-line-interpolated-precision.png) ----------------------------------------------------------------------- Summary of changes: web/content/usage/faq/index.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Mon Oct 27 07:25:32 2025 From: git at osgeo.org (git at osgeo.org) Date: Mon, 27 Oct 2025 07:25:32 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. 57d4d3994f273de8940dc86ddc21cb2b76efb975 Message-ID: <20251027142533.14F8B1992D4@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.14 has been updated via 57d4d3994f273de8940dc86ddc21cb2b76efb975 (commit) from 08e7d5412fb5bca73046252b42f0586aaab6017d (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 57d4d3994f273de8940dc86ddc21cb2b76efb975 Author: Daniel Baston Date: Mon Oct 27 10:25:03 2025 -0400 Update NEWS diff --git a/NEWS.md b/NEWS.md index 6d871d531..737bef7d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ - Fixes/Improvements: - Make floating-point exceptions optional for geosop (GH-1305, Maxim Kochetkov) + - Fix undefined behaviour in CoordinateSequence::closeRing (GH-1309, Paul Ramsey) - GridIntersection: Fix crash for certain polygons outside grid extent (Dan Baston) - Fix incorrect envelope calculation for arcs (GH-1314, Dan Baston) ----------------------------------------------------------------------- Summary of changes: NEWS.md | 1 + 1 file changed, 1 insertion(+) hooks/post-receive -- GEOS From git at osgeo.org Mon Oct 27 08:05:23 2025 From: git at osgeo.org (git at osgeo.org) Date: Mon, 27 Oct 2025 08:05:23 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. a2f2ceedf4806a0977727b07fbafc9e1d93974e7 Message-ID: <20251027150523.405D9199D8E@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via a2f2ceedf4806a0977727b07fbafc9e1d93974e7 (commit) from c0d97ffcdfd2f5f175053ff1aa9b44df6baf49be (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit a2f2ceedf4806a0977727b07fbafc9e1d93974e7 Author: Paul Ramsey Date: Mon Oct 27 08:04:46 2025 -0700 Add reverse test case diff --git a/tests/unit/operation/relateng/RelateNGTest.cpp b/tests/unit/operation/relateng/RelateNGTest.cpp index ec4a757f6..ee8d3a841 100644 --- a/tests/unit/operation/relateng/RelateNGTest.cpp +++ b/tests/unit/operation/relateng/RelateNGTest.cpp @@ -922,6 +922,7 @@ void object::test<64> () std::string a = "LINESTRING(120 215, 176 197)"; std::string b = "POLYGON ((100 200, 140 230, 180 310, 280 310, 390 270, 400 210, 320 140, 215 141, 150 170, 100 200))"; checkRelate(a, b, "1FF00F212"); + checkRelate(b, a, "102F01FF2"); } ----------------------------------------------------------------------- Summary of changes: tests/unit/operation/relateng/RelateNGTest.cpp | 1 + 1 file changed, 1 insertion(+) hooks/post-receive -- GEOS From git at osgeo.org Mon Oct 27 08:31:14 2025 From: git at osgeo.org (git at osgeo.org) Date: Mon, 27 Oct 2025 08:31:14 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. beb52e94b100efc12a8f764d896586adc10420c8 Message-ID: <20251027153114.F09BD199D77@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, 3.14 has been updated via beb52e94b100efc12a8f764d896586adc10420c8 (commit) via c389f532d25fe6228861d9b19339f9cb57ca4bdb (commit) from 57d4d3994f273de8940dc86ddc21cb2b76efb975 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit beb52e94b100efc12a8f764d896586adc10420c8 Author: Paul Ramsey Date: Mon Oct 27 08:30:49 2025 -0700 Bump to 3.14.2dev diff --git a/Version.txt b/Version.txt index b10bdc5b5..646db1a93 100644 --- a/Version.txt +++ b/Version.txt @@ -2,10 +2,10 @@ # GEOS Versions GEOS_VERSION_MAJOR=3 GEOS_VERSION_MINOR=14 -GEOS_VERSION_PATCH=1 +GEOS_VERSION_PATCH=2 # OPTIONS: "", "dev", "rc1" etc. -GEOS_PATCH_WORD= +GEOS_PATCH_WORD=dev # GEOS CAPI Versions # commit c389f532d25fe6228861d9b19339f9cb57ca4bdb Author: Paul Ramsey Date: Mon Oct 27 08:07:37 2025 -0700 Prep for 3.14.1 diff --git a/NEWS.md b/NEWS.md index 737bef7d0..d31f941e0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ ## Changes in 3.14.1 -2025-xx-xx +2025-10-27 - Fixes/Improvements: - Make floating-point exceptions optional for geosop (GH-1305, Maxim Kochetkov) diff --git a/Version.txt b/Version.txt index ad6cdbe32..b10bdc5b5 100644 --- a/Version.txt +++ b/Version.txt @@ -5,7 +5,7 @@ GEOS_VERSION_MINOR=14 GEOS_VERSION_PATCH=1 # OPTIONS: "", "dev", "rc1" etc. -GEOS_PATCH_WORD=dev +GEOS_PATCH_WORD= # GEOS CAPI Versions # @@ -16,7 +16,7 @@ GEOS_PATCH_WORD=dev # ( THIS MUST BE CAREFULLY AVOIDED ) # CAPI_INTERFACE_CURRENT=21 -CAPI_INTERFACE_REVISION=4 +CAPI_INTERFACE_REVISION=5 CAPI_INTERFACE_AGE=20 # JTS Port ----------------------------------------------------------------------- Summary of changes: NEWS.md | 2 +- Version.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Mon Oct 27 08:32:31 2025 From: git at osgeo.org (git at osgeo.org) Date: Mon, 27 Oct 2025 08:32:31 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. d7db390c7c59c13276cae6ca359b551d0026f01f Message-ID: <20251027153231.7F41E199B9E@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via d7db390c7c59c13276cae6ca359b551d0026f01f (commit) from a2f2ceedf4806a0977727b07fbafc9e1d93974e7 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit d7db390c7c59c13276cae6ca359b551d0026f01f Author: Paul Ramsey Date: Mon Oct 27 08:30:26 2025 -0700 update download link for 3.14.1 diff --git a/web/content/usage/download.md b/web/content/usage/download.md index 12e52274b..33f4d3003 100644 --- a/web/content/usage/download.md +++ b/web/content/usage/download.md @@ -8,7 +8,7 @@ draft: false | Release | Release Date | First Release | Final Release | Download Link | Changes | | :--: | :--:| :--: |:--: | :--: | :--: | -| **3.14.0** | 2025/08/21 | 2025/08/21 | *2029/10/01* | [geos-3.14.0.tar.bz2](https://download.osgeo.org/geos/geos-3.14.0.tar.bz2) | [Changes](https://github.com/libgeos/geos/blob/3.14.0/NEWS.md) | +| **3.14.1** | 2025/10/27 | 2025/08/21 | *2029/10/01* | [geos-3.14.1.tar.bz2](https://download.osgeo.org/geos/geos-3.14.1.tar.bz2) | [Changes](https://github.com/libgeos/geos/blob/3.14.1/NEWS.md) | | **3.13.1** | 2025/03/03 | 2024/09/06 | *2028/10/01* | [geos-3.13.1.tar.bz2](https://download.osgeo.org/geos/geos-3.13.1.tar.bz2) | [Changes](https://github.com/libgeos/geos/blob/3.13.1/NEWS.md) | | **3.12.3** | 2025/03/03 | 2023/06/27 | *2027/07/01* | [geos-3.12.3.tar.bz2](https://download.osgeo.org/geos/geos-3.12.3.tar.bz2) | [Changes](https://github.com/libgeos/geos/blob/3.12.3/NEWS.md) | | **3.11.5** | 2025/03/03 | 2022/07/01 | *2026/07/01* | [geos-3.11.5.tar.bz2](https://download.osgeo.org/geos/geos-3.11.5.tar.bz2) | [Changes](https://github.com/libgeos/geos/blob/3.11.5/NEWS.md) | ----------------------------------------------------------------------- Summary of changes: web/content/usage/download.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Mon Oct 27 08:49:57 2025 From: git at osgeo.org (git at osgeo.org) Date: Mon, 27 Oct 2025 08:49:57 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. eedd2c3059aa5445c72e8c74e6247e9013564bed Message-ID: <20251027154958.1862419A6A5@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via eedd2c3059aa5445c72e8c74e6247e9013564bed (commit) from d7db390c7c59c13276cae6ca359b551d0026f01f (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit eedd2c3059aa5445c72e8c74e6247e9013564bed Author: Paul Ramsey Date: Mon Oct 27 08:49:35 2025 -0700 Update reference section diff --git a/web/content/_index.md b/web/content/_index.md index 9e9976827..2b4b85116 100644 --- a/web/content/_index.md +++ b/web/content/_index.md @@ -55,7 +55,7 @@ and when they are caused by algorithm (both libraries fail). To cite GEOS in publications use: -> GEOS contributors (2024). GEOS computational geometry library. Open Source Geospatial Foundation. URL https://libgeos.org/. +> GEOS contributors (2025). GEOS computational geometry library. Open Source Geospatial Foundation. URL https://libgeos.org/. A BibTeX entry for LaTeX users is ``` @@ -63,7 +63,7 @@ A BibTeX entry for LaTeX users is title = {{GEOS} computational geometry library}, author = {{GEOS contributors}}, organization = {Open Source Geospatial Foundation}, - year = {2024}, + year = {2025}, url = {https://libgeos.org/}, doi = {10.5281/zenodo.11396894} } ----------------------------------------------------------------------- Summary of changes: web/content/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Oct 28 05:56:22 2025 From: git at osgeo.org (git at osgeo.org) Date: Tue, 28 Oct 2025 05:56:22 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. b5960c3a08c3ccaffee019529f847dc02371a790 Message-ID: <20251028125624.15CBC35F6@trac.osgeo.org> This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "GEOS". The branch, main has been updated via b5960c3a08c3ccaffee019529f847dc02371a790 (commit) from eedd2c3059aa5445c72e8c74e6247e9013564bed (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit b5960c3a08c3ccaffee019529f847dc02371a790 Author: Daniel Baston Date: Tue Oct 28 08:55:59 2025 -0400 Add CircularArcIntersector (#1171) * Add CircularArcIntersector * CircularArcIntersector: Add tests with nearly-degenerate arcs * Angle: Fix impl of isWithinCCW and add tests * CircularArc: Add some tests * CircularArcIntersector: Add some tests from ILI validator project * CircularArcIntersector: Pull circle-segment intersection into own method * Arcs: Make CircularArc store its coordinates so we can construct it from a known radius and centerpoint * CircularArcs: fix orientation in createArc * CircularArc: Add splitAtPoint() * Add CircularArc::getSagitta * CircularArcIntersector: Add (but do not use) alternate formulation for intersection test * CircularArcIntersector: Relax assertions on expected points, disable 3 failing tests diff --git a/include/geos/algorithm/Angle.h b/include/geos/algorithm/Angle.h index 6f86f3d33..192891022 100644 --- a/include/geos/algorithm/Angle.h +++ b/include/geos/algorithm/Angle.h @@ -218,6 +218,15 @@ public: /// static double normalizePositive(double angle); + /// Returns true if angle x is within the counterclockwise + /// arc from angle a to angle b + /// + /// @param angle angle to test + /// @param from starting angle of arc + /// @param to ending angle of arc + /// + /// @return true if `angle` is within [from, to] + static bool isWithinCCW(double angle, double from, double to); /// Computes the unoriented smallest difference between two angles. /// diff --git a/include/geos/algorithm/CircularArcIntersector.h b/include/geos/algorithm/CircularArcIntersector.h new file mode 100644 index 000000000..dad551906 --- /dev/null +++ b/include/geos/algorithm/CircularArcIntersector.h @@ -0,0 +1,97 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace geos::algorithm { + +class GEOS_DLL CircularArcIntersector { +public: + using CoordinateXY = geom::CoordinateXY; + using CircularArc = geom::CircularArc; + using Envelope = geom::Envelope; + + enum intersection_type : uint8_t { + NO_INTERSECTION = 0, + ONE_POINT_INTERSECTION = 1, + TWO_POINT_INTERSECTION = 2, + COCIRCULAR_INTERSECTION = 3, + }; + + intersection_type getResult() const + { + return result; + } + + const CoordinateXY& getPoint(std::uint8_t i) const + { + return intPt[i]; + } + + const CircularArc& getArc(std::uint8_t i) const + { + return intArc[i]; + } + + std::uint8_t getNumPoints() const + { + return nPt; + } + + std::uint8_t getNumArcs() const + { + return nArc; + } + + /// Determines whether and where a circular arc intersects a line segment. + /// + /// Sets the appropriate value of intersection_type and stores the intersection + /// points, if any. + void intersects(const CircularArc& arc, const CoordinateXY& p0, const CoordinateXY& p1); + + void intersects(const CircularArc& arc, const geom::LineSegment& seg) + { + intersects(arc, seg.p0, seg.p1); + } + + /// Determines whether and where two circular arcs intersect. + /// + /// Sets the appropriate value of intersection_type and stores the intersection + /// points and/or arcs, if any. + void intersects(const CircularArc& arc1, const CircularArc& arc2); + + static int + circleIntersects(const CoordinateXY& center, double r, const CoordinateXY& p0, const CoordinateXY& p1, CoordinateXY& isect0, CoordinateXY& isect1); + +private: + + void intersects(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& q0, const CoordinateXY& q1); + + std::array intPt; + std::array intArc; + intersection_type result = NO_INTERSECTION; + std::uint8_t nPt = 0; + std::uint8_t nArc = 0; + +}; + +} diff --git a/include/geos/algorithm/CircularArcs.h b/include/geos/algorithm/CircularArcs.h index 54f0a9b7d..60876b97e 100644 --- a/include/geos/algorithm/CircularArcs.h +++ b/include/geos/algorithm/CircularArcs.h @@ -14,6 +14,8 @@ #pragma once +#include + #include #include #include @@ -28,9 +30,19 @@ public: static geom::CoordinateXY getCenter(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2); + static double getAngle(const geom::CoordinateXY& pt, const geom::CoordinateXY& center); + + static double getMidpointAngle(double theta0, double theta2, bool isCCW); + + static geom::CoordinateXY getMidpoint(const geom::CoordinateXY& p0, const geom::CoordinateXY& p2, const geom::CoordinateXY& center, double radius, bool isCCW); + /// Expand an envelope to include an arc defined by three points static void expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2); + + /// Return the point defined by a circle center, radius, and angle + static geom::CoordinateXY createPoint(const geom::CoordinateXY& center, double radius, double theta); + }; } diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h index 283eaf3b0..621ab3ec1 100644 --- a/include/geos/geom/CircularArc.h +++ b/include/geos/geom/CircularArc.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,8 @@ public: using CoordinateXY = geom::CoordinateXY; + CircularArc() : CircularArc({0, 0}, {0, 0}, {0, 0}) {} + CircularArc(const CoordinateXY& q0, const CoordinateXY& q1, const CoordinateXY& q2) : p0(q0) , p1(q1) @@ -40,15 +43,39 @@ public: , m_orientation_known(false) {} - const CoordinateXY& p0; - const CoordinateXY& p1; - const CoordinateXY& p2; + CircularArc(double theta0, double theta2, const CoordinateXY& center, double radius, int orientation) + : p0(algorithm::CircularArcs::createPoint(center, radius, theta0)), + p1(algorithm::CircularArcs::createPoint(center, radius, algorithm::CircularArcs::getMidpointAngle(theta0, theta2, orientation==algorithm::Orientation::COUNTERCLOCKWISE))), + p2(algorithm::CircularArcs::createPoint(center, radius, theta2)), + m_center(center), + m_radius(radius), + m_orientation(orientation), + m_center_known(true), + m_radius_known(true), + m_orientation_known(true) + {} + + CircularArc(const CoordinateXY& q0, const CoordinateXY& q2, const CoordinateXY& center, double radius, int orientation) + : p0(q0), + p1(algorithm::CircularArcs::getMidpoint(q0, q2, center, radius, orientation==algorithm::Orientation::COUNTERCLOCKWISE)), + p2(q2), + m_center(center), + m_radius(radius), + m_orientation(orientation), + m_center_known(true), + m_radius_known(true), + m_orientation_known(true) + {} + + CoordinateXY p0; + CoordinateXY p1; + CoordinateXY p2; /// Return the orientation of the arc as one of: /// - algorithm::Orientation::CLOCKWISE, /// - algorithm::Orientation::COUNTERCLOCKWISE /// - algorithm::Orientation::COLLINEAR - int orientation() const { + int getOrientation() const { if (!m_orientation_known) { m_orientation = algorithm::Orientation::index(p0, p1, p2); m_orientation_known = true; @@ -56,6 +83,10 @@ public: return m_orientation; } + bool isCCW() const { + return getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE; + } + /// Return the center point of the circle associated with this arc const CoordinateXY& getCenter() const { if (!m_center_known) { @@ -83,7 +114,7 @@ public: /// Returns whether this arc forms a straight line (p0, p1, and p2 are collinear) bool isLinear() const { - return std::isnan(getRadius()); + return !std::isfinite(getRadius()); } /// Return the inner angle of the sector associated with this arc @@ -100,7 +131,7 @@ public: auto t0 = theta0(); auto t2 = theta2(); - if (orientation() == algorithm::Orientation::COUNTERCLOCKWISE) { + if (getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE) { std::swap(t0, t2); } @@ -133,14 +164,20 @@ public: return R*R/2*(theta - std::sin(theta)); } + /// Return the distance from the centerpoint of the arc to the line segment formed by the end points of the arc. + double getSagitta() const { + CoordinateXY midpoint = algorithm::CircularArcs::getMidpoint(p0, p2, getCenter(), getRadius(), isCCW()); + return algorithm::Distance::pointToSegment(midpoint, p0, p2); + } + /// Return the angle of p0 double theta0() const { - return std::atan2(p0.y - getCenter().y, p0.x - getCenter().x); + return algorithm::CircularArcs::getAngle(p0, getCenter()); } /// Return the angle of p2 double theta2() const { - return std::atan2(p2.y - getCenter().y, p2.x - getCenter().x); + return algorithm::CircularArcs::getAngle(p2, getCenter()); } /// Check to see if a coordinate lies on the arc @@ -153,18 +190,18 @@ public: /// Check to see if a coordinate lies on the arc, after testing whether /// it lies on the circle. - bool containsPoint(const CoordinateXY& q) { + bool containsPoint(const CoordinateXY& q) const { if (q == p0 || q == p1 || q == p2) { return true; } - auto dist = std::abs(q.distance(getCenter()) - getRadius()); + //auto dist = std::abs(q.distance(getCenter()) - getRadius()); - if (dist > 1e-8) { - return false; - } + //if (dist > 1e-8) { + // return false; + //} - if (triangulate::quadedge::TrianglePredicate::isInCircleNormalized(p0, p1, p2, q) != geom::Location::BOUNDARY) { + if (triangulate::quadedge::TrianglePredicate::isInCircleRobust(p0, p1, p2, q) != geom::Location::BOUNDARY) { return false; } @@ -180,7 +217,7 @@ public: return true; } - if (orientation() == algorithm::Orientation::COUNTERCLOCKWISE) { + if (getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE) { std::swap(t0, t2); } @@ -204,7 +241,7 @@ public: auto quad = geom::Quadrant::quadrant(getCenter(), q); bool isUpward; - if (orientation() == algorithm::Orientation::CLOCKWISE) { + if (getOrientation() == algorithm::Orientation::CLOCKWISE) { isUpward = (quad == geom::Quadrant::SW || quad == geom::Quadrant::NW); } else { isUpward = (quad == geom::Quadrant::SE || quad == geom::Quadrant::NE); @@ -213,6 +250,15 @@ public: return isUpward; } + // Split an arc at a specified point. + // The point is assumed to be o the arc. + std::pair splitAtPoint(const CoordinateXY& q) const { + return { + CircularArc(p0, q, getCenter(), getRadius(), getOrientation()), + CircularArc(q, p2, getCenter(), getRadius(), getOrientation()) + }; + } + class Iterator { public: using iterator_category = std::forward_iterator_tag; diff --git a/include/geos/math/DD.h b/include/geos/math/DD.h index 61e3a08a7..7be00a6f7 100644 --- a/include/geos/math/DD.h +++ b/include/geos/math/DD.h @@ -158,6 +158,7 @@ class GEOS_DLL DD { friend GEOS_DLL DD operator* (const DD &lhs, double rhs); friend GEOS_DLL DD operator/ (const DD &lhs, const DD &rhs); friend GEOS_DLL DD operator/ (const DD &lhs, double rhs); + friend GEOS_DLL DD operator- (const DD& x); static DD determinant(const DD &x1, const DD &y1, const DD &x2, const DD &y2); static DD determinant(double x1, double y1, double x2, double y2); diff --git a/include/geos/triangulate/quadedge/TrianglePredicate.h b/include/geos/triangulate/quadedge/TrianglePredicate.h index c356928ce..22273ad9f 100644 --- a/include/geos/triangulate/quadedge/TrianglePredicate.h +++ b/include/geos/triangulate/quadedge/TrianglePredicate.h @@ -70,7 +70,7 @@ public: * Tests if a point is inside the circle defined by * the triangle with vertices a, b, c (oriented counter-clockwise). * This test uses simple - * double-precision arithmetic, and thus is not 10% robust. + * double-precision arithmetic, and thus is not 100% robust. * However, by using normalization to the origin * it provides improved robustness and increased performance. *

diff --git a/src/algorithm/Angle.cpp b/src/algorithm/Angle.cpp index f9e176768..b63dc4db8 100644 --- a/src/algorithm/Angle.cpp +++ b/src/algorithm/Angle.cpp @@ -194,6 +194,15 @@ Angle::normalizePositive(double angle) return angle; } +bool +Angle::isWithinCCW(double x, double a, double b) { + if (b > a) { + return x >= a && x <= b; + } else { + return x >= a || x <= b; + } +} + /* public static */ double Angle::diff(double ang1, double ang2) diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp new file mode 100644 index 000000000..5ed2a44b7 --- /dev/null +++ b/src/algorithm/CircularArcIntersector.cpp @@ -0,0 +1,328 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include + +namespace geos::algorithm { + +static double +nextAngleCCW(double from, double a, double b) +{ + if (Angle::normalizePositive(a - from) < Angle::normalizePositive(b - from)) { + return a; + } + else { + return b; + } +} + +int +CircularArcIntersector::circleIntersects(const CoordinateXY& center, double r, const CoordinateXY& p0, const CoordinateXY& p1, CoordinateXY& ret0, CoordinateXY& ret1) +{ + const double& x0 = center.x; + const double& y0 = center.y; + + Envelope segEnv(p0, p1); + + CoordinateXY isect0, isect1; + int n = 0; + + if (p1.x == p0.x) { + // vertical line + double x = p1.x; + + double A = 1; + double B = 2*y0; + double C = x*x - 2*x*x0 + x0*x0 + y0*y0 - r*r; + + double d = std::sqrt(B*B - 4*A*C); + double Y1 = (-B + d)/(2*A); + double Y2 = (-B - d)/(2*A); + + isect0 = {x, Y1}; + isect1 = {x, Y2}; + } + else { + double m = (p1.y - p0.y) / (p1.x - p0.x); + double b = p1.y - p1.x*m; + + // Ax^2 + Bx + C = 0 + double A = 1 + m*m; + double B = -2*x0 + 2*m*b - 2*m*y0; + double C = x0*x0 + b*b - 2*b*y0 + y0*y0 - r*r; + + double d = std::sqrt(B*B - 4*A*C); + double X1 = (-B + d)/(2*A); + double X2 = (-B - d)/(2*A); + + // TODO use robust quadratic solver such as https://github.com/archermarx/quadratic ? + // auto [X1, X2] = quadratic::solve(A, B, C); + + isect0 = {X1, m* X1 + b}; + isect1 = {X2, m* X2 + b}; + } + + if (segEnv.intersects(isect0)) { + ret0 = isect0; + if (segEnv.intersects(isect1) && !isect1.equals2D(isect0)) { + ret1 = isect1; + n = 2; + } else { + n = 1; + } + } else if (segEnv.intersects(isect1)) { + ret0 = isect1; + n = 1; + } + + return n; +} + +void +CircularArcIntersector::intersects(const CircularArc& arc, const CoordinateXY& p0, const CoordinateXY& p1) +{ + if (arc.isLinear()) { + intersects(arc.p0, arc.p2, p0, p1); + return; + } + + // TODO: envelope check? + const CoordinateXY& c = arc.getCenter(); + const double r = arc.getRadius(); + + CoordinateXY isect0, isect1; + auto n = circleIntersects(c, r, p0, p1, isect0, isect1); + + if (n > 0 && arc.containsPointOnCircle(isect0)) { + intPt[nPt++] = isect0; + } + + if (n > 1 && arc.containsPointOnCircle(isect1)) { + intPt[nPt++] = isect1; + } + + switch (nPt) { + case 2: + result = TWO_POINT_INTERSECTION; + break; + case 1: + result = ONE_POINT_INTERSECTION; + break; + default: + result = NO_INTERSECTION; + } +} + +void +CircularArcIntersector::intersects(const CoordinateXY& p0, const CoordinateXY& p1, + const CoordinateXY& q0, const CoordinateXY& q1) +{ + algorithm::LineIntersector li; + li.computeIntersection(p0, p1, q0, q1); + if (li.getIntersectionNum() == 2) { + // FIXME this means a collinear intersection, so we should report as cocircular? + intPt[0] = li.getIntersection(0); + intPt[1] = li.getIntersection(1); + result = TWO_POINT_INTERSECTION; + } else if (li.getIntersectionNum() == 1) { + intPt[0] = li.getIntersection(0); + nPt = 1; + result = ONE_POINT_INTERSECTION; + } else { + result = NO_INTERSECTION; + } +} + +void +CircularArcIntersector::intersects(const CircularArc& arc1, const CircularArc& arc2) +{ + // Handle cases where one or both arcs are degenerate + if (arc1.isLinear()) { + if (arc2.isLinear()) { + intersects(arc1.p0, arc1.p2, arc2.p0, arc2.p2); + return; + } else { + intersects(arc2, arc1.p0, arc1.p2); + return; + } + } else if (arc2.isLinear()) { + intersects(arc1, arc2.p0, arc2.p2); + return; + } + + const auto& c1 = arc1.getCenter(); + const auto& c2 = arc2.getCenter(); + + const auto r1 = arc1.getRadius(); + const auto r2 = arc2.getRadius(); + + auto d = c1.distance(c2); + + if (d > r1 + r2) { + // Circles are disjoint + result = NO_INTERSECTION; + return; + } + + if (d < std::abs(r1-r2)) { + // One circle contained within the other; arcs cannot intersect + result = NO_INTERSECTION; + return; + } + + // a: the distance from c1 to the "radical line", which connects the two intersection points + // The following expression was rewritten by + double a = (d*d + r1*r1 - r2*r2) / (2*d); + // Expression rewritten by Herbie, https://herbie.uwplse.org/demo/ + // double a = std::fma(r1-r2, (r1 + r2) / (d+d), d*0.5); + + // TODO because the circle center calculation is inexact we need some kind of tolerance here. + // Take a PrecisionModel like LineIntersector? + if (a == 0 || (d == 0 && r1 == r2)) { + // COCIRCULAR + + double ap0 = arc1.theta0(); + double ap1 = arc1.theta2(); + double bp0 = arc2.theta0(); + double bp1 = arc2.theta2(); + + bool resultArcIsCCW = true; + + if (arc1.getOrientation() != Orientation::COUNTERCLOCKWISE) { + std::swap(ap0, ap1); + resultArcIsCCW = false; + } + if (arc2.getOrientation() != Orientation::COUNTERCLOCKWISE) { + std::swap(bp0, bp1); + } + ap0 = Angle::normalizePositive(ap0); + ap1 = Angle::normalizePositive(ap1); + bp0 = Angle::normalizePositive(bp0); + bp1 = Angle::normalizePositive(bp1); + + bool checkBp1inA = true; + + // check start of B within A? + if (Angle::isWithinCCW(bp0, ap0, ap1)) { + double start = bp0; + double end = nextAngleCCW(start, bp1, ap1); + + if (end == bp1) { + checkBp1inA = false; + } + + if (start == end) { + intPt[nPt++] = CircularArcs::createPoint(c1, r1, start); + } + else { + if (resultArcIsCCW) { + intArc[nArc++] = CircularArc(start, end, c1, r1, Orientation::COUNTERCLOCKWISE); + } + else { + intArc[nArc++] = CircularArc(end, start, c1, r1, Orientation::CLOCKWISE); + } + } + } + + if (checkBp1inA && Angle::isWithinCCW(bp1, ap0, ap1)) { + // end of B within A? + double start = ap0; + double end = bp1; + if (start == end) { + intPt[nPt++] = CircularArcs::createPoint(c1, r1, start); + } + else { + if (resultArcIsCCW) { + intArc[nArc++] = CircularArc(start, end, c1, r1, Orientation::COUNTERCLOCKWISE); + } + else { + intArc[nArc++] = CircularArc(end, start, c1, r1, Orientation::CLOCKWISE); + } + } + } + } else { + // NOT COCIRCULAR + + double dx = c2.x-c1.x; + double dy = c2.y-c1.y; + +#if 1 + // point where a line between the two circle center points intersects + // the radical line + CoordinateXY p{c1.x + a* dx/d, c1.y+a* dy/d}; + + // distance from p to the intersection points + double h = std::sqrt(r1*r1 - a*a); + + CoordinateXY isect0{p.x + h* dy/d, p.y - h* dx/d }; + CoordinateXY isect1{p.x - h* dy/d, p.y + h* dx/d }; + + if (arc1.containsPointOnCircle(isect0) && arc2.containsPointOnCircle(isect0)) { + intPt[nPt++] = isect0; + } + if (!isect1.equals2D(isect0) && arc1.containsPointOnCircle(isect1) && arc2.containsPointOnCircle(isect1)) { + intPt[nPt++] = isect1; + } +#else + // Alternate formulation. + // Instead of calculating the intersection points and determining if they fall on the arc, + // calculate the angles of the intersection points. If they fall on the arc, create intersection points + // at those angles. + + double centerPointAngle = std::atan2(dy, dx); + + double arc1IntPtAngleDeviation = std::acos(a / r1); + + double a11 = Angle::normalize(centerPointAngle - arc1IntPtAngleDeviation); + double a12 = Angle::normalize(centerPointAngle + arc1IntPtAngleDeviation); + + double b = d - a; + double arc2IntPtAngleDeviation = std::acos(b / r2); + + double a21 = Angle::normalize(centerPointAngle + MATH_PI + arc2IntPtAngleDeviation); + double a22 = Angle::normalize(centerPointAngle + MATH_PI - arc2IntPtAngleDeviation); + + if (arc1.containsAngle(a11) && arc2.containsAngle(a21)) { + intPt[nPt++] = CircularArcs::createPoint(arc1.getCenter(), arc1.getRadius(), a11); + } + if (arc1.containsAngle(a12) && arc2.containsAngle(a22)) { + intPt[nPt++] = CircularArcs::createPoint(arc1.getCenter(), arc1.getRadius(), a12); + if (nPt == 2 && intPt[0].equals(intPt[1])) { + nPt = 1; + } + } +#endif + } + + if (nArc) { + result = COCIRCULAR_INTERSECTION; + } + else { + switch (nPt) { + case 2: + result = TWO_POINT_INTERSECTION; + break; + case 1: + result = ONE_POINT_INTERSECTION; + break; + case 0: + result = NO_INTERSECTION; + break; + } + } +} + +} diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index 068dfd38c..99ddb04ff 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -12,16 +12,95 @@ * **********************************************************************/ +#include #include #include #include #include +#include using geos::geom::CoordinateXY; namespace geos { namespace algorithm { +CoordinateXY +CircularArcs::getMidpoint(const CoordinateXY& p0, const CoordinateXY& p2, const CoordinateXY& center, double radius, bool isCCW) +{ + double start = getAngle(p0, center); + double stop = getAngle(p2, center); + double mid = getMidpointAngle(start, stop, isCCW); + return createPoint(center, radius, mid); +} + +double +CircularArcs::getAngle(const CoordinateXY& p, const CoordinateXY& center) +{ + return std::atan2(p.y - center.y, p.x - center.x); +} + +double +CircularArcs::getMidpointAngle(double theta0, double theta2, bool isCCW) +{ + if (!isCCW) { + return getMidpointAngle(theta2, theta0, true); + } + + double mid = (theta0 + theta2) / 2; + if (!Angle::isWithinCCW(mid, theta0, theta2)) { + mid += MATH_PI; + } + + return mid; +} + +CoordinateXY +CircularArcs::createPoint(const CoordinateXY& center, double radius, double theta) +{ + return { center.x + radius* std::cos(theta), center.y + radius* std::sin(theta) }; +} + +template +CoordinateXY getCenterImpl(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) +{ + // Circumcenter formulas from Graphics Gems III + T p0x{p0.x}; + T p0y{p0.y}; + T p1x{p1.x}; + T p1y{p1.y}; + T p2x{p2.x}; + T p2y{p2.y}; + + T ax = p1x - p2x; + T ay = p1y - p2y; + T bx = p2x - p0x; + T by = p2y - p0y; + T cx = p0x - p1x; + T cy = p0y - p1y; + + T d1 = -(bx*cx + by*cy); + T d2 = -(cx*ax + cy*ay); + T d3 = -(ax*bx + ay*by); + + T e1 = d2*d3; + T e2 = d3*d1; + T e3 = d1*d2; + T e = e1 + e2 + e3; + + T G3x = p0.x + p1.x + p2.x; + T G3y = p0.y + p1.y + p2.y; + T Hx = (e1*p0.x + e2*p1.x + e3*p2.x) / e; + T Hy = (e1*p0.y + e2*p1.y + e3*p2.y) / e; + + T rx = 0.5*(G3x - Hx); + T ry = 0.5*(G3y - Hy); + + if constexpr (std::is_same_v) { + return {rx.doubleValue(), ry.doubleValue()}; + } else { + return {rx, ry}; + } +} CoordinateXY CircularArcs::getCenter(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) @@ -31,26 +110,7 @@ CircularArcs::getCenter(const CoordinateXY& p0, const CoordinateXY& p1, const Co return { 0.5*(p0.x + p1.x), 0.5*(p0.y + p1.y) }; } - // Circumcenter formulas from Graphics Gems III - CoordinateXY a{p1.x - p2.x, p1.y - p2.y}; - CoordinateXY b{p2.x - p0.x, p2.y - p0.y}; - CoordinateXY c{p0.x - p1.x, p0.y - p1.y}; - - double d1 = -(b.x*c.x + b.y*c.y); - double d2 = -(c.x*a.x + c.y*a.y); - double d3 = -(a.x*b.x + a.y*b.y); - - double e1 = d2*d3; - double e2 = d3*d1; - double e3 = d1*d2; - double e = e1 + e2 + e3; - - CoordinateXY G3{p0.x + p1.x + p2.x, p0.y + p1.y + p2.y}; - CoordinateXY H {(e1*p0.x + e2*p1.x + e3*p2.x) / e, (e1*p0.y + e2*p1.y + e3*p2.y) / e}; - - CoordinateXY center = {0.5*(G3.x - H.x), 0.5*(G3.y - H.y)}; - - return center; + return getCenterImpl(p0, p1, p2); } void diff --git a/src/math/DD.cpp b/src/math/DD.cpp index c620c1b62..662f1f1d3 100644 --- a/src/math/DD.cpp +++ b/src/math/DD.cpp @@ -236,6 +236,11 @@ DD operator/(const DD &lhs, double rhs) return rv; } +DD operator- (const DD& x) +{ + return x.negate(); +} + /* public */ DD DD::negate() const { diff --git a/tests/unit/algorithm/AngleTest.cpp b/tests/unit/algorithm/AngleTest.cpp index 4d3767182..f44d3ee42 100644 --- a/tests/unit/algorithm/AngleTest.cpp +++ b/tests/unit/algorithm/AngleTest.cpp @@ -25,7 +25,7 @@ struct test_angle_data { test_angle_data() : TOL(1e-5), - PI(3.14159265358979323846) + PI(geos::MATH_PI) {} }; @@ -196,7 +196,40 @@ void object::test<6> ensure_equals(std::to_string(angrad), rCos, std::cos(angrad)); } +} +template<> +template<> +void object::test<7>() +{ + set_test_name("isWithinCCW"); + + // interval from 0 to pi + { + double from = 0, to = PI; + ensure("pi/2 in [0, pi]", Angle::isWithinCCW(Angle::PI_OVER_2, from, to)); + ensure("0 in [0, pi]", Angle::isWithinCCW(0, from, to)); + ensure("pi in [0, pi]", Angle::isWithinCCW(PI, from, to)); + ensure("-pi/2 not in [0, pi]", !Angle::isWithinCCW(Angle::normalizePositive(-Angle::PI_OVER_2), from, to)); + } + + // interval from pi to 0 + { + double from = PI, to = 0; + ensure("pi/2 not in [pi, 0]", !Angle::isWithinCCW(Angle::PI_OVER_2, from, to)); + ensure("0 in [pi, 0]", Angle::isWithinCCW(0, from, to)); + ensure("pi in [pi, 0]", Angle::isWithinCCW(PI, from, to)); + ensure("-pi/2 in [pi, 0]", Angle::isWithinCCW(Angle::normalizePositive(-Angle::PI_OVER_2), from, to)); + } + + // interval from -pi/2 to pi/2 + { + double from = Angle::normalizePositive(-Angle::PI_OVER_2), to = Angle::PI_OVER_2; + ensure("0 in [-pi/2, pi/2]", Angle::isWithinCCW(0, from, to)); + ensure("pi/2 in [-pi/2, pi/2]", Angle::isWithinCCW(Angle::PI_OVER_2, from, to)); + ensure("-pi/2 in [-pi/2, pi/2]", Angle::isWithinCCW(Angle::normalizePositive(-Angle::PI_OVER_2), from, to)); + ensure("pi not in [-pi/2, pi/2]", !Angle::isWithinCCW(PI, from, to)); + } } diff --git a/tests/unit/algorithm/CircularArcIntersectorTest.cpp b/tests/unit/algorithm/CircularArcIntersectorTest.cpp new file mode 100644 index 000000000..2f9e51bdc --- /dev/null +++ b/tests/unit/algorithm/CircularArcIntersectorTest.cpp @@ -0,0 +1,884 @@ +#include + +#include +#include +#include +#include + +using geos::algorithm::CircularArcIntersector; +using geos::algorithm::Orientation; +using geos::geom::CoordinateXY; +using geos::geom::CircularArc; +using geos::MATH_PI; + +namespace tut { + +struct test_circulararcintersector_data { + + using ArcOrPoint = std::variant; + + static std::string to_string(CircularArcIntersector::intersection_type t) + { + switch (t) { + case geos::algorithm::CircularArcIntersector::NO_INTERSECTION: + return "no intersection"; + case geos::algorithm::CircularArcIntersector::ONE_POINT_INTERSECTION: + return "one-point intersection"; + case geos::algorithm::CircularArcIntersector::TWO_POINT_INTERSECTION: + return "two-point intersection"; + case geos::algorithm::CircularArcIntersector::COCIRCULAR_INTERSECTION: + return "cocircular intersection"; + break; + } + + return ""; + } + + static std::string toWKT(const CoordinateXY& pt) + { + return "POINT (" + pt.toString() + ")"; + } + + static std::string toWKT(const CircularArc& arc) + { + return "CIRCULARSTRING (" + arc.p0.toString() + ", " + arc.p1.toString() + ", " + arc.p2.toString() + ")"; + } + + static std::string toWKT(const geos::geom::LineSegment& seg) + { + return "LINESTRING (" + seg.p0.toString() + ", " + seg.p1.toString() + ")"; + } + + static void checkIntersection(CoordinateXY p0, CoordinateXY p1, CoordinateXY p2, + CoordinateXY q0, CoordinateXY q1, CoordinateXY q2, + CircularArcIntersector::intersection_type result, + ArcOrPoint i0 = CoordinateXY::getNull(), + ArcOrPoint i1 = CoordinateXY::getNull()) + { + CircularArc a0(p0, p1, p2); + CircularArc a1(q0, q1, q2); + + checkIntersection(a0, a1, result, i0, i1); + } + + static void checkIntersection(CoordinateXY p0, CoordinateXY p1, CoordinateXY p2, + CoordinateXY q0, CoordinateXY q1, + CircularArcIntersector::intersection_type result, + CoordinateXY i0 = CoordinateXY::getNull(), + CoordinateXY i1 = CoordinateXY::getNull()) + { + CircularArc a(p0, p1, p2); + geos::geom::LineSegment s(geos::geom::Coordinate{q0}, geos::geom::Coordinate{q1}); + + checkIntersection(a, s, result, i0, i1); + } + + static bool pointWithinTolerance(const CoordinateXY& actual, const CoordinateXY& expected, double tol) + { + if (actual.distance(expected) < tol) { + return true; + } + + return std::abs(actual.x - expected.x) < tol * std::abs(actual.x) && + std::abs(actual.y - expected.y) < tol * std::abs(actual.y); + } + + template + static void checkIntersection(const CircularArc& a0, + const CircularArcOrLineSegment& a1, + CircularArcIntersector::intersection_type result, + ArcOrPoint p0 = CoordinateXY::getNull(), + ArcOrPoint p1 = CoordinateXY::getNull()) + { + CircularArcIntersector cai; + cai.intersects(a0, a1); + + ensure_equals("incorrect intersection type between " + toWKT(a0) + " and " + toWKT(a1), to_string(cai.getResult()), to_string(result)); + + std::vector expectedPoints; + std::vector expectedArcs; + + for (const auto& intersection : { p0, p1 }) { + if (std::holds_alternative(intersection)) { + const CoordinateXY& pt = std::get(intersection); + if (!pt.isNull()) { + expectedPoints.push_back(pt); + } + } + else { + expectedArcs.push_back(std::get(intersection)); + } + } + + std::vector actualPoints; + std::vector actualArcs; + + for (std::uint8_t i = 0; i < cai.getNumPoints(); i++) { + actualPoints.push_back(cai.getPoint(i)); + } + + for (std::uint8_t i = 0; i < cai.getNumArcs(); i++) { + actualArcs.push_back(cai.getArc(i)); + } + + auto compareArcs = [](const CircularArc& a, const CircularArc& b) { + int cmp; + cmp = a.p0.compareTo(b.p0); + if (cmp != 0) { + return cmp == -1; + } + cmp = a.p2.compareTo(b.p2); + if (cmp != 0) { + return cmp == -1; + } + cmp = a.getCenter().compareTo(b.getCenter()); + if (cmp != 0) { + return cmp == -1; + } + return a.getOrientation() < b.getOrientation(); + }; + + std::sort(actualPoints.begin(), actualPoints.end()); + std::sort(actualArcs.begin(), actualArcs.end(), compareArcs); + std::sort(expectedPoints.begin(), expectedPoints.end()); + std::sort(expectedArcs.begin(), expectedArcs.end(), compareArcs); + + bool equal = true; + if (actualPoints.size() != expectedPoints.size()) { + equal = false; + } + if (actualArcs.size() != expectedArcs.size()) { + equal = false; + } + + constexpr double eps = 1e-8; + + if (equal) { + for (std::size_t i = 0; i < actualPoints.size(); i++) { + if (!pointWithinTolerance(actualPoints[i], expectedPoints[i], eps)) { + equal = false; + } + } + for (std::size_t i = 0; i < actualArcs.size(); i++) { + if (actualArcs[i].getOrientation() != expectedArcs[i].getOrientation()) { + equal = false; + } + + if (std::abs(actualArcs[i].getRadius() - expectedArcs[i].getRadius()) > eps) { + equal = false; + } + + if (!pointWithinTolerance(actualArcs[i].getCenter(), expectedArcs[i].getCenter(), eps)) { + equal = false; + } + + if (!pointWithinTolerance(actualArcs[i].p0, expectedArcs[i].p0, eps)) { + equal = false; + } + + if (!pointWithinTolerance(actualArcs[i].p2, expectedArcs[i].p2, eps)) { + equal = false; + } + } + } + + if (equal) { + return; + } + + std::string actual; + for (const auto& pt : actualPoints) { + if (!actual.empty()) { + actual += ", "; + } + actual += toWKT(pt); + } + for (const auto& arc : actualArcs) { + if (!actual.empty()) { + actual += ", "; + } + actual += toWKT(arc); + } + + std::string expected; + for (const auto& pt : expectedPoints) { + if (!expected.empty()) { + expected += ", "; + } + expected += toWKT(pt); + } + for (const auto& arc : expectedArcs) { + if (!expected.empty()) { + expected += ", "; + } + expected += toWKT(arc); + } + + ensure_equals(actual, expected); + } + + const CoordinateXY _NW = { -std::sqrt(2)/2, std::sqrt(2)/2 }; + const CoordinateXY _N = { 0, 1}; + const CoordinateXY _NE = { std::sqrt(2)/2, std::sqrt(2)/2 }; + const CoordinateXY _E = { 1, 0}; + const CoordinateXY _SE = { std::sqrt(2)/2, -std::sqrt(2)/2 }; + const CoordinateXY _S = { 0, -1}; + const CoordinateXY _SW = { -std::sqrt(2)/2, -std::sqrt(2)/2 }; + const CoordinateXY _W = { -1, 0}; +}; + +using group = test_group; +using object = group::object; + +group test_circulararcintersector_group("geos::algorithm::CircularArcIntersector"); + +template<> +template<> +void object::test<1>() +{ + set_test_name("interior/interior intersection (one point)"); + + checkIntersection({0, 0}, {1, std::sqrt(3)}, {2, 2}, + {0, 2}, {1, std::sqrt(3)}, {2, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{1, std::sqrt(3)}); +} + +template<> +template<> +void object::test<2>() +{ + set_test_name("interior/interior intersection (two points)"); + + // result from CGAL 5.4 + checkIntersection({0, 0}, {2, 2}, {4, 0}, + {0, 1}, {2, -1}, {4, 1}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + CoordinateXY{0.0635083268962914893, 0.5}, + CoordinateXY{3.93649167310370851, 0.5}); +} + +template<> +template<> +void object::test<3>() +{ + set_test_name("single endpoint-endpoint intersection"); + + checkIntersection({0, 0}, {1, 1}, {2, 0}, + {2, 0}, {3, -1}, {4, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 0}); +} + +template<> +template<> +void object::test<4>() +{ + set_test_name("single interior-interior intersection at point of tangency"); + + checkIntersection({0, 0}, {1, 1}, {2, 0}, + {0, 2}, {1, 1}, {2, 2}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{1, 1}); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("supporting circles intersect but arcs do not"); + + checkIntersection({0, 0}, {2, 2}, {4, 0}, + {1, 1}, {0, -1}, {-1, 1}, + CircularArcIntersector::NO_INTERSECTION); + +} + +template<> +template<> +void object::test<6>() +{ + set_test_name("one circle contained within other"); + + checkIntersection({0, 0}, {4, 4}, {8, 0}, + {2, 0}, {4, 2}, {6, 0}, + CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<7>() +{ + set_test_name("cocircular with double endpoint intersection"); + + checkIntersection({0, 0}, {1, 1}, {2, 0}, + {0, 0}, {1, -1}, {2, 0}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + CoordinateXY{0, 0}, CoordinateXY{2, 0}); +} + +template<> +template<> +void object::test<8>() +{ + set_test_name("cocircular with single endpoint intersection"); + + checkIntersection({-2, 0}, {0, 2}, {2, 0}, + {0, -2}, {std::sqrt(2), -std::sqrt(2)}, {2, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 0}); +} + +template<> +template<> +void object::test<9>() +{ + set_test_name("cocircular disjoint"); + + checkIntersection(_NW, _N, _NE, + _SW, _S, _SE, + CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<10>() +{ + set_test_name("cocircular with single arc intersection (clockwise)"); + + checkIntersection({-5, 0}, {0, 5}, {5, 0}, // CW + {-4, 3}, {0, 5}, {4, 3}, // CW + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{-4, 3}, {0, 5}, {4, 3}}); // CW +} + +template<> +template<> +void object::test<11>() +{ + set_test_name("cocircular with single arc intersection (counter-clockwise)"); + + checkIntersection({5, 0}, {0, 5}, {-5, 0}, // CCW + {-4, 3}, {0, 5}, {4, 3}, // CW + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{4, 3}, {0, 5}, {-4, 3}}); // CCW +} + +template<> +template<> +void object::test<12>() +{ + set_test_name("cocircular with arc and point intersections"); + + checkIntersection({-5, 0}, {0, 5}, {5, 0}, + {5, 0}, {0, -5}, {0, 5}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{-5, 0}, {-5*std::sqrt(2)/2, 5*std::sqrt(2)/2}, {0, 5}}, + CoordinateXY{5, 0}); +} + +template<> +template<> +void object::test<13>() +{ + set_test_name("cocircular with two arc intersections"); + + checkIntersection({-5, 0}, {0, 5}, {5, 0}, + {3, 4}, {0, -5}, {-3, 4}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{3, 4}, {4.4721359549995796, 2.2360679774997898}, {5, 0}}, + CircularArc{{-5, 0}, {-4.4721359549995796, 2.2360679774997907}, {-3, 4}}); +} + +template<> +template<> +void object::test<20>() +{ + set_test_name("arc - degenerate arc with single interior intersection"); + + checkIntersection({0, 0}, {2, 2}, {4, 0}, // CW arc + {-1, -4}, {1, 0}, {3, 4}, // degenerate arc + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 2}); + + checkIntersection({-1, -4}, {1, 0}, {3, 4}, // degenerate arc + {0, 0}, {2, 2}, {4, 0}, // CW arc + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2, 2}); +} + +template<> +template<> +void object::test<21>() +{ + set_test_name("two degenerate arcs with single interior intersection"); + + checkIntersection({0, 0}, {4, 4}, {10, 10}, + {10, 0}, {1, 9}, {0, 10}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{5, 5}); +} + +template<> +template<> +void object::test<30>() +{ + set_test_name("arc-segment with single interior intersection"); + + checkIntersection({0, 0}, {2, 2}, {4, 0}, + {1, 0}, {3, 4}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {2, 2}); +} + +template<> +template<> +void object::test<31>() +{ + set_test_name("arc-vertical segment with single interior intersection"); + + checkIntersection({-2, 0}, {0, 2}, {2, 0}, + {0, 0}, {0, 4}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {0, 2}); +} + +template<> +template<> +void object::test<32>() +{ + set_test_name("arc-segment with two interior intersections"); + + checkIntersection(_W, _E, _SW, + {-10, 10}, {10, -10}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + _NW, _SE); +} + +template<> +template<> +void object::test<33>() +{ + set_test_name("arc-vertical segment with two interior intersections"); + + checkIntersection(_W, _E, _SW, + {0, -2}, {0, 2}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + _S, _N); +} + +template<> +template<> +void object::test<34>() +{ + set_test_name("arc-segment disjoint with bbox containment"); + + checkIntersection(_W, _N, _E, + {0, 0}, {0.2, 0.2}, + CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<35>() +{ + set_test_name("degenerate arc-segment with interior intersection"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {0, 0}); +} + +template<> +template<> +void object::test<36>() +{ + set_test_name("intersection between a segment and a degenerate arc (radius = Infinity)"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5 + 1e-14}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{0, 0}); +} + +template<> +template<> +void object::test<37>() +{ + set_test_name("intersection between a segment and a nearly-degenerate arc (radius ~= 1e5)"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5 + 1e-4}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{0, 0}); +} + +template<> +template<> +void object::test<38>() +{ + set_test_name("arc-segment tests from ILI validator"); + // https://github.com/claeis/iox-ili/blob/master/jtsext/src/test/java/ch/interlis/iom_j/itf/impl/hrg/ISCILRTest.java + + // test_1a + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {20, 5}, {20, -5}, + CircularArcIntersector::NO_INTERSECTION), + + // test_2a + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {5, 5}, {5, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_2b + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_2c + checkIntersection({0, 5}, {4, 3}, {0, -5}, + {5, 5}, {5, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_2d + checkIntersection({0, 5}, {4, 3}, {0, -5}, + {5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {5, 0}); + + // test_3a + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 5}, {4, -5}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + {4, 3}, {4, -3}); + + // test_3b + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {-4, 5}, {-4, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_3c + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 10}, {4, 5}, + CircularArcIntersector::NO_INTERSECTION); + + + // test_3d + checkIntersection({0, 5}, {3, 4}, {5, 0}, + {4, 5}, {4, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {4, 3}); + + // test_3e + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 5}, {4, 0}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + {4, 3}); +} + +template<> +template<> +void object::test<39>() +{ + set_test_name("arc-arc tests from ILI validator"); + // https://github.com/claeis/iox-ili/blob/master/jtsext/src/test/java/ch/interlis/iom_j/itf/impl/hrg/ISCICRTest.java + + // test_1: circles do not overlap + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {20, 5}, {15, 0}, {20, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_2a: arcs overlap at a point + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {10, 5}, {5, 0}, {10, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{5, 0}); + // test_2b: arcs overlap at a point that is not a definition point of either arc + checkIntersection({0, 5}, {4, 3}, {0, -5}, + {10, 5}, {6, 3}, {10, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{5, 0}); + + // test_3a: circles overlap at two points that are within both arcs + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {8, 5}, {3, 0}, {8, -5}, + CircularArcIntersector::TWO_POINT_INTERSECTION, + CoordinateXY{4, 3}, CoordinateXY{4, -3}); + + // test_3b: circles overlap at two points but neither is on the first arc + checkIntersection({0, 5}, {-5, 0}, {0, -5}, + {8, 5}, {3, 0}, {8, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_3c: circles overlap at two points but neither is on the first or second arc + checkIntersection({0, 5}, {-5, 0}, {0, -5}, + {8, 5}, {13, 0}, {8, -5}, + CircularArcIntersector::NO_INTERSECTION); + + // test_3d: circles overlap at two points but one is not on the first arc + checkIntersection({5, 0}, {3, -4}, {0, -5}, + {8, 5}, {3, 0}, {8, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{4, -3}); + + // test_3e: circles overlap at two points but one is not on the second arc + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {3, 0}, {5, -4}, {8, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{4, -3}); + + // test_4a: cocircular + checkIntersection({0, 5}, {5, 0}, {0, -5}, + {4, 3}, {5, 0}, {4, -3}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{4, 3}, {5, 0}, {4, -3}}); +} + +#if 0 +// failed assertion: Values are not equal: expected `POINT (0 0)` actual `POINT (-5.4568517953157425e-06 5.4568517953157425e-06)` +template<> +template<> +void object::test<40>() +{ + set_test_name("intersection between a segment and a nearly-degenerate arc (radius ~= 2e6)"); + + checkIntersection({-5, -5}, {0, 0}, {5, 5 + 1e-9}, + {-5, 5}, {5, -5}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{0, 0}); +} +#endif + +template<> +template<> +void object::test<41>() +{ + set_test_name("IOX-ILI: testFastGerade"); + + checkIntersection({611770.424, 234251.322}, {611770.171, 234250.059}, {611769.918, 234248.796}, + {611613.84, 233467.819}, + {611610.392, 233468.995}, + CircularArcIntersector::NO_INTERSECTION); +} + +#if 0 +template<> +template<> +void object::test<42>() +{ + set_test_name("IOX-ILI: testCircleCircleEndptTolerance"); + // two nearly-linear arcs touching at a single endpoint + // Potential fix is to use tolerance for checking if computed points are within arc. + + checkIntersection({645175.553, 248745.374}, { 645092.332, 248711.677}, { 645009.11, 248677.98}, + {645009.11, 248677.98}, {644926.69, 248644.616}, { 644844.269, 248611.253}, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{645009.110, 248677.980}); +} +#endif + +template<> +template<> +void object::test<43>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_SameEndPoints_SameDirection"); + // two arcs with same arcPoint and radius. + // startPoints and endPoints are same. lines are in same direction + + checkIntersection( + {100.0, 100.0},{120,150.0},{100.0,200.0}, + {100.0, 100.0},{120,150.0},{100.0,200.0}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{100.0, 100.0}, {120, 150}, {100, 200}}); +} + +template<> +template<> +void object::test<44>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_DifferentArcPointOnSameArcLine_SameDirection"); + // two arcs with different arcPoint (on same arcLine) and same radius length. + // startPoints and endPoints are same. lines are in same direction. + + checkIntersection( + {0.0, 10.0},{4.0,8.0},{0.0,0.0}, + {0.0, 10.0},{4.0,2.0},{0.0,0.0}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{0, 10}, {5, 5}, {0, 0}}); +} + +template<> +template<> +void object::test<45>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_SameArcPointOnSameArcLine_OneArcLineIsLonger"); + // two arcs with same arcPoint (on same arcLine) and same radius length. + // one arc line is longer than the other arc line. + // startPoints is same, endPoints are different. lines are in same direction. + + checkIntersection( + {0.0, 10.0},{4.0,8.0},{0.0,0.0}, + {0.0, 10.0},{4.0,8.0},{4.0,2.0}, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc{{0, 10}, {4, 2}, {0, 5}, 5, Orientation::CLOCKWISE}); +} + +template<> +template<> +void object::test<46>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_SameEndPoints_OtherDirection"); + // two arcs with same arcPoint and radius + // startPoint1 is equal to endPoint2, startPoint2 is equal to endPoint1. + + checkIntersection( + CircularArc({100.0, 100.0}, {80.0, 150.0}, {100.0, 200.0}), + CircularArc({100.0, 200.0}, {80.0, 150.0}, {100.0, 100.0}), + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc({100.0, 100.0}, {80.0, 150.0}, {100.0, 200.0})); +} + +template<> +template<> +void object::test<47>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_DifferentStartPoints_SameDirection_DifferentLength"); + // two arcs. ArcPoint is equal. different angle. + // startPoints are different. endPoints are same. + CircularArc a({70.0, 60.0}, {50.0, 100.0}, {60.0, 130.0}); + CircularArc b({60.0, 70.0}, {50.0, 100.0}, {60.0, 130.0}); + + checkIntersection(a, b, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc({60, 70}, {60, 130}, a.getCenter(), a.getRadius(), a.getOrientation())); +} + +template<> +template<> +void object::test<48>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_DifferentStartEndPoints_OtherDirection_DifferentLength"); + // Two cocircular arcs with opposite orientation. + // ArcPoint is equal. + // startPoints are different. endPoints are different. + + CircularArc a({70.0, 60.0}, {50.0, 100.0}, {70.0, 140.0}); + CircularArc b({60.0, 130.0}, {50.0, 100.0}, {60.0, 70.0}); + + checkIntersection(a, b, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc({60, 70}, {60, 130}, a.getCenter(), a.getRadius(), a.getOrientation())); + +} + +template<> +template<> +void object::test<49>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_DifferentEndPoints_SameDirection_DifferentLength"); + // Two arcs with same orientation. + // ArcPoint is equal. + // startPoints are same, endpoints are different + + CircularArc a({70.0, 60.0}, {50.0, 100.0}, {70.0, 140.0}); + CircularArc b({70.0, 60.0}, {50.0, 100.0}, {60.0, 130.0}); + + checkIntersection(a, b, + CircularArcIntersector::COCIRCULAR_INTERSECTION, b); +} + +template<> +template<> +void object::test<50>() +{ + set_test_name("IOX-ILI: overlayTwoARCS_DifferentEndPoints_OtherDirection_DifferentLength"); + // Two arcs with opposite orientation. + // ArcPoint is equal. + // One endpoint is the same, one is different. + + + CircularArc a({70.0, 60.0}, {50.0, 100.0}, {70.0, 140.0}); + CircularArc b({60.0, 130.0}, {50.0, 100.0}, {70.0, 60.0}); + + checkIntersection(a, b, + CircularArcIntersector::COCIRCULAR_INTERSECTION, + CircularArc({70, 60}, {60, 130}, a.getCenter(), a.getRadius(), a.getOrientation())); +} + +template<> +template<> +void object::test<51>() +{ + set_test_name("IOX-ILI: twoARCS_SameRadiusAndCenter_DontOverlay"); + // two arcs with same center and radius that don't touch each other. + + CircularArc a({70.0, 60.0}, {50.0, 100.0}, {70.0, 140.0}); + CircularArc b({140.0, 70.0}, {150.0, 100.0}, {140.0, 130.0}); + + checkIntersection(a, b, CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<52>() +{ + set_test_name("IOX-ILI: twoARCS_SameRadiusAndCenter_Touch_DontOverlay"); + // Two arcs with same radius and center that touch at the endpoints + + CircularArc a({50.0, 100.0}, {100.0, 150.0}, {150.0, 100.0}); + CircularArc b({150.0, 100.0}, {100.0, 50.0}, {50.0, 100.0}); + + checkIntersection(a, b, CircularArcIntersector::TWO_POINT_INTERSECTION, a.p0, a.p2); +} + +#if 0 +template<> +template<> +void object::test<53>() +{ + set_test_name("IOX-ILI: twoARCS_SameRadiusAndCenter_Touch_DontOverlay_real"); + // arcs touch at endpoints + // Potential fix is to use tolerance for checking if computed points are within arc. + + CircularArc a({2654828.912, 1223354.671}, {2654829.982, 1223353.601}, {2654831.052, 1223354.671}); + CircularArc b({2654831.052, 1223354.671}, {2654829.982, 1223355.741}, {2654828.912, 1223354.671}); + + checkIntersection(a, b, CircularArcIntersector::TWO_POINT_INTERSECTION, a.p0, a.p2); +} +#endif + +template<> +template<> +void object::test<54>() +{ + set_test_name("IOX-ILI: twoARCS_intersect0"); + // https://github.com/claeis/ilivalidator/issues/186 + + CircularArc a({2658317.225, 1250832.586}, {2658262.543, 1250774.465}, {2658210.528, 1250713.944}); + CircularArc b({2658211.456, 1250715.072}, {2658161.386, 1250651.279}, {2658114.283, 1250585.266}); + + // An intersection is visually apparent in QGIS, but CGAL 5.6 reports no intersections... + checkIntersection(a, b, CircularArcIntersector::NO_INTERSECTION); +} + +template<> +template<> +void object::test<55>() +{ + set_test_name("IOX-ILI: twoARCS_issue308"); + // https://github.com/claeis/ili2db/issues/308 + + CircularArc a({2653134.354, 1227788.188}, {2653137.455, 1227797.289}, {2653140.555, 1227806.391}); + CircularArc b({2653135.557, 1227789.0}, {2653134.819, 1227788.796}, {2653134.354, 1227788.188}); + + // expected result calculated with CGAL 5.6 + checkIntersection(a, b, + CircularArcIntersector::ONE_POINT_INTERSECTION, + CoordinateXY{2653134.35399999982, 1227788.18800000008}); +} + +} \ No newline at end of file diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp index e40aa3435..96082078b 100644 --- a/tests/unit/algorithm/CircularArcsTest.cpp +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -2,10 +2,13 @@ #include #include +#include +using geos::geom::CircularArc; using geos::geom::CoordinateXY; using geos::algorithm::CircularArcs; using geos::geom::Envelope; +using geos::MATH_PI; namespace tut { @@ -35,6 +38,25 @@ struct test_circulararcs_data { ensure_equals("p2-p1-p0 ymax", e.getMaxY(), ymax, eps); } } + + static std::string toWKT(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) + { + std::stringstream ss; + ss << "CIRCULARSTRING (" << p0 << ", " << p1 << ", " << p2 << ")"; + return ss.str(); + } + + void checkArc(std::string message, + const CoordinateXY& center, double radius, bool ccw, double from, double to, + const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) const + { + CircularArc arc(from, to, center, radius, ccw); + + if (arc.p0.distance(p0) > eps || arc.p1.distance(p1) > eps || arc.p2.distance(p2) > eps) { + ensure_equals(message, toWKT(arc.p0, arc.p1, arc.p2), toWKT(p0, p1, p2)); + } + } + }; using group = test_group; @@ -188,11 +210,11 @@ void object::test<11>() -1, -1, 2, 2); } -// collinear template<> template<> void object::test<12>() { + set_test_name("envelope: arc defined by three collinear points"); CoordinateXY p0{1, 2}; CoordinateXY p1{2, 3}; @@ -202,11 +224,12 @@ void object::test<12>() 1, 2, 3, 4); } -// repeated template<> template<> void object::test<13>() { + set_test_name("envelope: arc defined by three repeated points"); + CoordinateXY p0{3, 4}; CoordinateXY p1{3, 4}; CoordinateXY p2{3, 4}; @@ -217,9 +240,7 @@ void object::test<13>() template<> template<> -void object::test<14>() -{ - set_test_name("envelope: GH #1313"); +void object::test<14>() { CoordinateXY p0{2, 0}; CoordinateXY p1{4, 2}; @@ -229,5 +250,60 @@ void object::test<14>() 2, -1.0811388300841898, 5.08113883008419,2.08113883008419); } +template<> +template<> +void object::test<15>() +{ + set_test_name("createArc"); + + constexpr bool CCW = true; + constexpr bool CW = false; + + checkArc("CCW: upper half-circle", {0, 0}, 1, CCW, 0, MATH_PI, {1, 0}, {0, 1}, {-1, 0}); + checkArc("CCW: lower half-circle", {0, 0}, 1, CCW, MATH_PI, 0, {-1, 0}, {0, -1}, {1, 0}); + checkArc("CCW: left half-circle", {0, 0}, 1, CCW, MATH_PI/2, -MATH_PI/2, {0, 1}, {-1, 0}, {0, -1}); + checkArc("CCW: right half-circle", {0, 0}, 1, CCW, -MATH_PI/2, MATH_PI/2, {0, -1}, {1, 0}, {0, 1}); + + checkArc("CW: upper half-circle", {0, 0}, 1, CW, MATH_PI, 0, {-1, 0}, {0, 1}, {1, 0}); + checkArc("CW: lower half-circle", {0, 0}, 1, CW, 0, MATH_PI, {1, 0}, {0, -1}, {-1, 0}); + checkArc("CW: left half-circle", {0, 0}, 1, CW, -MATH_PI/2, MATH_PI/2, {0, -1}, {-1, 0}, {0, 1}); + checkArc("CW: right half-circle", {0, 0}, 1, CW, MATH_PI/2, -MATH_PI/2, {0, 1}, {1, 0}, {0, -1}); +} + +template<> +template<> +void object::test<16>() +{ + set_test_name("splitAtPoint"); + + CircularArc cwArc({-1, 0}, {0, 1}, {1, 0}); + auto [arc1, arc2] = cwArc.splitAtPoint({std::sqrt(2)/2, std::sqrt(2)/2}); + + ensure_equals(arc1.p0, CoordinateXY{-1, 0}); + ensure_equals(arc1.p2, CoordinateXY{std::sqrt(2)/2, std::sqrt(2)/2}); + ensure_equals(arc1.getCenter(), cwArc.getCenter()); + ensure_equals(arc1.getRadius(), cwArc.getRadius()); + + ensure_equals(arc2.p0, CoordinateXY{std::sqrt(2)/2, std::sqrt(2)/2}); + ensure_equals(arc2.p2, CoordinateXY{1, 0}); + ensure_equals(arc2.getCenter(), cwArc.getCenter()); + ensure_equals(arc2.getRadius(), cwArc.getRadius()); + + ensure_equals(cwArc.getLength(), arc1.getLength() + arc2.getLength()); +} + +template<> +template<> +void object::test<17>() { + set_test_name("getSagitta"); + + CircularArc halfCircle({-1, 0}, {0, 1}, {1, 0}); + ensure_equals(halfCircle.getSagitta(), 1); + + CircularArc quarterCircle({0, 1}, {std::sqrt(2)/2, std::sqrt(2)/2}, {1, 0}); + ensure_equals(quarterCircle.getSagitta(), + CoordinateXY{std::sqrt(2)/2, std::sqrt(2)/2}.distance(CoordinateXY{0.5, 0.5})); +} + } diff --git a/tests/unit/geom/CircularArcTest.cpp b/tests/unit/geom/CircularArcTest.cpp index a1f730091..024820bb6 100644 --- a/tests/unit/geom/CircularArcTest.cpp +++ b/tests/unit/geom/CircularArcTest.cpp @@ -14,7 +14,8 @@ struct test_circulararc_data { const double eps = 1e-8; - void checkAngle(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) { + void checkAngle(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) + { CircularArc arc(p0, p1, p2); ensure_equals(p0.toString() + " / " + p1.toString() + " / " + p2.toString(), arc.getAngle(), expected, eps); @@ -22,7 +23,8 @@ struct test_circulararc_data { ensure_equals(p2.toString() + " / " + p1.toString() + " / " + p0.toString(), rev.getAngle(), expected, eps); } - void checkLength(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) { + void checkLength(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, double expected) + { CircularArc arc(p0, p1, p2); ensure_equals(p0.toString() + " / " + p1.toString() + " / " + p2.toString(), arc.getLength(), expected, eps); @@ -36,11 +38,12 @@ using object = group::object; group test_circulararc_group("geos::geom::CircularArc"); -// test angle() on unit circle template<> template<> void object::test<1>() { + set_test_name("CircularArc::getAngle() on a unit circle"); + auto x = std::sqrt(2.0)/2; // full circle @@ -65,27 +68,57 @@ void object::test<1>() checkAngle({x, -x}, {-1, 0}, {x, x}, 1.5*MATH_PI); // mouth right } -// test length() template<> template<> void object::test<2>() { + set_test_name("CircularArc::getLength()"); + checkLength({1.6, 0.4}, {1.6, 0.5}, {1.7, 1}, 0.6122445326877711); } - -// test getArea() template<> template<> void object::test<3>() { + set_test_name("CircularArc::getArea()"); + ensure_equals("half circle, R=2", CircularArc({-2, 0}, {0, 2}, {2, 0}).getArea(), MATH_PI*2); ensure_equals("full circle, R=3", CircularArc({-3, 0}, {3, 0}, {-3, 0}).getArea(), MATH_PI*3*3); - ensure_equals("3/4, mouth up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, -2}, {std::sqrt(2), std::sqrt(2)}).getArea(), MATH_PI*4 - 2*(MATH_PI/2-1), 1e-8); + ensure_equals("3/4, mouth up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, -2}, {std::sqrt(2), std::sqrt(2)}).getArea(), + MATH_PI*4 - 2*(MATH_PI/2-1), 1e-8); - ensure_equals("1/4, pointing up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, 2}, {std::sqrt(2), std::sqrt(2)}).getArea(), 2*(MATH_PI/2-1), 1e-8); + ensure_equals("1/4, pointing up, R=2", CircularArc({-std::sqrt(2), std::sqrt(2)}, {0, 2}, {std::sqrt(2), std::sqrt(2)}).getArea(), + 2*(MATH_PI/2-1), 1e-8); +} + +template<> +template<> +void object::test<4>() +{ + set_test_name("CircularArc::isLinear()"); + + ensure_equals("not linear", CircularArc({-1, 0}, {0, 1}, {1, 0}).isLinear(), false); + ensure_equals("linear", CircularArc({0, 0}, {1, 1}, {2, 2}).isLinear(), true); +} + +template<> +template<> +void object::test<5>() +{ + set_test_name("CircularArc::containsPointOnCircle"); + + // complete circle + CircularArc({5, 0}, {-5, 0}, {5, 0}).containsPointOnCircle({5, 0}); + CircularArc({5, 0}, {-5, 0}, {5, 0}).containsPointOnCircle({4, 3}); + + // lower semi-circle + CircularArc({-5, 0}, {0, -5}, {5, 0}).containsPointOnCircle({5, 0}); + + // upper semi-circle + CircularArc({-5, 0}, {0, 5}, {5, 0}).containsPointOnCircle({5, 0}); } } diff --git a/tests/unit/utility.h b/tests/unit/utility.h index a4ddbe6a8..5d3993d10 100644 --- a/tests/unit/utility.h +++ b/tests/unit/utility.h @@ -113,16 +113,16 @@ instanceOf(InstanceType const* instance) } inline void -ensure_equals_xy(geos::geom::Coordinate const& actual, - geos::geom::Coordinate const& expected) +ensure_equals_xy(geos::geom::CoordinateXY const& actual, + geos::geom::CoordinateXY const& expected) { ensure_equals("Coordinate X", actual.x, expected.x ); ensure_equals("Coordinate Y", actual.y, expected.y ); } inline void -ensure_equals_xy(geos::geom::Coordinate const& actual, - geos::geom::Coordinate const& expected, +ensure_equals_xy(geos::geom::CoordinateXY const& actual, + geos::geom::CoordinateXY const& expected, double tol) { ensure_equals("Coordinate X", actual.x, expected.x, tol ); ----------------------------------------------------------------------- Summary of changes: include/geos/algorithm/Angle.h | 9 + include/geos/algorithm/CircularArcIntersector.h | 97 +++ include/geos/algorithm/CircularArcs.h | 12 + include/geos/geom/CircularArc.h | 78 +- include/geos/math/DD.h | 1 + .../geos/triangulate/quadedge/TrianglePredicate.h | 2 +- src/algorithm/Angle.cpp | 9 + src/algorithm/CircularArcIntersector.cpp | 328 ++++++++ src/algorithm/CircularArcs.cpp | 100 ++- src/math/DD.cpp | 5 + tests/unit/algorithm/AngleTest.cpp | 35 +- .../unit/algorithm/CircularArcIntersectorTest.cpp | 884 +++++++++++++++++++++ tests/unit/algorithm/CircularArcsTest.cpp | 86 +- tests/unit/geom/CircularArcTest.cpp | 49 +- tests/unit/utility.h | 8 +- 15 files changed, 1648 insertions(+), 55 deletions(-) create mode 100644 include/geos/algorithm/CircularArcIntersector.h create mode 100644 src/algorithm/CircularArcIntersector.cpp create mode 100644 tests/unit/algorithm/CircularArcIntersectorTest.cpp hooks/post-receive -- GEOS