diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 5173b92c050..2c2b04f9edd 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -364,9 +364,13 @@ set(MBGL_CORE_FILES src/mbgl/style/layers/symbol_layer_properties.hpp # style/sources + include/mbgl/style/sources/custom_vector_source.hpp include/mbgl/style/sources/geojson_source.hpp include/mbgl/style/sources/raster_source.hpp include/mbgl/style/sources/vector_source.hpp + src/mbgl/style/sources/custom_vector_source.cpp + src/mbgl/style/sources/custom_vector_source_impl.cpp + src/mbgl/style/sources/custom_vector_source_impl.hpp src/mbgl/style/sources/geojson_source.cpp src/mbgl/style/sources/geojson_source_impl.cpp src/mbgl/style/sources/geojson_source_impl.hpp @@ -421,6 +425,7 @@ set(MBGL_CORE_FILES src/mbgl/tile/tile_cache.cpp src/mbgl/tile/tile_cache.hpp src/mbgl/tile/tile_id.hpp + src/mbgl/tile/tile_id_hash.hpp src/mbgl/tile/tile_id_io.cpp src/mbgl/tile/tile_loader.hpp src/mbgl/tile/tile_loader_impl.hpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 59929bbb701..c991c61dba5 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -80,6 +80,7 @@ set(MBGL_TEST_FILES test/style/conversion/stringify.test.cpp # style + test/style/custom_vector_source.test.cpp test/style/filter.test.cpp # style/function diff --git a/include/mbgl/style/sources/custom_vector_source.hpp b/include/mbgl/style/sources/custom_vector_source.hpp new file mode 100644 index 00000000000..c9efbd617ea --- /dev/null +++ b/include/mbgl/style/sources/custom_vector_source.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +class CustomVectorSource : public Source { +public: + CustomVectorSource(std::string id, GeoJSONOptions options, std::function fetchTile); + + void setTileData(const CanonicalTileID&, const mapbox::geojson::geojson&); + void reloadTile(const CanonicalTileID&); + void reloadRegion(mbgl::LatLngBounds bounds, uint8_t z); + void reload(); + + // Private implementation + class Impl; + Impl* const impl; +}; + +template <> +inline bool Source::is() const { + return type == SourceType::Vector; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index ede0301725c..d5d15f5db19 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -26,7 +27,9 @@ using SuperclusterPointer = std::unique_ptr; struct GeoJSONOptions { // GeoJSON-VT options + uint8_t minzoom = 0; uint8_t maxzoom = 18; + uint16_t tileSize = util::tileSize; uint16_t buffer = 128; double tolerance = 0.375; diff --git a/platform/darwin/docs/theme/assets/css/jazzy.css.scss b/platform/darwin/docs/theme/assets/css/jazzy.css.scss index ad0a3b70822..103ba601dc3 100644 --- a/platform/darwin/docs/theme/assets/css/jazzy.css.scss +++ b/platform/darwin/docs/theme/assets/css/jazzy.css.scss @@ -386,6 +386,7 @@ pre code { .nav-group-task[data-name="MGLStyleFunction"], .nav-group-task[data-name="MGLStyleLayer"], .nav-group-task[data-name="MGLTileSource"], +.nav-group-task[data-name="MGLAbstractShapeSource"], .nav-group-task[data-name="MGLVectorStyleLayer"] { .nav-group-task-link::after { @extend %nav-group-task-gloss; diff --git a/platform/darwin/src/MGLAbstractShapeSource.h b/platform/darwin/src/MGLAbstractShapeSource.h new file mode 100644 index 00000000000..83eb051c44d --- /dev/null +++ b/platform/darwin/src/MGLAbstractShapeSource.h @@ -0,0 +1,99 @@ +#import "MGLSource.h" + +/** + Options for `MGLShapeSource` objects. + */ +typedef NSString *MGLShapeSourceOption NS_STRING_ENUM; + +/** + An `NSNumber` object containing a Boolean enabling or disabling clustering. + If the `shape` property contains point shapes, setting this option to + `YES` clusters the points by radius into groups. The default value is `NO`. + + This attribute corresponds to the + cluster + source property in the Mapbox Style Specification. + + This option only affects point features within a shape source. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClustered; + +/** + An `NSNumber` object containing an integer; specifies the radius of each + cluster if clustering is enabled. A value of 512 produces a radius equal to + the width of a tile. The default value is 50. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius; + +/** + An `NSNumber` object containing an integer; specifies the maximum zoom level at + which to cluster points if clustering is enabled. Defaults to one zoom level + less than the value of `MGLShapeSourceOptionMaximumZoomLevel` so that, at the + maximum zoom level, the shapes are not clustered. + + This attribute corresponds to the + clusterMaxZoom + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering; + +/** + An `NSNumber` object containing an integer; specifies the minimum zoom level at + which to create vector tiles. The default value is 0. + + This attribute corresponds to the + minzoom + source property in the Mapbox Style Specification. + */ +extern const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel; + +/** + An `NSNumber` object containing an integer; specifies the maximum zoom level at + which to create vector tiles. A greater value produces greater detail at high + zoom levels. The default value is 18. + + This attribute corresponds to the + maxzoom + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel; + +/** + An `NSNumber` object containing an integer; specifies the size of the tile + buffer on each side. A value of 0 produces no buffer. A value of 512 produces a + buffer as wide as the tile itself. Larger values produce fewer rendering + artifacts near tile edges and slower performance. The default value is 128. + + This attribute corresponds to the + buffer + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer; + +/** + An `NSNumber` object containing a double; specifies the Douglas-Peucker + simplification tolerance. A greater value produces simpler geometries and + improves performance. The default value is 0.375. + + This attribute corresponds to the + tolerance + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; + +/** + `MGLAbstractShapeSource` is an abstract base class for map content sources that + supply vector shapes to be shown on the map. A shape source is added to an + `MGLStyle` object along with an `MGLVectorStyleLayer` object. The vector style + layer defines the appearance of any content supplied by the shape source. + + + Do not create instances of this class directly, and do not create your own + subclasses of this class. Instead, create instances of `MGLShapeSource` or + `MGLComputedShapeSource`. + */ +MGL_EXPORT +@interface MGLAbstractShapeSource : MGLSource + + +@end diff --git a/platform/darwin/src/MGLAbstractShapeSource.mm b/platform/darwin/src/MGLAbstractShapeSource.mm new file mode 100644 index 00000000000..ca839cb3ced --- /dev/null +++ b/platform/darwin/src/MGLAbstractShapeSource.mm @@ -0,0 +1,81 @@ +#import "MGLAbstractShapeSource.h" +#import "MGLAbstractShapeSource_Private.h" + +const MGLShapeSourceOption MGLShapeSourceOptionBuffer = @"MGLShapeSourceOptionBuffer"; +const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius = @"MGLShapeSourceOptionClusterRadius"; +const MGLShapeSourceOption MGLShapeSourceOptionClustered = @"MGLShapeSourceOptionClustered"; +const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSourceOptionMaximumZoomLevel"; +const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering"; +const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel"; +const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance"; + +@interface MGLAbstractShapeSource () + +@end + +@implementation MGLAbstractShapeSource + +@end + +mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options) { + auto geoJSONOptions = mbgl::style::GeoJSONOptions(); + + if (NSNumber *value = options[MGLShapeSourceOptionMinimumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + geoJSONOptions.minzoom = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + geoJSONOptions.maxzoom = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionBuffer]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionBuffer must be an NSNumber."]; + } + geoJSONOptions.buffer = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionSimplificationTolerance]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionSimplificationTolerance must be an NSNumber."]; + } + geoJSONOptions.tolerance = value.doubleValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionClusterRadius]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterRadius must be an NSNumber."]; + } + geoJSONOptions.clusterRadius = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevelForClustering]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; + } + geoJSONOptions.clusterMaxZoom = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionClustered]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClustered must be an NSNumber."]; + } + geoJSONOptions.cluster = value.boolValue; + } + + return geoJSONOptions; +} + diff --git a/platform/darwin/src/MGLAbstractShapeSource_Private.h b/platform/darwin/src/MGLAbstractShapeSource_Private.h new file mode 100644 index 00000000000..e10ed4e6462 --- /dev/null +++ b/platform/darwin/src/MGLAbstractShapeSource_Private.h @@ -0,0 +1,18 @@ +#import "MGLAbstractShapeSource.h" + +#import "MGLFoundation.h" +#import "MGLTypes.h" +#import "MGLShape.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLAbstractShapeSource (Private) + +MGL_EXPORT + +mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options); + +@end +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLComputedShapeSource.h b/platform/darwin/src/MGLComputedShapeSource.h new file mode 100644 index 00000000000..6b3d7a9cc9b --- /dev/null +++ b/platform/darwin/src/MGLComputedShapeSource.h @@ -0,0 +1,77 @@ +#import "MGLAbstractShapeSource.h" + +#import "MGLFoundation.h" +#import "MGLGeometry.h" +#import "MGLTypes.h" +#import "MGLShape.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MGLFeature; + +/** + Data source for `MGLComputedShapeSource`. This protocol defines two optionak methods for fetching + data, one based on tile coordinates, and one based on a bounding box. Clases that implement this + protocol must implement one, and only one of the methods. + */ +@protocol MGLComputedShapeSourceDataSource + +@optional +/** + Fetch features for a tile. This will not be called on the main queue, it will be called on the callers requestQueue. + @param x tile X coordinate + @param y tile Y coordinate + @param zoomLevel tile zoom level + */ +- (NSArray *>*)featuresInTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel; + +/** + Fetch features for a tile. This will not be called on the main queue, it will be called on the callers requestQueue. + @param bounds The bounds to fetch data for + @param zoomLevel tile zoom level + */ +- (NSArray *>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoomLevel; + +@end + +/** + A source for vector data that is fetched 1 tile at a time. Useful for sources that are + too large to fit in memory, or are already divided into tiles, but not in Mapbox Vector Tile format. + */ +MGL_EXPORT +@interface MGLComputedShapeSource : MGLAbstractShapeSource + +/** + Returns a custom vector datasource initialized with an identifier, datasource, and a + dictionary of options for the source according to the + style + specification. + + @param identifier A string that uniquely identifies the source. + @param options An `NSDictionary` of options for this source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; + +/** + Request that the source reloads a region. + */ +- (void)reloadTileInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoomLevel; + +/** + Reload all tiles. + */ +- (void)reloadData; + +/** + An object that implements the `MGLComputedShapeSource` protocol that will be queried for tile data. + */ +@property (nonatomic, weak, nullable) id dataSource; + +/** + A queue that calls to the datasource will be made on. + */ +@property (nonatomic, readonly) NSOperationQueue *requestQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLComputedShapeSource.mm b/platform/darwin/src/MGLComputedShapeSource.mm new file mode 100644 index 00000000000..dd6eedbb4f7 --- /dev/null +++ b/platform/darwin/src/MGLComputedShapeSource.mm @@ -0,0 +1,182 @@ +#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 +#include + +@interface MGLComputedShapeSource () { + std::unique_ptr _pendingSource; +} + +@property (nonatomic, readwrite) NSDictionary *options; +@property (nonnull) mbgl::style::CustomVectorSource *rawSource; +@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::CustomVectorSource *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::CustomVectorSource *rawSource = (mbgl::style::CustomVectorSource *)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}; + dispatch_sync(dispatch_get_main_queue(), ^{ + 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 { + if (self = [super initWithIdentifier:identifier]) { + _requestQueue = [[NSOperationQueue alloc] init]; + self.requestQueue.name = [NSString stringWithFormat:@"mgl.MGLComputedShapeSource.%@", identifier]; + auto geoJSONOptions = MGLGeoJSONOptionsFromDictionary(options); + auto source = std::make_unique + (self.identifier.UTF8String, geoJSONOptions, + ^void(const mbgl::CanonicalTileID& tileID) + { + NSOperation *operation = [[MGLComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID]; + [self.requestQueue addOperation:operation]; + }); + + _pendingSource = std::move(source); + self.rawSource = _pendingSource.get(); + } + return self; +} + +- (void)dealloc { + [self.requestQueue cancelAllOperations]; +} + +- (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)addToMapView:(MGLMapView *)mapView { + if (_pendingSource == nullptr) { + [NSException raise:@"MGLRedundantSourceException" + format:@"This instance %@ was already added to %@. Adding the same source instance " \ + "to the style more than once is invalid.", self, mapView.style]; + } + + mapView.mbglMap->addSource(std::move(_pendingSource)); +} + +- (void)removeFromMapView:(MGLMapView *)mapView { + [self.requestQueue cancelAllOperations]; + if (self.rawSource != mapView.mbglMap->getSource(self.identifier.UTF8String)) { + return; + } + + auto removedSource = mapView.mbglMap->removeSource(self.identifier.UTF8String); + + mbgl::style::CustomVectorSource *source = dynamic_cast(removedSource.get()); + if (!source) { + return; + } + + removedSource.release(); + + _pendingSource = std::unique_ptr(source); + self.rawSource = _pendingSource.get(); +} + +- (void)reloadTileInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoomLevel { + self.rawSource->reloadRegion(MGLLatLngBoundsFromCoordinateBounds(bounds), (uint8_t)zoomLevel); +} + +- (void)setNeedsUpdateAtZoomLevel:(NSUInteger)z x:(NSUInteger)x y:(NSUInteger)y { + mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)z, (uint32_t)x, (uint32_t)y); + self.rawSource->reloadTile(tileID); +} + +- (void)reloadData { + [self.requestQueue cancelAllOperations]; + self.rawSource->reload(); +} + +@end diff --git a/platform/darwin/src/MGLShapeSource.h b/platform/darwin/src/MGLShapeSource.h index 24cdf82bea8..750dcf2a7d2 100644 --- a/platform/darwin/src/MGLShapeSource.h +++ b/platform/darwin/src/MGLShapeSource.h @@ -1,4 +1,4 @@ -#import "MGLSource.h" +#import "MGLAbstractShapeSource.h" #import "MGLFoundation.h" #import "MGLTypes.h" @@ -13,72 +13,6 @@ NS_ASSUME_NONNULL_BEGIN */ typedef NSString *MGLShapeSourceOption NS_STRING_ENUM; -/** - An `NSNumber` object containing a Boolean enabling or disabling clustering. - If the `shape` property contains point shapes, setting this option to - `YES` clusters the points by radius into groups. The default value is `NO`. - - This attribute corresponds to the - cluster - source property in the Mapbox Style Specification. - - This option only affects point features within a shape source. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClustered; - -/** - An `NSNumber` object containing an integer; specifies the radius of each - cluster if clustering is enabled. A value of 512 produces a radius equal to - the width of a tile. The default value is 50. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius; - -/** - An `NSNumber` object containing an integer; specifies the maximum zoom level at - which to cluster points if clustering is enabled. Defaults to one zoom level - less than the value of `MGLShapeSourceOptionMaximumZoomLevel` so that, at the - maximum zoom level, the shapes are not clustered. - - This attribute corresponds to the - clusterMaxZoom - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering; - -/** - An `NSNumber` object containing an integer; specifies the maximum zoom level at - which to create vector tiles. A greater value produces greater detail at high - zoom levels. The default value is 18. - - This attribute corresponds to the - maxzoom - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel; - -/** - An `NSNumber` object containing an integer; specifies the size of the tile - buffer on each side. A value of 0 produces no buffer. A value of 512 produces a - buffer as wide as the tile itself. Larger values produce fewer rendering - artifacts near tile edges and slower performance. The default value is 128. - - This attribute corresponds to the - buffer - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer; - -/** - An `NSNumber` object containing a double; specifies the Douglas-Peucker - simplification tolerance. A greater value produces simpler geometries and - improves performance. The default value is 0.375. - - This attribute corresponds to the - tolerance - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; - /** `MGLShapeSource` is a map content source that supplies vector shapes to be shown on the map. The shapes may be instances of `MGLShape` or `MGLFeature`, @@ -111,7 +45,7 @@ extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationT ``` */ MGL_EXPORT -@interface MGLShapeSource : MGLSource +@interface MGLShapeSource : MGLAbstractShapeSource #pragma mark Initializing a Source diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index b37b01663f3..34d6a58ecd9 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -1,4 +1,5 @@ #import "MGLShapeSource_Private.h" +#import "MGLAbstractShapeSource_Private.h" #import "MGLMapView_Private.h" #import "MGLSource_Private.h" @@ -10,12 +11,7 @@ #include #include -const MGLShapeSourceOption MGLShapeSourceOptionClustered = @"MGLShapeSourceOptionClustered"; -const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius = @"MGLShapeSourceOptionClusterRadius"; -const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering"; -const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSourceOptionMaximumZoomLevel"; -const MGLShapeSourceOption MGLShapeSourceOptionBuffer = @"MGLShapeSourceOptionBuffer"; -const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance"; + @interface MGLShapeSource () @@ -136,57 +132,3 @@ - (NSString *)description { } @end - -mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options) { - auto geoJSONOptions = mbgl::style::GeoJSONOptions(); - - if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevel]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; - } - geoJSONOptions.maxzoom = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionBuffer]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionBuffer must be an NSNumber."]; - } - geoJSONOptions.buffer = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionSimplificationTolerance]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionSimplificationTolerance must be an NSNumber."]; - } - geoJSONOptions.tolerance = value.doubleValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionClusterRadius]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionClusterRadius must be an NSNumber."]; - } - geoJSONOptions.clusterRadius = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevelForClustering]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; - } - geoJSONOptions.clusterMaxZoom = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionClustered]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionClustered must be an NSNumber."]; - } - geoJSONOptions.cluster = value.boolValue; - } - - return geoJSONOptions; -} diff --git a/platform/darwin/src/MGLShapeSource_Private.h b/platform/darwin/src/MGLShapeSource_Private.h index c14f4fbb592..8191705b30c 100644 --- a/platform/darwin/src/MGLShapeSource_Private.h +++ b/platform/darwin/src/MGLShapeSource_Private.h @@ -16,7 +16,4 @@ namespace mbgl { @end -MGL_EXPORT -mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options); - NS_ASSUME_NONNULL_END diff --git a/platform/darwin/test/MGLComputedShapeSourceTests.m b/platform/darwin/test/MGLComputedShapeSourceTests.m new file mode 100644 index 00000000000..35499cbc9e9 --- /dev/null +++ b/platform/darwin/test/MGLComputedShapeSourceTests.m @@ -0,0 +1,25 @@ +#import + +#import + + +@interface MGLComputedShapeSourceTests : XCTestCase +@end + +@implementation MGLComputedShapeSourceTests + +- (void)testInitializer { + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"id" + options:@{}]; + XCTAssertNotNil(source); + XCTAssertNotNil(source.requestQueue); + XCTAssertNil(source.dataSource); +} + +- (void)testNilOptions { + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"id" options:nil]; + XCTAssertNotNil(source); +} + + +@end diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm index ba85d760204..588ad3dd991 100644 --- a/platform/darwin/test/MGLShapeSourceTests.mm +++ b/platform/darwin/test/MGLShapeSourceTests.mm @@ -2,6 +2,7 @@ #import #import "MGLFeature_Private.h" +#import "MGLAbstractShapeSource_Private.h" #import "MGLShapeSource_Private.h" #import "MGLSource_Private.h" diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 86f38728e86..32fa6cb4c3a 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -28,6 +28,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Fixed an issue causing vector style layer predicates to be evaluated as if each feature had a `$type` attribute of 1, 2, or 3. The `$type` key path can now be compared to `Point`, `LineString`, or `Polygon`, as described in the documentation. ([#7971](https://github.com/mapbox/mapbox-gl-native/pull/7971)) * When setting an `MGLShapeSource`’s shape to an `MGLFeature` instance, any `UIColor` attribute value is now converted to the equivalent CSS string representation for use with `MGLInterpolationModeIdentity` in style functions. ([#8025](https://github.com/mapbox/mapbox-gl-native/pull/8025)) * An exception is no longer thrown if layers or sources are removed from a style before they are added. ([#7962](https://github.com/mapbox/mapbox-gl-native/pull/7962)) +* Added `MGLComputedShapeSource` source class that allows applications to supply vector data on a per-tile basis. ### User interaction diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 63ca0ea1f69..876af697164 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -72,6 +72,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingCountryLabels, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, + MBXSettingsRuntimeStylingCustomLatLonGrid, }; typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @@ -105,7 +106,8 @@ @implementation MBXSpriteBackedAnnotation @interface MBXViewController () + MGLMapViewDelegate, + MGLComputedShapeSourceDataSource> @property (nonatomic) IBOutlet MGLMapView *mapView; @@ -342,6 +344,7 @@ - (void)dismissSettings:(__unused id)sender [NSString stringWithFormat:@"Label Countries in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", + @"Add Custom Lat/Lon Grid", ]]; break; case MBXSettingsMiscellaneous: @@ -581,6 +584,9 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath case MBXSettingsRuntimeStylingDDSPolygon: [self stylePolygonWithDDS]; break; + case MBXSettingsRuntimeStylingCustomLatLonGrid: + [self addLatLonGrid]; + break; default: NSAssert(NO, @"All runtime styling setting rows should be implemented"); break; @@ -1330,6 +1336,21 @@ - (void)stylePolygonWithDDS { [self.mapView.style addLayer:fillStyleLayer]; } +- (void)addLatLonGrid +{ + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"latlon" + options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + source.dataSource = self; + [self.mapView.style addSource:source]; + MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"latlonlines" + source:source]; + [self.mapView.style addLayer:lineLayer]; + MGLSymbolStyleLayer *labelLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"latlonlabels" + source:source]; + labelLayer.text = [MGLStyleValue valueWithRawValue:@"{value}"]; + [self.mapView.style addLayer:labelLayer]; +} + - (void)styleLabelLanguageForLayersNamed:(NSArray *)layers { _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; @@ -1345,16 +1366,16 @@ - (void)styleLabelLanguageForLayersNamed:(NSArray *)layers if ([label.rawValue hasPrefix:@"{name"]) { layer.text = [MGLStyleValue valueWithRawValue:language]; } - } - else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { - MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; - NSMutableDictionary *stops = function.stops.mutableCopy; - [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLStyleConstantValue *stop, BOOL *done) { - if ([stop.rawValue hasPrefix:@"{name"]) { - stops[zoomLevel] = [MGLStyleValue valueWithRawValue:language]; + } else if ([layer.text isKindOfClass:[MGLStyleFunction class]]) { + MGLStyleFunction *function = (MGLStyleFunction *)layer.text; + [function.stops enumerateKeysAndObjectsUsingBlock:^(id zoomLevel, id stop, BOOL *done) { + if ([stop isKindOfClass:[MGLStyleConstantValue class]]) { + MGLStyleConstantValue *label = (MGLStyleConstantValue *)stop; + if ([label.rawValue hasPrefix:@"{name"]) { + [function.stops setValue:[MGLStyleValue valueWithRawValue:language] forKey:zoomLevel]; + } } }]; - function.stops = stops; layer.text = function; } } else { @@ -1811,4 +1832,51 @@ - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { } } +#pragma mark - MGLComputedShapeSourceDataSource +- (NSArray>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoom { + double gridSpacing; + if(zoom >= 13) { + gridSpacing = 0.01; + } else if(zoom >= 11) { + gridSpacing = 0.05; + } else if(zoom == 10) { + gridSpacing = .1; + } else if(zoom == 9) { + gridSpacing = 0.25; + } else if(zoom == 8) { + gridSpacing = 0.5; + } else if (zoom >= 6) { + gridSpacing = 1; + } else if(zoom == 5) { + gridSpacing = 2; + } else if(zoom >= 4) { + gridSpacing = 5; + } else if(zoom == 2) { + gridSpacing = 10; + } else { + gridSpacing = 20; + } + + NSMutableArray > * features = [NSMutableArray array]; + CLLocationCoordinate2D coords[2]; + + for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); + coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(y)}; + [features addObject:feature]; + } + + for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); + coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(x)}; + [features addObject:feature]; + } + + return features; +} + @end diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 14548822c9f..71aaadc1763 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -186,6 +186,15 @@ 7E016D851D9E890300A29A21 /* MGLPolygon+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */; }; 7E016D861D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */; }; 7E016D871D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */; }; + 88B079A61E363A7200834FAB /* MGLAbstractShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B079A51E36371A00834FAB /* MGLAbstractShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88B079A71E363A7300834FAB /* MGLAbstractShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B079A51E36371A00834FAB /* MGLAbstractShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88DDFB291DCB7A9200B53BDD /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F0C0811DC8FD8C002DB7AE /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88DDFB2A1DCB7AFC00B53BDD /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F0C0811DC8FD8C002DB7AE /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88DDFB2F1DCBC21700B53BDD /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88DDFB2C1DCBC21700B53BDD /* MGLAbstractShapeSource.mm */; }; + 88DDFB301DCBC21700B53BDD /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88DDFB2C1DCBC21700B53BDD /* MGLAbstractShapeSource.mm */; }; + 88EF0E6C1E677358008A6617 /* MGLComputedShapeSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88EF0E6A1E677345008A6617 /* MGLComputedShapeSourceTests.m */; }; + 88F0C0851DC8FD8C002DB7AE /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88F0C0821DC8FD8C002DB7AE /* MGLComputedShapeSource.mm */; }; + 88F0C0861DC8FD8C002DB7AE /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88F0C0821DC8FD8C002DB7AE /* MGLComputedShapeSource.mm */; }; 968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; }; DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -640,6 +649,12 @@ 7E016D7D1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLPolyline+MGLAdditions.m"; sourceTree = ""; }; 7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLPolygon+MGLAdditions.h"; sourceTree = ""; }; 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLPolygon+MGLAdditions.m"; sourceTree = ""; }; + 88B079A51E36371A00834FAB /* MGLAbstractShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource.h; sourceTree = ""; }; + 88DDFB2C1DCBC21700B53BDD /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = ""; tabWidth = 4; }; + 88DDFB311DCBC36E00B53BDD /* MGLAbstractShapeSource_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource_Private.h; sourceTree = ""; }; + 88EF0E6A1E677345008A6617 /* MGLComputedShapeSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLComputedShapeSourceTests.m; path = ../../darwin/test/MGLComputedShapeSourceTests.m; sourceTree = ""; }; + 88F0C0811DC8FD8C002DB7AE /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = ""; }; + 88F0C0821DC8FD8C002DB7AE /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = ""; tabWidth = 4; }; 9660916B1E5BBFD700A9A03B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 9660916C1E5BBFD900A9A03B /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 9660916D1E5BBFDB00A9A03B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; @@ -949,6 +964,11 @@ 350098B91D480108004B2AF0 /* MGLVectorSource.h */, DAF0D8121DFE0EC500B28378 /* MGLVectorSource_Private.h */, 350098BA1D480108004B2AF0 /* MGLVectorSource.mm */, + 88F0C0811DC8FD8C002DB7AE /* MGLComputedShapeSource.h */, + 88F0C0821DC8FD8C002DB7AE /* MGLComputedShapeSource.mm */, + 88DDFB311DCBC36E00B53BDD /* MGLAbstractShapeSource_Private.h */, + 88B079A51E36371A00834FAB /* MGLAbstractShapeSource.h */, + 88DDFB2C1DCBC21700B53BDD /* MGLAbstractShapeSource.mm */, ); name = Sources; sourceTree = ""; @@ -1057,6 +1077,7 @@ isa = PBXGroup; children = ( 40CFA6501D787579008103BD /* MGLShapeSourceTests.mm */, + 88EF0E6A1E677345008A6617 /* MGLComputedShapeSourceTests.m */, 4085AF081D933DEA00F11B22 /* MGLTileSetTests.mm */, ); name = Sources; @@ -1580,6 +1601,7 @@ DD0902AB1DB192A800C5BDCE /* MGLNetworkConfiguration.h in Headers */, DA8848571CBAFB9800AB86E3 /* MGLMapboxEvents.h in Headers */, DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */, + 88B079A61E363A7200834FAB /* MGLAbstractShapeSource.h in Headers */, 353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */, DAAF722D1DA903C700312FA4 /* MGLStyleValue_Private.h in Headers */, DA8847F41CBAFA5100AB86E3 /* MGLOfflinePack.h in Headers */, @@ -1631,6 +1653,7 @@ DA8848871CBB033F00AB86E3 /* Fabric.h in Headers */, 35305D4A1D22AA6A0007D005 /* NSData+MGLAdditions.h in Headers */, 359F57461D2FDDA6005217F1 /* MGLUserLocationAnnotationView_Private.h in Headers */, + 88DDFB2A1DCB7AFC00B53BDD /* MGLComputedShapeSource.h in Headers */, 404C26E21D89B877000AA13D /* MGLTileSource.h in Headers */, DA8848841CBB033F00AB86E3 /* FABAttributes.h in Headers */, DA8847FD1CBAFA5100AB86E3 /* MGLTilePyramidOfflineRegion.h in Headers */, @@ -1653,6 +1676,7 @@ DA35A2CA1CCAAAD200E826B2 /* NSValue+MGLAdditions.h in Headers */, 350098BC1D480108004B2AF0 /* MGLVectorSource.h in Headers */, 353933FC1D3FB7C0003F57D7 /* MGLRasterStyleLayer.h in Headers */, + 88DDFB291DCB7A9200B53BDD /* MGLComputedShapeSource.h in Headers */, 3566C76D1D4A8DFA008152BC /* MGLRasterSource.h in Headers */, DAED38641D62D0FC00D7640F /* NSURL+MGLAdditions.h in Headers */, DABFB85E1CBE99E500D62B32 /* MGLAnnotation.h in Headers */, @@ -1700,6 +1724,7 @@ 40F887711D7A1E59008ECB67 /* MGLShapeSource_Private.h in Headers */, DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */, DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */, + 88B079A71E363A7300834FAB /* MGLAbstractShapeSource.h in Headers */, DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */, DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */, 357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, @@ -2060,6 +2085,7 @@ 3598544D1E1D38AA00B29F84 /* MGLDistanceFormatterTests.m in Sources */, DA2DBBCE1D51E80400D38FF9 /* MGLStyleLayerTests.m in Sources */, DA35A2C61CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */, + 88EF0E6C1E677358008A6617 /* MGLComputedShapeSourceTests.m in Sources */, DAE7DEC21E245455007505A6 /* MGLNSStringAdditionsTests.m in Sources */, 4085AF091D933DEA00F11B22 /* MGLTileSetTests.mm in Sources */, DAEDC4341D603417000224FF /* MGLAttributionInfoTests.m in Sources */, @@ -2097,6 +2123,7 @@ 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */, 40EDA1C11CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */, DA8848541CBAFB9800AB86E3 /* MGLCompactCalloutView.m in Sources */, + 88F0C0851DC8FD8C002DB7AE /* MGLComputedShapeSource.mm in Sources */, DA8848251CBAFA6200AB86E3 /* MGLPointAnnotation.mm in Sources */, 35136D3C1D42272500C20EFD /* MGLCircleStyleLayer.mm in Sources */, 350098DE1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.mm in Sources */, @@ -2128,6 +2155,7 @@ DA8848321CBAFA6200AB86E3 /* NSString+MGLAdditions.m in Sources */, 408AA8581DAEDA1E00022900 /* NSDictionary+MGLAdditions.mm in Sources */, DA35A2A11CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */, + 88DDFB2F1DCBC21700B53BDD /* MGLAbstractShapeSource.mm in Sources */, 35305D481D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */, DA8848291CBAFA6200AB86E3 /* MGLStyle.mm in Sources */, 357FE2DF1E02D2B20068B753 /* NSCoder+MGLAdditions.mm in Sources */, @@ -2174,6 +2202,7 @@ 30E5781A1DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */, 40EDA1C21CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */, DAA4E4291CBB730400178DFB /* NSBundle+MGLAdditions.m in Sources */, + 88F0C0861DC8FD8C002DB7AE /* MGLComputedShapeSource.mm in Sources */, DAA4E42E1CBB730400178DFB /* MGLAPIClient.m in Sources */, 35136D3D1D42272500C20EFD /* MGLCircleStyleLayer.mm in Sources */, 350098DF1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.mm in Sources */, @@ -2205,6 +2234,7 @@ DA35A2CC1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */, 408AA8591DAEDA1E00022900 /* NSDictionary+MGLAdditions.mm in Sources */, DAA4E4281CBB730400178DFB /* MGLTypes.m in Sources */, + 88DDFB301DCBC21700B53BDD /* MGLAbstractShapeSource.mm in Sources */, DA35A2A21CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */, 35305D491D22AA680007D005 /* NSData+MGLAdditions.mm in Sources */, 357FE2E01E02D2B20068B753 /* NSCoder+MGLAdditions.mm in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index 9a119db31e7..9beeb1ddc49 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -72,7 +72,9 @@ custom_categories: children: - MGLSource - MGLTileSource + - MGLAbstractShapeSource - MGLShapeSource + - MGLComputedShapeSource - MGLRasterSource - MGLVectorSource - name: Style Layers diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 2623777d8fb..c9073f1674e 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -49,6 +49,8 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLTileSource.h" #import "MGLVectorSource.h" #import "MGLShapeSource.h" +#import "MGLAbstractShapeSource.h" +#import "MGLComputedShapeSource.h" #import "MGLRasterSource.h" #import "MGLTilePyramidOfflineRegion.h" #import "MGLTypes.h" diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 38711b52025..5015ee08197 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -26,6 +26,7 @@ * Fixed an issue causing vector style layer predicates to be evaluated as if each feature had a `$type` attribute of 1, 2, or 3. The `$type` key path can now be compared to `Point`, `LineString`, or `Polygon`, as described in the documentation. ([#7971](https://github.com/mapbox/mapbox-gl-native/pull/7971)) * When setting an `MGLShapeSource`’s shape to an `MGLFeature` instance, any `NSColor` attribute value is now converted to the equivalent CSS string representation for use with `MGLInterpolationModeIdentity` in style functions. ([#8025](https://github.com/mapbox/mapbox-gl-native/pull/8025)) * An exception is no longer thrown if layers or sources are removed from a style before they are added. ([#7962](https://github.com/mapbox/mapbox-gl-native/pull/7962)) +* Added `MGLComputedShapeSource` source class that allows applications to supply vector data on a per-tile basis. ### User interaction diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 941bed21368..4f524df4d40 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -544,6 +544,12 @@ + + + + + + diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 39055d74478..5913e7300dd 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -49,7 +49,7 @@ return flattenedShapes; } -@interface MapDocument () +@interface MapDocument () @property (weak) IBOutlet NSArrayController *styleLayersArrayController; @property (weak) IBOutlet NSTableView *styleLayersTableView; @@ -639,6 +639,47 @@ - (IBAction)removeCustomStyleLayer:(id)sender { [self.mapView.style removeLayer:layer]; } +- (IBAction)insertGraticuleLayer:(id)sender { + [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { + [self removeGraticuleLayer:sender]; + }]; + + if (!self.undoManager.isUndoing) { + [self.undoManager setActionName:@"Add Graticule Layer"]; + } + + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"graticule" + options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + source.dataSource = self; + [self.mapView.style addSource:source]; + MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"graticule.lines" + source:source]; + [self.mapView.style addLayer:lineLayer]; + MGLSymbolStyleLayer *labelLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"graticule.labels" + source:source]; + labelLayer.text = [MGLStyleValue valueWithRawValue:@"{value}"]; + [self.mapView.style addLayer:labelLayer]; +} + +- (IBAction)removeGraticuleLayer:(id)sender { + [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { + [self insertGraticuleLayer:sender]; + }]; + + if (!self.undoManager.isUndoing) { + [self.undoManager setActionName:@"Delete Graticule Layer"]; + } + + MGLStyleLayer *layer = [self.mapView.style layerWithIdentifier:@"graticule.lines"]; + [self.mapView.style removeLayer:layer]; + + layer = [self.mapView.style layerWithIdentifier:@"graticule.labels"]; + [self.mapView.style removeLayer:layer]; + + MGLSource *source = [self.mapView.style sourceWithIdentifier:@"graticule"]; + [self.mapView.style removeSource:source]; +} + #pragma mark Offline packs - (IBAction)addOfflinePack:(id)sender { @@ -926,6 +967,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if (menuItem.action == @selector(insertCustomStyleLayer:)) { return ![self.mapView.style layerWithIdentifier:@"mbx-custom"]; } + if (menuItem.action == @selector(insertGraticuleLayer:)) { + return ![self.mapView.style sourceWithIdentifier:@"graticule"]; + } if (menuItem.action == @selector(showAllAnnotations:) || menuItem.action == @selector(removeAllAnnotations:)) { return self.mapView.annotations.count > 0; } @@ -1095,6 +1139,53 @@ - (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)ann return 0.8; } +#pragma mark - MGLComputedShapeSourceDataSource +- (NSArray>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoom { + double gridSpacing; + if(zoom >= 13) { + gridSpacing = 0.01; + } else if(zoom >= 11) { + gridSpacing = 0.05; + } else if(zoom == 10) { + gridSpacing = .1; + } else if(zoom == 9) { + gridSpacing = 0.25; + } else if(zoom == 8) { + gridSpacing = 0.5; + } else if (zoom >= 6) { + gridSpacing = 1; + } else if(zoom == 5) { + gridSpacing = 2; + } else if(zoom >= 4) { + gridSpacing = 5; + } else if(zoom == 2) { + gridSpacing = 10; + } else { + gridSpacing = 20; + } + + NSMutableArray > * features = [NSMutableArray array]; + CLLocationCoordinate2D coords[2]; + + for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); + coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(y)}; + [features addObject:feature]; + } + + for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); + coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(x)}; + [features addObject:feature]; + } + + return features; +} + @end @interface ValidatedToolbarItem : NSToolbarItem diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml index 3d74fbc652e..65a3451043a 100644 --- a/platform/macos/jazzy.yml +++ b/platform/macos/jazzy.yml @@ -59,7 +59,9 @@ custom_categories: children: - MGLSource - MGLTileSource + - MGLAbstractShapeSource - MGLShapeSource + - MGLComputedShapeSource - MGLRasterSource - MGLVectorSource - name: Style Layers diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index b5c54d8b2ae..92ae407b523 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -70,6 +70,12 @@ 558F18221D0B13B100123F46 /* libmbgl-loop.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 558F18211D0B13B000123F46 /* libmbgl-loop.a */; }; 55D9B4B11D005D3900C1CCE2 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D9B4B01D005D3900C1CCE2 /* libz.tbd */; }; 55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; }; + 8877024C1E37977D0097E255 /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88B079B01E3794F300834FAB /* MGLComputedShapeSource.mm */; }; + 88B079AC1E37941300834FAB /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 88B079AA1E3793E000834FAB /* MGLAbstractShapeSource.mm */; }; + 88B079AD1E37942700834FAB /* MGLAbstractShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B079A91E3793E000834FAB /* MGLAbstractShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88B079AE1E37943900834FAB /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B079A81E3793E000834FAB /* MGLAbstractShapeSource_Private.h */; }; + 88B079B21E37957000834FAB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B079AF1E3794F300834FAB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88EF0E6F1E6777ED008A6617 /* MGLComputedShapeSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88EF0E6D1E6777E8008A6617 /* MGLComputedShapeSourceTests.m */; }; 96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027331E57C9A7004B8E66 /* Localizable.strings */; }; DA00FC8A1D5EEAC3009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8B1D5EEAC3009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC891D5EEAC3009AABC8 /* MGLAttributionInfo.mm */; }; @@ -330,6 +336,12 @@ 55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = ""; }; 55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = ""; }; + 88B079A81E3793E000834FAB /* MGLAbstractShapeSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource_Private.h; sourceTree = ""; }; + 88B079A91E3793E000834FAB /* MGLAbstractShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource.h; sourceTree = ""; }; + 88B079AA1E3793E000834FAB /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = ""; }; + 88B079AF1E3794F300834FAB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = ""; }; + 88B079B01E3794F300834FAB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = ""; }; + 88EF0E6D1E6777E8008A6617 /* MGLComputedShapeSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLComputedShapeSourceTests.m; sourceTree = ""; }; 966091701E5BBFF700A9A03B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 966091711E5BBFF900A9A03B /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 966091721E5BBFFA00A9A03B /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; @@ -618,10 +630,15 @@ 3527427E1D4C242B00A1ECE6 /* Sources */ = { isa = PBXGroup; children = ( + 88B079AF1E3794F300834FAB /* MGLComputedShapeSource.h */, + 88B079B01E3794F300834FAB /* MGLComputedShapeSource.mm */, 352742831D4C244700A1ECE6 /* MGLRasterSource.h */, DA7DC9821DED647F0027472F /* MGLRasterSource_Private.h */, 352742841D4C244700A1ECE6 /* MGLRasterSource.mm */, 352742871D4C245800A1ECE6 /* MGLShapeSource.h */, + 88B079A81E3793E000834FAB /* MGLAbstractShapeSource_Private.h */, + 88B079A91E3793E000834FAB /* MGLAbstractShapeSource.h */, + 88B079AA1E3793E000834FAB /* MGLAbstractShapeSource.mm */, DA87A99B1DC9D8DD00810D09 /* MGLShapeSource_Private.h */, 352742881D4C245800A1ECE6 /* MGLShapeSource.mm */, 3527427F1D4C243B00A1ECE6 /* MGLSource.h */, @@ -722,6 +739,7 @@ isa = PBXGroup; children = ( DA87A9961DC9D88400810D09 /* MGLShapeSourceTests.mm */, + 88EF0E6D1E6777E8008A6617 /* MGLComputedShapeSourceTests.m */, DA87A9971DC9D88400810D09 /* MGLTileSetTests.mm */, ); name = Sources; @@ -1050,6 +1068,7 @@ 35C5D8471D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h in Headers */, DAE6C3A31CC31E9400DB3429 /* MGLAnnotationImage.h in Headers */, DAE6C3A41CC31E9400DB3429 /* MGLMapView.h in Headers */, + 88B079AE1E37943900834FAB /* MGLAbstractShapeSource_Private.h in Headers */, 355BA4ED1D41633E00CCC6D5 /* NSColor+MGLAdditions.h in Headers */, DAE6C3611CC31E0400DB3429 /* MGLOfflineStorage.h in Headers */, 352742781D4C220900A1ECE6 /* MGLStyleValue.h in Headers */, @@ -1100,6 +1119,7 @@ DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */, DAE6C38B1CC31E2A00DB3429 /* MGLOfflinePack_Private.h in Headers */, 558DE7A61E56161C00C7916D /* MGLFoundation_Private.h in Headers */, + 88B079B21E37957000834FAB /* MGLComputedShapeSource.h in Headers */, DACC22141CF3D3E200D220D9 /* MGLFeature.h in Headers */, 3538AA231D542685008EC33D /* MGLStyleLayer.h in Headers */, DAE6C35C1CC31E0400DB3429 /* MGLGeometry.h in Headers */, @@ -1118,6 +1138,7 @@ DAE6C3891CC31E2A00DB3429 /* MGLMultiPoint_Private.h in Headers */, DAE6C3A51CC31E9400DB3429 /* MGLMapView+IBAdditions.h in Headers */, DA35A2AD1CCA091800E826B2 /* MGLCompassDirectionFormatter.h in Headers */, + 88B079AD1E37942700834FAB /* MGLAbstractShapeSource.h in Headers */, 352742851D4C244700A1ECE6 /* MGLRasterSource.h in Headers */, 408AA85B1DAEECFE00022900 /* MGLShape_Private.h in Headers */, DACC22181CF3D4F700D220D9 /* MGLFeature_Private.h in Headers */, @@ -1379,9 +1400,11 @@ DAE6C3B51CC31EF300DB3429 /* MGLCompassCell.m in Sources */, DA8F25901D51CA600010E6B5 /* MGLRasterStyleLayer.mm in Sources */, DAD165751CF4CD7A001FF4B9 /* MGLShapeCollection.mm in Sources */, + 88B079AC1E37941300834FAB /* MGLAbstractShapeSource.mm in Sources */, 35C5D8481D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.mm in Sources */, DA35A2AE1CCA091800E826B2 /* MGLCompassDirectionFormatter.m in Sources */, DA8F258C1D51CA540010E6B5 /* MGLLineStyleLayer.mm in Sources */, + 8877024C1E37977D0097E255 /* MGLComputedShapeSource.mm in Sources */, 408AA8691DAEEE5500022900 /* MGLPolyline+MGLAdditions.m in Sources */, DA8F25941D51CA750010E6B5 /* MGLSymbolStyleLayer.mm in Sources */, 3529039C1D6C63B80002C7DF /* NSPredicate+MGLAdditions.mm in Sources */, @@ -1404,6 +1427,7 @@ DA87A9A41DCACC5000810D09 /* MGLSymbolStyleLayerTests.mm in Sources */, 40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */, DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.mm in Sources */, + 88EF0E6F1E6777ED008A6617 /* MGLComputedShapeSourceTests.m in Sources */, DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */, DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */, DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.mm in Sources */, diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 79ecfb21dc1..6f1bdd22bf3 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -47,6 +47,8 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLTileSource.h" #import "MGLVectorSource.h" #import "MGLShapeSource.h" +#import "MGLAbstractShapeSource.h" +#import "MGLComputedShapeSource.h" #import "MGLRasterSource.h" #import "MGLTilePyramidOfflineRegion.h" #import "MGLTypes.h" diff --git a/src/mbgl/algorithm/generate_clip_ids.hpp b/src/mbgl/algorithm/generate_clip_ids.hpp index d917b398af5..fb12fdcd2bb 100644 --- a/src/mbgl/algorithm/generate_clip_ids.hpp +++ b/src/mbgl/algorithm/generate_clip_ids.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/src/mbgl/algorithm/update_renderables.hpp b/src/mbgl/algorithm/update_renderables.hpp index fe2dc2c5708..d6d177f3299 100644 --- a/src/mbgl/algorithm/update_renderables.hpp +++ b/src/mbgl/algorithm/update_renderables.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/src/mbgl/style/source_impl.hpp b/src/mbgl/style/source_impl.hpp index e6340ae1cb0..4583b67d3cb 100644 --- a/src/mbgl/style/source_impl.hpp +++ b/src/mbgl/style/source_impl.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/src/mbgl/style/sources/custom_vector_source.cpp b/src/mbgl/style/sources/custom_vector_source.cpp new file mode 100644 index 00000000000..e29ab19c6e0 --- /dev/null +++ b/src/mbgl/style/sources/custom_vector_source.cpp @@ -0,0 +1,29 @@ +#include +#include + +namespace mbgl { +namespace style { + +CustomVectorSource::CustomVectorSource(std::string id, GeoJSONOptions options, std::function fetchTile) + : Source(SourceType::Vector, std::make_unique(std::move(id), *this, options, fetchTile)), + impl(static_cast(baseImpl.get())) { +} + +void CustomVectorSource::setTileData(const CanonicalTileID& tileId, const mapbox::geojson::geojson& geoJSON) { + impl->setTileData(tileId, geoJSON); +} + +void CustomVectorSource::reloadRegion(mbgl::LatLngBounds bounds, uint8_t z) { + impl->reloadRegion(bounds, z); +} + +void CustomVectorSource::reloadTile(const CanonicalTileID& tileId) { + impl->reloadTile(tileId); +} + +void CustomVectorSource::reload() { + impl->reload(); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_vector_source_impl.cpp b/src/mbgl/style/sources/custom_vector_source_impl.cpp new file mode 100644 index 00000000000..3175e0bb108 --- /dev/null +++ b/src/mbgl/style/sources/custom_vector_source_impl.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +CustomVectorSource::Impl::Impl(std::string id, Source& base_, GeoJSONOptions options_, std::function fetchTile_) + : Source::Impl(SourceType::Vector, std::move(id), base_), options(options_), fetchTile(fetchTile_) { + loaded = true; +} + +Range CustomVectorSource::Impl::getZoomRange() { + return { options.minzoom, options.maxzoom }; +} + +uint16_t CustomVectorSource::Impl::getTileSize() const { + return options.tileSize; +} + +std::unique_ptr CustomVectorSource::Impl::createTile(const OverscaledTileID& tileID, + const UpdateParameters& parameters) { + auto tilePointer = std::make_unique(tileID, base.getID(), parameters); + fetchTile(tileID.canonical); + return std::move(tilePointer); +} + +void CustomVectorSource::Impl::setTileData(const CanonicalTileID& tileID, const mapbox::geojson::geojson& geoJSON) { + constexpr double scale = util::EXTENT / util::tileSize; + + if(geoJSON.is() && geoJSON.get().empty()) { + for (auto const &item : tiles) { + GeoJSONTile* tile = static_cast(item.second.get()); + if(tile->id.canonical == tileID) { + tile->updateData(mapbox::geometry::feature_collection()); + } + } + } else { + variant geoJSONOrSupercluster; + if (!options.cluster) { + mapbox::geojsonvt::Options vtOptions; + vtOptions.maxZoom = options.maxzoom; + vtOptions.extent = util::EXTENT; + vtOptions.buffer = std::round(scale * options.buffer); + vtOptions.tolerance = scale * options.tolerance; + geoJSONOrSupercluster = std::make_unique(geoJSON, vtOptions); + } else { + mapbox::supercluster::Options clusterOptions; + clusterOptions.maxZoom = options.clusterMaxZoom; + clusterOptions.extent = util::EXTENT; + clusterOptions.radius = std::round(scale * options.clusterRadius); + + const auto& features = geoJSON.get>(); + geoJSONOrSupercluster = + std::make_unique(features, clusterOptions); + } + + for (auto const &item : tiles) { + GeoJSONTile* tile = static_cast(item.second.get()); + if(tile->id.canonical == tileID) { + if (geoJSONOrSupercluster.is()) { + tile->updateData(geoJSONOrSupercluster.get()->getTile(tileID.z, tileID.x, tileID.y).features); + } else { + assert(geoJSONOrSupercluster.is()); + tile->updateData(geoJSONOrSupercluster.get()->getTile(tileID.z, tileID.x, tileID.y)); + } + } + } + } +} + +void CustomVectorSource::Impl::reloadTile(const CanonicalTileID& tileId) { + if(cache.has(OverscaledTileID(tileId.z, tileId.x, tileId.y))) { + cache.clear(); + } + for (auto const &item : tiles) { + GeoJSONTile* tile = static_cast(item.second.get()); + if(tile->id.canonical == tileId) { + fetchTile(tileId); + } + } +} + +void CustomVectorSource::Impl::reloadRegion(mbgl::LatLngBounds bounds, uint8_t z) { + for (const auto& tile : mbgl::util::tileCover(bounds, z)) { + reloadTile(tile.canonical); + } +} + +void CustomVectorSource::Impl::reload() { + cache.clear(); + for (auto const &item : tiles) { + GeoJSONTile* tile = static_cast(item.second.get()); + fetchTile(tile->id.canonical); + } +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_vector_source_impl.hpp b/src/mbgl/style/sources/custom_vector_source_impl.hpp new file mode 100644 index 00000000000..4ca9e22d0e1 --- /dev/null +++ b/src/mbgl/style/sources/custom_vector_source_impl.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace style { + +class CustomVectorSource::Impl : public Source::Impl { +public: + Impl(std::string id, Source&, GeoJSONOptions options, std::function fetchTile); + + void loadDescription(FileSource&) final {} + void setTileData(const CanonicalTileID& tileID, const mapbox::geojson::geojson&); + void reloadTile(const CanonicalTileID& tileID); + void reloadRegion(mbgl::LatLngBounds bounds, uint8_t z); + void reload(); + +private: + GeoJSONOptions options; + std::function fetchTile; + + uint16_t getTileSize() const; + Range getZoomRange() final; + std::unique_ptr createTile(const OverscaledTileID&, const UpdateParameters&) final; + +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/tile/tile_id.hpp b/src/mbgl/tile/tile_id.hpp index 1ce3eea98ed..e93af41765c 100644 --- a/src/mbgl/tile/tile_id.hpp +++ b/src/mbgl/tile/tile_id.hpp @@ -8,7 +8,6 @@ #include #include #include -#include namespace mbgl { @@ -240,36 +239,3 @@ inline float UnwrappedTileID::pixelsToTileUnits(const float pixelValue, const fl } } // namespace mbgl - -namespace std { - -template <> struct hash { - size_t operator()(const mbgl::CanonicalTileID &id) const { - std::size_t seed = 0; - boost::hash_combine(seed, id.x); - boost::hash_combine(seed, id.y); - boost::hash_combine(seed, id.z); - return seed; - } -}; - -template <> struct hash { - size_t operator()(const mbgl::UnwrappedTileID &id) const { - std::size_t seed = 0; - boost::hash_combine(seed, std::hash{}(id.canonical)); - boost::hash_combine(seed, id.wrap); - return seed; - } -}; - -template <> struct hash { - size_t operator()(const mbgl::OverscaledTileID &id) const { - std::size_t seed = 0; - boost::hash_combine(seed, std::hash{}(id.canonical)); - boost::hash_combine(seed, id.overscaledZ); - return seed; - } -}; - -} // namespace std - diff --git a/src/mbgl/tile/tile_id_hash.hpp b/src/mbgl/tile/tile_id_hash.hpp new file mode 100644 index 00000000000..f978f20e558 --- /dev/null +++ b/src/mbgl/tile/tile_id_hash.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace std { + +template <> struct hash { + size_t operator()(const mbgl::CanonicalTileID &id) const { + std::size_t seed = 0; + boost::hash_combine(seed, id.x); + boost::hash_combine(seed, id.y); + boost::hash_combine(seed, id.z); + return seed; + } +}; + +template <> struct hash { + size_t operator()(const mbgl::UnwrappedTileID &id) const { + std::size_t seed = 0; + boost::hash_combine(seed, std::hash{}(id.canonical)); + boost::hash_combine(seed, id.wrap); + return seed; + } +}; + +template <> struct hash { + size_t operator()(const mbgl::OverscaledTileID &id) const { + std::size_t seed = 0; + boost::hash_combine(seed, std::hash{}(id.canonical)); + boost::hash_combine(seed, id.overscaledZ); + return seed; + } +}; + + +} // namespace std diff --git a/test/style/custom_vector_source.test.cpp b/test/style/custom_vector_source.test.cpp new file mode 100644 index 00000000000..1ae3d683622 --- /dev/null +++ b/test/style/custom_vector_source.test.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include +#include + +#include + +using namespace mbgl; + + +TEST(CustomVectorSource, EmptyData) { + mbgl::style::GeoJSONOptions options; + + const auto callback = [](CanonicalTileID) {}; + + auto testSource = std::make_unique("source", options, callback); + + mbgl::FeatureCollection featureCollection; + testSource->setTileData(CanonicalTileID(0, 0, 0), mbgl::GeoJSON{featureCollection}); +} + +TEST(CustomVectorSource, Geometry) { + mbgl::style::GeoJSONOptions options; + + + const auto callback = [](CanonicalTileID) {}; + + auto testSource = std::make_unique("source", options, callback); + + Polygon polygon = {{ {{ { 0, 0 }, { 0, 45 }, { 45, 45 }, { 45, 0 } }} }}; + testSource->setTileData(CanonicalTileID(0, 0, 0), mbgl::GeoJSON{polygon}); +} + +TEST(CustomVectorSource, ReloadWithNoTiles) { + mbgl::style::GeoJSONOptions options; + + bool called = false; + const auto callback = [&called](CanonicalTileID) { + called = true; + }; + + auto testSource = std::make_unique("source", options, callback); + testSource->reloadRegion(mbgl::LatLngBounds::world(), 0); + EXPECT_EQ(called, false); +}