#import #import #import "MLNFeature_Private.h" #import "MLNShapeSource_Private.h" #import "MLNSource_Private.h" #include @interface MLNShapeSourceTests : XCTestCase @end @implementation MLNShapeSourceTests - (void)testGeoJSONOptionsFromDictionary { NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[ [NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"] ]]; NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"]; NSArray *clusterPropertyArray = @[ reduceExpression, mapExpression ]; NSDictionary *options = @{ MLNShapeSourceOptionClustered : @YES, MLNShapeSourceOptionClusterRadius : @42, MLNShapeSourceOptionClusterMinPoints : @3, MLNShapeSourceOptionClusterProperties : @{@"sumValue" : clusterPropertyArray}, MLNShapeSourceOptionMaximumZoomLevelForClustering : @98, MLNShapeSourceOptionMaximumZoomLevel : @99, MLNShapeSourceOptionBuffer : @1976, MLNShapeSourceOptionSimplificationTolerance : @0.42, MLNShapeSourceOptionLineDistanceMetrics : @YES, MLNShapeSourceOptionSynchronousUpdate : @YES }; auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options); XCTAssertTrue(mbglOptions->cluster); XCTAssertEqual(mbglOptions->clusterRadius, 42); XCTAssertEqual(mbglOptions->clusterMaxZoom, 98); XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL); XCTAssertEqual(mbglOptions->maxzoom, 99); XCTAssertEqual(mbglOptions->buffer, 1976); XCTAssertEqual(mbglOptions->tolerance, 0.42); XCTAssertTrue(mbglOptions->lineMetrics); XCTAssertTrue(!mbglOptions->clusterProperties.empty()); XCTAssertTrue(mbglOptions->synchronousUpdate); options = @{MLNShapeSourceOptionClustered : @"number 1"}; XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options)); } - (void)testNilShape { MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil]; XCTAssertNil(source.shape); } - (void)testUnclusterableShape { NSDictionary *options = @{ MLNShapeSourceOptionClustered : @YES, }; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:[[MLNPointFeature alloc] init] options:options]; XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]); MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]]; source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options]; XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]); } - (void)testMLNShapeSourceWithDataMultipleFeatures { NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": " @"{},\"geometry\": {\"type\": \"LineString\",\"coordinates\": " @"[[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}"; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; XCTAssertNil(error); XCTAssertNotNil(shape); MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shape options:nil]; MLNShapeCollection *collection = (MLNShapeCollection *)source.shape; XCTAssertNotNil(collection); XCTAssertEqual(collection.shapes.count, 1UL); XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]); } - (void)testMLNShapeSourceWithSingleGeometry { NSData *data = [@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; XCTAssertNil(error); XCTAssertNotNil(shape); MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil]; XCTAssertNotNil(source.shape); XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]); } - (void)testMLNGeoJSONSourceWithSingleFeature { NSString *geoJSON = @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": " @"\"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}"; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; NSError *error; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; XCTAssertNil(error); XCTAssertNotNil(shape); MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil]; XCTAssertNotNil(source.shape); XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]); MLNPointFeature *feature = (MLNPointFeature *)source.shape; XCTAssert([feature.attributes.allKeys containsObject:@"color"]); } - (void)testMLNShapeSourceWithPolylineFeatures { CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:2]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polylineFeature options:nil]; XCTAssertNotNil(source.shape); XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]); } - (void)testMLNShapeSourceWithPolygonFeatures { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5]; polygonFeature.identifier = @"feature-id"; NSString *stringAttribute = @"string"; NSNumber *boolAttribute = [NSNumber numberWithBool:YES]; NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23]; NSDictionary *nestedDictionaryValue = @{@"nested-key-1" : @"nested-string-value"}; NSArray *arrayValue = @[ @"string-value", @2 ]; NSDictionary *dictionaryValue = @{ @"key-1" : @"string-value", @"key-2" : @1, @"key-3" : nestedDictionaryValue, @"key-4" : arrayValue }; NSArray *arrayOfArrays = @[ @[ @1, @"string-value", @[ @"jagged" ] ] ]; NSArray *arrayOfDictionaries = @[ @{@"key" : @"value"} ]; polygonFeature.attributes = @{ @"name" : stringAttribute, @"bool" : boolAttribute, @"double" : doubleAttribute, @"dictionary-attribute" : dictionaryValue, @"array-attribute" : arrayValue, @"array-of-array-attribute" : arrayOfArrays, @"array-of-dictionary-attribute" : arrayOfDictionaries }; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil]; XCTAssertNotNil(source.shape); MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape; XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier); XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]); XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"], dictionaryValue); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"], arrayOfArrays); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"], arrayOfDictionaries); } - (void)testMLNShapeSourceWithPolygonFeaturesInculdingInteriorPolygons { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8), CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2), CLLocationCoordinate2DMake(0.2, 100.2)}; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[ polygon ]]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil]; XCTAssertNotNil(source.shape); XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]); } - (void)testMLNShapeSourceWithMultiPolylineFeatures { CLLocationCoordinate2D firstCoordinates[] = {CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; MLNPolylineFeature *firstPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2]; CLLocationCoordinate2D secondCoordinates[] = {CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; MLNPolylineFeature *secondPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:secondCoordinates count:2]; MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[ firstPolylineFeature, secondPolylineFeature ]]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolylineFeature options:nil]; XCTAssertNotNil(source.shape); XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]); } - (void)testMLNShapeSourceWithMultiPolygonFeatures { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8), CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2), CLLocationCoordinate2DMake(0.2, 100.2)}; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[ polygon ]]; MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[ polygon ]]; MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[ firstPolygon, secondPolygon ]]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolygonFeature options:nil]; XCTAssertNotNil(source.shape); XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]); } - (void)testMLNShapeSourceWithPointFeature { MLNPointFeature *pointFeature = [MLNPointFeature new]; pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointFeature options:nil]; XCTAssertNotNil(source.shape); XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]); } - (void)testMLNShapeSourceWithPointCollectionFeature { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointCollectionFeature options:nil]; XCTAssertNotNil(source.shape); XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]); } - (void)testMLNShapeSourceWithShapeCollectionFeatures { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8), CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2), CLLocationCoordinate2DMake(0.2, 100.2)}; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[ polygon ]]; CLLocationCoordinate2D coordinates_2[] = {CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2 count:2]; MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygonFeature, polygonFeature ]]; MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polylineFeature, polylineFeature ]]; MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5]; MLNPointFeature *pointFeature = [MLNPointFeature new]; pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[ polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature, pointCollectionFeature, pointFeature ]]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shapeCollectionFeature options:nil]; MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; XCTAssertNotNil(shape); XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes"); } - (void)testMLNShapeSourceWithFeaturesConvenienceInitializer { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0]) interiorPolygons:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" features:@[ polygonFeature ] options:nil]; MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]); XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape"); // when a shape is included in the features array MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0]) interiorPolygons:nil]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-literal-conversion" XCTAssertThrowsSpecificNamed( [[MLNShapeSource alloc] initWithIdentifier:@"source-id-invalid" features:@[ polygon ] options:nil], NSException, NSInvalidArgumentException, @"Shape source should raise an exception if a shape is sent to the features initializer"); #pragma clang diagnostic pop } - (void)testMLNShapeSourceWithShapesConvenienceInitializer { CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0)}; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0]) interiorPolygons:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shapes:@[ polygon ] options:nil]; MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]); XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape"); } @end