Skip to content

Commit e49d1f5

Browse files
committed
Add map centering
* Needs more transform utilities * Switch to std::array instead of raw double array * Add ROS params for setting map origin * Set RasterIo origin in pixel space based on user supplied geo origin * Switch to GdalDatasetUniquePtr Signed-off-by: Ryan Friedman <[email protected]>
1 parent 9882f8f commit e49d1f5

File tree

4 files changed

+123
-10
lines changed

4 files changed

+123
-10
lines changed

include/grid_map_geo/grid_map_geo.hpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ static const std::string COLOR_MAP_DEFAULT_PATH{""};
4444

4545
#include "grid_map_geo/transform.hpp"
4646
#include "tf2_ros/transform_broadcaster.h"
47-
struct Location {
48-
ESPG espg{ESPG::WGS84};
49-
Eigen::Vector3d position{Eigen::Vector3d::Zero()};
50-
};
5147

5248
class GridMapGeo {
5349
public:

include/grid_map_geo/transform.hpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,18 @@
4747
#endif
4848

4949
#include <Eigen/Dense>
50+
#include <array>
51+
#include <cassert>
5052

5153
enum class ESPG { ECEF = 4978, WGS84 = 4326, WGS84_32N = 32632, CH1903_LV03 = 21781 };
5254

55+
struct Location {
56+
ESPG espg{ESPG::WGS84};
57+
// <east (lat), north (lng), up (alt)>
58+
//! @todo Switch to geographic_msgs/GeoPoint to make x-y not confusing?
59+
Eigen::Vector3d position{Eigen::Vector3d::Zero()};
60+
};
61+
5362
/**
5463
* @brief Helper function for transforming using gdal
5564
*
@@ -100,4 +109,81 @@ inline Eigen::Vector3d transformCoordinates(ESPG src_coord, const std::string wk
100109
return target_coordinates;
101110
}
102111

112+
struct Corners {
113+
ESPG espg{ESPG::WGS84};
114+
Eigen::Vector2d top_left{Eigen::Vector2d::Zero()};
115+
Eigen::Vector2d top_right{Eigen::Vector2d::Zero()};
116+
Eigen::Vector2d bottom_left{Eigen::Vector2d::Zero()};
117+
Eigen::Vector2d bottom_right{Eigen::Vector2d::Zero()};
118+
};
119+
120+
/**
121+
* @brief Helper function converting from image to geo coordinates
122+
*
123+
* @ref
124+
https://gdal.org/tutorials/geotransforms_tut.html#transformation-from-image-coordinate-space-to-georeferenced-coordinate-space
125+
* @see GDALApplyGeoTransform
126+
*
127+
* @param geoTransform The 6-element Geo transform
128+
* @param imageCoords The image-coordinates <row, column>, also called <pixel, line>
129+
130+
* @return The geo-coordinates in <x, y>
131+
*/
132+
inline Eigen::Vector2d imageToGeo(const std::array<double, 6> geoTransform, const Eigen::Vector2i imageCoords) {
133+
const auto x_pixel = imageCoords.x();
134+
const auto y_line = imageCoords.y();
135+
136+
return {geoTransform.at(0) + x_pixel * geoTransform.at(1) + y_line * geoTransform.at(2),
137+
geoTransform.at(3) + x_pixel * geoTransform.at(4) + y_line * geoTransform.at(5)};
138+
}
139+
140+
/**
141+
* @brief Helper function converting from geo to image coordinates. Assumes no rotation.
142+
* Uses the assumption that GT2 and GT4 are zero
143+
*
144+
* @ref
145+
* https://gis.stackexchange.com/questions/384221/calculating-inverse-polynomial-transforms-for-pixel-sampling-when-map-georeferen
146+
* @see GDALApplyGeoTransform
147+
*
148+
* @param geoTransform The 6-element forward Geo transform
149+
* @param geoCoords The geo-coordinates in <x, y>
150+
*
151+
* @return The image-coordinates in <row, column>, also called <pixel, line>
152+
*/
153+
inline Eigen::Vector2d geoToImageNoRot(const std::array<double, 6>& geoTransform, const Eigen::Vector2d geoCoords) {
154+
assert(geoTransform.at(2) == 0); // assume no rotation
155+
assert(geoTransform.at(4) == 0); // assume no rotation
156+
157+
return {(geoCoords.x() - geoTransform.at(0)) / geoTransform.at(1),
158+
(geoCoords.y() - geoTransform.at(3)) / geoTransform.at(5)};
159+
}
160+
161+
/**
162+
* @brief Helper function for getting dataset corners
163+
* Inspired by gdalinfo_lib.cpp::GDALInfoReportCorner()
164+
*
165+
* @param datasetPtr The pointer to the dataset
166+
* @param corners The returned corners in the geographic coordinates
167+
* @return
168+
*/
169+
inline bool getGeoCorners(const GDALDatasetUniquePtr& datasetPtr, Corners& corners) {
170+
double originX, originY, pixelSizeX, pixelSizeY;
171+
std::array<double, 6> geoTransform;
172+
173+
// https://gdal.org/tutorials/geotransforms_tut.html#introduction-to-geotransforms
174+
if (!datasetPtr->GetGeoTransform(geoTransform.data()) == CE_None) {
175+
return false;
176+
}
177+
178+
const auto raster_width = datasetPtr->GetRasterXSize();
179+
const auto raster_height = datasetPtr->GetRasterYSize();
180+
181+
corners.top_left = imageToGeo(geoTransform, {0, 0});
182+
corners.top_right = imageToGeo(geoTransform, {raster_width, 0});
183+
corners.bottom_left = imageToGeo(geoTransform, {0, raster_height});
184+
corners.bottom_right = imageToGeo(geoTransform, {raster_width, raster_height});
185+
186+
return true;
187+
}
188+
103189
#endif

launch/load_vrt_launch.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
<launch>
22
<arg name="rviz" default="false"/>
3+
4+
<!-- Default CMAC https://maps.app.goo.gl/NXJ9JC23oskMDaEx8-->
5+
<arg name="map_origin_latitude" default="-35.363266"/>
6+
<arg name="map_origin_longitude" default="149.165199"/>
7+
38
<node pkg="tf2_ros" exec="static_transform_publisher" name="world_map" args="--frame-id world --child-frame-id map"/>
49

510
<node pkg="grid_map_geo" exec="map_publisher" name="map_publisher" output="screen">
611
<param name="gdal_dataset_path" value="/vsizip/$(find-pkg-share grid_map_geo)/resources/ap_srtm1.vrt.zip"/>
12+
<param name="map_origin_latitude" value="$(var map_origin_latitude)"/>
13+
<param name="map_origin_longitude" value="$(var map_origin_longitude)"/>
714
</node>
815

916
<group if="$(var rviz)">
10-
<node exec="rviz2" name="rviz2" pkg="rviz2" args="-d $(find-pkg-share grid_map_geo)/launch/config.rviz" />
17+
<node exec="rviz2" name="rviz2" pkg="rviz2" args="-d $(find-pkg-share grid_map_geo)/launch/config.rviz"/>
1118
</group>
1219
</launch>

src/grid_map_geo.cpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "grid_map_geo/grid_map_geo.hpp"
4141

4242
#include <array>
43+
#include <cassert>
4344
#include <grid_map_core/GridMapMath.hpp>
4445
#include <grid_map_core/iterators/CircleIterator.hpp>
4546
#include <grid_map_core/iterators/GridMapIterator.hpp>
@@ -137,10 +138,28 @@ bool GridMapGeo::initializeFromGdalDataset(const std::string &path) {
137138
grid_map::Length length(lengthX, lengthY);
138139
std::cout << "GMLX: " << lengthX << " GMLY: " << lengthY << std::endl;
139140

140-
double mapcenter_e = originX + pixelSizeX * grid_width * 0.5;
141-
double mapcenter_n = originY + pixelSizeY * grid_height * 0.5;
142-
maporigin_.espg = ESPG::CH1903_LV03;
143-
maporigin_.position = Eigen::Vector3d(mapcenter_e, mapcenter_n, 0.0);
141+
//! @todo use WGS-84 as map origin if specified
142+
//! @todo
143+
//! @todo check the origin point is non-void (not in the middle of the ocean)
144+
Corners corners;
145+
if (!getGeoCorners(dataset, corners)) {
146+
std::cerr << "Failed to get geographic corners of dataset!" << std::endl;
147+
return false;
148+
}
149+
150+
if (std::isnan(maporigin_.position.x()) || std::isnan(maporigin_.position.y())) {
151+
//! @todo Figure out how to not hard code the espg, perhaps using the dataset GEOGCRS attribute.
152+
// maporigin_.espg = ESPG::CH1903_LV03;
153+
const double mapcenter_e = (corners.top_left.x() + corners.bottom_right.x()) / 2.0;
154+
const double mapcenter_n = (corners.top_left.y() + corners.bottom_right.y()) / 2.0;
155+
maporigin_.position = Eigen::Vector3d(mapcenter_e, mapcenter_n, 0.0);
156+
} else {
157+
if (maporigin_.position.x() < corners.top_left.x() || maporigin_.position.x() > corners.bottom_right.x() ||
158+
maporigin_.position.y() < corners.top_left.y() || maporigin_.position.y() > corners.bottom_left.y()) {
159+
std::cerr << "Requested map origin is outside of raster dataset" << std::endl;
160+
return false;
161+
}
162+
}
144163

145164
Eigen::Vector2d position{Eigen::Vector2d::Zero()};
146165

@@ -152,8 +171,13 @@ bool GridMapGeo::initializeFromGdalDataset(const std::string &path) {
152171
assert(raster_count == 1); // expect only elevation data, otherwise it's the wrong dataset.
153172
GDALRasterBand *elevationBand = dataset->GetRasterBand(1);
154173

174+
// Compute the center in pixel space, then get the raster bounds nXOff and nYOff to extract with RasterIO
175+
const auto center_px = geoToImageNoRot(geoTransform, {maporigin_.position.x(), maporigin_.position.y()});
176+
const auto nxOff = center_px.x() - grid_width / 2;
177+
const auto nYOff = center_px.y() - grid_height / 2;
178+
155179
std::vector<float> data(grid_width * grid_height, 0.0f);
156-
const auto raster_err = elevationBand->RasterIO(GF_Read, 0, 0, grid_width, grid_height, &data[0], grid_width,
180+
const auto raster_err = elevationBand->RasterIO(GF_Read, nxOff, nYOff, grid_width, grid_height, &data[0], grid_width,
157181
grid_height, GDT_Float32, 0, 0);
158182
if (raster_err != CPLE_None) {
159183
return false;

0 commit comments

Comments
 (0)