Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public class LocationComponentOptions implements Parcelable {
private RectF trackingMultiFingerProtectedMoveArea;
private String layerAbove;
private String layerBelow;
private boolean bearingOnTop;
private float trackingAnimationDurationMultiplier;
private boolean compassAnimationEnabled;
private boolean accuracyAnimationEnabled;
Expand Down Expand Up @@ -177,6 +178,7 @@ public LocationComponentOptions(
RectF trackingMultiFingerProtectedMoveArea,
String layerAbove,
String layerBelow,
boolean bearingOnTop,
float trackingAnimationDurationMultiplier,
boolean compassAnimationEnabled,
boolean accuracyAnimationEnabled,
Expand Down Expand Up @@ -221,6 +223,7 @@ public LocationComponentOptions(
this.trackingMultiFingerProtectedMoveArea = trackingMultiFingerProtectedMoveArea;
this.layerAbove = layerAbove;
this.layerBelow = layerBelow;
this.bearingOnTop = bearingOnTop;
this.trackingAnimationDurationMultiplier = trackingAnimationDurationMultiplier;
this.compassAnimationEnabled = compassAnimationEnabled;
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
Expand Down Expand Up @@ -327,6 +330,9 @@ public static LocationComponentOptions createFromAttributes(@NonNull Context con
builder.layerBelow(
typedArray.getString(R.styleable.maplibre_LocationComponent_maplibre_layer_below));

builder.bearingOnTop(
typedArray.getBoolean(R.styleable.maplibre_LocationComponent_maplibre_bearing_on_top, true));

float minScale = typedArray.getFloat(
R.styleable.maplibre_LocationComponent_maplibre_minZoomIconScale, MIN_ZOOM_ICON_SCALE_DEFAULT);
float maxScale = typedArray.getFloat(
Expand Down Expand Up @@ -798,6 +804,15 @@ public String layerBelow() {
return layerBelow;
}

/**
* Whether the bearing icon is rendered on top of the foreground icon in the location layer stack.
*
* @return true if the bearing icon is rendered above the foreground icon, false otherwise.
*/
public boolean bearingOnTop() {
return bearingOnTop;
}

/**
* Get the tracking animation duration multiplier.
*
Expand Down Expand Up @@ -927,6 +942,7 @@ public String toString() {
+ "trackingMultiFingerProtectedMoveArea=" + trackingMultiFingerProtectedMoveArea + ", "
+ "layerAbove=" + layerAbove
+ "layerBelow=" + layerBelow
+ "bearingOnTop=" + bearingOnTop
+ "trackingAnimationDurationMultiplier=" + trackingAnimationDurationMultiplier
+ "pulseEnabled=" + pulseEnabled
+ "pulseFadeEnabled=" + pulseFadeEnabled
Expand Down Expand Up @@ -1057,6 +1073,10 @@ public boolean equals(Object o) {
return false;
}

if (bearingOnTop != options.bearingOnTop) {
return false;
}

if (pulseEnabled != options.pulseEnabled) {
return false;
}
Expand Down Expand Up @@ -1120,6 +1140,7 @@ public int hashCode() {
? trackingMultiFingerProtectedMoveArea.hashCode() : 0);
result = 31 * result + (layerAbove != null ? layerAbove.hashCode() : 0);
result = 31 * result + (layerBelow != null ? layerBelow.hashCode() : 0);
result = 31 * result + (bearingOnTop ? 1 : 0);
result = 31 * result + (trackingAnimationDurationMultiplier != +0.0f
? Float.floatToIntBits(trackingAnimationDurationMultiplier) : 0);
result = 31 * result + (compassAnimationEnabled ? 1 : 0);
Expand Down Expand Up @@ -1171,6 +1192,7 @@ public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(this.trackingMultiFingerProtectedMoveArea, flags);
dest.writeString(this.layerAbove);
dest.writeString(this.layerBelow);
dest.writeByte(this.bearingOnTop ? (byte) 1 : (byte) 0);
dest.writeFloat(this.trackingAnimationDurationMultiplier);
dest.writeByte(this.compassAnimationEnabled ? (byte) 1 : (byte) 0);
dest.writeByte(this.accuracyAnimationEnabled ? (byte) 1 : (byte) 0);
Expand Down Expand Up @@ -1214,6 +1236,7 @@ protected LocationComponentOptions(Parcel in) {
this.trackingMultiFingerProtectedMoveArea = in.readParcelable(RectF.class.getClassLoader());
this.layerAbove = in.readString();
this.layerBelow = in.readString();
this.bearingOnTop = in.readByte() != 0;
this.trackingAnimationDurationMultiplier = in.readFloat();
this.compassAnimationEnabled = in.readByte() != 0;
this.accuracyAnimationEnabled = in.readByte() != 0;
Expand Down Expand Up @@ -1338,6 +1361,7 @@ public LocationComponentOptions build() {
private RectF trackingMultiFingerProtectedMoveArea;
private String layerAbove;
private String layerBelow;
private boolean bearingOnTop;
private Float trackingAnimationDurationMultiplier;
private Boolean compassAnimationEnabled;
private Boolean accuracyAnimationEnabled;
Expand Down Expand Up @@ -1385,6 +1409,7 @@ private Builder(LocationComponentOptions source) {
this.trackingMultiFingerProtectedMoveArea = source.trackingMultiFingerProtectedMoveArea();
this.layerAbove = source.layerAbove();
this.layerBelow = source.layerBelow();
this.bearingOnTop = source.bearingOnTop();
this.trackingAnimationDurationMultiplier = source.trackingAnimationDurationMultiplier();
this.compassAnimationEnabled = source.compassAnimationEnabled();
this.accuracyAnimationEnabled = source.accuracyAnimationEnabled();
Expand Down Expand Up @@ -1866,6 +1891,19 @@ public LocationComponentOptions.Builder layerBelow(String layerBelow) {
return this;
}

/**
* Sets whether the bearing icon is rendered on top of the foreground icon in the location
* layer stack. When true, the bearing layer is placed above the foreground layer; when false,
* it is rendered below.
*
* @param bearingOnTop true to render the bearing icon above the foreground icon, false for below.
*/
@NonNull
public LocationComponentOptions.Builder bearingOnTop(boolean bearingOnTop) {
this.bearingOnTop = bearingOnTop;
return this;
}

/**
* Sets the tracking animation duration multiplier.
*
Expand Down Expand Up @@ -2065,6 +2103,7 @@ LocationComponentOptions autoBuild() {
this.trackingMultiFingerProtectedMoveArea,
this.layerAbove,
this.layerBelow,
this.bearingOnTop,
this.trackingAnimationDurationMultiplier,
this.compassAnimationEnabled,
this.accuracyAnimationEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,28 @@ class LocationComponentPositionManager {
@Nullable
private String layerBelow;

LocationComponentPositionManager(@NonNull Style style, @Nullable String layerAbove, @Nullable String layerBelow) {
public boolean bearingOnTop;

LocationComponentPositionManager(@NonNull Style style, @Nullable String layerAbove,
@Nullable String layerBelow, Boolean bearingOnTop) {
this.style = style;
this.layerAbove = layerAbove;
this.layerBelow = layerBelow;
this.bearingOnTop = bearingOnTop;
}

/**
* Returns true whenever layer above/below configuration has changed and requires re-layout.
*/
boolean update(@Nullable String layerAbove, @Nullable String layerBelow) {
boolean update(@Nullable String layerAbove, @Nullable String layerBelow, boolean bearingOnTop) {
boolean requiresUpdate =
!(this.layerAbove == layerAbove || (this.layerAbove != null && this.layerAbove.equals(layerAbove)))
|| !(this.layerBelow == layerBelow || (this.layerBelow != null && this.layerBelow.equals(layerBelow)));
|| !(this.layerBelow == layerBelow || (this.layerBelow != null && this.layerBelow.equals(layerBelow)))
|| (this.bearingOnTop != bearingOnTop);

this.layerAbove = layerAbove;
this.layerBelow = layerBelow;
this.bearingOnTop = bearingOnTop;
return requiresUpdate;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ final class LocationLayerController {
}

void initializeComponents(Style style, LocationComponentOptions options) {
this.positionManager = new LocationComponentPositionManager(style, options.layerAbove(), options.layerBelow());
this.positionManager = new LocationComponentPositionManager(style, options.layerAbove(),
options.layerBelow(), options.bearingOnTop());
locationLayerRenderer.initializeComponents(style);
locationLayerRenderer.addLayers(positionManager);
applyStyle(options);
Expand All @@ -87,7 +88,7 @@ void initializeComponents(Style style, LocationComponentOptions options) {
}

void applyStyle(@NonNull LocationComponentOptions options) {
if (positionManager.update(options.layerAbove(), options.layerBelow())) {
if (positionManager.update(options.layerAbove(), options.layerBelow(), options.bearingOnTop())) {
locationLayerRenderer.removeLayers();
locationLayerRenderer.addLayers(positionManager);
if (isHidden) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,29 @@ public void initializeComponents(Style style) {

@Override
public void addLayers(LocationComponentPositionManager positionManager) {
// positions the top-most reference layer
Layer layer = layerSourceProvider.generateLayer(BEARING_LAYER);
positionManager.addLayerToMap(layer);
layerSet.add(layer.getId());

// adds remaining layers while keeping the order
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
if (positionManager.bearingOnTop) {
// positions the top-most reference layer
Layer layer = layerSourceProvider.generateLayer(BEARING_LAYER);
positionManager.addLayerToMap(layer);
layerSet.add(layer.getId());

// adds remaining layers while keeping the order
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
} else {
// positions the top-most reference layer
Layer layer = layerSourceProvider.generateLayer(FOREGROUND_LAYER);
positionManager.addLayerToMap(layer);
layerSet.add(layer.getId());

// adds remaining layers while keeping the order
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(BEARING_LAYER, BACKGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BEARING_LAYER);
}

addAccuracyLayer();
addPulsingCircleLayerToMap();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
<!-- Map layer configuration -->
<attr name="maplibre_layer_above" format="string"/>
<attr name="maplibre_layer_below" format="string"/>
<attr name="maplibre_bearing_on_top" format="boolean"/>

<!-- Icon scale based on map zoom levels -->
<attr name="maplibre_maxZoomIconScale" format="float"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,104 +26,125 @@ class LocationComponentPositionManagerTest : BaseTest() {

@Test
fun update_noChange_null() {
val positionManager = LocationComponentPositionManager(style, null, null)
val requiresUpdate = positionManager.update(null, null)
val positionManager = LocationComponentPositionManager(style, null, null, false)
val requiresUpdate = positionManager.update(null, null, false)
assertFalse(requiresUpdate)
}

@Test
fun update_noChange_above() {
val positionManager = LocationComponentPositionManager(style, "above", null)
val requiresUpdate = positionManager.update("above", null)
val positionManager = LocationComponentPositionManager(style, "above", null, false)
val requiresUpdate = positionManager.update("above", null, false)
assertFalse(requiresUpdate)
}

@Test
fun update_noChange_below() {
val positionManager = LocationComponentPositionManager(style, null, "below")
val requiresUpdate = positionManager.update(null, "below")
val positionManager = LocationComponentPositionManager(style, null, "below", false)
val requiresUpdate = positionManager.update(null, "below", false)
assertFalse(requiresUpdate)
}

@Test
fun update_noChange_bearingOnTop() {
val positionManager = LocationComponentPositionManager(style, null, null, true)
val requiresUpdate = positionManager.update(null, null, true)
assertFalse(requiresUpdate)
}

@Test
fun update_fromNull_above() {
val positionManager = LocationComponentPositionManager(style, null, null)
val requiresUpdate = positionManager.update("above", null)
val positionManager = LocationComponentPositionManager(style, null, null, false)
val requiresUpdate = positionManager.update("above", null, false)
assertTrue(requiresUpdate)
}

@Test
fun update_fromNull_below() {
val positionManager = LocationComponentPositionManager(style, null, null)
val requiresUpdate = positionManager.update(null, "below")
val positionManager = LocationComponentPositionManager(style, null, null, false)
val requiresUpdate = positionManager.update(null, "below", false)
assertTrue(requiresUpdate)
}

@Test
fun update_toNull_above() {
val positionManager = LocationComponentPositionManager(style, "above", null)
val requiresUpdate = positionManager.update(null, null)
val positionManager = LocationComponentPositionManager(style, "above", null, false)
val requiresUpdate = positionManager.update(null, null, false)
assertTrue(requiresUpdate)
}

@Test
fun update_toNull_below() {
val positionManager = LocationComponentPositionManager(style, null, "below")
val requiresUpdate = positionManager.update(null, null)
val positionManager = LocationComponentPositionManager(style, null, "below", false)
val requiresUpdate = positionManager.update(null, null, false)
assertTrue(requiresUpdate)
}

@Test
fun update_fromValue_above() {
val positionManager = LocationComponentPositionManager(style, "above1", null)
val requiresUpdate = positionManager.update("above2", null)
val positionManager = LocationComponentPositionManager(style, "above1", null, false)
val requiresUpdate = positionManager.update("above2", null, false)
assertTrue(requiresUpdate)
}

@Test
fun update_fromValue_below() {
val positionManager = LocationComponentPositionManager(style, null, "below1")
val requiresUpdate = positionManager.update(null, "below2")
val positionManager = LocationComponentPositionManager(style, null, "below1", false)
val requiresUpdate = positionManager.update(null, "below2", false)
assertTrue(requiresUpdate)
}

@Test
fun update_bearingOnTop_changed() {
val positionManager = LocationComponentPositionManager(style, null, null, false)
val requiresUpdate = positionManager.update(null, null, true)
assertTrue(requiresUpdate)
}

@Test
fun update_bearingOnTop_changedToFalse() {
val positionManager = LocationComponentPositionManager(style, null, null, true)
val requiresUpdate = positionManager.update(null, null, false)
assertTrue(requiresUpdate)
}

@Test
fun addLayer_noModifier() {
val positionManager = LocationComponentPositionManager(style, null, null)
val positionManager = LocationComponentPositionManager(style, null, null, false)
positionManager.addLayerToMap(layer)

verify { style.addLayer(layer) }
}

@Test
fun addLayer_above() {
val positionManager = LocationComponentPositionManager(style, "above", null)
val positionManager = LocationComponentPositionManager(style, "above", null, false)
positionManager.addLayerToMap(layer)

verify { style.addLayerAbove(layer, "above") }
}

@Test
fun addLayer_below() {
val positionManager = LocationComponentPositionManager(style, null, "below")
val positionManager = LocationComponentPositionManager(style, null, "below", false)
positionManager.addLayerToMap(layer)

verify { style.addLayerBelow(layer, "below") }
}

@Test
fun addLayer_afterUpdate_above() {
val positionManager = LocationComponentPositionManager(style, null, null)
positionManager.update("above", null)
val positionManager = LocationComponentPositionManager(style, null, null, false)
positionManager.update("above", null, false)
positionManager.addLayerToMap(layer)

verify { style.addLayerAbove(layer, "above") }
}

@Test
fun addLayer_afterUpdate_below() {
val positionManager = LocationComponentPositionManager(style, null, null)
positionManager.update(null, "below")
val positionManager = LocationComponentPositionManager(style, null, null, false)
positionManager.update(null, "below", false)
positionManager.addLayerToMap(layer)

verify { style.addLayerBelow(layer, "below") }
Expand Down
Loading
Loading