From c8c664fbf376b615b3f1e4b2a4d7f1c99b6097be Mon Sep 17 00:00:00 2001 From: Julian Rex Date: Mon, 14 Jan 2019 15:17:13 -0500 Subject: [ios, macos] Support getLeaves (and related) clustering methods (#12952) following feature extension API (#13382) --- .../docs/guides/Predicates and Expressions.md | 12 +- platform/darwin/src/MGLCluster.h | 53 ++ platform/darwin/src/MGLFeature.h | 18 +- platform/darwin/src/MGLFeature.mm | 60 +- platform/darwin/src/MGLFeature_Private.h | 1 + platform/darwin/src/MGLFoundation_Private.h | 6 + platform/darwin/src/MGLShapeSource.h | 42 ++ platform/darwin/src/MGLShapeSource.mm | 105 ++++ platform/darwin/src/MGLShapeSource_Private.h | 16 + platform/darwin/test/MGLCodingTests.m | 582 ------------------- platform/darwin/test/MGLCodingTests.mm | 615 +++++++++++++++++++++ .../darwin/test/MGLDocumentationExampleTests.swift | 57 +- platform/darwin/test/MGLFeatureTests.mm | 21 + platform/ios/CHANGELOG.md | 2 + platform/ios/ios.xcodeproj/project.pbxproj | 30 +- platform/ios/jazzy.yml | 2 + platform/ios/sdk-files.json | 1 + platform/ios/src/Mapbox.h | 1 + platform/macos/CHANGELOG.md | 2 + platform/macos/macos.xcodeproj/project.pbxproj | 42 +- platform/macos/sdk-files.json | 1 + platform/macos/src/Mapbox.h | 1 + 22 files changed, 1052 insertions(+), 618 deletions(-) create mode 100644 platform/darwin/src/MGLCluster.h delete mode 100644 platform/darwin/test/MGLCodingTests.m create mode 100644 platform/darwin/test/MGLCodingTests.mm diff --git a/platform/darwin/docs/guides/Predicates and Expressions.md b/platform/darwin/docs/guides/Predicates and Expressions.md index 71c869f7fe..0bb26b3bfd 100644 --- a/platform/darwin/docs/guides/Predicates and Expressions.md +++ b/platform/darwin/docs/guides/Predicates and Expressions.md @@ -120,7 +120,7 @@ dictionary contains the `floorCount` key, then the key path `floorCount` refers to the value of the `floorCount` attribute when evaluating that particular polygon. -The following special attribute is also available on features that are produced +The following special attributes are also available on features that are produced as a result of clustering multiple point features together in a shape source: @@ -128,6 +128,16 @@ as a result of clustering multiple point features together in a shape source: + + + + + + + + + + diff --git a/platform/darwin/src/MGLCluster.h b/platform/darwin/src/MGLCluster.h new file mode 100644 index 0000000000..2b99119b26 --- /dev/null +++ b/platform/darwin/src/MGLCluster.h @@ -0,0 +1,53 @@ +#import "MGLFoundation.h" + +@protocol MGLFeature; + +NS_ASSUME_NONNULL_BEGIN + +/** + An `NSUInteger` constant used to indicate an invalid cluster identifier. + This indicates a missing cluster feature. + */ +FOUNDATION_EXTERN MGL_EXPORT const NSUInteger MGLClusterIdentifierInvalid; + +/** + A protocol that feature subclasses (i.e. those already conforming to + the `MGLFeature` protocol) conform to if they represent clusters. + + Currently the only class that conforms to `MGLCluster` is + `MGLPointFeatureCluster` (a subclass of `MGLPointFeature`). + + To check if a feature is a cluster, check conformity to `MGLCluster`, for + example: + + ```swift + let shape = try! MGLShape(data: clusterShapeData, encoding: String.Encoding.utf8.rawValue) + + guard let pointFeature = shape as? MGLPointFeature else { + throw ExampleError.unexpectedFeatureType + } + + // Check for cluster conformance + guard let cluster = pointFeature as? MGLCluster else { + throw ExampleError.featureIsNotACluster + } + + // Currently the only supported class that conforms to `MGLCluster` is + // `MGLPointFeatureCluster` + guard cluster is MGLPointFeatureCluster else { + throw ExampleError.unexpectedFeatureType + } + ``` + */ +MGL_EXPORT +@protocol MGLCluster + +/** The identifier for the cluster. */ +@property (nonatomic, readonly) NSUInteger clusterIdentifier; + +/** The number of points within this cluster */ +@property (nonatomic, readonly) NSUInteger clusterPointCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLFeature.h b/platform/darwin/src/MGLFeature.h index 8886c8df55..51901d73c0 100644 --- a/platform/darwin/src/MGLFeature.h +++ b/platform/darwin/src/MGLFeature.h @@ -6,6 +6,7 @@ #import "MGLPointAnnotation.h" #import "MGLPointCollection.h" #import "MGLShapeCollection.h" +#import "MGLCluster.h" NS_ASSUME_NONNULL_BEGIN @@ -186,12 +187,27 @@ MGL_EXPORT #### Related examples See the Dynamically style interactive points example to learn how to initialize - `MGLPointFeature` objects and add it them your map. + `MGLPointFeature` objects and add them to your map. */ MGL_EXPORT @interface MGLPointFeature : MGLPointAnnotation @end +/** + An `MGLPointFeatureCluster` object associates a point shape (with an optional + identifier and attributes) and represents a point cluster. + + @see `MGLCluster` + + #### Related examples + See the + Clustering point data example to learn how to initialize + clusters and add them to your map. + */ +MGL_EXPORT +@interface MGLPointFeatureCluster : MGLPointFeature +@end + /** An `MGLPolylineFeature` object associates a polyline shape with an optional identifier and attributes. diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm index d24c807625..fbf262af29 100644 --- a/platform/darwin/src/MGLFeature.mm +++ b/platform/darwin/src/MGLFeature.mm @@ -1,4 +1,6 @@ +#import "MGLFoundation_Private.h" #import "MGLFeature_Private.h" +#import "MGLCluster.h" #import "MGLPointAnnotation.h" #import "MGLPolyline.h" @@ -19,6 +21,11 @@ #import #import +// Cluster constants +static NSString * const MGLClusterIdentifierKey = @"cluster_id"; +static NSString * const MGLClusterCountKey = @"point_count"; +const NSUInteger MGLClusterIdentifierInvalid = NSUIntegerMax; + @interface MGLEmptyFeature () @end @@ -92,6 +99,31 @@ MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); @end +@implementation MGLPointFeatureCluster + +- (NSUInteger)clusterIdentifier { + NSNumber *clusterNumber = MGL_OBJC_DYNAMIC_CAST([self attributeForKey:MGLClusterIdentifierKey], NSNumber); + MGLAssert(clusterNumber, @"Clusters should have a cluster_id"); + + if (!clusterNumber) { + return MGLClusterIdentifierInvalid; + } + + NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue]; + MGLAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit"); + + return clusterIdentifier; +} + +- (NSUInteger)clusterPointCount { + NSNumber *count = MGL_OBJC_DYNAMIC_CAST([self attributeForKey:MGLClusterCountKey], NSNumber); + MGLAssert(count, @"Clusters should have a point_count"); + + return [count unsignedIntegerValue]; +} +@end + + @interface MGLPolylineFeature () @end @@ -318,14 +350,38 @@ MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER(); */ template class GeometryEvaluator { +private: + const mbgl::PropertyMap *shared_properties; + public: + GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr): + shared_properties(properties) + {} + MGLShape * operator()(const mbgl::EmptyGeometry &) const { MGLEmptyFeature *feature = [[MGLEmptyFeature alloc] init]; return feature; } MGLShape * operator()(const mbgl::Point &geometry) const { - MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + Class pointFeatureClass = [MGLPointFeature class]; + + // If we're dealing with a cluster, we should change the class type. + // This could be generic and build the subclass at runtime if it turns + // out we need to support more than point clusters. + if (shared_properties) { + auto clusterIt = shared_properties->find("cluster"); + if (clusterIt != shared_properties->end()) { + auto clusterValue = clusterIt->second; + if (clusterValue.template is()) { + if (clusterValue.template get()) { + pointFeatureClass = [MGLPointFeatureCluster class]; + } + } + } + } + + MGLPointFeature *feature = [[pointFeatureClass alloc] init]; feature.coordinate = toLocationCoordinate2D(geometry); return feature; } @@ -443,7 +499,7 @@ id MGLFeatureFromMBGLFeature(const mbgl::Feature &feature) { ValueEvaluator evaluator; attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); } - GeometryEvaluator evaluator; + GeometryEvaluator evaluator(&feature.properties); MGLShape *shape = mapbox::geometry::geometry::visit(feature.geometry, evaluator); if (!feature.id.is()) { shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); diff --git a/platform/darwin/src/MGLFeature_Private.h b/platform/darwin/src/MGLFeature_Private.h index 9fb1f91820..9b0e16f4b9 100644 --- a/platform/darwin/src/MGLFeature_Private.h +++ b/platform/darwin/src/MGLFeature_Private.h @@ -18,6 +18,7 @@ NSArray *> *MGLFeaturesFromMBGLFeatures(const std::vector< /** Returns an `MGLFeature` object converted from the given mbgl::Feature */ +MGL_EXPORT id MGLFeatureFromMBGLFeature(const mbgl::Feature &feature); /** diff --git a/platform/darwin/src/MGLFoundation_Private.h b/platform/darwin/src/MGLFoundation_Private.h index 71737c2cf9..db81bde3de 100644 --- a/platform/darwin/src/MGLFoundation_Private.h +++ b/platform/darwin/src/MGLFoundation_Private.h @@ -11,3 +11,9 @@ void MGLInitializeRunLoop(); (type *)([temp##__LINE__ isKindOfClass:[type class]] ? temp##__LINE__ : nil); \ }) +#define MGL_OBJC_DYNAMIC_CAST_AS_PROTOCOL(object, proto) \ + ({ \ + __typeof__( object ) temp##__LINE__ = (object); \ + (id< proto >)([temp##__LINE__ conformsToProtocol:@protocol( proto )] ? temp##__LINE__ : nil); \ + }) + diff --git a/platform/darwin/src/MGLShapeSource.h b/platform/darwin/src/MGLShapeSource.h index edf8c0a174..b910fb02ce 100644 --- a/platform/darwin/src/MGLShapeSource.h +++ b/platform/darwin/src/MGLShapeSource.h @@ -5,6 +5,8 @@ NS_ASSUME_NONNULL_BEGIN @protocol MGLFeature; +@class MGLPointFeature; +@class MGLPointFeatureCluster; @class MGLShape; /** @@ -321,6 +323,46 @@ MGL_EXPORT */ - (NSArray> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate; +/** + Returns an array of map features that are the leaves of the specified cluster. + ("Leaves" are the original points that belong to the cluster.) + + This method supports pagination; you supply an offset (number of features to skip) + and a maximum number of features to return. + + @param cluster An object of type `MGLPointFeatureCluster` (that conforms to the `MGLCluster` protocol). + @param offset Number of features to skip. + @param limit The maximum number of features to return + + @return An array of objects that conform to the `MGLFeature` protocol. + */ +- (NSArray> *)leavesOfCluster:(MGLPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit; + +/** + Returns an array of map features that are the immediate children of the specified + cluster *on the next zoom level*. The may include features that also conform to + the `MGLCluster` protocol (currently only objects of type `MGLPointFeatureCluster`). + + @param cluster An object of type `MGLPointFeatureCluster` (that conforms to the `MGLCluster` protocol). + + @return An array of objects that conform to the `MGLFeature` protocol. + + @note The returned array may contain the `cluster` that was passed in, if the next + zoom level doesn't match the zoom level for expanding that cluster. See + `-[MGLShapeSource zoomLevelForExpandingCluster:]`. + */ +- (NSArray> *)childrenOfCluster:(MGLPointFeatureCluster *)cluster; + +/** + Returns the zoom level at which the given cluster expands. + + @param cluster An object of type `MGLPointFeatureCluster` (that conforms to the `MGLCluster` protocol). + + @return Zoom level. This should be >= 0; any negative return value should be + considered an error. + */ +- (double)zoomLevelForExpandingCluster:(MGLPointFeatureCluster *)cluster; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index c960f2a4a7..fc526f9850 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -1,10 +1,13 @@ +#import "MGLFoundation_Private.h" #import "MGLShapeSource_Private.h" +#import "MGLLoggingConfiguration_Private.h" #import "MGLStyle_Private.h" #import "MGLMapView_Private.h" #import "MGLSource_Private.h" #import "MGLFeature_Private.h" #import "MGLShape_Private.h" +#import "MGLCluster.h" #import "NSPredicate+MGLPrivateAdditions.h" #import "NSURL+MGLAdditions.h" @@ -184,4 +187,106 @@ mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary)featureExtensionValueOfCluster:(MGLShape *)cluster extension:(std::string)extension options:(const std::map)options { + mbgl::optional extensionValue; + + // Check parameters + if (!self.rawSource || !self.mapView || !cluster) { + return extensionValue; + } + + auto geoJSON = [cluster geoJSONObject]; + + if (!geoJSON.is()) { + MGLAssert(0, @"cluster geoJSON object is not a feature."); + return extensionValue; + } + + auto clusterFeature = geoJSON.get(); + + extensionValue = self.mapView.renderer->queryFeatureExtensions(self.rawSource->getID(), + clusterFeature, + "supercluster", + extension, + options); + return extensionValue; +} + +- (NSArray> *)leavesOfCluster:(MGLPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit { + const std::map options = { + { "limit", static_cast(limit) }, + { "offset", static_cast(offset) } + }; + + auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"leaves" options:options]; + + if (!featureExtension) { + return @[]; + } + + if (!featureExtension->is()) { + return @[]; + } + + std::vector leaves = featureExtension->get(); + return MGLFeaturesFromMBGLFeatures(leaves); +} + +- (NSArray> *)childrenOfCluster:(MGLPointFeatureCluster *)cluster { + auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"children" options:{}]; + + if (!featureExtension) { + return @[]; + } + + if (!featureExtension->is()) { + return @[]; + } + + std::vector leaves = featureExtension->get(); + return MGLFeaturesFromMBGLFeatures(leaves); +} + +- (double)zoomLevelForExpandingCluster:(MGLPointFeatureCluster *)cluster { + auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"expansion-zoom" options:{}]; + + if (!featureExtension) { + return -1.0; + } + + if (!featureExtension->is()) { + return -1.0; + } + + auto value = featureExtension->get(); + if (value.is()) { + auto zoom = value.get(); + return static_cast(zoom); + } + + return -1.0; +} + +- (void)debugRecursiveLogForFeature:(id )feature indent:(NSUInteger)indent { + NSString *description = feature.description; + + // Want our recursive log on a single line + NSString *log = [description stringByReplacingOccurrencesOfString:@"\\s+" + withString:@" " + options:NSRegularExpressionSearch + range:NSMakeRange(0, description.length)]; + + printf("%*s%s\n", (int)indent, "", log.UTF8String); + + MGLPointFeatureCluster *cluster = MGL_OBJC_DYNAMIC_CAST(feature, MGLPointFeatureCluster); + + if (cluster) { + for (id child in [self childrenOfCluster:cluster]) { + [self debugRecursiveLogForFeature:child indent:indent + 4]; + } + } +} + @end diff --git a/platform/darwin/src/MGLShapeSource_Private.h b/platform/darwin/src/MGLShapeSource_Private.h index fb5b3b3c0d..c7eaf3d0a8 100644 --- a/platform/darwin/src/MGLShapeSource_Private.h +++ b/platform/darwin/src/MGLShapeSource_Private.h @@ -12,4 +12,20 @@ namespace mbgl { MGL_EXPORT mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary *options); +@interface MGLShapeSource (Private) + +/** + :nodoc: + Debug log showing structure of an `MGLFeature`. This method recurses in the case + that the feature conforms to `MGLCluster`. This method is used for testing and + should be considered experimental, likely to be removed or changed in future + releases. + + @param feature An object that conforms to the `MGLFeature` protocol. + @param indent Used during recursion. Specify 0. + */ + +- (void)debugRecursiveLogForFeature:(id)feature indent:(NSUInteger)indent; +@end + NS_ASSUME_NONNULL_END diff --git a/platform/darwin/test/MGLCodingTests.m b/platform/darwin/test/MGLCodingTests.m deleted file mode 100644 index ac61672b76..0000000000 --- a/platform/darwin/test/MGLCodingTests.m +++ /dev/null @@ -1,582 +0,0 @@ -#import -#import - -#if TARGET_OS_IPHONE -#import "MGLUserLocation_Private.h" -#endif - -@interface MGLCodingTests : XCTestCase -@end - -@implementation MGLCodingTests - -- (NSString *)temporaryFilePathForClass:(Class)clazz { - return [NSTemporaryDirectory() stringByAppendingPathComponent:NSStringFromClass(clazz)]; -} - -- (void)testPointAnnotation { - MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init]; - annotation.coordinate = CLLocationCoordinate2DMake(0.5, 0.5); - annotation.title = @"title"; - annotation.subtitle = @"subtitle"; - - NSString *filePath = [self temporaryFilePathForClass:MGLPointAnnotation.class]; - [NSKeyedArchiver archiveRootObject:annotation toFile:filePath]; - MGLPointAnnotation *unarchivedAnnotation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(annotation, unarchivedAnnotation); -} - -- (void)testPointFeature { - MGLPointFeature *pointFeature = [[MGLPointFeature alloc] init]; - pointFeature.title = @"title"; - pointFeature.subtitle = @"subtitle"; - pointFeature.identifier = @(123); - pointFeature.attributes = @{@"bbox": @[@1, @2, @3, @4]}; - - NSString *filePath = [self temporaryFilePathForClass:MGLPointFeature.class]; - [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; - MGLPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); -} - -- (void)testPolyline { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551), - CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392) - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; - polyline.title = @"title"; - polyline.subtitle = @"subtitle"; - - NSString *filePath = [self temporaryFilePathForClass:[MGLPolyline class]]; - [NSKeyedArchiver archiveRootObject:polyline toFile:filePath]; - MGLPolyline *unarchivedPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(polyline, unarchivedPolyline); - - CLLocationCoordinate2D otherCoordinates[] = { - CLLocationCoordinate2DMake(-1, -2) - }; - - [unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates]; - - XCTAssertNotEqualObjects(polyline, unarchivedPolyline); - - CLLocationCoordinate2D multiLineCoordinates[] = { - CLLocationCoordinate2DMake(51.000000, 0.000000), - CLLocationCoordinate2DMake(51.000000, 1.000000), - CLLocationCoordinate2DMake(51.000000, 2.000000), - }; - - NSUInteger multiLineCoordinatesCount = sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D); - MGLPolyline *multiLine = [MGLPolyline polylineWithCoordinates:multiLineCoordinates count:multiLineCoordinatesCount]; - CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000); - - XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude); - XCTAssertEqual([multiLine coordinate].longitude, multiLineCenter.longitude); - - CLLocationCoordinate2D segmentCoordinates[] = { - CLLocationCoordinate2DMake(35.040390, -85.311477), - CLLocationCoordinate2DMake(35.040390, -85.209510), - }; - - NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D); - MGLPolyline *segmentLine = [MGLPolyline polylineWithCoordinates:segmentCoordinates count:segmentCoordinatesCount]; - CLLocationCoordinate2D segmentCenter = CLLocationCoordinate2DMake(35.0404006631, -85.2604935); - - XCTAssertEqualWithAccuracy([segmentLine coordinate].latitude, segmentCenter.latitude, 0.0001); - XCTAssertEqualWithAccuracy([segmentLine coordinate].longitude, segmentCenter.longitude, 0.0001); - - CLLocationCoordinate2D sfToBerkeleyCoordinates[] = { - CLLocationCoordinate2DMake(37.782440, -122.397111), - CLLocationCoordinate2DMake(37.818384, -122.352994), - CLLocationCoordinate2DMake(37.831401, -122.274545), - CLLocationCoordinate2DMake(37.862172, -122.262700), - }; - - NSUInteger sfToBerkeleyCoordinatesCount = sizeof(sfToBerkeleyCoordinates) / sizeof(CLLocationCoordinate2D); - MGLPolyline *sfToBerkeleyLine = [MGLPolyline polylineWithCoordinates:sfToBerkeleyCoordinates count:sfToBerkeleyCoordinatesCount]; - CLLocationCoordinate2D sfToBerkeleyCenter = CLLocationCoordinate2DMake(37.8230575118,-122.324867587); - - XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].latitude, sfToBerkeleyCenter.latitude, 0.0001); - XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].longitude, sfToBerkeleyCenter.longitude, 0.0001); - -} - -- (void)testPolygon { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(35.090745, -85.300259), - CLLocationCoordinate2DMake(35.092035, -85.298885), - CLLocationCoordinate2DMake(35.090639, -85.297416), - CLLocationCoordinate2DMake(35.089112, -85.298928) - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; - polygon.title = nil; - polygon.subtitle = @"subtitle"; - - NSString *filePath = [self temporaryFilePathForClass:[MGLPolygon class]]; - [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; - - MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - [unarchivedPolygon coordinate]; - - XCTAssertEqualObjects(polygon, unarchivedPolygon); - - CLLocationCoordinate2D squareCoordinates[] = { - CLLocationCoordinate2DMake(100.0, 0.0), - CLLocationCoordinate2DMake(101.0, 0.0), - CLLocationCoordinate2DMake(101.0, 1.0), - CLLocationCoordinate2DMake(100.0, 1.0), - }; - - NSUInteger squareCoordinatesCount = sizeof(squareCoordinates) / sizeof(CLLocationCoordinate2D); - MGLPolygon *squarePolygon = [MGLPolygon polygonWithCoordinates:squareCoordinates count:squareCoordinatesCount]; - CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); - - XCTAssertEqual([squarePolygon coordinate].latitude, squareCenter.latitude); - XCTAssertEqual([squarePolygon coordinate].longitude, squareCenter.longitude); - -} - -- (void)testPolygonWithInteriorPolygons { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 20) - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - CLLocationCoordinate2D interiorCoordinates[] = { - CLLocationCoordinate2DMake(4, 4), - CLLocationCoordinate2DMake(6, 6) - }; - - NSUInteger numberOfInteriorCoordinates = sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D); - - MGLPolygon *interiorPolygon = [MGLPolygon polygonWithCoordinates:interiorCoordinates count:numberOfInteriorCoordinates]; - MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates interiorPolygons:@[interiorPolygon]]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLPolygon class]]; - [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; - - MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(polygon, unarchivedPolygon); -} - -- (void)testPolylineFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 20) - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; - polylineFeature.attributes = @{@"bbox": @[@0, @1, @2, @3]}; - polylineFeature.identifier = @"identifier"; - - NSString *filePath = [self temporaryFilePathForClass:[MGLPolylineFeature class]]; - [NSKeyedArchiver archiveRootObject:polylineFeature toFile:filePath]; - - MGLPolylineFeature *unarchivedPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(polylineFeature, unarchivedPolylineFeature); - - unarchivedPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; - - XCTAssertNotEqualObjects(polylineFeature, unarchivedPolylineFeature); -} - -- (void)testPolygonFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 20) - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLPolygonFeature class]]; - [NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath]; - - MGLPolygonFeature *unarchivedPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature); - - unarchivedPolygonFeature.identifier = @"test"; - - XCTAssertNotEqualObjects(polygonFeature, unarchivedPolygonFeature); -} - -- (void)testPointCollection { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 11), - CLLocationCoordinate2DMake(20, 21), - CLLocationCoordinate2DMake(30, 31), - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - MGLPointCollection *pointCollection = [MGLPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; - CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1); - - XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude); - XCTAssertEqual([pointCollection coordinate].longitude, pointsCenter.longitude); - - NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollection class]]; - [NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath]; - - MGLPointCollection *unarchivedPointCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(pointCollection, unarchivedPointCollection); -} - -- (void)testPointCollectionFeature { - NSMutableArray *features = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++) { - MGLPointFeature *feature = [[MGLPointFeature alloc] init]; - feature.coordinate = CLLocationCoordinate2DMake(arc4random() % 90, arc4random() % 180); - [features addObject:feature]; - } - - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 11), - CLLocationCoordinate2DMake(20, 21), - CLLocationCoordinate2DMake(30, 31), - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - MGLPointCollectionFeature *collection = [MGLPointCollectionFeature pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; - collection.identifier = @"identifier"; - collection.attributes = @{@"bbox": @[@1, @2, @3, @4]}; - - NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollectionFeature class]]; - [NSKeyedArchiver archiveRootObject:collection toFile:filePath]; - - MGLPointCollectionFeature *unarchivedCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(collection, unarchivedCollection); - - unarchivedCollection.identifier = @"newIdentifier"; - - XCTAssertNotEqualObjects(collection, unarchivedCollection); -} - -- (void)testMultiPolyline { - - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 11), - CLLocationCoordinate2DMake(20, 21), - CLLocationCoordinate2DMake(30, 31), - }; - - CLLocationCoordinate2D line1[] = { - CLLocationCoordinate2DMake(100, 40), - CLLocationCoordinate2DMake(105, 45), - CLLocationCoordinate2DMake(110, 55) - }; - - CLLocationCoordinate2D line2[] = { - CLLocationCoordinate2DMake(105, 40), - CLLocationCoordinate2DMake(110, 45), - CLLocationCoordinate2DMake(115, 55) - }; - - NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D); - NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D); - - MGLPolyline *road1Polyline = [MGLPolyline polylineWithCoordinates:line1 count:road1CoordinatesCount]; - MGLPolyline *road2Polyline = [MGLPolyline polylineWithCoordinates:line1 count:road2CoordinatesCount]; - - MGLMultiPolyline *roads = [MGLMultiPolyline multiPolylineWithPolylines:@[road1Polyline, road2Polyline]]; - CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40); - - XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude); - XCTAssertEqual([roads coordinate].longitude, roadCenter.longitude); - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - NSMutableArray *polylines = [NSMutableArray array]; - - for (NSUInteger i = 0; i < 100; i++) { - MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; - [polylines addObject:polyline]; - } - - MGLMultiPolyline *multiPolyline = [MGLMultiPolyline multiPolylineWithPolylines:polylines]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolyline class]]; - [NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath]; - - MGLMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MGLMultiPolyline *anotherMultipolyline = [MGLMultiPolyline multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; - - XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline); - XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline); -} - -- (void)testMultiPolygon { - - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 11), - CLLocationCoordinate2DMake(20, 21), - CLLocationCoordinate2DMake(30, 31), - }; - - CLLocationCoordinate2D outerSquare[] = { - CLLocationCoordinate2DMake(100.0, 0.0), - CLLocationCoordinate2DMake(101.0, 0.0), - CLLocationCoordinate2DMake(101.0, 1.0), - CLLocationCoordinate2DMake(100.0, 1.0), - }; - - CLLocationCoordinate2D innerSquare[] = { - CLLocationCoordinate2DMake(100.35, 0.35), - CLLocationCoordinate2DMake(100.65, 0.35), - CLLocationCoordinate2DMake(100.65, 0.65), - CLLocationCoordinate2DMake(100.35, 0.65), - }; - - NSUInteger outerCoordinatesCount = sizeof(outerSquare) / sizeof(CLLocationCoordinate2D); - NSUInteger innerCoordinatesCount = sizeof(innerSquare) / sizeof(CLLocationCoordinate2D); - - MGLPolygon *innerPolygonSquare = [MGLPolygon polygonWithCoordinates:innerSquare count:innerCoordinatesCount]; - MGLPolygon *outerPolygonSquare = [MGLPolygon polygonWithCoordinates:outerSquare count:outerCoordinatesCount interiorPolygons:@[innerPolygonSquare]]; - MGLMultiPolygon *squares = [MGLMultiPolygon multiPolygonWithPolygons:@[outerPolygonSquare, innerPolygonSquare]]; - CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); - - XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude); - XCTAssertEqual([squares coordinate].longitude, squareCenter.longitude); - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - NSMutableArray *polygons = [NSMutableArray array]; - - for (NSUInteger i = 0; i < 100; i++) { - MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; - [polygons addObject:polygon]; - } - - MGLMultiPolygon *multiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:polygons]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolygon class]]; - [NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath]; - - MGLMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MGLMultiPolygon *anotherMultiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; - - XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon); - XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon); - -} - -- (void)testShapeCollection { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(10.12315786, 11.23451186), - CLLocationCoordinate2DMake(20.91836515, 21.93689215), - CLLocationCoordinate2DMake(30.55697246, 31.33988123), - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; - MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; - - MGLShapeCollection *shapeCollection = [MGLShapeCollection shapeCollectionWithShapes:@[polyline, polygon]]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLShapeCollection class]]; - [NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath]; - - MGLShapeCollection *unarchivedShapeCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MGLShapeCollection *anotherShapeCollection = [MGLShapeCollection shapeCollectionWithShapes:@[polygon]]; - - XCTAssertEqualObjects(shapeCollection, unarchivedShapeCollection); - XCTAssertNotEqualObjects(shapeCollection, anotherShapeCollection); -} - -- (void)testMultiPolylineFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(10.12315786, 11.23451186), - CLLocationCoordinate2DMake(20.91836515, 21.93689215), - CLLocationCoordinate2DMake(30.55697246, 31.33988123), - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - NSMutableArray *polylines = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++) { - MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; - polylineFeature.identifier = @(arc4random() % 100).stringValue; - [polylines addObject:polylineFeature]; - } - - MGLMultiPolylineFeature *multiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines]; - multiPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; - - NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolylineFeature class]]; - [NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath]; - - MGLMultiPolylineFeature *unarchivedMultiPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MGLMultiPolylineFeature *anotherMultiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; - - XCTAssertEqualObjects(multiPolylineFeature, unarchivedMultiPolylineFeature); - XCTAssertNotEqualObjects(unarchivedMultiPolylineFeature, anotherMultiPolylineFeature); -} - -- (void)testMultiPolygonFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(10.12315786, 11.23451185), - CLLocationCoordinate2DMake(20.88471238, 21.93684215), - CLLocationCoordinate2DMake(30.15697236, 31.32988123), - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - NSMutableArray *polygons = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++ ) { - MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; - polygonFeature.identifier = @(arc4random_uniform(100)).stringValue; - [polygons addObject:polygonFeature]; - } - - MGLMultiPolygonFeature *multiPolygonFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons]; - multiPolygonFeature.attributes = @{@"bbox": @[@(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100))]}; - - NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolylineFeature class]]; - [NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath]; - - MGLMultiPolygonFeature *unarchivedMultiPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MGLMultiPolygonFeature *anotherMultiPolygonFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; - - XCTAssertEqualObjects(multiPolygonFeature, unarchivedMultiPolygonFeature); - XCTAssertNotEqualObjects(anotherMultiPolygonFeature, unarchivedMultiPolygonFeature); -} - -- (void)testShapeCollectionFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(10.12315786, 11.23451186), - CLLocationCoordinate2DMake(20.91836515, 21.93689215), - CLLocationCoordinate2DMake(30.55697246, 31.33988123), - }; - - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - - MGLPolylineFeature *polyline = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; - MGLPolygonFeature *polygon = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; - - MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[polyline, polygon]]; - shapeCollectionFeature.identifier = @(arc4random_uniform(100)).stringValue; - shapeCollectionFeature.attributes = @{@"bbox":@[@(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100))]}; - - NSString *filePath = [self temporaryFilePathForClass:[MGLShapeCollectionFeature class]]; - [NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath]; - - MGLShapeCollectionFeature *unarchivedShapeCollectionFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(shapeCollectionFeature, unarchivedShapeCollectionFeature); -} - -- (void)testAnnotationImage { -#if TARGET_OS_IPHONE - UIGraphicsBeginImageContext(CGSizeMake(10, 10)); - [[UIColor redColor] setFill]; - UIRectFill(CGRectMake(0, 0, 10, 10)); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); -#else - NSImage *image = [[NSImage alloc] initWithSize:CGSizeMake(10, 10)]; - [image lockFocus]; - [[NSColor redColor] drawSwatchInRect:CGRectMake(0, 0, 10, 10)]; - [image unlockFocus]; -#endif - - MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image reuseIdentifier:@(arc4random_uniform(100)).stringValue]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLAnnotationImage class]]; - [NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath]; - - MGLAnnotationImage *unarchivedAnnotationImage = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage); -} - -#if TARGET_OS_IPHONE -- (void)testAnnotationView { - MGLAnnotationView *annotationView = [[MGLAnnotationView alloc] initWithReuseIdentifier:@"id"]; - annotationView.enabled = NO; - annotationView.selected = YES; - annotationView.draggable = YES; - annotationView.centerOffset = CGVectorMake(10, 10); - annotationView.scalesWithViewingDistance = NO; - - NSString *filePath = [self temporaryFilePathForClass:[MGLAnnotationView class]]; - [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; - - MGLAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); - XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); - XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); - XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); - XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); -} -#endif - -#if TARGET_OS_IPHONE -- (void)testUserLocation { - MGLUserLocation *userLocation = [[MGLUserLocation alloc] init]; - userLocation.location = [[CLLocation alloc] initWithLatitude:1 longitude:1]; - - NSString *filePath = [self temporaryFilePathForClass:[MGLUserLocation class]]; - [NSKeyedArchiver archiveRootObject:userLocation toFile:filePath]; - - MGLUserLocation *unarchivedUserLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqualObjects(userLocation, unarchivedUserLocation); - unarchivedUserLocation.location = [[CLLocation alloc] initWithLatitude:10 longitude:10]; - XCTAssertNotEqualObjects(userLocation, unarchivedUserLocation); -} -#endif - -#if TARGET_OS_IPHONE -- (void)testUserLocationAnnotationView { - MGLUserLocationAnnotationView *annotationView = [[MGLUserLocationAnnotationView alloc] init]; - annotationView.enabled = NO; - annotationView.selected = YES; - annotationView.draggable = YES; - annotationView.centerOffset = CGVectorMake(10, 10); - annotationView.scalesWithViewingDistance = NO; - - NSString *filePath = [self temporaryFilePathForClass:[MGLUserLocationAnnotationView class]]; - [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; - - MGLUserLocationAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - - XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); - XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); - XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); - XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); - XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); -} -#endif - -@end diff --git a/platform/darwin/test/MGLCodingTests.mm b/platform/darwin/test/MGLCodingTests.mm new file mode 100644 index 0000000000..e6417c99f5 --- /dev/null +++ b/platform/darwin/test/MGLCodingTests.mm @@ -0,0 +1,615 @@ +#import +#import + +#import "MGLFoundation_Private.h" +#import "MGLCluster.h" + +#if TARGET_OS_IPHONE +#import "MGLUserLocation_Private.h" +#endif + +@interface MGLCodingTests : XCTestCase +@end + +@implementation MGLCodingTests + +- (NSString *)temporaryFilePathForClass:(Class)clazz { + return [NSTemporaryDirectory() stringByAppendingPathComponent:NSStringFromClass(clazz)]; +} + +- (void)testPointAnnotation { + MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init]; + annotation.coordinate = CLLocationCoordinate2DMake(0.5, 0.5); + annotation.title = @"title"; + annotation.subtitle = @"subtitle"; + + NSString *filePath = [self temporaryFilePathForClass:MGLPointAnnotation.class]; + [NSKeyedArchiver archiveRootObject:annotation toFile:filePath]; + MGLPointAnnotation *unarchivedAnnotation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(annotation, unarchivedAnnotation); +} + +- (void)testPointFeature { + MGLPointFeature *pointFeature = [[MGLPointFeature alloc] init]; + pointFeature.title = @"title"; + pointFeature.subtitle = @"subtitle"; + pointFeature.identifier = @(123); + pointFeature.attributes = @{@"bbox": @[@1, @2, @3, @4]}; + + NSString *filePath = [self temporaryFilePathForClass:MGLPointFeature.class]; + [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; + MGLPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); +} + +- (void)testPointFeatureCluster { + MGLPointFeature *pointFeature = [[MGLPointFeatureCluster alloc] init]; + pointFeature.title = @"title"; + pointFeature.subtitle = @"subtitle"; + pointFeature.identifier = @(123); + pointFeature.attributes = @{ + @"cluster" : @(YES), + @"cluster_id" : @(456), + @"point_count" : @(2), + }; + + XCTAssert([pointFeature isKindOfClass:[MGLPointFeature class]], @""); + + NSString *filePath = [self temporaryFilePathForClass:MGLPointFeature.class]; + [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; + MGLPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); + + // Unarchive process should ensure we still have a cluster + XCTAssert([unarchivedPointFeature isMemberOfClass:[MGLPointFeatureCluster class]]); + + id cluster = MGL_OBJC_DYNAMIC_CAST_AS_PROTOCOL(unarchivedPointFeature, MGLCluster); + + XCTAssert(cluster); + XCTAssert(cluster.clusterIdentifier == 456); + XCTAssert(cluster.clusterPointCount == 2); +} + + +- (void)testPolyline { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551), + CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polyline.title = @"title"; + polyline.subtitle = @"subtitle"; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolyline class]]; + [NSKeyedArchiver archiveRootObject:polyline toFile:filePath]; + MGLPolyline *unarchivedPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polyline, unarchivedPolyline); + + CLLocationCoordinate2D otherCoordinates[] = { + CLLocationCoordinate2DMake(-1, -2) + }; + + [unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates]; + + XCTAssertNotEqualObjects(polyline, unarchivedPolyline); + + CLLocationCoordinate2D multiLineCoordinates[] = { + CLLocationCoordinate2DMake(51.000000, 0.000000), + CLLocationCoordinate2DMake(51.000000, 1.000000), + CLLocationCoordinate2DMake(51.000000, 2.000000), + }; + + NSUInteger multiLineCoordinatesCount = sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D); + MGLPolyline *multiLine = [MGLPolyline polylineWithCoordinates:multiLineCoordinates count:multiLineCoordinatesCount]; + CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000); + + XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude); + XCTAssertEqual([multiLine coordinate].longitude, multiLineCenter.longitude); + + CLLocationCoordinate2D segmentCoordinates[] = { + CLLocationCoordinate2DMake(35.040390, -85.311477), + CLLocationCoordinate2DMake(35.040390, -85.209510), + }; + + NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D); + MGLPolyline *segmentLine = [MGLPolyline polylineWithCoordinates:segmentCoordinates count:segmentCoordinatesCount]; + CLLocationCoordinate2D segmentCenter = CLLocationCoordinate2DMake(35.0404006631, -85.2604935); + + XCTAssertEqualWithAccuracy([segmentLine coordinate].latitude, segmentCenter.latitude, 0.0001); + XCTAssertEqualWithAccuracy([segmentLine coordinate].longitude, segmentCenter.longitude, 0.0001); + + CLLocationCoordinate2D sfToBerkeleyCoordinates[] = { + CLLocationCoordinate2DMake(37.782440, -122.397111), + CLLocationCoordinate2DMake(37.818384, -122.352994), + CLLocationCoordinate2DMake(37.831401, -122.274545), + CLLocationCoordinate2DMake(37.862172, -122.262700), + }; + + NSUInteger sfToBerkeleyCoordinatesCount = sizeof(sfToBerkeleyCoordinates) / sizeof(CLLocationCoordinate2D); + MGLPolyline *sfToBerkeleyLine = [MGLPolyline polylineWithCoordinates:sfToBerkeleyCoordinates count:sfToBerkeleyCoordinatesCount]; + CLLocationCoordinate2D sfToBerkeleyCenter = CLLocationCoordinate2DMake(37.8230575118,-122.324867587); + + XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].latitude, sfToBerkeleyCenter.latitude, 0.0001); + XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].longitude, sfToBerkeleyCenter.longitude, 0.0001); + +} + +- (void)testPolygon { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(35.090745, -85.300259), + CLLocationCoordinate2DMake(35.092035, -85.298885), + CLLocationCoordinate2DMake(35.090639, -85.297416), + CLLocationCoordinate2DMake(35.089112, -85.298928) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + polygon.title = nil; + polygon.subtitle = @"subtitle"; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolygon class]]; + [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; + + MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + [unarchivedPolygon coordinate]; + + XCTAssertEqualObjects(polygon, unarchivedPolygon); + + CLLocationCoordinate2D squareCoordinates[] = { + CLLocationCoordinate2DMake(100.0, 0.0), + CLLocationCoordinate2DMake(101.0, 0.0), + CLLocationCoordinate2DMake(101.0, 1.0), + CLLocationCoordinate2DMake(100.0, 1.0), + }; + + NSUInteger squareCoordinatesCount = sizeof(squareCoordinates) / sizeof(CLLocationCoordinate2D); + MGLPolygon *squarePolygon = [MGLPolygon polygonWithCoordinates:squareCoordinates count:squareCoordinatesCount]; + CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); + + XCTAssertEqual([squarePolygon coordinate].latitude, squareCenter.latitude); + XCTAssertEqual([squarePolygon coordinate].longitude, squareCenter.longitude); + +} + +- (void)testPolygonWithInteriorPolygons { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + CLLocationCoordinate2D interiorCoordinates[] = { + CLLocationCoordinate2DMake(4, 4), + CLLocationCoordinate2DMake(6, 6) + }; + + NSUInteger numberOfInteriorCoordinates = sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolygon *interiorPolygon = [MGLPolygon polygonWithCoordinates:interiorCoordinates count:numberOfInteriorCoordinates]; + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates interiorPolygons:@[interiorPolygon]]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolygon class]]; + [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; + + MGLPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polygon, unarchivedPolygon); +} + +- (void)testPolylineFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polylineFeature.attributes = @{@"bbox": @[@0, @1, @2, @3]}; + polylineFeature.identifier = @"identifier"; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:polylineFeature toFile:filePath]; + + MGLPolylineFeature *unarchivedPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polylineFeature, unarchivedPolylineFeature); + + unarchivedPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; + + XCTAssertNotEqualObjects(polylineFeature, unarchivedPolylineFeature); +} + +- (void)testPolygonFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20) + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPolygonFeature class]]; + [NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath]; + + MGLPolygonFeature *unarchivedPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature); + + unarchivedPolygonFeature.identifier = @"test"; + + XCTAssertNotEqualObjects(polygonFeature, unarchivedPolygonFeature); +} + +- (void)testPointCollection { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPointCollection *pointCollection = [MGLPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; + CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1); + + XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude); + XCTAssertEqual([pointCollection coordinate].longitude, pointsCenter.longitude); + + NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollection class]]; + [NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath]; + + MGLPointCollection *unarchivedPointCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(pointCollection, unarchivedPointCollection); +} + +- (void)testPointCollectionFeature { + NSMutableArray *features = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + feature.coordinate = CLLocationCoordinate2DMake(arc4random() % 90, arc4random() % 180); + [features addObject:feature]; + } + + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPointCollectionFeature *collection = [MGLPointCollectionFeature pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; + collection.identifier = @"identifier"; + collection.attributes = @{@"bbox": @[@1, @2, @3, @4]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLPointCollectionFeature class]]; + [NSKeyedArchiver archiveRootObject:collection toFile:filePath]; + + MGLPointCollectionFeature *unarchivedCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(collection, unarchivedCollection); + + unarchivedCollection.identifier = @"newIdentifier"; + + XCTAssertNotEqualObjects(collection, unarchivedCollection); +} + +- (void)testMultiPolyline { + + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + CLLocationCoordinate2D line1[] = { + CLLocationCoordinate2DMake(100, 40), + CLLocationCoordinate2DMake(105, 45), + CLLocationCoordinate2DMake(110, 55) + }; + + CLLocationCoordinate2D line2[] = { + CLLocationCoordinate2DMake(105, 40), + CLLocationCoordinate2DMake(110, 45), + CLLocationCoordinate2DMake(115, 55) + }; + + NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D); + NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D); + + MGLPolyline *road1Polyline = [MGLPolyline polylineWithCoordinates:line1 count:road1CoordinatesCount]; + MGLPolyline *road2Polyline = [MGLPolyline polylineWithCoordinates:line1 count:road2CoordinatesCount]; + + MGLMultiPolyline *roads = [MGLMultiPolyline multiPolylineWithPolylines:@[road1Polyline, road2Polyline]]; + CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40); + + XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude); + XCTAssertEqual([roads coordinate].longitude, roadCenter.longitude); + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polylines = [NSMutableArray array]; + + for (NSUInteger i = 0; i < 100; i++) { + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; + [polylines addObject:polyline]; + } + + MGLMultiPolyline *multiPolyline = [MGLMultiPolyline multiPolylineWithPolylines:polylines]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolyline class]]; + [NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath]; + + MGLMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolyline *anotherMultipolyline = [MGLMultiPolyline multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; + + XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline); + XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline); +} + +- (void)testMultiPolygon { + + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; + + CLLocationCoordinate2D outerSquare[] = { + CLLocationCoordinate2DMake(100.0, 0.0), + CLLocationCoordinate2DMake(101.0, 0.0), + CLLocationCoordinate2DMake(101.0, 1.0), + CLLocationCoordinate2DMake(100.0, 1.0), + }; + + CLLocationCoordinate2D innerSquare[] = { + CLLocationCoordinate2DMake(100.35, 0.35), + CLLocationCoordinate2DMake(100.65, 0.35), + CLLocationCoordinate2DMake(100.65, 0.65), + CLLocationCoordinate2DMake(100.35, 0.65), + }; + + NSUInteger outerCoordinatesCount = sizeof(outerSquare) / sizeof(CLLocationCoordinate2D); + NSUInteger innerCoordinatesCount = sizeof(innerSquare) / sizeof(CLLocationCoordinate2D); + + MGLPolygon *innerPolygonSquare = [MGLPolygon polygonWithCoordinates:innerSquare count:innerCoordinatesCount]; + MGLPolygon *outerPolygonSquare = [MGLPolygon polygonWithCoordinates:outerSquare count:outerCoordinatesCount interiorPolygons:@[innerPolygonSquare]]; + MGLMultiPolygon *squares = [MGLMultiPolygon multiPolygonWithPolygons:@[outerPolygonSquare, innerPolygonSquare]]; + CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); + + XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude); + XCTAssertEqual([squares coordinate].longitude, squareCenter.longitude); + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polygons = [NSMutableArray array]; + + for (NSUInteger i = 0; i < 100; i++) { + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + [polygons addObject:polygon]; + } + + MGLMultiPolygon *multiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:polygons]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolygon class]]; + [NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath]; + + MGLMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolygon *anotherMultiPolygon = [MGLMultiPolygon multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; + + XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon); + XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon); + +} + +- (void)testShapeCollection { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + + MGLShapeCollection *shapeCollection = [MGLShapeCollection shapeCollectionWithShapes:@[polyline, polygon]]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLShapeCollection class]]; + [NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath]; + + MGLShapeCollection *unarchivedShapeCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLShapeCollection *anotherShapeCollection = [MGLShapeCollection shapeCollectionWithShapes:@[polygon]]; + + XCTAssertEqualObjects(shapeCollection, unarchivedShapeCollection); + XCTAssertNotEqualObjects(shapeCollection, anotherShapeCollection); +} + +- (void)testMultiPolylineFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polylines = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MGLPolylineFeature *polylineFeature = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polylineFeature.identifier = @(arc4random() % 100).stringValue; + [polylines addObject:polylineFeature]; + } + + MGLMultiPolylineFeature *multiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines]; + multiPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath]; + + MGLMultiPolylineFeature *unarchivedMultiPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolylineFeature *anotherMultiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; + + XCTAssertEqualObjects(multiPolylineFeature, unarchivedMultiPolylineFeature); + XCTAssertNotEqualObjects(unarchivedMultiPolylineFeature, anotherMultiPolylineFeature); +} + +- (void)testMultiPolygonFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451185), + CLLocationCoordinate2DMake(20.88471238, 21.93684215), + CLLocationCoordinate2DMake(30.15697236, 31.32988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + NSMutableArray *polygons = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++ ) { + MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + polygonFeature.identifier = @(arc4random_uniform(100)).stringValue; + [polygons addObject:polygonFeature]; + } + + MGLMultiPolygonFeature *multiPolygonFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons]; + multiPolygonFeature.attributes = @{@"bbox": @[@(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100))]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLMultiPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath]; + + MGLMultiPolygonFeature *unarchivedMultiPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MGLMultiPolygonFeature *anotherMultiPolygonFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; + + XCTAssertEqualObjects(multiPolygonFeature, unarchivedMultiPolygonFeature); + XCTAssertNotEqualObjects(anotherMultiPolygonFeature, unarchivedMultiPolygonFeature); +} + +- (void)testShapeCollectionFeature { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; + + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + + MGLPolylineFeature *polyline = [MGLPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + MGLPolygonFeature *polygon = [MGLPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + + MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[polyline, polygon]]; + shapeCollectionFeature.identifier = @(arc4random_uniform(100)).stringValue; + shapeCollectionFeature.attributes = @{@"bbox":@[@(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100)), + @(arc4random_uniform(100))]}; + + NSString *filePath = [self temporaryFilePathForClass:[MGLShapeCollectionFeature class]]; + [NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath]; + + MGLShapeCollectionFeature *unarchivedShapeCollectionFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(shapeCollectionFeature, unarchivedShapeCollectionFeature); +} + +- (void)testAnnotationImage { +#if TARGET_OS_IPHONE + UIGraphicsBeginImageContext(CGSizeMake(10, 10)); + [[UIColor redColor] setFill]; + UIRectFill(CGRectMake(0, 0, 10, 10)); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); +#else + NSImage *image = [[NSImage alloc] initWithSize:CGSizeMake(10, 10)]; + [image lockFocus]; + [[NSColor redColor] drawSwatchInRect:CGRectMake(0, 0, 10, 10)]; + [image unlockFocus]; +#endif + + MGLAnnotationImage *annotationImage = [MGLAnnotationImage annotationImageWithImage:image reuseIdentifier:@(arc4random_uniform(100)).stringValue]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLAnnotationImage class]]; + [NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath]; + + MGLAnnotationImage *unarchivedAnnotationImage = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage); +} + +#if TARGET_OS_IPHONE +- (void)testAnnotationView { + MGLAnnotationView *annotationView = [[MGLAnnotationView alloc] initWithReuseIdentifier:@"id"]; + annotationView.enabled = NO; + annotationView.selected = YES; + annotationView.draggable = YES; + annotationView.centerOffset = CGVectorMake(10, 10); + annotationView.scalesWithViewingDistance = NO; + + NSString *filePath = [self temporaryFilePathForClass:[MGLAnnotationView class]]; + [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; + + MGLAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); + XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); + XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); + XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); + XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); +} +#endif + +#if TARGET_OS_IPHONE +- (void)testUserLocation { + MGLUserLocation *userLocation = [[MGLUserLocation alloc] init]; + userLocation.location = [[CLLocation alloc] initWithLatitude:1 longitude:1]; + + NSString *filePath = [self temporaryFilePathForClass:[MGLUserLocation class]]; + [NSKeyedArchiver archiveRootObject:userLocation toFile:filePath]; + + MGLUserLocation *unarchivedUserLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqualObjects(userLocation, unarchivedUserLocation); + unarchivedUserLocation.location = [[CLLocation alloc] initWithLatitude:10 longitude:10]; + XCTAssertNotEqualObjects(userLocation, unarchivedUserLocation); +} +#endif + +#if TARGET_OS_IPHONE +- (void)testUserLocationAnnotationView { + MGLUserLocationAnnotationView *annotationView = [[MGLUserLocationAnnotationView alloc] init]; + annotationView.enabled = NO; + annotationView.selected = YES; + annotationView.draggable = YES; + annotationView.centerOffset = CGVectorMake(10, 10); + annotationView.scalesWithViewingDistance = NO; + + NSString *filePath = [self temporaryFilePathForClass:[MGLUserLocationAnnotationView class]]; + [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; + + MGLUserLocationAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); + XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); + XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); + XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); + XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); +} +#endif + +@end diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 028ee2e856..b59d297f97 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -374,7 +374,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { return MGLDocumentationExampleTests.styleURL } } - + //#-example-code let camera = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365), altitude: 100, pitch: 20, heading: 0) @@ -394,6 +394,61 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { wait(for: [expectation], timeout: 5) } + func testMGLCluster() { + + enum ExampleError: Error { + case unexpectedFeatureType + case featureIsNotACluster + } + + let geoJSON: [String: Any] = [ + "type" : "Feature", + "geometry" : [ + "coordinates" : [ + -77.00896639534831, + 38.87031006108791, + 0.0 + ], + "type" : "Point" + ], + "properties" : [ + "cluster" : true, + "cluster_id" : 123, + "point_count" : 4567, + ] + ] + + let clusterShapeData = try! JSONSerialization.data(withJSONObject: geoJSON, options: []) + + do { + //#-example-code + let shape = try! MGLShape(data: clusterShapeData, encoding: String.Encoding.utf8.rawValue) + + guard let pointFeature = shape as? MGLPointFeature else { + throw ExampleError.unexpectedFeatureType + } + + // Check for cluster conformance + guard let cluster = pointFeature as? MGLCluster else { + throw ExampleError.featureIsNotACluster + } + + // Currently the only supported class that conforms to `MGLCluster` is + // `MGLPointFeatureCluster` + guard cluster is MGLPointFeatureCluster else { + throw ExampleError.unexpectedFeatureType + } + + //#-end-example-code + + XCTAssert(cluster.clusterIdentifier == 123) + XCTAssert(cluster.clusterPointCount == 4567) + } + catch let error { + XCTFail("Example failed with thrown error: \(error)") + } + } + // For testMGLMapView(). func myCustomFunction() {} } diff --git a/platform/darwin/test/MGLFeatureTests.mm b/platform/darwin/test/MGLFeatureTests.mm index 67f2a9a45e..edc105bca4 100644 --- a/platform/darwin/test/MGLFeatureTests.mm +++ b/platform/darwin/test/MGLFeatureTests.mm @@ -2,6 +2,7 @@ #import #import +#import "MGLFoundation_Private.h" #import "../../darwin/src/MGLFeature_Private.h" @interface MGLFeatureTests : XCTestCase @@ -85,6 +86,26 @@ [NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(3, 2)]); } +- (void)testClusterGeometryConversion { + mbgl::Point point = { -90.066667, 29.95 }; + mbgl::Feature pointFeature { point }; + pointFeature.id = { UINT64_MAX }; + pointFeature.properties["cluster"] = true; + pointFeature.properties["cluster_id"] = 1ULL; + pointFeature.properties["point_count"] = 5ULL; + + id feature = MGLFeatureFromMBGLFeature(pointFeature); + + XCTAssert([feature conformsToProtocol:@protocol(MGLFeature)]); + + id cluster = MGL_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MGLCluster); + XCTAssert(cluster); + XCTAssert(cluster.clusterIdentifier == 1); + XCTAssert(cluster.clusterPointCount == 5); + + XCTAssert([cluster isMemberOfClass:[MGLPointFeatureCluster class]]); +} + - (void)testPropertyConversion { std::vector features; diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index eac21f7dfa..bcd54800c8 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -8,6 +8,8 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Fixed a crash when casting large numbers in `NSExpression`. ([#13580](https://github.com/mapbox/mapbox-gl-native/pull/13580)) * Fixed a bug where the `animated` parameter to `-[MGLMapView selectAnnotation:animated:]` was being ignored. ([#13689](https://github.com/mapbox/mapbox-gl-native/pull/13689)) * Reinstates version 11 as the default Mapbox Streets style (as introduced in 4.7.0). ([#13690](https://github.com/mapbox/mapbox-gl-native/pull/13690)) +* Added the `-[MGLShapeSource leavesOfCluster:offset:limit:]`, `-[MGLShapeSource childrenOfCluster:]`, `-[MGLShapeSource zoomLevelForExpandingCluster:]` methods for inspecting a cluster in an `MGLShapeSource`s created with the `MGLShapeSourceOptionClustered` option. Feature querying now returns clusters represented by `MGLPointFeatureCluster` objects (that conform to the `MGLCluster` protocol). ([#12952](https://github.com/mapbox/mapbox-gl-native/pull/12952) + ## 4.7.1 - December 21, 2018 diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 45c42e65b9..2c4d7f2eab 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -170,7 +170,7 @@ 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */; }; 35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */ = {isa = PBXBuildFile; fileRef = 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */; }; 35D3A1E71E9BE7EC002B38EE /* MGLScaleBar.h in Headers */ = {isa = PBXBuildFile; fileRef = 355ADFFB1E9281DA00F3939D /* MGLScaleBar.h */; }; - 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */; }; + 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.mm */; }; 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */; }; 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */; }; 35E1A4D81D74336F007AA97F /* MGLValueEvaluator.h in Headers */ = {isa = PBXBuildFile; fileRef = 35E1A4D71D74336F007AA97F /* MGLValueEvaluator.h */; }; @@ -477,6 +477,8 @@ CA4EB8C720863487006AB465 /* MGLStyleLayerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA4EB8C620863487006AB465 /* MGLStyleLayerIntegrationTests.m */; }; CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA65C4F821E9BB080068B0D4 /* MGLCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = CA65C4F721E9BB080068B0D4 /* MGLCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA65C4F921E9BB080068B0D4 /* MGLCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = CA65C4F721E9BB080068B0D4 /* MGLCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA6914B520E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */; }; CA88DC3021C85D900059ED5A /* MGLStyleURLIntegrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA88DC2F21C85D900059ED5A /* MGLStyleURLIntegrationTest.m */; }; CA8FBC0921A47BB100D1203C /* MGLRendererConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA8FBC0821A47BB100D1203C /* MGLRendererConfigurationTests.mm */; }; @@ -935,7 +937,7 @@ 35D13AB61D3D15E300AFB4E0 /* MGLStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLStyleLayer.mm; sourceTree = ""; }; 35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFillStyleLayer.h; sourceTree = ""; }; 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFillStyleLayer.mm; sourceTree = ""; }; - 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLCodingTests.m; path = ../../darwin/test/MGLCodingTests.m; sourceTree = ""; }; + 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLCodingTests.mm; path = ../../darwin/test/MGLCodingTests.mm; sourceTree = ""; }; 35DE35531EB7CBA8004917C5 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sv; path = sv.lproj/Localizable.stringsdict; sourceTree = ""; }; 35E0CFE51D3E501500188327 /* MGLStyle_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyle_Private.h; sourceTree = ""; }; 35E1A4D71D74336F007AA97F /* MGLValueEvaluator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLValueEvaluator.h; sourceTree = ""; }; @@ -1049,7 +1051,7 @@ 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; }; - 556660C91E1BF3A900E2C41B /* MGLFoundation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLFoundation.h; sourceTree = ""; }; + 556660C91E1BF3A900E2C41B /* MGLFoundation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLFoundation.h; sourceTree = ""; wrapsLines = 0; }; 556660D71E1D085500E2C41B /* MGLVersionNumber.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLVersionNumber.m; path = ../../darwin/test/MGLVersionNumber.m; sourceTree = ""; }; 558DE79E1E5615E400C7916D /* MGLFoundation_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFoundation_Private.h; sourceTree = ""; }; 558DE79F1E5615E400C7916D /* MGLFoundation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFoundation.mm; sourceTree = ""; }; @@ -1143,6 +1145,7 @@ CA4EB8C620863487006AB465 /* MGLStyleLayerIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLStyleLayerIntegrationTests.m; sourceTree = ""; }; CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = ""; }; CA5E5042209BDC5F001A8A81 /* MGLTestUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MGLTestUtility.h; path = ../../darwin/test/MGLTestUtility.h; sourceTree = ""; }; + CA65C4F721E9BB080068B0D4 /* MGLCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCluster.h; sourceTree = ""; }; CA6914B420E67F50002DB0EE /* MGLAnnotationViewIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLAnnotationViewIntegrationTests.m; path = "Annotation Tests/MGLAnnotationViewIntegrationTests.m"; sourceTree = ""; }; CA88DC2F21C85D900059ED5A /* MGLStyleURLIntegrationTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLStyleURLIntegrationTest.m; sourceTree = ""; }; CA8FBC0821A47BB100D1203C /* MGLRendererConfigurationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLRendererConfigurationTests.mm; path = ../../darwin/test/MGLRendererConfigurationTests.mm; sourceTree = ""; }; @@ -1982,7 +1985,7 @@ 353D23951D0B0DFE002BE09D /* MGLAnnotationViewTests.m */, DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */, DA35A2C31CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m */, - 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */, + 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.mm */, DA35A2C41CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m */, DA35A2A91CCA058D00E826B2 /* MGLCoordinateFormatterTests.m */, 3598544C1E1D38AA00B29F84 /* MGLDistanceFormatterTests.m */, @@ -2207,30 +2210,31 @@ DAD165811CF4CFC4001FF4B9 /* Geometry */ = { isa = PBXGroup; children = ( + CA65C4F721E9BB080068B0D4 /* MGLCluster.h */, DA8847E01CBAFA5100AB86E3 /* MGLAnnotation.h */, - DAD165691CF41981001FF4B9 /* MGLFeature.h */, DAD1656A1CF41981001FF4B9 /* MGLFeature_Private.h */, + DAD165691CF41981001FF4B9 /* MGLFeature.h */, DAD1656B1CF41981001FF4B9 /* MGLFeature.mm */, - DA8847E11CBAFA5100AB86E3 /* MGLGeometry.h */, DA8848011CBAFA6200AB86E3 /* MGLGeometry_Private.h */, + DA8847E11CBAFA5100AB86E3 /* MGLGeometry.h */, DA8848021CBAFA6200AB86E3 /* MGLGeometry.mm */, - DA8847E31CBAFA5100AB86E3 /* MGLMultiPoint.h */, DA8848041CBAFA6200AB86E3 /* MGLMultiPoint_Private.h */, + DA8847E31CBAFA5100AB86E3 /* MGLMultiPoint.h */, DA8848051CBAFA6200AB86E3 /* MGLMultiPoint.mm */, DA8847E71CBAFA5100AB86E3 /* MGLOverlay.h */, DA8847E81CBAFA5100AB86E3 /* MGLPointAnnotation.h */, DA88480B1CBAFA6200AB86E3 /* MGLPointAnnotation.mm */, - 4049C29B1DB6CD6C00B3F799 /* MGLPointCollection.h */, 4049C2AB1DB6E05500B3F799 /* MGLPointCollection_Private.h */, + 4049C29B1DB6CD6C00B3F799 /* MGLPointCollection.h */, 4049C29C1DB6CD6C00B3F799 /* MGLPointCollection.mm */, - DA8847E91CBAFA5100AB86E3 /* MGLPolygon.h */, 9654C1271FFC1CC000DB6A19 /* MGLPolygon_Private.h */, + DA8847E91CBAFA5100AB86E3 /* MGLPolygon.h */, DA88480C1CBAFA6200AB86E3 /* MGLPolygon.mm */, - DA8847EA1CBAFA5100AB86E3 /* MGLPolyline.h */, 9654C1251FFC1AB900DB6A19 /* MGLPolyline_Private.h */, + DA8847EA1CBAFA5100AB86E3 /* MGLPolyline.h */, DA88480D1CBAFA6200AB86E3 /* MGLPolyline.mm */, - DA8847EB1CBAFA5100AB86E3 /* MGLShape.h */, 40CF6DBA1DAC3C1800A4D18B /* MGLShape_Private.h */, + DA8847EB1CBAFA5100AB86E3 /* MGLShape.h */, DA88480E1CBAFA6200AB86E3 /* MGLShape.mm */, DAD165761CF4CDFF001FF4B9 /* MGLShapeCollection.h */, DAD165771CF4CDFF001FF4B9 /* MGLShapeCollection.mm */, @@ -2351,6 +2355,7 @@ DA88483A1CBAFB8500AB86E3 /* MGLAnnotationImage.h in Headers */, 74CB5EBD219B280400102936 /* MGLFillStyleLayer_Private.h in Headers */, DAF2571B201901E200367EF5 /* MGLHillshadeStyleLayer.h in Headers */, + CA65C4F821E9BB080068B0D4 /* MGLCluster.h in Headers */, DA35A2BB1CCA9A6900E826B2 /* MGLClockDirectionFormatter.h in Headers */, 353933FE1D3FB7DD003F57D7 /* MGLSymbolStyleLayer.h in Headers */, DA8848201CBAFA6200AB86E3 /* MGLOfflinePack_Private.h in Headers */, @@ -2571,6 +2576,7 @@ 071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */, 74CB5EC0219B280400102936 /* MGLHeatmapStyleLayer_Private.h in Headers */, 74CB5ECB219B285000102936 /* MGLLineStyleLayer_Private.h in Headers */, + CA65C4F921E9BB080068B0D4 /* MGLCluster.h in Headers */, DABFB8611CBE99E500D62B32 /* MGLMultiPoint.h in Headers */, 74CB5ECD219B285000102936 /* MGLOpenGLStyleLayer_Private.h in Headers */, 74CB5ECF219B285000102936 /* MGLRasterStyleLayer_Private.h in Headers */, @@ -3080,7 +3086,7 @@ 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */, 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */, 357579801D501E09000B822E /* MGLFillStyleLayerTests.mm in Sources */, - 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */, + 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.mm in Sources */, DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */, 076171C32139C70900668A35 /* MGLMapViewTests.m in Sources */, 3598544D1E1D38AA00B29F84 /* MGLDistanceFormatterTests.m in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index 184d7cbd76..31381650da 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -69,12 +69,14 @@ custom_categories: children: - MGLFeature - MGLPointFeature + - MGLPointFeatureCluster - MGLPolygonFeature - MGLPolylineFeature - MGLMultiPolygonFeature - MGLMultiPolylineFeature - MGLPointCollectionFeature - MGLShapeCollectionFeature + - MGLEmptyFeature - name: Style Content children: - MGLSource diff --git a/platform/ios/sdk-files.json b/platform/ios/sdk-files.json index 31eebb0980..0bf09e48fa 100644 --- a/platform/ios/sdk-files.json +++ b/platform/ios/sdk-files.json @@ -136,6 +136,7 @@ "MGLFillStyleLayer.h": "platform/darwin/src/MGLFillStyleLayer.h", "MGLAnnotationImage.h": "platform/ios/src/MGLAnnotationImage.h", "MGLHillshadeStyleLayer.h": "platform/darwin/src/MGLHillshadeStyleLayer.h", + "MGLCluster.h": "platform/darwin/src/MGLCluster.h", "MGLClockDirectionFormatter.h": "platform/darwin/src/MGLClockDirectionFormatter.h", "MGLSymbolStyleLayer.h": "platform/darwin/src/MGLSymbolStyleLayer.h", "MGLAttributionInfo.h": "platform/darwin/src/MGLAttributionInfo.h", diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 38da38c47f..829583be6a 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -14,6 +14,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLAnnotationImage.h" #import "MGLCalloutView.h" #import "MGLClockDirectionFormatter.h" +#import "MGLCluster.h" #import "MGLCompassDirectionFormatter.h" #import "MGLCoordinateFormatter.h" #import "MGLDistanceFormatter.h" diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 11908f0f18..28ff0fc453 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -4,6 +4,8 @@ * Added an `MGLStyle.performsPlacementTransitions` property to control how long it takes for colliding labels to fade out. ([#13565](https://github.com/mapbox/mapbox-gl-native/pull/13565)) * Fixed a crash when casting large numbers in `NSExpression`. ([#13580](https://github.com/mapbox/mapbox-gl-native/pull/13580)) +* Added the `-[MGLShapeSource leavesOfCluster:offset:limit:]`, `-[MGLShapeSource childrenOfCluster:]`, `-[MGLShapeSource zoomLevelForExpandingCluster:]` methods for inspecting a cluster in an `MGLShapeSource`s created with the `MGLShapeSourceOptionClustered` option. Feature querying now returns clusters represented by `MGLPointFeatureCluster` objects (that conform to the `MGLCluster` protocol). ([#12952](https://github.com/mapbox/mapbox-gl-native/pull/12952) + ## 0.13.0 - December 20, 2018 diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 3cd807be7e..4773ae8249 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -31,7 +31,7 @@ 1FC481852098F323000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC481842098F323000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; 3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3508EC651D749D39009B0EE4 /* NSExpression+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */; }; - 3526EABD1DF9B19800006B43 /* MGLCodingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */; }; + 3526EABD1DF9B19800006B43 /* MGLCodingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3526EABC1DF9B19800006B43 /* MGLCodingTests.mm */; }; 352742781D4C220900A1ECE6 /* MGLStyleValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 352742771D4C220900A1ECE6 /* MGLStyleValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 352742811D4C243B00A1ECE6 /* MGLSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3527427F1D4C243B00A1ECE6 /* MGLSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 352742821D4C243B00A1ECE6 /* MGLSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 352742801D4C243B00A1ECE6 /* MGLSource.mm */; }; @@ -119,6 +119,7 @@ 9654C12B1FFC38E000DB6A19 /* MGLPolyline_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C12A1FFC38E000DB6A19 /* MGLPolyline_Private.h */; }; 9654C12D1FFC394700DB6A19 /* MGLPolygon_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9654C12C1FFC394700DB6A19 /* MGLPolygon_Private.h */; }; 96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027331E57C9A7004B8E66 /* Localizable.strings */; }; + CA4045C7216720D700B356E1 /* MGLCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = CA4045C4216720D700B356E1 /* MGLCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA8FBC0D21A4A74300D1203C /* MGLRendererConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA8FBC0C21A4A74300D1203C /* MGLRendererConfigurationTests.mm */; }; CA9461A620884CCB0015EB12 /* MGLAnnotationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CA9461A520884CCB0015EB12 /* MGLAnnotationTests.m */; }; DA00FC8A1D5EEAC3009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -342,7 +343,7 @@ 1FC481842098F323000D09B4 /* NSPredicate+MGLPrivateAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLPrivateAdditions.h"; sourceTree = ""; }; 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSExpression+MGLAdditions.h"; sourceTree = ""; }; 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSExpression+MGLAdditions.mm"; sourceTree = ""; }; - 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLCodingTests.m; path = ../../darwin/test/MGLCodingTests.m; sourceTree = ""; }; + 3526EABC1DF9B19800006B43 /* MGLCodingTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLCodingTests.mm; path = ../../darwin/test/MGLCodingTests.mm; sourceTree = ""; }; 352742771D4C220900A1ECE6 /* MGLStyleValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyleValue.h; sourceTree = ""; }; 3527427F1D4C243B00A1ECE6 /* MGLSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLSource.h; sourceTree = ""; }; 352742801D4C243B00A1ECE6 /* MGLSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLSource.mm; sourceTree = ""; }; @@ -444,6 +445,7 @@ 96E027391E57C9B9004B8E66 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; 96E0273A1E57C9BB004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 96E0273B1E57C9BC004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + CA4045C4216720D700B356E1 /* MGLCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCluster.h; sourceTree = ""; }; CA8FBC0C21A4A74300D1203C /* MGLRendererConfigurationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLRendererConfigurationTests.mm; path = ../../darwin/test/MGLRendererConfigurationTests.mm; sourceTree = ""; }; CA9461A520884CCB0015EB12 /* MGLAnnotationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLAnnotationTests.m; path = test/MGLAnnotationTests.m; sourceTree = SOURCE_ROOT; }; DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; }; @@ -1025,29 +1027,30 @@ isa = PBXGroup; children = ( DAE6C34B1CC31E0400DB3429 /* MGLAnnotation.h */, - DACC22121CF3D3E200D220D9 /* MGLFeature.h */, + CA4045C4216720D700B356E1 /* MGLCluster.h */, DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */, + DACC22121CF3D3E200D220D9 /* MGLFeature.h */, DACC22131CF3D3E200D220D9 /* MGLFeature.mm */, - DAE6C34C1CC31E0400DB3429 /* MGLGeometry.h */, DAE6C36C1CC31E2A00DB3429 /* MGLGeometry_Private.h */, + DAE6C34C1CC31E0400DB3429 /* MGLGeometry.h */, DAE6C36D1CC31E2A00DB3429 /* MGLGeometry.mm */, - DAE6C34E1CC31E0400DB3429 /* MGLMultiPoint.h */, DAE6C36F1CC31E2A00DB3429 /* MGLMultiPoint_Private.h */, + DAE6C34E1CC31E0400DB3429 /* MGLMultiPoint.h */, DAE6C3701CC31E2A00DB3429 /* MGLMultiPoint.mm */, DAE6C3521CC31E0400DB3429 /* MGLOverlay.h */, DAE6C3531CC31E0400DB3429 /* MGLPointAnnotation.h */, DAE6C3761CC31E2A00DB3429 /* MGLPointAnnotation.mm */, - 4049C2A11DB6CE7800B3F799 /* MGLPointCollection.h */, DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */, + 4049C2A11DB6CE7800B3F799 /* MGLPointCollection.h */, 4049C2A71DB6D09B00B3F799 /* MGLPointCollection.mm */, - DAE6C3541CC31E0400DB3429 /* MGLPolygon.h */, 9654C12C1FFC394700DB6A19 /* MGLPolygon_Private.h */, + DAE6C3541CC31E0400DB3429 /* MGLPolygon.h */, DAE6C3771CC31E2A00DB3429 /* MGLPolygon.mm */, - DAE6C3551CC31E0400DB3429 /* MGLPolyline.h */, 9654C12A1FFC38E000DB6A19 /* MGLPolyline_Private.h */, + DAE6C3551CC31E0400DB3429 /* MGLPolyline.h */, DAE6C3781CC31E2A00DB3429 /* MGLPolyline.mm */, - DAE6C3561CC31E0400DB3429 /* MGLShape.h */, 408AA85A1DAEECF100022900 /* MGLShape_Private.h */, + DAE6C3561CC31E0400DB3429 /* MGLShape.h */, DAE6C3791CC31E2A00DB3429 /* MGLShape.mm */, DAD165721CF4CD7A001FF4B9 /* MGLShapeCollection.h */, DAD165731CF4CD7A001FF4B9 /* MGLShapeCollection.mm */, @@ -1152,7 +1155,7 @@ DAEDC4311D6033F1000224FF /* MGLAttributionInfoTests.m */, DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */, DA35A2C11CCA9F4A00E826B2 /* MGLClockDirectionFormatterTests.m */, - 3526EABC1DF9B19800006B43 /* MGLCodingTests.m */, + 3526EABC1DF9B19800006B43 /* MGLCodingTests.mm */, DA35A2B51CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m */, DA35A2A71CC9F41600E826B2 /* MGLCoordinateFormatterTests.m */, DA2987591E1A4290002299F5 /* MGLDocumentationExampleTests.swift */, @@ -1348,6 +1351,7 @@ DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */, DAE6C38B1CC31E2A00DB3429 /* MGLOfflinePack_Private.h in Headers */, 558DE7A61E56161C00C7916D /* MGLFoundation_Private.h in Headers */, + CA4045C7216720D700B356E1 /* MGLCluster.h in Headers */, DACC22141CF3D3E200D220D9 /* MGLFeature.h in Headers */, 3538AA231D542685008EC33D /* MGLStyleLayer.h in Headers */, DAE6C35C1CC31E0400DB3429 /* MGLGeometry.h in Headers */, @@ -1712,7 +1716,7 @@ DAE7DEC41E24549F007505A6 /* MGLNSStringAdditionsTests.m in Sources */, DA87A9981DC9D88400810D09 /* MGLShapeSourceTests.mm in Sources */, 55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */, - 3526EABD1DF9B19800006B43 /* MGLCodingTests.m in Sources */, + 3526EABD1DF9B19800006B43 /* MGLCodingTests.mm in Sources */, DA87A9A21DC9DCF100810D09 /* MGLFillStyleLayerTests.mm in Sources */, DA57D4B11EBC699800793288 /* MGLDocumentationGuideTests.swift in Sources */, DAEDC4321D6033F1000224FF /* MGLAttributionInfoTests.m in Sources */, @@ -2144,10 +2148,10 @@ "$(geometry_cflags)", "$(geojson_cflags)", ); - OTHER_LDFLAGS = ( - "$(mbgl_core_LINK_LIBRARIES)", - "$(mbgl_filesource_LINK_LIBRARIES)", - ); + OTHER_LDFLAGS = ( + "$(mbgl_core_LINK_LIBRARIES)", + "$(mbgl_filesource_LINK_LIBRARIES)", + ); OTHER_SWIFT_FLAGS = "-warnings-as-errors"; PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2176,10 +2180,10 @@ "$(geometry_cflags)", "$(geojson_cflags)", ); - OTHER_LDFLAGS = ( - "$(mbgl_core_LINK_LIBRARIES)", - "$(mbgl_filesource_LINK_LIBRARIES)", - ); + OTHER_LDFLAGS = ( + "$(mbgl_core_LINK_LIBRARIES)", + "$(mbgl_filesource_LINK_LIBRARIES)", + ); OTHER_SWIFT_FLAGS = "-warnings-as-errors"; PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.test; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/platform/macos/sdk-files.json b/platform/macos/sdk-files.json index 884a925217..f7d2fd3426 100644 --- a/platform/macos/sdk-files.json +++ b/platform/macos/sdk-files.json @@ -118,6 +118,7 @@ "MGLTilePyramidOfflineRegion.h": "platform/darwin/src/MGLTilePyramidOfflineRegion.h", "NSValue+MGLAdditions.h": "platform/darwin/src/NSValue+MGLAdditions.h", "MGLMapViewDelegate.h": "platform/macos/src/MGLMapViewDelegate.h", + "MGLCluster.h": "platform/darwin/src/MGLCluster.h", "MGLFeature.h": "platform/darwin/src/MGLFeature.h", "MGLStyleLayer.h": "platform/darwin/src/MGLStyleLayer.h", "MGLGeometry.h": "platform/darwin/src/MGLGeometry.h", diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 88ed0acb7a..0fd81a4df7 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -12,6 +12,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLAnnotation.h" #import "MGLAnnotationImage.h" #import "MGLClockDirectionFormatter.h" +#import "MGLCluster.h" #import "MGLCompassDirectionFormatter.h" #import "MGLCoordinateFormatter.h" #import "MGLDistanceFormatter.h" -- cgit v1.2.1
AttributeTypeMeaning
clusterBoolTrue if the feature is a point cluster. If the attribute is false (or not present) then the feature should not be considered a cluster.
cluster_idNumberIdentifier for the point cluster.
point_count Number