* [ios, macos] Introduce MGLPointCollection This makes MGLMultiPoint abstract again so that it is only a place for shared functionality of polygons and polylines. The multipoint feature replaces the point collection feature and can be used to initialize a MGLGeoJSONSource. The previously added swift_names for polyline and polygon are removed, for now. This also updates the iOS and macOS annotation adding logic so that unwanted shapes really are avoided. Previously the combined OR conditions meant that an annotation had to logically be NOT a kind of all three types so the check always let the annotation slip through. This also expands the guard to deflect the new MGLPointCollection.
328 lines
16 KiB
Plaintext
328 lines
16 KiB
Plaintext
#import <Mapbox/Mapbox.h>
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#import <mbgl/util/geometry.hpp>
|
|
#import "../../darwin/src/MGLFeature_Private.h"
|
|
|
|
@interface MGLFeatureTests : XCTestCase
|
|
|
|
@end
|
|
|
|
@implementation MGLFeatureTests
|
|
|
|
- (void)testGeometryConversion {
|
|
std::vector<mbgl::Feature> features;
|
|
|
|
mbgl::Point<double> point = { -90.066667, 29.95 };
|
|
features.push_back(mbgl::Feature { point });
|
|
|
|
mbgl::LineString<double> lineString = {
|
|
{ -84.516667, 39.1 },
|
|
{ -90.066667, 29.95 },
|
|
};
|
|
features.push_back(mbgl::Feature { lineString });
|
|
|
|
mbgl::Polygon<double> polygon = {
|
|
{
|
|
{ 1, 1 },
|
|
{ 4, 1 },
|
|
{ 4, 4 },
|
|
{ 1, 4 },
|
|
},
|
|
{
|
|
{ 2, 2 },
|
|
{ 3, 2 },
|
|
{ 3, 3 },
|
|
{ 2, 3 },
|
|
},
|
|
};
|
|
features.push_back(mbgl::Feature { polygon });
|
|
|
|
NS_ARRAY_OF(MGLShape <MGLFeature> *) *shapes = MGLFeaturesFromMBGLFeatures(features);
|
|
XCTAssertEqual(shapes.count, 3, @"All features should be converted into shapes");
|
|
|
|
MGLPointFeature *pointShape = (MGLPointFeature *)shapes[0];
|
|
XCTAssertTrue([pointShape isKindOfClass:[MGLPointFeature class]]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:pointShape.coordinate],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
|
|
|
|
MGLPolylineFeature *polylineShape = (MGLPolylineFeature *)shapes[1];
|
|
XCTAssertTrue([polylineShape isKindOfClass:[MGLPolylineFeature class]]);
|
|
XCTAssertEqual(polylineShape.pointCount, 2);
|
|
CLLocationCoordinate2D polylineCoordinates[2];
|
|
[polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)];
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polylineCoordinates[0]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polylineCoordinates[1]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
|
|
|
|
MGLPolygonFeature *polygonShape = (MGLPolygonFeature *)shapes[2];
|
|
XCTAssertTrue([polygonShape isKindOfClass:[MGLPolygonFeature class]]);
|
|
XCTAssertEqual(polygonShape.pointCount, 4);
|
|
CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates;
|
|
XCTAssertNotEqual(polygonCoordinates, nil);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[0]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(1, 1)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[1]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(1, 4)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[2]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(4, 4)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:polygonCoordinates[3]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(4, 1)]);
|
|
NS_ARRAY_OF(MGLPolygon *) *interiorPolygons = polygonShape.interiorPolygons;
|
|
XCTAssertEqual(interiorPolygons.count, 1);
|
|
MGLPolygon *interiorPolygon = interiorPolygons.firstObject;
|
|
XCTAssertEqual(interiorPolygon.pointCount, 4);
|
|
CLLocationCoordinate2D interiorPolygonCoordinates[4];
|
|
[interiorPolygon getCoordinates:interiorPolygonCoordinates range:NSMakeRange(0, interiorPolygon.pointCount)];
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[0]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(2, 2)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[1]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(2, 3)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[2]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(3, 3)]);
|
|
XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:interiorPolygonCoordinates[3]],
|
|
[NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(3, 2)]);
|
|
}
|
|
|
|
- (void)testPropertyConversion {
|
|
std::vector<mbgl::Feature> features;
|
|
|
|
mbgl::Point<double> point = { -90.066667, 29.95 };
|
|
mbgl::Feature pointFeature { point };
|
|
pointFeature.id = { UINT64_MAX };
|
|
pointFeature.properties["null"] = nullptr;
|
|
pointFeature.properties["bool"] = true;
|
|
pointFeature.properties["unsigned int"] = UINT64_MAX;
|
|
pointFeature.properties["int"] = INT64_MIN;
|
|
pointFeature.properties["double"] = DBL_MAX;
|
|
pointFeature.properties["string"] = std::string("🚏");
|
|
std::vector<bool> vector;
|
|
vector.push_back(true);
|
|
vector.push_back(false);
|
|
vector.push_back(true);
|
|
features.push_back(pointFeature);
|
|
|
|
NS_ARRAY_OF(MGLShape <MGLFeature> *) *shapes = MGLFeaturesFromMBGLFeatures(features);
|
|
XCTAssertEqual(shapes.count, 1, @"All features should be converted into shapes");
|
|
|
|
MGLShape <MGLFeature> *shape = shapes.firstObject;
|
|
XCTAssertTrue([shape conformsToProtocol:@protocol(MGLFeature)]);
|
|
XCTAssertTrue([shape isKindOfClass:[MGLShape class]]);
|
|
|
|
NSNumber *identifier = shape.identifier;
|
|
XCTAssertTrue([identifier isKindOfClass:[NSNumber class]], @"Feature identifier should be NSNumber");
|
|
XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0, @"Feature identifier should be 64-bit unsigned integer");
|
|
|
|
NSNull *null = [shape attributeForKey:@"null"];
|
|
XCTAssertNotNil(null);
|
|
XCTAssertTrue([null isKindOfClass:[NSNull class]]);
|
|
XCTAssertEqual(null, shape.attributes[@"null"]);
|
|
|
|
NSNumber *boolean = [shape attributeForKey:@"bool"];
|
|
XCTAssertNotNil(boolean);
|
|
XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]);
|
|
#if (TARGET_OS_IPHONE && __LP64__) || TARGET_OS_WATCH
|
|
XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, @"Boolean property should be converted to bool NSNumber");
|
|
#else
|
|
XCTAssertEqual(strcmp(boolean.objCType, @encode(BOOL)), 0, @"Boolean property should be converted to bool NSNumber");
|
|
#endif
|
|
XCTAssertTrue(boolean.boolValue);
|
|
XCTAssertEqual(boolean, shape.attributes[@"bool"]);
|
|
|
|
NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"];
|
|
XCTAssertNotNil(unsignedInteger);
|
|
XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]);
|
|
XCTAssertEqual(strcmp(unsignedInteger.objCType, @encode(uint64_t)), 0, @"Unsigned integer property should be converted to unsigned long long NSNumber");
|
|
XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX);
|
|
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]);
|
|
|
|
NSNumber *integer = [shape attributeForKey:@"int"];
|
|
XCTAssertNotNil(integer);
|
|
XCTAssertTrue([integer isKindOfClass:[NSNumber class]]);
|
|
XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0, @"Integer property should be converted to long long NSNumber");
|
|
XCTAssertEqual(integer.longLongValue, INT64_MIN);
|
|
XCTAssertEqual(integer, shape.attributes[@"int"]);
|
|
|
|
NSNumber *floatingPointNumber = [shape attributeForKey:@"double"];
|
|
XCTAssertNotNil(floatingPointNumber);
|
|
XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]);
|
|
XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0, @"Floating-point number property should be converted to double NSNumber");
|
|
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
|
|
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
|
|
|
|
NSString *string = [shape attributeForKey:@"string"];
|
|
XCTAssertNotNil(string);
|
|
XCTAssertTrue([string isKindOfClass:[NSString class]]);
|
|
XCTAssertEqualObjects(string, @"🚏");
|
|
XCTAssertEqual(string, shape.attributes[@"string"]);
|
|
}
|
|
|
|
- (void)testPointFeatureGeoJSONDictionary {
|
|
MGLPointFeature<MGLFeaturePrivate> *pointFeature = (MGLPointFeature<MGLFeaturePrivate> *)[[MGLPointFeature alloc] init];
|
|
CLLocationCoordinate2D coordinate = { 10, 10 };
|
|
pointFeature.coordinate = coordinate;
|
|
|
|
// A GeoJSON feature
|
|
// when there are no identifier or properties
|
|
NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary];
|
|
|
|
// it has the correct type
|
|
XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature");
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"Point",
|
|
@"coordinates": @[@(coordinate.longitude), @(coordinate.latitude)]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
// it has no "id" key (or value)
|
|
XCTAssertNil(geoJSONFeature[@"id"]);
|
|
// it has a null representation of the properties object
|
|
XCTAssertEqualObjects(geoJSONFeature[@"properties"], [NSNull null]);
|
|
|
|
// when there is a string identifier
|
|
pointFeature.identifier = @"string-id";
|
|
|
|
// it has the identifier in the result
|
|
geoJSONFeature = [pointFeature geoJSONDictionary];
|
|
XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier);
|
|
|
|
// when there are properties
|
|
pointFeature.attributes = @{@"name": @"name-value"};
|
|
|
|
// it has the properties value in the result
|
|
geoJSONFeature = [pointFeature geoJSONDictionary];
|
|
XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes);
|
|
}
|
|
|
|
- (void)testPolylineFeatureGeoJSONDictionary {
|
|
CLLocationCoordinate2D coord1 = { 0, 0 };
|
|
CLLocationCoordinate2D coord2 = { 10, 10 };
|
|
CLLocationCoordinate2D coords[] = { coord1, coord2 };
|
|
MGLPolylineFeature *polyLineFeature = [MGLPolylineFeature polylineWithCoordinates:coords count:2];
|
|
|
|
// A GeoJSON feature
|
|
NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary];
|
|
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"LineString",
|
|
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)]]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
}
|
|
|
|
- (void)testPolygonFeatureGeoJSONDictionary {
|
|
CLLocationCoordinate2D coord1 = { 0, 0 };
|
|
CLLocationCoordinate2D coord2 = { 10, 10 };
|
|
CLLocationCoordinate2D coord3 = { 0, 0 };
|
|
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
|
MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coords count:3];
|
|
|
|
// A GeoJSON feature
|
|
NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary];
|
|
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"Polygon",
|
|
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)],
|
|
@[@(coord3.longitude), @(coord3.latitude)]]]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
}
|
|
|
|
- (void)testMultiPolylineFeatureGeoJSONDictionary {
|
|
CLLocationCoordinate2D coord1 = { 0, 0 };
|
|
CLLocationCoordinate2D coord2 = { 10, 10 };
|
|
CLLocationCoordinate2D coord3 = { 0, 0 };
|
|
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
|
|
|
MGLPolyline *polyLine1 = [MGLPolyline polylineWithCoordinates:coords count:3];
|
|
MGLPolyline *polyLine2 = [MGLPolyline polylineWithCoordinates:coords count:3];
|
|
|
|
MGLMultiPolylineFeature *multiPolylineFeature = [MGLMultiPolylineFeature multiPolylineWithPolylines:@[polyLine1, polyLine2]];
|
|
|
|
// A GeoJSON feature
|
|
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
|
|
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"MultiLineString",
|
|
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)],
|
|
@[@(coord3.longitude), @(coord3.latitude)]],
|
|
@[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)],
|
|
@[@(coord3.longitude), @(coord3.latitude)]]]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
}
|
|
|
|
- (void)testMultiPolygonFeatureGeoJSONDictionary {
|
|
CLLocationCoordinate2D coord1 = { 0, 0 };
|
|
CLLocationCoordinate2D coord2 = { 10, 10 };
|
|
CLLocationCoordinate2D coord3 = { 0, 0 };
|
|
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
|
|
|
MGLPolygon *polygon1 = [MGLPolygon polygonWithCoordinates:coords count:3];
|
|
MGLPolygon *polygon2 = [MGLPolygon polygonWithCoordinates:coords count:3];
|
|
|
|
MGLMultiPolygonFeature *multiPolylineFeature = [MGLMultiPolygonFeature multiPolygonWithPolygons:@[polygon1, polygon2]];
|
|
|
|
// A GeoJSON feature
|
|
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
|
|
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"MultiPolygon",
|
|
@"coordinates": @[
|
|
@[@[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)],
|
|
@[@(coord3.longitude), @(coord3.latitude)]]],
|
|
@[@[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)],
|
|
@[@(coord3.longitude), @(coord3.latitude)]]]]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
}
|
|
|
|
- (void)testPointCollectionFeatureGeoJSONDictionary {
|
|
CLLocationCoordinate2D coord1 = { 0, 0 };
|
|
CLLocationCoordinate2D coord2 = { 10, 10 };
|
|
CLLocationCoordinate2D coord3 = { 0, 0 };
|
|
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
|
MGLPointCollectionFeature *pointCollectionFeature = [MGLPointCollectionFeature pointCollectionWithCoordinates:coords count:3];
|
|
|
|
// A GeoJSON feature
|
|
NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary];
|
|
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"MultiPoint",
|
|
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)],
|
|
@[@(coord3.longitude), @(coord3.latitude)]]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
|
|
}
|
|
|
|
- (void)testShapeCollectionFeatureGeoJSONDictionary {
|
|
MGLPointAnnotation *pointFeature = [[MGLPointAnnotation alloc] init];
|
|
CLLocationCoordinate2D pointCoordinate = { 10, 10 };
|
|
pointFeature.coordinate = pointCoordinate;
|
|
|
|
CLLocationCoordinate2D coord1 = { 0, 0 };
|
|
CLLocationCoordinate2D coord2 = { 10, 10 };
|
|
CLLocationCoordinate2D coords[] = { coord1, coord2 };
|
|
MGLPolyline *polyline = [MGLPolyline polylineWithCoordinates:coords count:2];
|
|
|
|
MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature,
|
|
polyline]];
|
|
// A GeoJSON feature
|
|
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
|
|
|
|
// it has the correct geometry
|
|
NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection",
|
|
@"geometries": @[
|
|
@{@"type": @"Point",
|
|
@"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]},
|
|
@{@"type": @"LineString",
|
|
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
|
|
@[@(coord2.longitude), @(coord2.latitude)]]}
|
|
]};
|
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
}
|
|
|
|
@end
|