Skip to content

Commit ddabf93

Browse files
pengdevgithub-actions[bot]
authored andcommitted
Fix feature ID double-to-string format mismatch in JNI marshaling (#10615)
### Summary Fixes https://mapbox.atlassian.net/browse/MAPSAND-2531 - Fix incorrect string formatting of `double` feature IDs in JNI marshaling - Whole-number `double` IDs (e.g., `12345.0`) are now serialized as `"12345"` instead of `"12345.000000"` ### Problem Feature IDs in mapbox-feature are a `std::variant<uint64_t, int64_t, double, null_value_t>`. The previous code used a single generic `auto&` lambda to handle all numeric types, which called `std::to_string()` directly on the value. For `double` IDs that represent whole numbers (e.g., `12345.0`), this produced `"12345.000000"` on the JNI/Java side — causing ID mismatches when compared against integer-typed IDs stored elsewhere in the system. ### Solution Split the single generic numeric handler in `feature.hpp` into three type-specific lambdas: - **`uint64_t`** and **`int64_t`**: call `std::to_string()` directly → `"12345"` - **`double`**: use `std::modf` to check if the value is a whole number, finite, and within `int64_t` range; if so, cast to `int64_t` before formatting → `"12345"` instead of `"12345.000000"`; otherwise format as-is to preserve fractional precision Added `StandardBuildingsFeatureStateTest` instrumentation test verifying that feature IDs from `queryRenderedFeatures` are compatible with `setFeatureState`/`getFeatureState`. ### Validation Validated on device that clicking buildings with `ClickInteraction.standardBuildings` + `setFeatureState` works correctly with the fix applied. cc @mapbox/maps-android @mapbox/core-sdk @mapbox/sdk-platform GitOrigin-RevId: 4289724877a9f11917ca1d95c06e0c84076b7173
1 parent de3e6c3 commit ddabf93

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Mapbox welcomes participation and contributions from everyone.
77
# main
88
## Bug fixes 🐞
99
* Fix native memory leak in `AnnotationManager` where bitmap style images were not removed onDestroy.
10+
* Fix feature ID format mismatch in JNI marshaling where whole-number `double` feature IDs (e.g. `12345.0`) were incorrectly serialized as `"12345.000000"` instead of `"12345"`, causing `setFeatureState` to fail when using IDs obtained from `queryRenderedFeatures`.
1011

1112
# 11.20.1 March 17, 2026
1213
## Bug fixes 🐞
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.mapbox.maps.testapp.featurestate
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import com.mapbox.geojson.Point
5+
import com.mapbox.maps.MapboxExperimental
6+
import com.mapbox.maps.Style
7+
import com.mapbox.maps.dsl.cameraOptions
8+
import com.mapbox.maps.interactions.standard.generated.StandardBuildings
9+
import com.mapbox.maps.interactions.standard.generated.StandardBuildingsState
10+
import com.mapbox.maps.testapp.BaseMapTest
11+
import com.mapbox.maps.testapp.runOnUiThread
12+
import com.mapbox.maps.testapp.withLatch
13+
import org.junit.Assert.assertEquals
14+
import org.junit.Assert.assertNotNull
15+
import org.junit.Test
16+
import org.junit.runner.RunWith
17+
18+
/**
19+
* Instrumented test verifying that feature IDs returned by [com.mapbox.maps.MapboxMap.queryRenderedFeatures]
20+
* for StandardBuildings are compatible with [com.mapbox.maps.MapboxMap.setFeatureState] and
21+
* [com.mapbox.maps.MapboxMap.getFeatureState].
22+
*
23+
* Regression test for: double feature IDs being formatted as "12345.000000" instead of "12345"
24+
* due to a format mismatch in JNI marshaling (feature.hpp).
25+
*/
26+
@OptIn(MapboxExperimental::class)
27+
@RunWith(AndroidJUnit4::class)
28+
class StandardBuildingsFeatureStateTest : BaseMapTest() {
29+
30+
override fun loadMap() {
31+
withLatch(timeoutMillis = 30000) { latch ->
32+
rule.runOnUiThread {
33+
mapboxMap = mapView.mapboxMap
34+
mapboxMap.setCamera(
35+
cameraOptions {
36+
center(Point.fromLngLat(24.94180921290157, 60.171227338006844))
37+
zoom(16.0)
38+
}
39+
)
40+
mapboxMap.loadStyle(Style.STANDARD) { style ->
41+
this@StandardBuildingsFeatureStateTest.style = style
42+
// Wait for the map to finish rendering tiles before querying features
43+
mapboxMap.subscribeMapIdle {
44+
latch.countDown()
45+
}
46+
}
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Verifies that a building queried via [com.mapbox.maps.MapboxMap.queryRenderedFeatures] can
53+
* have its state set and retrieved using the typed featureset API.
54+
*
55+
* If the feature ID format is wrong (e.g. "12345.000000"), setFeatureState will target a
56+
* different feature and getFeatureState will return null for select, causing the assertion
57+
* to fail.
58+
*/
59+
@Test
60+
fun testQueryRenderedBuildingIdCompatibleWithSetFeatureState() {
61+
withLatch(timeoutMillis = 10000) { latch ->
62+
rule.runOnUiThread {
63+
mapboxMap.queryRenderedFeatures(StandardBuildings()) { buildings ->
64+
val building = buildings.firstOrNull { it.id != null }
65+
assertNotNull("Expected at least one rendered building with an ID", building)
66+
building!!
67+
68+
mapboxMap.setFeatureState(building, StandardBuildingsState { select(true) }) {
69+
mapboxMap.getFeatureState(building) { state ->
70+
assertEquals(true, state.select)
71+
latch.countDown()
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)