FixTheRobot Explore Robotics / Systems Engineering / Design & Selection / Module 12

Design & Selection · Module 12

Outdoor-to-indoor localization architecture

Outdoors GNSS anchors the robot, indoors a map does. Between them lies a gap where neither is good: design the handover and the fallback before the first test.

  • Intermediate
  • Module 12: Outdoor-to-Indoor Localization & GNSS-Denied Navigation
  • Written for ROS 2 Jazzy where ROS 2 is used
  • Sources checked 2026-11-06

Assumptions: Numbers in the examples are illustrative and the code is executed by the site’s tests. Facts taken from a cited source are marked; other statements are general engineering knowledge and must be confirmed on the datasheet of the part you use.

1. Engineering problem

GNSS gives absolute position outdoors and fails under roofs. Map-based localisation (LiDAR or visual against a map) works indoors where a map exists. A robot that crosses a door must hand over from one to the other, ride on odometry through the gap, and not jump or lose itself.

2. When is this needed?

  • Designing a delivery or inspection robot that crosses building boundaries.
  • When the pose jumps at an entrance.
  • When choosing which sensors each zone needs.

3. Selection and design decisions

REP 105 (read 2026-11-05) defines the frame chain map → odom → base_link: odom is continuous but drifts, map does not drift but may jump. That chain suits this problem: continuous local motion estimate in odom, and a global correction (GNSS or map matching) published as the map → odom transform. The NavSatFix message carries a status (no fix −1, fix 0, SBAS 1, GBAS 2; valid when ≥ 0) and a covariance in ENU order with a covariance type, which are the health signals for the GNSS side.

Overview
ZoneBest absolute sourceBackupNotes
Open outdoorsGNSS (RTK if required)LiDAR or visual odometryCheck status and covariance
Urban canyon, treesGNSS with gating plus LiDAR mapOdometryMultipath gives jumps
Entrance and gapNeither is reliable: odometry carries the poseMarkers or a prepared transition mapShortest possible
Indoors with a mapMap-based localisationOdometryNeeds a map aligned to the outdoor frame
Indoors without a mapSLAMOdometryNo global frame without extra input

4. Advantages and disadvantages

  • Hard switch is simple and can jump; covariance-weighted fusion is smoother and needs honest covariances (see the handover guide).
  • One filter for all vs per-zone estimators: one filter needs all sources to be consistent; separate ones need a handover.
  • A map of the entrance shortens the gap.

5. Requirements and constraints

  • Every localisation source publishes a covariance and a health flag.
  • The gap length at the entrance at maximum speed stays within the odometry drift budget.
  • All sources agree on the frame definitions (see the frames guide).

6. Architecture

GNSS ──┐
       ├─▶ fusion / handover ─▶ map→odom ─▶ odom→base_link (continuous) ─▶ planner
map-based localisation ─┘      odometry (wheel, IMU, LiDAR) feeds odom→base_link

7. Implementation

  1. List zones and which sources work in each.
  2. Define health signals per source (GNSS status and covariance; map match score and covariance).
  3. Define the mode logic with hysteresis (below).
  4. Define the behaviour in odometry-only mode (speed limit, time limit, stop).
  5. Test the crossing repeatedly at several speeds and in both directions.
def mode(prev, gnss_ok, gnss_sigma, map_match, map_sigma):
    # hysteresis: enter GNSS mode when sigma < 0.5 m, leave when sigma > 1.5 m; map mode needs match score >= 0.6
    if prev in ("gnss", "fused"):
        use_gnss = gnss_ok and gnss_sigma < 1.5
    else:
        use_gnss = gnss_ok and gnss_sigma < 0.5
    use_map = map_match >= 0.6 and map_sigma < 0.5
    if use_gnss and use_map:
        return "fused"
    if use_gnss:
        return "gnss"
    if use_map:
        return "map"
    return "odom-only"
log = [(True, 0.2, 0.1, 9.9), (True, 0.4, 0.9, 0.2), (True, 1.2, 0.9, 0.2), (True, 2.5, 0.8, 0.2), (False, 99, 0.9, 0.2), (False, 99, 0.3, 0.3), (False, 99, 0.2, 5.0)]
m = "odom-only"
for t, (ok, gs, mm, ms) in enumerate(log):
    m = mode(m, ok, gs, mm, ms)
    print(t, m)

Printed (checked by the site’s tests):

0 gnss
1 fused
2 fused
3 map
4 map
5 odom-only
6 odom-only

Reading it: with the example thresholds the robot starts on GNSS, becomes fused when the map matches, stays fused while GNSS degrades from 0.4 to 1.2 m (it leaves GNSS only above 1.5 m: hysteresis), moves to map-only when GNSS reaches 2.5 m, falls to odometry-only when the map match falls, and stays there while GNSS is absent and the map match is below the threshold or uncertain. The thresholds are illustrative: derive yours from tests.

8. Real examples

  • Campus delivery robot: RTK GNSS outdoors, a prebuilt LiDAR map inside the building, a short odometry-only passage through the door.

9. Failure scenarios

  • A hard switch at a fixed door position that mis-triggers on a different route.
  • Map and GNSS frames not aligned, so the pose jumps by metres at the handover.
  • No limit on odometry-only operation, so the robot drives on after losing both sources.

10. Troubleshooting

  1. Log the mode, GNSS status and covariance, and map match score for each crossing.
  2. Check the frame alignment first when the jump is constant at the same place.
  3. Check hysteresis if the mode flips repeatedly near the threshold.

11. Validation

  • The selector prints the lines shown (executed by the site tests).
  • Crossings in both directions at the maximum speed produce pose jumps within the requirement.

12. Engineering checklist

13. Further references

  • REP 105, Coordinate Frames for Mobile Platforms: rep-0105.rst, read 2026-11-05.
  • ROS 2 sensor_msgs/NavSatFix and NavSatStatus (Jazzy): NavSatFix.msg, read 2026-11-05.
  • robot_localization documentation (ros2 branch): state_estimation_nodes.rst, read 2026-11-06.

Related on FixTheRobot