merge(upgrade): port particle extensions onto official native 13.6.1 (unverified)
This commit is contained in:
@@ -195,6 +195,7 @@ void MapRenderer::requestSnapshot(SnapshotCallback callback) {
|
||||
// Called on OpenGL thread //
|
||||
|
||||
void MapRenderer::resetRenderer() {
|
||||
Log::Info(Event::Android, "MapRenderer::resetRenderer entered");
|
||||
renderer.reset();
|
||||
|
||||
if (!asyncRendererCleanup) {
|
||||
@@ -203,6 +204,7 @@ void MapRenderer::resetRenderer() {
|
||||
|
||||
window.reset();
|
||||
swapBehaviorFlush = false;
|
||||
Log::Info(Event::Android, "MapRenderer::resetRenderer completed");
|
||||
}
|
||||
|
||||
void MapRenderer::scheduleSnapshot(std::unique_ptr<SnapshotCallback> callback) {
|
||||
@@ -322,9 +324,11 @@ void MapRenderer::onSurfaceChanged([[maybe_unused]] JNIEnv& env, jint width, jin
|
||||
}
|
||||
|
||||
void MapRenderer::onRendererReset(JNIEnv&) {
|
||||
Log::Info(Event::Android, "MapRenderer::nativeReset entered");
|
||||
// Make sure to destroy the renderer on the GL Thread
|
||||
auto self = ActorRef<MapRenderer>(*this, mailboxData.getMailbox());
|
||||
self.ask(&MapRenderer::resetRenderer).wait();
|
||||
Log::Info(Event::Android, "MapRenderer::nativeReset completed");
|
||||
}
|
||||
|
||||
// needs to be called on GL thread
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
@@ -26,9 +30,15 @@
|
||||
#include <mln/util/platform.hpp>
|
||||
#include <mln/util/projection.hpp>
|
||||
#include <mln/style/style.hpp>
|
||||
#include <mln/style/layers/raster_particle_layer.hpp>
|
||||
#include <mln/style/layers/weather_color_layer.hpp>
|
||||
#include <mln/style/sources/raster_array_source.hpp>
|
||||
#include <mln/style/image.hpp>
|
||||
#include <mln/style/filter.hpp>
|
||||
#include <mln/renderer/query.hpp>
|
||||
#include <mln/renderer/render_segment_json.hpp>
|
||||
#include <mln/particle/field/field_profile_contract_json.hpp>
|
||||
#include <mln/particle/ingress/encoded_field_transport.hpp>
|
||||
|
||||
// Java -> C++ conversion
|
||||
#include "style/android_conversion.hpp"
|
||||
@@ -61,6 +71,8 @@
|
||||
namespace mln {
|
||||
namespace android {
|
||||
|
||||
namespace {}
|
||||
|
||||
NativeMapView::NativeMapView(jni::JNIEnv& _env,
|
||||
const jni::Object<NativeMapView>& _obj,
|
||||
const jni::Object<FileSource>& jFileSource,
|
||||
@@ -704,6 +716,76 @@ jni::jboolean NativeMapView::getDebug(JNIEnv&) {
|
||||
return map->getDebug() != DebugOptions::NoDebug;
|
||||
}
|
||||
|
||||
void NativeMapView::setRetainedUnderlayEnabled(JNIEnv&, jni::jboolean enabled) {
|
||||
map->setRetainedUnderlayEnabled(enabled);
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setContinuousLayerRange(JNIEnv& env,
|
||||
const jni::String& startLayerId,
|
||||
const jni::String& endLayerId) {
|
||||
return map->setContinuousLayerRange(jni::Make<std::string>(env, startLayerId),
|
||||
jni::Make<std::string>(env, endLayerId));
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRenderSegmentPlan(JNIEnv& env, const jni::String& planJson) {
|
||||
auto plan = renderSegmentPlanFromJSONOrInvalid(jni::Make<std::string>(env, planJson));
|
||||
return map->setRenderSegmentPlan(std::move(plan));
|
||||
}
|
||||
|
||||
jni::Local<jni::String> NativeMapView::getRenderSegmentDiagnostics(JNIEnv& env) {
|
||||
return jni::Make<jni::String>(env, renderSegmentDiagnosticsToJSON(map->getRenderSegmentDiagnostics()));
|
||||
}
|
||||
|
||||
jni::Local<jni::Array<jni::String>> NativeMapView::getRetainedUnderlayDiagnostics(JNIEnv& env) {
|
||||
const auto snapshot = map->getRetainedUnderlayDiagnostics();
|
||||
const std::array<std::string, 39> values = {
|
||||
snapshot.enabled ? "true" : "false",
|
||||
std::to_string(snapshot.build),
|
||||
std::to_string(snapshot.reuse),
|
||||
std::to_string(snapshot.fallback),
|
||||
std::to_string(snapshot.invalidation),
|
||||
snapshot.lastReason,
|
||||
snapshot.rangeConfigured ? "true" : "false",
|
||||
snapshot.rangeValid ? "true" : "false",
|
||||
snapshot.startLayerId,
|
||||
snapshot.endLayerId,
|
||||
std::to_string(snapshot.startIndex),
|
||||
std::to_string(snapshot.endIndex),
|
||||
std::to_string(snapshot.continuousBegin),
|
||||
std::to_string(snapshot.continuousEndExclusive),
|
||||
snapshot.bottomPresent ? "true" : "false",
|
||||
snapshot.bottomCacheState,
|
||||
std::to_string(snapshot.bottomBuild),
|
||||
std::to_string(snapshot.bottomReuse),
|
||||
std::to_string(snapshot.bottomInvalidation),
|
||||
snapshot.bottomLastInvalidationReason,
|
||||
std::to_string(snapshot.bottomTextureCreate),
|
||||
std::to_string(snapshot.bottomCompositeCreate),
|
||||
std::to_string(snapshot.bottomResourceRelease),
|
||||
snapshot.topPresent ? "true" : "false",
|
||||
snapshot.topCacheState,
|
||||
std::to_string(snapshot.topBuild),
|
||||
std::to_string(snapshot.topReuse),
|
||||
std::to_string(snapshot.topInvalidation),
|
||||
snapshot.topLastInvalidationReason,
|
||||
std::to_string(snapshot.topTextureCreate),
|
||||
std::to_string(snapshot.topCompositeCreate),
|
||||
std::to_string(snapshot.topResourceRelease),
|
||||
snapshot.rangeFallback ? "true" : "false",
|
||||
snapshot.fastPathEligible ? "true" : "false",
|
||||
snapshot.fastPathActive ? "true" : "false",
|
||||
snapshot.fastPathFallbackReason,
|
||||
std::to_string(snapshot.fastPathHit),
|
||||
std::to_string(snapshot.fastPathMiss),
|
||||
std::to_string(snapshot.fullCpuFrame),
|
||||
};
|
||||
auto result = jni::Array<jni::String>::New(env, values.size());
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
result.Set(env, index, jni::Make<jni::String>(env, values[index]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
jni::Local<jni::Array<jni::String>> NativeMapView::getActionJournalLogFiles(JNIEnv& env) {
|
||||
const auto& actionJournal = map->getActionJournal();
|
||||
if (!actionJournal) {
|
||||
@@ -1104,6 +1186,340 @@ jni::Local<jni::Object<Layer>> NativeMapView::getLayer(JNIEnv& env, const jni::S
|
||||
return LayerManagerAndroid::get()->createJavaLayerPeer(env, *coreLayer);
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::hasStyleLayer(JNIEnv& env, const jni::String& layerId) {
|
||||
return map->getStyle().getLayer(jni::Make<std::string>(env, layerId)) != nullptr;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setStyleLayerFloatProperty(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::String& propertyName,
|
||||
const jni::jfloat value) {
|
||||
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<mln::style::WeatherColorLayer*>(layer);
|
||||
if (!weatherColorLayer || (value != 0.0f && value != 1.0f)) {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleField: event=FIELD_VALUE_SOURCE_SET result=REJECTED");
|
||||
return false;
|
||||
}
|
||||
weatherColorLayer->setFieldValueSource(value == 0.0f ? mln::style::FieldValueSource::Scalar
|
||||
: mln::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-advection-scale") {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=UNSUPPORTED_PROPERTY");
|
||||
return false;
|
||||
}
|
||||
auto* particleLayer = dynamic_cast<mln::style::RasterParticleLayer*>(layer);
|
||||
if (!particleLayer) {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=UNSUPPORTED_LAYER");
|
||||
return false;
|
||||
}
|
||||
if (!std::isfinite(value)) {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_FLOAT");
|
||||
return false;
|
||||
}
|
||||
if (property == "raster-particle-time-mix") {
|
||||
particleLayer->setTimeMix(value);
|
||||
} else if (property == "raster-particle-particle-count") {
|
||||
if (value < 0.0f || std::floor(value) != value) {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_PARTICLE_COUNT");
|
||||
return false;
|
||||
}
|
||||
const auto requestedCount = static_cast<uint32_t>(value);
|
||||
particleLayer->setParticleCount(requestedCount);
|
||||
if (particleLayer->getParticleCount() != requestedCount) {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_PARTICLE_COUNT");
|
||||
return false;
|
||||
}
|
||||
} else if (property == "raster-particle-line-width") {
|
||||
particleLayer->setLineWidth(value);
|
||||
} else if (property == "raster-particle-particle-length") {
|
||||
particleLayer->setParticleLength(value);
|
||||
} else if (property == "raster-particle-particle-opacity") {
|
||||
particleLayer->setParticleOpacity(value);
|
||||
} else if (property == "raster-particle-trail-opacity") {
|
||||
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) {
|
||||
mln::Log::Error(mln::Event::JNI, "ParticleGPU: event=STYLE_PROPERTY_SET result=INVALID_COLOR_MODE");
|
||||
return false;
|
||||
}
|
||||
particleLayer->setSpeedColorEnabled(value > 0.5f);
|
||||
}
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"ParticleGPU: event=STYLE_PROPERTY_SET layerId=" + jni::Make<std::string>(env, layerId) +
|
||||
" property=" + property + " value=" + std::to_string(value) + " result=SUCCESS");
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setWeatherColorOpacity(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::jfloat opacity) {
|
||||
if (!std::isfinite(opacity)) return false;
|
||||
auto* layer = dynamic_cast<mln::style::WeatherColorLayer*>(
|
||||
map->getStyle().getLayer(jni::Make<std::string>(env, layerId)));
|
||||
if (!layer) return false;
|
||||
layer->setOpacity(opacity);
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setStyleLayerVisibility(JNIEnv& env, const jni::String& layerId, const jni::jboolean visible) {
|
||||
auto* layer = map->getStyle().getLayer(jni::Make<std::string>(env, layerId));
|
||||
if (!layer) return false;
|
||||
layer->setVisibility(visible ? mln::style::VisibilityType::Visible : mln::style::VisibilityType::None);
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRasterParticleColors(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::Array<jni::jfloat>& particle,
|
||||
const jni::Array<jni::jfloat>& trail) {
|
||||
auto* layer = dynamic_cast<mln::style::RasterParticleLayer*>(map->getStyle().getLayer(jni::Make<std::string>(env, layerId)));
|
||||
if (!layer || particle.Length(env) != 4 || trail.Length(env) != 4) return false;
|
||||
std::array<float, 4> particleColor, trailColor;
|
||||
particle.GetRegion(env, 0, particleColor);
|
||||
trail.GetRegion(env, 0, trailColor);
|
||||
if (!mln::style::validWeatherRGBA(particleColor) || !mln::style::validWeatherRGBA(trailColor)) return false;
|
||||
layer->setParticleColor(particleColor);
|
||||
layer->setTrailColor(trailColor);
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRasterParticleColor(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::Array<jni::jfloat>& particle) {
|
||||
auto* layer = dynamic_cast<mln::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 (!mln::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<mln::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 (!mln::style::validWeatherRGBA(color)) return false;
|
||||
layer->setTrailColor(color);
|
||||
return true;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setRasterArraySourceTargetFps(JNIEnv& env,
|
||||
const jni::String& sourceId,
|
||||
const jni::jint targetFps) {
|
||||
auto* source = dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env, sourceId)));
|
||||
return source && source->setRuntimeTargetFps(static_cast<uint8_t>(targetFps));
|
||||
}
|
||||
|
||||
jni::jint NativeMapView::getRasterArraySourceTargetFps(JNIEnv& env, const jni::String& sourceId) {
|
||||
auto* source = dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env, sourceId)));
|
||||
return source ? source->runtimeTargetFps() : 0;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::setWeatherColorStops(JNIEnv& env,
|
||||
const jni::String& layerId,
|
||||
const jni::Array<jni::jfloat>& speeds,
|
||||
const jni::Array<jni::jfloat>& rgba) {
|
||||
auto* layer = dynamic_cast<mln::style::WeatherColorLayer*>(map->getStyle().getLayer(jni::Make<std::string>(env, layerId)));
|
||||
const auto count = speeds.Length(env);
|
||||
if (!layer || count < 2 || count > static_cast<jni::jsize>(mln::style::MaxWeatherColorStops) || rgba.Length(env) != count * 4) return false;
|
||||
std::vector<float> speedValues(count), rgbaValues(static_cast<size_t>(count) * 4);
|
||||
speeds.GetRegion(env, 0, speedValues);
|
||||
rgba.GetRegion(env, 0, rgbaValues);
|
||||
std::vector<mln::style::WeatherColorStop> stops;
|
||||
stops.reserve(count);
|
||||
for (jni::jsize i = 0; i < count; ++i) {
|
||||
std::array<float, 4> color;
|
||||
std::copy_n(rgbaValues.data() + i * 4, 4, color.data());
|
||||
stops.push_back({speedValues[i], color});
|
||||
}
|
||||
return layer->setColorStops(std::move(stops));
|
||||
}
|
||||
|
||||
jni::jint NativeMapView::getWeatherColorStopCapacity(JNIEnv&) {
|
||||
return static_cast<jni::jint>(mln::style::MaxWeatherColorStops);
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::advanceRasterArraySourceFieldWindow(JNIEnv& env, const jni::String& sourceId) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = map->getStyle().getSource(id);
|
||||
auto* rasterArraySource = dynamic_cast<mln::style::RasterArraySource*>(source);
|
||||
if (!rasterArraySource) {
|
||||
mln::Log::Error(mln::Event::JNI,
|
||||
"ParticleField: event=FIELD_WINDOW_UPDATE_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id);
|
||||
return false;
|
||||
}
|
||||
const auto result = rasterArraySource->advanceRuntimeFieldWindow();
|
||||
const bool success = result == mln::style::RasterArrayRuntimeFieldUpdateResult::Applied ||
|
||||
result == mln::style::RasterArrayRuntimeFieldUpdateResult::Noop;
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"ParticleField: event=FIELD_WINDOW_UPDATE_BRIDGE sourceId=" + id +
|
||||
" result=" + (success ? "SUCCESS" : "REJECTED"));
|
||||
return success;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::repeatRasterArraySourceFieldWindow(JNIEnv& env, const jni::String& sourceId) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = map->getStyle().getSource(id);
|
||||
auto* rasterArraySource = dynamic_cast<mln::style::RasterArraySource*>(source);
|
||||
if (!rasterArraySource) {
|
||||
mln::Log::Error(mln::Event::JNI,
|
||||
"ParticleField: event=FIELD_WINDOW_UPDATE_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id);
|
||||
return false;
|
||||
}
|
||||
const auto result = rasterArraySource->repeatRuntimeFieldWindow();
|
||||
const bool success = result == mln::style::RasterArrayRuntimeFieldUpdateResult::Noop;
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"ParticleField: event=FIELD_WINDOW_UPDATE_DUPLICATE_BRIDGE sourceId=" + id +
|
||||
" result=" + (success ? "NOOP" : "REJECTED"));
|
||||
return success;
|
||||
}
|
||||
|
||||
jni::jint NativeMapView::submitRasterArraySourceHostField(JNIEnv& env,
|
||||
const jni::String& sourceId,
|
||||
const jni::String& metadata,
|
||||
const jni::Array<jbyte>& payload) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if (!source) {
|
||||
mln::Log::Error(mln::Event::JNI,
|
||||
"ParticleField: event=HOST_FIELD_SUBMIT_REJECTED reason=SOURCE_NOT_FOUND sourceId=" + id);
|
||||
return static_cast<jni::jint>(mln::style::RasterArrayHostFieldSubmitResult::RejectedSourceMode);
|
||||
}
|
||||
NullCheck(env, &payload);
|
||||
std::vector<char> bytes(payload.Length(env));
|
||||
if (!bytes.empty()) {
|
||||
jni::GetArrayRegion(env, *payload, 0, bytes.size(), reinterpret_cast<jbyte*>(bytes.data()));
|
||||
}
|
||||
const auto result = source->submitHostField(jni::Make<std::string>(env, metadata), bytes);
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"ParticleField: event=HOST_FIELD_SUBMIT_BRIDGE sourceId=" + id +
|
||||
" result=" + mln::style::rasterArrayHostFieldSubmitResultName(result));
|
||||
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<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if (!source) return static_cast<jni::jint>(mln::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>(mln::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>(mln::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;
|
||||
};
|
||||
mln::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);
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"ParticleField: event=HOST_FIELD_V2_SUBMIT_BRIDGE sourceId=" + id + " scalar=" +
|
||||
(scalarPresent ? "present" : "absent") + " vector=" + (vectorPresent ? "present" : "absent") +
|
||||
" result=" + mln::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<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if (!source) return false;
|
||||
const bool cleared = source->clearHostFields();
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"WeatherRuntime: event=CLEAR_FIELDS sourceId=" + id + " result=" +
|
||||
(cleared ? "SUCCESS" : "REJECTED"));
|
||||
return cleared;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::resetRasterArraySourceWeatherRuntime(JNIEnv& env, const jni::String& sourceId) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if (!source) return false;
|
||||
const bool reset = source->resetWeatherRuntimeState();
|
||||
mln::Log::Info(mln::Event::JNI, "WeatherRuntime: event=TERMINAL_RESET sourceId=" + id +
|
||||
" result=" + (reset ? "SUCCESS" : "REJECTED"));
|
||||
return reset;
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::seekRasterArraySourceTimelineToTimestamp(JNIEnv& env,
|
||||
const jni::String& sourceId,
|
||||
const jni::jlong timestamp) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
return source && timestamp >= 0 && source->seekRuntimeTimelineToTimestamp(static_cast<uint64_t>(timestamp));
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::controlRasterArraySourceTimeline(JNIEnv& env,
|
||||
const jni::String& sourceId,
|
||||
const jni::jint command,
|
||||
const jni::jfloat value) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if (!source) return false;
|
||||
const bool success = source->controlRuntimeTimeline(command, value);
|
||||
mln::Log::Info(mln::Event::JNI,
|
||||
"ParticleTimeline: event=COMMAND sourceId=" + id + " command=" + std::to_string(command) +
|
||||
" result=" + (success ? "SUCCESS" : "REJECTED"));
|
||||
return success;
|
||||
}
|
||||
|
||||
jni::jint NativeMapView::submitRasterArraySourceEncodedField(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, const jni::String& fieldId, jni::jlong generation, jni::jlong dataVersion, const jni::String& contractJson, const jni::Array<jbyte>& qbin) {
|
||||
const auto id=jni::Make<std::string>(env,sourceId); auto* source=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(id));
|
||||
if(!source) return 0; // bridge-layer SOURCE_MISSING sentinel, never a Core result
|
||||
if(generation<=0 || dataVersion<0 || !qbin || !mln::particle::ingress::isValidEncodedFieldTransportSize(qbin.Length(env))) return static_cast<jni::jint>(mln::style::RasterArrayEncodedFieldSubmitResult::RejectedDecode);
|
||||
const auto contract=mln::particle::parseResolvedFieldProfileContractJson(jni::Make<std::string>(env,contractJson));
|
||||
if(!contract) return static_cast<jni::jint>(mln::style::RasterArrayEncodedFieldSubmitResult::RejectedContract);
|
||||
std::vector<uint8_t> bytes(qbin.Length(env)); if(!bytes.empty()) jni::GetArrayRegion(env,*qbin,0,bytes.size(),reinterpret_cast<jbyte*>(bytes.data()));
|
||||
const auto result=source->submitEncodedField(jni::Make<std::string>(env,fieldKey), {jni::Make<std::string>(env,fieldId),static_cast<uint64_t>(generation),static_cast<uint64_t>(dataVersion)}, *contract, bytes);
|
||||
return static_cast<jni::jint>(result);
|
||||
}
|
||||
jni::jint NativeMapView::clearRasterArraySourceEncodedField(JNIEnv& env,const jni::String& sourceId,const jni::String& fieldKey) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; return s->clearEncodedField(jni::Make<std::string>(env,fieldKey)) ? 1 : -1; }
|
||||
jni::jint NativeMapView::clearAllRasterArraySourceEncodedFields(JNIEnv& env,const jni::String& sourceId) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; s->clearAllEncodedFields(); return 1; }
|
||||
jni::jint NativeMapView::setRasterArraySourceEncodedColorBinding(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, jni::jint semanticID, jni::jint groupID, jni::jint valueClass, jni::jint unitID) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; if(semanticID<=0||semanticID>UINT16_MAX||groupID<0||groupID>UINT16_MAX||unitID<0||unitID>UINT16_MAX||(valueClass<0||valueClass>2))return -1; s->setColorBinding({jni::Make<std::string>(env,fieldKey),{static_cast<uint16_t>(semanticID),static_cast<uint16_t>(groupID)},static_cast<mln::particle::FieldValueClass>(valueClass),static_cast<uint16_t>(unitID)}); return 1; }
|
||||
jni::jint NativeMapView::setRasterArraySourceEncodedParticleBinding(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, jni::jint mode, jni::jint uSemanticID, jni::jint uGroupID, jni::jint vSemanticID, jni::jint vGroupID, jni::jint directionSemanticID, jni::jint directionGroupID, jni::jint unitID) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; const auto valid=[](jni::jint semantic,jni::jint group){return semantic>0&&semantic<=UINT16_MAX&&group>=0&&group<=UINT16_MAX;}; const auto inactive=[](jni::jint semantic,jni::jint group){return semantic==0&&group==0;}; if(unitID<0||unitID>UINT16_MAX)return -1; mln::particle::ParticleBinding binding; binding.fieldKey=jni::Make<std::string>(env,fieldKey); binding.expectedUnitID=static_cast<uint16_t>(unitID); if(mode==0&&valid(uSemanticID,uGroupID)&&valid(vSemanticID,vGroupID)&&inactive(directionSemanticID,directionGroupID)){binding.mode=mln::particle::ParticleBindingMode::VectorComponents;binding.uPlane={static_cast<uint16_t>(uSemanticID),static_cast<uint16_t>(uGroupID)};binding.vPlane={static_cast<uint16_t>(vSemanticID),static_cast<uint16_t>(vGroupID)};} else if(mode==1&&inactive(uSemanticID,uGroupID)&&inactive(vSemanticID,vGroupID)&&valid(directionSemanticID,directionGroupID)){binding.mode=mln::particle::ParticleBindingMode::DirectionAngle;binding.directionPlane={static_cast<uint16_t>(directionSemanticID),static_cast<uint16_t>(directionGroupID)};} else return -1; s->setParticleBinding(std::move(binding)); return 1; }
|
||||
jni::jint NativeMapView::setRasterArraySourceEncodedForecastTime(JNIEnv& env,const jni::String& sourceId,jni::jlong timestamp) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; return timestamp>=0&&s->setRequestedForecastTime(static_cast<uint64_t>(timestamp))?1:-1; }
|
||||
jni::jint NativeMapView::setRasterArraySourceEncodedForecastPlaybackState(JNIEnv& env,const jni::String& sourceId,jni::jboolean playing) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; s->setForecastPlaybackState(playing ? mln::particle::ForecastPlaybackState::Playing : mln::particle::ForecastPlaybackState::Paused); return 1; }
|
||||
jni::jint NativeMapView::setRasterArraySourceEncodedForecastPlaybackSpeed(JNIEnv& env,const jni::String& sourceId,jni::jfloat speed) { auto* s=dynamic_cast<mln::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; return s->setForecastPlaybackSpeed(speed)?1:-1; }
|
||||
|
||||
void NativeMapView::addLayer(JNIEnv& env, jlong nativeLayerPtr, const jni::String& before) {
|
||||
assert(nativeLayerPtr != 0);
|
||||
|
||||
@@ -1255,6 +1671,10 @@ jni::Local<jni::Object<Source>> NativeMapView::getSource(JNIEnv& env, const jni:
|
||||
return jni::NewLocal(env, Source::peerForCoreSource(env, *coreSource, *rendererFrontend, *map));
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::hasStyleSource(JNIEnv& env, const jni::String& sourceId) {
|
||||
return map->getStyle().getSource(jni::Make<std::string>(env, sourceId)) != nullptr;
|
||||
}
|
||||
|
||||
void NativeMapView::addSource(JNIEnv& env, const jni::Object<Source>& obj, jlong sourcePtr) {
|
||||
assert(sourcePtr != 0);
|
||||
|
||||
@@ -1454,6 +1874,11 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::addMarkers, "nativeAddMarkers"),
|
||||
METHOD(&NativeMapView::setDebug, "nativeSetDebug"),
|
||||
METHOD(&NativeMapView::getDebug, "nativeGetDebug"),
|
||||
METHOD(&NativeMapView::setRetainedUnderlayEnabled, "nativeSetRetainedUnderlayEnabled"),
|
||||
METHOD(&NativeMapView::setContinuousLayerRange, "nativeSetContinuousLayerRange"),
|
||||
METHOD(&NativeMapView::setRenderSegmentPlan, "nativeSetRenderSegmentPlan"),
|
||||
METHOD(&NativeMapView::getRenderSegmentDiagnostics, "nativeGetRenderSegmentDiagnostics"),
|
||||
METHOD(&NativeMapView::getRetainedUnderlayDiagnostics, "nativeGetRetainedUnderlayDiagnostics"),
|
||||
METHOD(&NativeMapView::getActionJournalLogFiles, "nativeGetActionJournalLogFiles"),
|
||||
METHOD(&NativeMapView::getActionJournalLog, "nativeGetActionJournalLog"),
|
||||
METHOD(&NativeMapView::clearActionJournalLog, "nativeClearActionJournalLog"),
|
||||
@@ -1487,6 +1912,33 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::getLight, "nativeGetLight"),
|
||||
METHOD(&NativeMapView::getLayers, "nativeGetLayers"),
|
||||
METHOD(&NativeMapView::getLayer, "nativeGetLayer"),
|
||||
METHOD(&NativeMapView::hasStyleLayer, "nativeHasStyleLayer"),
|
||||
METHOD(&NativeMapView::setStyleLayerFloatProperty, "nativeSetStyleLayerFloatProperty"),
|
||||
METHOD(&NativeMapView::setWeatherColorOpacity, "nativeSetWeatherColorOpacity"),
|
||||
METHOD(&NativeMapView::setStyleLayerVisibility, "nativeSetStyleLayerVisibility"),
|
||||
METHOD(&NativeMapView::setRasterParticleColors, "nativeSetRasterParticleColors"),
|
||||
METHOD(&NativeMapView::setRasterParticleColor, "nativeSetRasterParticleColor"),
|
||||
METHOD(&NativeMapView::setRasterParticleTrailColor, "nativeSetRasterParticleTrailColor"),
|
||||
METHOD(&NativeMapView::setRasterArraySourceTargetFps, "nativeSetRasterArraySourceTargetFps"),
|
||||
METHOD(&NativeMapView::getRasterArraySourceTargetFps, "nativeGetRasterArraySourceTargetFps"),
|
||||
METHOD(&NativeMapView::setWeatherColorStops, "nativeSetWeatherColorStops"),
|
||||
METHOD(&NativeMapView::getWeatherColorStopCapacity, "nativeGetWeatherColorStopCapacity"),
|
||||
METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"),
|
||||
METHOD(&NativeMapView::repeatRasterArraySourceFieldWindow, "nativeRepeatRasterArraySourceFieldWindow"),
|
||||
METHOD(&NativeMapView::submitRasterArraySourceHostField, "nativeSubmitRasterArraySourceHostField"),
|
||||
METHOD(&NativeMapView::submitRasterArraySourceHostFieldV2, "nativeSubmitRasterArraySourceHostFieldV2"),
|
||||
METHOD(&NativeMapView::clearRasterArraySourceHostFields, "nativeClearRasterArraySourceHostFields"),
|
||||
METHOD(&NativeMapView::resetRasterArraySourceWeatherRuntime, "nativeResetRasterArraySourceWeatherRuntime"),
|
||||
METHOD(&NativeMapView::seekRasterArraySourceTimelineToTimestamp, "nativeSeekRasterArraySourceTimelineToTimestamp"),
|
||||
METHOD(&NativeMapView::controlRasterArraySourceTimeline, "nativeControlRasterArraySourceTimeline"),
|
||||
METHOD(&NativeMapView::submitRasterArraySourceEncodedField, "nativeSubmitRasterArraySourceEncodedField"),
|
||||
METHOD(&NativeMapView::clearRasterArraySourceEncodedField, "nativeClearRasterArraySourceEncodedField"),
|
||||
METHOD(&NativeMapView::clearAllRasterArraySourceEncodedFields, "nativeClearAllRasterArraySourceEncodedFields"),
|
||||
METHOD(&NativeMapView::setRasterArraySourceEncodedColorBinding, "nativeSetRasterArraySourceEncodedColorBinding"),
|
||||
METHOD(&NativeMapView::setRasterArraySourceEncodedParticleBinding, "nativeSetRasterArraySourceEncodedParticleBinding"),
|
||||
METHOD(&NativeMapView::setRasterArraySourceEncodedForecastTime, "nativeSetRasterArraySourceEncodedForecastTime"),
|
||||
METHOD(&NativeMapView::setRasterArraySourceEncodedForecastPlaybackState, "nativeSetRasterArraySourceEncodedForecastPlaybackState"),
|
||||
METHOD(&NativeMapView::setRasterArraySourceEncodedForecastPlaybackSpeed, "nativeSetRasterArraySourceEncodedForecastPlaybackSpeed"),
|
||||
METHOD(&NativeMapView::addLayer, "nativeAddLayer"),
|
||||
METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"),
|
||||
METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"),
|
||||
@@ -1494,6 +1946,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"),
|
||||
METHOD(&NativeMapView::getSources, "nativeGetSources"),
|
||||
METHOD(&NativeMapView::getSource, "nativeGetSource"),
|
||||
METHOD(&NativeMapView::hasStyleSource, "nativeHasStyleSource"),
|
||||
METHOD(&NativeMapView::addSource, "nativeAddSource"),
|
||||
METHOD(&NativeMapView::removeSource, "nativeRemoveSource"),
|
||||
METHOD(&NativeMapView::addImage, "nativeAddImage"),
|
||||
|
||||
@@ -201,6 +201,16 @@ public:
|
||||
|
||||
jni::jboolean getDebug(JNIEnv&);
|
||||
|
||||
void setRetainedUnderlayEnabled(JNIEnv&, jni::jboolean);
|
||||
|
||||
jni::jboolean setContinuousLayerRange(JNIEnv&, const jni::String&, const jni::String&);
|
||||
|
||||
jni::jboolean setRenderSegmentPlan(JNIEnv&, const jni::String&);
|
||||
|
||||
jni::Local<jni::String> getRenderSegmentDiagnostics(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getRetainedUnderlayDiagnostics(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getActionJournalLogFiles(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Array<jni::String>> getActionJournalLog(JNIEnv&);
|
||||
@@ -281,6 +291,44 @@ public:
|
||||
jni::Local<jni::Array<jni::Object<Layer>>> getLayers(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Object<Layer>> getLayer(JNIEnv&, const jni::String&);
|
||||
jni::jboolean hasStyleLayer(JNIEnv&, const jni::String&);
|
||||
jni::jboolean setStyleLayerFloatProperty(JNIEnv&, const jni::String&, const jni::String&, jni::jfloat);
|
||||
jni::jboolean setWeatherColorOpacity(JNIEnv&, 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 setRasterArraySourceTargetFps(JNIEnv&, const jni::String&, jni::jint);
|
||||
jni::jint getRasterArraySourceTargetFps(JNIEnv&, const jni::String&);
|
||||
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&);
|
||||
jni::jboolean repeatRasterArraySourceFieldWindow(JNIEnv&, const jni::String&);
|
||||
jni::jint submitRasterArraySourceHostField(JNIEnv&,
|
||||
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 resetRasterArraySourceWeatherRuntime(JNIEnv&, const jni::String&);
|
||||
jni::jboolean seekRasterArraySourceTimelineToTimestamp(JNIEnv&, const jni::String&, jni::jlong);
|
||||
jni::jboolean controlRasterArraySourceTimeline(JNIEnv&, const jni::String&, jni::jint, jni::jfloat);
|
||||
jni::jint submitRasterArraySourceEncodedField(JNIEnv&, const jni::String&, const jni::String&, const jni::String&, jni::jlong, jni::jlong, const jni::String&, const jni::Array<jbyte>&);
|
||||
jni::jint clearRasterArraySourceEncodedField(JNIEnv&, const jni::String&, const jni::String&);
|
||||
jni::jint clearAllRasterArraySourceEncodedFields(JNIEnv&, const jni::String&);
|
||||
jni::jint setRasterArraySourceEncodedColorBinding(JNIEnv&, const jni::String&, const jni::String&, jni::jint, jni::jint, jni::jint, jni::jint);
|
||||
jni::jint setRasterArraySourceEncodedParticleBinding(JNIEnv&, const jni::String&, const jni::String&, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint);
|
||||
jni::jint setRasterArraySourceEncodedForecastTime(JNIEnv&, const jni::String&, jni::jlong);
|
||||
jni::jint setRasterArraySourceEncodedForecastPlaybackState(JNIEnv&, const jni::String&, jni::jboolean);
|
||||
jni::jint setRasterArraySourceEncodedForecastPlaybackSpeed(JNIEnv&, const jni::String&, jni::jfloat);
|
||||
|
||||
void addLayer(JNIEnv&, jlong, const jni::String&);
|
||||
|
||||
@@ -295,6 +343,7 @@ public:
|
||||
jni::Local<jni::Array<jni::Object<Source>>> getSources(JNIEnv&);
|
||||
|
||||
jni::Local<jni::Object<Source>> getSource(JNIEnv&, const jni::String&);
|
||||
jni::jboolean hasStyleSource(JNIEnv&, const jni::String&);
|
||||
|
||||
void addSource(JNIEnv&, const jni::Object<Source>&, jlong nativePtr);
|
||||
|
||||
|
||||
@@ -19,12 +19,19 @@
|
||||
#include "symbol_layer.hpp"
|
||||
#include "fill_extrusion_layer.hpp"
|
||||
#include "location_indicator_layer.hpp"
|
||||
#include <mln/layermanager/particle_validation_layer_factory.hpp>
|
||||
#include <mln/layermanager/raster_particle_layer_factory.hpp>
|
||||
#include <mln/layermanager/weather_color_layer_factory.hpp>
|
||||
|
||||
namespace mln {
|
||||
|
||||
namespace android {
|
||||
|
||||
LayerManagerAndroid::LayerManagerAndroid() {
|
||||
// VALIDATION ONLY: JSON style access; intentionally no Android product API/JNI peer.
|
||||
addLayerTypeCoreOnly(std::make_unique<ParticleValidationLayerFactory>());
|
||||
addLayerTypeCoreOnly(std::make_unique<RasterParticleLayerFactory>());
|
||||
addLayerTypeCoreOnly(std::make_unique<WeatherColorLayerFactory>());
|
||||
#if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME)
|
||||
addLayerTypeCoreOnly(std::make_unique<FillLayerFactory>());
|
||||
#elif !defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.annotation.Size;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@@ -43,6 +44,7 @@ import org.maplibre.android.log.Logger;
|
||||
import org.maplibre.android.offline.OfflineRegionDefinition;
|
||||
import org.maplibre.android.style.expressions.Expression;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -68,6 +70,7 @@ public final class MapLibreMap {
|
||||
private final OnGesturesManagerInteractionListener onGesturesManagerInteractionListener;
|
||||
private final List<Style.OnStyleLoaded> awaitingStyleGetters = new ArrayList<>();
|
||||
private final List<OnDeveloperAnimationListener> developerAnimationStartedListeners;
|
||||
private final List<OnDestroyListener> onDestroyListeners = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private Style.OnStyleLoaded styleLoadedCallback;
|
||||
@@ -81,8 +84,29 @@ public final class MapLibreMap {
|
||||
@Nullable
|
||||
private Style style;
|
||||
|
||||
/**
|
||||
* Optional weather facade attached to this concrete map instance. It is deliberately owned by
|
||||
* the map rather than by an application or bridge registry, so a style replacement and a map
|
||||
* teardown cannot accidentally leave a facade pointing at another map.
|
||||
*/
|
||||
@Nullable
|
||||
private WeatherRuntime weatherRuntime;
|
||||
|
||||
private boolean debugActive;
|
||||
private boolean started;
|
||||
private boolean destroyed;
|
||||
|
||||
/** Internal map-lifetime observer used by map-owned integrations. */
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public interface OnDestroyListener {
|
||||
void onDestroy();
|
||||
}
|
||||
|
||||
/** Removable registration for an internal observer of this concrete map's destruction. */
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public interface OnDestroyListenerRegistration {
|
||||
void remove();
|
||||
}
|
||||
|
||||
MapLibreMap(NativeMap map, Transform transform, UiSettings ui, Projection projection,
|
||||
OnGesturesManagerInteractionListener listener, CameraChangeDispatcher cameraChangeDispatcher,
|
||||
@@ -103,6 +127,38 @@ public final class MapLibreMap {
|
||||
nativeMapView.triggerRepaint();
|
||||
}
|
||||
|
||||
/** Enables or disables the renderer's map-scoped retained static underlay optimization. */
|
||||
public void setRetainedUnderlayEnabled(boolean enabled) {
|
||||
nativeMapView.setRetainedUnderlayEnabled(enabled);
|
||||
}
|
||||
|
||||
/** Configures the inclusive Style-layer interval rendered continuously. */
|
||||
public boolean setContinuousLayerRange(@NonNull String startLayerId, @NonNull String endLayerId) {
|
||||
return nativeMapView.setContinuousLayerRange(startLayerId, endLayerId);
|
||||
}
|
||||
|
||||
/** Configures the renderer execution topology using schema-versioned JSON. */
|
||||
public boolean setRenderSegmentPlan(@NonNull String planJson) {
|
||||
return nativeMapView.setRenderSegmentPlan(planJson);
|
||||
}
|
||||
|
||||
/** Returns the pull-based Style-to-Segment-to-Layer diagnostics snapshot as JSON. */
|
||||
@NonNull
|
||||
public String getRenderSegmentDiagnostics() {
|
||||
return nativeMapView.getRenderSegmentDiagnostics();
|
||||
}
|
||||
|
||||
/** Returns whether the retained static underlay optimization is enabled for this map. */
|
||||
public boolean isRetainedUnderlayEnabled() {
|
||||
return nativeMapView.getRetainedUnderlayDiagnostics().enabled;
|
||||
}
|
||||
|
||||
/** Returns a thread-safe, pull-based retained-underlay diagnostics snapshot. */
|
||||
@NonNull
|
||||
public RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics() {
|
||||
return nativeMapView.getRetainedUnderlayDiagnostics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query rendering statistics overlay status.
|
||||
*/
|
||||
@@ -171,6 +227,67 @@ public final class MapLibreMap {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the fully loaded Core style contains {@code layerId}.
|
||||
*
|
||||
* This intentionally queries Core by id instead of materialising a Java {@link
|
||||
* org.maplibre.android.style.layers.Layer} peer. It therefore also supports layer types that
|
||||
* are registered Core-only.
|
||||
*/
|
||||
public boolean hasStyleLayer(@NonNull String layerId) {
|
||||
return nativeMapView.hasStyleLayer(layerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the fully loaded Core style contains {@code sourceId}.
|
||||
*/
|
||||
public boolean hasStyleSource(@NonNull String sourceId) {
|
||||
return nativeMapView.hasStyleSource(sourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces this map's style-scoped weather facade. The caller must have already verified the
|
||||
* configured source and layers in the currently loaded style.
|
||||
*/
|
||||
@NonNull
|
||||
public WeatherRuntime replaceWeatherRuntime(@NonNull WeatherRuntime.Config config) {
|
||||
disposeWeatherRuntime();
|
||||
weatherRuntime = new WeatherRuntime(this, config);
|
||||
return weatherRuntime;
|
||||
}
|
||||
|
||||
/** Returns this map's attached weather facade, or null when no style-ready facade exists. */
|
||||
@Nullable
|
||||
public WeatherRuntime getWeatherRuntime() {
|
||||
return weatherRuntime;
|
||||
}
|
||||
|
||||
/** Disposes and detaches the style-scoped weather facade. Safe to call repeatedly. */
|
||||
public void disposeWeatherRuntime() {
|
||||
if (weatherRuntime != null) {
|
||||
weatherRuntime.dispose();
|
||||
weatherRuntime = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Registers a removable observer for destruction of this concrete map instance. */
|
||||
@NonNull
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public OnDestroyListenerRegistration addOnDestroyListener(@NonNull OnDestroyListener listener) {
|
||||
if (destroyed) {
|
||||
listener.onDestroy();
|
||||
return () -> { };
|
||||
}
|
||||
onDestroyListeners.add(listener);
|
||||
WeakReference<MapLibreMap> mapReference = new WeakReference<>(this);
|
||||
return () -> {
|
||||
MapLibreMap map = mapReference.get();
|
||||
if (map != null) {
|
||||
map.onDestroyListeners.remove(listener);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the hosting Activity/Fragment onStart() method is called.
|
||||
*/
|
||||
@@ -221,6 +338,16 @@ public final class MapLibreMap {
|
||||
* Called when the hosting Activity/Fragment onDestroy()/onDestroyView() method is called.
|
||||
*/
|
||||
void onDestroy() {
|
||||
if (destroyed) {
|
||||
return;
|
||||
}
|
||||
destroyed = true;
|
||||
List<OnDestroyListener> listeners = new ArrayList<>(onDestroyListeners);
|
||||
onDestroyListeners.clear();
|
||||
for (OnDestroyListener listener : listeners) {
|
||||
listener.onDestroy();
|
||||
}
|
||||
disposeWeatherRuntime();
|
||||
locationComponent.onDestroy();
|
||||
if (style != null) {
|
||||
style.clear();
|
||||
@@ -286,6 +413,119 @@ public final class MapLibreMap {
|
||||
return nativeMapView.getNativePtr();
|
||||
}
|
||||
|
||||
/** Updates a constant property on an existing Core style layer without requiring a Java Layer peer. */
|
||||
public boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value) {
|
||||
return nativeMapView.setStyleLayerFloatProperty(layerId, propertyName, value);
|
||||
}
|
||||
|
||||
/** Updates the opacity of an existing Core-owned WeatherColor layer without a Java Layer peer. */
|
||||
public boolean setWeatherColorOpacity(String layerId, float opacity) {
|
||||
return nativeMapView.setWeatherColorOpacity(layerId, opacity);
|
||||
}
|
||||
|
||||
/** Low-frequency style visibility used by the typed WeatherRuntime facade. */
|
||||
public boolean setStyleLayerVisibility(String layerId, boolean visible) {
|
||||
return nativeMapView.setStyleLayerVisibility(layerId, visible);
|
||||
}
|
||||
|
||||
/** Sets independent particle and trail RGBA colours without a field upload. */
|
||||
public boolean setRasterParticleColors(String layerId, float[] particleColor, float[] trailColor) {
|
||||
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);
|
||||
}
|
||||
|
||||
/** Sets the source-local weather cadence (0=auto, 30 or 60 FPS). */
|
||||
public boolean setRasterArraySourceTargetFps(String sourceId, int targetFps) {
|
||||
return nativeMapView.setRasterArraySourceTargetFps(sourceId, targetFps);
|
||||
}
|
||||
|
||||
public int getRasterArraySourceTargetFps(String sourceId) {
|
||||
return nativeMapView.getRasterArraySourceTargetFps(sourceId);
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
|
||||
/** Returns the Core-owned WeatherColor stop capacity for typed Host validation. */
|
||||
public int getWeatherColorStopCapacity() {
|
||||
return nativeMapView.getWeatherColorStopCapacity();
|
||||
}
|
||||
|
||||
/** Advances a Source-owned raster-array runtime Field window without reloading the Style. */
|
||||
public boolean advanceRasterArraySourceFieldWindow(String sourceId) {
|
||||
return nativeMapView.advanceRasterArraySourceFieldWindow(sourceId);
|
||||
}
|
||||
|
||||
/** Resubmits the Source-owned current T1 to validate the Runtime Field NOOP path. */
|
||||
public boolean repeatRasterArraySourceFieldWindow(String sourceId) {
|
||||
return nativeMapView.repeatRasterArraySourceFieldWindow(sourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits one canonical particle Field as provider-neutral metadata plus contiguous binary U/V bytes.
|
||||
* This is a low-frequency ingress; Timeline and Runtime Field Window ownership remain in Core.
|
||||
*/
|
||||
public int submitRasterArraySourceHostField(String sourceId, String metadata, byte[] payload) {
|
||||
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);
|
||||
}
|
||||
|
||||
/** Internal terminal reset for V3+V4 state on the same existing Style source. */
|
||||
boolean resetRasterArraySourceWeatherRuntime(String sourceId) {
|
||||
return nativeMapView.resetRasterArraySourceWeatherRuntime(sourceId);
|
||||
}
|
||||
|
||||
/** Requests a Core-owned T0/T1/MIX selection for a producer timestamp in milliseconds. */
|
||||
public boolean seekRasterArraySourceTimelineToTimestamp(String sourceId, long timestamp) {
|
||||
return nativeMapView.seekRasterArraySourceTimelineToTimestamp(sourceId, timestamp);
|
||||
}
|
||||
|
||||
/** Sends a low-frequency playback command to the Core-owned raster-array timeline. */
|
||||
public boolean controlRasterArraySourceTimeline(String sourceId, int command, float value) {
|
||||
return nativeMapView.controlRasterArraySourceTimeline(sourceId, command, value);
|
||||
}
|
||||
|
||||
/** V4-only QBIN ingress. It never enters the V3 runtime timeline. */
|
||||
public int submitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin) { return nativeMapView.submitRasterArraySourceEncodedField(sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin); }
|
||||
public int clearRasterArraySourceEncodedField(String sourceId, String fieldKey) { return nativeMapView.clearRasterArraySourceEncodedField(sourceId, fieldKey); }
|
||||
public int clearAllRasterArraySourceEncodedFields(String sourceId) { return nativeMapView.clearAllRasterArraySourceEncodedFields(sourceId); }
|
||||
public int setRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID) { return nativeMapView.setRasterArraySourceEncodedColorBinding(sourceId, fieldKey, semanticID, groupID, valueClass, unitID); }
|
||||
public int setRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID) { return nativeMapView.setRasterArraySourceEncodedParticleBinding(sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, unitID); }
|
||||
public int setRasterArraySourceEncodedForecastTime(String sourceId, long timestamp) { return nativeMapView.setRasterArraySourceEncodedForecastTime(sourceId, timestamp); }
|
||||
public int setRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing) { return nativeMapView.setRasterArraySourceEncodedForecastPlaybackState(sourceId, playing); }
|
||||
public int setRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed) { return nativeMapView.setRasterArraySourceEncodedForecastPlaybackSpeed(sourceId, speed); }
|
||||
|
||||
public static final int HOST_FIELD_REJECTED_SOURCE_MODE = -1;
|
||||
public static final int HOST_FIELD_REJECTED_ADAPTER = -2;
|
||||
public static final int HOST_FIELD_REJECTED_INCOMPATIBLE = -3;
|
||||
public static final int HOST_FIELD_REJECTED_NON_MONOTONIC = -4;
|
||||
public static final int HOST_FIELD_REJECTED_CAPACITY = -5;
|
||||
public static final int HOST_FIELD_ACCEPTED_PENDING_INITIAL_WINDOW = 1;
|
||||
public static final int HOST_FIELD_INITIAL_WINDOW_READY = 2;
|
||||
public static final int HOST_FIELD_ACCEPTED_NEXT_SNAPSHOT = 3;
|
||||
public static final int HOST_FIELD_DUPLICATE_NOOP = 4;
|
||||
|
||||
// Style
|
||||
|
||||
/**
|
||||
@@ -1138,6 +1378,9 @@ public final class MapLibreMap {
|
||||
* @see Style
|
||||
*/
|
||||
public void setStyle(Style.Builder builder, final Style.OnStyleLoaded callback) {
|
||||
// A WeatherRuntime only controls objects from one loaded style. Never let it survive a
|
||||
// replacement where identical IDs could refer to newly-created style objects.
|
||||
disposeWeatherRuntime();
|
||||
styleLoadedCallback = callback;
|
||||
locationComponent.onStartLoadingMap();
|
||||
if (style != null) {
|
||||
|
||||
@@ -139,6 +139,54 @@ interface NativeMap {
|
||||
|
||||
Layer getLayer(String layerId);
|
||||
|
||||
boolean hasStyleLayer(@NonNull String layerId);
|
||||
|
||||
boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value);
|
||||
|
||||
boolean setWeatherColorOpacity(String layerId, float opacity);
|
||||
|
||||
boolean setStyleLayerVisibility(String layerId, boolean visible);
|
||||
|
||||
boolean setRasterParticleColors(String layerId, float[] particleColor, float[] trailColor);
|
||||
|
||||
boolean setRasterParticleColor(String layerId, float[] particleColor);
|
||||
|
||||
boolean setRasterParticleTrailColor(String layerId, float[] trailColor);
|
||||
|
||||
boolean setRasterArraySourceTargetFps(String sourceId, int targetFps);
|
||||
|
||||
int getRasterArraySourceTargetFps(String sourceId);
|
||||
|
||||
boolean setWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops);
|
||||
|
||||
int getWeatherColorStopCapacity();
|
||||
|
||||
boolean advanceRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
boolean repeatRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
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 resetRasterArraySourceWeatherRuntime(String sourceId);
|
||||
|
||||
boolean seekRasterArraySourceTimelineToTimestamp(String sourceId, long timestamp);
|
||||
|
||||
boolean controlRasterArraySourceTimeline(String sourceId, int command, float value);
|
||||
|
||||
int submitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin);
|
||||
int clearRasterArraySourceEncodedField(String sourceId, String fieldKey);
|
||||
int clearAllRasterArraySourceEncodedFields(String sourceId);
|
||||
int setRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID);
|
||||
int setRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID);
|
||||
int setRasterArraySourceEncodedForecastTime(String sourceId, long timestamp);
|
||||
int setRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing);
|
||||
int setRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed);
|
||||
|
||||
boolean removeLayer(@NonNull String layerId);
|
||||
|
||||
boolean removeLayer(@NonNull Layer layer);
|
||||
@@ -152,6 +200,8 @@ interface NativeMap {
|
||||
|
||||
Source getSource(@NonNull String sourceId);
|
||||
|
||||
boolean hasStyleSource(@NonNull String sourceId);
|
||||
|
||||
boolean removeSource(@NonNull String sourceId);
|
||||
|
||||
boolean removeSource(@NonNull Source source);
|
||||
@@ -237,6 +287,16 @@ interface NativeMap {
|
||||
|
||||
boolean getDebug();
|
||||
|
||||
void setRetainedUnderlayEnabled(boolean enabled);
|
||||
|
||||
boolean setContinuousLayerRange(String startLayerId, String endLayerId);
|
||||
|
||||
boolean setRenderSegmentPlan(String planJson);
|
||||
|
||||
String getRenderSegmentDiagnostics();
|
||||
|
||||
RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics();
|
||||
|
||||
String[] getActionJournalLogFiles();
|
||||
|
||||
String[] getActionJournalLog();
|
||||
|
||||
@@ -653,6 +653,41 @@ final class NativeMapView implements NativeMap {
|
||||
return nativeGetDebug();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRetainedUnderlayEnabled(boolean enabled) {
|
||||
if (checkState("setRetainedUnderlayEnabled")) {
|
||||
return;
|
||||
}
|
||||
nativeSetRetainedUnderlayEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setContinuousLayerRange(@NonNull String startLayerId, @NonNull String endLayerId) {
|
||||
if (checkState("setContinuousLayerRange")) return false;
|
||||
return nativeSetContinuousLayerRange(startLayerId, endLayerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRenderSegmentPlan(@NonNull String planJson) {
|
||||
if (checkState("setRenderSegmentPlan")) return false;
|
||||
return nativeSetRenderSegmentPlan(planJson);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getRenderSegmentDiagnostics() {
|
||||
if (checkState("getRenderSegmentDiagnostics")) return "{\"schemaVersion\":1,\"valid\":false}";
|
||||
return nativeGetRenderSegmentDiagnostics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetainedUnderlayDiagnostics getRetainedUnderlayDiagnostics() {
|
||||
if (checkState("getRetainedUnderlayDiagnostics")) {
|
||||
return RetainedUnderlayDiagnostics.unavailable();
|
||||
}
|
||||
return new RetainedUnderlayDiagnostics(nativeGetRetainedUnderlayDiagnostics());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getActionJournalLogFiles() {
|
||||
if (checkState("getActionJournalLogFiles")) {
|
||||
@@ -950,6 +985,118 @@ final class NativeMapView implements NativeMap {
|
||||
return nativeGetLayer(layerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStyleLayer(@NonNull String layerId) {
|
||||
return !checkState("hasStyleLayer") && nativeHasStyleLayer(layerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setStyleLayerFloatProperty(String layerId, String propertyName, float value) {
|
||||
return !checkState("setStyleLayerFloatProperty")
|
||||
&& nativeSetStyleLayerFloatProperty(layerId, propertyName, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setWeatherColorOpacity(String layerId, float opacity) {
|
||||
return !checkState("setWeatherColorOpacity") && nativeSetWeatherColorOpacity(layerId, opacity);
|
||||
}
|
||||
|
||||
public boolean setStyleLayerVisibility(String layerId, boolean visible) {
|
||||
return !checkState("setStyleLayerVisibility") && nativeSetStyleLayerVisibility(layerId, visible);
|
||||
}
|
||||
|
||||
public boolean setRasterParticleColors(String layerId, float[] particleColor, float[] trailColor) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRasterArraySourceTargetFps(String sourceId, int targetFps) {
|
||||
return !checkState("setRasterArraySourceTargetFps") && nativeSetRasterArraySourceTargetFps(sourceId, targetFps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRasterArraySourceTargetFps(String sourceId) {
|
||||
return checkState("getRasterArraySourceTargetFps") ? 0 : nativeGetRasterArraySourceTargetFps(sourceId);
|
||||
}
|
||||
|
||||
public boolean setWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops) {
|
||||
return !checkState("setWeatherColorStops") && nativeSetWeatherColorStops(layerId, speedsMps, rgbaStops);
|
||||
}
|
||||
|
||||
public int getWeatherColorStopCapacity() {
|
||||
return checkState("getWeatherColorStopCapacity") ? 0 : nativeGetWeatherColorStopCapacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advanceRasterArraySourceFieldWindow(String sourceId) {
|
||||
return !checkState("advanceRasterArraySourceFieldWindow")
|
||||
&& nativeAdvanceRasterArraySourceFieldWindow(sourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean repeatRasterArraySourceFieldWindow(String sourceId) {
|
||||
return !checkState("repeatRasterArraySourceFieldWindow")
|
||||
&& nativeRepeatRasterArraySourceFieldWindow(sourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int submitRasterArraySourceHostField(String sourceId, String metadata, byte[] payload) {
|
||||
return checkState("submitRasterArraySourceHostField")
|
||||
? MapLibreMap.HOST_FIELD_REJECTED_SOURCE_MODE
|
||||
: 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")
|
||||
&& nativeClearRasterArraySourceHostFields(sourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetRasterArraySourceWeatherRuntime(String sourceId) {
|
||||
return !checkState("resetRasterArraySourceWeatherRuntime")
|
||||
&& nativeResetRasterArraySourceWeatherRuntime(sourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean seekRasterArraySourceTimelineToTimestamp(String sourceId, long timestamp) {
|
||||
return !checkState("seekRasterArraySourceTimelineToTimestamp")
|
||||
&& nativeSeekRasterArraySourceTimelineToTimestamp(sourceId, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean controlRasterArraySourceTimeline(String sourceId, int command, float value) {
|
||||
return !checkState("controlRasterArraySourceTimeline")
|
||||
&& nativeControlRasterArraySourceTimeline(sourceId, command, value);
|
||||
}
|
||||
|
||||
@Override public int submitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin) { return checkState("submitRasterArraySourceEncodedField") ? 0 : nativeSubmitRasterArraySourceEncodedField(sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin); }
|
||||
@Override public int clearRasterArraySourceEncodedField(String sourceId, String fieldKey) { return checkState("clearRasterArraySourceEncodedField") ? 0 : nativeClearRasterArraySourceEncodedField(sourceId, fieldKey); }
|
||||
@Override public int clearAllRasterArraySourceEncodedFields(String sourceId) { return checkState("clearAllRasterArraySourceEncodedFields") ? 0 : nativeClearAllRasterArraySourceEncodedFields(sourceId); }
|
||||
@Override public int setRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID) { return checkState("setRasterArraySourceEncodedColorBinding") ? 0 : nativeSetRasterArraySourceEncodedColorBinding(sourceId, fieldKey, semanticID, groupID, valueClass, unitID); }
|
||||
@Override public int setRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID) { return checkState("setRasterArraySourceEncodedParticleBinding") ? 0 : nativeSetRasterArraySourceEncodedParticleBinding(sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, unitID); }
|
||||
@Override public int setRasterArraySourceEncodedForecastTime(String sourceId, long timestamp) { return checkState("setRasterArraySourceEncodedForecastTime") ? 0 : nativeSetRasterArraySourceEncodedForecastTime(sourceId, timestamp); }
|
||||
@Override public int setRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing) { return checkState("setRasterArraySourceEncodedForecastPlaybackState") ? 0 : nativeSetRasterArraySourceEncodedForecastPlaybackState(sourceId, playing); }
|
||||
@Override public int setRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed) { return checkState("setRasterArraySourceEncodedForecastPlaybackSpeed") ? 0 : nativeSetRasterArraySourceEncodedForecastPlaybackSpeed(sourceId, speed); }
|
||||
|
||||
@Override
|
||||
public void addLayer(@NonNull Layer layer) {
|
||||
if (checkState("addLayer")) {
|
||||
@@ -1039,6 +1186,11 @@ final class NativeMapView implements NativeMap {
|
||||
return nativeGetSource(sourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStyleSource(@NonNull String sourceId) {
|
||||
return !checkState("hasStyleSource") && nativeHasStyleSource(sourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSource(@NonNull Source source) {
|
||||
if (checkState("addSource")) {
|
||||
@@ -1598,6 +1750,21 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep
|
||||
private native boolean nativeGetDebug();
|
||||
|
||||
@Keep
|
||||
private native void nativeSetRetainedUnderlayEnabled(boolean enabled);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeSetContinuousLayerRange(String startLayerId, String endLayerId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeSetRenderSegmentPlan(String planJson);
|
||||
|
||||
@Keep
|
||||
private native String nativeGetRenderSegmentDiagnostics();
|
||||
|
||||
@Keep
|
||||
private native String[] nativeGetRetainedUnderlayDiagnostics();
|
||||
|
||||
@Keep
|
||||
private native String[] nativeGetActionJournalLogFiles();
|
||||
|
||||
@@ -1688,6 +1855,57 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep
|
||||
private native Layer nativeGetLayer(String layerId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeHasStyleLayer(String layerId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeSetStyleLayerFloatProperty(String layerId, String propertyName, float value);
|
||||
|
||||
@Keep private native boolean nativeSetWeatherColorOpacity(String layerId, float opacity);
|
||||
|
||||
@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 nativeSetRasterArraySourceTargetFps(String sourceId, int targetFps);
|
||||
@Keep private native int nativeGetRasterArraySourceTargetFps(String sourceId);
|
||||
@Keep private native boolean nativeSetWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops);
|
||||
@Keep private native int nativeGetWeatherColorStopCapacity();
|
||||
|
||||
@Keep
|
||||
private native boolean nativeAdvanceRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeRepeatRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
@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);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeResetRasterArraySourceWeatherRuntime(String sourceId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeSeekRasterArraySourceTimelineToTimestamp(String sourceId, long timestamp);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeControlRasterArraySourceTimeline(String sourceId, int command, float value);
|
||||
@Keep private native int nativeSubmitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin);
|
||||
@Keep private native int nativeClearRasterArraySourceEncodedField(String sourceId, String fieldKey);
|
||||
@Keep private native int nativeClearAllRasterArraySourceEncodedFields(String sourceId);
|
||||
@Keep private native int nativeSetRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID);
|
||||
@Keep private native int nativeSetRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID);
|
||||
@Keep private native int nativeSetRasterArraySourceEncodedForecastTime(String sourceId, long timestamp);
|
||||
@Keep private native int nativeSetRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing);
|
||||
@Keep private native int nativeSetRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed);
|
||||
|
||||
@Keep
|
||||
private native void nativeAddLayer(long layerPtr, String before) throws CannotAddLayerException;
|
||||
|
||||
@@ -1711,6 +1929,9 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep
|
||||
private native Source nativeGetSource(String sourceId);
|
||||
|
||||
@Keep
|
||||
private native boolean nativeHasStyleSource(String sourceId);
|
||||
|
||||
@Keep
|
||||
private native void nativeAddSource(Source source, long sourcePtr) throws CannotAddSourceException;
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/** A pull-based snapshot of this map's retained-underlay renderer state. */
|
||||
@Keep
|
||||
public final class RetainedUnderlayDiagnostics {
|
||||
public final boolean enabled;
|
||||
public final long build;
|
||||
public final long reuse;
|
||||
public final long fallback;
|
||||
public final long invalidation;
|
||||
@NonNull public final String lastReason;
|
||||
public final boolean rangeConfigured;
|
||||
public final boolean rangeValid;
|
||||
@NonNull public final String startLayerId;
|
||||
@NonNull public final String endLayerId;
|
||||
public final int startIndex;
|
||||
public final int endIndex;
|
||||
public final int continuousBegin;
|
||||
public final int continuousEndExclusive;
|
||||
public final boolean bottomPresent;
|
||||
@NonNull public final String bottomCacheState;
|
||||
public final long bottomBuild;
|
||||
public final long bottomReuse;
|
||||
public final long bottomInvalidation;
|
||||
@NonNull public final String bottomLastInvalidationReason;
|
||||
public final long bottomTextureCreate;
|
||||
public final long bottomCompositeCreate;
|
||||
public final long bottomResourceRelease;
|
||||
public final boolean topPresent;
|
||||
@NonNull public final String topCacheState;
|
||||
public final long topBuild;
|
||||
public final long topReuse;
|
||||
public final long topInvalidation;
|
||||
@NonNull public final String topLastInvalidationReason;
|
||||
public final long topTextureCreate;
|
||||
public final long topCompositeCreate;
|
||||
public final long topResourceRelease;
|
||||
public final boolean rangeFallback;
|
||||
public final boolean fastPathEligible;
|
||||
public final boolean fastPathActive;
|
||||
@NonNull public final String fastPathFallbackReason;
|
||||
public final long fastPathHit;
|
||||
public final long fastPathMiss;
|
||||
public final long fullCpuFrame;
|
||||
|
||||
RetainedUnderlayDiagnostics(@NonNull String[] values) {
|
||||
if (values.length != 39) {
|
||||
throw new IllegalArgumentException("Retained-range diagnostics require 39 values");
|
||||
}
|
||||
enabled = Boolean.parseBoolean(values[0]);
|
||||
build = Long.parseLong(values[1]);
|
||||
reuse = Long.parseLong(values[2]);
|
||||
fallback = Long.parseLong(values[3]);
|
||||
invalidation = Long.parseLong(values[4]);
|
||||
lastReason = values[5];
|
||||
rangeConfigured = Boolean.parseBoolean(values[6]);
|
||||
rangeValid = Boolean.parseBoolean(values[7]);
|
||||
startLayerId = values[8];
|
||||
endLayerId = values[9];
|
||||
startIndex = Integer.parseInt(values[10]);
|
||||
endIndex = Integer.parseInt(values[11]);
|
||||
continuousBegin = Integer.parseInt(values[12]);
|
||||
continuousEndExclusive = Integer.parseInt(values[13]);
|
||||
bottomPresent = Boolean.parseBoolean(values[14]);
|
||||
bottomCacheState = values[15];
|
||||
bottomBuild = Long.parseLong(values[16]);
|
||||
bottomReuse = Long.parseLong(values[17]);
|
||||
bottomInvalidation = Long.parseLong(values[18]);
|
||||
bottomLastInvalidationReason = values[19];
|
||||
bottomTextureCreate = Long.parseLong(values[20]);
|
||||
bottomCompositeCreate = Long.parseLong(values[21]);
|
||||
bottomResourceRelease = Long.parseLong(values[22]);
|
||||
topPresent = Boolean.parseBoolean(values[23]);
|
||||
topCacheState = values[24];
|
||||
topBuild = Long.parseLong(values[25]);
|
||||
topReuse = Long.parseLong(values[26]);
|
||||
topInvalidation = Long.parseLong(values[27]);
|
||||
topLastInvalidationReason = values[28];
|
||||
topTextureCreate = Long.parseLong(values[29]);
|
||||
topCompositeCreate = Long.parseLong(values[30]);
|
||||
topResourceRelease = Long.parseLong(values[31]);
|
||||
rangeFallback = Boolean.parseBoolean(values[32]);
|
||||
fastPathEligible = Boolean.parseBoolean(values[33]);
|
||||
fastPathActive = Boolean.parseBoolean(values[34]);
|
||||
fastPathFallbackReason = values[35];
|
||||
fastPathHit = Long.parseLong(values[36]);
|
||||
fastPathMiss = Long.parseLong(values[37]);
|
||||
fullCpuFrame = Long.parseLong(values[38]);
|
||||
}
|
||||
|
||||
static RetainedUnderlayDiagnostics unavailable() {
|
||||
return new RetainedUnderlayDiagnostics(new String[] {
|
||||
"true", "0", "0", "0", "0", "unavailable", "false", "false", "", "", "-1", "-1",
|
||||
"-1", "-1", "false", "disabled", "0", "0", "0", "none", "0", "0", "0",
|
||||
"false", "disabled", "0", "0", "0", "none", "0", "0", "0", "false", "false",
|
||||
"false", "no-prepared-tree", "0", "0", "0"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Main-thread, low-frequency Host facade for one raster-array weather runtime.
|
||||
* 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; } }
|
||||
public enum TargetFps { AUTO(0), FPS_30(30), FPS_60(60); final int value; TargetFps(int value) { this.value = value; } }
|
||||
public enum FieldSubmitResult {
|
||||
REJECTED_SOURCE_MODE(-1), REJECTED_ADAPTER(-2), REJECTED_INCOMPATIBLE(-3), REJECTED_NON_MONOTONIC(-4), REJECTED_CAPACITY(-5),
|
||||
ACCEPTED_PENDING_INITIAL_WINDOW(1), INITIAL_WINDOW_READY(2), ACCEPTED_NEXT_SNAPSHOT(3), DUPLICATE(4), UNKNOWN(Integer.MIN_VALUE);
|
||||
final int code; FieldSubmitResult(int code) { this.code = code; }
|
||||
public int getCode() { return code; }
|
||||
static FieldSubmitResult fromCode(int code) { for (FieldSubmitResult value : values()) if (value.code == code) return value; return UNKNOWN; }
|
||||
public boolean accepted() { return code > 0; }
|
||||
}
|
||||
public enum CommandResult { ACCEPTED, REJECTED }
|
||||
public enum EncodedFieldSubmitResult {
|
||||
ACCEPTED_INSERTED(1), ACCEPTED_REPLACED_REVISION(2), DUPLICATE(3), REJECTED_DECODE(-1), REJECTED_CONTRACT(-2), REJECTED_INVALID_FIELD_KEY(-3), REJECTED_IDENTITY_CONFLICT(-4), REJECTED_STALE_REVISION(-5), REJECTED_INCOMPATIBLE(-6), REJECTED_CAPACITY(-7);
|
||||
final int code; EncodedFieldSubmitResult(int code) { this.code = code; }
|
||||
static EncodedFieldSubmitResult fromCode(int code) { for (EncodedFieldSubmitResult v : values()) if (v.code == code) return v; throw new IllegalArgumentException("unknown encoded submit code"); }
|
||||
}
|
||||
|
||||
public static final class Config {
|
||||
public final String sourceId;
|
||||
public final String weatherColorLayerId;
|
||||
public final String particleLayerId;
|
||||
public Config(@NonNull String sourceId, @NonNull String weatherColorLayerId, @NonNull String particleLayerId) {
|
||||
if (sourceId.isEmpty() || weatherColorLayerId.isEmpty() || particleLayerId.isEmpty()) throw new IllegalArgumentException("weather runtime IDs must be non-empty");
|
||||
this.sourceId = sourceId; this.weatherColorLayerId = weatherColorLayerId; this.particleLayerId = particleLayerId;
|
||||
}
|
||||
}
|
||||
public static final class ColorStop {
|
||||
public final float speedMps, red, green, blue, alpha;
|
||||
public ColorStop(float speedMps, float red, float green, float blue, float alpha) {
|
||||
this.speedMps = speedMps; this.red = red; this.green = green; this.blue = blue; this.alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
private final MapLibreMap map;
|
||||
private final Config config;
|
||||
private final int weatherColorStopCapacity;
|
||||
private State state = State.WAITING_FOR_FIELDS;
|
||||
private boolean weatherColorVisible = true;
|
||||
private boolean particleVisible = true;
|
||||
private PlaybackState playbackState = PlaybackState.PAUSE;
|
||||
private TargetFps targetFps = TargetFps.AUTO;
|
||||
// V4 owns its window in Core, so it never transitions the legacy V3 state
|
||||
// to READY. This only owns Android's next-frame wake, not a second timeline.
|
||||
// This is a Host-command bookkeeping hint for scheduling only. Core still
|
||||
// decides whether a resolved V4 window may actually advance simulation.
|
||||
@Nullable private String v4ParticleBindingFieldKey;
|
||||
private boolean v4ForecastPlaying;
|
||||
private final Set<String> submittedEncodedFieldKeys = new HashSet<>();
|
||||
private boolean disposed;
|
||||
private Handler cadenceHandler;
|
||||
private final Runnable cadenceWake = new Runnable() {
|
||||
@Override public void run() { runCadenceWake(); }
|
||||
};
|
||||
|
||||
public WeatherRuntime(@NonNull MapLibreMap map, @NonNull Config config) {
|
||||
this.map = map; this.config = config; this.weatherColorStopCapacity = map.getWeatherColorStopCapacity();
|
||||
}
|
||||
public State getState() { return state; }
|
||||
public Config getConfig() { return config; }
|
||||
public boolean isDisposed() { return disposed; }
|
||||
/** Ends this map/style-scoped facade. It is safe to call repeatedly. */
|
||||
public void dispose() {
|
||||
if (disposed) return;
|
||||
cancelCadenceWake();
|
||||
// If Style has already dropped the source, there is no Source-owned V4 store
|
||||
// left to reset; otherwise disposal must not claim success before Core reset.
|
||||
if (map.hasStyleSource(config.sourceId)) {
|
||||
map.setStyleLayerVisibility(config.weatherColorLayerId, false);
|
||||
map.setStyleLayerVisibility(config.particleLayerId, false);
|
||||
if (!map.resetRasterArraySourceWeatherRuntime(config.sourceId)) {
|
||||
throw new IllegalStateException("WeatherRuntime terminal Core reset failed");
|
||||
}
|
||||
}
|
||||
submittedEncodedFieldKeys.clear();
|
||||
v4ParticleBindingFieldKey = null;
|
||||
v4ForecastPlaying = false;
|
||||
playbackState = PlaybackState.PAUSE;
|
||||
state = State.DISABLED;
|
||||
disposed = true;
|
||||
}
|
||||
/** Core-owned capacity; Host never sees the shader's UBO layout. */
|
||||
public int getWeatherColorStopCapacity() { return weatherColorStopCapacity; }
|
||||
|
||||
public FieldSubmitResult submitWindField(@NonNull String metadataJson, @NonNull byte[] payloadBytes) {
|
||||
requireAttached();
|
||||
requireEnabled();
|
||||
if (metadataJson.isEmpty() || payloadBytes.length == 0) return FieldSubmitResult.REJECTED_ADAPTER;
|
||||
FieldSubmitResult result = FieldSubmitResult.fromCode(map.submitRasterArraySourceHostField(config.sourceId, metadataJson, payloadBytes));
|
||||
state = stateAfterSubmit(state, result);
|
||||
if (state == State.READY && result.accepted()) scheduleCadenceWake();
|
||||
return result;
|
||||
}
|
||||
/** Isolated V4 ingress: it deliberately does not mutate V3 state/cadence/timeline. */
|
||||
public EncodedFieldSubmitResult submitEncodedField(@NonNull String fieldKey, @NonNull String fieldId, long generation, long dataVersion, @NonNull String contractJson, @NonNull byte[] qbin) {
|
||||
requireAttached(); requireEnabled();
|
||||
if (!WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey) || !WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldId) || !WeatherRuntimeV4Guards.hasValidEncodedContractUtf8Length(contractJson) || !hasValidEncodedContractProfileId(contractJson) || generation <= 0 || dataVersion < 0 || !WeatherRuntimeV4Guards.hasValidEncodedQbin(qbin)) throw new IllegalArgumentException("invalid encoded field argument");
|
||||
int code = map.submitRasterArraySourceEncodedField(config.sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin);
|
||||
if (code == 0) throw new IllegalStateException("SOURCE_MISSING");
|
||||
EncodedFieldSubmitResult result = EncodedFieldSubmitResult.fromCode(code);
|
||||
if (result.code > 0) { submittedEncodedFieldKeys.add(fieldKey); cancelCadenceWake(); scheduleCadenceWake(); }
|
||||
return result;
|
||||
}
|
||||
private static boolean hasValidEncodedContractProfileId(String contractJson) {
|
||||
if (contractJson == null) return false;
|
||||
try {
|
||||
return WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(new JSONObject(contractJson).optString("profileID", ""));
|
||||
} catch (JSONException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public CommandResult clearEncodedField(@NonNull String fieldKey) { requireAttached(); if (!WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey)) return CommandResult.REJECTED; CommandResult result = encodedCommand(map.clearRasterArraySourceEncodedField(config.sourceId, fieldKey)); if (result == CommandResult.ACCEPTED) { submittedEncodedFieldKeys.remove(fieldKey); cancelCadenceWake(); scheduleCadenceWake(); } return result; }
|
||||
public CommandResult clearAllEncodedFields() { requireAttached(); CommandResult result = encodedCommand(map.clearAllRasterArraySourceEncodedFields(config.sourceId)); if (result == CommandResult.ACCEPTED) { submittedEncodedFieldKeys.clear(); cancelCadenceWake(); scheduleCadenceWake(); } return result; }
|
||||
/** V4 binding intent; unit and all mode selectors reach Core unchanged. */
|
||||
public CommandResult setEncodedColorBinding(@NonNull String fieldKey, int semanticID, int groupID, int valueClass, int expectedUnitID) {
|
||||
requireAttached(); return !WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey) ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedColorBinding(config.sourceId, fieldKey, semanticID, groupID, valueClass, expectedUnitID));
|
||||
}
|
||||
/** ABI reserves all selector slots: inactive slots must be exactly (0, 0). */
|
||||
public CommandResult setEncodedParticleBinding(@NonNull String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int expectedUnitID) {
|
||||
requireAttached(); if (!WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey)) return CommandResult.REJECTED; CommandResult result = encodedCommand(map.setRasterArraySourceEncodedParticleBinding(config.sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, expectedUnitID)); if (result == CommandResult.ACCEPTED) { v4ParticleBindingFieldKey = fieldKey; cancelCadenceWake(); scheduleCadenceWake(); } return result;
|
||||
}
|
||||
public CommandResult setEncodedForecastTime(long timestamp) { requireAttached(); return timestamp < 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastTime(config.sourceId, timestamp)); }
|
||||
/** State and speed intentionally remain independent V4 Core commands. */
|
||||
public CommandResult setEncodedForecastPlaybackState(boolean playing) { requireAttached(); CommandResult result = encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackState(config.sourceId, playing)); if (result == CommandResult.ACCEPTED) { v4ForecastPlaying = playing; cancelCadenceWake(); scheduleCadenceWake(); } return result; }
|
||||
public CommandResult setEncodedForecastPlaybackSpeed(float speed) { requireAttached(); return !Float.isFinite(speed) || speed <= 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackSpeed(config.sourceId, speed)); }
|
||||
/** 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) {
|
||||
if (state == State.DISABLED) return CommandResult.ACCEPTED;
|
||||
map.controlRasterArraySourceTimeline(config.sourceId, 1, 0f);
|
||||
boolean color = map.setStyleLayerVisibility(config.weatherColorLayerId, false);
|
||||
boolean particle = map.setStyleLayerVisibility(config.particleLayerId, false);
|
||||
boolean clear = map.resetRasterArraySourceWeatherRuntime(config.sourceId);
|
||||
state = stateAfterDisable(color && particle && clear);
|
||||
if (clear) {
|
||||
submittedEncodedFieldKeys.clear();
|
||||
v4ParticleBindingFieldKey = null;
|
||||
v4ForecastPlaying = false;
|
||||
playbackState = PlaybackState.PAUSE;
|
||||
}
|
||||
if (state != State.READY) cancelCadenceWake();
|
||||
return result(color && particle && clear);
|
||||
}
|
||||
if (state == State.ERROR) return CommandResult.REJECTED;
|
||||
if (state != State.DISABLED) return CommandResult.ACCEPTED;
|
||||
boolean color = map.setStyleLayerVisibility(config.weatherColorLayerId, weatherColorVisible);
|
||||
boolean particle = map.setStyleLayerVisibility(config.particleLayerId, particleVisible);
|
||||
state = stateAfterEnable(state, color && particle);
|
||||
cancelCadenceWake();
|
||||
scheduleCadenceWake();
|
||||
return result(color && particle);
|
||||
}
|
||||
public CommandResult setWeatherColorVisible(boolean visible) { if (disposed) return CommandResult.REJECTED; weatherColorVisible = visible; CommandResult result = result(map.setStyleLayerVisibility(config.weatherColorLayerId, visible && state != State.DISABLED)); cancelCadenceWake(); scheduleCadenceWake(); return result; }
|
||||
public CommandResult setParticleVisible(boolean visible) { if (disposed) return CommandResult.REJECTED; particleVisible = visible; CommandResult result = result(map.setStyleLayerVisibility(config.particleLayerId, visible && state != State.DISABLED)); cancelCadenceWake(); scheduleCadenceWake(); return result; }
|
||||
public CommandResult setForecastTime(long timestampMs) { requireEnabled(); return result(timestampMs >= 0 && map.seekRasterArraySourceTimelineToTimestamp(config.sourceId, timestampMs)); }
|
||||
public CommandResult setPlaybackState(@NonNull PlaybackState playback) { requireEnabled(); CommandResult result = result(map.controlRasterArraySourceTimeline(config.sourceId, playback == PlaybackState.PLAY ? 0 : 1, 0f)); if (result == CommandResult.ACCEPTED) { playbackState = playback; cancelCadenceWake(); scheduleCadenceWake(); } return result; }
|
||||
public CommandResult setPlaybackSpeed(@NonNull PlaybackSpeed speed) { requireEnabled(); return result(map.controlRasterArraySourceTimeline(config.sourceId, 2, speed.value)); }
|
||||
public CommandResult setWeatherColorOpacity(float opacity) { return disposed ? CommandResult.REJECTED : result(Float.isFinite(opacity) && map.setWeatherColorOpacity(config.weatherColorLayerId, opacity)); }
|
||||
public CommandResult setParticleCount(int count) { return setParticleFloat("raster-particle-particle-count", count); }
|
||||
public CommandResult setParticleLineWidth(float width) { return setParticleFloat("raster-particle-line-width", width); }
|
||||
public CommandResult setParticleLength(float length) { return setParticleFloat("raster-particle-particle-length", length); }
|
||||
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) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
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) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
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) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
return result(map.setRasterParticleColors(config.particleLayerId, new float[]{red, green, blue, alpha}, new float[]{trailRed, trailGreen, trailBlue, trailAlpha}));
|
||||
}
|
||||
public CommandResult setTargetFps(@NonNull TargetFps target) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
if (!map.setRasterArraySourceTargetFps(config.sourceId, target.value)) return CommandResult.REJECTED;
|
||||
targetFps = target;
|
||||
cancelCadenceWake();
|
||||
scheduleCadenceWake();
|
||||
return CommandResult.ACCEPTED;
|
||||
}
|
||||
/** Native-authoritative playback mode for a low-frequency Host snapshot. */
|
||||
public PlaybackState getPlaybackState() { return playbackState; }
|
||||
public TargetFps getTargetFps() { return targetFps; }
|
||||
public CommandResult setWeatherColorStops(@NonNull List<ColorStop> stops) {
|
||||
if (disposed) return CommandResult.REJECTED;
|
||||
if (weatherColorStopCapacity < 2 || stops.size() < 2 || stops.size() > weatherColorStopCapacity) return CommandResult.REJECTED;
|
||||
float[] speeds = new float[stops.size()]; float[] colors = new float[stops.size() * 4];
|
||||
for (int i = 0; i < stops.size(); i++) { ColorStop stop = stops.get(i); speeds[i] = stop.speedMps; colors[i * 4] = stop.red; colors[i * 4 + 1] = stop.green; colors[i * 4 + 2] = stop.blue; colors[i * 4 + 3] = stop.alpha; }
|
||||
return result(map.setWeatherColorStops(config.weatherColorLayerId, speeds, colors));
|
||||
}
|
||||
private static CommandResult result(boolean accepted) { return accepted ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
|
||||
/** JNI 0 is private source/type lifecycle sentinel, never a Core V4 result. */
|
||||
private static CommandResult encodedCommand(int code) { if (code == 0) throw new IllegalStateException("SOURCE_MISSING"); return code > 0 ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
|
||||
public enum FieldValueSource { SCALAR(0f), VECTOR_MAGNITUDE(1f); final float code; FieldValueSource(float code) { this.code = code; } }
|
||||
private boolean shouldWakeWeather() {
|
||||
if (disposed || state == State.DISABLED) return false;
|
||||
if (v4ForecastPlaying && !submittedEncodedFieldKeys.isEmpty()) return true;
|
||||
if (targetFps == TargetFps.AUTO) return false;
|
||||
final boolean v4ParticleFieldReady = v4ParticleBindingFieldKey != null && submittedEncodedFieldKeys.contains(v4ParticleBindingFieldKey);
|
||||
return (v4ParticleFieldReady && particleVisible) ||
|
||||
(state == State.READY && (particleVisible || (weatherColorVisible && playbackState == PlaybackState.PLAY)));
|
||||
}
|
||||
private void scheduleCadenceWake() {
|
||||
if (!shouldWakeWeather()) return;
|
||||
try {
|
||||
if (cadenceHandler == null) cadenceHandler = new Handler(Looper.getMainLooper());
|
||||
cadenceHandler.removeCallbacks(cadenceWake);
|
||||
cadenceHandler.postDelayed(cadenceWake, targetFps == TargetFps.FPS_60 ? 17L : 34L);
|
||||
} catch (RuntimeException ignored) {
|
||||
// Local JVM tests may not provide an Android Looper; Core cadence remains authoritative.
|
||||
}
|
||||
}
|
||||
private void cancelCadenceWake() { if (cadenceHandler != null) cadenceHandler.removeCallbacks(cadenceWake); }
|
||||
void runCadenceWake() { if (!shouldWakeWeather()) return; map.triggerRepaint(); scheduleCadenceWake(); }
|
||||
private CommandResult setParticleFloat(String property, float value) { return disposed ? CommandResult.REJECTED : result(Float.isFinite(value) && map.setStyleLayerFloatProperty(config.particleLayerId, property, value)); }
|
||||
static State stateAfterSubmit(State current, FieldSubmitResult submit) {
|
||||
if (submit == FieldSubmitResult.ACCEPTED_PENDING_INITIAL_WINDOW) return State.WAITING_FOR_FIELDS;
|
||||
if (submit == FieldSubmitResult.INITIAL_WINDOW_READY || submit == FieldSubmitResult.ACCEPTED_NEXT_SNAPSHOT) return State.READY;
|
||||
if (submit == FieldSubmitResult.DUPLICATE) return current;
|
||||
return submit == FieldSubmitResult.REJECTED_SOURCE_MODE || submit == FieldSubmitResult.UNKNOWN ? State.ERROR : current;
|
||||
}
|
||||
static State stateAfterClear(State current, boolean accepted) { return accepted ? State.WAITING_FOR_FIELDS : current; }
|
||||
static State stateAfterDisable(boolean accepted) { return accepted ? State.DISABLED : State.ERROR; }
|
||||
static State stateAfterEnable(State current, boolean accepted) {
|
||||
if (!accepted) return State.ERROR;
|
||||
if (current == State.DISABLED) return State.WAITING_FOR_FIELDS;
|
||||
return current;
|
||||
}
|
||||
private void requireAttached() { if (disposed) throw new IllegalStateException("WeatherRuntime is disposed"); }
|
||||
private void requireEnabled() { requireAttached(); if (state == State.DISABLED) throw new IllegalStateException("WeatherRuntime is disabled; enable and submit fields first"); }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Small transport guards shared by Java facade checks; they do not define QBIN decoding. */
|
||||
final class WeatherRuntimeV4Guards {
|
||||
static final int MAX_ENCODED_ID_UTF8_BYTES = 64;
|
||||
static final int MAX_CONTRACT_UTF8_BYTES = 256 * 1024;
|
||||
static final int MAX_ENCODED_QBIN_BYTES = 64 * 1024 * 1024;
|
||||
|
||||
private WeatherRuntimeV4Guards() {}
|
||||
|
||||
static boolean hasValidEncodedIdUtf8Length(String value) {
|
||||
if (value == null) return false;
|
||||
final int length = value.getBytes(StandardCharsets.UTF_8).length;
|
||||
return length >= 1 && length <= MAX_ENCODED_ID_UTF8_BYTES;
|
||||
}
|
||||
|
||||
static boolean hasValidEncodedQbin(byte[] qbin) {
|
||||
return qbin != null && qbin.length >= 1 && qbin.length <= MAX_ENCODED_QBIN_BYTES;
|
||||
}
|
||||
|
||||
static boolean hasValidEncodedContractUtf8Length(String contractJson) {
|
||||
return contractJson != null && !contractJson.isEmpty()
|
||||
&& contractJson.getBytes(StandardCharsets.UTF_8).length <= MAX_CONTRACT_UTF8_BYTES;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
|
||||
@@ -28,11 +29,13 @@ public class SurfaceViewMapRenderer extends MapRenderer {
|
||||
surfaceView.setDetachedListener(new MapLibreSurfaceView.OnSurfaceViewDetachedListener() {
|
||||
@Override
|
||||
public void onSurfaceViewDetached() {
|
||||
Log.i("Mbgl-SurfaceRenderer", "SurfaceView detached; nativeReset entered");
|
||||
// because the GL thread is destroyed when the view is detached from window,
|
||||
// we need to ensure releasing the native renderer as well.
|
||||
// This avoids releasing it only when the view is being recreated, which is already on a new GL thread,
|
||||
// and leads to JNI crashes like https://github.com/mapbox/mapbox-gl-native/issues/14618
|
||||
nativeReset();
|
||||
Log.i("Mbgl-SurfaceRenderer", "nativeReset returned");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/** Exercises the concrete MapLibreMap.onDestroy() dispatch path used by Android integrations. */
|
||||
public class MapLibreMapDestroyListenerTest {
|
||||
private MapLibreMap newMap() {
|
||||
MapLibreMap map = new MapLibreMap(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Mockito.mock(CameraChangeDispatcher.class),
|
||||
Collections.emptyList());
|
||||
map.injectLocationComponent(Mockito.mock(org.maplibre.android.location.LocationComponent.class));
|
||||
return map;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDestroyNotifiesRegisteredListenerOnceAndLateRegistrationImmediately() {
|
||||
MapLibreMap map = newMap();
|
||||
AtomicInteger notifications = new AtomicInteger();
|
||||
map.addOnDestroyListener(notifications::incrementAndGet);
|
||||
|
||||
map.onDestroy();
|
||||
map.onDestroy();
|
||||
|
||||
assertEquals(1, notifications.get());
|
||||
map.addOnDestroyListener(notifications::incrementAndGet);
|
||||
assertEquals(2, notifications.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removedListenerIsNotCalledByOnDestroy() {
|
||||
MapLibreMap map = newMap();
|
||||
AtomicInteger notifications = new AtomicInteger();
|
||||
MapLibreMap.OnDestroyListenerRegistration registration =
|
||||
map.addOnDestroyListener(notifications::incrementAndGet);
|
||||
registration.remove();
|
||||
|
||||
map.onDestroy();
|
||||
|
||||
assertEquals(0, notifications.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
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. */
|
||||
public class WeatherRuntimeStateTest {
|
||||
@Test public void enableIsIdempotentAndDisabledEnableWaitsForFields() {
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterEnable(WeatherRuntime.State.READY, true));
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterEnable(WeatherRuntime.State.DISABLED, true));
|
||||
}
|
||||
@Test public void submitRecoversErrorAndReachesReadyOnlyWithWindow() {
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.ERROR, WeatherRuntime.FieldSubmitResult.ACCEPTED_PENDING_INITIAL_WINDOW));
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.FieldSubmitResult.INITIAL_WINDOW_READY));
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.READY, WeatherRuntime.FieldSubmitResult.DUPLICATE));
|
||||
}
|
||||
@Test public void ordinarySubmitRejectionsPreserveRuntimeState() {
|
||||
WeatherRuntime.FieldSubmitResult[] rejected = {
|
||||
WeatherRuntime.FieldSubmitResult.REJECTED_ADAPTER,
|
||||
WeatherRuntime.FieldSubmitResult.REJECTED_INCOMPATIBLE,
|
||||
WeatherRuntime.FieldSubmitResult.REJECTED_NON_MONOTONIC,
|
||||
WeatherRuntime.FieldSubmitResult.REJECTED_CAPACITY};
|
||||
for (WeatherRuntime.FieldSubmitResult result : rejected) {
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.READY, result));
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.WAITING_FOR_FIELDS, result));
|
||||
}
|
||||
}
|
||||
@Test public void onlyFacadeContractFailuresEnterError() {
|
||||
assertEquals(WeatherRuntime.State.ERROR, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.READY, WeatherRuntime.FieldSubmitResult.REJECTED_SOURCE_MODE));
|
||||
assertEquals(WeatherRuntime.State.ERROR, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.READY, WeatherRuntime.FieldSubmitResult.UNKNOWN));
|
||||
assertEquals(WeatherRuntime.State.ERROR, WeatherRuntime.stateAfterEnable(WeatherRuntime.State.ERROR, false));
|
||||
assertEquals(WeatherRuntime.State.ERROR, WeatherRuntime.stateAfterEnable(WeatherRuntime.State.ERROR, true));
|
||||
}
|
||||
@Test public void clearAndDisableUseSuccessfulCoreResult() {
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterClear(WeatherRuntime.State.READY, true));
|
||||
assertEquals(WeatherRuntime.State.DISABLED, WeatherRuntime.stateAfterDisable(true));
|
||||
assertEquals(WeatherRuntime.State.ERROR, WeatherRuntime.stateAfterDisable(false));
|
||||
}
|
||||
|
||||
@Test public void publicFlowKeepsReadyOnInvalidSubmission() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
Mockito.when(map.submitRasterArraySourceHostField(Mockito.anyString(), Mockito.anyString(), Mockito.any(byte[].class)))
|
||||
.thenReturn(MapLibreMap.HOST_FIELD_INITIAL_WINDOW_READY)
|
||||
.thenReturn(MapLibreMap.HOST_FIELD_REJECTED_ADAPTER);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(WeatherRuntime.FieldSubmitResult.INITIAL_WINDOW_READY, runtime.submitWindField("{}", new byte[] {1}));
|
||||
assertEquals(WeatherRuntime.FieldSubmitResult.REJECTED_ADAPTER, runtime.submitWindField("{}", new byte[] {1}));
|
||||
assertEquals(WeatherRuntime.State.READY, runtime.getState());
|
||||
assertEquals(WeatherRuntime.FieldSubmitResult.REJECTED_ADAPTER, runtime.submitWindField("", new byte[] {1}));
|
||||
assertEquals(WeatherRuntime.State.READY, runtime.getState());
|
||||
}
|
||||
|
||||
@Test public void publicFlowRejectsEnableFromErrorWithoutVisibilityRecovery() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(WeatherRuntime.FieldSubmitResult.UNKNOWN, runtime.submitWindField("{}", new byte[] {1}));
|
||||
assertEquals(WeatherRuntime.State.ERROR, runtime.getState());
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setWeatherEnabled(true));
|
||||
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));
|
||||
}
|
||||
|
||||
@Test public void targetFpsIsTypedAndDoesNotSubmitFields() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
Mockito.when(map.setRasterArraySourceTargetFps("source", 30)).thenReturn(true);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(WeatherRuntime.CommandResult.ACCEPTED, runtime.setTargetFps(WeatherRuntime.TargetFps.FPS_30));
|
||||
assertEquals(WeatherRuntime.TargetFps.FPS_30, runtime.getTargetFps());
|
||||
Mockito.verify(map, Mockito.never()).submitRasterArraySourceHostField(Mockito.anyString(), Mockito.anyString(), Mockito.any(byte[].class));
|
||||
}
|
||||
|
||||
@Test public void disableResetsV3AndV4ViaOneCoreCommandAndCanEnableAgain() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
Mockito.when(map.setStyleLayerVisibility(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(true);
|
||||
Mockito.when(map.resetRasterArraySourceWeatherRuntime("source")).thenReturn(true);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(WeatherRuntime.CommandResult.ACCEPTED, runtime.setWeatherEnabled(false));
|
||||
assertEquals(WeatherRuntime.State.DISABLED, runtime.getState());
|
||||
Mockito.verify(map).resetRasterArraySourceWeatherRuntime("source");
|
||||
Mockito.verify(map, Mockito.never()).clearRasterArraySourceHostFields(Mockito.anyString());
|
||||
assertEquals(WeatherRuntime.CommandResult.ACCEPTED, runtime.setWeatherEnabled(true));
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, runtime.getState());
|
||||
}
|
||||
|
||||
@Test public void terminalDisposeResetsSourceAndFailureDoesNotClaimDisposed() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
Mockito.when(map.hasStyleSource("source")).thenReturn(true);
|
||||
Mockito.when(map.resetRasterArraySourceWeatherRuntime("source")).thenReturn(false).thenReturn(true);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
try {
|
||||
runtime.dispose();
|
||||
org.junit.Assert.fail("Core reset failure must be reported");
|
||||
} catch (IllegalStateException expected) {
|
||||
assertEquals(false, runtime.isDisposed());
|
||||
}
|
||||
runtime.dispose();
|
||||
assertEquals(true, runtime.isDisposed());
|
||||
Mockito.verify(map, Mockito.times(2)).resetRasterArraySourceWeatherRuntime("source");
|
||||
Mockito.clearInvocations(map);
|
||||
runtime.dispose();
|
||||
Mockito.verifyNoInteractions(map);
|
||||
}
|
||||
|
||||
@Test public void disposeIsIdempotentAndRejectsFurtherControls() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
WeatherRuntime runtime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
runtime.dispose();
|
||||
runtime.dispose();
|
||||
assertEquals(true, runtime.isDisposed());
|
||||
Mockito.clearInvocations(map);
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setTargetFps(WeatherRuntime.TargetFps.FPS_30));
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setWeatherColorVisible(false));
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setParticleVisible(false));
|
||||
assertEquals(WeatherRuntime.CommandResult.REJECTED, runtime.setWeatherEnabled(false));
|
||||
Mockito.verifyNoInteractions(map);
|
||||
}
|
||||
|
||||
@Test public void disposedWakeCannotRepaintOrRescheduleOldMap() {
|
||||
MapLibreMap map = Mockito.mock(MapLibreMap.class);
|
||||
Mockito.when(map.getWeatherColorStopCapacity()).thenReturn(8);
|
||||
WeatherRuntime oldRuntime = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
oldRuntime.dispose();
|
||||
oldRuntime.runCadenceWake();
|
||||
Mockito.verify(map, Mockito.never()).triggerRepaint();
|
||||
WeatherRuntime replacement = new WeatherRuntime(map, new WeatherRuntime.Config("source", "color", "particle"));
|
||||
assertEquals(false, replacement.isDisposed());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
/** JDK-only directed guard test; Android facade compilation remains a later Android validation. */
|
||||
public final class WeatherRuntimeV4GuardsTest {
|
||||
public static void main(String[] args) {
|
||||
require(WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(repeat("a", 64)));
|
||||
require(!WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(repeat("风", 33)));
|
||||
require(WeatherRuntimeV4Guards.hasValidEncodedQbin(new byte[64 * 1024 * 1024]));
|
||||
require(!WeatherRuntimeV4Guards.hasValidEncodedQbin(new byte[0]));
|
||||
require(!WeatherRuntimeV4Guards.hasValidEncodedQbin(new byte[64 * 1024 * 1024 + 1]));
|
||||
require(WeatherRuntimeV4Guards.hasValidEncodedContractUtf8Length("{}"));
|
||||
require(!WeatherRuntimeV4Guards.hasValidEncodedContractUtf8Length("a".repeat(256 * 1024 + 1)));
|
||||
}
|
||||
|
||||
private static String repeat(String value, int count) { return value.repeat(count); }
|
||||
private static void require(boolean value) { if (!value) throw new AssertionError("V4 transport guard failed"); }
|
||||
}
|
||||
@@ -16,6 +16,9 @@ fun obtainTestBuildType(): String {
|
||||
}
|
||||
}
|
||||
|
||||
val stripValidationNativeDebugSymbols =
|
||||
providers.gradleProperty("maplibre.validation.stripNativeDebugSymbols").orNull == "true"
|
||||
|
||||
android {
|
||||
ndkVersion = Versions.ndkVersion
|
||||
|
||||
@@ -51,7 +54,9 @@ android {
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols += "**/*.so"
|
||||
if (!stripValidationNativeDebugSymbols) {
|
||||
keepDebugSymbols += "**/*.so"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -286,6 +286,19 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.particle.ParticleCoreLayerValidationActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:description="@string/description_particle_core_layer_validation"
|
||||
android:exported="true"
|
||||
android:label="@string/activity_particle_core_layer_validation">
|
||||
<meta-data
|
||||
android:name="@string/category"
|
||||
android:value="@string/category_custom_layer" />
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.annotation.PolygonActivity"
|
||||
android:description="@string/description_polygon"
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "nodata",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "5756a6ac403a435ff001ff7261160de718c4a2964434ba7b9be2bfe74f071ff0"}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "uniform-east",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "0da648fdcf61b5ea6383960583bdc74287c3fa758c6e5a7eb7d234f2d7c3283f"}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "uniform-north",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "20eb0da1812e616cee1740ee8e22a7bda377d6785cc6e7007fb26afa5ab255bc"}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "vortex",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "aa7fd26d5e4605dc363636b99e17c6bb495d51350a5a2cd8abe5bf0ada741c5a"}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1", "fieldId": "production-wind-t00", "fieldType": "WIND", "displayDomain": "GLOBAL", "generation": 1, "validTime": 202609040000, "dataVersion": 1,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0}, "grid": {"width": 16, "height": 16}, "vector": {"layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0}, "payload": {"byteLength": 2048}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1", "fieldId": "production-wind-t01", "fieldType": "WIND", "displayDomain": "GLOBAL", "generation": 2, "validTime": 202609040100, "dataVersion": 2,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0}, "grid": {"width": 16, "height": 16}, "vector": {"layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0}, "payload": {"byteLength": 2048}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1", "fieldId": "production-wind-t02", "fieldType": "WIND", "displayDomain": "GLOBAL", "generation": 3, "validTime": 202609040200, "dataVersion": 3,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0}, "grid": {"width": 16, "height": 16}, "vector": {"layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0}, "payload": {"byteLength": 2048}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1", "fieldId": "production-wind-t03", "fieldType": "WIND", "displayDomain": "GLOBAL", "generation": 4, "validTime": 202609040300, "dataVersion": 4,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0}, "grid": {"width": 16, "height": 16}, "vector": {"layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0}, "payload": {"byteLength": 2048}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1", "fieldId": "", "fieldType": "WIND", "displayDomain": "GLOBAL", "generation": 0, "validTime": 0, "dataVersion": 99,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0}, "grid": {"width": 16, "height": 16}, "vector": {"layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0}, "payload": {"byteLength": 2048}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"source": {
|
||||
"format": "weather-v2-particle-bin-v1",
|
||||
"magic": "WVECBIN1",
|
||||
"targetTime": "2026080700",
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"dtype": "float32 little-endian u plane, v plane, speed plane; uint8 valid plane",
|
||||
"arrayOrder": "row-major; WeatherV2 tile y increases southward, so rows are north-to-south",
|
||||
"uVUnit": "m/s",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"bytes": 5128989
|
||||
},
|
||||
"output": {
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"bytes": 3155976,
|
||||
"sha256": "363e2cd2e77501f6bceecbb4aaa68c4f9ef46998a60319ae5b7aba3548995283",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"resampled": false
|
||||
},
|
||||
"statistics": {
|
||||
"u": {
|
||||
"min": -35.172367095947266,
|
||||
"max": 22.501087188720703,
|
||||
"mean": 0.7285217046737671
|
||||
},
|
||||
"v": {
|
||||
"min": -25.472291946411133,
|
||||
"max": 28.200902938842773,
|
||||
"mean": 1.1300384998321533
|
||||
},
|
||||
"speed": {
|
||||
"min": 0.0036890381015837193,
|
||||
"max": 35.27834701538086,
|
||||
"mean": 6.401810169219971
|
||||
}
|
||||
},
|
||||
"sourceToCanonical": {
|
||||
"lossless": true,
|
||||
"maxAbsErrorU": 0.0,
|
||||
"maxAbsErrorV": 0.0,
|
||||
"samples": [
|
||||
{
|
||||
"row": 384,
|
||||
"column": 256,
|
||||
"sourceU": -10.921929359436035,
|
||||
"sourceV": 4.515348434448242,
|
||||
"canonicalU": -10.921929359436035,
|
||||
"canonicalV": 4.515348434448242
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 1,
|
||||
"sourceU": -0.980097770690918,
|
||||
"sourceV": 0.4984455108642578,
|
||||
"canonicalU": -0.980097770690918,
|
||||
"canonicalV": 0.4984455108642578
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 511,
|
||||
"sourceU": 1.2943105697631836,
|
||||
"sourceV": -0.6254873275756836,
|
||||
"canonicalU": 1.2943105697631836,
|
||||
"canonicalV": -0.6254873275756836
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 1,
|
||||
"sourceU": -2.738156318664551,
|
||||
"sourceV": 1.1959524154663086,
|
||||
"canonicalU": -2.738156318664551,
|
||||
"canonicalV": 1.1959524154663086
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 511,
|
||||
"sourceU": -5.0848894119262695,
|
||||
"sourceV": -0.26341915130615234,
|
||||
"canonicalU": -5.0848894119262695,
|
||||
"canonicalV": -0.26341915130615234
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 0,
|
||||
"sourceU": -1.1939868927001953,
|
||||
"sourceV": 1.7741403579711914,
|
||||
"canonicalU": -1.1939868927001953,
|
||||
"canonicalV": 1.7741403579711914
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 1,
|
||||
"sourceU": -1.051393985748291,
|
||||
"sourceV": 1.4262237548828125,
|
||||
"canonicalU": -1.051393985748291,
|
||||
"canonicalV": 1.4262237548828125
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 2,
|
||||
"sourceU": -0.6949124336242676,
|
||||
"sourceV": 0.9043483734130859,
|
||||
"canonicalU": -0.6949124336242676,
|
||||
"canonicalV": 0.9043483734130859
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1",
|
||||
"fieldId": "ecmwf-hres-wind-2026080700",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"generation": 1786060800000,
|
||||
"validTime": 1786060800000,
|
||||
"dataVersion": 1786060800000,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"grid": {
|
||||
"width": 513,
|
||||
"height": 769
|
||||
},
|
||||
"vector": {
|
||||
"layout": "interleaved-uv",
|
||||
"encoding": "float32-le",
|
||||
"noData": -9999.0,
|
||||
"unit": "m/s",
|
||||
"origin": "north-west",
|
||||
"rowOrder": "north-to-south",
|
||||
"columnOrder": "west-to-east",
|
||||
"uPositive": "east",
|
||||
"vPositive": "north"
|
||||
},
|
||||
"payload": {
|
||||
"byteLength": 3155976,
|
||||
"sha256": "363e2cd2e77501f6bceecbb4aaa68c4f9ef46998a60319ae5b7aba3548995283"
|
||||
},
|
||||
"source": {
|
||||
"kind": "WeatherV2 ECMWF HRES WVECBIN1",
|
||||
"referenceTime": "2026080600",
|
||||
"targetTime": "2026080700",
|
||||
"sourceBytes": 5128989,
|
||||
"resampled": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"source": {
|
||||
"format": "weather-v2-particle-bin-v1",
|
||||
"magic": "WVECBIN1",
|
||||
"targetTime": "2026080706",
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"dtype": "float32 little-endian u plane, v plane, speed plane; uint8 valid plane",
|
||||
"arrayOrder": "row-major; WeatherV2 tile y increases southward, so rows are north-to-south",
|
||||
"uVUnit": "m/s",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"bytes": 5128989
|
||||
},
|
||||
"output": {
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"bytes": 3155976,
|
||||
"sha256": "ef7a8e4b1c47f652f8f7012de16a87c16e3b25556a8f4686629f80514426c451",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"resampled": false
|
||||
},
|
||||
"statistics": {
|
||||
"u": {
|
||||
"min": -37.18255615234375,
|
||||
"max": 24.125526428222656,
|
||||
"mean": 0.9443698525428772
|
||||
},
|
||||
"v": {
|
||||
"min": -28.227636337280273,
|
||||
"max": 25.05806541442871,
|
||||
"mean": 1.0168105363845825
|
||||
},
|
||||
"speed": {
|
||||
"min": 0.010480343364179134,
|
||||
"max": 37.76322555541992,
|
||||
"mean": 6.7594804763793945
|
||||
}
|
||||
},
|
||||
"sourceToCanonical": {
|
||||
"lossless": true,
|
||||
"maxAbsErrorU": 0.0,
|
||||
"maxAbsErrorV": 0.0,
|
||||
"samples": [
|
||||
{
|
||||
"row": 384,
|
||||
"column": 256,
|
||||
"sourceU": -10.2057466506958,
|
||||
"sourceV": 4.789729118347168,
|
||||
"canonicalU": -10.2057466506958,
|
||||
"canonicalV": 4.789729118347168
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 1,
|
||||
"sourceU": -0.45672082901000977,
|
||||
"sourceV": 3.2181167602539062,
|
||||
"canonicalU": -0.45672082901000977,
|
||||
"canonicalV": 3.2181167602539062
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 511,
|
||||
"sourceU": 2.235017776489258,
|
||||
"sourceV": -0.7363739013671875,
|
||||
"canonicalU": 2.235017776489258,
|
||||
"canonicalV": -0.7363739013671875
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 1,
|
||||
"sourceU": -1.2302308082580566,
|
||||
"sourceV": 1.5909242630004883,
|
||||
"canonicalU": -1.2302308082580566,
|
||||
"canonicalV": 1.5909242630004883
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 511,
|
||||
"sourceU": -5.034683704376221,
|
||||
"sourceV": -0.15012121200561523,
|
||||
"canonicalU": -5.034683704376221,
|
||||
"canonicalV": -0.15012121200561523
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 0,
|
||||
"sourceU": -0.15583133697509766,
|
||||
"sourceV": 3.617917060852051,
|
||||
"canonicalU": -0.15583133697509766,
|
||||
"canonicalV": 3.617917060852051
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 1,
|
||||
"sourceU": -0.45672082901000977,
|
||||
"sourceV": 3.551283836364746,
|
||||
"canonicalU": -0.45672082901000977,
|
||||
"canonicalV": 3.551283836364746
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 2,
|
||||
"sourceU": -0.6974325180053711,
|
||||
"sourceV": 3.2181167602539062,
|
||||
"canonicalU": -0.6974325180053711,
|
||||
"canonicalV": 3.2181167602539062
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1",
|
||||
"fieldId": "ecmwf-hres-wind-2026080706",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"generation": 1786082400000,
|
||||
"validTime": 1786082400000,
|
||||
"dataVersion": 1786082400000,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"grid": {
|
||||
"width": 513,
|
||||
"height": 769
|
||||
},
|
||||
"vector": {
|
||||
"layout": "interleaved-uv",
|
||||
"encoding": "float32-le",
|
||||
"noData": -9999.0,
|
||||
"unit": "m/s",
|
||||
"origin": "north-west",
|
||||
"rowOrder": "north-to-south",
|
||||
"columnOrder": "west-to-east",
|
||||
"uPositive": "east",
|
||||
"vPositive": "north"
|
||||
},
|
||||
"payload": {
|
||||
"byteLength": 3155976,
|
||||
"sha256": "ef7a8e4b1c47f652f8f7012de16a87c16e3b25556a8f4686629f80514426c451"
|
||||
},
|
||||
"source": {
|
||||
"kind": "WeatherV2 ECMWF HRES WVECBIN1",
|
||||
"referenceTime": "2026080600",
|
||||
"targetTime": "2026080706",
|
||||
"sourceBytes": 5128989,
|
||||
"resampled": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"source": {
|
||||
"format": "weather-v2-particle-bin-v1",
|
||||
"magic": "WVECBIN1",
|
||||
"targetTime": "2026080712",
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"dtype": "float32 little-endian u plane, v plane, speed plane; uint8 valid plane",
|
||||
"arrayOrder": "row-major; WeatherV2 tile y increases southward, so rows are north-to-south",
|
||||
"uVUnit": "m/s",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"bytes": 5128989
|
||||
},
|
||||
"output": {
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"bytes": 3155976,
|
||||
"sha256": "d86a6a51fd5216cf9368756962a5366fd32a627afd40cfde235ba7358c02c5a0",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"resampled": false
|
||||
},
|
||||
"statistics": {
|
||||
"u": {
|
||||
"min": -37.39310836791992,
|
||||
"max": 24.769317626953125,
|
||||
"mean": 0.7698453664779663
|
||||
},
|
||||
"v": {
|
||||
"min": -28.266582489013672,
|
||||
"max": 27.809932708740234,
|
||||
"mean": 1.1808899641036987
|
||||
},
|
||||
"speed": {
|
||||
"min": 0.024363698437809944,
|
||||
"max": 37.42909240722656,
|
||||
"mean": 6.384326934814453
|
||||
}
|
||||
},
|
||||
"sourceToCanonical": {
|
||||
"lossless": true,
|
||||
"maxAbsErrorU": 0.0,
|
||||
"maxAbsErrorV": 0.0,
|
||||
"samples": [
|
||||
{
|
||||
"row": 384,
|
||||
"column": 256,
|
||||
"sourceU": -10.180034637451172,
|
||||
"sourceV": 4.663511276245117,
|
||||
"canonicalU": -10.180034637451172,
|
||||
"canonicalV": 4.663511276245117
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 1,
|
||||
"sourceU": -0.5417494773864746,
|
||||
"sourceV": 0.767613410949707,
|
||||
"canonicalU": -0.5417494773864746,
|
||||
"canonicalV": 0.767613410949707
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 511,
|
||||
"sourceU": -0.5780353546142578,
|
||||
"sourceV": 0.26222801208496094,
|
||||
"canonicalU": -0.5780353546142578,
|
||||
"canonicalV": 0.26222801208496094
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 1,
|
||||
"sourceU": -1.8420743942260742,
|
||||
"sourceV": 0.3505821228027344,
|
||||
"canonicalU": -1.8420743942260742,
|
||||
"canonicalV": 0.3505821228027344
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 511,
|
||||
"sourceU": -4.581581115722656,
|
||||
"sourceV": -1.1498479843139648,
|
||||
"canonicalU": -4.581581115722656,
|
||||
"canonicalV": -1.1498479843139648
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 0,
|
||||
"sourceU": -0.5939555168151855,
|
||||
"sourceV": 1.1400480270385742,
|
||||
"canonicalU": -0.5939555168151855,
|
||||
"canonicalV": 1.1400480270385742
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 1,
|
||||
"sourceU": -0.48954296112060547,
|
||||
"sourceV": 1.0159034729003906,
|
||||
"canonicalU": -0.48954296112060547,
|
||||
"canonicalV": 1.0159034729003906
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 2,
|
||||
"sourceU": -0.48954296112060547,
|
||||
"sourceV": 0.767613410949707,
|
||||
"canonicalU": -0.48954296112060547,
|
||||
"canonicalV": 0.767613410949707
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1",
|
||||
"fieldId": "ecmwf-hres-wind-2026080712",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"generation": 1786104000000,
|
||||
"validTime": 1786104000000,
|
||||
"dataVersion": 1786104000000,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"grid": {
|
||||
"width": 513,
|
||||
"height": 769
|
||||
},
|
||||
"vector": {
|
||||
"layout": "interleaved-uv",
|
||||
"encoding": "float32-le",
|
||||
"noData": -9999.0,
|
||||
"unit": "m/s",
|
||||
"origin": "north-west",
|
||||
"rowOrder": "north-to-south",
|
||||
"columnOrder": "west-to-east",
|
||||
"uPositive": "east",
|
||||
"vPositive": "north"
|
||||
},
|
||||
"payload": {
|
||||
"byteLength": 3155976,
|
||||
"sha256": "d86a6a51fd5216cf9368756962a5366fd32a627afd40cfde235ba7358c02c5a0"
|
||||
},
|
||||
"source": {
|
||||
"kind": "WeatherV2 ECMWF HRES WVECBIN1",
|
||||
"referenceTime": "2026080600",
|
||||
"targetTime": "2026080712",
|
||||
"sourceBytes": 5128989,
|
||||
"resampled": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"source": {
|
||||
"format": "weather-v2-particle-bin-v1",
|
||||
"magic": "WVECBIN1",
|
||||
"targetTime": "2026080718",
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"dtype": "float32 little-endian u plane, v plane, speed plane; uint8 valid plane",
|
||||
"arrayOrder": "row-major; WeatherV2 tile y increases southward, so rows are north-to-south",
|
||||
"uVUnit": "m/s",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"bytes": 5128989
|
||||
},
|
||||
"output": {
|
||||
"width": 513,
|
||||
"height": 769,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"bytes": 3155976,
|
||||
"sha256": "970785b0334a9d1511b8897874a81f681814a32b1e0b90e37cf64b77ca9fa39b",
|
||||
"validCount": 394497,
|
||||
"invalidCount": 0,
|
||||
"resampled": false
|
||||
},
|
||||
"statistics": {
|
||||
"u": {
|
||||
"min": -31.956016540527344,
|
||||
"max": 28.557456970214844,
|
||||
"mean": 1.212062120437622
|
||||
},
|
||||
"v": {
|
||||
"min": -26.13941192626953,
|
||||
"max": 27.131576538085938,
|
||||
"mean": 1.1284888982772827
|
||||
},
|
||||
"speed": {
|
||||
"min": 0.0210099034011364,
|
||||
"max": 33.67807388305664,
|
||||
"mean": 6.403453826904297
|
||||
}
|
||||
},
|
||||
"sourceToCanonical": {
|
||||
"lossless": true,
|
||||
"maxAbsErrorU": 0.0,
|
||||
"maxAbsErrorV": 0.0,
|
||||
"samples": [
|
||||
{
|
||||
"row": 384,
|
||||
"column": 256,
|
||||
"sourceU": -10.21538257598877,
|
||||
"sourceV": 2.3842287063598633,
|
||||
"canonicalU": -10.21538257598877,
|
||||
"canonicalV": 2.3842287063598633
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 1,
|
||||
"sourceU": -0.18732452392578125,
|
||||
"sourceV": 0.2720670700073242,
|
||||
"canonicalU": -0.18732452392578125,
|
||||
"canonicalV": 0.2720670700073242
|
||||
},
|
||||
{
|
||||
"row": 1,
|
||||
"column": 511,
|
||||
"sourceU": 0.460113525390625,
|
||||
"sourceV": 0.4729623794555664,
|
||||
"canonicalU": 0.460113525390625,
|
||||
"canonicalV": 0.4729623794555664
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 1,
|
||||
"sourceU": -1.4576325416564941,
|
||||
"sourceV": -0.03371381759643555,
|
||||
"canonicalU": -1.4576325416564941,
|
||||
"canonicalV": -0.03371381759643555
|
||||
},
|
||||
{
|
||||
"row": 767,
|
||||
"column": 511,
|
||||
"sourceU": -2.9916043281555176,
|
||||
"sourceV": 0.8597030639648438,
|
||||
"canonicalU": -2.9916043281555176,
|
||||
"canonicalV": 0.8597030639648438
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 0,
|
||||
"sourceU": -1.020096778869629,
|
||||
"sourceV": 0.9125776290893555,
|
||||
"canonicalU": -1.020096778869629,
|
||||
"canonicalV": 0.9125776290893555
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 1,
|
||||
"sourceU": -0.49961376190185547,
|
||||
"sourceV": 0.6990737915039062,
|
||||
"canonicalU": -0.49961376190185547,
|
||||
"canonicalV": 0.6990737915039062
|
||||
},
|
||||
{
|
||||
"row": 0,
|
||||
"column": 2,
|
||||
"sourceU": -0.08322811126708984,
|
||||
"sourceV": 0.41440296173095703,
|
||||
"canonicalU": -0.08322811126708984,
|
||||
"canonicalV": 0.41440296173095703
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schema": "maplibre-particle-field/v1",
|
||||
"fieldId": "ecmwf-hres-wind-2026080718",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"generation": 1786125600000,
|
||||
"validTime": 1786125600000,
|
||||
"dataVersion": 1786125600000,
|
||||
"bounds": {
|
||||
"west": 112.5,
|
||||
"south": 0.0,
|
||||
"east": 157.5,
|
||||
"north": 55.77657301866769
|
||||
},
|
||||
"grid": {
|
||||
"width": 513,
|
||||
"height": 769
|
||||
},
|
||||
"vector": {
|
||||
"layout": "interleaved-uv",
|
||||
"encoding": "float32-le",
|
||||
"noData": -9999.0,
|
||||
"unit": "m/s",
|
||||
"origin": "north-west",
|
||||
"rowOrder": "north-to-south",
|
||||
"columnOrder": "west-to-east",
|
||||
"uPositive": "east",
|
||||
"vPositive": "north"
|
||||
},
|
||||
"payload": {
|
||||
"byteLength": 3155976,
|
||||
"sha256": "970785b0334a9d1511b8897874a81f681814a32b1e0b90e37cf64b77ca9fa39b"
|
||||
},
|
||||
"source": {
|
||||
"kind": "WeatherV2 ECMWF HRES WVECBIN1",
|
||||
"referenceTime": "2026080600",
|
||||
"targetTime": "2026080718",
|
||||
"sourceBytes": 5128989,
|
||||
"resampled": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"schema": "maplibre-particle-test-dataset/v1",
|
||||
"datasetId": "ecmwf-hres-wind-20260807",
|
||||
"source": "WeatherV2 ECMWF HRES WVECBIN1",
|
||||
"referenceTime": "2026080600",
|
||||
"validTimes": [
|
||||
"2026080700",
|
||||
"2026080706",
|
||||
"2026080712",
|
||||
"2026080718"
|
||||
],
|
||||
"snapshotLayout": "<validTime>/metadata.json + <validTime>/field.f32",
|
||||
"notes": "开发期离线 canonical 测试索引;不是 Weather Manifest,也不进入 ParticleFieldSnapshot。"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"contractSchemaVersion":1,"profileID":"wind","profileVersion":1,"allowAdditionalPlanes":true,"sharedValidityRequired":true,"timeRelation":"VALID_GTE_REFERENCE","requiredPlanes":[{"semanticID":1,"valueClass":"VELOCITY_COMPONENT","unitID":1,"groupID":1,"component":"U_EAST","requiredFlag":true},{"semanticID":2,"valueClass":"VELOCITY_COMPONENT","unitID":1,"groupID":1,"component":"V_NORTH","requiredFlag":true},{"semanticID":3,"valueClass":"VALIDITY","unitID":0,"groupID":0,"component":"NONE","requiredFlag":true}]}
|
||||
@@ -9,6 +9,7 @@ import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -21,6 +22,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.maplibre.android.RenderingEngine
|
||||
import org.maplibre.android.BuildConfig as MapLibreBuildConfig
|
||||
import org.maplibre.android.testapp.BuildConfig
|
||||
import org.maplibre.android.testapp.R
|
||||
import org.maplibre.android.testapp.adapter.FeatureAdapter
|
||||
import org.maplibre.android.testapp.adapter.FeatureSectionAdapter
|
||||
@@ -48,6 +51,14 @@ class FeatureOverviewActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_feature_overview)
|
||||
findViewById<TextView>(R.id.apkVersionView).text = getString(
|
||||
R.string.apk_version_format,
|
||||
BuildConfig.VERSION_NAME,
|
||||
BuildConfig.VERSION_CODE,
|
||||
BuildConfig.FLAVOR,
|
||||
BuildConfig.BUILD_TYPE,
|
||||
MapLibreBuildConfig.GIT_REVISION.take(GIT_REVISION_LENGTH)
|
||||
)
|
||||
recyclerView = findViewById<View>(R.id.recyclerView) as RecyclerView
|
||||
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
recyclerView.addOnItemTouchListener(SimpleOnItemTouchListener())
|
||||
@@ -266,5 +277,6 @@ class FeatureOverviewActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val KEY_STATE_FEATURES = "featureList"
|
||||
private const val GIT_REVISION_LENGTH = 10
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
||||
package org.maplibre.android.testapp.activity.particle
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
/** Offline same-source tile fixture for retained target stencil regression. */
|
||||
internal object Step097StencilFixture {
|
||||
enum class Topology { RR, RCR, CRR }
|
||||
|
||||
const val FIRST_FILL = "step097-shared-fill-a"
|
||||
const val SECOND_FILL = "step097-shared-fill-b"
|
||||
const val THIRD_FILL = "step097-shared-fill-c"
|
||||
private const val FOURTH_FILL = "step097-shared-fill-d"
|
||||
private const val SOURCE = "step097-shared-polygon"
|
||||
|
||||
fun styleJson(): String {
|
||||
val geometry = JSONObject()
|
||||
.put("type", "Polygon")
|
||||
.put("coordinates", JSONArray().put(
|
||||
JSONArray().put(JSONArray().put(-45).put(-35))
|
||||
.put(JSONArray().put(45).put(-35))
|
||||
.put(JSONArray().put(45).put(35))
|
||||
.put(JSONArray().put(-45).put(35))
|
||||
.put(JSONArray().put(-45).put(-35))
|
||||
))
|
||||
val source = JSONObject().put("type", "geojson").put("data", JSONObject()
|
||||
.put("type", "Feature")
|
||||
.put("properties", JSONObject())
|
||||
.put("geometry", geometry))
|
||||
val layers = JSONArray()
|
||||
.put(JSONObject().put("id", "step097-background").put("type", "background")
|
||||
.put("paint", JSONObject().put("background-color", "#15243a")))
|
||||
.put(fill(FIRST_FILL, "#f94144"))
|
||||
.put(fill(SECOND_FILL, "#43aa8b"))
|
||||
.put(fill(THIRD_FILL, "#577590"))
|
||||
.put(fill(FOURTH_FILL, "#f9c74f"))
|
||||
return JSONObject()
|
||||
.put("version", 8)
|
||||
.put("name", "STEP-097 same-source stencil fixture")
|
||||
.put("sources", JSONObject().put(SOURCE, source))
|
||||
.put("layers", layers)
|
||||
.toString()
|
||||
}
|
||||
|
||||
fun planJson(topology: Topology): String {
|
||||
val segments = JSONArray()
|
||||
fun add(strategy: String, start: String? = null) {
|
||||
val segment = JSONObject().put("strategy", strategy)
|
||||
if (start != null) segment.put("startLayerId", start)
|
||||
segments.put(segment)
|
||||
}
|
||||
when (topology) {
|
||||
Topology.RR -> {
|
||||
add("RETAINED")
|
||||
add("RETAINED", SECOND_FILL)
|
||||
}
|
||||
Topology.RCR -> {
|
||||
add("RETAINED")
|
||||
add("CONTINUOUS", SECOND_FILL)
|
||||
add("RETAINED", THIRD_FILL)
|
||||
}
|
||||
Topology.CRR -> {
|
||||
add("CONTINUOUS")
|
||||
add("RETAINED", SECOND_FILL)
|
||||
add("RETAINED", THIRD_FILL)
|
||||
}
|
||||
}
|
||||
return JSONObject().put("schemaVersion", 1).put("segments", segments).toString()
|
||||
}
|
||||
|
||||
private fun fill(id: String, color: String): JSONObject = JSONObject()
|
||||
.put("id", id)
|
||||
.put("type", "fill")
|
||||
.put("source", SOURCE)
|
||||
.put("paint", JSONObject().put("fill-color", color).put("fill-opacity", 0.55))
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.maplibre.android.testapp.activity.particle
|
||||
|
||||
import android.content.res.AssetManager
|
||||
import android.util.Log
|
||||
import org.maplibre.android.maps.WeatherRuntime
|
||||
|
||||
/** STEP-097 A2 test-only ingress. Uses the existing public Android V4 API. */
|
||||
internal object Step097V4GoldenFixture {
|
||||
const val KEY = "fixture.wind"
|
||||
const val T0_MS = 1_789_239_600_000L
|
||||
const val T1_MS = 1_789_243_200_000L
|
||||
const val MID_MS = 1_789_241_400_000L
|
||||
private const val ROOT = "integration/step097-v4/wind/"
|
||||
private const val TAG = "ParticleValidation"
|
||||
|
||||
fun load(assets: AssetManager, runtime: WeatherRuntime) {
|
||||
val contract = assets.open(ROOT + "contract.json").bufferedReader().use { it.readText() }
|
||||
val clear = runtime.clearAllEncodedFields()
|
||||
val color = runtime.setEncodedColorBinding(KEY, 1, 1, 1, 1)
|
||||
val particle = runtime.setEncodedParticleBinding(KEY, 0, 1, 1, 2, 1, 0, 0, 1)
|
||||
val t0 = runtime.submitEncodedField(KEY, "fixture-wind", 1L, 1L,
|
||||
contract, assets.open(ROOT + "T0.qbin").use { it.readBytes() })
|
||||
val t1 = runtime.submitEncodedField(KEY, "fixture-wind", 2L, 2L,
|
||||
contract, assets.open(ROOT + "T1.qbin").use { it.readBytes() })
|
||||
val time = runtime.setEncodedForecastTime(MID_MS)
|
||||
Log.i(TAG, "STEP097_A2_V4_SUBMIT clear=$clear color=$color particle=$particle t0=$t0 t1=$t1 time=$time")
|
||||
check(clear == WeatherRuntime.CommandResult.ACCEPTED &&
|
||||
color == WeatherRuntime.CommandResult.ACCEPTED &&
|
||||
particle == WeatherRuntime.CommandResult.ACCEPTED &&
|
||||
t0 == WeatherRuntime.EncodedFieldSubmitResult.ACCEPTED_INSERTED &&
|
||||
t1 == WeatherRuntime.EncodedFieldSubmitResult.ACCEPTED_INSERTED &&
|
||||
time == WeatherRuntime.CommandResult.ACCEPTED) { "V4 golden ingress rejected" }
|
||||
Log.i(TAG, "STEP097_A2_V4_READY fieldKey=$KEY T0=$T0_MS T1=$T1_MS target=$MID_MS")
|
||||
}
|
||||
|
||||
fun command(runtime: WeatherRuntime, action: Int): WeatherRuntime.CommandResult = when (action) {
|
||||
1 -> runtime.setEncodedForecastPlaybackState(true)
|
||||
2 -> runtime.setEncodedForecastPlaybackState(false)
|
||||
3 -> runtime.setEncodedForecastTime(T0_MS + 900_000L)
|
||||
4 -> runtime.setEncodedForecastTime(T0_MS + 2_700_000L)
|
||||
5 -> runtime.setEncodedForecastPlaybackSpeed(2.0f)
|
||||
6 -> runtime.setEncodedForecastPlaybackSpeed(1.0f)
|
||||
7 -> runtime.clearAllEncodedFields()
|
||||
else -> WeatherRuntime.CommandResult.REJECTED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.maplibre.android.testapp.activity.particle
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
/** STEP-099/100 offline source readiness and retained segment fixtures. */
|
||||
internal object Step099ReadinessFixture {
|
||||
enum class Scenario(val label: String) {
|
||||
T1_STATIC("T1 STATIC"),
|
||||
T2_GEOJSON_1HZ("T2 GEOJSON 1HZ"),
|
||||
T3_PERMANENT_UNREADY("T3 PERMANENT UNREADY"),
|
||||
T4_RCR("T4 R-C-R")
|
||||
}
|
||||
|
||||
const val PARTICLE_SOURCE = "particle-fixture"
|
||||
const val STATIC_SOURCE = "step099-static"
|
||||
const val UPPER_STATIC_SOURCE = "step099-upper-static"
|
||||
const val DYNAMIC_SOURCE = "step099-dynamic"
|
||||
const val DYNAMIC_SOURCE_B = "step099-dynamic-b"
|
||||
const val UNREADY_SOURCE = "step099-unready-raster-array"
|
||||
|
||||
const val STATIC_LAYER = "step099-static-fill"
|
||||
const val WEATHER_LAYER = "step099-weather"
|
||||
const val PARTICLE_LAYER = "step099-particle"
|
||||
const val DYNAMIC_LAYER = "step099-dynamic-circle"
|
||||
const val DYNAMIC_LAYER_B = "step099-dynamic-circle-b"
|
||||
const val UPPER_STATIC_LAYER = "step099-static-upper-fill"
|
||||
|
||||
fun styleJson(scenario: Scenario): String {
|
||||
val sources = JSONObject()
|
||||
.put(PARTICLE_SOURCE, JSONObject()
|
||||
.put("type", "raster-array")
|
||||
.put("fixture", "uniform-east")
|
||||
.put("metadata", "asset://integration/particle-fixtures/uniform-east/metadata.json")
|
||||
.put("field", "asset://integration/particle-fixtures/uniform-east/field.f32"))
|
||||
.put(STATIC_SOURCE, geoJsonSource(
|
||||
"""{"type":"Polygon","coordinates":[[[112,0],[158,0],[158,56],[112,56],[112,0]]]}"""
|
||||
))
|
||||
|
||||
if (scenario == Scenario.T2_GEOJSON_1HZ || scenario == Scenario.T4_RCR) {
|
||||
sources.put(DYNAMIC_SOURCE, geoJsonSource(
|
||||
"""{"type":"Point","coordinates":[135.0,28.0]}"""
|
||||
))
|
||||
}
|
||||
if (scenario == Scenario.T1_STATIC || scenario == Scenario.T3_PERMANENT_UNREADY) {
|
||||
sources.put(UPPER_STATIC_SOURCE, geoJsonSource(
|
||||
"""{"type":"Polygon","coordinates":[[[116,4],[154,4],[154,52],[116,52],[116,4]]]}"""
|
||||
))
|
||||
}
|
||||
if (scenario == Scenario.T2_GEOJSON_1HZ) {
|
||||
sources.put(DYNAMIC_SOURCE_B, geoJsonSource(
|
||||
"""{"type":"Point","coordinates":[136.0,29.0]}"""
|
||||
))
|
||||
}
|
||||
if (scenario == Scenario.T3_PERMANENT_UNREADY) {
|
||||
// A valid host-submission RasterArraySource with no submitted field is intentionally isLoaded=false.
|
||||
// It has no layer on purpose: STEP-099 verifies whether a globally present but unrelated Source
|
||||
// can still hold RenderOrchestrator::isLoaded() false.
|
||||
sources.put(UNREADY_SOURCE, JSONObject()
|
||||
.put("type", "raster-array")
|
||||
.put("host-field-submission", true))
|
||||
}
|
||||
|
||||
val layers = JSONArray()
|
||||
.put(JSONObject()
|
||||
.put("id", "step099-background")
|
||||
.put("type", "background")
|
||||
.put("paint", JSONObject().put("background-color", "#0b2942")))
|
||||
.put(JSONObject()
|
||||
.put("id", STATIC_LAYER)
|
||||
.put("type", "fill")
|
||||
.put("source", STATIC_SOURCE)
|
||||
.put("paint", JSONObject()
|
||||
.put("fill-color", "#176b87")
|
||||
.put("fill-opacity", 0.58)))
|
||||
.put(JSONObject()
|
||||
.put("id", WEATHER_LAYER)
|
||||
.put("type", "weather-color")
|
||||
.put("source", PARTICLE_SOURCE)
|
||||
.put("paint", JSONObject().put("weather-color-opacity", 0.32)))
|
||||
.put(JSONObject()
|
||||
.put("id", PARTICLE_LAYER)
|
||||
.put("type", "raster-particle")
|
||||
.put("source", PARTICLE_SOURCE)
|
||||
.put("paint", JSONObject()
|
||||
.put("raster-particle-active", true)
|
||||
.put("raster-particle-particle-count", 1024)))
|
||||
|
||||
if (scenario == Scenario.T2_GEOJSON_1HZ || scenario == Scenario.T4_RCR) {
|
||||
layers.put(JSONObject()
|
||||
.put("id", DYNAMIC_LAYER)
|
||||
.put("type", "circle")
|
||||
.put("source", DYNAMIC_SOURCE)
|
||||
.put("paint", JSONObject()
|
||||
.put("circle-radius", 12)
|
||||
.put("circle-color", "#ffcc00")
|
||||
.put("circle-opacity", 0.95)))
|
||||
}
|
||||
if (scenario == Scenario.T2_GEOJSON_1HZ) {
|
||||
layers.put(JSONObject()
|
||||
.put("id", DYNAMIC_LAYER_B)
|
||||
.put("type", "circle")
|
||||
.put("source", DYNAMIC_SOURCE_B)
|
||||
.put("paint", JSONObject()
|
||||
.put("circle-radius", 10)
|
||||
.put("circle-color", "#ff6600")
|
||||
.put("circle-opacity", 0.90)))
|
||||
}
|
||||
if (scenario == Scenario.T1_STATIC || scenario == Scenario.T3_PERMANENT_UNREADY) {
|
||||
layers.put(JSONObject()
|
||||
.put("id", UPPER_STATIC_LAYER)
|
||||
.put("type", "fill")
|
||||
.put("source", UPPER_STATIC_SOURCE)
|
||||
.put("paint", JSONObject().put("fill-color", "#d3a640").put("fill-opacity", 0.38)))
|
||||
}
|
||||
|
||||
return JSONObject()
|
||||
.put("version", 8)
|
||||
.put("name", "STEP-099 Source Readiness Diagnostics")
|
||||
.put("sources", sources)
|
||||
.put("layers", layers)
|
||||
.toString()
|
||||
}
|
||||
|
||||
fun planJson(scenario: Scenario): String {
|
||||
val segments = JSONArray()
|
||||
fun add(strategy: String, start: String? = null) {
|
||||
val segment = JSONObject().put("strategy", strategy)
|
||||
if (start != null) segment.put("startLayerId", start)
|
||||
segments.put(segment)
|
||||
}
|
||||
|
||||
add("RETAINED")
|
||||
add("CONTINUOUS", WEATHER_LAYER)
|
||||
add("RETAINED", if (scenario == Scenario.T2_GEOJSON_1HZ || scenario == Scenario.T4_RCR) DYNAMIC_LAYER else UPPER_STATIC_LAYER)
|
||||
|
||||
return JSONObject()
|
||||
.put("schemaVersion", 1)
|
||||
.put("segments", segments)
|
||||
.toString()
|
||||
}
|
||||
|
||||
private fun geoJsonSource(geometryJson: String): JSONObject = JSONObject()
|
||||
.put("type", "geojson")
|
||||
.put("data", JSONObject()
|
||||
.put("type", "Feature")
|
||||
.put("properties", JSONObject())
|
||||
.put("geometry", JSONObject(geometryJson)))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.maplibre.android.testapp.activity.particle
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
/** Offline STEP-102 R-C-R fixture; only the upper retained range changes placement capability. */
|
||||
internal object Step102PlacementFixture {
|
||||
enum class Scenario { T1_FREE_SYMBOL, T2_TWO_FREE }
|
||||
|
||||
const val STATIC_SOURCE = "step102-static"
|
||||
const val SYMBOL_SOURCE = "step102-symbol"
|
||||
private const val PARTICLE_SOURCE = "step102-field"
|
||||
private const val PARTICLE_LAYER = "step102-particle"
|
||||
private const val UPPER_LAYER = "step102-upper"
|
||||
|
||||
fun styleJson(scenario: Scenario): String {
|
||||
val sources = JSONObject()
|
||||
.put(STATIC_SOURCE, geoJsonSource(
|
||||
"""{"type":"Polygon","coordinates":[[[112,0],[158,0],[158,56],[112,56],[112,0]]]}"""))
|
||||
.put(PARTICLE_SOURCE, JSONObject()
|
||||
.put("type", "raster-array")
|
||||
.put("fixture", "uniform-east")
|
||||
.put("metadata", "asset://integration/particle-fixtures/uniform-east/metadata.json")
|
||||
.put("field", "asset://integration/particle-fixtures/uniform-east/field.f32"))
|
||||
if (scenario == Scenario.T1_FREE_SYMBOL) {
|
||||
sources.put(SYMBOL_SOURCE, geoJsonSource("""{"type":"Point","coordinates":[135.0,28.0]}"""))
|
||||
}
|
||||
val layers = JSONArray()
|
||||
.put(JSONObject().put("id", "step102-background").put("type", "background")
|
||||
.put("paint", JSONObject().put("background-color", "#0b2942")))
|
||||
.put(JSONObject().put("id", "step102-static-fill").put("type", "fill")
|
||||
.put("source", STATIC_SOURCE)
|
||||
.put("paint", JSONObject().put("fill-color", "#176b87").put("fill-opacity", 0.58)))
|
||||
.put(JSONObject().put("id", PARTICLE_LAYER).put("type", "raster-particle")
|
||||
.put("source", PARTICLE_SOURCE)
|
||||
.put("paint", JSONObject().put("raster-particle-active", true)
|
||||
.put("raster-particle-particle-count", 1024)))
|
||||
if (scenario == Scenario.T1_FREE_SYMBOL) {
|
||||
layers.put(JSONObject().put("id", UPPER_LAYER).put("type", "symbol")
|
||||
.put("source", SYMBOL_SOURCE)
|
||||
.put("layout", JSONObject().put("text-field", "STEP-102")
|
||||
.put("text-font", JSONArray().put("DIN Offc Pro Regular").put("Arial Unicode MS Regular"))
|
||||
.put("text-size", 20))
|
||||
.put("paint", JSONObject().put("text-color", "#ffcc00")))
|
||||
} else {
|
||||
layers.put(JSONObject().put("id", UPPER_LAYER).put("type", "fill")
|
||||
.put("source", STATIC_SOURCE)
|
||||
.put("paint", JSONObject().put("fill-color", "#d3a640").put("fill-opacity", 0.38)))
|
||||
}
|
||||
return JSONObject().put("version", 8).put("name", "STEP-102 Placement Locality")
|
||||
.put("glyphs", "asset://fonts/{fontstack}/{range}.pbf")
|
||||
.put("sources", sources).put("layers", layers).toString()
|
||||
}
|
||||
|
||||
fun planJson(): String = JSONObject().put("schemaVersion", 1)
|
||||
.put("segments", JSONArray()
|
||||
.put(JSONObject().put("strategy", "RETAINED"))
|
||||
.put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", PARTICLE_LAYER))
|
||||
.put(JSONObject().put("strategy", "RETAINED").put("startLayerId", UPPER_LAYER)))
|
||||
.toString()
|
||||
|
||||
private fun geoJsonSource(geometryJson: String): JSONObject = JSONObject().put("type", "geojson")
|
||||
.put("data", JSONObject().put("type", "Feature").put("properties", JSONObject())
|
||||
.put("geometry", JSONObject(geometryJson)))
|
||||
}
|
||||
@@ -5,10 +5,21 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/apkVersionView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDark"
|
||||
android:padding="12dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:scrollbars="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
|
||||
<org.maplibre.android.maps.MapView android:id="@id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="#DD102030" android:orientation="vertical" android:padding="4dp">
|
||||
<TextView android:id="@+id/validationStatus" android:layout_width="match_parent" android:layout_height="42dp" android:gravity="center_vertical" android:maxLines="2" android:paddingLeft="4dp" android:paddingRight="4dp" android:textColor="@android:color/white" android:textSize="12sp" />
|
||||
<TextView android:id="@+id/validationDiagnostics" android:layout_width="match_parent" android:layout_height="92dp" android:fontFamily="monospace" android:gravity="center_vertical" android:maxLines="6" android:paddingLeft="4dp" android:paddingRight="4dp" android:text="STEP-093 diagnostics: open STEP 093" android:textColor="#FFDD88" android:textSize="9sp" />
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal">
|
||||
<Button android:id="@+id/validationMenuTime" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="TIME" />
|
||||
<Button android:id="@+id/validationMenuVisual" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="VIS" />
|
||||
<Button android:id="@+id/validationMenuCount" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="COUNT" />
|
||||
<Button android:id="@+id/validationMenuMap" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="MAP" />
|
||||
<Button android:id="@+id/validationMenuRealWeather" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="REAL WEATHER" android:textSize="10sp" />
|
||||
<Button android:id="@+id/validationMenuPbfMode" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="PBF MODE" android:textSize="9sp" />
|
||||
<Button android:id="@+id/validationMenuNavSeaPbf" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="NAVSEA PBF" android:textSize="9sp" />
|
||||
<Button android:id="@+id/validationMenuRetainedCache" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="CACHE ON" android:textSize="9sp" />
|
||||
<Button android:id="@+id/validationMenuStep093" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="STEP 093" android:textSize="9sp" />
|
||||
<Button android:id="@+id/validationMenuMore" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="MORE" />
|
||||
</LinearLayout>
|
||||
<!-- Existing action IDs are invisible menu targets; no permanent button grid. -->
|
||||
<LinearLayout android:visibility="gone" android:layout_width="1dp" android:layout_height="1dp" android:orientation="vertical">
|
||||
<Button style="@style/ParticleValidationButton" android:id="@+id/validationActive"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationStatic"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationDisable"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationEnable"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationGlobal"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationOceanA"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationOceanB"/>
|
||||
<Button style="@style/ParticleValidationButton" android:id="@+id/validationUniformEast"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationUniformNorth"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVortex"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationTimeWindow"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationMix0"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationMix50"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationMix100"/>
|
||||
<Button style="@style/ParticleValidationButton" android:id="@+id/validationParticle256"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationParticle1024"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationParticle4096"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationParticle10000"/>
|
||||
<Button style="@style/ParticleValidationButton" android:id="@+id/validationAdvanceWindow"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationDuplicateWindow"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationProductionInvalid"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationHostFieldSubmit"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationRealWeather"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationHostFieldDuplicate"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationHostFieldInvalid"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualDefault"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualSizeDown"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualSizeUp"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualLengthDown"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualLengthUp"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualOpacityDown"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualOpacityUp"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualTrailDown"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualTrailUp"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualFadeDown"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualFadeUp"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationVisualColor"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationDateline"/><Button style="@style/ParticleValidationButton" android:id="@+id/validationWorldWrap"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="description_custom_layer">Overlay a custom native layer on the map</string>
|
||||
<string name="description_particle_core_layer_validation">Validate a source-less Core Style/RenderLayer, frame transform and repaint lifecycle</string>
|
||||
<string name="description_info_window_adapter">Learn how to create a custom InfoWindow</string>
|
||||
<string name="description_cameraposition">CameraPosition capabilities</string>
|
||||
<string name="description_map_fragment">Showcase MapFragment</string>
|
||||
|
||||
@@ -8,10 +8,55 @@
|
||||
<string name="activity_benchmark_world_tour">World Tour Benchmark</string>
|
||||
<string name="description_world_tour_benchmark">Writes out avg. fps and 1% fps after world tour with .flyTo()</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="apk_version_format">APK %1$s (%2$d) · %3$s/%4$s · Git %5$s</string>
|
||||
<string name="category_data">Data Sources</string>
|
||||
<string name="rendering_engine_unsupported">%1$s is not supported by this build. Use the multiBackend flavor to switch backends at runtime.</string>
|
||||
<string name="action_rendering_engine">Rendering Engine</string>
|
||||
<string name="rendering_engine_dialog_title">Select Rendering Engine</string>
|
||||
<string name="rendering_engine_current_label">%1$s (current)</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="particle_validation_active">ACTIVE</string>
|
||||
<string name="particle_validation_static">STATIC</string>
|
||||
<string name="particle_validation_disable">DISABLE</string>
|
||||
<string name="particle_validation_enable">ENABLE</string>
|
||||
<string name="particle_validation_active_status">30-02 ACTIVE: GPU State/Trail ping-pong requests frames. Move, zoom, rotate, pitch and resize.</string>
|
||||
<string name="particle_validation_static_status">30-02 STATIC: GPU ping-pong stopped requesting frames. Gestures must still render normally.</string>
|
||||
<string name="particle_validation_disabled_status">30-02 DISABLED: validation layer and its GPU resources were removed. Tap ENABLE to recreate them.</string>
|
||||
<string name="particle_validation_global">GLOBAL</string>
|
||||
<string name="particle_validation_ocean_a">OCEAN A</string>
|
||||
<string name="particle_validation_ocean_b">OCEAN B</string>
|
||||
<string name="particle_validation_dateline">DATELINE</string>
|
||||
<string name="particle_validation_world_wrap">WORLD WRAP</string>
|
||||
<string name="particle_validation_uniform_east">UNIFORM EAST</string>
|
||||
<string name="particle_validation_uniform_north">UNIFORM NORTH</string>
|
||||
<string name="particle_validation_vortex">VORTEX</string>
|
||||
<string name="particle_validation_time_window">T0 EAST / T1 NORTH</string>
|
||||
<string name="particle_validation_advance_window">ADVANCE WINDOW</string>
|
||||
<string name="particle_validation_duplicate_window">DUPLICATE T1</string>
|
||||
<string name="particle_validation_mix_0">MIX 0%</string>
|
||||
<string name="particle_validation_mix_50">MIX 50%</string>
|
||||
<string name="particle_validation_mix_100">MIX 100%</string>
|
||||
<string name="particle_validation_particles_256">256</string>
|
||||
<string name="particle_validation_particles_1024">1K</string>
|
||||
<string name="particle_validation_particles_4096">4K</string>
|
||||
<string name="particle_validation_particles_16384">16K</string>
|
||||
<string name="particle_validation_particles_65536">64K</string>
|
||||
<string name="particle_validation_particles_262144">256K</string>
|
||||
<string name="particle_validation_particles_1048576">1M</string>
|
||||
<string name="particle_validation_visual_default">VIS DEFAULT</string>
|
||||
<string name="particle_validation_visual_size_down">SIZE −</string>
|
||||
<string name="particle_validation_visual_size_up">SIZE +</string>
|
||||
<string name="particle_validation_visual_length_down">LENGTH −</string>
|
||||
<string name="particle_validation_visual_length_up">LENGTH +</string>
|
||||
<string name="particle_validation_visual_opacity_down">OPACITY −</string>
|
||||
<string name="particle_validation_visual_opacity_up">OPACITY +</string>
|
||||
<string name="particle_validation_visual_trail_down">TRAIL −</string>
|
||||
<string name="particle_validation_visual_trail_up">TRAIL +</string>
|
||||
<string name="particle_validation_visual_fade_down">FADE −</string>
|
||||
<string name="particle_validation_visual_fade_up">FADE +</string>
|
||||
<string name="particle_validation_visual_color">COLOR</string>
|
||||
<string name="particle_validation_global_active_status">40-01 GLOBAL ACTIVE: offline field simulation and current-frame composite.</string>
|
||||
<string name="particle_validation_global_static_status">40-01 GLOBAL STATIC: zero-step bootstrap and frozen composite.</string>
|
||||
<string name="particle_validation_ocean_a_status">40-01 OCEAN_ONLY_A: shared coastal-mask consumer.</string>
|
||||
<string name="particle_validation_ocean_b_status">40-01 OCEAN_ONLY_B: shared coastal-mask consumer.</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="ParticleValidationButton">
|
||||
<item name="android:layout_width">1dp</item>
|
||||
<item name="android:layout_height">32dp</item>
|
||||
<item name="android:minHeight">32dp</item>
|
||||
<item name="android:minWidth">0dp</item>
|
||||
<item name="android:padding">0dp</item>
|
||||
<item name="android:textSize">11sp</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -25,6 +25,7 @@
|
||||
<string name="activity_snapshot">Snapshot Activity</string>
|
||||
<string name="activity_snapshot_within">Snapshot with within expression</string>
|
||||
<string name="activity_custom_layer">Custom Layer</string>
|
||||
<string name="activity_particle_core_layer_validation">Particle Core Layer Validation</string>
|
||||
<string name="activity_map_padding">Map Padding</string>
|
||||
<string name="activity_debug_mode">Debug Mode</string>
|
||||
<string name="activity_offline">Offline Map</string>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
extra["mapLibreArtifactGroupId"] = "org.maplibre.gl"
|
||||
extra["mapLibreArtifactId"] = "android-sdk"
|
||||
extra["mapLibreArtifactTitle"] = "MapLibre Android"
|
||||
extra["mapLibreArtifactDescription"] = "MapLibre Android"
|
||||
extra["mapLibreDeveloperName"] = "MapLibre"
|
||||
extra["mapLibreDeveloperId"] = "maplibre"
|
||||
extra["mapLibreArtifactGroupId"] = "cc.navsea.maplibre"
|
||||
extra["mapLibreArtifactId"] = "android-sdk-particle"
|
||||
extra["mapLibreArtifactTitle"] = "NavSea MapLibre Particle Android"
|
||||
extra["mapLibreArtifactDescription"] = "NavSea MapLibre Particle Android"
|
||||
extra["mapLibreDeveloperName"] = "NavSea"
|
||||
extra["mapLibreDeveloperId"] = "navsea"
|
||||
extra["mapLibreArtifactUrl"] = "https://github.com/maplibre/maplibre-native"
|
||||
extra["mapLibreArtifactScmUrl"] = "scm:git@github.com:maplibre/maplibre-native.git"
|
||||
extra["mapLibreArtifactLicenseName"] = "BSD"
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.authentication.http.BasicAuthentication
|
||||
import org.gradle.external.javadoc.StandardJavadocDocletOptions
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.plugins.signing.SigningExtension
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
@@ -24,6 +25,13 @@ val publicationRepositoryUsername = providers.gradleProperty("publicationReposit
|
||||
.orElse(providers.environmentVariable("MAVEN_REPOSITORY_USERNAME"))
|
||||
val publicationRepositoryPassword = providers.gradleProperty("publicationRepositoryPassword")
|
||||
.orElse(providers.environmentVariable("MAVEN_REPOSITORY_PASSWORD"))
|
||||
// Gradle SigningExtension evaluates this Provider when a Sign task executes. Maven Local keeps
|
||||
// the release coordinate but does not need a PGP signatory; Maven Central publication does.
|
||||
val signingRequiredForMavenCentral = providers.provider {
|
||||
gradle.taskGraph.allTasks.any {
|
||||
it.name.endsWith("PublicationToMavenCentralRepository")
|
||||
}
|
||||
}
|
||||
|
||||
androidLibrary.publishing {
|
||||
singleVariant("vulkanRelease")
|
||||
@@ -67,6 +75,10 @@ afterEvaluate {
|
||||
signAllPublications()
|
||||
}
|
||||
}
|
||||
// signAllPublications() creates the signatures used by the formal Central path. Override
|
||||
// only its execution requirement: Local publish Sign tasks are skipped without a signatory,
|
||||
// while every *PublicationToMavenCentralRepository task remains signing-required.
|
||||
extensions.getByType<SigningExtension>().setRequired(signingRequiredForMavenCentral)
|
||||
}
|
||||
|
||||
// Configure task dependencies after all tasks are created
|
||||
|
||||
Reference in New Issue
Block a user