Skip to content

Add navmap_tools: GIS-based world/NavMap generator - #24

Merged
fmrico merged 1 commit into
rollingfrom
get_satellite
Aug 19, 2026
Merged

Add navmap_tools: GIS-based world/NavMap generator#24
fmrico merged 1 commit into
rollingfrom
get_satellite

Conversation

@fmrico

@fmrico fmrico commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Hi,

This PR adds a new package, navmap_tools, with a CLI tool (navmap_gis_tool) that
generates a Gazebo world and/or a NavMap directly from real elevation and
aerial-imagery data, given only a GPS center and a size in meters. Also adds
the navmap_ros conversion this tool relies on, and a small
navmap_rviz_plugin refactor.

Captura desde 2026-08-19 10-26-17

navmap_tools (new package)

  • navmap_gis_tool: fetches a DEM (Copernicus GLO-30) and aerial imagery
    (Esri World Imagery, PNOA for Spain, or Google Maps Platform) for a given
    GPS center + square size, builds a regular elevation grid, and emits:
    • --gazebo: a full colcon package (worlds/, models/<name>/meshes/ {.dae,.stl}, launch/, env-hooks/) with a textured, Z-up terrain mesh
      and a matching <spherical_coordinates> GPS anchor.
    • --navmap: a colored, slope-aware .navmap (via the new
      pointcloud_to_navmap C++ tool), sharing the exact same local-ENU origin
      as the Gazebo world so both line up.
  • navmap_publisher: publishes a .navmap file to a topic for
    navmap_rviz_plugin.
  • Download caching (geo/cache.py) so repeated runs over the same area don't
    re-fetch DEM/imagery.

navmap_ros

  • from_regular_grid(): builds a NavMap directly from an organized
    (grid-shaped) point cloud using its known (i, j) connectivity, instead of
    neighbor search — no meshing-artifact gaps. Used by pointcloud_to_navmap
    for navmap_gis_tool-generated grids.
  • Fixed from_points()'s max_edge_len rejection: it could be tighter than
    the geometrically-possible worst case (search radius + max |Δz|),
    spuriously punching holes in otherwise-navigable, slightly noisy terrain.

navmap_rviz_plugin

  • Small refactor: the "Color (vertex RGBA)" layer name is now a single
    shared constant instead of a string literal duplicated in two places.

Usage

colcon build --symlink-install --packages-up-to navmap_tools
source install/setup.bash

# Gazebo world + NavMap for a 150 m square around a GPS point (Esri imagery, default)
ros2 run navmap_tools navmap_gis_tool \
  --center 40.3314,-3.8356 --size 150 \
  --gazebo --navmap \
  --package my_area_world

# Spain: PNOA usually has better real coverage/resolution than Esri in rural areas
ros2 run navmap_tools navmap_gis_tool \
  --center 42.630258,-5.551252 --size 400 \
  --gazebo --navmap --imagery-source pnoa \
  --package my_farm_world

# View the generated NavMap in RViz2
ros2 run navmap_tools navmap_publisher --input my_area_world/maps/my_area_world.navmap

# Load the generated world directly in Gazebo (no ROS)
export GZ_SIM_RESOURCE_PATH=$(pwd)/my_area_world/models:$(pwd)/my_area_world/worlds
gz sim my_area_world/worlds/my_area_world.world

Useful flags:

Flag Default Meaning
--resolution 1.0 Grid sampling resolution in meters (shared by --gazebo geometry and --navmap)
--max-slope-deg 30.0 Maximum navigable slope for the NavMap
--imagery-source esri esri (worldwide, keyless), pnoa (Spain only, keyless, sharper in rural areas), or google (worldwide, paid — needs GOOGLE_MAPS_API_KEY)
--output-dir ./<package> Where to create the output package
--force-refresh off Bypass the GIS download cache

Google Maps imagery requires a Google Maps Platform API key with "Map Tiles
API" enabled, set via GOOGLE_MAPS_API_KEY.

Signed-off-by: Francisco Martín Rico <fmrico@gmail.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 08:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new ROS 2 package (navmap_tools) that can generate a Gazebo world and/or a .navmap directly from real-world GIS elevation + imagery data, and extends navmap_ros with an organized-grid conversion path to avoid meshing gaps when the point cloud is already grid-structured.

Changes:

  • Add navmap_tools (Python GIS pipeline + scaffold/mesh/PCD utilities) and two C++ CLI tools: pointcloud_to_navmap and navmap_publisher.
  • Extend navmap_ros conversions with from_regular_grid() and adjust from_points() edge/candidate selection logic to reduce “hole” artifacts.
  • Refactor navmap_rviz_plugin to centralize the “Color (vertex RGBA)” layer name and expose it conditionally.

Reviewed changes

Copilot reviewed 39 out of 40 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
navmap_tools/test/test_xmllint.py Adds XML lint test harness for package files.
navmap_tools/test/test_terrain.py Tests terrain grid sampling + texture rendering logic.
navmap_tools/test/test_scaffold.py Tests generated world-package scaffold layout and contents.
navmap_tools/test/test_projection.py Tests local AEQD projection and bbox computations.
navmap_tools/test/test_pnoa.py Tests PNOA WMS chunking/mosaicking with monkeypatching.
navmap_tools/test/test_pep257.py Adds PEP257 lint test harness.
navmap_tools/test/test_pcd_writer.py Tests PCD + RGB sidecar writing (incl. organized clouds).
navmap_tools/test/test_net.py Tests retrying GET-to-file helper without real network.
navmap_tools/test/test_mesh_export.py Tests STL/DAE/texture export and geometry correctness.
navmap_tools/test/test_imagery.py Tests Esri imagery tiling, placeholders, and fallback zoom.
navmap_tools/test/test_google.py Tests Google Map Tiles API session/token/caching logic (mocked).
navmap_tools/test/test_flake8.py Adds flake8 lint test harness.
navmap_tools/test/test_dem.py Tests Copernicus DEM tile naming, sampling, and mosaicking (mocked).
navmap_tools/test/test_copyright.py Adds copyright lint test harness.
navmap_tools/test/test_cli.py Tests CLI argument parsing and validation behavior.
navmap_tools/test/test_cache.py Tests DiskCache correctness and cleanup semantics.
navmap_tools/test/init.py Marks test directory as a Python package.
navmap_tools/src/pointcloud_to_navmap.cpp New C++ tool to build .navmap (and optional vertex colors) from .pcd.
navmap_tools/src/navmap_publisher.cpp New C++ tool to periodically publish a .navmap to a topic.
navmap_tools/scripts/navmap_gis_tool Console entrypoint wrapper for the Python CLI.
navmap_tools/package.xml Declares dependencies for the new navmap_tools package.
navmap_tools/navmap_tools/terrain.py Builds TerrainGrid samples and renders a baked texture.
navmap_tools/navmap_tools/scaffold.py Generates colcon package structure and template files for Gazebo.
navmap_tools/navmap_tools/pcd_writer.py Writes XYZ PCD (optionally organized) plus RGB CSV sidecar.
navmap_tools/navmap_tools/mesh_export.py Exports regular-grid terrain as textured DAE + collision STL.
navmap_tools/navmap_tools/geo/projection.py Implements local ENU projection and size→bbox perimeter sampling.
navmap_tools/navmap_tools/geo/pnoa.py Fetches/mosaics PNOA imagery via WMS with caching and chunking.
navmap_tools/navmap_tools/geo/net.py Implements retrying streaming download helper.
navmap_tools/navmap_tools/geo/imagery.py Fetches/mosaics Esri XYZ imagery and detects placeholder tiles.
navmap_tools/navmap_tools/geo/google.py Fetches/mosaics Google Map Tiles imagery with session tokens + caching.
navmap_tools/navmap_tools/geo/dem.py Fetches/mosaics Copernicus DEM tiles and provides bilinear sampling.
navmap_tools/navmap_tools/geo/cache.py Implements content-addressed disk cache for downloads.
navmap_tools/navmap_tools/geo/init.py Package init for GIS submodule.
navmap_tools/navmap_tools/cli.py Orchestrates the full GIS→TerrainGrid→Gazebo/NavMap pipeline.
navmap_tools/navmap_tools/init.py Package init for navmap_tools.
navmap_tools/CMakeLists.txt Builds/installs the Python package and the two C++ tools; enables tests.
navmap_rviz_plugin/src/navmap_rviz_plugin/NavMapDisplay.cpp Refactors vertex-color layer name into a shared constant and conditionally exposes it.
navmap_ros/tests/test_conversions.cpp Adds gtests for from_regular_grid() behavior and coverage.
navmap_ros/src/navmap_ros/conversions.cpp Fixes/adjusts from_points() edge/candidate logic and implements from_regular_grid().
navmap_ros/include/navmap_ros/conversions.hpp Exposes from_regular_grid() API with documentation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +25 to +29
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
Comment on lines +101 to +111
std::istringstream iss(line);
std::string r_s, g_s, b_s;
if (!std::getline(iss, r_s, ',') || !std::getline(iss, g_s, ',') ||
!std::getline(iss, b_s, ','))
{
throw std::runtime_error("malformed colors CSV line: " + line);
}
colors.push_back(
{static_cast<uint8_t>(std::stoi(r_s)), static_cast<uint8_t>(std::stoi(g_s)),
static_cast<uint8_t>(std::stoi(b_s))});
}
Comment on lines +1429 to +1441
Eigen::Vector3f a(A.x, A.y, A.z), b(B.x, B.y, B.z), c(C.x, C.y, C.z);
Eigen::Vector3f n = (b - a).cross(c - a);
const float nn = n.norm();
if (nn < 1e-9f) {return;}
n /= nn;
if (n.dot(Eigen::Vector3f::UnitZ()) < cos_max_slope) {return;}

// Canonical orientation (normal facing +Z), matching try_add_triangle.
if (n.dot(Eigen::Vector3f::UnitZ()) < 0.0f) {
triangles.emplace_back(i0, i2, i1);
} else {
triangles.emplace_back(i0, i1, i2);
}
Comment on lines +77 to +80
def _is_placeholder_tile(path) -> bool:
"""Return True if the tile at `path` is Esri's known "no data" placeholder image."""
digest = hashlib.md5(open(path, 'rb').read()).hexdigest()
return digest in _PLACEHOLDER_MD5_HASHES
Comment on lines +70 to +76
} else if (arg == "--resolution") {
const char * v = next("--resolution"); if (!v) {return false;}
args.resolution = std::stof(v);
} else if (arg == "--max-slope-deg") {
const char * v = next("--max-slope-deg"); if (!v) {return false;}
args.max_slope_deg = std::stof(v);
} else {
@fmrico
fmrico merged commit 9c7ae21 into rolling Aug 19, 2026
2 checks passed
@fmrico
fmrico deleted the get_satellite branch August 19, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants