#import "MGLComputedShapeSource.h" #import "MGLMapView_Private.h" #import "MGLSource_Private.h" #import "MGLShape_Private.h" #import "MGLAbstractShapeSource_Private.h" #import "MGLGeometry_Private.h" #include #include #include #include @interface MGLComputedShapeSource () { std::unique_ptr _pendingSource; } @property (nonatomic, readwrite) NSDictionary *options; @property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForTile; @property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForBounds; @end @interface MGLComputedShapeSourceFetchOperation : NSOperation @property (nonatomic, readonly) uint8_t z; @property (nonatomic, readonly) uint32_t x; @property (nonatomic, readonly) uint32_t y; @property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForTile; @property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForBounds; @property (nonatomic, weak, nullable) id dataSource; @property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource; - (instancetype)initForSource:(MGLComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileId; @end @implementation MGLComputedShapeSourceFetchOperation - (instancetype)initForSource:(MGLComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileID { self = [super init]; _z = tileID.z; _x = tileID.x; _y = tileID.y; _dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile; _dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds; _dataSource = source.dataSource; mbgl::style::CustomGeometrySource *rawSource = static_cast(source.rawSource); _rawSource = rawSource; return self; } - (void)main { if ([self isCancelled]) { return; } NSArray *> *data; if(!self.dataSource) { data = nil; } else if(self.dataSourceImplementsFeaturesForTile) { data = [self.dataSource featuresInTileAtX:self.x y:self.y zoomLevel:self.z]; } else { mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y); mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID); data = [self.dataSource featuresInCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(tileBounds) zoomLevel:self.z]; } if(![self isCancelled]) { mbgl::FeatureCollection featureCollection; featureCollection.reserve(data.count); for (MGLShape * feature in data) { mbgl::Feature geoJsonObject = [feature geoJSONObject].get(); featureCollection.push_back(geoJsonObject); } const auto geojson = mbgl::GeoJSON{featureCollection}; if(![self isCancelled] && self.rawSource) { self.rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson); } } } - (void)cancel { [super cancel]; self.rawSource = NULL; } @end @implementation MGLComputedShapeSource - (instancetype)initWithIdentifier:(NSString *)identifier options:(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { _requestQueue = [[NSOperationQueue alloc] init]; self.requestQueue.name = [NSString stringWithFormat:@"mgl.MGLComputedShapeSource.%@", identifier]; self.requestQueue.qualityOfService = NSQualityOfServiceUtility; self.requestQueue.maxConcurrentOperationCount = 4; auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options); sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { NSOperation *operation = [[MGLComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID]; [self.requestQueue addOperation:operation]; }; sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { for(MGLComputedShapeSourceFetchOperation *operation in [self.requestQueue operations]) { if(operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) { [operation cancel]; } } }; auto source = std::make_unique(identifier.UTF8String, sourceOptions); return self = [super initWithPendingSource:std::move(source)]; } - (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id)dataSource options:(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { self = [self initWithIdentifier:identifier options:options]; [self setDataSource:dataSource]; return self; } - (void)dealloc { [self.requestQueue cancelAllOperations]; } - (void)setFeatures:(NSArray *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel { mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y); mbgl::FeatureCollection featureCollection; featureCollection.reserve(features.count); for (MGLShape * feature in features) { mbgl::Feature geoJsonObject = [feature geoJSONObject].get(); featureCollection.push_back(geoJsonObject); } const auto geojson = mbgl::GeoJSON{featureCollection}; static_cast(self.rawSource)->setTileData(tileID, geojson); } - (void)setDataSource:(id)dataSource { [self.requestQueue cancelAllOperations]; // Check which method the datasource implements, to avoid having to check for each tile self.dataSourceImplementsFeaturesForTile = [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)]; self.dataSourceImplementsFeaturesForBounds = [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)]; if(!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) { [NSException raise:@"Invalid Datasource" format:@"Datasource does not implement any MGLComputedShapeSourceDataSource methods"]; } else if(self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) { [NSException raise:@"Invalid Datasource" format:@"Datasource implements multiple MGLComputedShapeSourceDataSource methods"]; } _dataSource = dataSource; } - (void) invalidateBounds:(MGLCoordinateBounds)bounds { ((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateRegion(MGLLatLngBoundsFromCoordinateBounds(bounds)); } - (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z { ((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y)); } @end