/src/geos/src/operation/overlayng/ElevationModel.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 2.47M | T clamp(const T& v, const T& low, const T& high) { |
44 | 2.47M | return v < low ? low : high < v ? high : v; |
45 | 2.47M | } |
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 | 162k | { |
57 | 162k | Envelope extent; |
58 | 162k | if (! geom1.isEmpty() ) { |
59 | 158k | extent.expandToInclude(geom1.getEnvelopeInternal()); |
60 | 158k | } |
61 | 162k | if (! geom2.isEmpty() ) { |
62 | 131k | extent.expandToInclude(geom2.getEnvelopeInternal()); |
63 | 131k | } |
64 | 162k | std::unique_ptr<ElevationModel> model ( new ElevationModel(extent, DEFAULT_CELL_NUM, DEFAULT_CELL_NUM) ); |
65 | 162k | if (! geom1.isEmpty() ) model->add(geom1); |
66 | 162k | if (! geom2.isEmpty() ) model->add(geom2); |
67 | 162k | return model; |
68 | 162k | } |
69 | | |
70 | | /* public static */ |
71 | | std::unique_ptr<ElevationModel> |
72 | | ElevationModel::create(const geom::Geometry& geom1) |
73 | 69.1k | { |
74 | 69.1k | Envelope extent; |
75 | 69.1k | if (! geom1.isEmpty() ) { |
76 | 69.1k | extent.expandToInclude(geom1.getEnvelopeInternal()); |
77 | 69.1k | } |
78 | 69.1k | std::unique_ptr<ElevationModel> model ( new ElevationModel(extent, DEFAULT_CELL_NUM, DEFAULT_CELL_NUM) ); |
79 | 69.1k | if (! geom1.isEmpty() ) model->add(geom1); |
80 | 69.1k | return model; |
81 | 69.1k | } |
82 | | |
83 | | const int ElevationModel::DEFAULT_CELL_NUM = 3; |
84 | | |
85 | | ElevationModel::ElevationModel(const Envelope& nExtent, int nNumCellX, int nNumCellY) |
86 | | : |
87 | 231k | extent(nExtent), |
88 | 231k | numCellX(nNumCellX), |
89 | 231k | numCellY(nNumCellY) |
90 | 231k | { |
91 | | |
92 | 231k | cellSizeX = extent.getWidth() / numCellX; |
93 | 231k | cellSizeY = extent.getHeight() / numCellY; |
94 | 231k | if(cellSizeX <= 0.0) { |
95 | 16.0k | numCellX = 1; |
96 | 16.0k | } |
97 | 231k | if(cellSizeY <= 0.0) { |
98 | 5.34k | numCellY = 1; |
99 | 5.34k | } |
100 | 231k | cells.resize(static_cast<std::size_t>(numCellX) * static_cast<std::size_t>(numCellY)); |
101 | 231k | } |
102 | | |
103 | | /* public */ |
104 | | void |
105 | | ElevationModel::add(const Geometry& geom) |
106 | 360k | { |
107 | 360k | class Filter: public geom::CoordinateSequenceFilter { |
108 | 360k | ElevationModel& model; |
109 | 360k | bool hasZ; |
110 | 360k | public: |
111 | 360k | Filter(ElevationModel& nModel) : model(nModel), hasZ(true) {} |
112 | | |
113 | 360k | void filter_ro(const geom::CoordinateSequence& seq, std::size_t i) override |
114 | 1.40M | { |
115 | 1.40M | if (! seq.hasZ()) { |
116 | 343k | hasZ = false; |
117 | 343k | return; |
118 | 343k | } |
119 | 1.05M | 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 | 1.05M | model.add(c.x, c.y, c.z); |
125 | 1.05M | } |
126 | | |
127 | 1.82M | bool isDone() const override { |
128 | | // no need to scan if no Z present |
129 | 1.82M | return ! hasZ; |
130 | 1.82M | } |
131 | | |
132 | 360k | bool isGeometryChanged() const override { |
133 | 0 | return false; |
134 | 0 | } |
135 | | |
136 | 360k | }; |
137 | | |
138 | 360k | Filter filter(*this); |
139 | 360k | geom.apply_ro(filter); |
140 | | |
141 | 360k | } |
142 | | |
143 | | /* protected */ |
144 | | void |
145 | | ElevationModel::add(double x, double y, double z) |
146 | 1.05M | { |
147 | 1.05M | if (std::isnan(z)) |
148 | 9.37k | return; |
149 | 1.04M | hasZValue = true; |
150 | 1.04M | ElevationCell& cell = getCell(x, y); //, true); |
151 | 1.04M | cell.add(z); |
152 | 1.04M | } |
153 | | |
154 | | /* private */ |
155 | | void |
156 | | ElevationModel::init() |
157 | 8.37k | { |
158 | 8.37k | isInitialized = true; |
159 | 8.37k | int numCells = 0; |
160 | 8.37k | double sumZ = 0.0; |
161 | | |
162 | | #if GEOS_DEBUG |
163 | | int offset = 0; |
164 | | #endif |
165 | 8.37k | for (ElevationCell& cell : cells ) |
166 | 66.1k | { |
167 | 66.1k | if (!cell.isNull()) { |
168 | 18.9k | cell.compute(); |
169 | | #if GEOS_DEBUG |
170 | | std::cerr << "init: cell" << offset |
171 | | << " getZ: " << cell.getZ() |
172 | | << std::endl; |
173 | | #endif |
174 | 18.9k | numCells++; |
175 | 18.9k | sumZ += cell.getZ(); |
176 | 18.9k | } |
177 | | #if GEOS_DEBUG |
178 | | ++offset; |
179 | | #endif |
180 | 66.1k | } |
181 | 8.37k | averageZ = DoubleNotANumber; |
182 | 8.37k | if (numCells > 0) { |
183 | 8.37k | averageZ = sumZ / numCells; |
184 | 8.37k | } |
185 | | #if GEOS_DEBUG |
186 | | std::cerr << "init: numCells: " << numCells |
187 | | << " averageZ: " << averageZ << std::endl; |
188 | | #endif |
189 | 8.37k | } |
190 | | |
191 | | /* public */ |
192 | | double |
193 | | ElevationModel::getZ(double x, double y) |
194 | 189k | { |
195 | 189k | if (! isInitialized) |
196 | 0 | { |
197 | 0 | init(); |
198 | 0 | } |
199 | 189k | const ElevationModel::ElevationCell& cell = getCell(x, y); //, false); |
200 | 189k | if (cell.isNull()) |
201 | 12.2k | { |
202 | 12.2k | return averageZ; |
203 | 12.2k | } |
204 | 177k | return cell.getZ(); |
205 | 189k | } |
206 | | |
207 | | /* public */ |
208 | | void |
209 | | ElevationModel::populateZ(Geometry& geom) |
210 | 120k | { |
211 | | // short-circuit if no Zs are present in model |
212 | 120k | if (! hasZValue) |
213 | 111k | return; |
214 | | |
215 | 8.37k | if (! isInitialized) |
216 | 8.37k | init(); |
217 | | |
218 | 8.37k | class Filter: public geom::CoordinateFilter { |
219 | 8.37k | ElevationModel& model; |
220 | 8.37k | public: |
221 | 8.37k | Filter(ElevationModel& nModel) : model(nModel) {} |
222 | | |
223 | 8.37k | void filter_rw(geom::Coordinate* c) const override |
224 | 679k | { |
225 | | #if GEOS_DEBUG |
226 | | std::cerr << "Input coord: " << *c << std::endl; |
227 | | #endif |
228 | 679k | if (std::isnan( c->z )) { |
229 | 189k | c->z = model.getZ(c->x, c->y); |
230 | | #if GEOS_DEBUG |
231 | | std::cerr << "New coord: " << *c << std::endl; |
232 | | #endif |
233 | 189k | } |
234 | 679k | } |
235 | | |
236 | 8.37k | }; |
237 | | |
238 | 8.37k | 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 | 8.37k | geom.apply_rw(&filter); |
246 | 8.37k | } |
247 | | |
248 | | /* private */ |
249 | | ElevationModel::ElevationCell& |
250 | | ElevationModel::getCell(double x, double y) //, bool isCreateIfMissing |
251 | 1.23M | { |
252 | 1.23M | int ix = 0; |
253 | 1.23M | if (numCellX > 1) { |
254 | 1.23M | ix = (int) ((x - extent.getMinX()) / cellSizeX); |
255 | 1.23M | ix = clamp(ix, 0, numCellX - 1); |
256 | 1.23M | } |
257 | 1.23M | int iy = 0; |
258 | 1.23M | if (numCellY > 1) { |
259 | 1.23M | iy = (int) ((y - extent.getMinY()) / cellSizeY); |
260 | 1.23M | iy = clamp(iy, 0, numCellY - 1); |
261 | 1.23M | } |
262 | 1.23M | 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 | 1.23M | assert ( cellOffset < numCellX * numCellY ); |
269 | 1.23M | ElevationModel::ElevationCell& cell = cells[static_cast<std::size_t>(cellOffset)]; |
270 | 1.23M | return cell; |
271 | 1.23M | } |
272 | | |
273 | | } // namespace geos.operation.overlayng |
274 | | } // namespace geos.operation |
275 | | } // namespace geos |