Skip to content

Chapter 2: Map Visualization#

Welcome back to the loaders.gl-showcases tutorial! In our previous chapter, we learned about User Interface Components – the building blocks for the application's controls and panels. Now, let's shift our focus to the star of the show: the actual Map Visualization.

What is Map Visualization?#

Imagine you have a beautiful 3D model of a building or a whole city, and you want to see it placed correctly on Earth. You need a way to display this 3D data in its geographical context. That's where Map Visualization comes in.

As the concept description puts it:

This is the core part that displays the 3D geographical data. The application uses either Deck.gl or ArcGIS libraries to render the scenes. These components act like the stage where the 3D models and geographical layers are shown. They handle how you see the world, allowing you to pan, zoom, and rotate the camera to view the data from different angles.

Think of the map visualization as the main window or screen in the application where all the cool 3D geographical stuff appears. It's the "stage" where your 3D models, terrain, and other data layers are placed and rendered so you can see them.

The Central Use Case: Displaying a 3D Tileset#

Let's consider a core task this application handles: taking a large 3D dataset, like a city modeled in 3D (often called a "tileset"), and showing it on a map. The user needs to be able to fly around this 3D city, zoom in on specific buildings, pan across blocks, and tilt the view to see the buildings from different angles.

This requires a powerful rendering engine capable of handling large amounts of 3D geometry and positioning it accurately on a global map projection.

The Stage: Deck.gl or ArcGIS#

The loaders.gl-showcases application uses one of two powerful libraries to create this 3D map stage:

  1. Deck.gl: An open-source data visualization framework for the web, designed for exploring large geospatial datasets.
  2. ArcGIS Maps SDK for JavaScript: Esri's library for building web applications with 2D and 3D maps, integrating with the ArcGIS platform.

These libraries are like the sophisticated graphics engines in video games, but tailored for geographical data. They handle complex tasks like:

  • Drawing the base map (streets, satellite imagery).
  • Placing 3D models (like buildings) in their correct geographical locations.
  • Rendering different "layers" of data on top of each other.
  • Handling user interactions like panning, zooming, and rotating the view (controlling the "camera").

In the loaders.gl-showcases application, you'll see components that wrap these libraries to make them easier to use within the application's structure. The primary components for this are DeckGlWrapper (src/components/deck-gl-wrapper/deck-gl-wrapper.tsx) and ArcgisWrapper (src/components/arcgis-wrapper/arcgis-wrapper.tsx). The application logic decides which one to use based on user selection or configuration.

How It Works: Layers and View State#

To display data on the map and control how you see it, the map visualization components rely on two main concepts:

  1. Layers: These represent the actual data you want to show. Think of them as transparent sheets that you stack on top of the base map. One layer might be the 3D buildings, another might be terrain elevation data, another might show debugging information like bounding boxes. The map component takes a list of these layers and renders them together.
  2. View State: This describes the position, orientation, and zoom level of the "camera" viewing the map. It tells the map library what part of the world you're looking at and from where. Panning, zooming, and rotating the map changes the view state.

Let's look at a simplified idea of how the DeckGlWrapper might be used:

// Simplified example, not showing all complexity
import React from 'react';
import { DeckGlWrapper } from '../components/deck-gl-wrapper/deck-gl-wrapper'; // The map stage component
import { useAppSelector, useAppDispatch } from '../../redux/hooks'; // To get state
import { selectMapLayers } from '../../redux/slices/layers-slice'; // Example: Get layers from state
import { selectViewState, setViewState } from '../../redux/slices/view-state-slice'; // Example: Get/set view state

function MainApplicationView() {
  const dispatch = useAppDispatch();
  // Get the list of layers to display from our application's state (more on this in Chapter 3 & 4)
  const layersToShow = useAppSelector(selectMapLayers);
  // Get the current camera position/zoom from state
  const currentViewState = useAppSelector(selectViewState);

  // Function that is called when the user interacts with the map (pans, zooms, etc.)
  const handleViewStateChange = ({ viewState }) => {
    // Update the view state in our application's state
    dispatch(setViewState({ main: viewState })); // Store the new camera position
  };

  return (
    <div style={{ width: '100%', height: '100%' }}>
      {/* Here's our Map Visualization component */}
      <DeckGlWrapper
        layers3d={layersToShow} // Pass the data layers to the map
        viewState={currentViewState.main} // Pass the current camera position
        onViewStateChange={handleViewStateChange} // Tell the map what to do when the camera moves
        // ... other necessary props like loadedTilesets, etc. (simplified here)
        loadedTilesets={[]} // Required prop, simplified
        onTilesetLoad={() => {}} // Required prop, simplified
        lastLayerSelectedId="" // Required prop, simplified
      />
    </div>
  );
}

In this simplified code:

  1. We import the DeckGlWrapper component.
  2. We get the list of layersToShow (the data layers) and the currentViewState (camera position) from the application's central Global State (we'll cover this in Chapter 4: Global State Management).
  3. We define handleViewStateChange. This function is given to the DeckGlWrapper component. Whenever the user interacts with the map (panning, zooming, etc.), the wrapper calculates the new camera position and calls handleViewStateChange with that information. Inside this function, we update the currentViewState stored in our application's state.
  4. We render the <DeckGlWrapper> component. We pass it the layersToShow so it knows what data to draw, and the currentViewState.main so it knows where the camera is looking. We also tell it to call handleViewStateChange whenever the view state changes.

This shows the fundamental cycle: the application provides data (layers) and camera position (viewState) to the map component, and the map component tells the application when the camera position changes (onViewStateChange).

The ArcgisWrapper works on a very similar principle, although the specific properties and setup might differ slightly due to the different library. It also takes layers and view state information to render the scene. You can see its implementation in src/components/arcgis-wrapper/arcgis-wrapper.tsx. Notice how it also has a map.deck.set({ layers }) call and uses the onViewStateChangeHandler.

Under the Hood: The Rendering Cycle#

Let's visualize the basic flow when you interact with the map, focusing on the Deck.gl case as an example:

sequenceDiagram
    Participant User
    Participant Browser
    Participant DeckGLWrapper
    Participant GlobalState

    User->Browser: Pans the map
    Browser->DeckGLWrapper: Triggers map interaction event
    DeckGLWrapper->DeckGLWrapper: Calculates new view state (camera position/zoom)
    DeckGLWrapper->GlobalState: Calls 'onViewStateChange' prop (e.g., handleViewStateChange) with new view state
    GlobalState->GlobalState: Updates the stored view state (using setViewState)
    GlobalState-->DeckGLWrapper: Application re-renders because state changed, passes *new* viewState prop
    DeckGLWrapper->Browser: Instructs Deck.gl to render the scene using the *new* view state and current layers
    Browser->User: Displays the updated map view

This sequence shows that the map component doesn't just handle the rendering; it also communicates back to the rest of the application when the user changes the view. This allows the application to keep track of where the user is looking, which can be useful for many features.

The actual process of taking the layersToShow data and turning it into visual elements that Deck.gl or ArcGIS can draw happens within functions like renderLayers (src/utils/deckgl/render-layers.ts). This function looks at the type of data in each layer (e.g., I3S, 3D Tiles, Terrain) and creates the appropriate type of layer object for Deck.gl or ArcGIS to understand.

For example, a layer describing a 3D city model (type TilesetType.I3S) would be processed by renderI3SLayer within renderLayers, which creates a DataDrivenTile3DLayer object (a specific type of layer that Deck.gl understands). This layer object contains information about the data source URL, how it should be rendered (e.g., using Draco compression, compressed textures), and interactive properties (like what to do when clicked).

// Simplified snippet from render-layers.ts
import { DataDrivenTile3DLayer } from "@deck.gl-community/layers"; // The Deck.gl layer type
import { I3SLoader } from "@loaders.gl/i3s"; // Loader for I3S data

const renderI3SLayer = (layer, ...) => {
  // ... logic to prepare loadOptions and URL ...

  // Create and return the Deck.gl layer object
  return new DataDrivenTile3DLayer({
    id: `tile-layer-${layer.id}`, // Unique ID for the layer
    data: url, // Where to fetch the data from
    loader: I3SLoader, // How to load the data
    pickable: true, // Make this layer interactive
    onClick, // Function to call when clicked
    onTilesetLoad: onTilesetLoadHandler, // Function for when the dataset loads
    // ... other properties ...
  });
};

export const renderLayers = (params) => {
  const { layers3d, ... } = params;
  const deckGlLayers = layers3d.map((layer) => {
    switch (layer.type) {
      case TilesetType.I3S:
        return renderI3SLayer(layer, ...); // Create an I3S layer for Deck.gl
      // ... cases for other layer types (Cesium Ion, 3D Tiles, Terrain) ...
      default:
        return null; // Or handle unsupported types
    }
  });

  // Add other visualization layers like bounding volumes or debug layers
  // deckGlLayers.push(renderBoundingVolumeLayer(...));
  // deckGlLayers.push(renderNormals(...));
  // deckGlLayers.push(renderFrustum(...));

  return deckGlLayers; // Return the list of layers for the map wrapper
};

This snippet shows how renderLayers acts as a factory, taking the application's description of what data layers are needed and producing the specific layer objects that Deck.gl or ArcGIS require. These layer objects are then passed to the main map wrapper component (DeckGlWrapper or ArcgisWrapper) which feeds them to the underlying rendering library.

Helper hooks like useDeckGl (src/hooks/use-deckgl-hook/use-deckgl-hook.ts) and useArcgis (src/hooks/use-arcgis-hook/use-arcgis-hook.ts) manage a lot of the complexity, interacting with the state management (Chapter 4) to get the current configuration (like base map style, debug options, etc.) and prepare the necessary properties for the map wrapper components. They also handle library-specific initialization and event binding.

Conclusion#

In this chapter, we explored the heart of the loaders.gl-showcases application: the Map Visualization. We learned that it acts as the "stage" for displaying 3D geographical data, powered by libraries like Deck.gl or ArcGIS. We saw that displaying data involves defining Layers and controlling the view involves managing the View State (camera position). The application communicates with the map component by providing layers and view state, and the map component communicates back when the user changes the view.

Now that we understand where the data is displayed, let's look closer at Layers – how they are managed and controlled within the application.

Chapter 3: Layer Management


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/arcgis-wrapper/arcgis-wrapper.tsx), 2(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/deck-gl-wrapper/deck-gl-wrapper.tsx), 3(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/hooks/use-arcgis-hook/use-arcgis-hook.ts), 4(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/hooks/use-deckgl-hook/use-deckgl-hook.ts), 5(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/utils/deckgl/render-layers.ts)