115 lines
8.3 KiB
C++
115 lines
8.3 KiB
C++
#include <mbgl/particle/field/field_data_adapter.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <utility>
|
|
|
|
namespace mbgl::particle {
|
|
namespace {
|
|
ParticleFieldAdapterResult reject(ParticleFieldAdapterError error, std::string message) {
|
|
return {.snapshot = std::nullopt, .error = error, .message = std::move(message)};
|
|
}
|
|
bool validCommonDescriptor(const ParticleFieldDescriptor& d) {
|
|
return !d.fieldID.empty() && d.generation && d.validTime && d.width && d.height && d.west < d.east && d.south < d.north;
|
|
}
|
|
bool finitePlane(const std::vector<float>& values) {
|
|
return std::all_of(values.begin(), values.end(), [](float value) { return std::isfinite(value); });
|
|
}
|
|
bool isNoData(const std::optional<float>& noData, float value) { return noData && value == *noData; }
|
|
} // namespace
|
|
|
|
const char* particleFieldAdapterErrorName(ParticleFieldAdapterError error) {
|
|
switch (error) {
|
|
case ParticleFieldAdapterError::None: return "NONE";
|
|
case ParticleFieldAdapterError::InvalidDescriptor: return "INVALID_DESCRIPTOR";
|
|
case ParticleFieldAdapterError::UnsupportedFormat: return "UNSUPPORTED_FORMAT";
|
|
case ParticleFieldAdapterError::DimensionMismatch: return "DIMENSION_MISMATCH";
|
|
case ParticleFieldAdapterError::PayloadSizeMismatch: return "PAYLOAD_SIZE_MISMATCH";
|
|
case ParticleFieldAdapterError::InvalidTimestampOrIdentity: return "INVALID_TIMESTAMP_OR_IDENTITY";
|
|
case ParticleFieldAdapterError::NonFiniteData: return "NON_FINITE_DATA";
|
|
case ParticleFieldAdapterError::DecodeValidationFailure: return "DECODE_VALIDATION_FAILURE";
|
|
}
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
bool ParticleFieldSnapshot::isValid() const {
|
|
const auto cells = static_cast<size_t>(width) * height;
|
|
if (fieldID.empty() || !generation || !validTime || !width || !height || !(west < east) || !(south < north) ||
|
|
valid.size() != cells || (!hasScalar() && !hasVector())) return false;
|
|
if (hasScalar() && scalar.size() != cells) return false;
|
|
if (hasVector() && (vector.size() != cells * 2 || !vectorSemantics)) return false;
|
|
return (!hasVector() ? !vectorSemantics : true) && finitePlane(scalar) && finitePlane(vector);
|
|
}
|
|
|
|
bool ParticleFieldSnapshot::isTimeInterpolationCompatibleWith(const ParticleFieldSnapshot& other) const {
|
|
return width == other.width && height == other.height && west == other.west && south == other.south && east == other.east &&
|
|
north == other.north && hasScalar() == other.hasScalar() && hasVector() == other.hasVector() &&
|
|
vectorSemantics == other.vectorSemantics;
|
|
}
|
|
|
|
ParticleFieldAdapterResult ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor& d,
|
|
const ParticleFieldPayloadBundle& payloads) {
|
|
if (!validCommonDescriptor(d)) return reject(ParticleFieldAdapterError::InvalidTimestampOrIdentity, "field identity, time or grid is invalid");
|
|
const bool scalar = d.scalar.has_value(), vector = d.vector.has_value();
|
|
if ((!scalar && !vector) || scalar != payloads.scalar.has_value() || vector != payloads.vector.has_value() || !d.validity || !payloads.validity)
|
|
return reject(ParticleFieldAdapterError::InvalidDescriptor, "v2 channel declaration and payload presence differ");
|
|
if (vector != d.vectorSemantics.has_value())
|
|
return reject(ParticleFieldAdapterError::InvalidDescriptor, "vector semantics must be present exactly with vector");
|
|
const uint64_t cells = static_cast<uint64_t>(d.width) * d.height;
|
|
const auto planeMatches = [](const ParticleFieldPlaneDescriptor& p, const std::optional<std::vector<char>>& bytes,
|
|
uint64_t expected, const char* encoding, const char* layout) {
|
|
return p.byteLength == expected && bytes && bytes->size() == expected && p.encoding == encoding &&
|
|
(!layout[0] || p.layout == layout) && (!p.noData || std::isfinite(*p.noData));
|
|
};
|
|
if (scalar && !planeMatches(*d.scalar, payloads.scalar, cells * sizeof(float), "float32-le", ""))
|
|
return reject(ParticleFieldAdapterError::PayloadSizeMismatch, "scalar plane length or encoding is invalid");
|
|
if (vector && !planeMatches(*d.vector, payloads.vector, cells * 2 * sizeof(float), "float32-le", "interleaved-uv"))
|
|
return reject(ParticleFieldAdapterError::PayloadSizeMismatch, "vector plane length or encoding is invalid");
|
|
if (d.validity->byteLength != cells || payloads.validity->size() != cells || d.validity->encoding != "uint8")
|
|
return reject(ParticleFieldAdapterError::PayloadSizeMismatch, "validity plane length or encoding is invalid");
|
|
|
|
ParticleFieldSnapshot field;
|
|
field.fieldID = d.fieldID; field.fieldType = d.fieldType; field.displayDomain = d.displayDomain; field.validTime = d.validTime;
|
|
field.width = d.width; field.height = d.height; field.west = d.west; field.south = d.south; field.east = d.east; field.north = d.north;
|
|
field.dataVersion = d.dataVersion; field.generation = d.generation; field.fieldSha256 = d.fieldSha256; field.vectorSemantics = d.vectorSemantics;
|
|
field.valid.assign(payloads.validity->begin(), payloads.validity->end());
|
|
if (scalar) { field.scalar.resize(cells); std::memcpy(field.scalar.data(), payloads.scalar->data(), payloads.scalar->size()); }
|
|
if (vector) { field.vector.resize(cells * 2); std::memcpy(field.vector.data(), payloads.vector->data(), payloads.vector->size()); }
|
|
if (!finitePlane(field.scalar) || !finitePlane(field.vector)) return reject(ParticleFieldAdapterError::NonFiniteData, "field contains non-finite data");
|
|
for (size_t cell = 0; cell < field.valid.size(); ++cell) {
|
|
if (field.valid[cell] > 1) return reject(ParticleFieldAdapterError::DecodeValidationFailure, "validity values must be 0 or 1");
|
|
if (field.valid[cell]) {
|
|
if (scalar && isNoData(d.scalar->noData, field.scalar[cell])) return reject(ParticleFieldAdapterError::DecodeValidationFailure, "valid scalar cell contains noData");
|
|
if (vector && (isNoData(d.vector->noData, field.vector[cell * 2]) || isNoData(d.vector->noData, field.vector[cell * 2 + 1])))
|
|
return reject(ParticleFieldAdapterError::DecodeValidationFailure, "valid vector cell contains noData");
|
|
}
|
|
}
|
|
if (!field.isValid()) return reject(ParticleFieldAdapterError::DecodeValidationFailure, "decoded field is invalid");
|
|
return {.snapshot = std::move(field), .error = ParticleFieldAdapterError::None, .message = {}};
|
|
}
|
|
|
|
ParticleFieldAdapterResult ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor& d, const std::vector<char>& bytes, uint64_t generation) {
|
|
if (d.layout != "interleaved-uv" || d.encoding != "float32-le")
|
|
return reject(ParticleFieldAdapterError::UnsupportedFormat, "legacy v1 only supports interleaved-uv float32-le");
|
|
if (!d.generation || !generation || d.generation != generation || (d.stride && d.stride != 2 * sizeof(float)) || !std::isfinite(d.noData))
|
|
return reject(ParticleFieldAdapterError::InvalidDescriptor, "legacy v1 descriptor is invalid");
|
|
ParticleFieldDescriptor normalized = d;
|
|
normalized.vector = ParticleFieldPlaneDescriptor{.byteLength = d.byteLength, .encoding = d.encoding, .layout = d.layout, .noData = d.noData};
|
|
normalized.vectorSemantics = VectorSemantics::Velocity;
|
|
normalized.validity = ParticleFieldPlaneDescriptor{.byteLength = static_cast<uint64_t>(d.width) * d.height,
|
|
.encoding = "uint8", .layout = {}, .noData = std::nullopt};
|
|
std::vector<uint8_t> valid(static_cast<size_t>(normalized.validity->byteLength));
|
|
if (bytes.size() == static_cast<size_t>(d.width) * d.height * 2 * sizeof(float)) {
|
|
for (size_t cell = 0; cell < valid.size(); ++cell) {
|
|
float pair[2]; std::memcpy(pair, bytes.data() + cell * sizeof(pair), sizeof(pair));
|
|
if (!std::isfinite(pair[0]) || !std::isfinite(pair[1])) return reject(ParticleFieldAdapterError::NonFiniteData, "legacy v1 field contains non-finite data");
|
|
valid[cell] = (pair[0] == d.noData || pair[1] == d.noData) ? 0 : 1;
|
|
}
|
|
}
|
|
return decode(normalized, ParticleFieldPayloadBundle{.scalar = std::nullopt,
|
|
.vector = bytes,
|
|
.validity = std::vector<char>(valid.begin(), valid.end())});
|
|
}
|
|
} // namespace mbgl::particle
|