164 lines
8.6 KiB
C++
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
|