visgl / visgl/deck.gl

[RFC] Add camera roll to deck.gl geospatial view states and MapLibre synchronization

Open
#10,503 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

feature rfc
Dominant language
TypeScript
Stars
14.6k
Forks
2.3k
Avg merge
2d 9h
Merged PRs (30d)
42

Description

RFC: Add camera roll to deck.gl geospatial view states and MapLibre synchronization

Status

Proposed

Summary

deck.gl geospatial view states should support camera roll so that MapView and GlobeView can match a rolled MapLibre camera.

MapLibre v5 introduced public roll accessors and v6 includes roll movement events. MapboxOverlay currently synchronizes longitude, latitude, zoom, bearing, and pitch, but deck.gl has no roll field in its geospatial view states and no corresponding roll transform in its viewport math.

The first milestone is read-only synchronization: MapLibre remains the camera source of truth and deck.gl follows its roll. Interactive roll controls may be added later.

Goals

  • Add roll?: number to deck.gl's MapViewState and GlobeViewState.
  • Apply roll consistently in Web Mercator and globe view matrices.
  • Preserve roll through view-state normalization, transitions, and controller updates.
  • Keep projection, unprojection, picking, and shader coordinate systems aligned.
  • Synchronize MapLibre v5+ through map.getRoll().
  • Preserve behavior when roll is omitted or zero.

Non-goals

  • Adding a new roll gesture or widget in the first milestone.
  • Synchronizing arbitrary free-camera position or orientation.
  • Adding roll support to base-map engines that do not expose it.
  • Folding roll into the MapLibre v4.5.1/v5/v6 compatibility work in #10501.

Public API Proposal

Extend both geospatial view states:

export type MapViewState = {
  longitude: number;
  latitude: number;
  zoom: number;
  bearing?: number;
  pitch?: number;
  roll?: number;
  // Existing fields
};

export type GlobeViewState = {
  longitude: number;
  latitude: number;
  zoom: number;
  roll?: number;
  // Existing fields
};

Roll is measured in degrees and follows MapLibre's sign and orientation convention. Zero preserves the current matrices exactly.

Core Viewport Behavior

  • Apply roll as a rotation around the camera's forward/view axis.
  • Apply it after resolving bearing and pitch so the operation represents camera roll rather than a change in map bearing.
  • Use the same convention in WebMercatorViewport and GlobeViewport.
  • Include roll in viewport equality and change detection.
  • Ensure view-state transitions interpolate roll using the same angular wrapping rules used for bearing.
  • Ensure controllers that do not manipulate roll preserve the current value instead of resetting it.

The implementation should document matrix order with focused fixtures because bearing, pitch, and roll rotations are not commutative.

MapLibre Integration

Add an optional getter to the loose internal base-map interface:

getRoll?(): number;

When available, MapboxOverlay copies map.getRoll() into the synchronized deck.gl view state. MapLibre is the source of truth; deck.gl controllers remain disabled for the synchronized base-map view.

MapLibre versions without getRoll() use zero and retain current behavior.

Test Plan

Core tests:

  • Zero or omitted roll produces byte-for-byte equivalent matrices.
  • Positive and negative roll rotate screen axes in the documented direction.
  • Combined bearing, pitch, and roll use the specified matrix order.
  • Project/unproject round trips remain stable.
  • Picking coordinates remain aligned.
  • View-state transitions take the shortest angular path.
  • Map and globe controllers preserve an existing roll value.

MapLibre integration tests:

  • Synchronize getRoll() in overlaid and interleaved modes.
  • Match pitched and rolled Mercator scenes.
  • Match pitched and rolled globe scenes.
  • Preserve terrain depth alignment while rolled.
  • Respond to runtime roll changes and resize events.

Acceptance criteria:

  • Deck geometry and MapLibre geometry remain visually coincident at nonzero roll.
  • Picking remains aligned with rendered objects.
  • Existing applications observe no change when roll is absent.

Related Work

  • #10501
  • MapLibre camera roll documentation and getRoll() API
  • visgl/react-map-gl#2598

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing MapViewState and GlobeViewState through WebMercatorViewport, GlobeViewport, transitions, and controller updates, then inspect MapboxOverlay's MapLibre synchronization. Use the listed core and integration tests as the validation plan, including zero-roll compatibility, matrix order, projection and picking alignment, angular interpolation, and getRoll() synchronization. Done means rolled deck.gl and MapLibre geometry remain coincident without changing behavior when roll is absent.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.