Files
MapLibreParticleLab/platform/darwin/test/MLNShapeSourceTests.mm
Bart Louwers 2a8ebc4906 core: rename mbgl directories to mln (#4511)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-22 20:50:14 +02:00

403 lines
20 KiB
Plaintext

#import <XCTest/XCTest.h>
#import <Mapbox.h>
#import "MLNFeature_Private.h"
#import "MLNShapeSource_Private.h"
#import "MLNSource_Private.h"
#include <mln/style/sources/geojson_source.hpp>
@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