Files
MapLibreParticleLab/src/mbgl/particle/field/field_profile_validator.cpp
2026-09-13 10:18:33 +08:00

164 lines
8.6 KiB
C++

#include <mbgl/particle/field/field_profile_validator.hpp>
#include <algorithm>
#include <cmath>
#include <iterator>
#include <limits>
#include <map>
namespace mbgl::particle {
namespace {
using PlaneRef = const DecodedFieldPlane*;
// These are protocol-reserved Core semantic contracts, deliberately separate
// from product/profile resolution. Unknown semantic IDs remain extensible.
struct CoreSemanticContract {
uint16_t semanticID;
FieldValueClass valueClass;
uint16_t unitID;
FieldComponent component;
bool groupMustBeZero;
};
constexpr CoreSemanticContract CoreSemanticContracts[] = {
{1, FieldValueClass::VelocityComponent, FieldUnitMetersPerSecond, FieldComponent::UEast, false},
{2, FieldValueClass::VelocityComponent, FieldUnitMetersPerSecond, FieldComponent::VNorth, false},
{3, FieldValueClass::Validity, FieldUnitNone, FieldComponent::None, true},
{10, FieldValueClass::DirectionAngle, FieldUnitDegrees, FieldComponent::None, true},
{11, FieldValueClass::DirectionAngle, FieldUnitDegrees, FieldComponent::None, true},
};
const CoreSemanticContract* coreSemanticContract(const uint16_t semanticID) {
const auto found = std::find_if(std::begin(CoreSemanticContracts), std::end(CoreSemanticContracts),
[semanticID](const CoreSemanticContract& contract) {
return contract.semanticID == semanticID;
});
return found == std::end(CoreSemanticContracts) ? nullptr : &*found;
}
bool matchesCoreSemanticContract(const uint16_t semanticID,
const FieldValueClass valueClass,
const uint16_t unitID,
const uint16_t groupID,
const FieldComponent component) {
const auto* contract = coreSemanticContract(semanticID);
return !contract ||
(contract->valueClass == valueClass && contract->unitID == unitID && contract->component == component &&
(!contract->groupMustBeZero || groupID == 0));
}
bool sameKey(const FieldPlaneRequirement& requirement, const DecodedFieldPlane& plane) {
return requirement.semanticID == plane.key.semanticID && requirement.groupID == plane.key.groupID;
}
bool matches(const FieldPlaneRequirement& requirement, const DecodedFieldPlane& plane) {
return sameKey(requirement, plane) && requirement.valueClass == plane.valueClass &&
requirement.unitID == plane.unitID && requirement.component == plane.key.component &&
requirement.requiredFlag == plane.requiredFlag;
}
FieldProfileValidationResult result(const FieldProfileValidationCode code, const std::string& detail) {
return {code, detail};
}
bool isNumeric(const FieldDType dtype) {
return dtype != FieldDType::Bitset;
}
bool directionScaleIsCanonical(const float scale) {
constexpr float canonical = 360.0f / 65536.0f;
constexpr float tolerance = 4.0f * std::numeric_limits<float>::epsilon();
return std::fabs(scale - canonical) <= tolerance;
}
} // namespace
FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedFieldProfileContract& contract,
const DecodedFieldFrame& frame) {
if (contract.contractSchemaVersion != 1)
return result(FieldProfileValidationCode::UnsupportedContractSchema, "contractSchemaVersion");
if (!contract.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidContract, "contract");
if (!frame.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidFrame, "frame");
for (const auto& required : contract.requiredPlanes) {
if (!matchesCoreSemanticContract(required.semanticID, required.valueClass, required.unitID, required.groupID,
required.component))
return result(FieldProfileValidationCode::SemanticContractMismatch, "required Core semantic descriptor");
}
if (!contract.sharedValidityRequired)
return result(FieldProfileValidationCode::UnsupportedSharedValidityMode, "sharedValidityRequired=false");
if ((contract.timeRelation == FieldTimeRelation::ValidGteReference &&
frame.frameContentIdentity.validTimeMs < frame.referenceTimeMs) ||
(contract.timeRelation == FieldTimeRelation::ValidEqReference &&
frame.frameContentIdentity.validTimeMs != frame.referenceTimeMs))
return result(FieldProfileValidationCode::TimeRelationMismatch, "reference/valid time");
std::vector<PlaneRef> actual;
actual.reserve(frame.planes.size() + 1);
actual.push_back(&frame.validityPlane);
for (const auto& plane : frame.planes) actual.push_back(&plane);
for (const auto& required : contract.requiredPlanes) {
const auto same = std::find_if(actual.begin(), actual.end(), [&required](const PlaneRef plane) {
return sameKey(required, *plane);
});
if (same == actual.end()) return result(FieldProfileValidationCode::MissingRequiredPlane, "required plane absent");
if (!matches(required, **same))
return result(FieldProfileValidationCode::PlaneDescriptorMismatch, "required plane descriptor");
}
for (const auto plane : actual) {
if (!matchesCoreSemanticContract(plane->key.semanticID, plane->valueClass, plane->unitID, plane->key.groupID,
plane->key.component))
return result(FieldProfileValidationCode::SemanticContractMismatch, "actual Core semantic descriptor");
const auto declared = std::find_if(contract.requiredPlanes.begin(), contract.requiredPlanes.end(),
[plane](const FieldPlaneRequirement& requirement) {
return matches(requirement, *plane);
});
if (plane->requiredFlag && declared == contract.requiredPlanes.end())
return result(FieldProfileValidationCode::UndeclaredRequiredPlane, "actual REQUIRED plane");
if (!plane->requiredFlag && !contract.allowAdditionalPlanes && declared == contract.requiredPlanes.end())
return result(FieldProfileValidationCode::AdditionalPlaneNotAllowed, "additional plane");
if (plane->valueClass == FieldValueClass::Validity) {
if (plane->dtype != FieldDType::Bitset || plane->unitID != FieldUnitNone || plane->key.groupID != 0 ||
plane->key.component != FieldComponent::None)
return result(FieldProfileValidationCode::InvalidCoreUnit, "validity descriptor");
} else if (plane->valueClass == FieldValueClass::DirectionAngle) {
if (plane->unitID != FieldUnitDegrees || plane->key.component != FieldComponent::None || plane->key.groupID != 0 ||
plane->dtype != FieldDType::UInt16 || plane->offset != 0.0f || !directionScaleIsCanonical(plane->scale))
return result(FieldProfileValidationCode::InvalidCoreUnit, "direction circular quantization descriptor");
} else if (plane->valueClass == FieldValueClass::VelocityComponent) {
if (plane->unitID != FieldUnitMetersPerSecond || !isNumeric(plane->dtype))
return result(FieldProfileValidationCode::InvalidCoreUnit, "velocity unit/dtype");
} else if (plane->valueClass == FieldValueClass::Scalar && !isNumeric(plane->dtype)) {
return result(FieldProfileValidationCode::InvalidFrame, "scalar dtype");
}
}
std::map<uint16_t, std::vector<PlaneRef>> velocityGroups;
for (const auto plane : actual) {
if (plane->valueClass == FieldValueClass::VelocityComponent) velocityGroups[plane->key.groupID].push_back(plane);
}
for (const auto& entry : velocityGroups) {
if (entry.first == 0) return result(FieldProfileValidationCode::InvalidVelocityGroup, "velocity group zero");
PlaneRef u = nullptr;
PlaneRef v = nullptr;
for (const auto plane : entry.second) {
if (plane->key.component == FieldComponent::UEast) {
if (u) return result(FieldProfileValidationCode::InvalidVelocityGroup, "duplicate U");
u = plane;
} else if (plane->key.component == FieldComponent::VNorth) {
if (v) return result(FieldProfileValidationCode::InvalidVelocityGroup, "duplicate V");
v = plane;
}
}
if (!u || !v || u->count != v->count || u->unitID != v->unitID)
return result(FieldProfileValidationCode::InvalidVelocityGroup, "incomplete velocity group");
}
return result(FieldProfileValidationCode::Accepted, "accepted");
}
} // namespace mbgl::particle