Eclipse SUMO - Simulation of Urban MObility
Loading...
Searching...
No Matches
NBHeightMapper.cpp
Go to the documentation of this file.
1/****************************************************************************/
2// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3// Copyright (C) 2011-2026 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/****************************************************************************/
20// Set z-values for all network positions based on data from a height map
21/****************************************************************************/
22#include <config.h>
23
24#include <string>
30#include "NBHeightMapper.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// ===========================================================================
58
59
60// ===========================================================================
61// method definitions
62// ===========================================================================
64 myRTree(&Triangle::addSelf) {
65}
66
67
71
72
73const NBHeightMapper&
77
78
79bool
81 return myRasters.size() > 0 || myTriangles.size() > 0;
82}
83
84
85double
86NBHeightMapper::getZ(const Position& geo) const {
87 if (!ready()) {
88 WRITE_WARNING(TL("Cannot supply height since no height data was loaded"));
89 return 0;
90 }
91 for (auto& item : myRasters) {
92 const Boundary& boundary = item.boundary;
93 float* raster = item.raster;
94 double result = -1e6;
95
96 double x = geo.x();
97 double y = geo.y();
98
99#ifdef HAVE_GDAL
100 // Transform geo coordinates to the coordinate system of this
101 // raster image for lookup in its raster, if applicable.
102 if (item.transform != nullptr) {
103 // Since the input coordinates are always WGS84 (they may be
104 // transformed to it in NBNetBuilder::transformCoordinate), and
105 // WGS84 uses latitude-longitude order (y-x), we have to swap the
106 // input coordinates here.
107 std::swap(x, y);
108
109 item.transform->Transform(1, &x, &y);
110 }
111#endif
112
113 if (boundary.around2D(x, y)) {
114 const int xSize = item.xSize;
115 const double normX = (x - boundary.xmin()) / mySizeOfPixel.x();
116 const double normY = (y - boundary.ymax()) / mySizeOfPixel.y();
117 PositionVector corners;
118 corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX]));
119 if (normX - floor(normX) > 0.5) {
120 corners.push_back(Position(floor(normX) + 1.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX + 1]));
121 } else {
122 corners.push_back(Position(floor(normX) - 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX - 1]));
123 }
124 if (normY - floor(normY) > 0.5 && ((int)normY + 1) < item.ySize) {
125 corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 1.5, raster[((int)normY + 1) * xSize + (int)normX]));
126 } else {
127 corners.push_back(Position(floor(normX) + 0.5, floor(normY) - 0.5, raster[((int)normY - 1) * xSize + (int)normX]));
128 }
129 result = Triangle(corners).getZ(Position(normX, normY));
130 }
131 if (result > -1e5 && result < 1e5) {
132 return result;
133 }
134 }
135 // coordinates in degrees hence a small search window
136 float minB[2];
137 float maxB[2];
138 minB[0] = (float)geo.x() - 0.00001f;
139 minB[1] = (float)geo.y() - 0.00001f;
140 maxB[0] = (float)geo.x() + 0.00001f;
141 maxB[1] = (float)geo.y() + 0.00001f;
142 QueryResult queryResult;
143 int hits = myRTree.Search(minB, maxB, queryResult);
144 Triangles result = queryResult.triangles;
145 assert(hits == (int)result.size());
146 UNUSED_PARAMETER(hits); // only used for assertion
147
148 for (Triangles::iterator it = result.begin(); it != result.end(); it++) {
149 const Triangle* triangle = *it;
150 if (triangle->contains(geo)) {
151 return triangle->getZ(geo);
152 }
153 }
154 WRITE_WARNINGF(TL("Could not get height data for coordinate %"), toString(geo));
155 return 0;
156}
157
158
159void
161 Triangle* triangle = new Triangle(corners);
162 myTriangles.push_back(triangle);
163 Boundary b = corners.getBoxBoundary();
164 const float cmin[2] = {(float) b.xmin(), (float) b.ymin()};
165 const float cmax[2] = {(float) b.xmax(), (float) b.ymax()};
166 myRTree.Insert(cmin, cmax, triangle);
167}
168
169
170void
172 if (oc.isSet("heightmap.geotiff")) {
173 // parse file(s)
174 std::vector<std::string> files = oc.getStringVector("heightmap.geotiff");
175 for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
176 PROGRESS_BEGIN_MESSAGE("Parsing from GeoTIFF '" + *file + "'");
177 int numFeatures = myInstance.loadTiff(*file);
179 " done (parsed " + toString(numFeatures) +
180 " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
181 }
182 }
183 if (oc.isSet("heightmap.shapefiles")) {
184 // parse file(s)
185 std::vector<std::string> files = oc.getStringVector("heightmap.shapefiles");
186 for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
187 PROGRESS_BEGIN_MESSAGE("Parsing from shape-file '" + *file + "'");
188 int numFeatures = myInstance.loadShapeFile(*file);
190 " done (parsed " + toString(numFeatures) +
191 " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
192 }
193 }
194}
195
196
197int
198NBHeightMapper::loadShapeFile(const std::string& file) {
199#ifdef HAVE_GDAL
200#if GDAL_VERSION_MAJOR < 2
201 OGRRegisterAll();
202 OGRDataSource* ds = OGRSFDriverRegistrar::Open(file.c_str(), FALSE);
203#else
204 GDALAllRegister();
205 GDALDataset* ds = (GDALDataset*)GDALOpenEx(file.c_str(), GDAL_OF_VECTOR | GA_ReadOnly, nullptr, nullptr, nullptr);
206#endif
207 if (ds == nullptr) {
208 throw ProcessError(TLF("Could not open shape file '%'.", file));
209 }
210
211 // begin file parsing
212 OGRLayer* layer = ds->GetLayer(0);
213 layer->ResetReading();
214
215 // triangle coordinates are stored in WGS84 and later matched with network coordinates in WGS84
216 // build coordinate transformation
217#if GDAL_VERSION_MAJOR < 3
218 OGRSpatialReference* sr_src = layer->GetSpatialRef();
219#else
220 const OGRSpatialReference* sr_src = layer->GetSpatialRef();
221#endif
222 OGRSpatialReference sr_dest;
223 sr_dest.SetWellKnownGeogCS("WGS84");
224 OGRCoordinateTransformation* toWGS84 = OGRCreateCoordinateTransformation(sr_src, &sr_dest);
225 if (toWGS84 == nullptr) {
226 WRITE_WARNING(TL("Could not create geocoordinates converter; check whether proj.4 is installed."));
227 }
228
229 int numFeatures = 0;
230 OGRFeature* feature;
231 layer->ResetReading();
232 while ((feature = layer->GetNextFeature()) != nullptr) {
233 OGRGeometry* geom = feature->GetGeometryRef();
234 assert(geom != 0);
235
236 OGRwkbGeometryType gtype = geom->getGeometryType();
237 if (gtype == wkbPolygon) {
238 assert(std::string(geom->getGeometryName()) == std::string("POLYGON"));
239 // try transform to wgs84
240 geom->transform(toWGS84);
241 OGRLinearRing* cgeom = ((OGRPolygon*) geom)->getExteriorRing();
242 // assume TIN with with 4 points and point0 == point3
243 assert(cgeom->getNumPoints() == 4);
244 PositionVector corners;
245 for (int j = 0; j < 3; j++) {
246 Position pos((double) cgeom->getX(j), (double) cgeom->getY(j), (double) cgeom->getZ(j));
247 corners.push_back(pos);
248 myBoundary.add(pos);
249 }
250 addTriangle(corners);
251 numFeatures++;
252 } else {
253 WRITE_WARNINGF(TL("Ignored heightmap feature type %"), geom->getGeometryName());
254 }
255
256 /*
257 switch (gtype) {
258 case wkbPolygon: {
259 break;
260 }
261 case wkbPoint: {
262 WRITE_WARNING(TL("got wkbPoint"));
263 break;
264 }
265 case wkbLineString: {
266 WRITE_WARNING(TL("got wkbLineString"));
267 break;
268 }
269 case wkbMultiPoint: {
270 WRITE_WARNING(TL("got wkbMultiPoint"));
271 break;
272 }
273 case wkbMultiLineString: {
274 WRITE_WARNING(TL("got wkbMultiLineString"));
275 break;
276 }
277 case wkbMultiPolygon: {
278 WRITE_WARNING(TL("got wkbMultiPolygon"));
279 break;
280 }
281 default:
282 WRITE_WARNING(TL("Unsupported shape type occurred"));
283 break;
284 }
285 */
286 OGRFeature::DestroyFeature(feature);
287 }
288#if GDAL_VERSION_MAJOR < 2
289 OGRDataSource::DestroyDataSource(ds);
290#else
291 GDALClose(ds);
292#endif
293 OCTDestroyCoordinateTransformation(reinterpret_cast<OGRCoordinateTransformationH>(toWGS84));
294 OGRCleanupAll();
295 return numFeatures;
296#else
297 UNUSED_PARAMETER(file);
298 WRITE_ERROR(TL("Cannot load shape file since SUMO was compiled without GDAL support."));
299 return 0;
300#endif
301}
302
303
304int
305NBHeightMapper::loadTiff(const std::string& file) {
306#ifdef HAVE_GDAL
307 GDALAllRegister();
308 GDALDataset* poDataset = (GDALDataset*)GDALOpen(file.c_str(), GA_ReadOnly);
309 if (poDataset == 0) {
310 WRITE_ERROR(TL("Cannot load GeoTIFF file."));
311 return 0;
312 }
313 Boundary boundary;
314 const int xSize = poDataset->GetRasterXSize();
315 const int ySize = poDataset->GetRasterYSize();
316 double adfGeoTransform[6];
317 if (poDataset->GetGeoTransform(adfGeoTransform) == CE_None) {
318 Position topLeft(adfGeoTransform[0], adfGeoTransform[3]);
319 mySizeOfPixel.set(adfGeoTransform[1], adfGeoTransform[5]);
320 const double horizontalSize = xSize * mySizeOfPixel.x();
321 const double verticalSize = ySize * mySizeOfPixel.y();
322 boundary.add(topLeft);
323 boundary.add(topLeft.x() + horizontalSize, topLeft.y() + verticalSize);
324 } else {
325 WRITE_ERRORF(TL("Could not parse geo information from %."), file);
326 return 0;
327 }
328 const int picSize = xSize * ySize;
329 float* raster = (float*)CPLMalloc(sizeof(float) * picSize);
330 bool ok = true;
331 for (int i = 1; i <= poDataset->GetRasterCount(); i++) {
332 GDALRasterBand* poBand = poDataset->GetRasterBand(i);
333 if (poBand->GetColorInterpretation() != GCI_GrayIndex) {
334 WRITE_ERRORF(TL("Unknown color band in %."), file);
335 clearData();
336 ok = false;
337 break;
338 }
339 assert(xSize == poBand->GetXSize() && ySize == poBand->GetYSize());
340 if (poBand->RasterIO(GF_Read, 0, 0, xSize, ySize, raster, xSize, ySize, GDT_Float32, 0, 0) == CE_Failure) {
341 WRITE_ERRORF(TL("Failure in reading %."), file);
342 clearData();
343 ok = false;
344 break;
345 }
346 }
347 double min = std::numeric_limits<double>::max();
348 double max = -std::numeric_limits<double>::max();
349 for (int i = 0; i < picSize; i++) {
350 min = MIN2(min, (double)raster[i]);
351 max = MAX2(max, (double)raster[i]);
352 }
353
354 // Make a copy, GDALClose will destroy the original
355#if GDAL_VERSION_MAJOR < 3
356 OGRSpatialReference spatialRef;
357 char* wkt = const_cast<char*>(poDataset->GetProjectionRef());
358 spatialRef.importFromWkt(&wkt);
359#else
360 OGRSpatialReference spatialRef(*poDataset->GetSpatialRef());
361#endif
362 GDALClose(poDataset);
363 if (ok) {
364 WRITE_MESSAGE("Read geotiff heightmap with size " + toString(xSize) + "," + toString(ySize)
365 + " for geo boundary [" + toString(boundary)
366 + "] with elevation range [" + toString(min) + "," + toString(max) + "].");
367 OGRSpatialReference wgs;
368 wgs.SetWellKnownGeogCS("WGS84");
369 myRasters.push_back(RasterData{raster, boundary, xSize, ySize, OGRCreateCoordinateTransformation(&wgs, &spatialRef)});
370 return picSize;
371 }
372 return 0;
373#else
374 UNUSED_PARAMETER(file);
375 WRITE_ERROR(TL("Cannot load GeoTIFF file since SUMO was compiled without GDAL support."));
376 return 0;
377#endif
378}
379
380
381void
383 for (Triangles::iterator it = myTriangles.begin(); it != myTriangles.end(); it++) {
384 delete *it;
385 }
386 myTriangles.clear();
387#ifdef HAVE_GDAL
388 for (auto& item : myRasters) {
389 CPLFree(item.raster);
390 if (item.transform != nullptr) {
391 delete item.transform;
392 }
393 }
394 myRasters.clear();
395#endif
397}
398
399
400// ===========================================================================
401// Triangle member methods
402// ===========================================================================
404 myCorners(corners) {
405 assert(myCorners.size() == 3);
406 // @todo assert non-colinearity
407}
408
409
410void
412 queryResult.triangles.push_back(this);
413}
414
415
416bool
418 return myCorners.around(pos);
419}
420
421
422double
424 // en.wikipedia.org/wiki/Line-plane_intersection
425 Position p0 = myCorners.front();
426 Position line(0, 0, 1);
427 p0.sub(geo); // p0 - l0
428 Position normal = normalVector();
429 return p0.dotProduct(normal) / line.dotProduct(normal);
430}
431
432
435 // @todo maybe cache result to avoid multiple computations?
436 Position side1 = myCorners[1] - myCorners[0];
437 Position side2 = myCorners[2] - myCorners[0];
438 return side1.crossProduct(side2);
439}
440
441
442/****************************************************************************/
#define WRITE_WARNINGF(...)
Definition MsgHandler.h:287
#define WRITE_ERRORF(...)
Definition MsgHandler.h:296
#define WRITE_MESSAGE(msg)
Definition MsgHandler.h:288
#define WRITE_ERROR(msg)
Definition MsgHandler.h:295
#define WRITE_WARNING(msg)
Definition MsgHandler.h:286
#define TL(string)
Definition MsgHandler.h:304
#define TLF(string,...)
Definition MsgHandler.h:306
#define PROGRESS_BEGIN_MESSAGE(msg)
Definition MsgHandler.h:290
T MIN2(T a, T b)
Definition StdDefs.h:80
T MAX2(T a, T b)
Definition StdDefs.h:86
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
Definition ToString.h:49
A class that stores a 2D geometrical boundary.
Definition Boundary.h:39
void add(double x, double y, double z=0)
Makes the boundary include the given coordinate.
Definition Boundary.cpp:75
double ymin() const
Returns minimum y-coordinate.
Definition Boundary.cpp:127
void reset()
Resets the boundary.
Definition Boundary.cpp:63
double xmin() const
Returns minimum x-coordinate.
Definition Boundary.cpp:115
bool around2D(const Position &p, double offset=0) const
Returns whether the boundary contains the given 2D coordinate (position)
Definition Boundary.cpp:178
double ymax() const
Returns maximum y-coordinate.
Definition Boundary.cpp:133
double xmax() const
Returns maximum x-coordinate.
Definition Boundary.cpp:121
virtual void endProcessMsg(std::string msg)
Ends a process information.
static MsgHandler * getMessageInstance()
Returns the instance to add normal messages to.
class for cirumventing the const-restriction of RTree::Search-context
Position normalVector() const
returns the normal vector for this triangles plane
double getZ(const Position &geo) const
returns the projection of the give geoCoordinate (WGS84) onto triangle plane
PositionVector myCorners
the corners of the triangle
Triangle(const PositionVector &corners)
void addSelf(const QueryResult &queryResult) const
callback for RTree search
bool contains(const Position &pos) const
checks whether pos lies within triangle (only checks x,y)
Set z-values for all network positions based on data from a height map.
std::vector< const Triangle * > Triangles
Triangles myTriangles
double getZ(const Position &geo) const
returns height for the given geo coordinate (WGS84)
static const NBHeightMapper & get()
return the singleton instance (maybe 0)
int loadShapeFile(const std::string &file)
load height data from Arcgis-shape file and returns the number of parsed features
static NBHeightMapper myInstance
the singleton instance
bool ready() const
returns whether the NBHeightMapper has data
std::vector< RasterData > myRasters
raster height information in m for all loaded files
NBHeightMapper()
private constructor and destructor (Singleton)
const Boundary & getBoundary()
returns the convex boundary of all known triangles
static void loadIfSet(OptionsCont &oc)
loads height map data if any loading options are set
void clearData()
clears loaded data
Position mySizeOfPixel
dimensions of one pixel in raster data
void addTriangle(PositionVector corners)
adds one triangles worth of height data
Boundary myBoundary
convex boundary of all known triangles;
TRIANGLE_RTREE_QUAL myRTree
The RTree for spatial queries.
int loadTiff(const std::string &file)
load height data from GeoTIFF file and returns the number of non void pixels
A storage for options typed value containers)
Definition OptionsCont.h:89
bool isSet(const std::string &name, bool failOnNonExistant=true) const
Returns the information whether the named option is set.
const StringVector & getStringVector(const std::string &name) const
Returns the list of string-value of the named option (only for Option_StringVector)
A point in 2D or 3D with translation and scaling methods.
Definition Position.h:37
double dotProduct(const Position &pos) const
returns the dot product (scalar product) between this point and the second one
Definition Position.h:301
void set(double x, double y)
set positions x and y
Definition Position.h:82
void sub(double dx, double dy)
Subtracts the given position from this one.
Definition Position.h:149
double x() const
Returns the x-position.
Definition Position.h:52
Position crossProduct(const Position &pos)
returns the cross product between this point and the second one
Definition Position.h:293
double y() const
Returns the y-position.
Definition Position.h:57
A list of positions.
Boundary getBoxBoundary() const
Returns a boundary enclosing this list of lines.
#define UNUSED_PARAMETER(x)
NLOHMANN_BASIC_JSON_TPL_DECLARATION void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL &j1, nlohmann::NLOHMANN_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name) is_nothrow_move_constructible< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression) is_nothrow_move_assignable< nlohmann::NLOHMANN_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
Definition json.hpp:21884