Skip to content

Commit 4e3dcec

Browse files
bmanderclaude
andauthored
Keep zoom() at top level in route-stop circle expressions (#1927) (#1929)
MapLibre 13.3.1 rejects a zoom() expression nested inside anything but a top-level step/interpolate ("zoom expression may only be used as input to a top-level step or interpolate expression"). The route-stop circle layer wrapped its zoom()-driven radius interpolation in product() to derive the outer stroke width and the selected inner-circle radius, so those two layers failed validation under MapLibre 13 — circles lost their stroke ring and selected stops lost zoom-scaled highlighting. MapLibre 11.5.2 tolerated the nesting. Fold the scale factor into each interpolation stop's output instead of wrapping the whole interpolation, keeping zoom() at the top level. For a linear interpolation, scaling each endpoint is mathematically identical to scaling the interpolation, including the clamped regions outside the band. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6c71ddd commit 4e3dcec

1 file changed

Lines changed: 18 additions & 15 deletions

File tree

onebusaway-android/src/maplibre/java/org/onebusaway/android/map/maplibre/MapLibreRouteStopCircleLayer.kt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,14 @@ internal class MapLibreRouteStopCircleLayer(
6464
private var renderedScaleWithZoom = false
6565

6666
init {
67-
val radius = radiusExpression()
6867
style.addSource(source)
6968
style.addLayer(
7069
CircleLayer(OUTER_LAYER_ID, SOURCE_ID).withProperties(
71-
circleRadius(radius),
70+
circleRadius(radiusExpression()),
7271
circleColor(RouteStopCircles.FILL_COLOR),
7372
circleStrokeColor(RouteStopCircles.STROKE_COLOR),
7473
circleStrokeWidth(
75-
product(
76-
radius,
77-
literal(RouteStopCircles.STROKE_WIDTH_PX / RouteStopCircles.RADIUS_PX),
78-
)
74+
radiusExpression(RouteStopCircles.STROKE_WIDTH_PX / RouteStopCircles.RADIUS_PX)
7975
),
8076
circleSortKey(get(MAX_RADIUS_PROPERTY)),
8177
)
@@ -84,12 +80,7 @@ internal class MapLibreRouteStopCircleLayer(
8480
CircleLayer(INNER_LAYER_ID, SOURCE_ID)
8581
.withFilter(eq(get(SELECTED_PROPERTY), true))
8682
.withProperties(
87-
circleRadius(
88-
product(
89-
radius,
90-
literal(RouteStopCircles.INNER_RADIUS_SCALE),
91-
)
92-
),
83+
circleRadius(radiusExpression(RouteStopCircles.INNER_RADIUS_SCALE)),
9384
circleColor(RouteStopCircles.STROKE_COLOR),
9485
circleSortKey(get(MAX_RADIUS_PROPERTY)),
9586
)
@@ -147,13 +138,25 @@ internal class MapLibreRouteStopCircleLayer(
147138
renderedScaleWithZoom = false
148139
}
149140

150-
private fun radiusExpression(): Expression = interpolate(
141+
/**
142+
* A `zoom()`-driven radius interpolation, optionally scaled by [scale].
143+
*
144+
* The scale is folded into each interpolation stop's output rather than wrapping the whole
145+
* interpolation in a `product()`. MapLibre 13 rejects a `zoom()` expression nested inside
146+
* anything but a top-level step/interpolate ("zoom expression may only be used as input to a
147+
* top-level step or interpolate expression"), so keeping `zoom()` at the top level is required;
148+
* scaling the stop outputs is mathematically equivalent for a linear interpolation. (#1927)
149+
*/
150+
private fun radiusExpression(scale: Float = 1f): Expression = interpolate(
151151
linear(),
152152
zoom(),
153-
stop(DETAIL_RAMP_START_ZOOM, get(MIN_RADIUS_PROPERTY)),
154-
stop(DETAIL_RAMP_END_ZOOM, get(MAX_RADIUS_PROPERTY)),
153+
stop(DETAIL_RAMP_START_ZOOM, scaledRadius(MIN_RADIUS_PROPERTY, scale)),
154+
stop(DETAIL_RAMP_END_ZOOM, scaledRadius(MAX_RADIUS_PROPERTY, scale)),
155155
)
156156

157+
private fun scaledRadius(property: String, scale: Float): Expression =
158+
if (scale == 1f) get(property) else product(get(property), literal(scale))
159+
157160
private companion object {
158161
const val SOURCE_ID = "oba-route-stops"
159162
const val OUTER_LAYER_ID = "oba-route-stops-outer"

0 commit comments

Comments
 (0)