feat(ios): expose lossless-or-reject style filter API
This commit is contained in:
@@ -5,6 +5,23 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** 原始 Filter 接口的错误域。 */
|
||||
FOUNDATION_EXTERN MLN_EXPORT NSErrorDomain const MLNStyleFilterErrorDomain;
|
||||
|
||||
/** 原始 Filter 接口的稳定错误代码;不影响既有 predicate 接口。 */
|
||||
typedef NS_ENUM(NSInteger, MLNStyleFilterErrorCode) {
|
||||
/** E_FILTER_UNSUPPORTED:输入超限、不支持或无法证明无损重放;未写入。 */
|
||||
MLNStyleFilterErrorUnsupported = 1,
|
||||
/** E_FILTER_INVALID_LAYER:rawLayer 已失效或不是支持的 Vector Layer。 */
|
||||
MLNStyleFilterErrorInvalidLayer = 2,
|
||||
/** E_FILTER_WRONG_THREAD:必须在主线程调用。 */
|
||||
MLNStyleFilterErrorWrongThread = 3,
|
||||
/** E_FILTER_MUTATION_FAILED:写入/后验检查失败,已验证原 Filter 恢复。 */
|
||||
MLNStyleFilterErrorMutationFailed = 4,
|
||||
/** E_FILTER_ROLLBACK_FAILED:无法确认恢复;Host 必须废弃当前操作 Context。 */
|
||||
MLNStyleFilterErrorRollbackFailed = 5,
|
||||
};
|
||||
|
||||
/**
|
||||
``MLNVectorStyleLayer`` is an abstract superclass for style layers whose content
|
||||
is defined by an ``MLNShapeSource`` or ``MLNShapeSource`` object.
|
||||
@@ -53,6 +70,43 @@ MLN_EXPORT
|
||||
*/
|
||||
@property (nonatomic, nullable) NSPredicate *predicate;
|
||||
|
||||
/**
|
||||
读取当前有效 rawLayer 的 Filter JSON 快照;不经过 NSPredicate 或初始化 Style JSON。
|
||||
空 Filter 返回 NSNull;失败返回 nil,并通过 error 报告原因。
|
||||
|
||||
仅返回经现有 Foundation/Core 转换器往返验证的子集:JSON 最大深度 64(根为 1),
|
||||
最多 4096 个节点(含对象键),字符串和键的 UTF-8 总量最多 256 KiB。
|
||||
字符串/键含 U+0000 或非法 UTF-8、非有限数值、负零、绝对值大于 2^53-1 的数值、
|
||||
非 JSON Core 值以及不可重放的 Filter 均报 MLNStyleFilterErrorUnsupported。
|
||||
结构比较保留数组顺序、对象键和值、布尔/数值区别;仅精确可表示的数值存储类型可归一。
|
||||
|
||||
常用 all/any/!/比较/get/has/in/literal、类型断言和 collator 可以进入验证;
|
||||
其他操作符、混合 legacy/表达式 Filter 或发生折叠/默认值补全而无法结构往返者可能拒绝。
|
||||
返回对象与 Layer 状态分离,调用方不得在调用期间并发修改输入或 Style。
|
||||
|
||||
必须在主线程调用。本接口不证明 Layer 属于当前 Style:未加入或正常移除后仍存活的
|
||||
Layer 可以使用。Host 必须另行核验当前 Style 按 ID 查得的对象身份、Token/代次等;
|
||||
rawLayer 已销毁则失败。该接口不管理 Host Context 或 revision。
|
||||
*/
|
||||
- (nullable id)rawStyleFilterJSONObjectWithError:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
/**
|
||||
只设置当前 rawLayer 的 Filter。输入为 Filter 数组或 NSNull(精确恢复空 Filter);
|
||||
nil 不作为清空指令。NSDecimalNumber 及不支持的 Foundation 类型也会被拒绝。
|
||||
|
||||
在写入前对输入建立独立快照、验证 Core 解析/序列化不丢失结构,并证明新旧 Filter
|
||||
可以经上述 getter 的同一路径重放。任何准入失败都返回 NO,原 Layer 不变。
|
||||
输入必须已是 Core 可往返的形式;显式 literal 标量、常量折叠或省略 collator 默认项
|
||||
等即使语义可能等价,也可被保守拒绝。具体限制与 rawStyleFilterJSONObjectWithError: 一致。
|
||||
|
||||
写入后重新检查实际 Filter;若不符或写入抛出异常则尝试恢复原 Filter。
|
||||
MLNStyleFilterErrorMutationFailed 表示恢复已核实,但宿主仍应重新获取状态;
|
||||
MLNStyleFilterErrorRollbackFailed 表示状态无法确认,宿主必须废弃当前操作 Context。
|
||||
成功仅代表本次 Filter 操作,不能替代 Host 的 Style 成员关系/外部修改检测。
|
||||
不修改 Paint/Layout/Source/层序,也不移除 Layer 或重载 Style。
|
||||
*/
|
||||
- (BOOL)setRawStyleFilterJSONObject:(id)json error:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -1,8 +1,382 @@
|
||||
#import "MLNVectorStyleLayer.h"
|
||||
#import "MLNStyleLayer_Private.h"
|
||||
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <mln/style/conversion/filter.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
NSErrorDomain const MLNStyleFilterErrorDomain = @"MLNStyleFilterErrorDomain";
|
||||
|
||||
namespace {
|
||||
using Filter = mln::style::Filter;
|
||||
using Value = mln::Value;
|
||||
using Array = std::vector<Value>;
|
||||
using Object = std::unordered_map<std::string, Value>;
|
||||
|
||||
constexpr std::size_t MLNFilterMaxDepth = 64;
|
||||
constexpr std::size_t MLNFilterMaxNodes = 4096;
|
||||
constexpr std::size_t MLNFilterMaxStringBytes = 256 * 1024;
|
||||
constexpr int64_t MLNFilterMaxInteger = 9007199254740991LL;
|
||||
|
||||
// 对象键也占一个节点;循环 Foundation 容器在触及深度或节点上限时拒绝。
|
||||
struct FilterBudget {
|
||||
std::size_t nodes = 0;
|
||||
std::size_t stringBytes = 0;
|
||||
|
||||
bool enter(std::size_t depth) {
|
||||
return depth <= MLNFilterMaxDepth && ++nodes <= MLNFilterMaxNodes;
|
||||
}
|
||||
bool string(const std::string &text) {
|
||||
if (text.size() > MLNFilterMaxStringBytes - stringBytes ||
|
||||
text.find('\0') != std::string::npos) return false;
|
||||
if (![[NSString alloc] initWithBytes:text.data()
|
||||
length:text.size()
|
||||
encoding:NSUTF8StringEncoding]) return false;
|
||||
stringBytes += text.size();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<Value> FilterNumber(double number) {
|
||||
if (!std::isfinite(number) || std::abs(number) > MLNFilterMaxInteger ||
|
||||
(number == 0 && std::signbit(number))) return std::nullopt;
|
||||
return Value(number);
|
||||
}
|
||||
|
||||
// 只归一精确可表示的数值;不使用 JSON 文本比较、NSPredicate 或 Filter 的指针相等。
|
||||
std::optional<Value> NormalizeFilterValue(const Value &value, FilterBudget &budget,
|
||||
std::size_t depth = 1) {
|
||||
if (!budget.enter(depth)) return std::nullopt;
|
||||
return value.match(
|
||||
[](mln::NullValue) -> std::optional<Value> { return Value(mln::NullValue()); },
|
||||
[](bool v) -> std::optional<Value> { return Value(v); },
|
||||
[](int64_t v) -> std::optional<Value> {
|
||||
if (v < -MLNFilterMaxInteger || v > MLNFilterMaxInteger) return std::nullopt;
|
||||
return FilterNumber(static_cast<double>(v));
|
||||
},
|
||||
[](uint64_t v) -> std::optional<Value> {
|
||||
if (v > static_cast<uint64_t>(MLNFilterMaxInteger)) return std::nullopt;
|
||||
return FilterNumber(static_cast<double>(v));
|
||||
},
|
||||
[](float v) { return FilterNumber(v); }, [](double v) { return FilterNumber(v); },
|
||||
[&](const std::string &v) -> std::optional<Value> {
|
||||
return budget.string(v) ? std::optional<Value>(Value(v)) : std::nullopt;
|
||||
},
|
||||
[&](const Array &items) -> std::optional<Value> {
|
||||
if (items.size() > MLNFilterMaxNodes - budget.nodes) return std::nullopt;
|
||||
Array result;
|
||||
for (const auto &item : items) {
|
||||
auto child = NormalizeFilterValue(item, budget, depth + 1);
|
||||
if (!child) return std::nullopt;
|
||||
result.push_back(std::move(*child));
|
||||
}
|
||||
return Value(std::move(result));
|
||||
},
|
||||
[&](const Object &items) -> std::optional<Value> {
|
||||
if (items.size() > (MLNFilterMaxNodes - budget.nodes) / 2) return std::nullopt;
|
||||
Object result;
|
||||
for (const auto &item : items) {
|
||||
if (!budget.enter(depth + 1) || !budget.string(item.first)) return std::nullopt;
|
||||
auto child = NormalizeFilterValue(item.second, budget, depth + 1);
|
||||
if (!child) return std::nullopt;
|
||||
result.emplace(item.first, std::move(*child));
|
||||
}
|
||||
return Value(std::move(result));
|
||||
},
|
||||
[](const auto &) -> std::optional<Value> { return std::nullopt; });
|
||||
}
|
||||
|
||||
std::optional<Value> NormalizeFilterValue(const Value &value) {
|
||||
FilterBudget budget;
|
||||
return NormalizeFilterValue(value, budget);
|
||||
}
|
||||
|
||||
std::optional<Value> SnapshotFilterJSON(id json, FilterBudget &budget, std::size_t depth = 1) {
|
||||
if (!json || !budget.enter(depth)) return std::nullopt;
|
||||
if (json == [NSNull null]) return Value(mln::NullValue());
|
||||
if ([json isKindOfClass:[NSString class]]) {
|
||||
NSString *text = json;
|
||||
if (text.length > MLNFilterMaxStringBytes - budget.stringBytes) return std::nullopt;
|
||||
NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
|
||||
if (!data || data.length > MLNFilterMaxStringBytes - budget.stringBytes) return std::nullopt;
|
||||
std::string value(data.length ? static_cast<const char *>(data.bytes) : "", data.length);
|
||||
return budget.string(value) ? std::optional<Value>(Value(std::move(value))) : std::nullopt;
|
||||
}
|
||||
if ([json isKindOfClass:[NSNumber class]]) {
|
||||
if ([json isKindOfClass:[NSDecimalNumber class]]) return std::nullopt;
|
||||
if (CFGetTypeID((__bridge CFTypeRef)json) == CFBooleanGetTypeID())
|
||||
return Value([json boolValue] != NO);
|
||||
const char *type = [json objCType];
|
||||
if (!type || !type[0] || type[1] || !std::strchr("cCsSiIlLqQfdB", type[0]))
|
||||
return std::nullopt;
|
||||
return FilterNumber([json doubleValue]);
|
||||
}
|
||||
if ([json isKindOfClass:[NSArray class]]) {
|
||||
NSArray *items = json;
|
||||
if (items.count > MLNFilterMaxNodes - budget.nodes) return std::nullopt;
|
||||
Array result;
|
||||
for (id item in items) {
|
||||
auto child = SnapshotFilterJSON(item, budget, depth + 1);
|
||||
if (!child) return std::nullopt;
|
||||
result.push_back(std::move(*child));
|
||||
}
|
||||
return Value(std::move(result));
|
||||
}
|
||||
if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *items = json;
|
||||
if (items.count > (MLNFilterMaxNodes - budget.nodes) / 2) return std::nullopt;
|
||||
Object result;
|
||||
for (id key in items) {
|
||||
if (![key isKindOfClass:[NSString class]]) return std::nullopt;
|
||||
auto name = SnapshotFilterJSON(key, budget, depth + 1);
|
||||
auto child = name ? SnapshotFilterJSON(items[key], budget, depth + 1) : std::nullopt;
|
||||
if (!name || !child) return std::nullopt;
|
||||
if (!result.emplace(name->get<std::string>(), std::move(*child)).second) return std::nullopt;
|
||||
}
|
||||
return Value(std::move(result));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Value> SnapshotFilterJSON(id json) {
|
||||
FilterBudget budget;
|
||||
return SnapshotFilterJSON(json, budget);
|
||||
}
|
||||
|
||||
// 有限操作符准入;literal 的载荷是 JSON 数据,不按表达式解释。
|
||||
// 特别在 Core 访问 ["in"] 的缺失参数前拒绝非法形状。
|
||||
bool FilterSyntax(const Value &value) {
|
||||
if (!value.is<Array>()) return !value.is<Object>();
|
||||
const auto &items = value.get<Array>();
|
||||
if (items.empty() || !items[0].is<std::string>()) return false;
|
||||
const auto &op = items[0].get<std::string>();
|
||||
const auto count = items.size();
|
||||
if (op == "literal") return count == 2;
|
||||
if (op == "collator") {
|
||||
if (count != 2 || !items[1].is<Object>()) return false;
|
||||
for (const auto &item : items[1].get<Object>()) if (!FilterSyntax(item.second)) return false;
|
||||
return true;
|
||||
}
|
||||
bool valid = (op == "all" || op == "any" || op == "none") ? count >= 1
|
||||
: op == "!" ? count == 2
|
||||
: (op == "==" || op == "!=" || op == "<" || op == "<=" || op == ">" || op == ">=")
|
||||
? (count == 3 || count == 4)
|
||||
: (op == "get" || op == "has") ? (count == 2 || count == 3)
|
||||
: op == "!has" ? count == 2
|
||||
: (op == "in" || op == "!in") ? count >= 3
|
||||
: (op == "string" || op == "number" || op == "boolean") ? count >= 2
|
||||
: false;
|
||||
if (!valid) return false;
|
||||
for (std::size_t i = 1; i < count; ++i) if (!FilterSyntax(items[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 仅接受具有明确参数形状的 legacy Filter,拒绝 Core 宽松分支可能忽略的混合表达式。
|
||||
bool LegacyFilterSyntax(const Value &value) {
|
||||
if (!value.is<Array>()) return false;
|
||||
const auto &items = value.get<Array>();
|
||||
if (items.empty() || !items[0].is<std::string>()) return false;
|
||||
const auto &op = items[0].get<std::string>();
|
||||
if (op == "all" || op == "any" || op == "none") {
|
||||
if (items.size() < 2) return false;
|
||||
for (std::size_t i = 1; i < items.size(); ++i) if (!LegacyFilterSyntax(items[i])) return false;
|
||||
return true;
|
||||
}
|
||||
if (items.size() < 2 || !items[1].is<std::string>()) return false;
|
||||
if (op == "has" || op == "!has") return items.size() == 2;
|
||||
bool valid = (op == "in" || op == "!in") ? items.size() >= 3
|
||||
: (op == "==" || op == "!=" || op == "<" || op == "<=" || op == ">" || op == ">=")
|
||||
&& items.size() == 3;
|
||||
if (!valid) return false;
|
||||
for (std::size_t i = 2; i < items.size(); ++i)
|
||||
if (items[i].is<Array>() || items[i].is<Object>()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterExpressionFits(const Filter &filter) {
|
||||
if (!filter.expression) return true;
|
||||
if (!*filter.expression) return false;
|
||||
std::size_t nodes = 0;
|
||||
const auto visit = [&](const auto &self, const mln::style::expression::Expression &expression,
|
||||
std::size_t depth) -> bool {
|
||||
if (depth > MLNFilterMaxDepth || ++nodes > MLNFilterMaxNodes) return false;
|
||||
bool valid = true;
|
||||
expression.eachChild([&](const auto &child) { if (valid) valid = self(self, child, depth + 1); });
|
||||
return valid;
|
||||
};
|
||||
return visit(visit, **filter.expression, 1);
|
||||
}
|
||||
|
||||
bool SameFilter(const Filter &actual, const Filter &expected, const Value &expectedJSON) {
|
||||
if (!FilterExpressionFits(actual) || !FilterExpressionFits(expected)) return false;
|
||||
auto serialized = NormalizeFilterValue(actual.serialize());
|
||||
if (!serialized || *serialized != expectedJSON ||
|
||||
actual.expression.has_value() != expected.expression.has_value()) return false;
|
||||
if (!actual.expression) return true;
|
||||
auto actualExpression = NormalizeFilterValue((**actual.expression).serialize());
|
||||
auto expectedExpression = NormalizeFilterValue((**expected.expression).serialize());
|
||||
return actualExpression && expectedExpression && *actualExpression == *expectedExpression &&
|
||||
**actual.expression == **expected.expression;
|
||||
}
|
||||
|
||||
std::optional<Filter> ParseFilterJSON(const Value &expected, id json) {
|
||||
if (expected.is<mln::NullValue>()) return Filter();
|
||||
if (!expected.is<Array>() || !FilterSyntax(expected)) return std::nullopt;
|
||||
mln::style::conversion::Error error;
|
||||
auto parsed = mln::style::conversion::convert<Filter>(
|
||||
mln::style::conversion::makeConvertible(json), error);
|
||||
if (!parsed || !FilterExpressionFits(*parsed)) return std::nullopt;
|
||||
auto serialized = NormalizeFilterValue(parsed->serialize());
|
||||
if (!serialized || *serialized != expected || !parsed->expression) return std::nullopt;
|
||||
auto expression = NormalizeFilterValue((**parsed->expression).serialize());
|
||||
if (!expression || (*expression != expected && !LegacyFilterSyntax(expected))) return std::nullopt;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// 输出真实桥接对象前,先检查 C++ 树,再核对桥接值和 Core 重放的结构与表达式语义。
|
||||
id CheckedFilterJSON(const Filter &filter, Value &expected) {
|
||||
if (!FilterExpressionFits(filter)) return nil;
|
||||
auto normalized = NormalizeFilterValue(filter.serialize());
|
||||
if (!normalized) return nil;
|
||||
id json = MLNJSONObjectFromMBGLValue(*normalized);
|
||||
auto bridged = SnapshotFilterJSON(json);
|
||||
if (!bridged || *bridged != *normalized) return nil;
|
||||
auto replayed = ParseFilterJSON(*normalized, json);
|
||||
if (!replayed || !SameFilter(filter, *replayed, *normalized)) return nil;
|
||||
expected = std::move(*normalized);
|
||||
return json;
|
||||
}
|
||||
|
||||
NSError *FilterError(MLNStyleFilterErrorCode code, NSString *detail) {
|
||||
NSString *identifier = @"E_FILTER_UNSUPPORTED";
|
||||
switch (code) {
|
||||
case MLNStyleFilterErrorUnsupported: identifier = @"E_FILTER_UNSUPPORTED"; break;
|
||||
case MLNStyleFilterErrorInvalidLayer: identifier = @"E_FILTER_INVALID_LAYER"; break;
|
||||
case MLNStyleFilterErrorWrongThread: identifier = @"E_FILTER_WRONG_THREAD"; break;
|
||||
case MLNStyleFilterErrorMutationFailed: identifier = @"E_FILTER_MUTATION_FAILED"; break;
|
||||
case MLNStyleFilterErrorRollbackFailed: identifier = @"E_FILTER_ROLLBACK_FAILED"; break;
|
||||
}
|
||||
return [NSError errorWithDomain:MLNStyleFilterErrorDomain code:code
|
||||
userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@: %@", identifier, detail]}];
|
||||
}
|
||||
|
||||
mln::style::Layer *FilterLayer(MLNVectorStyleLayer *receiver, NSError **error) {
|
||||
if (![NSThread isMainThread]) {
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorWrongThread, @"原始 Filter 接口必须在主线程调用。");
|
||||
return nullptr;
|
||||
}
|
||||
auto *layer = receiver.rawLayer;
|
||||
if (!layer || layer->getTypeInfo()->tileKind != mln::style::LayerTypeInfo::TileKind::Geometry ||
|
||||
layer->getTypeInfo()->source != mln::style::LayerTypeInfo::Source::Required) {
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorInvalidLayer, @"rawLayer 已失效或不是 Vector Layer。");
|
||||
return nullptr;
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
|
||||
// 弱指针仅保护后验检查/回滚,不表示 Style 成员关系,也不管理 Host Context。
|
||||
NSError *ApplyFilter(MLNVectorStyleLayer *receiver, const Filter &next, const Value &nextJSON,
|
||||
const Filter &previous, const Value &previousJSON,
|
||||
const mapbox::base::WeakPtr<mln::style::Layer> &weakLayer) {
|
||||
const auto current = [&]() {
|
||||
auto *layer = weakLayer.get();
|
||||
return layer && receiver.rawLayer == layer ? layer : nullptr;
|
||||
};
|
||||
bool attempted = false;
|
||||
try {
|
||||
@try {
|
||||
auto *layer = current();
|
||||
if (!layer) return FilterError(MLNStyleFilterErrorInvalidLayer, @"写入前 rawLayer 已失效或被替换;未写入。");
|
||||
if (!SameFilter(layer->getFilter(), previous, previousJSON))
|
||||
return FilterError(MLNStyleFilterErrorUnsupported, @"验证期间原 Filter 已改变;未写入。");
|
||||
attempted = true;
|
||||
layer->setFilter(next);
|
||||
if (current() && SameFilter(current()->getFilter(), next, nextJSON)) return nil;
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
// 写入或 observer 抛出异常后,仍进入下面的原状态恢复和独立核对。
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
if (!attempted)
|
||||
return FilterError(MLNStyleFilterErrorUnsupported, @"写入前验证异常;未写入。");
|
||||
bool restored = false;
|
||||
try {
|
||||
@try {
|
||||
if (auto *layer = current()) layer->setFilter(previous);
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
// observer 可以在原 Filter 已恢复后抛出异常,恢复核对必须独立执行。
|
||||
try {
|
||||
@try {
|
||||
restored = current() && SameFilter(current()->getFilter(), previous, previousJSON);
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return restored ? FilterError(MLNStyleFilterErrorMutationFailed, @"写入或后验检查失败;原 Filter 已恢复并核实。")
|
||||
: FilterError(MLNStyleFilterErrorRollbackFailed, @"无法确认原 Filter 恢复;Host 必须废弃当前 Context。");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@implementation MLNVectorStyleLayer
|
||||
|
||||
- (id)rawStyleFilterJSONObjectWithError:(NSError **)error {
|
||||
if (error) *error = nil;
|
||||
auto *layer = FilterLayer(self, error);
|
||||
if (!layer) return nil;
|
||||
try {
|
||||
@try {
|
||||
Value expected;
|
||||
const Filter filter = layer->getFilter();
|
||||
if (id json = CheckedFilterJSON(filter, expected)) return json;
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorUnsupported, @"当前 Filter 超限、不受支持或无法证明无损重放。");
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)setRawStyleFilterJSONObject:(id)json error:(NSError **)error {
|
||||
if (error) *error = nil;
|
||||
auto *layer = FilterLayer(self, error);
|
||||
if (!layer) return NO;
|
||||
auto weakLayer = layer->makeWeakPtr();
|
||||
try {
|
||||
@try {
|
||||
const Filter previous = layer->getFilter();
|
||||
auto input = SnapshotFilterJSON(json);
|
||||
if (input) {
|
||||
// 解析独立的桥接快照,避免转换器读取调用方的可变容器。
|
||||
id snapshot = MLNJSONObjectFromMBGLValue(*input);
|
||||
auto bridged = SnapshotFilterJSON(snapshot);
|
||||
auto next = bridged && *bridged == *input ? ParseFilterJSON(*input, snapshot) : std::nullopt;
|
||||
Value nextJSON, previousJSON;
|
||||
if (next && CheckedFilterJSON(*next, nextJSON) && nextJSON == *input &&
|
||||
CheckedFilterJSON(previous, previousJSON)) {
|
||||
NSError *failure = ApplyFilter(self, *next, nextJSON, previous, previousJSON, weakLayer);
|
||||
if (error) *error = failure;
|
||||
return failure == nil;
|
||||
}
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
(void)exception;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
if (error) *error = FilterError(MLNStyleFilterErrorUnsupported, @"输入或原 Filter 超限、不受支持或无法证明无损重放;未写入。");
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setPredicate:(NSPredicate *)predicate {
|
||||
[NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user