Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2011-2025 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file NBHeightMapper.cpp
15 : /// @author Jakob Erdmann
16 : /// @author Laura Bieker
17 : /// @author Michael Behrisch
18 : /// @date Sept 2011
19 : ///
20 : // Set z-values for all network positions based on data from a height map
21 : /****************************************************************************/
22 : #include <config.h>
23 :
24 : #include <string>
25 : #include <utils/common/MsgHandler.h>
26 : #include <utils/common/ToString.h>
27 : #include <utils/common/StringUtils.h>
28 : #include <utils/options/OptionsCont.h>
29 : #include <utils/geom/GeomHelper.h>
30 : #include "NBHeightMapper.h"
31 : #include <utils/geom/GeoConvHelper.h>
32 : #include <utils/common/RGBColor.h>
33 :
34 : #ifdef HAVE_GDAL
35 : #ifdef _MSC_VER
36 : #pragma warning(push)
37 : #pragma warning(disable: 4435 5219 5220)
38 : #endif
39 : #if __GNUC__ > 3
40 : #pragma GCC diagnostic push
41 : #pragma GCC diagnostic ignored "-Wpedantic"
42 : #endif
43 : #include <ogrsf_frmts.h>
44 : #include <ogr_api.h>
45 : #include <gdal_priv.h>
46 : #if __GNUC__ > 3
47 : #pragma GCC diagnostic pop
48 : #endif
49 : #ifdef _MSC_VER
50 : #pragma warning(pop)
51 : #endif
52 : #endif
53 :
54 : // ===========================================================================
55 : // static members
56 : // ===========================================================================
57 : NBHeightMapper NBHeightMapper::myInstance;
58 :
59 :
60 : // ===========================================================================
61 : // method definitions
62 : // ===========================================================================
63 2154 : NBHeightMapper::NBHeightMapper():
64 2154 : myRTree(&Triangle::addSelf) {
65 2154 : }
66 :
67 :
68 2154 : NBHeightMapper::~NBHeightMapper() {
69 2154 : clearData();
70 2154 : }
71 :
72 :
73 : const NBHeightMapper&
74 546373 : NBHeightMapper::get() {
75 546373 : return myInstance;
76 : }
77 :
78 :
79 : bool
80 547908 : NBHeightMapper::ready() const {
81 547908 : return myRasters.size() > 0 || myTriangles.size() > 0;
82 : }
83 :
84 :
85 : double
86 1535 : NBHeightMapper::getZ(const Position& geo) const {
87 1535 : if (!ready()) {
88 0 : WRITE_WARNING(TL("Cannot supply height since no height data was loaded"));
89 0 : return 0;
90 : }
91 2383 : for (auto& item : myRasters) {
92 1532 : const Boundary& boundary = item.boundary;
93 1532 : int16_t* raster = item.raster;
94 : double result = -1e6;
95 1532 : if (boundary.around2D(geo)) {
96 684 : const int xSize = item.xSize;
97 684 : const double normX = (geo.x() - boundary.xmin()) / mySizeOfPixel.x();
98 684 : const double normY = (geo.y() - boundary.ymax()) / mySizeOfPixel.y();
99 684 : PositionVector corners;
100 684 : corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX]));
101 684 : if (normX - floor(normX) > 0.5) {
102 294 : corners.push_back(Position(floor(normX) + 1.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX + 1]));
103 : } else {
104 390 : corners.push_back(Position(floor(normX) - 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX - 1]));
105 : }
106 684 : if (normY - floor(normY) > 0.5 && ((int)normY + 1) < item.ySize) {
107 325 : corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 1.5, raster[((int)normY + 1) * xSize + (int)normX]));
108 : } else {
109 359 : corners.push_back(Position(floor(normX) + 0.5, floor(normY) - 0.5, raster[((int)normY - 1) * xSize + (int)normX]));
110 : }
111 684 : result = Triangle(corners).getZ(Position(normX, normY));
112 684 : }
113 684 : if (result > -1e5 && result < 1e5) {
114 : return result;
115 : }
116 : }
117 : // coordinates in degrees hence a small search window
118 : float minB[2];
119 : float maxB[2];
120 851 : minB[0] = (float)geo.x() - 0.00001f;
121 851 : minB[1] = (float)geo.y() - 0.00001f;
122 851 : maxB[0] = (float)geo.x() + 0.00001f;
123 851 : maxB[1] = (float)geo.y() + 0.00001f;
124 : QueryResult queryResult;
125 851 : int hits = myRTree.Search(minB, maxB, queryResult);
126 851 : Triangles result = queryResult.triangles;
127 : assert(hits == (int)result.size());
128 : UNUSED_PARAMETER(hits); // only used for assertion
129 :
130 852 : for (Triangles::iterator it = result.begin(); it != result.end(); it++) {
131 4 : const Triangle* triangle = *it;
132 4 : if (triangle->contains(geo)) {
133 3 : return triangle->getZ(geo);
134 : }
135 : }
136 1696 : WRITE_WARNINGF(TL("Could not get height data for coordinate %"), toString(geo));
137 848 : return 0;
138 851 : }
139 :
140 :
141 : void
142 3 : NBHeightMapper::addTriangle(PositionVector corners) {
143 3 : Triangle* triangle = new Triangle(corners);
144 3 : myTriangles.push_back(triangle);
145 3 : Boundary b = corners.getBoxBoundary();
146 3 : const float cmin[2] = {(float) b.xmin(), (float) b.ymin()};
147 3 : const float cmax[2] = {(float) b.xmax(), (float) b.ymax()};
148 3 : myRTree.Insert(cmin, cmax, triangle);
149 3 : }
150 :
151 :
152 : void
153 2003 : NBHeightMapper::loadIfSet(OptionsCont& oc) {
154 4006 : if (oc.isSet("heightmap.geotiff")) {
155 : // parse file(s)
156 10 : std::vector<std::string> files = oc.getStringVector("heightmap.geotiff");
157 10 : for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
158 15 : PROGRESS_BEGIN_MESSAGE("Parsing from GeoTIFF '" + *file + "'");
159 5 : int numFeatures = myInstance.loadTiff(*file);
160 10 : MsgHandler::getMessageInstance()->endProcessMsg(
161 10 : " done (parsed " + toString(numFeatures) +
162 15 : " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
163 : }
164 5 : }
165 4006 : if (oc.isSet("heightmap.shapefiles")) {
166 : // parse file(s)
167 0 : std::vector<std::string> files = oc.getStringVector("heightmap.shapefiles");
168 0 : for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
169 0 : PROGRESS_BEGIN_MESSAGE("Parsing from shape-file '" + *file + "'");
170 0 : int numFeatures = myInstance.loadShapeFile(*file);
171 0 : MsgHandler::getMessageInstance()->endProcessMsg(
172 0 : " done (parsed " + toString(numFeatures) +
173 0 : " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
174 : }
175 0 : }
176 2003 : }
177 :
178 :
179 : int
180 0 : NBHeightMapper::loadShapeFile(const std::string& file) {
181 : #ifdef HAVE_GDAL
182 : #if GDAL_VERSION_MAJOR < 2
183 : OGRRegisterAll();
184 : OGRDataSource* ds = OGRSFDriverRegistrar::Open(file.c_str(), FALSE);
185 : #else
186 0 : GDALAllRegister();
187 0 : GDALDataset* ds = (GDALDataset*)GDALOpenEx(file.c_str(), GDAL_OF_VECTOR | GA_ReadOnly, nullptr, nullptr, nullptr);
188 : #endif
189 0 : if (ds == nullptr) {
190 0 : throw ProcessError(TLF("Could not open shape file '%'.", file));
191 : }
192 :
193 : // begin file parsing
194 0 : OGRLayer* layer = ds->GetLayer(0);
195 0 : layer->ResetReading();
196 :
197 : // triangle coordinates are stored in WGS84 and later matched with network coordinates in WGS84
198 : // build coordinate transformation
199 : #if GDAL_VERSION_MAJOR < 3
200 : OGRSpatialReference* sr_src = layer->GetSpatialRef();
201 : #else
202 0 : const OGRSpatialReference* sr_src = layer->GetSpatialRef();
203 : #endif
204 0 : OGRSpatialReference sr_dest;
205 0 : sr_dest.SetWellKnownGeogCS("WGS84");
206 0 : OGRCoordinateTransformation* toWGS84 = OGRCreateCoordinateTransformation(sr_src, &sr_dest);
207 0 : if (toWGS84 == nullptr) {
208 0 : WRITE_WARNING(TL("Could not create geocoordinates converter; check whether proj.4 is installed."));
209 : }
210 :
211 : int numFeatures = 0;
212 : OGRFeature* feature;
213 0 : layer->ResetReading();
214 0 : while ((feature = layer->GetNextFeature()) != nullptr) {
215 0 : OGRGeometry* geom = feature->GetGeometryRef();
216 : assert(geom != 0);
217 :
218 0 : OGRwkbGeometryType gtype = geom->getGeometryType();
219 0 : if (gtype == wkbPolygon) {
220 : assert(std::string(geom->getGeometryName()) == std::string("POLYGON"));
221 : // try transform to wgs84
222 0 : geom->transform(toWGS84);
223 0 : OGRLinearRing* cgeom = ((OGRPolygon*) geom)->getExteriorRing();
224 : // assume TIN with with 4 points and point0 == point3
225 : assert(cgeom->getNumPoints() == 4);
226 0 : PositionVector corners;
227 0 : for (int j = 0; j < 3; j++) {
228 0 : Position pos((double) cgeom->getX(j), (double) cgeom->getY(j), (double) cgeom->getZ(j));
229 0 : corners.push_back(pos);
230 0 : myBoundary.add(pos);
231 : }
232 0 : addTriangle(corners);
233 0 : numFeatures++;
234 0 : } else {
235 0 : WRITE_WARNINGF(TL("Ignored heightmap feature type %"), geom->getGeometryName());
236 : }
237 :
238 : /*
239 : switch (gtype) {
240 : case wkbPolygon: {
241 : break;
242 : }
243 : case wkbPoint: {
244 : WRITE_WARNING(TL("got wkbPoint"));
245 : break;
246 : }
247 : case wkbLineString: {
248 : WRITE_WARNING(TL("got wkbLineString"));
249 : break;
250 : }
251 : case wkbMultiPoint: {
252 : WRITE_WARNING(TL("got wkbMultiPoint"));
253 : break;
254 : }
255 : case wkbMultiLineString: {
256 : WRITE_WARNING(TL("got wkbMultiLineString"));
257 : break;
258 : }
259 : case wkbMultiPolygon: {
260 : WRITE_WARNING(TL("got wkbMultiPolygon"));
261 : break;
262 : }
263 : default:
264 : WRITE_WARNING(TL("Unsupported shape type occurred"));
265 : break;
266 : }
267 : */
268 0 : OGRFeature::DestroyFeature(feature);
269 : }
270 : #if GDAL_VERSION_MAJOR < 2
271 : OGRDataSource::DestroyDataSource(ds);
272 : #else
273 0 : GDALClose(ds);
274 : #endif
275 0 : OCTDestroyCoordinateTransformation(reinterpret_cast<OGRCoordinateTransformationH>(toWGS84));
276 0 : OGRCleanupAll();
277 0 : return numFeatures;
278 : #else
279 : UNUSED_PARAMETER(file);
280 : WRITE_ERROR(TL("Cannot load shape file since SUMO was compiled without GDAL support."));
281 : return 0;
282 : #endif
283 0 : }
284 :
285 :
286 : int
287 5 : NBHeightMapper::loadTiff(const std::string& file) {
288 : #ifdef HAVE_GDAL
289 5 : GDALAllRegister();
290 5 : GDALDataset* poDataset = (GDALDataset*)GDALOpen(file.c_str(), GA_ReadOnly);
291 5 : if (poDataset == 0) {
292 0 : WRITE_ERROR(TL("Cannot load GeoTIFF file."));
293 0 : return 0;
294 : }
295 5 : Boundary boundary;
296 5 : const int xSize = poDataset->GetRasterXSize();
297 5 : const int ySize = poDataset->GetRasterYSize();
298 : double adfGeoTransform[6];
299 5 : if (poDataset->GetGeoTransform(adfGeoTransform) == CE_None) {
300 5 : Position topLeft(adfGeoTransform[0], adfGeoTransform[3]);
301 5 : mySizeOfPixel.set(adfGeoTransform[1], adfGeoTransform[5]);
302 5 : const double horizontalSize = xSize * mySizeOfPixel.x();
303 5 : const double verticalSize = ySize * mySizeOfPixel.y();
304 5 : boundary.add(topLeft);
305 5 : boundary.add(topLeft.x() + horizontalSize, topLeft.y() + verticalSize);
306 : } else {
307 0 : WRITE_ERRORF(TL("Could not parse geo information from %."), file);
308 0 : return 0;
309 : }
310 5 : const int picSize = xSize * ySize;
311 5 : int16_t* raster = (int16_t*)CPLMalloc(sizeof(int16_t) * picSize);
312 : bool ok = true;
313 10 : for (int i = 1; i <= poDataset->GetRasterCount(); i++) {
314 5 : GDALRasterBand* poBand = poDataset->GetRasterBand(i);
315 5 : if (poBand->GetColorInterpretation() != GCI_GrayIndex) {
316 0 : WRITE_ERRORF(TL("Unknown color band in %."), file);
317 0 : clearData();
318 : ok = false;
319 : break;
320 : }
321 : assert(xSize == poBand->GetXSize() && ySize == poBand->GetYSize());
322 5 : if (poBand->RasterIO(GF_Read, 0, 0, xSize, ySize, raster, xSize, ySize, GDT_Int16, 0, 0) == CE_Failure) {
323 0 : WRITE_ERRORF(TL("Failure in reading %."), file);
324 0 : clearData();
325 : ok = false;
326 : break;
327 : }
328 : }
329 5 : double min = std::numeric_limits<double>::max();
330 5 : double max = -std::numeric_limits<double>::max();
331 75605 : for (int i = 0; i < picSize; i++) {
332 75600 : min = MIN2(min, (double)raster[i]);
333 75850 : max = MAX2(max, (double)raster[i]);
334 : }
335 5 : GDALClose(poDataset);
336 5 : if (ok) {
337 35 : WRITE_MESSAGE("Read geotiff heightmap with size " + toString(xSize) + "," + toString(ySize)
338 : + " for geo boundary [" + toString(boundary)
339 : + "] with elevation range [" + toString(min) + "," + toString(max) + "].");
340 : RasterData rasterData;
341 5 : rasterData.raster = raster;
342 : rasterData.boundary = boundary;
343 5 : rasterData.xSize = xSize;
344 5 : rasterData.ySize = ySize;
345 5 : myRasters.push_back(rasterData);
346 : return picSize;
347 : } else {
348 : return 0;
349 : }
350 : #else
351 : UNUSED_PARAMETER(file);
352 : WRITE_ERROR(TL("Cannot load GeoTIFF file since SUMO was compiled without GDAL support."));
353 : return 0;
354 : #endif
355 : }
356 :
357 :
358 : void
359 2155 : NBHeightMapper::clearData() {
360 2158 : for (Triangles::iterator it = myTriangles.begin(); it != myTriangles.end(); it++) {
361 6 : delete *it;
362 : }
363 : myTriangles.clear();
364 : #ifdef HAVE_GDAL
365 2160 : for (auto& item : myRasters) {
366 5 : CPLFree(item.raster);
367 : }
368 : myRasters.clear();
369 : #endif
370 2155 : myBoundary.reset();
371 2155 : }
372 :
373 :
374 : // ===========================================================================
375 : // Triangle member methods
376 : // ===========================================================================
377 687 : NBHeightMapper::Triangle::Triangle(const PositionVector& corners):
378 : myCorners(corners) {
379 : assert(myCorners.size() == 3);
380 : // @todo assert non-colinearity
381 687 : }
382 :
383 :
384 : void
385 5 : NBHeightMapper::Triangle::addSelf(const QueryResult& queryResult) const {
386 5 : queryResult.triangles.push_back(this);
387 5 : }
388 :
389 :
390 : bool
391 4 : NBHeightMapper::Triangle::contains(const Position& pos) const {
392 4 : return myCorners.around(pos);
393 : }
394 :
395 :
396 : double
397 687 : NBHeightMapper::Triangle::getZ(const Position& geo) const {
398 : // en.wikipedia.org/wiki/Line-plane_intersection
399 687 : Position p0 = myCorners.front();
400 : Position line(0, 0, 1);
401 : p0.sub(geo); // p0 - l0
402 687 : Position normal = normalVector();
403 687 : return p0.dotProduct(normal) / line.dotProduct(normal);
404 : }
405 :
406 :
407 : Position
408 687 : NBHeightMapper::Triangle::normalVector() const {
409 : // @todo maybe cache result to avoid multiple computations?
410 687 : Position side1 = myCorners[1] - myCorners[0];
411 687 : Position side2 = myCorners[2] - myCorners[0];
412 687 : return side1.crossProduct(side2);
413 : }
414 :
415 :
416 : /****************************************************************************/
|