Skip to content

Commit cd9e531

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 Signed-off-by: Ryan Friedman <ryanfriedman5410+github@gmail.com>
1 parent 51fa0e5 commit cd9e531

File tree

5 files changed

+168
-24
lines changed

5 files changed

+168
-24
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

4646
#include <iostream>
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: 90 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
*
@@ -74,6 +83,8 @@ inline Eigen::Vector3d transformCoordinates(ESPG src_coord, ESPG tgt_coord, cons
7483
return target_coordinates;
7584
}
7685

86+
87+
7788
/**
7889
* @brief Helper function for transforming using gdal
7990
*
@@ -100,4 +111,83 @@ inline Eigen::Vector3d transformCoordinates(ESPG src_coord, const std::string wk
100111
return target_coordinates;
101112
}
102113

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

46+
#include <cassert>
47+
4648
#if __APPLE__
4749
#include <cpl_string.h>
4850
#include <gdal.h>
@@ -83,16 +85,17 @@ bool GridMapGeo::Load(const std::string &map_path, bool algin_terrain, const std
8385

8486
bool GridMapGeo::initializeFromGdalDataset(const std::string &path, bool align_terrain) {
8587
GDALAllRegister();
86-
GDALDataset *dataset = (GDALDataset *)GDALOpen(path.c_str(), GA_ReadOnly);
88+
GDALDatasetUniquePtr dataset;
89+
dataset = GDALDatasetUniquePtr(GDALDataset::FromHandle(GDALOpen( path.c_str(), GA_ReadOnly )));
8790
if (!dataset) {
8891
std::cout << __func__ << "Failed to open '" << path << "'" << std::endl;
8992
return false;
9093
}
9194
std::cout << std::endl << "Loading GDAL Dataset for gridmap" << std::endl;
9295

9396
double originX, originY, pixelSizeX, pixelSizeY;
94-
double geoTransform[6];
95-
if (dataset->GetGeoTransform(geoTransform) == CE_None) {
97+
std::array<double, 6> geoTransform;
98+
if (dataset->GetGeoTransform(geoTransform.data()) == CE_None) {
9699
originX = geoTransform[0];
97100
originY = geoTransform[3];
98101
pixelSizeX = geoTransform[1];
@@ -133,10 +136,30 @@ bool GridMapGeo::initializeFromGdalDataset(const std::string &path, bool align_t
133136
grid_map::Length length(lengthX, lengthY);
134137
std::cout << "GMLX: " << lengthX << " GMLY: " << lengthY << std::endl;
135138

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

141164
Eigen::Vector2d position{Eigen::Vector2d::Zero()};
142165

@@ -164,8 +187,13 @@ bool GridMapGeo::initializeFromGdalDataset(const std::string &path, bool align_t
164187
assert(raster_count == 1); // expect only elevation data, otherwise it's the wrong dataset.
165188
GDALRasterBand *elevationBand = dataset->GetRasterBand(1);
166189

190+
// Compute the center in pixel space, then get the raster bounds nXOff and nYOff to extract with RasterIO
191+
const auto center_px = geoToImageNoRot(geoTransform, {maporigin_.position.x(), maporigin_.position.y()});
192+
const auto nxOff = center_px.x() - grid_width / 2;
193+
const auto nYOff = center_px.y() - grid_height / 2;
194+
167195
std::vector<float> data(grid_width * grid_height, 0.0f);
168-
const auto raster_err = elevationBand->RasterIO(GF_Read, 0, 0, grid_width, grid_height, &data[0], grid_width, grid_height, GDT_Float32, 0, 0);
196+
const auto raster_err = elevationBand->RasterIO(GF_Read, nxOff, nYOff, grid_width, grid_height, &data[0], grid_width, grid_height, GDT_Float32, 0, 0);
169197
if (raster_err != CPLE_None ) {
170198
return false;
171199
}
@@ -186,10 +214,10 @@ bool GridMapGeo::initializeFromGdalDataset(const std::string &path, bool align_t
186214
altitude = grid_map_.atPosition("elevation", Eigen::Vector2d(0.0, 0.0));
187215
}
188216

189-
Eigen::Translation3d meshlab_translation(0.0, 0.0, -altitude);
190-
Eigen::AngleAxisd meshlab_rotation(Eigen::AngleAxisd::Identity());
191-
Eigen::Isometry3d transform = meshlab_translation * meshlab_rotation; // Apply affine transformation.
192-
grid_map_ = grid_map_.getTransformedMap(transform, "elevation", grid_map_.getFrameId(), true);
217+
// Eigen::Translation3d meshlab_translation(0.0, 0.0, -altitude);
218+
// Eigen::AngleAxisd meshlab_rotation(Eigen::AngleAxisd::Identity());
219+
// Eigen::Isometry3d transform = meshlab_translation * meshlab_rotation; // Apply affine transformation.
220+
// grid_map_ = grid_map_.getTransformedMap(transform, "elevation", grid_map_.getFrameId(), true);
193221
return true;
194222
}
195223

@@ -203,8 +231,8 @@ bool GridMapGeo::addColorFromGeotiff(const std::string &path) {
203231
std::cout << std::endl << "Loading color layer from GeoTIFF file for gridmap" << std::endl;
204232

205233
double originX, originY, pixelSizeX, pixelSizeY;
206-
double geoTransform[6];
207-
if (dataset->GetGeoTransform(geoTransform) == CE_None) {
234+
std::array<double, 6> geoTransform;
235+
if (dataset->GetGeoTransform(geoTransform.data()) == CE_None) {
208236
originX = geoTransform[0];
209237
originY = geoTransform[3];
210238
pixelSizeX = geoTransform[1];
@@ -264,8 +292,8 @@ bool GridMapGeo::addLayerFromGeotiff(const std::string &layer_name, const std::s
264292
std::cout << std::endl << "Loading color layer from GeoTIFF file for gridmap" << std::endl;
265293

266294
double originX, originY, pixelSizeX, pixelSizeY;
267-
double geoTransform[6];
268-
if (dataset->GetGeoTransform(geoTransform) == CE_None) {
295+
std::array<double, 6> geoTransform;
296+
if (dataset->GetGeoTransform(geoTransform.data()) == CE_None) {
269297
originX = geoTransform[0];
270298
originY = geoTransform[3];
271299
pixelSizeX = geoTransform[1];

src/map_publisher.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,34 @@ class MapPublisher : public rclcpp::Node {
8181
RCLCPP_INFO_STREAM(get_logger(), "gdal_dataset_path: '" << gdal_dataset_path << "'");
8282
RCLCPP_INFO_STREAM(get_logger(), "gdal_dataset_color_path: '" << color_path << "'");
8383

84+
rcl_interfaces::msg::ParameterDescriptor origin_descriptor;
85+
origin_descriptor.read_only = true;
86+
origin_descriptor.description = "Map origin latitude (WGS-84) in degrees.";
87+
rcl_interfaces::msg::FloatingPointRange origin_range;
88+
89+
origin_range.from_value = -90.0;
90+
origin_range.to_value = 90.0;
91+
origin_descriptor.floating_point_range.push_back(origin_range);
92+
93+
static_assert(std::numeric_limits<double>::has_quiet_NaN == true, "Need quiet NaN for default value");
94+
const auto map_origin_latitude = std::clamp(
95+
this->declare_parameter("map_origin_latitude", std::numeric_limits<double>::quiet_NaN(), origin_descriptor),
96+
origin_range.from_value,
97+
origin_range.to_value);
98+
99+
origin_range.from_value = -180.0;
100+
origin_range.to_value = 180.0;
101+
origin_descriptor.floating_point_range.at(0) = origin_range;
102+
103+
origin_descriptor.description = "Map origin longitude (WGS-84) in degrees.";
104+
const auto map_origin_longitude = std::clamp(
105+
this->declare_parameter("map_origin_longitude", std::numeric_limits<double>::quiet_NaN(), origin_descriptor),
106+
origin_range.from_value,
107+
origin_range.to_value);
108+
84109
map_ = std::make_shared<GridMapGeo>();
85110
map_->setMaxMapSizePixels(max_map_width, max_map_height);
111+
map_->setGlobalOrigin(ESPG::WGS84, Eigen::Vector3d(map_origin_latitude, map_origin_longitude, 0.0));
86112
map_->Load(gdal_dataset_path, false, color_path);
87113

88114
auto timer_callback = [this]() -> void {
@@ -95,9 +121,6 @@ class MapPublisher : public rclcpp::Node {
95121
timer_ = this->create_wall_timer(5s, timer_callback);
96122
}
97123
private:
98-
void timer_callback() {
99-
100-
}
101124
rclcpp::TimerBase::SharedPtr timer_;
102125
rclcpp::Publisher<grid_map_msgs::msg::GridMap>::SharedPtr original_map_pub_;
103126
std::shared_ptr<GridMapGeo> map_;

0 commit comments

Comments
 (0)