Saving & loading the DLIO map (G1 + MID-360)¶
TL;DR¶
- Save: call the
/save_pcdservice on the runningdlio_map_node. It writes<save_path>/dlio_map.pcd. - Load to resume / continue mapping: not possible with DLIO. DLIO is pure online LiDAR-inertial odometry — it rebuilds its map from scratch every run and resets the world origin to wherever the robot starts. The saved PCD is an output artifact (visualization, a global costmap, or input to a separate localization node), never a DLIO input. See § Loading.
Saving the map¶
What actually gets saved¶
dlio_map_node accumulates keyframe clouds (from /dlio/odom_node/pointcloud/keyframe)
into one cloud in the odom frame, held in memory. /save_pcd voxel-downsamples
that cloud at the request leaf_size and writes it
(map.cc:76-101).
- Fixed filename: always
dlio_map.pcdinside the directory you pass.save_path: '/ws/maps'→/ws/maps/dlio_map.pcd. leaf_sizeis an independent downsample of the saved file (notmap/sparse/leafSize).0.05–0.2is typical (0.05dense,0.2light); smaller = bigger file/more detail.
Procedure¶
Run inside the same container where dlio_map_node runs, and adopt the same
DDS domain as the launched stack first — otherwise the service is invisible and
the call hangs on waiting for service to become available. The stack's domain is
not the default: real_localization.launch.py puts the whole real stack on
ROS_DOMAIN_ID=42; the sim launches (sim_localization, dlio_debug) run on
the default 0.
export ROS_DOMAIN_ID=42 # REAL stack domain (sim: leave unset / 0)
ros2 daemon stop && ros2 daemon start # drop any stale wrong-domain CLI daemon
mkdir -p /ws/maps # make the maps dir if not already present
ros2 service list | grep save_pcd # sanity: /save_pcd must appear first
ros2 service call /save_pcd direct_lidar_inertial_odometry/srv/SavePCD \
"{leaf_size: 0.05, save_path: '/ws/maps'}"
Confirm: the dlio_map_node console prints Saving map to /ws/maps/dlio_map.pcd ... done,
and the service returns success: true. Then ls -la /ws/maps/.
Gotchas (learned the hard way)¶
/save_pcdlives only ondlio_map_node, not the odom node (map.cc:16,29). If your bring-up launches onlydlio_odom_node, the service does not exist.- "waiting for service to become available" while the node is clearly running
= your shell is on a different DDS view than the launched node (different RMW /
domain /
ROS_LOCALHOST_ONLY, or a stale CLI daemon showing duplicate node names). Adopt the node's env, then refresh the daemon: - The directory must exist and be writable in the container that runs
dlio_map_node— the file is written by that process, in its filesystem. - Save before the node dies. The map is in-memory only. If
dlio_odom_nodecrashes (e.g. themalloc(): invalid sizeheap fault),dlio_map_nodeoften stays alive holding the map — save immediately. Oncedlio_map_nodeexits the map is gone; there is no on-disk autosave.
Make it persist past the container¶
save_path must land on a host bind-mount or the PCD dies with the container. The
localization service mounts ../ros2_ws:/ws
(docker-compose.yml), so /ws/maps/dlio_map.pcd
appears on the host at Navigation/ros2_ws/maps/dlio_map.pcd and survives.
Viewing the saved map¶
The PCD is not a live topic — RViz cannot open a file directly. Publish it as a
PointCloud2, then add that topic. Run the publisher on the same ROS_DOMAIN_ID
as the RViz you want it to show up in (the real stack's RViz is on domain 42).
In RViz¶
export ROS_DOMAIN_ID=42 # match the target RViz (sim: unset / 0)
ros2 daemon stop && ros2 daemon start # drop any stale wrong-domain CLI daemon
# publishes /cloud_pcd in frame `map`, once per second:
ros2 run pcl_ros pcd_to_pointcloud --ros-args \
-p file_name:=/ws/maps/dlio_map.pcd \
-p tf_frame:=map \
-p publishing_period_ms:=1000
In RViz: set Fixed Frame = map, then Add → By topic → /cloud_pcd →
PointCloud2 (re-open the dialog if it was already up — the topic list is a
one-time snapshot, not live). Set the display Style: Flat Squares,
Size: ~0.05, Color Transformer: Intensity (or AxisColor on Z) — the default
1-px points on the dark background are easy to miss. No TF is needed: the cloud's
own frame is the fixed frame, so the No tf data status is harmless.
If
/cloud_pcdnever appears in the list, it's the usual domain split — the publisher and RViz are on differentROS_DOMAIN_IDs. Confirm withros2 topic list | grep cloud_pcdin the RViz terminal.
Without ROS (quick check)¶
On the host the file lives at Navigation/ros2_ws/maps/dlio_map.pcd — open it with
CloudCompare (best for inspecting/measuring) or Open3D:
import open3d as o3d
o3d.visualization.draw_geometries([o3d.io.read_point_cloud("ros2_ws/maps/dlio_map.pcd")])
Loading a map next time — the reality¶
DLIO cannot load a prior map to "not start from scratch." There is no
map_path / load parameter and no relocalization anywhere in dlio_odom_node
(odom.h:
only online keyframe/submap building; the first scan defines the origin). Every
run therefore:
- sets the world origin at the robot's start pose (so two runs are not in the same coordinate frame), and
- rebuilds the keyframe map from zero.
So you cannot continue or extend a saved DLIO map across sessions. What to do instead depends on your goal:
1. You only want the map as a reference / global costmap / RViz layer¶
Keep the saved dlio_map.pcd. View it (pcl_viewer dlio_map.pcd) or publish it as
a latched PointCloud2 in the odom/map frame to seed a global costmap. It does
not feed back into DLIO.
2. You want to localize against a known map (deploy without re-mapping)¶
Use a localization-against-prior-map node, not DLIO. The sibling stack already
has one: FAST_LIO_LOCALIZATION_HUMANOID (in the G1_navigation repo), which
loads a saved PCD and relocalizes against it. Workflow:
- Map once (DLIO mapping run),
/save_pcd. - Deploy with the localization node loading that PCD + an initial-pose guess; it estimates the robot's pose in the prior map's frame.
- Mind frame/PCD conventions — a FAST-LIO relocalizer expects its own map format/ frame; convert the DLIO PCD if needed, or build the deployment map with the same tool you localize with.
3. You want consistent multi-session maps / loop closure¶
DLIO has no loop closure and no map serialization (your test showed ~1 m residual returning near the origin after a 71 m loop). If that matters, do the mapping phase with a pose-graph SLAM that saves/loads its graph, and switch to a localization mode for deployment.
Recommended G1 workflow¶
- Mapping session: DLIO from a stationary start (see
DLIO_G1_MID360_TUNING.md§2.2), drive the area,/save_pcd. - Deployment sessions: localize against the saved PCD with a relocalization node (option 2) so you start in the known map frame instead of a fresh DLIO origin — that is the only way to "not start from scratch."
See also: DLIO_G1_MID360_TUNING.md,
LOCAL_VOXEL_MAP.md.