From 6b413f1244a45283aed4db098df4969d1bc8b97c Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Sat, 4 Jan 2025 16:38:10 -0500 Subject: [PATCH 1/3] Coverage union: add benchmark --- benchmarks/operation/CMakeLists.txt | 8 +++ .../operation/CoverageUnionPerfTest.cpp | 65 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 benchmarks/operation/CoverageUnionPerfTest.cpp diff --git a/benchmarks/operation/CMakeLists.txt b/benchmarks/operation/CMakeLists.txt index 8df2a6bfd0..0294a6eb39 100644 --- a/benchmarks/operation/CMakeLists.txt +++ b/benchmarks/operation/CMakeLists.txt @@ -19,4 +19,12 @@ if (benchmark_FOUND) $) target_link_libraries(perf_distance PRIVATE benchmark::benchmark geos geos_cxx_flags) + + add_executable(perf_coverage_union CoverageUnionPerfTest.cpp) + target_include_directories(perf_coverage_union PUBLIC + $ + $ + $) + target_link_libraries(perf_coverage_union PRIVATE + benchmark::benchmark geos geos_cxx_flags) endif() diff --git a/benchmarks/operation/CoverageUnionPerfTest.cpp b/benchmarks/operation/CoverageUnionPerfTest.cpp new file mode 100644 index 0000000000..8cb957f12a --- /dev/null +++ b/benchmarks/operation/CoverageUnionPerfTest.cpp @@ -0,0 +1,65 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2025 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +struct SegmentSet { + static void Union(const geos::geom::GeometryCollection& coll) { + geos::operation::geounion::CoverageUnion::Union(&coll); + } +}; + +struct BoundaryChain { + static void Union(const geos::geom::GeometryCollection& coll) { + auto result = geos::coverage::CoverageUnion::Union(&coll); + } +}; + +template +static void BM_CoverageUnion(benchmark::State& state) { + const auto& gfact = *geos::geom::GeometryFactory::getDefaultInstance(); + + auto nCells = state.range(0); + + auto nx = static_cast(std::ceil(std::sqrt(nCells))); + auto ny = static_cast(std::ceil(std::sqrt(nCells))); + + nCells = nx*ny; + + geos::geom::Envelope env(0, nx, 0, ny); + + auto cells = geos::benchmark::createGeometriesOnGrid(env, static_cast(nCells), [&gfact](const auto& base) { + geos::geom::Envelope box(base.x, base.x + 1, base.y, base.y + 1); + return gfact.toGeometry(&box); + }); + + auto coll = gfact.createGeometryCollection(std::move(cells)); + + for (auto _ : state) { + Impl::Union(*coll); + } +} + +BENCHMARK_TEMPLATE(BM_CoverageUnion, SegmentSet)->Range(1000, 1000000); +BENCHMARK_TEMPLATE(BM_CoverageUnion, BoundaryChain)->Range(1000, 1000000); + +BENCHMARK_MAIN(); + From d3743ed7019203dd7dcd415cc4556e4e5264f025 Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Wed, 11 Dec 2024 16:48:18 -0500 Subject: [PATCH 2/3] GeometryCollection: Cache properties to avoid O(n) lookups --- include/geos/geom/GeometryCollection.h | 12 ++++ src/geom/GeometryCollection.cpp | 95 +++++++++++++++++--------- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/include/geos/geom/GeometryCollection.h b/include/geos/geom/GeometryCollection.h index 9437f6af31..fd30f92046 100644 --- a/include/geos/geom/GeometryCollection.h +++ b/include/geos/geom/GeometryCollection.h @@ -198,6 +198,16 @@ class GEOS_DLL GeometryCollection : public Geometry { protected: + struct CollectionFlags { + bool flagsCalculated; + bool hasPoints; + bool hasLines; + bool hasPolygons; + bool hasM; + bool hasZ; + bool hasCurves; + }; + GeometryCollection(const GeometryCollection& gc); GeometryCollection& operator=(const GeometryCollection& gc); @@ -236,6 +246,7 @@ class GEOS_DLL GeometryCollection : public Geometry { }; std::vector> geometries; + mutable CollectionFlags flags; mutable Envelope envelope; Envelope computeEnvelopeInternal() const; @@ -248,6 +259,7 @@ class GEOS_DLL GeometryCollection : public Geometry { bool hasCurvedComponents() const override; + void setFlags() const; }; diff --git a/src/geom/GeometryCollection.cpp b/src/geom/GeometryCollection.cpp index 80ecbccae1..2744b2e52c 100644 --- a/src/geom/GeometryCollection.cpp +++ b/src/geom/GeometryCollection.cpp @@ -39,6 +39,7 @@ GeometryCollection::GeometryCollection(const GeometryCollection& gc) : Geometry(gc), geometries(gc.geometries.size()), + flags{}, // set all flags to zero envelope(gc.envelope) { for(std::size_t i = 0; i < geometries.size(); ++i) { @@ -51,6 +52,7 @@ GeometryCollection::operator=(const GeometryCollection& gc) { geometries.resize(gc.geometries.size()); envelope = gc.envelope; + flags = gc.flags; for (std::size_t i = 0; i < geometries.size(); i++) { geometries[i] = gc.geometries[i]->clone(); @@ -62,7 +64,9 @@ GeometryCollection::operator=(const GeometryCollection& gc) GeometryCollection::GeometryCollection(std::vector> && newGeoms, const GeometryFactory& factory) : Geometry(&factory), geometries(std::move(newGeoms)), - envelope(computeEnvelopeInternal()) { + flags{}, // set all flags to zero + envelope(computeEnvelopeInternal()) +{ if (hasNullElements(&geometries)) { throw util::IllegalArgumentException("geometries must not contain null elements\n"); @@ -114,31 +118,69 @@ GeometryCollection::isEmpty() const return true; } +void +GeometryCollection::setFlags() const { + if (flags.flagsCalculated) { + return; + } + + for (const auto& geom : geometries) { + flags.hasPoints |= geom->hasDimension(Dimension::P); + flags.hasLines |= geom->hasDimension(Dimension::L); + flags.hasPolygons |= geom->hasDimension(Dimension::A); + flags.hasM |= geom->hasM(); + flags.hasZ |= geom->hasZ(); + flags.hasCurves |= geom->hasCurvedComponents(); + } + + flags.flagsCalculated = true; +} + Dimension::DimensionType GeometryCollection::getDimension() const { - Dimension::DimensionType dimension = Dimension::False; - for(const auto& g : geometries) { - dimension = std::max(dimension, g->getDimension()); + setFlags(); + + if (flags.hasPolygons) { + return Dimension::A; } - return dimension; + if (flags.hasLines) { + return Dimension::L; + } + if (flags.hasPoints) { + return Dimension::P; + } + return Dimension::False; } bool GeometryCollection::isDimensionStrict(Dimension::DimensionType d) const { - return std::all_of(geometries.begin(), geometries.end(), - [&d](const std::unique_ptr & g) { - return g->getDimension() == d; - }); + setFlags(); + + if (isEmpty()) { + return true; + } + + switch(d) { + case Dimension::A: return flags.hasPolygons && !flags.hasLines && !flags.hasPoints; + case Dimension::L: return !flags.hasPolygons && flags.hasLines && !flags.hasPoints; + case Dimension::P: return !flags.hasPolygons && !flags.hasLines && flags.hasPoints; + default: + return false; + } } bool GeometryCollection::hasDimension(Dimension::DimensionType d) const { - return std::any_of(geometries.begin(), - geometries.end(), - [&d](const std::unique_ptr& g) { - return g->hasDimension(d); - }); + setFlags(); + + switch (d) { + case Dimension:: A: return flags.hasPolygons; + case Dimension:: L: return flags.hasLines; + case Dimension:: P: return flags.hasPoints; + default: + return false; + } } int @@ -165,23 +207,15 @@ GeometryCollection::getCoordinateDimension() const bool GeometryCollection::hasM() const { - for (const auto& g : geometries) { - if (g->hasM()) { - return true; - } - } - return false; + setFlags(); + return flags.hasM; } bool GeometryCollection::hasZ() const { - for (const auto& g : geometries) { - if (g->hasZ()) { - return true; - } - } - return false; + setFlags(); + return flags.hasZ; } size_t @@ -201,6 +235,7 @@ GeometryCollection::releaseGeometries() { auto ret = std::move(geometries); geometryChanged(); + flags.flagsCalculated = false; return ret; } @@ -334,12 +369,8 @@ GeometryCollection::compareToSameClass(const Geometry* g) const } bool GeometryCollection::hasCurvedComponents() const { - for (const auto& g : geometries) { - if (g->hasCurvedComponents()) { - return true; - } - } - return false; + setFlags(); + return flags.hasCurves; } const CoordinateXY* From 383439b0c8809fbf0bb34b53d7ae4b3cb952a93d Mon Sep 17 00:00:00 2001 From: Dan Baston Date: Sun, 5 Jan 2025 17:14:05 -0500 Subject: [PATCH 3/3] Update src/geom/GeometryCollection.cpp Co-authored-by: Even Rouault --- src/geom/GeometryCollection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geom/GeometryCollection.cpp b/src/geom/GeometryCollection.cpp index 2744b2e52c..25877b81a6 100644 --- a/src/geom/GeometryCollection.cpp +++ b/src/geom/GeometryCollection.cpp @@ -39,7 +39,7 @@ GeometryCollection::GeometryCollection(const GeometryCollection& gc) : Geometry(gc), geometries(gc.geometries.size()), - flags{}, // set all flags to zero + flags(gc.flags), envelope(gc.envelope) { for(std::size_t i = 0; i < geometries.size(); ++i) {