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 <gdal_version.h>
44#include <ogrsf_frmts.h>
45#include <ogr_api.h>
46#include <gdal_priv.h>
47#if __GNUC__ > 3
48#pragma GCC diagnostic pop
49#endif
50#ifdef _MSC_VER
51#pragma warning(pop)
52#endif
53#endif
54
55// ===========================================================================
56// static members
57// ===========================================================================
59
60
61// ===========================================================================
62// method definitions
63// ===========================================================================
65 myRTree(&Triangle::addSelf) {
66}
67
68
72
73
74const NBHeightMapper&
78
79
80bool
82 return myRasters.size() > 0 || myTriangles.size() > 0;
83}
84
85
86double
87NBHeightMapper::getZ(const Position& geo) const {
88 if (!ready()) {
89 WRITE_WARNING(TL("Cannot supply height since no height data was loaded"));
90 return 0;
91 }
92 for (auto& item : myRasters) {
93 const Boundary& boundary = item.boundary;
94 float* raster = item.raster;
95 double result = -1e6;
96
97 double x = geo.x();
98 double y = geo.y();
99
100#ifdef HAVE_GDAL
101 // Transform geo coordinates to the coordinate system of this
102 // raster image for lookup in its raster, if applicable.
103 if (item.transform != nullptr) {
104 // Since the input coordinates are always WGS84 (they may be
105 // transformed to it in NBNetBuilder::transformCoordinate), and
106 // WGS84 uses latitude-longitude order (y-x), we have to swap the
107 // input coordinates here.
108 std::swap(x, y);
109
110 item.transform->Transform(1, &x, &y);
111 }
112#endif
113
114 if (boundary.around2D(x, y)) {
115 const int xSize = item.xSize;
116 const double normX = (x - boundary.xmin()) / mySizeOfPixel.x();
117 const double normY = (y - boundary.ymax()) / mySizeOfPixel.y();
118 PositionVector corners;
119 corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX]));
120 if (normX - floor(normX) > 0.5) {
121 corners.push_back(Position(floor(normX) + 1.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX + 1]));
122 } else {
123 corners.push_back(Position(floor(normX) - 0.5, floor(normY) + 0.5, raster[(int)normY * xSize + (int)normX - 1]));
124 }
125 if (normY - floor(normY) > 0.5 && ((int)normY + 1) < item.ySize) {
126 corners.push_back(Position(floor(normX) + 0.5, floor(normY) + 1.5, raster[((int)normY + 1) * xSize + (int)normX]));
127 } else {
128 corners.push_back(Position(floor(normX) + 0.5, floor(normY) - 0.5, raster[((int)normY - 1) * xSize + (int)normX]));
129 }
130 result = Triangle(corners).getZ(Position(normX, normY));
131 }
132 if (result > -1e5 && result < 1e5) {
133 return result;
134 }
135 }
136 // coordinates in degrees hence a small search window
137 float minB[2];
138 float maxB[2];
139 minB[0] = (float)geo.x() - 0.00001f;
140 minB[1] = (float)geo.y() - 0.00001f;
141 maxB[0] = (float)geo.x() + 0.00001f;
142 maxB[1] = (float)geo.y() + 0.00001f;
143 QueryResult queryResult;
144 int hits = myRTree.Search(minB, maxB, queryResult);
145 Triangles result = queryResult.triangles;
146 assert(hits == (int)result.size());
147 UNUSED_PARAMETER(hits); // only used for assertion
148
149 for (Triangles::iterator it = result.begin(); it != result.end(); it++) {
150 const Triangle* triangle = *it;
151 if (triangle->contains(geo)) {
152 return triangle->getZ(geo);
153 }
154 }
155 WRITE_WARNINGF(TL("Could not get height data for coordinate %"), toString(geo));
156 return 0;
157}
158
159
160void
162 Triangle* triangle = new Triangle(corners);
163 myTriangles.push_back(triangle);
164 Boundary b = corners.getBoxBoundary();
165 const float cmin[2] = {(float) b.xmin(), (float) b.ymin()};
166 const float cmax[2] = {(float) b.xmax(), (float) b.ymax()};
167 myRTree.Insert(cmin, cmax, triangle);
168}
169
170
171void
173 if (oc.isSet("heightmap.geotiff")) {
174 // parse file(s)
175 std::vector<std::string> files = oc.getStringVector("heightmap.geotiff");
176 for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
177 PROGRESS_BEGIN_MESSAGE("Parsing from GeoTIFF '" + *file + "'");
178 int numFeatures = myInstance.loadTiff(*file);
180 " done (parsed " + toString(numFeatures) +
181 " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
182 }
183 }
184 if (oc.isSet("heightmap.shapefiles")) {
185 // parse file(s)
186 std::vector<std::string> files = oc.getStringVector("heightmap.shapefiles");
187 for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
188 PROGRESS_BEGIN_MESSAGE("Parsing from shape-file '" + *file + "'");
189 int numFeatures = myInstance.loadShapeFile(*file);
191 " done (parsed " + toString(numFeatures) +
192 " features, Boundary: " + toString(myInstance.getBoundary()) + ").");
193 }
194 }
195}
196
197
198int
199NBHeightMapper::loadShapeFile(const std::string& file) {
200#ifdef HAVE_GDAL
201#if GDAL_VERSION_MAJOR < 2
202 OGRRegisterAll();
203 OGRDataSource* ds = OGRSFDriverRegistrar::Open(file.c_str(), FALSE);
204#else
205 GDALAllRegister();
206 GDALDataset* ds = (GDALDataset*)GDALOpenEx(file.c_str(), GDAL_OF_VECTOR | GA_ReadOnly, nullptr, nullptr, nullptr);
207#endif
208 if (ds == nullptr) {
209 throw ProcessError(TLF("Could not open shape file '%'.", file));
210 }
211
212 // begin file parsing
213 OGRLayer* layer = ds->GetLayer(0);
214 layer->ResetReading();
215
216 // triangle coordinates are stored in WGS84 and later matched with network coordinates in WGS84
217 // build coordinate transformation
218#if GDAL_VERSION_MAJOR < 3
219 OGRSpatialReference* sr_src = layer->GetSpatialRef();
220#else
221 const OGRSpatialReference* sr_src = layer->GetSpatialRef();
222#endif
223 OGRSpatialReference sr_dest;
224 sr_dest.SetWellKnownGeogCS("WGS84");
225 OGRCoordinateTransformation* toWGS84 = OGRCreateCoordinateTransformation(sr_src, &sr_dest);
226 if (toWGS84 == nullptr) {
227 WRITE_WARNING(TL("Could not create geocoordinates converter; check whether proj.4 is installed."));
228 }
229
230 int numFeatures = 0;
231 OGRFeature* feature;
232 layer->ResetReading();
233 while ((feature = layer->GetNextFeature()) != nullptr) {
234 OGRGeometry* geom = feature->GetGeometryRef();
235 assert(geom != 0);
236
237 OGRwkbGeometryType gtype = geom->getGeometryType();
238 if (gtype == wkbPolygon) {
239 assert(std::string(geom->getGeometryName()) == std::string("POLYGON"));
240 // try transform to wgs84
241 geom->transform(toWGS84);
242 OGRLinearRing* cgeom = ((OGRPolygon*) geom)->getExteriorRing();
243 // assume TIN with with 4 points and point0 == point3
244 assert(cgeom->getNumPoints() == 4);
245 PositionVector corners;
246 for (int j = 0; j < 3; j++) {
247 Position pos((double) cgeom->getX(j), (double) cgeom->getY(j), (double) cgeom->getZ(j));
248 corners.push_back(pos);
249 myBoundary.add(pos);
250 }
251 addTriangle(corners);
252 numFeatures++;
253 } else {
254 WRITE_WARNINGF(TL("Ignored heightmap feature type %"), geom->getGeometryName());
255 }
256
257 /*
258 switch (gtype) {
259 case wkbPolygon: {
260 break;
261 }
262 case wkbPoint: {
263 WRITE_WARNING(TL("got wkbPoint"));
264 break;
265 }
266 case wkbLineString: {
267 WRITE_WARNING(TL("got wkbLineString"));
268 break;
269 }
270 case wkbMultiPoint: {
271 WRITE_WARNING(TL("got wkbMultiPoint"));
272 break;
273 }
274 case wkbMultiLineString: {
275 WRITE_WARNING(TL("got wkbMultiLineString"));
276 break;
277 }
278 case wkbMultiPolygon: {
279 WRITE_WARNING(TL("got wkbMultiPolygon"));
280 break;
281 }
282 default:
283 WRITE_WARNING(TL("Unsupported shape type occurred"));
284 break;
285 }
286 */
287 OGRFeature::DestroyFeature(feature);
288 }
289#if GDAL_VERSION_MAJOR < 2
290 OGRDataSource::DestroyDataSource(ds);
291#else
292 GDALClose(ds);
293#endif
294 OCTDestroyCoordinateTransformation(reinterpret_cast<OGRCoordinateTransformationH>(toWGS84));
295 OGRCleanupAll();
296 return numFeatures;
297#else
298 UNUSED_PARAMETER(file);
299 WRITE_ERROR(TL("Cannot load shape file since SUMO was compiled without GDAL support."));
300 return 0;
301#endif
302}
303
304
305int
306NBHeightMapper::loadTiff(const std::string& file) {
307#ifdef HAVE_GDAL
308 GDALAllRegister();
309 GDALDataset* poDataset = (GDALDataset*)GDALOpen(file.c_str(), GA_ReadOnly);
310 if (poDataset == 0) {
311 WRITE_ERROR(TL("Cannot load GeoTIFF file."));
312 return 0;
313 }
314 Boundary boundary;
315 const int xSize = poDataset->GetRasterXSize();
316 const int ySize = poDataset->GetRasterYSize();
317 double adfGeoTransform[6];
318 if (poDataset->GetGeoTransform(adfGeoTransform) == CE_None) {
319 Position topLeft(adfGeoTransform[0], adfGeoTransform[3]);
320 mySizeOfPixel.set(adfGeoTransform[1], adfGeoTransform[5]);
321 const double horizontalSize = xSize * mySizeOfPixel.x();
322 const double verticalSize = ySize * mySizeOfPixel.y();
323 boundary.add(topLeft);
324 boundary.add(topLeft.x() + horizontalSize, topLeft.y() + verticalSize);
325 } else {
326 WRITE_ERRORF(TL("Could not parse geo information from %."), file);
327 return 0;
328 }
329 const int picSize = xSize * ySize;
330 float* raster = (float*)CPLMalloc(sizeof(float) * picSize);
331 bool ok = true;
332 for (int i = 1; i <= poDataset->GetRasterCount(); i++) {
333 GDALRasterBand* poBand = poDataset->GetRasterBand(i);
334 if (poBand->GetColorInterpretation() != GCI_GrayIndex) {
335 WRITE_ERRORF(TL("Unknown color band in %."), file);
336 clearData();
337 ok = false;
338 break;
339 }
340 assert(xSize == poBand->GetXSize() && ySize == poBand->GetYSize());
341 if (poBand->RasterIO(GF_Read, 0, 0, xSize, ySize, raster, xSize, ySize, GDT_Float32, 0, 0) == CE_Failure) {
342 WRITE_ERRORF(TL("Failure in reading %."), file);
343 clearData();
344 ok = false;
345 break;
346 }
347 }
348 double min = std::numeric_limits<double>::max();
349 double max = -std::numeric_limits<double>::max();
350 for (int i = 0; i < picSize; i++) {
351 min = MIN2(min, (double)raster[i]);
352 max = MAX2(max, (double)raster[i]);
353 }
354
355 // Make a copy, GDALClose will destroy the original
356#if GDAL_VERSION_MAJOR < 3
357 OGRSpatialReference spatialRef;
358 char* wkt = const_cast<char*>(poDataset->GetProjectionRef());
359 spatialRef.importFromWkt(&wkt);
360#else
361 OGRSpatialReference spatialRef(*poDataset->GetSpatialRef());
362#endif
363 GDALClose(poDataset);
364 if (ok) {
365 WRITE_MESSAGE("Read geotiff heightmap with size " + toString(xSize) + "," + toString(ySize)
366 + " for geo boundary [" + toString(boundary)
367 + "] with elevation range [" + toString(min) + "," + toString(max) + "].");
368 OGRSpatialReference wgs;
369 wgs.SetWellKnownGeogCS("WGS84");
370 myRasters.push_back(RasterData{raster, boundary, xSize, ySize, OGRCreateCoordinateTransformation(&wgs, &spatialRef)});
371 return picSize;
372 }
373 return 0;
374#else
375 UNUSED_PARAMETER(file);
376 WRITE_ERROR(TL("Cannot load GeoTIFF file since SUMO was compiled without GDAL support."));
377 return 0;
378#endif
379}
380
381
382void
384 for (Triangles::iterator it = myTriangles.begin(); it != myTriangles.end(); it++) {
385 delete *it;
386 }
387 myTriangles.clear();
388#ifdef HAVE_GDAL
389 for (auto& item : myRasters) {
390 CPLFree(item.raster);
391 if (item.transform != nullptr) {
392 delete item.transform;
393 }
394 }
395 myRasters.clear();
396#endif
398}
399
400
401// ===========================================================================
402// Triangle member methods
403// ===========================================================================
405 myCorners(corners) {
406 assert(myCorners.size() == 3);
407 // @todo assert non-colinearity
408}
409
410
411void
413 queryResult.triangles.push_back(this);
414}
415
416
417bool
419 return myCorners.around(pos);
420}
421
422
423double
425 // en.wikipedia.org/wiki/Line-plane_intersection
426 Position p0 = myCorners.front();
427 Position line(0, 0, 1);
428 p0.sub(geo); // p0 - l0
429 Position normal = normalVector();
430 return p0.dotProduct(normal) / line.dotProduct(normal);
431}
432
433
436 // @todo maybe cache result to avoid multiple computations?
437 Position side1 = myCorners[1] - myCorners[0];
438 Position side2 = myCorners[2] - myCorners[0];
439 return side1.crossProduct(side2);
440}
441
442
443/****************************************************************************/
#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