feat: add ios particle field runtime facade
This commit is contained in:
@@ -5,6 +5,7 @@ MLN_IOS_SDK_HEADERS = [
|
||||
"src/MLNCameraChangeReason.h",
|
||||
"src/MLNCompassButton.h",
|
||||
"src/MLNMapProjection.h",
|
||||
"src/MLNParticleFieldRuntime.h",
|
||||
"src/MLNMapView+IBAdditions.h",
|
||||
"src/MLNMapViewDelegate.h",
|
||||
"src/MLNMapView.h",
|
||||
@@ -67,6 +68,7 @@ MLN_IOS_PUBLIC_OBJCPP_SOURCE = [
|
||||
"src/MLNMapProjection.mm",
|
||||
"src/MLNMapView.mm",
|
||||
"src/MLNMapView+Impl.mm",
|
||||
"src/MLNParticleFieldRuntime.mm",
|
||||
"src/MLNScaleBar.mm",
|
||||
"src/UIColor+MLNAdditions.mm",
|
||||
"src/UIImage+MLNAdditions.mm",
|
||||
|
||||
122
platform/ios/src/MLNParticleFieldRuntime.h
Normal file
122
platform/ios/src/MLNParticleFieldRuntime.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MLNFoundation.h"
|
||||
#import "MLNMapView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
FOUNDATION_EXPORT NSErrorDomain const MLNParticleFieldRuntimeErrorDomain;
|
||||
|
||||
typedef NS_ERROR_ENUM(MLNParticleFieldRuntimeErrorDomain, MLNParticleFieldRuntimeErrorCode) {
|
||||
MLNParticleFieldRuntimeErrorInvalidArgument = 1,
|
||||
MLNParticleFieldRuntimeErrorDisposed,
|
||||
MLNParticleFieldRuntimeErrorMapUnavailable,
|
||||
MLNParticleFieldRuntimeErrorStyleNotReady,
|
||||
MLNParticleFieldRuntimeErrorSourceMissing,
|
||||
MLNParticleFieldRuntimeErrorSourceTypeMismatch,
|
||||
MLNParticleFieldRuntimeErrorWeatherColorLayerMissing,
|
||||
MLNParticleFieldRuntimeErrorWeatherColorLayerTypeMismatch,
|
||||
MLNParticleFieldRuntimeErrorParticleLayerMissing,
|
||||
MLNParticleFieldRuntimeErrorParticleLayerTypeMismatch,
|
||||
MLNParticleFieldRuntimeErrorLayerSourceMismatch,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, MLNParticleEncodedFieldSubmitResult) {
|
||||
/// Facade/lifecycle errors are returned through NSError and use no Core result.
|
||||
MLNParticleEncodedFieldSubmitResultNone = 0,
|
||||
MLNParticleEncodedFieldSubmitResultAcceptedInserted = 1,
|
||||
MLNParticleEncodedFieldSubmitResultAcceptedReplacedRevision = 2,
|
||||
MLNParticleEncodedFieldSubmitResultDuplicate = 3,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedDecode = -1,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedContract = -2,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedInvalidFieldKey = -3,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedIdentityConflict = -4,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedStaleRevision = -5,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedIncompatible = -6,
|
||||
MLNParticleEncodedFieldSubmitResultRejectedCapacity = -7,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, MLNParticleFieldValueClass) {
|
||||
MLNParticleFieldValueClassScalar = 0,
|
||||
MLNParticleFieldValueClassVelocityComponent = 1,
|
||||
MLNParticleFieldValueClassDirectionAngle = 2,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, MLNParticleEncodedParticleBindingMode) {
|
||||
MLNParticleEncodedParticleBindingModeVectorComponents = 0,
|
||||
MLNParticleEncodedParticleBindingModeDirectionAngle = 1,
|
||||
};
|
||||
|
||||
/** Host-neutral identifiers for the existing style JSON raster-array and particle layers. */
|
||||
MLN_EXPORT
|
||||
@interface MLNParticleFieldRuntimeConfiguration : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *sourceIdentifier;
|
||||
@property (nonatomic, copy, readonly) NSString *weatherColorLayerIdentifier;
|
||||
@property (nonatomic, copy, readonly) NSString *particleLayerIdentifier;
|
||||
|
||||
- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier
|
||||
weatherColorLayerIdentifier:(NSString *)weatherColorLayerIdentifier
|
||||
particleLayerIdentifier:(NSString *)particleLayerIdentifier NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
A map-scoped V4 QBIN control facade. It owns neither a provider nor Core field
|
||||
state; each operation resolves the current style again so style reloads fail closed.
|
||||
*/
|
||||
MLN_EXPORT
|
||||
@interface MLNParticleFieldRuntime : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) MLNParticleFieldRuntimeConfiguration *configuration;
|
||||
@property (nonatomic, readonly, getter=isDisposed) BOOL disposed;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (void)dispose;
|
||||
|
||||
- (MLNParticleEncodedFieldSubmitResult)submitEncodedFieldWithFieldKey:(NSString *)fieldKey
|
||||
fieldID:(NSString *)fieldID
|
||||
generation:(uint64_t)generation
|
||||
dataVersion:(uint64_t)dataVersion
|
||||
contractJSON:(NSString *)contractJSON
|
||||
qbin:(NSData *)qbin
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
- (BOOL)setEncodedColorBindingWithFieldKey:(NSString *)fieldKey
|
||||
semanticID:(uint16_t)semanticID
|
||||
groupID:(uint16_t)groupID
|
||||
expectedValueClass:(MLNParticleFieldValueClass)expectedValueClass
|
||||
expectedUnitID:(uint16_t)expectedUnitID
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
- (BOOL)setEncodedParticleBindingWithFieldKey:(NSString *)fieldKey
|
||||
mode:(MLNParticleEncodedParticleBindingMode)mode
|
||||
uSemanticID:(uint16_t)uSemanticID
|
||||
uGroupID:(uint16_t)uGroupID
|
||||
vSemanticID:(uint16_t)vSemanticID
|
||||
vGroupID:(uint16_t)vGroupID
|
||||
directionSemanticID:(uint16_t)directionSemanticID
|
||||
directionGroupID:(uint16_t)directionGroupID
|
||||
expectedUnitID:(uint16_t)expectedUnitID
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
- (BOOL)setEncodedForecastTime:(uint64_t)timestamp error:(NSError * _Nullable * _Nullable)error;
|
||||
- (BOOL)setEncodedForecastPlaybackState:(BOOL)playing error:(NSError * _Nullable * _Nullable)error;
|
||||
- (BOOL)setEncodedForecastPlaybackSpeed:(float)speed error:(NSError * _Nullable * _Nullable)error;
|
||||
- (BOOL)clearEncodedFieldWithFieldKey:(NSString *)fieldKey error:(NSError * _Nullable * _Nullable)error;
|
||||
- (BOOL)clearAllEncodedFieldsWithError:(NSError * _Nullable * _Nullable)error;
|
||||
|
||||
@end
|
||||
|
||||
/** MapView owns the runtime and disposes a replaced runtime before attaching a new one. */
|
||||
@interface MLNMapView (ParticleFieldRuntime)
|
||||
|
||||
- (nullable MLNParticleFieldRuntime *)replaceParticleFieldRuntimeWithConfiguration:(MLNParticleFieldRuntimeConfiguration *)configuration
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
- (nullable MLNParticleFieldRuntime *)particleFieldRuntime;
|
||||
- (void)disposeParticleFieldRuntime;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
253
platform/ios/src/MLNParticleFieldRuntime.mm
Normal file
253
platform/ios/src/MLNParticleFieldRuntime.mm
Normal file
@@ -0,0 +1,253 @@
|
||||
#import "MLNParticleFieldRuntime.h"
|
||||
|
||||
#import "MLNMapView_Private.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#include <mbgl/particle/field/field_profile_contract_json.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer_impl.hpp>
|
||||
#include <mbgl/style/layers/weather_color_layer.hpp>
|
||||
#include <mbgl/style/layers/weather_color_layer_impl.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
NSErrorDomain const MLNParticleFieldRuntimeErrorDomain = @"org.maplibre.MLNParticleFieldRuntime";
|
||||
|
||||
namespace {
|
||||
constexpr NSUInteger MaxEncodedIdentifierUTF8Bytes = 64;
|
||||
constexpr NSUInteger MaxContractUTF8Bytes = 256 * 1024;
|
||||
constexpr NSUInteger MaxQbinBytes = 64 * 1024 * 1024;
|
||||
|
||||
void setError(NSError **error, MLNParticleFieldRuntimeErrorCode code, NSString *description) {
|
||||
if (error) *error = [NSError errorWithDomain:MLNParticleFieldRuntimeErrorDomain
|
||||
code:code
|
||||
userInfo:@{NSLocalizedDescriptionKey: description}];
|
||||
}
|
||||
|
||||
bool hasValidIdentifier(NSString *value) {
|
||||
return value && value.length > 0 && [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= MaxEncodedIdentifierUTF8Bytes;
|
||||
}
|
||||
|
||||
bool hasValidContract(NSString *value) {
|
||||
return value && value.length > 0 && [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= MaxContractUTF8Bytes;
|
||||
}
|
||||
|
||||
std::string stringFromNSString(NSString *value) {
|
||||
const char *utf8 = value.UTF8String;
|
||||
return utf8 ? std::string(utf8) : std::string();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@implementation MLNParticleFieldRuntimeConfiguration
|
||||
|
||||
- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier
|
||||
weatherColorLayerIdentifier:(NSString *)weatherColorLayerIdentifier
|
||||
particleLayerIdentifier:(NSString *)particleLayerIdentifier {
|
||||
NSParameterAssert(sourceIdentifier.length > 0);
|
||||
NSParameterAssert(weatherColorLayerIdentifier.length > 0);
|
||||
NSParameterAssert(particleLayerIdentifier.length > 0);
|
||||
if (sourceIdentifier.length == 0 || weatherColorLayerIdentifier.length == 0 || particleLayerIdentifier.length == 0) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Particle field runtime identifiers must be non-empty."];
|
||||
}
|
||||
if ((self = [super init])) {
|
||||
_sourceIdentifier = [sourceIdentifier copy];
|
||||
_weatherColorLayerIdentifier = [weatherColorLayerIdentifier copy];
|
||||
_particleLayerIdentifier = [particleLayerIdentifier copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return [[[self class] allocWithZone:zone] initWithSourceIdentifier:_sourceIdentifier
|
||||
weatherColorLayerIdentifier:_weatherColorLayerIdentifier
|
||||
particleLayerIdentifier:_particleLayerIdentifier];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface MLNParticleFieldRuntime () {
|
||||
__weak MLNMapView *_mapView;
|
||||
BOOL _disposed;
|
||||
}
|
||||
- (instancetype)initWithMapView:(MLNMapView *)mapView
|
||||
configuration:(MLNParticleFieldRuntimeConfiguration *)configuration
|
||||
error:(NSError **)error;
|
||||
@end
|
||||
|
||||
@implementation MLNParticleFieldRuntime
|
||||
|
||||
- (instancetype)initWithMapView:(MLNMapView *)mapView
|
||||
configuration:(MLNParticleFieldRuntimeConfiguration *)configuration
|
||||
error:(NSError **)error {
|
||||
if ((self = [super init])) {
|
||||
_mapView = mapView;
|
||||
_configuration = [configuration copy];
|
||||
if (![self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &) { return true; }]) return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isDisposed { return _disposed; }
|
||||
|
||||
- (void)dispose {
|
||||
_disposed = YES;
|
||||
_mapView = nil;
|
||||
}
|
||||
|
||||
- (BOOL)withCurrentSource:(NSError **)error action:(bool (^)(mbgl::style::RasterArraySource &))action {
|
||||
if (_disposed) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorDisposed, @"Particle field runtime has been disposed.");
|
||||
return NO;
|
||||
}
|
||||
MLNMapView *mapView = _mapView;
|
||||
if (!mapView) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorMapUnavailable, @"The owning map view is no longer available.");
|
||||
return NO;
|
||||
}
|
||||
@try {
|
||||
auto& style = [mapView mbglMap].getStyle();
|
||||
const auto sourceID = stringFromNSString(_configuration.sourceIdentifier);
|
||||
auto *untypedSource = style.getSource(sourceID);
|
||||
auto *source = untypedSource ? untypedSource->as<mbgl::style::RasterArraySource>() : nullptr;
|
||||
if (!source) {
|
||||
setError(error, untypedSource ? MLNParticleFieldRuntimeErrorSourceTypeMismatch : MLNParticleFieldRuntimeErrorSourceMissing,
|
||||
untypedSource ? @"The configured source is not a raster-array source." : @"The configured raster-array source is missing from the current style.");
|
||||
return NO;
|
||||
}
|
||||
const auto weatherID = stringFromNSString(_configuration.weatherColorLayerIdentifier);
|
||||
auto *untypedWeather = style.getLayer(weatherID);
|
||||
auto *weather = untypedWeather && mbgl::style::layerTypeInfoEquals(untypedWeather->getTypeInfo(), mbgl::style::WeatherColorLayer::Impl::staticTypeInfo())
|
||||
? static_cast<mbgl::style::WeatherColorLayer *>(untypedWeather) : nullptr;
|
||||
if (!weather) {
|
||||
setError(error, untypedWeather ? MLNParticleFieldRuntimeErrorWeatherColorLayerTypeMismatch : MLNParticleFieldRuntimeErrorWeatherColorLayerMissing,
|
||||
untypedWeather ? @"The configured weather-color layer has the wrong Core type." : @"The configured weather-color layer is missing from the current style.");
|
||||
return NO;
|
||||
}
|
||||
const auto particleID = stringFromNSString(_configuration.particleLayerIdentifier);
|
||||
auto *untypedParticle = style.getLayer(particleID);
|
||||
auto *particle = untypedParticle && mbgl::style::layerTypeInfoEquals(untypedParticle->getTypeInfo(), mbgl::style::RasterParticleLayer::Impl::staticTypeInfo())
|
||||
? static_cast<mbgl::style::RasterParticleLayer *>(untypedParticle) : nullptr;
|
||||
if (!particle) {
|
||||
setError(error, untypedParticle ? MLNParticleFieldRuntimeErrorParticleLayerTypeMismatch : MLNParticleFieldRuntimeErrorParticleLayerMissing,
|
||||
untypedParticle ? @"The configured raster-particle layer has the wrong Core type." : @"The configured raster-particle layer is missing from the current style.");
|
||||
return NO;
|
||||
}
|
||||
if (weather->getSourceID() != sourceID || particle->getSourceID() != sourceID) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorLayerSourceMismatch, @"Configured particle layers do not reference the configured raster-array source.");
|
||||
return NO;
|
||||
}
|
||||
return action(*source);
|
||||
} @catch (NSException *exception) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorMapUnavailable, exception.reason ?: @"The underlying map is unavailable.");
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (MLNParticleEncodedFieldSubmitResult)submitEncodedFieldWithFieldKey:(NSString *)fieldKey
|
||||
fieldID:(NSString *)fieldID
|
||||
generation:(uint64_t)generation
|
||||
dataVersion:(uint64_t)dataVersion
|
||||
contractJSON:(NSString *)contractJSON
|
||||
qbin:(NSData *)qbin
|
||||
error:(NSError **)error {
|
||||
if (!hasValidIdentifier(fieldKey) || !hasValidIdentifier(fieldID) || !hasValidContract(contractJSON) || generation == 0 || qbin.length == 0 || qbin.length > MaxQbinBytes) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Encoded field arguments violate the V4 transport contract.");
|
||||
return MLNParticleEncodedFieldSubmitResultNone;
|
||||
}
|
||||
const auto contract = mbgl::particle::parseResolvedFieldProfileContractJson(stringFromNSString(contractJSON));
|
||||
if (!contract || !hasValidIdentifier([NSString stringWithUTF8String:contract->profileID.c_str()])) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"contractJSON is not a valid V4 field profile contract.");
|
||||
return MLNParticleEncodedFieldSubmitResultNone;
|
||||
}
|
||||
std::vector<uint8_t> bytes(qbin.length);
|
||||
[qbin getBytes:bytes.data() length:bytes.size()];
|
||||
__block MLNParticleEncodedFieldSubmitResult result = MLNParticleEncodedFieldSubmitResultNone;
|
||||
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) {
|
||||
const auto coreResult = source.submitEncodedField(stringFromNSString(fieldKey),
|
||||
{stringFromNSString(fieldID), generation, dataVersion}, *contract, bytes);
|
||||
result = static_cast<MLNParticleEncodedFieldSubmitResult>(coreResult);
|
||||
return true;
|
||||
}];
|
||||
return completed ? result : MLNParticleEncodedFieldSubmitResultNone;
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedColorBindingWithFieldKey:(NSString *)fieldKey semanticID:(uint16_t)semanticID groupID:(uint16_t)groupID expectedValueClass:(MLNParticleFieldValueClass)expectedValueClass expectedUnitID:(uint16_t)expectedUnitID error:(NSError **)error {
|
||||
if (!hasValidIdentifier(fieldKey) || semanticID == 0 || expectedValueClass < MLNParticleFieldValueClassScalar || expectedValueClass > MLNParticleFieldValueClassDirectionAngle) {
|
||||
setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid encoded color binding."); return NO;
|
||||
}
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) {
|
||||
source.setColorBinding({stringFromNSString(fieldKey), {semanticID, groupID}, static_cast<mbgl::particle::FieldValueClass>(expectedValueClass), expectedUnitID});
|
||||
return true;
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedParticleBindingWithFieldKey:(NSString *)fieldKey mode:(MLNParticleEncodedParticleBindingMode)mode uSemanticID:(uint16_t)uSemanticID uGroupID:(uint16_t)uGroupID vSemanticID:(uint16_t)vSemanticID vGroupID:(uint16_t)vGroupID directionSemanticID:(uint16_t)directionSemanticID directionGroupID:(uint16_t)directionGroupID expectedUnitID:(uint16_t)expectedUnitID error:(NSError **)error {
|
||||
if (!hasValidIdentifier(fieldKey)) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid encoded particle field key."); return NO; }
|
||||
const bool vector = mode == MLNParticleEncodedParticleBindingModeVectorComponents && uSemanticID != 0 && vSemanticID != 0 && directionSemanticID == 0 && directionGroupID == 0;
|
||||
const bool direction = mode == MLNParticleEncodedParticleBindingModeDirectionAngle && uSemanticID == 0 && uGroupID == 0 && vSemanticID == 0 && vGroupID == 0 && directionSemanticID != 0;
|
||||
if (!vector && !direction) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Inactive particle selectors must be exactly semanticID = 0 and groupID = 0."); return NO; }
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) {
|
||||
mbgl::particle::ParticleBinding binding;
|
||||
binding.fieldKey = stringFromNSString(fieldKey);
|
||||
binding.expectedUnitID = expectedUnitID;
|
||||
if (vector) { binding.mode = mbgl::particle::ParticleBindingMode::VectorComponents; binding.uPlane = {uSemanticID, uGroupID}; binding.vPlane = {vSemanticID, vGroupID}; }
|
||||
else { binding.mode = mbgl::particle::ParticleBindingMode::DirectionAngle; binding.directionPlane = {directionSemanticID, directionGroupID}; }
|
||||
source.setParticleBinding(std::move(binding));
|
||||
return true;
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedForecastTime:(uint64_t)timestamp error:(NSError **)error {
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) { return source.setRequestedForecastTime(timestamp); }];
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedForecastPlaybackState:(BOOL)playing error:(NSError **)error {
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) { source.setForecastPlaybackState(playing ? mbgl::particle::ForecastPlaybackState::Playing : mbgl::particle::ForecastPlaybackState::Paused); return true; }];
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedForecastPlaybackSpeed:(float)speed error:(NSError **)error {
|
||||
if (!std::isfinite(speed) || speed <= 0) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Forecast playback speed must be finite and greater than zero."); return NO; }
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) { return source.setForecastPlaybackSpeed(speed); }];
|
||||
}
|
||||
|
||||
- (BOOL)clearEncodedFieldWithFieldKey:(NSString *)fieldKey error:(NSError **)error {
|
||||
if (!hasValidIdentifier(fieldKey)) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid encoded field key."); return NO; }
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) { return source.clearEncodedField(stringFromNSString(fieldKey)); }];
|
||||
}
|
||||
|
||||
- (BOOL)clearAllEncodedFieldsWithError:(NSError **)error {
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source) { source.clearAllEncodedFields(); return true; }];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace { const void *ParticleFieldRuntimeAssociationKey = &ParticleFieldRuntimeAssociationKey; }
|
||||
|
||||
@implementation MLNMapView (ParticleFieldRuntime)
|
||||
|
||||
- (MLNParticleFieldRuntime *)replaceParticleFieldRuntimeWithConfiguration:(MLNParticleFieldRuntimeConfiguration *)configuration error:(NSError **)error {
|
||||
[self disposeParticleFieldRuntime];
|
||||
if (!configuration) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"A particle field runtime configuration is required."); return nil; }
|
||||
MLNParticleFieldRuntime *runtime = [[MLNParticleFieldRuntime alloc] initWithMapView:self configuration:configuration error:error];
|
||||
if (!runtime) return nil;
|
||||
objc_setAssociatedObject(self, ParticleFieldRuntimeAssociationKey, runtime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
return runtime;
|
||||
}
|
||||
|
||||
- (MLNParticleFieldRuntime *)particleFieldRuntime {
|
||||
return objc_getAssociatedObject(self, ParticleFieldRuntimeAssociationKey);
|
||||
}
|
||||
|
||||
- (void)disposeParticleFieldRuntime {
|
||||
MLNParticleFieldRuntime *runtime = self.particleFieldRuntime;
|
||||
[runtime dispose];
|
||||
objc_setAssociatedObject(self, ParticleFieldRuntimeAssociationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -42,6 +42,7 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[];
|
||||
#import "MLNMapView+IBAdditions.h"
|
||||
#import "MLNMapView.h"
|
||||
#import "MLNMapViewDelegate.h"
|
||||
#import "MLNParticleFieldRuntime.h"
|
||||
#import "MLNMultiPoint.h"
|
||||
#import "MLNNetworkConfiguration.h"
|
||||
#import "MLNOfflinePack.h"
|
||||
|
||||
Reference in New Issue
Block a user