Coverage Report

Created: 2026-09-01 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/cluster/AbstractClusterFinder.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2020-2022 Daniel Baston
7
 *
8
 * This is free software; you can redistribute and/or modify it under
9
 * the terms of the GNU Lesser General Public Licence as published
10
 * by the Free Software Foundation.
11
 * See the COPYING file for more information.
12
 *
13
 **********************************************************************/
14
15
#include <geos/operation/cluster/AbstractClusterFinder.h>
16
#include <geos/geom/Geometry.h>
17
#include <geos/geom/GeometryCollection.h>
18
#include <geos/geom/GeometryFactory.h>
19
#include <geos/geom/prep/PreparedGeometry.h>
20
21
#include <geos/util.h>
22
#include <geos/index/strtree/TemplateSTRtree.h>
23
#include <geos/operation/cluster/UnionFind.h>
24
25
namespace geos {
26
namespace operation {
27
namespace cluster {
28
29
using geom::Geometry;
30
31
std::vector<std::unique_ptr<geom::Geometry>>
32
0
AbstractClusterFinder::clusterToVector(const geom::Geometry& g) {
33
0
    return clusterToVector(g.clone());
34
0
}
35
36
std::unique_ptr<geom::Geometry>
37
0
AbstractClusterFinder::clusterToCollection(std::unique_ptr<geom::Geometry> && g) {
38
0
    auto gfact = g->getFactory();
39
40
0
    return gfact->createGeometryCollection(clusterToVector(std::move(g)));
41
0
}
42
43
std::unique_ptr<geom::Geometry>
44
0
AbstractClusterFinder::clusterToCollection(const geom::Geometry & g) {
45
0
    return clusterToCollection(g.clone());
46
0
}
47
48
49
Clusters
50
0
AbstractClusterFinder::cluster(const std::vector<const geom::Geometry*> & components) {
51
0
    index::strtree::TemplateSTRtree<std::size_t> tree;
52
53
0
    for (std::size_t i = 0; i < components.size(); i++) {
54
0
        tree.insert(*components[i]->getEnvelopeInternal(), i);
55
0
    }
56
57
0
    UnionFind uf(components.size());
58
0
    return process(components, tree, uf);
59
0
}
60
61
std::vector<std::unique_ptr<geom::Geometry>>
62
0
AbstractClusterFinder::clusterToVector(std::unique_ptr<geom::Geometry> && g) {
63
0
    const geom::GeometryFactory& gfact = *g->getFactory();
64
65
0
    std::vector<const Geometry*> components(g->getNumGeometries());
66
0
    for (size_t i = 0; i < g->getNumGeometries(); i++) {
67
0
        components[i]= g->getGeometryN(i);
68
0
    }
69
70
0
    const auto& clusters = cluster(components);
71
72
0
    std::vector<std::unique_ptr<Geometry>> component_geoms = getComponents(std::move(g));
73
74
0
    std::vector<std::unique_ptr<Geometry>> cluster_geoms;
75
76
0
    for (size_t i = 0; i < clusters.getNumClusters(); i++) {
77
0
        std::vector<std::unique_ptr<Geometry>> cluster_component_geoms;
78
0
        cluster_component_geoms.reserve(clusters.getSize(i));
79
0
        for (auto it = clusters.begin(i); it != clusters.end(i); ++it) {
80
0
            cluster_component_geoms.push_back(std::move(component_geoms[*it]));
81
0
        }
82
0
        cluster_geoms.push_back(gfact.buildGeometry(std::move(cluster_component_geoms)));
83
0
    }
84
85
0
    return cluster_geoms;
86
0
}
87
88
Clusters
89
AbstractClusterFinder::process(const std::vector<const Geometry*> & components,
90
                   index::strtree::TemplateSTRtree<std::size_t> & tree,
91
0
                   UnionFind & uf) {
92
93
0
    std::vector<size_t> hits;
94
95
0
    for (size_t i = 0; i < components.size(); i++) {
96
0
        const geom::Geometry* gi = components[i];
97
98
0
        hits.clear();
99
100
        // Sort candidates based on envelope area to try and perform simpler intersection tests instead of
101
        // complex ones. This seems to perform better than sorting on the number of points.
102
0
        tree.query(queryEnvelope(gi), hits);
103
0
        std::sort(hits.begin(), hits.end(), [&components](std::size_t a, std::size_t b) {
104
0
            return components[a]->getEnvelopeInternal()->getArea() < components[b]->getEnvelopeInternal()->getArea();
105
0
        });
106
107
0
        for (std::size_t j : hits) {
108
0
            if (uf.different(i, j)) {
109
0
                const geom::Geometry* gj = components[j];
110
111
                // Only call shouldJoin with the more complex geometry in the LHS, where it will become
112
                // the prepared geometry.
113
                // TODO move point check to subclasses that benefit to avoid for those that don't?
114
0
                if (gi->getNumPoints() >= gj->getNumPoints() && shouldJoin(gi, gj)) {
115
0
                    uf.join(i, j);
116
0
                }
117
0
            }
118
0
        }
119
120
0
    }
121
122
0
    return uf.getClusters();
123
0
}
124
125
std::vector<std::unique_ptr<Geometry>>
126
AbstractClusterFinder::getComponents(std::unique_ptr<Geometry>&& g)
127
0
{
128
0
    if (g->isCollection()) {
129
0
        return detail::down_cast<geom::GeometryCollection*>(g.get())->releaseGeometries();
130
0
    } else {
131
0
        std::vector<std::unique_ptr<Geometry>> ret(1);
132
0
        ret[0] = std::move(g);
133
0
        return ret;
134
0
    }
135
0
}
136
137
138
139
}
140
}
141
}