feat: add v2 multi-plane field ingress
This commit is contained in:
@@ -1124,10 +1124,21 @@ jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env,
|
||||
auto* layer = map->getStyle().getLayer(jni::Make<std::string>(env, layerId));
|
||||
if (!layer) return false;
|
||||
const auto property = jni::Make<std::string>(env, propertyName);
|
||||
if (property == "weather-color-field-value-source") {
|
||||
auto* weatherColorLayer = dynamic_cast<mbgl::style::WeatherColorLayer*>(layer);
|
||||
if (!weatherColorLayer || (value != 0.0f && value != 1.0f)) {
|
||||
mbgl::Log::Error(mbgl::Event::JNI, "ParticleField: event=FIELD_VALUE_SOURCE_SET result=REJECTED");
|
||||
return false;
|
||||
}
|
||||
weatherColorLayer->setFieldValueSource(value == 0.0f ? mbgl::style::FieldValueSource::Scalar
|
||||
: mbgl::style::FieldValueSource::VectorMagnitude);
|
||||
return true;
|
||||
}
|
||||
if (property != "raster-particle-time-mix" && property != "raster-particle-particle-count" &&
|
||||
property != "raster-particle-line-width" && property != "raster-particle-particle-length" &&
|
||||
property != "raster-particle-particle-opacity" && property != "raster-particle-trail-opacity" &&
|
||||
property != "raster-particle-trail-fade" && property != "raster-particle-speed-color-enabled") {
|
||||
property != "raster-particle-trail-fade" && property != "raster-particle-speed-color-enabled" &&
|
||||
property != "raster-particle-advection-scale") {
|
||||
mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=UNSUPPORTED_PROPERTY");
|
||||
return false;
|
||||
}
|
||||
@@ -1163,6 +1174,9 @@ jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env,
|
||||
particleLayer->setTrailOpacity(value);
|
||||
} else if (property == "raster-particle-trail-fade") {
|
||||
particleLayer->setTrailFade(value);
|
||||
} else if (property == "raster-particle-advection-scale") {
|
||||
if (value <= 0.0f) return false;
|
||||
particleLayer->setAdvectionScale(value);
|
||||
} else {
|
||||
if (value != 0.0f && value != 1.0f) {
|
||||
mbgl::Log::Error(mbgl::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_COLOR_MODE");
|
||||
@@ -1327,6 +1341,51 @@ jni::jint NativeMapView::submitRasterArraySourceHostField(JNIEnv& env,
|
||||
return static_cast<jni::jint>(result);
|
||||
}
|
||||
|
||||
jni::jint NativeMapView::submitRasterArraySourceHostFieldV2(JNIEnv& env,
|
||||
const jni::String& sourceId,
|
||||
const jni::String& metadata,
|
||||
const jni::Array<jbyte>& packedPlanes,
|
||||
const jni::jboolean scalarPresent,
|
||||
const jni::jint scalarLength,
|
||||
const jni::jboolean vectorPresent,
|
||||
const jni::jint vectorLength,
|
||||
const jni::jint validityLength) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if (!source) return static_cast<jni::jint>(mbgl::style::RasterArrayHostFieldSubmitResult::RejectedSourceMode);
|
||||
NullCheck(env, &packedPlanes);
|
||||
if (scalarLength < 0 || vectorLength < 0 || validityLength <= 0 || (!scalarPresent && scalarLength != 0) ||
|
||||
(!vectorPresent && vectorLength != 0) || (scalarPresent && scalarLength == 0) ||
|
||||
(vectorPresent && vectorLength == 0) || (!scalarPresent && !vectorPresent)) {
|
||||
return static_cast<jni::jint>(mbgl::style::RasterArrayHostFieldSubmitResult::RejectedAdapter);
|
||||
}
|
||||
const uint64_t total = static_cast<uint64_t>(scalarLength) + static_cast<uint64_t>(vectorLength) +
|
||||
static_cast<uint64_t>(validityLength);
|
||||
if (total != packedPlanes.Length(env)) {
|
||||
return static_cast<jni::jint>(mbgl::style::RasterArrayHostFieldSubmitResult::RejectedAdapter);
|
||||
}
|
||||
std::vector<char> packed(total);
|
||||
if (!packed.empty()) jni::GetArrayRegion(env, *packedPlanes, 0, packed.size(), reinterpret_cast<jbyte*>(packed.data()));
|
||||
size_t offset = 0;
|
||||
const auto take = [&packed, &offset](const size_t length) {
|
||||
std::vector<char> result(packed.begin() + static_cast<std::ptrdiff_t>(offset),
|
||||
packed.begin() + static_cast<std::ptrdiff_t>(offset + length));
|
||||
offset += length;
|
||||
return result;
|
||||
};
|
||||
mbgl::particle::ParticleFieldPayloadBundle payloads{
|
||||
.scalar = scalarPresent ? std::optional<std::vector<char>>(take(static_cast<size_t>(scalarLength))) : std::nullopt,
|
||||
.vector = vectorPresent ? std::optional<std::vector<char>>(take(static_cast<size_t>(vectorLength))) : std::nullopt,
|
||||
.validity = take(static_cast<size_t>(validityLength)),
|
||||
};
|
||||
const auto result = source->submitHostField(jni::Make<std::string>(env, metadata), payloads);
|
||||
mbgl::Log::Info(mbgl::Event::JNI,
|
||||
"ParticleField: event=HOST_FIELD_V2_SUBMIT_BRIDGE sourceId=" + id + " scalar=" +
|
||||
(scalarPresent ? "present" : "absent") + " vector=" + (vectorPresent ? "present" : "absent") +
|
||||
" result=" + mbgl::style::rasterArrayHostFieldSubmitResultName(result));
|
||||
return static_cast<jni::jint>(result);
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::clearRasterArraySourceHostFields(JNIEnv& env, const jni::String& sourceId) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
@@ -1761,6 +1820,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"),
|
||||
METHOD(&NativeMapView::repeatRasterArraySourceFieldWindow, "nativeRepeatRasterArraySourceFieldWindow"),
|
||||
METHOD(&NativeMapView::submitRasterArraySourceHostField, "nativeSubmitRasterArraySourceHostField"),
|
||||
METHOD(&NativeMapView::submitRasterArraySourceHostFieldV2, "nativeSubmitRasterArraySourceHostFieldV2"),
|
||||
METHOD(&NativeMapView::clearRasterArraySourceHostFields, "nativeClearRasterArraySourceHostFields"),
|
||||
METHOD(&NativeMapView::seekRasterArraySourceTimelineToTimestamp, "nativeSeekRasterArraySourceTimelineToTimestamp"),
|
||||
METHOD(&NativeMapView::controlRasterArraySourceTimeline, "nativeControlRasterArraySourceTimeline"),
|
||||
|
||||
@@ -298,6 +298,15 @@ public:
|
||||
const jni::String&,
|
||||
const jni::String&,
|
||||
const jni::Array<jbyte>&);
|
||||
jni::jint submitRasterArraySourceHostFieldV2(JNIEnv&,
|
||||
const jni::String&,
|
||||
const jni::String&,
|
||||
const jni::Array<jbyte>&,
|
||||
jni::jboolean,
|
||||
jni::jint,
|
||||
jni::jboolean,
|
||||
jni::jint,
|
||||
jni::jint);
|
||||
jni::jboolean clearRasterArraySourceHostFields(JNIEnv&, const jni::String&);
|
||||
jni::jboolean seekRasterArraySourceTimelineToTimestamp(JNIEnv&, const jni::String&, jni::jlong);
|
||||
jni::jboolean controlRasterArraySourceTimeline(JNIEnv&, const jni::String&, jni::jint, jni::jfloat);
|
||||
|
||||
@@ -402,6 +402,14 @@ public final class MapLibreMap {
|
||||
return nativeMapView.submitRasterArraySourceHostField(sourceId, metadata, payload);
|
||||
}
|
||||
|
||||
/** Explicit v2 ingress: packed bytes are split by presence flags and lengths in JNI, never in JS. */
|
||||
public int submitRasterArraySourceHostFieldV2(String sourceId, String metadata, byte[] packedPlanes,
|
||||
boolean scalarPresent, int scalarLength,
|
||||
boolean vectorPresent, int vectorLength, int validityLength) {
|
||||
return nativeMapView.submitRasterArraySourceHostFieldV2(sourceId, metadata, packedPlanes, scalarPresent,
|
||||
scalarLength, vectorPresent, vectorLength, validityLength);
|
||||
}
|
||||
|
||||
/** Clears Host-owned canonical fields while retaining the raster-array Source and Style layers. */
|
||||
public boolean clearRasterArraySourceHostFields(String sourceId) {
|
||||
return nativeMapView.clearRasterArraySourceHostFields(sourceId);
|
||||
|
||||
@@ -167,6 +167,10 @@ interface NativeMap {
|
||||
|
||||
int submitRasterArraySourceHostField(String sourceId, String metadata, byte[] payload);
|
||||
|
||||
int submitRasterArraySourceHostFieldV2(String sourceId, String metadata, byte[] packedPlanes,
|
||||
boolean scalarPresent, int scalarLength,
|
||||
boolean vectorPresent, int vectorLength, int validityLength);
|
||||
|
||||
boolean clearRasterArraySourceHostFields(String sourceId);
|
||||
|
||||
boolean seekRasterArraySourceTimelineToTimestamp(String sourceId, long timestamp);
|
||||
|
||||
@@ -1019,6 +1019,16 @@ final class NativeMapView implements NativeMap {
|
||||
: nativeSubmitRasterArraySourceHostField(sourceId, metadata, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int submitRasterArraySourceHostFieldV2(String sourceId, String metadata, byte[] packedPlanes,
|
||||
boolean scalarPresent, int scalarLength,
|
||||
boolean vectorPresent, int vectorLength, int validityLength) {
|
||||
return checkState("submitRasterArraySourceHostFieldV2")
|
||||
? MapLibreMap.HOST_FIELD_REJECTED_SOURCE_MODE
|
||||
: nativeSubmitRasterArraySourceHostFieldV2(sourceId, metadata, packedPlanes, scalarPresent, scalarLength,
|
||||
vectorPresent, vectorLength, validityLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clearRasterArraySourceHostFields(String sourceId) {
|
||||
return !checkState("clearRasterArraySourceHostFields")
|
||||
@@ -1799,6 +1809,11 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep
|
||||
private native int nativeSubmitRasterArraySourceHostField(String sourceId, String metadata, byte[] payload);
|
||||
|
||||
@Keep
|
||||
private native int nativeSubmitRasterArraySourceHostFieldV2(String sourceId, String metadata, byte[] packedPlanes,
|
||||
boolean scalarPresent, int scalarLength,
|
||||
boolean vectorPresent, int vectorLength, int validityLength);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeClearRasterArraySourceHostFields(String sourceId);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -12,6 +13,7 @@ import java.util.List;
|
||||
* It owns no provider, network, route, ETA, Snapshot, T0/T1, or GPU state.
|
||||
*/
|
||||
public final class WeatherRuntime {
|
||||
private static final long MAX_FIELD_PAYLOAD_BYTES = 64L * 1024L * 1024L;
|
||||
public enum State { DISABLED, WAITING_FOR_FIELDS, READY, ERROR }
|
||||
public enum PlaybackState { PLAY, PAUSE }
|
||||
public enum PlaybackSpeed { HALF(0.5f), NORMAL(1.0f), DOUBLE(2.0f); final float value; PlaybackSpeed(float value) { this.value = value; } }
|
||||
@@ -76,7 +78,27 @@ public final class WeatherRuntime {
|
||||
if (state == State.READY && result.accepted()) scheduleCadenceWake();
|
||||
return result;
|
||||
}
|
||||
public CommandResult clearWindFields() { if (disposed || state == State.DISABLED) return CommandResult.REJECTED; boolean ok = map.clearRasterArraySourceHostFields(config.sourceId); state = stateAfterClear(state, ok); if (ok) cancelCadenceWake(); return result(ok); }
|
||||
/** v2 provider-neutral ingress; missing scalar/vector is expressed only by its explicit presence flag. */
|
||||
public FieldSubmitResult submitField(@NonNull String metadataJson, @Nullable byte[] scalarBytes, @Nullable byte[] vectorBytes, @NonNull byte[] validityBytes) {
|
||||
requireAttached(); requireEnabled();
|
||||
if (metadataJson.isEmpty() || validityBytes.length == 0 || (scalarBytes == null && vectorBytes == null)) return FieldSubmitResult.REJECTED_ADAPTER;
|
||||
final int scalarLength = scalarBytes == null ? 0 : scalarBytes.length;
|
||||
final int vectorLength = vectorBytes == null ? 0 : vectorBytes.length;
|
||||
if ((scalarBytes != null && scalarLength == 0) || (vectorBytes != null && vectorLength == 0)) return FieldSubmitResult.REJECTED_ADAPTER;
|
||||
final long total = (long) scalarLength + vectorLength + validityBytes.length;
|
||||
// Do not rely on Kotlin's file-reference guard: Java callers can bypass it.
|
||||
if (total > MAX_FIELD_PAYLOAD_BYTES || total > Integer.MAX_VALUE) return FieldSubmitResult.REJECTED_ADAPTER;
|
||||
final byte[] packed = new byte[(int) total]; int offset = 0;
|
||||
if (scalarBytes != null) { System.arraycopy(scalarBytes, 0, packed, offset, scalarLength); offset += scalarLength; }
|
||||
if (vectorBytes != null) { System.arraycopy(vectorBytes, 0, packed, offset, vectorLength); offset += vectorLength; }
|
||||
System.arraycopy(validityBytes, 0, packed, offset, validityBytes.length);
|
||||
FieldSubmitResult result = FieldSubmitResult.fromCode(map.submitRasterArraySourceHostFieldV2(config.sourceId, metadataJson,
|
||||
packed, scalarBytes != null, scalarLength, vectorBytes != null, vectorLength, validityBytes.length));
|
||||
state = stateAfterSubmit(state, result); if (state == State.READY && result.accepted()) scheduleCadenceWake(); return result;
|
||||
}
|
||||
public CommandResult clearFields() { if (disposed || state == State.DISABLED) return CommandResult.REJECTED; boolean ok = map.clearRasterArraySourceHostFields(config.sourceId); state = stateAfterClear(state, ok); if (ok) cancelCadenceWake(); return result(ok); }
|
||||
/** @deprecated v1 compatibility alias. */
|
||||
public CommandResult clearWindFields() { return clearFields(); }
|
||||
public CommandResult setWeatherEnabled(boolean enabled) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
if (!enabled) {
|
||||
@@ -108,6 +130,11 @@ public final class WeatherRuntime {
|
||||
public CommandResult setParticleOpacity(float opacity) { return setParticleFloat("raster-particle-particle-opacity", opacity); }
|
||||
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 setParticleAdvectionScale(float scale) { return disposed || !Float.isFinite(scale) || scale <= 0f ? CommandResult.REJECTED : setParticleFloat("raster-particle-advection-scale", scale); }
|
||||
public CommandResult setFieldValueSource(@NonNull FieldValueSource source) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
return result(map.setStyleLayerFloatProperty(config.weatherColorLayerId, "weather-color-field-value-source", source.code));
|
||||
}
|
||||
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) {
|
||||
@@ -143,6 +170,7 @@ public final class WeatherRuntime {
|
||||
return result(map.setWeatherColorStops(config.weatherColorLayerId, speeds, colors));
|
||||
}
|
||||
private static CommandResult result(boolean accepted) { return accepted ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
|
||||
public enum FieldValueSource { SCALAR(0f), VECTOR_MAGNITUDE(1f); final float code; FieldValueSource(float code) { this.code = code; } }
|
||||
private boolean shouldWakeWeather() {
|
||||
return !disposed && targetFps != TargetFps.AUTO && state == State.READY && (particleVisible || (weatherColorVisible && playbackState == PlaybackState.PLAY));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user