Skip to content

Commit

Permalink
GeometryCollection: Cache flags to avoid O(n) lookups (#1220)
Browse files Browse the repository at this point in the history
* Coverage union: add benchmark

* GeometryCollection: Cache properties to avoid O(n) lookups
  • Loading branch information
dbaston authored Jan 12, 2025
1 parent 145b8d1 commit 1660b79
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 32 deletions.
8 changes: 8 additions & 0 deletions benchmarks/operation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ if (benchmark_FOUND)
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>)
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
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>)
target_link_libraries(perf_coverage_union PRIVATE
benchmark::benchmark geos geos_cxx_flags)
endif()
65 changes: 65 additions & 0 deletions benchmarks/operation/CoverageUnionPerfTest.cpp
Original file line number Diff line number Diff line change
@@ -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 <stdexcept>
#include <benchmark/benchmark.h>

#include <BenchmarkUtils.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/Geometry.h>
#include <geos/coverage/CoverageUnion.h>
#include <geos/operation/union/CoverageUnion.h>

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<typename Impl>
static void BM_CoverageUnion(benchmark::State& state) {
const auto& gfact = *geos::geom::GeometryFactory::getDefaultInstance();

auto nCells = state.range(0);

auto nx = static_cast<int>(std::ceil(std::sqrt(nCells)));
auto ny = static_cast<int>(std::ceil(std::sqrt(nCells)));

nCells = nx*ny;

geos::geom::Envelope env(0, nx, 0, ny);

auto cells = geos::benchmark::createGeometriesOnGrid(env, static_cast<std::size_t>(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();

12 changes: 12 additions & 0 deletions include/geos/geom/GeometryCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -236,6 +246,7 @@ class GEOS_DLL GeometryCollection : public Geometry {
};

std::vector<std::unique_ptr<Geometry>> geometries;
mutable CollectionFlags flags;
mutable Envelope envelope;

Envelope computeEnvelopeInternal() const;
Expand All @@ -248,6 +259,7 @@ class GEOS_DLL GeometryCollection : public Geometry {

bool hasCurvedComponents() const override;

void setFlags() const;

};

Expand Down
95 changes: 63 additions & 32 deletions src/geom/GeometryCollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ GeometryCollection::GeometryCollection(const GeometryCollection& gc)
:
Geometry(gc),
geometries(gc.geometries.size()),
flags(gc.flags),
envelope(gc.envelope)
{
for(std::size_t i = 0; i < geometries.size(); ++i) {
Expand All @@ -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();
Expand All @@ -62,7 +64,9 @@ GeometryCollection::operator=(const GeometryCollection& gc)
GeometryCollection::GeometryCollection(std::vector<std::unique_ptr<Geometry>> && 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");
Expand Down Expand Up @@ -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<Geometry> & 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<Geometry>& 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
Expand All @@ -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
Expand All @@ -201,6 +235,7 @@ GeometryCollection::releaseGeometries()
{
auto ret = std::move(geometries);
geometryChanged();
flags.flagsCalculated = false;
return ret;
}

Expand Down Expand Up @@ -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*
Expand Down

0 comments on commit 1660b79

Please sign in to comment.