Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GeometryCollection: Cache flags to avoid O(n) lookups #1220

Merged
merged 3 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading