core, android: add support for camera roll (#3011)

This commit is contained in:
Nathan Olson
2026-01-20 00:57:48 -08:00
committed by GitHub
parent a4cf4d3ffa
commit 10dbd5d68d
57 changed files with 1275 additions and 207 deletions

View File

@@ -1,5 +1,7 @@
#include "camera_position.hpp"
#include "../geometry/lat_lng.hpp"
#include <mbgl/util/constants.hpp>
#include <mbgl/math/angles.hpp>
namespace mbgl {
namespace android {
@@ -8,12 +10,19 @@ jni::Local<jni::Object<CameraPosition>> CameraPosition::New(jni::JNIEnv& env,
mbgl::CameraOptions options,
float pixelRatio) {
static auto& javaClass = jni::Class<CameraPosition>::Singleton(env);
static auto constructor =
javaClass.GetConstructor<jni::Object<LatLng>, double, double, double, jni::Array<jni::jdouble>>(env);
static auto constructor = javaClass.GetConstructor<jni::Object<LatLng>,
double,
double,
double,
double,
double,
double,
jni::Array<jni::jdouble>>(env);
// wrap LatLng values coming from core
auto center = options.center.value();
center.wrap();
double centerAltitude = options.centerAltitude.value_or(0);
// convert bearing, measured in radians counterclockwise from true north.
// Wrapped to [−π rad, π rad). Android binding from 0 to 360 degrees
@@ -27,6 +36,8 @@ jni::Local<jni::Object<CameraPosition>> CameraPosition::New(jni::JNIEnv& env,
// convert tilt, core ranges from [0 rad, 1,0472 rad], android ranges from 0 to 60
double tilt_degrees = options.pitch.value_or(0);
double roll_degrees = options.roll.value_or(0);
double fov_degrees = options.fov.value_or(util::rad2deg(util::DEFAULT_FOV));
std::vector<jdouble> paddingVect;
auto insets = options.padding.value_or(EdgeInsets{0, 0, 0, 0});
@@ -37,8 +48,16 @@ jni::Local<jni::Object<CameraPosition>> CameraPosition::New(jni::JNIEnv& env,
paddingVect.push_back(insets.bottom() * pixelRatio);
padding.SetRegion<std::vector<jni::jdouble>>(env, 0, paddingVect);
return javaClass.New(
env, constructor, LatLng::New(env, center), options.zoom.value_or(0), tilt_degrees, bearing_degrees, padding);
return javaClass.New(env,
constructor,
LatLng::New(env, center),
centerAltitude,
options.zoom.value_or(0),
tilt_degrees,
bearing_degrees,
roll_degrees,
fov_degrees,
padding);
}
mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env,
@@ -50,11 +69,15 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env,
static auto tilt = javaClass.GetField<jni::jdouble>(env, "tilt");
static auto zoom = javaClass.GetField<jni::jdouble>(env, "zoom");
static auto paddingField = javaClass.GetField<jni::Array<jni::jdouble>>(env, "padding");
static auto centerAltitude = javaClass.GetField<jni::jdouble>(env, "centerAltitude");
static auto roll = javaClass.GetField<jni::jdouble>(env, "roll");
static auto fov = javaClass.GetField<jni::jdouble>(env, "fov");
auto padding = position.Get(env, paddingField);
auto center = LatLng::getLatLng(env, position.Get(env, target));
return mbgl::CameraOptions{center,
position.Get(env, centerAltitude),
padding && padding.Length(env) == 4 ? EdgeInsets{padding.Get(env, 1) * pixelRatio,
padding.Get(env, 0) * pixelRatio,
padding.Get(env, 3) * pixelRatio,
@@ -63,7 +86,9 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env,
{},
position.Get(env, zoom),
position.Get(env, bearing),
position.Get(env, tilt)};
position.Get(env, tilt),
position.Get(env, roll),
position.Get(env, fov)};
}
void CameraPosition::registerNative(jni::JNIEnv& env) {

View File

@@ -24,16 +24,21 @@ class CameraPosition
* Constructs a CameraPosition.
*
* @param target The target location to align with the center of the screen.
* @param centerAltitude The altitude of the target location to align with the center of the
* screen, in meters above sea level.
* @param zoom Zoom level at target. See zoom(float) for details of restrictions.
* @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float)
* for details of restrictions.
* @param bearing Direction that the camera is pointing in, in degrees clockwise from north.
* This value will be normalized to be within 0 degrees inclusive and 360 degrees
* exclusive.
* @param roll Roll of the camera, in degrees clockwise.
* @param fov Vertical field of view of the camera, in degrees.
* @param padding Padding in pixels. Specified in left, top, right, bottom order.
* @throws NullPointerException if target is null
* @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive.
*/ @Keep internal constructor(
*/
@Keep internal constructor(
/**
* The location that the camera is pointing at.
@@ -42,6 +47,13 @@ class CameraPosition
@JvmField
val target: LatLng?,
/**
* The altitude of the location that the camera is pointing at, in meters above sea level.
*/
@field:Keep
@JvmField
val centerAltitude: Double,
/**
* Zoom level near the center of the screen. See [Builder.zoom] for the definition of the camera's
* zoom level.
@@ -65,6 +77,20 @@ class CameraPosition
@JvmField
val bearing: Double,
/**
* Roll of the camera, in degrees clockwise.
*/
@field:Keep
@JvmField
val roll: Double,
/**
* Vertical field of view of the camera, in degrees.
*/
@field:Keep
@JvmField
val fov: Double,
/**
* Padding in pixels. Specified in left, top, right, bottom order.
* See [Builder.padding] for the definition of the camera's padding.
@@ -75,6 +101,22 @@ class CameraPosition
) : Parcelable {
/**
* Constructs a CameraPosition.
*
* @param target The target location to align with the center of the screen.
* @param zoom Zoom level at target. See zoom(float) for details of restrictions.
* @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float)
* for details of restrictions.
* @param bearing Direction that the camera is pointing in, in degrees clockwise from north.
* This value will be normalized to be within 0 degrees inclusive and 360 degrees
* exclusive.
* @param padding Padding in pixels. Specified in left, top, right, bottom order.
* @throws NullPointerException if target is null
* @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive.
*/
@Keep internal constructor(target: LatLng?, zoom: Double, tilt: Double, bearing: Double, padding: DoubleArray?) : this(target, 0.0, zoom, tilt, bearing, 0.0, MapLibreConstants.DEFAULT_FOV, padding)
/**
* Constructs a CameraPosition.
*
@@ -112,7 +154,10 @@ class CameraPosition
override fun writeToParcel(out: Parcel, flags: Int) {
out.writeDouble(bearing)
out.writeParcelable(target, flags)
out.writeDouble(centerAltitude)
out.writeDouble(tilt)
out.writeDouble(roll)
out.writeDouble(fov)
out.writeDouble(zoom)
if (padding != null) {
val length = padding.size
@@ -131,7 +176,7 @@ class CameraPosition
* @return A String with CameraPosition information.
*/
override fun toString(): String {
return ("Target: " + target + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt + ", Padding:" + Arrays.toString(padding))
return ("Target: " + target + ", Center Altitude:" + centerAltitude + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt + ", Roll:" + roll + ", FOV:" + fov + ", Padding:" + Arrays.toString(padding))
}
/**
@@ -152,10 +197,16 @@ class CameraPosition
val cameraPosition = other as CameraPosition
if (target != null && target != cameraPosition.target) {
return false
} else if (centerAltitude != cameraPosition.centerAltitude) {
return false
} else if (zoom != cameraPosition.zoom) {
return false
} else if (tilt != cameraPosition.tilt) {
return false
} else if (roll != cameraPosition.roll) {
return false
} else if (fov != cameraPosition.fov) {
return false
} else if (bearing != cameraPosition.bearing) {
return false
} else if (!Arrays.equals(padding, cameraPosition.padding)) {
@@ -178,8 +229,14 @@ class CameraPosition
temp = java.lang.Double.doubleToLongBits(bearing)
result = (temp xor (temp ushr 32)).toInt()
result = 31 * result + (target?.hashCode() ?: 0)
temp = java.lang.Double.doubleToLongBits(centerAltitude)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(tilt)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(roll)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(fov)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(zoom)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
result = 31 * result + Arrays.hashCode(padding)
@@ -192,7 +249,10 @@ class CameraPosition
class Builder {
private var bearing = -1.0
private var target: LatLng? = null
private var centerAltitude = -1.0
private var tilt = -1.0
private var roll = -1.0
private var fov = -1.0
private var zoom = -1.0
private var padding: DoubleArray? = null
@@ -210,7 +270,10 @@ class CameraPosition
if (previous != null) {
bearing = previous.bearing
target = previous.target
centerAltitude = previous.centerAltitude
tilt = previous.tilt
roll = previous.roll
fov = previous.fov
zoom = previous.zoom
padding = previous.padding
}
@@ -227,7 +290,10 @@ class CameraPosition
val lat = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTargetLat, 0.0f).toDouble()
val lng = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTargetLng, 0.0f).toDouble()
target = LatLng(lat, lng)
centerAltitude = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraCenterAltitude, 0.0f).toDouble()
tilt = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTilt, 0.0f).toDouble()
roll = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraRoll, 0.0f).toDouble()
fov = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraFov, 36.86989764584402f).toDouble()
zoom = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraZoom, 0.0f).toDouble()
}
}
@@ -241,7 +307,10 @@ class CameraPosition
if (update != null) {
bearing = update.bearing
target = update.target
centerAltitude = update.centerAltitude
tilt = update.tilt
roll = update.roll
fov = update.fov
zoom = update.zoom
padding = update.padding
}
@@ -287,6 +356,17 @@ class CameraPosition
return this
}
/**
* Set the altitude of the location where the camera is pointing at in meters above sea level
*
* @param centerAltitude altitude of the location where the camera is pointing at
* @return this
*/
fun centerAltitude(centerAltitude: Double): Builder {
this.centerAltitude = centerAltitude
return this
}
/**
* Set the tilt of the camera in degrees
*
@@ -302,6 +382,28 @@ class CameraPosition
return this
}
/**
* Set the roll of the camera in degrees
*
* @param roll Roll value of the camera
* @return this
*/
fun roll(roll: Double): Builder {
this.roll = roll
return this
}
/**
* Set the vertical field of view of the camera in degrees
*
* @param fov Vertical field of view of the camera
* @return this
*/
fun fov(fov: Double): Builder {
this.fov = fov
return this
}
/**
* Set the zoom of the camera
*
@@ -375,7 +477,10 @@ class CameraPosition
} else {
parcel.readParcelable(LatLng::class.java.classLoader)
}
val centerAltitude = parcel.readDouble()
val tilt = parcel.readDouble()
val roll = parcel.readDouble()
val fov = parcel.readDouble()
val zoom = parcel.readDouble()
var padding: DoubleArray? = null
val paddingSize = parcel.readInt()
@@ -385,7 +490,7 @@ class CameraPosition
padding[i] = parcel.readDouble()
}
}
return CameraPosition(target, zoom, tilt, bearing, padding)
return CameraPosition(target, centerAltitude, zoom, tilt, bearing, roll, fov, padding)
}
override fun newArray(size: Int): Array<CameraPosition?> {

View File

@@ -22,7 +22,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun newCameraPosition(cameraPosition: CameraPosition): CameraUpdate {
return CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom, cameraPosition.padding)
return CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.centerAltitude, cameraPosition.tilt, cameraPosition.roll, cameraPosition.fov, cameraPosition.zoom, cameraPosition.padding)
}
/**
@@ -34,7 +34,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun newLatLng(latLng: LatLng): CameraUpdate {
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, null)
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, -1.0, null)
}
/**
@@ -133,7 +133,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun newLatLngZoom(latLng: LatLng, zoom: Double): CameraUpdate {
return CameraPositionUpdate(-1.0, latLng, -1.0, zoom, null)
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, zoom, null)
}
/**
@@ -150,7 +150,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun newLatLngPadding(latLng: LatLng, left: Double, top: Double, right: Double, bottom: Double): CameraUpdate {
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, doubleArrayOf(left, top, right, bottom))
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, -1.0, doubleArrayOf(left, top, right, bottom))
}
/**
@@ -217,7 +217,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun bearingTo(bearing: Double): CameraUpdate {
return CameraPositionUpdate(bearing, null, -1.0, -1.0, null)
return CameraPositionUpdate(bearing, null, -1.0, -1.0, -1.0, -1.0, -1.0, null)
}
/**
@@ -228,7 +228,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun tiltTo(tilt: Double): CameraUpdate {
return CameraPositionUpdate(-1.0, null, tilt, -1.0, null)
return CameraPositionUpdate(-1.0, null, -1.0, tilt, -1.0, -1.0, -1.0, null)
}
/**
@@ -244,7 +244,7 @@ object CameraUpdateFactory {
*/
@JvmStatic
fun paddingTo(padding: DoubleArray?): CameraUpdate {
return CameraPositionUpdate(-1.0, null, -1.0, -1.0, padding)
return CameraPositionUpdate(-1.0, null, -1.0, -1.0, -1.0, -1.0, -1.0, padding)
}
/**
@@ -265,7 +265,7 @@ object CameraUpdateFactory {
//
// CameraUpdate types
//
class CameraPositionUpdate(val bearing: Double, val target: LatLng?, val tilt: Double, val zoom: Double, val padding: DoubleArray?) : CameraUpdate {
class CameraPositionUpdate(val bearing: Double, val target: LatLng?, val centerAltitude: Double, val tilt: Double, val roll: Double, val fov: Double, val zoom: Double, val padding: DoubleArray?) : CameraUpdate {
override fun getCameraPosition(maplibreMap: MapLibreMap): CameraPosition {
if (target == null) {
@@ -289,9 +289,18 @@ object CameraUpdateFactory {
if (java.lang.Double.compare(that.tilt, tilt) != 0) {
return false
}
if (java.lang.Double.compare(that.roll, roll) != 0) {
return false
}
if (java.lang.Double.compare(that.fov, fov) != 0) {
return false
}
if (java.lang.Double.compare(that.zoom, zoom) != 0) {
return false
}
if (java.lang.Double.compare(that.centerAltitude, centerAltitude) != 0) {
return false
}
return if (if (target != null) target != that.target else that.target != null) {
false
} else {
@@ -305,8 +314,14 @@ object CameraUpdateFactory {
temp = java.lang.Double.doubleToLongBits(bearing)
result = (temp xor (temp ushr 32)).toInt()
result = 31 * result + (target?.hashCode() ?: 0)
temp = java.lang.Double.doubleToLongBits(centerAltitude)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(tilt)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(roll)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(fov)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
temp = java.lang.Double.doubleToLongBits(zoom)
result = 31 * result + (temp xor (temp ushr 32)).toInt()
result = 31 * result + Arrays.hashCode(padding)
@@ -314,7 +329,7 @@ object CameraUpdateFactory {
}
override fun toString(): String {
return ("CameraPositionUpdate{" + "bearing=" + bearing + ", target=" + target + ", tilt=" + tilt + ", zoom=" + zoom + ", padding=" + Arrays.toString(padding) + '}')
return ("CameraPositionUpdate{" + "bearing=" + bearing + ", target=" + target + ", centerAltitude=" + centerAltitude + ", tilt=" + tilt + ", roll=" + roll + ", fov=" + fov + ", zoom=" + zoom + ", padding=" + Arrays.toString(padding) + '}')
}
}

View File

@@ -168,6 +168,11 @@ public class MapLibreConstants {
*/
public static final double MINIMUM_DIRECTION = 0;
/**
* The current default vertical field of view (matches mbgl::util::DEFAULT_FOV)
*/
public static final double DEFAULT_FOV = 36.86989764584402;
/**
* The currently used minimum scale factor to clamp to when a quick zoom gesture occurs
*

View File

@@ -21,9 +21,12 @@
<!--Camera-->
<attr name="maplibre_cameraTargetLat" format="float"/>
<attr name="maplibre_cameraTargetLng" format="float"/>
<attr name="maplibre_cameraCenterAltitude" format="float"/>
<attr name="maplibre_cameraZoom" format="float"/>
<attr name="maplibre_cameraBearing" format="float"/>
<attr name="maplibre_cameraTilt" format="float"/>
<attr name="maplibre_cameraRoll" format="float"/>
<attr name="maplibre_cameraFov" format="float"/>
<!--Zoom-->
<attr name="maplibre_cameraZoomMax" format="float"/>

View File

@@ -85,11 +85,12 @@ class CameraPositionTest : BaseTest() {
fun testToString() {
val latLng = LatLng(1.0, 2.0)
val cameraPosition =
CameraPosition(latLng, 3.0, 4.0, 5.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
CameraPosition(latLng, 9.0, 3.0, 4.0, 5.0, 6.0, 7.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
Assert.assertEquals(
"toString should match",
"Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], Zoom:3.0, " +
"Bearing:5.0, Tilt:4.0, Padding:[0.0, 500.0, 0.0, 0.0]",
"Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], " +
"Center Altitude:9.0, Zoom:3.0, Bearing:5.0, Tilt:4.0, Roll:6.0, FOV:7.0, " +
"Padding:[0.0, 500.0, 0.0, 0.0]",
cameraPosition.toString()
)
}
@@ -98,8 +99,8 @@ class CameraPositionTest : BaseTest() {
fun testHashcode() {
val latLng = LatLng(1.0, 2.0)
val cameraPosition =
CameraPosition(latLng, 3.0, 4.0, 5.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
Assert.assertEquals("hashCode should match", -420915327, cameraPosition.hashCode().toLong())
CameraPosition(latLng, 9.0, 3.0, 4.0, 5.0, 6.0, 7.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
Assert.assertEquals("hashCode should match", -1086105727, cameraPosition.hashCode().toLong())
}
@Test