fix: finalize WeatherRuntime host API boundary
This commit is contained in:
@@ -1194,6 +1194,30 @@ jni::jboolean NativeMapView::setRasterParticleColors(JNIEnv& env,
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRasterParticleColor(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::Array<jni::jfloat>& particle) {
|
||||
auto* layer = dynamic_cast<mbgl::style::RasterParticleLayer*>(map->getStyle().getLayer(jni::Make<std::string>(env, layerId)));
|
||||
if (!layer || particle.Length(env) != 4) return false;
|
||||
std::array<float, 4> color;
|
||||
particle.GetRegion(env, 0, color);
|
||||
if (!mbgl::style::validWeatherRGBA(color)) return false;
|
||||
layer->setParticleColor(color);
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRasterParticleTrailColor(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::Array<jni::jfloat>& trail) {
|
||||
auto* layer = dynamic_cast<mbgl::style::RasterParticleLayer*>(map->getStyle().getLayer(jni::Make<std::string>(env, layerId)));
|
||||
if (!layer || trail.Length(env) != 4) return false;
|
||||
std::array<float, 4> color;
|
||||
trail.GetRegion(env, 0, color);
|
||||
if (!mbgl::style::validWeatherRGBA(color)) return false;
|
||||
layer->setTrailColor(color);
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setWeatherColorStops(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::Array<jni::jfloat>& speeds,
|
||||
@@ -1695,6 +1719,8 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"),
|
||||
METHOD(&NativeMapView::setStyleLayerVisibility, "nativeSetStyleLayerVisibility"),
|
||||
METHOD(&NativeMapView::setRasterParticleColors, "nativeSetRasterParticleColors"),
|
||||
METHOD(&NativeMapView::setRasterParticleColor, "nativeSetRasterParticleColor"),
|
||||
METHOD(&NativeMapView::setRasterParticleTrailColor, "nativeSetRasterParticleTrailColor"),
|
||||
METHOD(&NativeMapView::setWeatherColorStops, "nativeSetWeatherColorStops"),
|
||||
METHOD(&NativeMapView::getWeatherColorStopCapacity, "nativeGetWeatherColorStopCapacity"),
|
||||
METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"),
|
||||
|
||||
@@ -284,6 +284,8 @@ public:
|
||||
jni::jboolean setStyleLayerFloatProperty(JNIEnv&, const jni::String&, const jni::String&, jni::jfloat);
|
||||
jni::jboolean setStyleLayerVisibility(JNIEnv&, const jni::String&, jni::jboolean);
|
||||
jni::jboolean setRasterParticleColors(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&, const jni::Array<jni::jfloat>&);
|
||||
jni::jboolean setRasterParticleColor(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&);
|
||||
jni::jboolean setRasterParticleTrailColor(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&);
|
||||
jni::jboolean setWeatherColorStops(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&, const jni::Array<jni::jfloat>&);
|
||||
jni::jint getWeatherColorStopCapacity(JNIEnv&);
|
||||
jni::jboolean advanceRasterArraySourceFieldWindow(JNIEnv&, const jni::String&);
|
||||
|
||||
@@ -301,6 +301,14 @@ public final class MapLibreMap {
|
||||
return nativeMapView.setRasterParticleColors(layerId, particleColor, trailColor);
|
||||
}
|
||||
|
||||
public boolean setRasterParticleColor(String layerId, float[] particleColor) {
|
||||
return nativeMapView.setRasterParticleColor(layerId, particleColor);
|
||||
}
|
||||
|
||||
public boolean setRasterParticleTrailColor(String layerId, float[] trailColor) {
|
||||
return nativeMapView.setRasterParticleTrailColor(layerId, trailColor);
|
||||
}
|
||||
|
||||
/** Installs a validated WeatherColor speed ramp without changing particle style. */
|
||||
public boolean setWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops) {
|
||||
return nativeMapView.setWeatherColorStops(layerId, speedsMps, rgbaStops);
|
||||
|
||||
@@ -145,6 +145,10 @@ interface NativeMap {
|
||||
|
||||
boolean setRasterParticleColors(String layerId, float[] particleColor, float[] trailColor);
|
||||
|
||||
boolean setRasterParticleColor(String layerId, float[] particleColor);
|
||||
|
||||
boolean setRasterParticleTrailColor(String layerId, float[] trailColor);
|
||||
|
||||
boolean setWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops);
|
||||
|
||||
int getWeatherColorStopCapacity();
|
||||
|
||||
@@ -964,6 +964,14 @@ final class NativeMapView implements NativeMap {
|
||||
return !checkState("setRasterParticleColors") && nativeSetRasterParticleColors(layerId, particleColor, trailColor);
|
||||
}
|
||||
|
||||
public boolean setRasterParticleColor(String layerId, float[] particleColor) {
|
||||
return !checkState("setRasterParticleColor") && nativeSetRasterParticleColor(layerId, particleColor);
|
||||
}
|
||||
|
||||
public boolean setRasterParticleTrailColor(String layerId, float[] trailColor) {
|
||||
return !checkState("setRasterParticleTrailColor") && nativeSetRasterParticleTrailColor(layerId, trailColor);
|
||||
}
|
||||
|
||||
public boolean setWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops) {
|
||||
return !checkState("setWeatherColorStops") && nativeSetWeatherColorStops(layerId, speedsMps, rgbaStops);
|
||||
}
|
||||
@@ -1745,6 +1753,8 @@ final class NativeMapView implements NativeMap {
|
||||
|
||||
@Keep private native boolean nativeSetStyleLayerVisibility(String layerId, boolean visible);
|
||||
@Keep private native boolean nativeSetRasterParticleColors(String layerId, float[] particleColor, float[] trailColor);
|
||||
@Keep private native boolean nativeSetRasterParticleColor(String layerId, float[] particleColor);
|
||||
@Keep private native boolean nativeSetRasterParticleTrailColor(String layerId, float[] trailColor);
|
||||
@Keep private native boolean nativeSetWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops);
|
||||
@Keep private native int nativeGetWeatherColorStopCapacity();
|
||||
|
||||
|
||||
@@ -91,6 +91,15 @@ public final class WeatherRuntime {
|
||||
public CommandResult setTrailOpacity(float opacity) { return setParticleFloat("raster-particle-trail-opacity", opacity); }
|
||||
public CommandResult setTrailFade(float fade) { return setParticleFloat("raster-particle-trail-fade", fade); }
|
||||
public CommandResult setParticleSpeedColorEnabled(boolean enabled) { return setParticleFloat("raster-particle-speed-color-enabled", enabled ? 1f : 0f); }
|
||||
/** Sets only the particle RGBA; trail color and WeatherColor ramp are unchanged. */
|
||||
public CommandResult setParticleColor(float red, float green, float blue, float alpha) {
|
||||
return result(map.setRasterParticleColor(config.particleLayerId, new float[]{red, green, blue, alpha}));
|
||||
}
|
||||
/** Sets only the trail RGBA; particle color and WeatherColor ramp are unchanged. */
|
||||
public CommandResult setTrailColor(float red, float green, float blue, float alpha) {
|
||||
return result(map.setRasterParticleTrailColor(config.particleLayerId, new float[]{red, green, blue, alpha}));
|
||||
}
|
||||
/** Compatibility helper for existing callers; new Host code should use the independent setters. */
|
||||
public CommandResult setParticleColors(float red, float green, float blue, float alpha, float trailRed, float trailGreen, float trailBlue, float trailAlpha) {
|
||||
return result(map.setRasterParticleColors(config.particleLayerId, new float[]{red, green, blue, alpha}, new float[]{trailRed, trailGreen, trailBlue, trailAlpha}));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.maplibre.android.maps;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/** Unit coverage for facade-only state transitions; no renderer frame callback is involved. */
|
||||
@@ -63,4 +65,31 @@ public class WeatherRuntimeStateTest {
|
||||
assertEquals(WeatherRuntime.State.ERROR, runtime.getState());
|
||||
Mockito.verify(map, Mockito.never()).setStyleLayerVisibility(Mockito.anyString(), Mockito.anyBoolean());
|
||||
}
|
||||
|
||||
@Test public void particleAndTrailColorsAreIndependentTypedCommands() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
Mockito.when(map.setRasterParticleColor(Mockito.anyString(), Mockito.any(float[].class))).thenReturn(true);
|
||||
Mockito.when(map.setRasterParticleTrailColor(Mockito.anyString(), Mockito.any(float[].class))).thenReturn(true);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(WeatherRuntime.CommandResult.ACCEPTED, runtime.setParticleColor(0.1f, 0.2f, 0.3f, 1.0f));
|
||||
assertEquals(WeatherRuntime.CommandResult.ACCEPTED, runtime.setTrailColor(0.9f, 0.8f, 0.7f, 1.0f));
|
||||
ArgumentCaptor<float[]> particleColor = ArgumentCaptor.forClass(float[].class);
|
||||
ArgumentCaptor<float[]> trailColor = ArgumentCaptor.forClass(float[].class);
|
||||
Mockito.verify(map).setRasterParticleColor(Mockito.eq("particle"), particleColor.capture());
|
||||
Mockito.verify(map).setRasterParticleTrailColor(Mockito.eq("particle"), trailColor.capture());
|
||||
assertArrayEquals(new float[] {0.1f, 0.2f, 0.3f, 1.0f}, particleColor.getValue(), 0.0f);
|
||||
assertArrayEquals(new float[] {0.9f, 0.8f, 0.7f, 1.0f}, trailColor.getValue(), 0.0f);
|
||||
Mockito.verify(map, Mockito.never()).submitRasterArraySourceHostField(Mockito.anyString(), Mockito.anyString(), Mockito.any(byte[].class));
|
||||
}
|
||||
|
||||
@Test public void invalidIndependentColorIsRejectedByNativeContract() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
Mockito.when(map.setRasterParticleColor(Mockito.anyString(), Mockito.any(float[].class))).thenReturn(false);
|
||||
Mockito.when(map.setRasterParticleTrailColor(Mockito.anyString(), Mockito.any(float[].class))).thenReturn(false);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setParticleColor(Float.NaN, 0f, 0f, 1f));
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setTrailColor(0f, 0f, 0f, 2f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
2 -> when (value) { 0.5f -> runtime?.setPlaybackSpeed(WeatherRuntime.PlaybackSpeed.HALF); 1.0f -> runtime?.setPlaybackSpeed(WeatherRuntime.PlaybackSpeed.NORMAL); 2.0f -> runtime?.setPlaybackSpeed(WeatherRuntime.PlaybackSpeed.DOUBLE); else -> null }
|
||||
else -> null // Existing fixture-only diagnostic commands remain outside the REAL WEATHER Host facade.
|
||||
}
|
||||
if (accepted != WeatherRuntime.CommandResult.ACCEPTED && (runtime == null || !maplibreMap.controlRasterArraySourceTimeline(PARTICLE_SOURCE_ID, command, value))) {
|
||||
if (accepted != WeatherRuntime.CommandResult.ACCEPTED && !realWeatherMode && (runtime == null || !maplibreMap.controlRasterArraySourceTimeline(PARTICLE_SOURCE_ID, command, value))) {
|
||||
Log.i(TAG, "ParticleTimeline: event=REJECTED command=$command")
|
||||
} else {
|
||||
timelineState = when (command) { 0 -> "PLAY"; 1 -> "PAUSE"; 6 -> "WAIT"; 7 -> "INVALID"; 8 -> "PAUSE"; else -> timelineState }
|
||||
@@ -547,7 +547,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
return runtimeResult == WeatherRuntime.CommandResult.ACCEPTED ||
|
||||
(runtimeResult == null && maplibreMap.setStyleLayerFloatProperty(PARTICLE_LAYER_ID, property, value))
|
||||
(!realWeatherMode && runtimeResult == null && maplibreMap.setStyleLayerFloatProperty(PARTICLE_LAYER_ID, property, value))
|
||||
}
|
||||
|
||||
private fun resetVisualDefaults() {
|
||||
@@ -615,16 +615,16 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
val metadata = assets.open(metadataPath).bufferedReader().use { it.readText() }
|
||||
val payload = assets.open(payloadPath).use { it.readBytes() }
|
||||
Log.i(TAG, "ParticleField: event=REAL_WEATHER_DATASET_LOAD index=$index metadata=$metadataPath payloadBytes=${payload.size}")
|
||||
weatherRuntime?.submitWindField(metadata, payload)?.getCode() ?: MapLibreMap.HOST_FIELD_REJECTED_SOURCE_MODE
|
||||
weatherRuntime?.submitWindField(metadata, payload) ?: WeatherRuntime.FieldSubmitResult.REJECTED_SOURCE_MODE
|
||||
} catch (error: Exception) {
|
||||
Log.e(TAG, "ParticleField: event=REAL_WEATHER_SUBMIT_REJECTED index=$index reason=ASSET_READ_FAILED", error)
|
||||
MapLibreMap.HOST_FIELD_REJECTED_ADAPTER
|
||||
WeatherRuntime.FieldSubmitResult.REJECTED_ADAPTER
|
||||
}
|
||||
}
|
||||
timelineState = if (results.size == 4 && results.all { it > 0 }) "READY" else "NOT READY"
|
||||
timelineState = if (results.size == 4 && results.all { it.accepted() }) "READY" else "NOT READY"
|
||||
fitRealWeatherBounds()
|
||||
status.text = statusText()
|
||||
Log.i(TAG, "ParticleField: event=REAL_WEATHER_INITIAL_WINDOW_READY results=${results.joinToString(",")} timeline=$timelineState")
|
||||
Log.i(TAG, "ParticleField: event=REAL_WEATHER_INITIAL_WINDOW_READY results=${results.joinToString(",") { it.name }} timeline=$timelineState")
|
||||
Log.i(TAG, "WeatherColorLayer: event=WEATHER_COLOR_FIELD_READY source=$PARTICLE_SOURCE_ID scale=0..30mps")
|
||||
Log.i(TAG, "ParticleField: event=REAL_WEATHER_COMPOSITE_READY source=$PARTICLE_SOURCE_ID basemap=GSI failSoft=true color=weather-color particle=raster-particle")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user