Skip to content

Chapter 2: LogViewer#

Welcome back to the streetscape.gl tutorial! In Chapter 1, we learned about the XVIZ Loader Interface, which is the standard way streetscape.gl components get access to your autonomous vehicle (AV) data, whether it's from files, a stream, or a live feed. We saw how a loader instance like XVIZFileLoader or XVIZStreamLoader loads the data and makes it available through a consistent set of methods and events.

Now that we have the data flowing from a loader, how do we actually see it? That's where the LogViewer component comes in.

Think of the LogViewer as the main display screen or canvas in your streetscape.gl application. Its primary job is to take the data provided by an XVIZ Loader and visualize it as a dynamic 3D scene. This is where you'll see:

  • The vehicle itself
  • The surrounding environment (map, buildings if available)
  • All the AV data like sensor readings (lidar points, camera feeds), detected objects (cars, pedestrians), planning trajectories, and more, represented as 3D objects like points, lines, and polygons.

The LogViewer is a central piece of the streetscape.gl puzzle because it's the bridge between your raw XVIZ data and a visually understandable 3D representation.

What is LogViewer?#

At its core, LogViewer is a React component. If you're building a web application using the React library, you'll use <LogViewer /> like any other component in your application's user interface.

Its most important job is to display the 3D visualization based on the current frame of data from an XVIZ log.

Using the LogViewer#

The LogViewer needs one crucial piece of information to do its job: an instance of an object that implements the XVIZ Loader Interface. This is how it gets the data it needs to render.

Here's the most basic way to use LogViewer in your React application, assuming you've already created a logLoader instance as we did in Chapter 1:

import React from 'react';
import { LogViewer } from 'streetscape.gl';
import { XVIZFileLoader } from 'streetscape.gl'; // Or XVIZStreamLoader, etc.

// Assume 'logLoader' is already created and connected (as in Chapter 1)
// const logLoader = new XVIZFileLoader({...});
// logLoader.connect();

function MyLogDisplay({ logLoader }) {
  return (
    <div style={{ width: '800px', height: '600px' }}>
      <LogViewer log={logLoader} />
    </div>
  );
}

In this simple example:

  1. We import the LogViewer component.
  2. We render <LogViewer />.
  3. We pass our logLoader instance to the log prop. This tells the LogViewer where to get the XVIZ data.
  4. We wrap it in a div with a size, because the LogViewer needs dimensions to render correctly.

Once the logLoader starts providing data (after connect() is called and the first update event fires), the LogViewer will automatically:

  • Get the metadata from the loader to understand the coordinate systems and stream definitions.
  • Get the current frame data (timeslices) from the loader as it becomes available or as the playback time changes.
  • Render the 3D scene based on that data.

Customizing the View#

The basic <LogViewer log={logLoader} /> will render the vehicle (a default gray box) and any 3D primitives (points, lines, polygons, etc.) found in the XVIZ streams that are configured to be visible by default. However, you'll likely want to customize its appearance and behavior.

Here are some common and important props you can use:

  • log: (Required) The instance of your XVIZ Loader. We've already seen this.
  • mapboxApiAccessToken: (Optional) If your log data is geo-referenced (has longitude/latitude information), you can provide a Mapbox token to display a base map underneath the 3D scene.
  • mapStyle: (Optional) Customize the appearance of the base map using a Mapbox style object or URL.
  • car: (Optional) Customize the appearance of the vehicle in the scene. You can provide a mesh, scale, color, or texture. streetscape.gl provides utilities like CarMesh to easily define simple car models.
  • viewMode: (Optional) Controls the camera perspective. streetscape.gl provides standard view modes like VIEW_MODE.TOP_DOWN, VIEW_MODE.PERSPECTIVE, and VIEW_MODE.DRIVER. This is an important prop for changing how the user sees the scene.
  • xvizStyles: (Optional) Override the default styling of XVIZ primitives, or styles defined in the log metadata.
  • streamFilter: (Optional) Control which specific data streams from the log are rendered in the 3D view. You can filter by stream name.
  • showTooltip: (Optional, boolean) Enable a tooltip that shows information when you hover over a rendered object in the 3D view.
  • onSelectObject: (Optional, function) A callback function that is triggered when the user clicks on a 3D object in the scene. This is useful for showing detailed information about a selected object.
  • viewState, viewOffset, objectStates: (Advanced, Optional) These props allow you to control the camera position/zoom and object selection state externally, typically when integrating with a state management library like Redux.

Here's an example demonstrating a few more common props:

import React from 'react';
import { LogViewer, VIEW_MODE, CarMesh } from 'streetscape.gl';
import { XVIZFileLoader } from 'streetscape.gl'; // Or XVIZStreamLoader, etc.

// Assume 'logLoader' is already created and connected
// const logLoader = new XVIZFileLoader({...});
// logLoader.connect();

const MAPBOX_TOKEN = 'YOUR_MAPBOX_ACCESS_TOKEN';
const MY_CAR = CarMesh.SEDAN; // Use a built-in car mesh

function MyEnhancedLogDisplay({ logLoader }) {
  return (
    <div style={{ width: '100%', height: '80vh' }}> {/* Use relative sizing */}
      <LogViewer
        log={logLoader}
        mapboxApiAccessToken={MAPBOX_TOKEN}
        mapStyle="mapbox://styles/mapbox/light-v10" // Example Mapbox style
        car={MY_CAR}
        viewMode={VIEW_MODE.PERSPECTIVE} // Use the standard perspective view
        showTooltip={true} // Enable tooltips on hover
        streamFilter={['/perception/object_detections', '/lidar/points']} // Only show these streams
        onSelectObject={(info, event) => {
          console.log('Object clicked:', info.object.id, info);
          // Return true if you handled the selection and don't want default behavior
          // return true;
        }}
      />
    </div>
  );
}

In this example, we've added:

  • A Mapbox token and style to show a base map.
  • A specific car mesh for the vehicle.
  • Set the viewMode to PERSPECTIVE (allowing the user to pan/zoom/rotate).
  • Enabled object tooltips and added a basic onSelectObject callback.
  • Filtered the displayed data streams to just object detections and lidar points.

The LogViewer takes care of using the data from the log prop and these configuration props to render the interactive 3D scene.

Under the Hood (A Simple Look)#

How does the LogViewer component work internally to turn XVIZ data into a 3D scene?

  1. Receiving Data: The LogViewer component (specifically, a wrapper component provided by streetscape.gl called connectToLog which we'll cover in Chapter 5) is connected to the XVIZLoader instance you provide via the log prop. This connection allows the LogViewer to automatically receive updates whenever the loader emits an update event (meaning new data is available) or when the log's current time changes (e.g., after a seek operation).
  2. Getting Current Frame & Metadata: When it receives an update or when its props change, the LogViewer calls methods on the log instance like getCurrentFrame(), getMetadata(), and getStreamsMetadata().
  3. Configuring 3D Renderer: LogViewer doesn't draw things itself. It uses a powerful 3D rendering library called deck.gl internally. It takes the frame data, the metadata, and your configuration props (like viewMode, xvizStyles, streamFilter, car) and translates them into a format that deck.gl can understand to create 3D layers.
  4. Rendering Layers: It typically creates an internal deck.gl layer for the vehicle (SimpleMeshLayer) and one or more XVIZLayer instances (we'll dive into XVIZLayer in Chapter 6) to render the XVIZ primitives (points, lines, polygons, etc.) from the current frame. It also uses react-map-gl to potentially render the base map.
  5. Handling Interaction: LogViewer also manages the camera controls based on the viewMode and handles user interaction like hovering and clicking on objects, translating those interactions into callbacks like onHover and onClick/onSelectObject. It also renders overlays for object labels and tooltips using other internal components.

Here's a simplified view of the data flow:

sequenceDiagram
    participant App as Your App
    participant Loader as XVIZ Loader
    participant LogViewer as LogViewer Component
    participant DeckGL as deck.gl (Internal)

    App->>Loader: Create and connect loader
    Loader->>App: emit('ready', metadata)
    Loader->>App: emit('update', timeslice)

    App->>LogViewer: Render <LogViewer log={loader} ... />
    LogViewer->>Loader: getCurrentFrame()
    LogViewer->>Loader: getMetadata()

    loop On update/seek/prop change
        LogViewer->>Loader: getCurrentFrame() (gets new data)
        LogViewer->>LogViewer: Process data & props
        LogViewer->>DeckGL: Configure 3D scene & layers
        DeckGL->>LogViewer: Render 3D output
        LogViewer->>App: Display 3D scene
    end

    App->>LogViewer: (User interaction)
    LogViewer->>App: Trigger onHover/onClick callbacks
Note: The connectToLog wrapping that automatically listens for loader events is simplified in this diagram.

Let's peek at a highly simplified code idea (not the actual full LogViewer source) to illustrate how it might get data and configure layers internally:

// Inside a simplified LogViewer component (conceptually)
import React, { PureComponent } from 'react';
import DeckGL from '@deck.gl/react';
import { XVIZLayer } from 'streetscape.gl'; // Conceptually uses this

class SimpleLogViewer extends PureComponent {
  // ... state and other methods ...

  render() {
    const { log, viewState, viewMode, ...otherProps } = this.props;
    const currentFrame = log.getCurrentFrame(); // Get current frame data
    const metadata = log.getMetadata();     // Get metadata

    if (!currentFrame || !metadata) {
      return <div>Loading data...</div>; // Or some loading indicator
    }

    // Conceptually build DeckGL layers based on data and props
    const layers = [
      // Example: Render the car layer (simplified)
      // new SimpleMeshLayer({...}),

      // Example: Render XVIZ streams using XVIZLayer (simplified)
      new XVIZLayer({
        id: 'xviz-data',
        frame: currentFrame,
        metadata: metadata,
        // Pass down streamFilter, xvizStyles, etc.
        streamFilter: this.props.streamFilter,
        xvizStyles: this.props.xvizStyles,
        // ... coordinate system props derived from frame/metadata ...
        // ... object interaction props ...
      }),

      // ... potentially other custom layers or map layer ...
    ];

    // Conceptually determine view states based on viewMode, frame, etc.
    // const viewStates = getViewStates({...});

    return (
      <DeckGL
        viewState={viewState} // Controlled internally or externally
        layers={layers}
        // ... other DeckGL props for interaction, effects, etc. ...
      >
        {/* Potentially map layer and other overlays */}
      </DeckGL>
    );
  }
}
This conceptual code shows that LogViewer primarily fetches data (currentFrame, metadata) from the log prop and then uses that data to construct the necessary 3D layers (like XVIZLayer) that deck.gl will render. The configuration props you pass to <LogViewer /> are used to customize how these internal layers are created and styled.

Conclusion#

The LogViewer component is the core visualization engine in streetscape.gl. It takes the stream of processed XVIZ data from an XVIZ Loader instance and renders it as an interactive 3D scene, showing the vehicle, map, and all the rich AV data. By providing configuration props like viewMode, car, and streamFilter, you can customize exactly what and how you see the log data.

However, just seeing the static current frame isn't enough. You need controls to play, pause, and seek through the log. In the next chapter, we'll introduce the PlaybackControl component, which complements the LogViewer by providing these essential log playback capabilities, also by interacting with the same XVIZ Loader Interface instance.

PlaybackControl


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/docs/api-reference/log-viewer.md), 2(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/docs/get-started/starter-kit.md), 3(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/log-viewer/constants.js), 4(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/log-viewer/core-3d-viewer.js), 5(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/log-viewer/hover-tooltip.js), 6(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/log-viewer/index.js), 7(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/log-viewer/object-labels-overlay.js), 8(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/log-viewer/perspective-popup.js), 9(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/constants.js), 10(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/index.js)