ros2-web2d
v1.7.3
Published
Web-based 2D visualization library for ROS 2 (rosbridge + EaselJS).
Downloads
1,795
Maintainers
Readme
ros2-web2d
A web-based 2D visualization library for ROS 2. Renders occupancy grids, markers, paths, poses, odometry, and laser scans onto an EaselJS stage, driven by roslibjs over rosbridge.
ROS 2 only. ROS 1 (
roscpp/rospy) installations are not supported — the library targets rosbridge v2 topic types (e.g.nav_msgs/msg/OccupancyGrid,geometry_msgs/msg/PoseStamped). Use the original ros2djs if you are on ROS 1.
import { Viewer, OccupancyGridClient } from 'ros2-web2d';
import ROSLIB from 'roslib';
const ros = new ROSLIB.Ros({ url: 'ws://localhost:9090' });
const viewer = new Viewer({ divID: 'map', width: 800, height: 600 });
new OccupancyGridClient({ ros, rootObject: viewer.scene });Features
- TF-aware rendering — every client accepts an optional
tfClientand wraps its output in aSceneNodethat subscribes to the message'sheader.frame_id. Multi-robot deployments with mixed frames (e.g./robot_0/map,/robot_1/odom) render correctly side-by-side. WithouttfClient, clients behave exactly as in 1.2.x. - Map rendering —
OccupancyGridClientfor livenav_msgs/OccupancyGridstreams,ImageMapClientformap_server-stylemap.yaml+.pgm/.png/.svgassets (the YAML and PGM loaders run entirely in the browser). - Navigation overlays —
PathClient,PoseStampedClient,OdometryClient,PoseArrayClient, all share the same TF path and compose cleanly on one viewer. - Markers —
MarkerArrayClienthonors the ADD / MODIFY / DELETE / DELETEALL actions, per-marker lifetimes, and the ten marker primitives that project meaningfully into 2D. - Sensors —
LaserScanClientrenderssensor_msgs/LaserScanas 2D points, with optional sampling and range filters. - Mouse controls — a drop-in
enableViewerMouseControlshelper in the example studio wires left-drag pan, right-drag rotate, and wheel zoom to anyViewer. - Modern build — ES modules, Rollup bundles (CJS / ESM / IIFE), TypeScript declarations, and a vitest suite with 156 tests at the time of writing.
Install
npm install ros2-web2dPeer-installed alongside roslib
^2.x and createjs / easeljs.
ESM
import { Viewer, OccupancyGridClient } from 'ros2-web2d';CommonJS
const { Viewer, OccupancyGridClient } = require('ros2-web2d');Browser IIFE
<script src="https://cdn.jsdelivr.net/npm/easeljs@1/lib/easeljs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/roslib@2"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/ros2d.min.js"></script>
<script>
const viewer = new ROS2D.Viewer({ divID: 'map', width: 640, height: 480 });
</script>The browser global is
ROS2D, kept for historical compatibility with the original ros2djs API surface. ESM / CJS imports use the newros2-web2dspecifier but the class names are unchanged, soROS2D.Viewerandimport { Viewer } from 'ros2-web2d'refer to the same constructor. A namespace rename (e.g.ROS2WEB2D) is deferred to a future major release with a deprecation window.
TF-aware rendering
Every client that used to ignore header.frame_id now accepts an
optional tfClient. On first message for a given frame_id, the
client creates a ROS2D.SceneNode that subscribes to TF, stays
hidden until the first transform arrives, and then composes the
message's pose into the configured fixedFrame.
import { PoseStampedClient, OdometryClient, PathClient } from 'ros2-web2d';
const tfClient = new ROSLIB.ROS2TFClient({
ros,
fixedFrame: 'map',
angularThres: 0.01,
transThres: 0.01,
rate: 10.0,
});
new PoseStampedClient({ ros, topic: '/goal_pose', rootObject: viewer.scene, tfClient });
new OdometryClient ({ ros, topic: '/odom', rootObject: viewer.scene, tfClient });
new PathClient ({ ros, topic: '/plan', rootObject: viewer.scene, tfClient });All four overlays converge into the same fixed frame without the
caller touching coordinate math. SceneNode owns the single Y-negate
on the TF path, so child display objects keep using ROS coordinates.
Multi-robot arrays work the same way — MarkerArrayClient picks up
each marker's own header.frame_id, so a single MarkerArray mixing
/robot_0/odom and /robot_1/odom frames renders each robot at its
own TF position.
Costmap overlays
OccupancyGridClient accepts a colorizer option that controls how
each cell is painted. The 'map' preset is the default grayscale
renderer (free/occupied/unknown). The 'costmap' preset implements an
rviz-style inflation gradient — free and unknown cells are
transparent, inflation goes blue → cyan → yellow, the inscribed band
renders pink, and lethal (100) is red — with per-cell alpha scaling so
a /local_costmap/costmap layer overlays cleanly on a base /map
rendered in a lower rootObject layer.
// Base /map, grayscale
new OccupancyGridClient({ ros, topic: '/map', rootObject: viewer.scene });
// nav2 local costmap on top of it
new OccupancyGridClient({
ros,
topic: '/local_costmap/costmap',
colorizer: 'costmap',
rootObject: viewer.scene,
});Pass a function for full control: colorizer: (value) => [r, g, b, a]
receives the raw cell value (-1 for unknown, 0..100 for cost) and
returns a 0..255 RGBA tuple.
Client reference
| Client | Topic type | Notes |
|--------|------------|-------|
| OccupancyGridClient | nav_msgs/OccupancyGrid | Continuous or one-shot; tfClient wraps the grid in a SceneNode; colorizer: 'costmap' renders nav2 inflation gradients over a base map |
| ImageMapClient | (none) | Loads map.yaml + image asset directly; supports .png / .svg / .pgm |
| MarkerArrayClient | visualization_msgs/MarkerArray | Supports ADD / MODIFY / DELETE / DELETEALL and lifetimes |
| PathClient | nav_msgs/Path | Draws the path through PathShape |
| PoseStampedClient | geometry_msgs/PoseStamped | Default arrow via NavigationArrow; pass shape to override |
| OdometryClient | nav_msgs/Odometry | Same arrow surface as PoseStampedClient; extracts pose.pose |
| PoseArrayClient | geometry_msgs/PoseArray | Rebuilds every message; useful for AMCL particle clouds |
| LaserScanClient | sensor_msgs/LaserScan | 2D hit points with optional sampleStep / maxRange |
| PolygonStampedClient | geometry_msgs/PolygonStamped | Closed outline via PolygonShape; default topic /local_costmap/published_footprint for active nav2 footprints; optional tfClient follows header.frame_id when frame conversion is needed |
Shared options on ROS-driven clients: ros, topic, rootObject,
tfClient. Every client also forwards the standard ROSLIB.Topic
subscribe options when supplied. Only options that the rosbridge
subscribe op actually carries (plus the connection-level
reconnect_on_close) are forwarded; advertise-only options like
queue_size and latch are intentionally omitted because every
client in this library is a subscriber.
| Option | Type | Notes |
|--------|------|-------|
| throttle_rate | number (ms) | Minimum interval between delivered messages |
| queue_length | number | Bridge-side subscriber queue length |
| compression | 'none' / 'cbor' / 'cbor-raw' / 'png' | rosbridge compression scheme |
| reconnect_on_close | boolean | Auto-resubscribe after disconnect |
new MarkerArrayClient({
ros, topic: '/markers', rootObject: viewer.scene,
throttle_rate: 100, // 10 Hz cap
queue_length: 1,
compression: 'cbor',
});Footprint and polygon overlays
PolygonStampedClient subscribes to geometry_msgs/PolygonStamped
and renders each message through PolygonShape, a thin stroked
(optionally filled) closed polygon. The default topic is
/local_costmap/published_footprint because the most common use is
visualizing the active nav2 robot footprint over the costmap. Nav2's
published footprint is already oriented in the message's
header.frame_id (often odom or map); pass tfClient only when
the viewer's fixed frame differs from that message frame.
new ROS2D.PolygonStampedClient({
ros, rootObject: viewer.scene,
topic: '/robot_0/local_costmap/published_footprint',
tfClient: tfClient, // optional frame conversion via header.frame_id
strokeColor: '#ef4444', // default red
strokeSize: 0.03, // ROS meters
fillColor: 'rgba(239,68,68,0.1)', // optional translucent fill
});In multi-robot deployments give each robot its own client (and topic)
with a distinct strokeColor so the footprints stay visually
attributable.
Interactive pose picking
ROS2D.PoseInteractionView is the web equivalent of rviz2's
"2D Goal Pose" tool: the user clicks on the map, drags to indicate a
heading, and on release receives { x, y, yaw } in the ROS world
frame. The view owns its own NavigationArrow preview and handles
the canvas Y-flip / rotation sign conventions internally.
var goalPicker = new ROS2D.PoseInteractionView({
viewer: viewer, // ROS2D.Viewer instance
arrowSize: 1.5, // ROS meters (default 1.5)
arrowFillColor: '#ef4444', // default red
dragThresholdPx: 10, // taps under this commit yaw=undefined
onCommit: function(commit) {
// commit: { x, y, yaw }
// yaw is in radians (CCW from +X) or undefined for taps
publishGoalPose(commit);
},
});
// Toggle on/off without losing the preview shape:
goalPicker.disable();
goalPicker.enable();
// Permanent teardown (removes preview from the scene):
goalPicker.destroy();Pair the view with setInteractionEnabled(false) (or your equivalent)
on Pan/Rotate so the goal-placement drag does not also pan/rotate the
map. The view ignores shift-modified clicks (reserved for pan) and any
non-left mouse button.
Example studio
examples/ ships a Vite + React app that exercises every client
end-to-end against a running rosbridge.
cd examples
npm install
npm run dev # → http://localhost:5173Demos included:
- OccupancyGridClient — live
/mapwith one-shot auto-fit - ImageMapClient — bundled
sample_map.pgm+ YAML, no ROS needed - MarkerArrayClient — TF-aware marker overlays
- LaserScanClient —
/scanwith a toggleable TF client - Navigation Overlays —
path + pose + odom + particlecloudcomposed together
examples/src/lib/ros2dHelpers.js also exports reusable pieces —
enableViewerMouseControls, createInitialMapViewFitter,
fitMapView, addMetricBackdrop — that any app can drop into its
own Viewer code.
Development
npm install
npm run build # prebuild (transpile) + rollup + tsc
npm test # vitest
npm run lint # eslint via gruntSource pipeline
src/ single source of truth (ES5 global-namespace)
↓ grunt transpile
src-esm/ auto-generated ES modules (gitignored — do not edit)
↓ rollup
build/
ros2d.cjs.js CommonJS
ros2d.esm.js ES module
ros2d.min.js IIFE for browser <script>
types/ TypeScript declarations (tsc)Edit files in src/ only. The prebuild hook regenerates
src-esm/ automatically, and npm run check:transpile acts as a
CI guardrail against hand-edited src-esm/ commits.
Scripts
| Script | Description |
|--------|-------------|
| npm run build | Full pipeline: prebuild + rollup + tsc |
| npm test | Run the vitest suite |
| npm run lint | ESLint via grunt |
| npm run transpile | Regenerate src-esm/ with debug output |
| npm run check:transpile | Regenerate + assert no diff |
| npm run doc | Rebuild JSDoc |
See CHANGELOG.md for per-release notes.
Origin
This project started as a fork of
RobotWebTools/ros2djs and
has since diverged into an independent, ROS 2-only library. The
upstream project predates ROS 2 and has been unmaintained since 2022;
ros2-web2d picks up the 2D-visualization role with a rebuilt TF
integration, modern Rollup/ES module pipeline, a Vite + React example
studio, and a test surface spanning 156 vitest cases plus a Playwright
smoke suite. ROS 1 support is intentionally dropped.
License
BSD-3-Clause. Original work © Robert Bosch LLC, Willow Garage Inc., Worcester Polytechnic Institute, and Yujin Robot. See LICENSE for details.
