Coverage Report

Created: 2025-10-28 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/overlayng/ElevationModel.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2020 Sandro Santilli <strk@kbt.io>
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
 * Last Port: operation/overlayng/ElevationModel.java 4c88fea52
16
 *
17
 **********************************************************************/
18
19
#include <geos/operation/overlayng/ElevationModel.h>
20
#include <geos/geom/Coordinate.h>
21
#include <geos/geom/Geometry.h>
22
#include <geos/geom/CoordinateSequence.h>
23
#include <geos/geom/CoordinateFilter.h>
24
#include <geos/geom/CoordinateSequenceFilter.h>
25
#include <geos/geom/Envelope.h>
26
27
#include <memory> // for unique_ptr
28
29
#ifndef GEOS_DEBUG
30
# define GEOS_DEBUG 0
31
#endif
32
33
#if GEOS_DEBUG
34
# include <iostream>
35
#endif
36
37
namespace geos {      // geos.
38
namespace operation { // geos.operation
39
namespace overlayng { // geos.operation.overlayng
40
41
namespace {
42
    template<typename T>
43
5.95M
    T clamp(const T& v, const T& low, const T& high) {
44
5.95M
        return v < low ? low : high < v ? high : v;
45
5.95M
    }
46
}
47
48
using geos::geom::Coordinate;
49
using geos::geom::CoordinateSequence;
50
using geos::geom::Geometry;
51
using geos::geom::Envelope;
52
53
/* public static */
54
std::unique_ptr<ElevationModel>
55
ElevationModel::create(const geom::Geometry& geom1, const geom::Geometry& geom2)
56
278k
{
57
278k
    Envelope extent;
58
278k
    if (! geom1.isEmpty() ) {
59
271k
      extent.expandToInclude(geom1.getEnvelopeInternal());
60
271k
    }
61
278k
    if (! geom2.isEmpty() ) {
62
217k
      extent.expandToInclude(geom2.getEnvelopeInternal());
63
217k
    }
64
278k
    std::unique_ptr<ElevationModel> model ( new ElevationModel(extent, DEFAULT_CELL_NUM, DEFAULT_CELL_NUM) );
65
278k
    if (! geom1.isEmpty() ) model->add(geom1);
66
278k
    if (! geom2.isEmpty() ) model->add(geom2);
67
278k
    return model;
68
278k
}
69
70
/* public static */
71
std::unique_ptr<ElevationModel>
72
ElevationModel::create(const geom::Geometry& geom1)
73
114k
{
74
114k
    Envelope extent;
75
114k
    if (! geom1.isEmpty() ) {
76
114k
      extent.expandToInclude(geom1.getEnvelopeInternal());
77
114k
    }
78
114k
    std::unique_ptr<ElevationModel> model ( new ElevationModel(extent, DEFAULT_CELL_NUM, DEFAULT_CELL_NUM) );
79
114k
    if (! geom1.isEmpty() ) model->add(geom1);
80
114k
    return model;
81
114k
}
82
83
const int ElevationModel::DEFAULT_CELL_NUM = 3;
84
85
ElevationModel::ElevationModel(const Envelope& nExtent, int nNumCellX, int nNumCellY)
86
    :
87
392k
    extent(nExtent),
88
392k
    numCellX(nNumCellX),
89
392k
    numCellY(nNumCellY)
90
392k
{
91
92
392k
    cellSizeX = extent.getWidth() / numCellX;
93
392k
    cellSizeY = extent.getHeight() / numCellY;
94
392k
    if(cellSizeX <= 0.0) {
95
30.2k
        numCellX = 1;
96
30.2k
    }
97
392k
    if(cellSizeY <= 0.0) {
98
10.1k
        numCellY = 1;
99
10.1k
    }
100
392k
    cells.resize(static_cast<std::size_t>(numCellX) * static_cast<std::size_t>(numCellY));
101
392k
}
102
103
/* public */
104
void
105
ElevationModel::add(const Geometry& geom)
106
602k
{
107
602k
    class Filter: public geom::CoordinateSequenceFilter {
108
602k
        ElevationModel& model;
109
602k
        bool hasZ;
110
602k
    public:
111
602k
        Filter(ElevationModel& nModel) : model(nModel), hasZ(true) {}
112
113
602k
        void filter_ro(const geom::CoordinateSequence& seq, std::size_t i) override
114
3.02M
        {
115
3.02M
            if (! seq.hasZ()) {
116
561k
                hasZ = false;
117
561k
                return;
118
561k
            }
119
2.46M
            const Coordinate& c = seq.getAt(i);
120
#if GEOS_DEBUG
121
            std::cerr << "Coordinate " << i << " of added geom is: "
122
<< c << std::endl;
123
#endif
124
2.46M
            model.add(c.x, c.y, c.z);
125
2.46M
        }
126
127
3.86M
        bool isDone() const override {
128
            // no need to scan if no Z present
129
3.86M
            return ! hasZ;
130
3.86M
        }
131
132
602k
        bool isGeometryChanged() const override {
133
0
            return false;
134
0
        }
135
136
602k
    };
137
138
602k
    Filter filter(*this);
139
602k
    geom.apply_ro(filter);
140
141
602k
}
142
143
/* protected */
144
void
145
ElevationModel::add(double x, double y, double z)
146
2.46M
{
147
2.46M
    if (std::isnan(z))
148
24.3k
      return;
149
2.43M
    hasZValue = true;
150
2.43M
    ElevationCell& cell = getCell(x, y); //, true);
151
2.43M
    cell.add(z);
152
2.43M
}
153
154
/* private */
155
void
156
ElevationModel::init()
157
21.8k
{
158
21.8k
    isInitialized = true;
159
21.8k
    int numCells = 0;
160
21.8k
    double sumZ = 0.0;
161
162
#if GEOS_DEBUG
163
    int offset = 0;
164
#endif
165
21.8k
    for (ElevationCell& cell : cells )
166
170k
    {
167
170k
        if (!cell.isNull()) {
168
50.1k
          cell.compute();
169
#if GEOS_DEBUG
170
          std::cerr << "init: cell" << offset
171
                    << " getZ: " << cell.getZ()
172
                    << std::endl;
173
#endif
174
50.1k
          numCells++;
175
50.1k
          sumZ += cell.getZ();
176
50.1k
        }
177
#if GEOS_DEBUG
178
        ++offset;
179
#endif
180
170k
    }
181
21.8k
    averageZ = DoubleNotANumber;
182
21.8k
    if (numCells > 0) {
183
21.8k
      averageZ = sumZ / numCells;
184
21.8k
    }
185
#if GEOS_DEBUG
186
    std::cerr << "init: numCells: " << numCells
187
              << " averageZ: " << averageZ << std::endl;
188
#endif
189
21.8k
}
190
191
/* public */
192
double
193
ElevationModel::getZ(double x, double y)
194
545k
{
195
545k
    if (! isInitialized)
196
0
    {
197
0
      init();
198
0
    }
199
545k
    const ElevationModel::ElevationCell& cell = getCell(x, y); //, false);
200
545k
    if (cell.isNull())
201
26.3k
    {
202
26.3k
      return averageZ;
203
26.3k
    }
204
519k
    return cell.getZ();
205
545k
}
206
207
/* public */
208
void
209
ElevationModel::populateZ(Geometry& geom)
210
208k
{
211
    // short-circuit if no Zs are present in model
212
208k
    if (! hasZValue)
213
186k
      return;
214
215
21.8k
    if (! isInitialized)
216
21.8k
      init();
217
218
21.8k
    class Filter: public geom::CoordinateFilter {
219
21.8k
        ElevationModel& model;
220
21.8k
    public:
221
21.8k
        Filter(ElevationModel& nModel) : model(nModel) {}
222
223
21.8k
        void filter_rw(geom::Coordinate* c) const override
224
1.82M
        {
225
#if GEOS_DEBUG
226
            std::cerr << "Input coord:  " << *c << std::endl;
227
#endif
228
1.82M
            if (std::isnan( c->z )) {
229
545k
                c->z = model.getZ(c->x, c->y);
230
#if GEOS_DEBUG
231
                std::cerr << "New coord: " << *c << std::endl;
232
#endif
233
545k
            }
234
1.82M
        }
235
236
21.8k
    };
237
238
21.8k
    Filter filter(*this);
239
#if GEOS_DEBUG
240
    std::cerr << "ElevationModel::poplateZ calling apply_rw(CoordinateSequenceFilter&) against" <<
241
              //std::type_name<decltype(ci)>()
242
              typeid(geom).name()
243
              << std::endl;
244
#endif
245
21.8k
    geom.apply_rw(&filter);
246
21.8k
}
247
248
/* private */
249
ElevationModel::ElevationCell&
250
ElevationModel::getCell(double x, double y) //, bool isCreateIfMissing
251
2.98M
{
252
2.98M
    int ix = 0;
253
2.98M
    if (numCellX > 1) {
254
2.97M
      ix = (int) ((x - extent.getMinX()) / cellSizeX);
255
2.97M
      ix = clamp(ix, 0, numCellX - 1);
256
2.97M
    }
257
2.98M
    int iy = 0;
258
2.98M
    if (numCellY > 1) {
259
2.97M
      iy = (int) ((y - extent.getMinY()) / cellSizeY);
260
2.97M
      iy = clamp(iy, 0, numCellY - 1);
261
2.97M
    }
262
2.98M
    int cellOffset = getCellOffset(ix, iy);
263
#if GEOS_DEBUG
264
    std::cerr << "Cell of coordinate " << x << "," << y
265
              << " is " << ix << "," << iy
266
              << " offset " << cellOffset << std::endl;
267
#endif
268
    assert ( cellOffset < numCellX * numCellY );
269
2.98M
    ElevationModel::ElevationCell& cell = cells[static_cast<std::size_t>(cellOffset)];
270
2.98M
    return cell;
271
2.98M
}
272
273
} // namespace geos.operation.overlayng
274
} // namespace geos.operation
275
} // namespace geos