From git at osgeo.org Mon Jun 1 09:11:58 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 1 Jun 2026 09:11:58 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 21d41a72a198b2a470b73dc83c6adfbcceffcbeb Message-ID: <20260601161158.825141B61B5@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 21d41a72a198b2a470b73dc83c6adfbcceffcbeb (commit) from 5ed4cd8dce266c07f962f42fa7b8fc57288067bb (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 21d41a72a198b2a470b73dc83c6adfbcceffcbeb Author: Daniel Baston Date: Mon Jun 1 12:11:34 2026 -0400 RectangleIntersects: guard against curved inputs (#1442) diff --git a/src/operation/predicate/RectangleIntersects.cpp b/src/operation/predicate/RectangleIntersects.cpp index c31fe8ce9..04fd166a8 100644 --- a/src/operation/predicate/RectangleIntersects.cpp +++ b/src/operation/predicate/RectangleIntersects.cpp @@ -31,9 +31,11 @@ #include #include +#include #include + //using namespace geos::geom::util; namespace geos { @@ -282,6 +284,8 @@ public: bool RectangleIntersects::intersects(const geom::Geometry& geom) { + util::ensureNoCurvedComponents(geom); + if(!rectEnv.intersects(geom.getEnvelopeInternal())) { return false; } diff --git a/tests/unit/capi/GEOSIntersectsTest.cpp b/tests/unit/capi/GEOSIntersectsTest.cpp index fe11fd70e..1beea562a 100644 --- a/tests/unit/capi/GEOSIntersectsTest.cpp +++ b/tests/unit/capi/GEOSIntersectsTest.cpp @@ -256,5 +256,18 @@ void object::test<13>() ensure_equals(GEOSIntersects(geom2_, geom1_), 0); } +template<> +template<> +void object::test<14>() +{ + set_test_name("curved geometries not supported by RectangleIntersects"); + + geom1_ = fromWKT("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"); + geom2_ = fromWKT("CIRCULARSTRING (5 0, 10 5, 15 0)"); + + ensure_equals(GEOSIntersects(geom1_, geom2_), 2); + ensure_equals(GEOSIntersects(geom2_, geom1_), 2); +} + } // namespace tut ----------------------------------------------------------------------- Summary of changes: src/operation/predicate/RectangleIntersects.cpp | 4 ++++ tests/unit/capi/GEOSIntersectsTest.cpp | 13 +++++++++++++ 2 files changed, 17 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 2 08:52:49 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 2 Jun 2026 08:52:49 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 7d945a82b70a10adbba35bbd71bfcbb5f4faa27b Message-ID: <20260602155249.8FAE91BFD42@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 7d945a82b70a10adbba35bbd71bfcbb5f4faa27b (commit) from 21d41a72a198b2a470b73dc83c6adfbcceffcbeb (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 7d945a82b70a10adbba35bbd71bfcbb5f4faa27b Author: Paul Ramsey Date: Tue Jun 2 08:51:57 2026 -0700 Remove ASAN from MacOS builds due to hanging We get lots of ASAN testing in Linux diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fddde083..568c4d4bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -427,11 +427,11 @@ jobs: runs_on: macos-14 - xcode: "16.4" cxxstd: 23 - build_type: ASAN + build_type: Debug runs_on: macos-15 - xcode: "26.0" cxxstd: 23 - build_type: ASAN + build_type: Release runs_on: macos-26 runs-on: ${{ matrix.runs_on }} ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 2 09:34:40 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 2 Jun 2026 09:34:40 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. e704204f34c95c24bfc3a8493c5cda2cbe3fe1c2 Message-ID: <20260602163440.73D771BFCF2@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 e704204f34c95c24bfc3a8493c5cda2cbe3fe1c2 (commit) via 8dc21fb58a1bb744bc7ab21bc6bb7e9f0f5eab11 (commit) from 7d945a82b70a10adbba35bbd71bfcbb5f4faa27b (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 e704204f34c95c24bfc3a8493c5cda2cbe3fe1c2 Author: Paul Ramsey Date: Tue Jun 2 09:34:00 2026 -0700 Remove g++15 from CI, not part of the distro repo yet diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6583f14c0..232969889 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,15 +96,6 @@ jobs: cmake: 3.31.* os: ubuntu-24.04 - - cxx_compiler: g++-15 - c_compiler: gcc-15 - build_type: Debug - cxxstd: 20 - arch: 64 - packages: 'g++-15-multilib gcc-15-multilib' - cmake: 4.2.* - os: ubuntu-24.04 - # clang 10 and lower are not supported # in ubuntu 22.04 and higher commit 8dc21fb58a1bb744bc7ab21bc6bb7e9f0f5eab11 Author: Paul Ramsey Date: Tue Jun 2 09:02:43 2026 -0700 bump versions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 568c4d4bd..6583f14c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,6 +96,15 @@ jobs: cmake: 3.31.* os: ubuntu-24.04 + - cxx_compiler: g++-15 + c_compiler: gcc-15 + build_type: Debug + cxxstd: 20 + arch: 64 + packages: 'g++-15-multilib gcc-15-multilib' + cmake: 4.2.* + os: ubuntu-24.04 + # clang 10 and lower are not supported # in ubuntu 22.04 and higher @@ -174,6 +183,15 @@ jobs: cmake: 3.30.* os: ubuntu-24.04 + - cxx_compiler: clang++-20 + c_compiler: clang-20 + build_type: Debug + cxxstd: 20 + arch: 64 + packages: 'clang-20' + cmake: 4.2.* + os: ubuntu-24.04 + runs-on: ${{ matrix.ci.os }} steps: ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 2 15:31:18 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 2 Jun 2026 15:31:18 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch dependabot/github_actions/all-actions-6c4df93294 deleted. 446752af7b7a34f4c484b7bf7f991ff8913da14e Message-ID: <20260602223118.CB5DD1C1643@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, dependabot/github_actions/all-actions-6c4df93294 has been deleted was 446752af7b7a34f4c484b7bf7f991ff8913da14e - Log ----------------------------------------------------------------- 446752af7b7a34f4c484b7bf7f991ff8913da14e Bump the all-actions group with 2 updates ----------------------------------------------------------------------- hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 2 15:31:18 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 2 Jun 2026 15:31:18 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch dependabot/github_actions/all-actions-d30a9e7a05 created. 3665ad79762524ee5f7c99154da58ffdb29b4fc0 Message-ID: <20260602223119.08A7D1C15D6@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, dependabot/github_actions/all-actions-d30a9e7a05 has been created at 3665ad79762524ee5f7c99154da58ffdb29b4fc0 (commit) - Log ----------------------------------------------------------------- commit 3665ad79762524ee5f7c99154da58ffdb29b4fc0 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Jun 2 22:30:44 2026 +0000 Bump the all-actions group across 1 directory with 4 updates Bumps the all-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm), [github/codeql-action](https://github.com/github/codeql-action) and [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `actions/checkout` from 6 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v6.0.2) Updates `vmactions/freebsd-vm` from 1 to 1.4.5 - [Release notes](https://github.com/vmactions/freebsd-vm/releases) - [Commits](https://github.com/vmactions/freebsd-vm/compare/v1...v1.4.5) Updates `github/codeql-action` from 4 to 4.36.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4...v4.36.0) Updates `softprops/action-gh-release` from 2 to 3 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-actions - dependency-name: vmactions/freebsd-vm dependency-version: 1.4.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-actions - dependency-name: github/codeql-action dependency-version: 4.36.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-actions - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions ... Signed-off-by: dependabot[bot] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 232969889..5f7a5e237 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,7 +197,7 @@ jobs: python3 -m pip install --disable-pip-version-check --user cmake==${{ matrix.ci.cmake }} echo "$(python3 -m site --user-base)/bin" >> $GITHUB_PATH - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -281,7 +281,7 @@ jobs: build_type: ['Debug', 'Release'] steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: 'Setup' uses: msys2/setup-msys2 at v2 @@ -337,7 +337,7 @@ jobs: arch: arm64 steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 # ccache not supported for this generator and/or Debug @@ -378,7 +378,7 @@ jobs: runs-on: ${{ matrix.ci.os }} steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -459,7 +459,7 @@ jobs: which clang++ clang++ --version - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -495,7 +495,7 @@ jobs: name: FreeBSD runs-on: ubuntu-latest steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -506,7 +506,7 @@ jobs: restore-keys: freebsd - name: Build and test - uses: vmactions/freebsd-vm at v1 + uses: vmactions/freebsd-vm at v1.4.5 with: release: "15.0" envs: CCACHE_COMPRESS CCACHE_COMPRESSLEVEL CCACHE_MAXSIZE @@ -540,7 +540,7 @@ jobs: sudo -E apt-get autopurge -y needrestart sudo -E apt-get -yq --no-install-suggests --no-install-recommends install make cmake emscripten ccache - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -582,7 +582,7 @@ jobs: sudo -E apt-get -yq --no-install-suggests --no-install-recommends install cppcheck python3 -m pip install --disable-pip-version-check --user codespell - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: 'cppcheck' run: ./tools/cppcheck.sh @@ -605,7 +605,7 @@ jobs: python3 -m pip install --disable-pip-version-check --user cmake==3.15.* echo "$(python3 -m site --user-base)/bin" >> $GITHUB_PATH - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 with: path: geos diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 46eb081ac..39126ac0d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,11 +36,11 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init at v4 + uses: github/codeql-action/init at v4.36.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,6 +63,6 @@ jobs: make -j$(nproc) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze at v4 + uses: github/codeql-action/analyze at v4.36.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 42a93ec20..d9168c715 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -24,7 +24,7 @@ jobs: steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Docker meta id: meta diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2edbafad7..2920c83ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: uname -a sudo -E apt-get -yq --no-install-suggests --no-install-recommends install make doxygen - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Get tag id: tag @@ -67,7 +67,7 @@ jobs: - name: Create Release if: ${{ steps.tag.outputs.tag == steps.version.outputs.geosversion }} id: create_release - uses: softprops/action-gh-release at v2 + uses: softprops/action-gh-release at v3 with: body_path: ${{ steps.notes.outputs.geosnotes }} name: Release ${{ steps.version.outputs.geosversion }} diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index c059e4210..cdd46c346 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -27,7 +27,7 @@ jobs: sudo -E apt-get -yq --no-install-suggests --no-install-recommends install make doxygen - name: 'Check Out ?' - uses: actions/checkout at v6 + uses: actions/checkout at v6.0.2 - name: 'Hugo Build ?' # ...or replace 'master' with a full version tag, such as: v0.64.1 ----------------------------------------------------------------------- hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 9 10:52:16 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 9 Jun 2026 10:52:16 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 7b2e578f4444678aa081af6c484266ed2f6392bc Message-ID: <20260609175217.BE2B31B3BDE@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 7b2e578f4444678aa081af6c484266ed2f6392bc (commit) from e704204f34c95c24bfc3a8493c5cda2cbe3fe1c2 (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 7b2e578f4444678aa081af6c484266ed2f6392bc Author: Paul Ramsey Date: Tue Jun 9 17:51:39 2026 +0000 Add Arc distance test case from PostGIS diff --git a/tests/unit/operation/distance/DistanceOpTest.cpp b/tests/unit/operation/distance/DistanceOpTest.cpp index 7c2686773..082c91916 100644 --- a/tests/unit/operation/distance/DistanceOpTest.cpp +++ b/tests/unit/operation/distance/DistanceOpTest.cpp @@ -1003,6 +1003,18 @@ void object::test<51>() checkDistance(*g1, *g2, 0.5, 1e-6); } +template<> +template<> +void object::test<52>() +{ + set_test_name("MultiSurface COMPOUNDCURVE and Polygon; PostGIS ticket #5989 reopened (Finnish coordinates)"); + + auto g1 = wktreader.read("MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(25493681.3085 6678739.6419,25493637.8256 6678776.2541,25493599.9716 6678818.6604),(25493599.9716 6678818.6604,25493583.8816 6678839.494,25493590.9591 6678844.9594,25493566.9698 6678851.9051,25493526.5793 6678861.0985,25493487.3853 6678868.2546,25493447.392 6678871.7846,25493429.5033 6678876.6846,25493435.0147 6678857.4594,25493454.9254 6678821.6192,25493452.4867 6678816.1292,25493402.3226 6678828.2153,25493354.7526 6678837.8291,25493349.0217 6678838.9873,25493310.914 6678843.6926,25493306.3784 6678859.9295,25493294.1011 6678858.7864,25493169.9028 6678847.2212,25493127.549 6678843.2773,25493057.8888 6678834.6763,25493017.4547 6678826.7261,25492928.2395 6678821.5524,25492909.1067 6678795.0681,25492946.9544 6678794.8193,25492967.1292 6678794.6866,25493010.8878 6678795.9163,25493111.5564 6678804.3109,25493113.4069 6678799.5694,25493134.2375 6678806.0729),CIRCULARSTRING(25493134.2375 6678806.0729,2549322 3.038 6678812.0533,25493311.4919 6678802.1948,25493317.3589 6678800.9679,25493323.2107 6678799.6707),(25493323.2107 6678799.6707,25493362.6615 6678792.3924,25493364.6948 6678785.173,25493375.0512 6678781.6008,25493404.9736 6678771.2798),CIRCULARSTRING(25493404.9736 6678771.2798,25493486.8049 6678737.6728,25493566.8862 6678700.0859),(25493566.8862 6678700.0859,25493574.903 6678714.0761,25493600.2214 6678702.8803,25493654.1114 6678670.2493,25493669.7219 6678659.2147,25493677.0288 6678653.353,25493684.6468 6678648.4119,25493724.0447 6678623.7638,25493800.1925 6678583.5847,25493805.6261 6678586.1292,25493811.9258 6678582.3016,25493812.8413 6678583.0522,25493846.581 6678558.6541,25493884.2653 6678531.6272,25493900.9168 6678580.1258,25493830.7852 6678630.9518,25493789.0848 6678661.2736,25493746.1612 6678692.485,25493686.5413 6678735.8369,25493681.3085 6678739.6419))))"); + auto g2 = wktreader.read("POLYGON ((25492929.752797972 6678919.124091367,25493008.675235115 6678876.783133076,25493098.02917443 6678854.33589699,25493139.669701554 6678977.913508233,25493122.946292613 6679011.427315322,25493050.53388224 6679068.791126598,25493140.33593199 6679140.958135231,25492931.688606273 6679401.2946243705,25492799.289171 6679295.949040241,25492651.5177725 6679115.144800108,25492929.752797972 6678919.124091367))"); + + checkDistance(*g1, *g2, 14.5, 1.0); +} + // TODO: finish the tests by adding: // LINESTRING - *all* ----------------------------------------------------------------------- Summary of changes: tests/unit/operation/distance/DistanceOpTest.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Wed Jun 10 00:25:45 2026 From: git at osgeo.org (git at osgeo.org) Date: Wed, 10 Jun 2026 00:25:45 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch dependabot/github_actions/all-actions-d30a9e7a05 deleted. 3665ad79762524ee5f7c99154da58ffdb29b4fc0 Message-ID: <20260610072545.530B916DF1E@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, dependabot/github_actions/all-actions-d30a9e7a05 has been deleted was 3665ad79762524ee5f7c99154da58ffdb29b4fc0 - Log ----------------------------------------------------------------- 3665ad79762524ee5f7c99154da58ffdb29b4fc0 Bump the all-actions group across 1 directory with 4 updates ----------------------------------------------------------------------- hooks/post-receive -- GEOS From git at osgeo.org Wed Jun 10 00:25:45 2026 From: git at osgeo.org (git at osgeo.org) Date: Wed, 10 Jun 2026 00:25:45 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 9d1676ca58c6878445b679d6b3228b96fab7d4e4 Message-ID: <20260610072545.B592016DA2D@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 9d1676ca58c6878445b679d6b3228b96fab7d4e4 (commit) from 7b2e578f4444678aa081af6c484266ed2f6392bc (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 9d1676ca58c6878445b679d6b3228b96fab7d4e4 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Jun 10 09:25:14 2026 +0200 Bump the all-actions group across 1 directory with 4 updates (#1443) Bumps the all-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm), [github/codeql-action](https://github.com/github/codeql-action) and [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `actions/checkout` from 6 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v6.0.2) Updates `vmactions/freebsd-vm` from 1 to 1.4.5 - [Release notes](https://github.com/vmactions/freebsd-vm/releases) - [Commits](https://github.com/vmactions/freebsd-vm/compare/v1...v1.4.5) Updates `github/codeql-action` from 4 to 4.36.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v4...v4.36.0) Updates `softprops/action-gh-release` from 2 to 3 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-actions - dependency-name: vmactions/freebsd-vm dependency-version: 1.4.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-actions - dependency-name: github/codeql-action dependency-version: 4.36.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-actions - dependency-name: softprops/action-gh-release dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 232969889..5f7a5e237 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,7 +197,7 @@ jobs: python3 -m pip install --disable-pip-version-check --user cmake==${{ matrix.ci.cmake }} echo "$(python3 -m site --user-base)/bin" >> $GITHUB_PATH - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -281,7 +281,7 @@ jobs: build_type: ['Debug', 'Release'] steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: 'Setup' uses: msys2/setup-msys2 at v2 @@ -337,7 +337,7 @@ jobs: arch: arm64 steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 # ccache not supported for this generator and/or Debug @@ -378,7 +378,7 @@ jobs: runs-on: ${{ matrix.ci.os }} steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -459,7 +459,7 @@ jobs: which clang++ clang++ --version - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -495,7 +495,7 @@ jobs: name: FreeBSD runs-on: ubuntu-latest steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -506,7 +506,7 @@ jobs: restore-keys: freebsd - name: Build and test - uses: vmactions/freebsd-vm at v1 + uses: vmactions/freebsd-vm at v1.4.5 with: release: "15.0" envs: CCACHE_COMPRESS CCACHE_COMPRESSLEVEL CCACHE_MAXSIZE @@ -540,7 +540,7 @@ jobs: sudo -E apt-get autopurge -y needrestart sudo -E apt-get -yq --no-install-suggests --no-install-recommends install make cmake emscripten ccache - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Retrieve build cache uses: actions/cache/restore at v5 @@ -582,7 +582,7 @@ jobs: sudo -E apt-get -yq --no-install-suggests --no-install-recommends install cppcheck python3 -m pip install --disable-pip-version-check --user codespell - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: 'cppcheck' run: ./tools/cppcheck.sh @@ -605,7 +605,7 @@ jobs: python3 -m pip install --disable-pip-version-check --user cmake==3.15.* echo "$(python3 -m site --user-base)/bin" >> $GITHUB_PATH - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 with: path: geos diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 46eb081ac..39126ac0d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,11 +36,11 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init at v4 + uses: github/codeql-action/init at v4.36.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,6 +63,6 @@ jobs: make -j$(nproc) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze at v4 + uses: github/codeql-action/analyze at v4.36.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 42a93ec20..d9168c715 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -24,7 +24,7 @@ jobs: steps: - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Docker meta id: meta diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2edbafad7..2920c83ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: uname -a sudo -E apt-get -yq --no-install-suggests --no-install-recommends install make doxygen - - uses: actions/checkout at v6 + - uses: actions/checkout at v6.0.2 - name: Get tag id: tag @@ -67,7 +67,7 @@ jobs: - name: Create Release if: ${{ steps.tag.outputs.tag == steps.version.outputs.geosversion }} id: create_release - uses: softprops/action-gh-release at v2 + uses: softprops/action-gh-release at v3 with: body_path: ${{ steps.notes.outputs.geosnotes }} name: Release ${{ steps.version.outputs.geosversion }} diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index c059e4210..cdd46c346 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -27,7 +27,7 @@ jobs: sudo -E apt-get -yq --no-install-suggests --no-install-recommends install make doxygen - name: 'Check Out ?' - uses: actions/checkout at v6 + uses: actions/checkout at v6.0.2 - name: 'Hugo Build ?' # ...or replace 'master' with a full version tag, such as: v0.64.1 ----------------------------------------------------------------------- Summary of changes: .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/codeql.yml | 6 +++--- .github/workflows/container.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/web.yml | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Jun 12 10:49:29 2026 From: git at osgeo.org (git at osgeo.org) Date: Fri, 12 Jun 2026 10:49:29 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. ac5da1cb2ed3e76cd286bb0f715e7fccde89281e Message-ID: <20260612174929.86B501320EE@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 ac5da1cb2ed3e76cd286bb0f715e7fccde89281e (commit) from 9d1676ca58c6878445b679d6b3228b96fab7d4e4 (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 ac5da1cb2ed3e76cd286bb0f715e7fccde89281e Author: Paul Ramsey Date: Fri Jun 12 17:46:41 2026 +0000 Remove warning in MSVC build diff --git a/include/geos/index/intervalrtree/IntervalRTreeNode.h b/include/geos/index/intervalrtree/IntervalRTreeNode.h index d15d54a7e..457190d8c 100644 --- a/include/geos/index/intervalrtree/IntervalRTreeNode.h +++ b/include/geos/index/intervalrtree/IntervalRTreeNode.h @@ -15,6 +15,7 @@ #pragma once +#include #include #include #include @@ -31,7 +32,7 @@ namespace geos { namespace index { namespace intervalrtree { -class IntervalRTreeNode { +class GEOS_DLL IntervalRTreeNode { private: protected: double min; ----------------------------------------------------------------------- Summary of changes: include/geos/index/intervalrtree/IntervalRTreeNode.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 16 11:27:15 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 16 Jun 2026 11:27:15 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 11f1851a82bdf5f9983f03d1b967adfb5da6fb39 Message-ID: <20260616182716.1419E1A1AFD@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 11f1851a82bdf5f9983f03d1b967adfb5da6fb39 (commit) via 47a2a8fba3190aa8604f2e2ef55e649cd67f3f7a (commit) via 420c2a988d54fc07e9ff8a513f7df1fdca7ad741 (commit) via 3da292784f83e0bfce769442d4d7e14cdc964bc6 (commit) via f0ffac4dc49d012f3d80cb28f76e9ff10d79772f (commit) via fac5376f75356ad9cd8b6f08ef0336f1d56e006c (commit) via c06102e28158cd1393a68c767f36087b249e4ee0 (commit) via f14c453ade3f86a98b3dafeaf5cca750593d4e70 (commit) via dc3935ba35f5fd6328bddb3637c7e0eeba307dd8 (commit) via 1aeef7493bc6d0804d094021fc1013a7227791af (commit) via b0506ea14bdc0339fb8ab61eb95ba5ec26762332 (commit) via 43edb6d35a6c66fe82712033bb3abd53f89fce3a (commit) via 095c72709ea5043b4a1433d2adb3611410564cd3 (commit) via fa14b9a717e2231679859d20adae502ebff07492 (commit) from ac5da1cb2ed3e76cd286bb0f715e7fccde89281e (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 11f1851a82bdf5f9983f03d1b967adfb5da6fb39 Author: Daniel Baston Date: Mon Jun 8 08:49:21 2026 -0400 Apply suggestions from @rouault diff --git a/include/geos/operation/overlayng/Edge.h b/include/geos/operation/overlayng/Edge.h index b88a44fe7..3ee964c5d 100644 --- a/include/geos/operation/overlayng/Edge.h +++ b/include/geos/operation/overlayng/Edge.h @@ -64,7 +64,7 @@ private: int bDepthDelta = 0; bool bIsHole = false; std::shared_ptr pts; - bool ptsCurved; + const bool ptsCurved; // Methods diff --git a/src/operation/overlayng/OverlayEdgeRing.cpp b/src/operation/overlayng/OverlayEdgeRing.cpp index 12f4c8e92..98db9f536 100644 --- a/src/operation/overlayng/OverlayEdgeRing.cpp +++ b/src/operation/overlayng/OverlayEdgeRing.cpp @@ -284,7 +284,7 @@ OverlayEdgeRing::isPointInOrOut(const OverlayEdgeRing& otherRing) const { struct PointTester : public geom::CoordinateFilter { public: - explicit PointTester(const OverlayEdgeRing* p_ring) : m_er(p_ring), m_result(false) {} + explicit PointTester(const OverlayEdgeRing* p_ring) : m_er(p_ring) {} void filter_ro(const CoordinateXY* pt) override { Location loc = m_er->locate(*pt); @@ -311,7 +311,7 @@ OverlayEdgeRing::isPointInOrOut(const OverlayEdgeRing& otherRing) const { private: const OverlayEdgeRing* m_er; bool m_done{false}; - bool m_result; + bool m_result{false}; }; PointTester tester(this); commit 47a2a8fba3190aa8604f2e2ef55e649cd67f3f7a Author: Daniel Baston Date: Sun Jun 7 14:42:06 2026 -0400 CircularArcIntersector: exactly preserve endpoints in cocircular arc intersection diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp index 8d3fabf58..5f82d3230 100644 --- a/src/algorithm/CircularArcIntersector.cpp +++ b/src/algorithm/CircularArcIntersector.cpp @@ -427,32 +427,34 @@ CircularArcIntersector::addCocircularIntersection(double startAngle, double endA CoordinateXYZM computedMidPt(CircularArcs::createPoint(center, radius, theta1)); CoordinateXYZM computedEndPt(CircularArcs::createPoint(center, radius, endAngle)); - if (precisionModel) { - precisionModel->makePrecise(computedStartPt); - precisionModel->makePrecise(computedMidPt); - precisionModel->makePrecise(computedEndPt); - } - // Check to see if the endpoints of the intersection match the endpoints of either of // the input arcs. Use angles for the check to avoid missing an endpoint intersection from // inaccuracy in the point construction. - if (startAngle == arc1.theta0()) { + if (startAngle == Angle::normalizePositive(arc1.theta0())) { + computedStartPt = arc1.p0(); setFromEndpoint(computedStartPt, arc1, 0); - } else if (startAngle == arc1.theta2()) { + } else if (startAngle == Angle::normalizePositive(arc1.theta2())) { + computedStartPt = arc1.p2(); setFromEndpoint(computedStartPt, arc1, 2); - } else if (startAngle == arc2.theta0()) { + } else if (startAngle == Angle::normalizePositive(arc2.theta0())) { + computedStartPt = arc2.p0(); setFromEndpoint(computedStartPt, arc2, 0); - } else if (startAngle == arc2.theta2()) { + } else if (startAngle == Angle::normalizePositive(arc2.theta2())) { + computedStartPt = arc2.p2(); setFromEndpoint(computedStartPt, arc2, 2); } - if (endAngle == arc1.theta0()) { + if (endAngle == Angle::normalizePositive(arc1.theta0())) { + computedEndPt = arc1.p0(); setFromEndpoint(computedEndPt, arc1, 0); - } else if (endAngle == arc1.theta2()) { + } else if (endAngle == Angle::normalizePositive(arc1.theta2())) { + computedEndPt = arc1.p2(); setFromEndpoint(computedEndPt, arc1, 2); - } else if (endAngle == arc2.theta0()) { + } else if (endAngle == Angle::normalizePositive(arc2.theta0())) { + computedEndPt = arc2.p0(); setFromEndpoint(computedEndPt, arc2, 0); - } else if (endAngle == arc2.theta2()) { + } else if (endAngle == Angle::normalizePositive(arc2.theta2())) { + computedEndPt = arc2.p2(); setFromEndpoint(computedEndPt, arc2, 2); } @@ -460,6 +462,12 @@ CircularArcIntersector::addCocircularIntersection(double startAngle, double endA interpolateZM(arc1, arc2, computedMidPt); interpolateZM(arc1, arc2, computedEndPt); + if (precisionModel) { + precisionModel->makePrecise(computedStartPt); + precisionModel->makePrecise(computedMidPt); + precisionModel->makePrecise(computedEndPt); + } + auto seq = std::make_unique(3, constructZ, constructM); seq->setAt(computedStartPt, 0); seq->setAt(computedMidPt, 1); diff --git a/tests/unit/algorithm/CircularArcIntersectorTest.cpp b/tests/unit/algorithm/CircularArcIntersectorTest.cpp index e9899213d..5d0123c17 100644 --- a/tests/unit/algorithm/CircularArcIntersectorTest.cpp +++ b/tests/unit/algorithm/CircularArcIntersectorTest.cpp @@ -1733,6 +1733,26 @@ void object::test<86>() } +template<> +template<> +void object::test<87>() +{ + set_test_name("arc intersected with itself yields arc with identical endpoints"); + + CircularArcIntersector cai; + + auto arc = CircularArc::create(XY{0, 0}, XY{2, 0}, XY{2, 1}); + + cai.intersects(arc, arc); + + ensure_equals(cai.getNumArcs(), 1u); + + const CircularArc& arcOut = cai.getArc(0); + + ensure_equals(arc.p0(), arcOut.p0()); + ensure_equals(arc.p2(), arcOut.p2()); +} + // TODO: check Z values of arc result centerpoints // TODO: add tests for seg/seg commit 420c2a988d54fc07e9ff8a513f7df1fdca7ad741 Author: Daniel Baston Date: Mon Jun 8 08:35:41 2026 -0400 NodableArcString: properly handle splits at arc endpoints diff --git a/src/noding/NodableArcString.cpp b/src/noding/NodableArcString.cpp index 4b824b3fa..d05865e79 100644 --- a/src/noding/NodableArcString.cpp +++ b/src/noding/NodableArcString.cpp @@ -21,6 +21,8 @@ using geos::geom::CoordinateXYZM; using geos::geom::CircularArc; +#define DEBUG_NODABLE_ARC_STRING 0 + namespace geos::noding { static double @@ -41,12 +43,22 @@ NodableArcString::NodableArcString(std::vector arcs, const st { } -static -std::vector prepareArcPoints(const CircularArc& arc, std::vector splitPoints) +struct SplitPoints { + + std::vector points{}; + bool splitStart{false}; + bool splitEnd{false}; +}; + +static SplitPoints +prepareArcPoints(const CircularArc& arc, std::vector splitPoints) { const bool isCCW = arc.getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE; const geom::CoordinateXY& center = arc.getCenter(); + bool splitStart = false; + bool splitEnd = false; + // Some potential split points may be skipped, for example, if they would create an arc section that is // too short to have a constructed midpoint. Because the results of this logic could depend on the // direction in which the arc is processed, we reverse clockwise arcs and then reverse the list of @@ -65,7 +77,17 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector double pa0 = geom::Quadrant::pseudoAngle(center, p0); double pa1 = geom::Quadrant::pseudoAngle(center, p1); - return pseudoAngleDiffCCW(paStart, pa0) < pseudoAngleDiffCCW(paStart, pa1); + double diff0 = pseudoAngleDiffCCW(paStart, pa0); + double diff1 = pseudoAngleDiffCCW(paStart, pa1); + + if (diff0 < diff1) { + return true; + } + if (diff0 > diff1) { + return false; + } + + return p0 < p1; }); // Add ending point of input arc @@ -75,20 +97,50 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector splitPoints.push_back(p2); } +#if DEBUG_NODABLE_ARC_STRING + std::cout << std::setprecision(17); + std::cout << std::endl; + std::cout << "Edge from " << arc.p0() << " to " << arc.p2() << " has " << splitPoints.size() << " potential split points:" << std::endl; + std::cout << "Edge is CIRCULARSTRING " << *arc.getCoordinateSequence() << std::endl; + std::cout << "Start point is " << retained.back() << " pa " << paStart << std::endl; + std::cout << "Potential split points:" << std::endl; + for (std::size_t i = 0; i < splitPoints.size(); i++) { + std::cout << " " << splitPoints[i] << " paDiff " << pseudoAngleDiffCCW(paStart, geom::Quadrant::pseudoAngle(center, splitPoints[i])); + if (i > 0) { + std::cout << " " << splitPoints[i].distance(splitPoints[i-1]); + } + std::cout << std::endl; + } +#endif + for (const auto& p2 : splitPoints) { auto& p0 = retained.back(); +#if DEBUG_NODABLE_ARC_STRING + std::cout << "p0 = " << p0 << std::endl; +#endif + if (p2.equals2D(p0)) { +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Split point " << p2 << " equal to start point " << p0 << std::endl; +#endif if (std::isnan(p0.z) && !std::isnan(p2.z)) { p0.z = p2.z; } if (std::isnan(p0.m) && !std::isnan(p2.m)) { p0.m = p2.m; } + + if (retained.size() == 1) { + splitStart = true; + } continue; } if (!arc.containsPointOnCircle(p2)) { +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Skipping split point " << p2 << " because it is outside of the arc" << std::endl; +#endif continue; } @@ -98,6 +150,22 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector const geom::CoordinateXY p1 = algorithm::CircularArcs::getMidpoint(p0, p2, center, arc.getRadius(), true); if (p1.equals2D(p0) || p1.equals2D(p2)) { +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Skipping split point " << p2 << " because the calculated arc midpoint " << p1 << " equals one of the endpoints" << std::endl; +#endif + if (retained.size() == 1) { + splitStart = true; + } + continue; + } + + if (algorithm::Orientation::index(p1, p0, p2) == algorithm::Orientation::COLLINEAR) { +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Skipping split point " << p2 << " because the calculated arc midpoint " << p1 << " is collinear with the endpoints" << std::endl; +#endif + if (retained.size() == 1) { + splitStart = true; + } continue; } @@ -106,10 +174,19 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector const double t1 = algorithm::Angle::normalizePositive(algorithm::CircularArcs::getAngle(p1, center)); const double t2 = algorithm::Angle::normalizePositive(isCCW ? arc.theta2() : arc.theta0()); - if (!algorithm::Angle::isWithinCCW(t1, t0, t2)) { + if (!algorithm::Angle::isWithinCCW(t1, t0, t2)) { // != isCCW) { +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Skipping split point " << p2 << " because the calculated arc midpoint " << p1 << " does not fall within the arc from " << p0 << " to " << p2 << std::endl; +#endif + if (retained.size() == 1) { + splitStart = true; + } continue; } +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Keeping split point " << p2 << std::endl; +#endif retained.push_back(p2); } @@ -120,6 +197,7 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector CoordinateXYZM& back = retained.back(); if (!back.equals2D(p2)) { + splitEnd = true; back.x = p2.x; back.y = p2.y; } @@ -133,15 +211,75 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector if (!isCCW) { std::reverse(retained.begin(), retained.end()); + std::swap(splitStart, splitEnd); } - return retained; +#if DEBUG_NODABLE_ARC_STRING + std::cout << "Retained split points:" << std::endl; + for (std::size_t i = 0; i < retained.size(); i++) { + std::cout << " " << retained[i] << " paDiff " << pseudoAngleDiffCCW(paStart, geom::Quadrant::pseudoAngle(center, retained[i])); + if (i > 0) { + std::cout << " " << retained[i].distance(retained[i-1]); + } + std::cout << std::endl; + } +#endif + + return SplitPoints{std::move(retained), splitStart, splitEnd}; } +class ArcBuilder { + +public: + + ArcBuilder(std::vector>& splitArcs, const void* data, bool constructZ, bool constructM) : + m_data(data), + m_splitArcs(splitArcs), + m_constructZ(constructZ), + m_constructM(constructM) {} + + void addArc(const CoordinateXYZM& p0, const CoordinateXYZM& p1, const CoordinateXYZM& p2, const geom::CoordinateXY& center, double radius, int orientation) { + if (!m_coords) { + m_coords = std::make_unique(0, m_constructZ, m_constructM); + } + + m_coords->add(p0, false); + m_coords->add(p1, false); + m_coords->add(p2, false); + + m_arcs.emplace_back(*m_coords, m_coords->getSize() - 3, center, radius, orientation); + } + + void addArc(const geom::CoordinateSequence& seq, std::size_t pos, const geom::CoordinateXY& center, double radius, int orientation) { + if (!m_coords) { + m_coords = std::make_unique(0, m_constructZ, m_constructM); + } + + m_coords->add(seq, pos, pos + 2, false); + m_arcs.emplace_back(*m_coords, m_coords->getSize() - 3, center, radius, orientation); + } + + void finish() { + if (m_arcs.empty()) { + return; + } + + m_splitArcs.push_back(std::make_unique(std::move(m_arcs), std::move(m_coords), m_constructZ, m_constructM, m_data)); + m_arcs = std::vector(); + } + +private: + const void* m_data; + std::vector>& m_splitArcs; + std::vector m_arcs; + std::unique_ptr m_coords; + const bool m_constructZ; + const bool m_constructM; +}; + void NodableArcString::getNoded(std::vector>& splitArcs) { - auto dstSeq = std::make_unique(0, m_constructZ, m_constructM); - std::vector arcs; + ArcBuilder builder(splitArcs, getData(), m_constructZ, m_constructM); for (size_t arcIndex = 0; arcIndex < m_arcs.size(); arcIndex++) { const CircularArc& toSplit = m_arcs[arcIndex]; @@ -149,36 +287,28 @@ NodableArcString::getNoded(std::vector>& splitArcs) { const double radius = toSplit.getRadius(); const int orientation = toSplit.getOrientation(); - bool arcIsSplit = true; - bool createArcString = true; - const bool preserveControlPoint = true; - std::vector arcPoints; - const auto it = m_adds.find(arcIndex); - if (it == m_adds.end()) { - arcIsSplit = false; - createArcString = false; - } else { - arcPoints = prepareArcPoints(toSplit, it->second); + SplitPoints split; - if (arcPoints.size() == 2) { - // All added nodes collapsed - // Still need to know if arc was split. - arcIsSplit = false; - } + const bool preserveControlPoint = true; + + const auto it = m_adds.find(arcIndex); + if (it != m_adds.end()) { + split = prepareArcPoints(toSplit, it->second); } - if (preserveControlPoint && !arcIsSplit) { + if (split.splitStart) { + builder.finish(); + } + + if (preserveControlPoint && split.points.size() <= 2) { // No nodes added, just copy the coordinates into the sequence. const geom::CoordinateSequence* srcSeq = m_arcs[arcIndex].getCoordinateSequence(); std::size_t srcPos = m_arcs[arcIndex].getCoordinatePosition(); - dstSeq->add(*srcSeq, srcPos, srcPos + 2, false); - std::size_t dstPos = dstSeq->getSize() - 3; - arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation); - if (createArcString) { - splitArcs.push_back(std::make_unique(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, getData())); - dstSeq = std::make_unique(0, m_constructZ, m_constructM); - arcs.clear(); + builder.addArc(*srcSeq, srcPos, center, radius, orientation); + + if (split.splitEnd) { + builder.finish(); } continue; @@ -186,9 +316,9 @@ NodableArcString::getNoded(std::vector>& splitArcs) { const bool isCCW = orientation == algorithm::Orientation::COUNTERCLOCKWISE; - for (std::size_t i = 1; i < arcPoints.size(); i++) { - const CoordinateXYZM& p0 = arcPoints[i - 1]; - const CoordinateXYZM& p2 = arcPoints[i]; + for (std::size_t i = 1; i < split.points.size(); i++) { + const CoordinateXYZM& p0 = split.points[i - 1]; + const CoordinateXYZM& p2 = split.points[i]; // TODO: Check if control point of original arc falls into this section, // and use it instead of calculating a midpoint here? @@ -196,28 +326,18 @@ NodableArcString::getNoded(std::vector>& splitArcs) { p1.z = (p0.z + p2.z) / 2; p1.m = (p0.m + p2.m) / 2; - if (dstSeq->isEmpty()) { - dstSeq->add(p0); - } - dstSeq->add(p1); - dstSeq->add(p2); - - const std::size_t dstPos = dstSeq->getSize() - 3; - arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation); + builder.addArc(p0, p1, p2, center, radius, orientation); // Finish the ArcString, start a new one. - const bool isSplitPoint = i != arcPoints.size() - 1; + const bool isSplitPoint = (i < split.points.size() - 1) || split.splitEnd; if (isSplitPoint) { - splitArcs.push_back(std::make_unique(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, getData())); - dstSeq = std::make_unique(0, m_constructZ, m_constructM); - arcs.clear(); + builder.finish(); + } } } - if (!arcs.empty()) { - splitArcs.push_back(std::make_unique(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, getData())); - } + builder.finish(); } } \ No newline at end of file diff --git a/tests/unit/operation/overlayng/OverlayNGTest.cpp b/tests/unit/operation/overlayng/OverlayNGTest.cpp index 46b8155fa..2da3cbcca 100644 --- a/tests/unit/operation/overlayng/OverlayNGTest.cpp +++ b/tests/unit/operation/overlayng/OverlayNGTest.cpp @@ -6,9 +6,6 @@ // geos #include -#include -#include -#include // std #include @@ -818,4 +815,32 @@ void object::test<62>() testOverlay(a, a, exp, OverlayNG::UNION, 0); } +template<> +template<> +void object::test<63>() +{ + set_test_name("Union of CurvePolygon / CurvePolygon -> CurvePolygon"); + + std::string a = "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-1.1087328289561522 -0.2365594619955851, -1.2116499629412223 -0.2219284209572556, -1.3064159292035398 -0.1792035398230087), (-1.3064159292035398 -0.1792035398230087, -1.2982128297312812 -0.0461310372730352), CIRCULARSTRING (-1.2982128297312812 -0.0461310372730352, -1.2213760168089367 -0.1591592707276735, -1.1087328289561522 -0.2365594619955851)))"; + std::string b = "CURVEPOLYGON (COMPOUNDCURVE ((-1.277492733105557 0.2899949746553787, -0.8433316896087282 -0.1241204385865409), CIRCULARSTRING (-0.8433316896087282 -0.1241204385865409, -0.9646131918909476 -0.2072935239179199, -1.1087328289561522 -0.2365594619955851, -1.2213760168089367 -0.1591592707276735, -1.2982128297312812 -0.0461310372730352), (-1.2982128297312812 -0.0461310372730352, -1.277492733105557 0.2899949746553787)))"; + + std::string exp = "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-1.1087328289561522 -0.2365594619955851, -1.2116499629412223 -0.2219284209572556, -1.3064159292035398 -0.1792035398230087), (-1.3064159292035398 -0.1792035398230087, -1.2982128297312812 -0.0461310372730352, -1.277492733105557 0.2899949746553787, -0.8433316896087282 -0.1241204385865409), CIRCULARSTRING (-0.8433316896087282 -0.1241204385865409, -0.9646131918909476 -0.2072935239179199, -1.1087328289561522 -0.2365594619955851)))"; + + testOverlay(a, b, exp, OverlayNG::UNION, 0); +} + +template<> +template<> +void object::test<64>() +{ + set_test_name("Union of CurvePolygon / CurvePolygon -> CurvePolygon (2)"); + + std::string a = "MULTISURFACE (CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-1.266592920353982 0.4668141592920354, -1.1143440097781072 0.502208020974126, -0.9610873502585282 0.4714695673823874, -1.1432212469102676 0.4224569115784076, -1.277492733105557 0.2899949746553787), (-1.277492733105557 0.2899949746553787, -1.266592920353982 0.4668141592920354))), CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-0.7885401828539679 0.3170953789649493, -0.7421261955995471 0.0873215546859087, -0.8433316896087282 -0.1241204385865409), (-0.8433316896087282 -0.1241204385865409, -0.7885401828539679 0.3170953789649493))), CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-1.1087328289561522 -0.2365594619955851, -1.2116499629412223 -0.2219284209572556, -1.3064159292035398 -0.1792035398230087), (-1.3064159292035398 -0.1792035398230087, -1.2982128297312812 -0.0461310372730352), CIRCULARSTRING (-1.2982128297312812 -0.0461310372730352, -1.2213760168089367 -0.1591592707276735, -1.1087328289561522 -0.23655946199 55851))))"; + std::string b = "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-0.9610873502585282 0.4714695673823874, -0.8624070192245114 0.4081497467816552, -0.7885401828539679 0.3170953789649493), (-0.7885401828539679 0.3170953789649493, -0.8433316896087282 -0.1241204385865409), CIRCULARSTRING (-0.8433316896087282 -0.1241204385865409, -0.9646131918909476 -0.2072935239179199, -1.1087328289561522 -0.2365594619955851, -1.2213760168089367 -0.1591592707276735, -1.2982128297312812 -0.0461310372730352), (-1.2982128297312812 -0.0461310372730352, -1.277492733105557 0.2899949746553787), CIRCULARSTRING (-1.277492733105557 0.2899949746553787, -1.1432212469102676 0.4224569115784076, -0.9610873502585282 0.4714695673823874)))"; + + std::string exp = "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-1.266592920353982 0.4668141592920354, -1.1143440097781072 0.502208020974126, -0.9610873502585282 0.4714695673823874, -0.8624070192245114 0.4081497467816552, -0.7885401828539679 0.3170953789649493, -0.7421261955995471 0.0873215546859087, -0.8433316896087282 -0.1241204385865409, -0.9646131918909476 -0.2072935239179199, -1.1087328289561522 -0.2365594619955851, -1.2116499629412223 -0.2219284209572556, -1.3064159292035398 -0.1792035398230087), (-1.3064159292035398 -0.1792035398230087, -1.2982128297312812 -0.0461310372730352, -1.277492733105557 0.2899949746553787, -1.266592920353982 0.4668141592920354)))"; + + testOverlay(a, b, exp, OverlayNG::UNION, 0); +} + } // namespace tut commit 3da292784f83e0bfce769442d4d7e14cdc964bc6 Author: Daniel Baston Date: Sun Jun 7 12:03:15 2026 -0400 CircularArcIntersector: improve robustness of arc/arc endpoint intersection diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp index ad79ee190..8d3fabf58 100644 --- a/src/algorithm/CircularArcIntersector.cpp +++ b/src/algorithm/CircularArcIntersector.cpp @@ -235,55 +235,39 @@ CircularArcIntersector::intersects(const CircularArc& arc1, const CircularArc& a if (a == 0 || (d == 0 && r1 == r2)) { computeCocircularIntersection(arc1, arc2); } else { - // Explicitly add endpoint intersections that may be missed or inexactly computed. - if (arc1.p0().equals2D(arc2.p0()) && !hasIntersection(arc1.p0())) { - addArcArcIntersectionPoint(arc1.p0(), arc1, arc2); + // Compute interior intersection points. + const double dx = c2.x-c1.x; + const double dy = c2.y-c1.y; + + // 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 + const 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 }; + + // Check to see if computed intersection points are inexact versions of an endpoint intersection + const CoordinateXY& ap0 = arc1.p0(); + const CoordinateXY& ap2 = arc1.p2(); + const CoordinateXY& bp0 = arc2.p0(); + const CoordinateXY& bp2 = arc2.p2(); + + if (ap0 == bp0 || ap0 == bp2) { + closestPoint(isect0, isect1, 2, ap0) = ap0; } - if (arc1.p0().equals2D(arc2.p2()) && !hasIntersection(arc1.p0())) { - addArcArcIntersectionPoint(arc1.p0(), arc1, arc2); - } - if (arc1.p2().equals2D(arc2.p0()) && !hasIntersection(arc1.p2())) { - addArcArcIntersectionPoint(arc1.p2(), arc1, arc2); - } - if (arc1.p2().equals2D(arc2.p2()) && !hasIntersection(arc1.p2())) { - addArcArcIntersectionPoint(arc1.p2(), arc1, arc2); + if (ap2 == bp0 || ap2 == bp2) { + closestPoint(isect0, isect1, 2, ap2) = ap2; } - if (nPt < 2) { - // Compute interior intersection points. - const double dx = c2.x-c1.x; - const double dy = c2.y-c1.y; + if (arc1.containsPointOnCircle(isect0) && arc2.containsPointOnCircle(isect0)) { + addArcArcIntersectionPoint(isect0, arc1, arc2); + } - // 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 - const 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 }; - - // One of the computed intersection points may be an inexact version of an endpoint. - // If we already have an endpoint intersection, we need to process the farther-away - // computed point first. - if (nPt == 1 && intPt[0].distance(isect0) < intPt[0].distance(isect1)) { - std::swap(isect0, isect1); - } - - for (const CoordinateXY& computedIntPt : {isect0, isect1}) { - if (nPt > 0 && computedIntPt.equals2D(intPt[0])) { - continue; - } - - if (nPt > 1) { - continue; - } - - if (arc1.containsPointOnCircle(computedIntPt) && arc2.containsPointOnCircle(computedIntPt)) { - addArcArcIntersectionPoint(computedIntPt, arc1, arc2); - } - } + if (isect1 != isect0 && arc1.containsPointOnCircle(isect1) && arc2.containsPointOnCircle(isect1)) { + addArcArcIntersectionPoint(isect1, arc1, arc2); } } commit f0ffac4dc49d012f3d80cb28f76e9ff10d79772f Author: Daniel Baston Date: Sun Jun 7 11:50:32 2026 -0400 CircularArc: Add getReverseDirectionPoint diff --git a/include/geos/geom/CircularArc.h b/include/geos/geom/CircularArc.h index 9f9bbc693..800eb0144 100644 --- a/include/geos/geom/CircularArc.h +++ b/include/geos/geom/CircularArc.h @@ -110,6 +110,8 @@ public: /// line tangent to the arc at the origin point. CoordinateXY getDirectionPoint() const; + CoordinateXY getReverseDirectionPoint() const; + Envelope getEnvelope() const; /// Return the length of the arc diff --git a/src/geom/CircularArc.cpp b/src/geom/CircularArc.cpp index fa772b69b..ed622c135 100644 --- a/src/geom/CircularArc.cpp +++ b/src/geom/CircularArc.cpp @@ -317,6 +317,16 @@ CircularArc::getDirectionPoint() const return CircularArcs::getDirectionPoint(getCenter(), getRadius(), theta0(), getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE); } +CoordinateXY +CircularArc::getReverseDirectionPoint() const +{ + if (isLinear()) { + return p0(); + } + + return CircularArcs::getDirectionPoint(getCenter(), getRadius(), theta2(), getOrientation() != algorithm::Orientation::COUNTERCLOCKWISE); +} + Envelope CircularArc::getEnvelope() const { diff --git a/src/operation/overlayng/OverlayUtil.cpp b/src/operation/overlayng/OverlayUtil.cpp index 077ca2efb..275ca422f 100644 --- a/src/operation/overlayng/OverlayUtil.cpp +++ b/src/operation/overlayng/OverlayUtil.cpp @@ -348,7 +348,7 @@ OverlayUtil::getDirectionPoint(const CoordinateSequence& pts, bool forward, bool return arc.getDirectionPoint(); } else { CircularArc arc(pts, pts.size() - 3); - return algorithm::CircularArcs::getDirectionPoint(arc.getCenter(), arc.getRadius(), arc.theta2(), !arc.isCCW()); + return arc.getReverseDirectionPoint(); } } commit fac5376f75356ad9cd8b6f08ef0336f1d56e006c Author: Daniel Baston Date: Mon Jun 8 08:35:59 2026 -0400 CircularArc: handle direction point of degenerate arcs diff --git a/src/geom/CircularArc.cpp b/src/geom/CircularArc.cpp index dd5a56d85..fa772b69b 100644 --- a/src/geom/CircularArc.cpp +++ b/src/geom/CircularArc.cpp @@ -310,6 +310,10 @@ CircularArc::getArea() const { CoordinateXY CircularArc::getDirectionPoint() const { + if (isLinear()) { + return p2(); + } + return CircularArcs::getDirectionPoint(getCenter(), getRadius(), theta0(), getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE); } commit c06102e28158cd1393a68c767f36087b249e4ee0 Author: Daniel Baston Date: Sun Jun 7 11:47:50 2026 -0400 CircularArcIntersector: Improve robustness of arc/segment endpoint intersection diff --git a/include/geos/algorithm/CircularArcs.h b/include/geos/algorithm/CircularArcs.h index a32a197cb..0aa868e23 100644 --- a/include/geos/algorithm/CircularArcs.h +++ b/include/geos/algorithm/CircularArcs.h @@ -65,6 +65,21 @@ public: static void interpolateZM(const geom::CoordinateSequence &seq, size_t i0, const geom::CoordinateXY ¢er, bool isCCW, geom::CoordinateXY &pt, double &z, double &m); + /** Determines whether and where a circle intersects a line. + * + * @param center The center point of the circle + * @param r The radius of the circle + * @param p0 One point defining a line of infinite length + * @param p1 Second point defining a line of infinite length + * @param isect0 Set to the first intersection point, if it exists + * @param isect1 Set to the second intersection point, if it exists + * @return The number of intersection points + */ + static int + circleIntersectsLine(const geom::CoordinateXY& center, double r, + const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, + geom::CoordinateXY& isect0, geom::CoordinateXY& isect1); + /** Determines whether and where a circle intersects a line segment. * * @param center The center point of the circle diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp index c63cd2c55..ad79ee190 100644 --- a/src/algorithm/CircularArcIntersector.cpp +++ b/src/algorithm/CircularArcIntersector.cpp @@ -101,6 +101,23 @@ CircularArcIntersector::hasIntersection(const geom::CoordinateXY &p) const { return false; } +static CoordinateXY& +closestPoint(CoordinateXY& p0, CoordinateXY& p1, int n, const CoordinateXY& q) +{ + if (n < 2) { + return p0; + } + + const double d0 = p0.distance(q); + const double d1 = p1.distance(q); + + if (d0 < d1) { + return p0; + } + + return p1; +} + void CircularArcIntersector::intersects(const CircularArc& arc, const CoordinateSequence& seq, std::size_t segPos0, std::size_t segPos1, bool useSegEndpoints) { @@ -117,14 +134,35 @@ CircularArcIntersector::intersects(const CircularArc& arc, const CoordinateSeque const CoordinateXY& c = arc.getCenter(); const double r = arc.getRadius(); - CoordinateXYZM isect0, isect1; - auto n = CircularArcs::circleIntersectsSegment(c, r, seq.getAt(segPos0), seq.getAt(segPos1), isect0, isect1); + CoordinateXY isect0, isect1; + const auto nPointsIntersectingLine = CircularArcs::circleIntersectsLine(c, r, seq.getAt(segPos0), seq.getAt(segPos1), isect0, isect1); - if (n > 0 && arc.containsPointOnCircle(isect0)) { + if (nPointsIntersectingLine == 0) { + result = NO_INTERSECTION; + return; + } + + // Check for exact endpoint-endpoint intersections + // If found, replace the computed intersection points with an exact endpoint + const CoordinateXY& ap0 = arc.p0(); + const CoordinateXY& ap2 = arc.p2(); + const CoordinateXY& bp0 = seq.getAt(segPos0); + const CoordinateXY& bp1 = seq.getAt(segPos1); + + if (ap0 == bp0 || ap0 == bp1) { + closestPoint(isect0, isect1, nPointsIntersectingLine, ap0) = ap0; + } + if (ap2 == bp0 || ap2 == bp1) { + closestPoint(isect0, isect1, nPointsIntersectingLine, ap2) = ap2; + } + + Envelope segEnv(bp0, bp1); + + if (nPointsIntersectingLine > 0 && segEnv.contains(isect0) && arc.containsPointOnCircle(isect0)) { addArcSegmentIntersectionPoint(isect0, arc, seq, segPos0, segPos1, useSegEndpoints); } - if (n > 1 && arc.containsPointOnCircle(isect1)) { + if (nPointsIntersectingLine > 1 && segEnv.contains(isect1) && arc.containsPointOnCircle(isect1)) { addArcSegmentIntersectionPoint(isect1, arc, seq, segPos0, segPos1, useSegEndpoints); } diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp index eeb779e2b..07f2bd630 100644 --- a/src/algorithm/CircularArcs.cpp +++ b/src/algorithm/CircularArcs.cpp @@ -309,22 +309,26 @@ CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, co } int -CircularArcs::circleIntersectsSegment(const CoordinateXY& center, double r, - const CoordinateXY& p0, const CoordinateXY& p1, - CoordinateXY& ret0, CoordinateXY& ret1) +CircularArcs::circleIntersectsLine(const geom::CoordinateXY& center, double r, + const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, + geom::CoordinateXY& isect0, geom::CoordinateXY& isect1) { 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; + if (p1.x > x0 + r || p1.x < x0 - r) { + return 0; + } + + if (p1.x == x0 + r || p1.x == x0 - r) { + isect0 = {p1.x, y0}; + return 1; + } + double A = 1; double B = -2*y0; double C = x*x - 2*x*x0 + x0*x0 + y0*y0 - r*r; @@ -335,41 +339,73 @@ CircularArcs::circleIntersectsSegment(const CoordinateXY& center, double r, isect0 = {x, Y1}; isect1 = {x, Y2}; + + return 2; } - 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; + const double m = (p1.y - p0.y) / (p1.x - p0.x); + const double b = p1.y - p1.x*m; - double d = std::sqrt(B*B - 4*A*C); - double X1 = (-B + d)/(2*A); - double X2 = (-B - d)/(2*A); + // Take equation defining circle: (x - x0)^2 + (y - y0)^2 = r^2 + // Substitute in equation defining line: y = mx + b + // Rearrange into standard quadratic form: Ax^2 + Bx + C = 0 + const double A = 1 + m*m; + const double B = -2*x0 + 2*m*b - 2*m*y0; + const double C = x0*x0 + b*b - 2*b*y0 + y0*y0 - r*r; - // TODO use robust quadratic solver such as https://github.com/archermarx/quadratic ? - // auto [X1, X2] = quadratic::solve(A, B, C); + const double dd = B*B - 4*A*C; - isect0 = {X1, m* X1 + b}; - isect1 = {X2, m* X2 + b}; + if (dd < 0) { + return 0; } + // TODO use robust quadratic solver such as https://github.com/archermarx/quadratic ? + // auto [X1, X2] = quadratic::solve(A, B, C); + + const double d = std::sqrt(dd); + const double X1 = (-B + d)/(2*A); + const double X2 = (-B - d)/(2*A); + + isect0 = {X1, m* X1 + b}; + if (d == 0) { + return 1; + } + + isect1 = {X2, m* X2 + b}; + return 2; +} + +int +CircularArcs::circleIntersectsSegment(const CoordinateXY& center, double r, + const CoordinateXY& p0, const CoordinateXY& p1, + CoordinateXY& ret0, CoordinateXY& ret1) +{ + CoordinateXY isect0, isect1; + + auto n = circleIntersectsLine(center, r, p0, p1, isect0, isect1); + + if (n == 0) { + return 0; + } + + Envelope segEnv(p0, p1); + if (segEnv.intersects(isect0)) { ret0 = isect0; - if (segEnv.intersects(isect1) && !isect1.equals2D(isect0)) { + if (n > 1 && segEnv.intersects(isect1)) { ret1 = isect1; - n = 2; - } else { - n = 1; + return 2; } - } else if (segEnv.intersects(isect1)) { - ret0 = isect1; - n = 1; + + return 1; } - return n; + if (segEnv.intersects(isect1)) { + ret0 = isect1; + return 1; + } + + return 0; } bool diff --git a/tests/unit/algorithm/CircularArcIntersectorTest.cpp b/tests/unit/algorithm/CircularArcIntersectorTest.cpp index 928a3df90..e9899213d 100644 --- a/tests/unit/algorithm/CircularArcIntersectorTest.cpp +++ b/tests/unit/algorithm/CircularArcIntersectorTest.cpp @@ -1712,6 +1712,26 @@ void object::test<85>() ensure_equals(cai1.getPoint(0), cai2.getPoint(0)); } +template<> +template<> +void object::test<86>() +{ + // arc/segment with single endpoint intersection + + CircularArcIntersector cai; + + auto arc = CircularArc::create(XY{-0.84333168960872817, -0.1241204385865409}, XY{-0.96461319189094762, -0.20729352391791989}, XY{-1.1087328289561522, -0.23655946199558511}); + + CoordinateSequence seg{ + XY{-1.277492733105557, 0.28999497465537871}, XY{-0.84333168960872817, -0.1241204385865409}}; + + cai.intersects(arc, seg, 0, 1, false); + + ensure_equals(cai.getNumPoints(), 1u); + // ensure endpoint intersection is represented exactly, not with distance() == 0 + ensure_equals(cai.getPoint(0), seg.getAt(1)); +} + // TODO: check Z values of arc result centerpoints // TODO: add tests for seg/seg commit f14c453ade3f86a98b3dafeaf5cca750593d4e70 Author: Daniel Baston Date: Fri May 15 14:12:27 2026 -0400 Edge: simplify direction() logic diff --git a/include/geos/operation/overlayng/Edge.h b/include/geos/operation/overlayng/Edge.h index 83f67dc6a..b88a44fe7 100644 --- a/include/geos/operation/overlayng/Edge.h +++ b/include/geos/operation/overlayng/Edge.h @@ -230,25 +230,22 @@ public: throw util::GEOSException("Edge must have >= 2 points"); } - const geom::Coordinate& p0 = pts->getAt(0); - const geom::Coordinate& p1 = pts->getAt(1); - const geom::Coordinate& pn0 = pts->getAt(pts->size() - 1); - const geom::Coordinate& pn1 = pts->getAt(pts->size() - 2); + const geom::CoordinateXY& p0 = getCoordinate(0); + const geom::CoordinateXY& pn0 = getCoordinate(pts->size() - 1); - int cmp = 0; - int cmp0 = p0.compareTo(pn0); - if (cmp0 != 0) cmp = cmp0; - - if (cmp == 0) { - int cmp1 = p1.compareTo(pn1); - if (cmp1 != 0) cmp = cmp1; + const int cmp0 = p0.compareTo(pn0); + if (cmp0 != 0) { + return cmp0 == -1; } - if (cmp == 0) { - throw util::GEOSException("Edge direction cannot be determined because endpoints are equal"); + const geom::CoordinateXY& p1 = getCoordinate(1); + const geom::CoordinateXY& pn1 = getCoordinate(pts->size() - 2); + const int cmp1 = p1.compareTo(pn1); + if (cmp1 != 0) { + return cmp1 == -1; } - return cmp == -1; + throw util::GEOSException("Edge direction cannot be determined because endpoints are equal"); }; /** commit dc3935ba35f5fd6328bddb3637c7e0eeba307dd8 Author: Daniel Baston Date: Fri May 15 14:09:56 2026 -0400 NodableArcString: Split ArcStrings when intersection point is already a node diff --git a/src/noding/NodableArcString.cpp b/src/noding/NodableArcString.cpp index d16acafd5..4b824b3fa 100644 --- a/src/noding/NodableArcString.cpp +++ b/src/noding/NodableArcString.cpp @@ -16,6 +16,7 @@ #include #include +#include using geos::geom::CoordinateXYZM; using geos::geom::CircularArc; @@ -26,7 +27,7 @@ static double pseudoAngleDiffCCW(double paStart, double pa) { double diff = pa - paStart; - if (diff <= 0) { + if (diff < 0) { diff += 4; } @@ -45,31 +46,32 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector { const bool isCCW = arc.getOrientation() == algorithm::Orientation::COUNTERCLOCKWISE; const geom::CoordinateXY& center = arc.getCenter(); - const double paStart = geom::Quadrant::pseudoAngle(center, arc.p0()); + + // Some potential split points may be skipped, for example, if they would create an arc section that is + // too short to have a constructed midpoint. Because the results of this logic could depend on the + // direction in which the arc is processed, we reverse clockwise arcs and then reverse the list of + // retained points. + const double paStart = geom::Quadrant::pseudoAngle(center, isCCW ? arc.p0() : arc.p2()); std::vector retained; // Add starting point of input arc { CoordinateXYZM p0; - arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition(), p0); + arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + (isCCW ? 0 : 2), p0); retained.push_back(p0); } - std::sort(splitPoints.begin(), splitPoints.end(), [¢er, paStart, isCCW](const auto& p0, const auto& p1) { + std::sort(splitPoints.begin(), splitPoints.end(), [¢er, paStart](const auto& p0, const auto& p1) { double pa0 = geom::Quadrant::pseudoAngle(center, p0); double pa1 = geom::Quadrant::pseudoAngle(center, p1); - if (isCCW) { - return pseudoAngleDiffCCW(paStart, pa0) < pseudoAngleDiffCCW(paStart, pa1); - } else { - return pseudoAngleDiffCCW(paStart, pa0) > pseudoAngleDiffCCW(paStart, pa1); - } + return pseudoAngleDiffCCW(paStart, pa0) < pseudoAngleDiffCCW(paStart, pa1); }); // Add ending point of input arc { CoordinateXYZM p2; - arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + 2, p2); + arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + (isCCW ? 2 : 0), p2); splitPoints.push_back(p2); } @@ -90,23 +92,21 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector continue; } - const auto p1 = algorithm::CircularArcs::getMidpoint(p0, p2, center, arc.getRadius(), isCCW); + // Calculate the midpoint of an arc between p0 and p2. + // We don't actually use the calculated point here, we just want to make sure that + // the arc from p0 to p2 is long enough to contain a midpoint. + const geom::CoordinateXY p1 = algorithm::CircularArcs::getMidpoint(p0, p2, center, arc.getRadius(), true); if (p1.equals2D(p0) || p1.equals2D(p2)) { continue; } - // Reject split point where sub-arc midpoint doesn't fall inside arc - if (!arc.containsPointOnCircle(p1)) { - continue; - } - // Reject split point where computed doesn't fall between endpoints - const double t0 = algorithm::Angle::normalizePositive(arc.theta0()); + const double t0 = algorithm::Angle::normalizePositive(isCCW ? arc.theta0() : arc.theta2()); const double t1 = algorithm::Angle::normalizePositive(algorithm::CircularArcs::getAngle(p1, center)); - const double t2 = algorithm::Angle::normalizePositive(algorithm::CircularArcs::getAngle(p2, center)); + const double t2 = algorithm::Angle::normalizePositive(isCCW ? arc.theta2() : arc.theta0()); - if (algorithm::Angle::isWithinCCW(t1, t0, t2) != isCCW) { + if (!algorithm::Angle::isWithinCCW(t1, t0, t2)) { continue; } @@ -116,7 +116,7 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector // Make sure that endpoint of split arc is unchanged { CoordinateXYZM p2; - arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + 2, p2); + arc.getCoordinateSequence()->getAt(arc.getCoordinatePosition() + (isCCW ? 2 : 0), p2); CoordinateXYZM& back = retained.back(); if (!back.equals2D(p2)) { @@ -131,6 +131,10 @@ std::vector prepareArcPoints(const CircularArc& arc, std::vector } } + if (!isCCW) { + std::reverse(retained.begin(), retained.end()); + } + return retained; } @@ -146,16 +150,19 @@ NodableArcString::getNoded(std::vector>& splitArcs) { const int orientation = toSplit.getOrientation(); bool arcIsSplit = true; + bool createArcString = true; const bool preserveControlPoint = true; std::vector arcPoints; const auto it = m_adds.find(arcIndex); if (it == m_adds.end()) { arcIsSplit = false; + createArcString = false; } else { arcPoints = prepareArcPoints(toSplit, it->second); if (arcPoints.size() == 2) { // All added nodes collapsed + // Still need to know if arc was split. arcIsSplit = false; } } @@ -168,6 +175,12 @@ NodableArcString::getNoded(std::vector>& splitArcs) { std::size_t dstPos = dstSeq->getSize() - 3; arcs.emplace_back(*dstSeq, dstPos, center, radius, orientation); + if (createArcString) { + splitArcs.push_back(std::make_unique(std::move(arcs), std::move(dstSeq), m_constructZ, m_constructM, getData())); + dstSeq = std::make_unique(0, m_constructZ, m_constructM); + arcs.clear(); + } + continue; } diff --git a/tests/unit/capi/GEOSNodeTest.cpp b/tests/unit/capi/GEOSNodeTest.cpp index 8feb5df96..63d893a55 100644 --- a/tests/unit/capi/GEOSNodeTest.cpp +++ b/tests/unit/capi/GEOSNodeTest.cpp @@ -235,10 +235,10 @@ void object::test<10>() ensure(result_ != nullptr); expected_ = fromWKT("MULTICURVE ZM (" - "CIRCULARSTRING ZM (-1 0 3 4, -0.7071067812 0.7071067812 2.5 6.5, -5.5511151231e-17 1 2 9)," - "CIRCULARSTRING ZM (-5.5511151231e-17 1 2 9, 0.7071067812 0.7071067812 3 8, 1 0 4 7)," - "CIRCULARSTRING ZM (-1 2 NaN 9, -0.2928932188 1.7071067812 NaN 9, -5.5511151231e-17 1 2 9)," - "CIRCULARSTRING ZM (-5.5511151231e-17 1 2 9, -0.2928932188 0.2928932188 2.5 6.5, -1 0 3 4))"); + "CIRCULARSTRING ZM (-1 0 3 4, -0.7071067812 0.7071067812 2.5 6.5, 0 1 2 9)," + "CIRCULARSTRING ZM (0 1 2 9, 0.7071067812 0.7071067812 3 8, 1 0 4 7)," + "CIRCULARSTRING ZM (-1 2 NaN 9, -0.2928932188 1.7071067812 NaN 9, 0 1 2 9)," + "CIRCULARSTRING ZM (0 1 2 9, -0.2928932188 0.2928932188 2.5 13, -1 0 3 17))"); ensure_equals(GEOSGetNumGeometries(result_), 4); diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp index ee4e26058..3678746c9 100644 --- a/tests/unit/geom/CircularStringTest.cpp +++ b/tests/unit/geom/CircularStringTest.cpp @@ -138,9 +138,9 @@ void object::test<3>() // Overlay ensure_THROW(cs_->Union(), geos::util::UnsupportedOperationException); - ensure_equals_geometry(cs_->Union(cs_.get()).get(), static_cast(cs_.get())); + ensure_equals(cs_->Union(cs_.get())->getLength(), cs_->getLength()); ensure(cs_->difference(cs_.get())->isEmpty()); - ensure_equals_geometry(cs_->intersection(cs_.get()).get(), static_cast(cs_.get())); + ensure_equals(cs_->intersection(cs_.get())->getLength(), cs_->getLength()); ensure(cs_->symDifference(cs_.get())->isEmpty()); // Distance diff --git a/tests/unit/geom/CurvePolygonTest.cpp b/tests/unit/geom/CurvePolygonTest.cpp index 07c3faad1..012fa8305 100644 --- a/tests/unit/geom/CurvePolygonTest.cpp +++ b/tests/unit/geom/CurvePolygonTest.cpp @@ -166,9 +166,9 @@ void object::test<3>() // Overlay ensure_THROW(cp_->Union(), geos::util::UnsupportedOperationException); - ensure_equals_geometry(cp_->Union(cp_.get()).get(), static_cast(cp_.get())); + ensure_equals(cp_->Union(cp_.get())->getLength(), cp_->getLength()); ensure(cp_->difference(cp_.get())->isEmpty()); - ensure_equals_geometry(cp_->intersection(cp_.get()).get(), static_cast(cp_.get())); + ensure_equals(cp_->intersection(cp_.get())->getLength(), cp_->getLength()); ensure(cp_->symDifference(cp_.get())->isEmpty()); // Distance diff --git a/tests/unit/operation/overlayng/OverlayNGTest.cpp b/tests/unit/operation/overlayng/OverlayNGTest.cpp index d7077e57a..46b8155fa 100644 --- a/tests/unit/operation/overlayng/OverlayNGTest.cpp +++ b/tests/unit/operation/overlayng/OverlayNGTest.cpp @@ -780,7 +780,7 @@ template<> template<> void object::test<60>() { - set_test_name("MultiSurface / MultiPoint -> MultiSurface"); + set_test_name("Union of MultiSurface / MultiPoint -> MultiSurface"); std::string a = "MULTISURFACE(CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))), ((4 4, 5 4, 5 5, 4 4)))"; std::string b = "MULTIPOINT (0 0, 4 3, 2 2, 4.5 4.1)"; @@ -789,4 +789,33 @@ void object::test<60>() testOverlay(a, b, exp, OverlayNG::UNION, 0); } +template<> +template<> +void object::test<61>() +{ + set_test_name("Union of CurvePolygon / CurvePolygon -> CurvePolygon"); + + std::string a = "CURVEPOLYGON (CIRCULARSTRING (-5 0, 0 5, 5 0, 0 4, -5 0))"; + std::string b = "CURVEPOLYGON (COMPOUNDCURVE((-5 0, 5 0), CIRCULARSTRING (5 0, 0 4, -5 0)))"; + + std::string exp = "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-5 0, 0 5, 5 0), (5 0, -5 0)))"; + + testOverlay(a, b, exp, OverlayNG::UNION, 0); +} + +template<> +template<> +void object::test<62>() +{ + set_test_name("CircularString self-union"); + + std::string a = "CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0)"; + + // Noding causes the CircularString to split at (2, 0) + // OverlayNG does not merge output lines, so we get a MultiCurve. + std::string exp = "MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), CIRCULARSTRING (2 0, 3 -1, 4 0))"; + + testOverlay(a, a, exp, OverlayNG::UNION, 0); +} + } // namespace tut commit 1aeef7493bc6d0804d094021fc1013a7227791af Author: Daniel Baston Date: Thu May 14 15:57:06 2026 -0400 CircularArcIntersector: Calc intersection points independently of argument order diff --git a/src/algorithm/CircularArcIntersector.cpp b/src/algorithm/CircularArcIntersector.cpp index b5e6adcc3..c63cd2c55 100644 --- a/src/algorithm/CircularArcIntersector.cpp +++ b/src/algorithm/CircularArcIntersector.cpp @@ -163,11 +163,15 @@ CircularArcIntersector::intersects(const CircularArc& arc1, const CircularArc& a reset(); - const auto& c1 = arc1.getCenter(); - const auto& c2 = arc2.getCenter(); + // Normalize arguments such that the computed intersection points do not depend + // on the order of the input arcs + const bool swapArgs = arc1.getCenter().compareTo(arc2.getCenter()) > 0; - const auto r1 = arc1.getRadius(); - const auto r2 = arc2.getRadius(); + const auto c1 = swapArgs ? arc2.getCenter() : arc1.getCenter(); + const auto c2 = swapArgs ? arc1.getCenter() : arc2.getCenter(); + + const auto r1 = swapArgs ? arc2.getRadius() : arc1.getRadius(); + const auto r2 = swapArgs ? arc1.getRadius() : arc2.getRadius(); const auto d = c1.distance(c2); diff --git a/tests/unit/algorithm/CircularArcIntersectorTest.cpp b/tests/unit/algorithm/CircularArcIntersectorTest.cpp index 6ad649d02..928a3df90 100644 --- a/tests/unit/algorithm/CircularArcIntersectorTest.cpp +++ b/tests/unit/algorithm/CircularArcIntersectorTest.cpp @@ -1692,6 +1692,26 @@ void object::test<84>() ensure_equals(cai.getPoint(0), XY{31.8, 68.2}); } +template<> +template<> +void object::test<85>() +{ + set_test_name("computed test points are independent of argument order"); + + auto arc0 = CircularArc::create(XY{0, 5}, XY{3, 4}, XY{4, 3}); + auto arc1 = CircularArc::create(XY{3, 5}, XY{4, 4}, XY{2, 3}); + + CircularArcIntersector cai1; + cai1.intersects(arc0, arc1); + ensure_equals(cai1.getNumPoints(), 1u); + + CircularArcIntersector cai2; + cai2.intersects(arc1, arc0); + ensure_equals(cai2.getNumPoints(), 1u); + + ensure_equals(cai1.getPoint(0), cai2.getPoint(0)); +} + // TODO: check Z values of arc result centerpoints // TODO: add tests for seg/seg commit b0506ea14bdc0339fb8ab61eb95ba5ec26762332 Author: Daniel Baston Date: Thu May 14 13:51:20 2026 -0400 OverlayUtil: Add getDirectionPoint diff --git a/include/geos/operation/overlayng/OverlayUtil.h b/include/geos/operation/overlayng/OverlayUtil.h index 25fa99312..5a83bda6a 100644 --- a/include/geos/operation/overlayng/OverlayUtil.h +++ b/include/geos/operation/overlayng/OverlayUtil.h @@ -205,6 +205,9 @@ public: */ static bool round(const Point* pt, const PrecisionModel* pm, Coordinate& rsltCoord); + static geom::CoordinateXY + getDirectionPoint(const CoordinateSequence& pts, bool forward, bool isCurved); + template static void moveGeometry(std::vector>& inGeoms, std::vector>& outGeoms) { diff --git a/src/operation/overlayng/OverlayGraph.cpp b/src/operation/overlayng/OverlayGraph.cpp index 7fd39fefd..8fc1de1ca 100644 --- a/src/operation/overlayng/OverlayGraph.cpp +++ b/src/operation/overlayng/OverlayGraph.cpp @@ -14,8 +14,8 @@ #include -#include #include +#include #include #include #include @@ -105,28 +105,6 @@ OverlayGraph::createEdgePair(const std::shared_ptr& pt return e0; } -static CoordinateXY -getDirectionPoint(const CoordinateSequence& pts, bool forward, bool isCurved) -{ - if (isCurved) { - assert(pts.size() >= 3); - if (forward) { - CircularArc arc(pts, 0); - return arc.getDirectionPoint(); - } else { - CircularArc arc(pts, pts.size() - 3); - return algorithm::CircularArcs::getDirectionPoint(arc.getCenter(), arc.getRadius(), arc.theta2(), !arc.isCCW()); - } - } - - assert(pts.size() >= 2); - if (forward) { - return pts.getAt(1); - } - - return pts.getAt(pts.size() - 2); -} - /*private*/ OverlayEdge* OverlayGraph::createOverlayEdge(const std::shared_ptr& pts, OverlayLabel* lbl, bool direction, bool isCurved) @@ -134,7 +112,7 @@ OverlayGraph::createOverlayEdge(const std::shared_ptr& assert(pts->size() >= 2); CoordinateXYZM origin; - const CoordinateXY dirPt = getDirectionPoint(*pts, direction, isCurved); + const CoordinateXY dirPt = OverlayUtil::getDirectionPoint(*pts, direction, isCurved); if (direction) { pts->getAt(0, origin); diff --git a/src/operation/overlayng/OverlayUtil.cpp b/src/operation/overlayng/OverlayUtil.cpp index 313837a34..077ca2efb 100644 --- a/src/operation/overlayng/OverlayUtil.cpp +++ b/src/operation/overlayng/OverlayUtil.cpp @@ -338,6 +338,28 @@ OverlayUtil::round(const Point* pt, const PrecisionModel* pm, Coordinate& rsltCo return true; } +CoordinateXY +OverlayUtil::getDirectionPoint(const CoordinateSequence& pts, bool forward, bool isCurved) +{ + if (isCurved) { + assert(pts.size() >= 3); + if (forward) { + CircularArc arc(pts, 0); + return arc.getDirectionPoint(); + } else { + CircularArc arc(pts, pts.size() - 3); + return algorithm::CircularArcs::getDirectionPoint(arc.getCenter(), arc.getRadius(), arc.theta2(), !arc.isCCW()); + } + } + + assert(pts.size() >= 2); + if (forward) { + return pts.getAt(1); + } + + return pts.getAt(pts.size() - 2); +} + } // namespace geos.operation.overlayng commit 43edb6d35a6c66fe82712033bb3abd53f89fce3a Author: Daniel Baston Date: Tue Apr 14 06:12:17 2026 -0400 EdgeNodingBuilder: Automatically use SimpleNoder for curved inputs diff --git a/include/geos/operation/overlayng/EdgeNodingBuilder.h b/include/geos/operation/overlayng/EdgeNodingBuilder.h index d981a51d5..8d34ceffd 100644 --- a/include/geos/operation/overlayng/EdgeNodingBuilder.h +++ b/include/geos/operation/overlayng/EdgeNodingBuilder.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -95,7 +97,9 @@ private: // For use in createFloatingPrecisionNoder() algorithm::LineIntersector lineInt; + algorithm::CircularArcIntersector arcInt; noding::IntersectionAdder intAdder; + noding::ArcIntersectionAdder arcIntAdder; std::unique_ptr internalNoder; std::unique_ptr spareInternalNoder; // EdgeSourceInfo*, Edge* owned by EdgeNodingBuilder, stored in deque @@ -103,6 +107,7 @@ private: std::deque edgeQue; bool inputHasZ; bool inputHasM; + bool inputHasCurves; /** * Gets a noder appropriate for the precision model supplied. @@ -207,8 +212,10 @@ public: , hasEdges{{false,false}} , clipEnv(nullptr) , intAdder(lineInt) + , arcIntAdder(arcInt) , inputHasZ(false) , inputHasM(false) + , inputHasCurves(false) {}; ~EdgeNodingBuilder(); diff --git a/src/operation/overlayng/EdgeNodingBuilder.cpp b/src/operation/overlayng/EdgeNodingBuilder.cpp index 13c123315..ded0c128b 100644 --- a/src/operation/overlayng/EdgeNodingBuilder.cpp +++ b/src/operation/overlayng/EdgeNodingBuilder.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ using geos::noding::NodableArcString; using geos::noding::MCIndexNoder; using geos::noding::ValidatingNoder; using geos::noding::SegmentString; +using geos::noding::SimpleNoder; using geos::noding::NodedSegmentString; using geos::noding::PathString; @@ -83,16 +85,26 @@ EdgeNodingBuilder::createFixedPrecisionNoder(const PrecisionModel* p_pm) std::unique_ptr EdgeNodingBuilder::createFloatingPrecisionNoder(bool doValidation) { - std::unique_ptr mcNoder(new MCIndexNoder()); - mcNoder->setSegmentIntersector(&intAdder); + std::unique_ptr ret; - if (doValidation) { - spareInternalNoder = std::move(mcNoder); + if (inputHasCurves) { + auto simpleNoder = std::make_unique(); + simpleNoder->setArcIntersector(arcIntAdder); + ret = std::move(simpleNoder); + } else { + auto mcNoder = std::make_unique(); + mcNoder->setSegmentIntersector(&intAdder); + ret = std::move(mcNoder); + } + + // TODO: Support arcs in ValidatingNoder + if (doValidation && !inputHasCurves) { + spareInternalNoder = std::move(ret); std::unique_ptr validNoder(new ValidatingNoder(*spareInternalNoder)); return validNoder; } - return std::unique_ptr(mcNoder.release()); + return ret; } /*public*/ @@ -110,6 +122,7 @@ EdgeNodingBuilder::build(const Geometry* geom0, const Geometry* geom1) { inputHasZ = geom0->hasZ() || (geom1 != nullptr && geom1->hasZ()); inputHasM = geom0->hasM() || (geom1 != nullptr && geom1->hasM()); + inputHasCurves = geom0->hasCurvedComponents() || (geom1 != nullptr && geom1->hasCurvedComponents()); add(geom0, 0); add(geom1, 1); diff --git a/src/operation/overlayng/OverlayNGRobust.cpp b/src/operation/overlayng/OverlayNGRobust.cpp index e9af20896..258a7ecda 100644 --- a/src/operation/overlayng/OverlayNGRobust.cpp +++ b/src/operation/overlayng/OverlayNGRobust.cpp @@ -83,9 +83,6 @@ OverlayNGRobust::Union(const Geometry* a) std::unique_ptr OverlayNGRobust::Overlay(const Geometry* geom0, const Geometry* geom1, int opCode) { - geos::util::ensureNoCurvedComponents(geom0); - geos::util::ensureNoCurvedComponents(geom1); - std::unique_ptr result; std::runtime_error exOriginal(""); diff --git a/tests/unit/capi/GEOSDifferenceTest.cpp b/tests/unit/capi/GEOSDifferenceTest.cpp index d739b0936..841f49188 100644 --- a/tests/unit/capi/GEOSDifferenceTest.cpp +++ b/tests/unit/capi/GEOSDifferenceTest.cpp @@ -63,6 +63,8 @@ template<> template<> void object::test<3>() { + set_test_name("curved inputs"); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); @@ -70,7 +72,11 @@ void object::test<3>() ensure(geom2_); result_ = GEOSDifference(geom1_, geom2_); - ensure("curved geometry not supported", result_ == nullptr); + ensure(result_); + + expected_ = fromWKT("MULTICURVE (CIRCULARSTRING (1.7071067812 0.7071067812, 1.9238795325 0.3826834324, 2 0), CIRCULARSTRING (0 0, 0.6173165676 0.9238795325, 1.7071067812 0.7071067812))"); + + ensure_geometry_equals(result_, expected_, 1e-8); } } // namespace tut diff --git a/tests/unit/capi/GEOSIntersectionTest.cpp b/tests/unit/capi/GEOSIntersectionTest.cpp index 4a03845df..a4aabb439 100644 --- a/tests/unit/capi/GEOSIntersectionTest.cpp +++ b/tests/unit/capi/GEOSIntersectionTest.cpp @@ -149,14 +149,16 @@ template<> template<> void object::test<8>() { + set_test_name("curved input"); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); - ensure(geom1_); - ensure(geom2_); - result_ = GEOSIntersection(geom1_, geom2_); - ensure("curved geometry not supported", result_ == nullptr); + ensure(result_); + + expected_ = fromWKT("POINT (1.7071067812 0.7071067812)"); + ensure_geometry_equals(result_, expected_, 1e-8); } // https://github.com/libgeos/geos/issues/1074 diff --git a/tests/unit/capi/GEOSSymDifferenceTest.cpp b/tests/unit/capi/GEOSSymDifferenceTest.cpp index def092b68..63436e135 100644 --- a/tests/unit/capi/GEOSSymDifferenceTest.cpp +++ b/tests/unit/capi/GEOSSymDifferenceTest.cpp @@ -33,14 +33,16 @@ template<> template<> void object::test<2>() { + set_test_name("curved inputs"); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); - ensure(geom1_); - ensure(geom2_); - result_ = GEOSSymDifference(geom1_, geom2_); - ensure("curved geometry not supported", result_ == nullptr); + ensure(result_); + + expected_ = fromWKT("MULTICURVE (CIRCULARSTRING (1.7071067812 0.7071067812, 1.9238795325 0.3826834324, 2 0), CIRCULARSTRING (0 0, 0.6173165676 0.9238795325, 1.7071067812 0.7071067812), (1.7071067812 0.7071067812, 2 1), (1 0, 1.7071067812 0.7071067812))"); + ensure_geometry_equals(result_, expected_, 1e-8); } } // namespace tut diff --git a/tests/unit/capi/GEOSUnionTest.cpp b/tests/unit/capi/GEOSUnionTest.cpp index 68c90867b..4c7e89e29 100644 --- a/tests/unit/capi/GEOSUnionTest.cpp +++ b/tests/unit/capi/GEOSUnionTest.cpp @@ -66,14 +66,16 @@ template<> template<> void object::test<3>() { + set_test_name("curved inputs"); + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); - ensure(geom1_); - ensure(geom2_); - result_ = GEOSUnion(geom1_, geom2_); - ensure("curved geometry not supported", result_ == nullptr); + ensure(result_); + + expected_ = fromWKT("MULTICURVE (CIRCULARSTRING (1.7071067812 0.7071067812, 1.9238795325 0.3826834324, 2 0), CIRCULARSTRING (0 0, 0.6173165676 0.9238795325, 1.7071067812 0.7071067812), (1.7071067812 0.7071067812, 2 1), (1 0, 1.7071067812 0.7071067812))"); + ensure_geometry_equals(result_, expected_, 1e-8); } } // namespace tut diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp index a31b70769..ee4e26058 100644 --- a/tests/unit/geom/CircularStringTest.cpp +++ b/tests/unit/geom/CircularStringTest.cpp @@ -138,10 +138,10 @@ void object::test<3>() // Overlay ensure_THROW(cs_->Union(), geos::util::UnsupportedOperationException); - ensure_THROW(cs_->Union(cs_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cs_->difference(cs_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cs_->intersection(cs_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cs_->symDifference(cs_.get()), geos::util::UnsupportedOperationException); + ensure_equals_geometry(cs_->Union(cs_.get()).get(), static_cast(cs_.get())); + ensure(cs_->difference(cs_.get())->isEmpty()); + ensure_equals_geometry(cs_->intersection(cs_.get()).get(), static_cast(cs_.get())); + ensure(cs_->symDifference(cs_.get())->isEmpty()); // Distance ensure_equals(cs_->distance(cs_.get()), 0); diff --git a/tests/unit/geom/CompoundCurveTest.cpp b/tests/unit/geom/CompoundCurveTest.cpp index cc2f79cd8..4d52c66ef 100644 --- a/tests/unit/geom/CompoundCurveTest.cpp +++ b/tests/unit/geom/CompoundCurveTest.cpp @@ -164,10 +164,11 @@ void object::test<3>() // Overlay ensure_THROW(cc_->Union(), geos::util::UnsupportedOperationException); - ensure_THROW(cc_->Union(cc_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cc_->difference(cc_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cc_->intersection(cc_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cc_->symDifference(cc_.get()), geos::util::UnsupportedOperationException); + // TODO: Prevent overlay from degrading CompoundCurve into MultiCurve + //ensure_equals_geometry(cc_->Union(cc_.get()).get(), static_cast(cc_.get())); + ensure(cc_->difference(cc_.get())->isEmpty()); + //ensure_equals_geometry(cc_->intersection(cc_.get()).get(), static_cast(cc_.get())); + ensure(cc_->symDifference(cc_.get())->isEmpty()); // Distance ensure_equals(cc_->distance(cc_.get()), 0); diff --git a/tests/unit/geom/CurvePolygonTest.cpp b/tests/unit/geom/CurvePolygonTest.cpp index c41dab112..07c3faad1 100644 --- a/tests/unit/geom/CurvePolygonTest.cpp +++ b/tests/unit/geom/CurvePolygonTest.cpp @@ -166,10 +166,10 @@ void object::test<3>() // Overlay ensure_THROW(cp_->Union(), geos::util::UnsupportedOperationException); - ensure_THROW(cp_->Union(cp_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cp_->difference(cp_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cp_->intersection(cp_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(cp_->symDifference(cp_.get()), geos::util::UnsupportedOperationException); + ensure_equals_geometry(cp_->Union(cp_.get()).get(), static_cast(cp_.get())); + ensure(cp_->difference(cp_.get())->isEmpty()); + ensure_equals_geometry(cp_->intersection(cp_.get()).get(), static_cast(cp_.get())); + ensure(cp_->symDifference(cp_.get())->isEmpty()); // Distance ensure_equals(cp_->distance(cp_.get()), 0); diff --git a/tests/unit/geom/MultiCurveTest.cpp b/tests/unit/geom/MultiCurveTest.cpp index 70b0ada4d..9973b5177 100644 --- a/tests/unit/geom/MultiCurveTest.cpp +++ b/tests/unit/geom/MultiCurveTest.cpp @@ -153,10 +153,11 @@ void object::test<3>() // Overlay ensure_THROW(mc_->Union(), geos::util::UnsupportedOperationException); - ensure_THROW(mc_->Union(mc_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(mc_->difference(mc_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(mc_->intersection(mc_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(mc_->symDifference(mc_.get()), geos::util::UnsupportedOperationException); + // TODO: Prevent overlay from degrading CompoundCurve into MultiCurve + //ensure_equals_geometry(mc_->Union(mc_.get()).get(), static_cast(mc_.get())); + ensure(mc_->difference(mc_.get())->isEmpty()); + //ensure_equals_geometry(mc_->intersection(mc_.get()).get(), static_cast(mc_.get())); + ensure(mc_->symDifference(mc_.get())->isEmpty()); // Distance ensure_equals(mc_->distance(mc_.get()), 0); diff --git a/tests/unit/geom/MultiSurfaceTest.cpp b/tests/unit/geom/MultiSurfaceTest.cpp index f12fb9e38..9ee1cc244 100644 --- a/tests/unit/geom/MultiSurfaceTest.cpp +++ b/tests/unit/geom/MultiSurfaceTest.cpp @@ -142,10 +142,10 @@ void object::test<3>() // Overlay ensure_THROW(ms_->Union(), geos::util::UnsupportedOperationException); - ensure_THROW(ms_->Union(ms_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(ms_->difference(ms_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(ms_->intersection(ms_.get()), geos::util::UnsupportedOperationException); - ensure_THROW(ms_->symDifference(ms_.get()), geos::util::UnsupportedOperationException); + ensure_equals_geometry(ms_->Union(ms_.get()).get(), static_cast(ms_.get())); + ensure(ms_->difference(ms_.get())->isEmpty()); + ensure_equals_geometry(ms_->intersection(ms_.get()).get(), static_cast(ms_.get())); + ensure(ms_->symDifference(ms_.get())->isEmpty()); // Distance ensure_equals(ms_->distance(ms_.get()), 0); diff --git a/tests/unit/operation/overlayng/OverlayNGTest.cpp b/tests/unit/operation/overlayng/OverlayNGTest.cpp index 92f1d32a1..d7077e57a 100644 --- a/tests/unit/operation/overlayng/OverlayNGTest.cpp +++ b/tests/unit/operation/overlayng/OverlayNGTest.cpp @@ -51,20 +51,8 @@ struct test_overlayng_data { std::unique_ptr geom_expected = r.read(expected); - std::unique_ptr geom_result; - if (geom_a->hasCurvedComponents() || geom_b->hasCurvedComponents()) { - SimpleNoder noder; - geos::algorithm::CircularArcIntersector cai; - ArcIntersectionAdder aia(cai); + auto geom_result = OverlayNG::overlay(geom_a.get(), geom_b.get(), opCode, pm.get()); - noder.setArcIntersector(aia); - OverlayNG ong(geom_a.get(), geom_b.get(), pm.get(), opCode); - ong.setNoder(&noder); - - geom_result = ong.getResult(); - } else { - geom_result = OverlayNG::overlay(geom_a.get(), geom_b.get(), opCode, pm.get()); - } //std::string wkt_result = w.write(geom_result.get()); //std::cout << std::endl << wkt_result << std::endl; ensure_equals_geometry_xyzm(geom_result.get(), geom_expected.get(), tol); commit 095c72709ea5043b4a1433d2adb3611410564cd3 Author: Daniel Baston Date: Fri Mar 27 11:31:49 2026 -0400 OverlayNG: Support curved types diff --git a/include/geos/algorithm/PointLocator.h b/include/geos/algorithm/PointLocator.h index be504c2b0..c249fdc42 100644 --- a/include/geos/algorithm/PointLocator.h +++ b/include/geos/algorithm/PointLocator.h @@ -25,6 +25,8 @@ // Forward declarations namespace geos { namespace geom { +class CircularString; +class CompoundCurve; class CoordinateXY; class Curve; class Geometry; @@ -98,6 +100,10 @@ private: geom::Location locate(const geom::CoordinateXY& p, const geom::LineString* l); + geom::Location locate(const geom::CoordinateXY& p, const geom::CircularString* cs); + + geom::Location locate(const geom::CoordinateXY& p, const geom::CompoundCurve* cs); + static geom::Location locateInPolygonRing(const geom::CoordinateXY& p, const geom::Curve* ring); static geom::Location locate(const geom::CoordinateXY& p, const geom::Surface* poly); diff --git a/include/geos/geom/Curve.h b/include/geos/geom/Curve.h index 1bb16df37..8f30900f2 100644 --- a/include/geos/geom/Curve.h +++ b/include/geos/geom/Curve.h @@ -81,6 +81,8 @@ public: virtual const SimpleCurve* getCurveN(std::size_t) const = 0; + std::unique_ptr clone() const; + std::unique_ptr reverse() const; protected: diff --git a/include/geos/operation/overlayng/Edge.h b/include/geos/operation/overlayng/Edge.h index 7ec26eb16..83f67dc6a 100644 --- a/include/geos/operation/overlayng/Edge.h +++ b/include/geos/operation/overlayng/Edge.h @@ -184,18 +184,6 @@ private: public: -#if 0 - Edge() - : aDim(OverlayLabel::DIM_UNKNOWN) - , aDepthDelta(0) - , aIsHole(false) - , bDim(OverlayLabel::DIM_UNKNOWN) - , bDepthDelta(0) - , bIsHole(false) - , pts(nullptr) - {}; -#endif - Edge(const std::shared_ptr& p_pts, const EdgeSourceInfo* info, bool isCurved); friend std::ostream& operator<<(std::ostream& os, const Edge& e); diff --git a/include/geos/operation/overlayng/LineBuilder.h b/include/geos/operation/overlayng/LineBuilder.h index 2abeda3be..9256f8751 100644 --- a/include/geos/operation/overlayng/LineBuilder.h +++ b/include/geos/operation/overlayng/LineBuilder.h @@ -77,7 +77,7 @@ private: const geom::GeometryFactory* geometryFactory; bool hasResultArea; int8_t inputAreaIndex; - std::vector> lines; + std::vector> lines; /** * Indicates whether intersections are allowed to produce @@ -121,7 +121,7 @@ private: void addResultLines(); void addResultLinesMerged(); - std::unique_ptr toLine(OverlayEdge* edge) const; + std::unique_ptr toLine(OverlayEdge* edge) const; void addResultLinesForNodes(); @@ -143,7 +143,7 @@ private: * E.g. using the direction of the majority of segments, * or preferring the direction of the A edges.) */ - std::unique_ptr buildLine(OverlayEdge* node); + std::unique_ptr buildLine(OverlayEdge* node) const; /* * Finds the next edge around a node which forms @@ -173,7 +173,7 @@ public: LineBuilder(const LineBuilder&) = delete; LineBuilder& operator=(const LineBuilder&) = delete; - std::vector> getLines(); + std::vector> getLines(); void setStrictMode(bool p_isStrictResultMode) { diff --git a/include/geos/operation/overlayng/OverlayEdge.h b/include/geos/operation/overlayng/OverlayEdge.h index 7ad5bdf9e..85dc54fb2 100644 --- a/include/geos/operation/overlayng/OverlayEdge.h +++ b/include/geos/operation/overlayng/OverlayEdge.h @@ -61,6 +61,7 @@ private: * The label must be interpreted accordingly. */ bool direction; + bool m_isCurved; CoordinateXY dirPt; OverlayLabel* label; bool m_isInResultArea; @@ -81,10 +82,12 @@ public: OverlayEdge(const CoordinateXYZM& p_orig, const CoordinateXY& p_dirPt, bool p_direction, OverlayLabel* p_label, - const std::shared_ptr& p_pts) + const std::shared_ptr& p_pts, + bool isCurved) : HalfEdge(p_orig) , pts(p_pts) , direction(p_direction) + , m_isCurved(isCurved) , dirPt(p_dirPt) , label(p_label) , m_isInResultArea(false) @@ -103,6 +106,11 @@ public: return direction; }; + bool isCurved() const + { + return m_isCurved; + } + const CoordinateXY& directionPt() const override { return dirPt; diff --git a/include/geos/operation/overlayng/OverlayEdgeRing.h b/include/geos/operation/overlayng/OverlayEdgeRing.h index f0e94445a..c3472c15e 100644 --- a/include/geos/operation/overlayng/OverlayEdgeRing.h +++ b/include/geos/operation/overlayng/OverlayEdgeRing.h @@ -30,9 +30,9 @@ namespace geom { class Coordinate; class CoordinateXY; class CoordinateSequence; +class Curve; class GeometryFactory; -class LinearRing; -class Polygon; +class Surface; } namespace operation { namespace overlayng { @@ -50,8 +50,8 @@ class GEOS_DLL OverlayEdgeRing { using CoordinateXY = geos::geom::CoordinateXY; using CoordinateSequence = geos::geom::CoordinateSequence; using GeometryFactory = geos::geom::GeometryFactory; - using LinearRing = geos::geom::LinearRing; - using Polygon = geos::geom::Polygon; + using Curve = geos::geom::Curve; + using Surface = geos::geom::Surface; using PointOnGeometryLocator = algorithm::locate::PointOnGeometryLocator; using IndexedPointInAreaLocator = algorithm::locate::IndexedPointInAreaLocator; @@ -59,23 +59,22 @@ private: // Members OverlayEdge* startEdge; - std::unique_ptr ring; + std::unique_ptr ring; bool m_isHole; - mutable std::unique_ptr locator; + mutable std::unique_ptr locator; OverlayEdgeRing* shell; // a list of EdgeRings which are holes in this EdgeRing std::vector holes; // Methods - void computeRingPts(OverlayEdge* start, CoordinateSequence& pts); - void computeRing(const std::shared_ptr & ringPts, const GeometryFactory* geometryFactory); + void computeRing(OverlayEdge* start, const GeometryFactory* geometryFactory); + std::unique_ptr computeRingGeometry(OverlayEdge* start, const GeometryFactory* geometryFactory) const; /** * Computes the list of coordinates which are contained in this ring. * The coordinates are computed once only and cached. * @return an array of the {@link Coordinate}s in this ring */ - const CoordinateSequence& getCoordinates() const; PointOnGeometryLocator* getLocator() const; static void closeRing(CoordinateSequence& pts); bool contains(const OverlayEdgeRing& otherRing) const; @@ -86,8 +85,8 @@ public: OverlayEdgeRing(OverlayEdge* start, const GeometryFactory* geometryFactory); - std::unique_ptr getRing(); - const LinearRing* getRingPtr() const; + std::unique_ptr getRing(); + const Curve* getRingPtr() const; const geom::Envelope& getEnvelope() const; @@ -128,7 +127,7 @@ public: * Computes the {@link Polygon} formed by this ring and any contained holes. * @return the {@link Polygon} formed by this ring and its holes. */ - std::unique_ptr toPolygon(const GeometryFactory* factory); + std::unique_ptr toSurface(const GeometryFactory* factory); OverlayEdge* getEdge(); diff --git a/include/geos/operation/overlayng/OverlayGraph.h b/include/geos/operation/overlayng/OverlayGraph.h index eda4d07ed..85a46d97a 100644 --- a/include/geos/operation/overlayng/OverlayGraph.h +++ b/include/geos/operation/overlayng/OverlayGraph.h @@ -70,13 +70,13 @@ private: * Create and add HalfEdge pairs to map and vector containers, * using local std::deque storage for objects. */ - OverlayEdge* createEdgePair(const std::shared_ptr &pts, OverlayLabel *lbl); + OverlayEdge* createEdgePair(const std::shared_ptr &pts, OverlayLabel *lbl, bool isCurved); /** * Create a single OverlayEdge in local std::deque storage, and return the * pointer. */ - OverlayEdge* createOverlayEdge(const std::shared_ptr &pts, OverlayLabel *lbl, bool direction); + OverlayEdge* createOverlayEdge(const std::shared_ptr &pts, OverlayLabel *lbl, bool direction, bool isCurved); void insert(OverlayEdge* e); diff --git a/include/geos/operation/overlayng/OverlayMixedPoints.h b/include/geos/operation/overlayng/OverlayMixedPoints.h index c7b681228..21907ad74 100644 --- a/include/geos/operation/overlayng/OverlayMixedPoints.h +++ b/include/geos/operation/overlayng/OverlayMixedPoints.h @@ -40,6 +40,9 @@ namespace locate { class PointOnGeometryLocator; } } +namespace noding { +class Noder; +} } namespace geos { // geos. @@ -74,6 +77,7 @@ namespace overlayng { // geos.operation.overlayng * @author Martin Davis */ class GEOS_DLL OverlayMixedPoints { + using Curve = geos::geom::Curve; using GeometryFactory = geos::geom::GeometryFactory; using PrecisionModel = geos::geom::PrecisionModel; using Geometry = geos::geom::Geometry; @@ -83,6 +87,8 @@ class GEOS_DLL OverlayMixedPoints { using Point = geos::geom::Point; using Polygon = geos::geom::Polygon; using LineString = geos::geom::LineString; + using Noder = geos::noding::Noder; + using Surface = geos::geom::Surface; using PointOnGeometryLocator = algorithm::locate::PointOnGeometryLocator; private: @@ -94,6 +100,7 @@ private: const Geometry* geomNonPointInput; const GeometryFactory* geometryFactory; bool isPointRHS; + Noder* noder; std::unique_ptr geomNonPoint; int geomNonPointDim; @@ -121,19 +128,19 @@ private: std::unique_ptr copyNonPoint() const; - std::unique_ptr extractCoordinates(const Geometry* points, const PrecisionModel* pm) const; + static std::unique_ptr extractCoordinates(const Geometry* points, const PrecisionModel* pm); - std::vector> extractPolygons(const Geometry* geom) const; + static std::vector> extractPolygons(const Geometry* geom); - std::vector> extractLines(const Geometry* geom) const; + static std::vector> extractLines(const Geometry* geom); public: - OverlayMixedPoints(int p_opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* p_pm); + OverlayMixedPoints(int p_opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* p_pm, Noder* p_noder); - static std::unique_ptr overlay(int opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* pm); + static std::unique_ptr overlay(int opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* pm, Noder* p_noder); std::unique_ptr getResult(); diff --git a/include/geos/operation/overlayng/OverlayUtil.h b/include/geos/operation/overlayng/OverlayUtil.h index 6bff9c358..25fa99312 100644 --- a/include/geos/operation/overlayng/OverlayUtil.h +++ b/include/geos/operation/overlayng/OverlayUtil.h @@ -14,10 +14,10 @@ #pragma once -#include -#include -#include +#include #include +#include +#include #include @@ -55,10 +55,10 @@ class GEOS_DLL OverlayUtil { using Geometry = geos::geom::Geometry; using Coordinate = geos::geom::Coordinate; using CoordinateSequence = geos::geom::CoordinateSequence; + using Curve = geos::geom::Curve; using Envelope = geos::geom::Envelope; using Point = geos::geom::Point; - using LineString = geos::geom::LineString; - using Polygon = geos::geom::Polygon; + using Surface = geos::geom::Surface; using GeometryFactory = geos::geom::GeometryFactory; using PrecisionModel = geos::geom::PrecisionModel; @@ -172,8 +172,8 @@ public: * Creates an overlay result geometry for homogeneous or mixed components. */ static std::unique_ptr createResultGeometry( - std::vector>& resultPolyList, - std::vector>& resultLineList, + std::vector>& resultPolyList, + std::vector>& resultLineList, std::vector>& resultPointList, const GeometryFactory* geometryFactory); diff --git a/include/geos/operation/overlayng/PolygonBuilder.h b/include/geos/operation/overlayng/PolygonBuilder.h index 9e820fc14..f30b43226 100644 --- a/include/geos/operation/overlayng/PolygonBuilder.h +++ b/include/geos/operation/overlayng/PolygonBuilder.h @@ -29,7 +29,7 @@ namespace geos { namespace geom { class GeometryFactory; class Geometry; -class Polygon; +class Surface; } namespace operation { namespace overlayng { @@ -55,7 +55,7 @@ private: // Storage std::vector> vecOER; - std::vector> computePolygons(const std::vector& shellList) const; + std::vector> computePolygons(const std::vector& shellList) const; void buildRings(const std::vector& resultAreaEdges); @@ -138,7 +138,7 @@ public: PolygonBuilder& operator=(const PolygonBuilder&) = delete; // Methods - std::vector> getPolygons() const; + std::vector> getPolygons() const; std::vector getShellRings() const; diff --git a/src/algorithm/PointLocator.cpp b/src/algorithm/PointLocator.cpp index 5545d7655..abcee3e12 100644 --- a/src/algorithm/PointLocator.cpp +++ b/src/algorithm/PointLocator.cpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include #include @@ -31,7 +33,6 @@ #include #include -#include using namespace geos::geom; @@ -97,6 +98,16 @@ PointLocator::computeLocation(const CoordinateXY& p, const Geometry* geom) updateLocationInfo(locate(p, ls)); break; } + case GEOS_CIRCULARSTRING: { + const CircularString* cs = static_cast(geom); + updateLocationInfo(locate(p, cs)); + break; + } + case GEOS_COMPOUNDCURVE: { + const CompoundCurve* cc = static_cast(geom); + updateLocationInfo(locate(p, cc)); + break; + } case GEOS_POLYGON: { const Polygon* po = static_cast(geom); updateLocationInfo(locate(p, po)); @@ -183,6 +194,73 @@ PointLocator::locate(const CoordinateXY& p, const LineString* l) return Location::EXTERIOR; } +/* private */ +Location +PointLocator::locate(const CoordinateXY& p, const CircularString* cs) +{ + if(!cs->getEnvelopeInternal()->intersects(p)) { + return Location::EXTERIOR; + } + + if (cs->isClosed()) { + const CoordinateSequence* seq = cs->getCoordinatesRO(); + if (p.equals2D(seq->front())) { + return Location::BOUNDARY; + } + if (p.equals2D(seq->back())) { + return Location::BOUNDARY; + } + } + + for (const auto& arc : cs->getArcs()) { + if (arc.containsPoint(p)) { + return Location::INTERIOR; + } + } + + return Location::EXTERIOR; +} + +/* private */ +Location +PointLocator::locate(const CoordinateXY& p, const CompoundCurve* cc) +{ + if(!cc->getEnvelopeInternal()->intersects(p)) { + return Location::EXTERIOR; + } + + if (cc->isClosed()) { + const SimpleCurve* firstCurve = cc->getCurveN(0); + if (p.equals2D(firstCurve->getCoordinatesRO()->front())) { + return Location::BOUNDARY; + } + + const SimpleCurve* lastCurve = cc->getCurveN(cc->getNumCurves() - 1); + if (p.equals2D(lastCurve->getCoordinatesRO()->back())) { + return Location::BOUNDARY; + } + } + + for (std::size_t i = 0; i < cc->getNumCurves(); i++) { + const SimpleCurve* curve = cc->getCurveN(i); + if (curve->getGeometryTypeId() == GEOS_CIRCULARSTRING) { + const CircularString* cs = static_cast(curve); + for (const auto& arc : cs->getArcs()) { + if (arc.containsPoint(p)) { + return Location::INTERIOR; + } + } + } else { + const CoordinateSequence* seq = curve->getCoordinatesRO(); + if(PointLocation::isOnLine(p, seq)) { + return Location::INTERIOR; + } + } + } + + return Location::EXTERIOR; +} + /* private */ Location PointLocator::locateInPolygonRing(const CoordinateXY& p, const Curve* ring) diff --git a/src/geom/Curve.cpp b/src/geom/Curve.cpp index a55678267..26b7dc533 100644 --- a/src/geom/Curve.cpp +++ b/src/geom/Curve.cpp @@ -48,6 +48,12 @@ Curve::apply_rw(GeometryFilter* filter) filter->filter_rw(this); } +std::unique_ptr +Curve::clone() const +{ + return std::unique_ptr(static_cast(Geometry::clone().release())); +} + bool Curve::isRing() const { diff --git a/src/geom/GeometryFactory.cpp b/src/geom/GeometryFactory.cpp index f53012a4c..cc5e97870 100644 --- a/src/geom/GeometryFactory.cpp +++ b/src/geom/GeometryFactory.cpp @@ -642,6 +642,13 @@ const return std::unique_ptr(new CurvePolygon(std::move(shell), std::move(holes), *this)); } +static std::unique_ptr +linearRingToLineString(std::unique_ptr linearRing) +{ + const std::shared_ptr pts = detail::down_cast(linearRing.get())->getSharedCoordinates(); + return linearRing->getFactory()->createLineString(pts); +} + /* public */ std::unique_ptr GeometryFactory::createSurface(std::unique_ptr && shell) @@ -652,6 +659,10 @@ const return createPolygon(std::move(shellLR)); } + if (shell->getGeometryTypeId() == GEOS_LINEARRING) { + shell = linearRingToLineString(std::move(shell)); + } + return createCurvePolygon(std::move(shell)); } @@ -675,6 +686,17 @@ const return createPolygon(std::move(shellLR), std::move(holesLR)); } + // Purge LinearRings + if (shell->getGeometryTypeId() == GEOS_LINEARRING) { + shell = linearRingToLineString(std::move(shell)); + } + + for (std::size_t i = 0; i < holes.size(); i++) { + if (holes[i]->getGeometryTypeId() == GEOS_LINEARRING) { + holes[i] = linearRingToLineString(std::move(holes[i])); + } + } + return createCurvePolygon(std::move(shell), std::move(holes)); } diff --git a/src/noding/snap/SnappingNoder.cpp b/src/noding/snap/SnappingNoder.cpp index f4848d1fc..e2f0643d6 100644 --- a/src/noding/snap/SnappingNoder.cpp +++ b/src/noding/snap/SnappingNoder.cpp @@ -111,9 +111,8 @@ SnappingNoder::snap(const CoordinateSequence *cs) snapCoords->reserve(cs->size()); cs->forEach([&snapCoords, this](const Coordinate& origPt) { - const Coordinate& pt = snapIndex.snap(origPt); + const Coordinate& pt = origPt.isValid() ? snapIndex.snap(origPt) : origPt; snapCoords->add(pt, false); // Remove repeated points - }); return snapCoords; } diff --git a/src/operation/overlayng/IndexedPointOnLineLocator.cpp b/src/operation/overlayng/IndexedPointOnLineLocator.cpp index 95a7e12da..b521145cb 100644 --- a/src/operation/overlayng/IndexedPointOnLineLocator.cpp +++ b/src/operation/overlayng/IndexedPointOnLineLocator.cpp @@ -29,7 +29,7 @@ namespace overlayng { // geos.operation.overlayng /*public*/ geom::Location IndexedPointOnLineLocator::locate(const geom::CoordinateXY* p) { - // TODO: optimize this with a segment index + // TODO: optimize this with a segment/arc index algorithm::PointLocator locator; return locator.locate(*p, &inputGeom); } diff --git a/src/operation/overlayng/InputGeometry.cpp b/src/operation/overlayng/InputGeometry.cpp index 6c3c5a2ba..320d887f0 100644 --- a/src/operation/overlayng/InputGeometry.cpp +++ b/src/operation/overlayng/InputGeometry.cpp @@ -13,6 +13,7 @@ **********************************************************************/ #include +#include namespace geos { // geos namespace operation { // geos.operation @@ -21,6 +22,7 @@ namespace overlayng { // geos.operation.overlayng using geos::geom::Location; using geos::geom::Geometry; using geos::geom::Envelope; +using geos::algorithm::locate::SimplePointInAreaLocator; using geos::algorithm::locate::IndexedPointInAreaLocator; using geos::algorithm::locate::PointOnGeometryLocator; @@ -166,13 +168,23 @@ PointOnGeometryLocator* InputGeometry::getLocator(uint8_t geomIndex) { if (geomIndex == 0) { - if (ptLocatorA == nullptr) - ptLocatorA.reset(new IndexedPointInAreaLocator(*getGeometry(geomIndex))); + if (ptLocatorA == nullptr) { + if (geom[0]->hasCurvedComponents()) { + ptLocatorA = std::make_unique(*getGeometry(geomIndex)); + } else { + ptLocatorA = std::make_unique(*getGeometry(geomIndex)); + } + } return ptLocatorA.get(); } else { - if (ptLocatorB == nullptr) - ptLocatorB.reset(new IndexedPointInAreaLocator(*getGeometry(geomIndex))); + if (ptLocatorB == nullptr) { + if (geom[1]->hasCurvedComponents()) { + ptLocatorB = std::make_unique(*getGeometry(geomIndex)); + } else { + ptLocatorB = std::make_unique(*getGeometry(geomIndex)); + } + } return ptLocatorB.get(); } } diff --git a/src/operation/overlayng/LineBuilder.cpp b/src/operation/overlayng/LineBuilder.cpp index a676b36e5..4d1a25877 100644 --- a/src/operation/overlayng/LineBuilder.cpp +++ b/src/operation/overlayng/LineBuilder.cpp @@ -17,8 +17,10 @@ #include #include #include +#include #include #include +#include @@ -29,7 +31,7 @@ namespace overlayng { // geos.operation.overlayng using namespace geos::geom; /*public*/ -std::vector> +std::vector> LineBuilder::getLines() { markResultLines(); @@ -174,19 +176,22 @@ LineBuilder::addResultLines() } } -std::unique_ptr +std::unique_ptr LineBuilder::toLine(OverlayEdge* edge) const { // bool isForward = edge->isForward(); const auto* edgePts = edge->getCoordinatesRO(); - std::unique_ptr pts(new CoordinateSequence(0u, edgePts->hasZ(), edgePts->hasM())); + auto pts = std::make_unique(0u, edgePts->hasZ(), edgePts->hasM()); pts->reserve(edgePts->size()); pts->add(edge->orig(), false); edge->addCoordinates(pts.get()); assert(pts->size() == edgePts->size()); + if (edge->isCurved()) { + return geometryFactory->createCircularString(std::move(pts)); + } return geometryFactory->createLineString(std::move(pts)); } @@ -217,7 +222,7 @@ LineBuilder::addResultLinesForNodes() * This will find all lines originating at nodes */ if (degreeOfLines(edge) != 2) { - std::unique_ptr line = buildLine(edge); + auto line = buildLine(edge); lines.push_back(std::move(line)); } } @@ -241,20 +246,24 @@ LineBuilder::addResultLinesRings() } /*private*/ -std::unique_ptr -LineBuilder::buildLine(OverlayEdge* node) +std::unique_ptr +LineBuilder::buildLine(OverlayEdge* node) const { + const bool constructZ = node->getCoordinatesRO()->hasZ(); + const bool constructM = node->getCoordinatesRO()->hasM(); + geom::util::CurveBuilder cb(*geometryFactory, constructZ, constructM); + // assert: edgeStart degree = 1 // assert: edgeStart direction = forward - std::unique_ptr pts(new CoordinateSequence()); - pts->add(node->orig(), false); + cb.add(*node->getCoordinatesRO(), node->isCurved()); bool isNodeForward = node->isForward(); OverlayEdge* e = node; do { e->markVisitedBoth(); - e->addCoordinates(pts.get()); + CoordinateSequence& pts = cb.getSeq(e->isCurved()); + e->addCoordinates(&pts); // end line if next vertex is a node if (degreeOfLines(e->symOE()) != 2) { @@ -264,12 +273,14 @@ LineBuilder::buildLine(OverlayEdge* node) // e will be nullptr if next edge has been visited, which indicates a ring } while (e != nullptr); + // reverse coordinates before constructing + auto geom = cb.getGeometry(); if(!isNodeForward) { - pts->reverse(); + return geom->reverse(); } - return geometryFactory->createLineString(std::move(pts)); + return geom; } /*private*/ diff --git a/src/operation/overlayng/OverlayEdgeRing.cpp b/src/operation/overlayng/OverlayEdgeRing.cpp index 1587f9e0c..12f4c8e92 100644 --- a/src/operation/overlayng/OverlayEdgeRing.cpp +++ b/src/operation/overlayng/OverlayEdgeRing.cpp @@ -17,11 +17,18 @@ #include #include #include +#include #include +#include +#include +#include #include +#include #include +#include #include #include +#include #include #include @@ -32,8 +39,7 @@ namespace overlayng { // geos.operation.overlayng using namespace geos::geom; using geos::operation::polygonize::EdgeRing; using geos::algorithm::locate::PointOnGeometryLocator; - - +using geos::algorithm::locate::SimplePointInAreaLocator; OverlayEdgeRing::OverlayEdgeRing(OverlayEdge* start, const GeometryFactory* geometryFactory) : startEdge(start) @@ -42,19 +48,17 @@ OverlayEdgeRing::OverlayEdgeRing(OverlayEdge* start, const GeometryFactory* geom , locator(nullptr) , shell(nullptr) { - auto ringPts = std::make_shared(0u, start->getCoordinatesRO()->hasZ(), start->getCoordinatesRO()->hasM()); - computeRingPts(start, *ringPts); - computeRing(ringPts, geometryFactory); + computeRing(start, geometryFactory); } /*public*/ -std::unique_ptr +std::unique_ptr OverlayEdgeRing::getRing() { return std::move(ring); } -const LinearRing* +const Curve* OverlayEdgeRing::getRingPtr() const { return ring.get(); @@ -133,46 +137,49 @@ OverlayEdgeRing::closeRing(CoordinateSequence& pts) } /*private*/ -void -OverlayEdgeRing::computeRingPts(OverlayEdge* start, CoordinateSequence& pts) +std::unique_ptr +OverlayEdgeRing::computeRingGeometry(OverlayEdge* start, const GeometryFactory* gfact) const { OverlayEdge* edge = start; + + const bool hasZ = start->getCoordinatesRO()->hasZ(); + const bool hasM = start->getCoordinatesRO()->hasM(); + + geom::util::CurveBuilder cb(*gfact, hasZ, hasM); + do { if (edge->getEdgeRing() == this) - throw util::TopologyException("Edge visited twice during ring-building", edge->getCoordinate()); + throw geos::util::TopologyException("Edge visited twice during ring-building", edge->getCoordinate()); // only valid for polygonal output + CoordinateSequence& pts = cb.getSeq(edge->isCurved()); edge->addCoordinates(&pts); edge->setEdgeRing(this); + if (edge->nextResult() == nullptr) - throw util::TopologyException("Found null edge in ring", edge->dest()); + throw geos::util::TopologyException("Found null edge in ring", edge->dest()); edge = edge->nextResult(); } while (edge != start); - closeRing(pts); + + cb.closeRing(); + return cb.getGeometry(); } /*private*/ void -OverlayEdgeRing::computeRing(const std::shared_ptr & p_ringPts, const GeometryFactory* geometryFactory) +OverlayEdgeRing::computeRing(OverlayEdge* start, const GeometryFactory* geometryFactory) { if (ring != nullptr) return; // don't compute more than once - ring = geometryFactory->createLinearRing(p_ringPts); - m_isHole = algorithm::Orientation::isCCW(ring->getCoordinatesRO()); -} + ring = computeRingGeometry(start, geometryFactory); -/** -* Computes the list of coordinates which are contained in this ring. -* The coordinates are computed once only and cached. -* -* @return an array of the {@link Coordinate}s in this ring -*/ -/*private*/ -const CoordinateSequence& -OverlayEdgeRing::getCoordinates() const -{ - return *ring->getCoordinatesRO(); + if (ring->getGeometryTypeId() == GEOS_COMPOUNDCURVE) { + const auto seq = ring->getCoordinates(); + m_isHole = algorithm::Orientation::isCCW(seq.get()); + } else { + m_isHole = algorithm::Orientation::isCCW(static_cast(ring.get())->getCoordinatesRO()); + } } /** @@ -209,12 +216,33 @@ OverlayEdgeRing::findEdgeRingContaining(const std::vector& erL return minContainingRing; } +// Adapter class to check whether a point lies within a ring. +// Unlike IndexedPointInAreaLocator, SimplePointInAreaLocator does not treat +// closed rings as areas. +class PointInCurvedRingLocator : public algorithm::locate::PointOnGeometryLocator { +public: + explicit PointInCurvedRingLocator(const Curve& ring) : m_ring(ring) {} + + geom::Location locate(const geom::CoordinateXY* p) override { + return algorithm::PointLocation::locateInRing(*p, m_ring); + } + +private: + const Curve& m_ring; +}; + /*private*/ PointOnGeometryLocator* OverlayEdgeRing::getLocator() const { if (locator == nullptr) { - locator.reset(new IndexedPointInAreaLocator(*(getRingPtr()))); + const Curve* p_ring = getRingPtr(); + if (p_ring->hasCurvedComponents()) { + locator = detail::make_unique(*p_ring); + } else { + locator = detail::make_unique(*p_ring); + } + } return locator.get(); } @@ -252,24 +280,51 @@ OverlayEdgeRing::contains(const OverlayEdgeRing& otherRing) const { bool OverlayEdgeRing::isPointInOrOut(const OverlayEdgeRing& otherRing) const { // in most cases only one or two points will be checked - for (const CoordinateXY& pt : otherRing.getCoordinates().items()) { - geom::Location loc = locate(pt); - if (loc == geom::Location::INTERIOR) { - return true; + + struct PointTester : public geom::CoordinateFilter { + + public: + explicit PointTester(const OverlayEdgeRing* p_ring) : m_er(p_ring), m_result(false) {} + + void filter_ro(const CoordinateXY* pt) override { + Location loc = m_er->locate(*pt); + + if (loc == geom::Location::INTERIOR) { + m_done = true; + m_result = true; + } else if (loc == geom::Location::EXTERIOR) { + m_done = true; + m_result = false; + } + + // pt is on BOUNDARY, so keep checking for a determining location } - if (loc == geom::Location::EXTERIOR) { - return false; + + bool isDone() const override { + return m_done; } - // pt is on BOUNDARY, so keep checking for a determining location - } - return false; + + bool getResult() const { + return m_result; + } + + private: + const OverlayEdgeRing* m_er; + bool m_done{false}; + bool m_result; + }; + + PointTester tester(this); + otherRing.getRingPtr()->apply_ro(&tester); + + return tester.getResult(); } /*public*/ const Coordinate& OverlayEdgeRing::getCoordinate() const { - return ring->getCoordinatesRO()->getAt(0); + return *detail::down_cast(ring->getCoordinate()); } /** @@ -277,20 +332,19 @@ OverlayEdgeRing::getCoordinate() const * @return the {@link Polygon} formed by this ring and its holes. */ /*public*/ -std::unique_ptr -OverlayEdgeRing::toPolygon(const GeometryFactory* factory) +std::unique_ptr +OverlayEdgeRing::toSurface(const GeometryFactory* factory) { if (holes.empty()) { - return factory->createPolygon(std::move(ring)); - } else { - std::vector> holeLR(holes.size()); - - for (std::size_t i = 0; i < holes.size(); i++) { - holeLR[i] = holes[i]->getRing(); - } - - return factory->createPolygon(std::move(ring), std::move(holeLR)); + return factory->createSurface(std::move(ring)); } + + std::vector> holeCurves(holes.size()); + for (std::size_t i = 0; i < holes.size(); i++) { + holeCurves[i] = holes[i]->getRing(); + } + + return factory->createSurface(std::move(ring), std::move(holeCurves)); } /*public*/ diff --git a/src/operation/overlayng/OverlayGraph.cpp b/src/operation/overlayng/OverlayGraph.cpp index a58983e21..7fd39fefd 100644 --- a/src/operation/overlayng/OverlayGraph.cpp +++ b/src/operation/overlayng/OverlayGraph.cpp @@ -14,9 +14,11 @@ #include +#include #include #include #include +#include #ifndef GEOS_DEBUG #define GEOS_DEBUG 0 @@ -84,7 +86,7 @@ OverlayGraph::addEdge(Edge* edge) { // CoordinateSequence* pts = = edge->getCoordinates().release(); std::shared_ptr pts = edge->releaseCoordinates(); - OverlayEdge* e = createEdgePair(pts, createOverlayLabel(edge)); + OverlayEdge* e = createEdgePair(pts, createOverlayLabel(edge), edge->isCurved()); #if GEOS_DEBUG std::cerr << "added edge: " << *e << std::endl; #endif @@ -95,32 +97,52 @@ OverlayGraph::addEdge(Edge* edge) /*private*/ OverlayEdge* -OverlayGraph::createEdgePair(const std::shared_ptr& pts, OverlayLabel *lbl) +OverlayGraph::createEdgePair(const std::shared_ptr& pts, OverlayLabel *lbl, bool isCurved) { - OverlayEdge* e0 = createOverlayEdge(pts, lbl, true); - OverlayEdge* e1 = createOverlayEdge(pts, lbl, false); + OverlayEdge* e0 = createOverlayEdge(pts, lbl, true, isCurved); + OverlayEdge* e1 = createOverlayEdge(pts, lbl, false, isCurved); e0->link(e1); return e0; } +static CoordinateXY +getDirectionPoint(const CoordinateSequence& pts, bool forward, bool isCurved) +{ + if (isCurved) { + assert(pts.size() >= 3); + if (forward) { + CircularArc arc(pts, 0); + return arc.getDirectionPoint(); + } else { + CircularArc arc(pts, pts.size() - 3); + return algorithm::CircularArcs::getDirectionPoint(arc.getCenter(), arc.getRadius(), arc.theta2(), !arc.isCCW()); + } + } + + assert(pts.size() >= 2); + if (forward) { + return pts.getAt(1); + } + + return pts.getAt(pts.size() - 2); +} + /*private*/ OverlayEdge* -OverlayGraph::createOverlayEdge(const std::shared_ptr& pts, OverlayLabel* lbl, bool direction) +OverlayGraph::createOverlayEdge(const std::shared_ptr& pts, OverlayLabel* lbl, bool direction, bool isCurved) { + assert(pts->size() >= 2); + CoordinateXYZM origin; - CoordinateXYZM dirPt; + const CoordinateXY dirPt = getDirectionPoint(*pts, direction, isCurved); if (direction) { pts->getAt(0, origin); - pts->getAt(1, dirPt); } else { - assert(pts->size() > 0); - std::size_t ilast = pts->size() - 1; - pts->getAt(ilast, origin); - pts->getAt(ilast-1, dirPt); + pts->getAt(pts->size() - 1, origin); } - ovEdgeQue.emplace_back(origin, dirPt, direction, lbl, pts); + ovEdgeQue.emplace_back(origin, dirPt, direction, lbl, pts, isCurved); OverlayEdge& ove = ovEdgeQue.back(); return &ove; } diff --git a/src/operation/overlayng/OverlayMixedPoints.cpp b/src/operation/overlayng/OverlayMixedPoints.cpp index 62492d6b7..d5a0b86e2 100644 --- a/src/operation/overlayng/OverlayMixedPoints.cpp +++ b/src/operation/overlayng/OverlayMixedPoints.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,7 @@ namespace overlayng { // geos.operation.overlayng using namespace geos::geom; using geos::algorithm::locate::PointOnGeometryLocator; using geos::algorithm::locate::IndexedPointInAreaLocator; +using geos::algorithm::locate::SimplePointInAreaLocator; /** * @brief Extracts and rounds coordinates from a geometry @@ -76,10 +78,11 @@ private: }; /*public*/ -OverlayMixedPoints::OverlayMixedPoints(int p_opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* p_pm) +OverlayMixedPoints::OverlayMixedPoints(int p_opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* p_pm, Noder* p_noder) : opCode(p_opCode) , pm(p_pm ? p_pm : geom0->getPrecisionModel()) , geometryFactory(geom0->getFactory()) + , noder(p_noder) , resultDim(OverlayUtil::resultDimension(opCode, geom0->getDimension(), geom1->getDimension())) { // name the dimensional geometries @@ -97,9 +100,9 @@ OverlayMixedPoints::OverlayMixedPoints(int p_opCode, const Geometry* geom0, cons /*public static*/ std::unique_ptr -OverlayMixedPoints::overlay(int opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* pm) +OverlayMixedPoints::overlay(int opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* pm, Noder* p_noder) { - OverlayMixedPoints overlay(opCode, geom0, geom1, pm); + OverlayMixedPoints overlay(opCode, geom0, geom1, pm, p_noder); return overlay.getResult(); } @@ -137,8 +140,11 @@ std::unique_ptr OverlayMixedPoints::createLocator(const Geometry* p_geomNonPoint) { if (geomNonPointDim == 2) { - std::unique_ptr ipial(new IndexedPointInAreaLocator(*p_geomNonPoint)); - return ipial; + if (p_geomNonPoint->hasCurvedComponents()) { + return std::make_unique(*p_geomNonPoint); + } else { + return std::make_unique(*p_geomNonPoint); + } } else { std::unique_ptr ipoll(new IndexedPointOnLineLocator(*p_geomNonPoint)); @@ -159,7 +165,7 @@ OverlayMixedPoints::prepareNonPoint(const Geometry* geomInput) return geomInput->clone(); } // Node and round the non-point geometry for output - return OverlayNG::geomunion(geomInput, pm); + return OverlayNG::geomunion(geomInput, pm, noder); } /*private*/ @@ -175,11 +181,11 @@ std::unique_ptr OverlayMixedPoints::computeUnion(const CoordinateSequence* coords) { std::vector> resultPointList = findPoints(false, coords); - std::vector> resultLineList; + std::vector> resultLineList; if (geomNonPointDim == 1) { resultLineList = extractLines(geomNonPoint.get()); } - std::vector> resultPolyList; + std::vector> resultPolyList; if (geomNonPointDim == 2) { resultPolyList = extractPolygons(geomNonPoint.get()); } @@ -274,7 +280,7 @@ OverlayMixedPoints::hasLocation(bool isCovered, const CoordinateXY& coord) const /*private*/ std::unique_ptr -OverlayMixedPoints::extractCoordinates(const Geometry* points, const PrecisionModel* p_pm) const +OverlayMixedPoints::extractCoordinates(const Geometry* points, const PrecisionModel* p_pm) { auto coords = detail::make_unique(0u, points->hasZ(), points->hasM()); coords->reserve(points->getNumPoints()); @@ -285,12 +291,12 @@ OverlayMixedPoints::extractCoordinates(const Geometry* points, const PrecisionMo } /*private*/ -std::vector> -OverlayMixedPoints::extractPolygons(const Geometry* geom) const +std::vector> +OverlayMixedPoints::extractPolygons(const Geometry* geom) { - std::vector> list; + std::vector> list; for (std::size_t i = 0; i < geom->getNumGeometries(); i++) { - const Polygon* poly = static_cast(geom->getGeometryN(i)); + const Surface* poly = static_cast(geom->getGeometryN(i)); if(!poly->isEmpty()) { list.emplace_back(poly->clone()); } @@ -299,12 +305,12 @@ OverlayMixedPoints::extractPolygons(const Geometry* geom) const } /*private*/ -std::vector> -OverlayMixedPoints::extractLines(const Geometry* geom) const +std::vector> +OverlayMixedPoints::extractLines(const Geometry* geom) { - std::vector> list; + std::vector> list; for (std::size_t i = 0; i < geom->getNumGeometries(); i++) { - const LineString* line = static_cast(geom->getGeometryN(i)); + const Curve* line = static_cast(geom->getGeometryN(i)); if (! line->isEmpty()) { list.emplace_back(line->clone()); } diff --git a/src/operation/overlayng/OverlayNG.cpp b/src/operation/overlayng/OverlayNG.cpp index 43f952f15..70acfbe59 100644 --- a/src/operation/overlayng/OverlayNG.cpp +++ b/src/operation/overlayng/OverlayNG.cpp @@ -175,7 +175,7 @@ OverlayNG::getResult() } else if (! inputGeom.isSingle() && inputGeom.hasPoints()) { // handle Point-nonPoint inputs - result = OverlayMixedPoints::overlay(opCode, ig0, ig1, pm); + result = OverlayMixedPoints::overlay(opCode, ig0, ig1, pm, noder); } else { // handle case where both inputs are formed of edges (Lines and Polygons) @@ -309,10 +309,10 @@ OverlayNG::extractResult(int p_opCode, OverlayGraph* graph) //--- Build polygons std::vector resultAreaEdges = graph->getResultAreaEdges(); PolygonBuilder polyBuilder(resultAreaEdges, geomFact); - std::vector> resultPolyList = polyBuilder.getPolygons(); + std::vector> resultPolyList = polyBuilder.getPolygons(); bool hasResultAreaComponents = (!resultPolyList.empty()); - std::vector> resultLineList; + std::vector> resultLineList; std::vector> resultPointList; GEOS_CHECK_FOR_INTERRUPTS(); diff --git a/src/operation/overlayng/OverlayUtil.cpp b/src/operation/overlayng/OverlayUtil.cpp index aa7138a45..313837a34 100644 --- a/src/operation/overlayng/OverlayUtil.cpp +++ b/src/operation/overlayng/OverlayUtil.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -279,8 +280,8 @@ OverlayUtil::isResultAreaConsistent( /*public static*/ std::unique_ptr OverlayUtil::createResultGeometry( - std::vector>& resultPolyList, - std::vector>& resultLineList, + std::vector>& resultPolyList, + std::vector>& resultLineList, std::vector>& resultPointList, const GeometryFactory* geometryFactory) { @@ -305,16 +306,22 @@ OverlayUtil::createResultGeometry( std::unique_ptr OverlayUtil::toLines(OverlayGraph* graph, bool isOutputEdges, const GeometryFactory* geomFact) { - std::vector> lines; + std::vector> lines; std::vector& edges = graph->getEdges(); for (OverlayEdge* edge : edges) { bool includeEdge = isOutputEdges || edge->isInResultArea(); if (! includeEdge) continue; const auto& pts = edge->getCoordinatesOriented(); - std::unique_ptr line = geomFact->createLineString(pts); + + std::unique_ptr edgeGeom; + if (edge->isCurved()) { + edgeGeom = geomFact->createCircularString(pts); + } else { + edgeGeom = geomFact->createLineString(pts); + } // line->setUserData(labelForResult(edge)); - lines.push_back(std::move(line)); + lines.push_back(std::move(edgeGeom)); } return geomFact->buildGeometry(std::move(lines)); } diff --git a/src/operation/overlayng/PolygonBuilder.cpp b/src/operation/overlayng/PolygonBuilder.cpp index 8cc88d306..d134832a4 100644 --- a/src/operation/overlayng/PolygonBuilder.cpp +++ b/src/operation/overlayng/PolygonBuilder.cpp @@ -20,7 +20,7 @@ #include -using geos::geom::Polygon; +using geos::geom::Surface; namespace geos { // geos namespace operation { // geos.operation @@ -28,7 +28,7 @@ namespace overlayng { // geos.operation.overlayng /*public*/ -std::vector> +std::vector> PolygonBuilder::getPolygons() const { return computePolygons(shellList); @@ -42,14 +42,14 @@ PolygonBuilder::getShellRings() const } /*private*/ -std::vector> +std::vector> PolygonBuilder::computePolygons(const std::vector& shells) const { - std::vector> resultPolyList; + std::vector> resultPolyList; resultPolyList.reserve(shells.size()); // add Polygons for all shells for (OverlayEdgeRing* er : shells) { - std::unique_ptr poly = er->toPolygon(geometryFactory); + std::unique_ptr poly = er->toSurface(geometryFactory); resultPolyList.push_back(std::move(poly)); } return resultPolyList; diff --git a/tests/unit/operation/overlayng/OverlayNGTest.cpp b/tests/unit/operation/overlayng/OverlayNGTest.cpp index ce7323121..92f1d32a1 100644 --- a/tests/unit/operation/overlayng/OverlayNGTest.cpp +++ b/tests/unit/operation/overlayng/OverlayNGTest.cpp @@ -6,12 +6,16 @@ // geos #include +#include +#include +#include // std #include using namespace geos::geom; using namespace geos::operation::overlayng; +using namespace geos::noding; using geos::io::WKTReader; using geos::io::WKTWriter; @@ -25,6 +29,13 @@ struct test_overlayng_data { WKTReader r; WKTWriter w; + double tol{0.0}; + + void + setEqualityTolerance(double d) + { + tol = d; + } void testOverlay(const std::string& a, const std::string& b, const std::string& expected, int opCode, double scaleFactor) @@ -38,10 +49,25 @@ struct test_overlayng_data { std::unique_ptr geom_a = r.read(a); std::unique_ptr geom_b = r.read(b); std::unique_ptr geom_expected = r.read(expected); - std::unique_ptr geom_result = OverlayNG::overlay(geom_a.get(), geom_b.get(), opCode, pm.get()); - // std::string wkt_result = w.write(geom_result.get()); - // std::cout << std::endl << wkt_result << std::endl; - ensure_equals_geometry(geom_expected.get(), geom_result.get()); + + + std::unique_ptr geom_result; + if (geom_a->hasCurvedComponents() || geom_b->hasCurvedComponents()) { + SimpleNoder noder; + geos::algorithm::CircularArcIntersector cai; + ArcIntersectionAdder aia(cai); + + noder.setArcIntersector(aia); + OverlayNG ong(geom_a.get(), geom_b.get(), pm.get(), opCode); + ong.setNoder(&noder); + + geom_result = ong.getResult(); + } else { + geom_result = OverlayNG::overlay(geom_a.get(), geom_b.get(), opCode, pm.get()); + } + //std::string wkt_result = w.write(geom_result.get()); + //std::cout << std::endl << wkt_result << std::endl; + ensure_equals_geometry_xyzm(geom_result.get(), geom_expected.get(), tol); } void @@ -59,7 +85,7 @@ struct test_overlayng_data { std::unique_ptr geom_result = OverlayNG::overlay(geom_a.get(), geom_b.get(), opCode, pm.get()); // std::string wkt_result = w.write(geom_result.get()); // std::cout << std::endl << wkt_result << std::endl; - ensure_equals_exact_geometry(geom_expected.get(), geom_result.get(), 0); + ensure_equals_exact_geometry(geom_expected.get(), geom_result.get(), tol); } void @@ -582,4 +608,197 @@ void object::test<45> () testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); } +template<> +template<> +void object::test<46> () +{ + set_test_name("CurvePolygon/CurvePolygon intersection -> CurvePolygon"); + + std::string a = "CURVEPOLYGON (COMPOUNDCURVE((10 0, 0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 15 5, 10 0)))"; + std::string b = "CURVEPOLYGON (COMPOUNDCURVE((10 10, 20 10, 20 0, 10 0), CIRCULARSTRING (10 0, 5 5, 10 10)))"; + std::string exp = "CURVEPOLYGON (CIRCULARSTRING (10 10, 15 5, 10 0, 5 5, 10 10))"; + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<47>() +{ + set_test_name("CurvePolygon/CurvePolygon symdifference -> MultiSurface"); + + std::string a = "CURVEPOLYGON (COMPOUNDCURVE((10 0, 0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 15 5, 10 0)))"; + std::string b = "CURVEPOLYGON (COMPOUNDCURVE((10 10, 20 10, 20 0, 10 0), CIRCULARSTRING (10 0, 5 5, 10 10)))"; + std::string exp = "MULTISURFACE (CURVEPOLYGON (COMPOUNDCURVE ((10 0, 0 0, 0 10, 10 10), CIRCULARSTRING (10 10, 5 5, 10 0))), CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (10 0, 15 5, 10 10), (10 10, 20 10, 20 0, 10 0))))"; + testOverlay(a, b, exp, OverlayNG::SYMDIFFERENCE, 0); +} + +template<> +template<> +void object::test<48>() +{ + set_test_name("Polygon/CircularString intersection -> CircularString"); + + std::string a = "POLYGON ((10 0, 10 10, 0 10, 0 0, 10 0))"; + std::string b = "CIRCULARSTRING (5 0, 10 5, 15 0)"; + std::string exp = "CIRCULARSTRING (5 0, 6.464466094067262 3.5355339059327378, 10 5)"; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<49>() +{ + set_test_name("CircularString / CircularString intersection -> MultiPoint"); + + std::string a = "CIRCULARSTRING (-5 0, 0 5, 5 0)"; + std::string b = "CIRCULARSTRING (-5 5, 0 0, 5 5)"; + std::string exp = "MULTIPOINT ((4.330127018922194 2.5), (-4.330127018922194 2.5))"; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<50>() +{ + set_test_name("CircularString / CircularString intersection -> MultiCurve"); + std::string a = "CIRCULARSTRING (-5 0, 0 5, 5 0)"; + std::string b = "CIRCULARSTRING (4 3, 0 -5, -4 3)"; + std::string exp = "MULTICURVE (CIRCULARSTRING (-5 0, -4.743416490252569 1.58113883008419, -4 3), CIRCULARSTRING (4 3, 4.743416490252569 1.5811388300841898, 5 0))"; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<51>() +{ + set_test_name("CircularString / CircularString difference -> CircularString"); + std::string a = "CIRCULARSTRING (-5 0, 0 5, 5 0)"; + std::string b = "CIRCULARSTRING (4 3, 0 -5, -4 3)"; + std::string exp = "CIRCULARSTRING (-4 3, 0 5, 4 3)"; + + setEqualityTolerance(1e-15); + testOverlay(a, b, exp, OverlayNG::DIFFERENCE, 0); +} + +template<> +template<> +void object::test<52>() +{ + set_test_name("CircularString / CircularString symdifference -> MultiCurve"); + std::string a = "CIRCULARSTRING (-5 0, 0 5, 5 0)"; + std::string b = "CIRCULARSTRING (4 3, 0 -5, -4 3)"; + std::string exp = "MULTICURVE (CIRCULARSTRING (-4 3, 0 5, 4 3), CIRCULARSTRING (5 0, 0 -5, -5 0))"; + + setEqualityTolerance(1e-15); + testOverlay(a, b, exp, OverlayNG::SYMDIFFERENCE, 0); +} + +template<> +template<> +void object::test<53>() +{ + set_test_name("CompoundCurves / LineString -> MultiPoint"); + + std::string a = "COMPOUNDCURVE ((-5 0, -1 0), CIRCULARSTRING (-1 0, 0 1, 1 0))"; + std::string b = "LINESTRING (-3 -1, -4 1, 1 0)"; + std::string exp = "MULTIPOINT ((-0.923076923076923 0.3846153846153846), (1 0), (-3.5 0))"; + + setEqualityTolerance(1e-15); + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<54>() +{ + set_test_name("CircularString / CircularString -> Point (tangent)"); + + std::string a = "CIRCULARSTRING (5 3, 5 0, 0 -5, -5 0, 4 3)"; + std::string b = "CIRCULARSTRING (-5 10, 0 15, 5 10, 4 7, -5 10)"; + std::string exp = "POINT (0 5)"; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<55>() +{ + set_test_name("Polygon / CurvePolygon -> CurvePolygon"); + + std::string a = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"; + std::string b = "CURVEPOLYGON (CIRCULARSTRING (4 5, 5 6, 6 5, 5 4, 4 5))"; + std::string exp = "CURVEPOLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), CIRCULARSTRING (4 5, 5 6, 6 5, 5 4, 4 5))"; + + testOverlay(a, b, exp, OverlayNG::DIFFERENCE, 0); +} + +template<> +template<> +void object::test<56>() +{ + set_test_name("CircularString / Point -> Point"); + + std::string a = "CIRCULARSTRING (-5 0, 0 5, 5 0)"; + std::string b = "POINT (4 3)"; + std::string exp = b; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<57>() +{ + set_test_name("CompoundCurve / MultiPoint -> MultiPoint"); + + std::string a = "COMPOUNDCURVE ((-10 5, -5 0), CIRCULARSTRING (-5 0, 0 5, 5 0))"; + std::string b = "MULTIPOINT (4 3, -7 2, 0 0)"; + std::string exp = "MULTIPOINT (4 3, -7 2)"; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<58>() +{ + set_test_name("CurvePolygon / MultiPoint -> MultiPoint"); + + std::string a = "CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0)))"; + std::string b = "MULTIPOINT (0 0, 4 3, 2 2, 4 4)"; + std::string exp = "MULTIPOINT (0 0, 4 3, 2 2)"; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<59>() +{ + set_test_name("MultiSurface / MultiPoint -> MultiPoint"); + + std::string a = "MULTISURFACE(CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))), ((4 4, 5 4, 5 5, 4 4)))"; + std::string b = "MULTIPOINT (0 0, 4 3, 2 2, 4.5 4.1)"; + std::string exp = b; + + testOverlay(a, b, exp, OverlayNG::INTERSECTION, 0); +} + +template<> +template<> +void object::test<60>() +{ + set_test_name("MultiSurface / MultiPoint -> MultiSurface"); + + std::string a = "MULTISURFACE(CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING(-5 0, 0 5, 5 0), (5 0, -5 0))), ((4 4, 5 4, 5 5, 4 4)))"; + std::string b = "MULTIPOINT (0 0, 4 3, 2 2, 4.5 4.1)"; + std::string exp = a; + + testOverlay(a, b, exp, OverlayNG::UNION, 0); +} + } // namespace tut commit fa14b9a717e2231679859d20adae502ebff07492 Author: Daniel Baston Date: Thu Mar 19 13:04:00 2026 -0400 EdgeNodingBuilder: Accept CircularString input diff --git a/include/geos/operation/overlayng/Edge.h b/include/geos/operation/overlayng/Edge.h index 2af2a0c67..7ec26eb16 100644 --- a/include/geos/operation/overlayng/Edge.h +++ b/include/geos/operation/overlayng/Edge.h @@ -64,6 +64,7 @@ private: int bDepthDelta = 0; bool bIsHole = false; std::shared_ptr pts; + bool ptsCurved; // Methods @@ -183,6 +184,7 @@ private: public: +#if 0 Edge() : aDim(OverlayLabel::DIM_UNKNOWN) , aDepthDelta(0) @@ -192,15 +194,20 @@ public: , bIsHole(false) , pts(nullptr) {}; +#endif + + Edge(const std::shared_ptr& p_pts, const EdgeSourceInfo* info, bool isCurved); friend std::ostream& operator<<(std::ostream& os, const Edge& e); static bool isCollapsed(const geom::CoordinateSequence* pts); - Edge(const std::shared_ptr& p_pts, const EdgeSourceInfo* info); + bool isCurved() const { + return ptsCurved; + } // return a clone of the underlying points - std::unique_ptr getCoordinates() + std::unique_ptr getCoordinates() const { return pts->clone(); }; diff --git a/include/geos/operation/overlayng/EdgeNodingBuilder.h b/include/geos/operation/overlayng/EdgeNodingBuilder.h index 0927241ba..d981a51d5 100644 --- a/include/geos/operation/overlayng/EdgeNodingBuilder.h +++ b/include/geos/operation/overlayng/EdgeNodingBuilder.h @@ -20,12 +20,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -65,11 +67,16 @@ class GEOS_DLL EdgeNodingBuilder { using PrecisionModel = geos::geom::PrecisionModel; using Envelope = geos::geom::Envelope; using GeometryCollection = geos::geom::GeometryCollection; + using Surface = geos::geom::Surface; using Polygon = geos::geom::Polygon; using CoordinateSequence = geos::geom::CoordinateSequence; + using CompoundCurve = geos::geom::CompoundCurve; + using CircularString = geos::geom::CircularString; using LinearRing = geos::geom::LinearRing; using LineString = geos::geom::LineString; using Geometry = geos::geom::Geometry; + using SimpleCurve = geos::geom::SimpleCurve; + using Curve = geos::geom::Curve; private: @@ -79,7 +86,7 @@ private: // Members const PrecisionModel* pm; - std::unique_ptr> inputEdges; + std::vector inputEdges; noding::Noder* customNoder; std::array hasEdges; const Envelope* clipEnv; @@ -112,11 +119,17 @@ private: void addCollection(const GeometryCollection* gc, uint8_t geomIndex); void addGeometryCollection(const GeometryCollection* gc, uint8_t geomIndex, int expectedDim); - void addPolygon(const Polygon* poly, uint8_t geomIndex); - void addPolygonRing(const LinearRing* ring, bool isHole, uint8_t geomIndex); - void addLine(const LineString* line, uint8_t geomIndex); - void addLine(std::unique_ptr& pts, uint8_t geomIndex); - void addEdge(std::unique_ptr& cas, const EdgeSourceInfo* info); + void addPolygon(const Surface* poly, uint8_t geomIndex); + void addPolygonRing(const Curve* ring, bool isHole, uint8_t geomIndex); + void addSimpleCurve(const SimpleCurve* line, uint8_t geomIndex); + void addCompoundCurve(const CompoundCurve* line, uint8_t geomIndex); + + void addCurve(const std::shared_ptr& pts, const std::vector& arcs, uint8_t geomIndex); + void addLine(const std::shared_ptr& pts, uint8_t geomIndex); + + void addEdge(const std::shared_ptr& cas, const EdgeSourceInfo* info); + void addCurvedEdge(const std::shared_ptr& cas, const std::vector& arcs, const EdgeSourceInfo* info); + // Create a EdgeSourceInfo* owned by EdgeNodingBuilder const EdgeSourceInfo* createEdgeSourceInfo(uint8_t index, int depthDelta, bool isHole); @@ -156,7 +169,7 @@ private: * @param ring the line to clip * @return the points in the clipped line */ - std::unique_ptr clip(const LinearRing* line); + std::shared_ptr clip(const LineString* ring) const; /** * Removes any repeated points from a linear component. @@ -165,9 +178,9 @@ private: * @param line the line to process * @return the points of the line with repeated points removed */ - static std::unique_ptr removeRepeatedPoints(const LineString* line); + static std::shared_ptr removeRepeatedPoints(const LineString* line); - static int computeDepthDelta(const LinearRing* ring, bool isHole); + static int computeDepthDelta(const CoordinateSequence* ringCoords, bool isHole); void add(const Geometry* g, uint8_t geomIndex); @@ -177,8 +190,8 @@ private: * which is used to provide source topology info to the constructed Edges * (and is then discarded). */ - std::vector node(const std::vector& segStrings); - std::vector createEdges(std::vector> &segStrings); + std::vector node(const std::vector& segStrings); + std::vector createEdges(std::vector> &segStrings); public: @@ -190,7 +203,6 @@ public: */ EdgeNodingBuilder(const PrecisionModel* p_pm, noding::Noder* p_customNoder) : pm(p_pm) - , inputEdges(new std::vector) , customNoder(p_customNoder) , hasEdges{{false,false}} , clipEnv(nullptr) diff --git a/src/operation/overlayng/Edge.cpp b/src/operation/overlayng/Edge.cpp index 7f231d722..2c4e8634a 100644 --- a/src/operation/overlayng/Edge.cpp +++ b/src/operation/overlayng/Edge.cpp @@ -27,7 +27,7 @@ using namespace geos::geom; using geos::util::GEOSException; /*public*/ -Edge::Edge(const std::shared_ptr& p_pts, const EdgeSourceInfo* info) +Edge::Edge(const std::shared_ptr& p_pts, const EdgeSourceInfo* info, bool p_isCurved) : aDim(OverlayLabel::DIM_UNKNOWN) , aDepthDelta(0) , aIsHole(false) @@ -35,6 +35,7 @@ Edge::Edge(const std::shared_ptr& p_pts, const EdgeSou , bDepthDelta(0) , bIsHole(false) , pts(p_pts) + , ptsCurved(p_isCurved) { copyInfo(info); } diff --git a/src/operation/overlayng/EdgeNodingBuilder.cpp b/src/operation/overlayng/EdgeNodingBuilder.cpp index b063770f1..13c123315 100644 --- a/src/operation/overlayng/EdgeNodingBuilder.cpp +++ b/src/operation/overlayng/EdgeNodingBuilder.cpp @@ -16,10 +16,13 @@ * **********************************************************************/ +#include +#include +#include #include #include #include -#include +#include #include #include #include @@ -36,15 +39,17 @@ using namespace geos::geom; using geos::operation::valid::RepeatedPointRemover; using geos::noding::snapround::SnapRoundingNoder; using geos::noding::Noder; +using geos::noding::NodableArcString; using geos::noding::MCIndexNoder; using geos::noding::ValidatingNoder; using geos::noding::SegmentString; using geos::noding::NodedSegmentString; +using geos::noding::PathString; EdgeNodingBuilder::~EdgeNodingBuilder() { - for (SegmentString* ss: *inputEdges) { + for (auto* ss: inputEdges) { delete ss; } } @@ -108,7 +113,7 @@ EdgeNodingBuilder::build(const Geometry* geom0, const Geometry* geom1) add(geom0, 0); add(geom1, 1); - std::vector nodedEdges = node(*inputEdges); + std::vector nodedEdges = node(inputEdges); /** * Merge the noded edges to eliminate duplicates. @@ -119,14 +124,14 @@ EdgeNodingBuilder::build(const Geometry* geom0, const Geometry* geom1) /*private*/ std::vector -EdgeNodingBuilder::node(const std::vector& segStrings) +EdgeNodingBuilder::node(const std::vector& segStrings) { std::vector nodedEdges; Noder* noder = getNoder(); - noder->computeNodes(segStrings); + noder->computePathNodes(segStrings); - auto nodedSS = noder->getNodedSubstrings(); + auto nodedSS = noder->getNodedPaths(); nodedEdges = createEdges(nodedSS); @@ -135,13 +140,15 @@ EdgeNodingBuilder::node(const std::vector& segStrings) /*private*/ std::vector -EdgeNodingBuilder::createEdges(std::vector>& segStrings) +EdgeNodingBuilder::createEdges(std::vector>& segStrings) { std::vector createdEdges; for (auto& ss : segStrings) { const auto& pts = ss->getCoordinates(); + const bool isCurved = dynamic_cast(ss.get()); + // don't create edges from collapsed lines if (Edge::isCollapsed(pts.get())) continue; @@ -150,7 +157,7 @@ EdgeNodingBuilder::createEdges(std::vector>& segS // Record that a non-collapsed edge exists for the parent geometry hasEdges[info->getIndex()] = true; // Allocate the new Edge locally in a std::deque - edgeQue.emplace_back(ss->getCoordinates(), info); + edgeQue.emplace_back(ss->getCoordinates(), info, isCurved); Edge* newEdge = &(edgeQue.back()); createdEdges.push_back(newEdge); } @@ -179,12 +186,18 @@ EdgeNodingBuilder::add(const Geometry* g, uint8_t geomIndex) switch (g->getGeometryTypeId()) { case GEOS_POLYGON: - return addPolygon(static_cast(g), geomIndex); + case GEOS_CURVEPOLYGON: + return addPolygon(static_cast(g), geomIndex); case GEOS_LINESTRING: case GEOS_LINEARRING: - return addLine(static_cast(g), geomIndex); + case GEOS_CIRCULARSTRING: + return addSimpleCurve(static_cast(g), geomIndex); + case GEOS_COMPOUNDCURVE: + return addCompoundCurve(static_cast(g), geomIndex); case GEOS_MULTILINESTRING: case GEOS_MULTIPOLYGON: + case GEOS_MULTICURVE: + case GEOS_MULTISURFACE: return addCollection(static_cast(g), geomIndex); case GEOS_GEOMETRYCOLLECTION: return addGeometryCollection(static_cast(g), geomIndex, g->getDimension()); @@ -192,6 +205,7 @@ EdgeNodingBuilder::add(const Geometry* g, uint8_t geomIndex) case GEOS_MULTIPOINT: return; // do nothing default: + throw util::IllegalArgumentException("Unexpected geometry type: " + g->getGeometryType()); return; // do nothing } } @@ -221,13 +235,13 @@ EdgeNodingBuilder::addGeometryCollection(const GeometryCollection* gc, uint8_t g /*private*/ void -EdgeNodingBuilder::addPolygon(const Polygon* poly, uint8_t geomIndex) +EdgeNodingBuilder::addPolygon(const Surface* poly, uint8_t geomIndex) { - const LinearRing* shell = poly->getExteriorRing(); + const Curve* shell = poly->getExteriorRing(); addPolygonRing(shell, false, geomIndex); for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) { - const LinearRing* hole = poly->getInteriorRingN(i); + const Curve* hole = poly->getInteriorRingN(i); // Holes are topologically labelled opposite to the shell, since // the interior of the polygon lies on their opposite side @@ -238,25 +252,60 @@ EdgeNodingBuilder::addPolygon(const Polygon* poly, uint8_t geomIndex) /*private*/ void -EdgeNodingBuilder::addPolygonRing(const LinearRing* ring, bool isHole, uint8_t geomIndex) - { +EdgeNodingBuilder::addPolygonRing(const Curve* ring, bool isHole, uint8_t geomIndex) +{ // don't add empty rings if (ring->isEmpty()) return; if (isClippedCompletely(ring->getEnvelopeInternal())) return; - std::unique_ptr pts = clip(ring); + const auto type = ring->getGeometryTypeId(); + + if (type == GEOS_COMPOUNDCURVE) { + const CompoundCurve* cc = static_cast(ring); + const auto coords = ring->getCoordinates(); + const int depthDelta = computeDepthDelta(coords.get(), isHole); + + const EdgeSourceInfo* eso = createEdgeSourceInfo(geomIndex, depthDelta, isHole); + + for (std::size_t i = 0; i < cc->getNumCurves(); i++) { + const SimpleCurve* section = cc->getCurveN(i); + if (section->getNumPoints() >= 2) { + if (section->getGeometryTypeId() == GEOS_CIRCULARSTRING) { + const CircularString* cs = static_cast(section); + addCurvedEdge(cs->getSharedCoordinates(), cs->getArcs(), eso); + } else { + addEdge(section->getSharedCoordinates(), eso); + } + } + } - /** - * Don't add edges that collapse to a point - */ - if (pts->size() < 2) { return; } - int depthDelta = computeDepthDelta(ring, isHole); - addEdge(pts, createEdgeSourceInfo(geomIndex, depthDelta, isHole)); + const SimpleCurve* scRing = static_cast(ring); + const int depthDelta = computeDepthDelta(scRing->getCoordinatesRO(), isHole); + const EdgeSourceInfo* esInfo = createEdgeSourceInfo(geomIndex, depthDelta, isHole); + + if (ring->getGeometryTypeId() == GEOS_LINEARRING || ring->getGeometryTypeId() == GEOS_LINESTRING) { + // TODO: Support CircularString in RingClipper + std::shared_ptr pts = clip(static_cast(ring)); + + /** + * Don't add edges that collapse to a point + */ + if (pts->size() < 2) { + return; + } + + addEdge(pts, esInfo); + } else { + assert(ring->getGeometryTypeId() == GEOS_CIRCULARSTRING); + + const CircularString* cs = static_cast(ring); + addCurvedEdge(cs->getSharedCoordinates(), cs->getArcs(), esInfo); + } } /*private*/ @@ -281,13 +330,20 @@ EdgeNodingBuilder::createEdgeSourceInfo(uint8_t index, int depthDelta, bool isHo /*private*/ void -EdgeNodingBuilder::addEdge(std::unique_ptr& cas, const EdgeSourceInfo* info) +EdgeNodingBuilder::addEdge(const std::shared_ptr& cas, const EdgeSourceInfo* info) { // TODO: manage these internally to EdgeNodingBuilder in a std::deque, // since they do not have a life span longer than the EdgeNodingBuilder // in OverlayNG::buildGraph() - NodedSegmentString* ss = new NodedSegmentString(std::move(cas), inputHasZ, inputHasM, reinterpret_cast(info)); - inputEdges->push_back(ss); + NodedSegmentString* ss = new NodedSegmentString(cas, inputHasZ, inputHasM, reinterpret_cast(info)); + inputEdges.push_back(ss); +} + +void +EdgeNodingBuilder::addCurvedEdge(const std::shared_ptr& cas, const std::vector& arcs, const EdgeSourceInfo* info) +{ + NodableArcString* as = new NodableArcString(arcs, cas, inputHasZ, inputHasM, reinterpret_cast(info)); + inputEdges.push_back(as); } /*private*/ @@ -299,8 +355,8 @@ EdgeNodingBuilder::isClippedCompletely(const Envelope* env) const } /* private */ -std::unique_ptr -EdgeNodingBuilder::clip(const LinearRing* ring) +std::shared_ptr +EdgeNodingBuilder::clip(const LineString* ring) const { const Envelope* env = ring->getEnvelopeInternal(); @@ -316,16 +372,20 @@ EdgeNodingBuilder::clip(const LinearRing* ring) } /*private*/ -std::unique_ptr +std::shared_ptr EdgeNodingBuilder::removeRepeatedPoints(const LineString* line) { - const CoordinateSequence* pts = line->getCoordinatesRO(); - return RepeatedPointRemover::removeRepeatedPoints(pts); + const std::shared_ptr& pts = line->getSharedCoordinates(); + if (pts->hasRepeatedPoints()) { + return RepeatedPointRemover::removeRepeatedPoints(pts.get()); + } else { + return pts; + } } /*private*/ int -EdgeNodingBuilder::computeDepthDelta(const LinearRing* ring, bool isHole) +EdgeNodingBuilder::computeDepthDelta(const CoordinateSequence* ringCoords, bool isHole) { /** * Compute the orientation of the ring, to @@ -335,7 +395,8 @@ EdgeNodingBuilder::computeDepthDelta(const LinearRing* ring, bool isHole) * It is important to compute orientation on the original ring, * since topology collapse can make the orientation computation give the wrong answer. */ - bool isCCW = algorithm::Orientation::isCCW(ring->getCoordinatesRO()); + + const bool isCCW = algorithm::Orientation::isCCW(ringCoords); /** * Compute whether ring is in canonical orientation or not. @@ -360,29 +421,46 @@ EdgeNodingBuilder::computeDepthDelta(const LinearRing* ring, bool isHole) /*private*/ void -EdgeNodingBuilder::addLine(const LineString* line, uint8_t geomIndex) +EdgeNodingBuilder::addSimpleCurve(const SimpleCurve* curve, uint8_t geomIndex) { // don't add empty lines - if (line->isEmpty()) return; + if (curve->isEmpty()) return; - if (isClippedCompletely(line->getEnvelopeInternal())) + if (isClippedCompletely(curve->getEnvelopeInternal())) return; + if (curve->getGeometryTypeId() == GEOS_CIRCULARSTRING) { + // TODO: Support CircularString in LineLimiter + const CircularString* cs = static_cast(curve); + addCurve(cs->getSharedCoordinates(), cs->getArcs(), geomIndex); + return; + } + + const LineString* line = detail::down_cast(curve); + if (isToBeLimited(line)) { std::vector>& sections = limit(line); for (auto& pts : sections) { - addLine(pts, geomIndex); + addLine(std::move(pts), geomIndex); } } else { - std::unique_ptr ptsNoRepeat = removeRepeatedPoints(line); + const auto ptsNoRepeat = removeRepeatedPoints(line); addLine(ptsNoRepeat, geomIndex); } } +void +EdgeNodingBuilder::addCompoundCurve(const CompoundCurve* curve, uint8_t geomIndex) +{ + for (std::size_t i = 0; i < curve->getNumCurves(); i++) { + addSimpleCurve(curve->getCurveN(i), geomIndex); + } +} + /*private*/ void -EdgeNodingBuilder::addLine(std::unique_ptr& pts, uint8_t geomIndex) +EdgeNodingBuilder::addLine(const std::shared_ptr& pts, uint8_t geomIndex) { /** * Don't add edges that collapse to a point @@ -394,6 +472,20 @@ EdgeNodingBuilder::addLine(std::unique_ptr& pts, uint8_t geo addEdge(pts, createEdgeSourceInfo(geomIndex)); } +/*private*/ +void +EdgeNodingBuilder::addCurve(const std::shared_ptr& pts, const std::vector& arcs, uint8_t geomIndex) +{ + /** + * Don't add edges that collapse to a point + */ + if (pts->size() < 2) { + return; + } + + addCurvedEdge(pts, arcs, createEdgeSourceInfo(geomIndex)); +} + /*private*/ bool EdgeNodingBuilder::isToBeLimited(const LineString* line) const diff --git a/tests/unit/operation/overlayng/EdgeNodingBuilderTest.cpp b/tests/unit/operation/overlayng/EdgeNodingBuilderTest.cpp new file mode 100644 index 000000000..017dffc9a --- /dev/null +++ b/tests/unit/operation/overlayng/EdgeNodingBuilderTest.cpp @@ -0,0 +1,110 @@ +// +// Test Suite for geos::operation::overlayng::EdgeNodingBuilder class. + +#include +#include + +// geos +#include +#include +#include +#include +#include + +// std +#include + + + +using namespace geos::geom; +using namespace geos::noding; +using namespace geos::operation::overlayng; +using geos::algorithm::CircularArcIntersector; +using geos::io::WKTReader; +using geos::io::WKTWriter; + +namespace tut { +struct test_edgenodingbuilder_data { + WKTReader reader_; + + PrecisionModel pm_floating{PrecisionModel::Type::FLOATING}; + GeometryFactory::Ptr factory_{GeometryFactory::create(&pm_floating)}; + + std::unique_ptr toGeometry(const Edge& edge) const + { + if (edge.isCurved()) { + return factory_->createCircularString(edge.getCoordinates()); + } + return factory_->createLineString(edge.getCoordinates()); + } + + std::unique_ptr toGeometry(const std::vector edges) const + { + std::vector> geoms; + for (const auto* edge : edges) { + geoms.push_back(toGeometry(*edge)); + } + return factory_->createGeometryCollection(std::move(geoms)); + } + + void checkEdges(const std::string& wkt1, const std::string& wkt2, const std::string& wktExpected) const + { + auto geom1 = reader_.read(wkt1); + auto geom2 = reader_.read(wkt2); + + SimpleNoder noder; + CircularArcIntersector cai; + ArcIntersectionAdder aia(cai); + noder.setArcIntersector(aia); + + EdgeNodingBuilder builder(geom1->getPrecisionModel(), &noder); + + auto edges = builder.build(geom1.get(), geom2.get()); + std::unique_ptr expected = reader_.read(wktExpected); + ensure_equals_geometry_xyzm(toGeometry(edges).get(), expected.get(), 1e-5); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_edgenodingbuilder_group("geos::operation::overlayng::EdgeNodingBuilder"); + +template<> +template<> +void object::test<1>() +{ + set_test_name("two CircularStrings"); + + checkEdges( + "CIRCULARSTRING (0 0, 1 1, 2 0)", + "CIRCULARSTRING (0 1, 1 0, 2 1)", + "GEOMETRYCOLLECTION (" + "CIRCULARSTRING (0 0, 0.0340742 0.258819, 0.133975 0.5)," + "CIRCULARSTRING (0.133975 0.5, 1 1, 1.86603 0.5)," + "CIRCULARSTRING (1.86603 0.5, 1.96593 0.258819, 2 0)," + "CIRCULARSTRING (0 1, 0.0340742 0.741181, 0.133975 0.5)," + "CIRCULARSTRING (0.133975 0.5, 1 0, 1.86603 0.5)," + "CIRCULARSTRING (1.86603 0.5, 1.96593 0.741181, 2 1)" + ")"); +} + +template<> +template<> +void object::test<2>() +{ + set_test_name("CurvePolygon and LineString"); + + checkEdges("CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))", + "LINESTRING (1 0, 2 1)", + "GEOMETRYCOLLECTION (" + "CIRCULARSTRING (0 0, 0.6173165676349103 0.9238795325112867, 1.7071067811865475 0.7071067811865475)," + "CIRCULARSTRING (1.7071067811865475 0.7071067811865475, 1.9238795325112867 0.3826834323650898, 2 0)," + "LINESTRING (1 0, 1.7071067811865475 0.7071067811865475)," + "LINESTRING (1.7071067811865475 0.7071067811865475, 2 1)," + "LINESTRING (0 0, 1 0)," + "LINESTRING (1 0, 2 0)" + ")"); +} + +} diff --git a/tests/unit/operation/overlayng/OverlayGraphTest.cpp b/tests/unit/operation/overlayng/OverlayGraphTest.cpp index e179702fe..7fce5ff72 100644 --- a/tests/unit/operation/overlayng/OverlayGraphTest.cpp +++ b/tests/unit/operation/overlayng/OverlayGraphTest.cpp @@ -52,7 +52,7 @@ struct test_overlaygraph_data { std::unique_ptr cs = line->getCoordinates(); EdgeSourceInfo esi(0); - Edge e(std::move(cs), &esi); + Edge e(std::move(cs), &esi, false); std::unique_ptr pts = e.getCoordinates(); graph->addEdge(&e); ----------------------------------------------------------------------- Summary of changes: include/geos/algorithm/CircularArcs.h | 15 ++ include/geos/algorithm/PointLocator.h | 6 + include/geos/geom/CircularArc.h | 2 + include/geos/geom/Curve.h | 2 + include/geos/operation/overlayng/Edge.h | 42 ++-- .../geos/operation/overlayng/EdgeNodingBuilder.h | 43 +++- include/geos/operation/overlayng/LineBuilder.h | 8 +- include/geos/operation/overlayng/OverlayEdge.h | 10 +- include/geos/operation/overlayng/OverlayEdgeRing.h | 23 +- include/geos/operation/overlayng/OverlayGraph.h | 4 +- .../geos/operation/overlayng/OverlayMixedPoints.h | 17 +- include/geos/operation/overlayng/OverlayUtil.h | 17 +- include/geos/operation/overlayng/PolygonBuilder.h | 6 +- src/algorithm/CircularArcIntersector.cpp | 162 +++++++----- src/algorithm/CircularArcs.cpp | 94 ++++--- src/algorithm/PointLocator.cpp | 80 +++++- src/geom/CircularArc.cpp | 14 ++ src/geom/Curve.cpp | 6 + src/geom/GeometryFactory.cpp | 22 ++ src/noding/NodableArcString.cpp | 243 +++++++++++++----- src/noding/snap/SnappingNoder.cpp | 3 +- src/operation/overlayng/Edge.cpp | 3 +- src/operation/overlayng/EdgeNodingBuilder.cpp | 191 +++++++++++---- .../overlayng/IndexedPointOnLineLocator.cpp | 2 +- src/operation/overlayng/InputGeometry.cpp | 20 +- src/operation/overlayng/LineBuilder.cpp | 33 ++- src/operation/overlayng/OverlayEdgeRing.cpp | 150 ++++++++---- src/operation/overlayng/OverlayGraph.cpp | 24 +- src/operation/overlayng/OverlayMixedPoints.cpp | 40 +-- src/operation/overlayng/OverlayNG.cpp | 6 +- src/operation/overlayng/OverlayNGRobust.cpp | 3 - src/operation/overlayng/OverlayUtil.cpp | 39 ++- src/operation/overlayng/PolygonBuilder.cpp | 10 +- .../unit/algorithm/CircularArcIntersectorTest.cpp | 60 +++++ tests/unit/capi/GEOSDifferenceTest.cpp | 8 +- tests/unit/capi/GEOSIntersectionTest.cpp | 10 +- tests/unit/capi/GEOSNodeTest.cpp | 8 +- tests/unit/capi/GEOSSymDifferenceTest.cpp | 10 +- tests/unit/capi/GEOSUnionTest.cpp | 10 +- tests/unit/geom/CircularStringTest.cpp | 8 +- tests/unit/geom/CompoundCurveTest.cpp | 9 +- tests/unit/geom/CurvePolygonTest.cpp | 8 +- tests/unit/geom/MultiCurveTest.cpp | 9 +- tests/unit/geom/MultiSurfaceTest.cpp | 8 +- .../operation/overlayng/EdgeNodingBuilderTest.cpp | 110 +++++++++ .../unit/operation/overlayng/OverlayGraphTest.cpp | 2 +- tests/unit/operation/overlayng/OverlayNGTest.cpp | 271 ++++++++++++++++++++- 47 files changed, 1453 insertions(+), 418 deletions(-) create mode 100644 tests/unit/operation/overlayng/EdgeNodingBuilderTest.cpp hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 16 11:36:50 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 16 Jun 2026 11:36:50 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. ce51dbd1f94661b99ae8b263e56b6c53cc7876ae Message-ID: <20260616183654.7F01F1A1C7E@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 ce51dbd1f94661b99ae8b263e56b6c53cc7876ae (commit) from 11f1851a82bdf5f9983f03d1b967adfb5da6fb39 (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 ce51dbd1f94661b99ae8b263e56b6c53cc7876ae Author: Daniel Baston Date: Tue Jun 16 14:36:04 2026 -0400 NEWS updates diff --git a/NEWS.md b/NEWS.md index cb455468c..a77954ff6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,16 @@ - Add GEOSCoverageEdges (Paul Ramsey) - Add new C API functions for Hausdorff distance (GH-1352, Sven Jensen) - Add GEOSSubdivideByGrid (GH-1232, Dan Baston) + - Add support for curved geometries to the following operations: + - BoundaryOp (GH-1394, Dan Baston) + - DistanceOp (GH-1428, Dan Baston) + - GeometryNoder (GH-1347, Dan Baston) + - GeometryPrecisionReducer (pointwise only, GH-1402; Dan Baston) + - LineMerger (GH-1441, Dan Baston) + - Normalize (GH-1397, Dan Baston) + - OverlayNG (GH-1427, Dan Baston) + - Polygonizer (GH-1420, Dan Baston) + - Add GeometrySplitter (GH-1424, Dan Baston) - Breaking Changes: - BufferOp returns POLYGON EMPTY when fed Inf/Nan coords (GH-1332) @@ -21,11 +31,12 @@ - Preserve M values in GEOSDensify (GH-1319, Dan Baston) - Preserve M values in GEOSSimplify (GH-1317, Dan Baston) - Preserve M values in GEOSPolygonize (GH-1363 Dan Baston) - - Fix some cases of dropped M values in overlay (GH-1364/GH-1388, Dan Baston) + - Fix some cases of dropped M values in overlay (GH-1364,GH-1388,GH-1407,GH-1408; Dan Baston) - Support curved inputs in GEOSNode (GH-1347, Dan Baston) - GEOSClusterDBSCAN fix unsassigned clusters with minPoints <= 1 (GH-1386, Dan Baston) - Fix crash in GEOSConvexHull (GH-1358, Dan Baston) - Overlay performance improvements (GH-1353, arriopolis, Martin Davis) + - Fix unintended ring rotation in Overlay results (GH-1412, Dan Baston) ## Changes in 3.14.0 2025-08-21 ----------------------------------------------------------------------- Summary of changes: NEWS.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 16 18:40:12 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 16 Jun 2026 18:40:12 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. a80d3c2da06305ce5d6cf03b882c2809c5ee43ab Message-ID: <20260617014013.28CBE1A5E21@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 a80d3c2da06305ce5d6cf03b882c2809c5ee43ab (commit) from a7a985eca203a98c19b36641ac29bdbac1c9db31 (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 a80d3c2da06305ce5d6cf03b882c2809c5ee43ab Author: Daniel Baston Date: Tue Jun 16 21:39:44 2026 -0400 NEWS update diff --git a/NEWS.md b/NEWS.md index a60c7ad58..b1bea0c64 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ - Fix crash in GEOSConvexHull (GH-1358, Dan Baston) - Guard against stack overflow in inputs (GH-1437, Paul Ramsey) - Avoid overflow risk in WKB reader (Paul Ramsey) + - GEOSNode: fix crash on collection with empty components (GH-1406, Dan Baston) ## Changes in 3.14.1 2025-10-27 ----------------------------------------------------------------------- Summary of changes: NEWS.md | 1 + 1 file changed, 1 insertion(+) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 16 19:39:53 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 16 Jun 2026 19:39:53 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 69b6dad8aa171142b2e0d4342b578232f40cd9a4 Message-ID: <20260617023953.37E6B1A5FDD@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 69b6dad8aa171142b2e0d4342b578232f40cd9a4 (commit) from ce51dbd1f94661b99ae8b263e56b6c53cc7876ae (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 69b6dad8aa171142b2e0d4342b578232f40cd9a4 Author: Daniel Baston Date: Tue Jun 16 22:03:30 2026 -0400 NodableSegmentString constructor: accept pts as const ref diff --git a/include/geos/noding/NodableSegmentString.h b/include/geos/noding/NodableSegmentString.h index 7b3440d95..8865c0781 100644 --- a/include/geos/noding/NodableSegmentString.h +++ b/include/geos/noding/NodableSegmentString.h @@ -37,7 +37,7 @@ class GEOS_DLL NodableSegmentString : public SegmentString { private: protected: public: - NodableSegmentString(const void* newContext, std::shared_ptr newSeq) + NodableSegmentString(const void* newContext, const std::shared_ptr& newSeq) : SegmentString(newContext, newSeq) { } ----------------------------------------------------------------------- Summary of changes: include/geos/noding/NodableSegmentString.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Wed Jun 17 09:45:40 2026 From: git at osgeo.org (git at osgeo.org) Date: Wed, 17 Jun 2026 09:45:40 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. fa569a231414a1f73cb6c4f3c31c259700337035 Message-ID: <20260617164543.778671B0043@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 fa569a231414a1f73cb6c4f3c31c259700337035 (commit) from 69b6dad8aa171142b2e0d4342b578232f40cd9a4 (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 fa569a231414a1f73cb6c4f3c31c259700337035 Author: Daniel Baston Date: Wed Jun 17 10:59:48 2026 -0400 KdTree: Resolve various clang-tidy issues diff --git a/include/geos/index/kdtree/KdNodeVisitor.h b/include/geos/index/kdtree/KdNodeVisitor.h index 17c9a9cfa..51589e4f5 100644 --- a/include/geos/index/kdtree/KdNodeVisitor.h +++ b/include/geos/index/kdtree/KdNodeVisitor.h @@ -35,6 +35,7 @@ public: KdNodeVisitor() {}; virtual void visit(KdNode *node) = 0; + virtual ~KdNodeVisitor() = default; }; diff --git a/include/geos/index/kdtree/KdTree.h b/include/geos/index/kdtree/KdTree.h index 144fbc13f..f98f203cc 100644 --- a/include/geos/index/kdtree/KdTree.h +++ b/include/geos/index/kdtree/KdTree.h @@ -19,9 +19,7 @@ #include #include -#include #include -#include #include #ifdef _MSC_VER @@ -64,8 +62,8 @@ private: KdNode* findBestMatchNode(const geom::Coordinate& p); KdNode* insertExact(const geom::Coordinate& p, void* data); - void queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, KdNodeVisitor& visitor); - KdNode* queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd); + static void queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, KdNodeVisitor& visitor); + static KdNode* queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd); /** * Create a node on a locally managed deque to allow easy @@ -85,15 +83,16 @@ private: KdNode* getNode(); void visit(KdNode* node) override; + // Declare type as noncopyable + BestMatchVisitor(const BestMatchVisitor& other) = delete; + BestMatchVisitor& operator=(const BestMatchVisitor& rhs) = delete; + private: // Members double tolerance; KdNode* matchNode; double matchDist; const geom::Coordinate& p; - // Declare type as noncopyable - BestMatchVisitor(const BestMatchVisitor& other); - BestMatchVisitor& operator=(const BestMatchVisitor& rhs); }; /** @@ -102,16 +101,17 @@ private: */ class AccumulatingVisitor : public KdNodeVisitor { public: - AccumulatingVisitor(std::vector& p_nodeList) : + explicit AccumulatingVisitor(std::vector& p_nodeList) : nodeList(p_nodeList) {}; void visit(KdNode* node) override { nodeList.push_back(node); } + // Declare type as noncopyable + AccumulatingVisitor(const AccumulatingVisitor& other) = delete; + AccumulatingVisitor& operator=(const AccumulatingVisitor& rhs) = delete; + private: // Members std::vector& nodeList; - // Declare type as noncopyable - AccumulatingVisitor(const AccumulatingVisitor& other); - AccumulatingVisitor& operator=(const AccumulatingVisitor& rhs); }; @@ -121,10 +121,10 @@ public: /** * Converts a collection of {@link KdNode}s to an vector of {@link geom::Coordinate}s. * - * @param kdnodes a collection of nodes - * @return an vector of the coordinates represented by the nodes + * @param kdNodes a collection of nodes + * @return a vector of the coordinates represented by the nodes */ - static std::unique_ptr> toCoordinates(std::vector& kdnodes); + static std::vector toCoordinates(const std::vector& kdNodes); /** * Converts a collection of {@link KdNode}s @@ -132,12 +132,12 @@ public: * specifying whether repeated nodes should be represented * by multiple coordinates. * - * @param kdnodes a collection of nodes + * @param kdNodes a collection of nodes * @param includeRepeated true if repeated nodes should * be included multiple times - * @return an vector of the coordinates represented by the nodes + * @return a vector of the coordinates represented by the nodes */ - static std::unique_ptr> toCoordinates(std::vector& kdnodes, bool includeRepeated); + static std::vector toCoordinates(const std::vector& kdNodes, bool includeRepeated); KdTree() : root(nullptr), @@ -145,7 +145,7 @@ public: tolerance(0.0) {}; - KdTree(double p_tolerance) : + explicit KdTree(double p_tolerance) : root(nullptr), numberOfNodes(0), tolerance(p_tolerance) @@ -167,7 +167,7 @@ public: /** * Performs a range search of the points in the index. */ - std::unique_ptr> query(const geom::Envelope& queryEnv); + std::vector query(const geom::Envelope& queryEnv); /** * Performs a range search of the points in the index. diff --git a/src/index/kdtree/KdTree.cpp b/src/index/kdtree/KdTree.cpp index a0c2dfbff..2118165aa 100644 --- a/src/index/kdtree/KdTree.cpp +++ b/src/index/kdtree/KdTree.cpp @@ -27,26 +27,26 @@ namespace kdtree { // geos.index.kdtree /*public static*/ -std::unique_ptr> -KdTree::toCoordinates(std::vector& kdnodes) +std::vector +KdTree::toCoordinates(const std::vector& kdnodes) { return toCoordinates(kdnodes, false); } /*public static*/ -std::unique_ptr> -KdTree::toCoordinates(std::vector& kdnodes, bool includeRepeated) +std::vector +KdTree::toCoordinates(const std::vector& kdnodes, bool includeRepeated) { - std::unique_ptr> coord(new std::vector); + std::vector coord; for (auto node: kdnodes) { std::size_t count = includeRepeated ? node->getCount() : 1; for (std::size_t i = 0; i < count; i++) { - coord->emplace_back(node->getCoordinate()); + coord.emplace_back(node->getCoordinate()); } } if (!includeRepeated) { // Remove duplicate Coordinates from coordList - coord->erase(std::unique(coord->begin(), coord->end()), coord->end()); + coord.erase(std::unique(coord.begin(), coord.end()), coord.end()); } return coord; } @@ -153,7 +153,7 @@ KdTree::insertExact(const geom::Coordinate& p, void* data) void KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, KdNodeVisitor& visitor) { - // Non recursive formulation of in-order traversal from + // Non-recursive formulation of in-order traversal from // http://web.cs.wpi.edu/~cs2005/common/iterative.inorder // Otherwise we may blow up the stack // See https://github.com/qgis/QGIS/issues/45226 @@ -175,7 +175,7 @@ KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, } bool searchLeft = min < discriminant; - activeNodes.emplace(Pair(currentNode, odd)); + activeNodes.emplace(currentNode, odd); // search is computed via in-order traversal KdNode* leftNode = nullptr; @@ -267,11 +267,11 @@ KdTree::query(const geom::Envelope& queryEnv, KdNodeVisitor& visitor) } /*public*/ -std::unique_ptr> +std::vector KdTree::query(const geom::Envelope& queryEnv) { - std::unique_ptr> result(new std::vector); - query(queryEnv, *result); + std::vector result; + query(queryEnv, result); return result; } diff --git a/tests/unit/index/kdtree/KdTreeTest.cpp b/tests/unit/index/kdtree/KdTreeTest.cpp index 2de94f162..34ef8d8c6 100644 --- a/tests/unit/index/kdtree/KdTreeTest.cpp +++ b/tests/unit/index/kdtree/KdTreeTest.cpp @@ -28,13 +28,13 @@ struct test_kdtree_data { std::vector expectedCoord; csExpected->toVector(expectedCoord); // Read tree into vector of coordinates - std::unique_ptr> result = KdTree::toCoordinates(*(index.query(queryEnv)), includeRepeated); + auto result = KdTree::toCoordinates(index.query(queryEnv), includeRepeated); - std::sort(result->begin(), result->end()); + std::sort(result.begin(), result.end()); std::sort(expectedCoord.begin(), expectedCoord.end()); - ensure("Result count not equal to expected count", result->size() == expectedCoord.size()); - ensure("Expected result coordinates not found", *result == expectedCoord); + ensure("Result count not equal to expected count", result.size() == expectedCoord.size()); + ensure("Expected result coordinates not found", result == expectedCoord); } void testQuery(std::string& wktInput, double tolerance, const Envelope& queryEnv, std::string& wktExpected) { @@ -67,11 +67,11 @@ void object::test<1> () ensure("Inserting 2 identical points should create one node", node1 == node2); Envelope queryEnv(0, 10, 0, 10); - std::unique_ptr> result = index.query(queryEnv); + std::vector result = index.query(queryEnv); - ensure("query should return 1 result", result->size() == 1); + ensure("query should return 1 result", result.size() == 1); - KdNode* node = result->at(0); + KdNode* node = result.at(0); ensure("node should have two entries", node->getCount() == 2); ensure("node should be repeated", node->isRepeated()); } @@ -180,11 +180,11 @@ void object::test<8> () ensure("Inserting 2 identical points should create one node", node1 == node2); Envelope queryEnv(0, 10, 0, 10); - std::unique_ptr> result = index.query(queryEnv); + std::vector result = index.query(queryEnv); - ensure(result->size() == 1); + ensure(result.size() == 1); - KdNode* node = (KdNode*)(*result)[0]; + KdNode* node = result[0]; ensure(node->getCount() == 2); ensure(node->isRepeated()); } ----------------------------------------------------------------------- Summary of changes: include/geos/index/kdtree/KdNodeVisitor.h | 1 + include/geos/index/kdtree/KdTree.h | 38 +++++++++++++++---------------- src/index/kdtree/KdTree.cpp | 24 +++++++++---------- tests/unit/index/kdtree/KdTreeTest.cpp | 20 ++++++++-------- 4 files changed, 42 insertions(+), 41 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Wed Jun 17 11:16:35 2026 From: git at osgeo.org (git at osgeo.org) Date: Wed, 17 Jun 2026 11:16:35 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 3ffdc29d3973d77d2b3505557ed793b3c4ca8111 Message-ID: <20260617181635.C4EF11B1985@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 3ffdc29d3973d77d2b3505557ed793b3c4ca8111 (commit) from fa569a231414a1f73cb6c4f3c31c259700337035 (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 3ffdc29d3973d77d2b3505557ed793b3c4ca8111 Author: Daniel Baston Date: Wed Jun 17 14:16:15 2026 -0400 CommonBits: clean UBSAN error (#1448) diff --git a/src/precision/CommonBits.cpp b/src/precision/CommonBits.cpp index e5d004ebb..179e0d557 100644 --- a/src/precision/CommonBits.cpp +++ b/src/precision/CommonBits.cpp @@ -14,6 +14,7 @@ **********************************************************************/ #include +#include namespace geos { namespace precision { // geos.precision @@ -71,6 +72,11 @@ CommonBits::CommonBits() void CommonBits::add(double num) { + if (num > static_cast(std::numeric_limits::max())) { + commonBits = 0; + return; + } + int64_t numBits = (int64_t) num; if(isFirst) { commonBits = numBits; ----------------------------------------------------------------------- Summary of changes: src/precision/CommonBits.cpp | 6 ++++++ 1 file changed, 6 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Wed Jun 17 12:36:00 2026 From: git at osgeo.org (git at osgeo.org) Date: Wed, 17 Jun 2026 12:36:00 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 4150b856e18b4f33460dbe12b23eea449af8095b Message-ID: <20260617193600.EE3771B28BC@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 4150b856e18b4f33460dbe12b23eea449af8095b (commit) from 3ffdc29d3973d77d2b3505557ed793b3c4ca8111 (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 4150b856e18b4f33460dbe12b23eea449af8095b Author: Sandro Santilli Date: Wed Jun 17 21:35:42 2026 +0200 Ensure rpath is used for libgeos_c.so (#1436) * Ensure rpath is used for libgeos_c.so This ensures each C library links to its corresponding C++ library Closes GH-1435 * Allow CMAKE_INSTALL_RPATH override from commandline, handle MacOS diff --git a/CMakeLists.txt b/CMakeLists.txt index d114245cf..541288b93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -406,6 +406,18 @@ if(BUILD_SHARED_LIBS) PRIVATE $<$:GEOS_DLL_EXPORT>) set_target_properties(geos_c PROPERTIES VERSION ${CAPI_VERSION}) + + # See https://github.com/libgeos/geos/issues/1435 + if(NOT DEFINED CMAKE_INSTALL_RPATH) + # Use relative rpath + if(APPLE) + set_target_properties(geos_c PROPERTIES + INSTALL_RPATH "@loader_path") + else() + set_target_properties(geos_c PROPERTIES INSTALL_RPATH "\$ORIGIN") + endif() + endif() + if(NOT WIN32 OR MINGW) set_target_properties(geos_c PROPERTIES SOVERSION ${CAPI_VERSION_MAJOR}) endif() ----------------------------------------------------------------------- Summary of changes: CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Wed Jun 17 14:04:31 2026 From: git at osgeo.org (git at osgeo.org) Date: Wed, 17 Jun 2026 14:04:31 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. cc58aad27d8e8d13fcfcfab00b1305944c6833e7 Message-ID: <20260617210431.D73581B3171@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 cc58aad27d8e8d13fcfcfab00b1305944c6833e7 (commit) from 4150b856e18b4f33460dbe12b23eea449af8095b (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 cc58aad27d8e8d13fcfcfab00b1305944c6833e7 Author: Kurt Schwehr Date: Wed Jun 17 14:04:09 2026 -0700 Fix `size_t <<` debug prints (#1452) Bug introduced in https://github.com/libgeos/geos/commit/d271aaa9a1939fe5a51c5b5d05f1a3d547a2f900 diff --git a/src/io/WKBReader.cpp b/src/io/WKBReader.cpp index b366d91b9..3562e0147 100644 --- a/src/io/WKBReader.cpp +++ b/src/io/WKBReader.cpp @@ -46,6 +46,9 @@ //#define DEBUG_WKB_READER 1 +#if DEBUG_WKB_READER +#include +#endif using namespace geos::geom; @@ -174,7 +177,7 @@ WKBReader::readHEX(std::istream& is) static_cast((result_high << 4) + result_low); #if DEBUG_HEX_READER - std::size_t << "HEX " << high << low << " -> DEC " << (int)value << std::endl; + std::cout << "HEX " << high << low << " -> DEC " << (int)value << std::endl; #endif // write the value to the output stream os << value; @@ -272,7 +275,7 @@ WKBReader::readGeometry() unsigned char byteOrder = dis.readByte(); #if DEBUG_WKB_READER - std::size_t << "WKB byteOrder: " << (int)byteOrder << std::endl; + std::cout << "WKB byteOrder: " << (int)byteOrder << std::endl; #endif // default is machine endian @@ -295,7 +298,7 @@ WKBReader::readGeometry() int sfsqlHasM = (typeInt & 0x40000000) != 0; #if DEBUG_WKB_READER - std::size_t << "WKB geometryType: " << geometryType << std::endl; + std::cout << "WKB geometryType: " << geometryType << std::endl; #endif hasZ = sfsqlHasZ || isoHasZ; @@ -311,17 +314,17 @@ WKBReader::readGeometry() } #if DEBUG_WKB_READER - std::size_t << "WKB hasZ: " << hasZ << std::endl; + std::cout << "WKB hasZ: " << hasZ << std::endl; #endif #if DEBUG_WKB_READER - std::size_t << "WKB dimensions: " << inputDimension << std::endl; + std::cout << "WKB dimensions: " << inputDimension << std::endl; #endif bool hasSRID = ((typeInt & 0x20000000) != 0); #if DEBUG_WKB_READER - std::size_t << "WKB hasSRID: " << hasSRID << std::endl; + std::cout << "WKB hasSRID: " << hasSRID << std::endl; #endif int SRID = 0; @@ -398,7 +401,7 @@ WKBReader::readLineString() uint32_t size = dis.readUnsigned(); minMemSize(GEOS_LINESTRING, size); #if DEBUG_WKB_READER - std::size_t << "WKB npoints: " << size << std::endl; + std::cout << "WKB npoints: " << size << std::endl; #endif auto pts = readCoordinateSequence(size); return factory.createLineString(std::move(pts)); @@ -410,7 +413,7 @@ WKBReader::readLinearRing() uint32_t size = dis.readUnsigned(); minMemSize(GEOS_LINEARRING, size); #if DEBUG_WKB_READER - std::size_t << "WKB npoints: " << size << std::endl; + std::cout << "WKB npoints: " << size << std::endl; #endif auto pts = readCoordinateSequence(size); // Replace unclosed ring with closed @@ -451,7 +454,7 @@ WKBReader::readPolygon() minMemSize(GEOS_POLYGON, numRings); #if DEBUG_WKB_READER - std::size_t << "WKB numRings: " << numRings << std::endl; + std::cout << "WKB numRings: " << numRings << std::endl; #endif std::unique_ptr shell; @@ -481,7 +484,7 @@ WKBReader::readCurvePolygon() minMemSize(GEOS_POLYGON, numRings); #if DEBUG_WKB_READER - std::size_t << "WKB numRings: " << numRings << std::endl; + std::cout << "WKB numRings: " << numRings << std::endl; #endif if (numRings == 0) { @@ -624,7 +627,7 @@ WKBReader::readCoordinate() } } #if DEBUG_WKB_READER - std::size_t << "WKB coordinate: " << ordValues[0] << "," << ordValues[1] << std::endl; + std::cout << "WKB coordinate: " << ordValues[0] << "," << ordValues[1] << std::endl; #endif } diff --git a/src/io/WKBWriter.cpp b/src/io/WKBWriter.cpp index f7c83d38d..95c64b044 100644 --- a/src/io/WKBWriter.cpp +++ b/src/io/WKBWriter.cpp @@ -42,6 +42,10 @@ #undef DEBUG_WKB_WRITER +#if DEBUG_WKB_READER +#include +#endif + using namespace geos::geom; @@ -380,7 +384,7 @@ void WKBWriter::writeCoordinate(const CoordinateSequence& cs, std::size_t idx) { #if DEBUG_WKB_WRITER - std::size_t << "writeCoordinate: X:" << cs.getX(idx) << " Y:" << cs.getY(idx) << std::endl; + std::cout << "writeCoordinate: X:" << cs.getX(idx) << " Y:" << cs.getY(idx) << std::endl; #endif assert(outStream); @@ -442,4 +446,3 @@ WKBWriter::getWkbType(const Geometry& g) { } // namespace geos.io } // namespace geos - diff --git a/src/operation/overlay/snap/GeometrySnapper.cpp b/src/operation/overlay/snap/GeometrySnapper.cpp index c0b429739..a9a4722e6 100644 --- a/src/operation/overlay/snap/GeometrySnapper.cpp +++ b/src/operation/overlay/snap/GeometrySnapper.cpp @@ -209,8 +209,8 @@ GeometrySnapper::snap(const geom::Geometry& g0, GeometrySnapper snapper1(g1); snapGeom.second = snapper1.snapTo(*snapGeom.first, snapTolerance); -// std::size_t << *snapGeom.first << std::endl; -// std::size_t << *snapGeom.second << std::endl; +// std::cout << *snapGeom.first << std::endl; +// std::cout << *snapGeom.second << std::endl; } @@ -228,4 +228,3 @@ GeometrySnapper::snapToSelf(const geom::Geometry& g, } // namespace geos.operation.overlay } // namespace geos.operation } // namespace geos - ----------------------------------------------------------------------- Summary of changes: src/io/WKBReader.cpp | 25 ++++++++++++++----------- src/io/WKBWriter.cpp | 7 +++++-- src/operation/overlay/snap/GeometrySnapper.cpp | 5 ++--- 3 files changed, 21 insertions(+), 16 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Jun 19 06:59:52 2026 From: git at osgeo.org (git at osgeo.org) Date: Fri, 19 Jun 2026 06:59:52 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 4226b5a034d249e64385d3dac7e69774ff30d6f3 Message-ID: <20260619135952.C04711B12E8@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 4226b5a034d249e64385d3dac7e69774ff30d6f3 (commit) via b0689cbf1ac4f21c9370e8485aae39c8a55d9c47 (commit) from cc58aad27d8e8d13fcfcfab00b1305944c6833e7 (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 4226b5a034d249e64385d3dac7e69774ff30d6f3 Author: Daniel Baston Date: Thu Jun 18 12:49:34 2026 -0400 KdTree: Disable optimizations in latest MSVC version References #1449 diff --git a/src/index/kdtree/KdTree.cpp b/src/index/kdtree/KdTree.cpp index 2118165aa..e11626aed 100644 --- a/src/index/kdtree/KdTree.cpp +++ b/src/index/kdtree/KdTree.cpp @@ -226,6 +226,11 @@ KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, } } +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +// Avoid segfault with MSVC 19.51.36246.0 +// See https://github.com/libgeos/geos/issues/1449 +#pragma optimize("", off) +#endif /*private*/ KdNode* KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd) @@ -257,7 +262,9 @@ KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, boo } return nullptr; } - +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +#pragma optimize("", on) +#endif /*public*/ void commit b0689cbf1ac4f21c9370e8485aae39c8a55d9c47 Author: Daniel Baston Date: Fri Jun 19 09:24:45 2026 -0400 KdTree: Add minimal test for #1449 diff --git a/tests/unit/index/kdtree/KdTreeTest.cpp b/tests/unit/index/kdtree/KdTreeTest.cpp index 34ef8d8c6..ec8d238e7 100644 --- a/tests/unit/index/kdtree/KdTreeTest.cpp +++ b/tests/unit/index/kdtree/KdTreeTest.cpp @@ -189,6 +189,24 @@ void object::test<8> () ensure(node->isRepeated()); } +template<> +template<> +void object::test<9>() +{ + set_test_name("query by point"); + // minimal test for https://github.com/libgeos/geos/issues/1449 + + KdTree index; + ensure(index.query({ 6, 8 }) == nullptr); + + const KdNode* node68 = index.insert({ 6, 8 }); + ensure(node68); + ensure_equals(index.query({ 6, 8 }), node68); + + const KdNode* node28 = index.insert({ 2, 8 }); + ensure(node28); + ensure_equals(index.query({ 2, 8 }), node28); +} ----------------------------------------------------------------------- Summary of changes: src/index/kdtree/KdTree.cpp | 9 ++++++++- tests/unit/index/kdtree/KdTreeTest.cpp | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Jun 19 14:57:18 2026 From: git at osgeo.org (git at osgeo.org) Date: Fri, 19 Jun 2026 14:57:18 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. b7e11f5710ace681562228b791651bbc28835986 Message-ID: <20260619215718.9088E1B866C@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 b7e11f5710ace681562228b791651bbc28835986 (commit) from 4226b5a034d249e64385d3dac7e69774ff30d6f3 (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 b7e11f5710ace681562228b791651bbc28835986 Author: Daniel Baston Date: Fri Jun 19 15:13:34 2026 -0400 KdNode: mark some methods as const diff --git a/include/geos/index/kdtree/KdNode.h b/include/geos/index/kdtree/KdNode.h index e1238ab0b..1d7ea0450 100644 --- a/include/geos/index/kdtree/KdNode.h +++ b/include/geos/index/kdtree/KdNode.h @@ -42,15 +42,15 @@ public: KdNode(double p_x, double p_y, void* p_data); KdNode(const geom::Coordinate& p_p, void* p_data); - double getX() { return p.x; } - double getY() { return p.y; } - const geom::Coordinate& getCoordinate() { return p; } + double getX() const { return p.x; } + double getY() const { return p.y; } + const geom::Coordinate& getCoordinate() const { return p; } void* getData() { return data; } KdNode* getLeft() { return left; } KdNode* getRight() { return right; } void increment() { count++; } - std::size_t getCount() { return count; } - bool isRepeated() { return count > 1; } + std::size_t getCount() const { return count; } + bool isRepeated() const { return count > 1; } void setLeft(KdNode* p_left) { left = p_left; } void setRight(KdNode* p_right) { right = p_right; } diff --git a/include/geos/index/kdtree/KdTree.h b/include/geos/index/kdtree/KdTree.h index f98f203cc..a715a4c4c 100644 --- a/include/geos/index/kdtree/KdTree.h +++ b/include/geos/index/kdtree/KdTree.h @@ -119,7 +119,7 @@ private: public: /** - * Converts a collection of {@link KdNode}s to an vector of {@link geom::Coordinate}s. + * Converts a collection of {@link KdNode}s to a vector of {@link geom::Coordinate}s. * * @param kdNodes a collection of nodes * @return a vector of the coordinates represented by the nodes @@ -128,7 +128,7 @@ public: /** * Converts a collection of {@link KdNode}s - * to an vector of {@link geom::Coordinate}s, + * to a vector of {@link geom::Coordinate}s, * specifying whether repeated nodes should be represented * by multiple coordinates. * ----------------------------------------------------------------------- Summary of changes: include/geos/index/kdtree/KdNode.h | 10 +++++----- include/geos/index/kdtree/KdTree.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Fri Jun 19 15:21:34 2026 From: git at osgeo.org (git at osgeo.org) Date: Fri, 19 Jun 2026 15:21:34 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. 70f292941d2e2243f2db66cf1292bfcffcc001fb Message-ID: <20260619222135.0DBDE1B97E5@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 70f292941d2e2243f2db66cf1292bfcffcc001fb (commit) via 64e97b299d3f08160debd796c629fd7075b57892 (commit) via b7ec1cbad96d0f518badeac697da4ab3381a86da (commit) from a80d3c2da06305ce5d6cf03b882c2809c5ee43ab (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 70f292941d2e2243f2db66cf1292bfcffcc001fb Author: Daniel Baston Date: Thu Jun 18 12:49:34 2026 -0400 KdTree: Disable optimizations in latest MSVC version References #1449 diff --git a/src/index/kdtree/KdTree.cpp b/src/index/kdtree/KdTree.cpp index a0c2dfbff..9142a0789 100644 --- a/src/index/kdtree/KdTree.cpp +++ b/src/index/kdtree/KdTree.cpp @@ -226,6 +226,11 @@ KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, } } +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +// Avoid segfault with MSVC 19.51.36246.0 +// See https://github.com/libgeos/geos/issues/1449 +#pragma optimize("", off) +#endif /*private*/ KdNode* KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd) @@ -257,7 +262,9 @@ KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, boo } return nullptr; } - +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +#pragma optimize("", on) +#endif /*public*/ void commit 64e97b299d3f08160debd796c629fd7075b57892 Author: Daniel Baston Date: Fri Jun 19 09:24:45 2026 -0400 KdTree: Add minimal test for #1449 diff --git a/tests/unit/index/kdtree/KdTreeTest.cpp b/tests/unit/index/kdtree/KdTreeTest.cpp index 2de94f162..1121ca25b 100644 --- a/tests/unit/index/kdtree/KdTreeTest.cpp +++ b/tests/unit/index/kdtree/KdTreeTest.cpp @@ -189,6 +189,24 @@ void object::test<8> () ensure(node->isRepeated()); } +template<> +template<> +void object::test<9>() +{ + set_test_name("query by point"); + // minimal test for https://github.com/libgeos/geos/issues/1449 + + KdTree index; + ensure(index.query({ 6, 8 }) == nullptr); + + const KdNode* node68 = index.insert({ 6, 8 }); + ensure(node68); + ensure_equals(index.query({ 6, 8 }), node68); + + const KdNode* node28 = index.insert({ 2, 8 }); + ensure(node28); + ensure_equals(index.query({ 2, 8 }), node28); +} commit b7ec1cbad96d0f518badeac697da4ab3381a86da Author: Kurt Schwehr Date: Wed Jun 17 14:04:09 2026 -0700 Fix `size_t <<` debug prints (#1452) Bug introduced in https://github.com/libgeos/geos/commit/d271aaa9a1939fe5a51c5b5d05f1a3d547a2f900 diff --git a/src/io/WKBReader.cpp b/src/io/WKBReader.cpp index 442d28506..0016e6328 100644 --- a/src/io/WKBReader.cpp +++ b/src/io/WKBReader.cpp @@ -46,6 +46,9 @@ //#define DEBUG_WKB_READER 1 +#if DEBUG_WKB_READER +#include +#endif using namespace geos::geom; @@ -174,7 +177,7 @@ WKBReader::readHEX(std::istream& is) static_cast((result_high << 4) + result_low); #if DEBUG_HEX_READER - std::size_t << "HEX " << high << low << " -> DEC " << (int)value << std::endl; + std::cout << "HEX " << high << low << " -> DEC " << (int)value << std::endl; #endif // write the value to the output stream os << value; @@ -265,7 +268,7 @@ WKBReader::readGeometry() unsigned char byteOrder = dis.readByte(); #if DEBUG_WKB_READER - std::size_t << "WKB byteOrder: " << (int)byteOrder << std::endl; + std::cout << "WKB byteOrder: " << (int)byteOrder << std::endl; #endif // default is machine endian @@ -288,7 +291,7 @@ WKBReader::readGeometry() int sfsqlHasM = (typeInt & 0x40000000) != 0; #if DEBUG_WKB_READER - std::size_t << "WKB geometryType: " << geometryType << std::endl; + std::cout << "WKB geometryType: " << geometryType << std::endl; #endif hasZ = sfsqlHasZ || isoHasZ; @@ -304,17 +307,17 @@ WKBReader::readGeometry() } #if DEBUG_WKB_READER - std::size_t << "WKB hasZ: " << hasZ << std::endl; + std::cout << "WKB hasZ: " << hasZ << std::endl; #endif #if DEBUG_WKB_READER - std::size_t << "WKB dimensions: " << inputDimension << std::endl; + std::cout << "WKB dimensions: " << inputDimension << std::endl; #endif bool hasSRID = ((typeInt & 0x20000000) != 0); #if DEBUG_WKB_READER - std::size_t << "WKB hasSRID: " << hasSRID << std::endl; + std::cout << "WKB hasSRID: " << hasSRID << std::endl; #endif int SRID = 0; @@ -391,7 +394,7 @@ WKBReader::readLineString() uint32_t size = dis.readUnsigned(); minMemSize(GEOS_LINESTRING, size); #if DEBUG_WKB_READER - std::size_t << "WKB npoints: " << size << std::endl; + std::cout << "WKB npoints: " << size << std::endl; #endif auto pts = readCoordinateSequence(size); return factory.createLineString(std::move(pts)); @@ -403,7 +406,7 @@ WKBReader::readLinearRing() uint32_t size = dis.readUnsigned(); minMemSize(GEOS_LINEARRING, size); #if DEBUG_WKB_READER - std::size_t << "WKB npoints: " << size << std::endl; + std::cout << "WKB npoints: " << size << std::endl; #endif auto pts = readCoordinateSequence(size); // Replace unclosed ring with closed @@ -444,7 +447,7 @@ WKBReader::readPolygon() minMemSize(GEOS_POLYGON, numRings); #if DEBUG_WKB_READER - std::size_t << "WKB numRings: " << numRings << std::endl; + std::cout << "WKB numRings: " << numRings << std::endl; #endif std::unique_ptr shell; @@ -474,7 +477,7 @@ WKBReader::readCurvePolygon() minMemSize(GEOS_POLYGON, numRings); #if DEBUG_WKB_READER - std::size_t << "WKB numRings: " << numRings << std::endl; + std::cout << "WKB numRings: " << numRings << std::endl; #endif if (numRings == 0) { @@ -617,7 +620,7 @@ WKBReader::readCoordinate() } } #if DEBUG_WKB_READER - std::size_t << "WKB coordinate: " << ordValues[0] << "," << ordValues[1] << std::endl; + std::cout << "WKB coordinate: " << ordValues[0] << "," << ordValues[1] << std::endl; #endif } diff --git a/src/io/WKBWriter.cpp b/src/io/WKBWriter.cpp index f7c83d38d..95c64b044 100644 --- a/src/io/WKBWriter.cpp +++ b/src/io/WKBWriter.cpp @@ -42,6 +42,10 @@ #undef DEBUG_WKB_WRITER +#if DEBUG_WKB_READER +#include +#endif + using namespace geos::geom; @@ -380,7 +384,7 @@ void WKBWriter::writeCoordinate(const CoordinateSequence& cs, std::size_t idx) { #if DEBUG_WKB_WRITER - std::size_t << "writeCoordinate: X:" << cs.getX(idx) << " Y:" << cs.getY(idx) << std::endl; + std::cout << "writeCoordinate: X:" << cs.getX(idx) << " Y:" << cs.getY(idx) << std::endl; #endif assert(outStream); @@ -442,4 +446,3 @@ WKBWriter::getWkbType(const Geometry& g) { } // namespace geos.io } // namespace geos - diff --git a/src/operation/overlay/snap/GeometrySnapper.cpp b/src/operation/overlay/snap/GeometrySnapper.cpp index a2aed6eaa..294c2f93d 100644 --- a/src/operation/overlay/snap/GeometrySnapper.cpp +++ b/src/operation/overlay/snap/GeometrySnapper.cpp @@ -203,8 +203,8 @@ GeometrySnapper::snap(const geom::Geometry& g0, GeometrySnapper snapper1(g1); snapGeom.second = snapper1.snapTo(*snapGeom.first, snapTolerance); -// std::size_t << *snapGeom.first << std::endl; -// std::size_t << *snapGeom.second << std::endl; +// std::cout << *snapGeom.first << std::endl; +// std::cout << *snapGeom.second << std::endl; } @@ -222,4 +222,3 @@ GeometrySnapper::snapToSelf(const geom::Geometry& g, } // namespace geos.operation.overlay } // namespace geos.operation } // namespace geos - ----------------------------------------------------------------------- Summary of changes: src/index/kdtree/KdTree.cpp | 9 ++++++++- src/io/WKBReader.cpp | 25 ++++++++++++++----------- src/io/WKBWriter.cpp | 7 +++++-- src/operation/overlay/snap/GeometrySnapper.cpp | 5 ++--- tests/unit/index/kdtree/KdTreeTest.cpp | 18 ++++++++++++++++++ 5 files changed, 47 insertions(+), 17 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Sat Jun 20 05:08:47 2026 From: git at osgeo.org (git at osgeo.org) Date: Sat, 20 Jun 2026 05:08:47 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.12 updated. ef1e94327bfb9f9f1b175d8a85f8220f72497f41 Message-ID: <20260620120848.028901C2BB1@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 ef1e94327bfb9f9f1b175d8a85f8220f72497f41 (commit) via 34fd1f89d476e8ffcba1d01afa86de7b1685868c (commit) from a8df87a0ff1989e8b3b088e25785ee7a0e4769a6 (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 ef1e94327bfb9f9f1b175d8a85f8220f72497f41 Author: Daniel Baston Date: Thu Jun 18 12:49:34 2026 -0400 KdTree: Disable optimizations in latest MSVC version References #1449 diff --git a/src/index/kdtree/KdTree.cpp b/src/index/kdtree/KdTree.cpp index a0c2dfbff..9142a0789 100644 --- a/src/index/kdtree/KdTree.cpp +++ b/src/index/kdtree/KdTree.cpp @@ -226,6 +226,11 @@ KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, } } +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +// Avoid segfault with MSVC 19.51.36246.0 +// See https://github.com/libgeos/geos/issues/1449 +#pragma optimize("", off) +#endif /*private*/ KdNode* KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd) @@ -257,7 +262,9 @@ KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, boo } return nullptr; } - +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +#pragma optimize("", on) +#endif /*public*/ void commit 34fd1f89d476e8ffcba1d01afa86de7b1685868c Author: Daniel Baston Date: Fri Jun 19 09:24:45 2026 -0400 KdTree: Add minimal test for #1449 diff --git a/tests/unit/index/kdtree/KdTreeTest.cpp b/tests/unit/index/kdtree/KdTreeTest.cpp index 2de94f162..1121ca25b 100644 --- a/tests/unit/index/kdtree/KdTreeTest.cpp +++ b/tests/unit/index/kdtree/KdTreeTest.cpp @@ -189,6 +189,24 @@ void object::test<8> () ensure(node->isRepeated()); } +template<> +template<> +void object::test<9>() +{ + set_test_name("query by point"); + // minimal test for https://github.com/libgeos/geos/issues/1449 + + KdTree index; + ensure(index.query({ 6, 8 }) == nullptr); + + const KdNode* node68 = index.insert({ 6, 8 }); + ensure(node68); + ensure_equals(index.query({ 6, 8 }), node68); + + const KdNode* node28 = index.insert({ 2, 8 }); + ensure(node28); + ensure_equals(index.query({ 2, 8 }), node28); +} ----------------------------------------------------------------------- Summary of changes: src/index/kdtree/KdTree.cpp | 9 ++++++++- tests/unit/index/kdtree/KdTreeTest.cpp | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Sat Jun 20 05:08:50 2026 From: git at osgeo.org (git at osgeo.org) Date: Sat, 20 Jun 2026 05:08:50 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.13 updated. 4a31a2b4d3d17e01c028fe0539f2d0235bbd27f7 Message-ID: <20260620120850.E0FA21C3B83@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 4a31a2b4d3d17e01c028fe0539f2d0235bbd27f7 (commit) via f84cfb521a2f72bf119d444701738eaac7bee1ea (commit) from 98da0fad0b9abff91349cf71d19196c5d48e258a (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 4a31a2b4d3d17e01c028fe0539f2d0235bbd27f7 Author: Daniel Baston Date: Thu Jun 18 12:49:34 2026 -0400 KdTree: Disable optimizations in latest MSVC version References #1449 diff --git a/src/index/kdtree/KdTree.cpp b/src/index/kdtree/KdTree.cpp index a0c2dfbff..9142a0789 100644 --- a/src/index/kdtree/KdTree.cpp +++ b/src/index/kdtree/KdTree.cpp @@ -226,6 +226,11 @@ KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, } } +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +// Avoid segfault with MSVC 19.51.36246.0 +// See https://github.com/libgeos/geos/issues/1449 +#pragma optimize("", off) +#endif /*private*/ KdNode* KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd) @@ -257,7 +262,9 @@ KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, boo } return nullptr; } - +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +#pragma optimize("", on) +#endif /*public*/ void commit f84cfb521a2f72bf119d444701738eaac7bee1ea Author: Daniel Baston Date: Fri Jun 19 09:24:45 2026 -0400 KdTree: Add minimal test for #1449 diff --git a/tests/unit/index/kdtree/KdTreeTest.cpp b/tests/unit/index/kdtree/KdTreeTest.cpp index 2de94f162..1121ca25b 100644 --- a/tests/unit/index/kdtree/KdTreeTest.cpp +++ b/tests/unit/index/kdtree/KdTreeTest.cpp @@ -189,6 +189,24 @@ void object::test<8> () ensure(node->isRepeated()); } +template<> +template<> +void object::test<9>() +{ + set_test_name("query by point"); + // minimal test for https://github.com/libgeos/geos/issues/1449 + + KdTree index; + ensure(index.query({ 6, 8 }) == nullptr); + + const KdNode* node68 = index.insert({ 6, 8 }); + ensure(node68); + ensure_equals(index.query({ 6, 8 }), node68); + + const KdNode* node28 = index.insert({ 2, 8 }); + ensure(node28); + ensure_equals(index.query({ 2, 8 }), node28); +} ----------------------------------------------------------------------- Summary of changes: src/index/kdtree/KdTree.cpp | 9 ++++++++- tests/unit/index/kdtree/KdTreeTest.cpp | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Sat Jun 20 05:09:10 2026 From: git at osgeo.org (git at osgeo.org) Date: Sat, 20 Jun 2026 05:09:10 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.11 updated. 72b0c32d012bbb078e6ba83b49b2676c340949a0 Message-ID: <20260620120910.757241C3C12@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.11 has been updated via 72b0c32d012bbb078e6ba83b49b2676c340949a0 (commit) via 3ea733fad5ee52d828d5e83feae667eb7788576c (commit) from 0576a1fd2a49ef2c7b1abc7245e769565b169b02 (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 72b0c32d012bbb078e6ba83b49b2676c340949a0 Author: Daniel Baston Date: Thu Jun 18 12:49:34 2026 -0400 KdTree: Disable optimizations in latest MSVC version References #1449 diff --git a/src/index/kdtree/KdTree.cpp b/src/index/kdtree/KdTree.cpp index a0c2dfbff..9142a0789 100644 --- a/src/index/kdtree/KdTree.cpp +++ b/src/index/kdtree/KdTree.cpp @@ -226,6 +226,11 @@ KdTree::queryNode(KdNode* currentNode, const geom::Envelope& queryEnv, bool odd, } } +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +// Avoid segfault with MSVC 19.51.36246.0 +// See https://github.com/libgeos/geos/issues/1449 +#pragma optimize("", off) +#endif /*private*/ KdNode* KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, bool odd) @@ -257,7 +262,9 @@ KdTree::queryNodePoint(KdNode* currentNode, const geom::Coordinate& queryPt, boo } return nullptr; } - +#if defined(_MSC_VER) && _MSC_FULL_VER > 195035734 +#pragma optimize("", on) +#endif /*public*/ void commit 3ea733fad5ee52d828d5e83feae667eb7788576c Author: Daniel Baston Date: Fri Jun 19 09:24:45 2026 -0400 KdTree: Add minimal test for #1449 diff --git a/tests/unit/index/kdtree/KdTreeTest.cpp b/tests/unit/index/kdtree/KdTreeTest.cpp index 2de94f162..1121ca25b 100644 --- a/tests/unit/index/kdtree/KdTreeTest.cpp +++ b/tests/unit/index/kdtree/KdTreeTest.cpp @@ -189,6 +189,24 @@ void object::test<8> () ensure(node->isRepeated()); } +template<> +template<> +void object::test<9>() +{ + set_test_name("query by point"); + // minimal test for https://github.com/libgeos/geos/issues/1449 + + KdTree index; + ensure(index.query({ 6, 8 }) == nullptr); + + const KdNode* node68 = index.insert({ 6, 8 }); + ensure(node68); + ensure_equals(index.query({ 6, 8 }), node68); + + const KdNode* node28 = index.insert({ 2, 8 }); + ensure(node28); + ensure_equals(index.query({ 2, 8 }), node28); +} ----------------------------------------------------------------------- Summary of changes: src/index/kdtree/KdTree.cpp | 9 ++++++++- tests/unit/index/kdtree/KdTreeTest.cpp | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) hooks/post-receive -- GEOS From git at osgeo.org Sat Jun 20 05:10:41 2026 From: git at osgeo.org (git at osgeo.org) Date: Sat, 20 Jun 2026 05:10:41 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch 3.14 updated. 7db6f62d50221a7ccac91c329a9a4fe61d172a56 Message-ID: <20260620121041.C86E41C2C62@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 7db6f62d50221a7ccac91c329a9a4fe61d172a56 (commit) from 70f292941d2e2243f2db66cf1292bfcffcc001fb (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 7db6f62d50221a7ccac91c329a9a4fe61d172a56 Author: Daniel Baston Date: Sat Jun 20 08:10:23 2026 -0400 CommonBits: clean UBSAN error (#1451) diff --git a/src/precision/CommonBits.cpp b/src/precision/CommonBits.cpp index e5d004ebb..179e0d557 100644 --- a/src/precision/CommonBits.cpp +++ b/src/precision/CommonBits.cpp @@ -14,6 +14,7 @@ **********************************************************************/ #include +#include namespace geos { namespace precision { // geos.precision @@ -71,6 +72,11 @@ CommonBits::CommonBits() void CommonBits::add(double num) { + if (num > static_cast(std::numeric_limits::max())) { + commonBits = 0; + return; + } + int64_t numBits = (int64_t) num; if(isFirst) { commonBits = numBits; ----------------------------------------------------------------------- Summary of changes: src/precision/CommonBits.cpp | 6 ++++++ 1 file changed, 6 insertions(+) hooks/post-receive -- GEOS From git at osgeo.org Mon Jun 22 17:52:00 2026 From: git at osgeo.org (git at osgeo.org) Date: Mon, 22 Jun 2026 17:52:00 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. ab8e9c9a6059b407644e041dd739459631856514 Message-ID: <20260623005201.416CB1A2F44@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 ab8e9c9a6059b407644e041dd739459631856514 (commit) from b7e11f5710ace681562228b791651bbc28835986 (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 ab8e9c9a6059b407644e041dd739459631856514 Author: Arthur Chan Date: Tue Jun 23 01:51:39 2026 +0100 OSS-Fuzz: Move fuzzers upstream (#1447) Signed-off-by: Arthur Chan diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3401775c8..70af383b4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,3 +10,4 @@ ################################################################################ add_subdirectory(unit) add_subdirectory(xmltester) +add_subdirectory(fuzz) diff --git a/tests/CMakeLists.txt b/tests/fuzz/CMakeLists.txt similarity index 50% copy from tests/CMakeLists.txt copy to tests/fuzz/CMakeLists.txt index 3401775c8..d8868ed88 100644 --- a/tests/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -8,5 +8,12 @@ # by the Free Software Foundation. # See the COPYING file for more information. ################################################################################ -add_subdirectory(unit) -add_subdirectory(xmltester) +if(DEFINED ENV{LIB_FUZZING_ENGINE}) + add_executable(fuzz_geo2 fuzz_geo2.c) + target_include_directories(fuzz_geo2 PUBLIC $) + target_link_libraries(fuzz_geo2 geos_c $ENV{LIB_FUZZING_ENGINE}) + + add_executable(fuzz_geojson fuzz_geojson.c) + target_include_directories(fuzz_geojson PUBLIC $) + target_link_libraries(fuzz_geojson geos_c $ENV{LIB_FUZZING_ENGINE}) +endif() diff --git a/tests/fuzz/fuzz_geo2.c b/tests/fuzz/fuzz_geo2.c new file mode 100644 index 000000000..ceee7ea6b --- /dev/null +++ b/tests/fuzz/fuzz_geo2.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +#include "geos_c.h" + +static int initialized = 0; +FILE * flogOut; + +void +notice(const char *fmt, ...) { + va_list ap; + fprintf( flogOut, "NOTICE: "); + va_start (ap, fmt); + vfprintf( flogOut, fmt, ap); + va_end(ap); + fprintf( flogOut, "\n" ); +} + +void +log_and_exit(const char *fmt, ...) { + va_list ap; + fprintf( flogOut, "ERROR: "); + va_start (ap, fmt); + vfprintf( flogOut, fmt, ap); + va_end(ap); + fprintf( flogOut, "\n" ); +} + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (initialized == 0) { + flogOut = fopen("/dev/null", "wb"); + initGEOS(notice, log_and_exit); + initialized = 1; + } + size_t sep; + for (sep = 0; sep < Size; sep ++) { + if (Data[sep] == 0) { + break; + } + } + if (sep == Size) { + return 0; + } + GEOSGeometry *g1 = GEOSGeomFromWKT(Data); + + if (g1 != NULL) { + GEOSGeometry *g2 = GEOSGeomFromWKB_buf(Data+sep, Size-sep); + if (g2 != NULL) { + size_t usize; + GEOSGeometry *g3 = GEOSIntersection(g1, g2); + GEOSGeom_destroy(g3); + g3 = GEOSDifference(g1, g2); + GEOSGeom_destroy(g3); + g3 = GEOSUnion(g1, g2); + GEOSGeom_destroy(g3); + unsigned char* uptr = GEOSGeomToWKB_buf(g1, &usize); + free(uptr); + GEOSGeom_destroy(g2); + } + char * r = GEOSGeomToWKT(g1); + free(r); + GEOSGeom_destroy(g1); + } + return 0; +} + diff --git a/tests/fuzz/fuzz_geojson.c b/tests/fuzz/fuzz_geojson.c new file mode 100644 index 000000000..bbdcc6bfd --- /dev/null +++ b/tests/fuzz/fuzz_geojson.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +#include "geos_c.h" + +static int initialized = 0; +FILE * flogOut; + +void +notice(const char *fmt, ...) { + va_list ap; + fprintf( flogOut, "NOTICE: "); + va_start (ap, fmt); + vfprintf( flogOut, fmt, ap); + va_end(ap); + fprintf( flogOut, "\n" ); +} + +void +log_and_exit(const char *fmt, ...) { + va_list ap; + fprintf( flogOut, "ERROR: "); + va_start (ap, fmt); + vfprintf( flogOut, fmt, ap); + va_end(ap); + fprintf( flogOut, "\n" ); +} + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (initialized == 0) { + flogOut = fopen("/dev/null", "wb"); + initGEOS(notice, log_and_exit); + initialized = 1; + } + + char *json = (char *) malloc(Size + 1); + if (json == NULL) { + return 0; + } + memcpy(json, Data, Size); + json[Size] = '\0'; + + GEOSGeoJSONReader *reader = GEOSGeoJSONReader_create(); + if (reader != NULL) { + GEOSGeometry *g = GEOSGeoJSONReader_readGeometry(reader, json); + if (g != NULL) { + GEOSGeom_destroy(g); + } + GEOSGeoJSONReader_destroy(reader); + } + + free(json); + return 0; +} diff --git a/tests/fuzz/geojson.dict b/tests/fuzz/geojson.dict new file mode 100644 index 000000000..14d2ee89b --- /dev/null +++ b/tests/fuzz/geojson.dict @@ -0,0 +1,38 @@ +# GeoJSON tokens for fuzz_geojson (see geos::io::GeoJSONReader) +# Object keys +"type" +"coordinates" +"geometry" +"geometries" +"features" +"properties" +# Geometry / object type values +"Point" +"LineString" +"Polygon" +"MultiPoint" +"MultiLineString" +"MultiPolygon" +"GeometryCollection" +"Feature" +"FeatureCollection" +# Common key/value fragments +"\"type\":" +"\"coordinates\":" +"\"geometry\":" +"\"properties\":" +"\"features\":" +"\"geometries\":" +# Structural / literal tokens +"{" +"}" +"[" +"]" +":" +"," +"true" +"false" +"null" +"0.0" +"1.0" +"1e10" diff --git a/tests/fuzz/geojson_seed_corpus/feature.json b/tests/fuzz/geojson_seed_corpus/feature.json new file mode 100644 index 000000000..0ead4f4eb --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/feature.json @@ -0,0 +1 @@ +{"type":"Feature","geometry":{"type":"Point","coordinates":[100.0,0.0]},"properties":{"name":"x","n":1,"b":true,"nil":null}} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/featurecollection.json b/tests/fuzz/geojson_seed_corpus/featurecollection.json new file mode 100644 index 000000000..9913dd38f --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/featurecollection.json @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[100.0,0.0]},"properties":{}},{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,0.0]]]},"properties":{"k":"v"}}]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/geometrycollection.json b/tests/fuzz/geojson_seed_corpus/geometrycollection.json new file mode 100644 index 000000000..0c78938cd --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/geometrycollection.json @@ -0,0 +1 @@ +{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[100.0,0.0]},{"type":"LineString","coordinates":[[101.0,0.0],[102.0,1.0]]}]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/linestring.json b/tests/fuzz/geojson_seed_corpus/linestring.json new file mode 100644 index 000000000..4a40c6726 --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/linestring.json @@ -0,0 +1 @@ +{"type":"LineString","coordinates":[[100.0,0.0],[101.0,1.0]]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/multilinestring.json b/tests/fuzz/geojson_seed_corpus/multilinestring.json new file mode 100644 index 000000000..68aa13f58 --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/multilinestring.json @@ -0,0 +1 @@ +{"type":"MultiLineString","coordinates":[[[100.0,0.0],[101.0,1.0]],[[102.0,2.0],[103.0,3.0]]]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/multipoint.json b/tests/fuzz/geojson_seed_corpus/multipoint.json new file mode 100644 index 000000000..27c3d27de --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/multipoint.json @@ -0,0 +1 @@ +{"type":"MultiPoint","coordinates":[[100.0,0.0],[101.0,1.0]]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/multipolygon.json b/tests/fuzz/geojson_seed_corpus/multipolygon.json new file mode 100644 index 000000000..afa67db9f --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/multipolygon.json @@ -0,0 +1 @@ +{"type":"MultiPolygon","coordinates":[[[[102.0,2.0],[103.0,2.0],[103.0,3.0],[102.0,3.0],[102.0,2.0]]],[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/point.json b/tests/fuzz/geojson_seed_corpus/point.json new file mode 100644 index 000000000..2756bae67 --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/point.json @@ -0,0 +1 @@ +{"type":"Point","coordinates":[100.0,0.0]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/point_z.json b/tests/fuzz/geojson_seed_corpus/point_z.json new file mode 100644 index 000000000..a13a91b66 --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/point_z.json @@ -0,0 +1 @@ +{"type":"Point","coordinates":[100.0,0.0,50.0]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/polygon.json b/tests/fuzz/geojson_seed_corpus/polygon.json new file mode 100644 index 000000000..9c573c1a3 --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/polygon.json @@ -0,0 +1 @@ +{"type":"Polygon","coordinates":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]} \ No newline at end of file diff --git a/tests/fuzz/geojson_seed_corpus/polygon_hole.json b/tests/fuzz/geojson_seed_corpus/polygon_hole.json new file mode 100644 index 000000000..11661ebf6 --- /dev/null +++ b/tests/fuzz/geojson_seed_corpus/polygon_hole.json @@ -0,0 +1 @@ +{"type":"Polygon","coordinates":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]} \ No newline at end of file ----------------------------------------------------------------------- Summary of changes: tests/CMakeLists.txt | 1 + {benchmarks/index => tests/fuzz}/CMakeLists.txt | 16 +++-- tests/fuzz/fuzz_geo2.c | 69 ++++++++++++++++++++++ tests/fuzz/fuzz_geojson.c | 57 ++++++++++++++++++ tests/fuzz/geojson.dict | 38 ++++++++++++ tests/fuzz/geojson_seed_corpus/feature.json | 1 + .../geojson_seed_corpus/featurecollection.json | 1 + .../geojson_seed_corpus/geometrycollection.json | 1 + tests/fuzz/geojson_seed_corpus/linestring.json | 1 + .../fuzz/geojson_seed_corpus/multilinestring.json | 1 + tests/fuzz/geojson_seed_corpus/multipoint.json | 1 + tests/fuzz/geojson_seed_corpus/multipolygon.json | 1 + tests/fuzz/geojson_seed_corpus/point.json | 1 + tests/fuzz/geojson_seed_corpus/point_z.json | 1 + tests/fuzz/geojson_seed_corpus/polygon.json | 1 + tests/fuzz/geojson_seed_corpus/polygon_hole.json | 1 + 16 files changed, 183 insertions(+), 9 deletions(-) copy {benchmarks/index => tests/fuzz}/CMakeLists.txt (51%) create mode 100644 tests/fuzz/fuzz_geo2.c create mode 100644 tests/fuzz/fuzz_geojson.c create mode 100644 tests/fuzz/geojson.dict create mode 100644 tests/fuzz/geojson_seed_corpus/feature.json create mode 100644 tests/fuzz/geojson_seed_corpus/featurecollection.json create mode 100644 tests/fuzz/geojson_seed_corpus/geometrycollection.json create mode 100644 tests/fuzz/geojson_seed_corpus/linestring.json create mode 100644 tests/fuzz/geojson_seed_corpus/multilinestring.json create mode 100644 tests/fuzz/geojson_seed_corpus/multipoint.json create mode 100644 tests/fuzz/geojson_seed_corpus/multipolygon.json create mode 100644 tests/fuzz/geojson_seed_corpus/point.json create mode 100644 tests/fuzz/geojson_seed_corpus/point_z.json create mode 100644 tests/fuzz/geojson_seed_corpus/polygon.json create mode 100644 tests/fuzz/geojson_seed_corpus/polygon_hole.json hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 23 14:14:53 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 23 Jun 2026 14:14:53 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. cbd9839e30e64d2f851358feaec2ccb2acc1b804 Message-ID: <20260623211453.DD3D41B4889@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 cbd9839e30e64d2f851358feaec2ccb2acc1b804 (commit) from ab8e9c9a6059b407644e041dd739459631856514 (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 cbd9839e30e64d2f851358feaec2ccb2acc1b804 Author: Paul Ramsey Date: Tue Jun 23 21:12:01 2026 +0000 Fuzzer fixes Ensure WKB pointer actually points to start of WKB segment and not the separator null byte. Only setup JSON reader once, not for every iteration. diff --git a/tests/fuzz/fuzz_geo2.c b/tests/fuzz/fuzz_geo2.c index ceee7ea6b..26c22e79c 100644 --- a/tests/fuzz/fuzz_geo2.c +++ b/tests/fuzz/fuzz_geo2.c @@ -47,7 +47,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { GEOSGeometry *g1 = GEOSGeomFromWKT(Data); if (g1 != NULL) { - GEOSGeometry *g2 = GEOSGeomFromWKB_buf(Data+sep, Size-sep); + GEOSGeometry *g2 = GEOSGeomFromWKB_buf(Data+sep+1, Size-sep-1); if (g2 != NULL) { size_t usize; GEOSGeometry *g3 = GEOSIntersection(g1, g2); @@ -57,11 +57,11 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { g3 = GEOSUnion(g1, g2); GEOSGeom_destroy(g3); unsigned char* uptr = GEOSGeomToWKB_buf(g1, &usize); - free(uptr); + GEOSFree(uptr); GEOSGeom_destroy(g2); } char * r = GEOSGeomToWKT(g1); - free(r); + GEOSFree(r); GEOSGeom_destroy(g1); } return 0; diff --git a/tests/fuzz/fuzz_geojson.c b/tests/fuzz/fuzz_geojson.c index bbdcc6bfd..28d100ce0 100644 --- a/tests/fuzz/fuzz_geojson.c +++ b/tests/fuzz/fuzz_geojson.c @@ -6,8 +6,8 @@ #include "geos_c.h" -static int initialized = 0; -FILE * flogOut; +static FILE * flogOut; +static GEOSGeoJSONReader *reader; void notice(const char *fmt, ...) { @@ -29,11 +29,17 @@ log_and_exit(const char *fmt, ...) { fprintf( flogOut, "\n" ); } +int LLVMFuzzerInitialize(int *argc, char ***argv) { + (void)argc; (void)argv; + flogOut = fopen("/dev/null", "wb"); + initGEOS(notice, log_and_exit); + reader = GEOSGeoJSONReader_create(); + return 0; +} + int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - if (initialized == 0) { - flogOut = fopen("/dev/null", "wb"); - initGEOS(notice, log_and_exit); - initialized = 1; + if (reader == NULL) { + return 0; } char *json = (char *) malloc(Size + 1); @@ -43,13 +49,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { memcpy(json, Data, Size); json[Size] = '\0'; - GEOSGeoJSONReader *reader = GEOSGeoJSONReader_create(); - if (reader != NULL) { - GEOSGeometry *g = GEOSGeoJSONReader_readGeometry(reader, json); - if (g != NULL) { - GEOSGeom_destroy(g); - } - GEOSGeoJSONReader_destroy(reader); + GEOSGeometry *g = GEOSGeoJSONReader_readGeometry(reader, json); + if (g != NULL) { + GEOSGeom_destroy(g); } free(json); ----------------------------------------------------------------------- Summary of changes: tests/fuzz/fuzz_geo2.c | 6 +++--- tests/fuzz/fuzz_geojson.c | 28 +++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) hooks/post-receive -- GEOS From git at osgeo.org Tue Jun 23 14:42:18 2026 From: git at osgeo.org (git at osgeo.org) Date: Tue, 23 Jun 2026 14:42:18 -0700 (PDT) Subject: [geos-commits] [SCM] GEOS branch main updated. 72a954f63aab8eaec71d6570d900b3450e0ea946 Message-ID: <20260623214218.71FA91B4D99@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 72a954f63aab8eaec71d6570d900b3450e0ea946 (commit) from cbd9839e30e64d2f851358feaec2ccb2acc1b804 (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 72a954f63aab8eaec71d6570d900b3450e0ea946 Author: Paul Ramsey Date: Tue Jun 23 14:41:54 2026 -0700 CMake status entry when ossfuzz is enabled diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index d8868ed88..e96ac876f 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -10,6 +10,7 @@ ################################################################################ if(DEFINED ENV{LIB_FUZZING_ENGINE}) add_executable(fuzz_geo2 fuzz_geo2.c) + message(STATUS "GEOS: OSSFuzz build ENABLED") target_include_directories(fuzz_geo2 PUBLIC $) target_link_libraries(fuzz_geo2 geos_c $ENV{LIB_FUZZING_ENGINE}) ----------------------------------------------------------------------- Summary of changes: tests/fuzz/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) hooks/post-receive -- GEOS