Skip to content

Android: pitch is capped at 60° by three hardcoded clamps; setMaxPitchPreference cannot raise it #4530

Description

@BuenGenio

Summary

On Android the camera pitch cannot exceed 60°, and MapLibreMap.setMaxPitchPreference() cannot lift it. The value is enforced in three separate places, two of which ignore the configured maximum entirely and use a hardcoded literal.

MapLibre GL JS allows up to 85° (maxPitch), so the same style renders a horizon on web and cannot on Android.

Use case

A marine navigation app. Tilting to the horizon is the difference between a chart read from above and the view over the bow — at 60° the horizon is still off-screen, so the "helm view" cannot be built on Android while it works on web from the same style.

Where it is enforced

Decompiled from org.maplibre.gl:android-sdk-opengl:13.2.0 (also present in 13.4.1 and 13.5.1):

1. Transform.setMaxPitch(double) — validates and silently returns:

if (value < 0.0 || value > 60.0) {
    Logger.e("Mbgl-Transform", "Not setting maxPitchPreference, value is in unsupported range: %s", value);
    return;
}
nativeMap.setMaxPitch(value);

This is reachable behaviour: setMaxPitchPreference(85) logs and does nothing. It does not throw, so callers get no signal beyond a log line.

2. MapGestureDetector$ShoveGestureListener — the tilt gesture, which never reads the configured maximum:

pitch = currentPitch - (0.1f * deltaPixels);
pitch = MathUtils.clamp(pitch, 0.0, 60.0);   // literal, not maxPitch
transform.setPitch(pitch);

3. CameraPosition$Builder — clamps programmatic camera positions to 60 as well, so easeTo/animateCamera cannot reach a higher pitch either.

What I verified

To check whether the restriction is in the Java layer or the renderer, I bypassed (1) by reflection and called NativeMap.setMaxPitch(85.0) directly. That succeeded — no exception, nothing logged — but the gesture still clamped at 60, because (2) does not consult the map's maximum. So the renderer appears willing; the Java wrapper is what refuses.

MapLibreConstants.MAXIMUM_PITCH is 60.0f in 13.2.0, 13.4.1 and 13.5.1, and the docs state it: "The default and upper bound for maximum Pitch is 60."

Suggested change

  1. Raise MapLibreConstants.MAXIMUM_PITCH to 85 to match MapLibre GL JS, or make the bound configurable.
  2. Have ShoveGestureListener clamp to transform.getMaxPitch() rather than the literal, so setMaxPitchPreference governs the gesture as its name implies.
  3. Same for CameraPosition.Builder.

(2) is arguably a bug independent of (1): a configured maximum that the gesture ignores is surprising regardless of what the ceiling is.

Environment

  • org.maplibre.gl:android-sdk-opengl 13.2.0 (also checked 13.4.1, 13.5.1)
  • via @maplibre/maplibre-react-native 11.3.6
  • Android 16, Samsung SM-S918B

Happy to open a PR for (2) and (3) if that direction is welcome.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions