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-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/****************************************************************************/
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 int16_t* raster = item.raster;
94 double result = -1e6;
95 if (boundary.around2D(geo)) {
96 const int xSize = item.xSize;
97 const double normX = (geo.x() - boundary.xmin()) / mySizeOfPixel.x();
98 const double normY = (geo.y() - boundary.ymax()) / mySizeOfPixel.y();
99 PositionVector corners;
100 corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX]));
101 if (normX - floor(normX) > 0.5) {
102 corners.push_back(Position(floor(normX) + 1.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX + 1]));
103 } else {
104 corners.push_back(Position(floor(normX) - 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX - 1]));
105 }
106 if (normY - floor(normY) > 0.5 && ((int)normY + 1) < item.ySize) {
107 corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 1.5, raster[((int)normY + 1) * xSize + (int)normX]));
108 } else {
109 corners.push_back(Position(floor(normX) + 0.5, floor(normY) - 0.5, raster[((int)normY - 1) * xSize + (int)normX]));
110 }
111 result = Triangle(corners).getZ(Position(normX, normY));
112 }
113 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 minB[0] = (float)geo.x() - 0.00001f;
121 minB[1] = (float)geo.y() - 0.00001f;
122 maxB[0] = (float)geo.x() + 0.00001f;
123 maxB[1] = (float)geo.y() + 0.00001f;
124 QueryResult queryResult;
125 int hits = myRTree.Search(minB, maxB, queryResult);
126 Triangles result = queryResult.triangles;
127 assert(hits == (int)result.size());
128 UNUSED_PARAMETER(hits); // only used for assertion
129
130 for (Triangles::iterator it = result.begin(); it != result.end(); it++) {
131 const Triangle* triangle = *it;
132 if (triangle->contains(geo)) {
133 return triangle->getZ(geo);
134 }
135 }
136 WRITE_WARNINGF(TL("Could not get height data for coordinate %"), toString(geo));
137 return 0;
138}
139
140
141void
143 Triangle* triangle = new Triangle(corners);
144 myTriangles.push_back(triangle);
145 Boundary b = corners.getBoxBoundary();
146 const float cmin[2] = {(float) b.xmin(), (float) b.ymin()};
147 const float cmax[2] = {(float) b.xmax(), (float) b.ymax()};
148 myRTree.Insert(cmin, cmax, triangle);
149}
150
151
152void
154 if (oc.isSet("heightmap.geotiff")) {
155 // parse file(s)
156 std::vector<std::string> files = oc.getStringVector("heightmap.geotiff");
157 for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
158 PROGRESS_BEGIN_MESSAGE("Parsing from GeoTIFF '" + *file + "'");
159 int numFeatures = myInstance.loadTiff(*file);
161 " done (parsed " + toString(numFeatures) +
162 " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
163 }
164 }
165 if (oc.isSet("heightmap.shapefiles")) {
166 // parse file(s)
167 std::vector<std::string> files = oc.getStringVector("heightmap.shapefiles");
168 for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
169 PROGRESS_BEGIN_MESSAGE("Parsing from shape-file '" + *file + "'");
170 int numFeatures = myInstance.loadShapeFile(*file);
172 " done (parsed " + toString(numFeatures) +
173 " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
174 }
175 }
176}
177
178
179int
180NBHeightMapper::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 GDALAllRegister();
187 GDALDataset* ds = (GDALDataset*)GDALOpenEx(file.c_str(), GDAL_OF_VECTOR | GA_ReadOnly, nullptr, nullptr, nullptr);
188#endif
189 if (ds == nullptr) {
190 throw ProcessError(TLF("Could not open shape file '%'.", file));
191 }
192
193 // begin file parsing
194 OGRLayer* layer = ds->GetLayer(0);
195 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 const OGRSpatialReference* sr_src = layer->GetSpatialRef();
203#endif
204 OGRSpatialReference sr_dest;
205 sr_dest.SetWellKnownGeogCS("WGS84");
206 OGRCoordinateTransformation* toWGS84 = OGRCreateCoordinateTransformation(sr_src, &sr_dest);
207 if (toWGS84 == nullptr) {
208 WRITE_WARNING(TL("Could not create geocoordinates converter; check whether proj.4 is installed."));
209 }
210
211 int numFeatures = 0;
212 OGRFeature* feature;
213 layer->ResetReading();
214 while ((feature = layer->GetNextFeature()) != nullptr) {
215 OGRGeometry* geom = feature->GetGeometryRef();
216 assert(geom != 0);
217
218 OGRwkbGeometryType gtype = geom->getGeometryType();
219 if (gtype == wkbPolygon) {
220 assert(std::string(geom->getGeometryName()) == std::string("POLYGON"));
221 // try transform to wgs84
222 geom->transform(toWGS84);
223 OGRLinearRing* cgeom = ((OGRPolygon*) geom)->getExteriorRing();
224 // assume TIN with with 4 points and point0 == point3
225 assert(cgeom->getNumPoints() == 4);
226 PositionVector corners;
227 for (int j = 0; j < 3; j++) {
228 Position pos((double) cgeom->getX(j), (double) cgeom->getY(j), (double) cgeom->getZ(j));
229 corners.push_back(pos);
230 myBoundary.add(pos);
231 }
232 addTriangle(corners);
233 numFeatures++;
234 } else {
235 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 OGRFeature::DestroyFeature(feature);
269 }
270#if GDAL_VERSION_MAJOR < 2
271 OGRDataSource::DestroyDataSource(ds);
272#else
273 GDALClose(ds);
274#endif
275 OCTDestroyCoordinateTransformation(reinterpret_cast<OGRCoordinateTransformationH>(toWGS84));
276 OGRCleanupAll();
277 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}
284
285
286int
287NBHeightMapper::loadTiff(const std::string& file) {
288#ifdef HAVE_GDAL
289 GDALAllRegister();
290 GDALDataset* poDataset = (GDALDataset*)GDALOpen(file.c_str(), GA_ReadOnly);
291 if (poDataset == 0) {
292 WRITE_ERROR(TL("Cannot load GeoTIFF file."));
293 return 0;
294 }
295 Boundary boundary;
296 const int xSize = poDataset->GetRasterXSize();
297 const int ySize = poDataset->GetRasterYSize();
298 double adfGeoTransform[6];
299 if (poDataset->GetGeoTransform(adfGeoTransform) == CE_None) {
300 Position topLeft(adfGeoTransform[0], adfGeoTransform[3]);
301 mySizeOfPixel.set(adfGeoTransform[1], adfGeoTransform[5]);
302 const double horizontalSize = xSize * mySizeOfPixel.x();
303 const double verticalSize = ySize * mySizeOfPixel.y();
304 boundary.add(topLeft);
305 boundary.add(topLeft.x() + horizontalSize, topLeft.y() + verticalSize);
306 } else {
307 WRITE_ERRORF(TL("Could not parse geo information from %."), file);
308 return 0;
309 }
310 const int picSize = xSize * ySize;
311 int16_t* raster = (int16_t*)CPLMalloc(sizeof(int16_t) * picSize);
312 bool ok = true;
313 for (int i = 1; i <= poDataset->GetRasterCount(); i++) {
314 GDALRasterBand* poBand = poDataset->GetRasterBand(i);
315 if (poBand->GetColorInterpretation() != GCI_GrayIndex) {
316 WRITE_ERRORF(TL("Unknown color band in %."), file);
317 clearData();
318 ok = false;
319 break;
320 }
321 assert(xSize == poBand->GetXSize() && ySize == poBand->GetYSize());
322 if (poBand->RasterIO(GF_Read, 0, 0, xSize, ySize, raster, xSize, ySize, GDT_Int16, 0, 0) == CE_Failure) {
323 WRITE_ERRORF(TL("Failure in reading %."), file);
324 clearData();
325 ok = false;
326 break;
327 }
328 }
329 double min = std::numeric_limits<double>::max();
330 double max = -std::numeric_limits<double>::max();
331 for (int i = 0; i < picSize; i++) {
332 min = MIN2(min, (double)raster[i]);
333 max = MAX2(max, (double)raster[i]);
334 }
335 GDALClose(poDataset);
336 if (ok) {
337 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 rasterData.raster = raster;
342 rasterData.boundary = boundary;
343 rasterData.xSize = xSize;
344 rasterData.ySize = ySize;
345 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
358void
360 for (Triangles::iterator it = myTriangles.begin(); it != myTriangles.end(); it++) {
361 delete *it;
362 }
363 myTriangles.clear();
364#ifdef HAVE_GDAL
365 for (auto& item : myRasters) {
366 CPLFree(item.raster);
367 }
368 myRasters.clear();
369#endif
371}
372
373
374// ===========================================================================
375// Triangle member methods
376// ===========================================================================
378 myCorners(corners) {
379 assert(myCorners.size() == 3);
380 // @todo assert non-colinearity
381}
382
383
384void
386 queryResult.triangles.push_back(this);
387}
388
389
390bool
392 return myCorners.around(pos);
393}
394
395
396double
398 // en.wikipedia.org/wiki/Line-plane_intersection
399 Position p0 = myCorners.front();
400 Position line(0, 0, 1);
401 p0.sub(geo); // p0 - l0
402 Position normal = normalVector();
403 return p0.dotProduct(normal) / line.dotProduct(normal);
404}
405
406
409 // @todo maybe cache result to avoid multiple computations?
410 Position side1 = myCorners[1] - myCorners[0];
411 Position side2 = myCorners[2] - myCorners[0];
412 return side1.crossProduct(side2);
413}
414
415
416/****************************************************************************/
#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:46
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)