diff --git a/platform/darwin/src/MGLGeoJSONSource.h b/platform/darwin/src/MGLGeoJSONSource.h index ca6ccb5b19b..50cab015b0c 100644 --- a/platform/darwin/src/MGLGeoJSONSource.h +++ b/platform/darwin/src/MGLGeoJSONSource.h @@ -6,6 +6,50 @@ NS_ASSUME_NONNULL_BEGIN @protocol MGLFeature; +/** + An `NSNumber` object containing a Boolean enabling or disabling clustering. + If the `features` property contains point features, setting this option to + `YES` clusters the points by radius into groups. The default value is `NO`. + */ +extern NSString * const MGLGeoJSONClusterOption; + +/** + An `NSNumber` object containing an integer; specifies the radius of each + cluster when clustering points, measured in 1/512ths of a + tile. The default value is 50. + */ +extern NSString * const MGLGeoJSONClusterRadiusOption; + +/** + An `NSNumber` object containing an integer; specifies the maximum zoom level at + which to cluster points. Defaults to one zoom level less than the value of + `MGLGeoJSONMaximumZoomLevelOption`, so that at the last zoom level, the + features are not clustered. + */ +extern NSString * const MGLGeoJSONClusterMaximumZoomLevelOption; + +/** + 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. + */ +extern NSString * const MGLGeoJSONMaximumZoomLevelOption; + +/** + An `NSNumber` object containing an integer; specifies the tile buffer size on + each side. This option is measured in 1/512ths of a tile. + A higher value reduces rendering artifacts near tile edges but may impact + performance. The default value is 128. + */ +extern NSString * const MGLGeoJSONBufferOption; + +/** + 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. + */ +extern NSString * const MGLGeoJSONToleranceOption; + @interface MGLGeoJSONSource : MGLSource /** @@ -46,6 +90,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier geoJSONData:(NSData *)data NS_DESIGNATED_INITIALIZER; +/** + Initializes a source with the given identifier, GeoJSON data, and a dictionary of options for the source. + + @param sourceIdentifier A string that uniquely identifies the source. + @param geoJSONData An NSData object representing GeoJSON source code. + @param options An NSDictionary of attributes for this source specified by the the style specification. + */ +- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier geoJSONData:(NSData *)data options:(NS_DICTIONARY_OF(NSString *, id) *)options NS_DESIGNATED_INITIALIZER; + /** Initializes a source with the given identifier and URL. @@ -55,6 +108,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL *)url NS_DESIGNATED_INITIALIZER; +/** + Initializes a source with the given identifier, a URL, and a dictionary of options for the source. + + @param sourceIdentifier A string that uniquely identifies the source. + @param URL An HTTP(S) URL, absolute file URL, or local file URL relative to the + current application’s resource bundle. + @param options An NSDictionary of attributes for this source specified by the the style specification. + */ +- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL *)url options:(NS_DICTIONARY_OF(NSString *, id) *)options NS_DESIGNATED_INITIALIZER; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLGeoJSONSource.mm b/platform/darwin/src/MGLGeoJSONSource.mm index b411e53429e..652644be472 100644 --- a/platform/darwin/src/MGLGeoJSONSource.mm +++ b/platform/darwin/src/MGLGeoJSONSource.mm @@ -7,25 +7,114 @@ #include +NSString * const MGLGeoJSONClusterOption = @"MGLGeoJSONCluster"; +NSString * const MGLGeoJSONClusterRadiusOption = @"MGLGeoJSONClusterRadius"; +NSString * const MGLGeoJSONClusterMaximumZoomLevelOption = @"MGLGeoJSONClusterMaximumZoomLevel"; +NSString * const MGLGeoJSONMaximumZoomLevelOption = @"MGLGeoJSONMaximumZoomLevel"; +NSString * const MGLGeoJSONBufferOption = @"MGLGeoJSONBuffer"; +NSString * const MGLGeoJSONToleranceOption = @"MGLGeoJSONOptionsClusterTolerance"; + +@interface MGLGeoJSONSource () + +@property (nonatomic, readwrite) NSDictionary *options; + +@end + @implementation MGLGeoJSONSource -- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier geoJSONData:(NSData *)data { - if (self = [super initWithSourceIdentifier:sourceIdentifier]) { +- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier geoJSONData:(NSData *)data +{ + if (self = [super initWithSourceIdentifier:sourceIdentifier]) + { + _geoJSONData = data; + } + return self; +} + +- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier geoJSONData:(NSData *)data options:(NS_DICTIONARY_OF(NSString *, id) *)options +{ + if (self = [super initWithSourceIdentifier:sourceIdentifier]) + { _geoJSONData = data; + _options = options; + } + return self; +} + +- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL *)url +{ + if (self = [super initWithSourceIdentifier:sourceIdentifier]) + { + _URL = url; } return self; } -- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL *)url { - if (self = [super initWithSourceIdentifier:sourceIdentifier]) { +- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL *)url options:(NS_DICTIONARY_OF(NSString *, id) *)options +{ + if (self = [super initWithSourceIdentifier:sourceIdentifier]) + { _URL = url; + _options = options; } return self; } -- (std::unique_ptr)mbgl_source +- (mbgl::style::GeoJSONOptions)geoJSONOptions +{ + auto mbglOptions = mbgl::style::GeoJSONOptions(); + + if (self.options[MGLGeoJSONMaximumZoomLevelOption]) { + id value = self.options[MGLGeoJSONMaximumZoomLevelOption]; + [self validateValue:value]; + mbglOptions.maxzoom = [value integerValue]; + } + + if (self.options[MGLGeoJSONBufferOption]) { + id value = self.options[MGLGeoJSONBufferOption]; + [self validateValue:value]; + mbglOptions.buffer = [value integerValue]; + } + + if (self.options[MGLGeoJSONToleranceOption]) { + id value = self.options[MGLGeoJSONToleranceOption]; + [self validateValue:value]; + mbglOptions.tolerance = [value doubleValue]; + } + + if (self.options[MGLGeoJSONClusterRadiusOption]) { + id value = self.options[MGLGeoJSONClusterRadiusOption]; + [self validateValue:value]; + mbglOptions.clusterRadius = [value integerValue]; + } + + if (self.options[MGLGeoJSONClusterMaximumZoomLevelOption]) { + id value = self.options[MGLGeoJSONClusterMaximumZoomLevelOption]; + [self validateValue:value]; + mbglOptions.clusterMaxZoom = [value integerValue]; + } + + if (self.options[MGLGeoJSONClusterOption]) { + id value = self.options[MGLGeoJSONClusterOption]; + [self validateValue:value]; + mbglOptions.cluster = [value boolValue]; + } + + return mbglOptions; +} + +- (void)validateValue:(id)value +{ + if (! [value isKindOfClass:[NSNumber class]]) + { + [NSException raise:@"Value not handled" format:@"%@ is not an NSNumber", value]; + } +} + +- (std::unique_ptr)mbglSource { - auto source = std::make_unique(self.sourceIdentifier.UTF8String); + auto source = std::make_unique(self.sourceIdentifier.UTF8String, [self geoJSONOptions]); + if (self.URL) { NSURL *url = self.URL.mgl_URLByStandardizingScheme; source->setURL(url.absoluteString.UTF8String); @@ -35,6 +124,7 @@ - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL source->setGeoJSON(geojson); _features = MGLFeaturesFromMBGLFeatures(geojson); } + return std::move(source); } diff --git a/platform/darwin/src/MGLGeoJSONSource_Private.h b/platform/darwin/src/MGLGeoJSONSource_Private.h new file mode 100644 index 00000000000..3aeb07ad255 --- /dev/null +++ b/platform/darwin/src/MGLGeoJSONSource_Private.h @@ -0,0 +1,9 @@ +#import "MGLGeoJSONSource_Private.h" + +#include + +@interface MGLGeoJSONSource (Private) + +- (mbgl::style::GeoJSONOptions)geoJSONOptions; + +@end diff --git a/platform/darwin/src/MGLRasterSource.mm b/platform/darwin/src/MGLRasterSource.mm index ad2530fc821..15ca2fc5587 100644 --- a/platform/darwin/src/MGLRasterSource.mm +++ b/platform/darwin/src/MGLRasterSource.mm @@ -14,7 +14,7 @@ - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL return self; } -- (std::unique_ptr)mbgl_source { +- (std::unique_ptr)mbglSource { auto source = std::make_unique(self.sourceIdentifier.UTF8String, self.URL.absoluteString.UTF8String, uint16_t(self.tileSize)); diff --git a/platform/darwin/src/MGLSource.mm b/platform/darwin/src/MGLSource.mm index 2b73074b094..cdf003cb001 100644 --- a/platform/darwin/src/MGLSource.mm +++ b/platform/darwin/src/MGLSource.mm @@ -11,7 +11,7 @@ - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier { return self; } -- (std::unique_ptr)mbgl_source { +- (std::unique_ptr)mbglSource { [NSException raise:@"Subclasses must override this method" format:@""]; return nil; } diff --git a/platform/darwin/src/MGLSource_Private.h b/platform/darwin/src/MGLSource_Private.h index b4e3c1ebc35..2b8658b4cbd 100644 --- a/platform/darwin/src/MGLSource_Private.h +++ b/platform/darwin/src/MGLSource_Private.h @@ -5,7 +5,7 @@ @interface MGLSource (Private) -- (std::unique_ptr)mbgl_source; +- (std::unique_ptr)mbglSource; @property (nonatomic) mbgl::style::Source *source; diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index e392cead3fc..02f15ed16f9 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -181,7 +181,7 @@ - (void)insertLayer:(id )styleLayer - (void)addSource:(MGLSource *)source { - self.mapView.mbglMap->addSource([source mbgl_source]); + self.mapView.mbglMap->addSource([source mbglSource]); } - (void)removeSource:(MGLSource *)source diff --git a/platform/darwin/src/MGLVectorSource.mm b/platform/darwin/src/MGLVectorSource.mm index 130144584bc..d0c9b0126a3 100644 --- a/platform/darwin/src/MGLVectorSource.mm +++ b/platform/darwin/src/MGLVectorSource.mm @@ -15,7 +15,7 @@ - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL return self; } -- (std::unique_ptr)mbgl_source +- (std::unique_ptr)mbglSource { auto source = std::make_unique(self.sourceIdentifier.UTF8String, self.URL.absoluteString.UTF8String); return std::move(source); diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 24e5c16d965..6c71b1b3df4 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -159,9 +159,12 @@ 4018B1CA1CDC288E00F666AF /* MGLAnnotationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4018B1CB1CDC288E00F666AF /* MGLAnnotationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 404326881D5B9B1A007111BD /* MGLAnnotationContainerView_Private.h */; }; + 40CFA6511D7875BB008103BD /* MGLGeoJSONSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 40CFA6501D787579008103BD /* MGLGeoJSONSourceTests.mm */; }; 40EDA1C01CFE0E0200D9EA68 /* MGLAnnotationContainerView.h in Headers */ = {isa = PBXBuildFile; fileRef = 40EDA1BD1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.h */; }; 40EDA1C11CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40EDA1BE1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.m */; }; 40EDA1C21CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40EDA1BE1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.m */; }; + 40F887701D7A1E58008ECB67 /* MGLGeoJSONSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 40F8876F1D7A1DB8008ECB67 /* MGLGeoJSONSource_Private.h */; }; + 40F887711D7A1E59008ECB67 /* MGLGeoJSONSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 40F8876F1D7A1DB8008ECB67 /* MGLGeoJSONSource_Private.h */; }; 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */; }; 554180421D2E97DE00012372 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 554180411D2E97DE00012372 /* OpenGLES.framework */; }; 55D8C9961D0F18CE00F42F10 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D8C9951D0F18CE00F42F10 /* libsqlite3.tbd */; }; @@ -566,8 +569,10 @@ 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationView.h; sourceTree = ""; }; 402E9DE01CD2C76200FD4519 /* Mapbox.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Mapbox.playground; sourceTree = ""; }; 404326881D5B9B1A007111BD /* MGLAnnotationContainerView_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationContainerView_Private.h; sourceTree = ""; }; + 40CFA6501D787579008103BD /* MGLGeoJSONSourceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLGeoJSONSourceTests.mm; sourceTree = ""; }; 40EDA1BD1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAnnotationContainerView.h; sourceTree = ""; }; 40EDA1BE1CFE0D4A00D9EA68 /* MGLAnnotationContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAnnotationContainerView.m; sourceTree = ""; }; + 40F8876F1D7A1DB8008ECB67 /* MGLGeoJSONSource_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLGeoJSONSource_Private.h; sourceTree = ""; }; 40FDA7691CCAAA6800442548 /* MBXAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXAnnotationView.h; sourceTree = ""; }; 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXAnnotationView.m; sourceTree = ""; }; 554180411D2E97DE00012372 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; @@ -804,6 +809,7 @@ 350098B91D480108004B2AF0 /* MGLVectorSource.h */, 350098BA1D480108004B2AF0 /* MGLVectorSource.mm */, 3566C7641D4A77BA008152BC /* MGLGeoJSONSource.h */, + 40F8876F1D7A1DB8008ECB67 /* MGLGeoJSONSource_Private.h */, 3566C7651D4A77BA008152BC /* MGLGeoJSONSource.mm */, 3566C76A1D4A8DFA008152BC /* MGLRasterSource.h */, 3566C76B1D4A8DFA008152BC /* MGLRasterSource.mm */, @@ -880,6 +886,7 @@ children = ( 3575798F1D513EF1000B822E /* Layers */, 35B8E08B1D6C8B5100E768D2 /* MGLFilterTests.mm */, + 40CFA64E1D78754A008103BD /* Sources */, ); name = Styling; sourceTree = ""; @@ -918,6 +925,14 @@ name = Playground; sourceTree = ""; }; + 40CFA64E1D78754A008103BD /* Sources */ = { + isa = PBXGroup; + children = ( + 40CFA6501D787579008103BD /* MGLGeoJSONSourceTests.mm */, + ); + name = Sources; + sourceTree = ""; + }; DA1DC9411CB6C1C2006E619F = { isa = PBXGroup; children = ( @@ -1368,6 +1383,7 @@ 35D13AB71D3D15E300AFB4E0 /* MGLStyleLayer.h in Headers */, 354D42DC1D4919F900F400A1 /* NSValue+MGLStyleAttributeAdditions_Private.h in Headers */, DA88488E1CBB047F00AB86E3 /* reachability.h in Headers */, + 40F887701D7A1E58008ECB67 /* MGLGeoJSONSource_Private.h in Headers */, 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, @@ -1513,6 +1529,7 @@ 353933F31D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */, 3593E5271D529EDC006D9365 /* UIColor+MGLStyleAttributeAdditions_Private.h in Headers */, 3538AA1E1D542239008EC33D /* MGLBaseStyleLayer.h in Headers */, + 40F887711D7A1E59008ECB67 /* MGLGeoJSONSource_Private.h in Headers */, DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */, DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */, DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */, @@ -1846,6 +1863,7 @@ 357579851D502AF5000B822E /* MGLSymbolStyleLayerTests.m in Sources */, 357579871D502AFE000B822E /* MGLLineStyleLayerTests.m in Sources */, 357579891D502B06000B822E /* MGLCircleStyleLayerTests.m in Sources */, + 40CFA6511D7875BB008103BD /* MGLGeoJSONSourceTests.mm in Sources */, DA35A2C51CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m in Sources */, 35B8E08C1D6C8B5100E768D2 /* MGLFilterTests.mm in Sources */, 3575798B1D502B0C000B822E /* MGLBackgroundStyleLayerTests.m in Sources */, diff --git a/platform/ios/test/MGLGeoJSONSourceTests.mm b/platform/ios/test/MGLGeoJSONSourceTests.mm new file mode 100644 index 00000000000..76353dc76b2 --- /dev/null +++ b/platform/ios/test/MGLGeoJSONSourceTests.mm @@ -0,0 +1,39 @@ +#import + +#import +#import "MGLGeoJSONSource_Private.h" + +#include + +@interface MGLGeoJSONSourceTests : XCTestCase + +@end + +@implementation MGLGeoJSONSourceTests + +- (void)testMGLGeoJSONSourceWithOptions { + NSURL *url = [NSURL URLWithString:@"http://www.mapbox.com/source"]; + + NSDictionary *options = @{MGLGeoJSONClusterOption: @(YES), + MGLGeoJSONClusterRadiusOption: @42, + MGLGeoJSONClusterMaximumZoomLevelOption: @98, + MGLGeoJSONMaximumZoomLevelOption: @99, + MGLGeoJSONBufferOption: @1976, + MGLGeoJSONToleranceOption: @0.42}; + MGLGeoJSONSource *source = [[MGLGeoJSONSource alloc] initWithSourceIdentifier:@"source-id" URL:url options:options]; + + auto mbglOptions = [source geoJSONOptions]; + XCTAssertTrue(mbglOptions.cluster); + XCTAssertEqual(mbglOptions.clusterRadius, 42); + XCTAssertEqual(mbglOptions.clusterMaxZoom, 98); + XCTAssertEqual(mbglOptions.maxzoom, 99); + XCTAssertEqual(mbglOptions.buffer, 1976); + XCTAssertEqual(mbglOptions.tolerance, 0.42); + + // when the supplied option cluster value is not of the correct type + options = @{MGLGeoJSONClusterOption: @"number 1"}; + source = [[MGLGeoJSONSource alloc] initWithSourceIdentifier:@"source-id" URL:url options:options]; + XCTAssertThrows([source geoJSONOptions]); +} + +@end